:: commit 47913c79fb3f03b8fe55753e6c81e4aade67eec1

Kamila Szewczyk <27734421+kspalaiologos@users.noreply.github.com> — 2022-07-11 21:18

parents: b750bc41e6

Rework the argument parsing to use `getopt`. (#36)

* use getopt

* autoconf fluff

* increase the default block size.

* supply our own getopt (weird GNU behaviours on decrementing optind?)
diff --git a/Makefile.am b/Makefile.am
index 21c63ea..2de8237 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -73,6 +73,6 @@ BZIP3 := bzip3$(EXEEXT)
 
 roundtrip: $(BZIP3)
 	rm -f LICENSE2
-	./$(BZIP3) -e -b 6 LICENSE
+	./$(BZIP3) -feb 6 LICENSE
 	./$(BZIP3) -d LICENSE.bz3 LICENSE2
 	cmp LICENSE LICENSE2
diff --git a/src/main.c b/src/main.c
index adbf3cf..51f83cd 100644
--- a/src/main.c
+++ b/src/main.c
@@ -30,6 +30,63 @@
     #include <io.h>
 #endif
 
+/* Use our own getopt implementation. */
+static int opind;
+static int operr = 1;
+static int opopt;
+static char *oparg;
+
+static int getopt_impl(int argc, char * const argv[], const char *optstring) {
+    static int optpos = 1;
+    const char *arg;
+
+    /* Reset? */
+    if (opind == 0) {
+        opind = !!argc;
+        optpos = 1;
+    }
+
+    arg = argv[opind];
+    if (arg && strcmp(arg, "--") == 0) {
+        opind++;
+        return -1;
+    } else if (!arg || arg[0] != '-' || !isalnum(arg[1])) {
+        return -1;
+    } else {
+        const char *opt = strchr(optstring, arg[optpos]);
+        opopt = arg[optpos];
+        if (!opt) {
+            if (operr && *optstring != ':')
+                fprintf(stderr, "%s: illegal option: %c\n", argv[0], opopt);
+            return '?';
+        } else if (opt[1] == ':') {
+            if (arg[optpos + 1]) {
+                oparg = (char *)arg + optpos + 1;
+                opind++;
+                optpos = 1;
+                return opopt;
+            } else if (argv[opind + 1]) {
+                oparg = (char *)argv[opind + 1];
+                opind += 2;
+                optpos = 1;
+                return opopt;
+            } else {
+                if (operr && *optstring != ':')
+                    fprintf(stderr, 
+                            "%s: option requires an argument: %c\n", 
+                            argv[0], opopt);
+                return *optstring == ':' ? ':' : '?';
+            }
+        } else {
+            if (!arg[++optpos]) {
+                opind++;
+                optpos = 1;
+            }
+            return opopt;
+        }
+    }
+}
+
 #include "common.h"
 #include "libbz3.h"
 
@@ -38,13 +95,33 @@
 #define MODE_ENCODE 1
 #define MODE_TEST 2
 
-int is_dir(const char * path) {
+static void help() {
+    fprintf(stderr, "bzip3 version %s\n", VERSION);
+    fprintf(stderr, "- A better and stronger spiritual successor to bzip2.\n");
+    fprintf(stderr, "Copyright (C) by Kamila Szewczyk, 2022. Licensed under the terms of LGPLv3.\n");
+    fprintf(stderr, "Usage: bzip3 [-e/-d/-t/-c/-h/-v] [-b block_size] [-j jobs] files...\n");
+    fprintf(stderr, "Operations:\n");
+    fprintf(stderr, "  -e: encode\n");
+    fprintf(stderr, "  -d: decode\n");
+    fprintf(stderr, "  -t: test\n");
+    fprintf(stderr, "  -h: help\n");
+    fprintf(stderr, "  -f: force overwrite output if it already exists\n");
+    fprintf(stderr, "  -v: version\n");
+    fprintf(stderr, "Extra flags:\n");
+    fprintf(stderr, "  -c: force reading/writing from standard streams\n");
+    fprintf(stderr, "  -b N: set block size in MiB\n");
+#ifdef PTHREAD
+    fprintf(stderr, "  -j N: set the amount of parallel threads\n");
+#endif
+}
+
+static int is_dir(const char * path) {
     struct stat sb;
     if (stat(path, &sb) == 0 && S_ISDIR(sb.st_mode)) return 1;
     return 0;
 }
 
-int is_numeric(const char * str) {
+static int is_numeric(const char * str) {
     for (; *str; str++)
         if (!isdigit(*str)) return 0;
     return 1;
@@ -52,7 +129,6 @@ int is_numeric(const char * str) {
 
 int main(int argc, char * argv[]) {
     int mode = MODE_UNSPECIFIED;
-    int args_status = 1;
 
     // input and output file names
     char *input = NULL, *output = NULL;
@@ -64,99 +140,83 @@ int main(int argc, char * argv[]) {
     int double_dash = 0;
 
     // the block size
-    u32 block_size = MiB(8);
+    u32 block_size = MiB(16);
 
-    for (int i = 1; i < argc; i++) {
-        if (argv[i][0] == '-' && !double_dash) {
-            if (strlen(argv[i]) < 2) {
-                fprintf(stderr, "Invalid flag '%s'.\n", argv[i]);
-                return 1;
-            } else if (argv[i][1] == 'e') {
-                mode = MODE_ENCODE;
-            } else if (argv[i][1] == 'd') {
-                mode = MODE_DECODE;
-            } else if (argv[i][1] == 't') {
-                mode = MODE_TEST;
-            } else if (argv[i][1] == 'f') {
-                force = 1;
-            } else if (argv[i][1] == 'b') {
-                if (i + 1 >= argc) {
-                    fprintf(stderr, "Error: -b requires an argument.\n");
-                    return 1;
-                }
-
-                if (!is_numeric(argv[i + 1])) {
-                    fprintf(stderr, "Error: -b requires an integer argument.\n");
-                    return 1;
-                }
-
-                block_size = MiB(atoi(argv[i + 1]));
-                i++;
-            } else if (argv[i][1] == 'c') {
-                force_stdstreams = 1;
 #ifdef PTHREAD
-            } else if (argv[i][1] == 'j') {
-                if (i + 1 >= argc) {
-                    fprintf(stderr, "Error: -j requires an argument.\n");
-                    return 1;
-                }
-
-                if (!is_numeric(argv[i + 1])) {
-                    fprintf(stderr, "Error: -j requires an integer argument.\n");
-                    return 1;
-                }
+    const char * getopt_args = "edtfchvb:j:";
+#else
+    const char * getopt_args = "edtfchvb:";
+#endif
 
-                workers = atoi(argv[i + 1]);
-                i++;
+    operr = 1; // Should be set by default, just make sure.
+    while (opind < argc) {
+        int opt;
+        if((opt = getopt_impl(argc, argv, getopt_args)) != -1) {
+            // Normal dash argument.
+            switch(opt) {
+                case 'e':
+                    mode = MODE_ENCODE;
+                    break;
+                case 'd':
+                    mode = MODE_DECODE;
+                    break;
+                case 't':
+                    mode = MODE_TEST;
+                    break;
+                case 'f':
+                    force = 1;
+                    break;
+                case 'c':
+                    force_stdstreams = 1;
+                    break;
+                case 'v':
+                    fprintf(stderr, "bzip3 %s\n", VERSION);
+                    return 0;
+                case 'h':
+                    help();
+                    return 0;
+                case 'b':
+                    if (is_numeric(oparg)) {
+                        block_size = MiB(atoi(oparg));
+                    } else {
+                        fprintf(stderr, "Invalid block size: %s\n", oparg);
+                        return 1;
+                    }
+                    break;
+#ifdef PTHREAD
+                case 'j':
+                    if (is_numeric(oparg)) {
+                        workers = atoi(oparg);
+                    } else {
+                        fprintf(stderr, "Invalid number of workers: %s\n", oparg);
+                        return 1;
+                    }
+                    break;
 #endif
-            } else if (argv[i][1] == 'h') {
-                mode = MODE_UNSPECIFIED;
-                args_status = 0;
-            } else if (argv[i][1] == 'v') {
-                fprintf(stderr, "bzip3 %s\n", VERSION);
-                return 0;
-            } else if (argv[i][1] == '-') {
-                double_dash = 1;
-            } else {
-                fprintf(stderr, "Invalid flag '%s'.\n", argv[i]);
-                return 1;
             }
         } else {
+            // Positional argument. Likely a file name.
+            char * arg = argv[opind++];
+
             if (bz3_file != NULL && regular_file != NULL) {
                 fprintf(stderr, "Error: too many files specified.\n");
                 return 1;
             }
 
-            int has_bz3_suffix = strlen(argv[i]) > 4 && !strcmp(argv[i] + strlen(argv[i]) - 4, ".bz3");
+            int has_bz3_suffix = strlen(arg) > 4 && !strcmp(arg + strlen(arg) - 4, ".bz3");
 
             if (has_bz3_suffix || regular_file != NULL) {
-                bz3_file = argv[i];
+                bz3_file = arg;
                 no_bz3_suffix = !has_bz3_suffix;
             } else {
-                regular_file = argv[i];
+                regular_file = arg;
             }
         }
     }
 
     if (mode == MODE_UNSPECIFIED) {
-        fprintf(stderr, "bzip3 version %s\n", VERSION);
-        fprintf(stderr, "- A better and stronger spiritual successor to bzip2.\n");
-        fprintf(stderr, "Copyright (C) by Kamila Szewczyk, 2022. Licensed under the terms of LGPLv3.\n");
-        fprintf(stderr, "Usage: bzip3 [-e/-d/-t/-c/-h/-v] [-b block_size] input output\n");
-        fprintf(stderr, "Operations:\n");
-        fprintf(stderr, "  -e: encode\n");
-        fprintf(stderr, "  -d: decode\n");
-        fprintf(stderr, "  -t: test\n");
-        fprintf(stderr, "  -h: help\n");
-        fprintf(stderr, "  -f: force overwrite output if it already exists\n");
-        fprintf(stderr, "  -v: version\n");
-        fprintf(stderr, "Extra flags:\n");
-        fprintf(stderr, "  -c: force reading/writing from standard streams\n");
-        fprintf(stderr, "  -b N: set block size in MiB\n");
-#ifdef PTHREAD
-        fprintf(stderr, "  -j N: set the amount of parallel threads\n");
-#endif
-        return args_status;
+        help();
+        return 0;
     }
 #ifndef O_BINARY
     #define O_BINARY 0
@@ -432,7 +492,10 @@ int main(int argc, char * argv[]) {
                 for (; i < workers; i++) {
                     if (fread(&byteswap_buf, 1, 4, input_des) != 4) break;
                     sizes[i] = read_neutral_s32(byteswap_buf);
-                    if (fread(&byteswap_buf, 1, 4, input_des) != 4) break;
+                    if (fread(&byteswap_buf, 1, 4, input_des) != 4) {
+                        fprintf(stderr, "I/O error.\n");
+                        return 1;
+                    }
                     old_sizes[i] = read_neutral_s32(byteswap_buf);
                     if (fread(buffers[i], 1, sizes[i], input_des) != sizes[i]) {
                         fprintf(stderr, "I/O error.\n");
@@ -457,7 +520,10 @@ int main(int argc, char * argv[]) {
                 for (; i < workers; i++) {
                     if (fread(&byteswap_buf, 1, 4, input_des) != 4) break;
                     sizes[i] = read_neutral_s32(byteswap_buf);
-                    if (fread(&byteswap_buf, 1, 4, input_des) != 4) break;
+                    if (fread(&byteswap_buf, 1, 4, input_des) != 4) {
+                        fprintf(stderr, "I/O error.\n");
+                        return 1;
+                    }
                     old_sizes[i] = read_neutral_s32(byteswap_buf);
                     if (fread(buffers[i], 1, sizes[i], input_des) != sizes[i]) {
                         fprintf(stderr, "I/O error.\n");
tab: 248 wrap: offon