]> git.ipfire.org Git - thirdparty/util-linux.git/blame - sys-utils/rtcwake.c
mkswap: fix memory leaks, cleanup check_blocks()
[thirdparty/util-linux.git] / sys-utils / rtcwake.c
CommitLineData
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
22#include <stdio.h>
23#include <getopt.h>
24#include <fcntl.h>
c41e1340 25#include <libgen.h>
76700389
BW
26#include <stdlib.h>
27#include <string.h>
28#include <unistd.h>
29#include <errno.h>
30#include <time.h>
31
32#include <sys/ioctl.h>
33#include <sys/time.h>
34#include <sys/types.h>
35
36#include <linux/rtc.h>
37
38#include "nls.h"
437fa54f 39#include "usleep.h"
76700389
BW
40
41/* constants from legacy PC/AT hardware */
42#define RTC_PF 0x40
43#define RTC_AF 0x20
44#define RTC_UF 0x10
45
46#define MAX_LINE 1024
47
48static char *progname;
49
50#define VERSION_STRING "rtcwake from " PACKAGE_STRING
51#define RTC_PATH "/sys/class/rtc/%s/device/power/wakeup"
d3cf5414 52#define SYS_POWER_STATE_PATH "/sys/power/state"
76700389
BW
53#define ADJTIME_PATH "/etc/adjtime"
54#define DEFAULT_DEVICE "/dev/rtc0"
47bf8ef7 55#define DEFAULT_MODE "standby"
76700389
BW
56
57enum ClockMode {
58 CM_AUTO,
59 CM_UTC,
60 CM_LOCAL
61};
62
63static unsigned verbose;
64enum ClockMode clock_mode = CM_AUTO;
65
66static struct option long_options[] = {
67 {"auto", no_argument, 0, 'a'},
68 {"local", no_argument, 0, 'l'},
69 {"utc", no_argument, 0, 'u'},
70 {"verbose", no_argument, 0, 'v'},
71 {"version", no_argument, 0, 'V'},
72 {"help", no_argument, 0, 'h'},
73 {"mode", required_argument, 0, 'm'},
74 {"device", required_argument, 0, 'd'},
75 {"seconds", required_argument, 0, 's'},
76 {"time", required_argument, 0, 't'},
77 {0, 0, 0, 0 }
78};
79
80static void usage(int retval)
81{
82 printf(_("usage: %s [options]\n"
83 " -d | --device <device> select rtc device (rtc0|rtc1|...)\n"
84 " -l | --local RTC uses local timezone\n"
85 " -m | --mode standby|mem|... sleep mode\n"
86 " -s | --seconds <seconds> seconds to sleep\n"
87 " -t | --time <time_t> time to wake\n"
88 " -u | --utc RTC uses UTC\n"
89 " -v | --verbose verbose messages\n"
90 " -V | --version show version\n"),
91 progname);
92 exit(retval);
93}
94
f8d87ab1 95static int is_wakeup_enabled(const char *devname)
76700389
BW
96{
97 char buf[128], *s;
98 FILE *f;
99
100 /* strip the '/dev/' from the devname here */
101 snprintf(buf, sizeof buf, RTC_PATH, devname + strlen("/dev/"));
102 f = fopen(buf, "r");
103 if (!f) {
104 perror(buf);
105 return 0;
106 }
107 s = fgets(buf, sizeof buf, f);
108 fclose(f);
109 if (!s)
110 return 0;
111
112 s = strchr(buf, '\n');
113 if (!s)
114 return 0;
115 *s = 0;
116
117 /* wakeup events could be disabled or not supported */
118 return strcmp(buf, "enabled") == 0;
119}
120
121/* all times should be in UTC */
122static time_t sys_time;
123static time_t rtc_time;
124
125static int get_basetimes(int fd)
126{
127 struct tm tm;
128 struct rtc_time rtc;
129
130 /* this process works in RTC time, except when working
131 * with the system clock (which always uses UTC).
132 */
133 if (clock_mode == CM_UTC)
134 setenv("TZ", "UTC", 1);
135 tzset();
136
137 /* read rtc and system clocks "at the same time", or as
138 * precisely (+/- a second) as we can read them.
139 */
140 if (ioctl(fd, RTC_RD_TIME, &rtc) < 0) {
141 perror(_("read rtc time"));
f8d87ab1 142 return -1;
76700389
BW
143 }
144 sys_time = time(0);
145 if (sys_time == (time_t)-1) {
146 perror(_("read system time"));
f8d87ab1 147 return -1;
76700389
BW
148 }
149
150 /* convert rtc_time to normal arithmetic-friendly form,
151 * updating tm.tm_wday as used by asctime().
152 */
153 memset(&tm, 0, sizeof tm);
154 tm.tm_sec = rtc.tm_sec;
155 tm.tm_min = rtc.tm_min;
156 tm.tm_hour = rtc.tm_hour;
157 tm.tm_mday = rtc.tm_mday;
158 tm.tm_mon = rtc.tm_mon;
159 tm.tm_year = rtc.tm_year;
160 tm.tm_isdst = rtc.tm_isdst; /* stays unspecified? */
161 rtc_time = mktime(&tm);
162
163 if (rtc_time == (time_t)-1) {
164 perror(_("convert rtc time"));
f8d87ab1 165 return -1;
76700389
BW
166 }
167
168 if (verbose) {
2148b051
DB
169 /* Unless the system uses UTC, either delta or tzone
170 * reflects a seconds offset from UTC. The value can
171 * help sort out problems like bugs in your C library.
172 */
173 printf("\tdelta = %ld\n", sys_time - rtc_time);
174 printf("\ttzone = %ld\n", timezone);
175
176 printf("\ttzname = %s\n", tzname[daylight]);
177 gmtime_r(&rtc_time, &tm);
178 printf("\tsystime = %ld, (UTC) %s",
76700389 179 (long) sys_time, asctime(gmtime(&sys_time)));
2148b051 180 printf("\trtctime = %ld, (UTC) %s",
76700389
BW
181 (long) rtc_time, asctime(&tm));
182 }
183
f8d87ab1 184 return 0;
76700389
BW
185}
186
187static int setup_alarm(int fd, time_t *wakeup)
188{
189 struct tm *tm;
190 struct rtc_wkalrm wake;
191
1b7c164c
DB
192 /* The wakeup time is in POSIX time (more or less UTC).
193 * Ideally RTCs use that same time; but PCs can't do that
194 * if they need to boot MS-Windows. Messy...
195 *
196 * When clock_mode == CM_UTC this process's timezone is UTC,
197 * so we'll pass a UTC date to the RTC.
198 *
199 * Else clock_mode == CM_LOCAL so the time given to the RTC
200 * will instead use the local time zone.
201 */
202 tm = localtime(wakeup);
76700389
BW
203
204 wake.time.tm_sec = tm->tm_sec;
205 wake.time.tm_min = tm->tm_min;
206 wake.time.tm_hour = tm->tm_hour;
207 wake.time.tm_mday = tm->tm_mday;
208 wake.time.tm_mon = tm->tm_mon;
209 wake.time.tm_year = tm->tm_year;
2148b051
DB
210 /* wday, yday, and isdst fields are unused by Linux */
211 wake.time.tm_wday = -1;
212 wake.time.tm_yday = -1;
213 wake.time.tm_isdst = -1;
76700389 214
fc181184
GB
215 wake.enabled = 1;
216 /* First try the preferred RTC_WKALM_SET */
217 if (ioctl(fd, RTC_WKALM_SET, &wake) < 0) {
218 wake.enabled = 0;
219 /* Fall back on the non-preferred way of setting wakeups; only
220 * works for alarms < 24 hours from now */
221 if ((rtc_time + (24 * 60 * 60)) > *wakeup) {
222 if (ioctl(fd, RTC_ALM_SET, &wake.time) < 0) {
223 perror(_("set rtc alarm"));
224 return -1;
225 }
226 if (ioctl(fd, RTC_AIE_ON, 0) < 0) {
227 perror(_("enable rtc alarm"));
228 return -1;
229 }
230 } else {
76700389 231 perror(_("set rtc wake alarm"));
fc181184 232 return -1;
76700389
BW
233 }
234 }
235
f8d87ab1 236 return 0;
76700389
BW
237}
238
239static void suspend_system(const char *suspend)
240{
d3cf5414 241 FILE *f = fopen(SYS_POWER_STATE_PATH, "w");
76700389
BW
242
243 if (!f) {
d3cf5414 244 perror(SYS_POWER_STATE_PATH);
76700389
BW
245 return;
246 }
247
248 fprintf(f, "%s\n", suspend);
249 fflush(f);
250
251 /* this executes after wake from suspend */
252 fclose(f);
253}
254
255
256static int read_clock_mode(void)
257{
258 FILE *fp;
259 char linebuf[MAX_LINE];
260
261 fp = fopen(ADJTIME_PATH, "r");
262 if (!fp)
f8d87ab1 263 return -1;
76700389
BW
264
265 /* skip first line */
266 if (!fgets(linebuf, MAX_LINE, fp)) {
267 fclose(fp);
f8d87ab1 268 return -1;
76700389
BW
269 }
270
271 /* skip second line */
272 if (!fgets(linebuf, MAX_LINE, fp)) {
273 fclose(fp);
f8d87ab1 274 return -1;
76700389
BW
275 }
276
277 /* read third line */
278 if (!fgets(linebuf, MAX_LINE, fp)) {
279 fclose(fp);
f8d87ab1 280 return -1;
76700389
BW
281 }
282
283 if (strncmp(linebuf, "UTC", 3) == 0)
284 clock_mode = CM_UTC;
285 else if (strncmp(linebuf, "LOCAL", 5) == 0)
286 clock_mode = CM_LOCAL;
287
288 fclose(fp);
289
f8d87ab1 290 return 0;
76700389
BW
291}
292
293int main(int argc, char **argv)
294{
295 char *devname = DEFAULT_DEVICE;
296 unsigned seconds = 0;
297 char *suspend = DEFAULT_MODE;
298
299 int t;
300 int fd;
301 time_t alarm = 0;
302
303 setlocale(LC_ALL, "");
304 bindtextdomain(PACKAGE, LOCALEDIR);
305 textdomain(PACKAGE);
306
307 progname = basename(argv[0]);
308
309 while ((t = getopt_long(argc, argv, "ahd:lm:s:t:uVv",
310 long_options, NULL)) != EOF) {
311 switch (t) {
312 case 'a':
313 /* CM_AUTO is default */
314 break;
315
316 case 'd':
317 devname = strdup(optarg);
318 break;
319
320 case 'l':
321 clock_mode = CM_LOCAL;
322 break;
323
324 /* what system power mode to use? for now handle only
325 * standardized mode names; eventually when systems
326 * define their own state names, parse
327 * /sys/power/state.
328 *
329 * "on" is used just to test the RTC alarm mechanism,
330 * bypassing all the wakeup-from-sleep infrastructure.
331 */
332 case 'm':
333 if (strcmp(optarg, "standby") == 0
334 || strcmp(optarg, "mem") == 0
335 || strcmp(optarg, "disk") == 0
336 || strcmp(optarg, "on") == 0
e4b0fc36 337 || strcmp(optarg, "no") == 0
76700389
BW
338 ) {
339 suspend = strdup(optarg);
340 break;
341 }
2148b051
DB
342 fprintf(stderr,
343 _("%s: unrecognized suspend state '%s'\n"),
344 progname, optarg);
76700389
BW
345 usage(EXIT_FAILURE);
346
347 /* alarm time, seconds-to-sleep (relative) */
348 case 's':
349 t = atoi(optarg);
350 if (t < 0) {
351 fprintf(stderr,
352 _("%s: illegal interval %s seconds\n"),
353 progname, optarg);
354 usage(EXIT_FAILURE);
355 }
356 seconds = t;
357 break;
358
359 /* alarm time, time_t (absolute, seconds since
360 * 1/1 1970 UTC)
361 */
362 case 't':
363 t = atoi(optarg);
364 if (t < 0) {
365 fprintf(stderr,
366 _("%s: illegal time_t value %s\n"),
367 progname, optarg);
368 usage(EXIT_FAILURE);
369 }
370 alarm = t;
371 break;
372
373 case 'u':
374 clock_mode = CM_UTC;
375 break;
376
377 case 'v':
378 verbose++;
379 break;
380
381 case 'V':
382 printf(_("%s: version %s\n"), progname, VERSION_STRING);
383 exit(EXIT_SUCCESS);
384
385 case 'h':
386 usage(EXIT_SUCCESS);
387
388 default:
389 usage(EXIT_FAILURE);
390 }
391 }
392
393 if (clock_mode == CM_AUTO) {
f8d87ab1 394 if (read_clock_mode() < 0) {
76700389
BW
395 printf(_("%s: assuming RTC uses UTC ...\n"), progname);
396 clock_mode = CM_UTC;
397 }
76700389 398 }
2148b051
DB
399 if (verbose)
400 printf(clock_mode == CM_UTC ? _("Using UTC time.\n") :
401 _("Using local time.\n"));
76700389
BW
402
403 if (!alarm && !seconds) {
404 fprintf(stderr, _("%s: must provide wake time\n"), progname);
405 usage(EXIT_FAILURE);
406 }
407
408 /* when devname doesn't start with /dev, append it */
409 if (strncmp(devname, "/dev/", strlen("/dev/")) != 0) {
410 char *new_devname;
411
412 new_devname = malloc(strlen(devname) + strlen("/dev/") + 1);
413 if (!new_devname) {
414 perror(_("malloc() failed"));
415 exit(EXIT_FAILURE);
416 }
417
418 strcpy(new_devname, "/dev/");
419 strcat(new_devname, devname);
420 free(devname);
421 devname = new_devname;
422 }
423
e4b0fc36
MI
424 if (strcmp(suspend, "on") != 0 && strcmp(suspend, "no") != 0
425 && !is_wakeup_enabled(devname)) {
76700389
BW
426 fprintf(stderr, _("%s: %s not enabled for wakeup events\n"),
427 progname, devname);
428 exit(EXIT_FAILURE);
429 }
430
431 /* this RTC must exist and (if we'll sleep) be wakeup-enabled */
432 fd = open(devname, O_RDONLY);
433 if (fd < 0) {
434 perror(devname);
435 exit(EXIT_FAILURE);
436 }
437
438 /* relative or absolute alarm time, normalized to time_t */
f8d87ab1 439 if (get_basetimes(fd) < 0)
76700389
BW
440 exit(EXIT_FAILURE);
441 if (verbose)
442 printf(_("alarm %ld, sys_time %ld, rtc_time %ld, seconds %u\n"),
443 alarm, sys_time, rtc_time, seconds);
444 if (alarm) {
445 if (alarm < sys_time) {
2148b051
DB
446 fprintf(stderr,
447 _("%s: time doesn't go backward to %s\n"),
448 progname, ctime(&alarm));
76700389
BW
449 exit(EXIT_FAILURE);
450 }
451 alarm += sys_time - rtc_time;
452 } else
453 alarm = rtc_time + seconds + 1;
454 if (setup_alarm(fd, &alarm) < 0)
455 exit(EXIT_FAILURE);
456
76700389
BW
457 printf(_("%s: wakeup from \"%s\" using %s at %s\n"),
458 progname, suspend, devname,
459 ctime(&alarm));
460 fflush(stdout);
461 usleep(10 * 1000);
462
e4b0fc36
MI
463 if (strcmp(suspend, "no") == 0)
464 exit(EXIT_SUCCESS);
465 else if (strcmp(suspend, "on") != 0) {
466 sync();
76700389 467 suspend_system(suspend);
e4b0fc36 468 } else {
76700389
BW
469 unsigned long data;
470
471 do {
472 t = read(fd, &data, sizeof data);
473 if (t < 0) {
474 perror(_("rtc read"));
475 break;
476 }
477 if (verbose)
478 printf("... %s: %03lx\n", devname, data);
479 } while (!(data & RTC_AF));
480 }
481
482 if (ioctl(fd, RTC_AIE_OFF, 0) < 0)
483 perror(_("disable rtc alarm interrupt"));
484
485 close(fd);
486
487 exit(EXIT_SUCCESS);
488}