/* Define if you have the <sys/time.h> header file. */
#undef HAVE_SYS_TIME_H
+/* Define if you have the <sys/timerfd.h> header file. */
+#undef HAVE_SYS_TIMERFD_H
+
/* Define if you have the <sys/types.h> header file. */
#undef HAVE_SYS_TYPES_H
AC_PREREQ(2.57)
m4_include([m4/ax_lib_readline.m4])
-vers="3.2.1"
+vers="3.3.0"
vers_quoted="\"$vers\""
AC_DEFINE_UNQUOTED(VERSION, $vers)
AC_DEFINE_UNQUOTED(VERSION_QUOTED, $vers_quoted)
AC_CHECK_HEADERS(stdarg.h)
AC_CHECK_HEADERS(termios.h)
AC_CHECK_HEADERS(strings.h)
-AC_CHECK_HEADERS(sys/select.h sys/types.h sys/socket.h sys/un.h)
+AC_CHECK_HEADERS(sys/select.h sys/timerfd.h sys/types.h sys/socket.h sys/un.h)
AC_CHECK_HEADERS(security/pam_appl.h pam/pam_appl.h crypt.h shadow.h libaudit.h)
AC_CHECK_HEADERS(sys/resource.h)
AC_CHECK_HEADERS(grp.h)
else {
if (cl->cl_nextexe != LONG_MAX) {
cl->cl_nextexe += sleep_duration;
- if (cl->cl_nextexe < now || cl->cl_nextexe > TIME_T_MAX) {
+
+ if (cl->cl_nextexe < now && context == CONTEXT_RESUME) {
+ /* userland processes including fcron may not run for a short
+ * time before suspend, or after resume, so we can end up in this situation.
+ * it's not really an error though, and it's best to simply run that job now */
+ cl->cl_nextexe = now;
+ }
+ else if (cl->cl_nextexe < now || cl->cl_nextexe > TIME_T_MAX) {
/* either there was an integer overflow, or the sleep_duration time is incorrect
* (e.g. fcron didn't shut down cleanly and the fcrontab wasn't saved correctly) */
error
<sect1 id="changes">
<title>Changes</title>
+ <itemizedlist>
+ <title>From version 3.2.1 to 3.3.0</title>
+ <listitem>
+ <para>fcron now handles computer suspend/resume. On Linux systems, fcron can detect resumes and measure the suspend time. On other OSes, a script must be run at suspend and resume via system hooks.</para>
+ </listitem>
+ <listitem>
+ <para>Refactored the socket (for fcrondyn), suspend and select code.</para>
+ </listitem>
+ </itemizedlist>
+
+
<itemizedlist>
<title>From version 3.2.0 to 3.2.1</title>
<listitem>
<answer>
<para>fcron now fully supports suspend (to memory or disk).
On resume it will adjust the task schedules accordingly, run runatreboot tasks if appropriate, and report non-execution of noticenotrun tasks.</para>
- <para>fcron will try to notice suspends by itself without external help,
- by checking if it wakes up later than it expected after a sleep.
- However this is far from bullet-proof, as fcron may not notice
+ <para>fcron will try to notice suspends by itself without external help.
+ On Linux it can do so reliably via system APIs, but on other OSes it will instead
+ check if it wakes up later than it expected after a sleep.
+ This is far from bullet-proof, as fcron may not notice
the computer was suspended or under-estimate the suspend duration.
This is because fcron can be woken up by external events such
as receiving a signal or fcrondyn interation. There is also a risk
clock correctly after resume. If so, it may compute an incorrect
the time and date of the execution of a job (please see the entry
about system clock adjustment in the present FAQ).</para>
- <para>Because of this, it is recommended to explicitly 'tell' fcron
+ <para>Because of this, on non-Linux systems it is recommended to explicitly 'tell' fcron
about suspends and the precise suspend duration:
<orderedlist>
<listitem>
<para>Wake up fcron and tell it to process the suspend file and reschedule tasks accordingly:
<programlisting>$ kill -CONT $(cat &fcron.pid;)</programlisting></para>
</orderedlist>
+ <para>An example script implementing this for pm-utils and systemd is included: fcron.suspend.sh.
+ Note that it is however not needed on Linux. Contribution of equivalent scripts for other
+ systems is welcome.</para>
<para>Alternatively you could have fcron stop on suspend and restart on resume.
However the main drawback would then be that tasks running at the time of suspend
would be run again at resume, even though they may not have been stopped and finish on resume
<varlistentry>
<term><constant>SIGCONT</constant></term>
<listitem>
- <para>Notify &fcron; that the system was just resumed from suspend (to memory or disk). This will trigger &fcron; to read the &suspendfile; and update the task schedules accordingly.</para>
+ <para>Notify &fcron; that the system was just resumed from suspend (to memory or disk). This will trigger &fcron; to read the &suspendfile; and update the task schedules accordingly. Note that fcron doesn't need this to detect suspend/resume on Linux.</para>
</listitem>
</varlistentry>
</variablelist>
<varlistentry>
<term><filename>&suspendfile;</filename></term>
<listitem>
- <para>Location of &fcron; suspend file. This should be used to let &fcron; know how long the system was suspended (to memory or disk), so as task schedules can be updated accordingly. The file must be owned by &rootname;:&rootgroup;, and not writable by others. When the system resumes, write the number of seconds (as a string) the system was suspended into this file, and then send a <constant>SIGCONT</constant> signal to make fcron process (and then delete) that file.</para>
+ <para>Location of &fcron; suspend file, for non-Linux systems. This should be used to let &fcron; know how long the system was suspended (to memory or disk), so as task schedules can be updated accordingly. The file must be owned by &rootname;:&rootgroup;, and not writable by others. When the system resumes, write the number of seconds (as a string) the system was suspended into this file, and then send a <constant>SIGCONT</constant> signal to make fcron process (and then delete) that file. This is not needed on Linux as fcron uses Linux-specific APIs to detect the resume and measure the suspend duration.</para>
</listitem>
</varlistentry>
</variablelist>
<term><varname>suspendfile</varname>=<replaceable>file-path</replaceable>
(<filename>&suspendfile;</filename>)</term>
<listitem>
- <para>Location of &fcron; suspend file. This should be used to let fcron know how long the system was suspended (to memory or disk), so as task schedules can be updated accordingly.</para>
+ <para>Location of &fcron; suspend file. On non-Linux systems, this should be used to let fcron know how long the system was suspended (to memory or disk), so as task schedules can be updated accordingly.</para>
</listitem>
</varlistentry>
<varlistentry>
<para>Option to compile and install from git sources without generating the doc</para>
</listitem>
<listitem>
- <para>On Linux systems, replace suspendfile by clock_gettime() calls: CLOCK_BOOTTIME - CLOCK_MONOTONIC will give us the total suspend duration. Should be simpler, more elegant and accurate.</para>
- </listitem>
- <listitem>
- <para>On Linux systems, use timerfd_create()/timerfd_settime(TFD_TIMER_ABSTIME|TFD_TIMER_CANCEL_ON_SET) to get notified of time jumps. On other systems, keep SIGCONT with a hook in systemd/pm-utils.</para>
- </listitem>
- <listitem>
- <para>add systemd suspend hooks for fcron (contribution welcome)</para>
- </listitem>
- <listitem>
- <para>add a 'runatresume' (or 'runonclockchanges'?) option, to run when the computer resumes? (similar to runatreboot)</para>
+ <para>split bootrun into bootrun vs. resumerun, to run when the computer resumes? (similar to bootrun)</para>
</listitem>
<listitem>
<para>use ask_user() in boot-install</para>
#ifdef FCRONDYN
fcrondyn_socket_init(&main_select);
#endif
+ init_suspend(&main_select);
#endif
now = my_time();
debug("\n");
now = my_time();
- debug_print_tstamp("just woke up")
+ debug_print_tstamp("just woke up");
- check_signal();
- check_suspend(slept_from, nwt, &sig_cont);
+ check_signal();
+ check_suspend(slept_from, nwt, &sig_cont, &main_select);
reset_sig_cont();
- debug_print_tstamp("after check_signal and suspend")
+ debug_print_tstamp("after check_signal and suspend");
- test_jobs();
- debug_print_tstamp("after test_jobs")
+ test_jobs();
+ debug_print_tstamp("after test_jobs");
- while (serial_num > 0 && serial_running < serial_max_running) {
+ while (serial_num > 0 && serial_running < serial_max_running) {
run_serial_job();
}
/* save all files */
save_file(NULL);
}
- debug_print_tstamp("after save")
+ debug_print_tstamp("after save");
#if defined(FCRONDYN) && defined(HAVE_GETTIMEOFDAY)
- /* check if there's a new connection, a new command to answer, etc ... */
- /* we do that *after* other checks, to avoid Denial Of Service attacks */
- fcrondyn_socket_check(&main_select);
+ /* check if there's a new connection, a new command to answer, etc ... */
+ /* we do that *after* other checks, to avoid Denial Of Service attacks */
+ fcrondyn_socket_check(&main_select);
#endif
nwt = check_lavg(save);
- debug_print_tstamp("after check_lavg")
- debug("next wake time : %s", ctime(&nwt));
+ debug_print_tstamp("after check_lavg");
+ debug("next wake time : %s", ctime(&nwt));
check_signal();
#
# Now install the suspend script under the appropriate suspend framework
+# Note that we don't need this on Linux as fcron works this out
+# by itself using systems API.
#
+if test `uname` != Linux ; then
echo
-if test -d /usr/lib/systemd/system-sleep ; then
- # systemd
- SUSPEND_DEST=/usr/lib/systemd/system-sleep/fcron.sh
- echo "This system appears to run systemd. Would you like to install the systemd"
- echo "suspend script under $SUSPEND_DEST?"
- if test "`ask_user`" = y; then
- $INSPROG -c -m 754 script/fcron.suspend.sh $SUSPEND_DEST
- fi
-elif test -d /etc/pm/sleep.d ; then
- # pm-utils
- SUSPEND_DEST=/etc/pm/sleep.d/74_fcron
- echo "This system appears to run pm-utils. Would you like to install the pm-utils"
- echo "suspend script under $SUSPEND_DEST?"
- if test "`ask_user`" = y; then
- $INSPROG -c -m 754 script/fcron.suspend.sh $SUSPEND_DEST
+ if test -d /usr/lib/systemd/system-sleep -o -d /lib/systemd/system-sleep ; then
+ # systemd
+ if test -d /usr/lib/systemd/system-sleep ; then
+ SUSPEND_DEST=/usr/lib/systemd/system-sleep/fcron.sh
+ else
+ SUSPEND_DEST=/lib/systemd/system-sleep/fcron.sh
+ fi
+ echo "This system appears to run systemd. Would you like to install the systemd"
+ echo "suspend script under $SUSPEND_DEST?"
+ if test "`ask_user`" = y; then
+ $INSPROG -c -m 754 script/fcron.suspend.sh $SUSPEND_DEST
+ fi
+ elif test -d /etc/pm/sleep.d ; then
+ # pm-utils
+ SUSPEND_DEST=/etc/pm/sleep.d/74_fcron
+ echo "This system appears to run pm-utils. Would you like to install the pm-utils"
+ echo "suspend script under $SUSPEND_DEST?"
+ if test "`ask_user`" = y; then
+ $INSPROG -c -m 754 script/fcron.suspend.sh $SUSPEND_DEST
+ fi
+ else
+ echo "This script didn't find any suspend system it supports."
+ echo "Please install an appropriate suspend script manually."
fi
-else
- echo "This script didn't find any suspend system it supports."
- echo "Please install an appropriate suspend script manually."
-fi
+fi # uname != Linux
if PID=`pidof fcron`; then
KILL="kill -TERM $PID"
/*
- * FCRON - periodic command scheduler
+ * FCRON - periodic command scheduler
*
* Copyright 2000-2014 Thibault Godouet <fcron@free.fr>
*
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
+ *
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
- *
+ *
* The GNU General Public License can also be found in the file
* `LICENSE' that comes with the fcron source distribution.
*/
#include "fcronconf.h"
#include "fcron.h"
#include "database.h"
+#ifdef HAVE_SYS_TIMERFD_H
+#include <sys/select.h>
+#include <sys/timerfd.h>
+#include <stdint.h> /* for uint64_t */
+#ifdef __linux__
+/* Not too sure how to get that from the Linux header, so we redefine here instead */
+#ifndef TFD_TIMER_CANCEL_ON_SET
+#define TFD_TIMER_CANCEL_ON_SET (1 << 1)
+#endif /* TFD_TIMER_CANCEL_ON_SET */
+#endif /* __linux__ */
+#endif /* HAVE_SYS_TIMERFD_H */
+
+/* types */
+#if defined(CLOCK_BOOTTIME) && defined(CLOCK_MONOTONIC)
+struct linux_timeref {
+ struct timespec monotonic;
+ struct timespec boottime;
+};
+#endif /* defined(CLOCK_BOOTTIME) && defined(CLOCK_MONOTONIC) */
+
+/* variables */
+#if defined(CLOCK_BOOTTIME) && defined(CLOCK_MONOTONIC)
+struct linux_timeref linux_suspend_ref;
+#endif /* defined(CLOCK_BOOTTIME) && defined(CLOCK_MONOTONIC) */
+#ifdef TFD_TIMER_CANCEL_ON_SET
+int timer_cancel_on_set_fd = -1;
+#endif /* TFD_TIMER_CANCEL_ON_SET */
+
+
+/* internal functions */
+long int read_suspend_duration(time_t slept_from);
+double timespec_diff(struct timespec *from, struct timespec *to);
+#if defined(CLOCK_BOOTTIME) && defined(CLOCK_MONOTONIC)
+void linux_init_timeref(struct linux_timeref *tr);
+double linux_get_suspend_time(struct linux_timeref *tr);
+#endif /* defined(CLOCK_BOOTTIME) && defined(CLOCK_MONOTONIC) */
+
+double
+timespec_diff(struct timespec *from, struct timespec *to)
+{
+ return ((to->tv_sec - from->tv_sec) * 1000000000.0 +
+ (to->tv_nsec - from->tv_nsec)) / 1000000000.0;
+}
+
+#if defined(CLOCK_BOOTTIME) && defined(CLOCK_MONOTONIC)
+void
+linux_init_timeref(struct linux_timeref *tr)
+ /* Initialize the time reference */
+{
+ clock_gettime(CLOCK_MONOTONIC, &tr->monotonic);
+ clock_gettime(CLOCK_BOOTTIME, &tr->boottime);
+}
+
+double
+linux_get_suspend_time(struct linux_timeref *tr)
+ /* return the suspend time, using Linux-specific APIs */
+{
+ struct timespec tpm, tpb;
+ double tpm_diff, tpb_diff;
+
+ debug("Calculating suspend duration from CLOCK_BOOTTIME-CLOCK_MONOTONIC");
+
+ clock_gettime(CLOCK_MONOTONIC, &tpm);
+ clock_gettime(CLOCK_BOOTTIME, &tpb);
+
+ tpm_diff = timespec_diff(&tr->monotonic, &tpm);
+ tpb_diff = timespec_diff(&tr->boottime, &tpb);
+
+ linux_init_timeref(tr);
+
+ return (tpb_diff - tpm_diff);
+}
+#endif /* defined(CLOCK_BOOTTIME) && defined(CLOCK_MONOTONIC) */
long int
read_suspend_duration(time_t slept_from)
long int suspend_duration = 0; /* default value to return on error */
struct stat s;
+ debug("Attempting to read suspend duration from %s", suspendfile);
fd = open(suspendfile, O_RDONLY | O_NONBLOCK);
if (fd == -1) {
/* If the file doesn't exist, then we assume the user/system
}
void
-check_suspend(time_t slept_from, time_t nwt, char *sig_cont)
+init_suspend(select_instance * si)
+{
+
+#if defined(CLOCK_BOOTTIME) && defined(CLOCK_MONOTONIC)
+ linux_init_timeref(&linux_suspend_ref);
+#endif /* defined(CLOCK_BOOTTIME) && defined(CLOCK_MONOTONIC) */
+
+#ifdef TFD_TIMER_CANCEL_ON_SET
+ {
+ struct itimerspec expiration = {
+ .it_value.tv_sec = TIME_T_MAX,
+ };
+
+
+ timer_cancel_on_set_fd = timerfd_create(CLOCK_REALTIME, 0);
+ if (timer_cancel_on_set_fd == -1) {
+ die_e("timerfd_create");
+ }
+ if (timerfd_settime
+ (timer_cancel_on_set_fd,
+ TFD_TIMER_ABSTIME | TFD_TIMER_CANCEL_ON_SET, &expiration,
+ NULL) == -1) {
+ die_e("timerfd_settime");
+ }
+
+ }
+ select_add_read(si, timer_cancel_on_set_fd);
+#endif /* TFD_TIMER_CANCEL_ON_SET */
+
+}
+
+
+void
+check_suspend(time_t slept_from, time_t nwt, char *sig_cont,
+ select_instance * si)
/* Check if the machine was suspended (to mem or disk), and if so
* reschedule jobs accordingly */
{
- long int suspend_duration; /* amount of time the system was suspended */
+ long int suspend_duration = 0; /* amount of time the system was suspended */
long int time_diff; /* estimate of suspend_duration (as fallback) */
+ time_diff = now - nwt;
+
+#ifdef TFD_TIMER_CANCEL_ON_SET
+ if (si->retcode > 0 && FD_ISSET(timer_cancel_on_set_fd, &si->readfds)) {
+ /* we don't need the data from that fd, but we must read it
+ * or select() would return immediately again */
+ uint64_t exp;
+ read(timer_cancel_on_set_fd, &exp, sizeof(uint64_t));
+
+ debug
+ ("We were notified of a change of time, find out suspend duration");
+#ifdef JUST_FOR_FORMATTING
+ }
+#endif
+
+#else /* TFD_TIMER_CANCEL_ON_SET */
+
if (*sig_cont > 0) {
- /* the signal CONT was raised, check the suspendfile */
+ debug("We received a SIGCONT, find out the suspend duration");
+
+#endif /* TFD_TIMER_CANCEL_ON_SET */
+
+#if defined(CLOCK_BOOTTIME) && defined(CLOCK_MONOTONIC)
+ suspend_duration = linux_get_suspend_time(&linux_suspend_ref);
+#else /* defined(CLOCK_BOOTTIME) && defined(CLOCK_MONOTONIC) */
suspend_duration = read_suspend_duration(slept_from);
+#endif /* defined(CLOCK_BOOTTIME) && defined(CLOCK_MONOTONIC) */
}
+
+
/* Also check if there was an unaccounted sleep duration, in case
* the OS is not configured to let fcron properly know about suspends
* via suspendfile.
* NOTE: the +5 second is arbitrary -- just a way to make sure
* we don't get any false positive. If the suspend or hibernate
* is very short it seems fine to simply ignore it anyway */
- time_diff = now - nwt;
if (suspend_duration <= 0 && time_diff > 5) {
+#if defined(CLOCK_BOOTTIME) && defined(CLOCK_MONOTONIC)
+ suspend_duration = linux_get_suspend_time(&linux_suspend_ref);
+#else /* defined(CLOCK_BOOTTIME) && defined(CLOCK_MONOTONIC) */
suspend_duration = time_diff;
+#endif /* defined(CLOCK_BOOTTIME) && defined(CLOCK_MONOTONIC) */
}
if (suspend_duration > 0) {
long int actual_sleep = now - slept_from;
long int scheduled_sleep = nwt - slept_from;
+
explain("suspend/hibernate detected: we woke up after %lus"
" instead of %lus. The system was suspended for %lus.",
actual_sleep, scheduled_sleep, suspend_duration);
#include "config.h"
#include "global.h"
+#include "select.h"
/* functions prototypes */
-extern long int read_suspend_duration(time_t slept_from);
-extern void check_suspend(time_t slept_from, time_t nwt, char *sig_cont);
+extern void init_suspend(select_instance * si);
+extern void check_suspend(time_t slept_from, time_t nwt, char *sig_cont,
+ select_instance * si);
#endif /* __SUSPEND_H__ */