umount-test-opts

Equivalence checking of BusyBox and util-linux umount(8) -O option handling

git clone https://git.8pit.net/umount-test-opts.git

  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}