chiark / gitweb /
Prep v233.3: Add 'loginctl list' as a shorthand for list-sessions
[elogind.git] / src / login / elogind.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-util.h"
22 #include "cgroup.h"
23 #include "elogind.h"
24 #include "fd-util.h"
25 #include "mount-setup.h"
26 #include "socket-util.h"
27 #include "string-util.h"
28 #include "strv.h"
29 #include "umask-util.h"
30
31
32 #define CGROUPS_AGENT_RCVBUF_SIZE (8*1024*1024)
33
34
35 static int signal_agent_released(sd_bus_message *message, void *userdata, sd_bus_error *error) {
36         _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL;
37         Manager *m = userdata;
38         const char *cgroup;
39         uid_t sender_uid;
40         int r;
41
42         assert(message);
43         assert(m);
44
45         /* only accept org.freedesktop.login1.Agent from UID=0 */
46         r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_EUID, &creds);
47         if (r < 0)
48                 return r;
49
50         r = sd_bus_creds_get_euid(creds, &sender_uid);
51         if (r < 0 || sender_uid != 0)
52                 return 0;
53
54         /* parse 'cgroup-empty' notification */
55         r = sd_bus_message_read(message, "s", &cgroup);
56         if (r < 0) {
57                 bus_log_parse_error(r);
58                 return 0;
59         }
60
61         manager_notify_cgroup_empty(m, cgroup);
62
63         return 0;
64 }
65
66 /// Add-On for manager_connect_bus()
67 /// Original: src/core/dbus.c:bus_setup_system()
68 void elogind_bus_setup_system(Manager* m) {
69         int r;
70
71         assert(m);
72         assert(m->bus);
73
74         /* if we are a user instance we get the Released message via the system bus */
75         if (MANAGER_IS_USER(m)) {
76                 r = sd_bus_add_match(
77                                 m->bus,
78                                 NULL,
79                                 "type='signal',"
80                                 "interface='org.freedesktop.login1.Agent',"
81                                 "member='Released',"
82                                 "path='/org/freedesktop.login1/agent'",
83                                 signal_agent_released, m);
84                 if (r < 0)
85                         log_warning_errno(r, "Failed to register Released match on system bus: %m");
86         }
87
88         log_debug("Successfully connected to system bus.");
89 }
90
91 static int manager_dispatch_cgroups_agent_fd(sd_event_source *source, int fd, uint32_t revents, void *userdata) {
92         Manager *m = userdata;
93         char buf[PATH_MAX+1];
94         ssize_t n;
95
96         n = recv(fd, buf, sizeof(buf), 0);
97         if (n < 0)
98                 return log_error_errno(errno, "Failed to read cgroups agent message: %m");
99         if (n == 0) {
100                 log_error("Got zero-length cgroups agent message, ignoring.");
101                 return 0;
102         }
103         if ((size_t) n >= sizeof(buf)) {
104                 log_error("Got overly long cgroups agent message, ignoring.");
105                 return 0;
106         }
107
108         if (memchr(buf, 0, n)) {
109                 log_error("Got cgroups agent message with embedded NUL byte, ignoring.");
110                 return 0;
111         }
112         buf[n] = 0;
113
114         manager_notify_cgroup_empty(m, buf);
115
116         return 0;
117 }
118
119 /// Add-On for manager_connect_bus()
120 /// Original: src/core/manager.c:manager_setup_cgroups_agent()
121 int elogind_setup_cgroups_agent(Manager *m) {
122
123         static const union sockaddr_union sa = {
124                 .un.sun_family = AF_UNIX,
125                 .un.sun_path = "/run/systemd/cgroups-agent",
126         };
127         int r = 0;
128
129         /* This creates a listening socket we receive cgroups agent messages on. We do not use D-Bus for delivering
130          * these messages from the cgroups agent binary to PID 1, as the cgroups agent binary is very short-living, and
131          * each instance of it needs a new D-Bus connection. Since D-Bus connections are SOCK_STREAM/AF_UNIX, on
132          * overloaded systems the backlog of the D-Bus socket becomes relevant, as not more than the configured number
133          * of D-Bus connections may be queued until the kernel will start dropping further incoming connections,
134          * possibly resulting in lost cgroups agent messages. To avoid this, we'll use a private SOCK_DGRAM/AF_UNIX
135          * socket, where no backlog is relevant as communication may take place without an actual connect() cycle, and
136          * we thus won't lose messages.
137          *
138          * Note that PID 1 will forward the agent message to system bus, so that the user systemd instance may listen
139          * to it. The system instance hence listens on this special socket, but the user instances listen on the system
140          * bus for these messages. */
141
142         if (m->test_run)
143                 return 0;
144
145         if (!MANAGER_IS_SYSTEM(m))
146                 return 0;
147
148         r = cg_unified_controller(SYSTEMD_CGROUP_CONTROLLER);
149         if (r < 0)
150                 return log_error_errno(r, "Failed to determine whether unified cgroups hierarchy is used: %m");
151         if (r > 0) /* We don't need this anymore on the unified hierarchy */
152                 return 0;
153
154         if (m->cgroups_agent_fd < 0) {
155                 _cleanup_close_ int fd = -1;
156
157                 /* First free all secondary fields */
158                 m->cgroups_agent_event_source = sd_event_source_unref(m->cgroups_agent_event_source);
159
160                 fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
161                 if (fd < 0)
162                         return log_error_errno(errno, "Failed to allocate cgroups agent socket: %m");
163
164                 fd_inc_rcvbuf(fd, CGROUPS_AGENT_RCVBUF_SIZE);
165
166                 (void) unlink(sa.un.sun_path);
167
168                 /* Only allow root to connect to this socket */
169                 RUN_WITH_UMASK(0077)
170                         r = bind(fd, &sa.sa, SOCKADDR_UN_LEN(sa.un));
171                 if (r < 0)
172                         return log_error_errno(errno, "bind(%s) failed: %m", sa.un.sun_path);
173
174                 m->cgroups_agent_fd = fd;
175                 fd = -1;
176         }
177
178         if (!m->cgroups_agent_event_source) {
179                 r = sd_event_add_io(m->event, &m->cgroups_agent_event_source, m->cgroups_agent_fd, EPOLLIN, manager_dispatch_cgroups_agent_fd, m);
180                 if (r < 0)
181                         return log_error_errno(r, "Failed to allocate cgroups agent event source: %m");
182
183                 /* Process cgroups notifications early, but after having processed service notification messages or
184                  * SIGCHLD signals, so that a cgroup running empty is always just the last safety net of notification,
185                  * and we collected the metadata the notification and SIGCHLD stuff offers first. Also see handling of
186                  * cgroup inotify for the unified cgroup stuff. */
187                 r = sd_event_source_set_priority(m->cgroups_agent_event_source, SD_EVENT_PRIORITY_NORMAL-5);
188                 if (r < 0)
189                         return log_error_errno(r, "Failed to set priority of cgroups agent event source: %m");
190
191                 (void) sd_event_source_set_description(m->cgroups_agent_event_source, "manager-cgroups-agent");
192         }
193
194         return 0;
195 }
196
197 /// Add-On for manager_free()
198 void elogind_manager_free(Manager* m) {
199
200         manager_shutdown_cgroup(m, true);
201
202         sd_event_source_unref(m->cgroups_agent_event_source);
203
204         safe_close(m->cgroups_agent_fd);
205
206         strv_free(m->suspend_mode);
207         strv_free(m->suspend_state);
208         strv_free(m->hibernate_mode);
209         strv_free(m->hibernate_state);
210         strv_free(m->hybrid_sleep_mode);
211         strv_free(m->hybrid_sleep_state);
212 }
213
214 /// Add-On for manager_new()
215 int elogind_manager_new(Manager* m) {
216         int r = 0;
217
218         m->cgroups_agent_fd = -1;
219         m->pin_cgroupfs_fd  = -1;
220         m->test_run         = false;
221
222         /* Init sleep modes and states */
223         m->suspend_mode       = NULL;
224         m->suspend_state      = NULL;
225         m->hibernate_mode     = NULL;
226         m->hibernate_state    = NULL;
227         m->hybrid_sleep_mode  = NULL;
228         m->hybrid_sleep_state = NULL;
229
230         /* If elogind should be its own controller, mount its cgroup */
231         if (streq(SYSTEMD_CGROUP_CONTROLLER, "name=elogind")) {
232                 m->is_system = true;
233                 r = mount_setup(true);
234         } else
235                 m->is_system = false;
236
237         /* Make cgroups */
238         if (r > -1)
239                 r = manager_setup_cgroup(m);
240
241         return r;
242 }
243
244 /// Add-On for manager_reset_config()
245 void elogind_manager_reset_config(Manager* m) {
246
247 #ifdef ENABLE_DEBUG_ELOGIND
248         int dbg_cnt;
249 #endif // ENABLE_DEBUG_ELOGIND
250
251         /* Set default Sleep config if not already set by logind.conf */
252         if (!m->suspend_state)
253                 m->suspend_state = strv_new("mem", "standby", "freeze", NULL);
254         if (!m->hibernate_mode)
255                 m->hibernate_mode = strv_new("platform", "shutdown", NULL);
256         if (!m->hibernate_state)
257                 m->hibernate_state = strv_new("disk", NULL);
258         if (!m->hybrid_sleep_mode)
259                 m->hybrid_sleep_mode = strv_new("suspend", "platform", "shutdown", NULL);
260         if (!m->hybrid_sleep_state)
261                 m->hybrid_sleep_state = strv_new("disk", NULL);
262
263 #ifdef ENABLE_DEBUG_ELOGIND
264         dbg_cnt = -1;
265         while (m->suspend_mode && m->suspend_mode[++dbg_cnt])
266                 log_debug_elogind("suspend_mode[%d] = %s",
267                                   dbg_cnt, m->suspend_mode[dbg_cnt]);
268         dbg_cnt = -1;
269         while (m->suspend_state[++dbg_cnt])
270                 log_debug_elogind("suspend_state[%d] = %s",
271                                   dbg_cnt, m->suspend_state[dbg_cnt]);
272         dbg_cnt = -1;
273         while (m->hibernate_mode[++dbg_cnt])
274                 log_debug_elogind("hibernate_mode[%d] = %s",
275                                   dbg_cnt, m->hibernate_mode[dbg_cnt]);
276         dbg_cnt = -1;
277         while (m->hibernate_state[++dbg_cnt])
278                 log_debug_elogind("hibernate_state[%d] = %s",
279                                   dbg_cnt, m->hibernate_state[dbg_cnt]);
280         dbg_cnt = -1;
281         while (m->hybrid_sleep_mode[++dbg_cnt])
282                 log_debug_elogind("hybrid_sleep_mode[%d] = %s",
283                                   dbg_cnt, m->hybrid_sleep_mode[dbg_cnt]);
284         dbg_cnt = -1;
285         while (m->hybrid_sleep_state[++dbg_cnt])
286                 log_debug_elogind("hybrid_sleep_state[%d] = %s",
287                                   dbg_cnt, m->hybrid_sleep_state[dbg_cnt]);
288 #endif // ENABLE_DEBUG_ELOGIND
289 }