blob: f0918f7fe264063caa59c23ad438bff2b2720693 [file] [log] [blame]
/*
* 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.
*/
#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;
unsigned 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;
}