]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/login/logind-user.c
tree-wide: expose "p"-suffix unref calls in public APIs to make gcc cleanup easy
[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 /* we don't fail due to this, let's try to continue */
417 if (!sd_bus_error_has_name(&error, BUS_ERROR_UNIT_EXISTS))
418 log_error_errno(r, "Failed to start user slice %s, ignoring: %s (%s)", u->slice, bus_error_message(&error, r), error.name);
419 } else {
420 u->slice_job = job;
421 }
422
423 return 0;
424 }
425
426 static int user_start_service(User *u) {
427 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
428 char *job;
429 int r;
430
431 assert(u);
432
433 u->service_job = mfree(u->service_job);
434
435 r = manager_start_unit(
436 u->manager,
437 u->service,
438 &error,
439 &job);
440 if (r < 0) {
441 /* we don't fail due to this, let's try to continue */
442 log_error_errno(r, "Failed to start user service, ignoring: %s", bus_error_message(&error, r));
443 } else {
444 u->service_job = job;
445 }
446
447 return 0;
448 }
449
450 int user_start(User *u) {
451 int r;
452
453 assert(u);
454
455 if (u->started && !u->stopping)
456 return 0;
457
458 /*
459 * If u->stopping is set, the user is marked for removal and the slice
460 * and service stop-jobs are queued. We have to clear that flag before
461 * queing the start-jobs again. If they succeed, the user object can be
462 * re-used just fine (pid1 takes care of job-ordering and proper
463 * restart), but if they fail, we want to force another user_stop() so
464 * possibly pending units are stopped.
465 * Note that we don't clear u->started, as we have no clue what state
466 * the user is in on failure here. Hence, we pretend the user is
467 * running so it will be properly taken down by GC. However, we clearly
468 * return an error from user_start() in that case, so no further
469 * reference to the user is taken.
470 */
471 u->stopping = false;
472
473 if (!u->started) {
474 log_debug("New user %s logged in.", u->name);
475
476 /* Make XDG_RUNTIME_DIR */
477 r = user_mkdir_runtime_path(u);
478 if (r < 0)
479 return r;
480 }
481
482 /* Create cgroup */
483 r = user_start_slice(u);
484 if (r < 0)
485 return r;
486
487 /* Save the user data so far, because pam_systemd will read the
488 * XDG_RUNTIME_DIR out of it while starting up systemd --user.
489 * We need to do user_save_internal() because we have not
490 * "officially" started yet. */
491 user_save_internal(u);
492
493 /* Spawn user systemd */
494 r = user_start_service(u);
495 if (r < 0)
496 return r;
497
498 if (!u->started) {
499 if (!dual_timestamp_is_set(&u->timestamp))
500 dual_timestamp_get(&u->timestamp);
501 user_send_signal(u, true);
502 u->started = true;
503 }
504
505 /* Save new user data */
506 user_save(u);
507
508 return 0;
509 }
510
511 static int user_stop_slice(User *u) {
512 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
513 char *job;
514 int r;
515
516 assert(u);
517
518 r = manager_stop_unit(u->manager, u->slice, &error, &job);
519 if (r < 0) {
520 log_error("Failed to stop user slice: %s", bus_error_message(&error, r));
521 return r;
522 }
523
524 free(u->slice_job);
525 u->slice_job = job;
526
527 return r;
528 }
529
530 static int user_stop_service(User *u) {
531 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
532 char *job;
533 int r;
534
535 assert(u);
536
537 r = manager_stop_unit(u->manager, u->service, &error, &job);
538 if (r < 0) {
539 log_error("Failed to stop user service: %s", bus_error_message(&error, r));
540 return r;
541 }
542
543 free(u->service_job);
544 u->service_job = job;
545
546 return r;
547 }
548
549 static int user_remove_runtime_path(User *u) {
550 int r;
551
552 assert(u);
553
554 r = rm_rf(u->runtime_path, 0);
555 if (r < 0)
556 log_error_errno(r, "Failed to remove runtime directory %s: %m", u->runtime_path);
557
558 /* Ignore cases where the directory isn't mounted, as that's
559 * quite possible, if we lacked the permissions to mount
560 * something */
561 r = umount2(u->runtime_path, MNT_DETACH);
562 if (r < 0 && errno != EINVAL && errno != ENOENT)
563 log_error_errno(errno, "Failed to unmount user runtime directory %s: %m", u->runtime_path);
564
565 r = rm_rf(u->runtime_path, REMOVE_ROOT);
566 if (r < 0)
567 log_error_errno(r, "Failed to remove runtime directory %s: %m", u->runtime_path);
568
569 return r;
570 }
571
572 int user_stop(User *u, bool force) {
573 Session *s;
574 int r = 0, k;
575 assert(u);
576
577 /* Stop jobs have already been queued */
578 if (u->stopping) {
579 user_save(u);
580 return r;
581 }
582
583 LIST_FOREACH(sessions_by_user, s, u->sessions) {
584 k = session_stop(s, force);
585 if (k < 0)
586 r = k;
587 }
588
589 /* Kill systemd */
590 k = user_stop_service(u);
591 if (k < 0)
592 r = k;
593
594 /* Kill cgroup */
595 k = user_stop_slice(u);
596 if (k < 0)
597 r = k;
598
599 u->stopping = true;
600
601 user_save(u);
602
603 return r;
604 }
605
606 int user_finalize(User *u) {
607 Session *s;
608 int r = 0, k;
609
610 assert(u);
611
612 if (u->started)
613 log_debug("User %s logged out.", u->name);
614
615 LIST_FOREACH(sessions_by_user, s, u->sessions) {
616 k = session_finalize(s);
617 if (k < 0)
618 r = k;
619 }
620
621 /* Kill XDG_RUNTIME_DIR */
622 k = user_remove_runtime_path(u);
623 if (k < 0)
624 r = k;
625
626 /* Clean SysV + POSIX IPC objects */
627 if (u->manager->remove_ipc) {
628 k = clean_ipc(u->uid);
629 if (k < 0)
630 r = k;
631 }
632
633 unlink(u->state_file);
634 user_add_to_gc_queue(u);
635
636 if (u->started) {
637 user_send_signal(u, false);
638 u->started = false;
639 }
640
641 return r;
642 }
643
644 int user_get_idle_hint(User *u, dual_timestamp *t) {
645 Session *s;
646 bool idle_hint = true;
647 dual_timestamp ts = DUAL_TIMESTAMP_NULL;
648
649 assert(u);
650
651 LIST_FOREACH(sessions_by_user, s, u->sessions) {
652 dual_timestamp k;
653 int ih;
654
655 ih = session_get_idle_hint(s, &k);
656 if (ih < 0)
657 return ih;
658
659 if (!ih) {
660 if (!idle_hint) {
661 if (k.monotonic < ts.monotonic)
662 ts = k;
663 } else {
664 idle_hint = false;
665 ts = k;
666 }
667 } else if (idle_hint) {
668
669 if (k.monotonic > ts.monotonic)
670 ts = k;
671 }
672 }
673
674 if (t)
675 *t = ts;
676
677 return idle_hint;
678 }
679
680 int user_check_linger_file(User *u) {
681 _cleanup_free_ char *cc = NULL;
682 char *p = NULL;
683
684 cc = cescape(u->name);
685 if (!cc)
686 return -ENOMEM;
687
688 p = strjoina("/var/lib/systemd/linger/", cc);
689
690 return access(p, F_OK) >= 0;
691 }
692
693 bool user_check_gc(User *u, bool drop_not_started) {
694 assert(u);
695
696 if (drop_not_started && !u->started)
697 return false;
698
699 if (u->sessions)
700 return true;
701
702 if (user_check_linger_file(u) > 0)
703 return true;
704
705 if (u->slice_job && manager_job_is_active(u->manager, u->slice_job))
706 return true;
707
708 if (u->service_job && manager_job_is_active(u->manager, u->service_job))
709 return true;
710
711 return false;
712 }
713
714 void user_add_to_gc_queue(User *u) {
715 assert(u);
716
717 if (u->in_gc_queue)
718 return;
719
720 LIST_PREPEND(gc_queue, u->manager->user_gc_queue, u);
721 u->in_gc_queue = true;
722 }
723
724 UserState user_get_state(User *u) {
725 Session *i;
726
727 assert(u);
728
729 if (u->stopping)
730 return USER_CLOSING;
731
732 if (!u->started || u->slice_job || u->service_job)
733 return USER_OPENING;
734
735 if (u->sessions) {
736 bool all_closing = true;
737
738 LIST_FOREACH(sessions_by_user, i, u->sessions) {
739 SessionState state;
740
741 state = session_get_state(i);
742 if (state == SESSION_ACTIVE)
743 return USER_ACTIVE;
744 if (state != SESSION_CLOSING)
745 all_closing = false;
746 }
747
748 return all_closing ? USER_CLOSING : USER_ONLINE;
749 }
750
751 if (user_check_linger_file(u) > 0)
752 return USER_LINGERING;
753
754 return USER_CLOSING;
755 }
756
757 int user_kill(User *u, int signo) {
758 assert(u);
759
760 return manager_kill_unit(u->manager, u->slice, KILL_ALL, signo, NULL);
761 }
762
763 static bool elect_display_filter(Session *s) {
764 /* Return true if the session is a candidate for the user’s ‘primary
765 * session’ or ‘display’. */
766 assert(s);
767
768 return (s->class == SESSION_USER && !s->stopping);
769 }
770
771 static int elect_display_compare(Session *s1, Session *s2) {
772 /* Indexed by SessionType. Lower numbers mean more preferred. */
773 const int type_ranks[_SESSION_TYPE_MAX] = {
774 [SESSION_UNSPECIFIED] = 0,
775 [SESSION_TTY] = -2,
776 [SESSION_X11] = -3,
777 [SESSION_WAYLAND] = -3,
778 [SESSION_MIR] = -3,
779 [SESSION_WEB] = -1,
780 };
781
782 /* Calculate the partial order relationship between s1 and s2,
783 * returning < 0 if s1 is preferred as the user’s ‘primary session’,
784 * 0 if s1 and s2 are equally preferred or incomparable, or > 0 if s2
785 * is preferred.
786 *
787 * s1 or s2 may be NULL. */
788 if (!s1 && !s2)
789 return 0;
790
791 if ((s1 == NULL) != (s2 == NULL))
792 return (s1 == NULL) - (s2 == NULL);
793
794 if (s1->stopping != s2->stopping)
795 return s1->stopping - s2->stopping;
796
797 if ((s1->class != SESSION_USER) != (s2->class != SESSION_USER))
798 return (s1->class != SESSION_USER) - (s2->class != SESSION_USER);
799
800 if ((s1->type == _SESSION_TYPE_INVALID) != (s2->type == _SESSION_TYPE_INVALID))
801 return (s1->type == _SESSION_TYPE_INVALID) - (s2->type == _SESSION_TYPE_INVALID);
802
803 if (s1->type != s2->type)
804 return type_ranks[s1->type] - type_ranks[s2->type];
805
806 return 0;
807 }
808
809 void user_elect_display(User *u) {
810 Session *s;
811
812 assert(u);
813
814 /* This elects a primary session for each user, which we call
815 * the "display". We try to keep the assignment stable, but we
816 * "upgrade" to better choices. */
817 log_debug("Electing new display for user %s", u->name);
818
819 LIST_FOREACH(sessions_by_user, s, u->sessions) {
820 if (!elect_display_filter(s)) {
821 log_debug("Ignoring session %s", s->id);
822 continue;
823 }
824
825 if (elect_display_compare(s, u->display) < 0) {
826 log_debug("Choosing session %s in preference to %s", s->id, u->display ? u->display->id : "-");
827 u->display = s;
828 }
829 }
830 }
831
832 static const char* const user_state_table[_USER_STATE_MAX] = {
833 [USER_OFFLINE] = "offline",
834 [USER_OPENING] = "opening",
835 [USER_LINGERING] = "lingering",
836 [USER_ONLINE] = "online",
837 [USER_ACTIVE] = "active",
838 [USER_CLOSING] = "closing"
839 };
840
841 DEFINE_STRING_TABLE_LOOKUP(user_state, UserState);
842
843 int config_parse_tmpfs_size(
844 const char* unit,
845 const char *filename,
846 unsigned line,
847 const char *section,
848 unsigned section_line,
849 const char *lvalue,
850 int ltype,
851 const char *rvalue,
852 void *data,
853 void *userdata) {
854
855 size_t *sz = data;
856 const char *e;
857 int r;
858
859 assert(filename);
860 assert(lvalue);
861 assert(rvalue);
862 assert(data);
863
864 e = endswith(rvalue, "%");
865 if (e) {
866 unsigned long ul;
867 char *f;
868
869 errno = 0;
870 ul = strtoul(rvalue, &f, 10);
871 if (errno != 0 || f != e) {
872 log_syntax(unit, LOG_ERR, filename, line, errno, "Failed to parse percentage value, ignoring: %s", rvalue);
873 return 0;
874 }
875
876 if (ul <= 0 || ul >= 100) {
877 log_syntax(unit, LOG_ERR, filename, line, 0, "Percentage value out of range, ignoring: %s", rvalue);
878 return 0;
879 }
880
881 *sz = PAGE_ALIGN((size_t) ((physical_memory() * (uint64_t) ul) / (uint64_t) 100));
882 } else {
883 uint64_t k;
884
885 r = parse_size(rvalue, 1024, &k);
886 if (r < 0 || (uint64_t) (size_t) k != k) {
887 log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse size value, ignoring: %s", rvalue);
888 return 0;
889 }
890
891 *sz = PAGE_ALIGN((size_t) k);
892 }
893
894 return 0;
895 }