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