cb398bd2d4d714fd5aa1ab798f3763ed0e9a80f4
[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  * Upon return, @string contains the address of a replacement
259  * C string containing a comma-delimited list of mount options
260  * and values; or the passed-in string is freed and NULL is
261  * returned if some failure occurred.
262  */
263 po_return_t po_join(struct mount_options *options, char **str)
264 {
265         size_t len = 0;
266         struct mount_option *option;
267
268         if (!str || !options)
269                 return PO_FAILED;
270                 
271         free(*str);
272         *str = NULL;
273
274         if (options_empty(options)) {
275                 *str = strdup("");
276                 return *str ? PO_SUCCEEDED : PO_FAILED;
277         }
278
279         for (option = options->head; option; option = option->next) {
280                 len += strlen(option->keyword);
281                 if (option->value)
282                         len +=strlen(option->value) + 1;  /* equals sign */
283                 if (option->next)
284                         len++;  /* comma */
285         }
286
287         len++;  /* NULL on the end */
288
289         *str = malloc(len);
290         if (!*str)
291                 return PO_FAILED;
292         *str[0] = '\0';
293
294         for (option = options->head; option; option = option->next) {
295                 strcat(*str, option->keyword);
296                 if (option->value) {
297                         strcat(*str, "=");
298                         strcat(*str, option->value);
299                 }
300                 if (option->next)
301                         strcat(*str, ",");
302         }
303
304         return PO_SUCCEEDED;
305 }
306
307 /**
308  * po_append - concatenate an option onto a group of options
309  * @options: pointer to mount options
310  * @option: pointer to a C string containing the option to add
311  *
312  */
313 po_return_t po_append(struct mount_options *options, char *str)
314 {
315         struct mount_option *option = option_create(str);
316
317         if (option) {
318                 options_tail_insert(options, option);
319                 return PO_SUCCEEDED;
320         }
321         return PO_FAILED;
322 }
323
324 /**
325  * po_contains - check for presense of an option in a group
326  * @options: pointer to mount options
327  * @keyword: pointer to a C string containing option keyword for which to search
328  *
329  */
330 po_found_t po_contains(struct mount_options *options, char *keyword)
331 {
332         struct mount_option *option;
333
334         if (options && keyword) {
335                 for (option = options->head; option; option = option->next)
336                         if (strcmp(option->keyword, keyword) == 0)
337                                 return PO_FOUND;
338         }
339
340         return PO_NOT_FOUND;
341 }
342
343 /**
344  * po_get - return the value of the rightmost instance of an option
345  * @options: pointer to mount options
346  * @keyword: pointer to a C string containing option keyword for which to search
347  *
348  * If multiple instances of the same option are present in a mount option
349  * list, the rightmost instance is always the effective one.
350  *
351  * Returns pointer to C string containing the value of the option.
352  * Returns NULL if the option isn't found, or if the option doesn't
353  * have a value.
354  */
355 char *po_get(struct mount_options *options, char *keyword)
356 {
357         struct mount_option *option;
358
359         if (options && keyword) {
360                 for (option = options->tail; option; option = option->prev)
361                         if (strcmp(option->keyword, keyword) == 0)
362                                 return option->value;
363         }
364
365         return NULL;
366 }
367
368 /**
369  * po_rightmost - determine the relative position of two options
370  * @options: pointer to mount options
371  * @key1: pointer to a C string containing an option keyword
372  * @key2: pointer to a C string containing another option keyword
373  *
374  * The kernel parses the mount option string from left to right.
375  * If an option is specified more than once (for example, "intr"
376  * and "nointr", the rightmost option is the last to be parsed,
377  * and it therefore takes precedence over previous similar options.
378  *
379  * This function can be used to determine which of two similar
380  * options will be the one to take effect.
381  */
382 po_rightmost_t po_rightmost(struct mount_options *options,
383                             char *key1, char *key2)
384 {
385         struct mount_option *option;
386
387         if (options) {
388                 for (option = options->tail; option; option = option->prev) {
389                         if (key2 && strcmp(option->keyword, key2) == 0)
390                                 return PO_KEY2_RIGHTMOST;
391                         if (key1 && strcmp(option->keyword, key1) == 0)
392                                 return PO_KEY1_RIGHTMOST;
393                 }
394         }
395
396         return PO_NEITHER_FOUND;
397 }
398
399 /**
400  * po_remove_all - remove instances of an option from a group
401  * @options: pointer to mount options
402  * @keyword: pointer to a C string containing an option keyword to remove
403  *
404  * Side-effect: the passed-in list is truncated on success.
405  */
406 po_found_t po_remove_all(struct mount_options *options, char *keyword)
407 {
408         struct mount_option *option, *next;
409         int found = PO_NOT_FOUND;
410
411         if (options && keyword) {
412                 for (option = options->head; option; option = next) {
413                         next = option->next;
414                         if (strcmp(option->keyword, keyword) == 0) {
415                                 options_delete(options, option);
416                                 found = PO_FOUND;
417                         }
418                 }
419         }
420
421         return found;
422 }