]> git.neil.brown.name Git - susman.git/blob - wakealarmd.c
wakealarmd: cope with delta between system time and RTC time.
[susman.git] / wakealarmd.c
1
2 /* wake alarm service.
3  * We provide a wakeup service to use the rtc wakealarm
4  * to ensure the system is running at given times and to
5  * alert clients.
6  *
7  * Client can connect and register a time as seconds since epoch
8  * We echo back the time and then when the time comes we echo "Now".
9  * We keep system awake until another time is written, or until
10  * connection is closed.
11  *
12  * Copyright (C) 2011 Neil Brown <neilb@suse.de>
13  *
14  *    This program is free software; you can redistribute it and/or modify
15  *    it under the terms of the GNU General Public License as published by
16  *    the Free Software Foundation; either version 2 of the License, or
17  *    (at your option) any later version.
18  *
19  *    This program is distributed in the hope that it will be useful,
20  *    but WITHOUT ANY WARRANTY; without even the implied warranty of
21  *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22  *    GNU General Public License for more details.
23  *
24  *    You should have received a copy of the GNU General Public License along
25  *    with this program; if not, write to the Free Software Foundation, Inc.,
26  *    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
27  */
28
29 #define _GNU_SOURCE
30 #include <stdlib.h>
31 #include <event.h>
32 #include <sys/socket.h>
33 #include <sys/un.h>
34 #include <stdio.h>
35 #include <fcntl.h>
36 #include <errno.h>
37 #include "libsus.h"
38
39 struct conn {
40         struct event    ev;
41         time_t          stamp;  /* When to wake */
42         int             active; /* stamp has passed */
43         struct conn     *next;  /* sorted by 'stamp' */
44         struct state    *state;
45 };
46
47 struct state {
48         struct event    ev;
49         struct event    tev;
50         int             disablefd;
51         int             disabled;
52         void            *watcher;
53         struct conn     *conns;
54         int             active_count;
55 };
56
57 static void do_timeout(int fd, short ev, void *data);
58 static void add_han(struct conn *han)
59 {
60         struct state *state = han->state;
61         struct conn **hanp = &state->conns;
62         struct timeval tv;
63         time_t now;
64
65         while (*hanp &&
66                (*hanp)->stamp < han->stamp)
67                 hanp = &(*hanp)->next;
68         han->next = *hanp;
69         *hanp = han;
70
71         if (event_get_base(&state->tev))
72                 evtimer_del(&state->tev);
73         do_timeout(0, 0, (void*)state);
74 }
75
76 static void del_han(struct conn *han)
77 {
78         struct state *state = han->state;
79         struct conn **hanp = &state->conns;
80
81         while (*hanp &&
82                *hanp != han)
83                 hanp = &(*hanp)->next;
84         if (*hanp == han) {
85                 *hanp = han->next;
86                 if (han->active) {
87                         han->active = 0;
88                         state->active_count--;
89                         if (state->active_count == 0
90                             && state->disabled) {
91                                 suspend_allow(state->disablefd);
92                                 state->disabled = 0;
93                         }
94                 }
95         }
96 }
97
98 static void destroy_han(struct conn *han)
99 {
100         event_del(&han->ev);
101         free(han);
102 }
103
104 static void do_read(int fd, short ev, void *data)
105 {
106         struct conn *han = data;
107         char buf[20];
108         int n;
109
110         n = read(fd, buf, sizeof(buf)-1);
111         if (n < 0 && errno == EAGAIN)
112                 return;
113         if (n <= 0) {
114                 del_han(han);
115                 destroy_han(han);
116                 close(fd);
117                 return;
118         }
119         del_han(han);
120         han->stamp = atol(buf);
121         sprintf(buf, "%lld\n", (long long)han->stamp);
122         write(fd, buf, strlen(buf));
123         add_han(han);
124 }
125
126 static void do_timeout(int fd, short ev, void *data)
127 {
128         struct state *state = data;
129         struct conn *han;
130         time_t now = time(0);
131
132         for (han = state->conns; han && han->stamp <= now; han = han->next)
133                 if (!han->active) {
134                         han->active = 1;
135                         han->state->active_count++;
136                         write(EVENT_FD(&han->ev), "Now\n", 4);
137                 }
138         if (han) {
139                 struct timeval tv;
140                 tv.tv_sec = han->stamp - now;
141                 tv.tv_usec = 0;
142                 evtimer_add(&state->tev, &tv);
143         }
144 }
145
146 static void do_accept(int fd, short ev, void *data)
147 {
148         struct state *state = data;
149         struct conn *han;
150         int newfd = accept4(fd, NULL, NULL, SOCK_NONBLOCK|SOCK_CLOEXEC);
151
152         if (newfd < 0)
153                 return;
154         han = malloc(sizeof(*han));
155         if (!han) {
156                 close(newfd);
157                 return;
158         }
159         han->state = state;
160         han->stamp = 0;
161         han->active = 1;
162         state->active_count++;
163         han->next = state->conns;
164         state->conns = han;
165
166         event_set(&han->ev, newfd, EV_READ | EV_PERSIST, do_read, han);
167         event_add(&han->ev, NULL);
168         write(newfd, "0\n", 2);
169 }
170
171 static int do_suspend(void *data)
172 {
173         struct state *state = data;
174         time_t now;
175
176         time(&now);
177
178         if (event_get_base(&state->tev))
179                 evtimer_del(&state->tev);
180         /* active_count must be zero */
181         if (state->conns == NULL)
182                 return 1;
183
184         if (state->conns->stamp > now + 4) {
185                 int fd = open("/sys/class/rtc/rtc0/since_epoch", O_RDONLY);
186                 time_t rtc_now = now;
187                 if (fd) {
188                         char buf[20];
189                         int n = read(fd, buf, 20);
190                         close(fd);
191                         if (n > 1 && n < 20) {
192                                 buf[n] = 0;
193                                 rtc_now = strtoul(buf, NULL, 10);
194                         }
195                 }
196                 fd = open("/sys/class/rtc/rtc0/wakealarm", O_WRONLY);
197                 if (fd >= 0) {
198                         char buf[20];
199                         write(fd, "0\n", 2);
200                         sprintf(buf, "%lld\n",
201                                 (long long)state->conns->stamp
202                                 - now + rtc_now - 2);
203                         write(fd, buf, strlen(buf));
204                         close(fd);
205                 }
206                 return 1;
207         }
208         /* too close to next wakeup */
209         if (!state->disabled) {
210                 suspend_block(state->disablefd);
211                 state->disabled = 1;
212         }
213         return 1;
214 }
215
216 static void do_resume(void *data)
217 {
218         struct state *state = data;
219
220         do_timeout(0, 0, (void*)state);
221 }
222
223 int main(int argc, char *argv[])
224 {
225         struct state st;
226         struct sockaddr_un addr;
227         int s;
228
229         st.disablefd = suspend_open();
230         st.disabled = 0;
231         st.conns = NULL;
232         st.active_count = 0;
233
234         s = socket(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK|SOCK_CLOEXEC, 0);
235         addr.sun_family = AF_UNIX;
236         strcpy(addr.sun_path, "/run/suspend/wakealarm");
237         unlink("/run/suspend/wakealarm");
238         if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0)
239                 exit(2);
240         listen(s, 20);
241
242         event_init();
243         st.watcher = suspend_watch(do_suspend, do_resume, &st);
244         event_set(&st.ev, s, EV_READ | EV_PERSIST, do_accept, &st);
245         event_add(&st.ev, NULL);
246         evtimer_set(&st.tev, do_timeout, &st);
247
248         event_loop(0);
249         exit(0);
250 }