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