| /* |
| * 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., |
| * 51 Franklin Street, Fifth Floor, Boston, MA 0211-1301 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. |
| */ |
| |
| |
| #ifdef HAVE_CONFIG_H |
| #include <config.h> |
| #endif |
| |
| #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 struct mount_option *option_dup(const struct mount_option *option) |
| { |
| struct mount_option *new; |
| |
| new = malloc(sizeof(*new)); |
| if (!new) |
| return NULL; |
| |
| new->next = NULL; |
| new->prev = NULL; |
| |
| new->keyword = strdup(option->keyword); |
| if (!new->keyword) |
| goto fail; |
| |
| new->value = NULL; |
| if (option->value) { |
| new->value = strdup(option->value); |
| if (!new->value) { |
| free(new->keyword); |
| goto fail; |
| } |
| } |
| |
| return new; |
| |
| fail: |
| free(new); |
| 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_dup - duplicate an existing list of options |
| * @options: pointer to mount options |
| * |
| */ |
| struct mount_options *po_dup(struct mount_options *source) |
| { |
| struct mount_options *target; |
| struct mount_option *current; |
| |
| if (!source) |
| return NULL; |
| |
| target = options_create(); |
| if (options_empty(source) || target == NULL) |
| return target; |
| |
| current = source->head; |
| while (target->count < source->count) { |
| struct mount_option *option; |
| |
| option = option_dup(current); |
| if (!option) { |
| po_destroy(target); |
| return NULL; |
| } |
| |
| options_tail_insert(target, option); |
| current = current->next; |
| } |
| |
| return target; |
| } |
| |
| /** |
| * 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. |
| * |
| * 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. |
| */ |
| po_return_t 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 |
| * |
| */ |
| po_return_t 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 |
| * |
| */ |
| po_found_t 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_get_numeric - return numeric value of rightmost instance of keyword option |
| * @options: pointer to mount options |
| * @keyword: pointer to a C string containing option keyword for which to search |
| * @value: OUT: set to the value of the keyword |
| * |
| * This is specifically for parsing keyword options that take only a numeric |
| * value. If multiple instances of the same option are present in a mount |
| * option list, the rightmost instance is always the effective one. |
| * |
| * Returns: |
| * * PO_FOUND if the keyword was found and the value is numeric; @value is |
| * set to the keyword's value |
| * * PO_NOT_FOUND if the keyword was not found |
| * * PO_BAD_VALUE if the keyword was found, but the value is not numeric |
| * |
| * These last two are separate in case the caller wants to warn about bad mount |
| * options instead of silently using a default. |
| */ |
| #ifdef HAVE_STRTOL |
| po_found_t po_get_numeric(struct mount_options *options, char *keyword, long *value) |
| { |
| char *option, *endptr; |
| long tmp; |
| |
| option = po_get(options, keyword); |
| if (option == NULL) |
| return PO_NOT_FOUND; |
| |
| errno = 0; |
| tmp = strtol(option, &endptr, 10); |
| if (errno == 0 && endptr != option) { |
| *value = tmp; |
| return PO_FOUND; |
| } |
| return PO_BAD_VALUE; |
| } |
| #else /* HAVE_STRTOL */ |
| po_found_t po_get_numeric(struct mount_options *options, char *keyword, long *value) |
| { |
| char *option; |
| |
| option = po_get(options, keyword); |
| if (option == NULL) |
| return PO_NOT_FOUND; |
| |
| *value = atoi(option); |
| return PO_FOUND; |
| } |
| #endif /* HAVE_STRTOL */ |
| |
| /** |
| * po_rightmost - determine the relative position of several options |
| * @options: pointer to mount options |
| * @keys: pointer to an array of C strings containing option keywords |
| * |
| * This function can be used to determine which of several similar |
| * options will be the one to take effect. |
| * |
| * 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 can also distinguish among multiple synonymous options, such |
| * as "proto=," "udp" and "tcp." |
| * |
| * Returns the index into @keys of the option that is rightmost. |
| * If none of the options listed in @keys is present in @options, or |
| * if @options is NULL, returns -1. |
| */ |
| int po_rightmost(struct mount_options *options, const char *keys[]) |
| { |
| struct mount_option *option; |
| int i; |
| |
| if (options) { |
| for (option = options->tail; option; option = option->prev) { |
| for (i = 0; keys[i] != NULL; i++) |
| if (strcmp(option->keyword, keys[i]) == 0) |
| return i; |
| } |
| } |
| |
| return -1; |
| } |
| |
| /** |
| * 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 |
| * |
| * Side-effect: the passed-in list is truncated on success. |
| */ |
| po_found_t 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; |
| } |