]> git.decadent.org.uk Git - nfs-utils.git/blob - utils/mount/parse_opt.c
text-based mount command: fix return value from po_rightmost()
[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 #ifdef HAVE_CONFIG_H
40 #include <config.h>
41 #endif
42
43 #include <ctype.h>
44 #include <unistd.h>
45 #include <stdio.h>
46 #include <string.h>
47 #include <stdlib.h>
48 #include <errno.h>
49
50 #include "parse_opt.h"
51 #include "token.h"
52
53
54 struct mount_option {
55         struct mount_option *next, *prev;
56         char *keyword;
57         char *value;
58 };
59
60 struct mount_options {
61         struct mount_option *head, *tail;
62         unsigned int count;
63 };
64
65 static struct mount_option *option_create(char *str)
66 {
67         struct mount_option *option;
68         char *opteq;
69
70         if (!str)
71                 return NULL;
72
73         option = malloc(sizeof(*option));
74         if (!option)
75                 return NULL;
76         
77         option->next = NULL;
78         option->prev = NULL;
79
80         opteq = strchr(str, '=');
81         if (opteq) {
82                 option->keyword = strndup(str, opteq - str);
83                 if (!option->keyword)
84                         goto fail;
85                 option->value = strdup(opteq + 1);
86                 if (!option->value) {
87                         free(option->keyword);
88                         goto fail;
89                 }
90         } else {
91                 option->keyword = strdup(str);
92                 if (!option->keyword)
93                         goto fail;
94                 option->value = NULL;
95         }
96
97         return option;
98
99 fail:
100         free(option);
101         return NULL;
102 }
103
104 static void option_destroy(struct mount_option *option)
105 {
106         free(option->keyword);
107         free(option->value);
108         free(option);
109 }
110
111 static void options_init(struct mount_options *options)
112 {
113         options->head = options->tail = NULL;
114         options->count = 0;
115 }
116
117 static struct mount_options *options_create(void)
118 {
119         struct mount_options *options;
120
121         options = malloc(sizeof(*options));
122         if (options)
123                 options_init(options);
124
125         return options;
126 }
127
128 static int options_empty(struct mount_options *options)
129 {
130         return options->count == 0;
131 }
132
133 static void options_tail_insert(struct mount_options *options,
134                                 struct mount_option *option)
135 {
136         struct mount_option *prev = options->tail;
137
138         option->next = NULL;
139         option->prev = prev;
140
141         if (prev)
142                 prev->next = option;
143         else
144                 options->head = option;
145         options->tail = option;
146
147         options->count++;
148 }
149
150 static void options_delete(struct mount_options *options,
151                            struct mount_option *option)
152 {
153         struct mount_option *prev = option->prev;
154         struct mount_option *next = option->next;
155
156         if (!options_empty(options)) {
157                 if (prev)
158                         prev->next = option->next;
159                 if (next)
160                         next->prev = option->prev;
161
162                 if (options->head == option)
163                         options->head = option->next;
164                 if (options->tail == option)
165                         options->tail = prev;
166
167                 options->count--;
168
169                 option_destroy(option);
170         }
171 }
172
173
174 /**
175  * po_destroy - deallocate a group of mount options
176  * @options: pointer to mount options to free
177  *
178  */
179 void po_destroy(struct mount_options *options)
180 {
181         if (options) {
182                 while (!options_empty(options))
183                         options_delete(options, options->head);
184                 free(options);
185         }
186 }
187
188 /**
189  * po_split - split options string into group of options
190  * @options: pointer to C string containing zero or more comma-delimited options
191  *
192  * Convert our mount options string to a list to make it easier
193  * to adjust the options as we go.  This is just an exercise in
194  * lexical parsing -- this function doesn't pay attention to the
195  * meaning of the options themselves.
196  *
197  * Returns a new group of mount options if successful; otherwise NULL
198  * is returned if some failure occurred.
199  */
200 struct mount_options *po_split(char *str)
201 {
202         struct mount_options *options;
203         struct tokenizer_state *tstate;
204         char *opt;
205
206         if (!str)
207                 return options_create();
208
209         options = options_create();
210         if (options) {
211                 tstate = init_tokenizer(str, ',');
212                 for (opt = next_token(tstate); opt; opt = next_token(tstate)) {
213                         struct mount_option *option = option_create(opt);
214                         free(opt);
215                         if (!option)
216                                 goto fail;
217                         options_tail_insert(options, option);
218                 }
219                 if (tokenizer_error(tstate))
220                         goto fail;
221                 end_tokenizer(tstate);
222         }
223         return options;
224
225 fail:
226         end_tokenizer(tstate);
227         po_destroy(options);
228         return NULL;
229 }
230
231 /**
232  * po_replace - replace mount options in one mount_options object with another
233  * @target: pointer to previously instantiated object to replace
234  * @source: pointer to object containing source mount options
235  *
236  * Side effect: the object referred to by source is emptied.
237  */
238 void po_replace(struct mount_options *target, struct mount_options *source)
239 {
240         if (target) {
241                 while (!options_empty(target))
242                         options_delete(target, target->head);
243
244                 if (source) {
245                         target->head = source->head;
246                         target->tail = source->tail;
247                         target->count = source->count;
248
249                         options_init(source);
250                 }
251         }
252 }
253
254 /**
255  * po_join - recombine group of mount options into a C string
256  * @options: pointer to mount options to recombine
257  * @str: handle on string to replace (input and output)
258  *
259  * Convert our mount options object back into a string that the
260  * rest of the world can use.
261  *
262  * Upon return, @string contains the address of a replacement
263  * C string containing a comma-delimited list of mount options
264  * and values; or the passed-in string is freed and NULL is
265  * returned if some failure occurred.
266  */
267 po_return_t po_join(struct mount_options *options, char **str)
268 {
269         size_t len = 0;
270         struct mount_option *option;
271
272         if (!str || !options)
273                 return PO_FAILED;
274                 
275         free(*str);
276         *str = NULL;
277
278         if (options_empty(options)) {
279                 *str = strdup("");
280                 return *str ? PO_SUCCEEDED : PO_FAILED;
281         }
282
283         for (option = options->head; option; option = option->next) {
284                 len += strlen(option->keyword);
285                 if (option->value)
286                         len +=strlen(option->value) + 1;  /* equals sign */
287                 if (option->next)
288                         len++;  /* comma */
289         }
290
291         len++;  /* NULL on the end */
292
293         *str = malloc(len);
294         if (!*str)
295                 return PO_FAILED;
296         *str[0] = '\0';
297
298         for (option = options->head; option; option = option->next) {
299                 strcat(*str, option->keyword);
300                 if (option->value) {
301                         strcat(*str, "=");
302                         strcat(*str, option->value);
303                 }
304                 if (option->next)
305                         strcat(*str, ",");
306         }
307
308         return PO_SUCCEEDED;
309 }
310
311 /**
312  * po_append - concatenate an option onto a group of options
313  * @options: pointer to mount options
314  * @option: pointer to a C string containing the option to add
315  *
316  */
317 po_return_t po_append(struct mount_options *options, char *str)
318 {
319         struct mount_option *option = option_create(str);
320
321         if (option) {
322                 options_tail_insert(options, option);
323                 return PO_SUCCEEDED;
324         }
325         return PO_FAILED;
326 }
327
328 /**
329  * po_contains - check for presense of an option in a group
330  * @options: pointer to mount options
331  * @keyword: pointer to a C string containing option keyword for which to search
332  *
333  */
334 po_found_t 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_get_numeric - return numeric value of rightmost instance of keyword option
374  * @options: pointer to mount options
375  * @keyword: pointer to a C string containing option keyword for which to search
376  * @value: OUT: set to the value of the keyword
377  *
378  * This is specifically for parsing keyword options that take only a numeric
379  * value.  If multiple instances of the same option are present in a mount
380  * option list, the rightmost instance is always the effective one.
381  *
382  * Returns:
383  *      * PO_FOUND if the keyword was found and the value is numeric; @value is
384  *        set to the keyword's value
385  *      * PO_NOT_FOUND if the keyword was not found
386  *      * PO_BAD_VALUE if the keyword was found, but the value is not numeric
387  *
388  * These last two are separate in case the caller wants to warn about bad mount
389  * options instead of silently using a default.
390  */
391 #ifdef HAVE_STRTOL
392 po_found_t po_get_numeric(struct mount_options *options, char *keyword, long *value)
393 {
394         char *option, *endptr;
395         long tmp;
396
397         option = po_get(options, keyword);
398         if (option == NULL)
399                 return PO_NOT_FOUND;
400
401         errno = 0;
402         tmp = strtol(option, &endptr, 10);
403         if (errno == 0 && endptr != option) {
404                 *value = tmp;
405                 return PO_FOUND;
406         }
407         return PO_BAD_VALUE;
408 }
409 #else   /* HAVE_STRTOL */
410 po_found_t po_get_numeric(struct mount_options *options, char *keyword, long *value)
411 {
412         char *option;
413
414         option = po_get(options, keyword);
415         if (option == NULL)
416                 return PO_NOT_FOUND;
417
418         *value = atoi(option);
419         return PO_FOUND;
420 }
421 #endif  /* HAVE_STRTOL */
422
423 /**
424  * po_rightmost - determine the relative position of several options
425  * @options: pointer to mount options
426  * @keys: pointer to an array of C strings containing option keywords
427  *
428  * This function can be used to determine which of several similar
429  * options will be the one to take effect.
430  *
431  * The kernel parses the mount option string from left to right.
432  * If an option is specified more than once (for example, "intr"
433  * and "nointr", the rightmost option is the last to be parsed,
434  * and it therefore takes precedence over previous similar options.
435  *
436  * This can also distinguish among multiple synonymous options, such
437  * as "proto=," "udp" and "tcp."
438  *
439  * Returns the index into @keys of the option that is rightmost.
440  * If none of the options listed in @keys is present in @options, or
441  * if @options is NULL, returns -1.
442  */
443 int po_rightmost(struct mount_options *options, const char *keys[])
444 {
445         struct mount_option *option;
446         unsigned int i;
447
448         if (options) {
449                 for (option = options->tail; option; option = option->prev) {
450                         for (i = 0; keys[i] != NULL; i++)
451                                 if (strcmp(option->keyword, keys[i]) == 0)
452                                         return i;
453                 }
454         }
455
456         return -1;
457 }
458
459 /**
460  * po_remove_all - remove instances of an option from a group
461  * @options: pointer to mount options
462  * @keyword: pointer to a C string containing an option keyword to remove
463  *
464  * Side-effect: the passed-in list is truncated on success.
465  */
466 po_found_t po_remove_all(struct mount_options *options, char *keyword)
467 {
468         struct mount_option *option, *next;
469         int found = PO_NOT_FOUND;
470
471         if (options && keyword) {
472                 for (option = options->head; option; option = next) {
473                         next = option->next;
474                         if (strcmp(option->keyword, keyword) == 0) {
475                                 options_delete(options, option);
476                                 found = PO_FOUND;
477                         }
478                 }
479         }
480
481         return found;
482 }