Browse Source

unistd: add a preliminary implementation of getopt()

Alex Lyon 7 years ago
parent
commit
af78348d4a

+ 2 - 0
Cargo.lock

@@ -534,6 +534,8 @@ version = "0.1.0"
 dependencies = [
  "cbindgen 0.5.2",
  "platform 0.1.0",
+ "stdio 0.1.0",
+ "string 0.1.0",
 ]
 
 [[package]]

+ 2 - 0
src/unistd/Cargo.toml

@@ -9,3 +9,5 @@ cbindgen = { path = "../../cbindgen" }
 
 [dependencies]
 platform = { path = "../platform" }
+stdio = { path = "../stdio" }
+string = { path = "../string" }

+ 142 - 0
src/unistd/src/getopt.rs

@@ -0,0 +1,142 @@
+//! getopt implementation for Redox, following http://pubs.opengroup.org/onlinepubs/009695399/functions/getopt.html
+
+use super::platform::types::*;
+use super::platform;
+use super::stdio;
+use super::string;
+use core::ptr;
+
+#[no_mangle]
+pub static mut optarg: *mut c_char = ptr::null_mut();
+
+#[no_mangle]
+pub static mut optind: c_int = 1;
+
+#[no_mangle]
+pub static mut opterr: c_int = 1;
+
+#[no_mangle]
+pub static mut optopt: c_int = -1;
+
+static mut current_opt: *mut c_char = ptr::null_mut();
+
+#[no_mangle]
+pub unsafe extern "C" fn getopt(
+    argc: c_int,
+    argv: *const *mut c_char,
+    optstring: *const c_char,
+) -> c_int {
+    if current_opt == ptr::null_mut() || *current_opt == 0 {
+        let current_arg = *argv.offset(optind as isize);
+        // XXX: is argc check needed?
+        if optind > argc || current_arg == ptr::null_mut() || *current_arg != b'-' as c_char
+            || string::strcmp(current_arg, b"-\0".as_ptr() as _) == 0
+        {
+            -1
+        } else if string::strcmp(current_arg, b"--\0".as_ptr() as _) == 0 {
+            optind += 1;
+            -1
+        } else {
+            // remove the '-'
+            let current_arg = current_arg.offset(1);
+
+            parse_arg(argc, argv, current_arg, optstring)
+        }
+    } else {
+        parse_arg(argc, argv, current_opt, optstring)
+    }
+}
+
+unsafe fn parse_arg(
+    argc: c_int,
+    argv: *const *mut c_char,
+    current_arg: *mut c_char,
+    optstring: *const c_char,
+) -> c_int {
+    let update_current_opt = || {
+        current_opt = current_arg.offset(1);
+        if *current_opt == 0 {
+            optind += 1;
+        }
+    };
+
+    let print_error = |desc: &[u8]| {
+        // NOTE: we don't use fprintf to get around the usage of va_list
+        stdio::fputs(*argv as _, stdio::stderr);
+        stdio::fputs(desc.as_ptr() as _, stdio::stderr);
+        stdio::fputc(*current_arg as _, stdio::stderr);
+        stdio::fputc(b'\n' as _, stdio::stderr);
+    };
+
+    match find_option(*current_arg, optstring) {
+        Some(GetoptOption::Flag) => {
+            update_current_opt();
+
+            *current_arg as c_int
+        }
+        Some(GetoptOption::OptArg) => {
+            current_opt = b"\0".as_ptr() as _;
+            if *current_arg.offset(1) == 0 {
+                optind += 2;
+                if optind > argc {
+                    current_opt = ptr::null_mut();
+
+                    optopt = *current_arg as c_int;
+                    let errch = if *optstring == b':' as c_char {
+                        b':'
+                    } else {
+                        if opterr != 0 {
+                            print_error(b": option requries an argument -- \0");
+                        }
+
+                        b'?'
+                    };
+                    errch as c_int
+                } else {
+                    optarg = *argv.offset(optind as isize - 1);
+
+                    *current_arg as c_int
+                }
+            } else {
+                optarg = current_arg.offset(1);
+                optind += 1;
+
+                *current_arg as c_int
+            }
+        }
+        None => {
+            // couldn't find the given option in optstring
+            if opterr != 0 {
+                print_error(b": illegal option -- \0");
+            }
+
+            update_current_opt();
+
+            optopt = *current_arg as _;
+            b'?' as c_int
+        }
+    }
+}
+
+enum GetoptOption {
+    Flag,
+    OptArg,
+}
+
+unsafe fn find_option(ch: c_char, optstring: *const c_char) -> Option<GetoptOption> {
+    let mut i = 0;
+
+    while *optstring.offset(i) != 0 {
+        if *optstring.offset(i) == ch {
+            let result = if *optstring.offset(i + 1) == b':' as c_char {
+                GetoptOption::OptArg
+            } else {
+                GetoptOption::Flag
+            };
+            return Some(result);
+        }
+        i += 1;
+    }
+
+    None
+}

