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