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