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