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