1/* SPDX-License-Identifier: LGPL-2.1-or-later */
2/*
3 * This file is part of libmount from util-linux project.
4 *
5 * Copyright (C) 2009-2018 Karel Zak <kzak@redhat.com>
6 *
7 * libmount is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU Lesser General Public License as published by
9 * the Free Software Foundation; either version 2.1 of the License, or
10 * (at your option) any later version.
11 */
12
13#include <assert.h>
14#include <stdlib.h>
15#include <string.h>
16#include <errno.h>
17
18#ifndef min
19# define min(x, y) __extension__ ({ \
20 __typeof__(x) _min1 = (x); \
21 __typeof__(y) _min2 = (y); \
22 (void) (&_min1 == &_min2); \
23 _min1 < _min2 ? _min1 : _min2; })
24#endif
25
26/*
27 * Parses the first option from @optstr. The @optstr pointer is set to the beginning
28 * of the next option. The options string looks like 'aaa,bbb=data,foo,bar="xxx"'.
29 *
30 * Note this function is used by libmount to parse mount options. Be careful when modify.
31 *
32 * Returns -EINVAL on parse error, 1 at the end of optstr and 0 on success.
33 */
34int ul_optstr_next(char **optstr, char **name, size_t *namesz,
35 char **value, size_t *valsz)
36{
37 int open_quote = 0;
38 char *start = NULL, *stop = NULL, *p, *sep = NULL;
39 char *optstr0;
40
41 assert(optstr);
42 assert(*optstr);
43
44 optstr0 = *optstr;
45
46 if (name)
47 *name = NULL;
48 if (namesz)
49 *namesz = 0;
50 if (value)
51 *value = NULL;
52 if (valsz)
53 *valsz = 0;
54
55 /* trim leading commas as to not invalidate option
56 * strings with multiple consecutive commas */
57 while (optstr0 && *optstr0 == ',')
58 optstr0++;
59
60 for (p = optstr0; p && *p; p++) {
61 if (!start)
62 start = p; /* beginning of the option item */
63 if (*p == '"')
64 open_quote ^= 1; /* reverse the status */
65 if (open_quote)
66 continue; /* still in quoted block */
67 if (!sep && p > start && *p == '=')
68 sep = p; /* name and value separator */
69 if (*p == ',')
70 stop = p; /* terminate the option item */
71 else if (*(p + 1) == '\0')
72 stop = p + 1; /* end of optstr */
73 if (!start || !stop)
74 continue;
75 if (stop <= start)
76 return -EINVAL;
77
78 if (name)
79 *name = start;
80 if (namesz)
81 *namesz = sep ? sep - start : stop - start;
82 *optstr = *stop ? stop + 1 : stop;
83
84 if (sep) {
85 if (value)
86 *value = sep + 1;
87 if (valsz)
88 *valsz = stop - sep - 1;
89 }
90 return 0;
91 }
92
93 return 1; /* end of optstr */
94}
95
96/*
97 * Match string beginning.
98 */
99static inline const char *startswith(const char *s, const char *prefix)
100{
101 size_t sz = prefix ? strlen(prefix) : 0;
102
103 if (s && sz && strncmp(s, prefix, sz) == 0)
104 return s + sz;
105 return NULL;
106}
107
108/* caller guarantees n > 0 */
109static void xstrncpy(char *dest, const char *src, size_t n)
110{
111 size_t len = src ? strlen(src) : 0;
112
113 if (!len)
114 return;
115 len = min(len, n - 1);
116 memcpy(dest, src, len);
117 dest[len] = 0;
118}
119
120/*
121 * Option location
122 */
123struct libmnt_optloc {
124 char *begin;
125 char *end;
126 char *value;
127 size_t valsz;
128 size_t namesz;
129};
130
131#define MNT_INIT_OPTLOC { .begin = NULL }
132
133#define mnt_optmap_entry_novalue(e) \
134 (e && (e)->name && !strchr((e)->name, '=') && !((e)->mask & MNT_PREFIX))
135
136/*
137 * Locates the first option that matches @name. The @end is set to the
138 * char behind the option (it means ',' or \0).
139 *
140 * Returns negative number on parse error, 1 when not found and 0 on success.
141 */
142static int mnt_optstr_locate_option(char *optstr, const char *name,
143 struct libmnt_optloc *ol)
144{
145 char *n;
146 size_t namesz, nsz;
147 int rc;
148
149 if (!optstr)
150 return 1;
151
152 assert(name);
153
154 namesz = strlen(name);
155 if (!namesz)
156 return 1;
157
158 do {
159 rc = ul_optstr_next(&optstr, &n, &nsz,
160 &ol->value, &ol->valsz);
161 if (rc)
162 break;
163
164 if (namesz == nsz && strncmp(n, name, nsz) == 0) {
165 ol->begin = n;
166 ol->end = *(optstr - 1) == ',' ? optstr - 1 : optstr;
167 ol->namesz = nsz;
168 return 0;
169 }
170 } while(1);
171
172 return rc;
173}
174
175/**
176 * mnt_optstr_get_option:
177 * @optstr: string with a comma separated list of options
178 * @name: requested option name
179 * @value: returns a pointer to the beginning of the value (e.g. name=VALUE) or NULL
180 * @valsz: returns size of the value or 0
181 *
182 * Returns: 0 on success, 1 when not found the @name or negative number in case
183 * of error.
184 */
185int mnt_optstr_get_option(const char *optstr, const char *name,
186 char **value, size_t *valsz)
187{
188 struct libmnt_optloc ol = MNT_INIT_OPTLOC;
189 int rc;
190
191 if (!optstr || !name)
192 return -EINVAL;
193
194 rc = mnt_optstr_locate_option((char *) optstr, name, &ol);
195 if (!rc) {
196 if (value)
197 *value = ol.value;
198 if (valsz)
199 *valsz = ol.valsz;
200 }
201 return rc;
202}
203
204/**
205 * mnt_optstr_next_option:
206 * @optstr: option string, returns the position of the next option
207 * @name: returns the option name
208 * @namesz: returns the option name length
209 * @value: returns the option value or NULL
210 * @valuesz: returns the option value length or zero
211 *
212 * Parses the first option in @optstr.
213 *
214 * Returns: 0 on success, 1 at the end of @optstr or negative number in case of
215 * error.
216 */
217static int mnt_optstr_next_option(char **optstr, char **name, size_t *namesz,
218 char **value, size_t *valuesz)
219{
220 if (!optstr || !*optstr)
221 return -EINVAL;
222
223 return ul_optstr_next(optstr, name, namesz, value, valuesz);
224}
225
226int mnt_match_options(const char *optstr, const char *pattern)
227{
228 char *name, *pat = (char *) pattern;
229 char *buf = NULL, *patval;
230 size_t namesz = 0, patvalsz = 0;
231 int match = 1;
232
233 if (!pattern && !optstr)
234 return 1;
235 if (pattern && optstr && !*pattern && !*optstr)
236 return 1;
237 if (!pattern)
238 return 0;
239
240 /* walk on pattern string
241 */
242 while (match && !mnt_optstr_next_option(&pat, &name, &namesz,
243 &patval, &patvalsz)) {
244 char *val;
245 size_t sz = 0;
246 int no = 0, rc;
247
248 if (*name == '+' && namesz > 1)
249 name++, namesz--;
250 else if ((no = (startswith(name, "no") != NULL))) {
251 name += 2, namesz -= 2;
252 if (!*name || *name == ',') {
253 match = 0;
254 break; /* alone "no" keyword is error */
255 }
256 }
257
258 if (optstr && *optstr && *name) {
259 if (!buf) {
260 buf = malloc(strlen(pattern) + 1);
261 if (!buf)
262 return 0;
263 }
264
265 xstrncpy(buf, name, namesz + 1);
266 rc = mnt_optstr_get_option(optstr, buf, &val, &sz);
267
268 } else if (!*name) {
269 rc = 0; /* empty pattern matches */
270 } else {
271 rc = 1; /* not found in empty string */
272 }
273
274 /* check also value (if the pattern is "foo=value") */
275 if (rc == 0 && patvalsz > 0 &&
276 (patvalsz != sz || strncmp(patval, val, sz) != 0))
277 rc = 1;
278
279 switch (rc) {
280 case 0: /* found */
281 match = no == 0 ? 1 : 0;
282 break;
283 case 1: /* not found */
284 match = no == 1 ? 1 : 0;
285 break;
286 default: /* parse error */
287 match = 0;
288 break;
289 }
290 }
291
292 free(buf);
293 return match;
294}