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 "macro.h"
11 #include "mount-util.h"
12 #include "nulstr-util.h"
13 #include "parse-util.h"
14 #include "path-util.h"
15 #include "string-util.h"
16 #include "strv.h"
17
fstab_has_fstype(const char * fstype)18 int fstab_has_fstype(const char *fstype) {
19 _cleanup_endmntent_ FILE *f = NULL;
20 struct mntent *m;
21
22 f = setmntent(fstab_path(), "re");
23 if (!f)
24 return errno == ENOENT ? false : -errno;
25
26 for (;;) {
27 errno = 0;
28 m = getmntent(f);
29 if (!m)
30 return errno != 0 ? -errno : false;
31
32 if (streq(m->mnt_type, fstype))
33 return true;
34 }
35 return false;
36 }
37
fstab_is_extrinsic(const char * mount,const char * opts)38 bool fstab_is_extrinsic(const char *mount, const char *opts) {
39
40 /* Don't bother with the OS data itself */
41 if (PATH_IN_SET(mount,
42 "/",
43 "/usr",
44 "/etc"))
45 return true;
46
47 if (PATH_STARTSWITH_SET(mount,
48 "/run/initramfs", /* This should stay around from before we boot until after we shutdown */
49 "/proc", /* All of this is API VFS */
50 "/sys", /* … dito … */
51 "/dev")) /* … dito … */
52 return true;
53
54 /* If this is an initrd mount, and we are not in the initrd, then leave
55 * this around forever, too. */
56 if (opts && fstab_test_option(opts, "x-initrd.mount\0") && !in_initrd())
57 return true;
58
59 return false;
60 }
61
fstab_is_mount_point(const char * mount)62 int fstab_is_mount_point(const char *mount) {
63 _cleanup_endmntent_ FILE *f = NULL;
64 struct mntent *m;
65
66 f = setmntent(fstab_path(), "re");
67 if (!f)
68 return errno == ENOENT ? false : -errno;
69
70 for (;;) {
71 errno = 0;
72 m = getmntent(f);
73 if (!m)
74 return errno != 0 ? -errno : false;
75
76 if (path_equal(m->mnt_dir, mount))
77 return true;
78 }
79 return false;
80 }
81
fstab_filter_options(const char * opts,const char * names,const char ** ret_namefound,char ** ret_value,char *** ret_values,char ** ret_filtered)82 int fstab_filter_options(
83 const char *opts,
84 const char *names,
85 const char **ret_namefound,
86 char **ret_value,
87 char ***ret_values,
88 char **ret_filtered) {
89
90 const char *name, *namefound = NULL, *x;
91 _cleanup_strv_free_ char **stor = NULL, **values = NULL;
92 _cleanup_free_ char *value = NULL, **filtered = NULL;
93 int r;
94
95 assert(names && *names);
96 assert(!(ret_value && ret_values));
97
98 if (!opts)
99 goto answer;
100
101 /* Finds any options matching 'names', and returns:
102 * - the last matching option name in ret_namefound,
103 * - the last matching value in ret_value,
104 * - any matching values in ret_values,
105 * - the rest of the option string in ret_filtered.
106 *
107 * If !ret_value and !ret_values and !ret_filtered, this function is not allowed to fail.
108 *
109 * Returns negative on error, true if any matching options were found, false otherwise. */
110
111 if (ret_filtered || ret_value || ret_values) {
112 /* For backwards compatibility, we need to pass-through escape characters.
113 * The only ones we "consume" are the ones used as "\," or "\\". */
114 r = strv_split_full(&stor, opts, ",", EXTRACT_UNESCAPE_SEPARATORS | EXTRACT_UNESCAPE_RELAX);
115 if (r < 0)
116 return r;
117
118 filtered = memdup(stor, sizeof(char*) * (strv_length(stor) + 1));
119 if (!filtered)
120 return -ENOMEM;
121
122 char **t = filtered;
123 for (char **s = t; *s; s++) {
124 NULSTR_FOREACH(name, names) {
125 x = startswith(*s, name);
126 if (!x)
127 continue;
128 /* Match name, but when ret_values, only when followed by assignment. */
129 if (*x == '=' || (!ret_values && *x == '\0'))
130 goto found;
131 }
132
133 *t = *s;
134 t++;
135 continue;
136 found:
137 /* Keep the last occurrence found */
138 namefound = name;
139
140 if (ret_value || ret_values) {
141 assert(IN_SET(*x, '=', '\0'));
142
143 if (ret_value) {
144 r = free_and_strdup(&value, *x == '=' ? x + 1 : NULL);
145 if (r < 0)
146 return r;
147 } else if (*x) {
148 r = strv_extend(&values, x + 1);
149 if (r < 0)
150 return r;
151 }
152 }
153 }
154 *t = NULL;
155 } else
156 for (const char *word = opts;;) {
157 const char *end = word;
158
159 /* Look for a *non-escaped* comma separator. Only commas and backslashes can be
160 * escaped, so "\," and "\\" are the only valid escape sequences, and we can do a
161 * very simple test here. */
162 for (;;) {
163 end += strcspn(end, ",\\");
164
165 if (IN_SET(*end, ',', '\0'))
166 break;
167 assert(*end == '\\');
168 end ++; /* Skip the backslash */
169 if (*end != '\0')
170 end ++; /* Skip the escaped char, but watch out for a trailing comma */
171 }
172
173 NULSTR_FOREACH(name, names) {
174 if (end < word + strlen(name))
175 continue;
176 if (!strneq(word, name, strlen(name)))
177 continue;
178
179 /* We know that the string is NUL terminated, so *x is valid */
180 x = word + strlen(name);
181 if (IN_SET(*x, '\0', '=', ',')) {
182 namefound = name;
183 break;
184 }
185 }
186
187 if (*end)
188 word = end + 1;
189 else
190 break;
191 }
192
193 answer:
194 if (ret_namefound)
195 *ret_namefound = namefound;
196 if (ret_filtered) {
197 char *f;
198
199 f = strv_join_full(filtered, ",", NULL, true);
200 if (!f)
201 return -ENOMEM;
202
203 *ret_filtered = f;
204 }
205 if (ret_value)
206 *ret_value = TAKE_PTR(value);
207 if (ret_values)
208 *ret_values = TAKE_PTR(values);
209
210 return !!namefound;
211 }
212
fstab_find_pri(const char * options,int * ret)213 int fstab_find_pri(const char *options, int *ret) {
214 _cleanup_free_ char *opt = NULL;
215 int r, pri;
216
217 assert(ret);
218
219 r = fstab_filter_options(options, "pri\0", NULL, &opt, NULL, NULL);
220 if (r < 0)
221 return r;
222 if (r == 0 || !opt)
223 return 0;
224
225 r = safe_atoi(opt, &pri);
226 if (r < 0)
227 return r;
228
229 *ret = pri;
230 return 1;
231 }
232
unquote(const char * s,const char * quotes)233 static char *unquote(const char *s, const char* quotes) {
234 size_t l;
235 assert(s);
236
237 /* This is rather stupid, simply removes the heading and
238 * trailing quotes if there is one. Doesn't care about
239 * escaping or anything.
240 *
241 * DON'T USE THIS FOR NEW CODE ANYMORE! */
242
243 l = strlen(s);
244 if (l < 2)
245 return strdup(s);
246
247 if (strchr(quotes, s[0]) && s[l-1] == s[0])
248 return strndup(s+1, l-2);
249
250 return strdup(s);
251 }
252
tag_to_udev_node(const char * tagvalue,const char * by)253 static char *tag_to_udev_node(const char *tagvalue, const char *by) {
254 _cleanup_free_ char *t = NULL, *u = NULL;
255 size_t enc_len;
256
257 u = unquote(tagvalue, QUOTES);
258 if (!u)
259 return NULL;
260
261 enc_len = strlen(u) * 4 + 1;
262 t = new(char, enc_len);
263 if (!t)
264 return NULL;
265
266 if (encode_devnode_name(u, t, enc_len) < 0)
267 return NULL;
268
269 return strjoin("/dev/disk/by-", by, "/", t);
270 }
271
fstab_node_to_udev_node(const char * p)272 char *fstab_node_to_udev_node(const char *p) {
273 assert(p);
274
275 if (startswith(p, "LABEL="))
276 return tag_to_udev_node(p+6, "label");
277
278 if (startswith(p, "UUID="))
279 return tag_to_udev_node(p+5, "uuid");
280
281 if (startswith(p, "PARTUUID="))
282 return tag_to_udev_node(p+9, "partuuid");
283
284 if (startswith(p, "PARTLABEL="))
285 return tag_to_udev_node(p+10, "partlabel");
286
287 return strdup(p);
288 }
289