systemd-cron-next/systemd-crontab-generator

241 lines
7.2 KiB
Plaintext
Raw Normal View History

2014-04-21 04:52:46 -07:00
#!/usr/bin/python2
import sys
import os
2014-04-22 02:20:17 -07:00
import re
2014-04-21 04:52:46 -07:00
def files(dirname):
2014-04-22 02:20:17 -07:00
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*(.*)$')
2014-04-21 04:52:46 -07:00
CRONTAB_FILES = ['/etc/crontab'] + files('/etc/cron.d')
ANACRONTAB_FILES = ['/etc/anacrontab']
USERCRONTAB_FILES = files('/var/spool/cron')
TARGER_DIR = sys.argv[1]
2014-04-23 11:27:35 +03:00
TIMERS_DIR = os.path.join(TARGER_DIR, 'cron.target.wants')
2014-04-22 02:20:17 -07:00
SELF = os.path.basename(sys.argv[0])
2014-04-21 04:52:46 -07:00
2014-04-23 11:27:35 +03:00
try:
os.makedirs(TIMERS_DIR)
2014-04-23 20:05:22 +03:00
except OSError as e:
2014-04-23 11:27:35 +03:00
if e.errno != os.errno.EEXIST:
raise
2014-04-21 04:52:46 -07:00
def parse_crontab(filename, withuser=True, monotonic=False):
basename = os.path.basename(filename)
2014-04-22 02:20:17 -07:00
environment = {}
2014-04-21 04:52:46 -07:00
with open(filename, 'r') as f:
for line in f.readlines():
if line.startswith('#'):
continue
2014-04-22 02:20:17 -07:00
line = line.rstrip('\n')
envvar = envvar_re.match(line)
if envvar:
environment[envvar.group(1)] = envvar.group(2)
2014-04-22 08:46:38 -07:00
continue
2014-04-22 02:20:17 -07:00
2014-04-21 04:52:46 -07:00
parts = line.split()
if monotonic:
2014-04-22 08:50:26 -07:00
if len(parts) < 4:
continue
2014-04-21 04:52:46 -07:00
period, delay, jobid = parts[0:3]
command = parts[3:]
2014-04-22 08:14:47 -07:00
period = {
'1': 'daily',
'7': 'weekly',
'@annually': 'yearly'
}.get(period, None) or period.lstrip('@')
2014-04-21 04:52:46 -07:00
2014-04-22 02:20:17 -07:00
yield {
'e': ' '.join('"%s=%s"' % kv for kv in environment.iteritems()),
'l': line,
2014-04-22 08:14:47 -07:00
'f': filename,
'p': period,
2014-04-21 04:52:46 -07:00
'd': delay,
'j': jobid,
2014-04-21 05:31:47 -07:00
'c': ' '.join(command),
'u': 'root'
2014-04-21 04:52:46 -07:00
}
else:
if line.startswith('@'):
2014-04-22 08:50:26 -07:00
if len(parts) < 2:
continue
2014-04-22 08:14:47 -07:00
period = parts[0]
period = {
'1': 'daily',
'7': 'weekly',
'@annually': 'yearly'
}.get(period, None) or period.lstrip('@')
2014-04-21 05:31:47 -07:00
user, command = (parts[1], parts[2:]) if withuser else (basename, parts[1:])
2014-04-22 02:20:17 -07:00
yield {
'e': ' '.join('"%s=%s"' % kv for kv in environment.iteritems()),
'l': line,
2014-04-22 08:14:47 -07:00
'f': filename,
2014-04-21 05:31:47 -07:00
'p': period,
'u': user,
'c': ' '.join(command)
2014-04-21 04:52:46 -07:00
}
else:
2014-04-22 08:51:45 -07:00
if len(parts) < 6 + int(withuser):
2014-04-22 08:50:26 -07:00
continue
2014-04-21 04:52:46 -07:00
minutes, hours, days = parts[0:3]
2014-04-22 02:20:17 -07:00
months, dows = parts[3:5]
2014-04-21 04:52:46 -07:00
user, command = (parts[5], parts[6:]) if withuser else (basename, parts[5:])
2014-04-22 02:20:17 -07:00
yield {
'e': ' '.join('"%s=%s"' % kv for kv in environment.iteritems()),
'l': line,
2014-04-22 08:14:47 -07:00
'f': filename,
2014-04-21 04:52:46 -07:00
'm': parse_time_unit(minutes, range(0, 60)),
'h': parse_time_unit(hours, range(0, 24)),
2014-04-22 02:20:17 -07:00
'd': parse_time_unit(days, range(0, 32)),
2014-04-21 04:52:46 -07:00
'w': parse_time_unit(dows, ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], dow_map),
2014-04-22 02:20:17 -07:00
'M': parse_time_unit(months, range(0, 13), month_map),
2014-04-21 04:52:46 -07:00
'u': user,
'c': ' '.join(command)
}
def parse_time_unit(value, values, mapping=int):
if value == '*':
return ['*']
2014-04-22 02:20:17 -07:00
return list(reduce(lambda a, i: a.union(set(i)), map(values.__getitem__,
map(parse_period(mapping), value.split(','))), set()))
2014-04-21 04:52:46 -07:00
def month_map(month):
try:
return int(month)
2014-04-22 02:20:17 -07:00
except ValueError:
return ['jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'nov', 'dec'].index(month.lower()[0:3]) + 1
2014-04-21 04:52:46 -07:00
def dow_map(dow):
try:
2014-04-22 02:20:17 -07:00
return ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'].index(dow[0:3].lower())
except ValueError:
return int(dow) % 7
2014-04-21 04:52:46 -07:00
def parse_period(mapping=int):
def parser(value):
try:
range, step = value.split('/')
except ValueError:
2014-04-21 05:31:47 -07:00
value = mapping(value)
return slice(value, value + 1)
2014-04-21 04:52:46 -07:00
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
2014-04-22 02:20:17 -07:00
def generate_timer_unit(job, seq):
2014-04-21 05:31:47 -07:00
n = next(seq)
2014-04-21 06:25:22 -07:00
unit_name = "cron-%s-%s" % (job['u'], n)
2014-04-23 11:27:35 +03:00
2014-04-21 07:14:11 -07:00
if 'p' in job:
if job['p'] == 'reboot':
2014-04-22 08:16:37 -07:00
schedule = 'OnBootSec=%sm' % job.get('d', 5)
2014-04-21 07:14:11 -07:00
else:
2014-04-22 08:14:47 -07:00
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)
2014-04-21 07:14:11 -07:00
else:
2014-04-22 02:20:17 -07:00
dows = ','.join(job['w'])
dows = '' if dows == '*' else dows + ' '
schedule = 'OnCalendar=%s*-%s-%s %s:%s:00' % (dows, ','.join(map(str, job['M'])),
2014-04-21 07:14:11 -07:00
','.join(map(str, job['d'])), ','.join(map(str, job['h'])), ','.join(map(str, job['m'])))
2014-04-22 08:14:47 -07:00
accuracy = 1
2014-04-21 04:52:46 -07:00
2014-04-21 05:31:47 -07:00
with open('%s/%s.timer' % (TARGER_DIR, unit_name), 'w') as f:
2014-04-22 02:20:17 -07:00
f.write('''# Automatically generated by %s
2014-04-22 08:14:47 -07:00
# Source crontab: %s
2014-04-21 07:21:38 -07:00
[Unit]
2014-04-21 06:49:37 -07:00
Description=[Cron] "%s"
PartOf=cron.target
RefuseManualStart=true
RefuseManualStop=true
[Timer]
Unit=%s.service
2014-04-22 02:35:24 -07:00
Persistent=true
2014-04-22 08:14:47 -07:00
AccuracySec=%sm
2014-04-21 07:14:11 -07:00
%s
2014-04-22 08:14:47 -07:00
''' % (SELF, job['f'], job['l'], unit_name, accuracy, schedule))
2014-04-21 06:25:22 -07:00
2014-04-23 20:05:22 +03:00
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
2014-04-23 11:27:35 +03:00
2014-04-21 05:31:47 -07:00
with open('%s/%s.service' % (TARGER_DIR, unit_name), 'w') as f:
2014-04-22 02:20:17 -07:00
f.write('''# Automatically generated by %s
2014-04-22 08:14:47 -07:00
# Source crontab: %s
2014-04-21 07:21:38 -07:00
[Unit]
2014-04-21 06:49:37 -07:00
Description=[Cron] "%s"
RefuseManualStart=true
RefuseManualStop=true
[Service]
Type=oneshot
User=%s
2014-04-22 02:20:17 -07:00
Environment=%s
ExecStart=/bin/sh -c '%s'
2014-04-22 08:14:47 -07:00
''' % (SELF, job['f'], job['l'], job['u'], job['e'], job['c']))
2014-04-22 02:20:17 -07:00
return '%s.timer' % unit_name
2014-04-21 05:31:47 -07:00
seqs = {}
def count():
n = 0
while True:
yield n
n += 1
2014-04-22 02:20:17 -07:00
2014-04-21 05:31:47 -07:00
for filename in CRONTAB_FILES:
try:
2014-04-23 11:27:35 +03:00
for job in parse_crontab(filename, withuser=True):
generate_timer_unit(job, seqs.setdefault(job['u'], count()))
2014-04-21 05:31:47 -07:00
except IOError:
pass
2014-04-22 08:14:47 -07:00
for filename in ANACRONTAB_FILES:
try:
2014-04-23 11:27:35 +03:00
for job in parse_crontab(filename, monotonic=True):
generate_timer_unit(job, seqs.setdefault(job['u'], count()))
2014-04-22 08:14:47 -07:00
except IOError:
pass
2014-04-21 05:31:47 -07:00
for filename in USERCRONTAB_FILES:
try:
2014-04-23 11:27:35 +03:00
for job in parse_crontab(filename, withuser=False):
generate_timer_unit(job, seqs.setdefault(job['u'], count()))
2014-04-21 05:31:47 -07:00
except IOError:
pass
2014-04-22 02:20:17 -07:00