blob: f89bee4c19d201f15e6e9f2b76a0d6267cfceb5d [file] [log] [blame]
/*
* options.c -- Option parsing
*
* (c) Copyright IBM Corporation 2014, 2015.
*
* Author: Stefan Berger <stefanb@us.ibm.com>
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* Neither the names of the IBM Corporation nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "sys_dependencies.h"
#include <limits.h>
#include <stdarg.h>
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>
#include <stdio.h>
#include <pwd.h>
#include <grp.h>
#include "options.h"
static void
option_error_set(char **error, const char *format, ...)
{
va_list ap;
int ret;
va_start(ap, format);
ret = vasprintf(error, format, ap);
va_end(ap);
(void)ret;
}
/*
* option_value_add
* Add a option's value that was parsed following a template to the collection
* of option values.
* @ovs: OptionValues where to add the given value
* @optdesc: the template to use for parsing this option
* @val: the value to parses as a datatype given in optdesc
* @error: Pointer to a pointer for an error message
*
* Returns 0 on success, -1 on error.
*/
static int
option_value_add(OptionValues *ovs, const OptionDesc optdesc, const char *val,
char **error)
{
int ret = 0;
char *endptr = NULL;
long int li;
long unsigned int lui;
struct passwd *passwd;
struct group *group;
size_t idx = ovs->n_options;
void *tmp;
tmp = realloc(ovs->options, (idx + 1) * sizeof(*ovs->options));
if (!tmp) {
free(ovs->options);
option_error_set(error, "Out of memory");
return -1;
}
ovs->options = tmp;
ovs->n_options = idx + 1;
ovs->options[idx].type = optdesc.type;
ovs->options[idx].name = optdesc.name;
switch (optdesc.type) {
case OPT_TYPE_STRING:
ovs->options[idx].u.string = strdup(val);
if (!ovs->options[idx].u.string) {
option_error_set(error, "Out of memory");
return -1;
}
break;
case OPT_TYPE_INT:
li = strtol(val, &endptr, 10);
if (*endptr != '\0') {
option_error_set(error, "invalid number '%s'", val);
return -1;
}
if (li < INT_MIN || li > INT_MAX) {
option_error_set(error, "number %li outside valid range", li);
}
ovs->options[idx].u.integer = li;
break;
case OPT_TYPE_UINT:
lui = strtol(val, &endptr, 10);
if (*endptr != '\0') {
option_error_set(error, "invalid number '%s'", val);
return -1;
}
if (lui > UINT_MAX) {
option_error_set(error, "number %li outside valid range", lui);
}
ovs->options[idx].u.uinteger = lui;
break;
case OPT_TYPE_BOOLEAN:
if (!strcasecmp(val, "true") || !strcasecmp(val, "1")) {
ovs->options[idx].u.boolean = true;
} else {
ovs->options[idx].u.boolean = false;
}
break;
case OPT_TYPE_MODE_T:
lui = strtol(val, &endptr, 8);
if (*endptr != '\0') {
option_error_set(error, "invalid mode type '%s'", val);
return -1;
}
if (lui > 0777) {
option_error_set(error, "mode %s is invalid", val);
return -1;
}
ovs->options[idx].u.mode = (mode_t)lui;
break;
case OPT_TYPE_UID_T:
lui = strtol(val, &endptr, 10);
if (*endptr == '\0') {
if (lui > UINT_MAX) {
option_error_set(error, "uid %s outside valid range", val);
return -1;
}
} else {
/* try as a string */
passwd = getpwnam(val);
if (!passwd) {
option_error_set(error, "User '%s' does not exist.\n", val);
return -1;
}
lui = passwd->pw_uid;
}
ovs->options[idx].u.uid = (uid_t)lui;
break;
case OPT_TYPE_GID_T:
lui = strtol(val, &endptr, 10);
if (*endptr == '\0') {
if (lui > UINT_MAX) {
option_error_set(error, "gid %s outside valid range", val);
return -1;
}
} else {
/* try as a string */
group = getgrnam(val);
if (!group) {
option_error_set(error, "Group '%s' does not exist.\n", val);
return -1;
}
lui = group->gr_gid;
}
ovs->options[idx].u.gid = (uid_t)lui;
break;
}
return ret;
}
/*
* options_parse:
* Parse the string of options following the template; return the
* parsed Options or an error string.
* @opts: string containing the comma separated options to parse
* @optdesc: template to follow when parsing individual options
* @error: Pointer to a pointer for holding an error message
*
* Returns the parse options, types, and values in OptionValues.
*/
OptionValues *
options_parse(char *opts, const OptionDesc optdesc[], char **error)
{
char *saveptr;
char *tok;
int i;
OptionValues *ovs = calloc(sizeof(OptionValues), 1);
bool found;
char *opts_bak;
if (!ovs) {
option_error_set(error, "Out of memory.");
return NULL;
}
opts_bak = strdup(opts);
if (!opts_bak) {
option_error_set(error, "Out of memory.");
goto error;
}
saveptr = opts_bak; /* make coverity happy */
tok = strtok_r(opts_bak, ",", &saveptr);
while (tok) {
size_t toklen = strlen(tok);
found = false;
i = 0;
while (optdesc[i].name) {
size_t len = strlen(optdesc[i].name);
if (toklen > len + 1 && tok[len] == '=' &&
!strncmp(optdesc[i].name, tok, len)) {
const char *v = &tok[len + 1];
if (option_value_add(ovs, optdesc[i], v, error) < 0)
goto error;
found = true;
break;
} else if (!strcmp(optdesc[i].name, tok)) {
if (option_value_add(ovs, optdesc[i], "true", error) < 0)
goto error;
found = true;
break;
}
i++;
}
if (!found) {
option_error_set(error, "Unknown option '%s'", tok);
goto error;
}
tok = strtok_r(NULL, ",", &saveptr);
}
free(opts_bak);
return ovs;
error:
free(opts_bak);
option_values_free(ovs);
return NULL;
}
/*
* option_values_free
* Free the option values
* @ovs: OptionValues structure to free; may be NULL
*/
void
option_values_free(OptionValues *ovs)
{
size_t i;
if (!ovs)
return;
for (i = 0; i < ovs->n_options; i++) {
switch (ovs->options[i].type) {
case OPT_TYPE_STRING:
free(ovs->options[i].u.string);
break;
case OPT_TYPE_INT:
case OPT_TYPE_UINT:
case OPT_TYPE_BOOLEAN:
case OPT_TYPE_MODE_T:
case OPT_TYPE_UID_T:
case OPT_TYPE_GID_T:
break;
}
}
free(ovs->options);
free(ovs);
}
/*
* Given the name of a string option, return the value it received when it
* was parsed.
* @ovs: The OptionValues
* @name: the name of the option
* @def: the default value
*
* Returns the parsed value or the default value if none was parsed;
* If the value is of different type than a string, NULL is returned.
*/
const char *
option_get_string(OptionValues *ovs, const char *name, const char *def)
{
size_t i;
for (i = 0; i < ovs->n_options; i++) {
if (!strcmp(name, ovs->options[i].name)) {
if (ovs->options[i].type == OPT_TYPE_STRING)
return ovs->options[i].u.string;
return NULL;
}
}
return def;
}
/*
* Given the name of an int option, return the value it received when it
* was parsed.
* @ovs: The OptionValues
* @name: the name of the option
* @def: the default value
*
* Returns the parsed value or the default value if none was parsed
* If the value is of different type than an integer, -1 is returned.
*/
int
option_get_int(OptionValues *ovs, const char *name, int def)
{
size_t i;
for (i = 0; i < ovs->n_options; i++) {
if (!strcmp(name, ovs->options[i].name)) {
if (ovs->options[i].type == OPT_TYPE_INT)
return ovs->options[i].u.integer;
return -1;
}
}
return def;
}
/*
* Given the name of an uint option, return the value it received when it
* was parsed.
* @ovs: The OptionValues
* @name: the name of the option
* @def: the default value
*
* Returns the parsed value or the default value if none was parsed
* If the value is of different type than an integer, ~0 is returned.
*/
unsigned int
option_get_uint(OptionValues *ovs, const char *name, unsigned int def)
{
size_t i;
for (i = 0; i < ovs->n_options; i++) {
if (!strcmp(name, ovs->options[i].name)) {
if (ovs->options[i].type == OPT_TYPE_UINT)
return ovs->options[i].u.uinteger;
return ~0;
}
}
return def;
}
/*
* Given the name of a boolean option, return the value it received when it
* was parsed.
* @ovs: The OptionValues
* @name: the name of the option
* @def: the default value
*
* Returns the parsed value or the default value if none was parsed
* If the value is of different type than a boolean, false is returned.
*/
bool
option_get_bool(OptionValues *ovs, const char *name, bool def)
{
size_t i;
for (i = 0; i < ovs->n_options; i++) {
if (!strcmp(name, ovs->options[i].name)) {
if (ovs->options[i].type == OPT_TYPE_BOOLEAN)
return ovs->options[i].u.boolean;
return false;
}
}
return def;
}
/*
* Given the name of a mode_t (chmod) option, return the value it received when it
* was parsed.
* @ovs: The OptionValues
* @name: the name of the option
* @def: the default value
*
* Returns the parsed value or the default value if none was parsed
* If the value is of different type than a mode_t, ~0 is returned.
*/
mode_t
option_get_mode_t(OptionValues *ovs, const char *name, mode_t def)
{
size_t i;
for (i = 0; i < ovs->n_options; i++) {
if (!strcmp(name, ovs->options[i].name)) {
if (ovs->options[i].type == OPT_TYPE_MODE_T)
return ovs->options[i].u.mode;
return ~0;
}
}
return def;
}
/*
* Given the name of a uid_t (chown) option, return the value it received when it
* was parsed.
* @ovs: The OptionValues
* @name: the name of the option
* @def: the default value
*
* Returns the parsed value or the default value if none was parsed
* If the value is of different type than a uid_t, -1 is returned.
*/
uid_t
option_get_uid_t(OptionValues *ovs, const char *name, uid_t def)
{
size_t i;
for (i = 0; i < ovs->n_options; i++) {
if (!strcmp(name, ovs->options[i].name)) {
if (ovs->options[i].type == OPT_TYPE_UID_T)
return ovs->options[i].u.uid;
return -1;
}
}
return def;
}
/*
* Given the name of a gid_t (chown) option, return the value it received when it
* was parsed.
* @ovs: The OptionValues
* @name: the name of the option
* @def: the default value
*
* Returns the parsed value or the default value if none was parsed
* If the value is of different type than a uid_t, -1 is returned.
*/
gid_t
option_get_gid_t(OptionValues *ovs, const char *name, gid_t def)
{
size_t i;
for (i = 0; i < ovs->n_options; i++) {
if (!strcmp(name, ovs->options[i].name)) {
if (ovs->options[i].type == OPT_TYPE_GID_T)
return ovs->options[i].u.gid;
return -1;
}
}
return def;
}