chiark / gitweb /
udev: really exclude device-mapper from block device ownership event locking
[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 "sd-id128.h"
31 #include "sd-messages.h"
32 #include "logind-seat.h"
33 #include "logind-acl.h"
34 #include "util.h"
35 #include "mkdir.h"
36 #include "path-util.h"
37
38 Seat *seat_new(Manager *m, const char *id) {
39         Seat *s;
40
41         assert(m);
42         assert(id);
43
44         s = new0(Seat, 1);
45         if (!s)
46                 return NULL;
47
48         s->state_file = strappend("/run/systemd/seats/", id);
49         if (!s->state_file) {
50                 free(s);
51                 return NULL;
52         }
53
54         s->id = basename(s->state_file);
55         s->manager = m;
56
57         if (hashmap_put(m->seats, s->id, s) < 0) {
58                 free(s->state_file);
59                 free(s);
60                 return NULL;
61         }
62
63         return s;
64 }
65
66 void seat_free(Seat *s) {
67         assert(s);
68
69         if (s->in_gc_queue)
70                 LIST_REMOVE(gc_queue, s->manager->seat_gc_queue, s);
71
72         while (s->sessions)
73                 session_free(s->sessions);
74
75         assert(!s->active);
76
77         while (s->devices)
78                 device_free(s->devices);
79
80         hashmap_remove(s->manager->seats, s->id);
81
82         free(s->positions);
83         free(s->state_file);
84         free(s);
85 }
86
87 int seat_save(Seat *s) {
88         _cleanup_free_ char *temp_path = NULL;
89         _cleanup_fclose_ FILE *f = NULL;
90         int r;
91
92         assert(s);
93
94         if (!s->started)
95                 return 0;
96
97         r = mkdir_safe_label("/run/systemd/seats", 0755, 0, 0);
98         if (r < 0)
99                 goto finish;
100
101         r = fopen_temporary(s->state_file, &f, &temp_path);
102         if (r < 0)
103                 goto finish;
104
105         fchmod(fileno(f), 0644);
106
107         fprintf(f,
108                 "# This is private data. Do not parse.\n"
109                 "IS_SEAT0=%i\n"
110                 "CAN_MULTI_SESSION=%i\n"
111                 "CAN_TTY=%i\n"
112                 "CAN_GRAPHICAL=%i\n",
113                 seat_is_seat0(s),
114                 seat_can_multi_session(s),
115                 seat_can_tty(s),
116                 seat_can_graphical(s));
117
118         if (s->active) {
119                 assert(s->active->user);
120
121                 fprintf(f,
122                         "ACTIVE=%s\n"
123                         "ACTIVE_UID="UID_FMT"\n",
124                         s->active->id,
125                         s->active->user->uid);
126         }
127
128         if (s->sessions) {
129                 Session *i;
130
131                 fputs("SESSIONS=", f);
132                 LIST_FOREACH(sessions_by_seat, i, s->sessions) {
133                         fprintf(f,
134                                 "%s%c",
135                                 i->id,
136                                 i->sessions_by_seat_next ? ' ' : '\n');
137                 }
138
139                 fputs("UIDS=", f);
140                 LIST_FOREACH(sessions_by_seat, i, s->sessions)
141                         fprintf(f,
142                                 UID_FMT"%c",
143                                 i->user->uid,
144                                 i->sessions_by_seat_next ? ' ' : '\n');
145         }
146
147         fflush(f);
148
149         if (ferror(f) || rename(temp_path, s->state_file) < 0) {
150                 r = -errno;
151                 unlink(s->state_file);
152                 unlink(temp_path);
153         }
154
155 finish:
156         if (r < 0)
157                 log_error("Failed to save seat data %s: %s", s->state_file, 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(unsigned int vtnr) {
171         char p[sizeof("/dev/tty") + DECIMAL_STR_MAX(unsigned int)];
172         _cleanup_close_ int fd = -1;
173
174         assert(vtnr >= 1);
175
176         snprintf(p, sizeof(p), "/dev/tty%u", vtnr);
177         fd = open_terminal(p, O_RDWR|O_NOCTTY|O_CLOEXEC);
178         if (fd < 0)
179                 return -errno;
180
181         return 0;
182 }
183
184 int seat_preallocate_vts(Seat *s) {
185         int r = 0;
186         unsigned i;
187
188         assert(s);
189         assert(s->manager);
190
191         log_debug("Preallocating VTs...");
192
193         if (s->manager->n_autovts <= 0)
194                 return 0;
195
196         if (!seat_has_vts(s))
197                 return 0;
198
199         for (i = 1; i <= s->manager->n_autovts; i++) {
200                 int q;
201
202                 q = vt_allocate(i);
203                 if (q < 0) {
204                         log_error("Failed to preallocate VT %i: %s", i, strerror(-q));
205                         r = q;
206                 }
207         }
208
209         return r;
210 }
211
212 int seat_apply_acls(Seat *s, Session *old_active) {
213         int r;
214
215         assert(s);
216
217         r = devnode_acl_all(s->manager->udev,
218                             s->id,
219                             false,
220                             !!old_active, old_active ? old_active->user->uid : 0,
221                             !!s->active, s->active ? s->active->user->uid : 0);
222
223         if (r < 0)
224                 log_error("Failed to apply ACLs: %s", strerror(-r));
225
226         return r;
227 }
228
229 int seat_set_active(Seat *s, Session *session) {
230         Session *old_active;
231
232         assert(s);
233         assert(!session || session->seat == s);
234
235         if (session == s->active)
236                 return 0;
237
238         old_active = s->active;
239         s->active = session;
240
241         if (old_active) {
242                 session_device_pause_all(old_active);
243                 session_send_changed(old_active, "Active", NULL);
244         }
245
246         seat_apply_acls(s, old_active);
247
248         if (session && session->started) {
249                 session_send_changed(session, "Active", NULL);
250                 session_device_resume_all(session);
251         }
252
253         if (!session || session->started)
254                 seat_send_changed(s, "ActiveSession", NULL);
255
256         seat_save(s);
257
258         if (session) {
259                 session_save(session);
260                 user_save(session->user);
261         }
262
263         if (old_active) {
264                 session_save(old_active);
265                 if (!session || session->user != old_active->user)
266                         user_save(old_active->user);
267         }
268
269         return 0;
270 }
271
272 int seat_switch_to(Seat *s, unsigned int num) {
273         /* Public session positions skip 0 (there is only F1-F12). Maybe it
274          * will get reassigned in the future, so return error for now. */
275         if (!num)
276                 return -EINVAL;
277
278         if (num >= s->position_count || !s->positions[num])
279                 return -EINVAL;
280
281         return session_activate(s->positions[num]);
282 }
283
284 int seat_switch_to_next(Seat *s) {
285         unsigned int start, i;
286
287         if (!s->position_count)
288                 return -EINVAL;
289
290         start = 1;
291         if (s->active && s->active->pos > 0)
292                 start = s->active->pos;
293
294         for (i = start + 1; i < s->position_count; ++i)
295                 if (s->positions[i])
296                         return session_activate(s->positions[i]);
297
298         for (i = 1; i < start; ++i)
299                 if (s->positions[i])
300                         return session_activate(s->positions[i]);
301
302         return -EINVAL;
303 }
304
305 int seat_switch_to_previous(Seat *s) {
306         unsigned int start, i;
307
308         if (!s->position_count)
309                 return -EINVAL;
310
311         start = 1;
312         if (s->active && s->active->pos > 0)
313                 start = s->active->pos;
314
315         for (i = start - 1; i > 0; --i)
316                 if (s->positions[i])
317                         return session_activate(s->positions[i]);
318
319         for (i = s->position_count - 1; i > start; --i)
320                 if (s->positions[i])
321                         return session_activate(s->positions[i]);
322
323         return -EINVAL;
324 }
325
326 int seat_active_vt_changed(Seat *s, unsigned int vtnr) {
327         Session *i, *new_active = NULL;
328         int r;
329
330         assert(s);
331         assert(vtnr >= 1);
332
333         if (!seat_has_vts(s))
334                 return -EINVAL;
335
336         log_debug("VT changed to %u", vtnr);
337
338         LIST_FOREACH(sessions_by_seat, i, s->sessions)
339                 if (i->vtnr == vtnr) {
340                         new_active = i;
341                         break;
342                 }
343
344         r = seat_set_active(s, new_active);
345         manager_spawn_autovt(s->manager, vtnr);
346
347         return r;
348 }
349
350 int seat_read_active_vt(Seat *s) {
351         char t[64];
352         ssize_t k;
353         unsigned int vtnr;
354         int r;
355
356         assert(s);
357
358         if (!seat_has_vts(s))
359                 return 0;
360
361         lseek(s->manager->console_active_fd, SEEK_SET, 0);
362
363         k = read(s->manager->console_active_fd, t, sizeof(t)-1);
364         if (k <= 0) {
365                 log_error("Failed to read current console: %s", k < 0 ? strerror(-errno) : "EOF");
366                 return k < 0 ? -errno : -EIO;
367         }
368
369         t[k] = 0;
370         truncate_nl(t);
371
372         if (!startswith(t, "tty")) {
373                 log_error("Hm, /sys/class/tty/tty0/active is badly formatted.");
374                 return -EIO;
375         }
376
377         r = safe_atou(t+3, &vtnr);
378         if (r < 0) {
379                 log_error("Failed to parse VT number %s", t+3);
380                 return r;
381         }
382
383         if (!vtnr) {
384                 log_error("VT number invalid: %s", t+3);
385                 return -EIO;
386         }
387
388         return seat_active_vt_changed(s, vtnr);
389 }
390
391 int seat_start(Seat *s) {
392         assert(s);
393
394         if (s->started)
395                 return 0;
396
397         log_struct(LOG_INFO,
398                    MESSAGE_ID(SD_MESSAGE_SEAT_START),
399                    "SEAT_ID=%s", s->id,
400                    "MESSAGE=New seat %s.", s->id,
401                    NULL);
402
403         /* Initialize VT magic stuff */
404         seat_preallocate_vts(s);
405
406         /* Read current VT */
407         seat_read_active_vt(s);
408
409         s->started = true;
410
411         /* Save seat data */
412         seat_save(s);
413
414         seat_send_signal(s, true);
415
416         return 0;
417 }
418
419 int seat_stop(Seat *s, bool force) {
420         int r = 0;
421
422         assert(s);
423
424         if (s->started)
425                 log_struct(LOG_INFO,
426                            MESSAGE_ID(SD_MESSAGE_SEAT_STOP),
427                            "SEAT_ID=%s", s->id,
428                            "MESSAGE=Removed seat %s.", s->id,
429                            NULL);
430
431         seat_stop_sessions(s, force);
432
433         unlink(s->state_file);
434         seat_add_to_gc_queue(s);
435
436         if (s->started)
437                 seat_send_signal(s, false);
438
439         s->started = false;
440
441         return r;
442 }
443
444 int seat_stop_sessions(Seat *s, bool force) {
445         Session *session;
446         int r = 0, k;
447
448         assert(s);
449
450         LIST_FOREACH(sessions_by_seat, session, s->sessions) {
451                 k = session_stop(session, force);
452                 if (k < 0)
453                         r = k;
454         }
455
456         return r;
457 }
458
459 void seat_evict_position(Seat *s, Session *session) {
460         Session *iter;
461         unsigned int pos = session->pos;
462
463         session->pos = 0;
464
465         if (!pos)
466                 return;
467
468         if (pos < s->position_count && s->positions[pos] == session) {
469                 s->positions[pos] = NULL;
470
471                 /* There might be another session claiming the same
472                  * position (eg., during gdm->session transition), so lets look
473                  * for it and set it on the free slot. */
474                 LIST_FOREACH(sessions_by_seat, iter, s->sessions) {
475                         if (iter->pos == pos) {
476                                 s->positions[pos] = iter;
477                                 break;
478                         }
479                 }
480         }
481 }
482
483 void seat_claim_position(Seat *s, Session *session, unsigned int pos) {
484         /* with VTs, the position is always the same as the VTnr */
485         if (seat_has_vts(s))
486                 pos = session->vtnr;
487
488         if (!GREEDY_REALLOC0(s->positions, s->position_count, pos + 1))
489                 return;
490
491         seat_evict_position(s, session);
492
493         session->pos = pos;
494         if (pos > 0 && !s->positions[pos])
495                 s->positions[pos] = session;
496 }
497
498 static void seat_assign_position(Seat *s, Session *session) {
499         unsigned int pos;
500
501         if (session->pos > 0)
502                 return;
503
504         for (pos = 1; pos < s->position_count; ++pos)
505                 if (!s->positions[pos])
506                         break;
507
508         seat_claim_position(s, session, pos);
509 }
510
511 int seat_attach_session(Seat *s, Session *session) {
512         assert(s);
513         assert(session);
514         assert(!session->seat);
515
516         if (!seat_has_vts(s) != !session->vtnr)
517                 return -EINVAL;
518
519         session->seat = s;
520         LIST_PREPEND(sessions_by_seat, s->sessions, session);
521         seat_assign_position(s, session);
522
523         seat_send_changed(s, "Sessions", NULL);
524
525         /* On seats with VTs, the VT logic defines which session is active. On
526          * seats without VTs, we automatically activate new sessions. */
527         if (!seat_has_vts(s))
528                 seat_set_active(s, session);
529
530         return 0;
531 }
532
533 void seat_complete_switch(Seat *s) {
534         Session *session;
535
536         assert(s);
537
538         /* if no session-switch is pending or if it got canceled, do nothing */
539         if (!s->pending_switch)
540                 return;
541
542         session = s->pending_switch;
543         s->pending_switch = NULL;
544
545         seat_set_active(s, session);
546 }
547
548 bool seat_has_vts(Seat *s) {
549         assert(s);
550
551         return seat_is_seat0(s) && s->manager->console_active_fd >= 0;
552 }
553
554 bool seat_is_seat0(Seat *s) {
555         assert(s);
556
557         return s->manager->seat0 == s;
558 }
559
560 bool seat_can_multi_session(Seat *s) {
561         assert(s);
562
563         return seat_has_vts(s);
564 }
565
566 bool seat_can_tty(Seat *s) {
567         assert(s);
568
569         return seat_has_vts(s);
570 }
571
572 bool seat_has_master_device(Seat *s) {
573         assert(s);
574
575         /* device list is ordered by "master" flag */
576         return !!s->devices && s->devices->master;
577 }
578
579 bool seat_can_graphical(Seat *s) {
580         assert(s);
581
582         return seat_has_master_device(s);
583 }
584
585 int seat_get_idle_hint(Seat *s, dual_timestamp *t) {
586         Session *session;
587         bool idle_hint = true;
588         dual_timestamp ts = { 0, 0 };
589
590         assert(s);
591
592         LIST_FOREACH(sessions_by_seat, session, s->sessions) {
593                 dual_timestamp k;
594                 int ih;
595
596                 ih = session_get_idle_hint(session, &k);
597                 if (ih < 0)
598                         return ih;
599
600                 if (!ih) {
601                         if (!idle_hint) {
602                                 if (k.monotonic > ts.monotonic)
603                                         ts = k;
604                         } else {
605                                 idle_hint = false;
606                                 ts = k;
607                         }
608                 } else if (idle_hint) {
609
610                         if (k.monotonic > ts.monotonic)
611                                 ts = k;
612                 }
613         }
614
615         if (t)
616                 *t = ts;
617
618         return idle_hint;
619 }
620
621 bool seat_check_gc(Seat *s, bool drop_not_started) {
622         assert(s);
623
624         if (drop_not_started && !s->started)
625                 return false;
626
627         if (seat_is_seat0(s))
628                 return true;
629
630         return seat_has_master_device(s);
631 }
632
633 void seat_add_to_gc_queue(Seat *s) {
634         assert(s);
635
636         if (s->in_gc_queue)
637                 return;
638
639         LIST_PREPEND(gc_queue, s->manager->seat_gc_queue, s);
640         s->in_gc_queue = true;
641 }
642
643 static bool seat_name_valid_char(char c) {
644         return
645                 (c >= 'a' && c <= 'z') ||
646                 (c >= 'A' && c <= 'Z') ||
647                 (c >= '0' && c <= '9') ||
648                 c == '-' ||
649                 c == '_';
650 }
651
652 bool seat_name_is_valid(const char *name) {
653         const char *p;
654
655         assert(name);
656
657         if (!startswith(name, "seat"))
658                 return false;
659
660         if (!name[4])
661                 return false;
662
663         for (p = name; *p; p++)
664                 if (!seat_name_valid_char(*p))
665                         return false;
666
667         if (strlen(name) > 255)
668                 return false;
669
670         return true;
671 }