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