diff --git a/lib/std/io/io.c3 b/lib/std/io/io.c3 index 140b78816..da9328ba8 100644 --- a/lib/std/io/io.c3 +++ b/lib/std/io/io.c3 @@ -29,6 +29,7 @@ fault IoError OUT_OF_DISK, INVALID_PUSHBACK, EOF, + CANNOT_READ_DIR, TOO_MANY_DESCRIPTORS, FILE_IS_DIR, READ_ONLY, diff --git a/lib/std/io/io_path.c3 b/lib/std/io/io_path.c3 index b8ae03479..e65313613 100644 --- a/lib/std/io/io_path.c3 +++ b/lib/std/io/io_path.c3 @@ -1,10 +1,13 @@ module std::io::path; +import std::collections::list; const PathEnv DEFAULT_PATH_ENV = env::os_is_win32() ? PathEnv.WIN32 : PathEnv.POSIX; const char PREFERRED_SEPARATOR_WIN32 = '\\'; const char PREFERRED_SEPARATOR_POSIX = '/'; const char PREFERRED_SEPARATOR = env::os_is_win32() ? PREFERRED_SEPARATOR_WIN32 : PREFERRED_SEPARATOR_POSIX; +typedef PathList = List; + fault PathResult { INVALID_PATH, @@ -100,6 +103,8 @@ fn Path! Path.append(Path path, String filename, Allocator* using = mem::heap()) }; } +fn Path! Path.tappend(Path path, String filename) => path.append(filename, mem::temp()); + fn Path! temp_directory(Allocator* using = mem::heap()) { return os::native_temp_directory(using); @@ -214,8 +219,25 @@ fn String! normalize(String path_str, PathEnv path_env = DEFAULT_PATH_ENV) i++; continue; case 2: - // We're walking back, doing so when already at the root is invalid. - if (len == path_start) return PathResult.INVALID_PATH!; + // This is an error: /a/../.. + if (len == path_start && has_root) return PathResult.INVALID_PATH!; + + // If this .. at the start, or after ../? If so, we just copy .. + if (len == path_start || + (len - path_start >= 3 && path_str[len - 1] == path_separator + && path_str[len - 3] == '.' && path_str[len - 3] == '.' && + (len - 3 == 0 || path_str[len - 4] == path_separator))) + { + if (i != len) + { + path_str[len] = '.'; + path_str[len + 1] = '.'; + } + len += 2; + if (len < path_len) path_str[len++] = path_separator; + i += 2; + continue; + } // Step back, now looking at '/' abc/def/. -> abc/def/ len--; // Step back until finding a separator or the start. @@ -241,6 +263,8 @@ fn String! normalize(String path_str, PathEnv path_env = DEFAULT_PATH_ENV) return path_str[:len]; } +fn ZString Path.as_zstr(Path path) => (ZString)path.path_string.ptr; + fn String Path.root_directory(Path path) { String path_str = path.as_str(); diff --git a/lib/std/io/os/fileinfo_darwin.c3 b/lib/std/io/os/fileinfo_darwin.c3 index 9cf8194f4..24fa7f836 100644 --- a/lib/std/io/os/fileinfo_darwin.c3 +++ b/lib/std/io/os/fileinfo_darwin.c3 @@ -3,6 +3,7 @@ import libc; $if (env::os_is_darwin()): + struct DarwinTimespec @private { long tv_sec; diff --git a/lib/std/io/os/fileinfo_other.c3 b/lib/std/io/os/fileinfo_other.c3 index 5b787e856..aa25d88ac 100644 --- a/lib/std/io/os/fileinfo_other.c3 +++ b/lib/std/io/os/fileinfo_other.c3 @@ -13,6 +13,40 @@ fn Path! native_temp_directory(Allocator* using = mem::heap()) return path::new("/tmp", using); } +$if (env::COMPILER_LIBC_AVAILABLE): + +extern fn void* opendir(ZString); +extern fn void closedir(void*); + +const DT_UNKNOWN = 0; +const DT_FIFO = 1; +const DT_CHR = 2; +const DT_DIR = 4; +const DT_BLK = 6; +const DT_REG = 8; +const DT_LNK = 10; +const DT_SOCK = 12; +const DT_WHT = 14; + +fn void! native_readdir(PathList* list, Path dir, bool no_dirs, bool no_symlinks, String mask, Allocator* using) +{ + void* directory = opendir(dir.as_str() ? dir.as_zstr() : (ZString)"."); + defer if (directory) closedir(directory); + if (!directory) return (dir.is_dir() ? IoError.CANNOT_READ_DIR : IoError.FILE_NOT_DIR)!; + NativeDirentry* entry; + while ((entry = readdir(directory))) + { + String name = ((ZString)&entry.name).as_str(); + if (!name || name == "." || name == "..") continue; + if (entry.type == DT_LNK && no_symlinks) continue; + if (entry.type == DT_DIR && no_dirs) continue; + Path path = path::new(name.copy(using), using)!!; + list.append(path); + } +} + +$endif; + $endif; $if (!env::os_is_darwin() && !env::os_is_win32()): @@ -67,4 +101,42 @@ fn bool native_is_file(String path) $endif; -$endif; \ No newline at end of file +$endif; + +$switch (env::OS_TYPE): +$case IOS: +$case MACOSX: +$case TVOS: +$case WATCHOS: +extern fn NativeDirentry* readdir(void*) @extern("readdir$INODE64"); +struct NativeDirentry +{ + usz ino; + usz seekoff; + ushort reclen; + ushort namelen; + char type; + char[1024] name; +} +$case LINUX: +extern fn NativeDirentry* readdir(void*); +struct NativeDirentry +{ + usz ino; + isz seekoff; + ushort reclen; + char type; + char[*] name; +} +$default: +// Fix this as we go along. +extern fn NativeDirentry* readdir(void*); +struct NativeDirentry +{ + usz ino; + isz seekoff; + ushort reclen; + char type; + char[*] name; +} +$endswitch; diff --git a/src/version.h b/src/version.h index b8ba8eb2f..debd08296 100644 --- a/src/version.h +++ b/src/version.h @@ -1 +1 @@ -#define COMPILER_VERSION "0.4.105" \ No newline at end of file +#define COMPILER_VERSION "0.4.106" \ No newline at end of file diff --git a/test/unit/stdlib/io/path.c3 b/test/unit/stdlib/io/path.c3 index 3d08f81ba..43a644d6a 100644 --- a/test/unit/stdlib/io/path.c3 +++ b/test/unit/stdlib/io/path.c3 @@ -24,18 +24,10 @@ fn void! test_path_normalized() assert(catch(path::new(`\\server\a\b\..\..\..\c`, .path_env = PathEnv.WIN32))); assert(catch(path::new(`\\a`, .path_env = PathEnv.WIN32))); - assert(catch(path::new(`a/b/../../../c`, .path_env = PathEnv.WIN32))); - assert(catch(path::new(`a/b/../../../c`, .path_env = PathEnv.POSIX))); assert(catch(path::new(`/a/b/../../../c`, .path_env = PathEnv.WIN32))); assert(catch(path::new(`/a/b/../../../c`, .path_env = PathEnv.POSIX))); - assert(catch(path::new(`a/b/../../..`, .path_env = PathEnv.WIN32))); - assert(catch(path::new(`a/b/../../..`, .path_env = PathEnv.POSIX))); assert(catch(path::new(`/a/b/../../..`, .path_env = PathEnv.WIN32))); assert(catch(path::new(`/a/b/../../..`, .path_env = PathEnv.POSIX))); - assert(catch(path::new(`../a`, .path_env = PathEnv.WIN32))); - assert(catch(path::new(`../a`, .path_env = PathEnv.POSIX))); - assert(catch(path::new(`..`, .path_env = PathEnv.WIN32))); - assert(catch(path::new(`..`, .path_env = PathEnv.POSIX))); assert(catch(path::new(`/../a`, .path_env = PathEnv.WIN32))); assert(catch(path::new(`/../a`, .path_env = PathEnv.POSIX))); assert(catch(path::new(`/..`, .path_env = PathEnv.WIN32))); @@ -43,9 +35,6 @@ fn void! test_path_normalized() assert(catch(path::new(`C:/a/b/../../../c`, .path_env = PathEnv.WIN32))); assert(catch(path::new(`C:/../a`, .path_env = PathEnv.WIN32))); assert(catch(path::new(`C:/..`, .path_env = PathEnv.WIN32))); - assert(catch(path::new(`C:a/b/../../../c`, .path_env = PathEnv.WIN32))); - assert(catch(path::new(`C:../a`, .path_env = PathEnv.WIN32))); - assert(catch(path::new(`C:..`, .path_env = PathEnv.WIN32))); assert(path::new("/", .path_env = PathEnv.POSIX).as_str()? == "/"); assert(path::new("/./", .path_env = PathEnv.POSIX).as_str()? == "/"); @@ -64,6 +53,15 @@ fn void! test_path_normalized() assert(path::new(`~\a\b/c.txt`, .path_env = PathEnv.WIN32).as_str()? == `~\a\b\c.txt`); assert(path::new(`~\a\b/c.txt`, .path_env = PathEnv.POSIX).as_str()? == `~\a\b/c.txt`); + + assert(path::new(`a/b/../../../c`, .path_env = PathEnv.WIN32).as_str()? == `..\c`); + assert(path::new(`a/b/../../../c`, .path_env = PathEnv.POSIX).as_str()? == `../c`); + assert(path::new(`a/b/../../..`, .path_env = PathEnv.WIN32).as_str()? == `..`); + assert(path::new(`a/b/../../..`, .path_env = PathEnv.POSIX).as_str()? == `..`); + assert(path::new(`../a`, .path_env = PathEnv.WIN32).as_str()? == `..\a`); + assert(path::new(`../a`, .path_env = PathEnv.POSIX).as_str()? == `../a`); + assert(path::new(`..`, .path_env = PathEnv.WIN32).as_str()? == `..`); + assert(path::new(`..`, .path_env = PathEnv.POSIX).as_str()? == `..`); assert(path::new(`a/b/../c`, .path_env = PathEnv.WIN32).as_str()? == `a\c`); assert(path::new(`a/b/../c`, .path_env = PathEnv.POSIX).as_str()? == `a/c`); assert(path::new(`a/b/../../c`, .path_env = PathEnv.WIN32).as_str()? == `c`); @@ -162,12 +160,15 @@ fn void! test_path_normalized() assert(path::new(`C:a/b//d`, .path_env = PathEnv.POSIX).as_str()? == `C:a/b/d`); assert(path::new(`C:a/b/././.`, .path_env = PathEnv.WIN32).as_str()? == `C:a\b`); assert(path::new(`C:a/b/././.`, .path_env = PathEnv.POSIX).as_str()? == `C:a/b`); + assert(path::new(`C:a/b/../../../c`, .path_env = PathEnv.WIN32).as_str()? == `C:..\c`); assert(path::new(`C:./a`, .path_env = PathEnv.WIN32).as_str()? == `C:a`); assert(path::new(`C:./a`, .path_env = PathEnv.POSIX).as_str()? == `C:./a`); assert(path::new(`C:./`, .path_env = PathEnv.WIN32).as_str()? == `C:`); assert(path::new(`C:./`, .path_env = PathEnv.POSIX).as_str()? == `C:.`); assert(path::new(`C:../a`, .path_env = PathEnv.POSIX).as_str()? == `C:../a`); + assert(path::new(`C:../a`, .path_env = PathEnv.WIN32).as_str()? == `C:..\a`); assert(path::new(`C:..`, .path_env = PathEnv.POSIX).as_str()? == `C:..`); + assert(path::new(`C:..`, .path_env = PathEnv.WIN32).as_str()? == `C:..`); assert(path::new(`C:`, .path_env = PathEnv.WIN32).as_str()? == `C:`); assert(path::new(`C:`, .path_env = PathEnv.POSIX).as_str()? == `C:`);