]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/login/user-runtime-dir.c
Fixes for vscode/intellisense parsing (#38040)
[thirdparty/systemd.git] / src / login / user-runtime-dir.c
CommitLineData
db9ecf05 1/* SPDX-License-Identifier: LGPL-2.1-or-later */
a9f0f5e5 2
a9f0f5e5
ZJS
3#include <sys/mount.h>
4
07ee5adb
LP
5#include "sd-bus.h"
6
7#include "bus-error.h"
d962e737 8#include "bus-locator.h"
b1c95fb2 9#include "devnum-util.h"
6ad1d1ed 10#include "errno-util.h"
b1c95fb2 11#include "fd-util.h"
ca78ad1d 12#include "format-util.h"
5d1e68b4 13#include "fs-util.h"
0690160e 14#include "label-util.h"
5d1e68b4 15#include "limits-util.h"
76d62b63 16#include "log.h"
5e332028 17#include "main-func.h"
b1c95fb2
LP
18#include "missing_magic.h"
19#include "missing_syscall.h"
35cd0ba5 20#include "mkdir-label.h"
21935150 21#include "mount-util.h"
049af8ad 22#include "mountpoint-util.h"
a9f0f5e5 23#include "path-util.h"
b1c95fb2 24#include "quota-util.h"
a9f0f5e5 25#include "rm-rf.h"
6ad1d1ed 26#include "set.h"
a9f0f5e5 27#include "smack-util.h"
d9ccf6b3 28#include "stat-util.h"
a9f0f5e5
ZJS
29#include "stdio-util.h"
30#include "string-util.h"
31#include "strv.h"
32#include "user-util.h"
b1c95fb2 33#include "userdb.h"
a9f0f5e5 34
909ba690 35static int acquire_runtime_dir_properties(uint64_t *ret_size, uint64_t *ret_inodes) {
07ee5adb 36 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
92e31da1 37 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
909ba690 38 uint64_t size, inodes;
a9f0f5e5
ZJS
39 int r;
40
909ba690
MY
41 assert(ret_size);
42 assert(ret_inodes);
43
07ee5adb
LP
44 r = sd_bus_default_system(&bus);
45 if (r < 0)
46 return log_error_errno(r, "Failed to connect to system bus: %m");
a9f0f5e5 47
909ba690 48 r = bus_get_property_trivial(bus, bus_login_mgr, "RuntimeDirectorySize", &error, 't', &size);
5d1e68b4
LP
49 if (r < 0) {
50 log_warning_errno(r, "Failed to acquire runtime directory size, ignoring: %s", bus_error_message(&error, r));
909ba690
MY
51 sd_bus_error_free(&error);
52
53 size = physical_memory_scale(10U, 100U); /* 10% */
5d1e68b4 54 }
a9f0f5e5 55
909ba690 56 r = bus_get_property_trivial(bus, bus_login_mgr, "RuntimeDirectoryInodesMax", &error, 't', &inodes);
5d1e68b4
LP
57 if (r < 0) {
58 log_warning_errno(r, "Failed to acquire number of inodes for runtime directory, ignoring: %s", bus_error_message(&error, r));
909ba690
MY
59 sd_bus_error_free(&error);
60
61 inodes = DIV_ROUND_UP(size, 4096);
5d1e68b4 62 }
cc1c85fb 63
909ba690
MY
64 *ret_size = size;
65 *ret_inodes = inodes;
66
a9f0f5e5
ZJS
67 return 0;
68}
69
07ee5adb
LP
70static int user_mkdir_runtime_path(
71 const char *runtime_path,
72 uid_t uid,
73 gid_t gid,
cc1c85fb
TM
74 uint64_t runtime_dir_size,
75 uint64_t runtime_dir_inodes) {
07ee5adb 76
a9f0f5e5
ZJS
77 int r;
78
79 assert(runtime_path);
80 assert(path_is_absolute(runtime_path));
81 assert(uid_is_valid(uid));
82 assert(gid_is_valid(gid));
83
84 r = mkdir_safe_label("/run/user", 0755, 0, 0, MKDIR_WARN_MODE);
85 if (r < 0)
86 return log_error_errno(r, "Failed to create /run/user: %m");
87
b409aacb 88 if (path_is_mount_point(runtime_path) > 0)
a9f0f5e5
ZJS
89 log_debug("%s is already a mount point", runtime_path);
90 else {
0b8a714b 91 char options[STRLEN("mode=0700,uid=,gid=,size=,nr_inodes=,smackfsroot=*")
a9f0f5e5
ZJS
92 + DECIMAL_STR_MAX(uid_t)
93 + DECIMAL_STR_MAX(gid_t)
cc1c85fb 94 + DECIMAL_STR_MAX(uint64_t)
07ee5adb 95 + DECIMAL_STR_MAX(uint64_t)];
a9f0f5e5
ZJS
96
97 xsprintf(options,
cc1c85fb
TM
98 "mode=0700,uid=" UID_FMT ",gid=" GID_FMT ",size=%" PRIu64 ",nr_inodes=%" PRIu64 "%s",
99 uid, gid, runtime_dir_size, runtime_dir_inodes,
a9f0f5e5
ZJS
100 mac_smack_use() ? ",smackfsroot=*" : "");
101
9ef12bc1
LP
102 _cleanup_free_ char *d = strdup(runtime_path);
103 if (!d)
104 return log_oom();
105
4a00b45f
ZJS
106 r = mkdir_label(runtime_path, 0700);
107 if (r < 0 && r != -EEXIST)
108 return log_error_errno(r, "Failed to create %s: %m", runtime_path);
a9f0f5e5 109
9ef12bc1
LP
110 _cleanup_(rmdir_and_freep) char *destroy = TAKE_PTR(d); /* auto-destroy */
111
21935150 112 r = mount_nofollow_verbose(LOG_DEBUG, "tmpfs", runtime_path, "tmpfs", MS_NODEV|MS_NOSUID, options);
a9f0f5e5 113 if (r < 0) {
9ef12bc1
LP
114 if (!ERRNO_IS_PRIVILEGE(r))
115 return log_error_errno(r, "Failed to mount per-user tmpfs directory %s: %m", runtime_path);
a9f0f5e5 116
21935150 117 log_debug_errno(r,
32429805 118 "Failed to mount per-user tmpfs directory %s.\n"
a9f0f5e5
ZJS
119 "Assuming containerized execution, ignoring: %m", runtime_path);
120
121 r = chmod_and_chown(runtime_path, 0700, uid, gid);
9ef12bc1
LP
122 if (r < 0)
123 return log_error_errno(r, "Failed to change ownership and mode of \"%s\": %m", runtime_path);
a9f0f5e5
ZJS
124 }
125
9ef12bc1
LP
126 destroy = mfree(destroy); /* deactivate auto-destroy */
127
a9f0f5e5
ZJS
128 r = label_fix(runtime_path, 0);
129 if (r < 0)
130 log_warning_errno(r, "Failed to fix label of \"%s\", ignoring: %m", runtime_path);
131 }
132
133 return 0;
a9f0f5e5
ZJS
134}
135
b1c95fb2
LP
136static int do_mount(UserRecord *ur) {
137 int r;
138
139 assert(ur);
140
b3adb7cd 141 if (!uid_is_valid(ur->uid) || !gid_is_valid(user_record_gid(ur)))
b1c95fb2
LP
142 return log_error_errno(SYNTHETIC_ERRNO(ENOMSG), "User '%s' lacks UID or GID, refusing.", ur->user_name);
143
144 uint64_t runtime_dir_size, runtime_dir_inodes;
145 r = acquire_runtime_dir_properties(&runtime_dir_size, &runtime_dir_inodes);
146 if (r < 0)
147 return r;
148
149 char runtime_path[STRLEN("/run/user/") + DECIMAL_STR_MAX(uid_t)];
150 xsprintf(runtime_path, "/run/user/" UID_FMT, ur->uid);
151
b3adb7cd
LP
152 log_debug("Will mount %s owned by "UID_FMT":"GID_FMT, runtime_path, ur->uid, user_record_gid(ur));
153 return user_mkdir_runtime_path(runtime_path, ur->uid, user_record_gid(ur), runtime_dir_size, runtime_dir_inodes);
b1c95fb2
LP
154}
155
a9f0f5e5
ZJS
156static int user_remove_runtime_path(const char *runtime_path) {
157 int r;
158
159 assert(runtime_path);
160 assert(path_is_absolute(runtime_path));
161
162 r = rm_rf(runtime_path, 0);
163 if (r < 0)
3a13442b 164 log_debug_errno(r, "Failed to remove runtime directory %s (before unmounting), ignoring: %m", runtime_path);
a9f0f5e5 165
3a13442b
LP
166 /* Ignore cases where the directory isn't mounted, as that's quite possible, if we lacked the permissions to
167 * mount something */
9ef12bc1
LP
168 r = RET_NERRNO(umount2(runtime_path, MNT_DETACH));
169 if (r < 0 && !IN_SET(r, -EINVAL, -ENOENT))
170 log_debug_errno(r, "Failed to unmount user runtime directory %s, ignoring: %m", runtime_path);
a9f0f5e5
ZJS
171
172 r = rm_rf(runtime_path, REMOVE_ROOT);
3a13442b
LP
173 if (r < 0 && r != -ENOENT)
174 return log_error_errno(r, "Failed to remove runtime directory %s (after unmounting): %m", runtime_path);
a9f0f5e5 175
3a13442b 176 return 0;
a9f0f5e5
ZJS
177}
178
86d18f3b 179static int do_umount(const char *user) {
0b8a714b 180 char runtime_path[STRLEN("/run/user/") + DECIMAL_STR_MAX(uid_t)];
86d18f3b
YW
181 uid_t uid;
182 int r;
183
184 /* The user may be already removed. So, first try to parse the string by parse_uid(),
7802194a 185 * and if it fails, fall back to get_user_creds(). */
86d18f3b 186 if (parse_uid(user, &uid) < 0) {
fafff8f1 187 r = get_user_creds(&user, &uid, NULL, NULL, NULL, 0);
86d18f3b
YW
188 if (r < 0)
189 return log_error_errno(r,
190 r == -ESRCH ? "No such user \"%s\"" :
191 r == -ENOMSG ? "UID \"%s\" is invalid or has an invalid main group"
192 : "Failed to look up user \"%s\": %m",
193 user);
194 }
195
196 xsprintf(runtime_path, "/run/user/" UID_FMT, uid);
197
a9f0f5e5
ZJS
198 log_debug("Will remove %s", runtime_path);
199 return user_remove_runtime_path(runtime_path);
200}
201
b1c95fb2
LP
202static int apply_tmpfs_quota(
203 char **paths,
204 uid_t uid,
205 uint64_t limit,
206 uint32_t scale) {
207
208 _cleanup_set_free_ Set *processed = NULL;
209 int r;
210
211 assert(uid_is_valid(uid));
212
213 STRV_FOREACH(p, paths) {
214 _cleanup_close_ int fd = open(*p, O_DIRECTORY|O_CLOEXEC);
215 if (fd < 0) {
216 log_warning_errno(errno, "Failed to open '%s' in order to set quota, ignoring: %m", *p);
217 continue;
218 }
219
220 struct stat st;
221 if (fstat(fd, &st) < 0) {
222 log_warning_errno(errno, "Failed to stat '%s' in order to set quota, ignoring: %m", *p);
223 continue;
224 }
225
226 /* Cover for bind mounted or symlinked /var/tmp/ + /tmp/ */
227 if (set_contains(processed, DEVNUM_TO_PTR(st.st_dev))) {
228 log_debug("Not setting quota on '%s', since already processed.", *p);
229 continue;
230 }
231
232 /* Remember we already dealt with this fs, even if the subsequent operation fails, since
233 * there's no point in appyling quota twice, regardless if it succeeds or not. */
234 if (set_ensure_put(&processed, /* hash_ops= */ NULL, DEVNUM_TO_PTR(st.st_dev)) < 0)
235 return log_oom();
236
237 struct statfs sfs;
238 if (fstatfs(fd, &sfs) < 0) {
239 log_warning_errno(errno, "Failed to statfs '%s' in order to set quota, ignoring: %m", *p);
240 continue;
241 }
242
243 if (!is_fs_type(&sfs, TMPFS_MAGIC)) {
244 log_debug("Not setting quota on '%s', since not tmpfs.", *p);
245 continue;
246 }
247
248 struct dqblk req;
249 r = RET_NERRNO(quotactl_fd(fd, QCMD_FIXED(Q_GETQUOTA, USRQUOTA), uid, &req));
250 if (r == -ESRCH)
251 zero(req);
252 else if (ERRNO_IS_NEG_NOT_SUPPORTED(r)) {
253 log_debug_errno(r, "No UID quota support on %s, not setting quota: %m", *p);
254 continue;
255 } else if (ERRNO_IS_NEG_PRIVILEGE(r)) {
256 log_debug_errno(r, "Lacking privileges to query UID quota on %s, not setting quota: %m", *p);
257 continue;
258 } else if (r < 0) {
259 log_warning_errno(r, "Failed to query disk quota on %s for UID " UID_FMT ", ignoring: %m", *p, uid);
260 continue;
261 }
262
263 uint64_t v =
264 (scale == 0) ? 0 :
265 (scale == UINT32_MAX) ? UINT64_MAX :
6790db81 266 (uint64_t) ((double) (sfs.f_blocks * sfs.f_frsize) * scale / UINT32_MAX);
b1c95fb2
LP
267
268 v = MIN(v, limit);
269 v /= QIF_DQBLKSIZE;
270
271 if (FLAGS_SET(req.dqb_valid, QIF_BLIMITS) && v == req.dqb_bhardlimit) {
272 /* Shortcut things if everything is set up properly already */
273 log_debug("Configured quota on '%s' already matches the intended setting, not updating quota.", *p);
274 continue;
275 }
276
277 req.dqb_valid = QIF_BLIMITS;
278 req.dqb_bsoftlimit = req.dqb_bhardlimit = v;
279
280 r = RET_NERRNO(quotactl_fd(fd, QCMD_FIXED(Q_SETQUOTA, USRQUOTA), uid, &req));
281 if (r == -ESRCH) {
282 log_debug_errno(r, "Not setting UID quota on %s since UID quota is not supported: %m", *p);
283 continue;
284 } else if (ERRNO_IS_NEG_PRIVILEGE(r)) {
285 log_debug_errno(r, "Lacking privileges to set UID quota on %s, skipping: %m", *p);
286 continue;
287 } else if (r < 0) {
7ebe910c 288 log_warning_errno(r, "Failed to set disk quota limit to '%s' on %s for UID " UID_FMT ", ignoring: %m", FORMAT_BYTES(v), *p, uid);
b1c95fb2
LP
289 continue;
290 }
291
292 log_info("Successfully configured disk quota for UID " UID_FMT " on %s to %s", uid, *p, FORMAT_BYTES(v * QIF_DQBLKSIZE));
293 }
294
295 return 0;
296}
297
298static int do_tmpfs_quota(UserRecord *ur) {
299 int r;
300
301 assert(ur);
302
303 if (user_record_is_root(ur)) {
304 log_debug("Not applying tmpfs quota to root user.");
305 return 0;
306 }
307
308 if (!uid_is_valid(ur->uid))
309 return log_error_errno(SYNTHETIC_ERRNO(ENOMSG), "User '%s' lacks UID, refusing.", ur->user_name);
310
311 r = apply_tmpfs_quota(STRV_MAKE("/tmp", "/var/tmp"), ur->uid, ur->tmp_limit.limit, user_record_tmp_limit_scale(ur));
312 if (r < 0)
313 return r;
314
315 r = apply_tmpfs_quota(STRV_MAKE("/dev/shm"), ur->uid, ur->dev_shm_limit.limit, user_record_dev_shm_limit_scale(ur));
316 if (r < 0)
317 return r;
318
319 return 0;
320}
321
cc639ee7 322static int run(int argc, char *argv[]) {
a9f0f5e5
ZJS
323 int r;
324
aa976d87 325 log_setup();
a9f0f5e5 326
baaa35ad
ZJS
327 if (argc != 3)
328 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
329 "This program takes two arguments.");
9ef12bc1
LP
330
331 const char *verb = argv[1], *user = argv[2];
332
333 if (!STR_IN_SET(verb, "start", "stop"))
baaa35ad
ZJS
334 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
335 "First argument must be either \"start\" or \"stop\".");
a9f0f5e5 336
a9ba0e32
CG
337 umask(0022);
338
a452c807 339 r = mac_init();
cc639ee7 340 if (r < 0)
a9ba0e32 341 return r;
a9f0f5e5 342
b1c95fb2
LP
343 if (streq(verb, "start")) {
344 _cleanup_(user_record_unrefp) UserRecord *ur = NULL;
74192916 345 r = userdb_by_name(user, /* match= */ NULL, USERDB_PARSE_NUMERIC|USERDB_SUPPRESS_SHADOW, &ur);
b1c95fb2
LP
346 if (r == -ESRCH)
347 return log_error_errno(r, "User '%s' does not exist: %m", user);
348 if (r < 0)
349 return log_error_errno(r, "Failed to resolve user '%s': %m", user);
350
351 /* We do two things here: mount the per-user XDG_RUNTIME_DIR, and set up tmpfs quota on /tmp/
352 * and /dev/shm/. */
353
354 r = 0;
355 RET_GATHER(r, do_mount(ur));
356 RET_GATHER(r, do_tmpfs_quota(ur));
357 return r;
358 }
359
9ef12bc1
LP
360 if (streq(verb, "stop"))
361 return do_umount(user);
b1c95fb2 362
04499a70 363 assert_not_reached();
a9f0f5e5 364}
cc639ee7
ZJS
365
366DEFINE_MAIN_FUNCTION(run);