]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/shared/fstab-util.c
tree-wide: drop space between variable and an increment/decrement
[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_is_mount_point_full(const char *where, const char *path) {
109 _cleanup_endmntent_ FILE *f = NULL;
110 int r;
111
112 assert(where || path);
113
114 if (!fstab_enabled())
115 return false;
116
117 f = setmntent(fstab_path(), "re");
118 if (!f)
119 return errno == ENOENT ? false : -errno;
120
121 for (;;) {
122 struct mntent *me;
123
124 errno = 0;
125 me = getmntent(f);
126 if (!me)
127 return errno != 0 ? -errno : false;
128
129 if (where && !path_equal(where, me->mnt_dir))
130 continue;
131
132 if (!path)
133 return true;
134
135 r = fstab_is_same_node(me->mnt_fsname, path);
136 if (r > 0 || (r < 0 && !ERRNO_IS_DEVICE_ABSENT(r)))
137 return r;
138 }
139
140 return false;
141 }
142
143 int fstab_filter_options(
144 const char *opts,
145 const char *names,
146 const char **ret_namefound,
147 char **ret_value,
148 char ***ret_values,
149 char **ret_filtered) {
150
151 const char *namefound = NULL, *x;
152 _cleanup_strv_free_ char **stor = NULL, **values = NULL;
153 _cleanup_free_ char *value = NULL, **filtered = NULL;
154 int r;
155
156 assert(names && *names);
157 assert(!(ret_value && ret_values));
158
159 if (!opts)
160 goto answer;
161
162 /* Finds any options matching 'names', and returns:
163 * - the last matching option name in ret_namefound,
164 * - the last matching value in ret_value,
165 * - any matching values in ret_values,
166 * - the rest of the option string in ret_filtered.
167 *
168 * If !ret_value and !ret_values and !ret_filtered, this function is not allowed to fail.
169 *
170 * Returns negative on error, true if any matching options were found, false otherwise. */
171
172 if (ret_filtered || ret_value || ret_values) {
173 /* For backwards compatibility, we need to pass-through escape characters.
174 * The only ones we "consume" are the ones used as "\," or "\\". */
175 r = strv_split_full(&stor, opts, ",", EXTRACT_UNESCAPE_SEPARATORS | EXTRACT_UNESCAPE_RELAX);
176 if (r < 0)
177 return r;
178
179 filtered = memdup(stor, sizeof(char*) * (strv_length(stor) + 1));
180 if (!filtered)
181 return -ENOMEM;
182
183 char **t = filtered;
184 for (char **s = t; *s; s++) {
185 NULSTR_FOREACH(name, names) {
186 x = startswith(*s, name);
187 if (!x)
188 continue;
189 /* Match name, but when ret_values, only when followed by assignment. */
190 if (*x == '=' || (!ret_values && *x == '\0')) {
191 /* Keep the last occurrence found */
192 namefound = name;
193 goto found;
194 }
195 }
196
197 *t = *s;
198 t++;
199 continue;
200 found:
201 if (ret_value || ret_values) {
202 assert(IN_SET(*x, '=', '\0'));
203
204 if (ret_value) {
205 r = free_and_strdup(&value, *x == '=' ? x + 1 : NULL);
206 if (r < 0)
207 return r;
208 } else if (*x) {
209 r = strv_extend(&values, x + 1);
210 if (r < 0)
211 return r;
212 }
213 }
214 }
215 *t = NULL;
216 } else
217 for (const char *word = opts;;) {
218 const char *end = word;
219
220 /* Look for a *non-escaped* comma separator. Only commas and backslashes can be
221 * escaped, so "\," and "\\" are the only valid escape sequences, and we can do a
222 * very simple test here. */
223 for (;;) {
224 end += strcspn(end, ",\\");
225
226 if (IN_SET(*end, ',', '\0'))
227 break;
228 assert(*end == '\\');
229 end++; /* Skip the backslash */
230 if (*end != '\0')
231 end++; /* Skip the escaped char, but watch out for a trailing comma */
232 }
233
234 NULSTR_FOREACH(name, names) {
235 if (end < word + strlen(name))
236 continue;
237 if (!strneq(word, name, strlen(name)))
238 continue;
239
240 /* We know that the string is NUL terminated, so *x is valid */
241 x = word + strlen(name);
242 if (IN_SET(*x, '\0', '=', ',')) {
243 namefound = name;
244 break;
245 }
246 }
247
248 if (*end)
249 word = end + 1;
250 else
251 break;
252 }
253
254 answer:
255 if (ret_namefound)
256 *ret_namefound = namefound;
257 if (ret_filtered) {
258 char *f;
259
260 f = strv_join_full(filtered, ",", NULL, true);
261 if (!f)
262 return -ENOMEM;
263
264 *ret_filtered = f;
265 }
266 if (ret_value)
267 *ret_value = TAKE_PTR(value);
268 if (ret_values)
269 *ret_values = TAKE_PTR(values);
270
271 return !!namefound;
272 }
273
274 int fstab_find_pri(const char *options, int *ret) {
275 _cleanup_free_ char *opt = NULL;
276 int r, pri;
277
278 assert(ret);
279
280 r = fstab_filter_options(options, "pri\0", NULL, &opt, NULL, NULL);
281 if (r < 0)
282 return r;
283 if (r == 0 || !opt)
284 return 0;
285
286 r = safe_atoi(opt, &pri);
287 if (r < 0)
288 return r;
289
290 *ret = pri;
291 return 1;
292 }
293
294 static char *unquote(const char *s, const char* quotes) {
295 size_t l;
296 assert(s);
297
298 /* This is rather stupid, simply removes the heading and
299 * trailing quotes if there is one. Doesn't care about
300 * escaping or anything.
301 *
302 * DON'T USE THIS FOR NEW CODE ANYMORE! */
303
304 l = strlen(s);
305 if (l < 2)
306 return strdup(s);
307
308 if (strchr(quotes, s[0]) && s[l-1] == s[0])
309 return strndup(s+1, l-2);
310
311 return strdup(s);
312 }
313
314 static char *tag_to_udev_node(const char *tagvalue, const char *by) {
315 _cleanup_free_ char *t = NULL, *u = NULL;
316 size_t enc_len;
317
318 u = unquote(tagvalue, QUOTES);
319 if (!u)
320 return NULL;
321
322 enc_len = strlen(u) * 4 + 1;
323 t = new(char, enc_len);
324 if (!t)
325 return NULL;
326
327 if (encode_devnode_name(u, t, enc_len) < 0)
328 return NULL;
329
330 return strjoin("/dev/disk/by-", by, "/", t);
331 }
332
333 char *fstab_node_to_udev_node(const char *p) {
334 const char *q;
335
336 assert(p);
337
338 q = startswith(p, "LABEL=");
339 if (q)
340 return tag_to_udev_node(q, "label");
341
342 q = startswith(p, "UUID=");
343 if (q)
344 return tag_to_udev_node(q, "uuid");
345
346 q = startswith(p, "PARTUUID=");
347 if (q)
348 return tag_to_udev_node(q, "partuuid");
349
350 q = startswith(p, "PARTLABEL=");
351 if (q)
352 return tag_to_udev_node(q, "partlabel");
353
354 return strdup(p);
355 }
356
357 bool fstab_is_bind(const char *options, const char *fstype) {
358
359 if (fstab_test_option(options, "bind\0" "rbind\0"))
360 return true;
361
362 if (fstype && STR_IN_SET(fstype, "bind", "rbind"))
363 return true;
364
365 return false;
366 }