chiark / gitweb /
unit-name: style fix in unit_name_is_template()
[elogind.git] / src / login / logind-seat.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4   This file is part of systemd.
5
6   Copyright 2011 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 <errno.h>
24 #include <unistd.h>
25 #include <fcntl.h>
26 #include <sys/ioctl.h>
27 #include <linux/vt.h>
28 #include <string.h>
29
30 #include "logind-seat.h"
31 #include "logind-acl.h"
32 #include "util.h"
33 #include "mkdir.h"
34 #include "path-util.h"
35
36 Seat *seat_new(Manager *m, const char *id) {
37         Seat *s;
38
39         assert(m);
40         assert(id);
41
42         s = new0(Seat, 1);
43         if (!s)
44                 return NULL;
45
46         s->state_file = strappend("/run/systemd/seats/", id);
47         if (!s->state_file) {
48                 free(s);
49                 return NULL;
50         }
51
52         s->id = path_get_file_name(s->state_file);
53         s->manager = m;
54
55         if (hashmap_put(m->seats, s->id, s) < 0) {
56                 free(s->state_file);
57                 free(s);
58                 return NULL;
59         }
60
61         return s;
62 }
63
64 void seat_free(Seat *s) {
65         assert(s);
66
67         if (s->in_gc_queue)
68                 LIST_REMOVE(Seat, gc_queue, s->manager->seat_gc_queue, s);
69
70         while (s->sessions)
71                 session_free(s->sessions);
72
73         assert(!s->active);
74
75         while (s->devices)
76                 device_free(s->devices);
77
78         hashmap_remove(s->manager->seats, s->id);
79
80         free(s->state_file);
81         free(s);
82 }
83
84 int seat_save(Seat *s) {
85         int r;
86         FILE *f;
87         char *temp_path;
88
89         assert(s);
90
91         if (!s->started)
92                 return 0;
93
94         r = mkdir_safe_label("/run/systemd/seats", 0755, 0, 0);
95         if (r < 0)
96                 goto finish;
97
98         r = fopen_temporary(s->state_file, &f, &temp_path);
99         if (r < 0)
100                 goto finish;
101
102         fchmod(fileno(f), 0644);
103
104         fprintf(f,
105                 "# This is private data. Do not parse.\n"
106                 "IS_VTCONSOLE=%i\n"
107                 "CAN_MULTI_SESSION=%i\n"
108                 "CAN_TTY=%i\n"
109                 "CAN_GRAPHICAL=%i\n",
110                 seat_is_vtconsole(s),
111                 seat_can_multi_session(s),
112                 seat_can_tty(s),
113                 seat_can_graphical(s));
114
115         if (s->active) {
116                 assert(s->active->user);
117
118                 fprintf(f,
119                         "ACTIVE=%s\n"
120                         "ACTIVE_UID=%lu\n",
121                         s->active->id,
122                         (unsigned long) s->active->user->uid);
123         }
124
125         if (s->sessions) {
126                 Session *i;
127
128                 fputs("SESSIONS=", f);
129                 LIST_FOREACH(sessions_by_seat, i, s->sessions) {
130                         fprintf(f,
131                                 "%s%c",
132                                 i->id,
133                                 i->sessions_by_seat_next ? ' ' : '\n');
134                 }
135
136                 fputs("UIDS=", f);
137                 LIST_FOREACH(sessions_by_seat, i, s->sessions)
138                         fprintf(f,
139                                 "%lu%c",
140                                 (unsigned long) i->user->uid,
141                                 i->sessions_by_seat_next ? ' ' : '\n');
142         }
143
144         fflush(f);
145
146         if (ferror(f) || rename(temp_path, s->state_file) < 0) {
147                 r = -errno;
148                 unlink(s->state_file);
149                 unlink(temp_path);
150         }
151
152         fclose(f);
153         free(temp_path);
154
155 finish:
156         if (r < 0)
157                 log_error("Failed to save seat data for %s: %s", s->id, strerror(-r));
158
159         return r;
160 }
161
162 int seat_load(Seat *s) {
163         assert(s);
164
165         /* There isn't actually anything to read here ... */
166
167         return 0;
168 }
169
170 static int vt_allocate(int vtnr) {
171         int fd, r;
172         char *p;
173
174         assert(vtnr >= 1);
175
176         if (asprintf(&p, "/dev/tty%i", vtnr) < 0)
177                 return -ENOMEM;
178
179         fd = open_terminal(p, O_RDWR|O_NOCTTY|O_CLOEXEC);
180         free(p);
181
182         r = fd < 0 ? -errno : 0;
183
184         if (fd >= 0)
185                 close_nointr_nofail(fd);
186
187         return r;
188 }
189
190 int seat_preallocate_vts(Seat *s) {
191         int r = 0;
192         unsigned i;
193
194         assert(s);
195         assert(s->manager);
196
197         log_debug("Preallocating VTs...");
198
199         if (s->manager->n_autovts <= 0)
200                 return 0;
201
202         if (!seat_can_multi_session(s))
203                 return 0;
204
205         for (i = 1; i <= s->manager->n_autovts; i++) {
206                 int q;
207
208                 q = vt_allocate(i);
209                 if (q < 0) {
210                         log_error("Failed to preallocate VT %i: %s", i, strerror(-q));
211                         r = q;
212                 }
213         }
214
215         return r;
216 }
217
218 int seat_apply_acls(Seat *s, Session *old_active) {
219         int r;
220
221         assert(s);
222
223         r = devnode_acl_all(s->manager->udev,
224                             s->id,
225                             false,
226                             !!old_active, old_active ? old_active->user->uid : 0,
227                             !!s->active, s->active ? s->active->user->uid : 0);
228
229         if (r < 0)
230                 log_error("Failed to apply ACLs: %s", strerror(-r));
231
232         return r;
233 }
234
235 int seat_set_active(Seat *s, Session *session) {
236         Session *old_active;
237
238         assert(s);
239         assert(!session || session->seat == s);
240
241         if (session == s->active)
242                 return 0;
243
244         old_active = s->active;
245         s->active = session;
246
247         seat_apply_acls(s, old_active);
248
249         if (session && session->started)
250                 session_send_changed(session, "Active\0");
251
252         if (!session || session->started)
253                 seat_send_changed(s, "ActiveSession\0");
254
255         seat_save(s);
256
257         if (session) {
258                 session_save(session);
259                 user_save(session->user);
260         }
261
262         if (old_active) {
263                 session_save(old_active);
264                 user_save(old_active->user);
265         }
266
267         return 0;
268 }
269
270 int seat_active_vt_changed(Seat *s, int vtnr) {
271         Session *i, *new_active = NULL;
272         int r;
273
274         assert(s);
275         assert(vtnr >= 1);
276
277         if (!seat_can_multi_session(s))
278                 return -EINVAL;
279
280         log_debug("VT changed to %i", vtnr);
281
282         LIST_FOREACH(sessions_by_seat, i, s->sessions)
283                 if (i->vtnr == vtnr) {
284                         new_active = i;
285                         break;
286                 }
287
288         r = seat_set_active(s, new_active);
289         manager_spawn_autovt(s->manager, vtnr);
290
291         return r;
292 }
293
294 int seat_read_active_vt(Seat *s) {
295         char t[64];
296         ssize_t k;
297         int r, vtnr;
298
299         assert(s);
300
301         if (!seat_can_multi_session(s))
302                 return 0;
303
304         lseek(s->manager->console_active_fd, SEEK_SET, 0);
305
306         k = read(s->manager->console_active_fd, t, sizeof(t)-1);
307         if (k <= 0) {
308                 log_error("Failed to read current console: %s", k < 0 ? strerror(-errno) : "EOF");
309                 return k < 0 ? -errno : -EIO;
310         }
311
312         t[k] = 0;
313         truncate_nl(t);
314
315         if (!startswith(t, "tty")) {
316                 log_error("Hm, /sys/class/tty/tty0/active is badly formatted.");
317                 return -EIO;
318         }
319
320         r = safe_atoi(t+3, &vtnr);
321         if (r < 0) {
322                 log_error("Failed to parse VT number %s", t+3);
323                 return r;
324         }
325
326         if (vtnr <= 0) {
327                 log_error("VT number invalid: %s", t+3);
328                 return -EIO;
329         }
330
331         return seat_active_vt_changed(s, vtnr);
332 }
333
334 int seat_start(Seat *s) {
335         assert(s);
336
337         if (s->started)
338                 return 0;
339
340         log_info("New seat %s.", s->id);
341
342         /* Initialize VT magic stuff */
343         seat_preallocate_vts(s);
344
345         /* Read current VT */
346         seat_read_active_vt(s);
347
348         s->started = true;
349
350         /* Save seat data */
351         seat_save(s);
352
353         seat_send_signal(s, true);
354
355         return 0;
356 }
357
358 int seat_stop(Seat *s) {
359         int r = 0;
360
361         assert(s);
362
363         if (s->started)
364                 log_info("Removed seat %s.", s->id);
365
366         seat_stop_sessions(s);
367
368         unlink(s->state_file);
369         seat_add_to_gc_queue(s);
370
371         if (s->started)
372                 seat_send_signal(s, false);
373
374         s->started = false;
375
376         return r;
377 }
378
379 int seat_stop_sessions(Seat *s) {
380         Session *session;
381         int r = 0, k;
382
383         assert(s);
384
385         LIST_FOREACH(sessions_by_seat, session, s->sessions) {
386                 k = session_stop(session);
387                 if (k < 0)
388                         r = k;
389         }
390
391         return r;
392 }
393
394 int seat_attach_session(Seat *s, Session *session) {
395         assert(s);
396         assert(session);
397         assert(!session->seat);
398
399         session->seat = s;
400         LIST_PREPEND(Session, sessions_by_seat, s->sessions, session);
401
402         seat_send_changed(s, "Sessions\0");
403
404         /* Note that even if a seat is not multi-session capable it
405          * still might have multiple sessions on it since old, dead
406          * sessions might continue to be tracked until all their
407          * processes are gone. The most recently added session
408          * (i.e. the first in s->sessions) is the one that matters. */
409
410         if (!seat_can_multi_session(s))
411                 seat_set_active(s, session);
412
413         return 0;
414 }
415
416 bool seat_is_vtconsole(Seat *s) {
417         assert(s);
418
419         return s->manager->vtconsole == s;
420 }
421
422 bool seat_can_multi_session(Seat *s) {
423         assert(s);
424
425         if (!seat_is_vtconsole(s))
426                 return false;
427
428         /* If we can't watch which VT is in the foreground, we don't
429          * support VT switching */
430
431         return s->manager->console_active_fd >= 0;
432 }
433
434 bool seat_can_tty(Seat *s) {
435         assert(s);
436
437         return seat_is_vtconsole(s);
438 }
439
440 bool seat_can_graphical(Seat *s) {
441         assert(s);
442
443         return !!s->devices;
444 }
445
446 int seat_get_idle_hint(Seat *s, dual_timestamp *t) {
447         Session *session;
448         bool idle_hint = true;
449         dual_timestamp ts = { 0, 0 };
450
451         assert(s);
452
453         LIST_FOREACH(sessions_by_seat, session, s->sessions) {
454                 dual_timestamp k;
455                 int ih;
456
457                 ih = session_get_idle_hint(session, &k);
458                 if (ih < 0)
459                         return ih;
460
461                 if (!ih) {
462                         if (!idle_hint) {
463                                 if (k.monotonic < ts.monotonic)
464                                         ts = k;
465                         } else {
466                                 idle_hint = false;
467                                 ts = k;
468                         }
469                 } else if (idle_hint) {
470
471                         if (k.monotonic > ts.monotonic)
472                                 ts = k;
473                 }
474         }
475
476         if (t)
477                 *t = ts;
478
479         return idle_hint;
480 }
481
482 int seat_check_gc(Seat *s, bool drop_not_started) {
483         assert(s);
484
485         if (drop_not_started && !s->started)
486                 return 0;
487
488         if (seat_is_vtconsole(s))
489                 return 1;
490
491         return !!s->devices;
492 }
493
494 void seat_add_to_gc_queue(Seat *s) {
495         assert(s);
496
497         if (s->in_gc_queue)
498                 return;
499
500         LIST_PREPEND(Seat, gc_queue, s->manager->seat_gc_queue, s);
501         s->in_gc_queue = true;
502 }
503
504 static bool seat_name_valid_char(char c) {
505         return
506                 (c >= 'a' && c <= 'z') ||
507                 (c >= 'A' && c <= 'Z') ||
508                 (c >= '0' && c <= '9') ||
509                 c == '-' ||
510                 c == '_';
511 }
512
513 bool seat_name_is_valid(const char *name) {
514         const char *p;
515
516         assert(name);
517
518         if (!startswith(name, "seat"))
519                 return false;
520
521         if (!name[4])
522                 return false;
523
524         for (p = name; *p; p++)
525                 if (!seat_name_valid_char(*p))
526                         return false;
527
528         if (strlen(name) > 255)
529                 return false;
530
531         return true;
532 }