262 lines
7.9 KiB
Python
Executable File
262 lines
7.9 KiB
Python
Executable File
#!/usr/bin/python2
|
|
import sys
|
|
import pwd
|
|
import os
|
|
import re
|
|
|
|
def files(dirname):
|
|
try:
|
|
return filter(os.path.isfile, map(lambda f: os.path.join(dirname, f), os.listdir(dirname)))
|
|
except OSError:
|
|
return []
|
|
|
|
envvar_re = re.compile(r'^([A-Za-z_0-9]+)\s*=\s*(.*)$')
|
|
|
|
CRONTAB_FILES = ['/etc/crontab'] + files('/etc/cron.d')
|
|
ANACRONTAB_FILES = ['/etc/anacrontab']
|
|
USERCRONTAB_FILES = files('/var/spool/cron')
|
|
|
|
TARGER_DIR = sys.argv[1]
|
|
TIMERS_DIR = os.path.join(TARGER_DIR, 'cron.target.wants')
|
|
SELF = os.path.basename(sys.argv[0])
|
|
|
|
MINUTES_SET = range(0, 60)
|
|
HOURS_SET = range(0, 24)
|
|
DAYS_SET = range(0, 32)
|
|
DOWS_SET = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
|
|
MONTHS_SET = range(0, 13)
|
|
|
|
ROOT_USER = pwd.getpwnam('root')
|
|
|
|
try:
|
|
os.makedirs(TIMERS_DIR)
|
|
except OSError as e:
|
|
if e.errno != os.errno.EEXIST:
|
|
raise
|
|
|
|
def parse_crontab(filename, withuser=True, monotonic=False):
|
|
basename = os.path.basename(filename)
|
|
environment = {
|
|
'SHELL': '/bin/sh',
|
|
'PATH': '/usr/bin:/bin',
|
|
}
|
|
with open(filename, 'r') as f:
|
|
for line in f.readlines():
|
|
if line.startswith('#'):
|
|
continue
|
|
|
|
line = line.rstrip('\n')
|
|
envvar = envvar_re.match(line)
|
|
if envvar:
|
|
environment[envvar.group(1)] = envvar.group(2)
|
|
continue
|
|
|
|
parts = line.split()
|
|
line = ' '.join(parts)
|
|
|
|
if monotonic:
|
|
if len(parts) < 4:
|
|
continue
|
|
|
|
period, delay, jobid = parts[0:3]
|
|
command = ' '.join(parts[3:])
|
|
period = {
|
|
'1': 'daily',
|
|
'7': 'weekly',
|
|
'@midnight': 'daily'
|
|
}.get(period, None) or period.lstrip('@')
|
|
|
|
environment['LOGNAME'] = environment['USER'] = 'root'
|
|
environment['HOME'] = ROOT_USER.pw_dir
|
|
yield {
|
|
'e': ' '.join('"%s=%s"' % kv for kv in environment.iteritems()),
|
|
'l': line,
|
|
'f': filename,
|
|
'p': period,
|
|
'd': delay,
|
|
'j': jobid,
|
|
'c': command,
|
|
'u': 'root'
|
|
}
|
|
|
|
else:
|
|
if line.startswith('@'):
|
|
if len(parts) < 2:
|
|
continue
|
|
|
|
period = parts[0]
|
|
period = {
|
|
'1': 'daily',
|
|
'7': 'weekly',
|
|
'@midnight': 'daily'
|
|
}.get(period, None) or period.lstrip('@')
|
|
|
|
user, command = (parts[1], ' '.join(parts[2:])) if withuser else (basename, ' '.join(parts[1:]))
|
|
|
|
environment['LOGNAME'] = environment['USER'] = user
|
|
environment['HOME'] = pwd.getpwnam(user).pw_dir
|
|
|
|
yield {
|
|
'e': ' '.join('"%s=%s"' % kv for kv in environment.iteritems()),
|
|
'l': line,
|
|
'f': filename,
|
|
'p': period,
|
|
'u': user,
|
|
'c': command
|
|
}
|
|
else:
|
|
if len(parts) < 6 + int(withuser):
|
|
continue
|
|
|
|
minutes, hours, days = parts[0:3]
|
|
months, dows = parts[3:5]
|
|
user, command = (parts[5], ' '.join(parts[6:])) if withuser else (basename, ' '.join(parts[5:]))
|
|
|
|
environment['LOGNAME'] = environment['USER'] = user
|
|
environment['HOME'] = pwd.getpwnam(user).pw_dir
|
|
|
|
yield {
|
|
'e': ' '.join('"%s=%s"' % kv for kv in environment.iteritems()),
|
|
'l': line,
|
|
'f': filename,
|
|
'm': parse_time_unit(minutes, MINUTES_SET),
|
|
'h': parse_time_unit(hours, HOURS_SET),
|
|
'd': parse_time_unit(days, DAYS_SET),
|
|
'w': parse_time_unit(dows, DOWS_SET, dow_map),
|
|
'M': parse_time_unit(months, MONTHS_SET, month_map),
|
|
'u': user,
|
|
'c': command
|
|
}
|
|
|
|
def parse_time_unit(value, values, mapping=int):
|
|
if value == '*':
|
|
return ['*']
|
|
return list(reduce(lambda a, i: a.union(set(i)), map(values.__getitem__,
|
|
map(parse_period(mapping), value.split(','))), set()))
|
|
|
|
def month_map(month):
|
|
try:
|
|
return int(month)
|
|
except ValueError:
|
|
return ['jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'nov', 'dec'].index(month.lower()[0:3]) + 1
|
|
|
|
def dow_map(dow):
|
|
try:
|
|
return ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'].index(dow[0:3].lower())
|
|
except ValueError:
|
|
return int(dow) % 7
|
|
|
|
def parse_period(mapping=int):
|
|
def parser(value):
|
|
try:
|
|
range, step = value.split('/')
|
|
except ValueError:
|
|
value = mapping(value)
|
|
return slice(value, value + 1)
|
|
|
|
if range == '*':
|
|
return slice(None, None, int(step))
|
|
|
|
try:
|
|
start, end = range.split('-')
|
|
except ValueError:
|
|
return slice(mapping(range), None, int(step))
|
|
|
|
return slice(mapping(start), mapping(end), int(step))
|
|
|
|
return parser
|
|
|
|
def generate_timer_unit(job, seq):
|
|
n = next(seq)
|
|
unit_name = "cron-%s-%s" % (job['u'], n)
|
|
|
|
if 'p' in job:
|
|
if job['p'] == 'reboot':
|
|
schedule = 'OnBootSec=%sm' % job.get('d', 5)
|
|
else:
|
|
try:
|
|
schedule = 'OnCalendar=*-*-1/%s 0:%s:0' % (int(job['p']), job.get('d', 0))
|
|
except ValueError:
|
|
schedule = 'OnCalendar=%s' % job['p']
|
|
|
|
accuracy = job.get('d', 1)
|
|
|
|
else:
|
|
dows = ','.join(job['w'])
|
|
dows = '' if dows == '*' else dows + ' '
|
|
|
|
schedule = 'OnCalendar=%s*-%s-%s %s:%s:00' % (dows, ','.join(map(str, job['M'])),
|
|
','.join(map(str, job['d'])), ','.join(map(str, job['h'])), ','.join(map(str, job['m'])))
|
|
accuracy = 1
|
|
|
|
with open('%s/%s.timer' % (TARGER_DIR, unit_name), 'w') as f:
|
|
f.write('''# Automatically generated by %s
|
|
# Source crontab: %s
|
|
|
|
[Unit]
|
|
Description=[Cron] "%s"
|
|
PartOf=cron.target
|
|
RefuseManualStart=true
|
|
RefuseManualStop=true
|
|
|
|
[Timer]
|
|
Unit=%s.service
|
|
Persistent=true
|
|
AccuracySec=%sm
|
|
%s
|
|
''' % (SELF, job['f'], job['l'], unit_name, accuracy, schedule))
|
|
|
|
try:
|
|
os.symlink('%s/%s.timer' % (TARGER_DIR, unit_name), '%s/%s.timer' % (TIMERS_DIR, unit_name))
|
|
except OSError as e:
|
|
if e.errno != os.errno.EEXIST:
|
|
raise
|
|
|
|
with open('%s/%s.service' % (TARGER_DIR, unit_name), 'w') as f:
|
|
f.write('''# Automatically generated by %s
|
|
# Source crontab: %s
|
|
|
|
[Unit]
|
|
Description=[Cron] "%s"
|
|
RefuseManualStart=true
|
|
RefuseManualStop=true
|
|
|
|
[Service]
|
|
Type=oneshot
|
|
User=%s
|
|
Environment=%s
|
|
ExecStart=%s -c '%s'
|
|
''' % (SELF, job['f'], job['l'], job['u'], job['e'], job['e'].get('SHELL', '/bin/sh'), job['c']))
|
|
|
|
return '%s.timer' % unit_name
|
|
|
|
seqs = {}
|
|
def count():
|
|
n = 0
|
|
while True:
|
|
yield n
|
|
n += 1
|
|
|
|
|
|
for filename in CRONTAB_FILES:
|
|
try:
|
|
for job in parse_crontab(filename, withuser=True):
|
|
generate_timer_unit(job, seqs.setdefault(job['u'], count()))
|
|
except IOError:
|
|
pass
|
|
|
|
for filename in ANACRONTAB_FILES:
|
|
try:
|
|
for job in parse_crontab(filename, monotonic=True):
|
|
generate_timer_unit(job, seqs.setdefault(job['u'], count()))
|
|
except IOError:
|
|
pass
|
|
|
|
for filename in USERCRONTAB_FILES:
|
|
try:
|
|
for job in parse_crontab(filename, withuser=False):
|
|
generate_timer_unit(job, seqs.setdefault(job['u'], count()))
|
|
except IOError:
|
|
pass
|
|
|