689 lines
17 KiB
C
689 lines
17 KiB
C
/*
|
|
* rtcwake -- enter a system sleep state until specified wakeup time.
|
|
*
|
|
* This uses cross-platform Linux interfaces to enter a system sleep state,
|
|
* and leave it no later than a specified time. It uses any RTC framework
|
|
* driver that supports standard driver model wakeup flags.
|
|
*
|
|
* This is normally used like the old "apmsleep" utility, to wake from a
|
|
* suspend state like ACPI S1 (standby) or S3 (suspend-to-RAM). Most
|
|
* platforms can implement those without analogues of BIOS, APM, or ACPI.
|
|
*
|
|
* On some systems, this can also be used like "nvram-wakeup", waking
|
|
* from states like ACPI S4 (suspend to disk). Not all systems have
|
|
* persistent media that are appropriate for such suspend modes.
|
|
*
|
|
* The best way to set the system's RTC is so that it holds the current
|
|
* time in UTC. Use the "-l" flag to tell this program that the system
|
|
* RTC uses a local timezone instead (maybe you dual-boot MS-Windows).
|
|
* That flag should not be needed on systems with adjtime support.
|
|
*/
|
|
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <getopt.h>
|
|
#include <linux/rtc.h>
|
|
#include <poll.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/time.h>
|
|
#include <sys/types.h>
|
|
#include <termios.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
|
|
#include "c.h"
|
|
#include "closestream.h"
|
|
#include "env.h"
|
|
#include "nls.h"
|
|
#include "optutils.h"
|
|
#include "pathnames.h"
|
|
#include "strutils.h"
|
|
#include "strv.h"
|
|
#include "timeutils.h"
|
|
#include "xalloc.h"
|
|
|
|
#ifndef RTC_AF
|
|
# define RTC_AF 0x20 /* Alarm interrupt */
|
|
#endif
|
|
|
|
#define ADJTIME_ZONE_BUFSIZ 8
|
|
#define SYS_WAKEUP_PATH_TEMPLATE "/sys/class/rtc/%s/device/power/wakeup"
|
|
#define SYS_POWER_STATE_PATH "/sys/power/state"
|
|
#define DEFAULT_RTC_DEVICE "/dev/rtc0"
|
|
|
|
enum rtc_modes { /* manual page --mode option explains these. */
|
|
OFF_MODE = 0,
|
|
NO_MODE,
|
|
ON_MODE,
|
|
DISABLE_MODE,
|
|
SHOW_MODE,
|
|
|
|
SYSFS_MODE /* keep it last */
|
|
|
|
};
|
|
|
|
static const char *rtcwake_mode_string[] = {
|
|
[OFF_MODE] = "off",
|
|
[NO_MODE] = "no",
|
|
[ON_MODE] = "on",
|
|
[DISABLE_MODE] = "disable",
|
|
[SHOW_MODE] = "show"
|
|
};
|
|
|
|
enum clock_modes {
|
|
CM_AUTO,
|
|
CM_UTC,
|
|
CM_LOCAL
|
|
};
|
|
|
|
struct rtcwake_control {
|
|
char *mode_str; /* name of the requested mode */
|
|
char **possible_modes; /* modes listed in /sys/power/state */
|
|
char *adjfile; /* adjtime file path */
|
|
enum clock_modes clock_mode; /* hwclock timezone */
|
|
time_t sys_time; /* system time */
|
|
time_t rtc_time; /* hardware time */
|
|
unsigned int verbose:1, /* verbose messaging */
|
|
dryrun:1; /* do not set alarm, suspend system, etc */
|
|
};
|
|
|
|
static void __attribute__((__noreturn__)) usage(void)
|
|
{
|
|
FILE *out = stdout;
|
|
fputs(USAGE_HEADER, out);
|
|
fprintf(out,
|
|
_(" %s [options]\n"), program_invocation_short_name);
|
|
|
|
fputs(USAGE_SEPARATOR, out);
|
|
fputs(_("Enter a system sleep state until a specified wakeup time.\n"), out);
|
|
|
|
fputs(USAGE_OPTIONS, out);
|
|
fputs(_(" -a, --auto reads the clock mode from adjust file (default)\n"), out);
|
|
fprintf(out,
|
|
_(" -A, --adjfile <file> specifies the path to the adjust file\n"
|
|
" the default is %s\n"), _PATH_ADJTIME);
|
|
fputs(_(" --date <timestamp> date time of timestamp to wake\n"), out);
|
|
fputs(_(" -d, --device <device> select rtc device (rtc0|rtc1|...)\n"), out);
|
|
fputs(_(" -n, --dry-run does everything, but suspend\n"), out);
|
|
fputs(_(" -l, --local RTC uses local timezone\n"), out);
|
|
fputs(_(" --list-modes list available modes\n"), out);
|
|
fputs(_(" -m, --mode <mode> standby|mem|... sleep mode\n"), out);
|
|
fputs(_(" -s, --seconds <seconds> seconds to sleep\n"), out);
|
|
fputs(_(" -t, --time <time_t> time to wake\n"), out);
|
|
fputs(_(" -u, --utc RTC uses UTC\n"), out);
|
|
fputs(_(" -v, --verbose verbose messages\n"), out);
|
|
|
|
fputs(USAGE_SEPARATOR, out);
|
|
printf(USAGE_HELP_OPTIONS(26));
|
|
printf(USAGE_MAN_TAIL("rtcwake(8)"));
|
|
exit(EXIT_SUCCESS);
|
|
}
|
|
|
|
static int is_wakeup_enabled(const char *devname)
|
|
{
|
|
char buf[128], *s;
|
|
FILE *f;
|
|
size_t skip = 0;
|
|
|
|
if (startswith(devname, "/dev/"))
|
|
skip = 5;
|
|
snprintf(buf, sizeof buf, SYS_WAKEUP_PATH_TEMPLATE, devname + skip);
|
|
f = fopen(buf, "r");
|
|
if (!f) {
|
|
warn(_("cannot open %s"), buf);
|
|
return 0;
|
|
}
|
|
|
|
s = fgets(buf, sizeof buf, f);
|
|
fclose(f);
|
|
if (!s)
|
|
return 0;
|
|
s = strchr(buf, '\n');
|
|
if (!s)
|
|
return 0;
|
|
*s = 0;
|
|
/* wakeup events could be disabled or not supported */
|
|
return strcmp(buf, "enabled") == 0;
|
|
}
|
|
|
|
static int get_basetimes(struct rtcwake_control *ctl, int fd)
|
|
{
|
|
struct tm tm = { 0 };
|
|
struct rtc_time rtc;
|
|
|
|
/* This process works in RTC time, except when working
|
|
* with the system clock (which always uses UTC).
|
|
*/
|
|
if (ctl->clock_mode == CM_UTC)
|
|
xsetenv("TZ", "UTC", 1);
|
|
tzset();
|
|
/* Read rtc and system clocks "at the same time", or as
|
|
* precisely (+/- a second) as we can read them.
|
|
*/
|
|
if (ioctl(fd, RTC_RD_TIME, &rtc) < 0) {
|
|
warn(_("read rtc time failed"));
|
|
return -1;
|
|
}
|
|
|
|
ctl->sys_time = time(NULL);
|
|
if (ctl->sys_time == (time_t)-1) {
|
|
warn(_("read system time failed"));
|
|
return -1;
|
|
}
|
|
/* Convert rtc_time to normal arithmetic-friendly form,
|
|
* updating tm.tm_wday as used by asctime().
|
|
*/
|
|
tm.tm_sec = rtc.tm_sec;
|
|
tm.tm_min = rtc.tm_min;
|
|
tm.tm_hour = rtc.tm_hour;
|
|
tm.tm_mday = rtc.tm_mday;
|
|
tm.tm_mon = rtc.tm_mon;
|
|
tm.tm_year = rtc.tm_year;
|
|
tm.tm_isdst = -1; /* assume the system knows better than the RTC */
|
|
|
|
ctl->rtc_time = mktime(&tm);
|
|
if (ctl->rtc_time == (time_t)-1) {
|
|
warn(_("convert rtc time failed"));
|
|
return -1;
|
|
}
|
|
|
|
if (ctl->verbose) {
|
|
/* Unless the system uses UTC, either delta or tzone
|
|
* reflects a seconds offset from UTC. The value can
|
|
* help sort out problems like bugs in your C library. */
|
|
char s[64];
|
|
printf("\tdelta = %"PRId64"\n", (int64_t) ctl->sys_time - ctl->rtc_time);
|
|
printf("\ttzone = %ld\n", timezone);
|
|
printf("\ttzname = %s\n", tzname[daylight]);
|
|
gmtime_r(&ctl->sys_time, &tm);
|
|
printf("\tsystime = %"PRId64", (UTC) %s",
|
|
(int64_t) ctl->sys_time, asctime_r(&tm, s));
|
|
gmtime_r(&ctl->rtc_time, &tm);
|
|
printf("\trtctime = %"PRId64", (UTC) %s",
|
|
(int64_t) ctl->rtc_time, asctime_r(&tm, s));
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int setup_alarm(struct rtcwake_control *ctl, int fd, time_t *wakeup)
|
|
{
|
|
struct tm tm;
|
|
struct rtc_wkalrm wake = { 0 };
|
|
|
|
/* The wakeup time is in POSIX time (more or less UTC). Ideally
|
|
* RTCs use that same time; but PCs can't do that if they need to
|
|
* boot MS-Windows. Messy...
|
|
*
|
|
* When clock_mode == CM_UTC this process's timezone is UTC, so
|
|
* we'll pass a UTC date to the RTC.
|
|
*
|
|
* Else clock_mode == CM_LOCAL so the time given to the RTC will
|
|
* instead use the local time zone. */
|
|
localtime_r(wakeup, &tm);
|
|
wake.time.tm_sec = tm.tm_sec;
|
|
wake.time.tm_min = tm.tm_min;
|
|
wake.time.tm_hour = tm.tm_hour;
|
|
wake.time.tm_mday = tm.tm_mday;
|
|
wake.time.tm_mon = tm.tm_mon;
|
|
wake.time.tm_year = tm.tm_year;
|
|
/* wday, yday, and isdst fields are unused */
|
|
wake.time.tm_wday = -1;
|
|
wake.time.tm_yday = -1;
|
|
wake.time.tm_isdst = -1;
|
|
wake.enabled = 1;
|
|
|
|
if (!ctl->dryrun && ioctl(fd, RTC_WKALM_SET, &wake) < 0) {
|
|
warn(_("set rtc wake alarm failed"));
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static char **get_sys_power_states(struct rtcwake_control *ctl)
|
|
{
|
|
int fd = -1;
|
|
|
|
if (!ctl->possible_modes) {
|
|
char buf[256] = { 0 };
|
|
ssize_t ss;
|
|
|
|
fd = open(SYS_POWER_STATE_PATH, O_RDONLY);
|
|
if (fd < 0)
|
|
goto nothing;
|
|
ss = read(fd, &buf, sizeof(buf) - 1);
|
|
if (ss <= 0)
|
|
goto nothing;
|
|
buf[ss] = '\0';
|
|
ctl->possible_modes = strv_split(buf, " \n");
|
|
close(fd);
|
|
}
|
|
return ctl->possible_modes;
|
|
nothing:
|
|
if (fd >= 0)
|
|
close(fd);
|
|
return NULL;
|
|
}
|
|
|
|
static void wait_stdin(struct rtcwake_control *ctl)
|
|
{
|
|
struct pollfd fd[] = {
|
|
{.fd = STDIN_FILENO, .events = POLLIN}
|
|
};
|
|
int tries = 0;
|
|
|
|
while (tries < 8 && poll(fd, 1, 10) == 1) {
|
|
if (ctl->verbose)
|
|
warnx(_("discarding stdin"));
|
|
xusleep(250000);
|
|
tcflush(STDIN_FILENO, TCIFLUSH);
|
|
tries++;
|
|
}
|
|
}
|
|
|
|
static void suspend_system(struct rtcwake_control *ctl)
|
|
{
|
|
FILE *f = fopen(SYS_POWER_STATE_PATH, "w");
|
|
|
|
if (!f) {
|
|
warn(_("cannot open %s"), SYS_POWER_STATE_PATH);
|
|
return;
|
|
}
|
|
|
|
if (!ctl->dryrun) {
|
|
if (isatty(STDIN_FILENO))
|
|
wait_stdin(ctl);
|
|
fprintf(f, "%s\n", ctl->mode_str);
|
|
fflush(f);
|
|
}
|
|
/* this executes after wake from suspend */
|
|
if (close_stream(f))
|
|
errx(EXIT_FAILURE, _("write error"));
|
|
}
|
|
|
|
static int read_clock_mode(struct rtcwake_control *ctl)
|
|
{
|
|
FILE *fp;
|
|
char linebuf[ADJTIME_ZONE_BUFSIZ];
|
|
|
|
fp = fopen(ctl->adjfile, "r");
|
|
if (!fp)
|
|
return -1;
|
|
/* skip two lines */
|
|
if (skip_fline(fp) || skip_fline(fp)) {
|
|
fclose(fp);
|
|
return -1;
|
|
}
|
|
/* read third line */
|
|
if (!fgets(linebuf, sizeof linebuf, fp)) {
|
|
fclose(fp);
|
|
return -1;
|
|
}
|
|
|
|
if (strncmp(linebuf, "UTC", 3) == 0)
|
|
ctl->clock_mode = CM_UTC;
|
|
else if (strncmp(linebuf, "LOCAL", 5) == 0)
|
|
ctl->clock_mode = CM_LOCAL;
|
|
else if (ctl->verbose)
|
|
warnx(_("unexpected third line in: %s: %s"), ctl->adjfile, linebuf);
|
|
|
|
fclose(fp);
|
|
return 0;
|
|
}
|
|
|
|
static int print_alarm(struct rtcwake_control *ctl, int fd)
|
|
{
|
|
struct rtc_wkalrm wake;
|
|
struct tm tm = { 0 };
|
|
time_t alarm;
|
|
char s[CTIME_BUFSIZ];
|
|
|
|
if (ioctl(fd, RTC_WKALM_RD, &wake) < 0) {
|
|
warn(_("read rtc alarm failed"));
|
|
return -1;
|
|
}
|
|
|
|
if (wake.enabled != 1 || wake.time.tm_year == -1) {
|
|
printf(_("alarm: off\n"));
|
|
return 0;
|
|
}
|
|
tm.tm_sec = wake.time.tm_sec;
|
|
tm.tm_min = wake.time.tm_min;
|
|
tm.tm_hour = wake.time.tm_hour;
|
|
tm.tm_mday = wake.time.tm_mday;
|
|
tm.tm_mon = wake.time.tm_mon;
|
|
tm.tm_year = wake.time.tm_year;
|
|
tm.tm_isdst = -1; /* assume the system knows better than the RTC */
|
|
|
|
alarm = mktime(&tm);
|
|
if (alarm == (time_t)-1) {
|
|
warn(_("convert time failed"));
|
|
return -1;
|
|
}
|
|
/* 0 if both UTC, or expresses diff if RTC in local time */
|
|
alarm += ctl->sys_time - ctl->rtc_time;
|
|
ctime_r(&alarm, s);
|
|
printf(_("alarm: on %s"), s);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int get_rtc_mode(struct rtcwake_control *ctl, const char *s)
|
|
{
|
|
size_t i;
|
|
char **modes = get_sys_power_states(ctl), **m;
|
|
|
|
STRV_FOREACH(m, modes) {
|
|
if (strcmp(s, *m) == 0)
|
|
return SYSFS_MODE;
|
|
}
|
|
|
|
for (i = 0; i < ARRAY_SIZE(rtcwake_mode_string); i++)
|
|
if (!strcmp(s, rtcwake_mode_string[i]))
|
|
return i;
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
static int open_dev_rtc(const char *devname)
|
|
{
|
|
int fd;
|
|
char *devpath = NULL;
|
|
|
|
if (startswith(devname, "/dev"))
|
|
devpath = xstrdup(devname);
|
|
else
|
|
xasprintf(&devpath, "/dev/%s", devname);
|
|
fd = open(devpath, O_RDONLY | O_CLOEXEC);
|
|
if (fd < 0)
|
|
err(EXIT_FAILURE, _("%s: unable to find device"), devpath);
|
|
free(devpath);
|
|
return fd;
|
|
}
|
|
|
|
static void list_modes(struct rtcwake_control *ctl)
|
|
{
|
|
size_t i;
|
|
char **modes = get_sys_power_states(ctl), **m;
|
|
|
|
if (!modes)
|
|
errx(EXIT_FAILURE, _("could not read: %s"), SYS_POWER_STATE_PATH);
|
|
|
|
STRV_FOREACH(m, modes)
|
|
printf("%s ", *m);
|
|
|
|
for (i = 0; i < ARRAY_SIZE(rtcwake_mode_string); i++)
|
|
printf("%s ", rtcwake_mode_string[i]);
|
|
putchar('\n');
|
|
}
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
struct rtcwake_control ctl = {
|
|
.mode_str = "suspend", /* default mode */
|
|
.adjfile = _PATH_ADJTIME,
|
|
.clock_mode = CM_AUTO
|
|
};
|
|
char *devname = DEFAULT_RTC_DEVICE;
|
|
int suspend = SYSFS_MODE;
|
|
int rc = EXIT_SUCCESS;
|
|
int t;
|
|
int fd;
|
|
time_t alarm = 0, seconds = 0;
|
|
enum {
|
|
OPT_DATE = CHAR_MAX + 1,
|
|
OPT_LIST
|
|
};
|
|
static const struct option long_options[] = {
|
|
{ "adjfile", required_argument, NULL, 'A' },
|
|
{ "auto", no_argument, NULL, 'a' },
|
|
{ "dry-run", no_argument, NULL, 'n' },
|
|
{ "local", no_argument, NULL, 'l' },
|
|
{ "utc", no_argument, NULL, 'u' },
|
|
{ "verbose", no_argument, NULL, 'v' },
|
|
{ "version", no_argument, NULL, 'V' },
|
|
{ "help", no_argument, NULL, 'h' },
|
|
{ "mode", required_argument, NULL, 'm' },
|
|
{ "device", required_argument, NULL, 'd' },
|
|
{ "seconds", required_argument, NULL, 's' },
|
|
{ "time", required_argument, NULL, 't' },
|
|
{ "date", required_argument, NULL, OPT_DATE },
|
|
{ "list-modes", no_argument, NULL, OPT_LIST },
|
|
{ NULL, 0, NULL, 0 }
|
|
};
|
|
static const ul_excl_t excl[] = {
|
|
{ 'a', 'l', 'u' },
|
|
{ 's', 't', OPT_DATE },
|
|
{ 0 },
|
|
};
|
|
int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT;
|
|
|
|
setlocale(LC_ALL, "");
|
|
bindtextdomain(PACKAGE, LOCALEDIR);
|
|
textdomain(PACKAGE);
|
|
close_stdout_atexit();
|
|
|
|
while ((t = getopt_long(argc, argv, "A:ahd:lm:ns:t:uVv",
|
|
long_options, NULL)) != EOF) {
|
|
err_exclusive_options(t, long_options, excl, excl_st);
|
|
switch (t) {
|
|
case 'A':
|
|
/* for better compatibility with hwclock */
|
|
ctl.adjfile = optarg;
|
|
break;
|
|
case 'a':
|
|
ctl.clock_mode = CM_AUTO;
|
|
break;
|
|
case 'd':
|
|
devname = optarg;
|
|
break;
|
|
case 'l':
|
|
ctl.clock_mode = CM_LOCAL;
|
|
break;
|
|
|
|
case OPT_LIST:
|
|
list_modes(&ctl);
|
|
return EXIT_SUCCESS;
|
|
|
|
case 'm':
|
|
if ((suspend = get_rtc_mode(&ctl, optarg)) < 0)
|
|
errx(EXIT_FAILURE, _("unrecognized suspend state '%s'"), optarg);
|
|
ctl.mode_str = optarg;
|
|
break;
|
|
case 'n':
|
|
ctl.dryrun = 1;
|
|
break;
|
|
case 's':
|
|
/* alarm time, seconds-to-sleep (relative) */
|
|
seconds = strtotime_or_err(optarg, _("invalid seconds argument"));
|
|
break;
|
|
case 't':
|
|
/* alarm time, time_t (absolute, seconds since epoch) */
|
|
alarm = strtotime_or_err(optarg, _("invalid time argument"));
|
|
break;
|
|
case OPT_DATE:
|
|
{ /* alarm time, see timestamp format from manual */
|
|
usec_t p;
|
|
if (parse_timestamp(optarg, &p) < 0)
|
|
errx(EXIT_FAILURE, _("invalid time value \"%s\""), optarg);
|
|
alarm = (time_t) (p / 1000000);
|
|
break;
|
|
}
|
|
case 'u':
|
|
ctl.clock_mode = CM_UTC;
|
|
break;
|
|
case 'v':
|
|
ctl.verbose = 1;
|
|
break;
|
|
|
|
case 'V':
|
|
print_version(EXIT_SUCCESS);
|
|
case 'h':
|
|
usage();
|
|
default:
|
|
errtryhelp(EXIT_FAILURE);
|
|
}
|
|
}
|
|
|
|
if (ctl.clock_mode == CM_AUTO && read_clock_mode(&ctl) < 0) {
|
|
printf(_("%s: assuming RTC uses UTC ...\n"), program_invocation_short_name);
|
|
ctl.clock_mode = CM_UTC;
|
|
}
|
|
|
|
if (ctl.verbose)
|
|
printf("%s", ctl.clock_mode == CM_UTC ? _("Using UTC time.\n") :
|
|
_("Using local time.\n"));
|
|
|
|
if (!alarm && !seconds && suspend != DISABLE_MODE && suspend != SHOW_MODE)
|
|
errx(EXIT_FAILURE, _("must provide wake time (see --seconds, --time and --date options)"));
|
|
|
|
/* device must exist and (if we'll sleep) be wakeup-enabled */
|
|
fd = open_dev_rtc(devname);
|
|
|
|
if (suspend != ON_MODE && suspend != NO_MODE && !is_wakeup_enabled(devname))
|
|
errx(EXIT_FAILURE, _("%s not enabled for wakeup events"), devname);
|
|
|
|
/* relative or absolute alarm time, normalized to time_t */
|
|
if (get_basetimes(&ctl, fd) < 0)
|
|
exit(EXIT_FAILURE);
|
|
|
|
if (ctl.verbose)
|
|
printf(_("alarm %"PRId64", sys_time %"PRId64", "
|
|
"rtc_time %"PRId64", seconds %"PRIu64"\n"),
|
|
(int64_t) alarm, (int64_t) ctl.sys_time,
|
|
(int64_t) ctl.rtc_time,
|
|
(int64_t) seconds);
|
|
|
|
if (suspend != DISABLE_MODE && suspend != SHOW_MODE) {
|
|
/* perform alarm setup when the show or disable modes are not set */
|
|
if (alarm) {
|
|
if (alarm < ctl.sys_time) {
|
|
char s[CTIME_BUFSIZ];
|
|
|
|
ctime_r(&alarm, s);
|
|
errx(EXIT_FAILURE, _("time doesn't go backward to %s"), s);
|
|
}
|
|
alarm -= ctl.sys_time - ctl.rtc_time;
|
|
} else
|
|
alarm = ctl.rtc_time + seconds + 1;
|
|
|
|
if (setup_alarm(&ctl, fd, &alarm) < 0)
|
|
exit(EXIT_FAILURE);
|
|
|
|
if (suspend == NO_MODE || suspend == ON_MODE) {
|
|
char s[CTIME_BUFSIZ];
|
|
|
|
ctime_r(&alarm, s);
|
|
printf(_("%s: wakeup using %s at %s"),
|
|
program_invocation_short_name, devname, s);
|
|
} else {
|
|
char s[CTIME_BUFSIZ];
|
|
|
|
ctime_r(&alarm, s);
|
|
printf(_("%s: wakeup from \"%s\" using %s at %s"),
|
|
program_invocation_short_name, ctl.mode_str, devname, s);
|
|
}
|
|
fflush(stdout);
|
|
xusleep(10 * 1000);
|
|
}
|
|
|
|
switch (suspend) {
|
|
case NO_MODE:
|
|
if (ctl.verbose)
|
|
printf(_("suspend mode: no; leaving\n"));
|
|
ctl.dryrun = 1; /* to skip disabling alarm at the end */
|
|
break;
|
|
case OFF_MODE:
|
|
{
|
|
char *arg[5];
|
|
int i = 0;
|
|
|
|
if (!access(_PATH_SHUTDOWN, X_OK)) {
|
|
arg[i++] = _PATH_SHUTDOWN;
|
|
arg[i++] = "-h";
|
|
arg[i++] = "-P";
|
|
arg[i++] = "now";
|
|
arg[i] = NULL;
|
|
} else if (!access(_PATH_POWEROFF, X_OK)) {
|
|
arg[i++] = _PATH_POWEROFF;
|
|
arg[i] = NULL;
|
|
} else {
|
|
arg[i] = NULL;
|
|
}
|
|
|
|
if (arg[0]) {
|
|
if (ctl.verbose)
|
|
printf(_("suspend mode: off; executing %s\n"),
|
|
arg[0]);
|
|
if (!ctl.dryrun) {
|
|
execv(arg[0], arg);
|
|
warn(_("failed to execute %s"), arg[0]);
|
|
rc = EX_EXEC_ENOENT;
|
|
}
|
|
} else {
|
|
/* Failed to find shutdown command */
|
|
warn(_("failed to find shutdown command"));
|
|
rc = EX_EXEC_ENOENT;
|
|
}
|
|
break;
|
|
}
|
|
case ON_MODE:
|
|
{
|
|
unsigned long data;
|
|
|
|
if (ctl.verbose)
|
|
printf(_("suspend mode: on; reading rtc\n"));
|
|
if (!ctl.dryrun) {
|
|
do {
|
|
t = read(fd, &data, sizeof data);
|
|
if (t < 0) {
|
|
warn(_("rtc read failed"));
|
|
break;
|
|
}
|
|
if (ctl.verbose)
|
|
printf("... %s: %03lx\n", devname, data);
|
|
} while (!(data & RTC_AF));
|
|
}
|
|
break;
|
|
}
|
|
case DISABLE_MODE:
|
|
/* just break, alarm gets disabled in the end */
|
|
if (ctl.verbose)
|
|
printf(_("suspend mode: disable; disabling alarm\n"));
|
|
break;
|
|
case SHOW_MODE:
|
|
if (ctl.verbose)
|
|
printf(_("suspend mode: show; printing alarm info\n"));
|
|
if (print_alarm(&ctl, fd))
|
|
rc = EXIT_FAILURE;
|
|
ctl.dryrun = 1; /* don't really disable alarm in the end, just show */
|
|
break;
|
|
default:
|
|
if (ctl.verbose)
|
|
printf(_("suspend mode: %s; suspending system\n"), ctl.mode_str);
|
|
sync();
|
|
suspend_system(&ctl);
|
|
}
|
|
|
|
if (!ctl.dryrun) {
|
|
struct rtc_wkalrm wake;
|
|
|
|
if (ioctl(fd, RTC_WKALM_RD, &wake) < 0) {
|
|
warn(_("read rtc alarm failed"));
|
|
rc = EXIT_FAILURE;
|
|
} else {
|
|
wake.enabled = 0;
|
|
if (ioctl(fd, RTC_WKALM_SET, &wake) < 0) {
|
|
warn(_("disable rtc alarm interrupt failed"));
|
|
rc = EXIT_FAILURE;
|
|
}
|
|
}
|
|
}
|
|
|
|
close(fd);
|
|
return rc;
|
|
}
|