#!/usr/bin/env python3 from __future__ import print_function import argparse import fcntl import os import pwd import select import signal import sys import syslog from subprocess import Popen, PIPE, STDOUT from pipes import quote VERSION = '1.6.1' def is_root(): return os.getuid() == 0 def _get_users(config): config_lines = list() if os.path.exists(config): with open(config, 'r') as config_file: for raw_line in config_file: line = raw_line.strip() if line == '' or line.startswith('#'): continue config_lines.append(line.strip()) users = list() for user in pwd.getpwall(): if user.pw_uid < 1000 or user.pw_uid >= 2000: if user.pw_name not in config_lines: continue elif '!' + user.pw_name in config_lines: continue users.append(user) return users class Executor(Popen): def __init__(self, *args, **kwargs): self.running = True self.orig_signal = signal.signal(signal.SIGCHLD, self._signal) return super(Executor, self).__init__(*args, **kwargs) def readlines(self): fl = fcntl.fcntl(self.stdout, fcntl.F_GETFL) fcntl.fcntl(self.stdout, fcntl.F_SETFL, fl | os.O_NONBLOCK) buffer = '' while self.running: try: ready = select.select([self.stdout], [], [], 0) except select.error: pass if ready[0] != []: buffer += self.stdout.read(8192).decode('utf-8') lines = buffer.splitlines() for line in lines[:-1]: yield line if len(lines) > 0: buffer = lines[-1] self.wait() try: buffer += self.stdout.read(8192).decode('utf-8') except: pass for line in buffer.splitlines(): yield line def wait(self): result = super(Executor, self).wait() signal.signal(signal.SIGCHLD, self.orig_signal) return result def _signal(self, *args): self.running = False class SplineStartup(object): def __init__(self): self.options = None self._parse_args() if self.options.syslog: syslog.openlog(logoption=syslog.LOG_PID) def _parse_args(self): parser = argparse.ArgumentParser(description='Startup for users.') parser.add_argument('actions', metavar='ACTION', nargs='*', default=['start'], help='argument supplied to each called ' 'script, if multiple arguments are given ' 'each script is called with each argument ' 'until the first one exits with return code ' '0 (default: %(default)s)') parser.add_argument('-q', '--quiet', action='store_true', help='only log error messages') parser.add_argument('-v', '--verbose', action='store_true', help='print extra debugging mesasges to stderr') parser.add_argument('-V', '--version', action='version', version='%(prog)s ' + VERSION) parser.add_argument('-n', '--dry-run', action='store_true', help="print script names which would run, " "but don't run them") parser.add_argument('-s', '--syslog', action='store_true', help='log to syslog and not to stderr') if is_root(): parser.add_argument('-u', '--user', metavar='USER', action='append', help='user to execute scripts (by default all ' 'users with 1000 <= uid < 2000 are used)') parser.add_argument('-c', '--config', metavar='CONFIG', help='path to config file for additional users', default='/etc/spline-startup.conf') self.options = parser.parse_args() def _perror(self, msg): if self.options.syslog: syslog.syslog(syslog.LOG_CRIT, msg) else: print('[ERROR] %s' % msg, file=sys.stderr) def _pinfo(self, msg): if self.options.syslog: syslog.syslog(syslog.LOG_INFO, msg) elif not self.options.quiet: print('[INFO] %s' % msg, file=sys.stderr) def _pdebug(self, msg): if self.options.syslog: syslog.syslog(syslog.LOG_DEBUG, msg) elif self.options.verbose: print('[DEBUG] %s' % msg, file=sys.stderr) def _call(self, cmd): self._pinfo('Calling: %s' % ' '.join(cmd)) if not self.options.dry_run: proc = Executor(cmd, stdout=PIPE, stderr=STDOUT) for line in proc.readlines(): self._pinfo(line) return proc.returncode else: return 0 def _get_scripts(self, reverse, directory): args = [] if reverse: args.append('--reverse') cmd = ['run-parts', '--list', '--regex', '^[a-zA-Z0-9_.-]+$'] + \ args + ['--', directory] self._pinfo('Getting scripts: %s' % ' '.join(cmd)) proc = Popen(cmd, stdout=PIPE, stderr=STDOUT) output, _ = proc.communicate() return output.strip().decode('utf-8').splitlines() def _run_scripts(self, user, actions, use_su=True): self._pdebug("Running scripts for user '%s'" % user.pw_name) directory = os.path.join(user.pw_dir, 'etc', 'rc.d') if not os.path.isdir(directory): return True scripts = self._get_scripts(actions[0] == 'stop', directory) self._pinfo('Running scripts: %r' % scripts) error = False for script in scripts: for action in actions: if use_su: exitcode = self._call(['su', '-', user.pw_name, '-s', '/bin/sh', '-c', '%s %s' % (quote(script), quote(action))]) else: exitcode = self._call([script, action]) if exitcode == 0: break if exitcode != 0: error = True return error == False def run(self): if not is_root(): user = pwd.getpwuid(os.getuid()) self._run_scripts(user, self.options.actions, False) return if self.options.user is not None: for username in self.options.user: try: user = pwd.getpwnam(username) self._run_scripts(user, self.options.actions) except KeyError: self._perror("Invalid user '%s'" % username) else: userlist = _get_users(self.options.config) for user in userlist: self._run_scripts(user, self.options.actions) def main(): signal.signal(signal.SIGCHLD, signal.SIG_IGN) app = SplineStartup() app.run() if __name__ == '__main__': main()