diff --git a/libc/Makefile b/libc/Makefile index c7843348..2a702cca 100644 --- a/libc/Makefile +++ b/libc/Makefile @@ -220,6 +220,8 @@ fsmarshall/fsm_listen.o \ fsmarshall/fsm_mkserver.o \ fsmarshall/fsm_recv.o \ fsmarshall/fsm_send.o \ +getopt/getopt_long.o \ +getopt/getopt.o \ grp/grent.o \ init/init.o \ libgen/basename.o \ diff --git a/libc/getopt/getopt.cpp b/libc/getopt/getopt.cpp new file mode 100644 index 00000000..75776e79 --- /dev/null +++ b/libc/getopt/getopt.cpp @@ -0,0 +1,31 @@ +/******************************************************************************* + + Copyright(C) Jonas 'Sortie' Termansen 2013. + + This file is part of the Sortix C Library. + + The Sortix C Library is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or (at your + option) any later version. + + The Sortix C Library 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 Lesser General Public + License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with the Sortix C Library. If not, see . + + getopt/getopt.cpp + Command-line parsing utility function. + +*******************************************************************************/ + +#include +#include + +extern "C" int getopt(int argc, char* const* argv, const char* shortopts) +{ + return getopt_long(argc, argv, shortopts, NULL, NULL); +} diff --git a/libc/getopt/getopt_long.cpp b/libc/getopt/getopt_long.cpp new file mode 100644 index 00000000..33fdefe5 --- /dev/null +++ b/libc/getopt/getopt_long.cpp @@ -0,0 +1,255 @@ +/******************************************************************************* + + Copyright(C) Jonas 'Sortie' Termansen 2013. + + This file is part of the Sortix C Library. + + The Sortix C Library is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or (at your + option) any later version. + + The Sortix C Library 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 Lesser General Public + License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with the Sortix C Library. If not, see . + + getopt/getopt_long.cpp + Command-line parsing utility function. + +*******************************************************************************/ + +#include +#include +#include +#include +#include + +extern "C" { char* optarg = NULL; } +extern "C" { int opterr = 1; } +extern "C" { int optind = 1; } +extern "C" { int optopt = 0; } + +static char* const* optcurargv; +static const char* optcurarg; +static int optcurind; +static size_t optcurpos; + +static bool optcurvalid = false; + +static bool is_supported_option_declaration(const char* shortopts) +{ + // Optional parameters to options only make sense after a option character. + if ( shortopts[0] == ':' && shortopts[1] == ':' ) + return false; + + for ( size_t i = 0; shortopts[i]; i++ ) + { + if ( shortopts[i] == '-' || shortopts[i] == '+' || shortopts[i] == ';' ) + return false; + + // We support a: and a::, but a::: doesn't make sense. + if ( shortopts[i] == ':' && + shortopts[i+1] == ':' && + shortopts[i+2] == ':' ) + return false; + + // We don't support that -W foo is the same as --foo. + if ( shortopts[i] == 'W' && shortopts[i+1] == ';' ) + return false; + } + return true; +} + +// TODO: It would appear that it is allowed to abbreviate options if the +// abbreviation isn't ambiguous. (WTF) +static +const struct option* find_long_option(char* arg, + const struct option* longopts, + int* option_index, + char** option_arg) +{ + char* argname = arg + 2; + size_t argname_length = strcspn(arg, "="); + + for ( int i = 0; longopts && longopts[i].name; i++ ) + { + if ( longopts[i].has_arg == no_argument && + argname[argname_length] == '=' ) + continue; + if ( strncmp(argname, longopts[i].name, argname_length) != 0 ) + continue; + if ( longopts[i].name[argname_length] != '\0' ) + continue; + return *option_arg = (argname[argname_length] == '=' ? + argname + argname_length + 1 : + NULL), + *option_index = i, + &longopts[i]; + } + return NULL; +} + +extern "C" +int getopt_long(int argc, char* const* argv, const char* shortopts, + const struct option* longopts, int* longindex) +{ + assert(shortopts); + assert(0 <= optind); + + // Verify that the input string was compatible. + if ( !is_supported_option_declaration(shortopts) ) + return errno = EINVAL, -1; + + // The caller will handle missing arguments if the short options string + // start with a colon character. + bool caller_handles_missing_argument = false; + if ( shortopts[0] == ':' ) + shortopts++, + caller_handles_missing_argument = false; + + // Check if we have finished processing the command line. + if ( argc <= optind ) + return optcurvalid = false, -1; + + char* arg = argv[optind]; + + // Stop if we encountered a NULL pointer. + if ( !arg ) + return optcurvalid = false, -1; + + // Stop if we encounteed an argument that didn't start with -. + if ( arg[0] != '-' ) + return optcurvalid = false, -1; + + // Stop if we encounted the string "-" as an argument. + if ( arg[1] == '\0' ) + return optcurvalid = false, -1; + + // Stop and skip the argument if we encountered the string "--". + if ( arg[1] == '-' && arg[2] == '\0' ) + return optcurvalid = false, optind++, -1; + + // Check if we encountered a long option. + if ( arg[1] == '-' && arg[2] != '\0' ) + { + // Locate the long option. + int option_index; + char* option_arg; + const struct option* option = + find_long_option(arg, longopts, &option_index, &option_arg); + + // Increment so we don't process the next argument on the next call. + optind++; + + // Verify the long option is allowed. + if ( !option ) + return '?'; + + // Check whether the next argument is the parameter to this option. + if ( !option_arg && option->has_arg != no_argument ) + { + if ( optind + 1 < argc && argv[optind] && + (option->has_arg == required_argument || argv[optind][0] != '-') ) + option_arg = argv[optind++]; + } + + // Return an error if a required option parameter was missing. + if ( !option_arg && option->has_arg == required_argument ) + { + if ( caller_handles_missing_argument ) + return *longindex = option_index, ':'; + if ( opterr ) + error(0, 0, "option requires an argument `--%s'", option->name); + return '?'; + } + + return optarg = option_arg, + *longindex = option_index, + option->flag ? (*option->flag = option->val, 0) : option->val; + } + + // Unfortunately, there is no standardized optpos global variable, and + // programs are supposed to be able to restart the parsing by setting optind + // to 1. We need to keep track of the current position in the current + // argument, but it would have to be invalidated whenever we restart the + // parsing. We keep track of what the parsing input is supposed to be so we + // can easily invalidate the current position in the current argument + // whenever something fishy is going on. + if ( !optcurvalid || optcurargv != argv || optcurarg != arg || optcurind != optind ) + { + optcurargv = argv; + optcurarg = arg; + optcurind = optind; + optcurpos = 1; + optcurvalid = true; + } + + int short_option = (unsigned char) arg[optcurpos++]; + + // The character : can't be a option character. + if ( short_option == ':' ) + short_option = '?'; + + // Verify the short option is allowed. + char* opt_decl = strchr(shortopts, short_option); + if ( !opt_decl ) + { + optopt = short_option; + short_option = '?'; + } + + // Check whether the short option requires an parameter. + if ( opt_decl[1] == ':' ) + { + // This argument cannot contain more short options after this. + optcurvalid = false; + + // A double :: means optional parameter. + bool required = opt_decl[2] != ':'; + + // The rest of the argument is the parameter if non-empty. + if ( arg[optcurpos] ) + { + optarg = arg + optcurpos; + optind += 1; + } + + // Otherwise, the next element (if any) is the parameter. + else if ( optind + 1 < argc && argv[optind+1] && + (required || argv[optind+1][0] != '-') ) + { + optarg = argv[optind+1]; + optind += 2; + } + + // It's okay to have a missing parameter if it was optional. + else if ( !required ) + { + optarg = NULL; + optind += 1; + } + + // Return an error if a required option parameter was missing. + else + { + if ( caller_handles_missing_argument ) + return optopt = short_option, ':'; + if ( opterr ) + error(0, 0, "option requires an argument -- '%c'", short_option); + return optopt = short_option, '?'; + } + } + + // If we reached the last option, use the next index next time. + else if ( !arg[optcurpos] ) + { + optind++; + optcurvalid = false; + } + + return short_option; +} diff --git a/libc/include/getopt.h b/libc/include/getopt.h new file mode 100644 index 00000000..ee0047f8 --- /dev/null +++ b/libc/include/getopt.h @@ -0,0 +1,59 @@ +/******************************************************************************* + + Copyright(C) Jonas 'Sortie' Termansen 2013. + + This file is part of the Sortix C Library. + + The Sortix C Library is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or (at your + option) any later version. + + The Sortix C Library 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 Lesser General Public + License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with the Sortix C Library. If not, see . + + getopt.h + Command-line parsing facility. + +*******************************************************************************/ + +#ifndef INCLUDE_GETOPT_H +#define INCLUDE_GETOPT_H + +#include + +__BEGIN_DECLS + +/* These declarations are repeated in . */ +#ifndef __getopt_unistd_shared_declared +#define __getopt_unistd_shared_declared +extern char* optarg; +extern int opterr; +extern int optind; +extern int optopt; + +int getopt(int, char* const*, const char*); +#endif + +struct option +{ + const char* name; + int has_arg; + int* flag; + int val; +}; + +#define no_argument 0 +#define required_argument 1 +#define optional_argument 2 + +int getopt_long(int, char* const*, const char*, const struct option*, int*); + +__END_DECLS + +#endif diff --git a/libc/include/unistd.h b/libc/include/unistd.h index a695b262..00f93bc7 100644 --- a/libc/include/unistd.h +++ b/libc/include/unistd.h @@ -271,7 +271,7 @@ int fexecve(int, char* const [], char* const []); long fpathconf(int, int); int getgroups(int, gid_t []); long gethostid(void); -int getopt(int, char* const [], const char*); + pid_t getpgrp(void); pid_t getsid(pid_t); int lockf(int, int, off_t); @@ -289,9 +289,6 @@ int ttyname_r(int, char*, size_t); #if __POSIX_OBSOLETE <= 200801 pid_t setpgrp(void); #endif - -extern char* optarg; -extern int opterr, optind, optopt; #endif int access(const char*, int); @@ -384,6 +381,20 @@ size_t writeleast(int fd, const void* buf, size_t least, size_t max); void* sbrk(__intptr_t increment); #endif +/* For compatibility with POSIX, declare getopt(3) here. */ +#if !defined(_SORTIX_SOURCE) +/* These declarations are repeated in . */ +#ifndef __getopt_unistd_shared_declared +#define __getopt_unistd_shared_declared +extern char* optarg; +extern int opterr; +extern int optind; +extern int optopt; + +int getopt(int, char* const*, const char*); +#endif +#endif + __END_DECLS #endif