]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/shared/fstab-util.c
ci: Optimize pull request labeler
[thirdparty/systemd.git] / src / shared / fstab-util.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include <errno.h>
4 #include <stdio.h>
5 #include <stdlib.h>
6
7 #include "alloc-util.h"
8 #include "device-nodes.h"
9 #include "fstab-util.h"
10 #include "initrd-util.h"
11 #include "macro.h"
12 #include "mount-util.h"
13 #include "nulstr-util.h"
14 #include "parse-util.h"
15 #include "path-util.h"
16 #include "proc-cmdline.h"
17 #include "string-util.h"
18 #include "strv.h"
19
20 bool fstab_enabled_full(int enabled) {
21 static int cached = -1;
22 bool val = true; /* If nothing specified or the check fails, then defaults to true. */
23 int r;
24
25 /* If 'enabled' is non-negative, then update the cache with it. */
26 if (enabled >= 0)
27 cached = enabled;
28
29 if (cached >= 0)
30 return cached;
31
32 r = proc_cmdline_get_bool("fstab", PROC_CMDLINE_STRIP_RD_PREFIX|PROC_CMDLINE_TRUE_WHEN_MISSING, &val);
33 if (r < 0)
34 log_debug_errno(r, "Failed to parse fstab= kernel command line option, ignoring: %m");
35
36 return (cached = val);
37 }
38
39 int fstab_has_fstype(const char *fstype) {
40 _cleanup_endmntent_ FILE *f = NULL;
41 struct mntent *m;
42
43 assert(fstype);
44
45 if (!fstab_enabled())
46 return false;
47
48 f = setmntent(fstab_path(), "re");
49 if (!f)
50 return errno == ENOENT ? false : -errno;
51
52 for (;;) {
53 errno = 0;
54 m = getmntent(f);
55 if (!m)
56 return errno != 0 ? -errno : false;
57
58 if (streq(m->mnt_type, fstype))
59 return true;
60 }
61 return false;
62 }
63
64 bool fstab_is_extrinsic(const char *mount, const char *opts) {
65
66 /* Don't bother with the OS data itself */
67 if (PATH_IN_SET(mount,
68 "/",
69 "/usr",
70 "/etc"))
71 return true;
72
73 if (PATH_STARTSWITH_SET(mount,
74 "/run/initramfs", /* This should stay around from before we boot until after we shutdown */
75 "/run/nextroot", /* Similar (though might be updated from the host) */
76 "/proc", /* All of this is API VFS */
77 "/sys", /* … dito … */
78 "/dev")) /* … dito … */
79 return true;
80
81 /* If this is an initrd mount, and we are not in the initrd, then leave
82 * this around forever, too. */
83 if (fstab_test_option(opts, "x-initrd.mount\0") && !in_initrd())
84 return true;
85
86 return false;
87 }
88
89 static int fstab_is_same_node(const char *what_fstab, const char *path) {
90 _cleanup_free_ char *node = NULL;
91
92 assert(what_fstab);
93 assert(path);
94
95 node = fstab_node_to_udev_node(what_fstab);
96 if (!node)
97 return -ENOMEM;
98
99 if (path_equal(node, path))
100 return true;
101
102 if (is_device_path(path) && is_device_path(node))
103 return devnode_same(node, path);
104
105 return false;
106 }
107
108 int fstab_has_mount_point_prefix_strv(char **prefixes) {
109 _cleanup_endmntent_ FILE *f = NULL;
110
111 assert(prefixes);
112
113 /* This function returns true if at least one entry in fstab has a mount point that starts with one
114 * of the passed prefixes. */
115
116 if (!fstab_enabled())
117 return false;
118
119 f = setmntent(fstab_path(), "re");
120 if (!f)
121 return errno == ENOENT ? false : -errno;
122
123 for (;;) {
124 struct mntent *me;
125
126 errno = 0;
127 me = getmntent(f);
128 if (!me)
129 return errno != 0 ? -errno : false;
130
131 if (path_startswith_strv(me->mnt_dir, prefixes))
132 return true;
133 }
134 }
135
136 int fstab_is_mount_point_full(const char *where, const char *path) {
137 _cleanup_endmntent_ FILE *f = NULL;
138 int r;
139
140 assert(where || path);
141
142 if (!fstab_enabled())
143 return false;
144
145 f = setmntent(fstab_path(), "re");
146 if (!f)
147 return errno == ENOENT ? false : -errno;
148
149 for (;;) {
150 struct mntent *me;
151
152 errno = 0;
153 me = getmntent(f);
154 if (!me)
155 return errno != 0 ? -errno : false;
156
157 if (where && !path_equal(where, me->mnt_dir))
158 continue;
159
160 if (!path)
161 return true;
162
163 r = fstab_is_same_node(me->mnt_fsname, path);
164 if (r > 0 || (r < 0 && !ERRNO_IS_DEVICE_ABSENT(r)))
165 return r;
166 }
167 }
168
169 int fstab_filter_options(
170 const char *opts,
171 const char *names,
172 const char **ret_namefound,
173 char **ret_value,
174 char ***ret_values,
175 char **ret_filtered) {
176
177 _cleanup_strv_free_ char **values = NULL;
178 _cleanup_free_ char *value = NULL, *filtered = NULL;
179 const char *namefound = NULL;
180 int r;
181
182 assert(!isempty(names));
183 assert(!(ret_value && ret_values));
184
185 if (!opts)
186 goto finish;
187
188 /* Finds any options matching 'names', and returns:
189 * - the last matching option name in ret_namefound,
190 * - the last matching value in ret_value,
191 * - any matching values in ret_values,
192 * - the rest of the option string in ret_filtered.
193 *
194 * If !ret_value and !ret_values and !ret_filtered, this function is not allowed to fail.
195 *
196 * Returns negative on error, true if any matching options were found, false otherwise. */
197
198 if (ret_value || ret_values || ret_filtered) {
199 _cleanup_strv_free_ char **opts_split = NULL;
200 _cleanup_free_ char **filtered_strv = NULL; /* strings are owned by 'opts_split' */
201
202 /* For backwards compatibility, we need to pass-through escape characters.
203 * The only ones we "consume" are the ones used as "\," or "\\". */
204 r = strv_split_full(&opts_split, opts, ",", EXTRACT_UNESCAPE_SEPARATORS|EXTRACT_UNESCAPE_RELAX);
205 if (r < 0)
206 return r;
207
208 STRV_FOREACH(opt, opts_split) {
209 bool found = false;
210 const char *x;
211
212 NULSTR_FOREACH(name, names) {
213 x = startswith(*opt, name);
214 if (!x)
215 continue;
216
217 /* If ret_values, only accept settings followed by assignment. */
218 if (*x == '=' || (!ret_values && *x == '\0')) {
219 namefound = name;
220 found = true;
221 break;
222 }
223 }
224
225 if (found) {
226 if (ret_value)
227 r = free_and_strdup(&value, *x == '=' ? x + 1 : NULL);
228 else if (ret_values)
229 r = strv_extend(&values, x + 1);
230 else
231 r = 0;
232 } else
233 r = strv_push(&filtered_strv, *opt);
234 if (r < 0)
235 return r;
236 }
237
238 filtered = strv_join_full(filtered_strv, ",", NULL, /* escape_separator = */ true);
239 if (!filtered)
240 return -ENOMEM;
241 } else
242 for (const char *word = opts;;) {
243 const char *end = word;
244
245 /* Look for a *non-escaped* comma separator. Only commas and backslashes can be
246 * escaped, so "\," and "\\" are the only valid escape sequences, and we can do a
247 * very simple test here. */
248 for (;;) {
249 end += strcspn(end, ",\\");
250
251 if (IN_SET(*end, ',', '\0'))
252 break;
253 assert(*end == '\\');
254 end++; /* Skip the backslash */
255 if (*end != '\0')
256 end++; /* Skip the escaped char, but watch out for a trailing comma */
257 }
258
259 NULSTR_FOREACH(name, names) {
260 const char *x = startswith(word, name);
261 if (!x || x > end)
262 continue;
263
264 /* We know that the string is NUL terminated, so *x is valid */
265 if (IN_SET(*x, '\0', '=', ',')) {
266 namefound = name;
267 break;
268 }
269 }
270
271 if (*end == '\0')
272 break;
273
274 word = end + 1;
275 }
276
277 finish:
278 if (ret_namefound)
279 *ret_namefound = namefound; /* owned by 'names' (passed-in) */
280 if (ret_value)
281 *ret_value = TAKE_PTR(value);
282 if (ret_values)
283 *ret_values = TAKE_PTR(values);
284 if (ret_filtered)
285 *ret_filtered = TAKE_PTR(filtered);
286
287 return !!namefound;
288 }
289
290 int fstab_find_pri(const char *opts, int *ret) {
291 _cleanup_free_ char *v = NULL;
292 int r, pri;
293
294 assert(ret);
295
296 r = fstab_filter_options(opts, "pri\0", NULL, &v, NULL, NULL);
297 if (r < 0)
298 return r;
299 if (r == 0 || !v)
300 return 0;
301
302 r = safe_atoi(v, &pri);
303 if (r < 0)
304 return r;
305
306 *ret = pri;
307 return 1;
308 }
309
310 static char *unquote(const char *s, const char* quotes) {
311 size_t l;
312 assert(s);
313
314 /* This is rather stupid, simply removes the heading and
315 * trailing quotes if there is one. Doesn't care about
316 * escaping or anything.
317 *
318 * DON'T USE THIS FOR NEW CODE ANYMORE! */
319
320 l = strlen(s);
321 if (l < 2)
322 return strdup(s);
323
324 if (strchr(quotes, s[0]) && s[l-1] == s[0])
325 return strndup(s+1, l-2);
326
327 return strdup(s);
328 }
329
330 static char *tag_to_udev_node(const char *tagvalue, const char *by) {
331 _cleanup_free_ char *t = NULL, *u = NULL;
332 size_t enc_len;
333
334 u = unquote(tagvalue, QUOTES);
335 if (!u)
336 return NULL;
337
338 enc_len = strlen(u) * 4 + 1;
339 t = new(char, enc_len);
340 if (!t)
341 return NULL;
342
343 if (encode_devnode_name(u, t, enc_len) < 0)
344 return NULL;
345
346 return strjoin("/dev/disk/by-", by, "/", t);
347 }
348
349 char *fstab_node_to_udev_node(const char *p) {
350 const char *q;
351
352 assert(p);
353
354 q = startswith(p, "LABEL=");
355 if (q)
356 return tag_to_udev_node(q, "label");
357
358 q = startswith(p, "UUID=");
359 if (q)
360 return tag_to_udev_node(q, "uuid");
361
362 q = startswith(p, "PARTUUID=");
363 if (q)
364 return tag_to_udev_node(q, "partuuid");
365
366 q = startswith(p, "PARTLABEL=");
367 if (q)
368 return tag_to_udev_node(q, "partlabel");
369
370 return strdup(p);
371 }
372
373 bool fstab_is_bind(const char *options, const char *fstype) {
374
375 if (fstab_test_option(options, "bind\0" "rbind\0"))
376 return true;
377
378 if (fstype && STR_IN_SET(fstype, "bind", "rbind"))
379 return true;
380
381 return false;
382 }