chiark / gitweb /
logind: allow users to override their own suspend/sleep inhibitors
[elogind.git] / src / login / logind-button.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4   This file is part of systemd.
5
6   Copyright 2012 Lennart Poettering
7
8   systemd is free software; you can redistribute it and/or modify it
9   under the terms of the GNU Lesser General Public License as published by
10   the Free Software Foundation; either version 2.1 of the License, or
11   (at your option) any later version.
12
13   systemd is distributed in the hope that it will be useful, but
14   WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16   Lesser General Public License for more details.
17
18   You should have received a copy of the GNU Lesser General Public License
19   along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <assert.h>
23 #include <string.h>
24 #include <errno.h>
25 #include <fcntl.h>
26 #include <sys/ioctl.h>
27 #include <unistd.h>
28 #include <linux/input.h>
29 #include <sys/epoll.h>
30
31 #include "conf-parser.h"
32 #include "util.h"
33 #include "logind-button.h"
34 #include "special.h"
35 #include "dbus-common.h"
36
37 Button* button_new(Manager *m, const char *name) {
38         Button *b;
39
40         assert(m);
41         assert(name);
42
43         b = new0(Button, 1);
44         if (!b)
45                 return NULL;
46
47         b->name = strdup(name);
48         if (!b->name) {
49                 free(b);
50                 return NULL;
51         }
52
53         if (hashmap_put(m->buttons, b->name, b) < 0) {
54                 free(b->name);
55                 free(b);
56                 return NULL;
57         }
58
59         b->manager = m;
60         b->fd = -1;
61
62         return b;
63 }
64
65 void button_free(Button *b) {
66         assert(b);
67
68         hashmap_remove(b->manager->buttons, b->name);
69
70         if (b->fd >= 0) {
71                 hashmap_remove(b->manager->button_fds, INT_TO_PTR(b->fd + 1));
72                 assert_se(epoll_ctl(b->manager->epoll_fd, EPOLL_CTL_DEL, b->fd, NULL) == 0);
73                 close_nointr_nofail(b->fd);
74         }
75
76         free(b->name);
77         free(b->seat);
78         free(b);
79 }
80
81 int button_set_seat(Button *b, const char *sn) {
82         char *s;
83
84         assert(b);
85         assert(sn);
86
87         s = strdup(sn);
88         if (!s)
89                 return -ENOMEM;
90
91         free(b->seat);
92         b->seat = s;
93
94         return 0;
95 }
96
97 int button_open(Button *b) {
98         char name[256], *p;
99         struct epoll_event ev;
100         int r;
101
102         assert(b);
103
104         if (b->fd >= 0) {
105                 close_nointr_nofail(b->fd);
106                 b->fd = -1;
107         }
108
109         p = strappend("/dev/input/", b->name);
110         if (!p)
111                 return log_oom();
112
113         b->fd = open(p, O_RDWR|O_CLOEXEC|O_NOCTTY|O_NONBLOCK);
114         free(p);
115         if (b->fd < 0) {
116                 log_warning("Failed to open %s: %m", b->name);
117                 return -errno;
118         }
119
120         if (ioctl(b->fd, EVIOCGNAME(sizeof(name)), name) < 0) {
121                 log_error("Failed to get input name: %m");
122                 r = -errno;
123                 goto fail;
124         }
125
126         zero(ev);
127         ev.events = EPOLLIN;
128         ev.data.u32 = FD_OTHER_BASE + b->fd;
129
130         if (epoll_ctl(b->manager->epoll_fd, EPOLL_CTL_ADD, b->fd, &ev) < 0) {
131                 log_error("Failed to add to epoll: %m");
132                 r = -errno;
133                 goto fail;
134         }
135
136         r = hashmap_put(b->manager->button_fds, INT_TO_PTR(b->fd + 1), b);
137         if (r < 0) {
138                 log_error("Failed to add to hash map: %s", strerror(-r));
139                 assert_se(epoll_ctl(b->manager->epoll_fd, EPOLL_CTL_DEL, b->fd, NULL) == 0);
140                 goto fail;
141         }
142
143         log_info("Watching system buttons on /dev/input/%s (%s)", b->name, name);
144
145         return 0;
146
147 fail:
148         close_nointr_nofail(b->fd);
149         b->fd = -1;
150         return r;
151 }
152
153 static int button_handle(
154                 Button *b,
155                 InhibitWhat inhibit_key,
156                 HandleButton handle,
157                 bool ignore_inhibited,
158                 bool is_edge) {
159
160         static const char * const message_table[_HANDLE_BUTTON_MAX] = {
161                 [HANDLE_POWEROFF] = "Powering Off...",
162                 [HANDLE_REBOOT] = "Rebooting...",
163                 [HANDLE_HALT] = "Halting...",
164                 [HANDLE_KEXEC] = "Rebooting via kexec...",
165                 [HANDLE_SUSPEND] = "Suspending...",
166                 [HANDLE_HIBERNATE] = "Hibernating..."
167         };
168
169         static const char * const target_table[_HANDLE_BUTTON_MAX] = {
170                 [HANDLE_POWEROFF] = "poweroff.target",
171                 [HANDLE_REBOOT] = "reboot.target",
172                 [HANDLE_HALT] = "halt.target",
173                 [HANDLE_KEXEC] = "kexec.target",
174                 [HANDLE_SUSPEND] = "suspend.target",
175                 [HANDLE_HIBERNATE] = "hibernate.target"
176         };
177
178         DBusError error;
179         int r;
180         InhibitWhat inhibit_operation;
181
182         assert(b);
183
184         /* If the key handling is turned off, don't do anything */
185         if (handle == HANDLE_IGNORE) {
186                 log_debug("Refusing key handling, as it is turned off.");
187                 return 0;
188         }
189
190         /* If the key handling is inhibited, don't do anything */
191         if (manager_is_inhibited(b->manager, inhibit_key, INHIBIT_BLOCK, NULL, true, false, 0)) {
192                 log_debug("Refusing key handling, %s is inhibited.", inhibit_what_to_string(inhibit_key));
193                 return 0;
194         }
195
196         inhibit_operation = handle == HANDLE_SUSPEND || handle == HANDLE_HIBERNATE ? INHIBIT_SLEEP : INHIBIT_SHUTDOWN;
197
198         /* If the actual operation is inhibited, warn and fail */
199         if (!ignore_inhibited &&
200             manager_is_inhibited(b->manager, inhibit_operation, INHIBIT_BLOCK, NULL, false, false, 0)) {
201
202
203                 /* If this is just a recheck of the lid switch then don't warn about anything */
204                 if (!is_edge) {
205                         log_debug("Refusing operation, %s is inhibited.", inhibit_what_to_string(inhibit_operation));
206                         return 0;
207                 }
208
209                 log_error("Refusing operation, %s is inhibited.", inhibit_what_to_string(inhibit_operation));
210                 warn_melody();
211                 return -EPERM;
212         }
213
214         log_info("%s", message_table[handle]);
215
216         /* We are executing the operation, so make sure we don't
217          * execute another one until the lid is opened/closed again */
218         b->lid_close_queued = false;
219
220         dbus_error_init(&error);
221         r = bus_manager_shutdown_or_sleep_now_or_later(b->manager, target_table[handle], inhibit_operation, &error);
222         if (r < 0) {
223                 log_error("Failed to execute operation: %s", bus_error_message(&error));
224                 dbus_error_free(&error);
225                 return r;
226         }
227
228         return 1;
229 }
230
231 int button_process(Button *b) {
232         struct input_event ev;
233         ssize_t l;
234
235         assert(b);
236
237         l = read(b->fd, &ev, sizeof(ev));
238         if (l < 0)
239                 return errno != EAGAIN ? -errno : 0;
240         if ((size_t) l < sizeof(ev))
241                 return -EIO;
242
243         if (ev.type == EV_KEY && ev.value > 0) {
244
245                 switch (ev.code) {
246
247                 case KEY_POWER:
248                 case KEY_POWER2:
249                         log_info("Power key pressed.");
250                         return button_handle(b, INHIBIT_HANDLE_POWER_KEY, b->manager->handle_power_key, b->manager->power_key_ignore_inhibited, true);
251
252                 case KEY_SLEEP:
253                 case KEY_SUSPEND:
254                         log_info("Sleep key pressed.");
255                         return button_handle(b, INHIBIT_HANDLE_SLEEP_KEY, b->manager->handle_sleep_key, b->manager->sleep_key_ignore_inhibited, true);
256
257                 }
258         } else if (ev.type == EV_SW && ev.value > 0) {
259
260                 switch (ev.code) {
261
262                 case SW_LID:
263                         log_info("Lid closed.");
264                         b->lid_close_queued = true;
265
266                         return button_handle(b, INHIBIT_HANDLE_LID_SWITCH, b->manager->handle_lid_switch, b->manager->lid_switch_ignore_inhibited, true);
267                 }
268
269         } else if (ev.type == EV_SW && ev.value == 0) {
270
271                 switch (ev.code) {
272
273                 case SW_LID:
274                         log_info("Lid opened.");
275                         b->lid_close_queued = false;
276                         break;
277                 }
278         }
279
280         return 0;
281 }
282
283 int button_recheck(Button *b) {
284         assert(b);
285
286         if (!b->lid_close_queued)
287                 return 0;
288
289         return button_handle(b, INHIBIT_HANDLE_LID_SWITCH, b->manager->handle_lid_switch, b->manager->lid_switch_ignore_inhibited, false);
290 }
291
292 static const char* const handle_button_table[_HANDLE_BUTTON_MAX] = {
293         [HANDLE_IGNORE] = "ignore",
294         [HANDLE_POWEROFF] = "poweroff",
295         [HANDLE_REBOOT] = "reboot",
296         [HANDLE_HALT] = "halt",
297         [HANDLE_KEXEC] = "kexec",
298         [HANDLE_SUSPEND] = "suspend",
299         [HANDLE_HIBERNATE] = "hibernate"
300 };
301 DEFINE_STRING_TABLE_LOOKUP(handle_button, HandleButton);
302 DEFINE_CONFIG_PARSE_ENUM(config_parse_handle_button, handle_button, HandleButton, "Failed to parse handle button setting");