]> git.neil.brown.name Git - susman.git/blob - lsusd.c
wakealarmd: cope with delta between system time and RTC time.
[susman.git] / lsusd.c
1 /*
2  * lsusd - Linus SUSpend daemon.
3  * This daemon enters suspend when required and allows clients
4  * to block suspend, request suspend, or be notified of suspend.
5  *
6  * Copyright (C) 2011 Neil Brown <neilb@suse.de>
7  *
8  *    This program is free software; you can redistribute it and/or modify
9  *    it under the terms of the GNU General Public License as published by
10  *    the Free Software Foundation; either version 2 of the License, or
11  *    (at your option) any later version.
12  *
13  *    This program is distributed in the hope that it will be useful,
14  *    but WITHOUT ANY WARRANTY; without even the implied warranty of
15  *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  *    GNU General Public License for more details.
17  *
18  *    You should have received a copy of the GNU General Public License along
19  *    with this program; if not, write to the Free Software Foundation, Inc.,
20  *    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21  */
22 #define _GNU_SOURCE
23
24 #include <unistd.h>
25 #include <stdlib.h>
26 #include <fcntl.h>
27 #include <stdio.h>
28 #include <string.h>
29 #include <signal.h>
30 #include <dirent.h>
31
32 static void alert_watchers(void)
33 {
34         int fd;
35         char zero = 0;
36
37         fd = open("/run/suspend/watching-next",
38                   O_RDWR|O_CREAT|O_TRUNC, 0640);
39         if (fd < 0)
40                 return;
41         close(fd);
42         fd = open("/run/suspend/watching",
43                   O_RDWR|O_CREAT|O_TRUNC, 0640);
44         if (fd < 0)
45                 return;
46         if (write(fd, &zero, 1) != 1)
47                 return;
48         flock(fd, LOCK_EX);
49         /* all watches must have moved to next file */
50         close(fd);
51 }
52
53 static void cycle_watchers(void)
54 {
55         int fd;
56         char zero[2];
57
58         fd = open("/run/suspend/watching", O_RDWR|O_CREAT, 0640);
59         if (fd < 0)
60                 return;
61         rename("/run/suspend/watching-next",
62                "/run/suspend/watching");
63         zero[0] = zero[1] = 0;
64         write(fd, zero, 2);
65         close(fd);
66 }
67
68 static int read_wakeup_count()
69 {
70         int fd;
71         int n;
72         char buf[20];
73
74         fd = open("/sys/power/wakeup_count", O_RDONLY);
75         if (fd < 0)
76                 return -1;
77         n = read(fd, buf, sizeof(buf)-1);
78         close(fd);
79         if (n < 0)
80                 return -1;
81         buf[n] = 0;
82         return atoi(buf);
83 }
84
85 static int set_wakeup_count(int count)
86 {
87         int fd;
88         char buf[20];
89         int n;
90
91         if (count < 0)
92                 return 1; /* Something wrong - just suspend */
93
94         fd = open("/sys/power/wakeup_count", O_RDWR);
95         if (fd < 0)
96                 return 1;
97
98         snprintf(buf, sizeof(buf), "%d", count);
99         n = write(fd, buf, strlen(buf));
100         close(fd);
101         if (n < 0)
102                 return 0;
103         return 1;
104 }
105
106 static void catch(int sig)
107 {
108         return;
109 }
110
111 static void wait_request(int dirfd)
112 {
113         int found_immediate = 0;
114         int found_request = 0;
115
116         do {
117                 DIR *dir;
118                 struct dirent *de;
119                 sigset_t set, oldset;
120                 sigemptyset(&set);
121                 sigaddset(&set, SIGIO);
122
123                 sigprocmask(SIG_BLOCK, &set, &oldset);
124                 signal(SIGIO, catch);
125
126                 fcntl(dirfd, F_NOTIFY, DN_CREATE);
127
128                 lseek(dirfd, 0L, 0);
129                 dir = fdopendir(dup(dirfd));
130                 while ((de = readdir(dir)) != NULL) {
131                         if (strcmp(de->d_name, "immediate") == 0)
132                                 found_immediate = 1;
133                         if (strcmp(de->d_name, "request") == 0)
134                                 found_request = 1;
135                 }
136
137                 if (!found_request && !found_immediate)
138                         sigsuspend(&oldset);
139                 closedir(dir);
140                 signal(SIGIO, SIG_DFL);
141                 sigprocmask(SIG_UNBLOCK, &set, &oldset);
142         } while (!found_immediate && !found_request);
143 }
144
145 static int request_valid()
146 {
147         /* check if the request to suspend is still valid.
148          * If the 'immediate' file is not locked, we remove
149          * and ignore it as the requesting process has died
150          */
151         int fd = open("/run/suspend/immediate", O_RDWR);
152         if (fd >= 0) {
153                 if (flock(fd, LOCK_EX|LOCK_NB) == 0) {
154                         /* we got the lock, so owner must have died */
155                         unlink("/run/suspend/immediate");
156                         close(fd);
157                 } else {
158                         /* Still valid */
159                         close(fd);
160                         return 1;
161                 }
162         }
163         fd = open("/run/suspend/request", O_RDONLY);
164         if (fd < 0)
165                 return 0;
166         close(fd);
167         return 1;
168 }
169
170 static void do_suspend(void)
171 {
172         int fd = open("/sys/power/state", O_RDWR);
173         if (fd >= 0) {
174                 write(fd, "mem\n", 4);
175                 close(fd);
176         } else
177                 sleep(5);
178 }
179
180 main(int argc, char *argv)
181 {
182         int dir;
183         int disable;
184
185         mkdir("/run/suspend", 0770);
186
187         dir = open("/run/suspend", O_RDONLY);
188         disable = open("/run/suspend/disabled", O_RDWR|O_CREAT, 0640);
189
190         if (dir < 0 || disable < 0)
191                 exit(1);
192
193         /* Create the initial files */
194         alert_watchers();
195         cycle_watchers();
196
197         close(0);
198
199         while (1) {
200                 int count;
201                 struct timespec ts;
202                 struct stat stb;
203
204                 /* Don't accept an old request */
205                 unlink("/run/suspend/request");
206                 wait_request(dir);
207                 if (flock(disable, LOCK_EX|LOCK_NB) != 0) {
208                         flock(disable, LOCK_EX);
209                         flock(disable, LOCK_UN);
210                         unlink("/run/suspend/request");
211                         /* blocked - so need to ensure request still valid */
212                         continue;
213                 }
214                 flock(disable, LOCK_UN);;
215                 /* we got that without blocking but are not holding it */
216
217                 /* Next two might block, but that doesn't abort suspend */
218                 count = read_wakeup_count();
219                 fstat(disable, &stb);
220                 ts = stb.st_atim;
221                 alert_watchers();
222
223                 fstat(disable, &stb);
224                 if (flock(disable, LOCK_EX|LOCK_NB) == 0
225                     && request_valid()
226                     && ts.tv_sec == stb.st_atim.tv_sec
227                     && ts.tv_nsec == stb.st_atim.tv_nsec
228                     && set_wakeup_count(count))
229                         do_suspend();
230                 flock(disable, LOCK_UN);
231                 cycle_watchers();
232         }
233 }