diff options
| -rw-r--r-- | src/etc/libc.c | 3 | ||||
| -rw-r--r-- | src/libcore/core.rc | 6 | ||||
| -rw-r--r-- | src/libcore/core.rs | 6 | ||||
| -rw-r--r-- | src/libcore/libc.rs | 228 | ||||
| -rw-r--r-- | src/libcore/os.rs | 529 | ||||
| -rw-r--r-- | src/libcore/path.rs | 505 | ||||
| -rw-r--r-- | src/libcore/str.rs | 2 | ||||
| -rw-r--r-- | src/libcore/vec.rs | 4 |
8 files changed, 1270 insertions, 13 deletions
diff --git a/src/etc/libc.c b/src/etc/libc.c index 5ab0c74f180..a91c4452088 100644 --- a/src/etc/libc.c +++ b/src/etc/libc.c @@ -10,6 +10,7 @@ #include <stdio.h> #include <stdlib.h> #include <limits.h> +#include <wchar.h> /* c99 */ #include <inttypes.h> @@ -53,6 +54,8 @@ void c95_types() { put_type("clock_t", clock_t); put_type("time_t", time_t); + put_type("wchar_t", wchar_t); + printf(" }\n"); } diff --git a/src/libcore/core.rc b/src/libcore/core.rc index 37e7fe6ec02..ee3f90a3333 100644 --- a/src/libcore/core.rc +++ b/src/libcore/core.rc @@ -82,6 +82,12 @@ mod to_str; // Runtime and language-primitive support mod libc; + +// Rename when transition done. +#[path = "os.rs"] +mod new_os; +mod path; + mod ctypes; mod math; mod cmath; diff --git a/src/libcore/core.rs b/src/libcore/core.rs index c68e4ddf617..96bff1250fc 100644 --- a/src/libcore/core.rs +++ b/src/libcore/core.rs @@ -1,12 +1,12 @@ // Top-level, visible-everywhere definitions. -// Export type option as a synonym for option and export the some and none -// enum constructors. +// Export various ubiquitous types, constructors, methods. import option::{some, none}; import option = option::t; +import path = path::path; import vec::vec_len; -export option, some, none, vec_len; +export path, option, some, none, vec_len; // Export the log levels as global constants. Higher levels mean // more-verbosity. Error is the bottom level, default logging level is diff --git a/src/libcore/libc.rs b/src/libcore/libc.rs index 60ede88c89a..0c16a378c25 100644 --- a/src/libcore/libc.rs +++ b/src/libcore/libc.rs @@ -5,7 +5,8 @@ // - ISO 9899:1990 ('C95', 'ANSI C', 'Standard C'), NA1, 1995. // - ISO 9899:1999 ('C99' or 'C9x'). // - ISO 9945:1988 / IEEE 1003.1-1988 ('POSIX.1'). -// - ISO 9945:2008 / IEEE 1003.1-2008 ('POSIX:2008'). +// - ISO 9945:2001 / IEEE 1003.1-2001 ('POSIX:2001', 'SUSv3'). +// - ISO 9945:2008 / IEEE 1003.1-2008 ('POSIX:2008', 'SUSv4'). // // Despite having several names each, these are *reasonably* coherent // point-in-time, list-of-definition sorts of specs. You can get each under a @@ -17,9 +18,18 @@ // platforms. // // We therefore wind up dividing our module-space up (mostly for the sake of -// sanity while editing) into definitions common-to-all (held in modules named -// c95, c99, posix88, and posix08) and definitions that appear only on *some* -// platforms (named 'extra'). +// sanity while editing, filling-in-details and eliminating duplication) into +// definitions common-to-all (held in modules named c95, c99, posix88, posix01 +// and posix08) and definitions that appear only on *some* platforms (named +// 'extra'). This would be things like significant OSX foundation kit, or +// win32 library kernel32.dll, or various fancy glibc, linux or BSD +// extensions. +// +// In addition to the per-platform 'extra' modules, we define a module of +// "common BSD" libc routines that never quite made it into POSIX but show up +// in multiple derived systems. This is the 4.4BSD r2 / 1995 release, the +// final one from Berkeley after the lawsuits died down and the CSRG +// dissolved. // // Initial glob-exports mean that all the contents of all the modules @@ -28,14 +38,25 @@ // FIXME: change these to glob-exports when sufficiently supported. import types::common::c95::*; +import types::common::c99::*; import types::common::posix88::*; +import types::common::posix01::*; +import types::common::posix08::*; +import types::common::bsd44::*; import types::os::arch::c95::*; import types::os::arch::c99::*; import types::os::arch::posix88::*; +import types::os::arch::posix01::*; +import types::os::arch::posix08::*; +import types::os::arch::bsd44::*; import types::os::arch::extra::*; import consts::os::c95::*; +import consts::os::c99::*; import consts::os::posix88::*; +import consts::os::posix01::*; +import consts::os::posix08::*; +import consts::os::bsd44::*; import consts::os::extra::*; import funcs::c95::ctype::*; @@ -49,6 +70,18 @@ import funcs::posix88::fcntl::*; import funcs::posix88::dirent::*; import funcs::posix88::unistd::*; +import funcs::posix01::unistd::*; +import funcs::posix08::unistd::*; + +import funcs::bsd44::*; +import funcs::extra::*; + +// FIXME: remove these 3 exports (and their uses next door in os::) when +// export globs work. They provide access (for now) for os:: to dig around in +// the rest of the platform-specific definitions. + +export types, funcs, consts; + // Explicit export lists for the intersection (provided here) mean that // you can write more-platform-agnostic code if you stick to just these // symbols. @@ -76,7 +109,7 @@ export strcpy, strncpy, strcat, strncat, strcmp, strncmp, strcoll, strchr, strxfrm, memcpy, memmove, memcmp, memchr, memset; export chmod, mkdir; -export popen, pclose; +export popen, pclose, fdopen; export open, creat; export access, chdir, close, dup, dup2, execv, execve, execvp, getcwd, getpid, isatty, lseek, pipe, read, rmdir, unlink, write; @@ -95,11 +128,14 @@ mod types { enum FILE {} enum fpos_t {} } - + mod c99 { } mod posix88 { enum DIR {} enum dirent {} } + mod posix01 { } + mod posix08 { } + mod bsd44 { } } // Standard types that are scalar but vary by OS and arch. @@ -122,6 +158,7 @@ mod types { type ptrdiff_t = i32; type clock_t = i32; type time_t = i32; + type wchar_t = i32; } mod c99 { type c_longlong = i64; @@ -140,6 +177,9 @@ mod types { type mode_t = u32; type ssize_t = i32; } + mod posix01 { } + mod posix08 { } + mod bsd44 { } mod extra { } } @@ -160,6 +200,7 @@ mod types { type ptrdiff_t = i64; type clock_t = i64; type time_t = i64; + type wchar_t = i32; } mod c99 { type c_longlong = i64; @@ -178,6 +219,9 @@ mod types { type mode_t = u32; type ssize_t = i64; } + mod posix01 { } + mod posix08 { } + mod bsd44 { } mod extra { } } @@ -201,6 +245,7 @@ mod types { type ptrdiff_t = i64; type clock_t = i32; type time_t = i64; + type wchar_t = i32; } mod c99 { type c_longlong = i64; @@ -219,6 +264,9 @@ mod types { type mode_t = u16; type ssize_t = i64; } + mod posix01 { } + mod posix08 { } + mod bsd44 { } mod extra { } } @@ -242,6 +290,7 @@ mod types { type ptrdiff_t = i32; type clock_t = i32; type time_t = i32; + type wchar_t = u16; } mod c99 { type c_longlong = i64; @@ -258,7 +307,39 @@ mod types { type mode_t = u16; type ssize_t = i32; } + mod posix01 { } + mod posix08 { } + mod bsd44 { } mod extra { + type BOOL = c_int; + type BYTE = u8; + type CCHAR = c_char; + type CHAR = c_char; + + type DWORD = c_ulong; + type DWORDLONG = c_ulonglong; + + type HANDLE = LPVOID; + type HMODULE = c_uint; + + type LONG_PTR = c_long; + + type LPCWSTR = *WCHAR; + type LPCSTR = *CHAR; + + type LPWSTR = *mutable WCHAR; + type LPSTR = *mutable CHAR; + + // Not really, but opaque to us. + type LPSECURITY_ATTRIBUTES = LPVOID; + + type LPVOID = *mutable c_void; + type LPWORD = *mutable WORD; + + type LRESULT = LONG_PTR; + type PBOOL = *mutable BOOL; + type WCHAR = wchar_t; + type WORD = u16; } } } @@ -281,6 +362,7 @@ mod types { type ptrdiff_t = i32; type clock_t = u32; type time_t = i32; + type wchar_t = i32; } mod c99 { type c_longlong = i64; @@ -299,6 +381,9 @@ mod types { type mode_t = u16; type ssize_t = i32; } + mod posix01 { } + mod posix08 { } + mod bsd44 { } mod extra { } } @@ -319,6 +404,7 @@ mod types { type ptrdiff_t = i64; type clock_t = u64; type time_t = i64; + type wchar_t = i32; } mod c99 { type c_longlong = i64; @@ -337,6 +423,9 @@ mod types { type mode_t = u16; type ssize_t = i64; } + mod posix01 { } + mod posix08 { } + mod bsd44 { } mod extra { } } @@ -367,6 +456,7 @@ mod consts { const L_tmpnam : uint = 16_u; const TMP_MAX : uint = 32767_u; } + mod c99 { } mod posix88 { const O_RDONLY : int = 0; const O_WRONLY : int = 1; @@ -396,9 +486,13 @@ mod consts { const STDIN_FILENO : int = 0; const STDOUT_FILENO : int = 1; } + mod posix01 { } + mod posix08 { } + mod bsd44 { } mod extra { const O_TEXT : int = 16384; const O_BINARY : int = 32768; + const O_NOINHERIT: int = 128; } } @@ -422,6 +516,7 @@ mod consts { const L_tmpnam : uint = 20_u; const TMP_MAX : uint = 238328_u; } + mod c99 { } mod posix88 { const O_RDONLY : int = 0; const O_WRONLY : int = 1; @@ -452,6 +547,9 @@ mod consts { const F_TLOCK : int = 2; const F_ULOCK : int = 0; } + mod posix01 { } + mod posix08 { } + mod bsd44 { } mod extra { const O_RSYNC : int = 1052672; const O_DSYNC : int = 4096; @@ -478,6 +576,7 @@ mod consts { const L_tmpnam : uint = 1024_u; const TMP_MAX : uint = 308915776_u; } + mod c99 { } mod posix88 { const O_RDONLY : int = 0; const O_WRONLY : int = 1; @@ -511,8 +610,14 @@ mod consts { const F_TLOCK : int = 2; const F_ULOCK : int = 0; } + mod posix01 { } + mod posix08 { } + mod bsd44 { } mod extra { const O_SYNC : int = 128; + const CTL_KERN: int = 1; + const KERN_PROC: int = 14; + const KERN_PROC_PATHNAME: int = 12; } } @@ -535,6 +640,7 @@ mod consts { const L_tmpnam : uint = 1024_u; const TMP_MAX : uint = 308915776_u; } + mod c99 { } mod posix88 { const O_RDONLY : int = 0; const O_WRONLY : int = 1; @@ -568,9 +674,13 @@ mod consts { const F_TLOCK : int = 2; const F_ULOCK : int = 0; } + mod posix01 { } + mod posix08 { } + mod bsd44 { } mod extra { const O_DSYNC : int = 4194304; const O_SYNC : int = 128; + const F_FULLFSYNC : int = 51; } } } @@ -725,6 +835,9 @@ mod funcs { #[link_name = "_pclose"] fn pclose(stream: *FILE) -> c_int; + + #[link_name = "_fdopen"] + fn fdopen(fd: c_int, mode: *c_char) -> *FILE; } #[nolink] @@ -765,7 +878,8 @@ mod funcs { fn execv(prog: *c_char, argv: **c_char) -> intptr_t; #[link_name = "_execve"] - fn execve(prog: *c_char, argv: **c_char, envp: **c_char) -> c_int; + fn execve(prog: *c_char, argv: **c_char, + envp: **c_char) -> c_int; #[link_name = "_execvp"] fn execvp(c: *c_char, argv: **c_char) -> c_int; @@ -786,7 +900,8 @@ mod funcs { fn lseek(fd: c_int, offset: c_long, origin: c_int) -> c_long; #[link_name = "_pipe"] - fn pipe(fds: *c_int, psize: c_uint, textmode: c_int) -> c_int; + fn pipe(fds: *mutable c_int, psize: c_uint, + textmode: c_int) -> c_int; #[link_name = "_read"] fn read(fd: c_int, buf: *c_void, count: c_uint) -> c_int; @@ -823,6 +938,7 @@ mod funcs { native mod stdio { fn popen(command: *c_char, mode: *c_char) -> *FILE; fn pclose(stream: *FILE) -> c_int; + fn fdopen(fd: c_int, mode: *c_char) -> *FILE; } #[nolink] @@ -875,7 +991,7 @@ mod funcs { fn lseek(fd: c_int, offset: off_t, whence: c_int) -> off_t; fn pathconf(path: *c_char, name: c_int) -> c_long; fn pause() -> c_int; - fn pipe(fds: *c_int) -> c_int; + fn pipe(fds: *mutable c_int) -> c_int; fn read(fd: c_int, buf: *c_void, count: size_t) -> ssize_t; fn rmdir(path: *c_char) -> c_int; fn setgid(gid: gid_t) -> c_int; @@ -890,7 +1006,101 @@ mod funcs { fn write(fd: c_int, buf: *c_void, count: size_t) -> ssize_t; } } + + #[cfg(target_os = "linux")] + #[cfg(target_os = "macos")] + #[cfg(target_os = "freebsd")] + mod posix01 { + + #[nolink] + #[abi = "cdecl"] + native mod unistd { + fn readlink(path: *c_char, buf: *mutable c_char, + bufsz: size_t) -> ssize_t; + + fn setenv(name: *c_char, val: *c_char, + overwrite: c_int) -> c_int; + fn unsetenv(name: *c_char) -> c_int; + fn putenv(string: *c_char) -> c_int; + } + } + + #[cfg(target_os = "win32")] + mod posix01 { + #[nolink] + native mod unistd { } + } + + + #[cfg(target_os = "win32")] + #[cfg(target_os = "linux")] + #[cfg(target_os = "macos")] + #[cfg(target_os = "freebsd")] + mod posix08 { + #[nolink] + native mod unistd { } + } + + + #[cfg(target_os = "macos")] + #[cfg(target_os = "freebsd")] + #[nolink] + #[abi = "cdecl"] + native mod bsd44 { + + fn sysctl(name: *c_int, namelen: c_uint, + oldp: *mutable c_void, oldlenp: *mutable size_t, + newp: *c_void, newlen: size_t) -> c_int; + + fn sysctlbyname(name: *c_char, + oldp: *mutable c_void, oldlenp: *mutable size_t, + newp: *c_void, newlen: size_t) -> c_int; + + fn sysctlnametomib(name: *c_char, mibp: *mutable c_int, + sizep: *mutable size_t) -> c_int; + } + + + #[cfg(target_os = "linux")] + #[cfg(target_os = "win32")] + mod bsd44 { + } + + + #[cfg(target_os = "macos")] + #[nolink] + #[abi = "cdecl"] + native mod extra { + fn _NSGetExecutablePath(buf: *mutable c_char, + bufsize: *mutable u32) -> c_int; + } + + #[cfg(target_os = "freebsd")] + mod extra { } + + #[cfg(target_os = "linux")] + mod extra { } + + + #[cfg(target_os = "win32")] mod extra { + import types::os::arch::extra::*; + + #[abi = "stdcall"] + native mod kernel32 { + fn GetEnvironmentVariableA(n: LPCSTR, + v: LPSTR, + nsize: DWORD) -> DWORD; + fn SetEnvironmentVariableA(n: LPCSTR, v: LPCSTR) -> BOOL; + fn GetModuleFileNameA(hModule: HMODULE, + lpFilename: LPSTR, + nSize: DWORD) -> DWORD; + fn CreateDirectoryA(lpPathName: LPCSTR, + lpSecurityAttributes: + LPSECURITY_ATTRIBUTES) -> BOOL; + fn RemoveDirectoryA(lpPathName: LPCSTR) -> BOOL; + fn SetCurrentDirectoryA(lpPathName: LPCSTR) -> BOOL; + } } } diff --git a/src/libcore/os.rs b/src/libcore/os.rs new file mode 100644 index 00000000000..a458fe49a30 --- /dev/null +++ b/src/libcore/os.rs @@ -0,0 +1,529 @@ +// Higher-level interfaces to libc::* functions and operating system services. +// +// In general these take and return rust types, use rust idioms (enums, +// closures, vectors) rather than C idioms, and do more extensive safety +// checks. +// +// This module is not meant to only contain 1:1 mappings to libc entries; any +// os-interface code that is reasonably useful and broadly applicable can go +// here. Including utility routines that merely build on other os code. +// +// We assume the general case is that users do not care, and do not want to +// be made to care, which operating system they are on. While they may want +// to special case various special cases -- and so we will not _hide_ the +// facts of which OS the user is on -- they should be given the opportunity +// to write OS-ignorant code by default. + +import libc::{c_char, c_void, c_int, c_uint, size_t, mode_t, FILE}; +import libc::{close, fclose}; + +import getcwd = rustrt::rust_getcwd; +import consts::*; + +export close, fclose; +export env, getenv, setenv, fdopen, pipe; +export getcwd, dll_filename, self_exe_path; +export exe_suffix, dll_suffix, sysname; +export homedir, list_dir, path_is_dir, path_exists; + +native mod rustrt { + fn rust_env_pairs() -> [str]; + fn rust_getcwd() -> str; + fn rust_path_is_dir(path: str::sbuf) -> c_int; + fn rust_path_exists(path: str::sbuf) -> c_int; + fn rust_list_files(path: str) -> [str]; +} + + +fn env() -> [(str,str)] { + let pairs = []; + for p in rustrt::rust_env_pairs() { + let vs = str::splitn_char(p, '=', 1u); + assert vec::len(vs) == 2u; + pairs += [(vs[0], vs[1])]; + } + ret pairs; +} + +fn as_c_charp<T>(s: str, f: fn(*c_char) -> T) -> T { + str::as_buf(s) {|b| f(b as *c_char) } +} + +// FIXME: UTF-16-ify this and revive win32 Unicode variant. +fn getenv(n: str) -> option<str> unsafe { + let s = as_c_charp(n, libc::getenv); + ret if unsafe::reinterpret_cast(s) == 0 { + option::none::<str> + } else { + let s = unsafe::reinterpret_cast(s); + option::some::<str>(str::from_cstr(s)) + }; +} + +#[cfg(target_os = "linux")] +#[cfg(target_os = "macos")] +#[cfg(target_os = "freebsd")] +fn setenv(n: str, v: str) { + + // FIXME: remove this when export globs work properly. + import libc::funcs::posix01::unistd::setenv; + + as_c_charp(n) {|nbuf| + as_c_charp(v) {|vbuf| + setenv(nbuf, vbuf, 1i32); + } + } +} + + +#[cfg(target_os = "win32")] +// FIXME: UTF-16-ify this and switch to the W version. +fn setenv(n: str, v: str) { + // FIXME: remove imports when export globs work properly. + import libc::funcs::extra::kernel32; + as_c_charp(n) {|nbuf| + as_c_charp(v) {|vbuf| + kernel32::SetEnvironmentVariableA(nbuf, vbuf); + } + } +} + + +fn fdopen(fd: c_int) -> *FILE { + ret as_c_charp("r") {|modebuf| + libc::fdopen(fd, modebuf) + }; +} + + + +#[cfg(target_os = "linux")] +#[cfg(target_os = "freebsd")] +#[cfg(target_os = "macos")] +fn pipe() -> {in: c_int, out: c_int} { + let fds = {mutable in: 0 as c_int, + mutable out: 0 as c_int }; + assert (libc::pipe(ptr::mut_addr_of(fds.in)) == (0 as c_int)); + ret {in: fds.in, out: fds.out}; +} + + + +#[cfg(target_os = "win32")] +fn pipe() -> {in: c_int, out: c_int} { + // FIXME: remove this when export globs work properly. + import libc::consts::os::extra::*; + // Windows pipes work subtly differently than unix pipes, and their + // inheritance has to be handled in a different way that I do not fully + // understand. Here we explicitly make the pipe non-inheritable, which + // means to pass it to a subprocess they need to be duplicated first, as + // in rust_run_program. + let fds = { mutable in: 0 as c_int, + mutable out: 0 as c_int }; + let res = libc::pipe(ptr::mut_addr_of(fds.in), + 1024 as c_uint, + (O_BINARY | O_NOINHERIT) as c_int); + assert (res == 0 as c_int); + assert (fds.in != -1 as c_int && fds.in != 0 as c_int); + assert (fds.out != -1 as c_int && fds.in != 0 as c_int); + ret {in: fds.in, out: fds.out}; +} + + +fn dll_filename(base: str) -> str { + ret pre() + base + dll_suffix(); + + #[cfg(target_os = "macos")] + #[cfg(target_os = "linux")] + #[cfg(target_os = "freebsd")] + fn pre() -> str { "lib" } + + #[cfg(target_os = "win32")] + fn pre() -> str { "" } +} + +fn self_exe_path() -> option<path> unsafe { + let bufsize = 1023u; + let buf = vec::init_elt_mut(bufsize, 0u8 as c_char); + // FIXME: This does not handle the case where the buffer is too small + ret vec::as_mut_buf(buf) {|pbuf| + if load_self(pbuf as *mutable c_char, bufsize as c_uint) { + let path = str::from_cstr(pbuf as str::sbuf); + option::some(path::dirname(path) + path::path_sep()) + } else { + option::none + } + }; + + #[cfg(target_os = "freebsd")] + unsafe fn load_self(pth: *mutable c_char, plen: c_uint) -> bool { + // FIXME: remove imports when export globs work properly. + import libc::funcs::bsd44::*; + import libc::consts::os::extra::*; + let mib = [CTL_KERN as c_int, + KERN_PROC as c_int, + KERN_PROC_PATHNAME as c_int, -1 as c_int]; + ret sysctl(vec::unsafe::to_ptr(mib), vec::len(mib) as c_uint, + pth as *mutable c_void, ptr::mut_addr_of(plen), + ptr::null(), 0u as size_t) + == (0 as c_int); + } + + #[cfg(target_os = "linux")] + unsafe fn load_self(pth: *mutable c_char, plen: c_uint) -> bool { + // FIXME: remove imports when export globs work properly. + import libc::funcs::posix01::unistd::readlink; + as_c_charp("/proc/self/exe") { |proc_self_buf| + ret readlink(proc_self_buf, pth, plen as size_t) != -1; + } + } + + #[cfg(target_os = "win32")] + unsafe fn load_self(pth: *mutable c_char, plen: c_uint) -> bool { + // FIXME: remove imports when export globs work properly. + import libc::types::os::arch::extra::*; + import libc::funcs::extra::kernel32; + ret kernel32::GetModuleFileNameA(0u, pth, plen) != (0 as DWORD); + } + + #[cfg(target_os = "macos")] + unsafe fn load_self(pth: *mutable c_char, plen: c_uint) -> bool { + // FIXME: remove imports when export globs work properly. + import libc::funcs::extra::*; + let mplen = plen; + ret _NSGetExecutablePath(pth, ptr::mut_addr_of(mplen)) + == (0 as c_int); + } + +} + + +/* +Function: homedir + +Returns the path to the user's home directory, if known. + +On Unix, returns the value of the "HOME" environment variable if it is set and +not equal to the empty string. + +On Windows, returns the value of the "HOME" environment variable if it is set +and not equal to the empty string. Otherwise, returns the value of the +"USERPROFILE" environment variable if it is set and not equal to the empty +string. + +Otherwise, homedir returns option::none. +*/ +fn homedir() -> option<path> { + ret alt getenv("HOME") { + some(p) { + if !str::is_empty(p) { + some(p) + } else { + secondary() + } + } + none { + secondary() + } + }; + + #[cfg(target_os = "linux")] + #[cfg(target_os = "macos")] + #[cfg(target_os = "freebsd")] + fn secondary() -> option<path> { + none + } + + #[cfg(target_os = "win32")] + fn secondary() -> option<path> { + option::maybe(none, getenv("USERPROFILE")) {|p| + if !str::is_empty(p) { + some(p) + } else { + none + } + } + } +} + + + +/* +Function: path_is_dir + +Indicates whether a path represents a directory. +*/ +fn path_is_dir(p: path) -> bool { + ret str::as_buf(p, {|buf| + rustrt::rust_path_is_dir(buf) != 0 as c_int + }); +} + +/* +Function: path_exists + +Indicates whether a path exists. +*/ +fn path_exists(p: path) -> bool { + ret str::as_buf(p, {|buf| + rustrt::rust_path_exists(buf) != 0 as c_int + }); +} + +// FIXME: under Windows, we should prepend the current drive letter to paths +// that start with a slash. +/* +Function: make_absolute + +Convert a relative path to an absolute path + +If the given path is relative, return it prepended with the current working +directory. If the given path is already an absolute path, return it +as is. +*/ +// NB: this is here rather than in path because it is a form of environment +// querying; what it does depends on the process working directory, not just +// the input paths. +fn make_absolute(p: path) -> path { + if path::path_is_absolute(p) { + p + } else { + path::connect(getcwd(), p) + } +} + + +/* +Function: make_dir + +Creates a directory at the specified path. +*/ +fn make_dir(p: path, mode: c_int) -> bool { + ret mkdir(p, mode); + + #[cfg(target_os = "win32")] + fn mkdir(_p: path, _mode: c_int) -> bool unsafe { + // FIXME: turn mode into something useful? + ret as_c_charp(_p, {|buf| + // FIXME: remove imports when export globs work properly. + import libc::types::os::arch::extra::*; + import libc::funcs::extra::kernel32; + kernel32::CreateDirectoryA( + buf, unsafe::reinterpret_cast(0)) != (0 as BOOL) + }); + } + + #[cfg(target_os = "linux")] + #[cfg(target_os = "macos")] + #[cfg(target_os = "freebsd")] + fn mkdir(p: path, mode: c_int) -> bool { + ret as_c_charp(p) {|c| + libc::mkdir(c, mode as mode_t) == (0 as c_int) + }; + } +} + +/* +Function: list_dir + +Lists the contents of a directory. +*/ +fn list_dir(p: path) -> [str] { + + #[cfg(target_os = "linux")] + #[cfg(target_os = "macos")] + #[cfg(target_os = "freebsd")] + fn star() -> str { "" } + + #[cfg(target_os = "win32")] + fn star() -> str { "*" } + + let p = p; + let pl = str::len(p); + if pl == 0u || (p[pl - 1u] as char != path::consts::path_sep + && p[pl - 1u] as char != path::consts::alt_path_sep) { + p += path::path_sep(); + } + let full_paths: [str] = []; + for filename: str in rustrt::rust_list_files(p + star()) { + if !str::eq(filename, ".") { + if !str::eq(filename, "..") { + full_paths += [p + filename]; + } + } + } + ret full_paths; +} + +/* +Function: remove_dir + +Removes a directory at the specified path. +*/ +fn remove_dir(p: path) -> bool { + ret rmdir(p); + + #[cfg(target_os = "win32")] + fn rmdir(p: path) -> bool { + // FIXME: remove imports when export globs work properly. + import libc::funcs::extra::kernel32; + import libc::types::os::arch::extra::*; + ret as_c_charp(p) {|buf| + kernel32::RemoveDirectoryA(buf) != (0 as BOOL) + }; + } + + #[cfg(target_os = "linux")] + #[cfg(target_os = "macos")] + #[cfg(target_os = "freebsd")] + fn rmdir(p: path) -> bool { + ret as_c_charp(p) {|buf| + libc::rmdir(buf) == (0 as c_int) + }; + } +} + +fn change_dir(p: path) -> bool { + ret chdir(p); + + #[cfg(target_os = "win32")] + fn chdir(p: path) -> bool { + // FIXME: remove imports when export globs work properly. + import libc::funcs::extra::kernel32; + import libc::types::os::arch::extra::*; + ret as_c_charp(p) {|buf| + kernel32::SetCurrentDirectoryA(buf) != (0 as BOOL) + }; + } + + #[cfg(target_os = "linux")] + #[cfg(target_os = "macos")] + #[cfg(target_os = "freebsd")] + fn chdir(p: path) -> bool { + ret as_c_charp(p) {|buf| + libc::chdir(buf) == (0 as c_int) + }; + } +} + + + +#[cfg(target_os = "macos")] +mod consts { + fn sysname() -> str { "macos" } + fn exe_suffix() -> str { "" } + fn dll_suffix() -> str { ".dylib" } +} + +#[cfg(target_os = "freebsd")] +mod consts { + fn sysname() -> str { "freebsd" } + fn exe_suffix() -> str { "" } + fn dll_suffix() -> str { ".so" } +} + +#[cfg(target_os = "linux")] +mod consts { + fn sysname() -> str { "linux" } + fn exe_suffix() -> str { "" } + fn dll_suffix() -> str { ".so" } +} + +#[cfg(target_os = "win32")] +mod consts { + fn sysname() -> str { "win32" } + fn exe_suffix() -> str { ".exe" } + fn dll_suffix() -> str { ".dll" } +} + + + + +#[cfg(test)] +mod tests { + + import os = new_os; + + #[test] + fn test() { + assert (!path::path_is_absolute("test-path")); + + log(debug, "Current working directory: " + getcwd()); + + log(debug, make_absolute("test-path")); + log(debug, make_absolute("/usr/bin")); + } + + #[test] + #[cfg(target_os = "linux")] + #[cfg(target_os = "macos")] + #[cfg(target_os = "freebsd")] + fn homedir() { + let oldhome = getenv("HOME"); + + setenv("HOME", "/home/MountainView"); + assert os::homedir() == some("/home/MountainView"); + + setenv("HOME", ""); + assert os::homedir() == none; + + option::may(oldhome, {|s| setenv("HOME", s)}); + } + + #[test] + #[cfg(target_os = "win32")] + fn homedir() { + + let oldhome = getenv("HOME"); + let olduserprofile = getenv("USERPROFILE"); + + setenv("HOME", ""); + setenv("USERPROFILE", ""); + + assert os::homedir() == none; + + setenv("HOME", "/home/MountainView"); + assert os::homedir() == some("/home/MountainView"); + + setenv("HOME", ""); + + setenv("USERPROFILE", "/home/MountainView"); + assert os::homedir() == some("/home/MountainView"); + + setenv("USERPROFILE", "/home/MountainView"); + assert os::homedir() == some("/home/MountainView"); + + setenv("HOME", "/home/MountainView"); + setenv("USERPROFILE", "/home/PaloAlto"); + assert os::homedir() == some("/home/MountainView"); + + option::may(oldhome, {|s| setenv("HOME", s)}); + option::may(olduserprofile, {|s| setenv("USERPROFILE", s)}); + } + + // Issue #712 + #[test] + fn test_list_dir_no_invalid_memory_access() { os::list_dir("."); } + + #[test] + fn list_dir() { + let dirs = os::list_dir("."); + // Just assuming that we've got some contents in the current directory + assert (vec::len(dirs) > 0u); + + for dir in dirs { log(debug, dir); } + } + + #[test] + fn path_is_dir() { + assert (os::path_is_dir(".")); + assert (!os::path_is_dir("test/stdtest/fs.rs")); + } + + #[test] + fn path_exists() { + assert (os::path_exists(".")); + assert (!os::path_exists("test/nonexistent-bogus-path")); + } + +} \ No newline at end of file diff --git a/src/libcore/path.rs b/src/libcore/path.rs new file mode 100644 index 00000000000..d5a80c39c7f --- /dev/null +++ b/src/libcore/path.rs @@ -0,0 +1,505 @@ +/* +Module: path + +Path data type and helper functions. +*/ + +#[cfg(target_os = "macos")] +#[cfg(target_os = "freebsd")] +#[cfg(target_os = "linux")] +mod consts { + const path_sep: char = '/'; + const alt_path_sep: char = '/'; +} + +#[cfg(target_os = "win32")] +mod consts { + const path_sep: char = '/'; + const alt_path_sep: char = '\\'; +} + +/* +Function: path_is_absolute + +Indicates whether a path is absolute. + +A path is considered absolute if it begins at the filesystem root ("/") or, +on Windows, begins with a drive letter. +*/ +#[cfg(target_os = "macos")] +#[cfg(target_os = "freebsd")] +#[cfg(target_os = "linux")] +fn path_is_absolute(p: path) -> bool { + str::char_at(p, 0u) == '/' +} + +#[cfg(target_os = "win32")] +fn path_is_absolute(p: str) -> bool { + ret str::char_at(p, 0u) == '/' || + str::char_at(p, 1u) == ':' + && (str::char_at(p, 2u) == consts::path_sep + || str::char_at(p, 2u) == consts::alt_path_sep); +} + + + +/* +Function: path_sep + +Get the default path separator for the host platform +*/ +fn path_sep() -> str { ret str::from_char(consts::path_sep); } + +// FIXME: This type should probably be constrained +/* +Type: path + +A path or fragment of a filesystem path +*/ +type path = str; + +fn split_dirname_basename (pp: path) -> {dirname: str, basename: str} { + alt str::rfind(pp, {|ch| + ch == consts::path_sep || ch == consts::alt_path_sep + }) { + some(i) { + {dirname: str::slice(pp, 0u, i), + basename: str::slice(pp, i + 1u, str::len(pp))} + } + none { {dirname: ".", basename: pp} } + } +} + +/* +Function: dirname + +Get the directory portion of a path + +Returns all of the path up to, but excluding, the final path separator. +The dirname of "/usr/share" will be "/usr", but the dirname of +"/usr/share/" is "/usr/share". + +If the path is not prefixed with a directory, then "." is returned. +*/ +fn dirname(pp: path) -> path { + ret split_dirname_basename(pp).dirname; +} + +/* +Function: basename + +Get the file name portion of a path + +Returns the portion of the path after the final path separator. +The basename of "/usr/share" will be "share". If there are no +path separators in the path then the returned path is identical to +the provided path. If an empty path is provided or the path ends +with a path separator then an empty path is returned. +*/ +fn basename(pp: path) -> path { + ret split_dirname_basename(pp).basename; +} + +// FIXME: Need some typestate to avoid bounds check when len(pre) == 0 +/* +Function: connect + +Connects to path segments + +Given paths `pre` and `post, removes any trailing path separator on `pre` and +any leading path separator on `post`, and returns the concatenation of the two +with a single path separator between them. +*/ + +fn connect(pre: path, post: path) -> path unsafe { + let pre_ = pre; + let post_ = post; + let sep = consts::path_sep as u8; + let pre_len = str::len(pre); + let post_len = str::len(post); + if pre_len > 1u && pre[pre_len-1u] == sep { str::unsafe::pop_byte(pre_); } + if post_len > 1u && post[0] == sep { str::unsafe::shift_byte(post_); } + ret pre_ + path_sep() + post_; +} + +/* +Function: connect_many + +Connects a vector of path segments into a single path. + +Inserts path separators as needed. +*/ +fn connect_many(paths: [path]) -> path { + ret if vec::len(paths) == 1u { + paths[0] + } else { + let rest = vec::slice(paths, 1u, vec::len(paths)); + connect(paths[0], connect_many(rest)) + } +} + +/* +Function: split + +Split a path into it's individual components + +Splits a given path by path separators and returns a vector containing +each piece of the path. On Windows, if the path is absolute then +the first element of the returned vector will be the drive letter +followed by a colon. +*/ +fn split(p: path) -> [path] { + str::split_nonempty(p, {|c| + c == consts::path_sep || c == consts::alt_path_sep + }) +} + +/* +Function: splitext + +Split a path into a pair of strings with the first element being the filename +without the extension and the second being either empty or the file extension +including the period. Leading periods in the basename are ignored. If the +path includes directory components then they are included in the filename part +of the result pair. +*/ +fn splitext(p: path) -> (str, str) { + if str::is_empty(p) { ("", "") } + else { + let parts = str::split_char(p, '.'); + if vec::len(parts) > 1u { + let base = str::connect(vec::init(parts), "."); + let ext = "." + vec::last_total(parts); + + fn is_dotfile(base: str) -> bool { + str::is_empty(base) + || str::ends_with( + base, str::from_char(consts::path_sep)) + || str::ends_with( + base, str::from_char(consts::alt_path_sep)) + } + + fn ext_contains_sep(ext: str) -> bool { + vec::len(split(ext)) > 1u + } + + fn no_basename(ext: str) -> bool { + str::ends_with( + ext, str::from_char(consts::path_sep)) + || str::ends_with( + ext, str::from_char(consts::alt_path_sep)) + } + + if is_dotfile(base) + || ext_contains_sep(ext) + || no_basename(ext) { + (p, "") + } else { + (base, ext) + } + } else { + (p, "") + } + } +} + +/* +Function: normalize + +Removes extra "." and ".." entries from paths. + +Does not follow symbolic links. +*/ +fn normalize(p: path) -> path { + let s = split(p); + let s = strip_dots(s); + let s = rollup_doubledots(s); + + let s = if check vec::is_not_empty(s) { + connect_many(s) + } else { + "" + }; + let s = reabsolute(p, s); + let s = reterminate(p, s); + + let s = if str::len(s) == 0u { + "." + } else { + s + }; + + ret s; + + fn strip_dots(s: [path]) -> [path] { + vec::filter_map(s, { |elem| + if elem == "." { + option::none + } else { + option::some(elem) + } + }) + } + + fn rollup_doubledots(s: [path]) -> [path] { + if vec::is_empty(s) { + ret []; + } + + let t = []; + let i = vec::len(s); + let skip = 0; + do { + i -= 1u; + if s[i] == ".." { + skip += 1; + } else { + if skip == 0 { + t += [s[i]]; + } else { + skip -= 1; + } + } + } while i != 0u; + let t = vec::reversed(t); + while skip > 0 { + t += [".."]; + skip -= 1; + } + ret t; + } + + #[cfg(target_os = "linux")] + #[cfg(target_os = "macos")] + #[cfg(target_os = "freebsd")] + fn reabsolute(orig: path, new: path) -> path { + if path_is_absolute(orig) { + path_sep() + new + } else { + new + } + } + + #[cfg(target_os = "win32")] + fn reabsolute(orig: path, new: path) -> path { + if path_is_absolute(orig) && orig[0] == consts::path_sep as u8 { + str::from_char(consts::path_sep) + new + } else { + new + } + } + + fn reterminate(orig: path, new: path) -> path { + let last = orig[str::len(orig) - 1u]; + if last == consts::path_sep as u8 + || last == consts::path_sep as u8 { + ret new + path_sep(); + } else { + ret new; + } + } +} + +#[cfg(test)] +mod tests { + #[test] + fn test_connect() { + let slash = path_sep(); + log(error, connect("a", "b")); + assert (connect("a", "b") == "a" + slash + "b"); + assert (connect("a" + slash, "b") == "a" + slash + "b"); + } + + fn ps() -> str { + path_sep() + } + + fn aps() -> str { + "/" + } + + #[test] + fn split1() { + let actual = split("a" + ps() + "b"); + let expected = ["a", "b"]; + assert actual == expected; + } + + #[test] + fn split2() { + let actual = split("a" + aps() + "b"); + let expected = ["a", "b"]; + assert actual == expected; + } + + #[test] + fn split3() { + let actual = split(ps() + "a" + ps() + "b"); + let expected = ["a", "b"]; + assert actual == expected; + } + + #[test] + fn split4() { + let actual = split("a" + ps() + "b" + aps() + "c"); + let expected = ["a", "b", "c"]; + assert actual == expected; + } + + #[test] + fn normalize1() { + let actual = normalize("a/b/.."); + let expected = "a"; + assert actual == expected; + } + + #[test] + fn normalize2() { + let actual = normalize("/a/b/.."); + let expected = "/a"; + assert actual == expected; + } + + #[test] + fn normalize3() { + let actual = normalize("a/../b"); + let expected = "b"; + assert actual == expected; + } + + #[test] + fn normalize4() { + let actual = normalize("/a/../b"); + let expected = "/b"; + assert actual == expected; + } + + #[test] + fn normalize5() { + let actual = normalize("a/."); + let expected = "a"; + assert actual == expected; + } + + #[test] + fn normalize6() { + let actual = normalize("a/./b/"); + let expected = "a/b/"; + assert actual == expected; + } + + #[test] + fn normalize7() { + let actual = normalize("a/.."); + let expected = "."; + assert actual == expected; + } + + #[test] + fn normalize8() { + let actual = normalize("../../.."); + let expected = "../../.."; + assert actual == expected; + } + + #[test] + fn normalize9() { + let actual = normalize("a/b/../../.."); + let expected = ".."; + assert actual == expected; + } + + #[test] + fn normalize10() { + let actual = normalize("/a/b/c/../d/./../../e/"); + let expected = "/a/e/"; + log(error, actual); + assert actual == expected; + } + + #[test] + fn normalize11() { + let actual = normalize("/a/.."); + let expected = "/"; + assert actual == expected; + } + + #[test] + #[cfg(target_os = "win32")] + fn normalize12() { + let actual = normalize("C:/whatever"); + let expected = "C:/whatever"; + log(error, actual); + assert actual == expected; + } + + #[test] + #[cfg(target_os = "win32")] + fn path_is_absolute_win32() { + assert path_is_absolute("C:/whatever"); + } + + #[test] + fn splitext_empty() { + let (base, ext) = splitext(""); + assert base == ""; + assert ext == ""; + } + + #[test] + fn splitext_ext() { + let (base, ext) = splitext("grum.exe"); + assert base == "grum"; + assert ext == ".exe"; + } + + #[test] + fn splitext_noext() { + let (base, ext) = splitext("grum"); + assert base == "grum"; + assert ext == ""; + } + + #[test] + fn splitext_dotfile() { + let (base, ext) = splitext(".grum"); + assert base == ".grum"; + assert ext == ""; + } + + #[test] + fn splitext_path_ext() { + let (base, ext) = splitext("oh/grum.exe"); + assert base == "oh/grum"; + assert ext == ".exe"; + } + + #[test] + fn splitext_path_noext() { + let (base, ext) = splitext("oh/grum"); + assert base == "oh/grum"; + assert ext == ""; + } + + #[test] + fn splitext_dot_in_path() { + let (base, ext) = splitext("oh.my/grum"); + assert base == "oh.my/grum"; + assert ext == ""; + } + + #[test] + fn splitext_nobasename() { + let (base, ext) = splitext("oh.my/"); + assert base == "oh.my/"; + assert ext == ""; + } +} + +// Local Variables: +// mode: rust; +// fill-column: 78; +// indent-tabs-mode: nil +// c-basic-offset: 4 +// buffer-file-coding-system: utf-8-unix +// End: diff --git a/src/libcore/str.rs b/src/libcore/str.rs index 16f5e2370be..5be3cb64037 100644 --- a/src/libcore/str.rs +++ b/src/libcore/str.rs @@ -1390,7 +1390,7 @@ fn as_buf<T>(s: str, f: fn(sbuf) -> T) -> T unsafe { /* Type: sbuf -An unsafe buffer of bytes. Corresponds to a C char pointer. +An unsafe buffer of bytes. */ type sbuf = *u8; diff --git a/src/libcore/vec.rs b/src/libcore/vec.rs index a101f5f3ba2..27163e9f190 100644 --- a/src/libcore/vec.rs +++ b/src/libcore/vec.rs @@ -996,6 +996,10 @@ fn as_buf<E,T>(v: [const E], f: fn(*E) -> T) -> T unsafe { let buf = unsafe::to_ptr(v); f(buf) } +fn as_mut_buf<E,T>(v: [mutable E], f: fn(*mutable E) -> T) -> T unsafe { + let buf = unsafe::to_ptr(v) as *mutable E; f(buf) +} + impl vec_len<T> for [T] { fn len() -> uint { len(self) } } |
