pax_global_header00006660000000000000000000000064141621063520014512gustar00rootroot0000000000000052 comment=0bac0eb05b92cdb8663f76c7433f965035613a09 systemd-cron-1.15.18/000077500000000000000000000000001416210635200143165ustar00rootroot00000000000000systemd-cron-1.15.18/.gitignore000066400000000000000000000000251416210635200163030ustar00rootroot00000000000000out/ Makefile *.swp systemd-cron-1.15.18/CHANGELOG000066400000000000000000000162331416210635200155350ustar00rootroot00000000000000v1.5.15 : 2020-10-17 * fix skipping OnFailure for empty MAILTO=, thanks to Richard Laager * make run-parts uses optional v1.5.14 : 2018-11-11 * Python 3.7 comptability, thanks to enrico.detoma * Handle sending mail_on_error when the locale is not correctly set * Use KillMode=process for generated units v1.5.13 : 2018-03-28 * escape '%' in unit description (Thanks Mateusz Kowalewski) v1.5.12 : 2017-12-08 * fix typo in configure script introduced in 1.5.11 v1.5.11 : 2017-12-08 * add distinct configure option for systemd generator dir (Thanks Mike Gilbert) * add support for RandomizedDelaySec, this requires systemd ≥ 229 v1.5.10 : 2017-07-01 * fix regression in handling of masked/overriden timer units v1.5.9 : 2017-06-18 * allow runtime masking of units * log masked timers in a distinct way * drop cargo-culted RefuseManual[Start|Stop] * delay cron-boot.timer of 1 minute v1.5.8 : 2017-01-15 * handle weekly slices as expected * document that someone can use 'systemctl edit' to override (generated) units. v1.5.7 : 2017-01-11 * try to fix it again, hopefully finally, making parse_time_unit() even harder to understand v1.5.6 : 2017-01-10 * fix last off-by-one error in processing of - ranges. v1.5.5 : 2017-01-10 * pass $(CPPFLAGS) to compilator for setgid helper * almost always generate .sh scripts to avoid that systemd complains with "Invalid escape sequences in line, correcting:" when parsing some complex one-liners https://github.com/systemd/systemd/blob/master/src/basic/extract-word.c#L204 * fix off-by-one error in processing of '*/' for months & days. Issue #49 v1.5.4 : 2016-01-29 * generator: don't call OnFailure unit without a MTA avalaible. * sync with systemd-cron-next: remove --stale-stamps configure option * quote "Environment=" keypairs only when necessary v1.5.3 : 2015-02-16 Minor bugfixes: * add crontab --show option that lists crontabs * crontab: try /usr/bin/editor, /usr/bin/vim, /usr/bin/nano, /usr/bin/mcedit if VISUAL and EDITOR are not set * avoid forgeting successful edits in /tmp/ v1.5.2 : 2014-12-21 Bug-fix release: * generator now process UTF-8 files correctly (generators are run with LANG=C) * global exception handler added to generator; now prints error in journal * make install : setgid helper will be chgrp cron / chmod 2755 if group cron exists ; but won't create this group by itself * now support BATCH=yes|no into crontabs; this is translasted into CPUSchedulingPolicy=idle & IOSchedulingClass=idle * the generator can now co-exist with a boilerplate /etc/crontab that does includes include definitions for /etc/cron.daily etc... these will only be processed if the matching native unit is not activated during configure step v1.5.1 : 2014-12-13 * make all writes in crontab (both Python & C parts) atomic * keep rejected crontabs in /tmp/crontab* for review * added support for /etc.cron.allow and /etc/cron.deny; without any of those, only root can create crontabs * turn the setuid/root helper into a setguid/crontab to let it run with the least privieges If you enable this feature in your package, some additional setup is needed after it has been unpacked: getent group crontab > /dev/null 2>&1 || addgroup --system crontab chgrp crontab /lib/systemd-cron/crontab_setgid chmod 2755 /lib/systemd-cron/crontab_setgid mkdir -p /var/spool/cron/crontabs chown root:crontab /var/spool/cron/crontabs chmod 1730 /var/spool/cron/crontabs cd /var/spool/cron/crontabs ls -1 | xargs -r -n 1 --replace=xxx chown 'xxx:crontab' 'xxx' ls -1 | xargs -r -n 1 chmod 600 The crontab program, when run as root, will also try to fixup file permissions; but won't create the crontab group. You can use sudo crontab -l -u $USER to fix-up your own crontab's permissions. THANKS to Lorenzo Beretta for review v1.5.0 : 2014-12-11 * added an optional C setuid helper to let non-root users use crontab v1.4.2 : 2014-11-25 Bug-Fix release * build now honor bindir for systemctl location * generator: better handling of quoted commands in crontabs v1.4.1 : 2014-11-11 * generator: will now log warnings & errors about bad crontabs in the journal * generator: try less aggressively to make jobs persistent: a job like 0 19 * * * root poweroff on a school/office PC would had run the next morning; which is not the expected behaviour; this can now be overiden with the PERSISTENT=yes|auto|no variable * new trivial internal utility boot_delay : this is needed because combining OnCalendar= & OnBootSec= in timer units doesn't have the needed behaviour (OR instead of AND). A boot delay is a standard feature of anacrontab; but can now also be used in crontab by specifying the DELAY= value. Thanks to @wavexx for the extensive review. v1.4.0 : 2014-11-04 * new utility mail_on_failure that is called by a new OnFailure= hook, both for static & generated units * add support for new time unit introduced in systemd 217: minutely, quarterly, semi-annually * review of man pages v1.3.3 : 2014-10-21 This release solves a bug introduced in crontab with the switch to Python3 v1.3.2 : 2014-10-20 * switch to Python3 for crontab & generator * add a manpage for /etc/anacrontab * add a new utility remove_stale_stamps than removes stales stamps from long deleted crontabs ( It can be called from /etc/cron.weekly/ ) * configure: statedir defaults now to '/var/spool/cron' * crontab: improved error handling * generator: add support for RANDOM_DELAY & START_HOURS_RANGE to anacrontab. This works in crontabs too. v1.3.1 : 2014-09-22 * turn Path watching unit in static unit * make Persistent=true smarter * make /var/spool configurable * put man page in right section v1.3.0 : 2014-09-04 merge in systemd-crontab-generator from @kstep https://github.com/kstep/systemd-crontab-generator v1.2.1 : 2014-04-01 * FIX: Build should now be parallelizable with make -j v1.2.0 : 2014-03-31 * NEW: Yearly timers with system ≥ 209. * NEW: Persistent timers with systemd ≥ 212. v1.1.2 : 2013-08-24 * FIX: Reconfigure units so that service units are automatically started by targets without needing to have them separately enabled. v1.1.1 : 2013-07-31 * Only activate service units if corresponding cron directory is non-empty. * Makefile does not automatically install cron directories. Thanks @WithHat v1.1.0 : 2013-07-16 * Add target units which allow you to write custom cron jobs. Thanks @WithHat v1.0.1 : 2013-05-31 * Update build mechanism with configure script and templated Makefile. v1.0.0 : 2013-05-30 * Support cron.boot for scripts to be executed at boot-up. v0.1.0 : 2013-05-27 * Initial release supporting hourly, daily, weekly, and monthly scripts. systemd-cron-1.15.18/LICENSE000066400000000000000000000022641416210635200153270ustar00rootroot00000000000000Copyright (C) 2013 Dwayne Bent Copyright (C) 2013 Dominik Peteler Copyright (C) 2014 Konstantin Stepanov (me@kstep.me) Copyright (C) 2014 Alexandre Detiste (alexandre@detiste.be) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. systemd-cron-1.15.18/Makefile.in000066400000000000000000000203431416210635200163650ustar00rootroot00000000000000CFLAGS ?= -O2 CFLAGS += -Wall package := $(shell head -n1 README.md) version := $(shell cat VERSION) schedules := @schedules@ schedules_not := @schedules_not@ enable_runparts := @enable_runparts@ enable_persistent := @enable_persistent@ enable_randomized_delay := @enable_randomized_delay@ enable_setgid := @enable_setgid@ use_loglevelmax := @use_loglevelmax@ prefix := @prefix@ bindir := @bindir@ confdir := @confdir@ datadir := @datadir@ libdir := @libdir@ statedir := @statedir@ mandir := @mandir@ docdir := @docdir@ unitdir := @unitdir@ generatordir := @generatordir@ runparts := @runparts@ srcdir := $(CURDIR)/src outdir := $(CURDIR)/out builddir := $(outdir)/build distname := $(package)-$(version) distdir := $(outdir)/dist/$(distname) tarball := $(outdir)/dist/$(distname).tar.xz out_services := $(foreach schedule,$(schedules),$(builddir)/units/cron-$(schedule).service) out_timers := $(foreach schedule,$(schedules),$(builddir)/units/cron-$(schedule).timer) out_targets := $(foreach schedule,$(schedules),$(builddir)/units/cron-$(schedule).target) out_units := $(out_services) $(out_timers) $(out_targets) $(builddir)/units/cron.target \ $(builddir)/units/cron-update.path $(builddir)/units/cron-update.service \ $(builddir)/units/cron-failure@.service out_manuals := $(patsubst $(srcdir)/man/%.in,$(builddir)/man/%,$(wildcard $(srcdir)/man/*)) out_programs := $(patsubst $(srcdir)/bin/%.py,$(builddir)/bin/%,$(wildcard $(srcdir)/bin/*.py)) outputs := $(out_units) $(out_manuals) $(out_programs) $(builddir)/bin/crontab_setgid define \n endef null := requires = $(subst ${null} ${null},\n,$(foreach schedule,$(schedules),Requires=cron-$(schedule).timer)) use_runparts = $(if $(filter $(enable_runparts),yes),True,False) persistent = $(if $(filter $(enable_persistent),yes),True,False) randomized_delay = $(if $(filter $(enable_randomized_delay),yes),True,False) # $(call in2out,$input,$output,$schedule,$requires) define in2out sed \ -e "s|\@package\@|$(package)|g" \ -e "s|\@confdir\@|$(confdir)|g" \ -e "s|\@statedir\@|$(statedir)|g" \ -e "s|\@bindir\@|$(bindir)|g" \ -e "s|\@libdir\@|$(libdir)|g" \ -e "s|\@unitdir\@|$(unitdir)|g" \ -e "s|\@generatordir\@|$(generatordir)|g" \ -e "s|\@runparts\@|$(runparts)|g" \ -e "s|\@use_runparts\@|$(use_runparts)|g" \ -e "s|\@version\@|$(version)|g" \ -e "s|\@persistent\@|$(persistent)|g" \ -e "s|\@randomized_delay\@|$(randomized_delay)|g" \ -e "s|\@use_loglevelmax\@|$(use_loglevelmax)|g" \ -e "s|\@schedule\@|$3|g" \ -e "s|\@requires\@|$4|g" \ $1 > $2 endef all: $(builddir) $(outputs) clean: rm -rf $(outdir) distprep: $(distdir) cp -a configure $(distdir) cp -a Makefile.in $(distdir) cp -a LICENSE $(distdir) cp -a README.md $(distdir) cp -a VERSION $(distdir) cp -a src $(distdir) dist: $(tarball) test: all $(foreach manpage,$(out_manuals),\ man --warnings --encoding=utf8 --local-file $(manpage) 2>&1 > /dev/null${\n}) $(foreach program,$(out_programs),\ pyflakes3 $(program) ${\n}) install: all install -m755 -D $(builddir)/bin/crontab $(DESTDIR)$(bindir)/crontab install -m755 -D $(builddir)/bin/systemd-crontab-generator $(DESTDIR)$(generatordir)/systemd-crontab-generator install -m755 -D $(builddir)/bin/remove_stale_stamps $(DESTDIR)$(libdir)/$(package)/remove_stale_stamps install -m755 -D $(builddir)/bin/mail_on_failure $(DESTDIR)$(libdir)/$(package)/mail_on_failure install -m755 -D $(builddir)/bin/boot_delay $(DESTDIR)$(libdir)/$(package)/boot_delay install -m644 -D $(srcdir)/lib/sysusers.d/systemd-cron.conf $(DESTDIR)$(libdir)/sysusers.d/systemd-cron.conf ifneq ($(enable_setgid),no) install -m755 -D $(builddir)/bin/crontab_setgid $(DESTDIR)$(libdir)/$(package)/crontab_setgid if getent group cron > /dev/null 2>&1; then \ chgrp cron $(DESTDIR)$(libdir)/$(package)/crontab_setgid ; \ chmod 2755 $(DESTDIR)$(libdir)/$(package)/crontab_setgid ; \ fi endif install -m644 -D $(builddir)/man/systemd.cron.7 $(DESTDIR)$(mandir)/man7/systemd.cron.7 install -m644 -D $(builddir)/man/crontab.1 $(DESTDIR)$(mandir)/man1/crontab.1 install -m644 -D $(builddir)/man/crontab.5 $(DESTDIR)$(mandir)/man5/crontab.5 install -m644 -D $(builddir)/man/anacrontab.5 $(DESTDIR)$(mandir)/man5/anacrontab.5 install -m644 -D $(builddir)/man/systemd-crontab-generator.8 $(DESTDIR)$(mandir)/man8/systemd-crontab-generator.8 install -m644 -D $(builddir)/units/cron.target $(DESTDIR)$(unitdir)/cron.target install -m644 $(builddir)/units/cron-update.path $(DESTDIR)$(unitdir) install -m644 $(builddir)/units/cron-update.service $(DESTDIR)$(unitdir) install -m644 $(builddir)/units/cron-failure@.service $(DESTDIR)$(unitdir) $(foreach schedule,$(schedules),\ install -m644 $(builddir)/units/cron-$(schedule).timer $(DESTDIR)$(unitdir)${\n}) $(foreach schedule,$(schedules),\ install -m644 $(builddir)/units/cron-$(schedule).target $(DESTDIR)$(unitdir)${\n}) $(foreach schedule,$(schedules),\ install -m644 $(builddir)/units/cron-$(schedule).service $(DESTDIR)$(unitdir)${\n}) uninstall: rm -f $(DESTDIR)$(bindir)/crontab rm -f $(DESTDIR)$(libdir)/systemd/system-generators/systemd-crontab-generator rm -f $(DESTDIR)$(libdir)/$(package)/remove_stale_stamps rm -f $(DESTDIR)$(libdir)/$(package)/mail_on_failure rm -f $(DESTDIR)$(libdir)/$(package)/boot_delay rm -f $(DESTDIR)$(libdir)/$(package)/crontab_setgid rm -f $(DESTDIR)$(mandir)/man7/systemd.cron.7 rm -f $(DESTDIR)$(mandir)/man1/crontab.1 rm -f $(DESTDIR)$(mandir)/man5/crontab.5 rm -f $(DESTDIR)$(mandir)/man5/anacrontab.5 rm -f $(DESTDIR)$(mandir)/man8/systemd-crontab-generator.8 rm -f $(DESTDIR)$(unitdir)/cron.target rm -f $(DESTDIR)$(unitdir)/cron-update.path rm -f $(DESTDIR)$(unitdir)/cron-update.service rm -f $(DESTDIR)$(unitdir)/cron-failure@.service $(foreach schedule,$(schedules),\ rm -f $(DESTDIR)$(unitdir)/cron-$(schedule).timer${\n}) $(foreach schedule,$(schedules),\ rm -f $(DESTDIR)$(unitdir)/cron-$(schedule).target${\n}) $(foreach schedule,$(schedules),\ rm -f $(DESTDIR)$(unitdir)/cron-$(schedule).service${\n}) $(builddir)/units/cron-update.path: $(srcdir)/units/cron-update.path.in $(call in2out,$<,$@) $(builddir)/units/cron-update.service: $(srcdir)/units/cron-update.service.in $(call in2out,$<,$@) $(builddir)/units/cron-failure@.service: $(srcdir)/units/cron-failure@.service.in $(call in2out,$<,$@) $(builddir)/units/cron-%.service: $(srcdir)/units/cron-schedule.service.in $(call in2out,$<,$@,$*) ifeq ($(use_loglevelmax),no) sed -i -e '/^LogLevelMax=/d' $@ endif $(builddir)/units/cron-boot.service: $(srcdir)/units/cron-boot.service.in $(call in2out,$<,$@,boot) $(builddir)/units/cron-%.timer: $(srcdir)/units/cron-schedule.timer.in $(call in2out,$<,$@,$*) ifneq ($(enable_persistent),yes) sed -i -e '/^Persistent=/d' $@ endif $(builddir)/units/cron-boot.timer: $(srcdir)/units/cron-boot.timer.in $(call in2out,$<,$@,boot) $(builddir)/units/cron-%.target: $(srcdir)/units/cron-schedule.target.in $(call in2out,$<,$@,$*) $(builddir)/units/cron.target: $(srcdir)/units/cron.target.in $(call in2out,$<,$@,,$(requires)) $(builddir)/man/%: $(srcdir)/man/%.in $(call in2out,$<,$@) $(builddir)/man/systemd.cron.7: $(srcdir)/man/systemd.cron.7.in $(call in2out,$<,$@) $(foreach schedule_not,$(schedules_not),\ sed -i $@ -e '/cron-$(schedule_not).timer/d' \ -e '/cron\.$(schedule_not)/,+3d' \ -e '/journalctl -u cron-$(schedule_not)/,+1d' ${\n}) $(builddir)/bin/systemd-crontab-generator: $(srcdir)/bin/systemd-crontab-generator.py $(call in2out,$<,$@) # BUG: this should only run if schedule_not in hourly/daily/weekly/monthly $(foreach schedule_not,$(schedules_not),\ sed -i $@ -e '/\/etc\/cron.$(schedule_not)/d' ${\n}) chmod +x $@ $(builddir)/bin/%: $(srcdir)/bin/%.py $(call in2out,$<,$@) chmod +x $@ $(builddir)/bin/crontab_setgid: $(srcdir)/bin/crontab_setgid.c ifneq ($(enable_setgid),no) $(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) $< -DCRONTAB_DIR='"$(statedir)"' -o $@ endif $(outputs): | $(builddir) $(outdir): mkdir -p $@ $(builddir): mkdir -p $@ mkdir -p $@/bin mkdir -p $@/man mkdir -p $@/units $(distdir): mkdir -p $(distdir) $(tarball): distprep cd $(distdir)/..; tar -cJ --owner=root --group=root --file $(tarball) $(distname) .PHONY: all clean dist distprep install uninstall systemd-cron-1.15.18/README.md000066400000000000000000000123411416210635200155760ustar00rootroot00000000000000systemd-cron ================ [systemd][1] units to run [cron][2] scripts Description --------------- systemd units to provide cron daemon functionality by running scripts in cron directories. The crontabs are automaticaly translated using (/usr)/lib/systemd/system-generators/[systemd-crontab-generator][6]. Usage --------- Add executable scripts to the appropriate cron directory (e.g. `/etc/cron.daily`) and enable systemd-cron: # systemctl daemon-reload # systemctl enable cron.target # systemctl start cron.target The project also includes simple crontab command equivalent, which behaves like standard crontab command (and accepts the same main options). The scripts should now be automatically run by systemd. See man:systemd.cron(7) for more information. Dependencies ---------------- * systemd ≥ 197 * systemd ≥ 209, yearly timers * systemd ≥ 212, persistent timers * systemd ≥ 217, minutely, quarterly & semi-annually timers * systemd ≥ 229, real random delay support with `RandomizedDelaySec` option * [run-parts][3] * python 3 * /usr/sbin/sendmail (optional, evaluated at runtime) * gcc or clang (needed to build `crontab_setgid.c`; this helper is optional and evaluated at runtime) * support for /usr/lib/sysusers.d/*.conf (optional) Installation ---------------- There exists packages avaible for: * [Debian][7] * [Ubuntu][8] * [Arch][9] * [Gentoo][10] There is also a .spec file for Fedora in [contrib/][11]. You can also build it manually from source. Packaging -------------- ### Building $ ./configure $ make ### Staging $ make DESTDIR="$destdir" install ### Configuration The `configure` script takes command line arguments to configure various details of the build. The following options follow the standard GNU [installation directories][4]: * `--prefix=` * `--bindir=` * `--confdir=` * `--datadir=` * `--libdir=` * `--statedir=` * `--mandir=` * `--docdir=` Other options include: * `--unitdir=` Path to systemd unit files. Default: `/systemd/system`. * `--runparts=` The path installations should use for the `run-parts` executable. Default: `/bin/run-parts`. * `--enable-boot[=yes|no]` Include support for the boot timer. Default: `yes`. * `--enable-minutely[=yes|no]` Include support for the minutely timer. Requires systemd ≥ 217. Default: `no`. * `--enable-hourly[=yes|no]` Include support for the hourly timer. Default: `yes`. * `--enable-daily[=yes|no]` Include support for the daily timer. Default: `yes`. * `--enable-weekly[=yes|no]` Include support for the weekly timer. Default: `yes`. * `--enable-monthly[=yes|no]` Include support for the monthly timer. Default: `yes`. * `--enable-quarterly[=yes|no]` Include support for the quarterly timer. Requires systemd ≥ 217. Default: `no`. * `--enable-semi_annually[=yes|no]` Include support for the semi-annually timer. Requires systemd ≥ 217. Default: `no`. * `--enable-yearly[=yes|no]` Include support for the yearly timer. Requires systemd ≥ 209. Default: `no`. * `--enable-persistent[=yes|no]` Make timers [persistent][5]. Requires systemd ≥ 212. Default: `no`. * `--enable-randomized-delay=[yes|no]` Use [`RandomizedDelaySec`] option for `RANDOM_DELAY` support. Requires systemd ≥ 229. Default: `yes`. * `--enable-setgid[=yes|no]` Compile setgid C helper for crontab. Needs GCC or Clang. Default: `no`. A typical configuration for the latest systemd would be: $ ./configure --prefix=/usr --confdir=/etc --enable-yearly --enable-persistent If you only want the generator (you'll have to provide your own `/etc/crontab` to drive /etc/cron.daily/ etc...): $ ./configure --enable-boot=no --enable-hourly=no --enable-daily=no --enable-weekly=no --enable-month=no --enable-persistent --prefix=/usr --confdir=/etc ### Caveat Your package should also run these extra commands before starting cron.target to ensure that @reboot scripts doesn't trigger right away: # touch /run/crond.reboot # touch /run/crond.bootdir See Also ------------ `systemd.cron(7)` or in source tree `man -l src/man/systemd.cron.7` License ----------- The project is licensed under MIT. Copyright ------------- © 2014, Dwayne Bent : original package with static units © 2014, Konstantin Stepanov (me@kstep.me) : author of crontab generator © 2014, Daniel Schaal : review of crontab generator © 2014, Alexandre Detiste (alexandre@detiste.be) : manpage for crontab generator [1]: http://www.freedesktop.org/wiki/Software/systemd/ "systemd" [2]: http://en.wikipedia.org/wiki/Cron "cron" [3]: http://packages.qa.debian.org/d/debianutils.html "debianutils" [4]: https://www.gnu.org/prep/standards/html_node/Directory-Variables.html "Directory Variables" [5]: http://www.freedesktop.org/software/systemd/man/systemd.timer.html#Persistent= "systemd.timer" [6]: https://github.com/kstep/systemd-crontab-generator "crontab generator" [7]: http://packages.debian.org/systemd-cron [8]: http://packages.ubuntu.com/search?suite=all&searchon=names&keywords=systemd-cron [9]: https://aur.archlinux.org/packages/systemd-cron [10]: https://packages.gentoo.org/package/sys-process/systemd-cron [11]: https://github.com/systemd-cron/systemd-cron/blob/master/contrib/systemd-cron.spec systemd-cron-1.15.18/VERSION000066400000000000000000000000071416210635200153630ustar00rootroot000000000000001.5.18 systemd-cron-1.15.18/configure000077500000000000000000000132271416210635200162320ustar00rootroot00000000000000#!/bin/sh prefix='/usr/local' bindir='$(prefix)/bin' confdir='$(prefix)/etc' datadir='$(prefix)/share' libdir='$(prefix)/lib' statedir='/var/spool/cron' mandir='$(datadir)/man' docdir='$(datadir)/doc/$(package)' unitdir='$(libdir)/systemd/system' generatordir='$(libdir)/systemd/system-generators' runparts='/usr/bin/run-parts' enable_runparts=yes enable_setgid=no # systemd ≥ 197 enable_boot=yes enable_hourly=yes enable_daily=yes enable_weekly=yes enable_monthly=yes # systemd ≥ 209 enable_yearly=no # systemd ≥ 212 enable_persistent=no # systemd ≥ 217 enable_minutely=no enable_quarterly=no enable_semi_annually=no # systemd ≥ 229 enable_randomized_delay=yes # systemd ≥ 236 use_loglevelmax=no ARGS=$(getopt -n "$(basename "${0}")" -o '' -l ' prefix:, bindir:, confdir:, datadir:, libdir:, statedir:, mandir:, docdir:, unitdir:, generatordir:, runparts:, enable-boot::, enable-minutely::, enable-hourly::, enable-daily::, enable-weekly::, enable-monthly::, enable-quarterly::, enable-semi_annually::, enable-yearly::, enable-persistent::, enable-randomized-delay::, enable-setgid::, enable-runparts::, use-loglevelmax::, ' -- "${@}") if [ $? -ne 0 ]; then exit 1 fi set_enable_flag() { if [ -z ${2} ]; then eval "enable_${1}=yes" elif [ ${2} = 'yes' ] || [ ${2} = 'no' ]; then eval "enable_${1}=${2}" else echo "ERROR: Unknown value for enable-${1}: '${2}'. Expected 'yes' or 'no'." fi } eval set -- "${ARGS}" while true; do case "${1}" in '--prefix') prefix="${2}" shift 2;; '--bindir') bindir="${2}" shift 2;; '--confdir') confdir="${2}" shift 2;; '--datadir') datadir="${2}" shift 2;; '--libdir') libdir="${2}" shift 2;; '--statedir') statedir="${2}" shift 2;; '--mandir') mandir="${2}" shift 2;; '--docdir') docdir="${2}" shift 2;; '--unitdir') unitdir="${2}" shift 2;; '--generatordir') generatordir="${2}" shift 2;; '--runparts') runparts="${2}" shift 2;; '--enable-boot') set_enable_flag boot ${2} shift 2;; '--enable-minutely') set_enable_flag minutely ${2} shift 2;; '--enable-hourly') set_enable_flag hourly ${2} shift 2;; '--enable-daily') set_enable_flag daily ${2} shift 2;; '--enable-weekly') set_enable_flag weekly ${2} shift 2;; '--enable-monthly') set_enable_flag monthly ${2} shift 2;; '--enable-quarterly') set_enable_flag quarterly ${2} shift 2;; '--enable-semi_annually') set_enable_flag semi_annually ${2} shift 2;; '--enable-yearly') set_enable_flag yearly ${2} shift 2;; '--enable-persistent') set_enable_flag persistent ${2} shift 2;; '--enable-randomized-delay') set_enable_flag randomized_delay ${2} shift 2;; '--enable-setgid') set_enable_flag setgid ${2} shift 2;; '--enable-runparts') set_enable_flag runparts ${2} shift 2;; '--use-loglevelmax') case "${2}" in 'alert'|'crit'|'err'|'warning'|'notice'|'info'|'debug') use_loglevelmax="${2}";; *) echo "ERROR: Unknown value for use-loglevelmax: '${2}'. Expected 'alert', 'crit', 'err', 'warning', 'notice', 'info', or 'debug'.";; esac shift 2;; '--') shift break;; esac done if [ ${enable_boot} = 'yes' ]; then schedules="${schedules} boot" else schedules_not="${schedules_not} boot" fi if [ ${enable_minutely} = 'yes' ]; then schedules="${schedules} minutely" else schedules_not="${schedules_not} minutely" fi if [ ${enable_hourly} = 'yes' ]; then schedules="${schedules} hourly" else schedules_not="${schedules_not} hourly" fi if [ ${enable_daily} = 'yes' ]; then schedules="${schedules} daily" else schedules_not="${schedules_not} daily" fi if [ ${enable_weekly} = 'yes' ]; then schedules="${schedules} weekly" else schedules_not="${schedules_not} weekly" fi if [ ${enable_monthly} = 'yes' ]; then schedules="${schedules} monthly" else schedules_not="${schedules_not} monthly" fi if [ ${enable_quarterly} = 'yes' ]; then schedules="${schedules} quarterly" else schedules_not="${schedules_not} quarterly" fi if [ ${enable_semi_annually} = 'yes' ]; then schedules="${schedules} semi-annually" else schedules_not="${schedules_not} semi-annually" fi if [ ${enable_yearly} = 'yes' ]; then schedules="${schedules} yearly" else schedules_not="${schedules_not} yearly" fi echo '# Generated by ./configure' > Makefile sed " s|@schedules@|${schedules}|g s|@schedules_not@|${schedules_not}|g s|@enable_runparts@|${enable_runparts}|g s|@enable_persistent@|${enable_persistent}|g s|@enable_randomized_delay@|${enable_randomized_delay}|g s|@enable_setgid@|${enable_setgid}|g s|@prefix@|${prefix}|g s|@bindir@|${bindir}|g s|@confdir@|${confdir}|g s|@datadir@|${datadir}|g s|@libdir@|${libdir}|g s|@statedir@|${statedir}|g s|@mandir@|${mandir}|g s|@docdir@|${docdir}|g s|@unitdir@|${unitdir}|g s|@generatordir@|${generatordir}|g s|@runparts@|${runparts}|g s|@use_loglevelmax@|${use_loglevelmax}|g " Makefile.in >> Makefile systemd-cron-1.15.18/contrib/000077500000000000000000000000001416210635200157565ustar00rootroot00000000000000systemd-cron-1.15.18/contrib/0wait000077500000000000000000000022231416210635200167270ustar00rootroot00000000000000#!/bin/bash # wait for local apt-cacher-ng proxy to come online # before starting unattended-upgrades # (that may well never happen before shutdown, # but can happen later on the same day) # # /etc/cron.daily/0wait rollback() { echo "0wait aborted" touch -t 201401010000 /var/lib/systemd/timers/stamp-cron-daily.timer sleep 1 killall run-parts exit 0 } trap "rollback" TERM INT QUIT HUP while true do ping -qc 1 antec > /dev/null && exit 0 sleep 300 done # there should be a more systemd-ish way to do this # #systemd # |-atd -f # |-dbus-daemon --system --address=systemd: --nofork --nopidfile --systemd-activation # |-fancontrol /usr/sbin/fancontrol # | `-sleep 60 # |-kdm -nodaemon # | |-Xorg :0 vt7 -br -nolisten tcp -auth /var/run/xauth/A:0-86s7nb # | `-kdm # | `-kdm_greet # | `-{QProcessManager} # |-nullmailer-send # |-run-parts --report /etc/cron.daily # | `-0wait /etc/cron.daily/0wait # | `-sleep 300 # |-sshd # | `-sshd # | `-su - # | `-bash # | `-pstree -a # |-systemd --user # | `-(sd-pam) # |-systemd-journal # |-systemd-logind # `-systemd-udevd systemd-cron-1.15.18/contrib/changelog.py000077500000000000000000000025051416210635200202640ustar00rootroot00000000000000#!/usr/bin/python3 import re import time # lynx -dump https://github.com/systemd-cron/systemd-cron/releases with open('releases', 'r', encoding='utf8') as f: while f.readline(): # get release while True: line = f.readline() # should use a regexp if line.startswith('[') and ']v' in line: break version = line.split(']')[1].strip() # get date f.readline() block = f.readline().rstrip() + f.readline() date = block[block.find('released this')+13:-1][1:13].strip() try: date = time.strftime('%Y-%m-%d',time.strptime(date,"%b %d, %Y")) except ValueError: pass print("%s : %s\n" % (version, date)) # get details first = True while True: line = f.readline().rstrip() line = re.sub('\[\d*\]', '', line) if 'Source code (zip)' in line: break if line.strip() == 'systemd-cron': break if line or not first: if line and not line.startswith(' '): print (' ', end='') print(line) first = False last = line if last: print() if version == 'v0.1.0': break systemd-cron-1.15.18/contrib/pre-commit000077500000000000000000000005711416210635200177630ustar00rootroot00000000000000#!/bin/sh # # this hook will check for TABs in python sources before commit # # installation steps: # 1) git config --local --add core.whitespace tab-in-indent # 2) put his file in .git/hooks , chmod +x # Redirect output to stderr. exec 1>&2 # If there are whitespace errors, print the offending file names and fail. exec git diff-index --check --cached HEAD -- src/bin/*.py systemd-cron-1.15.18/contrib/systemd-cron.spec000066400000000000000000000065651416210635200212750ustar00rootroot00000000000000# https://fedoraproject.org/wiki/Packaging:ScriptletSnippets#Systemd # https://github.com/systemd/systemd/blob/42911a567dc22c3115fb3ee3c56a7dcfb034f102/src/core/macros.systemd.in # "If your package includes one or more systemd units that need # to be enabled by default on package installation, # they must be covered by the Fedora preset policy." Name: systemd-cron Version: 1.5.3 Release: 2 License: MIT Summary: systemd units to provide cron daemon & anacron functionality Url: https://github.com/systemd-cron/systemd-cron/ Group: System Environment/Base Source: https://github.com/systemd-cron/systemd-cron/archive/v%{version}.tar.gz Provides: cronie Provides: cronie-anacron Conflicts: cronie Conflicts: cronie-anacron BuildArch: noarch BuildRoot: %{_tmppath}/%{name}-%{version}-build Requires: crontabs Requires: systemd %description Provides systemd units to run cron jobs in /etc/cron.hourly cron.daily cron.weekly and cron.monthly directories, without having cron or anacron installed. It also provides a generator that dynamically translate /etc/crontab, /etc/cron.d/* and user cronjobs in systemd units. %pre touch /run/crond.reboot %preun %systemd_preun cron.target %post # XXX this macro doesn't seems to do anything %systemd_post cron.target if [ $1 -eq 1 ] ; then systemctl daemon-reload systemctl enable cron.target systemctl start cron.target fi %postun %systemd_postun_with_restart cron.target %prep %setup -q %build ./configure --enable-persistent=yes --prefix=/usr --confdir=/etc --enable-boot=no make %install make DESTDIR=$RPM_BUILD_ROOT install sed -i '/Persistent=true/d' $RPM_BUILD_ROOT/usr/lib/systemd/system/cron-hourly.timer mkdir -p $RPM_BUILD_ROOT/var/spool/cron mkdir -p $RPM_BUILD_ROOT/etc/cron.d/ mkdir -p $RPM_BUILD_ROOT/etc/cron.weekly/ cat << EOF > $RPM_BUILD_ROOT/etc/cron.weekly/systemd-cron #!/bin/sh test -x /usr/bin/python3 || exit 0 test -x /lib/systemd-cron/remove_stale_stamps || exit 0 exec /lib/systemd-cron/remove_stale_stamps EOF mkdir -p $RPM_BUILD_ROOT/usr/lib/systemd/system-preset/ echo 'enable cron.target' > $RPM_BUILD_ROOT/usr/lib/systemd/system-preset/50-systemd-cron.preset %files %license LICENSE %doc README.md CHANGELOG %dir /etc/cron.d/ /etc/cron.weekly/ /usr/bin/crontab /usr/lib/systemd-cron/mail_on_failure /usr/lib/systemd-cron/boot_delay /usr/lib/systemd-cron/remove_stale_stamps /usr/lib/systemd/system-preset/50-systemd-cron.preset /usr/lib/systemd/system/cron.target /usr/lib/systemd/system/cron-weekly.service /usr/lib/systemd/system/cron-update.path /usr/lib/systemd/system/cron-monthly.timer /usr/lib/systemd/system/cron-hourly.target /usr/lib/systemd/system/cron-weekly.timer /usr/lib/systemd/system/cron-monthly.service /usr/lib/systemd/system/cron-weekly.target /usr/lib/systemd/system/cron-failure@.service /usr/lib/systemd/system/cron-daily.timer /usr/lib/systemd/system/cron-daily.service /usr/lib/systemd/system/cron-daily.target /usr/lib/systemd/system/cron-hourly.service /usr/lib/systemd/system/cron-update.service /usr/lib/systemd/system/cron-hourly.timer /usr/lib/systemd/system/cron-monthly.target /usr/lib/systemd/system-generators/systemd-crontab-generator %{_mandir}/man1/crontab.* %{_mandir}/man5/crontab.* %{_mandir}/man5/anacrontab.* %{_mandir}/man7/systemd.cron.* %{_mandir}/man8/systemd-crontab-generator.* %dir /var/spool/cron systemd-cron-1.15.18/contrib/zz_desktop_notification000077500000000000000000000044111416210635200226460ustar00rootroot00000000000000#!/bin/bash # # this is a sample script that show a desktop notification # when your cron.daily job is done # # a better GUI to systemd timers (like the KDE Weather/Calendar applet) # would be better # # you should put this in /etc/cron.daily/ set -e [ -e /var/log/unattended-upgrades/unattended-upgrades.log ] || exit 0 line=$(cat /var/log/unattended-upgrades/unattended-upgrades.log | grep "will be upgraded" | tail -n 1) if [ -z "$line" -a -e /var/log/unattended-upgrades/unattended-upgrades.log.1.gz ] then line=$(zcat /var/log/unattended-upgrades/unattended-upgrades.log.1.gz | grep "will be upgraded" | tail -n 1) fi updated=$(echo $line | grep ^$(date +%Y-%m-%d) | cut -d: -f4) # reboot-required isn't broad enough to my taste (mostly only kernel); # these are the usual suspects that might make a system # unbootable or make X doens't come up for a in $updated; do if [[ $a == *grub* ]]; then grub="$grub $a"; fi if [[ $a == *systemd* ]]; then systemd="$systemd $a"; fi if [[ $a == *kdm* ]]; then kdm="$radeon $a"; fi if [[ $a == *radeon* ]]; then radeon="$radeon $a"; fi if [[ $a == *mesa* ]]; then mesa="$mesa $a"; fi done; if [ -e /var/run/reboot-required -o -n "$grub" -o -n "$systemd" -o -n "$kdm" -o -n "$radeon" -o -n "$mesa" ] then # check if user is active if [ -n "$(users)" ] then echo grub:$grub echo systemd:$systemd echo kdm:$kdm echo radeon:$radeon echo mesa:$mesa echo "user(s) active:" who users=$(loginctl list-sessions | grep seat0 | awk '{print $3}') if [ -n "$users" ] then for user in $users do export DISPLAY=:0 su $user -c "notify-send 'REBOOT NEEDED' -i dialog-warning \"$updated\" " done else echo "REBOOT NEEDED: $updated" | wall fi else # reboot after 1 minute shutdown --reboot fi else # check if a graphical session exists users=$(loginctl list-sessions | grep seat0 | awk '{print $3}') if [ -n "$users" ] then export DISPLAY=:0 if [ -n "$updated" ] then for user in $users do echo $user su $user -c "notify-send 'daily upgrade completed' -i dialog-information \"$updated\" " done else for user in $users do su $user -c "notify-send 'cron-daily finished' -i dialog-information 'nothing to update today'" done fi fi fi systemd-cron-1.15.18/src/000077500000000000000000000000001416210635200151055ustar00rootroot00000000000000systemd-cron-1.15.18/src/bin/000077500000000000000000000000001416210635200156555ustar00rootroot00000000000000systemd-cron-1.15.18/src/bin/boot_delay.py000077500000000000000000000005031416210635200203510ustar00rootroot00000000000000#!/usr/bin/python3 import sys import time try: delay = int(sys.argv[1]) * 60 except: sys.exit("Usage: %s " % sys.argv[0]) f = open('/proc/uptime', 'r') uptime = int(float(f.readline().split()[0])) if delay > uptime: try: time.sleep(delay - uptime) except KeyboardInterrupt: pass systemd-cron-1.15.18/src/bin/crontab.py000077500000000000000000000305371416210635200176720ustar00rootroot00000000000000#!/usr/bin/python3 import tempfile import sys import os import stat import argparse import getpass import pwd import subprocess import glob from subprocess import Popen, PIPE import importlib.machinery import errno for pgm in ('/usr/bin/editor', '/usr/bin/vim', '/usr/bin/nano', '/usr/bin/mcedit'): if os.path.isfile(pgm) and os.access(pgm, os.X_OK): editor = pgm break else: editor = 'false' EDITOR = (os.environ.get('EDITOR') or os.environ.get('VISUAL') or editor) CRONTAB_DIR = '@statedir@' SETGID_HELPER = '@libdir@/@package@/crontab_setgid' HAS_SETGID = os.geteuid() != 0 \ and os.path.isfile(SETGID_HELPER) \ and os.stat(SETGID_HELPER).st_uid == 0 \ and os.stat(SETGID_HELPER).st_gid != 0 \ and os.stat(SETGID_HELPER).st_mode & stat.S_ISGID \ and os.stat(SETGID_HELPER).st_mode & stat.S_IXGRP REBOOT_FILE = '/run/crond.reboot' args_parser = argparse.ArgumentParser(description='maintain crontab files for individual users') group = args_parser.add_mutually_exclusive_group() 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='?') group.add_argument('-l', '--list', dest='action', action='store_const', const='list', help='''The current crontab will be displayed on standard output.''') group.add_argument('-r', '--remove', dest='action', action='store_const', const='remove', help='''The current crontab will be removed.''') group.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.''') group.add_argument('-s', '--show', dest='action', action='store_const', const='show', help='''Show all user who have a crontab.''') 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 = input(message).lower() if answer not in 'yn': print('Please reply "y" or "n"') continue return answer == 'y' def check(cron_file): good = True for job in parser.parse_crontab(cron_file, withuser=False): if 'c' not in job: good = False sys.stderr.write('%s: truncated line in %s: %s\n' % (SELF, cron_file, job['l'])) elif 'p' in job: if job['p'] not in ['reboot', 'minutely', 'hourly', 'daily', 'midnight', 'weekly', 'monthly', 'quarterly', 'semi-annually', 'semiannually', 'bi-annually', 'biannually', 'annually', 'yearly']: good = False sys.stderr.write("%s: unknown schedule in %s: %s\n" % (SELF, cron_file, job['l'])) elif 0 in job['M'] or 0 in job['d']: good = False sys.stderr.write("%s: month and day can't be 0 in %s: %s\n" % (SELF, cron_file, job['l'])) else: if not len(job['M']) or not len(job['d']) or not len(job['h']) or not len(job['m']): good = False return good def try_chmod(cron_file, user): if CRON_GROUP: try: os.chown(cron_file, pwd.getpwnam(user).pw_uid, CRON_GROUP) os.chmod(cron_file, stat.S_IRUSR | stat.S_IWUSR) except (PermissionError, KeyError): pass def list(cron_file, args): try: with open(cron_file, 'r') as f: sys.stdout.write(f.read()) check(cron_file) try_chmod(cron_file, args.user) except (IOError, PermissionError) as e: if e.errno == errno.ENOENT: sys.exit('no crontab for %s' % args.user) elif args.user != getpass.getuser(): sys.exit("you can not display %s's crontab" % args.user) elif HAS_SETGID: returncode = subprocess.call([SETGID_HELPER,'r']) if returncode == errno.ENOENT: sys.exit('no crontab for %s' % args.user) elif returncode: # helper will send error to stderr sys.exit('failed to read %s' % cron_file) else: raise def remove(cron_file, args): if not args.ask or confirm('Are you sure you want to delete %s (y/n)? ' % cron_file): try: os.unlink(cron_file) except OSError as e: if e.errno == errno.ENOENT: sys.stderr.write("no crontab for %s\n" % args.user) elif args.user != getpass.getuser(): sys.exit("you can not delete %s's crontab" % args.user) elif e.errno == errno.EROFS: if os.path.isfile(cron_file): sys.exit("%s is on a read-only filesystem" % cron_file) else: sys.stderr.write("no crontab for %s\n" % args.user) elif HAS_SETGID: subprocess.check_output([SETGID_HELPER,'d'], universal_newlines=True) elif e.errno == errno.EACCES: open(cron_file, 'w').close() sys.stderr.write("couldn't remove %s , wiped it instead\n" % cron_file) else: raise def edit(cron_file, args): tmp = tempfile.NamedTemporaryFile(mode='w+', encoding='UTF-8', delete=False, prefix='crontab_') try: with open(cron_file, 'r') as inp: tmp.file.write(inp.read()) except IOError as e: if e.errno == errno.ENOENT: tmp.file.write('# min hour dom month dow command') elif args.user != getpass.getuser(): sys.stderr.write("you can not edit %s's crontab\n" % args.user) tmp.close() os.unlink(tmp.name) exit(1) elif HAS_SETGID: try: tmp.file.write(subprocess.check_output([SETGID_HELPER,'r'], universal_newlines=True)) except subprocess.CalledProcessError as f: if f.returncode == errno.ENOENT: tmp.file.write('# min hour dom month dow command') else: # helper will send error to stderr sys.stderr.write('failed to read %s\n' % cron_file) tmp.close() os.unlink(tmp.name) exit(f.returncode) else: tmp.close() os.unlink(tmp.name) raise tmp.close() if subprocess.call([EDITOR, tmp.name]) != 0: sys.exit('edit aborted, your edit is kept here:%s' % tmp.name) if not check(tmp.name): sys.exit("not replacing crontab, your edit is kept here:%s" % tmp.name) with open(tmp.name,"r") as edited: newtab = edited.read() try: new = tempfile.NamedTemporaryFile(mode='w+', encoding='UTF-8', dir=CRONTAB_DIR, prefix=args.user + '.', delete=False) new.write(newtab) new.close() os.rename(new.name, cron_file) os.unlink(tmp.name) try_chmod(cron_file, args.user) except (IOError, PermissionError) as e: if e.errno == errno.ENOSPC: os.unlink(new.name) sys.exit("no space left on %s, your edit is kept here:%s" % (CRONTAB_DIR, tmp.name)) elif args.user != getpass.getuser(): sys.exit("you can not edit %s's crontab, your edit is kept here:%s" % (args.user, tmp.name)) elif e.errno == errno.EROFS: sys.exit("%s is on a read-only filesystem, your edit is kept here:%s" % (CRONTAB_DIR, tmp.name)) elif HAS_SETGID: p = Popen([SETGID_HELPER,'w'], stdin=PIPE) p.communicate(bytes(newtab, 'UTF-8')) if p.returncode: sys.exit("your edit is kept here:%s" % tmp.name) else: os.unlink(tmp.name) else: sys.stderr.write("unexpected error, your edit is kept here:%s\n" % tmp.name) raise def show(cron_file, args): if os.geteuid() != 0: sys.exit("must be privileged to use -s") for cron_file in glob.glob(os.path.join(CRONTAB_DIR, '*')): user = os.path.basename(cron_file) try: pwd.getpwnam(user) print(user) except KeyError: sys.stderr.write("WARNING: crontab found with no matching user: %s\n" % user) def replace(cron_file, args): if args.file == '-': try: crontab = sys.stdin.read() except KeyboardInterrupt: sys.exit() tmp = tempfile.NamedTemporaryFile(mode='w+', encoding='UTF-8') tmp.write(crontab) tmp.file.flush() tmp.file.seek(0) infile = tmp.name else: infile = args.file try: with open(infile, 'r') as inp: crontab = inp.read() except IOError as e: if e.errno == errno.ENOENT: sys.exit("file %s doesn't exists" % infile) elif e.errno == errno.EACCES: sys.exit("you can't read file %s" % infile) else: raise if not check(infile): sys.exit("not replacing crontab\n") try: new = tempfile.NamedTemporaryFile(mode='w+', encoding='UTF-8', dir=CRONTAB_DIR, prefix=args.user + '.', delete=False) new.write(crontab) new.close() os.rename(new.name, cron_file) try_chmod(cron_file, args.user) except (IOError, PermissionError) as e: if args.user != getpass.getuser(): sys.exit("you can not replace %s's crontab" % args.user) elif e.errno == errno.ENOSPC: sys.stderr.write("no space left on %s\n" % CRONTAB_DIR) os.unlink(new.name) exit(1) elif e.errno == errno.EROFS: sys.exit("%s is on a read-only filesystem" % CRONTAB_DIR) elif HAS_SETGID: p = Popen([SETGID_HELPER,'w'], stdin=PIPE) p.communicate(bytes(crontab, 'UTF-8')) exit(p.returncode) else: raise if __name__ == '__main__': SELF = os.path.basename(sys.argv[0]) # try to fixup CRONTAB_DIR if it has not been handled in package script try: if not os.path.exists(CRONTAB_DIR): os.makedirs(CRONTAB_DIR) except: sys.exit("%s doesn't exists!" % CRONTAB_DIR) try: CRON_GROUP = os.stat(SETGID_HELPER).st_gid except: CRON_GROUP = None if CRON_GROUP: try: os.chown(CRONTAB_DIR, 0, CRON_GROUP) os.chmod(CRONTAB_DIR, stat.S_ISVTX | stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR | stat.S_IWGRP | stat.S_IXGRP) except: pass args = args_parser.parse_args() # deluser in adduser package expect this behaviour if args.file != '-' and args.action in ['list', 'edit', 'remove']: args.user = args.file cron_file = os.path.join(CRONTAB_DIR, args.user) try: pwd.getpwnam(args.user) except KeyError: sys.exit("user '%s' unknown" % args.user) try: open(REBOOT_FILE,'a').close() except: pass action = { 'list': list, 'edit': edit, 'remove': remove, 'show': show, }.get(args.action, replace) loader = importlib.machinery.SourceFileLoader('name', '@generatordir@/systemd-crontab-generator') parser = loader.load_module() action(cron_file, args) systemd-cron-1.15.18/src/bin/crontab_setgid.c000066400000000000000000000055621416210635200210200ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #define MAX_COMMAND 1000 #define MAX_LINES 1000 #ifndef DEFAULT_NOACCESS #define DEFAULT_NOACCESS 0 #endif void end(char * msg){ fprintf(stderr,"crontab_setgid: %s\n", msg); exit(1); } void rtrim(char *str){ int n=strlen(str); while((--n>0)&&(str[n]==' ' || str[n]=='\n')); str[n+1]='\0'; } int main(int argc, char *argv[]) { if (!getuid()) end("root doesn't need this helper"); if (argc != 2) end("bad argument"); struct passwd *pw; pw = getpwuid(getuid()); if (!pw) end("user doesn't exist"); char users[LOGIN_NAME_MAX]; char crontab[sizeof CRONTAB_DIR + 1 + LOGIN_NAME_MAX]; char temp[sizeof crontab + 7]; snprintf(crontab, sizeof crontab, "%s/%s", CRONTAB_DIR, pw->pw_name); FILE *file; char buffer[MAX_COMMAND]; switch(argv[1][0]) { case 'r': file = fopen(crontab, "r"); if (file == NULL) { if(errno == ENOENT) return ENOENT; perror("Cannot open input file"); return 1; }; while(fgets(buffer, sizeof(buffer), file)) { printf("%s", buffer); } fclose(file); break; case 'w': file = fopen("/etc/cron.allow", "r"); if (file != NULL) { int allowed=0; while(fgets(users, sizeof(users), file)) { rtrim(users); if (!strcmp(pw->pw_name, users)) { allowed=1; break; } } fclose(file); if (!allowed) end("you're not in /etc/cron.allow"); } else { file = fopen("/etc/cron.deny", "r"); if (file != NULL) { while(fgets(users, sizeof(users), file)) { rtrim(users); if (!strcmp(pw->pw_name, users)) { fclose(file); end("you are in /etc/cron.deny"); } } fclose(file); } else if (DEFAULT_NOACCESS) end("without /etc/cron.allow or /etc/cron.deny; only root can install crontabs"); } snprintf(temp, sizeof temp, "%s.XXXXXX", crontab); // this file is created $user:crontab / 0600 int fd = mkstemp(temp); file = fdopen(fd, "w"); if (file == NULL) { perror("Cannot open output file"); return 1; } int lines=0; while(fgets(buffer, sizeof(buffer), stdin)) { lines++; if (fprintf(file, "%s", buffer) < 0) { perror("Cannot write to file"); fclose(file); unlink(temp); return 1; }; if (lines > MAX_LINES) { fclose(file); unlink(temp); end("maximum lines reached"); } } if (fclose(file)) {perror("fclose"); return 1;} if (rename(temp,crontab)) {perror("rename"); return 1;} break; case 'd': if (unlink(crontab) == -1) { if(errno == ENOENT) { fprintf(stderr, "no crontab for %s\n", pw->pw_name); return 0; } perror("Cannot delete file"); return 1; }; break; default: end("unknown action"); return 1; } return 0; } systemd-cron-1.15.18/src/bin/mail_on_failure.py000077500000000000000000000051071416210635200213620ustar00rootroot00000000000000#!/usr/bin/python3 import argparse import email.mime.text import email.utils import logging import os import subprocess __DOC__ = """ send a panic email about a failed cron job """ parser = argparse.ArgumentParser(description=__DOC__) parser.add_argument('unit', help='the failing unit, e.g. cron-foo-1.service') parser.add_argument('--verbose', action='store_true') args = parser.parse_args() if args.verbose: logging.getLogger().setLevel(logging.INFO) for pgm in ('/usr/sbin/sendmail', '/usr/lib/sendmail'): if os.path.exists(pgm): break else: print("<3>can't send error mail for %s without a MTA" % args.unit) exit(0) user = subprocess.check_output( ['systemctl', 'show', args.unit, '--property=User'], universal_newlines=True) user = user.rstrip('\n') user = user.split('=')[1] if user else 'root' mailto = user mailfrom = 'root' job_env = subprocess.check_output( ['systemctl', 'show', args.unit, '--property=Environment'], universal_newlines=True) job_env = job_env.rstrip('\n') if job_env: for var in job_env.split('=', 1)[1].split(' '): try: key , value = var.split('=', 1) if key == 'MAILTO': mailto = value if key == 'MAILFROM': mailfrom = value except ValueError: pass if not mailto: logging.info('This cron job (%s) opted out of email, therefore quitting', args.unit) exit(0) hostname = os.uname()[1] for locale in (None, 'C.UTF-8', 'C'): if locale: os.environ['LC_ALL'] = locale try: output = subprocess.check_output(['systemctl', 'status', args.unit], universal_newlines=True) logging.warning('systemctl status should have failed') break except UnicodeDecodeError: logging.info('current locale (%s) is broken, try again', locale) except subprocess.CalledProcessError as e: if e.returncode != 3: raise else: output = e.output break message_object = email.mime.text.MIMEText(_text=output) message_object['Date'] = email.utils.formatdate() message_object['From'] = mailfrom + ' (systemd-cron)' message_object['To'] = mailto message_object['Subject'] = "[" + hostname + "] job " + args.unit + " failed" # https://datatracker.ietf.org/doc/html/rfc3834#section-5 message_object['Auto-Submitted'] = 'auto-generated' subprocess.run( ['sendmail', '-i', '-B8BITMIME', mailto], universal_newlines=False, input=message_object.as_bytes()) systemd-cron-1.15.18/src/bin/remove_stale_stamps.py000077500000000000000000000017621416210635200223140ustar00rootroot00000000000000#!/usr/bin/python3 import glob import time import os actual_stamps=glob.glob('/var/lib/systemd/timers/stamp-cron-*.timer') timers=glob.glob('/run/systemd/generator/cron-*.timer') needed_stamps=['/var/lib/systemd/timers/stamp-cron-daily.timer', '/var/lib/systemd/timers/stamp-cron-weekly.timer', '/var/lib/systemd/timers/stamp-cron-monthly.timer', '/var/lib/systemd/timers/stamp-cron-quarterly.timer', '/var/lib/systemd/timers/stamp-cron-semi-annually.timer', '/var/lib/systemd/timers/stamp-cron-yearly.timer'] for timer in timers: needed_stamps.append(timer.replace('/run/systemd/generator/cron-', '/var/lib/systemd/timers/stamp-cron-')) stale_stamps=set(actual_stamps) - set(needed_stamps) now = time.time() for stale_stamp in stale_stamps: if os.stat(stale_stamp).st_mtime < now - 10 * 86400: try: os.remove(stale_stamp) except IOError: pass systemd-cron-1.15.18/src/bin/systemd-crontab-generator.py000077500000000000000000000601651416210635200233440ustar00rootroot00000000000000#!/usr/bin/python3 import sys import os import pwd import re import string from functools import reduce import hashlib import errno envvar_re = re.compile(r'^([A-Za-z_0-9]+)\s*=\s*(.*)$') MINUTES_SET = list(range(0, 60)) HOURS_SET = list(range(0, 24)) DAYS_SET = list(range(1, 32)) DOWS_SET = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'] MONTHS_SET = list(range(1, 13)) TIME_UNITS_SET = ['daily', 'weekly', 'monthly', 'quarterly', 'semi-annually', 'yearly'] KSH_SHELLS = ['/bin/sh', '/bin/dash', '/bin/ksh', '/bin/bash', '/usr/bin/zsh'] REBOOT_FILE = '/run/crond.reboot' RANDOMIZED_DELAY = @randomized_delay@ RUN_PARTS_FLAG = '/run/systemd/use_run_parts' USE_LOGLEVELMAX = '@use_loglevelmax@' SELF = os.path.basename(sys.argv[0]) for pgm in ('/usr/sbin/sendmail', '/usr/lib/sendmail'): if os.path.exists(pgm): HAS_SENDMAIL = True break else: HAS_SENDMAIL = False class Persistent(object): yes, no, auto = range(3) @classmethod def parse(cls, value): value = value.strip().lower() if value in ['yes', 'true', '1']: return cls.yes elif value in ['auto', '']: return cls.auto else: return cls.no def files(dirname): try: return list(filter(os.path.isfile, [os.path.join(dirname, f) for f in os.listdir(dirname)])) except OSError: return [] def expand_home_path(path, user): try: home = pwd.getpwnam(user).pw_dir except KeyError: return path parts = path.split(':') for i, part in enumerate(parts): if part.startswith('~/'): parts[i] = home + part[1:] return ':'.join(parts) def environment_string(env): line = [] for k, v in env.items(): if ' ' in v: line.append('"%s=%s"' % (k, v)) else: line.append('%s=%s' % (k, v)) return ' '.join(line) def parse_crontab(filename, withuser=True, monotonic=False): basename = os.path.basename(filename) environment = { } random_delay = 1 start_hours_range = 0 boot_delay = 0 persistent = Persistent.yes if monotonic else Persistent.auto batch = False run_parts = @use_runparts@ with open(filename, 'r', encoding='utf8') as f: for line in f.readlines(): line = line.strip() if not line or line.startswith('#'): continue envvar = envvar_re.match(line) if envvar: value = envvar.group(2) value = value.strip("'").strip('"') if envvar.group(1) == 'RANDOM_DELAY': try: random_delay = int(value) except ValueError: log(4, 'invalid RANDOM_DELAY in %s: %s' % (filename, line)) elif envvar.group(1) == 'START_HOURS_RANGE': try: start_hours_range = int(value.split('-')[0]) except ValueError: log(4, 'invalid START_HOURS_RANGE in %s: %s' % (filename, line)) elif envvar.group(1) == 'DELAY': try: boot_delay = int(value) except ValueError: log(4, 'invalid DELAY in %s: %s' % (filename, line)) elif envvar.group(1) == 'PERSISTENT': persistent = Persistent.parse(value) elif not withuser and envvar.group(1) == 'PATH': environment['PATH'] = expand_home_path(value, basename) elif envvar.group(1) == 'BATCH': batch = (value.strip().lower() in ['yes','true','1']) elif envvar.group(1) == 'RUN_PARTS': run_parts = (value.strip().lower() in ['yes','true','1']) elif envvar.group(1) == 'MAILTO': environment[envvar.group(1)] = value if value and not HAS_SENDMAIL: log(4, 'a MTA is not installed, but MAILTO is set in %s' % filename) else: environment[envvar.group(1)] = value continue parts = line.split() line = ' '.join(parts) if monotonic: if len(parts) < 4: yield { 'l': line } continue period, delay, jobid = parts[0:3] command = ' '.join(parts[3:]) period = { '1': 'daily', '7': 'weekly', '30': 'monthly', '31': 'monthly', '@biannually': 'semi-annually', '@bi-annually': 'semi-annually', '@semiannually': 'semi-annually', '@anually': 'yearly', '@annually': 'yearly', }.get(period, None) or period.lstrip('@') try: boot_delay = int(delay) except ValueError: log(4, 'invalid DELAY in %s: %s' % (filename, line)) boot_delay = 0 if boot_delay < 0: boot_delay = 0 valid_chars = "-_%s%s" % (string.ascii_letters, string.digits) jobid = ''.join(c for c in jobid if c in valid_chars) yield { 'e': environment_string(environment), 's': environment.get('SHELL','/bin/sh'), 'a': random_delay, 'l': line, 'f': filename, 'p': period.lower(), 'b': boot_delay, 'h': start_hours_range, 'P': False if persistent == Persistent.no else True, 'j': jobid, 'u': 'root', 'c': command, 'Z': batch, } else: if line.startswith('@'): if len(parts) < 2 + int(withuser): yield { 'l': line } continue period = parts[0] period = { '@biannually': 'semi-annually', '@bi-annually': 'semi-annually', '@semiannually': 'semi-annually', '@anually': 'yearly', '@annually': 'yearly', }.get(period, None) or period.lstrip('@') user, command = (parts[1], ' '.join(parts[2:])) if withuser else (basename, ' '.join(parts[1:])) yield { 'e': environment_string(environment), 's': environment.get('SHELL','/bin/sh'), 'a': random_delay, 'l': line, 'f': filename, 'p': period.lower(), 'b': boot_delay, 'h': start_hours_range, 'P': False if persistent == Persistent.no else True, 'j': basename, 'u': user, 'c': command, 'Z': batch, 'J': run_parts, } else: if len(parts) < 6 + int(withuser): yield { 'l': line } 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:])) yield { 'e': environment_string(environment), 's': environment.get('SHELL','/bin/sh'), 'a': random_delay, 'l': line, 'f': filename, 'b': boot_delay, 'm': parse_time_unit(filename, line, minutes, MINUTES_SET), 'h': parse_time_unit(filename, line, hours, HOURS_SET), 'd': parse_time_unit(filename, line, days, DAYS_SET), 'w': parse_time_unit(filename, line, dows, DOWS_SET, dow_map), 'M': parse_time_unit(filename, line, months, MONTHS_SET, month_map), 'P': True if persistent == Persistent.yes else False, 'j': basename, 'u': user, 'c': command, 'Z': batch, 'J': run_parts, } def parse_time_unit(filename, line, value, values, mapping=int): if value == '*': return ['*'] try: base = min(values) # day of weeks if isinstance(base, str): base = 0 result = sorted(reduce(lambda a, i: a.union(set(i)), list(map(values.__getitem__, list(map(parse_period(mapping, base), value.split(','))))), set())) except ValueError: result = [] if not len(result): log(3, 'garbled time in %s [%s]: %s' % (filename, line, value)) return result 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, base=0): def parser(value): try: range, step = value.split('/') except ValueError: range = value step = 1 if range == '*': return slice(None, None, int(step)) try: start, end = range.split('-') except ValueError: start = end = range return slice(mapping(start) - 1 + int(not(bool(base))), mapping(end) + int(not(bool(base))), int(step)) return parser def generate_timer_unit(job, seq=None, unit_name=None): persistent = job['P'] command = job['c'] parts = command.split() testremoved = None standardoutput = None delay = job['b'] daemon_reload = os.path.isfile(REBOOT_FILE) try: home = pwd.getpwnam(job['u']).pw_dir except KeyError: home = None # perform smart substitutions for known shells if 's' not in job or job['s'] in KSH_SHELLS: if home and command.startswith('~/'): command = home + command[1:] if (len(parts) >= 3 and parts[-2] == '>' and parts[-1] == '/dev/null'): command = ' '.join(parts[0:-2]) parts = command.split() standardoutput='null'; if (len(parts) >= 2 and parts[-1] == '>/dev/null'): command = ' '.join(parts[0:-1]) parts = command.split() standardoutput='null'; if (len(parts) == 6 and parts[0] == '[' and parts[1] in ['-x','-f','-e'] and parts[2] == parts[5] and parts[3] == ']' and parts[4] == '&&' ): testremoved = parts[2] command = ' '.join(parts[5:]) parts = command.split() if (len(parts) == 5 and parts[0] == 'test' and parts[1] in ['-x','-f','-e'] and parts[2] == parts[4] and parts[3] == '&&' ): testremoved = parts[2] command = ' '.join(parts[4:]) parts = command.split() if testremoved and not os.path.isfile(testremoved): return if (len(parts) == 6 and parts[0] == '[' and parts[1] in ['-d','-e'] and parts[2] == '/run/systemd/system' and parts[3] == ']' and parts[4] == '||'): return if (len(parts) == 5 and parts[0] == 'test' and parts[1] in ['-d','-e'] and parts[2] == '/run/systemd/system' and parts[3] == '||'): return # TODO: translate 'command%line1%line2%line3 # in '/bin/echo -e line1\\nline2\\nline3 | command' # to be POSIX compliant if 'p' in job: hour = job['h'] if 'h' in job else 0 if job['p'] == 'reboot': if daemon_reload: return if delay == 0: delay = 1 schedule = None persistent = False elif job['p'] == 'minutely': schedule = job['p'] persistent = False elif job['p'] == 'hourly' and delay == 0: schedule = 'hourly' elif job['p'] == 'hourly': schedule = '*-*-* *:%s:0' % delay delay = 0 elif job['p'] == 'midnight' and delay == 0: schedule = 'daily' elif job['p'] == 'midnight': schedule = '*-*-* 0:%s:0' % delay elif job['p'] in TIME_UNITS_SET and hour == 0 and delay == 0: schedule = job['p'] elif job['p'] == 'daily': schedule = '*-*-* %s:%s:0' % (hour, delay) elif job['p'] == 'weekly': schedule = 'Mon *-*-* %s:%s:0' % (hour, delay) elif job['p'] == 'monthly': schedule = '*-*-1 %s:%s:0' % (hour, delay) elif job['p'] == 'quarterly': schedule = '*-1,4,7,10-1 %s:%s:0' % (hour, delay) elif job['p'] == 'semi-annually': schedule = '*-1,7-1 %s:%s:0' % (hour, delay) elif job['p'] == 'yearly': schedule = '*-1-1 %s:%s:0' % (hour, delay) else: try: if int(job['p']) > 31: # workaround for anacrontab schedule = '*-1/%s-1 %s:%s:0' % (int(round(job['p']/30)), hour, delay) else: schedule = '*-*-1/%s %s:%s:0' % (int(job['p']), hour, delay) except ValueError: log(3, 'unknown schedule in %s: %s' % (job['f'], job['l'])) schedule = job['p'] else: if job['w'] == ['*']: dows='' else: dows_sorted = [] for day in DOWS_SET: if day in job['w']: dows_sorted.append(day) dows = ','.join(dows_sorted) + ' ' if 0 in job['M']: job['M'].remove(0) if 0 in job['d']: job['d'].remove(0) if not len(job['M']) or not len(job['d']) or not len(job['h']) or not len(job['m']): return schedule = '%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']))) if not unit_name: if not persistent: unit_id = next(seq) else: unit_id = hashlib.md5() unit_id.update(bytes('\0'.join([schedule, command]), 'utf-8')) unit_id = unit_id.hexdigest() unit_name = "cron-%s-%s-%s" % (job['j'], job['u'], unit_id) if not (len(parts) == 1 and os.path.isfile(command)): with open('%s/%s.sh' % (TARGET_DIR, unit_name), 'w', encoding='utf8') as f: f.write(command) command=job['s'] + ' ' + TARGET_DIR + '/' + unit_name + '.sh' with open('%s/%s.timer' % (TARGET_DIR, unit_name), 'w' , encoding='utf8') as f: f.write('[Unit]\n') f.write('Description=[Timer] "%s"\n' % job['l'].replace('%', '%%')) f.write('Documentation=man:systemd-crontab-generator(8)\n') f.write('PartOf=cron.target\n') f.write('SourcePath=%s\n' % job['f']) if testremoved: f.write('ConditionFileIsExecutable=%s\n' % testremoved) f.write('\n[Timer]\n') f.write('Unit=%s.service\n' % unit_name) if schedule: f.write('OnCalendar=%s\n' % schedule) else: f.write('OnBootSec=%sm\n' % delay) if 'a' in job and job['a'] != 1: if RANDOMIZED_DELAY: f.write('RandomizedDelaySec=%sm\n' % job['a']) else: f.write('AccuracySec=%sm\n' % job['a']) if @persistent@ and persistent: f.write('Persistent=true\n') try: os.symlink('%s/%s.timer' % (TARGET_DIR, unit_name), '%s/%s.timer' % (TIMERS_DIR, unit_name)) except OSError as e: if e.errno != errno.EEXIST: raise with open('%s/%s.service' % (TARGET_DIR, unit_name), 'w', encoding='utf8') as f: f.write('[Unit]\n') f.write('Description=[Cron] "%s"\n' % job['l'].replace('%', '%%')) f.write('Documentation=man:systemd-crontab-generator(8)\n') f.write('SourcePath=%s\n' % job['f']) if re.search('MAILTO=(\s+|$)', job['e']): pass # mails explicitely disabled elif not HAS_SENDMAIL: pass # mails automaticaly disabled else: f.write('OnFailure=cron-failure@%i.service\n') if job['u'] != 'root' or job['f'] == '@statedir@/root': f.write('Requires=systemd-user-sessions.service\n') if home: f.write('RequiresMountsFor=%s\n' % home) f.write('\n[Service]\n') f.write('Type=oneshot\n') f.write('IgnoreSIGPIPE=false\n') f.write('KillMode=process\n') if USE_LOGLEVELMAX != 'no': f.write('LogLevelMax=%s\n' % USE_LOGLEVELMAX) if schedule and delay: f.write('ExecStartPre=-@libdir@/@package@/boot_delay %s\n' % delay) f.write('ExecStart=%s\n' % command) if job['e']: f.write('Environment=%s\n' % job['e']) if job['u'] != 'root': f.write('User=%s\n' % job['u']) if standardoutput: f.write('StandardOutput=%s\n' % standardoutput) if 'Z' in job and job['Z']: f.write('CPUSchedulingPolicy=idle\n') f.write('IOSchedulingClass=idle\n') return '%s.timer' % unit_name def log(level, message): if len(sys.argv) == 4: with open('/dev/kmsg', 'w', encoding='utf8') as kmsg: kmsg.write('<%s>%s[%s]: %s\n' % (level, SELF, os.getpid(), message)) else: sys.stderr.write('%s: %s\n' % (SELF, message)) seqs = {} def count(): n = 0 while True: yield n n += 1 def main(): try: os.makedirs(TIMERS_DIR) except OSError as e: if e.errno != errno.EEXIST: raise run_parts = @use_runparts@ if os.path.isfile('/etc/crontab'): for job in parse_crontab('/etc/crontab', withuser=True): if 'J' in job: run_parts = job['J'] if 'c' not in job: log(3, 'truncated line in /etc/crontab: %s' % job['l']) continue if '/etc/cron.hourly' in job['c']: continue if '/etc/cron.daily' in job['c']: continue if '/etc/cron.weekly' in job['c']: continue if '/etc/cron.monthly' in job['c']: continue generate_timer_unit(job, seq=seqs.setdefault(job['j']+job['u'], count())) CRONTAB_FILES = files('/etc/cron.d') for filename in CRONTAB_FILES: basename = os.path.basename(filename) masked = False for unit_file in ('@unitdir@/%s.timer' % basename, '/etc/systemd/system/%s.timer' % basename, '/run/systemd/system/%s.timer' % basename): if os.path.exists(unit_file): masked = True if os.path.realpath(unit_file) == '/dev/null': log(5, 'ignoring %s because it is masked' % filename) else: log(5, 'ignoring %s because native timer is present' % filename) break if masked: continue if basename.startswith('.'): continue if '.dpkg-' in basename: log(5, 'ignoring %s' % filename) continue if '~' in basename: log(5, 'ignoring %s' % filename) continue for job in parse_crontab(filename, withuser=True): if 'c' not in job: log(3, 'truncated line in %s: %s' % (filename, job['l'])) continue generate_timer_unit(job, seq=seqs.setdefault(job['j']+job['u'], count())) if run_parts: open(RUN_PARTS_FLAG, 'a').close() else: if os.path.exists(RUN_PARTS_FLAG): os.unlink(RUN_PARTS_FLAG) # https://github.com/systemd-cron/systemd-cron/issues/47 job_template = dict() job_template['P'] = @persistent@ job_template['u'] = 'root' job_template['e'] = '' i = 0 for period in ['hourly', 'daily', 'weekly', 'monthly']: i = i + 1 job_template['b'] = i * 5 directory = '/etc/cron.' + period if not os.path.isdir(directory): continue CRONTAB_FILES = files('/etc/cron.' + period) for filename in CRONTAB_FILES: job_template['p'] = period basename = os.path.basename(filename) if (os.path.exists('/lib/systemd/system/%s.timer' % basename) or os.path.exists('/etc/systemd/system/%s.timer' % basename)): log(5, 'ignoring %s because native timer is present' % filename) continue elif basename.startswith('.'): continue elif '.dpkg-' in basename: log(5, 'ignoring %s' % filename) continue else: job = job_template job['l'] = filename job['f'] = filename job['j'] = period + '-' + basename job['c'] = filename generate_timer_unit(job, unit_name='cron-' + job['j']) if os.path.isfile('/etc/anacrontab'): for job in parse_crontab('/etc/anacrontab', monotonic=True): if 'c' not in job: log(3, 'truncated line in /etc/anacrontab: %s' % job['l']) continue generate_timer_unit(job, seq=seqs.setdefault(job['j']+job['u'], count())) if os.path.isdir('@statedir@'): # /var is avaible USERCRONTAB_FILES = files('@statedir@') for filename in USERCRONTAB_FILES: basename = os.path.basename(filename) if '.' in basename: continue else: for job in parse_crontab(filename, withuser=False): generate_timer_unit(job, seq=seqs.setdefault(job['j']+job['u'], count())) try: open(REBOOT_FILE,'a').close() except: pass else: # schedule rerun with open('%s/cron-after-var.service' % TARGET_DIR, 'w') as f: f.write('[Unit]\n') f.write('Description=Rerun systemd-crontab-generator because /var is a separate mount\n') f.write('Documentation=man:systemd.cron(7)\n') f.write('After=cron.target\n') f.write('ConditionDirectoryNotEmpty=@statedir@\n') f.write('\n[Service]\n') f.write('Type=oneshot\n') f.write('ExecStart=/bin/sh -c "systemctl daemon-reload ; systemctl try-restart cron.target"\n') MULTIUSER_DIR = os.path.join(TARGET_DIR, 'multi-user.target.wants') try: os.makedirs(MULTIUSER_DIR) except OSError as e: if e.errno != errno.EEXIST: raise try: os.symlink('%s/cron-after-var.service' % TARGET_DIR, '%s/cron-after-var.service' % MULTIUSER_DIR) except OSError as e: if e.errno != errno.EEXIST: raise if __name__ == '__main__': if len(sys.argv) == 1 or not os.path.isdir(sys.argv[1]): sys.exit("Usage: %s " % sys.argv[0]) TARGET_DIR = sys.argv[1] TIMERS_DIR = os.path.join(TARGET_DIR, 'cron.target.wants') try: main() except Exception as e: if len(sys.argv) == 4: open('/dev/kmsg', 'w').write('<2> %s[%s]: global exception: %s\n' % (SELF, os.getpid(), e)) exit(1) else: raise systemd-cron-1.15.18/src/lib/000077500000000000000000000000001416210635200156535ustar00rootroot00000000000000systemd-cron-1.15.18/src/lib/sysusers.d/000077500000000000000000000000001416210635200177755ustar00rootroot00000000000000systemd-cron-1.15.18/src/lib/sysusers.d/systemd-cron.conf000066400000000000000000000001031416210635200232650ustar00rootroot00000000000000u _cron-failure -:systemd-journal - /nonexistent /usr/sbin/nologin systemd-cron-1.15.18/src/man/000077500000000000000000000000001416210635200156605ustar00rootroot00000000000000systemd-cron-1.15.18/src/man/anacrontab.5.in000066400000000000000000000044201416210635200204630ustar00rootroot00000000000000.TH ANACRONTAB 5 "2014-09-16" "@package@ @version@" anacrontab .SH NAME /etc/anacrontab \- monotonic jobs .SH DESCRIPTION The file .I /etc/anacrontab follow the rules previously set by \fBanacron(8)\fR. .PP Lines starting with '#' are comments. .PP Environment variables can be set using .B VAR=VALUE keypairs. .PP The special .B RANDOM_DELAY (in minutes) environment variable is translated to .B AccuracySec=. The special .B START_HOURS_RANGE (in hours) environment variable is translated to the .I \'hour\' component of .B OnCalendar=. anacron expect a range in the format ##-##, systemd-crontab-generator only use the starting hour of the range as reference. The other lines are job-descriptions that follow this layout: .PP .B period delay job-identifier command .PP .TP * .I period is a number of days to wait between each job execution, or special values @daily, @weekly, @monthly, @yearly .PP .TP * .I delay is a number of extra minutes to wait before starting job. It is translated in .B OnBootSec= . .PP .TP * .I job-identifier is a single word. systemd-crontab-generator uses it to construct the dynamic unit names: .I cron--root-0.timer and matching .I cron--root-0.service .PP .TP * .I command is the command that is run by a shell .SH BUGS systemd-crontab-generator doesn't support multiline commands. .PP Any .I period greater than 30 is rounded to the closest month .PP There are subtle differences on how anacron & systemd handle persistente timers: anacron will run a weekly job at most once a week, with always a minimum delay of 6 days between runs; where systemd will try to run it every monday at 00:00; or as soon the system boot. In the most extreme case, if a system was only started on sunday; a weekly job will run this day and the again the next (mon)day. .br With careful manual settings, it would be possible to run the real anacron binary (not your distro's package) with systemd-cron; if you need an identical behaviour. .br There is no difference for the daily job. .SH DIAGNOSTICS After editing /etc/anacrontab, you can run .I journalctl -n and .I systemctl list-timers to see if the timers have well been updated. .SH "SEE ALSO" .B systemd-crontab-generator(8), systemd.timer(5) .SH AUTHOR Alexandre Detiste systemd-cron-1.15.18/src/man/crontab.1.in000066400000000000000000000026421416210635200200030ustar00rootroot00000000000000.TH "CRONTAB" "1" "2014-12-10" "@package@ @version@" "crontab" .SH NAME crontab - maintain crontab files for individual users .SH SYNOPSIS crontab [\-u user] file .br crontab [\-u user] [\-l | \-r | \-e | \-s] [\-i] .TP .B (blank) default operation is replace .TP .B -u, --user edit some other user's crontab .TP .B -l, --list list user's crontab .TP .B -r, --remove delete user's crontab .TP .B -e, --edit edit user's crontab .TP .B -s, --show show all user who have a crontab .TP .B -i, --ask prompt before deleting user's crontab .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 @statedir@, 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 @statedir@ Directory for users crontabs. .TP .I /etc/cron.allow list of users that can use crontab .TP .I /etc/cron.deny list of users that aren't allowed to use crontab .br (by default, only root can use crontab) .SH LIMITATIONS SELinux is not supported. .TP Some extra settings can only be tweaked with .PP systemctl edit cron-.[timer|service] .TP see systemd.cron(7) for more details. .SH AUTHOR Konstantin Stepanov .br Alexandre Detiste for this manpage & setgid helper systemd-cron-1.15.18/src/man/crontab.5.in000066400000000000000000000303601416210635200200050ustar00rootroot00000000000000.\"/* 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 uunet!decwrl!vixie!paul .\" */ .\" .\" $Id: crontab.5,v 2.4 1994/01/15 20:43:43 vixie Exp $ .\" .TH CRONTAB 5 "03 July 2014" "@package@ @version@" "crontab" .UC 4 .SH NAME crontab \- tables for driving @package@ .SH DESCRIPTION A .I crontab file contains instructions to .IR @package@ 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. .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. 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 substitution 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 .I Special variables: .TP .B SHELL, PATH, USER, LOGNAME, HOME, LANG Those are set up automatically by systemd itself, see .IR systemd.exec (5) SHELL defaults to /bin/sh. SHELL and PATH may be overridden by settings in the crontab. .TP .B MAILTO .br On error .IR systemd.cron (7) will look at MAILTO. If MAILTO is defined mail is sent to this email address. 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. .br This mail only contains an small excerpt from the log, as seen when using .B systemctl status The full output remains available in the journal. .TP .B RANDOM_DELAY (in minutes) environment variable is translated to .B AccuracySec=. .TP .B DELAY (in minutes) environment variable is translated to .B OnBootSec=. This works like the 'delay' field of anacrontab(5) and make systemd wait # minutes after boot before starting the unit. This value can also be used to spread out the start times of @daily/@weekly/@monthly... jobs on a 24/24 system. .TP .B START_HOURS_RANGE (in hours) environment variable is translated to the .I \'hour\' component of .B OnCalendar=. This variable is inheritted from anacrontab(5), but also supported in crontab(5) by systemd-crontab-generator. Anacron expect a time range in the START-END format (eg: 6-9), systemd-crontab-generator will only use the starting hour of the range as reference. Unless you set this variable, all the @daily/@weekly/@monthly/@yearly jobs will run at midnight. If you set this variable and the system was off during the ours defined in the range, the (persitent) job will start at boot. .TP .B PERSISTENT With this flag, you can override the generator default heuristic. .br .B 'yes': force all further jobs to be persistent .br .B 'auto': only recognize @ keywords to be persistent .br .B 'no': force all further jobs not to be persistent .TP .B BATCH This boolean flag is translated to options .B CPUSchedulingPolicy=idle and .B IOSchedulingClass=idle when set. .PP The format of a .B cron command is the same as the one defined by the cron daemon. Each line has five time and date fields, followed by a command, followed by a newline character ('\\n'). The system crontab (/etc/crontab) and the packages crontabs (/etc/cron.d/*) use 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 systemd 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). 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 ``/'' 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, 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 errors 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. Unlike 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 .PP This is only an example, .B systemd-cron uses native units instead for those jobs. .br If you add those lines, your jobs will run twice. .SH SEE ALSO systemd.cron(7), systemd-crontab-generator(8), crontab(1) Some extra settings can only be tweaked with .PP systemctl edit cron-.[timer|service] .TP see systemd.cron(7) for more details. .SH LIMITATIONS The .I systemd-cron units 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. .B systemd-crontab-generator doesn't support these .B vixie-cron features: .TP * spawning forking daemons, the 'Service' units are all set with 'Type=oneshot' .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 You can see how your crontab where translated by typing: .br .B systemctl cat cron--* .PP .B systemctl cat does support command-line completion. .SH AUTHOR Paul Vixie 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. .br This page has been reworded by Alexandre Detiste for inclusion in systemd-cron. systemd-cron-1.15.18/src/man/systemd-crontab-generator.8.in000066400000000000000000000041751416210635200234670ustar00rootroot00000000000000.TH SYSTEMD-CRONTAB-GENERATOR 8 "2014-06-29" "@package@ @version@" systemd-crontab-generator .SH NAME systemd-crontab-generator - translate cron schedules in systemd Units .SH SYNOPSIS @generatordir@/systemd-crontab-generator output_folder .SH DESCRIPTION systemd-crontab-generator is a generator that translates the legacy cron files (see FILES) into native systemd units & timers. .PP It is not meant to be run manually, it is called automatically by systemd. .PP It is run .TP * during early boot, .TP * a second time by cron-after-var.service, only if /var is a separate mount, in order to process user crontabs in @statedir@; if any, .TP * after each manual updates to the cron files, (*) .TP * and when distribution packages add files in /etc/cron.d/. (*) .PP .B (*): those are monitored by cron-update.path .PP systemd\-crontab\-generator implements the \m[blue]\fBgenerator specification\fR\m[]\&\s-2\u[1]\d\s+2\&. .SH FILES .TP .B /etc/crontab System crontab, see \fBcrontab\fR(5). .TP .B /etc/cron.d Directory for system crontabs provided by packages. .TP .B /etc/anacrontab See \fBanacrontab\fR(5). .TP .B @statedir@ Directory for users crontabs. .br .TP .B /run/systemd/generator Directory where the generated units are stored. .TP .B /run/crond.reboot Flag used to avoid running @reboot jobs again after boot. .TP .B /var/lib/systemd/timers Directory where systemd store time stamps needed for the .I Persistent feature. .SH DIAGNOSTICS With systemd >= 209, you can execute .B "systemctl list-timers" to have a overview of timers and know when they will elapse. .br If you get errors like .br .B @generatordir@/systemd-crontab-generator failed with error code 1. .br in the journal, you can manually run .br .B "@generatordir@/systemd-crontab-generator /tmp" .br to get a more verbose error message. .SH SEE ALSO \fBsystemd.cron\fR(7),\fBcrontab\fR(5),\fBsystemd.unit\fR(5),\fBsystemd.timer\fR(5) .SH "NOTES" .IP " 1." 4 generator specification .RS 4 \%http://www.freedesktop.org/wiki/Software/systemd/Generators .RE .SH AUTHOR Konstantin Stepanov for the generator .br Alexandre Detiste for this man page systemd-cron-1.15.18/src/man/systemd.cron.7.in000066400000000000000000000126131416210635200210100ustar00rootroot00000000000000.TH SYSTEMD.CRON 7 "" "@package@ @version@" systemd.cron .SH NAME systemd.cron - systemd cron units .SH SYNOPSIS cron.target, cron-boot.timer, cron-boot.target, cron-boot.service, cron-minutely.timer, cron-minutely.target, cron-minutely.service, cron-hourly.timer, cron-hourly.target, cron-hourly.service, cron-daily.timer, cron-daily.target, cron-daily.service, cron-weekly.timer, cron-weekly.target, cron-weekly.service, cron-monthly.timer, cron-monthly.target, cron-monthly.service, cron-quarterly.timer, cron-quarterly.target, cron-quarterly.service, cron-semi-annually.timer, cron-semi-annually.target, cron-semi-annually.service, cron-yearly.timer, cron-yearly.target, cron-yearly.service, cron-update.path, cron-update.service. .SH DESCRIPTION These units provide cron daemon functionality by running scripts in cron directories. .br The crontabs are monitored by cron-update.path and are automatically translated by \fBsystemd-crontab-generator\fR(8) . .SH FILES .TP .I @confdir@/cron.boot Directory for scripts to be executed on boot. .TP .I @confdir@/cron.minutely Directory for scripts to be executed every minute. .TP .I @confdir@/cron.hourly Directory for scripts to be executed every hour. .TP .I @confdir@/cron.daily Directory for scripts to be executed every day. .TP .I @confdir@/cron.weekly Directory for scripts to be executed every week. .TP .I @confdir@/cron.monthly Directory for scripts to be executed every month. .TP .I @confdir@/cron.quarterly Directory for scripts to be executed every 3 months. .TP .I @confdir@/cron.semi-annually Directory for scripts to be executed every 6 months. .TP .I @confdir@/cron.yearly Directory for scripts to be executed every year. .TP .I @confdir@/cron.d Directory for \fBcrontabs\fR to be executed on a custom schedule. The files in this folder must follow the \fBcrontab\fR(5) layout. .br If there exists a timer of the same name + '.timer' in @unitdir@ or /etc/systemd/system, this crontab will be ignored to enable a smooth migration to native timers. .br You can also use this to mask an unneeded crontab provide by a package: .br ln \-s /dev/null /etc/systemd/system/[package].timer .SH SYSTEM UNITS .TP cron.target The target unit which starts the others. This should be enabled and started to use cron functionality. .TP cron-\fIschedule\fR.timer The timer units which pull the cron-\fIschedule\fR.target units at the appropriate time. Started and stopped by the cron.target unit. These units cannot be controlled manually. .TP cron-\fIschedule\fR.target The targets invoke all service units wanted by them, including cron-\fIschedule\fR.service. .TP cron-\fIschedule\fR.service The service units which run scripts in the cron directories. Started and stopped by the cron-\fIschedule\fR.target units. These units cannot be controlled manually. You can use \fBjournalctl\fR(1) to view the output of scripts run from these units. .SH LIMITATIONS This cron replacement only send mails on failure. The log of jobs is saved in systemd journal. Do \fInot\fR use with a cron daemon or anacron, otherwise scripts may be executed multiple times. .br All services are run with .B Type=oneshot , that means you can't use systemd-cron to launch long lived forking daemons. .SH EXTENSIONS The generator can optionally turn all crontabs in persistent timers with the .B PERSISTENT=true flag, while a regular cron+anacron setup won't catch-up the missed executions of crontabs on boot. .SH EXAMPLES .IP "Start cron units" .SB # systemctl start cron.target .IP "Start cron units on boot" .SB # systemctl enable cron.target .IP "View script output" .SB # journalctl -u cron-boot .br .SB # journalctl -u cron-minutely .br .SB # journalctl -u cron-hourly .br .SB # journalctl -u cron-daily .br .SB # journalctl -u cron-weekly .br .SB # journalctl -u cron-monthly .br .SB # journalctl -u cron-quarterly .br .SB # journalctl -u cron-semi-annually .br .SB # journalctl -u cron-yearly .br .IP "Override some generated timer start time" .SB # systemctl edit cron-geoip-database-contrib-root-<...>.timer --full .br .I keep existing statements, but change this one: .br .SB [Timer] .br .SB OnCalendar=*-*-* 18:36:00 .IP "Override cron-daily.service priority, useful for old computers" .br .SB # systemctl edit cron-daily.service .br .I this will open a blank editor when you can type a drop-in configuration file .I that will extend the current .service .br .SB [Service] .br .SB CPUSchedulingPolicy=idle .br .SB IOSchedulingClass=idle .br .IP "Example service file executed every hour" .SB [Unit] .br .SB Description=Update the man db .SB [Service] .br .SB Nice=19 .br .SB IOSchedulingClass=2 .br .SB IOSchedulingPriority=7 .br .SB ExecStart=/usr/bin/mandb --quiet .SB [Install] .br .SB WantedBy=cron-hourly.target .SH NOTES .nr step 1 1 .IP \n[step]. 3 The exact times scripts are executed is determined by the values of the special calendar events \fIhourly\fR, \fIdaily\fR, \fIweekly\fR, \fImonthly\fR, and \fIyearly\fR defined by \fBsystemd.time\fR(7). .IP \n+[step]. \fBrun-parts\fR(8) is used to run scripts. Scripts must be executable by \fIroot\fR to run. .SH DIAGNOSTICS With systemd >= 209, you can execute "systemctl list-timers" to have a overview of timers and know when they will elapse. .SH SEE ALSO .BR systemd (1), .BR systemd.unit (5), .BR systemd.service (5), .BR systemd.target (5), .BR systemd.timer (5), .BR systemd.time (7), .BR systemd-crontab-generator (8), .BR crontab (5), .BR run-parts (8) .SH AUTHOR Dwayne Bent systemd-cron-1.15.18/src/units/000077500000000000000000000000001416210635200162475ustar00rootroot00000000000000systemd-cron-1.15.18/src/units/cron-boot.service.in000066400000000000000000000006011416210635200221350ustar00rootroot00000000000000[Unit] Description=@package@ @schedule@ script service Documentation=man:systemd.cron(7) PartOf=cron-@schedule@.target ConditionDirectoryNotEmpty=@confdir@/cron.@schedule@ ConditionPathExists=!/run/crond.bootdir OnFailure=cron-failure@%i.service [Service] Type=oneshot IgnoreSIGPIPE=false ExecStart=@runparts@ @confdir@/cron.@schedule@ ExecStartPost=/usr/bin/touch /run/crond.bootdir systemd-cron-1.15.18/src/units/cron-boot.timer.in000066400000000000000000000002251416210635200216170ustar00rootroot00000000000000[Unit] Description=@package@ @schedule@ timer Documentation=man:systemd.cron(7) PartOf=cron.target [Timer] OnBootSec=60 Unit=cron-@schedule@.target systemd-cron-1.15.18/src/units/cron-failure@.service.in000066400000000000000000000004521416210635200227250ustar00rootroot00000000000000[Unit] Description=@package@ OnFailure for %i Documentation=man:systemd.cron(7) RefuseManualStart=true RefuseManualStop=true ConditionFileIsExecutable=/usr/sbin/sendmail [Service] Type=oneshot ExecStart=@libdir@/@package@/mail_on_failure %i DynamicUser=no User=_cron-failure Group=systemd-journal systemd-cron-1.15.18/src/units/cron-schedule.service.in000066400000000000000000000005071416210635200227730ustar00rootroot00000000000000[Unit] Description=@package@ @schedule@ script service Documentation=man:systemd.cron(7) PartOf=cron-@schedule@.target ConditionDirectoryNotEmpty=@confdir@/cron.@schedule@ OnFailure=cron-failure@%i.service [Service] Type=oneshot IgnoreSIGPIPE=false LogLevelMax=@use_loglevelmax@ ExecStart=@runparts@ @confdir@/cron.@schedule@ systemd-cron-1.15.18/src/units/cron-schedule.target.in000066400000000000000000000002071416210635200226160ustar00rootroot00000000000000[Unit] Description=@package@ @schedule@ target Documentation=man:systemd.cron(7) Requires=cron-@schedule@.service StopWhenUnneeded=yes systemd-cron-1.15.18/src/units/cron-schedule.timer.in000066400000000000000000000003351416210635200224520ustar00rootroot00000000000000[Unit] Description=@package@ @schedule@ timer Documentation=man:systemd.cron(7) PartOf=cron.target ConditionPathExists=/run/systemd/use_run_parts [Timer] Persistent=true OnCalendar=@schedule@ Unit=cron-@schedule@.target systemd-cron-1.15.18/src/units/cron-update.path.in000066400000000000000000000002701416210635200217520ustar00rootroot00000000000000[Unit] Description=@package@ path monitor Documentation=man:systemd.cron(7) [Path] PathChanged=/etc/crontab PathChanged=/etc/cron.d PathChanged=/etc/anacrontab PathChanged=@statedir@ systemd-cron-1.15.18/src/units/cron-update.service.in000066400000000000000000000003531416210635200224600ustar00rootroot00000000000000[Unit] Description=@package@ update units Documentation=man:systemd.cron(7) [Service] Type=oneshot ExecStartPre=/usr/bin/touch /run/crond.reboot ExecStart=/bin/sh -c 'systemctl daemon-reload ; sleep 1 ; systemctl restart cron.target' systemd-cron-1.15.18/src/units/cron.target.in000066400000000000000000000002101416210635200210160ustar00rootroot00000000000000[Unit] Description=@package@ Documentation=man:systemd.cron(7) @requires@ Wants=cron-update.path [Install] WantedBy=multi-user.target