diff options
| author | Dennis Camera <skonfig@dtnr.ch> | 2022-08-02 20:30:29 +0200 |
|---|---|---|
| committer | Dennis Camera <skonfig@dtnr.ch> | 2022-08-03 17:57:59 +0200 |
| commit | 1fc6697f4de4290575202661b664ca617df9b8e1 (patch) | |
| tree | 1a0edeb8e5c4288c7257bd3c48265d332cd04607 | |
| parent | 50c956a7c79ebdec158d761e6065a31e0733ff5a (diff) | |
| download | skonfig-c-1fc6697f4de4290575202661b664ca617df9b8e1.tar.gz skonfig-c-1fc6697f4de4290575202661b664ca617df9b8e1.zip | |
[skonfig.c] Implement option parser
| -rw-r--r-- | Makefile.am | 2 | ||||
| -rw-r--r-- | configure.ac | 15 | ||||
| -rw-r--r-- | src/skonfig.c | 238 |
3 files changed, 245 insertions, 10 deletions
diff --git a/Makefile.am b/Makefile.am index 2a9c70c..b53241a 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,4 +1,4 @@ -AM_CFLAGS = -std=c99 -D_FORTIFY_SOURCE=2 -D_POSIX_C_SOURCE=1 +AM_CFLAGS = -std=c99 -D_FORTIFY_SOURCE=2 -D_POSIX_C_SOURCE=200112L AM_CFLAGS += -Wall if DEBUG diff --git a/configure.ac b/configure.ac index 0d29814..a0b8fdd 100644 --- a/configure.ac +++ b/configure.ac @@ -11,18 +11,27 @@ AC_CONFIG_FILES([Makefile]) AM_INIT_AUTOMAKE([subdir-objects]) # Checks for programs. -AC_PROG_CC - AC_LANG([C]) +AC_PROG_CC +# autoconf 2.70 obsoletes AC_PROG_CC_C99 and includes it in AC_PROG_CC +m4_version_prereq([2.70],[],[AC_PROG_CC_C99]) + # Checks for libraries. # Checks for header files. -AC_CHECK_HEADERS([stdio.h]) +AC_HEADER_STDC +AC_HEADER_STDBOOL +AC_CHECK_HEADERS([sys/stat.h]) +AC_HEADER_STAT # Checks for typedefs, structures, and compiler characteristics. +AC_C_CONST +AC_C_RESTRICT +AC_TYPE_UINT8_T # Checks for library functions. +AC_CHECK_FUNCS_ONCE([getenv getpwuid stat strncpy strncat]) # Add debug support AC_ARG_ENABLE( diff --git a/src/skonfig.c b/src/skonfig.c index 09e2a40..b5ab37b 100644 --- a/src/skonfig.c +++ b/src/skonfig.c @@ -1,16 +1,242 @@ +#include "../config.h" + #include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <limits.h> +#include <pwd.h> +#include <sys/stat.h> -#ifdef HAVE_CONFIG_H -#include "../config.h" -#else -#error no config.h +#if HAVE_STDBOOL_H +#include <stdbool.h> #endif +#if HAVE_STDINT_H +#include <stdint.h> +#endif + +struct sk_options { + const char *conf_root; + bool dry_run; + const char *init_manifest; + unsigned int jobs; + uint8_t verbosity; + const char *target_host; +}; + +static const char *EXECNAME = PACKAGE; /* default */ + +/* utils */ + +#define arraylen(a) (sizeof(a)/sizeof(*a)) + +static bool is_dir(const char * restrict pathname) { + /** + * Check if a given pathname is a directory. + * + * @returns true if the given pathname exists and is a directory, false + * otherwise. + */ +#ifdef STAT_MACROS_BROKEN +#error stat() macros are broken on this system. Cannot compile is_dir(). +#endif + struct stat s; + return (0 == stat(pathname, &s) && S_ISDIR(s.st_mode)); +} + +static const char *myhome() { + /** + * Returns a pointer to a string containing the current user’s home + * directory. + * The returned pointer points to a static location (don’t free() it) and + * may be changed on future invocations. + * + * This function makes sure that the returned string is null-terminated and + * truncates the result to fit within PATH_MAX if necessary. + * + * @returns home dir + */ + static char home[PATH_MAX+1]; + + home[0] = '\0'; + if (!getenv("HOME")) { + (void)strncpy(home, getenv("HOME"), PATH_MAX); + } else { /* HOME unset or empty */ + (void)strncpy(home, getpwuid(getuid())->pw_dir, PATH_MAX); + } + home[PATH_MAX] = '\0'; + + return (const char *)&home; +} + + +/* help */ + +void sk_print_usage(FILE *outstream) { + fprintf( + outstream, + "usage: %s [-h] [-n] [-v] [-V] [-j JOBS] [-i MANIFEST] target_host\n", + EXECNAME); +} + +void sk_print_version(FILE *outstream) { + fprintf(outstream, "%s %s\n", PACKAGE, VERSION); +} + +void sk_print_help(FILE *outstream) { + /* usage */ + sk_print_usage(outstream); + fprintf(outstream, "\n"); -#include "config.h" + /* version */ + sk_print_version(outstream); + fprintf(outstream, "\n"); + + /* positional arguments */ + fprintf( + outstream, + "positional arguments:\n" + " target_host host to configure\n" + "\n"); + + /* optional arguments */ + fprintf( + outstream, + "optional arguments:\n" + " -i path to the manifest to be used or '-' to read from stdin\n" + " -j number of jobs to execute objects in parallel\n" + " -h show this help and exit\n" + " -n dry run: do not execute generated code\n" + " -v increase verbosity (repeat for more verbosity)\n" + " -V output version information and exit\n" + "\n"); +} + + +/* argument parsing */ + +static int sk_get_global_opts( + int argc, char *argv[], struct sk_options *options) { + /** + * Parse skonfig options. + * Will update the `options` struct. + * + * Options defined to print some information and exit the program will + * already do so in this function, in which case the function will never + * return. + * + * @returns true if no errors occurred during parsing, false otherwise. + */ + + extern char *optarg; + extern int opterr, optind, optopt; + opterr = 0; + + int c; + unsigned int posind = 1, errors = 0; + bool eoo = false; /* end of options */ + + while (optind < argc) { + while (!eoo && (c = getopt(argc, argv, ":i:j:hnvV")) != -1) { + switch (c) { + case 'i': + options->init_manifest = optarg; + break; + case 'j': { + unsigned int j; + if ('-' == optarg[0] || 1 > (j = atoi(optarg))) { + fprintf(stderr, "-j must be at least 1. Assuming -j 1.\n"); + j = 1; + } + options->jobs = j; + break; + } + case 'h': + sk_print_help(stdout); + exit(EXIT_SUCCESS); + case 'n': + options->dry_run = true; + break; + case 'v': + options->verbosity++; + break; + case 'V': + sk_print_version(stdout); + exit(EXIT_SUCCESS); + case ':': + fprintf(stderr, "%s: option requires an argument -- %c\n", EXECNAME, optopt); + errors++; + break; + case '?': + default: + fprintf(stderr, "%s: illegal option -- %c\n", EXECNAME, optopt); + errors++; + break; + } + } + + if (0 == strcmp("--", argv[optind-1])) { + eoo = true; + } + + /* handle positional argument */ + switch (posind) { + case 1: + /* target host */ + options->target_host = argv[optind]; /* XXX: strcpy() needed? */ + break; + default: + fprintf(stderr, "%s: unrecognized positional argument: %s\n", + EXECNAME, argv[optind]); + errors++; + break; + } + + posind++; optind++; + }; + + if (errors) { + sk_print_usage(stderr); + return false; + } + return true; +} int main(int argc, char *argv[]) { + EXECNAME = argv[0]; + #if DEBUG printf("Debugging enabled.\n"); #endif - return 0; + + /* determine absolute path to ~/.skonfig */ + char default_conf_root[PATH_MAX+1] = ""; + (void)strncpy(default_conf_root, myhome(), arraylen(default_conf_root)-1); + strncat(default_conf_root, "/.skonfig", + (arraylen(default_conf_root) - strlen(default_conf_root))); + + struct sk_options options = { + /* defaults */ + .conf_root = (const char *)default_conf_root, + .dry_run = false, + .init_manifest = "manifest/init", + .jobs = 1, + .verbosity = 0, + .target_host = NULL + }; + + sk_get_global_opts(argc, argv, &options); + + /* check if conf_root exists */ + if (!is_dir(options.conf_root)) { + fprintf(stderr, "Cannot find configuration directory.\n"); + perror(options.conf_root); /* is_dir sets errno */ + /* TODO: Add link to Getting Started guide */ + return EXIT_FAILURE; + } + + printf("conf dir: %s\n", options.conf_root); + printf("target host: %s\n", options.target_host); + + return EXIT_SUCCESS; } |
