diff options
author | Konstantin Stepanov | 2015-06-19 11:04:47 +0300 |
---|---|---|
committer | Konstantin Stepanov | 2015-06-19 11:04:47 +0300 |
commit | e3857f34c0bb79f2276ab2df7b748f0f6e1a631e (patch) | |
tree | c41743f98b55eca9a67443be23f27f44e8b91a6e | |
download | aur-e3857f34c0bb79f2276ab2df7b748f0f6e1a631e.tar.gz |
import package from old aur
-rw-r--r-- | .SRCINFO | 30 | ||||
-rw-r--r-- | PKGBUILD | 42 | ||||
-rw-r--r-- | cron.target | 5 | ||||
-rwxr-xr-x | crontab | 95 | ||||
-rw-r--r-- | crontab.1 | 32 | ||||
-rw-r--r-- | crontab.5 | 377 | ||||
-rwxr-xr-x | systemd-crontab-generator | 301 | ||||
-rw-r--r-- | systemd-crontab-generator.1 | 53 | ||||
-rwxr-xr-x | systemd-crontab-update | 4 |
9 files changed, 939 insertions, 0 deletions
diff --git a/.SRCINFO b/.SRCINFO new file mode 100644 index 000000000000..d0e35c0306d2 --- /dev/null +++ b/.SRCINFO @@ -0,0 +1,30 @@ +pkgbase = systemd-crontab-generator + pkgdesc = systemd generator to generate timers/services from crontab and anacrontab files + pkgver = 0.8 + pkgrel = 1 + url = https://github.com/kstep/systemd-crontab-generator + arch = any + license = GPL3 + depends = python2 + depends = systemd + provides = cron + provides = anacron + replaces = cron + replaces = anacron + source = systemd-crontab-generator + source = systemd-crontab-update + source = cron.target + source = crontab + source = systemd-crontab-generator.1 + source = crontab.1 + source = crontab.5 + md5sums = 3e6e2fb5bf3a6f05cacfbbb113516026 + md5sums = 6f00710ad710e319b52edef3e98bd010 + md5sums = 97450f27b69a1e88f1b21faad403df7c + md5sums = 4ac2cfc8de6dabf2e08f39b3c3557879 + md5sums = 15acf6fd2a9533c13ce21c6e03210194 + md5sums = d863925d682395cef72701725f180884 + md5sums = f5e92c03bcb37acd580e2e27f5facc6a + +pkgname = systemd-crontab-generator + diff --git a/PKGBUILD b/PKGBUILD new file mode 100644 index 000000000000..3c2b3f4bfaee --- /dev/null +++ b/PKGBUILD @@ -0,0 +1,42 @@ +# Maintainer: Konstantin Stepanov <me@kstep.me> +pkgname=systemd-crontab-generator +pkgver=0.8 +pkgrel=1 +pkgdesc="systemd generator to generate timers/services from crontab and anacrontab files" +url="https://github.com/kstep/systemd-crontab-generator" +arch=('any') +license=('GPL3') +depends=('python2' 'systemd') +provides=('cron' 'anacron') +replaces=('cron' 'anacron') +source=(systemd-crontab-generator + systemd-crontab-update + cron.target + crontab + systemd-crontab-generator.1 + crontab.1 + crontab.5) +md5sums=('3e6e2fb5bf3a6f05cacfbbb113516026' + '6f00710ad710e319b52edef3e98bd010' + '97450f27b69a1e88f1b21faad403df7c' + '4ac2cfc8de6dabf2e08f39b3c3557879' + '15acf6fd2a9533c13ce21c6e03210194' + 'd863925d682395cef72701725f180884' + 'f5e92c03bcb37acd580e2e27f5facc6a') + +build() { + echo +} + +package() { + install --mode=0755 -D systemd-crontab-generator ${pkgdir}/usr/lib/systemd/system-generators/systemd-crontab-generator + install --mode=0644 -D systemd-crontab-generator.1 ${pkgdir}/usr/share/man/man1/systemd-crontab-generator.1 + gzip ${pkgdir}/usr/share/man/man1/systemd-crontab-generator.1 + install --mode=0755 -D systemd-crontab-update ${pkgdir}/usr/bin/systemd-crontab-update + install --mode=0644 -D cron.target ${pkgdir}/usr/lib/systemd/system/cron.target + install --mode=0755 -D crontab ${pkgdir}/usr/bin/crontab + install --mode=0644 -D crontab.1 ${pkgdir}/usr/share/man/man1/crontab.1 + gzip ${pkgdir}/usr/share/man/man1/crontab.1 + install --mode=0644 -D crontab.5 ${pkgdir}/usr/share/man/man5/crontab.5 + gzip ${pkgdir}/usr/share/man/man5/crontab.5 +} diff --git a/cron.target b/cron.target new file mode 100644 index 000000000000..580e7084904e --- /dev/null +++ b/cron.target @@ -0,0 +1,5 @@ +[Unit] +Description=Cron Jobs + +[Install] +WantedBy=multi-user.target diff --git a/crontab b/crontab new file mode 100755 index 000000000000..5b95d32ae58f --- /dev/null +++ b/crontab @@ -0,0 +1,95 @@ +#!/usr/bin/python2 + +import tempfile +import sys +import os +import argparse +import getpass + +EDITOR = (os.environ.get('EDITOR') or + os.environ.get('VISUAL','/usr/bin/vim')) +CRONTAB_DIR = '/var/spool/cron' + +args_parser = argparse.ArgumentParser(description='maintain crontab files for individual users') + +args_parser.add_argument('-u', '--user', type=str, dest='user', default=getpass.getuser(), + help='''It specifies the name of the user whose crontab is to be + tweaked. If this option is not given, crontab examines "your" crontab, i.e., the crontab of the person + executing the command. Note that su(8) can confuse crontab and that if you are running inside of su(8) you + should always use the -u option for safety's sake. The first form of this command is used to install a new + crontab from some named file or standard input if the pseudo-filename "-" is given.''') + +args_parser.add_argument('file', type=str, default='-', nargs='?') + +args_parser.add_argument('-l', '--list', dest='action', action='store_const', const='list', + help='''The current crontab will be displayed on standard output.''') + +args_parser.add_argument('-r', '--remove', dest='action', action='store_const', const='remove', + help='''The current crontab will be removed.''') + +args_parser.add_argument('-e', '--edit', dest='action', action='store_const', const='edit', + help='''This option is used to edit the current crontab using the editor + specified by the VISUAL or EDITOR environment variables. After + you exit from the editor, the modified crontab will be installed + automatically.''') + +args_parser.add_argument('-i', '--ask', dest='ask', action='store_true', default=False, + help='''This option modifies the -r option to prompt the user for a + 'y/Y' response before actually removing the crontab.''') + +#args_parser.add_argument('-s', '--secure', dest='secure', action='store_true', default=False, + #help='''It will append the current SELinux security context string as an + #MLS_LEVEL setting to the crontab file before editing / replacement occurs + #- see the documentation of MLS_LEVEL in crontab(5).''') + +def confirm(message): + while True: + answer = raw_input(message).lower() + if answer not in 'yn': + print('Please reply "y" or "n"') + continue + + return answer == 'y' + +def list(cron_file, args): + with open(cron_file, 'r') as f: + sys.stdout.write(f.read()) + +def remove(cron_file, args): + if not args.ask or confirm('Are you sure you want to delete %s (y/n)? ' % cron_file): + os.unlink(cron_file) + +def edit(cron_file, args): + with tempfile.NamedTemporaryFile() as tmp: + with open(cron_file, 'r') as inp: + tmp.file.write(inp.read()) + + tmp.file.flush() + + if os.system("'%s' '%s'" % (EDITOR, tmp.name)) == 0: + tmp.file.seek(0) + with open(cron_file, 'w') as out: + out.write(tmp.file.read()) + +def replace(cron_file, args): + infile = args.file + if infile == '-': + with open(cron_file, 'w') as out: + out.write(sys.stdin.read()) + + else: + with open(cron_file, 'w'), open(infile, 'r') as out, inp: + out.write(inp.read()) + +if __name__ == '__main__': + args = args_parser.parse_args() + cron_file = os.path.join(CRONTAB_DIR, args.user) + + action = { + 'list': list, + 'edit': edit, + 'remove': remove, + }.get(args.action, replace) + + action(cron_file, args) + diff --git a/crontab.1 b/crontab.1 new file mode 100644 index 000000000000..18b04f1cae85 --- /dev/null +++ b/crontab.1 @@ -0,0 +1,32 @@ +.TH CRONTAB 1 "2014-06-26" "" systemd-crontab-generator + +.SH NAME +crontab - maintain crontab files for individual users + +.SH SYNOPSIS +crontab [\-u user] file +.br +crontab [\-u user] [\-l | \-r | \-e] [\-i] + +.SH DESCRIPTION +Crontab is the program used to let users install, deinstall or list +recurrent jobs in the legacy cron format. +.br +Each user can have their own crontab, and though these are files in /var/spool/, +they are not intended to be edited directly. +.br +These jobs are then automatically translated in systemd Timers & Units +by systemd-crontab-generator. + +.SH FILES +.TP +.I /var/spool/cron +Directory for users crontabs. + +.SH LIMITATIONS +The \-s flag (SELinux) is not supported. +.br +/etc/cron.allow & /etc/cron.deny are not supported. + +.SH AUTHOR +Alexandre Detiste <alexandre@detiste.be> diff --git a/crontab.5 b/crontab.5 new file mode 100644 index 000000000000..40f17662ce28 --- /dev/null +++ b/crontab.5 @@ -0,0 +1,377 @@ +.\"/* Copyright 1988,1990,1993,1994 by Paul Vixie +.\" * All rights reserved +.\" * +.\" * Distribute freely, except: don't remove my name from the source or +.\" * documentation (don't take credit for my work), mark your changes (don't +.\" * get me blamed for your possible bugs), don't alter or remove this +.\" * notice. May be sold if buildable source is provided to buyer. No +.\" * warrantee of any kind, express or implied, is included with this +.\" * software; use at your own risk, responsibility for damages (if any) to +.\" * anyone resulting from the use of this software rests entirely with the +.\" * user. +.\" * +.\" * Send bug reports, bug fixes, enhancements, requests, flames, etc., and +.\" * I'll try to keep a version up to date. I can be reached as follows: +.\" * Paul Vixie <paul@vix.com> uunet!decwrl!vixie!paul +.\" */ +.\" +.\" $Id: crontab.5,v 2.4 1994/01/15 20:43:43 vixie Exp $ +.\" +.TH CRONTAB 5 "03 July 2014" +.UC 4 +.SH NAME +crontab \- tables for driving cron +.SH DESCRIPTION +A +.I crontab +file contains instructions to the +.IR cron (8) +daemon of the general form: ``run this command at this time on this date''. +Each user has their own crontab, and commands in any given crontab will be +executed as the user who owns the crontab. Uucp and News will usually have +their own crontabs, eliminating the need for explicitly running +.IR su (1) +as part of a cron command. +.PP +Blank lines and leading spaces and tabs are ignored. Lines whose first +non-space character is a hash-sign (#) are comments, and are ignored. +Note that comments are not allowed on the same line as cron commands, since +they will be taken to be part of the command. Similarly, comments are not +allowed on the same line as environment variable settings. +.PP +An active line in a crontab will be either an environment setting or a cron +command. The crontab file is parsed from top to bottom, so any environment +settings will affect only the cron commands below them in the file. +An environment setting is of the form, +.PP + name = value +.PP +where the spaces around the equal-sign (=) are optional, and any subsequent +non-leading spaces in +.I value +will be part of the value assigned to +.IR name . +The +.I value +string may be placed in quotes (single or double, but matching) to preserve +leading or trailing blanks. To define an empty variable, quotes +.B must +be used. The +.I value +string is +.B not +parsed for environmental substitutions or replacement of variables, thus lines +like +.PP + PATH = $HOME/bin:$PATH +.PP +will not work as you might expect. And neither will this work +.PP + A=1 + B=2 + C=$A $B +.PP +There will not be any subsitution for the defined variables in the +last value. +.PP +An alternative for setting up the commands path is using the fact that +many shells will treat the tilde(~) as substitution of $HOME, so if you use +.I bash +for your tasks you can use this: +.PP + SHELL=/bin/bash + PATH=~/bin:/usr/bin/:/bin +.PP +Several environment variables are set up automatically by the +.IR cron (8) +daemon. +SHELL is set to /bin/sh, and LOGNAME and HOME are set from the /etc/passwd +line of the crontab's owner. PATH is set to "/usr/bin:/bin". +HOME, SHELL, and PATH may be overridden by settings in the crontab; +LOGNAME is the user that the job is running from, and may not be changed. +.PP +(Another note: the LOGNAME variable is sometimes called USER on BSD systems... +on these systems, USER will be set also.) +.PP +systemd-crontab-generator doesn't set the MAILTO variable nor send +any mails like vixie-cron. The output of jobs is written in the journal. +.\"In addition to LOGNAME, HOME, and SHELL, +.\".IR cron (8) +.\"will look at MAILTO if it has any reason to send mail as a result of running +.\"commands in ``this'' crontab. If MAILTO is defined (and non-empty), mail is +.\"sent to the user so named. MAILTO may also be used to direct mail to multiple +.\"recipients by separating recipient users with a comma. If MAILTO is defined +.\"but empty (MAILTO=""), no mail will be sent. Otherwise mail is sent to the +.\"owner of the crontab. +.\".PP +.\"On the Debian GNU/Linux system, cron supports the +.\".B pam_env +.\"module, and loads the environment specified by +.\".IR /etc/environment +.\"and +.\".IR /etc/security/pam_env.conf . +.\"It also reads locale information from +.\".IR /etc/default/locale . +.\"However, the PAM settings do +.\".B NOT +.\"override the settings described above nor any settings in the +.\".I crontab +.\"file itself. Note in particular that if you want a PATH other than +.\" "/usr/bin:/bin", you will need to set it in the crontab file. +.\".PP +.\"By default, cron will send mail using the mail "Content-Type:" header of +.\" "text/plain" with the "charset=" parameter set to the charmap / codeset of the +.\"locale in which +.\".IR crond (8) +.\"is started up - ie. either the default system locale, if no LC_* environment +.\"variables are set, or the locale specified by the LC_* environment variables +.\"( see +.\".IR locale (7) ). +.\"You can use different character encodings for mailed cron job output by +.\"setting the CONTENT_TYPE and CONTENT_TRANSFER_ENCODING variables in crontabs, +.\"to the correct values of the mail headers of those names. +.PP +The format of a cron command is very much the V7 standard, with a number of +upward-compatible extensions. Each line has five time and date fields, +followed by a command, followed by a newline character ('\\n'). +The system crontab (/etc/crontab) uses the same format, except that +the username for the command is specified after the time and +date fields and before the command. The fields may be separated +by spaces or tabs. +.PP +Commands are executed by +.IR cron (8) +when the minute, hour, and month of year fields match the current time, +.I and +when at least one of the two day fields (day of month, or day of week) +match the current time (see ``Note'' below). +.IR cron (8) +examines cron entries once every minute. +The time and date fields are: +.IP +.ta 1.5i +field allowed values +.br +----- -------------- +.br +minute 0-59 +.br +hour 0-23 +.br +day of month 1-31 +.br +month 1-12 (or names, see below) +.br +day of week 0-7 (0 or 7 is Sun, or use names) +.br +.PP +A field may be an asterisk (*), which always stands for ``first\-last''. +.PP +Ranges of numbers are allowed. Ranges are two numbers separated +with a hyphen. The specified range is inclusive. For example, +8-11 for an ``hours'' entry specifies execution at hours 8, 9, 10 +and 11. +.PP +Lists are allowed. A list is a set of numbers (or ranges) +separated by commas. Examples: ``1,2,5,9'', ``0-4,8-12''. +.PP +Step values can be used in conjunction with ranges. Following +a range with ``/<number>'' specifies skips of the number's value +through the range. For example, ``0-23/2'' can be used in the hours +field to specify command execution every other hour (the alternative +in the V7 standard is ``0,2,4,6,8,10,12,14,16,18,20,22''). Steps are +also permitted after an asterisk, so if you want to say ``every two +hours'', just use ``*/2''. +.PP +Names can also be used for the ``month'' and ``day of week'' +fields. Use the first three letters of the particular +day or month (case doesn't matter). Ranges or +lists of names are not allowed. +.PP +The ``sixth'' field (the rest of the line) specifies the command to be +run. +The entire command portion of the line, up to a newline +.\" or % character +, will be executed by /bin/sh or by the shell +specified in the SHELL variable of the crontab file. +.\"Percent-signs (%) in the command, unless escaped with backslash +.\"(\\), will be changed into newline characters, and all data +.\"after the first % will be sent to the command as standard +.\"input. There is no way to split a single command line onto multiple +.\"lines, like the shell's trailing "\\". +.PP +systemd-crontab-generator doesn't handle multi-line command split by +the % character like vixie-cron. +.PP +Note: The day of a command's execution can be specified by two +fields \(em day of month, and day of week. If both fields are +restricted (i.e., aren't *), the command will be run when +.I either +field matches the current time. For example, +.br +``30 4 1,15 * 5'' +would cause a command to be run at 4:30 am on the 1st and 15th of each +month, plus every Friday. One can, however, achieve the desired result +by adding a test to the command (see the last example in EXAMPLE CRON FILE +below). +.PP +Instead of the first five fields, one of eight special strings may appear: +.IP +.ta 1.5i +string meaning +.br +------ ------- +.br +@reboot Run once, at startup. +.br +@yearly Run once a year, "0 0 1 1 *". +.br +@annually (same as @yearly) +.br +@monthly Run once a month, "0 0 1 * *". +.br +@weekly Run once a week, "0 0 * * 0". +.br +@daily Run once a day, "0 0 * * *". +.br +@midnight (same as @daily) +.br +@hourly Run once an hour, "0 * * * *". +.br +.PP +Please note that startup, as far as @reboot is concerned, is the time when +the +.IR cron (8) +daemon startup. In particular, it may be before some system daemons, +or other facilities, were startup. This is due to the boot order +sequence of the machine. + +.SH EXAMPLE CRON FILE + +The following lists an example of a user crontab file. + +.nf + +# use /bin/bash to run commands, instead of the default /bin/sh +SHELL=/bin/bash +.\" # mail any output to `paul', no matter whose crontab this is +.\" MAILTO=paul +# +# run five minutes after midnight, every day +5 0 * * * $HOME/bin/daily.job >> $HOME/tmp/out 2>&1 +# run at 2:15pm on the first of every month +.\" -- output mailed to paul +15 14 1 * * $HOME/bin/monthly +.\"# run at 10 pm on weekdays, annoy Joe +.\"0 22 * * 1-5 mail \-s "It's 10pm" joe%Joe,%%Where are your kids?% +23 0-23/2 * * * echo "run 23 minutes after midn, 2am, 4am ..., everyday" +5 4 * * sun echo "run at 5 after 4 every sunday" +# Run on every second Saturday of the month +0 4 8-14 * * test $(date +\\%u) \-eq 6 && echo "2nd Saturday" +.fi +.SH EXAMPLE SYSTEM CRON FILE + +The following lists the content of a regular system-wide crontab file. Unlinke a +user's crontab, this file has the username field, as used by /etc/crontab. + +.nf +# /etc/crontab: system-wide crontab +# Unlike any other crontab you don't have to run the `crontab' +# command to install the new version when you edit this file +# and files in /etc/cron.d. These files also have username fields, +# that none of the other crontabs do. + +SHELL=/bin/sh +PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin + +# m h dom mon dow user command +17 * * * * root cd / && run-parts \-\-report /etc/cron.hourly +25 6 * * * root test \-x /usr/sbin/anacron || ( cd / && run-parts \-\-report /etc/cron.daily ) +47 6 * * 7 root test \-x /usr/sbin/anacron || ( cd / && run-parts \-\-report /etc/cron.weekly ) +52 6 1 * * root test \-x /usr/sbin/anacron || ( cd / && run-parts \-\-report /etc/cron.monthly ) +# +.fi +.SH SEE ALSO +cron(8), crontab(1) +.SH EXTENSIONS +When specifying day of week, both day 0 and day 7 will be considered Sunday. +BSD and AT&T seem to disagree about this. +.PP +Lists and ranges are allowed to co-exist in the same field. "1-3,7-9" would +be rejected by AT&T or BSD cron -- they want to see "1-3" or "7,8,9" ONLY. +.PP +Ranges can include "steps", so "1-9/2" is the same as "1,3,5,7,9". +.PP +Months or days of the week can be specified by name. +.PP +Environment variables can be set in the crontab. In BSD or AT&T, the +environment handed to child processes is basically the one from /etc/rc. +.PP +.\"Command output is mailed to the crontab owner (BSD can't do this), can be +.\"mailed to a person other than the crontab owner (SysV can't do this), or the +.\"feature can be turned off and no mail will be sent at all (SysV can't do this +.\"either). +.\".PP +All of the `@' commands that can appear in place of the first five fields +are extensions. +.SH LIMITATIONS +The +.I cron +daemon runs with a defined timezone. It currently does not support +per-user timezones. All the tasks: system's and user's will be run based on the +configured timezone. Even if a user specifies the +.I TZ +environment variable in his +.I crontab +this will affect only the commands executed in the crontab, not the execution +of the crontab tasks themselves. + +The +.I crontab +syntax does not make it possible to define all possible periods one could +image off. For example, it is not straightforward to define the last +weekday of a month. If a task needs to be run in a specific period of time +that cannot be defined in the +.I crontab +syntaxs the best approach would be to have the program itself check the +date and time information and continue execution only if the period +matches the desired one. + +If the program itself cannot do the checks then a wrapper script would be +required. Useful tools that could be used for date analysis are +.I ncal +or +.I calendar +For example, to run a program the last Saturday of every month you could use +the following wrapper code: + +.nf +0 4 * * Sat [ "$(date +\\%e)" = "`ncal | grep $(date +\\%a | sed \-e 's/.$//') | sed \-e 's/^.*\\s\\([0-9]\\+\\)\\s*$/\\1/'`" ] && echo "Last Saturday" && program_to_run +.fi + +.B systemd-crontab-generator +doesn't support these +.B vixie-cron +features: +.TP +* +sending emails with job output, the output is written to systemd journal +.TP +* +multi-line jobs separated by the '%' character +.TP +* +vixie-cron requires that each entry in a crontab end in a newline character. If the +last entry in a crontab is missing a newline (ie, terminated by EOF), vixie-cron will +consider the crontab (at least partially) broken. +.br +systemd-crontab-generator considers this crontab as valid + +.\".SH DIAGNOSTICS + +.SH AUTHOR +Paul Vixie <paul@vix.com> is the author of +.I cron +and original creator of this manual page. This page has also been modified for +Debian by Steve Greenland, Javier Fernandez-Sanguino and Christian Kastner. + diff --git a/systemd-crontab-generator b/systemd-crontab-generator new file mode 100755 index 000000000000..0f6408b516e9 --- /dev/null +++ b/systemd-crontab-generator @@ -0,0 +1,301 @@ +#!/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()), + 's': environment['SHELL'], + '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()), + 's': environment['SHELL'], + '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()), + 's': environment['SHELL'], + '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 sorted(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['s'], job['c'])) + + return '%s.timer' % unit_name + +def generate_path_unit(): + combinedcronfiles = [ '/etc/crontab', '/etc/cron.d', '/etc/anacrontab', '/var/spool/cron' ] + with open('%s/systemd-crontab-update.path' % (TARGER_DIR), 'w') as f: + f.write('''# Automatically generated by %s + +[Unit] +Description=[Cron] Update cron units +RefuseManualStart=true +RefuseManualStop=true + +[Path] +%s +''' % (SELF,'\n'.join([ "PathChanged="+f for f in combinedcronfiles ]) )) + + try: + os.symlink('%s/systemd-crontab-update.path' % (TARGER_DIR), '%s/systemd-crontab-update.path' % (TIMERS_DIR)) + except OSError as e: + if e.errno != os.errno.EEXIST: + raise + + with open('%s/systemd-crontab-update.service' % (TARGER_DIR), 'w') as f: + f.write('''# Automatically generated by %s + +[Unit] +Description=[Cron] Update cron units + +[Service] +Type=oneshot +ExecStart=/usr/bin/systemd-crontab-update +''' % (SELF)) + + return "systemd-crontab-update.path" + +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 + +try: + generate_path_unit() +except IOError: + pass diff --git a/systemd-crontab-generator.1 b/systemd-crontab-generator.1 new file mode 100644 index 000000000000..a3c63e6037bf --- /dev/null +++ b/systemd-crontab-generator.1 @@ -0,0 +1,53 @@ +.TH SYSTEMD-CRONTAB-GENERATOR 1 "2014-06-29" "" systemd-crontab-generator + +.SH NAME +systemd-crontab-generator - translate cron schedules in systemd Units + +.SH SYNOPSIS +systemd-crontab-generator folder + +.SH DESCRIPTION +systemd-crontab-generator translates the legacy cron files (see FILES) +into systemd units & timers. +.PP +It should be run +.TP +* +at boot, +.TP +* +after each manual updates to the cron files, +.TP +* +and when distribution packages add files in /etc/cron.d/. +.PP +\fBsystemd-crontab-update\fR is a sample script that +calls systemd-crontab-generator. + +.SH FILES +.TP +.B /etc/crontab +System crontab. + +.TP +.B /etc/cron.d +Directory for system crontabs. + +.TP +.B /etc/anacrontab + +.TP +.B /var/spool/cron +Directory for users crontabs. +.br + +.\.SH LIMITATIONS +.\The \-s flag (SELinux) is not supported. +.\.br +.\/etc/cron.allow & /etc/cron.deny are not supported. + +.SH SEE ALSO +\fBcrontab\fR(1),\fBsystemd.unit\fR(5),\fBsystemd.timer\fR(5) + +.SH AUTHOR +Alexandre Detiste <alexandre@detiste.be> diff --git a/systemd-crontab-update b/systemd-crontab-update new file mode 100755 index 000000000000..96fe3f5b2aa7 --- /dev/null +++ b/systemd-crontab-update @@ -0,0 +1,4 @@ +#!/bin/sh + +systemctl daemon-reload +systemctl try-restart cron.target |