chiark / gitweb /
Prep v239: Add support for suspend-then-hibernate to loginctl.
[elogind.git] / src / login / eloginctl.c
1 /***
2   This file is part of elogind.
3
4   Copyright 2017 Sven Eden
5
6   elogind is free software; you can redistribute it and/or modify it
7   under the terms of the GNU Lesser General Public License as published by
8   the Free Software Foundation; either version 2.1 of the License, or
9   (at your option) any later version.
10
11   elogind is distributed in the hope that it will be useful, but
12   WITHOUT ANY WARRANTY; without even the implied warranty of
13   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14   Lesser General Public License for more details.
15
16   You should have received a copy of the GNU Lesser General Public License
17   along with elogind; If not, see <http://www.gnu.org/licenses/>.
18 ***/
19
20
21 #include "bus-error.h"
22 #include "bus-util.h"
23 #include "eloginctl.h"
24 #include "logind-action.h"
25 #include "parse-util.h"
26 #include "process-util.h"
27 #include "sd-login.h"
28 #include "sd-messages.h"
29 #include "spawn-polkit-agent.h"
30 #include "string-util.h"
31 #include "strv.h"
32 #include "terminal-util.h"
33 #include "user-util.h"
34 #include "virt.h"
35
36
37 elogind_action arg_action            = _ACTION_INVALID;
38 bool           arg_ask_password      = true;
39 bool           arg_dry_run           = false;
40 bool           arg_quiet             = false;
41 bool           arg_ignore_inhibitors = false;
42 bool           arg_no_wall           = false;
43 BusTransport   arg_transport         = BUS_TRANSPORT_LOCAL;
44 char**         arg_wall              = NULL;
45 usec_t         arg_when              = 0;
46
47 static const struct {
48         HandleAction action;
49         const char*  verb;
50 } action_table[_ACTION_MAX] = {
51         [ACTION_HALT]                   = { HANDLE_HALT,                   "halt"         },
52         [ACTION_POWEROFF]               = { HANDLE_POWEROFF,               "poweroff",    },
53         [ACTION_REBOOT]                 = { HANDLE_REBOOT,                 "reboot",      },
54         [ACTION_KEXEC]                  = { HANDLE_KEXEC,                  "kexec",       },
55         [ACTION_SUSPEND]                = { HANDLE_SUSPEND,                "suspend",     },
56         [ACTION_HIBERNATE]              = { HANDLE_HIBERNATE,              "hibernate",   },
57         [ACTION_HYBRID_SLEEP]           = { HANDLE_HYBRID_SLEEP,           "hybrid-sleep" },
58         [ACTION_SUSPEND_THEN_HIBERNATE] = { HANDLE_SUSPEND_THEN_HIBERNATE, "suspend-then-hibernate" }
59         /* ACTION_CANCEL_SHUTDOWN is handled differently */
60 };
61
62 static int elogind_set_wall_message(sd_bus* bus, const char* msg);
63
64 static enum elogind_action verb_to_action(const char *verb) {
65         enum elogind_action i;
66
67         for (i = _ACTION_INVALID; i < _ACTION_MAX; i++)
68                 if (streq_ptr(action_table[i].verb, verb))
69                         return i;
70
71         return _ACTION_INVALID;
72 }
73
74 /* Original:
75  * systemctl/systemctl.c:3314:logind_check_inhibitors()
76  */
77 static int check_inhibitors(sd_bus* bus, enum elogind_action a) {
78         _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
79         _cleanup_strv_free_ char **sessions = NULL;
80         const char *what, *who, *why, *mode;
81         uint32_t uid, pid;
82         unsigned c = 0;
83         char **s;
84         int r;
85
86         if (arg_ignore_inhibitors)
87                 return 0;
88
89         if (arg_when > 0)
90                 return 0;
91
92         if (geteuid() == 0)
93                 return 0;
94
95         if (!on_tty())
96                 return 0;
97
98         if (arg_transport != BUS_TRANSPORT_LOCAL)
99                 return 0;
100
101         r = sd_bus_call_method(
102                         bus,
103                         "org.freedesktop.login1",
104                         "/org/freedesktop/login1",
105                         "org.freedesktop.login1.Manager",
106                         "ListInhibitors",
107                         NULL,
108                         &reply,
109                         NULL);
110         if (r < 0)
111                 /* If logind is not around, then there are no inhibitors... */
112                 return 0;
113
114         r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssssuu)");
115         if (r < 0)
116                 return bus_log_parse_error(r);
117
118         while ((r = sd_bus_message_read(reply, "(ssssuu)", &what, &who, &why, &mode, &uid, &pid)) > 0) {
119                 _cleanup_free_ char *comm = NULL, *user = NULL;
120                 _cleanup_strv_free_ char **sv = NULL;
121
122                 if (!streq(mode, "block"))
123                         continue;
124
125                 sv = strv_split(what, ":");
126                 if (!sv)
127                         return log_oom();
128
129                 if (!pid_is_valid((pid_t) pid)) {
130                         log_error("Invalid PID "PID_FMT".", (pid_t) pid);
131                         return -ERANGE;
132                 }
133
134                 if (!strv_contains(sv,
135                                    IN_SET(a,
136                                           ACTION_HALT,
137                                           ACTION_POWEROFF,
138                                           ACTION_REBOOT,
139                                           ACTION_KEXEC) ? "shutdown" : "sleep"))
140                         continue;
141
142                 get_process_comm(pid, &comm);
143                 user = uid_to_name(uid);
144
145                 log_warning("Operation inhibited by \"%s\" (PID "PID_FMT" \"%s\", user %s), reason is \"%s\".",
146                             who, (pid_t) pid, strna(comm), strna(user), why);
147
148                 c++;
149         }
150         if (r < 0)
151                 return bus_log_parse_error(r);
152
153         r = sd_bus_message_exit_container(reply);
154         if (r < 0)
155                 return bus_log_parse_error(r);
156
157         /* Check for current sessions */
158         sd_get_sessions(&sessions);
159         STRV_FOREACH(s, sessions) {
160                 _cleanup_free_ char *type = NULL, *tty = NULL, *seat = NULL, *user = NULL, *service = NULL, *class = NULL;
161
162                 if (sd_session_get_uid(*s, &uid) < 0 || uid == getuid())
163                         continue;
164
165                 if (sd_session_get_class(*s, &class) < 0 || !streq(class, "user"))
166                         continue;
167
168                 if (sd_session_get_type(*s, &type) < 0 || !STR_IN_SET(type, "x11", "wayland", "tty", "mir"))
169                         continue;
170
171                 sd_session_get_tty(*s, &tty);
172                 sd_session_get_seat(*s, &seat);
173                 sd_session_get_service(*s, &service);
174                 user = uid_to_name(uid);
175
176                 log_warning("User %s is logged in on %s.", strna(user), isempty(tty) ? (isempty(seat) ? strna(service) : seat) : tty);
177                 c++;
178         }
179
180         if (c <= 0)
181                 return 0;
182
183         log_error("Please retry operation after closing inhibitors and logging out other users.\nAlternatively, ignore inhibitors and users with 'loginctl %s -i'.",
184                   action_table[a].verb);
185
186         return -EPERM;
187 }
188
189 /* Original:
190  * systemctl/systemctl.c:8683:logind_cancel_shutdown()
191  */
192 int elogind_cancel_shutdown(sd_bus *bus) {
193         _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
194         int r;
195
196         (void) elogind_set_wall_message(bus, NULL);
197
198         r = sd_bus_call_method(
199                         bus,
200                         "org.freedesktop.login1",
201                         "/org/freedesktop/login1",
202                         "org.freedesktop.login1.Manager",
203                         "CancelScheduledShutdown",
204                         &error,
205                         NULL, NULL);
206         if (r < 0)
207                 return log_warning_errno(r,
208                         "Failed to talk to elogind, shutdown hasn't been cancelled: %s",
209                         bus_error_message(&error, r));
210
211         return 0;
212 }
213
214 /* Only a little helper for cleaning up elogind specific extra stuff. */
215 void elogind_cleanup(void) {
216         polkit_agent_close();
217         strv_free(arg_wall);
218 }
219
220 /* Little debug log helper, helps debugging systemctl comands we mimic. */
221 static void elogind_log_special(enum elogind_action a) {
222 #if ENABLE_DEBUG_ELOGIND
223         switch (a) {
224         case ACTION_HALT:
225                 log_struct(LOG_INFO,
226                            LOG_MESSAGE("Halt action called."),
227                            "MESSAGE_ID=" SD_MESSAGE_SHUTDOWN_STR,
228                            NULL);
229                 break;
230         case ACTION_POWEROFF:
231                 log_struct(LOG_INFO,
232                            LOG_MESSAGE("Poweroff action called."),
233                            "MESSAGE_ID=" SD_MESSAGE_SHUTDOWN_STR,
234                            NULL);
235                 break;
236         case ACTION_REBOOT:
237                 log_struct(LOG_INFO,
238                            LOG_MESSAGE("Reboot action called."),
239                            "MESSAGE_ID=" SD_MESSAGE_SHUTDOWN_STR,
240                            NULL);
241                 break;
242         case ACTION_KEXEC:
243                 log_struct(LOG_INFO,
244                            LOG_MESSAGE("KExec action called."),
245                            "MESSAGE_ID=" SD_MESSAGE_SHUTDOWN_STR,
246                            NULL);
247                 break;
248         case ACTION_SUSPEND:
249                 log_struct(LOG_INFO,
250                            LOG_MESSAGE("Suspend action called."),
251                            "MESSAGE_ID=" SD_MESSAGE_SLEEP_START_STR,
252                            NULL);
253                 break;
254         case ACTION_HIBERNATE:
255                 log_struct(LOG_INFO,
256                            LOG_MESSAGE("Hibernate action called."),
257                            "MESSAGE_ID=" SD_MESSAGE_SLEEP_START_STR,
258                            NULL);
259                 break;
260         case ACTION_HYBRID_SLEEP:
261                 log_struct(LOG_INFO,
262                            LOG_MESSAGE("Hybrid-Sleep action called."),
263                            "MESSAGE_ID=" SD_MESSAGE_SLEEP_START_STR,
264                            NULL);
265                 break;
266         case ACTION_SUSPEND_THEN_HIBERNATE:
267                 log_struct(LOG_INFO,
268                            LOG_MESSAGE("Suspend-Then-Hibernate action called."),
269                            "MESSAGE_ID=" SD_MESSAGE_SLEEP_START_STR,
270                            NULL);
271                 break;
272         case ACTION_CANCEL_SHUTDOWN:
273                 log_struct(LOG_INFO,
274                            LOG_MESSAGE("Cancel Shutdown called."),
275                            "MESSAGE_ID=" SD_MESSAGE_SHUTDOWN_STR,
276                            NULL);
277                 break;
278         default:
279                 break;
280         }
281 #endif // ENABLE_DEBUG_ELOGIND
282 }
283
284 /* Original:
285  * systemctl/systemctl.c:3242:logind_reboot()
286  */
287 static int elogind_reboot(sd_bus *bus, enum elogind_action a) {
288         _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
289         const char *method  = NULL, *description = NULL;
290         int r;
291         static const char *table[_ACTION_MAX] = {
292                 [ACTION_HALT]     = "The system is going down for halt NOW!",
293                 [ACTION_REBOOT]   = "The system is going down for reboot NOW!",
294                 [ACTION_POWEROFF] = "The system is going down for power-off NOW!"
295         };
296
297         if (!bus)
298                 return -EIO;
299
300         switch (a) {
301
302         case ACTION_HALT:
303                 method = "Halt";
304                 description = "halt system";
305                 break;
306
307         case ACTION_POWEROFF:
308                 method = "PowerOff";
309                 description = "power off system";
310                 break;
311
312         case ACTION_REBOOT:
313                 method = "Reboot";
314                 description = "reboot system";
315                 break;
316
317         case ACTION_SUSPEND:
318                 method = "Suspend";
319                 description = "suspend system";
320                 break;
321
322         case ACTION_HIBERNATE:
323                 method = "Hibernate";
324                 description = "hibernate system";
325                 break;
326
327         case ACTION_HYBRID_SLEEP:
328                 method = "HybridSleep";
329                 description = "put system into hybrid sleep";
330                 break;
331
332         case ACTION_SUSPEND_THEN_HIBERNATE:
333                 method = "SuspendThenHibernate";
334                 description = "put system into suspend followed by hibernate";
335                 break;
336
337         default:
338                 return -EINVAL;
339         }
340
341         /* No need for polkit_agent_open_maybe() in elogind. Do it directly. */
342         polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
343
344         if ( IN_SET(a, ACTION_HALT, ACTION_POWEROFF, ACTION_REBOOT) )
345                 (void) elogind_set_wall_message(bus, table[a]);
346
347         log_debug("%s org.freedesktop.login1.Manager %s dbus call.", arg_dry_run ? "Would execute" : "Executing", method);
348
349         if (arg_dry_run)
350                 return 0;
351
352         /* Now call elogind itself to request the operation */
353         r = sd_bus_call_method(
354                         bus,
355                         "org.freedesktop.login1",
356                         "/org/freedesktop/login1",
357                         "org.freedesktop.login1.Manager",
358                         method,
359                         &error,
360                         NULL,
361                         "b", arg_ask_password);
362
363         if (r < 0)
364                 return log_error_errno(r, "Failed to %s via elogind: %s",
365                                        description,
366                                        bus_error_message(&error, r));
367
368         return 0;
369 }
370
371 /* Original:
372  * systemctl/systemctl.c:8553:logind_schedule_shutdown()
373  */
374 static int elogind_schedule_shutdown(sd_bus *bus, enum elogind_action a) {
375         _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
376         char date[FORMAT_TIMESTAMP_MAX];
377         const char *action;
378         int r;
379
380         if (!bus)
381                 return -EIO;
382
383         switch (a) {
384         case ACTION_HALT:
385                 action = "halt";
386                 break;
387         case ACTION_POWEROFF:
388                 action = "poweroff";
389                 break;
390         case ACTION_KEXEC:
391                 action = "kexec";
392                 break;
393         case ACTION_REBOOT:
394         default:
395                 action = "reboot";
396                 break;
397         }
398
399         if (arg_dry_run)
400                 action = strjoina("dry-", action);
401
402         (void) elogind_set_wall_message(bus, NULL);
403
404         r = sd_bus_call_method(
405                         bus,
406                         "org.freedesktop.login1",
407                         "/org/freedesktop/login1",
408                         "org.freedesktop.login1.Manager",
409                         "ScheduleShutdown",
410                         &error,
411                         NULL,
412                         "st",
413                         action,
414                         arg_when);
415
416         if (r < 0)
417                 return log_warning_errno(r,
418                                 "Failed to call ScheduleShutdown in logind, proceeding with immediate shutdown: %s",
419                                 bus_error_message(&error, r));
420
421         if (!arg_quiet)
422                 log_info("Shutdown scheduled for %s, use 'shutdown -c' to cancel.", format_timestamp(date, sizeof(date), arg_when));
423
424         return 0;
425 }
426
427 /* Original:
428  * systemctl/systemctl.c:3204:logind_set_wall_message()
429  * (Tweaked to allow an extra message to be appended.)
430  */
431 static int elogind_set_wall_message(sd_bus* bus, const char* msg) {
432         _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
433         _cleanup_free_ char *m = NULL;
434         int r;
435
436         if (strv_extend(&arg_wall, msg) < 0)
437                 return log_oom();
438
439         m = strv_join(arg_wall, " ");
440         if (!m)
441                 return log_oom();
442
443         log_debug("%s wall message \"%s\".", arg_dry_run ? "Would set" : "Setting", m);
444         if (arg_dry_run)
445                 return 0;
446
447         r = sd_bus_call_method(
448                         bus,
449                         "org.freedesktop.login1",
450                         "/org/freedesktop/login1",
451                         "org.freedesktop.login1.Manager",
452                         "SetWallMessage",
453                         &error,
454                         NULL,
455                         "sb",
456                         m,
457                         !arg_no_wall);
458
459         if (r < 0)
460                 return log_warning_errno(r, "Failed to set wall message, ignoring: %s", bus_error_message(&error, r));
461
462         return 0;
463 }
464
465 /* Original:
466  * systemctl/systemctl.c:7743:parse_shutdown_time_spec()
467  */
468 static int parse_shutdown_time_spec(const char *t, usec_t *_u) {
469         assert(t);
470         assert(_u);
471
472         if (streq(t, "now"))
473                 *_u = 0;
474         else if (!strchr(t, ':')) {
475                 uint64_t u;
476
477                 if (safe_atou64(t, &u) < 0)
478                         return -EINVAL;
479
480                 *_u = now(CLOCK_REALTIME) + USEC_PER_MINUTE * u;
481         } else {
482                 char *e = NULL;
483                 long hour, minute;
484                 struct tm tm = {};
485                 time_t s;
486                 usec_t n;
487
488                 errno = 0;
489                 hour = strtol(t, &e, 10);
490                 if (errno > 0 || *e != ':' || hour < 0 || hour > 23)
491                         return -EINVAL;
492
493                 minute = strtol(e+1, &e, 10);
494                 if (errno > 0 || *e != 0 || minute < 0 || minute > 59)
495                         return -EINVAL;
496
497                 n = now(CLOCK_REALTIME);
498                 s = (time_t) (n / USEC_PER_SEC);
499
500                 assert_se(localtime_r(&s, &tm));
501
502                 tm.tm_hour = (int) hour;
503                 tm.tm_min = (int) minute;
504                 tm.tm_sec = 0;
505
506                 assert_se(s = mktime(&tm));
507
508                 *_u = (usec_t) s * USEC_PER_SEC;
509
510                 while (*_u <= n)
511                         *_u += USEC_PER_DAY;
512         }
513
514         return 0;
515 }
516
517 /* Original:
518  * systemctl/systemctl.c:3482:start_special()
519  * However, this elogind variant is very different from the original.
520  */
521 int start_special(int argc, char *argv[], void *userdata) {
522         sd_bus *bus = userdata;
523         enum elogind_action a;
524         int r;
525         char** wall = NULL;
526
527         assert(argv);
528
529         a = verb_to_action(argv[0]);
530
531         elogind_log_special(a);
532
533         /* For poweroff and reboot, some extra checks are performed: */
534         if ( IN_SET(a, ACTION_POWEROFF, ACTION_REBOOT) ) {
535
536                 /* No power off actions in chroot environments */
537                 if ( running_in_chroot() > 0 ) {
538                         log_info("Running in chroot, ignoring request.");
539                         return 0;
540                 }
541
542                 /* Check time argument */
543                 if ( (argc > 1) && (ACTION_CANCEL_SHUTDOWN != arg_action)) {
544                         r = parse_shutdown_time_spec(argv[1], &arg_when);
545                         if (r < 0) {
546                                 log_error("Failed to parse time specification: %s", argv[optind]);
547                                 return r;
548                         }
549                 }
550
551                 /* The optional user wall message must be set */
552                 if ( (argc > 1)
553                   && ( (arg_action == ACTION_CANCEL_SHUTDOWN)
554                     || (0 == arg_when) ) )
555                         /* No time argument for shutdown cancel, or no
556                          * time argument given. */
557                         wall = argv + 1;
558                 else if (argc > 2)
559                         /* We skip the time argument */
560                         wall = argv + 2;
561
562                 if (wall) {
563                         arg_wall = strv_copy(wall);
564                         if (!arg_wall)
565                                 return log_oom();
566                 }
567         }
568
569         /* Switch to cancel shutdown, if a shutdown action was requested,
570            and the option to cancel it was set: */
571         if ( IN_SET(a, ACTION_POWEROFF, ACTION_REBOOT)
572           && (arg_action == ACTION_CANCEL_SHUTDOWN) )
573                 return elogind_cancel_shutdown(bus);
574
575         r = check_inhibitors(bus, a);
576         if (r < 0)
577                 return r;
578
579         /* Perform requested action */
580         if (IN_SET(a,
581                    ACTION_POWEROFF,
582                    ACTION_REBOOT,
583                    ACTION_SUSPEND,
584                    ACTION_HIBERNATE,
585                    ACTION_HYBRID_SLEEP,
586                    ACTION_SUSPEND_THEN_HIBERNATE)) {
587                 if (arg_when > 0)
588                         return elogind_schedule_shutdown(bus, a);
589                 else
590                         return elogind_reboot(bus, a);
591         }
592
593         return -EOPNOTSUPP;
594 }
595