2 * rtcwake -- enter a system sleep state until specified wakeup time.
4 * This uses cross-platform Linux interfaces to enter a system sleep state,
5 * and leave it no later than a specified time. It uses any RTC framework
6 * driver that supports standard driver model wakeup flags.
8 * This is normally used like the old "apmsleep" utility, to wake from a
9 * suspend state like ACPI S1 (standby) or S3 (suspend-to-RAM). Most
10 * platforms can implement those without analogues of BIOS, APM, or ACPI.
12 * On some systems, this can also be used like "nvram-wakeup", waking
13 * from states like ACPI S4 (suspend to disk). Not all systems have
14 * persistent media that are appropriate for such suspend modes.
16 * The best way to set the system's RTC is so that it holds the current
17 * time in UTC. Use the "-l" flag to tell this program that the system
18 * RTC uses a local timezone instead (maybe you dual-boot MS-Windows).
19 * That flag should not be needed on systems with adjtime support.
25 #include <linux/rtc.h>
30 #include <sys/ioctl.h>
32 #include <sys/types.h>
38 #include "closestream.h"
42 #include "pathnames.h"
45 #include "timeutils.h"
49 # define RTC_AF 0x20 /* Alarm interrupt */
52 #define ADJTIME_ZONE_BUFSIZ 8
53 #define SYS_WAKEUP_PATH_TEMPLATE "/sys/class/rtc/%s/device/power/wakeup"
54 #define SYS_POWER_STATE_PATH "/sys/power/state"
55 #define DEFAULT_RTC_DEVICE "/dev/rtc0"
57 enum rtc_modes
{ /* manual page --mode option explains these. */
64 SYSFS_MODE
/* keep it last */
68 static const char *rtcwake_mode_string
[] = {
72 [DISABLE_MODE
] = "disable",
82 struct rtcwake_control
{
83 char *mode_str
; /* name of the requested mode */
84 char **possible_modes
; /* modes listed in /sys/power/state */
85 char *adjfile
; /* adjtime file path */
86 enum clock_modes clock_mode
; /* hwclock timezone */
87 time_t sys_time
; /* system time */
88 time_t rtc_time
; /* hardware time */
89 unsigned int verbose
:1, /* verbose messaging */
90 dryrun
:1; /* do not set alarm, suspend system, etc */
93 static void __attribute__((__noreturn__
)) usage(void)
96 fputs(USAGE_HEADER
, out
);
98 _(" %s [options]\n"), program_invocation_short_name
);
100 fputs(USAGE_SEPARATOR
, out
);
101 fputs(_("Enter a system sleep state until a specified wakeup time.\n"), out
);
103 fputs(USAGE_OPTIONS
, out
);
104 fputs(_(" -a, --auto reads the clock mode from adjust file (default)\n"), out
);
106 _(" -A, --adjfile <file> specifies the path to the adjust file\n"
107 " the default is %s\n"), _PATH_ADJTIME
);
108 fputs(_(" --date <timestamp> date time of timestamp to wake\n"), out
);
109 fputs(_(" -d, --device <device> select rtc device (rtc0|rtc1|...)\n"), out
);
110 fputs(_(" -n, --dry-run does everything, but suspend\n"), out
);
111 fputs(_(" -l, --local RTC uses local timezone\n"), out
);
112 fputs(_(" --list-modes list available modes\n"), out
);
113 fputs(_(" -m, --mode <mode> standby|mem|... sleep mode\n"), out
);
114 fputs(_(" -s, --seconds <seconds> seconds to sleep\n"), out
);
115 fputs(_(" -t, --time <time_t> time to wake\n"), out
);
116 fputs(_(" -u, --utc RTC uses UTC\n"), out
);
117 fputs(_(" -v, --verbose verbose messages\n"), out
);
119 fputs(USAGE_SEPARATOR
, out
);
120 printf(USAGE_HELP_OPTIONS(26));
121 printf(USAGE_MAN_TAIL("rtcwake(8)"));
125 static int is_wakeup_enabled(const char *devname
)
131 if (startswith(devname
, "/dev/"))
133 snprintf(buf
, sizeof buf
, SYS_WAKEUP_PATH_TEMPLATE
, devname
+ skip
);
136 warn(_("cannot open %s"), buf
);
140 s
= fgets(buf
, sizeof buf
, f
);
144 s
= strchr(buf
, '\n');
148 /* wakeup events could be disabled or not supported */
149 return strcmp(buf
, "enabled") == 0;
152 static int get_basetimes(struct rtcwake_control
*ctl
, int fd
)
154 struct tm tm
= { 0 };
157 /* This process works in RTC time, except when working
158 * with the system clock (which always uses UTC).
160 if (ctl
->clock_mode
== CM_UTC
)
161 xsetenv("TZ", "UTC", 1);
163 /* Read rtc and system clocks "at the same time", or as
164 * precisely (+/- a second) as we can read them.
166 if (ioctl(fd
, RTC_RD_TIME
, &rtc
) < 0) {
167 warn(_("read rtc time failed"));
171 ctl
->sys_time
= time(NULL
);
172 if (ctl
->sys_time
== (time_t)-1) {
173 warn(_("read system time failed"));
176 /* Convert rtc_time to normal arithmetic-friendly form,
177 * updating tm.tm_wday as used by asctime().
179 tm
.tm_sec
= rtc
.tm_sec
;
180 tm
.tm_min
= rtc
.tm_min
;
181 tm
.tm_hour
= rtc
.tm_hour
;
182 tm
.tm_mday
= rtc
.tm_mday
;
183 tm
.tm_mon
= rtc
.tm_mon
;
184 tm
.tm_year
= rtc
.tm_year
;
185 tm
.tm_isdst
= -1; /* assume the system knows better than the RTC */
187 ctl
->rtc_time
= mktime(&tm
);
188 if (ctl
->rtc_time
== (time_t)-1) {
189 warn(_("convert rtc time failed"));
194 /* Unless the system uses UTC, either delta or tzone
195 * reflects a seconds offset from UTC. The value can
196 * help sort out problems like bugs in your C library. */
197 printf("\tdelta = %ld\n", ctl
->sys_time
- ctl
->rtc_time
);
198 printf("\ttzone = %ld\n", timezone
);
199 printf("\ttzname = %s\n", tzname
[daylight
]);
200 gmtime_r(&ctl
->rtc_time
, &tm
);
201 printf("\tsystime = %ld, (UTC) %s",
202 (long) ctl
->sys_time
, asctime(gmtime(&ctl
->sys_time
)));
203 printf("\trtctime = %ld, (UTC) %s",
204 (long) ctl
->rtc_time
, asctime(&tm
));
209 static int setup_alarm(struct rtcwake_control
*ctl
, int fd
, time_t *wakeup
)
212 struct rtc_wkalrm wake
= { 0 };
214 /* The wakeup time is in POSIX time (more or less UTC). Ideally
215 * RTCs use that same time; but PCs can't do that if they need to
216 * boot MS-Windows. Messy...
218 * When clock_mode == CM_UTC this process's timezone is UTC, so
219 * we'll pass a UTC date to the RTC.
221 * Else clock_mode == CM_LOCAL so the time given to the RTC will
222 * instead use the local time zone. */
223 tm
= localtime(wakeup
);
224 wake
.time
.tm_sec
= tm
->tm_sec
;
225 wake
.time
.tm_min
= tm
->tm_min
;
226 wake
.time
.tm_hour
= tm
->tm_hour
;
227 wake
.time
.tm_mday
= tm
->tm_mday
;
228 wake
.time
.tm_mon
= tm
->tm_mon
;
229 wake
.time
.tm_year
= tm
->tm_year
;
230 /* wday, yday, and isdst fields are unused */
231 wake
.time
.tm_wday
= -1;
232 wake
.time
.tm_yday
= -1;
233 wake
.time
.tm_isdst
= -1;
236 if (!ctl
->dryrun
&& ioctl(fd
, RTC_WKALM_SET
, &wake
) < 0) {
237 warn(_("set rtc wake alarm failed"));
243 static char **get_sys_power_states(struct rtcwake_control
*ctl
)
247 if (!ctl
->possible_modes
) {
248 char buf
[256] = { 0 };
250 fd
= open(SYS_POWER_STATE_PATH
, O_RDONLY
);
253 if (read(fd
, &buf
, sizeof(buf
) - 1) <= 0)
255 ctl
->possible_modes
= strv_split(buf
, " \n");
258 return ctl
->possible_modes
;
265 static void wait_stdin(struct rtcwake_control
*ctl
)
267 struct pollfd fd
[] = {
268 {.fd
= STDIN_FILENO
, .events
= POLLIN
}
272 while (tries
< 8 && poll(fd
, 1, 10) == 1) {
274 warnx(_("discarding stdin"));
276 tcflush(STDIN_FILENO
, TCIFLUSH
);
281 static void suspend_system(struct rtcwake_control
*ctl
)
283 FILE *f
= fopen(SYS_POWER_STATE_PATH
, "w");
286 warn(_("cannot open %s"), SYS_POWER_STATE_PATH
);
291 if (isatty(STDIN_FILENO
))
293 fprintf(f
, "%s\n", ctl
->mode_str
);
296 /* this executes after wake from suspend */
298 errx(EXIT_FAILURE
, _("write error"));
301 static int read_clock_mode(struct rtcwake_control
*ctl
)
304 char linebuf
[ADJTIME_ZONE_BUFSIZ
];
306 fp
= fopen(ctl
->adjfile
, "r");
310 if (skip_fline(fp
) || skip_fline(fp
)) {
314 /* read third line */
315 if (!fgets(linebuf
, sizeof linebuf
, fp
)) {
320 if (strncmp(linebuf
, "UTC", 3) == 0)
321 ctl
->clock_mode
= CM_UTC
;
322 else if (strncmp(linebuf
, "LOCAL", 5) == 0)
323 ctl
->clock_mode
= CM_LOCAL
;
324 else if (ctl
->verbose
)
325 warnx(_("unexpected third line in: %s: %s"), ctl
->adjfile
, linebuf
);
331 static int print_alarm(struct rtcwake_control
*ctl
, int fd
)
333 struct rtc_wkalrm wake
;
334 struct tm tm
= { 0 };
337 if (ioctl(fd
, RTC_WKALM_RD
, &wake
) < 0) {
338 warn(_("read rtc alarm failed"));
342 if (wake
.enabled
!= 1 || wake
.time
.tm_year
== -1) {
343 printf(_("alarm: off\n"));
346 tm
.tm_sec
= wake
.time
.tm_sec
;
347 tm
.tm_min
= wake
.time
.tm_min
;
348 tm
.tm_hour
= wake
.time
.tm_hour
;
349 tm
.tm_mday
= wake
.time
.tm_mday
;
350 tm
.tm_mon
= wake
.time
.tm_mon
;
351 tm
.tm_year
= wake
.time
.tm_year
;
352 tm
.tm_isdst
= -1; /* assume the system knows better than the RTC */
355 if (alarm
== (time_t)-1) {
356 warn(_("convert time failed"));
359 /* 0 if both UTC, or expresses diff if RTC in local time */
360 alarm
+= ctl
->sys_time
- ctl
->rtc_time
;
361 printf(_("alarm: on %s"), ctime(&alarm
));
366 static int get_rtc_mode(struct rtcwake_control
*ctl
, const char *s
)
369 char **modes
= get_sys_power_states(ctl
), **m
;
371 STRV_FOREACH(m
, modes
) {
372 if (strcmp(s
, *m
) == 0)
376 for (i
= 0; i
< ARRAY_SIZE(rtcwake_mode_string
); i
++)
377 if (!strcmp(s
, rtcwake_mode_string
[i
]))
383 static int open_dev_rtc(const char *devname
)
386 char *devpath
= NULL
;
388 if (startswith(devname
, "/dev"))
389 devpath
= xstrdup(devname
);
391 xasprintf(&devpath
, "/dev/%s", devname
);
392 fd
= open(devpath
, O_RDONLY
| O_CLOEXEC
);
394 err(EXIT_FAILURE
, _("%s: unable to find device"), devpath
);
399 static void list_modes(struct rtcwake_control
*ctl
)
402 char **modes
= get_sys_power_states(ctl
), **m
;
405 errx(EXIT_FAILURE
, _("could not read: %s"), SYS_POWER_STATE_PATH
);
407 STRV_FOREACH(m
, modes
)
410 for (i
= 0; i
< ARRAY_SIZE(rtcwake_mode_string
); i
++)
411 printf("%s ", rtcwake_mode_string
[i
]);
415 int main(int argc
, char **argv
)
417 struct rtcwake_control ctl
= {
418 .mode_str
= "suspend", /* default mode */
419 .adjfile
= _PATH_ADJTIME
,
420 .clock_mode
= CM_AUTO
422 char *devname
= DEFAULT_RTC_DEVICE
;
423 unsigned seconds
= 0;
424 int suspend
= SYSFS_MODE
;
425 int rc
= EXIT_SUCCESS
;
430 OPT_DATE
= CHAR_MAX
+ 1,
433 static const struct option long_options
[] = {
434 { "adjfile", required_argument
, NULL
, 'A' },
435 { "auto", no_argument
, NULL
, 'a' },
436 { "dry-run", no_argument
, NULL
, 'n' },
437 { "local", no_argument
, NULL
, 'l' },
438 { "utc", no_argument
, NULL
, 'u' },
439 { "verbose", no_argument
, NULL
, 'v' },
440 { "version", no_argument
, NULL
, 'V' },
441 { "help", no_argument
, NULL
, 'h' },
442 { "mode", required_argument
, NULL
, 'm' },
443 { "device", required_argument
, NULL
, 'd' },
444 { "seconds", required_argument
, NULL
, 's' },
445 { "time", required_argument
, NULL
, 't' },
446 { "date", required_argument
, NULL
, OPT_DATE
},
447 { "list-modes", no_argument
, NULL
, OPT_LIST
},
450 static const ul_excl_t excl
[] = {
452 { 's', 't', OPT_DATE
},
454 int excl_st
[ARRAY_SIZE(excl
)] = UL_EXCL_STATUS_INIT
;
456 setlocale(LC_ALL
, "");
457 bindtextdomain(PACKAGE
, LOCALEDIR
);
459 atexit(close_stdout
);
461 while ((t
= getopt_long(argc
, argv
, "A:ahd:lm:ns:t:uVv",
462 long_options
, NULL
)) != EOF
) {
463 err_exclusive_options(t
, long_options
, excl
, excl_st
);
466 /* for better compatibility with hwclock */
467 ctl
.adjfile
= optarg
;
470 ctl
.clock_mode
= CM_AUTO
;
476 ctl
.clock_mode
= CM_LOCAL
;
484 if ((suspend
= get_rtc_mode(&ctl
, optarg
)) < 0)
485 errx(EXIT_FAILURE
, _("unrecognized suspend state '%s'"), optarg
);
486 ctl
.mode_str
= optarg
;
492 /* alarm time, seconds-to-sleep (relative) */
493 seconds
= strtou32_or_err(optarg
, _("invalid seconds argument"));
496 /* alarm time, time_t (absolute, seconds since epoch) */
497 alarm
= strtou32_or_err(optarg
, _("invalid time argument"));
500 { /* alarm time, see timestamp format from manual */
502 if (parse_timestamp(optarg
, &p
) < 0)
503 errx(EXIT_FAILURE
, _("invalid time value \"%s\""), optarg
);
504 alarm
= (time_t) (p
/ 1000000);
508 ctl
.clock_mode
= CM_UTC
;
514 printf(UTIL_LINUX_VERSION
);
519 errtryhelp(EXIT_FAILURE
);
523 if (ctl
.clock_mode
== CM_AUTO
&& read_clock_mode(&ctl
) < 0) {
524 printf(_("%s: assuming RTC uses UTC ...\n"), program_invocation_short_name
);
525 ctl
.clock_mode
= CM_UTC
;
529 printf("%s", ctl
.clock_mode
== CM_UTC
? _("Using UTC time.\n") :
530 _("Using local time.\n"));
532 if (!alarm
&& !seconds
&& suspend
!= DISABLE_MODE
&& suspend
!= SHOW_MODE
)
533 errx(EXIT_FAILURE
, _("must provide wake time (see --seconds, --time and --date options)"));
535 /* device must exist and (if we'll sleep) be wakeup-enabled */
536 fd
= open_dev_rtc(devname
);
538 if (suspend
!= ON_MODE
&& suspend
!= NO_MODE
&& !is_wakeup_enabled(devname
))
539 errx(EXIT_FAILURE
, _("%s not enabled for wakeup events"), devname
);
541 /* relative or absolute alarm time, normalized to time_t */
542 if (get_basetimes(&ctl
, fd
) < 0)
546 printf(_("alarm %ld, sys_time %ld, rtc_time %ld, seconds %u\n"),
547 alarm
, ctl
.sys_time
, ctl
.rtc_time
, seconds
);
549 if (suspend
!= DISABLE_MODE
&& suspend
!= SHOW_MODE
) {
550 /* perform alarm setup when the show or disable modes are not set */
552 if (alarm
< ctl
.sys_time
)
553 errx(EXIT_FAILURE
, _("time doesn't go backward to %s"),
555 alarm
-= ctl
.sys_time
- ctl
.rtc_time
;
557 alarm
= ctl
.rtc_time
+ seconds
+ 1;
559 if (setup_alarm(&ctl
, fd
, &alarm
) < 0)
562 if (suspend
== NO_MODE
|| suspend
== ON_MODE
)
563 printf(_("%s: wakeup using %s at %s"),
564 program_invocation_short_name
, devname
,
567 printf(_("%s: wakeup from \"%s\" using %s at %s"),
568 program_invocation_short_name
, ctl
.mode_str
, devname
,
577 printf(_("suspend mode: no; leaving\n"));
578 ctl
.dryrun
= 1; /* to skip disabling alarm at the end */
586 printf(_("suspend mode: off; executing %s\n"),
588 arg
[i
++] = _PATH_SHUTDOWN
;
595 warn(_("failed to execute %s"), _PATH_SHUTDOWN
);
605 printf(_("suspend mode: on; reading rtc\n"));
608 t
= read(fd
, &data
, sizeof data
);
610 warn(_("rtc read failed"));
614 printf("... %s: %03lx\n", devname
, data
);
615 } while (!(data
& RTC_AF
));
620 /* just break, alarm gets disabled in the end */
622 printf(_("suspend mode: disable; disabling alarm\n"));
626 printf(_("suspend mode: show; printing alarm info\n"));
627 if (print_alarm(&ctl
, fd
))
629 ctl
.dryrun
= 1; /* don't really disable alarm in the end, just show */
633 printf(_("suspend mode: %s; suspending system\n"), ctl
.mode_str
);
635 suspend_system(&ctl
);
639 struct rtc_wkalrm wake
;
641 if (ioctl(fd
, RTC_WKALM_RD
, &wake
) < 0) {
642 warn(_("read rtc alarm failed"));
646 if (ioctl(fd
, RTC_WKALM_SET
, &wake
) < 0) {
647 warn(_("disable rtc alarm interrupt failed"));