aboutsummaryrefslogtreecommitdiff
path: root/lava_server/cmdutils.py
blob: ad884dbb8f4d879a4d69a797d00c9617738544b0 (plain)
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
# Copyright (C) 2016 Linaro Limited
#
# Author: Remi Duraffort <remi.duraffort@linaro.org>
#
# This file is part of LAVA Server.
#
# LAVA Server is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# LAVA Server is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along
# with this program; if not, see <http://www.gnu.org/licenses>.

import ctypes
import ctypes.util
import fcntl
import grp
import logging
import logging.handlers
import os
import pwd
import signal

from django.core.management.base import BaseCommand


class LAVADaemonCommand(BaseCommand):

    def add_arguments(self, parser):
        log = parser.add_argument_group("logging")
        log.add_argument('-l', '--level',
                         default='DEBUG',
                         help="Logging level (ERROR, WARN, INFO, DEBUG) "
                              "Default: DEBUG")

        log.add_argument('-o', '--log-file',
                         default=self.default_logfile,
                         help="Logging file path")

        priv = parser.add_argument_group("privileges")
        priv.add_argument('-u', '--user',
                          default='lavaserver',
                          help="Run the process under this user. It should "
                               "be the same user as the gunicorn process.")

        priv.add_argument('-g', '--group',
                          default='lavaserver',
                          help="Run the process under this group. It should "
                               "be the same group as the gunicorn process.")

    def drop_privileges(self, user, group):
        try:
            user_id = pwd.getpwnam(user)[2]
            group_id = grp.getgrnam(group)[2]
        except KeyError:
            self.logger.error("Unable to lookup the user or the group")
            return False
        self.logger.debug("Switching to (%s(%d), %s(%d))",
                          user, user_id, group, group_id)

        try:
            os.setgid(group_id)
            os.setuid(user_id)
        except OSError:
            self.logger.error("Unable to the set (user, group)=(%s, %s)",
                              user, group)
            return False

        # Set a restrictive umask (rwxr-xr-x)
        os.umask(0o022)

        return True

    def setup_logging(self, logger_name, level, log_file, log_format):
        del logging.root.handlers[:]
        del logging.root.filters[:]
        # Create the logger
        self.logger = logging.getLogger(logger_name)
        if log_file == "-":
            handler = logging.StreamHandler()
        else:
            handler = logging.handlers.WatchedFileHandler(log_file)
        handler.setFormatter(logging.Formatter(log_format))
        self.logger.addHandler(handler)

        # Set log level
        if level == 'ERROR':
            self.logger.setLevel(logging.ERROR)
        elif level == 'WARN':
            self.logger.setLevel(logging.WARN)
        elif level == 'INFO':
            self.logger.setLevel(logging.INFO)
        else:
            self.logger.setLevel(logging.DEBUG)

    def setup_zmq_signal_handler(self):
        # Mask signals and create a pipe that will receive a bit for each
        # signal received. Poll the pipe along with the zmq socket so that we
        # can only be interupted while reading data.
        (pipe_r, pipe_w) = os.pipe()
        flags = fcntl.fcntl(pipe_w, fcntl.F_GETFL, 0)
        fcntl.fcntl(pipe_w, fcntl.F_SETFL, flags | os.O_NONBLOCK)

        def signal_to_pipe(signumber, _):
            # Send the signal number on the pipe
            os.write(pipe_w, chr(signumber).encode("utf-8"))

        signal.signal(signal.SIGINT, signal_to_pipe)
        signal.signal(signal.SIGTERM, signal_to_pipe)
        signal.signal(signal.SIGQUIT, signal_to_pipe)

        return (pipe_r, pipe_w)


def watch_directory(directory):
    IN_MODIFY = 0x00000002
    IN_ATTRIB = 0x00000004
    IN_MOVED_FROM = 0x00000040
    IN_MOVED_TO = 0x00000080
    IN_CREATE = 0x00000100
    IN_DELETE = 0x00000200
    IN_DELETE_SELF = 0x00000400
    IN_MOVE_SELF = 0x00000800

    IN_EVENTS = (IN_MODIFY | IN_ATTRIB | IN_MOVED_FROM | IN_MOVED_TO |
                 IN_CREATE | IN_DELETE | IN_DELETE_SELF | IN_MOVE_SELF)

    # watch a directory using inotify
    # return the corresponding file descriptor
    libc_name = ctypes.util.find_library('c')
    libc = ctypes.cdll.LoadLibrary(libc_name)

    # create the inotify file descriptor
    inotify_fd = libc.inotify_init()
    # watch the "test" directory
    ret = libc.inotify_add_watch(inotify_fd, directory.encode("utf-8"), IN_EVENTS)
    return None if ret == -1 else inotify_fd