chiark / gitweb /
Prep v228: Condense elogind source masks (5/5)
[elogind.git] / src / login / logind-action.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 <unistd.h>
23
24 #include "alloc-util.h"
25 #include "bus-error.h"
26 #include "bus-util.h"
27 #include "conf-parser.h"
28 #include "formats-util.h"
29 #include "logind-action.h"
30 #include "process-util.h"
31 #include "sleep-config.h"
32 //#include "special.h"
33 #include "string-table.h"
34 #include "terminal-util.h"
35 #include "user-util.h"
36
37 // Additional includes needed by elogind
38 #include "fd-util.h"
39 #include "fileio.h"
40 #include "sd-messages.h"
41 #include "strv.h"
42
43
44 int manager_handle_action(
45                 Manager *m,
46                 InhibitWhat inhibit_key,
47                 HandleAction handle,
48                 bool ignore_inhibited,
49                 bool is_edge) {
50
51         static const char * const message_table[_HANDLE_ACTION_MAX] = {
52                 [HANDLE_POWEROFF] = "Powering Off...",
53                 [HANDLE_REBOOT] = "Rebooting...",
54                 [HANDLE_HALT] = "Halting...",
55                 [HANDLE_KEXEC] = "Rebooting via kexec...",
56                 [HANDLE_SUSPEND] = "Suspending...",
57                 [HANDLE_HIBERNATE] = "Hibernating...",
58                 [HANDLE_HYBRID_SLEEP] = "Hibernating and suspending..."
59         };
60
61 #if 0 /// elogind does this itself. No target table required
62         static const char * const target_table[_HANDLE_ACTION_MAX] = {
63                 [HANDLE_POWEROFF] = SPECIAL_POWEROFF_TARGET,
64                 [HANDLE_REBOOT] = SPECIAL_REBOOT_TARGET,
65                 [HANDLE_HALT] = SPECIAL_HALT_TARGET,
66                 [HANDLE_KEXEC] = SPECIAL_KEXEC_TARGET,
67                 [HANDLE_SUSPEND] = SPECIAL_SUSPEND_TARGET,
68                 [HANDLE_HIBERNATE] = SPECIAL_HIBERNATE_TARGET,
69                 [HANDLE_HYBRID_SLEEP] = SPECIAL_HYBRID_SLEEP_TARGET
70         };
71 #endif // 0
72
73         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
74         InhibitWhat inhibit_operation;
75         Inhibitor *offending = NULL;
76         bool supported;
77         int r;
78
79         assert(m);
80
81         /* If the key handling is turned off, don't do anything */
82         if (handle == HANDLE_IGNORE) {
83                 log_debug("Refusing operation, as it is turned off.");
84                 return 0;
85         }
86
87         if (inhibit_key == INHIBIT_HANDLE_LID_SWITCH) {
88                 /* If the last system suspend or startup is too close,
89                  * let's not suspend for now, to give USB docking
90                  * stations some time to settle so that we can
91                  * properly watch its displays. */
92                 if (m->lid_switch_ignore_event_source) {
93                         log_debug("Ignoring lid switch request, system startup or resume too close.");
94                         return 0;
95                 }
96         }
97
98         /* If the key handling is inhibited, don't do anything */
99         if (inhibit_key > 0) {
100                 if (manager_is_inhibited(m, inhibit_key, INHIBIT_BLOCK, NULL, true, false, 0, NULL)) {
101                         log_debug("Refusing operation, %s is inhibited.", inhibit_what_to_string(inhibit_key));
102                         return 0;
103                 }
104         }
105
106         /* Locking is handled differently from the rest. */
107         if (handle == HANDLE_LOCK) {
108
109                 if (!is_edge)
110                         return 0;
111
112                 log_info("Locking sessions...");
113                 session_send_lock_all(m, true);
114                 return 1;
115         }
116
117         if (handle == HANDLE_SUSPEND)
118                 supported = can_sleep("suspend") > 0;
119         else if (handle == HANDLE_HIBERNATE)
120                 supported = can_sleep("hibernate") > 0;
121         else if (handle == HANDLE_HYBRID_SLEEP)
122                 supported = can_sleep("hybrid-sleep") > 0;
123         else if (handle == HANDLE_KEXEC)
124                 supported = access(KEXEC, X_OK) >= 0;
125         else
126                 supported = true;
127
128         if (!supported) {
129                 log_warning("Requested operation not supported, ignoring.");
130                 return -EOPNOTSUPP;
131         }
132
133         if (m->action_what) {
134                 log_debug("Action already in progress, ignoring.");
135                 return -EALREADY;
136         }
137
138         inhibit_operation = handle == HANDLE_SUSPEND || handle == HANDLE_HIBERNATE || handle == HANDLE_HYBRID_SLEEP ? INHIBIT_SLEEP : INHIBIT_SHUTDOWN;
139
140         /* If the actual operation is inhibited, warn and fail */
141         if (!ignore_inhibited &&
142             manager_is_inhibited(m, inhibit_operation, INHIBIT_BLOCK, NULL, false, false, 0, &offending)) {
143                 _cleanup_free_ char *comm = NULL, *u = NULL;
144
145                 get_process_comm(offending->pid, &comm);
146                 u = uid_to_name(offending->uid);
147
148                 /* If this is just a recheck of the lid switch then don't warn about anything */
149                 if (!is_edge) {
150                         log_debug("Refusing operation, %s is inhibited by UID "UID_FMT"/%s, PID "PID_FMT"/%s.",
151                                   inhibit_what_to_string(inhibit_operation),
152                                   offending->uid, strna(u),
153                                   offending->pid, strna(comm));
154                         return 0;
155                 }
156
157                 log_error("Refusing operation, %s is inhibited by UID "UID_FMT"/%s, PID "PID_FMT"/%s.",
158                           inhibit_what_to_string(inhibit_operation),
159                           offending->uid, strna(u),
160                           offending->pid, strna(comm));
161
162                 return -EPERM;
163         }
164
165         log_info("%s", message_table[handle]);
166
167 #if 0 /// elogind uses its own variant, which can use the handle directly.
168         r = bus_manager_shutdown_or_sleep_now_or_later(m, target_table[handle], inhibit_operation, &error);
169 #else
170         r = bus_manager_shutdown_or_sleep_now_or_later(m, handle, inhibit_operation, &error);
171 #endif // 0
172         if (r < 0) {
173                 log_error("Failed to execute operation: %s", bus_error_message(&error, r));
174                 return r;
175         }
176
177         return 1;
178 }
179
180 static int run_helper(const char *helper) {
181         int pid = fork();
182         if (pid < 0) {
183                 return log_error_errno(errno, "Failed to fork: %m");
184         }
185
186         if (pid == 0) {
187                 /* Child */
188
189                 close_all_fds(NULL, 0);
190
191                 execlp(helper, helper, NULL);
192                 log_error_errno(errno, "Failed to execute %s: %m", helper);
193                 _exit(EXIT_FAILURE);
194         }
195
196         return wait_for_terminate_and_warn(helper, pid, true);
197 }
198
199 static int write_mode(char **modes) {
200         int r = 0;
201         char **mode;
202
203         STRV_FOREACH(mode, modes) {
204                 int k;
205
206                 k = write_string_file("/sys/power/disk", *mode, 0);
207                 if (k == 0)
208                         return 0;
209
210                 log_debug_errno(k, "Failed to write '%s' to /sys/power/disk: %m",
211                                 *mode);
212                 if (r == 0)
213                         r = k;
214         }
215
216         if (r < 0)
217                 log_error_errno(r, "Failed to write mode to /sys/power/disk: %m");
218
219         return r;
220 }
221
222 static int write_state(FILE **f, char **states) {
223         char **state;
224         int r = 0;
225
226         STRV_FOREACH(state, states) {
227                 int k;
228
229                 k = write_string_stream(*f, *state, true);
230                 if (k == 0)
231                         return 0;
232                 log_debug_errno(k, "Failed to write '%s' to /sys/power/state: %m",
233                                 *state);
234                 if (r == 0)
235                         r = k;
236
237                 fclose(*f);
238                 *f = fopen("/sys/power/state", "we");
239                 if (!*f)
240                         return log_error_errno(errno, "Failed to open /sys/power/state: %m");
241         }
242
243         return r;
244 }
245
246 static int do_sleep(const char *arg_verb, char **modes, char **states) {
247         char *arguments[] = {
248                 NULL,
249                 (char*) "pre",
250                 (char*) arg_verb,
251                 NULL
252         };
253         static const char* const dirs[] = {SYSTEM_SLEEP_PATH, NULL};
254
255         int r;
256         _cleanup_fclose_ FILE *f = NULL;
257
258         /* This file is opened first, so that if we hit an error,
259          * we can abort before modifying any state. */
260         f = fopen("/sys/power/state", "we");
261         if (!f)
262                 return log_error_errno(errno, "Failed to open /sys/power/state: %m");
263
264         /* Configure the hibernation mode */
265         r = write_mode(modes);
266         if (r < 0)
267                 return r;
268
269         execute_directories(dirs, DEFAULT_TIMEOUT_USEC, arguments);
270
271         log_struct(LOG_INFO,
272                    LOG_MESSAGE_ID(SD_MESSAGE_SLEEP_START),
273                    LOG_MESSAGE("Suspending system..."),
274                    "SLEEP=%s", arg_verb,
275                    NULL);
276
277         r = write_state(&f, states);
278         if (r < 0)
279                 return r;
280
281         log_struct(LOG_INFO,
282                    LOG_MESSAGE_ID(SD_MESSAGE_SLEEP_STOP),
283                    LOG_MESSAGE("System resumed."),
284                    "SLEEP=%s", arg_verb,
285                    NULL);
286
287         arguments[1] = (char*) "post";
288         execute_directories(dirs, DEFAULT_TIMEOUT_USEC, arguments);
289
290         return r;
291 }
292
293 int shutdown_or_sleep(Manager *m, HandleAction action) {
294
295         assert(m);
296
297         switch (action) {
298         case HANDLE_POWEROFF:
299                 return run_helper(HALT);
300         case HANDLE_REBOOT:
301                 return run_helper(REBOOT);
302         case HANDLE_HALT:
303                 return run_helper(HALT);
304         case HANDLE_KEXEC:
305                 return run_helper(KEXEC);
306         case HANDLE_SUSPEND:
307                 return do_sleep("suspend", m->suspend_mode, m->suspend_state);
308         case HANDLE_HIBERNATE:
309                 return do_sleep("hibernate", m->hibernate_mode, m->hibernate_state);
310         case HANDLE_HYBRID_SLEEP:
311                 return do_sleep("hybrid-sleep", m->hybrid_sleep_mode, m->hybrid_sleep_state);
312         default:
313                 return -EINVAL;
314         }
315 }
316
317 static const char* const handle_action_table[_HANDLE_ACTION_MAX] = {
318         [HANDLE_IGNORE] = "ignore",
319         [HANDLE_POWEROFF] = "poweroff",
320         [HANDLE_REBOOT] = "reboot",
321         [HANDLE_HALT] = "halt",
322         [HANDLE_KEXEC] = "kexec",
323         [HANDLE_SUSPEND] = "suspend",
324         [HANDLE_HIBERNATE] = "hibernate",
325         [HANDLE_HYBRID_SLEEP] = "hybrid-sleep",
326         [HANDLE_LOCK] = "lock"
327 };
328
329 DEFINE_STRING_TABLE_LOOKUP(handle_action, HandleAction);
330 DEFINE_CONFIG_PARSE_ENUM(config_parse_handle_action, handle_action, HandleAction, "Failed to parse handle action setting");