File: pm_request.py

package info (click to toggle)
mock 1.3.2-2
  • links: PTS, VCS
  • area: main
  • in suites: buster, stretch
  • size: 1,572 kB
  • ctags: 533
  • sloc: python: 4,816; sh: 429; ansic: 66; makefile: 47
file content (154 lines) | stat: -rw-r--r-- 5,279 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
# -*- coding: utf-8 -*-
# vim:expandtab:autoindent:tabstop=4:shiftwidth=4:filetype=python
# License: GPL2 or later see COPYING
# Written by Michael Simacek
# Copyright (C) 2015 Red Hat, Inc.

import logging
import multiprocessing
import os
import shlex
import socket
import sys

from six.moves import cStringIO as StringIO

from mockbuild import util
from mockbuild.exception import Error
from mockbuild.trace_decorator import traceLog

requires_api_version = "1.1"

RUNDIR = '/var/run/mock'
SOCKET_NAME = 'pm-request'
MAX_CONNECTIONS = 10


@traceLog()
def init(plugins, conf, buildroot):
    PMRequestPlugin(plugins, conf, buildroot)


class OutputFilter(object):
    def filter(self, record):
        return record.levelno == logging.DEBUG


class PMRequestPlugin(object):
    """
    Executes package manager commands requested by processes runninng in the
    chroot.
    """

    @traceLog()
    def __init__(self, plugins, conf, buildroot):
        self.buildroot = buildroot
        self.config = conf
        plugins.add_hook("earlyprebuild", self.start_listener)
        plugins.add_hook("preshell", self.start_listener)
        plugins.add_hook("postbuild", self.log_executed)

    @traceLog()
    def start_listener(self):
        process = multiprocessing.Process(
            name="pm-request-listener",
            target=lambda: PMRequestListener(self.config, self.buildroot).listen())
        process.daemon = True
        self.buildroot.env['PM_REQUEST_SOCKET'] = os.path.join(RUNDIR, SOCKET_NAME)
        self.buildroot.root_log.info("Enabled pm_request plugin")
        process.start()

    @traceLog()
    def log_executed(self):
        """ Obtains the list of executed commands from the daemon process """
        sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
        try:
            sock.connect(self.buildroot.make_chroot_path(RUNDIR, SOCKET_NAME))
            sock.sendall(b'!LOG_EXECUTED\n')
            executed_commands = sock.makefile().read()
            if executed_commands:
                self.buildroot.root_log.warning(
                    "The pm_request plugin executed following commands:\n"
                    + executed_commands
                    + "\nThe build may not be reproducible.\n")
        except socket.error:
            pass
        finally:
            sock.close()


class PMRequestListener(object):
    """ Daemon process that responds to requests """

    def __init__(self, config, buildroot):
        self.config = config
        self.buildroot = buildroot
        self.rundir = buildroot.make_chroot_path(RUNDIR)
        self.socket_path = os.path.join(self.rundir, SOCKET_NAME)
        self.executed_commands = []
        # util.do cannot return output when the command fails, we need to
        # capture it's logging
        self.log_buffer = StringIO()
        self.log = logging.getLogger("mockbuild.plugin.pm_request")
        self.log.level = logging.DEBUG
        self.log.addFilter(OutputFilter())
        self.log.propagate = False
        self.log.addHandler(logging.StreamHandler(self.log_buffer))

    def prepare_socket(self):
        sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
        try:
            sock.connect(self.socket_path)
        except (socket.error, OSError):
            try:
                os.unlink(self.socket_path)
            except OSError:
                pass
        else:
            # there's another process listening
            sys.exit(0)

        util.mkdirIfAbsent(self.rundir)
        # Don't allow regular users to access the socket as they may not be in
        # the mock group
        os.chown(self.rundir, self.buildroot.chrootuid, self.buildroot.chrootgid)
        os.chmod(self.rundir, 0o770)
        sock.bind(self.socket_path)
        os.chown(self.socket_path, self.buildroot.chrootuid, self.buildroot.chrootgid)
        return sock

    def listen(self):
        sock = self.prepare_socket()
        sock.listen(MAX_CONNECTIONS)
        while True:
            try:
                connection, _ = sock.accept()
                try:
                    line = connection.makefile().readline()
                    command = shlex.split(line)
                    # pylint:disable=E1101
                    if command == ["!LOG_EXECUTED"]:
                        connection.sendall('\n'.join(self.executed_commands).encode())
                    elif command:
                        success, out = self.execute_command(command)
                        connection.sendall(b"ok\n" if success else b"nok\n")
                        connection.sendall(out.encode())
                        if success:
                            self.executed_commands.append(line.strip())
                finally:
                    connection.close()
            except socket.error:
                continue

    def execute_command(self, command):
        try:
            self.buildroot.pkg_manager.execute(
                *command, printOutput=False, logger=self.log,
                returnOutput=False, pty=False, raiseExc=True)
            success = True
        except Error:
            success = False
        out = self.log_buffer.getvalue()
        self.log_buffer.seek(0)
        self.log_buffer.truncate()
        return success, out