]> git.decadent.org.uk Git - nfs-utils.git/blob - utils/mount/parse_opt.c
text-based mount.nfs: add a few useful parser return codes
[nfs-utils.git] / utils / mount / parse_opt.c
1 /*
2  * parse_opt.c -- mount option string parsing helpers
3  *
4  * Copyright (C) 2007 Oracle.  All rights reserved.
5  * Copyright (C) 2007 Chuck Lever <chuck.lever@oracle.com>
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public
9  * License as published by the Free Software Foundation; either
10  * version 2 of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public
18  * License along with this program; if not, write to the
19  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20  * Boston, MA 021110-1307, USA.
21  *
22  */
23
24 /*
25  * Converting a C string containing mount options to a data object
26  * and manipulating that object is cleaner in C than manipulating
27  * the C string itself.  This is similar to the way Python handles
28  * string manipulation.
29  *
30  * The current implementation uses a linked list as the data object
31  * since lists are simple, and we don't need to worry about more
32  * than ten or twenty options at a time.
33  *
34  * Hopefully the interface is abstract enough that the underlying
35  * data structure can be replaced if needed without changing the API.
36  */
37
38
39 #include <ctype.h>
40 #include <unistd.h>
41 #include <stdio.h>
42 #include <string.h>
43 #include <stdlib.h>
44 #include <errno.h>
45
46 #include "parse_opt.h"
47 #include "token.h"
48
49
50 struct mount_option {
51         struct mount_option *next, *prev;
52         char *keyword;
53         char *value;
54 };
55
56 struct mount_options {
57         struct mount_option *head, *tail;
58         unsigned int count;
59 };
60
61 static struct mount_option *option_create(char *str)
62 {
63         struct mount_option *option;
64         char *opteq;
65
66         if (!str)
67                 return NULL;
68
69         option = malloc(sizeof(*option));
70         if (!option)
71                 return NULL;
72         
73         option->next = NULL;
74         option->prev = NULL;
75
76         opteq = strchr(str, '=');
77         if (opteq) {
78                 option->keyword = strndup(str, opteq - str);
79                 if (!option->keyword)
80                         goto fail;
81                 option->value = strdup(opteq + 1);
82                 if (!option->value) {
83                         free(option->keyword);
84                         goto fail;
85                 }
86         } else {
87                 option->keyword = strdup(str);
88                 if (!option->keyword)
89                         goto fail;
90                 option->value = NULL;
91         }
92
93         return option;
94
95 fail:
96         free(option);
97         return NULL;
98 }
99
100 static void option_destroy(struct mount_option *option)
101 {
102         free(option->keyword);
103         free(option->value);
104         free(option);
105 }
106
107 static void options_init(struct mount_options *options)
108 {
109         options->head = options->tail = NULL;
110         options->count = 0;
111 }
112
113 static struct mount_options *options_create(void)
114 {
115         struct mount_options *options;
116
117         options = malloc(sizeof(*options));
118         if (options)
119                 options_init(options);
120
121         return options;
122 }
123
124 static int options_empty(struct mount_options *options)
125 {
126         return options->count == 0;
127 }
128
129 static void options_tail_insert(struct mount_options *options,
130                                 struct mount_option *option)
131 {
132         struct mount_option *prev = options->tail;
133
134         option->next = NULL;
135         option->prev = prev;
136
137         if (prev)
138                 prev->next = option;
139         else
140                 options->head = option;
141         options->tail = option;
142
143         options->count++;
144 }
145
146 static void options_delete(struct mount_options *options,
147                            struct mount_option *option)
148 {
149         struct mount_option *prev = option->prev;
150         struct mount_option *next = option->next;
151
152         if (!options_empty(options)) {
153                 if (prev)
154                         prev->next = option->next;
155                 if (next)
156                         next->prev = option->prev;
157
158                 if (options->head == option)
159                         options->head = option->next;
160                 if (options->tail == option)
161                         options->tail = prev;
162
163                 options->count--;
164
165                 option_destroy(option);
166         }
167 }
168
169
170 /**
171  * po_destroy - deallocate a group of mount options
172  * @options: pointer to mount options to free
173  *
174  */
175 void po_destroy(struct mount_options *options)
176 {
177         if (options) {
178                 while (!options_empty(options))
179                         options_delete(options, options->head);
180                 free(options);
181         }
182 }
183
184 /**
185  * po_split - split options string into group of options
186  * @options: pointer to C string containing zero or more comma-delimited options
187  *
188  * Convert our mount options string to a list to make it easier
189  * to adjust the options as we go.  This is just an exercise in
190  * lexical parsing -- this function doesn't pay attention to the
191  * meaning of the options themselves.
192  *
193  * Returns a new group of mount options if successful; otherwise NULL
194  * is returned if some failure occurred.
195  */
196 struct mount_options *po_split(char *str)
197 {
198         struct mount_options *options;
199         struct tokenizer_state *tstate;
200         char *opt;
201
202         if (!str)
203                 return options_create();
204
205         options = options_create();
206         if (options) {
207                 tstate = init_tokenizer(str, ',');
208                 for (opt = next_token(tstate); opt; opt = next_token(tstate)) {
209                         struct mount_option *option = option_create(opt);
210                         free(opt);
211                         if (!option)
212                                 goto fail;
213                         options_tail_insert(options, option);
214                 }
215                 if (tokenizer_error(tstate))
216                         goto fail;
217                 end_tokenizer(tstate);
218         }
219         return options;
220
221 fail:
222         end_tokenizer(tstate);
223         po_destroy(options);
224         return NULL;
225 }
226
227 /**
228  * po_replace - replace mount options in one mount_options object with another
229  * @target: pointer to previously instantiated object to replace
230  * @source: pointer to object containing source mount options
231  *
232  * Side effect: the object referred to by source is emptied.
233  */
234 void po_replace(struct mount_options *target, struct mount_options *source)
235 {
236         if (target) {
237                 while (!options_empty(target))
238                         options_delete(target, target->head);
239
240                 if (source) {
241                         target->head = source->head;
242                         target->tail = source->tail;
243                         target->count = source->count;
244
245                         options_init(source);
246                 }
247         }
248 }
249
250 /**
251  * po_join - recombine group of mount options into a C string
252  * @options: pointer to mount options to recombine
253  * @str: handle on string to replace (input and output)
254  *
255  * Convert our mount options object back into a string that the
256  * rest of the world can use.
257  *
258  * Returns 1 if the string was successfully created; otherwise
259  * zero.  Upon return, @string contains the address of a
260  * replacement C string containing a comma-delimited list of
261  * mount options and values; or the passed-in string is freed
262  * and NULL is returned if some failure occurred.
263  */
264 int po_join(struct mount_options *options, char **str)
265 {
266         size_t len = 0;
267         struct mount_option *option;
268
269         if (!str || !options)
270                 return PO_FAILED;
271                 
272         free(*str);
273         *str = NULL;
274
275         if (options_empty(options)) {
276                 *str = strdup("");
277                 return *str ? PO_SUCCEEDED : PO_FAILED;
278         }
279
280         for (option = options->head; option; option = option->next) {
281                 len += strlen(option->keyword);
282                 if (option->value)
283                         len +=strlen(option->value) + 1;  /* equals sign */
284                 if (option->next)
285                         len++;  /* comma */
286         }
287
288         len++;  /* NULL on the end */
289
290         *str = malloc(len);
291         if (!*str)
292                 return PO_FAILED;
293         *str[0] = '\0';
294
295         for (option = options->head; option; option = option->next) {
296                 strcat(*str, option->keyword);
297                 if (option->value) {
298                         strcat(*str, "=");
299                         strcat(*str, option->value);
300                 }
301                 if (option->next)
302                         strcat(*str, ",");
303         }
304
305         return PO_SUCCEEDED;
306 }
307
308 /**
309  * po_append - concatenate an option onto a group of options
310  * @options: pointer to mount options
311  * @option: pointer to a C string containing the option to add
312  *
313  * Returns 1 if the list was successfully concatenated; otherwise
314  * zero.
315  */
316 int po_append(struct mount_options *options, char *str)
317 {
318         struct mount_option *option = option_create(str);
319
320         if (option) {
321                 options_tail_insert(options, option);
322                 return PO_SUCCEEDED;
323         }
324         return PO_FAILED;
325 }
326
327 /**
328  * po_contains - check for presense of an option in a group
329  * @options: pointer to mount options
330  * @keyword: pointer to a C string containing option keyword for which to search
331  *
332  * Returns 1 if the option is present in the list; otherwise zero.
333  */
334 int po_contains(struct mount_options *options, char *keyword)
335 {
336         struct mount_option *option;
337
338         if (options && keyword) {
339                 for (option = options->head; option; option = option->next)
340                         if (strcmp(option->keyword, keyword) == 0)
341                                 return PO_FOUND;
342         }
343
344         return PO_NOT_FOUND;
345 }
346
347 /**
348  * po_get - return the value of the rightmost instance of an option
349  * @options: pointer to mount options
350  * @keyword: pointer to a C string containing option keyword for which to search
351  *
352  * If multiple instances of the same option are present in a mount option
353  * list, the rightmost instance is always the effective one.
354  *
355  * Returns pointer to C string containing the value of the option.
356  * Returns NULL if the option isn't found, or if the option doesn't
357  * have a value.
358  */
359 char *po_get(struct mount_options *options, char *keyword)
360 {
361         struct mount_option *option;
362
363         if (options && keyword) {
364                 for (option = options->tail; option; option = option->prev)
365                         if (strcmp(option->keyword, keyword) == 0)
366                                 return option->value;
367         }
368
369         return NULL;
370 }
371
372 /**
373  * po_rightmost - determine the relative position of two options
374  * @options: pointer to mount options
375  * @key1: pointer to a C string containing an option keyword
376  * @key2: pointer to a C string containing another option keyword
377  *
378  * The kernel parses the mount option string from left to right.
379  * If an option is specified more than once (for example, "intr"
380  * and "nointr", the rightmost option is the last to be parsed,
381  * and it therefore takes precedence over previous similar options.
382  *
383  * This function can be used to determine which of two similar
384  * options will be the one to take effect.
385  *
386  * Returns 1 if key2 is rightmost or key1 is not present.
387  * Returns -1 if key1 is rightmost or key2 is not present.
388  * Returns 0 if neither key is present.
389  */
390 int po_rightmost(struct mount_options *options, char *key1, char *key2)
391 {
392         struct mount_option *option;
393
394         if (options) {
395                 for (option = options->tail; option; option = option->prev) {
396                         if (key2 && strcmp(option->keyword, key2) == 0)
397                                 return PO_KEY2_RIGHTMOST;
398                         if (key1 && strcmp(option->keyword, key1) == 0)
399                                 return PO_KEY1_RIGHTMOST;
400                 }
401         }
402
403         return PO_NOT_FOUND;
404 }
405
406 /**
407  * po_remove_all - remove instances of an option from a group
408  * @options: pointer to mount options
409  * @keyword: pointer to a C string containing an option keyword to remove
410  *
411  * Returns 1 if the option was found and removed; passed-in list is
412  * truncated upon return; otherwise zero.
413  */
414 int po_remove_all(struct mount_options *options, char *keyword)
415 {
416         struct mount_option *option, *next;
417         int found = PO_NOT_FOUND;
418
419         if (options && keyword) {
420                 for (option = options->head; option; option = next) {
421                         next = option->next;
422                         if (strcmp(option->keyword, keyword) == 0) {
423                                 options_delete(options, option);
424                                 found = PO_FOUND;
425                         }
426                 }
427         }
428
429         return found;
430 }