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