????

Your IP : 13.58.26.185


Current Path : /scripts/
Upload File :
Current File : //scripts/retry_rpm

#!/usr/local/cpanel/bin/python-packman
import argparse
import fcntl
import os
import select
import signal
import subprocess
import sys
import time


class Script(object):
    rpm_bin = '/usr/bin/rpm'
    yum_bin = '/usr/bin/yum'
    lock_error_text = b'''error: can't create transaction lock on'''

    def __init__(self) -> None:
        self.max_wait = 300
        self.saw_lock_error = False
        self.lockfile_name = subprocess.check_output('/usr/bin/rpm -E %_rpmlock_path'.split()).strip()
        if not self.lockfile_name:
            raise RuntimeError('get rpm lock path: empty')

        self.lockfile = open(self.lockfile_name, 'wb+')
        self.stdout = open(os.dup(sys.stdout.fileno()), 'wb')
        self.stderr = open(os.dup(sys.stderr.fileno()), 'wb', buffering=0)

    def run(self, args=None):
        parser = argparse.ArgumentParser()
        parser.add_argument('rpm_or_yum', choices=[Script.rpm_bin, Script.yum_bin])
        args, wrapped_args = parser.parse_known_args(args=args)

        cmd = [args.rpm_or_yum] + wrapped_args

        start = time.time()
        try:
            while True:
                time_elapsed = time.time() - start
                if self.max_wait < time_elapsed:
                    raise TimeExceeded()

                rc = self.try_cmd(cmd)
                if 0 == rc:
                    return 0

                if self.saw_lock_error:
                    alarm_timeout = int(self.max_wait - time_elapsed)
                    self.err(b'lock error detected; waiting on rpm lock\n')

                    def handle_alarm(signo, frame):
                        raise TimeExceeded()

                    signal.signal(signal.SIGALRM, handle_alarm)
                    signal.alarm(alarm_timeout)
                    fcntl.lockf(self.lockfile, fcntl.LOCK_EX)
                    signal.alarm(0)
                    self.err(b'lock acquired\n')

                    # We waited on lock as a cheap way to know when it was available. We have to give it up so that the
                    # subprocess can acquire it, but it's possible some other process will get the lock between now and
                    # then so that's why we're in this loop. You might be thinking you could carry this lock across an
                    # execve call and that _is_ possible, however there is no guarantee that this lock is the first one
                    # the subprocess will acquire and so you might end up deadlocking.
                    fcntl.lockf(self.lockfile, fcntl.LOCK_UN)
                else:
                    return rc
        except TimeExceeded as e:
            self.err(b'error: max wait time has elapsed; returning error result\n')
            return 99

    def err(self, msg):
        self.stderr.write(msg)
        # self.stderr.flush()

    def try_cmd(self, args):
        self.saw_lock_error = False

        devnull = open(os.devnull, 'rb')
        proc = subprocess.Popen(args, stdin=devnull, stdout=subprocess.PIPE, stderr=subprocess.PIPE)

        def out_cb(line, script):
            script.stdout.write(line)
            script.stdout.flush()

        def error_cb(line, script):
            if Script.lock_error_text in line:
                script.saw_lock_error = True
                # some Cpanel code looks for '^error:' text, so we prefix retryable errors with our name so that a
                # successful retry will not be detected as an error that needs review.
                line = b'retry_rpm.py: ' + line

            script.err(line)

        readers = [
            LineReader(proc.stdout, out_cb, self),
            LineReader(proc.stderr, error_cb, self),
        ]
        while readers:
            readable, _, _ = select.select(readers, [], [], 1.0)
            for w in readable:
                if not w.read():
                    readers.remove(w)

        return proc.wait()


class LineReader(object):
    def __init__(self, fl, cb, arg):
        self.file = fl
        os.set_blocking(self.file.fileno(), False)
        self.cb = cb
        self.arg = arg

    def fileno(self):
        return self.file.fileno()

    def read(self):
        lines = 0
        for line in self.file:
            lines += 1

            self.cb(line, self.arg)

        any_lines = 0 < lines
        return any_lines


class TimeExceeded(Exception):
    pass


if __name__ == '__main__':
    sys.exit(Script().run())