diff --git a/Cargo.lock b/Cargo.lock index 0ae5091493..ca2660b040 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -141,6 +141,14 @@ dependencies = [ "platform 0.1.0", ] +[[package]] +name = "fnmatch" +version = "0.1.0" +dependencies = [ + "cbindgen 0.5.2", + "platform 0.1.0", +] + [[package]] name = "fuchsia-zircon" version = "0.3.3" @@ -329,6 +337,7 @@ dependencies = [ "fcntl 0.1.0", "fenv 0.1.0", "float 0.1.0", + "fnmatch 0.1.0", "grp 0.1.0", "inttypes 0.1.0", "locale 0.1.0", diff --git a/Cargo.toml b/Cargo.toml index 1567fd447f..41e6711aec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,7 @@ errno = { path = "src/errno" } fcntl = { path = "src/fcntl" } fenv = { path = "src/fenv" } float = { path = "src/float" } +fnmatch = { path = "src/fnmatch" } grp = { path = "src/grp" } inttypes = { path = "src/inttypes" } locale = { path = "src/locale" } diff --git a/src/fnmatch/Cargo.toml b/src/fnmatch/Cargo.toml new file mode 100644 index 0000000000..9d9a891de3 --- /dev/null +++ b/src/fnmatch/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "fnmatch" +version = "0.1.0" +authors = ["jD91mZM2 "] + +[build-dependencies] +cbindgen = { path = "../../cbindgen" } + +[dependencies] +platform = { path = "../platform" } diff --git a/src/fnmatch/build.rs b/src/fnmatch/build.rs new file mode 100644 index 0000000000..2b6e70c118 --- /dev/null +++ b/src/fnmatch/build.rs @@ -0,0 +1,11 @@ +extern crate cbindgen; + +use std::{env, fs}; + +fn main() { + let crate_dir = env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR not set"); + fs::create_dir_all("../../target/include").expect("failed to create include directory"); + cbindgen::generate(crate_dir) + .expect("failed to generate bindings") + .write_to_file("../../target/include/fnmatch.h"); +} diff --git a/src/fnmatch/cbindgen.toml b/src/fnmatch/cbindgen.toml new file mode 100644 index 0000000000..aa95758cb0 --- /dev/null +++ b/src/fnmatch/cbindgen.toml @@ -0,0 +1,6 @@ +include_guard = "_FNMATCH_H" +language = "C" +style = "Tag" + +[enum] +prefix_with_name = true diff --git a/src/fnmatch/src/lib.rs b/src/fnmatch/src/lib.rs new file mode 100644 index 0000000000..ff60a80fd3 --- /dev/null +++ b/src/fnmatch/src/lib.rs @@ -0,0 +1,166 @@ +//! fnmatch implementation +#![no_std] +#![feature(alloc)] + +extern crate alloc; +extern crate platform; + +use alloc::vec::Vec; +use platform::types::*; + +pub const FNM_NOMATCH: c_int = 1; + +pub const FNM_NOESCAPE: c_int = 1; +pub const FNM_PATHNAME: c_int = 2; +pub const FNM_PERIOD: c_int = 4; +pub const FNM_CASEFOLD: c_int = 8; + +#[derive(Debug)] +enum Token { + Any, + Char(u8), + Match(bool, Vec), + Wildcard, + // TODO: FNM_EXTMATCH, which is basically a whole another custom regex + // format that's ambigious and ugh. The C standard library is really bloaty + // and I sure hope we can get away with delaying this as long as possible. + // If you need to implement this, you can contact jD91mZM2 for assistance + // in reading this code in case it's ugly. +} + +unsafe fn next_token(pattern: &mut *const c_char, flags: c_int) -> Option { + let c = **pattern as u8; + if c == 0 { + return None; + } + *pattern = pattern.offset(1); + Some(match c { + b'\\' if flags & FNM_NOESCAPE == FNM_NOESCAPE => { + let c = **pattern as u8; + if c == 0 { + // Trailing backslash. Maybe error here? + return None; + } + *pattern = pattern.offset(1); + Token::Char(c) + }, + b'?' => Token::Any, + b'*' => Token::Wildcard, + b'[' => { + let mut matches = Vec::new(); + let invert = if **pattern as u8 == b'!' { + *pattern = pattern.offset(1); + true + } else { false }; + + loop { + let mut c = **pattern as u8; + if c == 0 { + break; + } + *pattern = pattern.offset(1); + match c { + b']' => break, + b'\\' => { + c = **pattern as u8; + *pattern = pattern.offset(1); + if c == 0 { + // Trailing backslash. Maybe error? + break; + } + }, + _ => () + } + if matches.len() >= 2 && matches[matches.len() - 1] == b'-' { + let len = matches.len(); + let start = matches[len - 2]; + matches.drain(len - 2..); + // Exclusive range because we'll push C later + for c in start..c { + matches.push(c); + } + } + matches.push(c); + } + // Otherwise, there was no closing ]. Maybe error? + + Token::Match(invert, matches) + }, + c => Token::Char(c) + }) +} + +#[no_mangle] +pub unsafe extern "C" fn fnmatch(mut pattern: *const c_char, mut input: *const c_char, flags: c_int) -> c_int { + let pathname = flags & FNM_PATHNAME == FNM_PATHNAME; + let casefold = flags & FNM_CASEFOLD == FNM_CASEFOLD; + + let mut leading = true; + + loop { + if *input == 0 { + return if *pattern == 0 { 0 } else { FNM_NOMATCH }; + } + if leading && flags & FNM_PERIOD == FNM_PERIOD { + if *input as u8 == b'.' && *pattern as u8 != b'.' { + return FNM_NOMATCH; + } + } + leading = false; + match next_token(&mut pattern, flags) { + Some(Token::Any) => { + if pathname && *input as u8 == b'/' { + return FNM_NOMATCH; + } + input = input.offset(1); + }, + Some(Token::Char(c)) => { + let mut a = *input as u8; + if casefold && a >= b'a' && a <= b'z' { + a -= b'a' - b'A'; + } + let mut b = c; + if casefold && b >= b'a' && b <= b'z' { + b -= b'a' - b'A'; + } + if a != b { + return FNM_NOMATCH; + } + if pathname && a == b'/' { + leading = true; + } + input = input.offset(1); + }, + Some(Token::Match(invert, matches)) => { + if (pathname && *input as u8 == b'/') || matches.contains(&(*input as u8)) == invert { + // Found it, but it's inverted! Or vise versa. + return FNM_NOMATCH; + } + input = input.offset(1); + }, + Some(Token::Wildcard) => { + loop { + let c = *input as u8; + if c == 0 { + return if *pattern == 0 { 0 } else { FNM_NOMATCH }; + } + + let ret = fnmatch(pattern, input, flags); + if ret == FNM_NOMATCH { + input = input.offset(1); + } else { + // Either an error or a match. Forward the return. + return ret; + } + + if pathname && c == b'/' { + // End of segment, no match yet + return FNM_NOMATCH; + } + } + unreachable!("nothing should be able to break out of the loop"); + }, + None => return FNM_NOMATCH // Pattern ended but there's still some input + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 1a18027589..0cffd0abeb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,6 +13,7 @@ pub extern crate errno; pub extern crate fcntl; pub extern crate fenv; pub extern crate float; +pub extern crate fnmatch; pub extern crate grp; pub extern crate locale; pub extern crate netinet; diff --git a/src/stdio/src/scanf.rs b/src/stdio/src/scanf.rs index f677fe5609..2a1ae92152 100644 --- a/src/stdio/src/scanf.rs +++ b/src/stdio/src/scanf.rs @@ -2,7 +2,6 @@ use alloc::String; use alloc::Vec; use platform::types::*; use platform::Read; -#[cfg(not(test))] use vl::VaList; #[derive(PartialEq, Eq)] diff --git a/src/sys_times/cbindgen.toml b/src/sys_times/cbindgen.toml index e8995a6f1c..4aa7a766c9 100644 --- a/src/sys_times/cbindgen.toml +++ b/src/sys_times/cbindgen.toml @@ -1,3 +1,4 @@ +sys_includes = ["sys/types.h"] include_guard = "_SYS_TIMES_H" language = "C" style = "Tag" diff --git a/src/time/src/strftime.rs b/src/time/src/strftime.rs index b616b1c0e8..3ff30577cc 100644 --- a/src/time/src/strftime.rs +++ b/src/time/src/strftime.rs @@ -24,11 +24,15 @@ pub unsafe fn strftime( }}; (byte $b:expr) => {{ w!(reserve 1); - w.write_u8($b); + if w.write_u8($b).is_err() { + return !0; + } }}; (char $chr:expr) => {{ w!(reserve $chr.len_utf8()); - w.write_char($chr); + if w.write_char($chr).is_err() { + return !0; + } }}; (recurse $fmt:expr) => {{ let mut fmt = String::with_capacity($fmt.len() + 1); @@ -44,7 +48,9 @@ pub unsafe fn strftime( }}; ($str:expr) => {{ w!(reserve $str.len()); - w.write_str($str); + if w.write_str($str).is_err() { + return !0; + } }}; ($fmt:expr, $($args:expr),+) => {{ // Would use write!() if I could get the length written @@ -140,7 +146,9 @@ pub unsafe fn strftime( } if toplevel { // nul byte is already counted in written - w.write_u8(0); + if w.write_u8(0).is_err() { + return !0; + } } written } diff --git a/tests/Makefile b/tests/Makefile index 3ca980061d..82bd1f8074 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -7,6 +7,7 @@ EXPECT_BINS=\ error \ fcntl/create \ fcntl/fcntl \ + fnmatch \ locale \ math \ select \ diff --git a/tests/expected/fnmatch.stderr b/tests/expected/fnmatch.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/fnmatch.stdout b/tests/expected/fnmatch.stdout new file mode 100644 index 0000000000..14e0ae0140 --- /dev/null +++ b/tests/expected/fnmatch.stdout @@ -0,0 +1,31 @@ +Should succeed: +"*World" matches "Hello World" +"*World" matches "World" +"Hello*" matches "Hello World" +"H[ae]llo?World" matches "Hallo+World" +"H[ae]llo?World" matches "Hello_World" +"[0-9][!a]" matches "1b" +"/a/*/d" matches "/a/b/c/d" +"/a/*/d" matches "/a/bc/d" +"*hello" matches ".hello" +"/*hello" matches "/.hello" +"[a!][a!]" matches "!a" +"[\]]" matches "]" +"[\\]" matches "\" +"hello[/+]world" matches "hello/world" +"hello world" matches "HELLO WORLD" + +Should fail: +"*World" doesn't match "Hello Potato" +"*World" doesn't match "Potato" +"H[ae]llo?World" doesn't match "Hillo+World" +"H[ae]llo?World" doesn't match "Hello__World" +"[0-9][!a]" doesn't match "ab" +"[0-9][!a]" doesn't match "2a" +"/a/*/d" doesn't match "/a/b/c/d" +"/a/*/d" doesn't match "/a/bc/d/" +"*hello" doesn't match ".hello" +"/*hello" doesn't match "/.hello" +"[a!][a!]" doesn't match "ab" +"hello[/+]world" doesn't match "hello/world" +"hello world" doesn't match "HELLO WORLD" diff --git a/tests/fnmatch.c b/tests/fnmatch.c new file mode 100644 index 0000000000..4d10a642ef --- /dev/null +++ b/tests/fnmatch.c @@ -0,0 +1,45 @@ +#include +#include + +void test(char* pattern, char* input, int flags) { + if (!fnmatch(pattern, input, flags)) { + printf("\"%s\" matches \"%s\"\n", pattern, input); + } else { + printf("\"%s\" doesn't match \"%s\"\n", pattern, input); + } +} + +int main() { + puts("Should succeed:"); + test("*World", "Hello World", 0); + test("*World", "World", 0); + test("Hello*", "Hello World", 0); + test("H[ae]llo?World", "Hallo+World", 0); + test("H[ae]llo?World", "Hello_World", 0); + test("[0-9][!a]", "1b", 0); + test("/a/*/d", "/a/b/c/d", 0); + test("/a/*/d", "/a/bc/d", FNM_PATHNAME); + test("*hello", ".hello", 0); + test("/*hello", "/.hello", FNM_PERIOD); + test("[a!][a!]", "!a", 0); + test("[\\]]", "]", 0); + test("[\\\\]", "\\", 0); + test("hello[/+]world", "hello/world", 0); + test("hello world", "HELLO WORLD", FNM_CASEFOLD); + + puts(""); + puts("Should fail:"); + test("*World", "Hello Potato", 0); + test("*World", "Potato", 0); + test("H[ae]llo?World", "Hillo+World", 0); + test("H[ae]llo?World", "Hello__World", 0); + test("[0-9][!a]", "ab", 0); + test("[0-9][!a]", "2a", 0); + test("/a/*/d", "/a/b/c/d", FNM_PATHNAME); + test("/a/*/d", "/a/bc/d/", FNM_PATHNAME); + test("*hello", ".hello", FNM_PERIOD); + test("/*hello", "/.hello", FNM_PERIOD | FNM_PATHNAME); + test("[a!][a!]", "ab", 0); + test("hello[/+]world", "hello/world", FNM_PATHNAME); + test("hello world", "HELLO WORLD", 0); +} diff --git a/tests/stdio/scanf.c b/tests/stdio/scanf.c index c99c43f886..3f6a236631 100644 --- a/tests/stdio/scanf.c +++ b/tests/stdio/scanf.c @@ -1,3 +1,4 @@ +#include #include struct params {