+ 5 - 5
src/unistd/src/lib.rs

@@ -3,10 +3,15 @@
 #![no_std]
 
 extern crate platform;
+extern crate stdio;
+extern crate string;
 
 pub use platform::types::*;
+pub use getopt::*;
 use core::ptr;
 
+mod getopt;
+
 pub const R_OK: c_int = 1;
 pub const W_OK: c_int = 2;
 pub const X_OK: c_int = 4;
@@ -204,11 +209,6 @@ pub extern "C" fn getlogin_r(name: *mut c_char, namesize: size_t) -> c_int {
     unimplemented!();
 }
 
-#[no_mangle]
-pub extern "C" fn getopt(argc: c_int, argv: *const *mut c_char, opstring: *const c_char) -> c_int {
-    unimplemented!();
-}
-
 #[no_mangle]
 pub extern "C" fn getpagesize() -> c_int {
     unimplemented!();

+ 1 - 0
tests/.gitignore

@@ -33,6 +33,7 @@
 /string/strchr
 /string/strrchr
 /string/strspn
+/unistd/getopt
 /unlink
 /waitpid
 /write

+ 1 - 0
tests/Makefile

@@ -32,6 +32,7 @@ EXPECT_BINS=\
 	string/strpbrk \
 	string/strtok \
 	string/strtok_r \
+	unistd/getopt \
 	unlink \
 	waitpid \
 	write

+ 0 - 0
tests/expected/unistd/getopt.stderr


+ 36 - 0
tests/expected/unistd/getopt.stdout

@@ -0,0 +1,36 @@
+bflg: 0
+aflg: 1
+errflg: 0
+ifile: 
+ofile: arg
+result: 0
+bflg: 0
+aflg: 1
+errflg: 0
+ifile: 
+ofile: arg
+result: 0
+bflg: 0
+aflg: 1
+errflg: 0
+ifile: 
+ofile: arg
+result: 0
+bflg: 0
+aflg: 1
+errflg: 0
+ifile: 
+ofile: arg
+result: 0
+bflg: 0
+aflg: 1
+errflg: 0
+ifile: 
+ofile: arg
+result: 0
+bflg: 0
+aflg: 1
+errflg: 0
+ifile: 
+ofile: arg
+result: 0

+ 68 - 0
tests/unistd/getopt.c

@@ -0,0 +1,68 @@
+#include <unistd.h>
+#include <stdio.h>
+
+#define RUN(...) \
+    { \
+        optind = 1; \
+        optarg = NULL; \
+        opterr = 1; \
+        optopt = -1; \
+        char *args_arr[] = { __VA_ARGS__ }; \
+        printf("result: %d\n", runner(sizeof(args_arr) / sizeof(args_arr[0]), args_arr)); \
+    }
+
+int runner(int argc, const char *argv[]) {
+    int c;
+    int bflg = 0, aflg = 0, errflg = 0;
+    char *ifile = "";
+    char *ofile = "";
+
+    while((c = getopt(argc, argv, ":abf:o:")) != -1) {
+        switch(c) {
+            case 'a':
+                if(bflg)
+                    errflg++;
+                else
+                    aflg++;
+                break;
+            case 'b':
+                if(aflg)
+                    errflg++;
+                else
+                    bflg++;
+                break;
+            case 'f':
+                ifile = optarg;
+                break;
+            case 'o':
+                ofile = optarg;
+                break;
+            case ':':
+                printf("Option -%c requires an operand\n", optopt);
+                errflg++;
+                break;
+            case '?':
+                printf("Unrecognized option: -%c\n", optopt);
+                errflg++;
+        }
+    }
+    printf("bflg: %d\n", bflg);
+    printf("aflg: %d\n", aflg);
+    printf("errflg: %d\n", errflg);
+    printf("ifile: %s\n", ifile);
+    printf("ofile: %s\n", ofile);
+    if(errflg) {
+        printf("Usage: info goes here\n");
+        return 2;
+    }
+    return 0;
+}
+
+int main(int argc, const char *argv[]) {
+    RUN("test", "-ao", "arg", "path", "path");
+    RUN("test", "-a", "-o", "arg", "path", "path");
+    RUN("test", "-o", "arg", "-a", "path", "path");
+    RUN("test", "-a", "-o", "arg", "--", "path", "path");
+    RUN("test", "-a", "-oarg", "path", "path");
+    RUN("test", "-aoarg", "path", "path");
+}