]> git.decadent.org.uk Git - nfs-utils.git/blobdiff - utils/mount/parse_opt.c
text-based mount.nfs: parse option strings into lists
[nfs-utils.git] / utils / mount / parse_opt.c
diff --git a/utils/mount/parse_opt.c b/utils/mount/parse_opt.c
new file mode 100644 (file)
index 0000000..fb4a0e4
--- /dev/null
@@ -0,0 +1,430 @@
+/*
+ * parse_opt.c -- mount option string parsing helpers
+ *
+ * Copyright (C) 2007 Oracle.  All rights reserved.
+ * Copyright (C) 2007 Chuck Lever <chuck.lever@oracle.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 021110-1307, USA.
+ *
+ */
+
+/*
+ * Converting a C string containing mount options to a data object
+ * and manipulating that object is cleaner in C than manipulating
+ * the C string itself.  This is similar to the way Python handles
+ * string manipulation.
+ *
+ * The current implementation uses a linked list as the data object
+ * since lists are simple, and we don't need to worry about more
+ * than ten or twenty options at a time.
+ *
+ * Hopefully the interface is abstract enough that the underlying
+ * data structure can be replaced if needed without changing the API.
+ */
+
+
+#include <ctype.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+
+#include "parse_opt.h"
+#include "token.h"
+
+
+struct mount_option {
+       struct mount_option *next, *prev;
+       char *keyword;
+       char *value;
+};
+
+struct mount_options {
+       struct mount_option *head, *tail;
+       unsigned int count;
+};
+
+static struct mount_option *option_create(char *str)
+{
+       struct mount_option *option;
+       char *opteq;
+
+       if (!str)
+               return NULL;
+
+       option = malloc(sizeof(*option));
+       if (!option)
+               return NULL;
+       
+       option->next = NULL;
+       option->prev = NULL;
+
+       opteq = strchr(str, '=');
+       if (opteq) {
+               option->keyword = strndup(str, opteq - str);
+               if (!option->keyword)
+                       goto fail;
+               option->value = strdup(opteq + 1);
+               if (!option->value) {
+                       free(option->keyword);
+                       goto fail;
+               }
+       } else {
+               option->keyword = strdup(str);
+               if (!option->keyword)
+                       goto fail;
+               option->value = NULL;
+       }
+
+       return option;
+
+fail:
+       free(option);
+       return NULL;
+}
+
+static void option_destroy(struct mount_option *option)
+{
+       free(option->keyword);
+       free(option->value);
+       free(option);
+}
+
+static void options_init(struct mount_options *options)
+{
+       options->head = options->tail = NULL;
+       options->count = 0;
+}
+
+static struct mount_options *options_create(void)
+{
+       struct mount_options *options;
+
+       options = malloc(sizeof(*options));
+       if (options)
+               options_init(options);
+
+       return options;
+}
+
+static int options_empty(struct mount_options *options)
+{
+       return options->count == 0;
+}
+
+static void options_tail_insert(struct mount_options *options,
+                               struct mount_option *option)
+{
+       struct mount_option *prev = options->tail;
+
+       option->next = NULL;
+       option->prev = prev;
+
+       if (prev)
+               prev->next = option;
+       else
+               options->head = option;
+       options->tail = option;
+
+       options->count++;
+}
+
+static void options_delete(struct mount_options *options,
+                          struct mount_option *option)
+{
+       struct mount_option *prev = option->prev;
+       struct mount_option *next = option->next;
+
+       if (!options_empty(options)) {
+               if (prev)
+                       prev->next = option->next;
+               if (next)
+                       next->prev = option->prev;
+
+               if (options->head == option)
+                       options->head = option->next;
+               if (options->tail == option)
+                       options->tail = prev;
+
+               options->count--;
+
+               option_destroy(option);
+       }
+}
+
+
+/**
+ * po_destroy - deallocate a group of mount options
+ * @options: pointer to mount options to free
+ *
+ */
+void po_destroy(struct mount_options *options)
+{
+       if (options) {
+               while (!options_empty(options))
+                       options_delete(options, options->head);
+               free(options);
+       }
+}
+
+/**
+ * po_split - split options string into group of options
+ * @options: pointer to C string containing zero or more comma-delimited options
+ *
+ * Convert our mount options string to a list to make it easier
+ * to adjust the options as we go.  This is just an exercise in
+ * lexical parsing -- this function doesn't pay attention to the
+ * meaning of the options themselves.
+ *
+ * Returns a new group of mount options if successful; otherwise NULL
+ * is returned if some failure occurred.
+ */
+struct mount_options *po_split(char *str)
+{
+       struct mount_options *options;
+       struct tokenizer_state *tstate;
+       char *opt;
+
+       if (!str)
+               return options_create();
+
+       options = options_create();
+       if (options) {
+               tstate = init_tokenizer(str, ',');
+               for (opt = next_token(tstate); opt; opt = next_token(tstate)) {
+                       struct mount_option *option = option_create(opt);
+                       free(opt);
+                       if (!option)
+                               goto fail;
+                       options_tail_insert(options, option);
+               }
+               if (tokenizer_error(tstate))
+                       goto fail;
+               end_tokenizer(tstate);
+       }
+       return options;
+
+fail:
+       end_tokenizer(tstate);
+       po_destroy(options);
+       return NULL;
+}
+
+/**
+ * po_replace - replace mount options in one mount_options object with another
+ * @target: pointer to previously instantiated object to replace
+ * @source: pointer to object containing source mount options
+ *
+ * Side effect: the object referred to by source is emptied.
+ */
+void po_replace(struct mount_options *target, struct mount_options *source)
+{
+       if (target) {
+               while (!options_empty(target))
+                       options_delete(target, target->head);
+
+               if (source) {
+                       target->head = source->head;
+                       target->tail = source->tail;
+                       target->count = source->count;
+
+                       options_init(source);
+               }
+       }
+}
+
+/**
+ * po_join - recombine group of mount options into a C string
+ * @options: pointer to mount options to recombine
+ * @str: handle on string to replace (input and output)
+ *
+ * Convert our mount options object back into a string that the
+ * rest of the world can use.
+ *
+ * Returns 1 if the string was successfully created; otherwise
+ * zero.  Upon return, @string contains the address of a
+ * replacement C string containing a comma-delimited list of
+ * mount options and values; or the passed-in string is freed
+ * and NULL is returned if some failure occurred.
+ */
+int po_join(struct mount_options *options, char **str)
+{
+       size_t len = 0;
+       struct mount_option *option;
+
+       if (!str || !options)
+               return PO_FAILED;
+               
+       free(*str);
+       *str = NULL;
+
+       if (options_empty(options)) {
+               *str = strdup("");
+               return *str ? PO_SUCCEEDED : PO_FAILED;
+       }
+
+       for (option = options->head; option; option = option->next) {
+               len += strlen(option->keyword);
+               if (option->value)
+                       len +=strlen(option->value) + 1;  /* equals sign */
+               if (option->next)
+                       len++;  /* comma */
+       }
+
+       len++;  /* NULL on the end */
+
+       *str = malloc(len);
+       if (!*str)
+               return PO_FAILED;
+       *str[0] = '\0';
+
+       for (option = options->head; option; option = option->next) {
+               strcat(*str, option->keyword);
+               if (option->value) {
+                       strcat(*str, "=");
+                       strcat(*str, option->value);
+               }
+               if (option->next)
+                       strcat(*str, ",");
+       }
+
+       return PO_SUCCEEDED;
+}
+
+/**
+ * po_append - concatenate an option onto a group of options
+ * @options: pointer to mount options
+ * @option: pointer to a C string containing the option to add
+ *
+ * Returns 1 if the list was successfully concatenated; otherwise
+ * zero.
+ */
+int po_append(struct mount_options *options, char *str)
+{
+       struct mount_option *option = option_create(str);
+
+       if (option) {
+               options_tail_insert(options, option);
+               return PO_SUCCEEDED;
+       }
+       return PO_FAILED;
+}
+
+/**
+ * po_contains - check for presense of an option in a group
+ * @options: pointer to mount options
+ * @keyword: pointer to a C string containing option keyword for which to search
+ *
+ * Returns 1 if the option is present in the list; otherwise zero.
+ */
+int po_contains(struct mount_options *options, char *keyword)
+{
+       struct mount_option *option;
+
+       if (options && keyword) {
+               for (option = options->head; option; option = option->next)
+                       if (strcmp(option->keyword, keyword) == 0)
+                               return PO_FOUND;
+       }
+
+       return PO_NOT_FOUND;
+}
+
+/**
+ * po_get - return the value of the rightmost instance of an option
+ * @options: pointer to mount options
+ * @keyword: pointer to a C string containing option keyword for which to search
+ *
+ * If multiple instances of the same option are present in a mount option
+ * list, the rightmost instance is always the effective one.
+ *
+ * Returns pointer to C string containing the value of the option.
+ * Returns NULL if the option isn't found, or if the option doesn't
+ * have a value.
+ */
+char *po_get(struct mount_options *options, char *keyword)
+{
+       struct mount_option *option;
+
+       if (options && keyword) {
+               for (option = options->tail; option; option = option->prev)
+                       if (strcmp(option->keyword, keyword) == 0)
+                               return option->value;
+       }
+
+       return NULL;
+}
+
+/**
+ * po_rightmost - determine the relative position of two options
+ * @options: pointer to mount options
+ * @key1: pointer to a C string containing an option keyword
+ * @key2: pointer to a C string containing another option keyword
+ *
+ * The kernel parses the mount option string from left to right.
+ * If an option is specified more than once (for example, "intr"
+ * and "nointr", the rightmost option is the last to be parsed,
+ * and it therefore takes precedence over previous similar options.
+ *
+ * This function can be used to determine which of two similar
+ * options will be the one to take effect.
+ *
+ * Returns 1 if key2 is rightmost or key1 is not present.
+ * Returns -1 if key1 is rightmost or key2 is not present.
+ * Returns 0 if neither key is present.
+ */
+int po_rightmost(struct mount_options *options, char *key1, char *key2)
+{
+       struct mount_option *option;
+
+       if (options) {
+               for (option = options->tail; option; option = option->prev) {
+                       if (key2 && strcmp(option->keyword, key2) == 0)
+                               return 1;
+                       if (key1 && strcmp(option->keyword, key1) == 0)
+                               return -1;
+               }
+       }
+
+       return PO_NOT_FOUND;
+}
+
+/**
+ * po_remove_all - remove instances of an option from a group
+ * @options: pointer to mount options
+ * @keyword: pointer to a C string containing an option keyword to remove
+ *
+ * Returns 1 if the option was found and removed; passed-in list is
+ * truncated upon return; otherwise zero.
+ */
+int po_remove_all(struct mount_options *options, char *keyword)
+{
+       struct mount_option *option, *next;
+       int found = PO_NOT_FOUND;
+
+       if (options && keyword) {
+               for (option = options->head; option; option = next) {
+                       next = option->next;
+                       if (strcmp(option->keyword, keyword) == 0) {
+                               options_delete(options, option);
+                               found = PO_FOUND;
+                       }
+               }
+       }
+
+       return found;
+}