]> git.neil.brown.name Git - susman.git/blob - watcher.c
wakealarmd: cope with delta between system time and RTC time.
[susman.git] / watcher.c
1
2 /*
3  * Use libevent to watch for suspends and take action.
4  * The calling program must already have a libevent loop running.
5  * One or two callbacks are registered with suspend_watch.
6  * The first is required and gets called just before suspend.
7  * It must return promptly but may call suspend_block first.
8  * The second is options and will get called after resume.
9  *
10  *
11  * Copyright (C) 2011 Neil Brown <neilb@suse.de>
12  *
13  *    This program is free software; you can redistribute it and/or modify
14  *    it under the terms of the GNU General Public License as published by
15  *    the Free Software Foundation; either version 2 of the License, or
16  *    (at your option) any later version.
17  *
18  *    This program is distributed in the hope that it will be useful,
19  *    but WITHOUT ANY WARRANTY; without even the implied warranty of
20  *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21  *    GNU General Public License for more details.
22  *
23  *    You should have received a copy of the GNU General Public License along
24  *    with this program; if not, write to the Free Software Foundation, Inc.,
25  *    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
26  */
27
28 #define _GNU_SOURCE
29 #include <stdlib.h>
30 #include <signal.h>
31 #include <fcntl.h>
32 #include <event.h>
33 #include <sys/stat.h>
34 #include <malloc.h>
35
36 struct cb {
37         int (*will_suspend)(void *data);
38         void (*did_resume)(void *data);
39         void *data;
40         int dirfd;
41         int fd, nextfd;
42         struct event ev;
43 };
44
45 static void checkdir(int efd, short ev, void *vp)
46 {
47         struct cb *han = vp;
48         struct stat stb;
49         int fd;
50         int rv;
51
52         if (han->fd < 0)
53                 /* too early */
54                 return;
55
56         if (han->nextfd >= 0) {
57                 /*suspended - maybe not any more */
58                 fstat(han->fd, &stb);
59                 if (stb.st_size <= 0)
60                         /*false alarm */
61                         return;
62                 /* back from resume */
63                 close(han->fd);
64                 han->fd = han->nextfd;
65                 han->nextfd = -1;
66                 if (han->did_resume)
67                         han->did_resume(han->data);
68                 /* Fall through incase suspend has started again */
69         }
70
71         /* not suspended yet */
72         if (fstat(han->fd, &stb) == 0
73             && stb.st_size == 0)
74                 /* false alarm */
75                 return;
76
77         /* We need to move on now. */
78         fd = open("/run/suspend/watching-next", O_RDONLY|O_CLOEXEC);
79         flock(fd, LOCK_SH);
80         han->nextfd = fd;
81         rv = han->will_suspend(han->data);
82         if (rv)
83                 suspend_ok(han);
84 }
85
86 int suspend_ok(void *v)
87 {
88         struct cb *han = v;
89         flock(han->fd, LOCK_UN);
90         return 1;
91 }
92
93 void *suspend_watch(int (*will_suspend)(void *data),
94                     void (*did_resume)(void *data),
95                     void *data)
96 {
97         struct cb *han = malloc(sizeof(*han));
98         struct stat stb;
99         int fd = -1;
100         if (!han)
101                 return NULL;
102
103         han->data = data;
104         han->will_suspend = will_suspend;
105         han->did_resume = did_resume;
106         han->fd = -1;
107         han->nextfd = -1;
108         signal_set(&han->ev, SIGIO, checkdir, han);
109         signal_add(&han->ev, NULL);
110         han->dirfd = open("/run/suspend", O_RDONLY|O_CLOEXEC);
111         if (han->dirfd < 0)
112                 goto abort;
113         fcntl(han->dirfd, F_NOTIFY, DN_MODIFY | DN_MULTISHOT);
114 again:
115         fd = open("/run/suspend/watching", O_RDONLY|O_CLOEXEC);
116         flock(fd, LOCK_SH);
117         han->fd = fd;
118         checkdir(0, 0, han);
119         /* OK, he won't suspend until I say OK. */
120
121         return han;
122 abort:
123         signal_del(&han->ev);
124         if (fd >= 0)
125                 close(fd);
126         if (han->dirfd >= 0)
127                 close(han->dirfd);
128         free(han);
129 }
130
131 void suspend_unwatch(void *v)
132 {
133         struct cb *han = v;
134         if (han->dirfd >= 0)
135                 close(han->dirfd);
136         signal_del(&han->ev);
137         if (han->fd >= 0)
138                 close(han->fd);
139         if (han->nextfd >= 0)
140                 close(han->nextfd);
141         free(han);
142 }
143