summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDennis Camera <skonfig@dtnr.ch>2022-08-02 20:30:29 +0200
committerDennis Camera <skonfig@dtnr.ch>2022-08-03 17:57:59 +0200
commit1fc6697f4de4290575202661b664ca617df9b8e1 (patch)
tree1a0edeb8e5c4288c7257bd3c48265d332cd04607
parent50c956a7c79ebdec158d761e6065a31e0733ff5a (diff)
downloadskonfig-c-1fc6697f4de4290575202661b664ca617df9b8e1.tar.gz
skonfig-c-1fc6697f4de4290575202661b664ca617df9b8e1.zip
[skonfig.c] Implement option parser
-rw-r--r--Makefile.am2
-rw-r--r--configure.ac15
-rw-r--r--src/skonfig.c238
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;
}