From 92e23ec3979623418ce8eb2d2faafee2de3c5a2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luka=20Aleksi=C4=87?= Date: Thu, 21 Jan 2021 20:20:38 +0100 Subject: [PATCH] * Add some procedures to path_unix to mirror the path_windows API * Add files stat_linux and dir_linux to mirror the stat/dir_windows API * Add helper functions to os_linux that are used by the above --- core/os/dir_linux.odin | 67 ++++++++++++++ core/os/os_linux.odin | 144 ++++++++++++++++++++++++++++-- core/os/stat_linux.odin | 116 ++++++++++++++++++++++++ core/path/filepath/match.odin | 18 ++-- core/path/filepath/path_unix.odin | 20 +++++ 5 files changed, 352 insertions(+), 13 deletions(-) create mode 100644 core/os/dir_linux.odin create mode 100644 core/os/stat_linux.odin diff --git a/core/os/dir_linux.odin b/core/os/dir_linux.odin new file mode 100644 index 000000000..8d988405c --- /dev/null +++ b/core/os/dir_linux.odin @@ -0,0 +1,67 @@ +package os + +import "core:strconv" +import "core:strings" +import "core:mem" + +read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []File_Info, err: Errno) { + dirp: Dir; + dirp, err = _fdopendir(fd); + if err != ERROR_NONE { + return; + } + + defer _closedir(dirp); + + dirpath: string; + dirpath, err = absolute_path_from_handle(fd); + + if err != ERROR_NONE { + return; + } + + defer delete(dirpath); + + n := n; + size := n; + if n <= 0 { + n = -1; + size = 100; + } + + dfi := make([dynamic]File_Info, 0, size); + + for { + entry: Dirent; + end_of_stream: bool; + entry, err, end_of_stream = _readdir(dirp); + if err != ERROR_NONE { + for fi_ in dfi { + file_info_delete(fi_); + } + delete(dfi); + return; + } else if end_of_stream { + break; + } + + fi_: File_Info; + + filename := cast(string)(transmute(cstring)mem.Raw_Cstring{ data = &entry.name[0] }); + fullpath := strings.join( []string{ dirpath, filename }, "/" ); + defer delete(fullpath); + + fi_, err = stat(fullpath); + if err != ERROR_NONE { + for fi_ in dfi { + file_info_delete(fi_); + } + delete(dfi); + return; + } + + append(&dfi, fi_); + } + + return dfi[:], ERROR_NONE; +} diff --git a/core/os/os_linux.odin b/core/os/os_linux.odin index cfa5f9c44..f8523cfd4 100644 --- a/core/os/os_linux.odin +++ b/core/os/os_linux.odin @@ -5,7 +5,9 @@ foreign import libc "system:c" import "core:runtime" import "core:strings" +import "core:strconv" import "core:c" +import "core:mem" Handle :: distinct i32; File_Time :: distinct u64; @@ -206,6 +208,17 @@ Stat :: struct { _reserve3: i64, }; +// NOTE(laleksic, 2021-01-21): Comment and rename these to match Stat above +Dirent :: struct { + ino: u64, + off: u64, + reclen: u16, + type: u8, + name: [256]byte, +}; + +Dir :: distinct rawptr; // DIR* + // File type S_IFMT :: 0o170000; // Type of file mask S_IFIFO :: 0o010000; // Named pipe (fifo) @@ -267,7 +280,13 @@ foreign libc { @(link_name="gettid") _unix_gettid :: proc() -> u64 ---; @(link_name="getpagesize") _unix_getpagesize :: proc() -> c.int ---; @(link_name="stat64") _unix_stat :: proc(path: cstring, stat: ^Stat) -> c.int ---; + @(link_name="lstat") _unix_lstat :: proc(path: cstring, stat: ^Stat) -> c.int ---; @(link_name="fstat") _unix_fstat :: proc(fd: Handle, stat: ^Stat) -> c.int ---; + @(link_name="fdopendir") _unix_fdopendir :: proc(fd: Handle) -> Dir ---; + @(link_name="closedir") _unix_closedir :: proc(dirp: Dir) -> c.int ---; + @(link_name="rewinddir") _unix_rewinddir :: proc(dirp: Dir) ---; + @(link_name="readdir_r") _unix_readdir_r :: proc(dirp: Dir, entry: ^Dirent, result: ^^Dirent) -> c.int ---; + @(link_name="readlink") _unix_readlink :: proc(path: cstring, buf: ^byte, bufsiz: c.size_t) -> c.ssize_t ---; @(link_name="access") _unix_access :: proc(path: cstring, mask: c.int) -> c.int ---; @(link_name="malloc") _unix_malloc :: proc(size: c.size_t) -> rawptr ---; @@ -277,6 +296,7 @@ foreign libc { @(link_name="getenv") _unix_getenv :: proc(cstring) -> cstring ---; @(link_name="getcwd") _unix_getcwd :: proc(buf: cstring, len: c.size_t) -> cstring ---; @(link_name="chdir") _unix_chdir :: proc(buf: cstring) -> c.int ---; + @(link_name="realpath") _unix_realpath :: proc(path: cstring, resolved_path: rawptr) -> rawptr ---; @(link_name="exit") _unix_exit :: proc(status: c.int) -> ! ---; } @@ -341,7 +361,7 @@ seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Errno) { } file_size :: proc(fd: Handle) -> (i64, Errno) { - s, err := fstat(fd); + s, err := _fstat(fd); if err != ERROR_NONE { return -1, err; } @@ -360,7 +380,7 @@ last_write_time :: proc(fd: Handle) -> File_Time {} last_write_time_by_name :: proc(name: string) -> File_Time {} */ last_write_time :: proc(fd: Handle) -> (File_Time, Errno) { - s, err := fstat(fd); + s, err := _fstat(fd); if err != ERROR_NONE { return 0, err; } @@ -369,7 +389,7 @@ last_write_time :: proc(fd: Handle) -> (File_Time, Errno) { } last_write_time_by_name :: proc(name: string) -> (File_Time, Errno) { - s, err := stat(name); + s, err := _stat(name); if err != ERROR_NONE { return 0, err; } @@ -377,7 +397,8 @@ last_write_time_by_name :: proc(name: string) -> (File_Time, Errno) { return File_Time(modified), ERROR_NONE; } -stat :: inline proc(path: string) -> (Stat, Errno) { +@private +_stat :: inline proc(path: string) -> (Stat, Errno) { cstr := strings.clone_to_cstring(path); defer delete(cstr); @@ -389,7 +410,21 @@ stat :: inline proc(path: string) -> (Stat, Errno) { return s, ERROR_NONE; } -fstat :: inline proc(fd: Handle) -> (Stat, Errno) { +@private +_lstat :: inline proc(path: string) -> (Stat, Errno) { + cstr := strings.clone_to_cstring(path); + defer delete(cstr); + + s: Stat; + result := _unix_lstat(cstr, &s); + if result == -1 { + return s, Errno(get_last_error()); + } + return s, ERROR_NONE; +} + +@private +_fstat :: inline proc(fd: Handle) -> (Stat, Errno) { s: Stat; result := _unix_fstat(fd, &s); if result == -1 { @@ -398,6 +433,105 @@ fstat :: inline proc(fd: Handle) -> (Stat, Errno) { return s, ERROR_NONE; } +@private +_fdopendir :: inline proc(fd: Handle) -> (Dir, Errno) { + dirp := _unix_fdopendir(fd); + if dirp == cast(Dir)nil { + return nil, Errno(get_last_error()); + } + return dirp, ERROR_NONE; +} + +@private +_closedir :: inline proc(dirp: Dir) -> Errno { + rc := _unix_closedir(dirp); + if rc != 0 { + return Errno(get_last_error()); + } + return ERROR_NONE; +} + +@private +_rewinddir :: inline proc(dirp: Dir) { + _unix_rewinddir(dirp); +} + +@private +_readdir :: inline proc(dirp: Dir) -> (entry: Dirent, err: Errno, end_of_stream: bool) { + result: ^Dirent; + rc := _unix_readdir_r(dirp, &entry, &result); + + if rc != 0 { + err = Errno(get_last_error()); + return; + } + err = ERROR_NONE; + + if result == nil { + end_of_stream = true; + return; + } + end_of_stream = false; + + return; +} + +@private +_readlink :: inline proc(path: string) -> (string, Errno) { + path_cstr := strings.clone_to_cstring(path); + defer delete(path_cstr); + + bufsz : uint = 256; + buf := make([]byte, bufsz); + for { + rc := _unix_readlink(path_cstr, &(buf[0]), bufsz); + if rc == -1 { + delete(buf); + return "", Errno(get_last_error()); + } else if rc == int(bufsz) { + // NOTE(laleksic, 2021-01-21): Any cleaner way to resize the slice? + bufsz *= 2; + delete(buf); + buf := make([]byte, bufsz); + } else { + return strings.string_from_ptr(&buf[0], rc), ERROR_NONE; + } + } +} + +absolute_path_from_handle :: proc(fd: Handle) -> (string, Errno) { + buf: [256]byte; + fd_str := strconv.itoa( buf[:], cast(int)fd ); + + procfs_path := strings.concatenate( []string{ "/proc/self/fd/", fd_str } ); + defer delete(procfs_path); + + return _readlink(procfs_path); +} + +absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Errno) { + ta := context.temp_allocator; + + rel := rel; + if rel == "" { + rel = "."; + } + + rel_cstr := strings.clone_to_cstring(rel); + defer delete(rel_cstr); + + path_ptr := _unix_realpath(rel_cstr, nil); + if path_ptr == nil { + return "", Errno(get_last_error()); + } + defer _unix_free(path_ptr); + + path_cstr := transmute(cstring)path_ptr; + path = strings.clone( string(path_cstr) ); + + return path, ERROR_NONE; +} + access :: inline proc(path: string, mask: int) -> (bool, Errno) { cstr := strings.clone_to_cstring(path); defer delete(cstr); diff --git a/core/os/stat_linux.odin b/core/os/stat_linux.odin new file mode 100644 index 000000000..61673aa9e --- /dev/null +++ b/core/os/stat_linux.odin @@ -0,0 +1,116 @@ +package os + +import "core:time" +import "core:path" + +/* +For reference +------------- + +Unix_File_Time :: struct { + seconds: i64, + nanoseconds: i64, +} + +Stat :: struct { + device_id: u64, // ID of device containing file + serial: u64, // File serial number + nlink: u64, // Number of hard links + mode: u32, // Mode of the file + uid: u32, // User ID of the file's owner + gid: u32, // Group ID of the file's group + _padding: i32, // 32 bits of padding + rdev: u64, // Device ID, if device + size: i64, // Size of the file, in bytes + block_size: i64, // Optimal bllocksize for I/O + blocks: i64, // Number of 512-byte blocks allocated + + last_access: Unix_File_Time, // Time of last access + modified: Unix_File_Time, // Time of last modification + status_change: Unix_File_Time, // Time of last status change + + _reserve1, + _reserve2, + _reserve3: i64, +}; + +Time :: struct { + _nsec: i64, // zero is 1970-01-01 00:00:00 +} + +File_Info :: struct { + fullpath: string, + name: string, + size: i64, + mode: File_Mode, + is_dir: bool, + creation_time: time.Time, + modification_time: time.Time, + access_time: time.Time, +} +*/ + +@private +_make_time_from_unix_file_time :: proc(uft: Unix_File_Time) -> time.Time { + return time.Time{ + _nsec = uft.nanoseconds + uft.seconds * 1_000_000_000, + }; +} + +@private +_fill_file_info_from_stat :: proc(fi: ^File_Info, s: Stat) { + fi.size = s.size; + fi.mode = cast(File_Mode)s.mode; + fi.is_dir = S_ISDIR(s.mode); + + // NOTE(laleksic, 2021-01-21): Not really creation time, but closest we can get (maybe better to leave it 0?) + fi.creation_time = _make_time_from_unix_file_time(s.status_change); + + fi.modification_time = _make_time_from_unix_file_time(s.modified); + fi.access_time = _make_time_from_unix_file_time(s.last_access); +} + +lstat :: proc(name: string, allocator := context.allocator) -> (fi: File_Info, err: Errno) { + s: Stat; + s, err = _lstat(name); + if err != ERROR_NONE { + return fi, err; + } + _fill_file_info_from_stat(&fi, s); + fi.fullpath, err = absolute_path_from_relative(name); + if err != ERROR_NONE { + return; + } + fi.name = path.base(fi.fullpath); + return fi, ERROR_NONE; +} + +stat :: proc(name: string, allocator := context.allocator) -> (fi: File_Info, err: Errno) { + s: Stat; + s, err = _stat(name); + if err != ERROR_NONE { + return fi, err; + } + _fill_file_info_from_stat(&fi, s); + fi.fullpath, err = absolute_path_from_relative(name); + if err != ERROR_NONE { + return; + } + fi.name = path.base(fi.fullpath); + return fi, ERROR_NONE; +} + +fstat :: proc(fd: Handle, allocator := context.allocator) -> (fi: File_Info, err: Errno) { + s: Stat; + s, err = _fstat(fd); + if err != ERROR_NONE { + return fi, err; + } + _fill_file_info_from_stat(&fi, s); + fi.fullpath, err = absolute_path_from_handle(fd); + if err != ERROR_NONE { + return; + } + fi.name = path.base(fi.fullpath); + return fi, ERROR_NONE; +} diff --git a/core/path/filepath/match.odin b/core/path/filepath/match.odin index 57307934e..1446ceb4d 100644 --- a/core/path/filepath/match.odin +++ b/core/path/filepath/match.odin @@ -218,6 +218,7 @@ get_escape :: proc(chunk: string) -> (r: rune, next_chunk: string, err: Match_Er // // glob ignores file system errors // + glob :: proc(pattern: string, allocator := context.allocator) -> (matches: []string, err: Match_Error) { if !has_meta(pattern) { // TODO(bill): os.lstat on here to check for error @@ -272,14 +273,15 @@ _glob :: proc(dir, pattern: string, matches: ^[dynamic]string) -> (m: [dynamic]s } defer os.close(d); - file_info, ferr := os.fstat(d); - if ferr != 0 { - os.file_info_delete(file_info); - return; - } - if !file_info.is_dir { - os.file_info_delete(file_info); - return; + { + file_info, ferr := os.fstat(d); + defer os.file_info_delete(file_info); + if ferr != 0 { + return; + } + if !file_info.is_dir { + return; + } } diff --git a/core/path/filepath/path_unix.odin b/core/path/filepath/path_unix.odin index c9d0e28f1..faf1d9568 100644 --- a/core/path/filepath/path_unix.odin +++ b/core/path/filepath/path_unix.odin @@ -1,6 +1,26 @@ //+build linux, darwin, freebsd package filepath +import "core:strings" +import "core:os" + SEPARATOR :: '/'; SEPARATOR_STRING :: `/`; LIST_SEPARATOR :: ':'; + +abs :: proc(path: string, allocator := context.allocator) -> (string, bool) { + full_path, err := os.absolute_path_from_relative(path); + if err != os.ERROR_NONE { + return "", false; + } + return full_path, true; +} + +join :: proc(elems: ..string, allocator := context.allocator) -> string { + s := strings.join(elems, SEPARATOR_STRING); + return s; +} + +is_abs :: proc(path: string) -> bool { + return (path[0] == '/'); +}