]>
Commit | Line | Data |
---|---|---|
76700389 | 1 | /* |
9abd5e4b KZ |
2 | * SPDX-License-Identifier: GPL-2.0-or-later |
3 | * | |
4 | * This program is free software; you can redistribute it and/or modify | |
5 | * it under the terms of the GNU General Public License as published by | |
6 | * the Free Software Foundation; either version 2 of the License, or | |
7 | * (at your option) any later version. | |
8 | * | |
9 | * Copyright (C) 2007 Bernhard Walle <bwalle@suse.de> | |
10 | * Copyright (C) 2007-2023 Karel Zak <kzak@redhat.com> | |
11 | * | |
76700389 BW |
12 | * rtcwake -- enter a system sleep state until specified wakeup time. |
13 | * | |
14 | * This uses cross-platform Linux interfaces to enter a system sleep state, | |
15 | * and leave it no later than a specified time. It uses any RTC framework | |
16 | * driver that supports standard driver model wakeup flags. | |
17 | * | |
18 | * This is normally used like the old "apmsleep" utility, to wake from a | |
19 | * suspend state like ACPI S1 (standby) or S3 (suspend-to-RAM). Most | |
20 | * platforms can implement those without analogues of BIOS, APM, or ACPI. | |
21 | * | |
22 | * On some systems, this can also be used like "nvram-wakeup", waking | |
23 | * from states like ACPI S4 (suspend to disk). Not all systems have | |
24 | * persistent media that are appropriate for such suspend modes. | |
25 | * | |
26 | * The best way to set the system's RTC is so that it holds the current | |
27 | * time in UTC. Use the "-l" flag to tell this program that the system | |
28 | * RTC uses a local timezone instead (maybe you dual-boot MS-Windows). | |
2148b051 | 29 | * That flag should not be needed on systems with adjtime support. |
76700389 | 30 | */ |
5b4185e1 | 31 | #include <errno.h> |
76700389 | 32 | #include <fcntl.h> |
5b4185e1 | 33 | #include <getopt.h> |
5b4185e1 | 34 | #include <linux/rtc.h> |
c8ff2e55 | 35 | #include <poll.h> |
5b4185e1 | 36 | #include <stdio.h> |
76700389 BW |
37 | #include <stdlib.h> |
38 | #include <string.h> | |
76700389 | 39 | #include <sys/ioctl.h> |
e1686b25 | 40 | #include <sys/stat.h> |
76700389 BW |
41 | #include <sys/time.h> |
42 | #include <sys/types.h> | |
c8ff2e55 | 43 | #include <termios.h> |
5b4185e1 SK |
44 | #include <time.h> |
45 | #include <unistd.h> | |
76700389 | 46 | |
5b4185e1 SK |
47 | #include "c.h" |
48 | #include "closestream.h" | |
984a6096 | 49 | #include "env.h" |
76700389 | 50 | #include "nls.h" |
254e9e58 | 51 | #include "optutils.h" |
77f5744c | 52 | #include "pathnames.h" |
07b336c9 | 53 | #include "strutils.h" |
f87b73ab | 54 | #include "strv.h" |
a10ce9a3 | 55 | #include "timeutils.h" |
5b4185e1 | 56 | #include "xalloc.h" |
76700389 | 57 | |
356b2989 | 58 | #ifndef RTC_AF |
f0781342 | 59 | # define RTC_AF 0x20 /* Alarm interrupt */ |
356b2989 | 60 | #endif |
76700389 | 61 | |
f0781342 | 62 | #define ADJTIME_ZONE_BUFSIZ 8 |
067fde32 | 63 | #define SYS_WAKEUP_PATH_TEMPLATE "/sys/class/rtc/%s/device/power/wakeup" |
f0781342 KZ |
64 | #define SYS_POWER_STATE_PATH "/sys/power/state" |
65 | #define DEFAULT_RTC_DEVICE "/dev/rtc0" | |
6e1ec14f SK |
66 | |
67 | enum rtc_modes { /* manual page --mode option explains these. */ | |
f87b73ab | 68 | OFF_MODE = 0, |
6e1ec14f SK |
69 | NO_MODE, |
70 | ON_MODE, | |
71 | DISABLE_MODE, | |
f87b73ab SK |
72 | SHOW_MODE, |
73 | ||
74 | SYSFS_MODE /* keep it last */ | |
75 | ||
6e1ec14f SK |
76 | }; |
77 | ||
f87b73ab | 78 | static const char *rtcwake_mode_string[] = { |
6e1ec14f SK |
79 | [OFF_MODE] = "off", |
80 | [NO_MODE] = "no", | |
81 | [ON_MODE] = "on", | |
82 | [DISABLE_MODE] = "disable", | |
83 | [SHOW_MODE] = "show" | |
84 | }; | |
76700389 | 85 | |
71d95e92 | 86 | enum clock_modes { |
76700389 BW |
87 | CM_AUTO, |
88 | CM_UTC, | |
89 | CM_LOCAL | |
90 | }; | |
91 | ||
71d95e92 | 92 | struct rtcwake_control { |
f87b73ab SK |
93 | char *mode_str; /* name of the requested mode */ |
94 | char **possible_modes; /* modes listed in /sys/power/state */ | |
71d95e92 SK |
95 | char *adjfile; /* adjtime file path */ |
96 | enum clock_modes clock_mode; /* hwclock timezone */ | |
97 | time_t sys_time; /* system time */ | |
98 | time_t rtc_time; /* hardware time */ | |
f0781342 KZ |
99 | unsigned int verbose:1, /* verbose messaging */ |
100 | dryrun:1; /* do not set alarm, suspend system, etc */ | |
71d95e92 | 101 | }; |
76700389 | 102 | |
9325dbfd | 103 | static void __attribute__((__noreturn__)) usage(void) |
76700389 | 104 | { |
9325dbfd | 105 | FILE *out = stdout; |
0e00261d | 106 | fputs(USAGE_HEADER, out); |
704c7705 KZ |
107 | fprintf(out, |
108 | _(" %s [options]\n"), program_invocation_short_name); | |
109 | ||
451dbcfa BS |
110 | fputs(USAGE_SEPARATOR, out); |
111 | fputs(_("Enter a system sleep state until a specified wakeup time.\n"), out); | |
112 | ||
0e00261d | 113 | fputs(USAGE_OPTIONS, out); |
49ebda9b | 114 | fputs(_(" -a, --auto reads the clock mode from adjust file (default)\n"), out); |
3a2f3e82 KZ |
115 | fprintf(out, |
116 | _(" -A, --adjfile <file> specifies the path to the adjust file\n" | |
7528fae9 | 117 | " the default is %s\n"), _PATH_ADJTIME); |
a10ce9a3 | 118 | fputs(_(" --date <timestamp> date time of timestamp to wake\n"), out); |
09e092ad KZ |
119 | fputs(_(" -d, --device <device> select rtc device (rtc0|rtc1|...)\n"), out); |
120 | fputs(_(" -n, --dry-run does everything, but suspend\n"), out); | |
121 | fputs(_(" -l, --local RTC uses local timezone\n"), out); | |
43a44bfc | 122 | fputs(_(" --list-modes list available modes\n"), out); |
09e092ad KZ |
123 | fputs(_(" -m, --mode <mode> standby|mem|... sleep mode\n"), out); |
124 | fputs(_(" -s, --seconds <seconds> seconds to sleep\n"), out); | |
125 | fputs(_(" -t, --time <time_t> time to wake\n"), out); | |
126 | fputs(_(" -u, --utc RTC uses UTC\n"), out); | |
127 | fputs(_(" -v, --verbose verbose messages\n"), out); | |
704c7705 | 128 | |
9325dbfd | 129 | fputs(USAGE_SEPARATOR, out); |
bad4c729 MY |
130 | fprintf(out, USAGE_HELP_OPTIONS(26)); |
131 | fprintf(out, USAGE_MAN_TAIL("rtcwake(8)")); | |
9325dbfd | 132 | exit(EXIT_SUCCESS); |
76700389 BW |
133 | } |
134 | ||
f8d87ab1 | 135 | static int is_wakeup_enabled(const char *devname) |
76700389 BW |
136 | { |
137 | char buf[128], *s; | |
138 | FILE *f; | |
067fde32 | 139 | size_t skip = 0; |
76700389 | 140 | |
067fde32 SK |
141 | if (startswith(devname, "/dev/")) |
142 | skip = 5; | |
143 | snprintf(buf, sizeof buf, SYS_WAKEUP_PATH_TEMPLATE, devname + skip); | |
76700389 BW |
144 | f = fopen(buf, "r"); |
145 | if (!f) { | |
289dcc90 | 146 | warn(_("cannot open %s"), buf); |
76700389 BW |
147 | return 0; |
148 | } | |
f0781342 | 149 | |
76700389 BW |
150 | s = fgets(buf, sizeof buf, f); |
151 | fclose(f); | |
152 | if (!s) | |
153 | return 0; | |
76700389 BW |
154 | s = strchr(buf, '\n'); |
155 | if (!s) | |
156 | return 0; | |
157 | *s = 0; | |
76700389 BW |
158 | /* wakeup events could be disabled or not supported */ |
159 | return strcmp(buf, "enabled") == 0; | |
160 | } | |
161 | ||
71d95e92 | 162 | static int get_basetimes(struct rtcwake_control *ctl, int fd) |
76700389 | 163 | { |
f0781342 | 164 | struct tm tm = { 0 }; |
76700389 BW |
165 | struct rtc_time rtc; |
166 | ||
5b4185e1 | 167 | /* This process works in RTC time, except when working |
76700389 BW |
168 | * with the system clock (which always uses UTC). |
169 | */ | |
71d95e92 | 170 | if (ctl->clock_mode == CM_UTC) |
984a6096 | 171 | xsetenv("TZ", "UTC", 1); |
76700389 | 172 | tzset(); |
5b4185e1 | 173 | /* Read rtc and system clocks "at the same time", or as |
76700389 BW |
174 | * precisely (+/- a second) as we can read them. |
175 | */ | |
176 | if (ioctl(fd, RTC_RD_TIME, &rtc) < 0) { | |
07b336c9 | 177 | warn(_("read rtc time failed")); |
f8d87ab1 | 178 | return -1; |
76700389 | 179 | } |
f0781342 | 180 | |
87918040 | 181 | ctl->sys_time = time(NULL); |
71d95e92 | 182 | if (ctl->sys_time == (time_t)-1) { |
07b336c9 | 183 | warn(_("read system time failed")); |
f8d87ab1 | 184 | return -1; |
76700389 | 185 | } |
5b4185e1 | 186 | /* Convert rtc_time to normal arithmetic-friendly form, |
76700389 BW |
187 | * updating tm.tm_wday as used by asctime(). |
188 | */ | |
76700389 BW |
189 | tm.tm_sec = rtc.tm_sec; |
190 | tm.tm_min = rtc.tm_min; | |
191 | tm.tm_hour = rtc.tm_hour; | |
192 | tm.tm_mday = rtc.tm_mday; | |
193 | tm.tm_mon = rtc.tm_mon; | |
194 | tm.tm_year = rtc.tm_year; | |
1da17ec6 | 195 | tm.tm_isdst = -1; /* assume the system knows better than the RTC */ |
f0781342 | 196 | |
71d95e92 | 197 | ctl->rtc_time = mktime(&tm); |
71d95e92 | 198 | if (ctl->rtc_time == (time_t)-1) { |
07b336c9 | 199 | warn(_("convert rtc time failed")); |
f8d87ab1 | 200 | return -1; |
76700389 | 201 | } |
f0781342 | 202 | |
71d95e92 | 203 | if (ctl->verbose) { |
2148b051 DB |
204 | /* Unless the system uses UTC, either delta or tzone |
205 | * reflects a seconds offset from UTC. The value can | |
5b4185e1 | 206 | * help sort out problems like bugs in your C library. */ |
3160589d | 207 | char s[64]; |
01b37aff | 208 | printf("\tdelta = %"PRId64"\n", (int64_t) ctl->sys_time - ctl->rtc_time); |
2148b051 | 209 | printf("\ttzone = %ld\n", timezone); |
2148b051 | 210 | printf("\ttzname = %s\n", tzname[daylight]); |
f6b6beaf | 211 | gmtime_r(&ctl->sys_time, &tm); |
01b37aff KZ |
212 | printf("\tsystime = %"PRId64", (UTC) %s", |
213 | (int64_t) ctl->sys_time, asctime_r(&tm, s)); | |
f6b6beaf | 214 | gmtime_r(&ctl->rtc_time, &tm); |
01b37aff KZ |
215 | printf("\trtctime = %"PRId64", (UTC) %s", |
216 | (int64_t) ctl->rtc_time, asctime_r(&tm, s)); | |
76700389 | 217 | } |
f8d87ab1 | 218 | return 0; |
76700389 BW |
219 | } |
220 | ||
71d95e92 | 221 | static int setup_alarm(struct rtcwake_control *ctl, int fd, time_t *wakeup) |
76700389 | 222 | { |
3160589d | 223 | struct tm tm; |
76037ac7 | 224 | struct rtc_wkalrm wake = { 0 }; |
76700389 | 225 | |
5b4185e1 SK |
226 | /* The wakeup time is in POSIX time (more or less UTC). Ideally |
227 | * RTCs use that same time; but PCs can't do that if they need to | |
228 | * boot MS-Windows. Messy... | |
1b7c164c | 229 | * |
5b4185e1 SK |
230 | * When clock_mode == CM_UTC this process's timezone is UTC, so |
231 | * we'll pass a UTC date to the RTC. | |
1b7c164c | 232 | * |
5b4185e1 SK |
233 | * Else clock_mode == CM_LOCAL so the time given to the RTC will |
234 | * instead use the local time zone. */ | |
3160589d SK |
235 | localtime_r(wakeup, &tm); |
236 | wake.time.tm_sec = tm.tm_sec; | |
237 | wake.time.tm_min = tm.tm_min; | |
238 | wake.time.tm_hour = tm.tm_hour; | |
239 | wake.time.tm_mday = tm.tm_mday; | |
240 | wake.time.tm_mon = tm.tm_mon; | |
241 | wake.time.tm_year = tm.tm_year; | |
5b4185e1 | 242 | /* wday, yday, and isdst fields are unused */ |
2148b051 DB |
243 | wake.time.tm_wday = -1; |
244 | wake.time.tm_yday = -1; | |
245 | wake.time.tm_isdst = -1; | |
fc181184 | 246 | wake.enabled = 1; |
f0781342 | 247 | |
71d95e92 | 248 | if (!ctl->dryrun && ioctl(fd, RTC_WKALM_SET, &wake) < 0) { |
5fde2f0d SK |
249 | warn(_("set rtc wake alarm failed")); |
250 | return -1; | |
76700389 | 251 | } |
f8d87ab1 | 252 | return 0; |
76700389 BW |
253 | } |
254 | ||
f87b73ab | 255 | static char **get_sys_power_states(struct rtcwake_control *ctl) |
e6d1dc94 | 256 | { |
6b497c0e KZ |
257 | int fd = -1; |
258 | ||
f87b73ab | 259 | if (!ctl->possible_modes) { |
f87b73ab | 260 | char buf[256] = { 0 }; |
d3149a85 | 261 | ssize_t ss; |
f87b73ab | 262 | |
6b497c0e KZ |
263 | fd = open(SYS_POWER_STATE_PATH, O_RDONLY); |
264 | if (fd < 0) | |
265 | goto nothing; | |
d3149a85 KZ |
266 | ss = read(fd, &buf, sizeof(buf) - 1); |
267 | if (ss <= 0) | |
6b497c0e | 268 | goto nothing; |
d3149a85 | 269 | buf[ss] = '\0'; |
f87b73ab | 270 | ctl->possible_modes = strv_split(buf, " \n"); |
6b497c0e | 271 | close(fd); |
f87b73ab SK |
272 | } |
273 | return ctl->possible_modes; | |
6b497c0e KZ |
274 | nothing: |
275 | if (fd >= 0) | |
276 | close(fd); | |
277 | return NULL; | |
e6d1dc94 LR |
278 | } |
279 | ||
c8ff2e55 SK |
280 | static void wait_stdin(struct rtcwake_control *ctl) |
281 | { | |
282 | struct pollfd fd[] = { | |
283 | {.fd = STDIN_FILENO, .events = POLLIN} | |
284 | }; | |
285 | int tries = 0; | |
286 | ||
287 | while (tries < 8 && poll(fd, 1, 10) == 1) { | |
288 | if (ctl->verbose) | |
289 | warnx(_("discarding stdin")); | |
290 | xusleep(250000); | |
291 | tcflush(STDIN_FILENO, TCIFLUSH); | |
292 | tries++; | |
293 | } | |
294 | } | |
295 | ||
f87b73ab | 296 | static void suspend_system(struct rtcwake_control *ctl) |
76700389 | 297 | { |
d3cf5414 | 298 | FILE *f = fopen(SYS_POWER_STATE_PATH, "w"); |
76700389 BW |
299 | |
300 | if (!f) { | |
289dcc90 | 301 | warn(_("cannot open %s"), SYS_POWER_STATE_PATH); |
76700389 BW |
302 | return; |
303 | } | |
f0781342 | 304 | |
71d95e92 | 305 | if (!ctl->dryrun) { |
c8ff2e55 SK |
306 | if (isatty(STDIN_FILENO)) |
307 | wait_stdin(ctl); | |
f87b73ab | 308 | fprintf(f, "%s\n", ctl->mode_str); |
569f3ca2 KZ |
309 | fflush(f); |
310 | } | |
76700389 | 311 | /* this executes after wake from suspend */ |
efb8854f SK |
312 | if (close_stream(f)) |
313 | errx(EXIT_FAILURE, _("write error")); | |
76700389 BW |
314 | } |
315 | ||
71d95e92 | 316 | static int read_clock_mode(struct rtcwake_control *ctl) |
76700389 BW |
317 | { |
318 | FILE *fp; | |
f0781342 | 319 | char linebuf[ADJTIME_ZONE_BUFSIZ]; |
76700389 | 320 | |
71d95e92 | 321 | fp = fopen(ctl->adjfile, "r"); |
76700389 | 322 | if (!fp) |
f8d87ab1 | 323 | return -1; |
3e5a5455 SK |
324 | /* skip two lines */ |
325 | if (skip_fline(fp) || skip_fline(fp)) { | |
76700389 | 326 | fclose(fp); |
f8d87ab1 | 327 | return -1; |
76700389 | 328 | } |
76700389 | 329 | /* read third line */ |
3e5a5455 | 330 | if (!fgets(linebuf, sizeof linebuf, fp)) { |
76700389 | 331 | fclose(fp); |
f8d87ab1 | 332 | return -1; |
76700389 | 333 | } |
f0781342 | 334 | |
76700389 | 335 | if (strncmp(linebuf, "UTC", 3) == 0) |
71d95e92 | 336 | ctl->clock_mode = CM_UTC; |
76700389 | 337 | else if (strncmp(linebuf, "LOCAL", 5) == 0) |
71d95e92 | 338 | ctl->clock_mode = CM_LOCAL; |
3e5a5455 SK |
339 | else if (ctl->verbose) |
340 | warnx(_("unexpected third line in: %s: %s"), ctl->adjfile, linebuf); | |
f0781342 | 341 | |
76700389 | 342 | fclose(fp); |
f8d87ab1 | 343 | return 0; |
76700389 BW |
344 | } |
345 | ||
71d95e92 | 346 | static int print_alarm(struct rtcwake_control *ctl, int fd) |
fcf67294 MO |
347 | { |
348 | struct rtc_wkalrm wake; | |
499a0c79 | 349 | struct tm tm = { 0 }; |
fcf67294 | 350 | time_t alarm; |
3160589d | 351 | char s[CTIME_BUFSIZ]; |
fcf67294 | 352 | |
fcf67294 | 353 | if (ioctl(fd, RTC_WKALM_RD, &wake) < 0) { |
5fde2f0d SK |
354 | warn(_("read rtc alarm failed")); |
355 | return -1; | |
fcf67294 | 356 | } |
f0781342 | 357 | |
fcf67294 MO |
358 | if (wake.enabled != 1 || wake.time.tm_year == -1) { |
359 | printf(_("alarm: off\n")); | |
360 | return 0; | |
361 | } | |
499a0c79 SK |
362 | tm.tm_sec = wake.time.tm_sec; |
363 | tm.tm_min = wake.time.tm_min; | |
364 | tm.tm_hour = wake.time.tm_hour; | |
365 | tm.tm_mday = wake.time.tm_mday; | |
366 | tm.tm_mon = wake.time.tm_mon; | |
367 | tm.tm_year = wake.time.tm_year; | |
fcf67294 | 368 | tm.tm_isdst = -1; /* assume the system knows better than the RTC */ |
f0781342 | 369 | |
fcf67294 MO |
370 | alarm = mktime(&tm); |
371 | if (alarm == (time_t)-1) { | |
07b336c9 | 372 | warn(_("convert time failed")); |
fcf67294 MO |
373 | return -1; |
374 | } | |
fcf67294 | 375 | /* 0 if both UTC, or expresses diff if RTC in local time */ |
71d95e92 | 376 | alarm += ctl->sys_time - ctl->rtc_time; |
3160589d SK |
377 | ctime_r(&alarm, s); |
378 | printf(_("alarm: on %s"), s); | |
f0781342 | 379 | |
fcf67294 MO |
380 | return 0; |
381 | } | |
382 | ||
eb2306e6 | 383 | static int get_rtc_mode(struct rtcwake_control *ctl, const char *s) |
6e1ec14f | 384 | { |
f87b73ab SK |
385 | size_t i; |
386 | char **modes = get_sys_power_states(ctl), **m; | |
6e1ec14f | 387 | |
f87b73ab | 388 | STRV_FOREACH(m, modes) { |
eb2306e6 | 389 | if (strcmp(s, *m) == 0) |
f87b73ab SK |
390 | return SYSFS_MODE; |
391 | } | |
392 | ||
bac778c0 | 393 | for (i = 0; i < ARRAY_SIZE(rtcwake_mode_string); i++) |
eb2306e6 | 394 | if (!strcmp(s, rtcwake_mode_string[i])) |
6e1ec14f | 395 | return i; |
f87b73ab | 396 | |
6e1ec14f SK |
397 | return -EINVAL; |
398 | } | |
399 | ||
067fde32 SK |
400 | static int open_dev_rtc(const char *devname) |
401 | { | |
402 | int fd; | |
403 | char *devpath = NULL; | |
404 | ||
405 | if (startswith(devname, "/dev")) | |
406 | devpath = xstrdup(devname); | |
407 | else | |
408 | xasprintf(&devpath, "/dev/%s", devname); | |
409 | fd = open(devpath, O_RDONLY | O_CLOEXEC); | |
410 | if (fd < 0) | |
411 | err(EXIT_FAILURE, _("%s: unable to find device"), devpath); | |
412 | free(devpath); | |
413 | return fd; | |
414 | } | |
415 | ||
f87b73ab | 416 | static void list_modes(struct rtcwake_control *ctl) |
43a44bfc | 417 | { |
f87b73ab SK |
418 | size_t i; |
419 | char **modes = get_sys_power_states(ctl), **m; | |
420 | ||
421 | if (!modes) | |
422 | errx(EXIT_FAILURE, _("could not read: %s"), SYS_POWER_STATE_PATH); | |
43a44bfc | 423 | |
f87b73ab SK |
424 | STRV_FOREACH(m, modes) |
425 | printf("%s ", *m); | |
426 | ||
427 | for (i = 0; i < ARRAY_SIZE(rtcwake_mode_string); i++) | |
428 | printf("%s ", rtcwake_mode_string[i]); | |
43a44bfc SK |
429 | putchar('\n'); |
430 | } | |
431 | ||
76700389 BW |
432 | int main(int argc, char **argv) |
433 | { | |
71d95e92 | 434 | struct rtcwake_control ctl = { |
f87b73ab | 435 | .mode_str = "suspend", /* default mode */ |
71d95e92 | 436 | .adjfile = _PATH_ADJTIME, |
f0781342 | 437 | .clock_mode = CM_AUTO |
71d95e92 | 438 | }; |
f0781342 | 439 | char *devname = DEFAULT_RTC_DEVICE; |
f87b73ab | 440 | int suspend = SYSFS_MODE; |
5b4185e1 SK |
441 | int rc = EXIT_SUCCESS; |
442 | int t; | |
443 | int fd; | |
01b37aff | 444 | time_t alarm = 0, seconds = 0; |
a10ce9a3 | 445 | enum { |
43a44bfc SK |
446 | OPT_DATE = CHAR_MAX + 1, |
447 | OPT_LIST | |
a10ce9a3 | 448 | }; |
3a2f3e82 | 449 | static const struct option long_options[] = { |
87918040 SK |
450 | { "adjfile", required_argument, NULL, 'A' }, |
451 | { "auto", no_argument, NULL, 'a' }, | |
452 | { "dry-run", no_argument, NULL, 'n' }, | |
453 | { "local", no_argument, NULL, 'l' }, | |
454 | { "utc", no_argument, NULL, 'u' }, | |
455 | { "verbose", no_argument, NULL, 'v' }, | |
456 | { "version", no_argument, NULL, 'V' }, | |
457 | { "help", no_argument, NULL, 'h' }, | |
458 | { "mode", required_argument, NULL, 'm' }, | |
459 | { "device", required_argument, NULL, 'd' }, | |
460 | { "seconds", required_argument, NULL, 's' }, | |
461 | { "time", required_argument, NULL, 't' }, | |
462 | { "date", required_argument, NULL, OPT_DATE }, | |
463 | { "list-modes", no_argument, NULL, OPT_LIST }, | |
464 | { NULL, 0, NULL, 0 } | |
3a2f3e82 | 465 | }; |
254e9e58 SK |
466 | static const ul_excl_t excl[] = { |
467 | { 'a', 'l', 'u' }, | |
468 | { 's', 't', OPT_DATE }, | |
d3149a85 | 469 | { 0 }, |
254e9e58 SK |
470 | }; |
471 | int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT; | |
3a2f3e82 | 472 | |
76700389 BW |
473 | setlocale(LC_ALL, ""); |
474 | bindtextdomain(PACKAGE, LOCALEDIR); | |
475 | textdomain(PACKAGE); | |
2c308875 | 476 | close_stdout_atexit(); |
f87b73ab | 477 | |
3a2f3e82 | 478 | while ((t = getopt_long(argc, argv, "A:ahd:lm:ns:t:uVv", |
76700389 | 479 | long_options, NULL)) != EOF) { |
254e9e58 | 480 | err_exclusive_options(t, long_options, excl, excl_st); |
76700389 | 481 | switch (t) { |
3a2f3e82 KZ |
482 | case 'A': |
483 | /* for better compatibility with hwclock */ | |
71d95e92 | 484 | ctl.adjfile = optarg; |
3a2f3e82 | 485 | break; |
76700389 | 486 | case 'a': |
6e1ec14f | 487 | ctl.clock_mode = CM_AUTO; |
76700389 | 488 | break; |
76700389 | 489 | case 'd': |
c1196d3a | 490 | devname = optarg; |
76700389 | 491 | break; |
76700389 | 492 | case 'l': |
71d95e92 | 493 | ctl.clock_mode = CM_LOCAL; |
76700389 BW |
494 | break; |
495 | ||
43a44bfc | 496 | case OPT_LIST: |
f87b73ab | 497 | list_modes(&ctl); |
43a44bfc SK |
498 | return EXIT_SUCCESS; |
499 | ||
76700389 | 500 | case 'm': |
f87b73ab | 501 | if ((suspend = get_rtc_mode(&ctl, optarg)) < 0) |
6e1ec14f | 502 | errx(EXIT_FAILURE, _("unrecognized suspend state '%s'"), optarg); |
f87b73ab | 503 | ctl.mode_str = optarg; |
07b336c9 | 504 | break; |
569f3ca2 | 505 | case 'n': |
71d95e92 | 506 | ctl.dryrun = 1; |
569f3ca2 | 507 | break; |
76700389 | 508 | case 's': |
a10ce9a3 | 509 | /* alarm time, seconds-to-sleep (relative) */ |
01b37aff | 510 | seconds = strtotime_or_err(optarg, _("invalid seconds argument")); |
76700389 | 511 | break; |
76700389 | 512 | case 't': |
9e930041 | 513 | /* alarm time, time_t (absolute, seconds since epoch) */ |
01b37aff | 514 | alarm = strtotime_or_err(optarg, _("invalid time argument")); |
76700389 | 515 | break; |
a10ce9a3 | 516 | case OPT_DATE: |
5b4185e1 | 517 | { /* alarm time, see timestamp format from manual */ |
a10ce9a3 SK |
518 | usec_t p; |
519 | if (parse_timestamp(optarg, &p) < 0) | |
520 | errx(EXIT_FAILURE, _("invalid time value \"%s\""), optarg); | |
521 | alarm = (time_t) (p / 1000000); | |
522 | break; | |
523 | } | |
76700389 | 524 | case 'u': |
71d95e92 | 525 | ctl.clock_mode = CM_UTC; |
76700389 | 526 | break; |
76700389 | 527 | case 'v': |
71d95e92 | 528 | ctl.verbose = 1; |
76700389 | 529 | break; |
2c308875 | 530 | |
76700389 | 531 | case 'V': |
2c308875 | 532 | print_version(EXIT_SUCCESS); |
76700389 | 533 | case 'h': |
9325dbfd | 534 | usage(); |
76700389 | 535 | default: |
9325dbfd | 536 | errtryhelp(EXIT_FAILURE); |
76700389 BW |
537 | } |
538 | } | |
f0781342 | 539 | |
74ce680a SK |
540 | if (ctl.clock_mode == CM_AUTO && read_clock_mode(&ctl) < 0) { |
541 | printf(_("%s: assuming RTC uses UTC ...\n"), program_invocation_short_name); | |
542 | ctl.clock_mode = CM_UTC; | |
76700389 | 543 | } |
f0781342 | 544 | |
71d95e92 SK |
545 | if (ctl.verbose) |
546 | printf("%s", ctl.clock_mode == CM_UTC ? _("Using UTC time.\n") : | |
2148b051 | 547 | _("Using local time.\n")); |
2626f123 KZ |
548 | |
549 | if (!alarm && !seconds && suspend != DISABLE_MODE && suspend != SHOW_MODE) | |
550 | errx(EXIT_FAILURE, _("must provide wake time (see --seconds, --time and --date options)")); | |
551 | ||
067fde32 SK |
552 | /* device must exist and (if we'll sleep) be wakeup-enabled */ |
553 | fd = open_dev_rtc(devname); | |
f0781342 | 554 | |
6e1ec14f | 555 | if (suspend != ON_MODE && suspend != NO_MODE && !is_wakeup_enabled(devname)) |
07b336c9 | 556 | errx(EXIT_FAILURE, _("%s not enabled for wakeup events"), devname); |
f87b73ab | 557 | |
76700389 | 558 | /* relative or absolute alarm time, normalized to time_t */ |
71d95e92 | 559 | if (get_basetimes(&ctl, fd) < 0) |
76700389 | 560 | exit(EXIT_FAILURE); |
f87b73ab | 561 | |
71d95e92 | 562 | if (ctl.verbose) |
01b37aff KZ |
563 | printf(_("alarm %"PRId64", sys_time %"PRId64", " |
564 | "rtc_time %"PRId64", seconds %"PRIu64"\n"), | |
565 | (int64_t) alarm, (int64_t) ctl.sys_time, | |
566 | (int64_t) ctl.rtc_time, | |
567 | (int64_t) seconds); | |
f87b73ab | 568 | |
6e1ec14f | 569 | if (suspend != DISABLE_MODE && suspend != SHOW_MODE) { |
f0781342 | 570 | /* perform alarm setup when the show or disable modes are not set */ |
fcf67294 | 571 | if (alarm) { |
3160589d SK |
572 | if (alarm < ctl.sys_time) { |
573 | char s[CTIME_BUFSIZ]; | |
574 | ||
575 | ctime_r(&alarm, s); | |
576 | errx(EXIT_FAILURE, _("time doesn't go backward to %s"), s); | |
577 | } | |
e72027da | 578 | alarm -= ctl.sys_time - ctl.rtc_time; |
fcf67294 | 579 | } else |
71d95e92 | 580 | alarm = ctl.rtc_time + seconds + 1; |
f0781342 | 581 | |
71d95e92 | 582 | if (setup_alarm(&ctl, fd, &alarm) < 0) |
fcf67294 | 583 | exit(EXIT_FAILURE); |
f0781342 | 584 | |
3160589d SK |
585 | if (suspend == NO_MODE || suspend == ON_MODE) { |
586 | char s[CTIME_BUFSIZ]; | |
587 | ||
588 | ctime_r(&alarm, s); | |
07b336c9 | 589 | printf(_("%s: wakeup using %s at %s"), |
3160589d SK |
590 | program_invocation_short_name, devname, s); |
591 | } else { | |
592 | char s[CTIME_BUFSIZ]; | |
593 | ||
594 | ctime_r(&alarm, s); | |
07b336c9 | 595 | printf(_("%s: wakeup from \"%s\" using %s at %s"), |
3160589d SK |
596 | program_invocation_short_name, ctl.mode_str, devname, s); |
597 | } | |
fcf67294 | 598 | fflush(stdout); |
a5bd7939 | 599 | xusleep(10 * 1000); |
fcf67294 | 600 | } |
f87b73ab | 601 | |
64a80678 SK |
602 | switch (suspend) { |
603 | case NO_MODE: | |
71d95e92 | 604 | if (ctl.verbose) |
ecd55f96 | 605 | printf(_("suspend mode: no; leaving\n")); |
71d95e92 | 606 | ctl.dryrun = 1; /* to skip disabling alarm at the end */ |
64a80678 | 607 | break; |
64a80678 SK |
608 | case OFF_MODE: |
609 | { | |
caf60f22 | 610 | char *arg[5]; |
77f5744c KZ |
611 | int i = 0; |
612 | ||
e1686b25 JC |
613 | if (!access(_PATH_SHUTDOWN, X_OK)) { |
614 | arg[i++] = _PATH_SHUTDOWN; | |
615 | arg[i++] = "-h"; | |
616 | arg[i++] = "-P"; | |
617 | arg[i++] = "now"; | |
618 | arg[i] = NULL; | |
619 | } else if (!access(_PATH_POWEROFF, X_OK)) { | |
620 | arg[i++] = _PATH_POWEROFF; | |
621 | arg[i] = NULL; | |
622 | } else { | |
623 | arg[i] = NULL; | |
624 | } | |
625 | ||
626 | if (arg[0]) { | |
627 | if (ctl.verbose) | |
628 | printf(_("suspend mode: off; executing %s\n"), | |
629 | arg[0]); | |
630 | if (!ctl.dryrun) { | |
631 | execv(arg[0], arg); | |
632 | warn(_("failed to execute %s"), arg[0]); | |
633 | rc = EX_EXEC_ENOENT; | |
634 | } | |
635 | } else { | |
636 | /* Failed to find shutdown command */ | |
637 | warn(_("failed to find shutdown command")); | |
638 | rc = EX_EXEC_ENOENT; | |
569f3ca2 | 639 | } |
64a80678 SK |
640 | break; |
641 | } | |
642 | case ON_MODE: | |
643 | { | |
76700389 BW |
644 | unsigned long data; |
645 | ||
71d95e92 | 646 | if (ctl.verbose) |
ecd55f96 | 647 | printf(_("suspend mode: on; reading rtc\n")); |
71d95e92 | 648 | if (!ctl.dryrun) { |
569f3ca2 KZ |
649 | do { |
650 | t = read(fd, &data, sizeof data); | |
651 | if (t < 0) { | |
07b336c9 | 652 | warn(_("rtc read failed")); |
569f3ca2 KZ |
653 | break; |
654 | } | |
71d95e92 | 655 | if (ctl.verbose) |
569f3ca2 KZ |
656 | printf("... %s: %03lx\n", devname, data); |
657 | } while (!(data & RTC_AF)); | |
658 | } | |
64a80678 SK |
659 | break; |
660 | } | |
661 | case DISABLE_MODE: | |
c15dd93b | 662 | /* just break, alarm gets disabled in the end */ |
71d95e92 | 663 | if (ctl.verbose) |
c15dd93b | 664 | printf(_("suspend mode: disable; disabling alarm\n")); |
64a80678 | 665 | break; |
64a80678 | 666 | case SHOW_MODE: |
71d95e92 | 667 | if (ctl.verbose) |
fcf67294 | 668 | printf(_("suspend mode: show; printing alarm info\n")); |
71d95e92 | 669 | if (print_alarm(&ctl, fd)) |
fcf67294 | 670 | rc = EXIT_FAILURE; |
71d95e92 | 671 | ctl.dryrun = 1; /* don't really disable alarm in the end, just show */ |
64a80678 | 672 | break; |
64a80678 | 673 | default: |
71d95e92 | 674 | if (ctl.verbose) |
f87b73ab | 675 | printf(_("suspend mode: %s; suspending system\n"), ctl.mode_str); |
ecd55f96 | 676 | sync(); |
f87b73ab | 677 | suspend_system(&ctl); |
76700389 BW |
678 | } |
679 | ||
71d95e92 | 680 | if (!ctl.dryrun) { |
829eab67 G |
681 | struct rtc_wkalrm wake; |
682 | ||
683 | if (ioctl(fd, RTC_WKALM_RD, &wake) < 0) { | |
5fde2f0d SK |
684 | warn(_("read rtc alarm failed")); |
685 | rc = EXIT_FAILURE; | |
829eab67 G |
686 | } else { |
687 | wake.enabled = 0; | |
688 | if (ioctl(fd, RTC_WKALM_SET, &wake) < 0) { | |
689 | warn(_("disable rtc alarm interrupt failed")); | |
690 | rc = EXIT_FAILURE; | |
691 | } | |
692 | } | |
693 | } | |
f0781342 | 694 | |
76700389 | 695 | close(fd); |
77f5744c | 696 | return rc; |
76700389 | 697 | } |