convert all to use sys/linux over sys/unix; new implementations for pipe, process and env

This commit is contained in:
jason
2024-06-27 17:14:48 -04:00
parent f22754fc90
commit f24f72c280
12 changed files with 1269 additions and 543 deletions
+210 -9
View File
@@ -2,29 +2,230 @@
package os2
import "base:runtime"
import "base:intrinsics"
import "core:sync"
import "core:slice"
import "core:strings"
// TODO: IF NO_CRT:
// Override the libc environment functions' weak linkage to
// allow us to interact with 3rd party code that DOES link
// to libc. Otherwise, our environment can be out of sync.
// ELSE:
// Just use the libc.
NOT_FOUND :: -1
// the environment is a 0 delimited list of <key>=<value> strings
_env: [dynamic]string
_env_mutex: sync.Mutex
// We need to be able to figure out if the environment variable
// is contained in the original environment or not. This also
// serves as a flag to determine if we have built _env.
_org_env_begin: uintptr
_org_env_end: uintptr
// Returns value + index location into _env
// or -1 if not found
_lookup :: proc(key: string) -> (value: string, idx: int) {
sync.mutex_lock(&_env_mutex)
defer sync.mutex_unlock(&_env_mutex)
for entry, i in _env {
if k, v := _kv_from_entry(entry); k == key {
return v, i
}
}
return "", -1
}
_lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) {
//TODO
if _org_env_begin == 0 {
_build_env()
}
if v, idx := _lookup(key); idx != -1 {
found = true
value, _ = clone_string(v, allocator)
}
return
}
_set_env :: proc(key, value: string) -> bool {
//TODO
return false
_set_env :: proc(key, v_new: string) -> bool {
if _org_env_begin == 0 {
_build_env()
}
// all key values are stored as "key=value\x00"
kv_size := len(key) + len(v_new) + 2
if v_curr, idx := _lookup(key); idx != NOT_FOUND {
if v_curr == v_new {
return true
}
sync.mutex_lock(&_env_mutex)
defer sync.mutex_unlock(&_env_mutex)
unordered_remove(&_env, idx)
if !_is_in_org_env(v_curr) {
// We allocated this key-value. Possibly resize and
// overwrite the value only. Otherwise, treat as if it
// wasn't in the environment in the first place.
k_addr, v_addr := _kv_addr_from_val(v_curr, key)
if len(v_new) > len(v_curr) {
k_addr = ([^]u8)(heap_resize(k_addr, kv_size))
if k_addr == nil {
return false
}
v_addr = &k_addr[len(key) + 1]
}
intrinsics.mem_copy_non_overlapping(v_addr, raw_data(v_new), len(v_new))
v_addr[len(v_new)] = 0
append(&_env, string(k_addr[:kv_size]))
return true
}
}
k_addr := ([^]u8)(heap_alloc(kv_size))
if k_addr == nil {
return false
}
intrinsics.mem_copy_non_overlapping(k_addr, raw_data(key), len(key))
k_addr[len(key)] = '='
val_slice := k_addr[len(key) + 1:]
intrinsics.mem_copy_non_overlapping(&val_slice[0], raw_data(v_new), len(v_new))
val_slice[len(v_new)] = 0
sync.mutex_lock(&_env_mutex)
append(&_env, string(k_addr[:kv_size - 1]))
sync.mutex_unlock(&_env_mutex)
return true
}
_unset_env :: proc(key: string) -> bool {
//TODO
return false
if _org_env_begin == 0 {
_build_env()
}
v: string
i: int
if v, i = _lookup(key); i == -1 {
return false
}
sync.mutex_lock(&_env_mutex)
unordered_remove(&_env, i)
sync.mutex_unlock(&_env_mutex)
if _is_in_org_env(v) {
return true
}
// if we got this far, the envrionment variable
// existed AND was allocated by us.
k_addr, _ := _kv_addr_from_val(v, key)
heap_free(k_addr)
return true
}
_clear_env :: proc() {
//TODO
sync.mutex_lock(&_env_mutex)
defer sync.mutex_unlock(&_env_mutex)
for kv in _env {
if !_is_in_org_env(kv) {
heap_free(raw_data(kv))
}
}
clear(&_env)
// nothing resides in the original environment either
_org_env_begin = ~uintptr(0)
_org_env_end = ~uintptr(0)
}
_environ :: proc(allocator: runtime.Allocator) -> []string {
//TODO
return nil
if _org_env_begin == 0 {
_build_env()
}
env := make([]string, len(_env), allocator)
sync.mutex_lock(&_env_mutex)
defer sync.mutex_unlock(&_env_mutex)
for entry, i in _env {
env[i], _ = clone_string(entry, allocator)
}
return env
}
// The entire environment is stored as 0 terminated strings,
// so there is no need to clone/free individual variables
export_cstring_environment :: proc(allocator: runtime.Allocator) -> []cstring {
if _org_env_begin == 0 {
// The environment has not been modified, so we can just
// send the original environment
org_env := _get_original_env()
n: int
for ; org_env[n] != nil; n += 1 {}
return slice.clone(org_env[:n + 1], allocator)
}
// NOTE: already terminated by nil pointer via + 1
env := make([]cstring, len(_env) + 1, allocator)
sync.mutex_lock(&_env_mutex)
defer sync.mutex_unlock(&_env_mutex)
for entry, i in _env {
env[i] = cstring(raw_data(entry))
}
return env
}
_build_env :: proc() {
sync.mutex_lock(&_env_mutex)
defer sync.mutex_unlock(&_env_mutex)
if _org_env_begin != 0 {
return
}
_env = make(type_of(_env), heap_allocator())
cstring_env := _get_original_env()
_org_env_begin = uintptr(rawptr(cstring_env[0]))
for i := 0; cstring_env[i] != nil; i += 1 {
bytes := ([^]u8)(cstring_env[i])
n := len(cstring_env[i])
_org_env_end = uintptr(&bytes[n])
append(&_env, string(bytes[:n]))
}
}
_get_original_env :: #force_inline proc() -> [^]cstring {
// essentially &argv[argc] which should be a nil pointer!
#no_bounds_check env: [^]cstring = &runtime.args__[len(runtime.args__)]
assert(env[0] == nil)
return &env[1]
}
_kv_from_entry :: #force_inline proc(entry: string) -> (k, v: string) {
eq_idx := strings.index_byte(entry, '=')
if eq_idx == -1 {
return entry, ""
}
return entry[:eq_idx], entry[eq_idx + 1:]
}
_kv_addr_from_val :: #force_inline proc(val: string, key: string) -> ([^]u8, [^]u8) {
v_addr := raw_data(val)
k_addr := ([^]u8)(&v_addr[-(len(key) + 1)])
return k_addr, v_addr
}
_is_in_org_env :: #force_inline proc(env_data: string) -> bool {
addr := uintptr(raw_data(env_data))
return addr >= _org_env_begin && addr < _org_env_end
}
+16
View File
@@ -99,3 +99,19 @@ error_string :: proc(ferr: Error) -> string {
return "unknown error"
}
print_error :: proc(ferr: Error, msg: string) {
TEMP_ALLOCATOR_GUARD()
err_str := error_string(ferr)
// msg + ": " + err_str + '\n'
length := len(msg) + 2 + len(err_str) + 1
buf := make([]u8, length, temp_allocator())
copy(buf, msg)
buf[len(msg)] = ':'
buf[len(msg) + 1] = ' '
copy(buf[len(msg) + 2:], err_str)
buf[length - 1] = '\n'
write(stderr, buf)
}
+153 -134
View File
@@ -1,145 +1,164 @@
//+private
package os2
import "core:sys/unix"
import "core:sys/linux"
EPERM :: 1
ENOENT :: 2
ESRCH :: 3
EINTR :: 4
EIO :: 5
ENXIO :: 6
EBADF :: 9
EAGAIN :: 11
ENOMEM :: 12
EACCES :: 13
EFAULT :: 14
EEXIST :: 17
ENODEV :: 19
ENOTDIR :: 20
EISDIR :: 21
EINVAL :: 22
ENFILE :: 23
EMFILE :: 24
ETXTBSY :: 26
EFBIG :: 27
ENOSPC :: 28
ESPIPE :: 29
EROFS :: 30
EPIPE :: 32
ERANGE :: 34 /* Result too large */
EDEADLK :: 35 /* Resource deadlock would occur */
ENAMETOOLONG :: 36 /* File name too long */
ENOLCK :: 37 /* No record locks available */
ENOSYS :: 38 /* Invalid system call number */
ENOTEMPTY :: 39 /* Directory not empty */
ELOOP :: 40 /* Too many symbolic links encountered */
EWOULDBLOCK :: EAGAIN /* Operation would block */
ENOMSG :: 42 /* No message of desired type */
EIDRM :: 43 /* Identifier removed */
ECHRNG :: 44 /* Channel number out of range */
EL2NSYNC :: 45 /* Level 2 not synchronized */
EL3HLT :: 46 /* Level 3 halted */
EL3RST :: 47 /* Level 3 reset */
ELNRNG :: 48 /* Link number out of range */
EUNATCH :: 49 /* Protocol driver not attached */
ENOCSI :: 50 /* No CSI structure available */
EL2HLT :: 51 /* Level 2 halted */
EBADE :: 52 /* Invalid exchange */
EBADR :: 53 /* Invalid request descriptor */
EXFULL :: 54 /* Exchange full */
ENOANO :: 55 /* No anode */
EBADRQC :: 56 /* Invalid request code */
EBADSLT :: 57 /* Invalid slot */
EDEADLOCK :: EDEADLK
EBFONT :: 59 /* Bad font file format */
ENOSTR :: 60 /* Device not a stream */
ENODATA :: 61 /* No data available */
ETIME :: 62 /* Timer expired */
ENOSR :: 63 /* Out of streams resources */
ENONET :: 64 /* Machine is not on the network */
ENOPKG :: 65 /* Package not installed */
EREMOTE :: 66 /* Object is remote */
ENOLINK :: 67 /* Link has been severed */
EADV :: 68 /* Advertise error */
ESRMNT :: 69 /* Srmount error */
ECOMM :: 70 /* Communication error on send */
EPROTO :: 71 /* Protocol error */
EMULTIHOP :: 72 /* Multihop attempted */
EDOTDOT :: 73 /* RFS specific error */
EBADMSG :: 74 /* Not a data message */
EOVERFLOW :: 75 /* Value too large for defined data type */
ENOTUNIQ :: 76 /* Name not unique on network */
EBADFD :: 77 /* File descriptor in bad state */
EREMCHG :: 78 /* Remote address changed */
ELIBACC :: 79 /* Can not access a needed shared library */
ELIBBAD :: 80 /* Accessing a corrupted shared library */
ELIBSCN :: 81 /* .lib section in a.out corrupted */
ELIBMAX :: 82 /* Attempting to link in too many shared libraries */
ELIBEXEC :: 83 /* Cannot exec a shared library directly */
EILSEQ :: 84 /* Illegal byte sequence */
ERESTART :: 85 /* Interrupted system call should be restarted */
ESTRPIPE :: 86 /* Streams pipe error */
EUSERS :: 87 /* Too many users */
ENOTSOCK :: 88 /* Socket operation on non-socket */
EDESTADDRREQ :: 89 /* Destination address required */
EMSGSIZE :: 90 /* Message too long */
EPROTOTYPE :: 91 /* Protocol wrong type for socket */
ENOPROTOOPT :: 92 /* Protocol not available */
EPROTONOSUPPORT:: 93 /* Protocol not supported */
ESOCKTNOSUPPORT:: 94 /* Socket type not supported */
EOPNOTSUPP :: 95 /* Operation not supported on transport endpoint */
EPFNOSUPPORT :: 96 /* Protocol family not supported */
EAFNOSUPPORT :: 97 /* Address family not supported by protocol */
EADDRINUSE :: 98 /* Address already in use */
EADDRNOTAVAIL :: 99 /* Cannot assign requested address */
ENETDOWN :: 100 /* Network is down */
ENETUNREACH :: 101 /* Network is unreachable */
ENETRESET :: 102 /* Network dropped connection because of reset */
ECONNABORTED :: 103 /* Software caused connection abort */
ECONNRESET :: 104 /* Connection reset by peer */
ENOBUFS :: 105 /* No buffer space available */
EISCONN :: 106 /* Transport endpoint is already connected */
ENOTCONN :: 107 /* Transport endpoint is not connected */
ESHUTDOWN :: 108 /* Cannot send after transport endpoint shutdown */
ETOOMANYREFS :: 109 /* Too many references: cannot splice */
ETIMEDOUT :: 110 /* Connection timed out */
ECONNREFUSED :: 111 /* Connection refused */
EHOSTDOWN :: 112 /* Host is down */
EHOSTUNREACH :: 113 /* No route to host */
EALREADY :: 114 /* Operation already in progress */
EINPROGRESS :: 115 /* Operation now in progress */
ESTALE :: 116 /* Stale file handle */
EUCLEAN :: 117 /* Structure needs cleaning */
ENOTNAM :: 118 /* Not a XENIX named type file */
ENAVAIL :: 119 /* No XENIX semaphores available */
EISNAM :: 120 /* Is a named type file */
EREMOTEIO :: 121 /* Remote I/O error */
EDQUOT :: 122 /* Quota exceeded */
ENOMEDIUM :: 123 /* No medium found */
EMEDIUMTYPE :: 124 /* Wrong medium type */
ECANCELED :: 125 /* Operation Canceled */
ENOKEY :: 126 /* Required key not available */
EKEYEXPIRED :: 127 /* Key has expired */
EKEYREVOKED :: 128 /* Key has been revoked */
EKEYREJECTED :: 129 /* Key was rejected by service */
EOWNERDEAD :: 130 /* Owner died */
ENOTRECOVERABLE:: 131 /* State not recoverable */
ERFKILL :: 132 /* Operation not possible due to RF-kill */
EHWPOISON :: 133 /* Memory page has hardware error */
_errno_strings : [int(max(linux.Errno)) + 1]string = {
linux.Errno.NONE = "Success",
linux.Errno.EPERM = "Operation not permitted",
linux.Errno.ENOENT = "No such file or directory",
linux.Errno.ESRCH = "No such process",
linux.Errno.EINTR = "Interrupted system call",
linux.Errno.EIO = "Input/output error",
linux.Errno.ENXIO = "No such device or address",
linux.Errno.E2BIG = "Argument list too long",
linux.Errno.ENOEXEC = "Exec format error",
linux.Errno.EBADF = "Bad file descriptor",
linux.Errno.ECHILD = "No child processes",
linux.Errno.EAGAIN = "Resource temporarily unavailable",
linux.Errno.ENOMEM = "Cannot allocate memory",
linux.Errno.EACCES = "Permission denied",
linux.Errno.EFAULT = "Bad address",
linux.Errno.ENOTBLK = "Block device required",
linux.Errno.EBUSY = "Device or resource busy",
linux.Errno.EEXIST = "File exists",
linux.Errno.EXDEV = "Invalid cross-device link",
linux.Errno.ENODEV = "No such device",
linux.Errno.ENOTDIR = "Not a directory",
linux.Errno.EISDIR = "Is a directory",
linux.Errno.EINVAL = "Invalid argument",
linux.Errno.ENFILE = "Too many open files in system",
linux.Errno.EMFILE = "Too many open files",
linux.Errno.ENOTTY = "Inappropriate ioctl for device",
linux.Errno.ETXTBSY = "Text file busy",
linux.Errno.EFBIG = "File too large",
linux.Errno.ENOSPC = "No space left on device",
linux.Errno.ESPIPE = "Illegal seek",
linux.Errno.EROFS = "Read-only file system",
linux.Errno.EMLINK = "Too many links",
linux.Errno.EPIPE = "Broken pipe",
linux.Errno.EDOM = "Numerical argument out of domain",
linux.Errno.ERANGE = "Numerical result out of range",
linux.Errno.EDEADLK = "Resource deadlock avoided",
linux.Errno.ENAMETOOLONG = "File name too long",
linux.Errno.ENOLCK = "No locks available",
linux.Errno.ENOSYS = "Function not implemented",
linux.Errno.ENOTEMPTY = "Directory not empty",
linux.Errno.ELOOP = "Too many levels of symbolic links",
41 = "Unknown Error (41)",
linux.Errno.ENOMSG = "No message of desired type",
linux.Errno.EIDRM = "Identifier removed",
linux.Errno.ECHRNG = "Channel number out of range",
linux.Errno.EL2NSYNC = "Level 2 not synchronized",
linux.Errno.EL3HLT = "Level 3 halted",
linux.Errno.EL3RST = "Level 3 reset",
linux.Errno.ELNRNG = "Link number out of range",
linux.Errno.EUNATCH = "Protocol driver not attached",
linux.Errno.ENOCSI = "No CSI structure available",
linux.Errno.EL2HLT = "Level 2 halted",
linux.Errno.EBADE = "Invalid exchange",
linux.Errno.EBADR = "Invalid request descriptor",
linux.Errno.EXFULL = "Exchange full",
linux.Errno.ENOANO = "No anode",
linux.Errno.EBADRQC = "Invalid request code",
linux.Errno.EBADSLT = "Invalid slot",
58 = "Unknown Error (58)",
linux.Errno.EBFONT = "Bad font file format",
linux.Errno.ENOSTR = "Device not a stream",
linux.Errno.ENODATA = "No data available",
linux.Errno.ETIME = "Timer expired",
linux.Errno.ENOSR = "Out of streams resources",
linux.Errno.ENONET = "Machine is not on the network",
linux.Errno.ENOPKG = "Package not installed",
linux.Errno.EREMOTE = "Object is remote",
linux.Errno.ENOLINK = "Link has been severed",
linux.Errno.EADV = "Advertise error",
linux.Errno.ESRMNT = "Srmount error",
linux.Errno.ECOMM = "Communication error on send",
linux.Errno.EPROTO = "Protocol error",
linux.Errno.EMULTIHOP = "Multihop attempted",
linux.Errno.EDOTDOT = "RFS specific error",
linux.Errno.EBADMSG = "Bad message",
linux.Errno.EOVERFLOW = "Value too large for defined data type",
linux.Errno.ENOTUNIQ = "Name not unique on network",
linux.Errno.EBADFD = "File descriptor in bad state",
linux.Errno.EREMCHG = "Remote address changed",
linux.Errno.ELIBACC = "Can not access a needed shared library",
linux.Errno.ELIBBAD = "Accessing a corrupted shared library",
linux.Errno.ELIBSCN = ".lib section in a.out corrupted",
linux.Errno.ELIBMAX = "Attempting to link in too many shared libraries",
linux.Errno.ELIBEXEC = "Cannot exec a shared library directly",
linux.Errno.EILSEQ = "Invalid or incomplete multibyte or wide character",
linux.Errno.ERESTART = "Interrupted system call should be restarted",
linux.Errno.ESTRPIPE = "Streams pipe error",
linux.Errno.EUSERS = "Too many users",
linux.Errno.ENOTSOCK = "Socket operation on non-socket",
linux.Errno.EDESTADDRREQ = "Destination address required",
linux.Errno.EMSGSIZE = "Message too long",
linux.Errno.EPROTOTYPE = "Protocol wrong type for socket",
linux.Errno.ENOPROTOOPT = "Protocol not available",
linux.Errno.EPROTONOSUPPORT = "Protocol not supported",
linux.Errno.ESOCKTNOSUPPORT = "Socket type not supported",
linux.Errno.EOPNOTSUPP = "Operation not supported",
linux.Errno.EPFNOSUPPORT = "Protocol family not supported",
linux.Errno.EAFNOSUPPORT = "Address family not supported by protocol",
linux.Errno.EADDRINUSE = "Address already in use",
linux.Errno.EADDRNOTAVAIL = "Cannot assign requested address",
linux.Errno.ENETDOWN = "Network is down",
linux.Errno.ENETUNREACH = "Network is unreachable",
linux.Errno.ENETRESET = "Network dropped connection on reset",
linux.Errno.ECONNABORTED = "Software caused connection abort",
linux.Errno.ECONNRESET = "Connection reset by peer",
linux.Errno.ENOBUFS = "No buffer space available",
linux.Errno.EISCONN = "Transport endpoint is already connected",
linux.Errno.ENOTCONN = "Transport endpoint is not connected",
linux.Errno.ESHUTDOWN = "Cannot send after transport endpoint shutdown",
linux.Errno.ETOOMANYREFS = "Too many references: cannot splice",
linux.Errno.ETIMEDOUT = "Connection timed out",
linux.Errno.ECONNREFUSED = "Connection refused",
linux.Errno.EHOSTDOWN = "Host is down",
linux.Errno.EHOSTUNREACH = "No route to host",
linux.Errno.EALREADY = "Operation already in progress",
linux.Errno.EINPROGRESS = "Operation now in progress",
linux.Errno.ESTALE = "Stale file handle",
linux.Errno.EUCLEAN = "Structure needs cleaning",
linux.Errno.ENOTNAM = "Not a XENIX named type file",
linux.Errno.ENAVAIL = "No XENIX semaphores available",
linux.Errno.EISNAM = "Is a named type file",
linux.Errno.EREMOTEIO = "Remote I/O error",
linux.Errno.EDQUOT = "Disk quota exceeded",
linux.Errno.ENOMEDIUM = "No medium found",
linux.Errno.EMEDIUMTYPE = "Wrong medium type",
linux.Errno.ECANCELED = "Operation canceled",
linux.Errno.ENOKEY = "Required key not available",
linux.Errno.EKEYEXPIRED = "Key has expired",
linux.Errno.EKEYREVOKED = "Key has been revoked",
linux.Errno.EKEYREJECTED = "Key was rejected by service",
linux.Errno.EOWNERDEAD = "Owner died",
linux.Errno.ENOTRECOVERABLE = "State not recoverable",
linux.Errno.ERFKILL = "Operation not possible due to RF-kill",
linux.Errno.EHWPOISON = "Memory page has hardware error",
}
_get_platform_error :: proc(errno: linux.Errno) -> Error {
#partial switch errno {
case .NONE:
return nil
case .EPERM:
return .Permission_Denied
case .EEXIST:
return .Exist
case .ENOENT:
return .Not_Exist
}
_get_platform_error :: proc(res: int) -> Error {
errno := unix.get_errno(res)
return Platform_Error(i32(errno))
}
_ok_or_error :: proc(res: int) -> Error {
return res >= 0 ? nil : _get_platform_error(res)
}
_error_string :: proc(errno: i32) -> string {
if errno == 0 {
return ""
if errno >= 0 && errno <= i32(max(linux.Errno)) {
return _errno_strings[errno]
}
return "Error"
return "Unknown Error"
}
+3 -4
View File
@@ -46,10 +46,9 @@ O_SPARSE :: File_Flags{.Sparse}
O_CLOEXEC :: File_Flags{.Close_On_Exec}
stdin: ^File = nil // OS-Specific
stdout: ^File = nil // OS-Specific
stderr: ^File = nil // OS-Specific
stdin: ^File = &_stdin
stdout: ^File = &_stdout
stderr: ^File = &_stderr
@(require_results)
+227 -161
View File
@@ -1,39 +1,60 @@
//+private
package os2
import "base:runtime"
import "core:io"
import "core:time"
import "core:sys/unix"
INVALID_HANDLE :: -1
_O_RDONLY :: 0o00000000
_O_WRONLY :: 0o00000001
_O_RDWR :: 0o00000002
_O_CREAT :: 0o00000100
_O_EXCL :: 0o00000200
_O_NOCTTY :: 0o00000400
_O_TRUNC :: 0o00001000
_O_APPEND :: 0o00002000
_O_NONBLOCK :: 0o00004000
_O_LARGEFILE :: 0o00100000
_O_DIRECTORY :: 0o00200000
_O_NOFOLLOW :: 0o00400000
_O_SYNC :: 0o04010000
_O_CLOEXEC :: 0o02000000
_O_PATH :: 0o10000000
_AT_FDCWD :: -100
_CSTRING_NAME_HEAP_THRESHOLD :: 512
import "base:runtime"
import "core:sys/linux"
_File :: struct {
name: string,
fd: int,
fd: linux.Fd,
allocator: runtime.Allocator,
}
_stdin : File = {
impl = {
name = "/proc/self/fd/0",
fd = 0,
allocator = _file_allocator(),
},
stream = {
procedure = _file_stream_proc,
},
}
_stdout : File = {
impl = {
name = "/proc/self/fd/1",
fd = 1,
allocator = _file_allocator(),
},
stream = {
procedure = _file_stream_proc,
},
}
_stderr : File = {
impl = {
name = "/proc/self/fd/2",
fd = 2,
allocator = _file_allocator(),
},
stream = {
procedure = _file_stream_proc,
},
}
@init
_standard_stream_init :: proc() {
// cannot define these manually because cyclic reference
_stdin.stream.data = &_stdin
_stdout.stream.data = &_stdout
_stderr.stream.data = &_stderr
}
_file_allocator :: proc() -> runtime.Allocator {
return heap_allocator()
}
_open :: proc(name: string, flags: File_Flags, perm: File_Mode) -> (f: ^File, err: Error) {
TEMP_ALLOCATOR_GUARD()
name_cstr := temp_cstring(name) or_return
@@ -41,40 +62,48 @@ _open :: proc(name: string, flags: File_Flags, perm: File_Mode) -> (f: ^File, er
// Just default to using O_NOCTTY because needing to open a controlling
// terminal would be incredibly rare. This has no effect on files while
// allowing us to open serial devices.
flags_i: int = _O_NOCTTY
sys_flags: linux.Open_Flags = {.NOCTTY}
switch flags & O_RDONLY|O_WRONLY|O_RDWR {
case O_RDONLY: flags_i = _O_RDONLY
case O_WRONLY: flags_i = _O_WRONLY
case O_RDWR: flags_i = _O_RDWR
case O_RDONLY:
case O_WRONLY: sys_flags += {.WRONLY}
case O_RDWR: sys_flags += {.RDWR}
}
if .Append in flags { flags_i |= _O_APPEND }
if .Create in flags { flags_i |= _O_CREAT }
if .Excl in flags { flags_i |= _O_EXCL }
if .Sync in flags { flags_i |= _O_SYNC }
if .Trunc in flags { flags_i |= _O_TRUNC }
if .Close_On_Exec in flags { flags_i |= _O_CLOEXEC }
if .Append in flags { sys_flags += {.APPEND} }
if .Create in flags { sys_flags += {.CREAT} }
if .Excl in flags { sys_flags += {.EXCL} }
if .Sync in flags { sys_flags += {.DSYNC} }
if .Trunc in flags { sys_flags += {.TRUNC} }
if .Close_On_Exec in flags { sys_flags += {.CLOEXEC} }
fd := unix.sys_open(name_cstr, flags_i, uint(perm))
if fd < 0 {
return nil, _get_platform_error(fd)
fd, errno := linux.open(name_cstr, sys_flags, transmute(linux.Mode)(u32(perm)))
if errno != .NONE {
return nil, _get_platform_error(errno)
}
return _new_file(uintptr(fd), name), nil
}
_new_file :: proc(fd: uintptr, _: string) -> ^File {
_new_file :: proc(fd: uintptr, _: string = "") -> ^File {
file := new(File, file_allocator())
file.impl.fd = int(fd)
file.impl.allocator = file_allocator()
file.impl.name = _get_full_path(file.impl.fd, file.impl.allocator)
file.stream = {
data = file,
procedure = _file_stream_proc,
}
_construct_file(file, fd, "")
return file
}
_construct_file :: proc(file: ^File, fd: uintptr, _: string = "") {
file^ = {
impl = {
fd = linux.Fd(fd),
allocator = file_allocator(),
name = _get_full_path(file.impl.fd, file.impl.allocator),
},
stream = {
data = file,
procedure = _file_stream_proc,
},
}
}
_destroy :: proc(f: ^File) -> Error {
if f == nil {
return nil
@@ -86,12 +115,15 @@ _destroy :: proc(f: ^File) -> Error {
_close :: proc(f: ^File) -> Error {
if f != nil {
res := unix.sys_close(f.impl.fd)
_destroy(f)
return _ok_or_error(res)
if f == nil {
return nil
}
return nil
errno := linux.close(f.impl.fd)
if errno == .EBADF { // avoid possible double free
return _get_platform_error(errno)
}
_destroy(f)
return _get_platform_error(errno)
}
_fd :: proc(f: ^File) -> uintptr {
@@ -106,20 +138,32 @@ _name :: proc(f: ^File) -> string {
}
_seek :: proc(f: ^File, offset: i64, whence: io.Seek_From) -> (ret: i64, err: Error) {
res := unix.sys_lseek(f.impl.fd, offset, int(whence))
if res < 0 {
return -1, _get_platform_error(int(res))
n, errno := linux.lseek(f.impl.fd, offset, linux.Seek_Whence(whence))
if errno != .NONE {
return -1, _get_platform_error(errno)
}
return res, nil
return n, nil
}
_read :: proc(f: ^File, p: []byte) -> (i64, Error) {
if len(p) == 0 {
return 0, nil
}
n := unix.sys_read(f.impl.fd, &p[0], len(p))
if n < 0 {
return -1, _get_platform_error(n)
n, errno := linux.read(f.impl.fd, p[:])
if errno != .NONE {
return -1, _get_platform_error(errno)
}
return i64(n), n == 0 ? io.Error.EOF : nil
}
_read_at :: proc(f: ^File, p: []byte, offset: i64) -> (i64, Error) {
if offset < 0 {
return 0, .Invalid_Offset
}
n, errno := linux.pread(f.impl.fd, p[:], offset)
if errno != .NONE {
return -1, _get_platform_error(errno)
}
if n == 0 {
return 0, .EOF
@@ -127,91 +171,67 @@ _read :: proc(f: ^File, p: []byte) -> (i64, Error) {
return i64(n), nil
}
_read_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: i64, err: Error) {
if offset < 0 {
return 0, .Invalid_Offset
}
b, offset := p, offset
for len(b) > 0 {
m := unix.sys_pread(f.impl.fd, &b[0], len(b), offset)
if m < 0 {
return -1, _get_platform_error(m)
}
if m == 0 {
return 0, .EOF
}
n += i64(m)
b = b[m:]
offset += i64(m)
}
return
}
_write :: proc(f: ^File, p: []byte) -> (i64, Error) {
if len(p) == 0 {
return 0, nil
}
n := unix.sys_write(f.impl.fd, &p[0], uint(len(p)))
if n < 0 {
return -1, _get_platform_error(n)
n, errno := linux.write(f.impl.fd, p[:])
if errno != .NONE {
return -1, _get_platform_error(errno)
}
return i64(n), nil
}
_write_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: i64, err: Error) {
_write_at :: proc(f: ^File, p: []byte, offset: i64) -> (i64, Error) {
if offset < 0 {
return 0, .Invalid_Offset
}
b, offset := p, offset
for len(b) > 0 {
m := unix.sys_pwrite(f.impl.fd, &b[0], len(b), offset)
if m < 0 {
return -1, _get_platform_error(m)
}
n += i64(m)
b = b[m:]
offset += i64(m)
n, errno := linux.pwrite(f.impl.fd, p[:], offset)
if errno != .NONE {
return -1, _get_platform_error(errno)
}
return
return i64(n), nil
}
_file_size :: proc(f: ^File) -> (n: i64, err: Error) {
s: _Stat = ---
res := unix.sys_fstat(f.impl.fd, &s)
if res < 0 {
return -1, _get_platform_error(res)
s: linux.Stat = ---
errno := linux.fstat(f.impl.fd, &s)
if errno != .NONE {
return -1, _get_platform_error(errno)
}
return s.size, nil
return i64(s.size), nil
}
_sync :: proc(f: ^File) -> Error {
return _ok_or_error(unix.sys_fsync(f.impl.fd))
return _get_platform_error(linux.fsync(f.impl.fd))
}
_flush :: proc(f: ^File) -> Error {
return _ok_or_error(unix.sys_fsync(f.impl.fd))
return _get_platform_error(linux.fsync(f.impl.fd))
}
_truncate :: proc(f: ^File, size: i64) -> Error {
return _ok_or_error(unix.sys_ftruncate(f.impl.fd, size))
return _get_platform_error(linux.ftruncate(f.impl.fd, size))
}
_remove :: proc(name: string) -> Error {
TEMP_ALLOCATOR_GUARD()
name_cstr := temp_cstring(name) or_return
fd := unix.sys_open(name_cstr, int(File_Flags.Read))
if fd < 0 {
return _get_platform_error(fd)
fd, errno := linux.open(name_cstr, {.NOFOLLOW})
#partial switch (errno) {
case .ELOOP: /* symlink */
case .NONE:
defer linux.close(fd)
if _is_dir_fd(fd) {
return _get_platform_error(linux.rmdir(name_cstr))
}
case:
return _get_platform_error(errno)
}
defer unix.sys_close(fd)
if _is_dir_fd(fd) {
return _ok_or_error(unix.sys_rmdir(name_cstr))
}
return _ok_or_error(unix.sys_unlink(name_cstr))
return _get_platform_error(linux.unlink(name_cstr))
}
_rename :: proc(old_name, new_name: string) -> Error {
@@ -219,7 +239,7 @@ _rename :: proc(old_name, new_name: string) -> Error {
old_name_cstr := temp_cstring(old_name) or_return
new_name_cstr := temp_cstring(new_name) or_return
return _ok_or_error(unix.sys_rename(old_name_cstr, new_name_cstr))
return _get_platform_error(linux.rename(old_name_cstr, new_name_cstr))
}
_link :: proc(old_name, new_name: string) -> Error {
@@ -227,148 +247,194 @@ _link :: proc(old_name, new_name: string) -> Error {
old_name_cstr := temp_cstring(old_name) or_return
new_name_cstr := temp_cstring(new_name) or_return
return _ok_or_error(unix.sys_link(old_name_cstr, new_name_cstr))
return _get_platform_error(linux.link(old_name_cstr, new_name_cstr))
}
_symlink :: proc(old_name, new_name: string) -> Error {
TEMP_ALLOCATOR_GUARD()
old_name_cstr := temp_cstring(old_name) or_return
new_name_cstr := temp_cstring(new_name) or_return
return _ok_or_error(unix.sys_symlink(old_name_cstr, new_name_cstr))
return _get_platform_error(linux.symlink(old_name_cstr, new_name_cstr))
}
_read_link_cstr :: proc(name_cstr: cstring, allocator: runtime.Allocator) -> (string, Error) {
bufsz : uint = 256
buf := make([]byte, bufsz, allocator)
for {
rc := unix.sys_readlink(name_cstr, &buf[0], bufsz)
if rc < 0 {
delete(buf)
return "", _get_platform_error(rc)
} else if rc == int(bufsz) {
sz, errno := linux.readlink(name_cstr, buf[:])
if errno != .NONE {
delete(buf, allocator)
return "", _get_platform_error(errno)
} else if sz == int(bufsz) {
bufsz *= 2
delete(buf)
delete(buf, allocator)
buf = make([]byte, bufsz, allocator)
} else {
return string(buf[:rc]), nil
return string(buf[:sz]), nil
}
}
}
_read_link :: proc(name: string, allocator: runtime.Allocator) -> (path: string, err: Error) {
_read_link :: proc(name: string, allocator: runtime.Allocator) -> (s: string, e: Error) {
TEMP_ALLOCATOR_GUARD()
name_cstr := temp_cstring(name) or_return
return _read_link_cstr(name_cstr, allocator)
}
_unlink :: proc(name: string) -> Error {
TEMP_ALLOCATOR_GUARD()
name_cstr := temp_cstring(name) or_return
return _ok_or_error(unix.sys_unlink(name_cstr))
}
_chdir :: proc(name: string) -> Error {
TEMP_ALLOCATOR_GUARD()
name_cstr := temp_cstring(name) or_return
return _ok_or_error(unix.sys_chdir(name_cstr))
return _get_platform_error(linux.chdir(name_cstr))
}
_fchdir :: proc(f: ^File) -> Error {
return _ok_or_error(unix.sys_fchdir(f.impl.fd))
return _get_platform_error(linux.fchdir(f.impl.fd))
}
_chmod :: proc(name: string, mode: File_Mode) -> Error {
TEMP_ALLOCATOR_GUARD()
name_cstr := temp_cstring(name) or_return
return _ok_or_error(unix.sys_chmod(name_cstr, uint(mode)))
return _get_platform_error(linux.chmod(name_cstr, transmute(linux.Mode)(u32(mode))))
}
_fchmod :: proc(f: ^File, mode: File_Mode) -> Error {
return _ok_or_error(unix.sys_fchmod(f.impl.fd, uint(mode)))
return _get_platform_error(linux.fchmod(f.impl.fd, transmute(linux.Mode)(u32(mode))))
}
// NOTE: will throw error without super user priviledges
_chown :: proc(name: string, uid, gid: int) -> Error {
TEMP_ALLOCATOR_GUARD()
name_cstr := temp_cstring(name) or_return
return _ok_or_error(unix.sys_chown(name_cstr, uid, gid))
return _get_platform_error(linux.chown(name_cstr, linux.Uid(uid), linux.Gid(gid)))
}
// NOTE: will throw error without super user priviledges
_lchown :: proc(name: string, uid, gid: int) -> Error {
TEMP_ALLOCATOR_GUARD()
name_cstr := temp_cstring(name) or_return
return _ok_or_error(unix.sys_lchown(name_cstr, uid, gid))
return _get_platform_error(linux.lchown(name_cstr, linux.Uid(uid), linux.Gid(gid)))
}
// NOTE: will throw error without super user priviledges
_fchown :: proc(f: ^File, uid, gid: int) -> Error {
return _ok_or_error(unix.sys_fchown(f.impl.fd, uid, gid))
return _get_platform_error(linux.fchown(f.impl.fd, linux.Uid(uid), linux.Gid(gid)))
}
_chtimes :: proc(name: string, atime, mtime: time.Time) -> Error {
TEMP_ALLOCATOR_GUARD()
name_cstr := temp_cstring(name) or_return
times := [2]Unix_File_Time {
{ atime._nsec, 0 },
{ mtime._nsec, 0 },
times := [2]linux.Time_Spec {
{
uint(atime._nsec) / uint(time.Second),
uint(atime._nsec) % uint(time.Second),
},
{
uint(mtime._nsec) / uint(time.Second),
uint(mtime._nsec) % uint(time.Second),
},
}
return _ok_or_error(unix.sys_utimensat(_AT_FDCWD, name_cstr, &times, 0))
return _get_platform_error(linux.utimensat(linux.AT_FDCWD, name_cstr, &times[0], nil))
}
_fchtimes :: proc(f: ^File, atime, mtime: time.Time) -> Error {
times := [2]Unix_File_Time {
{ atime._nsec, 0 },
{ mtime._nsec, 0 },
times := [2]linux.Time_Spec {
{
uint(atime._nsec) / uint(time.Second),
uint(atime._nsec) % uint(time.Second),
},
{
uint(mtime._nsec) / uint(time.Second),
uint(mtime._nsec) % uint(time.Second),
},
}
return _ok_or_error(unix.sys_utimensat(f.impl.fd, nil, &times, 0))
return _get_platform_error(linux.utimensat(f.impl.fd, nil, &times[0], nil))
}
_exists :: proc(name: string) -> bool {
TEMP_ALLOCATOR_GUARD()
name_cstr, _ := temp_cstring(name)
return unix.sys_access(name_cstr, F_OK) == 0
res, errno := linux.access(name_cstr, linux.F_OK)
return !res && errno == .NONE
}
_is_file :: proc(name: string) -> bool {
TEMP_ALLOCATOR_GUARD()
name_cstr, _ := temp_cstring(name)
s: _Stat
res := unix.sys_stat(name_cstr, &s)
if res < 0 {
s: linux.Stat
if linux.stat(name_cstr, &s) != .NONE {
return false
}
return S_ISREG(s.mode)
return linux.S_ISREG(s.mode)
}
_is_file_fd :: proc(fd: int) -> bool {
s: _Stat
res := unix.sys_fstat(fd, &s)
if res < 0 { // error
_is_file_fd :: proc(fd: linux.Fd) -> bool {
s: linux.Stat
if linux.fstat(fd, &s) != .NONE {
return false
}
return S_ISREG(s.mode)
return linux.S_ISREG(s.mode)
}
_is_dir :: proc(name: string) -> bool {
TEMP_ALLOCATOR_GUARD()
name_cstr, _ := temp_cstring(name)
s: _Stat
res := unix.sys_stat(name_cstr, &s)
if res < 0 {
s: linux.Stat
if linux.stat(name_cstr, &s) != .NONE {
return false
}
return S_ISDIR(s.mode)
return linux.S_ISDIR(s.mode)
}
_is_dir_fd :: proc(fd: int) -> bool {
s: _Stat
res := unix.sys_fstat(fd, &s)
if res < 0 { // error
_is_dir_fd :: proc(fd: linux.Fd) -> bool {
s: linux.Stat
if linux.fstat(fd, &s) != .NONE {
return false
}
return S_ISDIR(s.mode)
return linux.S_ISDIR(s.mode)
}
/* Certain files in the Linux file system are not actual
* files (e.g. everything in /proc/). Therefore, the
* read_entire_file procs fail to actually read anything
* since these "files" stat to a size of 0. Here, we just
* read until there is nothing left.
*/
_read_entire_pseudo_file :: proc { _read_entire_pseudo_file_string, _read_entire_pseudo_file_cstring }
_read_entire_pseudo_file_string :: proc(name: string, allocator: runtime.Allocator) -> (b: []u8, e: Error) {
name_cstr := clone_to_cstring(name, allocator) or_return
defer delete(name, allocator)
return _read_entire_pseudo_file_cstring(name_cstr, allocator)
}
_read_entire_pseudo_file_cstring :: proc(name: cstring, allocator: runtime.Allocator) -> ([]u8, Error) {
fd, errno := linux.open(name, {})
if errno != .NONE {
return nil, _get_platform_error(errno)
}
defer linux.close(fd)
BUF_SIZE_STEP :: 128
contents := make([dynamic]u8, 0, BUF_SIZE_STEP, allocator)
n: int
i: int
for {
resize(&contents, i + BUF_SIZE_STEP)
n, errno = linux.read(fd, contents[i:i+BUF_SIZE_STEP])
if errno != .NONE {
delete(contents)
return nil, _get_platform_error(errno)
}
if n < BUF_SIZE_STEP {
break
}
i += BUF_SIZE_STEP
}
resize(&contents, i + n)
return contents[:], nil
}
@(private="package")
+11 -17
View File
@@ -1,7 +1,7 @@
//+private
package os2
import "core:sys/unix"
import "core:sys/linux"
import "core:sync"
import "core:mem"
@@ -97,9 +97,8 @@ CURRENTLY_ACTIVE :: (^^Region)(~uintptr(0))
FREE_LIST_ENTRIES_PER_BLOCK :: BLOCK_SIZE / size_of(u16)
MMAP_FLAGS :: unix.MAP_ANONYMOUS | unix.MAP_PRIVATE
MMAP_PROT :: unix.PROT_READ | unix.PROT_WRITE
MMAP_FLAGS : linux.Map_Flags : {.ANONYMOUS, .PRIVATE}
MMAP_PROT : linux.Mem_Protection : {.READ, .WRITE}
@thread_local _local_region: ^Region
global_regions: ^Region
@@ -324,11 +323,11 @@ heap_free :: proc(memory: rawptr) {
// Regions
//
_new_region :: proc() -> ^Region #no_bounds_check {
res := unix.sys_mmap(nil, uint(SIZE_OF_REGION), MMAP_PROT, MMAP_FLAGS, -1, 0)
if res < 0 {
ptr, errno := linux.mmap(0, uint(SIZE_OF_REGION), MMAP_PROT, MMAP_FLAGS, -1, 0)
if errno != .NONE {
return nil
}
new_region := (^Region)(uintptr(res))
new_region := (^Region)(ptr)
new_region.hdr.local_addr = CURRENTLY_ACTIVE
new_region.hdr.reset_addr = &_local_region
@@ -634,8 +633,8 @@ _region_free_list_remove :: proc(region: ^Region, free_idx: u16) #no_bounds_chec
//
_direct_mmap_alloc :: proc(size: int) -> rawptr {
mmap_size := _round_up_to_nearest(size + BLOCK_SIZE, PAGE_SIZE)
new_allocation := unix.sys_mmap(nil, uint(mmap_size), MMAP_PROT, MMAP_FLAGS, -1, 0)
if new_allocation < 0 && new_allocation > -4096 {
new_allocation, errno := linux.mmap(0, uint(mmap_size), MMAP_PROT, MMAP_FLAGS, -1, 0)
if errno != .NONE {
return nil
}
@@ -655,13 +654,8 @@ _direct_mmap_resize :: proc(alloc: ^Allocation_Header, new_size: int) -> rawptr
return mem.ptr_offset(alloc, 1)
}
new_allocation := unix.sys_mremap(
alloc,
uint(old_mmap_size),
uint(new_mmap_size),
unix.MREMAP_MAYMOVE,
)
if new_allocation < 0 && new_allocation > -4096 {
new_allocation, errno := linux.mremap(alloc, uint(old_mmap_size), uint(new_mmap_size), {.MAYMOVE})
if errno != .NONE {
return nil
}
@@ -702,7 +696,7 @@ _direct_mmap_to_region :: proc(alloc: ^Allocation_Header, new_size: int) -> rawp
_direct_mmap_free :: proc(alloc: ^Allocation_Header) {
requested := int(alloc.requested & REQUESTED_MASK)
mmap_size := _round_up_to_nearest(requested + BLOCK_SIZE, PAGE_SIZE)
unix.sys_munmap(alloc, uint(mmap_size))
linux.munmap(alloc, uint(mmap_size))
}
//
+70 -86
View File
@@ -3,104 +3,89 @@ package os2
import "core:strconv"
import "base:runtime"
import "core:sys/unix"
import "core:sys/linux"
_Path_Separator :: '/'
_Path_Separator_String :: "/"
_Path_List_Separator :: ':'
_S_IFMT :: 0o170000 // Type of file mask
_S_IFIFO :: 0o010000 // Named pipe (fifo)
_S_IFCHR :: 0o020000 // Character special
_S_IFDIR :: 0o040000 // Directory
_S_IFBLK :: 0o060000 // Block special
_S_IFREG :: 0o100000 // Regular
_S_IFLNK :: 0o120000 // Symbolic link
_S_IFSOCK :: 0o140000 // Socket
_OPENDIR_FLAGS :: _O_RDONLY|_O_NONBLOCK|_O_DIRECTORY|_O_LARGEFILE|_O_CLOEXEC
_OPENDIR_FLAGS : linux.Open_Flags : {.NONBLOCK, .DIRECTORY, .LARGEFILE, .CLOEXEC}
_is_path_separator :: proc(c: byte) -> bool {
return c == '/'
}
_mkdir :: proc(path: string, perm: File_Mode) -> Error {
// NOTE: These modes would require sys_mknod, however, that would require
// additional arguments to this function.
// TODO: These modes would require mknod, however, that would also
// require additional arguments to this function..
if perm & (File_Mode_Named_Pipe | File_Mode_Device | File_Mode_Char_Device | File_Mode_Sym_Link) != 0 {
return .Invalid_Argument
}
TEMP_ALLOCATOR_GUARD()
path_cstr := temp_cstring(path) or_return
return _ok_or_error(unix.sys_mkdir(path_cstr, uint(perm & 0o777)))
return _get_platform_error(linux.mkdir(path_cstr, transmute(linux.Mode)(u32(perm) & 0o777)))
}
_mkdir_all :: proc(path: string, perm: File_Mode) -> Error {
_mkdirat :: proc(dfd: int, path: []u8, perm: int, has_created: ^bool) -> Error {
if len(path) == 0 {
return _ok_or_error(unix.sys_close(dfd))
}
mkdirat :: proc(dfd: linux.Fd, path: []u8, perm: int, has_created: ^bool) -> Error {
i: int
for /**/; i < len(path) - 1 && path[i] != '/'; i += 1 {}
for ; i < len(path) - 1 && path[i] != '/'; i += 1 {}
if i == 0 {
return _get_platform_error(linux.close(dfd))
}
path[i] = 0
new_dfd := unix.sys_openat(dfd, cstring(&path[0]), _OPENDIR_FLAGS)
switch new_dfd {
case -ENOENT:
if res := unix.sys_mkdirat(dfd, cstring(&path[0]), uint(perm)); res < 0 {
return _get_platform_error(res)
new_dfd, errno := linux.openat(dfd, cstring(&path[0]), _OPENDIR_FLAGS)
#partial switch errno {
case .ENOENT:
if errno = linux.mkdirat(dfd, cstring(&path[0]), transmute(linux.Mode)(u32(perm))); errno != .NONE {
return _get_platform_error(errno)
}
has_created^ = true
if new_dfd = unix.sys_openat(dfd, cstring(&path[0]), _OPENDIR_FLAGS); new_dfd < 0 {
return _get_platform_error(new_dfd)
if new_dfd, errno = linux.openat(dfd, cstring(&path[0]), _OPENDIR_FLAGS); errno != .NONE {
return _get_platform_error(errno)
}
fallthrough
case 0:
if res := unix.sys_close(dfd); res < 0 {
return _get_platform_error(res)
case .NONE:
if errno = linux.close(dfd); errno != .NONE {
return _get_platform_error(errno)
}
// skip consecutive '/'
for i += 1; i < len(path) && path[i] == '/'; i += 1 {}
return _mkdirat(new_dfd, path[i:], perm, has_created)
return mkdirat(new_dfd, path[i:], perm, has_created)
case:
return _get_platform_error(new_dfd)
return _get_platform_error(errno)
}
unreachable()
}
// TODO
if perm & (File_Mode_Named_Pipe | File_Mode_Device | File_Mode_Char_Device | File_Mode_Sym_Link) != 0 {
return .Invalid_Argument
}
TEMP_ALLOCATOR_GUARD()
// need something we can edit, and use to generate cstrings
allocated: bool
path_bytes: []u8
if len(path) > _CSTRING_NAME_HEAP_THRESHOLD {
allocated = true
path_bytes = make([]u8, len(path) + 1)
} else {
path_bytes = make([]u8, len(path) + 1, temp_allocator())
}
path_bytes := make([]u8, len(path) + 1, temp_allocator())
// NULL terminate the byte slice to make it a valid cstring
// zero terminate the byte slice to make it a valid cstring
copy(path_bytes, path)
path_bytes[len(path)] = 0
dfd: int
dfd: linux.Fd
errno: linux.Errno
if path_bytes[0] == '/' {
dfd = unix.sys_open("/", _OPENDIR_FLAGS)
dfd, errno = linux.open("/", _OPENDIR_FLAGS)
path_bytes = path_bytes[1:]
} else {
dfd = unix.sys_open(".", _OPENDIR_FLAGS)
dfd, errno = linux.open(".", _OPENDIR_FLAGS)
}
if dfd < 0 {
return _get_platform_error(dfd)
if errno != .NONE {
return _get_platform_error(errno)
}
has_created: bool
_mkdirat(dfd, path_bytes, int(perm & 0o777), &has_created) or_return
mkdirat(dfd, path_bytes, int(perm & 0o777), &has_created) or_return
if has_created {
return nil
}
@@ -119,28 +104,28 @@ dirent64 :: struct {
_remove_all :: proc(path: string) -> Error {
DT_DIR :: 4
_remove_all_dir :: proc(dfd: int) -> Error {
remove_all_dir :: proc(dfd: linux.Fd) -> Error {
n := 64
buf := make([]u8, n)
defer delete(buf)
loop: for {
getdents_res := unix.sys_getdents64(dfd, &buf[0], n)
switch getdents_res {
case -EINVAL:
buflen, errno := linux.getdents(dfd, buf[:])
#partial switch errno {
case .EINVAL:
delete(buf)
n *= 2
buf = make([]u8, n)
continue loop
case -4096..<0:
return _get_platform_error(getdents_res)
case 0:
break loop
case .NONE:
if buflen == 0 { break loop }
case:
return _get_platform_error(errno)
}
d: ^dirent64
for i := 0; i < getdents_res; i += int(d.d_reclen) {
for i := 0; i < buflen; i += int(d.d_reclen) {
d = (^dirent64)(rawptr(&buf[i]))
d_name_cstr := cstring(&d.d_name[0])
@@ -156,23 +141,22 @@ _remove_all :: proc(path: string) -> Error {
continue
}
unlink_res: int
switch d.d_type {
case DT_DIR:
new_dfd := unix.sys_openat(dfd, d_name_cstr, _OPENDIR_FLAGS)
if new_dfd < 0 {
return _get_platform_error(new_dfd)
new_dfd: linux.Fd
new_dfd, errno = linux.openat(dfd, d_name_cstr, _OPENDIR_FLAGS)
if errno != .NONE {
return _get_platform_error(errno)
}
defer unix.sys_close(new_dfd)
_remove_all_dir(new_dfd) or_return
unlink_res = unix.sys_unlinkat(dfd, d_name_cstr, int(unix.AT_REMOVEDIR))
defer linux.close(new_dfd)
remove_all_dir(new_dfd) or_return
errno = linux.unlinkat(dfd, d_name_cstr, {.REMOVEDIR})
case:
unlink_res = unix.sys_unlinkat(dfd, d_name_cstr)
errno = linux.unlinkat(dfd, d_name_cstr, nil)
}
if unlink_res < 0 {
return _get_platform_error(unlink_res)
if errno != .NONE {
return _get_platform_error(errno)
}
}
}
@@ -182,17 +166,19 @@ _remove_all :: proc(path: string) -> Error {
TEMP_ALLOCATOR_GUARD()
path_cstr := temp_cstring(path) or_return
fd := unix.sys_open(path_cstr, _OPENDIR_FLAGS)
switch fd {
case -ENOTDIR:
return _ok_or_error(unix.sys_unlink(path_cstr))
case -4096..<0:
return _get_platform_error(fd)
fd, errno := linux.open(path_cstr, _OPENDIR_FLAGS)
#partial switch errno {
case .NONE:
break
case .ENOTDIR:
return _get_platform_error(linux.unlink(path_cstr))
case:
return _get_platform_error(errno)
}
defer unix.sys_close(fd)
_remove_all_dir(fd) or_return
return _ok_or_error(unix.sys_rmdir(path_cstr))
defer linux.close(fd)
remove_all_dir(fd) or_return
return _get_platform_error(linux.rmdir(path_cstr))
}
_getwd :: proc(allocator: runtime.Allocator) -> (string, Error) {
@@ -203,13 +189,12 @@ _getwd :: proc(allocator: runtime.Allocator) -> (string, Error) {
PATH_MAX :: 4096
buf := make([dynamic]u8, PATH_MAX, allocator)
for {
#no_bounds_check res := unix.sys_getcwd(&buf[0], uint(len(buf)))
if res >= 0 {
return string_from_null_terminated_bytes(buf[:]), nil
#no_bounds_check n, errno := linux.getcwd(buf[:])
if errno == .NONE {
return string(buf[:n-1]), nil
}
if res != -ERANGE {
return "", _get_platform_error(res)
if errno != .ERANGE {
return "", _get_platform_error(errno)
}
resize(&buf, len(buf)+PATH_MAX)
}
@@ -218,16 +203,16 @@ _getwd :: proc(allocator: runtime.Allocator) -> (string, Error) {
_setwd :: proc(dir: string) -> Error {
dir_cstr := temp_cstring(dir) or_return
return _ok_or_error(unix.sys_chdir(dir_cstr))
return _get_platform_error(linux.chdir(dir_cstr))
}
_get_full_path :: proc(fd: int, allocator: runtime.Allocator) -> string {
_get_full_path :: proc(fd: linux.Fd, allocator: runtime.Allocator) -> string {
PROC_FD_PATH :: "/proc/self/fd/"
buf: [32]u8
copy(buf[:], PROC_FD_PATH)
strconv.itoa(buf[len(PROC_FD_PATH):], fd)
strconv.itoa(buf[len(PROC_FD_PATH):], int(fd))
fullpath: string
err: Error
@@ -236,4 +221,3 @@ _get_full_path :: proc(fd: int, allocator: runtime.Allocator) -> string {
}
return fullpath
}
+11 -1
View File
@@ -1,7 +1,17 @@
//+private
package os2
import "core:sys/linux"
_pipe :: proc() -> (r, w: ^File, err: Error) {
return nil, nil, nil
fds: [2]linux.Fd
errno := linux.pipe2(&fds, {.CLOEXEC})
if errno != .NONE {
return nil, nil,_get_platform_error(errno)
}
r = _new_file(uintptr(fds[0]))
w = _new_file(uintptr(fds[1]))
return
}
+53 -35
View File
@@ -2,36 +2,42 @@ package os2
import "core:sync"
import "core:time"
import "base:runtime"
import "core:c"
args: []string
args: []string = _alloc_command_line_arguments()
exit :: proc "contextless" (code: int) -> ! {
runtime.trap()
_exit(code)
}
@(require_results)
get_uid :: proc() -> int {
return -1
return _get_uid()
}
@(require_results)
get_euid :: proc() -> int {
return -1
return _get_euid()
}
@(require_results)
get_gid :: proc() -> int {
return -1
return _get_gid()
}
@(require_results)
get_egid :: proc() -> int {
return -1
return _get_euid()
}
@(require_results)
get_pid :: proc() -> int {
return -1
return _get_pid()
}
@(require_results)
get_ppid :: proc() -> int {
return -1
return _get_ppid()
}
@@ -46,16 +52,12 @@ Process :: struct {
Process_Attributes :: struct {
dir: string,
env: []string,
files: []^File,
stdin: ^File,
stdout: ^File,
stderr: ^File,
sys: ^Process_Attributes_OS_Specific,
}
Process_Attributes_OS_Specific :: struct{}
Process_Error :: enum {
None,
}
Process_State :: struct {
pid: int,
exit_code: int,
@@ -66,37 +68,53 @@ Process_State :: struct {
sys: rawptr,
}
Signal :: #type proc()
Kill: Signal = nil
Interrupt: Signal = nil
find_process :: proc(pid: int) -> (^Process, Process_Error) {
return nil, .None
Signal :: enum {
Abort,
Floating_Point_Exception,
Illegal_Instruction,
Interrupt,
Segmentation_Fault,
Termination,
}
process_start :: proc(name: string, argv: []string, attr: ^Process_Attributes) -> (^Process, Process_Error) {
return nil, .None
Signal_Handler_Proc :: #type proc "c" (c.int)
Signal_Handler_Special :: enum {
Default,
Ignore,
}
process_release :: proc(p: ^Process) -> Process_Error {
return .None
Signal_Handler :: union {
Signal_Handler_Proc,
Signal_Handler_Special,
}
process_kill :: proc(p: ^Process) -> Process_Error {
return .None
@(require_results)
process_find :: proc(pid: int) -> (Process, Error) {
return _process_find(pid)
}
process_signal :: proc(p: ^Process, sig: Signal) -> Process_Error {
return .None
@(require_results)
process_get_state :: proc(p: Process) -> (Process_State, Error) {
return _process_get_state(p)
}
process_wait :: proc(p: ^Process) -> (Process_State, Process_Error) {
return {}, .None
@(require_results)
process_start :: proc(name: string, argv: []string, attr: ^Process_Attributes = nil) -> (Process, Error) {
return _process_start(name, argv, attr)
}
process_release :: proc(p: ^Process) -> Error {
return _process_release(p)
}
process_kill :: proc(p: ^Process) -> Error {
return _process_kill(p)
}
process_signal :: proc(sig: Signal, h: Signal_Handler) -> Error {
return _process_signal(sig, h)
}
process_wait :: proc(p: ^Process, t: time.Duration = time.MAX_DURATION) -> (Process_State, Error) {
return _process_wait(p, t)
}
+428
View File
@@ -0,0 +1,428 @@
//+private
package os2
import "base:runtime"
import "core:fmt"
import "core:mem"
import "core:time"
import "core:strings"
import "core:strconv"
import "core:sys/linux"
import "core:path/filepath"
_alloc_command_line_arguments :: proc() -> []string {
res := make([]string, len(runtime.args__), heap_allocator())
for arg, i in runtime.args__ {
res[i] = string(arg)
}
return res
}
_exit :: proc "contextless" (code: int) -> ! {
linux.exit_group(i32(code))
}
_get_uid :: proc() -> int {
return int(linux.getuid())
}
_get_euid :: proc() -> int {
return int(linux.geteuid())
}
_get_gid :: proc() -> int {
return int(linux.getgid())
}
_get_egid :: proc() -> int {
return int(linux.getegid())
}
_get_pid :: proc() -> int {
return int(linux.getpid())
}
_get_ppid :: proc() -> int {
return int(linux.getppid())
}
Process_Attributes_OS_Specific :: struct {}
_process_find :: proc(pid: int) -> (Process, Error) {
TEMP_ALLOCATOR_GUARD()
pid_path := fmt.ctprintf("/proc/%d", pid)
p: Process
dir_fd: linux.Fd
errno: linux.Errno
#partial switch dir_fd, errno = linux.open(pid_path, _OPENDIR_FLAGS); errno {
case .NONE:
linux.close(dir_fd)
p.pid = pid
return p, nil
case .ENOTDIR:
return p, .Invalid_Dir
case .ENOENT:
return p, .Not_Exist
}
return p, _get_platform_error(errno)
}
_process_get_state :: proc(p: Process) -> (state: Process_State, err: Error) {
TEMP_ALLOCATOR_GUARD()
stat_name := fmt.ctprintf("/proc/%d/stat", p.pid)
stat_buf: []u8
stat_buf, err = _read_entire_pseudo_file(stat_name, temp_allocator())
if err != nil {
return
}
idx := strings.last_index_byte(string(stat_buf), ')')
stats := string(stat_buf[idx + 2:])
// utime and stime are the 12 and 13th items, respectively
// skip the first 11 items here.
for i := 0; i < 11; i += 1 {
stats = stats[strings.index_byte(stats, ' ') + 1:]
}
idx = strings.index_byte(stats, ' ')
utime_str := stats[:idx]
stats = stats[idx + 1:]
stime_str := stats[:strings.index_byte(stats, ' ')]
utime, _ := strconv.parse_int(utime_str, 10)
stime, _ := strconv.parse_int(stime_str, 10)
// NOTE: Assuming HZ of 100, 1 jiffy == 10 ms
state.user_time = time.Duration(utime) * 10 * time.Millisecond
state.system_time = time.Duration(stime) * 10 * time.Millisecond
return
}
_process_start :: proc(name: string, argv: []string, attr: ^Process_Attributes) -> (child: Process, err: Error) {
TEMP_ALLOCATOR_GUARD()
dir_fd := linux.AT_FDCWD
errno: linux.Errno
if attr != nil && attr.dir != "" {
dir_cstr := temp_cstring(attr.dir) or_return
if dir_fd, errno = linux.open(dir_cstr, _OPENDIR_FLAGS); errno != .NONE {
return child, _get_platform_error(errno)
}
}
// search PATH if just a plain name is provided
executable: cstring
if !strings.contains_rune(name, '/') {
path_env := get_env("PATH", temp_allocator())
path_dirs := filepath.split_list(path_env, temp_allocator())
found: bool
for dir in path_dirs {
executable = fmt.ctprintf("%s/%s", dir, name)
fail: bool
if fail, errno = linux.faccessat(dir_fd, executable, linux.F_OK); errno == .NONE && !fail {
found = true
break
}
}
if !found {
// check in cwd to match windows behavior
executable = fmt.ctprintf("./%s", name)
fail: bool
if fail, errno = linux.faccessat(dir_fd, executable, linux.F_OK); errno != .NONE || fail {
return child, .Not_Exist
}
}
} else {
executable = temp_cstring(name) or_return
}
not_exec: bool
if not_exec, errno = linux.faccessat(dir_fd, executable, linux.F_OK | linux.X_OK); errno != .NONE || not_exec {
return child, errno == .NONE ? .Permission_Denied : _get_platform_error(errno)
}
// args and environment need to be a list of cstrings
// that are terminated by a nil pointer.
// The first argument is a copy of the executable name.
cargs := make([]cstring, len(argv) + 2, temp_allocator())
cargs[0] = executable
for i := 0; i < len(argv); i += 1 {
cargs[i + 1] = temp_cstring(argv[i]) or_return
}
// Use current process's environment if attributes not provided
env: [^]cstring
if attr == nil {
// take this process's current environment
env = raw_data(export_cstring_environment(temp_allocator()))
} else {
cenv := make([]cstring, len(attr.env) + 1, temp_allocator())
for i := 0; i < len(attr.env); i += 1 {
cenv[i] = temp_cstring(attr.env[i]) or_return
}
env = &cenv[0]
}
// TODO: This is the traditional textbook implementation with fork.
// A more efficient implementation with vfork:
//
// 1. retrieve signal handlers
// 2. block all signals
// 3. allocate some stack space
// 4. vfork (waits for child exit or execve); In child:
// a. set child signal handlers
// b. set up any necessary pipes
// c. execve
// 5. restore signal handlers
//
stdin_fds: [2]linux.Fd
stdout_fds: [2]linux.Fd
stderr_fds: [2]linux.Fd
if attr != nil && attr.stdin != nil {
if errno = linux.pipe2(&stdin_fds, nil); errno != .NONE {
return child, _get_platform_error(errno)
}
}
if attr != nil && attr.stdout != nil {
if errno = linux.pipe2(&stdout_fds, nil); errno != .NONE {
return child, _get_platform_error(errno)
}
}
if attr != nil && attr.stderr != nil {
if errno = linux.pipe2(&stderr_fds, nil); errno != .NONE {
return child, _get_platform_error(errno)
}
}
pid: linux.Pid
if pid, errno = linux.fork(); errno != .NONE {
return child, _get_platform_error(errno)
}
IN :: 1
OUT :: 0
STDIN :: linux.Fd(0)
STDOUT :: linux.Fd(1)
STDERR :: linux.Fd(2)
if pid == 0 {
// in child process now
if attr != nil && attr.stdin != nil {
if linux.close(stdin_fds[IN]) != .NONE { linux.exit(1) }
if _, errno = linux.dup2(stdin_fds[OUT], STDIN); errno != .NONE { linux.exit(1) }
if linux.close(stdin_fds[OUT]) != .NONE { linux.exit(1) }
}
if attr != nil && attr.stdout != nil {
if linux.close(stdout_fds[OUT]) != .NONE { linux.exit(1) }
if _, errno = linux.dup2(stdout_fds[IN], STDOUT); errno != .NONE { linux.exit(1) }
if linux.close(stdout_fds[IN]) != .NONE { linux.exit(1) }
}
if attr != nil && attr.stderr != nil {
if linux.close(stderr_fds[OUT]) != .NONE { linux.exit(1) }
if _, errno = linux.dup2(stderr_fds[IN], STDERR); errno != .NONE { linux.exit(1) }
if linux.close(stderr_fds[IN]) != .NONE { linux.exit(1) }
}
if errno = linux.execveat(dir_fd, executable, &cargs[OUT], env); errno != .NONE {
print_error(_get_platform_error(errno), string(executable))
panic("execve failed to replace process")
}
unreachable()
}
// in parent process
if attr != nil && attr.stdin != nil {
linux.close(stdin_fds[OUT])
_construct_file(attr.stdin, uintptr(stdin_fds[IN]))
}
if attr != nil && attr.stdout != nil {
linux.close(stdout_fds[IN])
_construct_file(attr.stdout, uintptr(stdout_fds[OUT]))
}
if attr != nil && attr.stderr != nil {
linux.close(stderr_fds[IN])
_construct_file(attr.stderr, uintptr(stderr_fds[OUT]))
}
child.pid = int(pid)
return child, nil
}
_process_release :: proc(p: ^Process) -> Error {
// We didn't allocate...
return nil
}
_process_kill :: proc(p: ^Process) -> Error {
res := linux.kill(linux.Pid(p.pid), .SIGKILL)
return _get_platform_error(res)
}
_process_signal :: proc(sig: Signal, h: Signal_Handler) -> Error {
signo: linux.Signal
switch sig {
case .Abort: signo = .SIGABRT
case .Floating_Point_Exception: signo = .SIGFPE
case .Illegal_Instruction: signo = .SIGILL
case .Interrupt: signo = .SIGINT
case .Segmentation_Fault: signo = .SIGSEGV
case .Termination: signo = .SIGTERM
}
sigact: linux.Sig_Action(int)
old: ^linux.Sig_Action(int) = nil
switch v in h {
case Signal_Handler_Special:
switch v {
case .Default:
sigact.special = .SIG_DFL
case .Ignore:
sigact.special = .SIG_IGN
}
case Signal_Handler_Proc:
sigact.handler = (linux.Sig_Handler_Fn)(v)
}
return _get_platform_error(linux.rt_sigaction(signo, &sigact, old))
}
_process_wait :: proc(p: ^Process, t: time.Duration) -> (state: Process_State, err: Error) {
safe_state :: proc(p: Process, state: Process_State = {}) -> (Process_State, Error) {
// process_get_state can fail, so we don't want to return it directly.
if new_state, err := _process_get_state(p); err == nil {
return new_state, nil
}
return state, nil
}
state.pid = p.pid
options: linux.Wait_Options
big_if: if t == 0 {
options += {.WNOHANG}
} else if t != time.MAX_DURATION {
ts: linux.Time_Spec = {
time_sec = uint(t / time.Second),
time_nsec = uint(t % time.Second),
}
@static has_pidfd_open: bool = true
// pidfd_open is fairly new, so don't error out on ENOSYS
pid_fd: linux.Pid_FD
errno: linux.Errno
if has_pidfd_open {
pid_fd, errno = linux.pidfd_open(linux.Pid(p.pid), nil)
if errno != .NONE && errno != .ENOSYS {
return state, _get_platform_error(errno)
}
}
if has_pidfd_open && errno != .ENOSYS {
defer linux.close(linux.Fd(pid_fd))
pollfd: [1]linux.Poll_Fd = {
{
fd = linux.Fd(pid_fd),
events = {.IN},
},
}
for {
n, e := linux.ppoll(pollfd[:], &ts, nil)
if e == .EINTR {
continue
}
if e != .NONE {
return state, _get_platform_error(errno)
}
if n == 0 {
return safe_state(p^, state)
}
break
}
} else {
has_pidfd_open = false
mask: bit_set[0..=63]
mask += { int(linux.Signal.SIGCHLD) - 1 }
org_sigset: linux.Sig_Set
sigset: linux.Sig_Set
mem.copy(&sigset, &mask, size_of(mask))
errno = linux.rt_sigprocmask(.SIG_BLOCK, &sigset, &org_sigset)
if errno != .NONE {
return state, _get_platform_error(errno)
}
defer linux.rt_sigprocmask(.SIG_SETMASK, &org_sigset, nil)
// In case there was a signal handler on SIGCHLD, avoid race
// condition by checking wait first.
options += {.WNOHANG}
waitid_options := options + {.WNOWAIT, .WEXITED}
info: linux.Sig_Info
errno = linux.waitid(.PID, linux.Id(p.pid), &info, waitid_options, nil)
if errno == .NONE && info.code != 0 {
break big_if
}
loop: for {
sigset = {}
mem.copy(&sigset, &mask, size_of(mask))
_, errno = linux.rt_sigtimedwait(&sigset, &info, &ts)
#partial switch errno {
case .EAGAIN: // timeout
return safe_state(p^, state)
case .EINVAL:
return state, _get_platform_error(errno)
case .EINTR:
continue
case:
if int(info.pid) == p.pid {
break loop
}
}
}
}
}
state, _ = safe_state(p^, state)
status: u32
errno: linux.Errno = .EINTR
for errno == .EINTR {
_, errno = linux.wait4(linux.Pid(p.pid), &status, options, nil)
if errno != .NONE {
return state, _get_platform_error(errno)
}
}
// terminated by exit
if linux.WIFEXITED(status) {
p.is_done = true
state.exited = true
state.exit_code = int(linux.WEXITSTATUS(status))
state.success = state.exit_code == 0
return state, nil
}
// terminated by signal
if linux.WIFSIGNALED(status) {
// NOTE: what's the correct behavior here??
p.is_done = true
state.exited = false
state.exit_code = int(linux.WTERMSIG(status))
state.success = false
return state, nil
}
return safe_state(p^, state)
}
+67
View File
@@ -0,0 +1,67 @@
//+private
package os2
import "core:runtime"
import "core:time"
_alloc_command_line_arguments :: proc() -> []string {
return nil
}
_exit :: proc "contextless" (_: int) -> ! {
runtime.trap()
}
_get_uid :: proc() -> int {
return -1
}
_get_euid :: proc() -> int {
return -1
}
_get_gid :: proc() -> int {
return -1
}
_get_egid :: proc() -> int {
return -1
}
_get_pid :: proc() -> int {
return -1
}
_get_ppid :: proc() -> int {
return -1
}
Process_Attributes_OS_Specific :: struct{}
_process_find :: proc(pid: int) -> (Process, Error) {
return Process{}, nil
}
_process_get_state :: proc(p: Process) -> (Process_State, Error) {
return Process_State{}, nil
}
_process_start :: proc(name: string, argv: []string, attr: ^Process_Attributes) -> (Process, Error) {
return Process{}, nil
}
_process_release :: proc(p: ^Process) -> Error {
return nil
}
_process_kill :: proc(p: ^Process) -> Error {
return nil
}
_process_signal :: proc(sig: Signal, handler: Signal_Handler) -> Error {
return nil
}
_process_wait :: proc(p: ^Process, t: time.Duration) -> (Process_State, Error) {
return Process_State{}, nil
}
+20 -96
View File
@@ -3,108 +3,32 @@ package os2
import "core:time"
import "base:runtime"
import "core:sys/unix"
import "core:sys/linux"
import "core:path/filepath"
// File type
S_IFMT :: 0o170000 // Type of file mask
S_IFIFO :: 0o010000 // Named pipe (fifo)
S_IFCHR :: 0o020000 // Character special
S_IFDIR :: 0o040000 // Directory
S_IFBLK :: 0o060000 // Block special
S_IFREG :: 0o100000 // Regular
S_IFLNK :: 0o120000 // Symbolic link
S_IFSOCK :: 0o140000 // Socket
// File mode
// Read, write, execute/search by owner
S_IRWXU :: 0o0700 // RWX mask for owner
S_IRUSR :: 0o0400 // R for owner
S_IWUSR :: 0o0200 // W for owner
S_IXUSR :: 0o0100 // X for owner
// Read, write, execute/search by group
S_IRWXG :: 0o0070 // RWX mask for group
S_IRGRP :: 0o0040 // R for group
S_IWGRP :: 0o0020 // W for group
S_IXGRP :: 0o0010 // X for group
// Read, write, execute/search by others
S_IRWXO :: 0o0007 // RWX mask for other
S_IROTH :: 0o0004 // R for other
S_IWOTH :: 0o0002 // W for other
S_IXOTH :: 0o0001 // X for other
S_ISUID :: 0o4000 // Set user id on execution
S_ISGID :: 0o2000 // Set group id on execution
S_ISVTX :: 0o1000 // Directory restrcted delete
S_ISLNK :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFLNK }
S_ISREG :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFREG }
S_ISDIR :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFDIR }
S_ISCHR :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFCHR }
S_ISBLK :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFBLK }
S_ISFIFO :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFIFO }
S_ISSOCK :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFSOCK }
F_OK :: 0 // Test for file existance
X_OK :: 1 // Test for execute permission
W_OK :: 2 // Test for write permission
R_OK :: 4 // Test for read permission
@private
Unix_File_Time :: struct {
seconds: i64,
nanoseconds: i64,
}
@private
_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,
}
_fstat :: proc(f: ^File, allocator: runtime.Allocator) -> (File_Info, Error) {
return _fstat_internal(f.impl.fd, allocator)
}
_fstat_internal :: proc(fd: int, allocator: runtime.Allocator) -> (File_Info, Error) {
s: _Stat
result := unix.sys_fstat(fd, &s)
if result < 0 {
return {}, _get_platform_error(result)
_fstat_internal :: proc(fd: linux.Fd, allocator: runtime.Allocator) -> (File_Info, Error) {
s: linux.Stat
errno := linux.fstat(fd, &s)
if errno != .NONE {
return {}, _get_platform_error(errno)
}
// TODO: As of Linux 4.11, the new statx syscall can retrieve creation_time
fi := File_Info {
fullpath = _get_full_path(fd, allocator),
name = "",
size = s.size,
size = i64(s.size),
mode = 0,
is_directory = S_ISDIR(s.mode),
modification_time = time.Time {s.modified.seconds},
access_time = time.Time {s.last_access.seconds},
creation_time = time.Time{0}, // regular stat does not provide this
is_directory = linux.S_ISDIR(s.mode),
modification_time = time.Time {i64(s.mtime.time_sec) * i64(time.Second) + i64(s.mtime.time_nsec)},
access_time = time.Time {i64(s.atime.time_sec) * i64(time.Second) + i64(s.atime.time_nsec)},
creation_time = time.Time{i64(s.ctime.time_sec) * i64(time.Second) + i64(s.ctime.time_nsec)}, // regular stat does not provide this
}
fi.creation_time = fi.modification_time
fi.name = filepath.base(fi.fullpath)
return fi, nil
@@ -115,11 +39,11 @@ _stat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, err
TEMP_ALLOCATOR_GUARD()
name_cstr := temp_cstring(name) or_return
fd := unix.sys_open(name_cstr, _O_RDONLY)
if fd < 0 {
return {}, _get_platform_error(fd)
fd, errno := linux.open(name_cstr, {})
if errno != .NONE {
return {}, _get_platform_error(errno)
}
defer unix.sys_close(fd)
defer linux.close(fd)
return _fstat_internal(fd, allocator)
}
@@ -127,11 +51,11 @@ _lstat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, er
TEMP_ALLOCATOR_GUARD()
name_cstr := temp_cstring(name) or_return
fd := unix.sys_open(name_cstr, _O_RDONLY | _O_PATH | _O_NOFOLLOW)
if fd < 0 {
return {}, _get_platform_error(fd)
fd, errno := linux.open(name_cstr, {.PATH, .NOFOLLOW})
if errno != .NONE {
return {}, _get_platform_error(errno)
}
defer unix.sys_close(fd)
defer linux.close(fd)
return _fstat_internal(fd, allocator)
}