]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/login/logind-user.c
Merge pull request #2329 from ssahani/tunnel
[thirdparty/systemd.git] / src / login / logind-user.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 <string.h>
24 #include <sys/mount.h>
25 #include <unistd.h>
26
27 #include "alloc-util.h"
28 #include "bus-common-errors.h"
29 #include "bus-error.h"
30 #include "bus-util.h"
31 #include "clean-ipc.h"
32 #include "conf-parser.h"
33 #include "escape.h"
34 #include "fd-util.h"
35 #include "fileio.h"
36 #include "formats-util.h"
37 #include "fs-util.h"
38 #include "hashmap.h"
39 #include "label.h"
40 #include "logind-user.h"
41 #include "mkdir.h"
42 #include "mount-util.h"
43 #include "parse-util.h"
44 #include "path-util.h"
45 #include "rm-rf.h"
46 #include "smack-util.h"
47 #include "special.h"
48 #include "stdio-util.h"
49 #include "string-table.h"
50 #include "unit-name.h"
51 #include "user-util.h"
52 #include "util.h"
53
54 int user_new(User **out, Manager *m, uid_t uid, gid_t gid, const char *name) {
55 _cleanup_(user_freep) User *u = NULL;
56 char lu[DECIMAL_STR_MAX(uid_t) + 1];
57 int r;
58
59 assert(out);
60 assert(m);
61 assert(name);
62
63 u = new0(User, 1);
64 if (!u)
65 return -ENOMEM;
66
67 u->manager = m;
68 u->uid = uid;
69 u->gid = gid;
70 xsprintf(lu, UID_FMT, uid);
71
72 u->name = strdup(name);
73 if (!u->name)
74 return -ENOMEM;
75
76 if (asprintf(&u->state_file, "/run/systemd/users/"UID_FMT, uid) < 0)
77 return -ENOMEM;
78
79 if (asprintf(&u->runtime_path, "/run/user/"UID_FMT, uid) < 0)
80 return -ENOMEM;
81
82 r = slice_build_subslice(SPECIAL_USER_SLICE, lu, &u->slice);
83 if (r < 0)
84 return r;
85
86 r = unit_name_build("user", lu, ".service", &u->service);
87 if (r < 0)
88 return r;
89
90 r = hashmap_put(m->users, UID_TO_PTR(uid), u);
91 if (r < 0)
92 return r;
93
94 r = hashmap_put(m->user_units, u->slice, u);
95 if (r < 0)
96 return r;
97
98 r = hashmap_put(m->user_units, u->service, u);
99 if (r < 0)
100 return r;
101
102 *out = u;
103 u = NULL;
104 return 0;
105 }
106
107 User *user_free(User *u) {
108 if (!u)
109 return NULL;
110
111 if (u->in_gc_queue)
112 LIST_REMOVE(gc_queue, u->manager->user_gc_queue, u);
113
114 while (u->sessions)
115 session_free(u->sessions);
116
117 if (u->service)
118 hashmap_remove_value(u->manager->user_units, u->service, u);
119
120 if (u->slice)
121 hashmap_remove_value(u->manager->user_units, u->slice, u);
122
123 hashmap_remove_value(u->manager->users, UID_TO_PTR(u->uid), u);
124
125 u->slice_job = mfree(u->slice_job);
126 u->service_job = mfree(u->service_job);
127
128 u->service = mfree(u->service);
129 u->slice = mfree(u->slice);
130 u->runtime_path = mfree(u->runtime_path);
131 u->state_file = mfree(u->state_file);
132 u->name = mfree(u->name);
133
134 return mfree(u);
135 }
136
137 static int user_save_internal(User *u) {
138 _cleanup_free_ char *temp_path = NULL;
139 _cleanup_fclose_ FILE *f = NULL;
140 int r;
141
142 assert(u);
143 assert(u->state_file);
144
145 r = mkdir_safe_label("/run/systemd/users", 0755, 0, 0);
146 if (r < 0)
147 goto fail;
148
149 r = fopen_temporary(u->state_file, &f, &temp_path);
150 if (r < 0)
151 goto fail;
152
153 fchmod(fileno(f), 0644);
154
155 fprintf(f,
156 "# This is private data. Do not parse.\n"
157 "NAME=%s\n"
158 "STATE=%s\n",
159 u->name,
160 user_state_to_string(user_get_state(u)));
161
162 /* LEGACY: no-one reads RUNTIME= anymore, drop it at some point */
163 if (u->runtime_path)
164 fprintf(f, "RUNTIME=%s\n", u->runtime_path);
165
166 if (u->service_job)
167 fprintf(f, "SERVICE_JOB=%s\n", u->service_job);
168
169 if (u->slice_job)
170 fprintf(f, "SLICE_JOB=%s\n", u->slice_job);
171
172 if (u->display)
173 fprintf(f, "DISPLAY=%s\n", u->display->id);
174
175 if (dual_timestamp_is_set(&u->timestamp))
176 fprintf(f,
177 "REALTIME="USEC_FMT"\n"
178 "MONOTONIC="USEC_FMT"\n",
179 u->timestamp.realtime,
180 u->timestamp.monotonic);
181
182 if (u->sessions) {
183 Session *i;
184 bool first;
185
186 fputs("SESSIONS=", f);
187 first = true;
188 LIST_FOREACH(sessions_by_user, i, u->sessions) {
189 if (first)
190 first = false;
191 else
192 fputc(' ', f);
193
194 fputs(i->id, f);
195 }
196
197 fputs("\nSEATS=", f);
198 first = true;
199 LIST_FOREACH(sessions_by_user, i, u->sessions) {
200 if (!i->seat)
201 continue;
202
203 if (first)
204 first = false;
205 else
206 fputc(' ', f);
207
208 fputs(i->seat->id, f);
209 }
210
211 fputs("\nACTIVE_SESSIONS=", f);
212 first = true;
213 LIST_FOREACH(sessions_by_user, i, u->sessions) {
214 if (!session_is_active(i))
215 continue;
216
217 if (first)
218 first = false;
219 else
220 fputc(' ', f);
221
222 fputs(i->id, f);
223 }
224
225 fputs("\nONLINE_SESSIONS=", f);
226 first = true;
227 LIST_FOREACH(sessions_by_user, i, u->sessions) {
228 if (session_get_state(i) == SESSION_CLOSING)
229 continue;
230
231 if (first)
232 first = false;
233 else
234 fputc(' ', f);
235
236 fputs(i->id, f);
237 }
238
239 fputs("\nACTIVE_SEATS=", f);
240 first = true;
241 LIST_FOREACH(sessions_by_user, i, u->sessions) {
242 if (!session_is_active(i) || !i->seat)
243 continue;
244
245 if (first)
246 first = false;
247 else
248 fputc(' ', f);
249
250 fputs(i->seat->id, f);
251 }
252
253 fputs("\nONLINE_SEATS=", f);
254 first = true;
255 LIST_FOREACH(sessions_by_user, i, u->sessions) {
256 if (session_get_state(i) == SESSION_CLOSING || !i->seat)
257 continue;
258
259 if (first)
260 first = false;
261 else
262 fputc(' ', f);
263
264 fputs(i->seat->id, f);
265 }
266 fputc('\n', f);
267 }
268
269 r = fflush_and_check(f);
270 if (r < 0)
271 goto fail;
272
273 if (rename(temp_path, u->state_file) < 0) {
274 r = -errno;
275 goto fail;
276 }
277
278 return 0;
279
280 fail:
281 (void) unlink(u->state_file);
282
283 if (temp_path)
284 (void) unlink(temp_path);
285
286 return log_error_errno(r, "Failed to save user data %s: %m", u->state_file);
287 }
288
289 int user_save(User *u) {
290 assert(u);
291
292 if (!u->started)
293 return 0;
294
295 return user_save_internal (u);
296 }
297
298 int user_load(User *u) {
299 _cleanup_free_ char *display = NULL, *realtime = NULL, *monotonic = NULL;
300 Session *s = NULL;
301 int r;
302
303 assert(u);
304
305 r = parse_env_file(u->state_file, NEWLINE,
306 "SERVICE_JOB", &u->service_job,
307 "SLICE_JOB", &u->slice_job,
308 "DISPLAY", &display,
309 "REALTIME", &realtime,
310 "MONOTONIC", &monotonic,
311 NULL);
312 if (r < 0) {
313 if (r == -ENOENT)
314 return 0;
315
316 log_error_errno(r, "Failed to read %s: %m", u->state_file);
317 return r;
318 }
319
320 if (display)
321 s = hashmap_get(u->manager->sessions, display);
322
323 if (s && s->display && display_is_local(s->display))
324 u->display = s;
325
326 if (realtime) {
327 unsigned long long l;
328 if (sscanf(realtime, "%llu", &l) > 0)
329 u->timestamp.realtime = l;
330 }
331
332 if (monotonic) {
333 unsigned long long l;
334 if (sscanf(monotonic, "%llu", &l) > 0)
335 u->timestamp.monotonic = l;
336 }
337
338 return r;
339 }
340
341 static int user_mkdir_runtime_path(User *u) {
342 int r;
343
344 assert(u);
345
346 r = mkdir_safe_label("/run/user", 0755, 0, 0);
347 if (r < 0)
348 return log_error_errno(r, "Failed to create /run/user: %m");
349
350 if (path_is_mount_point(u->runtime_path, 0) <= 0) {
351 _cleanup_free_ char *t = NULL;
352
353 (void) mkdir_label(u->runtime_path, 0700);
354
355 if (mac_smack_use())
356 r = asprintf(&t, "mode=0700,smackfsroot=*,uid=" UID_FMT ",gid=" GID_FMT ",size=%zu", u->uid, u->gid, u->manager->runtime_dir_size);
357 else
358 r = asprintf(&t, "mode=0700,uid=" UID_FMT ",gid=" GID_FMT ",size=%zu", u->uid, u->gid, u->manager->runtime_dir_size);
359 if (r < 0) {
360 r = log_oom();
361 goto fail;
362 }
363
364 r = mount("tmpfs", u->runtime_path, "tmpfs", MS_NODEV|MS_NOSUID, t);
365 if (r < 0) {
366 if (errno != EPERM) {
367 r = log_error_errno(errno, "Failed to mount per-user tmpfs directory %s: %m", u->runtime_path);
368 goto fail;
369 }
370
371 /* Lacking permissions, maybe
372 * CAP_SYS_ADMIN-less container? In this case,
373 * just use a normal directory. */
374
375 r = chmod_and_chown(u->runtime_path, 0700, u->uid, u->gid);
376 if (r < 0) {
377 log_error_errno(r, "Failed to change runtime directory ownership and mode: %m");
378 goto fail;
379 }
380 }
381
382 r = label_fix(u->runtime_path, false, false);
383 if (r < 0)
384 log_warning_errno(r, "Failed to fix label of '%s', ignoring: %m", u->runtime_path);
385 }
386
387 return 0;
388
389 fail:
390 /* Try to clean up, but ignore errors */
391 (void) rmdir(u->runtime_path);
392 return r;
393 }
394
395 static int user_start_slice(User *u) {
396 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
397 const char *description;
398 char *job;
399 int r;
400
401 assert(u);
402
403 u->slice_job = mfree(u->slice_job);
404 description = strjoina("User Slice of ", u->name);
405
406 r = manager_start_slice(
407 u->manager,
408 u->slice,
409 description,
410 "systemd-logind.service",
411 "systemd-user-sessions.service",
412 u->manager->user_tasks_max,
413 &error,
414 &job);
415 if (r >= 0)
416 u->slice_job = job;
417 else if (!sd_bus_error_has_name(&error, BUS_ERROR_UNIT_EXISTS))
418 /* we don't fail due to this, let's try to continue */
419 log_error_errno(r, "Failed to start user slice %s, ignoring: %s (%s)",
420 u->slice, bus_error_message(&error, r), error.name);
421
422 return 0;
423 }
424
425 static int user_start_service(User *u) {
426 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
427 char *job;
428 int r;
429
430 assert(u);
431
432 u->service_job = mfree(u->service_job);
433
434 r = manager_start_unit(
435 u->manager,
436 u->service,
437 &error,
438 &job);
439 if (r < 0) {
440 /* we don't fail due to this, let's try to continue */
441 log_error_errno(r, "Failed to start user service, ignoring: %s", bus_error_message(&error, r));
442 } else {
443 u->service_job = job;
444 }
445
446 return 0;
447 }
448
449 int user_start(User *u) {
450 int r;
451
452 assert(u);
453
454 if (u->started && !u->stopping)
455 return 0;
456
457 /*
458 * If u->stopping is set, the user is marked for removal and the slice
459 * and service stop-jobs are queued. We have to clear that flag before
460 * queing the start-jobs again. If they succeed, the user object can be
461 * re-used just fine (pid1 takes care of job-ordering and proper
462 * restart), but if they fail, we want to force another user_stop() so
463 * possibly pending units are stopped.
464 * Note that we don't clear u->started, as we have no clue what state
465 * the user is in on failure here. Hence, we pretend the user is
466 * running so it will be properly taken down by GC. However, we clearly
467 * return an error from user_start() in that case, so no further
468 * reference to the user is taken.
469 */
470 u->stopping = false;
471
472 if (!u->started) {
473 log_debug("New user %s logged in.", u->name);
474
475 /* Make XDG_RUNTIME_DIR */
476 r = user_mkdir_runtime_path(u);
477 if (r < 0)
478 return r;
479 }
480
481 /* Create cgroup */
482 r = user_start_slice(u);
483 if (r < 0)
484 return r;
485
486 /* Save the user data so far, because pam_systemd will read the
487 * XDG_RUNTIME_DIR out of it while starting up systemd --user.
488 * We need to do user_save_internal() because we have not
489 * "officially" started yet. */
490 user_save_internal(u);
491
492 /* Spawn user systemd */
493 r = user_start_service(u);
494 if (r < 0)
495 return r;
496
497 if (!u->started) {
498 if (!dual_timestamp_is_set(&u->timestamp))
499 dual_timestamp_get(&u->timestamp);
500 user_send_signal(u, true);
501 u->started = true;
502 }
503
504 /* Save new user data */
505 user_save(u);
506
507 return 0;
508 }
509
510 static int user_stop_slice(User *u) {
511 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
512 char *job;
513 int r;
514
515 assert(u);
516
517 r = manager_stop_unit(u->manager, u->slice, &error, &job);
518 if (r < 0) {
519 log_error("Failed to stop user slice: %s", bus_error_message(&error, r));
520 return r;
521 }
522
523 free(u->slice_job);
524 u->slice_job = job;
525
526 return r;
527 }
528
529 static int user_stop_service(User *u) {
530 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
531 char *job;
532 int r;
533
534 assert(u);
535
536 r = manager_stop_unit(u->manager, u->service, &error, &job);
537 if (r < 0) {
538 log_error("Failed to stop user service: %s", bus_error_message(&error, r));
539 return r;
540 }
541
542 free(u->service_job);
543 u->service_job = job;
544
545 return r;
546 }
547
548 static int user_remove_runtime_path(User *u) {
549 int r;
550
551 assert(u);
552
553 r = rm_rf(u->runtime_path, 0);
554 if (r < 0)
555 log_error_errno(r, "Failed to remove runtime directory %s: %m", u->runtime_path);
556
557 /* Ignore cases where the directory isn't mounted, as that's
558 * quite possible, if we lacked the permissions to mount
559 * something */
560 r = umount2(u->runtime_path, MNT_DETACH);
561 if (r < 0 && errno != EINVAL && errno != ENOENT)
562 log_error_errno(errno, "Failed to unmount user runtime directory %s: %m", u->runtime_path);
563
564 r = rm_rf(u->runtime_path, REMOVE_ROOT);
565 if (r < 0)
566 log_error_errno(r, "Failed to remove runtime directory %s: %m", u->runtime_path);
567
568 return r;
569 }
570
571 int user_stop(User *u, bool force) {
572 Session *s;
573 int r = 0, k;
574 assert(u);
575
576 /* Stop jobs have already been queued */
577 if (u->stopping) {
578 user_save(u);
579 return r;
580 }
581
582 LIST_FOREACH(sessions_by_user, s, u->sessions) {
583 k = session_stop(s, force);
584 if (k < 0)
585 r = k;
586 }
587
588 /* Kill systemd */
589 k = user_stop_service(u);
590 if (k < 0)
591 r = k;
592
593 /* Kill cgroup */
594 k = user_stop_slice(u);
595 if (k < 0)
596 r = k;
597
598 u->stopping = true;
599
600 user_save(u);
601
602 return r;
603 }
604
605 int user_finalize(User *u) {
606 Session *s;
607 int r = 0, k;
608
609 assert(u);
610
611 if (u->started)
612 log_debug("User %s logged out.", u->name);
613
614 LIST_FOREACH(sessions_by_user, s, u->sessions) {
615 k = session_finalize(s);
616 if (k < 0)
617 r = k;
618 }
619
620 /* Kill XDG_RUNTIME_DIR */
621 k = user_remove_runtime_path(u);
622 if (k < 0)
623 r = k;
624
625 /* Clean SysV + POSIX IPC objects */
626 if (u->manager->remove_ipc) {
627 k = clean_ipc(u->uid);
628 if (k < 0)
629 r = k;
630 }
631
632 unlink(u->state_file);
633 user_add_to_gc_queue(u);
634
635 if (u->started) {
636 user_send_signal(u, false);
637 u->started = false;
638 }
639
640 return r;
641 }
642
643 int user_get_idle_hint(User *u, dual_timestamp *t) {
644 Session *s;
645 bool idle_hint = true;
646 dual_timestamp ts = DUAL_TIMESTAMP_NULL;
647
648 assert(u);
649
650 LIST_FOREACH(sessions_by_user, s, u->sessions) {
651 dual_timestamp k;
652 int ih;
653
654 ih = session_get_idle_hint(s, &k);
655 if (ih < 0)
656 return ih;
657
658 if (!ih) {
659 if (!idle_hint) {
660 if (k.monotonic < ts.monotonic)
661 ts = k;
662 } else {
663 idle_hint = false;
664 ts = k;
665 }
666 } else if (idle_hint) {
667
668 if (k.monotonic > ts.monotonic)
669 ts = k;
670 }
671 }
672
673 if (t)
674 *t = ts;
675
676 return idle_hint;
677 }
678
679 int user_check_linger_file(User *u) {
680 _cleanup_free_ char *cc = NULL;
681 char *p = NULL;
682
683 cc = cescape(u->name);
684 if (!cc)
685 return -ENOMEM;
686
687 p = strjoina("/var/lib/systemd/linger/", cc);
688
689 return access(p, F_OK) >= 0;
690 }
691
692 bool user_check_gc(User *u, bool drop_not_started) {
693 assert(u);
694
695 if (drop_not_started && !u->started)
696 return false;
697
698 if (u->sessions)
699 return true;
700
701 if (user_check_linger_file(u) > 0)
702 return true;
703
704 if (u->slice_job && manager_job_is_active(u->manager, u->slice_job))
705 return true;
706
707 if (u->service_job && manager_job_is_active(u->manager, u->service_job))
708 return true;
709
710 return false;
711 }
712
713 void user_add_to_gc_queue(User *u) {
714 assert(u);
715
716 if (u->in_gc_queue)
717 return;
718
719 LIST_PREPEND(gc_queue, u->manager->user_gc_queue, u);
720 u->in_gc_queue = true;
721 }
722
723 UserState user_get_state(User *u) {
724 Session *i;
725
726 assert(u);
727
728 if (u->stopping)
729 return USER_CLOSING;
730
731 if (!u->started || u->slice_job || u->service_job)
732 return USER_OPENING;
733
734 if (u->sessions) {
735 bool all_closing = true;
736
737 LIST_FOREACH(sessions_by_user, i, u->sessions) {
738 SessionState state;
739
740 state = session_get_state(i);
741 if (state == SESSION_ACTIVE)
742 return USER_ACTIVE;
743 if (state != SESSION_CLOSING)
744 all_closing = false;
745 }
746
747 return all_closing ? USER_CLOSING : USER_ONLINE;
748 }
749
750 if (user_check_linger_file(u) > 0)
751 return USER_LINGERING;
752
753 return USER_CLOSING;
754 }
755
756 int user_kill(User *u, int signo) {
757 assert(u);
758
759 return manager_kill_unit(u->manager, u->slice, KILL_ALL, signo, NULL);
760 }
761
762 static bool elect_display_filter(Session *s) {
763 /* Return true if the session is a candidate for the user’s ‘primary
764 * session’ or ‘display’. */
765 assert(s);
766
767 return (s->class == SESSION_USER && !s->stopping);
768 }
769
770 static int elect_display_compare(Session *s1, Session *s2) {
771 /* Indexed by SessionType. Lower numbers mean more preferred. */
772 const int type_ranks[_SESSION_TYPE_MAX] = {
773 [SESSION_UNSPECIFIED] = 0,
774 [SESSION_TTY] = -2,
775 [SESSION_X11] = -3,
776 [SESSION_WAYLAND] = -3,
777 [SESSION_MIR] = -3,
778 [SESSION_WEB] = -1,
779 };
780
781 /* Calculate the partial order relationship between s1 and s2,
782 * returning < 0 if s1 is preferred as the user’s ‘primary session’,
783 * 0 if s1 and s2 are equally preferred or incomparable, or > 0 if s2
784 * is preferred.
785 *
786 * s1 or s2 may be NULL. */
787 if (!s1 && !s2)
788 return 0;
789
790 if ((s1 == NULL) != (s2 == NULL))
791 return (s1 == NULL) - (s2 == NULL);
792
793 if (s1->stopping != s2->stopping)
794 return s1->stopping - s2->stopping;
795
796 if ((s1->class != SESSION_USER) != (s2->class != SESSION_USER))
797 return (s1->class != SESSION_USER) - (s2->class != SESSION_USER);
798
799 if ((s1->type == _SESSION_TYPE_INVALID) != (s2->type == _SESSION_TYPE_INVALID))
800 return (s1->type == _SESSION_TYPE_INVALID) - (s2->type == _SESSION_TYPE_INVALID);
801
802 if (s1->type != s2->type)
803 return type_ranks[s1->type] - type_ranks[s2->type];
804
805 return 0;
806 }
807
808 void user_elect_display(User *u) {
809 Session *s;
810
811 assert(u);
812
813 /* This elects a primary session for each user, which we call
814 * the "display". We try to keep the assignment stable, but we
815 * "upgrade" to better choices. */
816 log_debug("Electing new display for user %s", u->name);
817
818 LIST_FOREACH(sessions_by_user, s, u->sessions) {
819 if (!elect_display_filter(s)) {
820 log_debug("Ignoring session %s", s->id);
821 continue;
822 }
823
824 if (elect_display_compare(s, u->display) < 0) {
825 log_debug("Choosing session %s in preference to %s", s->id, u->display ? u->display->id : "-");
826 u->display = s;
827 }
828 }
829 }
830
831 static const char* const user_state_table[_USER_STATE_MAX] = {
832 [USER_OFFLINE] = "offline",
833 [USER_OPENING] = "opening",
834 [USER_LINGERING] = "lingering",
835 [USER_ONLINE] = "online",
836 [USER_ACTIVE] = "active",
837 [USER_CLOSING] = "closing"
838 };
839
840 DEFINE_STRING_TABLE_LOOKUP(user_state, UserState);
841
842 int config_parse_tmpfs_size(
843 const char* unit,
844 const char *filename,
845 unsigned line,
846 const char *section,
847 unsigned section_line,
848 const char *lvalue,
849 int ltype,
850 const char *rvalue,
851 void *data,
852 void *userdata) {
853
854 size_t *sz = data;
855 const char *e;
856 int r;
857
858 assert(filename);
859 assert(lvalue);
860 assert(rvalue);
861 assert(data);
862
863 e = endswith(rvalue, "%");
864 if (e) {
865 unsigned long ul;
866 char *f;
867
868 errno = 0;
869 ul = strtoul(rvalue, &f, 10);
870 if (errno > 0 || f != e) {
871 log_syntax(unit, LOG_ERR, filename, line, errno, "Failed to parse percentage value, ignoring: %s", rvalue);
872 return 0;
873 }
874
875 if (ul <= 0 || ul >= 100) {
876 log_syntax(unit, LOG_ERR, filename, line, 0, "Percentage value out of range, ignoring: %s", rvalue);
877 return 0;
878 }
879
880 *sz = PAGE_ALIGN((size_t) ((physical_memory() * (uint64_t) ul) / (uint64_t) 100));
881 } else {
882 uint64_t k;
883
884 r = parse_size(rvalue, 1024, &k);
885 if (r < 0 || (uint64_t) (size_t) k != k) {
886 log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse size value, ignoring: %s", rvalue);
887 return 0;
888 }
889
890 *sz = PAGE_ALIGN((size_t) k);
891 }
892
893 return 0;
894 }