-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathpcrond
More file actions
executable file
·160 lines (130 loc) · 5.75 KB
/
Copy pathpcrond
File metadata and controls
executable file
·160 lines (130 loc) · 5.75 KB
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
155
156
157
158
159
#!/usr/bin/python3
# -----------------------------------------------------------------------
#
# pcron - a periodic cron-like job scheduler.
# Copyright (C) 2009-2016 Lars Gustäbel <lars@gustaebel.de>
#
# This program 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.
#
# This program 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, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# -----------------------------------------------------------------------
import os
import time
import pwd
import grp
import argparse
import subprocess
import signal
from libpcron import __version__, __copyright__, CRONTAB_NAME, PID_NAME
from libpcron.shared import DaemonContext, Logger, create_environ
from libpcron.time import TimeProvider
def signal_handler(signum, frame):
# pylint:disable=unused-argument
raise SystemExit(1)
class Controller:
# TODO Restart failed pcron instances.
def __init__(self, log_path, locale, groups, pcron_path, check_interval):
self.log_path = log_path
self.locale = locale
self.groups = groups
self.pcron_path = pcron_path
self.check_interval = check_interval
self.logfile = open(self.log_path, "a")
self.logger = Logger(TimeProvider(), self.logfile, Logger.DEBUG)
self.log = self.logger.new("main")
self.log.info("start pcrond with pid %d", os.getpid())
self.running = set()
def __enter__(self):
return self
def __exit__(self, *exc):
for user, record in self.get_users():
if user not in self.running:
continue
pid = self.get_pid(record)
if pid is not None:
os.kill(pid, signal.SIGTERM)
def get_pid(self, record):
path = os.path.join(record.pw_dir, ".pcron", PID_NAME)
try:
with open(path, "r") as fileobj:
return int(fileobj.read().strip())
except (OSError, ValueError):
return None
def get_all_users(self):
# pylint:disable=no-self-use
for record in pwd.getpwall():
yield record.pw_name, record
def get_group_users(self):
users = set()
for group in self.groups.split(","):
try:
record = grp.getgrnam(group.strip())
except KeyError:
continue
for user in record.gr_mem:
if user not in users:
try:
yield user, pwd.getpwnam(user)
except KeyError:
continue
users.add(user)
def get_users(self):
if self.groups is None:
yield from self.get_all_users()
else:
yield from self.get_group_users()
def mainloop(self):
while True:
for user, record in self.get_users():
if user in self.running:
continue
try:
self.start_user_instance(user, record)
except Exception as exc:
self.log.exception("unable to start pcron for user %s:", user)
time.sleep(self.check_interval * 60)
def start_user_instance(self, user, record):
path = os.path.join(record.pw_dir, ".pcron", CRONTAB_NAME)
if os.path.exists(path):
subprocess.call(
["/bin/su", "--shell", record.pw_shell, user, "--command", self.pcron_path],
env=create_environ(record, LANG=self.locale),
stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT)
self.running.add(user)
self.log.info("started pcron instance for user %s with pid %s", user, self.get_pid(record))
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--version", action="version",
version="%%(prog)s %s - %s" % (__version__, __copyright__))
parser.add_argument("-g", "--groups", metavar="GROUPS",
help="restrict pcrond to users that belong to one of GROUPS which is a "\
"comma-separated list of group names")
parser.add_argument("-i", "--check-interval", type=int, metavar="N", default=15,
help="check for users' crontabs every N minutes, default is %(default)s")
parser.add_argument("-l", "--log-path", metavar="NAME", default="/var/log/pcrond.log",
help="the path of the log file, default is %(default)s")
parser.add_argument("-p", "--pid-path", metavar="NAME", default="/var/run/pcrond.pid",
help="the path of the pid file, default is %(default)s")
parser.add_argument("--pcron-path", metavar="NAME", default="/usr/bin/pcron",
help="the path to the pcron executable, default is %(default)s")
parser.add_argument("--locale", metavar="NAME", default="en_US.UTF-8",
help="the global locale, default is %(default)s")
args = parser.parse_args()
with DaemonContext(args.pid_path):
signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)
with Controller(args.log_path, args.locale, args.groups, args.pcron_path, args.check_interval) as controller:
controller.mainloop()
if __name__ == "__main__":
main()