/* * parse_opt.c -- mount option string parsing helpers * * Copyright (C) 2007 Oracle. All rights reserved. * Copyright (C) 2007 Chuck Lever * * 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 #endif #include #include #include #include #include #include #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; }