Merge pull request #3822 from jasonKercher/os2-rebase

os2 linux round 2
This commit is contained in:
gingerBill
2024-06-28 15:43:18 +01:00
committed by GitHub
12 changed files with 906 additions and 582 deletions
+210 -9
View File
@@ -2,29 +2,230 @@
package os2 package os2
import "base:runtime" 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) { _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 return
} }
_set_env :: proc(key, value: string) -> bool { _set_env :: proc(key, v_new: string) -> bool {
//TODO if _org_env_begin == 0 {
return false _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 { _unset_env :: proc(key: string) -> bool {
//TODO if _org_env_begin == 0 {
return false _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() { _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 { _environ :: proc(allocator: runtime.Allocator) -> []string {
//TODO if _org_env_begin == 0 {
return nil _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" return "unknown error"
} }
print_error :: proc(f: ^File, 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(f, buf)
}
+154 -134
View File
@@ -1,145 +1,165 @@
//+private //+private
package os2 package os2
import "core:sys/unix" import "core:sys/linux"
EPERM :: 1 @(rodata)
ENOENT :: 2 _errno_strings : [linux.Errno]string = {
ESRCH :: 3 .NONE = "Success",
EINTR :: 4 .EPERM = "Operation not permitted",
EIO :: 5 .ENOENT = "No such file or directory",
ENXIO :: 6 .ESRCH = "No such process",
EBADF :: 9 .EINTR = "Interrupted system call",
EAGAIN :: 11 .EIO = "Input/output error",
ENOMEM :: 12 .ENXIO = "No such device or address",
EACCES :: 13 .E2BIG = "Argument list too long",
EFAULT :: 14 .ENOEXEC = "Exec format error",
EEXIST :: 17 .EBADF = "Bad file descriptor",
ENODEV :: 19 .ECHILD = "No child processes",
ENOTDIR :: 20 .EAGAIN = "Resource temporarily unavailable",
EISDIR :: 21 .ENOMEM = "Cannot allocate memory",
EINVAL :: 22 .EACCES = "Permission denied",
ENFILE :: 23 .EFAULT = "Bad address",
EMFILE :: 24 .ENOTBLK = "Block device required",
ETXTBSY :: 26 .EBUSY = "Device or resource busy",
EFBIG :: 27 .EEXIST = "File exists",
ENOSPC :: 28 .EXDEV = "Invalid cross-device link",
ESPIPE :: 29 .ENODEV = "No such device",
EROFS :: 30 .ENOTDIR = "Not a directory",
EPIPE :: 32 .EISDIR = "Is a directory",
ERANGE :: 34 /* Result too large */ .EINVAL = "Invalid argument",
EDEADLK :: 35 /* Resource deadlock would occur */ .ENFILE = "Too many open files in system",
ENAMETOOLONG :: 36 /* File name too long */ .EMFILE = "Too many open files",
ENOLCK :: 37 /* No record locks available */ .ENOTTY = "Inappropriate ioctl for device",
ENOSYS :: 38 /* Invalid system call number */ .ETXTBSY = "Text file busy",
ENOTEMPTY :: 39 /* Directory not empty */ .EFBIG = "File too large",
ELOOP :: 40 /* Too many symbolic links encountered */ .ENOSPC = "No space left on device",
EWOULDBLOCK :: EAGAIN /* Operation would block */ .ESPIPE = "Illegal seek",
ENOMSG :: 42 /* No message of desired type */ .EROFS = "Read-only file system",
EIDRM :: 43 /* Identifier removed */ .EMLINK = "Too many links",
ECHRNG :: 44 /* Channel number out of range */ .EPIPE = "Broken pipe",
EL2NSYNC :: 45 /* Level 2 not synchronized */ .EDOM = "Numerical argument out of domain",
EL3HLT :: 46 /* Level 3 halted */ .ERANGE = "Numerical result out of range",
EL3RST :: 47 /* Level 3 reset */ .EDEADLK = "Resource deadlock avoided",
ELNRNG :: 48 /* Link number out of range */ .ENAMETOOLONG = "File name too long",
EUNATCH :: 49 /* Protocol driver not attached */ .ENOLCK = "No locks available",
ENOCSI :: 50 /* No CSI structure available */ .ENOSYS = "Function not implemented",
EL2HLT :: 51 /* Level 2 halted */ .ENOTEMPTY = "Directory not empty",
EBADE :: 52 /* Invalid exchange */ .ELOOP = "Too many levels of symbolic links",
EBADR :: 53 /* Invalid request descriptor */ .EUNKNOWN_41 = "Unknown Error (41)",
EXFULL :: 54 /* Exchange full */ .ENOMSG = "No message of desired type",
ENOANO :: 55 /* No anode */ .EIDRM = "Identifier removed",
EBADRQC :: 56 /* Invalid request code */ .ECHRNG = "Channel number out of range",
EBADSLT :: 57 /* Invalid slot */ .EL2NSYNC = "Level 2 not synchronized",
EDEADLOCK :: EDEADLK .EL3HLT = "Level 3 halted",
EBFONT :: 59 /* Bad font file format */ .EL3RST = "Level 3 reset",
ENOSTR :: 60 /* Device not a stream */ .ELNRNG = "Link number out of range",
ENODATA :: 61 /* No data available */ .EUNATCH = "Protocol driver not attached",
ETIME :: 62 /* Timer expired */ .ENOCSI = "No CSI structure available",
ENOSR :: 63 /* Out of streams resources */ .EL2HLT = "Level 2 halted",
ENONET :: 64 /* Machine is not on the network */ .EBADE = "Invalid exchange",
ENOPKG :: 65 /* Package not installed */ .EBADR = "Invalid request descriptor",
EREMOTE :: 66 /* Object is remote */ .EXFULL = "Exchange full",
ENOLINK :: 67 /* Link has been severed */ .ENOANO = "No anode",
EADV :: 68 /* Advertise error */ .EBADRQC = "Invalid request code",
ESRMNT :: 69 /* Srmount error */ .EBADSLT = "Invalid slot",
ECOMM :: 70 /* Communication error on send */ .EUNKNOWN_58 = "Unknown Error (58)",
EPROTO :: 71 /* Protocol error */ .EBFONT = "Bad font file format",
EMULTIHOP :: 72 /* Multihop attempted */ .ENOSTR = "Device not a stream",
EDOTDOT :: 73 /* RFS specific error */ .ENODATA = "No data available",
EBADMSG :: 74 /* Not a data message */ .ETIME = "Timer expired",
EOVERFLOW :: 75 /* Value too large for defined data type */ .ENOSR = "Out of streams resources",
ENOTUNIQ :: 76 /* Name not unique on network */ .ENONET = "Machine is not on the network",
EBADFD :: 77 /* File descriptor in bad state */ .ENOPKG = "Package not installed",
EREMCHG :: 78 /* Remote address changed */ .EREMOTE = "Object is remote",
ELIBACC :: 79 /* Can not access a needed shared library */ .ENOLINK = "Link has been severed",
ELIBBAD :: 80 /* Accessing a corrupted shared library */ .EADV = "Advertise error",
ELIBSCN :: 81 /* .lib section in a.out corrupted */ .ESRMNT = "Srmount error",
ELIBMAX :: 82 /* Attempting to link in too many shared libraries */ .ECOMM = "Communication error on send",
ELIBEXEC :: 83 /* Cannot exec a shared library directly */ .EPROTO = "Protocol error",
EILSEQ :: 84 /* Illegal byte sequence */ .EMULTIHOP = "Multihop attempted",
ERESTART :: 85 /* Interrupted system call should be restarted */ .EDOTDOT = "RFS specific error",
ESTRPIPE :: 86 /* Streams pipe error */ .EBADMSG = "Bad message",
EUSERS :: 87 /* Too many users */ .EOVERFLOW = "Value too large for defined data type",
ENOTSOCK :: 88 /* Socket operation on non-socket */ .ENOTUNIQ = "Name not unique on network",
EDESTADDRREQ :: 89 /* Destination address required */ .EBADFD = "File descriptor in bad state",
EMSGSIZE :: 90 /* Message too long */ .EREMCHG = "Remote address changed",
EPROTOTYPE :: 91 /* Protocol wrong type for socket */ .ELIBACC = "Can not access a needed shared library",
ENOPROTOOPT :: 92 /* Protocol not available */ .ELIBBAD = "Accessing a corrupted shared library",
EPROTONOSUPPORT:: 93 /* Protocol not supported */ .ELIBSCN = ".lib section in a.out corrupted",
ESOCKTNOSUPPORT:: 94 /* Socket type not supported */ .ELIBMAX = "Attempting to link in too many shared libraries",
EOPNOTSUPP :: 95 /* Operation not supported on transport endpoint */ .ELIBEXEC = "Cannot exec a shared library directly",
EPFNOSUPPORT :: 96 /* Protocol family not supported */ .EILSEQ = "Invalid or incomplete multibyte or wide character",
EAFNOSUPPORT :: 97 /* Address family not supported by protocol */ .ERESTART = "Interrupted system call should be restarted",
EADDRINUSE :: 98 /* Address already in use */ .ESTRPIPE = "Streams pipe error",
EADDRNOTAVAIL :: 99 /* Cannot assign requested address */ .EUSERS = "Too many users",
ENETDOWN :: 100 /* Network is down */ .ENOTSOCK = "Socket operation on non-socket",
ENETUNREACH :: 101 /* Network is unreachable */ .EDESTADDRREQ = "Destination address required",
ENETRESET :: 102 /* Network dropped connection because of reset */ .EMSGSIZE = "Message too long",
ECONNABORTED :: 103 /* Software caused connection abort */ .EPROTOTYPE = "Protocol wrong type for socket",
ECONNRESET :: 104 /* Connection reset by peer */ .ENOPROTOOPT = "Protocol not available",
ENOBUFS :: 105 /* No buffer space available */ .EPROTONOSUPPORT = "Protocol not supported",
EISCONN :: 106 /* Transport endpoint is already connected */ .ESOCKTNOSUPPORT = "Socket type not supported",
ENOTCONN :: 107 /* Transport endpoint is not connected */ .EOPNOTSUPP = "Operation not supported",
ESHUTDOWN :: 108 /* Cannot send after transport endpoint shutdown */ .EPFNOSUPPORT = "Protocol family not supported",
ETOOMANYREFS :: 109 /* Too many references: cannot splice */ .EAFNOSUPPORT = "Address family not supported by protocol",
ETIMEDOUT :: 110 /* Connection timed out */ .EADDRINUSE = "Address already in use",
ECONNREFUSED :: 111 /* Connection refused */ .EADDRNOTAVAIL = "Cannot assign requested address",
EHOSTDOWN :: 112 /* Host is down */ .ENETDOWN = "Network is down",
EHOSTUNREACH :: 113 /* No route to host */ .ENETUNREACH = "Network is unreachable",
EALREADY :: 114 /* Operation already in progress */ .ENETRESET = "Network dropped connection on reset",
EINPROGRESS :: 115 /* Operation now in progress */ .ECONNABORTED = "Software caused connection abort",
ESTALE :: 116 /* Stale file handle */ .ECONNRESET = "Connection reset by peer",
EUCLEAN :: 117 /* Structure needs cleaning */ .ENOBUFS = "No buffer space available",
ENOTNAM :: 118 /* Not a XENIX named type file */ .EISCONN = "Transport endpoint is already connected",
ENAVAIL :: 119 /* No XENIX semaphores available */ .ENOTCONN = "Transport endpoint is not connected",
EISNAM :: 120 /* Is a named type file */ .ESHUTDOWN = "Cannot send after transport endpoint shutdown",
EREMOTEIO :: 121 /* Remote I/O error */ .ETOOMANYREFS = "Too many references: cannot splice",
EDQUOT :: 122 /* Quota exceeded */ .ETIMEDOUT = "Connection timed out",
ENOMEDIUM :: 123 /* No medium found */ .ECONNREFUSED = "Connection refused",
EMEDIUMTYPE :: 124 /* Wrong medium type */ .EHOSTDOWN = "Host is down",
ECANCELED :: 125 /* Operation Canceled */ .EHOSTUNREACH = "No route to host",
ENOKEY :: 126 /* Required key not available */ .EALREADY = "Operation already in progress",
EKEYEXPIRED :: 127 /* Key has expired */ .EINPROGRESS = "Operation now in progress",
EKEYREVOKED :: 128 /* Key has been revoked */ .ESTALE = "Stale file handle",
EKEYREJECTED :: 129 /* Key was rejected by service */ .EUCLEAN = "Structure needs cleaning",
EOWNERDEAD :: 130 /* Owner died */ .ENOTNAM = "Not a XENIX named type file",
ENOTRECOVERABLE:: 131 /* State not recoverable */ .ENAVAIL = "No XENIX semaphores available",
ERFKILL :: 132 /* Operation not possible due to RF-kill */ .EISNAM = "Is a named type file",
EHWPOISON :: 133 /* Memory page has hardware error */ .EREMOTEIO = "Remote I/O error",
.EDQUOT = "Disk quota exceeded",
.ENOMEDIUM = "No medium found",
.EMEDIUMTYPE = "Wrong medium type",
.ECANCELED = "Operation canceled",
.ENOKEY = "Required key not available",
.EKEYEXPIRED = "Key has expired",
.EKEYREVOKED = "Key has been revoked",
.EKEYREJECTED = "Key was rejected by service",
.EOWNERDEAD = "Owner died",
.ENOTRECOVERABLE = "State not recoverable",
.ERFKILL = "Operation not possible due to RF-kill",
.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)) 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 { _error_string :: proc(errno: i32) -> string {
if errno == 0 { if errno >= 0 && errno <= i32(max(linux.Errno)) {
return "" return _errno_strings[linux.Errno(errno)]
} }
return "Error" return "Unknown Error"
} }
-3
View File
@@ -45,13 +45,10 @@ O_TRUNC :: File_Flags{.Trunc}
O_SPARSE :: File_Flags{.Sparse} O_SPARSE :: File_Flags{.Sparse}
O_CLOEXEC :: File_Flags{.Close_On_Exec} O_CLOEXEC :: File_Flags{.Close_On_Exec}
stdin: ^File = nil // OS-Specific stdin: ^File = nil // OS-Specific
stdout: ^File = nil // OS-Specific stdout: ^File = nil // OS-Specific
stderr: ^File = nil // OS-Specific stderr: ^File = nil // OS-Specific
@(require_results) @(require_results)
create :: proc(name: string) -> (^File, Error) { create :: proc(name: string) -> (^File, Error) {
return open(name, {.Read, .Write, .Create}, File_Mode(0o777)) return open(name, {.Read, .Write, .Create}, File_Mode(0o777))
+231 -161
View File
@@ -1,39 +1,64 @@
//+private //+private
package os2 package os2
import "base:runtime"
import "core:io" import "core:io"
import "core:time" import "core:time"
import "core:sys/unix" import "base:runtime"
import "core:sys/linux"
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
_File :: struct { _File :: struct {
name: string, name: string,
fd: int, fd: linux.Fd,
allocator: runtime.Allocator, 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
stdin = &_stdin
stdout = &_stdout
stderr = &_stderr
}
_file_allocator :: proc() -> runtime.Allocator {
return heap_allocator()
}
_open :: proc(name: string, flags: File_Flags, perm: File_Mode) -> (f: ^File, err: Error) { _open :: proc(name: string, flags: File_Flags, perm: File_Mode) -> (f: ^File, err: Error) {
TEMP_ALLOCATOR_GUARD() TEMP_ALLOCATOR_GUARD()
name_cstr := temp_cstring(name) or_return name_cstr := temp_cstring(name) or_return
@@ -41,40 +66,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 // Just default to using O_NOCTTY because needing to open a controlling
// terminal would be incredibly rare. This has no effect on files while // terminal would be incredibly rare. This has no effect on files while
// allowing us to open serial devices. // 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 { switch flags & O_RDONLY|O_WRONLY|O_RDWR {
case O_RDONLY: flags_i = _O_RDONLY case O_RDONLY:
case O_WRONLY: flags_i = _O_WRONLY case O_WRONLY: sys_flags += {.WRONLY}
case O_RDWR: flags_i = _O_RDWR case O_RDWR: sys_flags += {.RDWR}
} }
if .Append in flags { flags_i |= _O_APPEND } if .Append in flags { sys_flags += {.APPEND} }
if .Create in flags { flags_i |= _O_CREAT } if .Create in flags { sys_flags += {.CREAT} }
if .Excl in flags { flags_i |= _O_EXCL } if .Excl in flags { sys_flags += {.EXCL} }
if .Sync in flags { flags_i |= _O_SYNC } if .Sync in flags { sys_flags += {.DSYNC} }
if .Trunc in flags { flags_i |= _O_TRUNC } if .Trunc in flags { sys_flags += {.TRUNC} }
if .Close_On_Exec in flags { flags_i |= _O_CLOEXEC } if .Close_On_Exec in flags { sys_flags += {.CLOEXEC} }
fd := unix.sys_open(name_cstr, flags_i, uint(perm)) fd, errno := linux.open(name_cstr, sys_flags, transmute(linux.Mode)(u32(perm)))
if fd < 0 { if errno != .NONE {
return nil, _get_platform_error(fd) return nil, _get_platform_error(errno)
} }
return _new_file(uintptr(fd), name), nil 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 := new(File, file_allocator())
file.impl.fd = int(fd) _construct_file(file, 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,
}
return file 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 { _destroy :: proc(f: ^File) -> Error {
if f == nil { if f == nil {
return nil return nil
@@ -86,12 +119,15 @@ _destroy :: proc(f: ^File) -> Error {
_close :: proc(f: ^File) -> Error { _close :: proc(f: ^File) -> Error {
if f != nil { if f == nil {
res := unix.sys_close(f.impl.fd) return nil
_destroy(f)
return _ok_or_error(res)
} }
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 { _fd :: proc(f: ^File) -> uintptr {
@@ -106,20 +142,32 @@ _name :: proc(f: ^File) -> string {
} }
_seek :: proc(f: ^File, offset: i64, whence: io.Seek_From) -> (ret: i64, err: Error) { _seek :: proc(f: ^File, offset: i64, whence: io.Seek_From) -> (ret: i64, err: Error) {
res := unix.sys_lseek(f.impl.fd, offset, int(whence)) n, errno := linux.lseek(f.impl.fd, offset, linux.Seek_Whence(whence))
if res < 0 { if errno != .NONE {
return -1, _get_platform_error(int(res)) return -1, _get_platform_error(errno)
} }
return res, nil return n, nil
} }
_read :: proc(f: ^File, p: []byte) -> (i64, Error) { _read :: proc(f: ^File, p: []byte) -> (i64, Error) {
if len(p) == 0 { if len(p) == 0 {
return 0, nil return 0, nil
} }
n := unix.sys_read(f.impl.fd, &p[0], len(p)) n, errno := linux.read(f.impl.fd, p[:])
if n < 0 { if errno != .NONE {
return -1, _get_platform_error(n) 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 { if n == 0 {
return 0, .EOF return 0, .EOF
@@ -127,91 +175,67 @@ _read :: proc(f: ^File, p: []byte) -> (i64, Error) {
return i64(n), nil 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) { _write :: proc(f: ^File, p: []byte) -> (i64, Error) {
if len(p) == 0 { if len(p) == 0 {
return 0, nil return 0, nil
} }
n := unix.sys_write(f.impl.fd, &p[0], uint(len(p))) n, errno := linux.write(f.impl.fd, p[:])
if n < 0 { if errno != .NONE {
return -1, _get_platform_error(n) return -1, _get_platform_error(errno)
} }
return i64(n), nil 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 { if offset < 0 {
return 0, .Invalid_Offset return 0, .Invalid_Offset
} }
b, offset := p, offset n, errno := linux.pwrite(f.impl.fd, p[:], offset)
for len(b) > 0 { if errno != .NONE {
m := unix.sys_pwrite(f.impl.fd, &b[0], len(b), offset) return -1, _get_platform_error(errno)
if m < 0 {
return -1, _get_platform_error(m)
}
n += i64(m)
b = b[m:]
offset += i64(m)
} }
return return i64(n), nil
} }
_file_size :: proc(f: ^File) -> (n: i64, err: Error) { _file_size :: proc(f: ^File) -> (n: i64, err: Error) {
s: _Stat = --- s: linux.Stat = ---
res := unix.sys_fstat(f.impl.fd, &s) errno := linux.fstat(f.impl.fd, &s)
if res < 0 { if errno != .NONE {
return -1, _get_platform_error(res) return -1, _get_platform_error(errno)
} }
return s.size, nil return i64(s.size), nil
} }
_sync :: proc(f: ^File) -> Error { _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 { _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 { _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 { _remove :: proc(name: string) -> Error {
TEMP_ALLOCATOR_GUARD() TEMP_ALLOCATOR_GUARD()
name_cstr := temp_cstring(name) or_return name_cstr := temp_cstring(name) or_return
fd := unix.sys_open(name_cstr, int(File_Flags.Read)) fd, errno := linux.open(name_cstr, {.NOFOLLOW})
if fd < 0 { #partial switch (errno) {
return _get_platform_error(fd) 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 _get_platform_error(linux.unlink(name_cstr))
return _ok_or_error(unix.sys_rmdir(name_cstr))
}
return _ok_or_error(unix.sys_unlink(name_cstr))
} }
_rename :: proc(old_name, new_name: string) -> Error { _rename :: proc(old_name, new_name: string) -> Error {
@@ -219,7 +243,7 @@ _rename :: proc(old_name, new_name: string) -> Error {
old_name_cstr := temp_cstring(old_name) or_return old_name_cstr := temp_cstring(old_name) or_return
new_name_cstr := temp_cstring(new_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 { _link :: proc(old_name, new_name: string) -> Error {
@@ -227,148 +251,194 @@ _link :: proc(old_name, new_name: string) -> Error {
old_name_cstr := temp_cstring(old_name) or_return old_name_cstr := temp_cstring(old_name) or_return
new_name_cstr := temp_cstring(new_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 { _symlink :: proc(old_name, new_name: string) -> Error {
TEMP_ALLOCATOR_GUARD() TEMP_ALLOCATOR_GUARD()
old_name_cstr := temp_cstring(old_name) or_return old_name_cstr := temp_cstring(old_name) or_return
new_name_cstr := temp_cstring(new_name) or_return new_name_cstr := temp_cstring(new_name) or_return
return _get_platform_error(linux.symlink(old_name_cstr, new_name_cstr))
return _ok_or_error(unix.sys_symlink(old_name_cstr, new_name_cstr))
} }
_read_link_cstr :: proc(name_cstr: cstring, allocator: runtime.Allocator) -> (string, Error) { _read_link_cstr :: proc(name_cstr: cstring, allocator: runtime.Allocator) -> (string, Error) {
bufsz : uint = 256 bufsz : uint = 256
buf := make([]byte, bufsz, allocator) buf := make([]byte, bufsz, allocator)
for { for {
rc := unix.sys_readlink(name_cstr, &buf[0], bufsz) sz, errno := linux.readlink(name_cstr, buf[:])
if rc < 0 { if errno != .NONE {
delete(buf) delete(buf, allocator)
return "", _get_platform_error(rc) return "", _get_platform_error(errno)
} else if rc == int(bufsz) { } else if sz == int(bufsz) {
bufsz *= 2 bufsz *= 2
delete(buf) delete(buf, allocator)
buf = make([]byte, bufsz, allocator) buf = make([]byte, bufsz, allocator)
} else { } 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() TEMP_ALLOCATOR_GUARD()
name_cstr := temp_cstring(name) or_return name_cstr := temp_cstring(name) or_return
return _read_link_cstr(name_cstr, allocator) 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 { _chdir :: proc(name: string) -> Error {
TEMP_ALLOCATOR_GUARD() TEMP_ALLOCATOR_GUARD()
name_cstr := temp_cstring(name) or_return 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 { _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 { _chmod :: proc(name: string, mode: File_Mode) -> Error {
TEMP_ALLOCATOR_GUARD() TEMP_ALLOCATOR_GUARD()
name_cstr := temp_cstring(name) or_return 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 { _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 // NOTE: will throw error without super user priviledges
_chown :: proc(name: string, uid, gid: int) -> Error { _chown :: proc(name: string, uid, gid: int) -> Error {
TEMP_ALLOCATOR_GUARD() TEMP_ALLOCATOR_GUARD()
name_cstr := temp_cstring(name) or_return 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 // NOTE: will throw error without super user priviledges
_lchown :: proc(name: string, uid, gid: int) -> Error { _lchown :: proc(name: string, uid, gid: int) -> Error {
TEMP_ALLOCATOR_GUARD() TEMP_ALLOCATOR_GUARD()
name_cstr := temp_cstring(name) or_return 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 // NOTE: will throw error without super user priviledges
_fchown :: proc(f: ^File, uid, gid: int) -> Error { _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 { _chtimes :: proc(name: string, atime, mtime: time.Time) -> Error {
TEMP_ALLOCATOR_GUARD() TEMP_ALLOCATOR_GUARD()
name_cstr := temp_cstring(name) or_return name_cstr := temp_cstring(name) or_return
times := [2]Unix_File_Time { times := [2]linux.Time_Spec {
{ atime._nsec, 0 }, {
{ mtime._nsec, 0 }, 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 { _fchtimes :: proc(f: ^File, atime, mtime: time.Time) -> Error {
times := [2]Unix_File_Time { times := [2]linux.Time_Spec {
{ atime._nsec, 0 }, {
{ mtime._nsec, 0 }, 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 { _exists :: proc(name: string) -> bool {
TEMP_ALLOCATOR_GUARD() TEMP_ALLOCATOR_GUARD()
name_cstr, _ := temp_cstring(name) 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 { _is_file :: proc(name: string) -> bool {
TEMP_ALLOCATOR_GUARD() TEMP_ALLOCATOR_GUARD()
name_cstr, _ := temp_cstring(name) name_cstr, _ := temp_cstring(name)
s: _Stat s: linux.Stat
res := unix.sys_stat(name_cstr, &s) if linux.stat(name_cstr, &s) != .NONE {
if res < 0 {
return false return false
} }
return S_ISREG(s.mode) return linux.S_ISREG(s.mode)
} }
_is_file_fd :: proc(fd: int) -> bool { _is_file_fd :: proc(fd: linux.Fd) -> bool {
s: _Stat s: linux.Stat
res := unix.sys_fstat(fd, &s) if linux.fstat(fd, &s) != .NONE {
if res < 0 { // error
return false return false
} }
return S_ISREG(s.mode) return linux.S_ISREG(s.mode)
} }
_is_dir :: proc(name: string) -> bool { _is_dir :: proc(name: string) -> bool {
TEMP_ALLOCATOR_GUARD() TEMP_ALLOCATOR_GUARD()
name_cstr, _ := temp_cstring(name) name_cstr, _ := temp_cstring(name)
s: _Stat s: linux.Stat
res := unix.sys_stat(name_cstr, &s) if linux.stat(name_cstr, &s) != .NONE {
if res < 0 {
return false return false
} }
return S_ISDIR(s.mode) return linux.S_ISDIR(s.mode)
} }
_is_dir_fd :: proc(fd: int) -> bool { _is_dir_fd :: proc(fd: linux.Fd) -> bool {
s: _Stat s: linux.Stat
res := unix.sys_fstat(fd, &s) if linux.fstat(fd, &s) != .NONE {
if res < 0 { // error
return false 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") @(private="package")
+11 -17
View File
@@ -1,7 +1,7 @@
//+private //+private
package os2 package os2
import "core:sys/unix" import "core:sys/linux"
import "core:sync" import "core:sync"
import "core:mem" import "core:mem"
@@ -97,9 +97,8 @@ CURRENTLY_ACTIVE :: (^^Region)(~uintptr(0))
FREE_LIST_ENTRIES_PER_BLOCK :: BLOCK_SIZE / size_of(u16) FREE_LIST_ENTRIES_PER_BLOCK :: BLOCK_SIZE / size_of(u16)
MMAP_FLAGS :: unix.MAP_ANONYMOUS | unix.MAP_PRIVATE MMAP_FLAGS : linux.Map_Flags : {.ANONYMOUS, .PRIVATE}
MMAP_PROT :: unix.PROT_READ | unix.PROT_WRITE MMAP_PROT : linux.Mem_Protection : {.READ, .WRITE}
@thread_local _local_region: ^Region @thread_local _local_region: ^Region
global_regions: ^Region global_regions: ^Region
@@ -324,11 +323,11 @@ heap_free :: proc(memory: rawptr) {
// Regions // Regions
// //
_new_region :: proc() -> ^Region #no_bounds_check { _new_region :: proc() -> ^Region #no_bounds_check {
res := unix.sys_mmap(nil, uint(SIZE_OF_REGION), MMAP_PROT, MMAP_FLAGS, -1, 0) ptr, errno := linux.mmap(0, uint(SIZE_OF_REGION), MMAP_PROT, MMAP_FLAGS, -1, 0)
if res < 0 { if errno != .NONE {
return nil return nil
} }
new_region := (^Region)(uintptr(res)) new_region := (^Region)(ptr)
new_region.hdr.local_addr = CURRENTLY_ACTIVE new_region.hdr.local_addr = CURRENTLY_ACTIVE
new_region.hdr.reset_addr = &_local_region 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 { _direct_mmap_alloc :: proc(size: int) -> rawptr {
mmap_size := _round_up_to_nearest(size + BLOCK_SIZE, PAGE_SIZE) 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) new_allocation, errno := linux.mmap(0, uint(mmap_size), MMAP_PROT, MMAP_FLAGS, -1, 0)
if new_allocation < 0 && new_allocation > -4096 { if errno != .NONE {
return nil return nil
} }
@@ -655,13 +654,8 @@ _direct_mmap_resize :: proc(alloc: ^Allocation_Header, new_size: int) -> rawptr
return mem.ptr_offset(alloc, 1) return mem.ptr_offset(alloc, 1)
} }
new_allocation := unix.sys_mremap( new_allocation, errno := linux.mremap(alloc, uint(old_mmap_size), uint(new_mmap_size), {.MAYMOVE})
alloc, if errno != .NONE {
uint(old_mmap_size),
uint(new_mmap_size),
unix.MREMAP_MAYMOVE,
)
if new_allocation < 0 && new_allocation > -4096 {
return nil 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) { _direct_mmap_free :: proc(alloc: ^Allocation_Header) {
requested := int(alloc.requested & REQUESTED_MASK) requested := int(alloc.requested & REQUESTED_MASK)
mmap_size := _round_up_to_nearest(requested + BLOCK_SIZE, PAGE_SIZE) 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 "core:strconv"
import "base:runtime" import "base:runtime"
import "core:sys/unix" import "core:sys/linux"
_Path_Separator :: '/' _Path_Separator :: '/'
_Path_Separator_String :: "/" _Path_Separator_String :: "/"
_Path_List_Separator :: ':' _Path_List_Separator :: ':'
_S_IFMT :: 0o170000 // Type of file mask _OPENDIR_FLAGS : linux.Open_Flags : {.NONBLOCK, .DIRECTORY, .LARGEFILE, .CLOEXEC}
_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
_is_path_separator :: proc(c: byte) -> bool { _is_path_separator :: proc(c: byte) -> bool {
return c == '/' return c == '/'
} }
_mkdir :: proc(path: string, perm: File_Mode) -> Error { _mkdir :: proc(path: string, perm: File_Mode) -> Error {
// NOTE: These modes would require sys_mknod, however, that would require // TODO: These modes would require mknod, however, that would also
// additional arguments to this function. // require additional arguments to this function..
if perm & (File_Mode_Named_Pipe | File_Mode_Device | File_Mode_Char_Device | File_Mode_Sym_Link) != 0 { if perm & (File_Mode_Named_Pipe | File_Mode_Device | File_Mode_Char_Device | File_Mode_Sym_Link) != 0 {
return .Invalid_Argument return .Invalid_Argument
} }
TEMP_ALLOCATOR_GUARD() TEMP_ALLOCATOR_GUARD()
path_cstr := temp_cstring(path) or_return 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 { _mkdir_all :: proc(path: string, perm: File_Mode) -> Error {
_mkdirat :: proc(dfd: int, path: []u8, perm: int, has_created: ^bool) -> Error { mkdirat :: proc(dfd: linux.Fd, path: []u8, perm: int, has_created: ^bool) -> Error {
if len(path) == 0 {
return _ok_or_error(unix.sys_close(dfd))
}
i: int 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 path[i] = 0
new_dfd := unix.sys_openat(dfd, cstring(&path[0]), _OPENDIR_FLAGS) new_dfd, errno := linux.openat(dfd, cstring(&path[0]), _OPENDIR_FLAGS)
switch new_dfd { #partial switch errno {
case -ENOENT: case .ENOENT:
if res := unix.sys_mkdirat(dfd, cstring(&path[0]), uint(perm)); res < 0 { if errno = linux.mkdirat(dfd, cstring(&path[0]), transmute(linux.Mode)(u32(perm))); errno != .NONE {
return _get_platform_error(res) return _get_platform_error(errno)
} }
has_created^ = true has_created^ = true
if new_dfd = unix.sys_openat(dfd, cstring(&path[0]), _OPENDIR_FLAGS); new_dfd < 0 { if new_dfd, errno = linux.openat(dfd, cstring(&path[0]), _OPENDIR_FLAGS); errno != .NONE {
return _get_platform_error(new_dfd) return _get_platform_error(errno)
} }
fallthrough fallthrough
case 0: case .NONE:
if res := unix.sys_close(dfd); res < 0 { if errno = linux.close(dfd); errno != .NONE {
return _get_platform_error(res) return _get_platform_error(errno)
} }
// skip consecutive '/' // skip consecutive '/'
for i += 1; i < len(path) && path[i] == '/'; i += 1 {} 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: case:
return _get_platform_error(new_dfd) return _get_platform_error(errno)
} }
unreachable() unreachable()
} }
// TODO
if perm & (File_Mode_Named_Pipe | File_Mode_Device | File_Mode_Char_Device | File_Mode_Sym_Link) != 0 { if perm & (File_Mode_Named_Pipe | File_Mode_Device | File_Mode_Char_Device | File_Mode_Sym_Link) != 0 {
return .Invalid_Argument return .Invalid_Argument
} }
TEMP_ALLOCATOR_GUARD() TEMP_ALLOCATOR_GUARD()
// need something we can edit, and use to generate cstrings // need something we can edit, and use to generate cstrings
allocated: bool path_bytes := make([]u8, len(path) + 1, temp_allocator())
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())
}
// 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) copy(path_bytes, path)
path_bytes[len(path)] = 0 path_bytes[len(path)] = 0
dfd: int dfd: linux.Fd
errno: linux.Errno
if path_bytes[0] == '/' { if path_bytes[0] == '/' {
dfd = unix.sys_open("/", _OPENDIR_FLAGS) dfd, errno = linux.open("/", _OPENDIR_FLAGS)
path_bytes = path_bytes[1:] path_bytes = path_bytes[1:]
} else { } else {
dfd = unix.sys_open(".", _OPENDIR_FLAGS) dfd, errno = linux.open(".", _OPENDIR_FLAGS)
} }
if dfd < 0 { if errno != .NONE {
return _get_platform_error(dfd) return _get_platform_error(errno)
} }
has_created: bool 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 { if has_created {
return nil return nil
} }
@@ -119,28 +104,28 @@ dirent64 :: struct {
_remove_all :: proc(path: string) -> Error { _remove_all :: proc(path: string) -> Error {
DT_DIR :: 4 DT_DIR :: 4
_remove_all_dir :: proc(dfd: int) -> Error { remove_all_dir :: proc(dfd: linux.Fd) -> Error {
n := 64 n := 64
buf := make([]u8, n) buf := make([]u8, n)
defer delete(buf) defer delete(buf)
loop: for { loop: for {
getdents_res := unix.sys_getdents64(dfd, &buf[0], n) buflen, errno := linux.getdents(dfd, buf[:])
switch getdents_res { #partial switch errno {
case -EINVAL: case .EINVAL:
delete(buf) delete(buf)
n *= 2 n *= 2
buf = make([]u8, n) buf = make([]u8, n)
continue loop continue loop
case -4096..<0: case .NONE:
return _get_platform_error(getdents_res) if buflen == 0 { break loop }
case 0: case:
break loop return _get_platform_error(errno)
} }
d: ^dirent64 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 = (^dirent64)(rawptr(&buf[i]))
d_name_cstr := cstring(&d.d_name[0]) d_name_cstr := cstring(&d.d_name[0])
@@ -156,23 +141,22 @@ _remove_all :: proc(path: string) -> Error {
continue continue
} }
unlink_res: int
switch d.d_type { switch d.d_type {
case DT_DIR: case DT_DIR:
new_dfd := unix.sys_openat(dfd, d_name_cstr, _OPENDIR_FLAGS) new_dfd: linux.Fd
if new_dfd < 0 { new_dfd, errno = linux.openat(dfd, d_name_cstr, _OPENDIR_FLAGS)
return _get_platform_error(new_dfd) if errno != .NONE {
return _get_platform_error(errno)
} }
defer unix.sys_close(new_dfd) defer linux.close(new_dfd)
_remove_all_dir(new_dfd) or_return remove_all_dir(new_dfd) or_return
unlink_res = unix.sys_unlinkat(dfd, d_name_cstr, int(unix.AT_REMOVEDIR)) errno = linux.unlinkat(dfd, d_name_cstr, {.REMOVEDIR})
case: case:
unlink_res = unix.sys_unlinkat(dfd, d_name_cstr) errno = linux.unlinkat(dfd, d_name_cstr, nil)
} }
if unlink_res < 0 { if errno != .NONE {
return _get_platform_error(unlink_res) return _get_platform_error(errno)
} }
} }
} }
@@ -182,17 +166,19 @@ _remove_all :: proc(path: string) -> Error {
TEMP_ALLOCATOR_GUARD() TEMP_ALLOCATOR_GUARD()
path_cstr := temp_cstring(path) or_return path_cstr := temp_cstring(path) or_return
fd := unix.sys_open(path_cstr, _OPENDIR_FLAGS) fd, errno := linux.open(path_cstr, _OPENDIR_FLAGS)
switch fd { #partial switch errno {
case -ENOTDIR: case .NONE:
return _ok_or_error(unix.sys_unlink(path_cstr)) break
case -4096..<0: case .ENOTDIR:
return _get_platform_error(fd) return _get_platform_error(linux.unlink(path_cstr))
case:
return _get_platform_error(errno)
} }
defer unix.sys_close(fd) defer linux.close(fd)
_remove_all_dir(fd) or_return remove_all_dir(fd) or_return
return _ok_or_error(unix.sys_rmdir(path_cstr)) return _get_platform_error(linux.rmdir(path_cstr))
} }
_getwd :: proc(allocator: runtime.Allocator) -> (string, Error) { _getwd :: proc(allocator: runtime.Allocator) -> (string, Error) {
@@ -203,13 +189,12 @@ _getwd :: proc(allocator: runtime.Allocator) -> (string, Error) {
PATH_MAX :: 4096 PATH_MAX :: 4096
buf := make([dynamic]u8, PATH_MAX, allocator) buf := make([dynamic]u8, PATH_MAX, allocator)
for { for {
#no_bounds_check res := unix.sys_getcwd(&buf[0], uint(len(buf))) #no_bounds_check n, errno := linux.getcwd(buf[:])
if errno == .NONE {
if res >= 0 { return string(buf[:n-1]), nil
return string_from_null_terminated_bytes(buf[:]), nil
} }
if res != -ERANGE { if errno != .ERANGE {
return "", _get_platform_error(res) return "", _get_platform_error(errno)
} }
resize(&buf, len(buf)+PATH_MAX) resize(&buf, len(buf)+PATH_MAX)
} }
@@ -218,16 +203,16 @@ _getwd :: proc(allocator: runtime.Allocator) -> (string, Error) {
_setwd :: proc(dir: string) -> Error { _setwd :: proc(dir: string) -> Error {
dir_cstr := temp_cstring(dir) or_return 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/" PROC_FD_PATH :: "/proc/self/fd/"
buf: [32]u8 buf: [32]u8
copy(buf[:], PROC_FD_PATH) copy(buf[:], PROC_FD_PATH)
strconv.itoa(buf[len(PROC_FD_PATH):], fd) strconv.itoa(buf[len(PROC_FD_PATH):], int(fd))
fullpath: string fullpath: string
err: Error err: Error
@@ -236,4 +221,3 @@ _get_full_path :: proc(fd: int, allocator: runtime.Allocator) -> string {
} }
return fullpath return fullpath
} }
+11 -1
View File
@@ -1,7 +1,17 @@
//+private //+private
package os2 package os2
import "core:sys/linux"
_pipe :: proc() -> (r, w: ^File, err: Error) { _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
} }
+20 -96
View File
@@ -3,108 +3,32 @@ package os2
import "core:time" import "core:time"
import "base:runtime" import "base:runtime"
import "core:sys/unix" import "core:sys/linux"
import "core:path/filepath" 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) { _fstat :: proc(f: ^File, allocator: runtime.Allocator) -> (File_Info, Error) {
return _fstat_internal(f.impl.fd, allocator) return _fstat_internal(f.impl.fd, allocator)
} }
_fstat_internal :: proc(fd: int, allocator: runtime.Allocator) -> (File_Info, Error) { _fstat_internal :: proc(fd: linux.Fd, allocator: runtime.Allocator) -> (File_Info, Error) {
s: _Stat s: linux.Stat
result := unix.sys_fstat(fd, &s) errno := linux.fstat(fd, &s)
if result < 0 { if errno != .NONE {
return {}, _get_platform_error(result) return {}, _get_platform_error(errno)
} }
// TODO: As of Linux 4.11, the new statx syscall can retrieve creation_time // TODO: As of Linux 4.11, the new statx syscall can retrieve creation_time
fi := File_Info { fi := File_Info {
fullpath = _get_full_path(fd, allocator), fullpath = _get_full_path(fd, allocator),
name = "", name = "",
size = s.size, size = i64(s.size),
mode = 0, mode = 0,
is_directory = S_ISDIR(s.mode), is_directory = linux.S_ISDIR(s.mode),
modification_time = time.Time {s.modified.seconds}, modification_time = time.Time {i64(s.mtime.time_sec) * i64(time.Second) + i64(s.mtime.time_nsec)},
access_time = time.Time {s.last_access.seconds}, access_time = time.Time {i64(s.atime.time_sec) * i64(time.Second) + i64(s.atime.time_nsec)},
creation_time = time.Time{0}, // regular stat does not provide this 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) fi.name = filepath.base(fi.fullpath)
return fi, nil return fi, nil
@@ -115,11 +39,11 @@ _stat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, err
TEMP_ALLOCATOR_GUARD() TEMP_ALLOCATOR_GUARD()
name_cstr := temp_cstring(name) or_return name_cstr := temp_cstring(name) or_return
fd := unix.sys_open(name_cstr, _O_RDONLY) fd, errno := linux.open(name_cstr, {})
if fd < 0 { if errno != .NONE {
return {}, _get_platform_error(fd) return {}, _get_platform_error(errno)
} }
defer unix.sys_close(fd) defer linux.close(fd)
return _fstat_internal(fd, allocator) return _fstat_internal(fd, allocator)
} }
@@ -127,11 +51,11 @@ _lstat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, er
TEMP_ALLOCATOR_GUARD() TEMP_ALLOCATOR_GUARD()
name_cstr := temp_cstring(name) or_return name_cstr := temp_cstring(name) or_return
fd := unix.sys_open(name_cstr, _O_RDONLY | _O_PATH | _O_NOFOLLOW) fd, errno := linux.open(name_cstr, {.PATH, .NOFOLLOW})
if fd < 0 { if errno != .NONE {
return {}, _get_platform_error(fd) return {}, _get_platform_error(errno)
} }
defer unix.sys_close(fd) defer linux.close(fd)
return _fstat_internal(fd, allocator) return _fstat_internal(fd, allocator)
} }
+76 -39
View File
@@ -48,6 +48,7 @@ Errno :: enum i32 {
ENOSYS = 38, ENOSYS = 38,
ENOTEMPTY = 39, ENOTEMPTY = 39,
ELOOP = 40, ELOOP = 40,
EUNKNOWN_41 = 41,
ENOMSG = 42, ENOMSG = 42,
EIDRM = 43, EIDRM = 43,
ECHRNG = 44, ECHRNG = 44,
@@ -64,6 +65,7 @@ Errno :: enum i32 {
ENOANO = 55, ENOANO = 55,
EBADRQC = 56, EBADRQC = 56,
EBADSLT = 57, EBADSLT = 57,
EUNKNOWN_58 = 58,
EBFONT = 59, EBFONT = 59,
ENOSTR = 60, ENOSTR = 60,
ENODATA = 61, ENODATA = 61,
@@ -150,44 +152,66 @@ Errno :: enum i32 {
RDONLY flag is not present, because it has the value of 0, i.e. it is the RDONLY flag is not present, because it has the value of 0, i.e. it is the
default, unless WRONLY or RDWR is specified. default, unless WRONLY or RDWR is specified.
*/ */
Open_Flags_Bits :: enum { when ODIN_ARCH != .arm64 && ODIN_ARCH != .arm32 {
WRONLY = 0, Open_Flags_Bits :: enum {
RDWR = 1, WRONLY = 0,
CREAT = 6, RDWR = 1,
EXCL = 7, CREAT = 6,
NOCTTY = 8, EXCL = 7,
TRUNC = 9, NOCTTY = 8,
APPEND = 10, TRUNC = 9,
NONBLOCK = 11, APPEND = 10,
DSYNC = 12, NONBLOCK = 11,
ASYNC = 13, DSYNC = 12,
DIRECT = 14, ASYNC = 13,
LARGEFILE = 15, DIRECT = 14,
DIRECTORY = 16, LARGEFILE = 15,
NOFOLLOW = 17, DIRECTORY = 16,
NOATIME = 18, NOFOLLOW = 17,
CLOEXEC = 19, NOATIME = 18,
PATH = 21, CLOEXEC = 19,
} PATH = 21,
}
// https://github.com/torvalds/linux/blob/7367539ad4b0f8f9b396baf02110962333719a48/include/uapi/asm-generic/fcntl.h#L19
#assert(1 << uint(Open_Flags_Bits.WRONLY) == 0o0000000_1)
#assert(1 << uint(Open_Flags_Bits.RDWR) == 0o0000000_2)
#assert(1 << uint(Open_Flags_Bits.CREAT) == 0o00000_100)
#assert(1 << uint(Open_Flags_Bits.EXCL) == 0o00000_200)
#assert(1 << uint(Open_Flags_Bits.NOCTTY) == 0o00000_400)
#assert(1 << uint(Open_Flags_Bits.TRUNC) == 0o0000_1000)
#assert(1 << uint(Open_Flags_Bits.APPEND) == 0o0000_2000)
#assert(1 << uint(Open_Flags_Bits.NONBLOCK) == 0o0000_4000)
#assert(1 << uint(Open_Flags_Bits.DSYNC) == 0o000_10000)
#assert(1 << uint(Open_Flags_Bits.ASYNC) == 0o000_20000)
#assert(1 << uint(Open_Flags_Bits.DIRECT) == 0o000_40000)
#assert(1 << uint(Open_Flags_Bits.LARGEFILE) == 0o00_100000)
#assert(1 << uint(Open_Flags_Bits.DIRECTORY) == 0o00_200000)
#assert(1 << uint(Open_Flags_Bits.NOFOLLOW) == 0o00_400000)
#assert(1 << uint(Open_Flags_Bits.NOATIME) == 0o0_1000000)
#assert(1 << uint(Open_Flags_Bits.CLOEXEC) == 0o0_2000000)
#assert(1 << uint(Open_Flags_Bits.PATH) == 0o_10000000)
// https://github.com/torvalds/linux/blob/7367539ad4b0f8f9b396baf02110962333719a48/include/uapi/asm-generic/fcntl.h#L19 } else {
#assert(1 << uint(Open_Flags_Bits.WRONLY) == 0o0000000_1) Open_Flags_Bits :: enum {
#assert(1 << uint(Open_Flags_Bits.RDWR) == 0o0000000_2) WRONLY = 0,
#assert(1 << uint(Open_Flags_Bits.CREAT) == 0o00000_100) RDWR = 1,
#assert(1 << uint(Open_Flags_Bits.EXCL) == 0o00000_200) CREAT = 6,
#assert(1 << uint(Open_Flags_Bits.NOCTTY) == 0o00000_400) EXCL = 7,
#assert(1 << uint(Open_Flags_Bits.TRUNC) == 0o0000_1000) NOCTTY = 8,
#assert(1 << uint(Open_Flags_Bits.APPEND) == 0o0000_2000) TRUNC = 9,
#assert(1 << uint(Open_Flags_Bits.NONBLOCK) == 0o0000_4000) APPEND = 10,
#assert(1 << uint(Open_Flags_Bits.DSYNC) == 0o000_10000) NONBLOCK = 11,
#assert(1 << uint(Open_Flags_Bits.ASYNC) == 0o000_20000) DSYNC = 12,
#assert(1 << uint(Open_Flags_Bits.DIRECT) == 0o000_40000) ASYNC = 13,
#assert(1 << uint(Open_Flags_Bits.LARGEFILE) == 0o00_100000) DIRECTORY = 14,
#assert(1 << uint(Open_Flags_Bits.DIRECTORY) == 0o00_200000) NOFOLLOW = 15,
#assert(1 << uint(Open_Flags_Bits.NOFOLLOW) == 0o00_400000) DIRECT = 16,
#assert(1 << uint(Open_Flags_Bits.NOATIME) == 0o0_1000000) LARGEFILE = 17,
#assert(1 << uint(Open_Flags_Bits.CLOEXEC) == 0o0_2000000) NOATIME = 18,
#assert(1 << uint(Open_Flags_Bits.PATH) == 0o_10000000) CLOEXEC = 19,
PATH = 21,
}
}
/* /*
Bits for FD_Flags bitset Bits for FD_Flags bitset
@@ -867,7 +891,7 @@ Wait_Option :: enum {
WSTOPPED = 1, WSTOPPED = 1,
WEXITED = 2, WEXITED = 2,
WCONTINUED = 3, WCONTINUED = 3,
WNOWAIT = 24, WNOWAIT = 24,
// // For processes created using clone // // For processes created using clone
__WNOTHREAD = 29, __WNOTHREAD = 29,
__WALL = 30, __WALL = 30,
@@ -946,9 +970,22 @@ Sig_Stack_Flag :: enum i32 {
AUTODISARM = 31, AUTODISARM = 31,
} }
Sig_Action_Flag :: enum u32 {
NOCLDSTOP = 0,
NOCLDWAIT = 1,
SIGINFO = 2,
UNSUPPORTED = 10,
EXPOSE_TAGBITS = 11,
RESTORER = 26,
ONSTACK = 27,
RESTART = 28,
NODEFER = 30,
RESETHAND = 31,
}
/* /*
Type of socket to create Type of socket to create
- For TCP you want to use SOCK_STREAM - For TCP you want to use SOCK_STREAM
- For UDP you want to use SOCK_DGRAM - For UDP you want to use SOCK_DGRAM
Also see `Protocol` Also see `Protocol`
*/ */
+31 -16
View File
@@ -69,7 +69,7 @@ close :: proc "contextless" (fd: Fd) -> (Errno) {
stat :: proc "contextless" (filename: cstring, stat: ^Stat) -> (Errno) { stat :: proc "contextless" (filename: cstring, stat: ^Stat) -> (Errno) {
when size_of(int) == 8 { when size_of(int) == 8 {
when ODIN_ARCH == .arm64 { when ODIN_ARCH == .arm64 {
ret := syscall(SYS_fstatat, AT_FDCWD, cast(rawptr) filename, stat) ret := syscall(SYS_fstatat, AT_FDCWD, cast(rawptr) filename, stat, 0)
return Errno(-ret) return Errno(-ret)
} else { } else {
ret := syscall(SYS_stat, cast(rawptr) filename, stat) ret := syscall(SYS_stat, cast(rawptr) filename, stat)
@@ -200,10 +200,25 @@ brk :: proc "contextless" (addr: uintptr) -> (Errno) {
return Errno(-ret) return Errno(-ret)
} }
/*
Returns from signal handlers on some archs.
*/
rt_sigreturn :: proc "c" () -> ! {
intrinsics.syscall(uintptr(SYS_rt_sigreturn))
unreachable()
}
/* /*
Alter an action taken by a process. Alter an action taken by a process.
*/ */
rt_sigaction :: proc "contextless" (sig: Signal, sigaction: ^Sig_Action, old_sigaction: ^Sig_Action) -> Errno { rt_sigaction :: proc "contextless" (sig: Signal, sigaction: ^Sig_Action($T), old_sigaction: ^Sig_Action) -> Errno {
// NOTE(jason): It appears that the restorer is required for i386 and amd64
when ODIN_ARCH == .i386 || ODIN_ARCH == .amd64 {
sigaction.flags += {.RESTORER}
}
if sigaction != nil && sigaction.restorer == nil && .RESTORER in sigaction.flags {
sigaction.restorer = rt_sigreturn
}
ret := syscall(SYS_rt_sigaction, sig, sigaction, old_sigaction, size_of(Sig_Set)) ret := syscall(SYS_rt_sigaction, sig, sigaction, old_sigaction, size_of(Sig_Set))
return Errno(-ret) return Errno(-ret)
} }
@@ -1123,7 +1138,7 @@ ftruncate :: proc "contextless" (fd: Fd, length: i64) -> (Errno) {
ret := syscall(SYS_ftruncate64, fd, compat64_arg_pair(length)) ret := syscall(SYS_ftruncate64, fd, compat64_arg_pair(length))
return Errno(-ret) return Errno(-ret)
} else { } else {
ret := syscall(SYS_truncate, fd, compat64_arg_pair(length)) ret := syscall(SYS_ftruncate, fd, compat64_arg_pair(length))
return Errno(-ret) return Errno(-ret)
} }
} }
@@ -1231,7 +1246,7 @@ creat :: proc "contextless" (name: cstring, mode: Mode) -> (Fd, Errno) {
*/ */
link :: proc "contextless" (target: cstring, linkpath: cstring) -> (Errno) { link :: proc "contextless" (target: cstring, linkpath: cstring) -> (Errno) {
when ODIN_ARCH == .arm64 { when ODIN_ARCH == .arm64 {
ret := syscall(SYS_linkat, AT_FDCWD, cast(rawptr) target, AT_FDCWD, cast(rawptr) linkpath) ret := syscall(SYS_linkat, AT_FDCWD, cast(rawptr) target, AT_FDCWD, cast(rawptr) linkpath, 0)
return Errno(-ret) return Errno(-ret)
} else { } else {
ret := syscall(SYS_link, cast(rawptr) target, cast(rawptr) linkpath) ret := syscall(SYS_link, cast(rawptr) target, cast(rawptr) linkpath)
@@ -1261,7 +1276,7 @@ unlink :: proc "contextless" (name: cstring) -> (Errno) {
*/ */
symlink :: proc "contextless" (target: cstring, linkpath: cstring) -> (Errno) { symlink :: proc "contextless" (target: cstring, linkpath: cstring) -> (Errno) {
when ODIN_ARCH == .arm64 { when ODIN_ARCH == .arm64 {
ret := syscall(SYS_symlinkat, AT_FDCWD, cast(rawptr) target, cast(rawptr) linkpath) ret := syscall(SYS_symlinkat, cast(rawptr) target, AT_FDCWD, cast(rawptr) linkpath)
return Errno(-ret) return Errno(-ret)
} else { } else {
ret := syscall(SYS_symlink, cast(rawptr) target, cast(rawptr) linkpath) ret := syscall(SYS_symlink, cast(rawptr) target, cast(rawptr) linkpath)
@@ -1291,7 +1306,7 @@ readlink :: proc "contextless" (name: cstring, buf: []u8) -> (int, Errno) {
*/ */
chmod :: proc "contextless" (name: cstring, mode: Mode) -> (Errno) { chmod :: proc "contextless" (name: cstring, mode: Mode) -> (Errno) {
when ODIN_ARCH == .arm64 { when ODIN_ARCH == .arm64 {
ret := syscall(SYS_fchmodat, cast(rawptr) name, transmute(u32) mode, 0) ret := syscall(SYS_fchmodat, AT_FDCWD, cast(rawptr) name, transmute(u32) mode)
return Errno(-ret) return Errno(-ret)
} else { } else {
ret := syscall(SYS_chmod, cast(rawptr) name, transmute(u32) mode) ret := syscall(SYS_chmod, cast(rawptr) name, transmute(u32) mode)
@@ -2476,8 +2491,8 @@ tgkill :: proc "contextless" (tgid, tid: Pid, sig: Signal) -> (Errno) {
Wait on process, process group or pid file descriptor. Wait on process, process group or pid file descriptor.
Available since Linux 2.6.10. Available since Linux 2.6.10.
*/ */
waitid :: proc "contextless" (id_type: Id_Type, id: Id, sig_info: ^Sig_Info, options: Wait_Options) -> (Errno) { waitid :: proc "contextless" (id_type: Id_Type, id: Id, sig_info: ^Sig_Info, options: Wait_Options, rusage: ^RUsage) -> (Errno) {
ret := syscall(SYS_waitid, id_type, id, sig_info, transmute(i32) options) ret := syscall(SYS_waitid, id_type, id, sig_info, transmute(i32) options, rusage)
return Errno(-ret) return Errno(-ret)
} }
@@ -2504,7 +2519,7 @@ waitid :: proc "contextless" (id_type: Id_Type, id: Id, sig_info: ^Sig_Info, opt
Available since Linux 2.6.16. Available since Linux 2.6.16.
*/ */
openat :: proc "contextless" (fd: Fd, name: cstring, flags: Open_Flags, mode: Mode = {}) -> (Fd, Errno) { openat :: proc "contextless" (fd: Fd, name: cstring, flags: Open_Flags, mode: Mode = {}) -> (Fd, Errno) {
ret := syscall(SYS_openat, fd, AT_FDCWD, transmute(uintptr) name, transmute(u32) mode) ret := syscall(SYS_openat, fd, transmute(uintptr) name, transmute(u32) flags, transmute(u32) mode)
return errno_unwrap(ret, Fd) return errno_unwrap(ret, Fd)
} }
@@ -2583,8 +2598,8 @@ linkat :: proc "contextless" (target_dirfd: Fd, oldpath: cstring, link_dirfd: Fd
Create a symbolic link at specified dirfd. Create a symbolic link at specified dirfd.
Available since Linux 2.6.16. Available since Linux 2.6.16.
*/ */
symlinkat :: proc "contextless" (dirfd: Fd, target: cstring, linkpath: cstring) -> (Errno) { symlinkat :: proc "contextless" (target: cstring, dirfd: Fd, linkpath: cstring) -> (Errno) {
ret := syscall(SYS_symlinkat, dirfd, cast(rawptr) target, cast(rawptr) linkpath) ret := syscall(SYS_symlinkat, cast(rawptr) target, dirfd, cast(rawptr) linkpath)
return Errno(-ret) return Errno(-ret)
} }
@@ -2619,13 +2634,13 @@ faccessat :: proc "contextless" (dirfd: Fd, name: cstring, mode: Mode = F_OK) ->
Wait for events on a file descriptor. Wait for events on a file descriptor.
Available since Linux 2.6.16. Available since Linux 2.6.16.
*/ */
ppoll :: proc "contextless" (fds: []Poll_Fd, timeout: ^Time_Spec, sigmask: ^Sig_Set) -> (Errno) { ppoll :: proc "contextless" (fds: []Poll_Fd, timeout: ^Time_Spec, sigmask: ^Sig_Set) -> (i32, Errno) {
when size_of(int) == 8 { when size_of(int) == 8 {
ret := syscall(SYS_ppoll, raw_data(fds), len(fds), timeout, sigmask, size_of(Sig_Set)) ret := syscall(SYS_ppoll, raw_data(fds), len(fds), timeout, sigmask, size_of(Sig_Set))
return Errno(-ret) return errno_unwrap(ret, i32)
} else { } else {
ret := syscall(SYS_ppoll_time64, raw_data(fds), len(fds), timeout, sigmask, size_of(Sig_Set)) ret := syscall(SYS_ppoll_time64, raw_data(fds), len(fds), timeout, sigmask, size_of(Sig_Set))
return Errno(-ret) return errno_unwrap(ret, i32)
} }
} }
@@ -2808,8 +2823,8 @@ getrandom :: proc "contextless" (buf: []u8, flags: Get_Random_Flags) -> (int, Er
Execute program relative to a directory file descriptor. Execute program relative to a directory file descriptor.
Available since Linux 3.19. Available since Linux 3.19.
*/ */
execveat :: proc "contextless" (dirfd: Fd, name: cstring, argv: [^]cstring, envp: [^]cstring) -> (Errno) { execveat :: proc "contextless" (dirfd: Fd, name: cstring, argv: [^]cstring, envp: [^]cstring, flags: FD_Flags = {}) -> (Errno) {
ret := syscall(SYS_execveat, dirfd, cast(rawptr) name, cast(rawptr) argv, cast(rawptr) envp) ret := syscall(SYS_execveat, dirfd, cast(rawptr) name, cast(rawptr) argv, cast(rawptr) envp, transmute(i32) flags)
return Errno(-ret) return Errno(-ret)
} }
+76 -20
View File
@@ -18,7 +18,7 @@ Gid :: distinct u32
/* /*
Type for Process IDs, Thread IDs, Thread group ID. Type for Process IDs, Thread IDs, Thread group ID.
*/ */
Pid :: distinct int Pid :: distinct i32
/* /*
Type for any of: pid, pidfd, pgid. Type for any of: pid, pidfd, pgid.
@@ -89,11 +89,11 @@ FD_Flags :: bit_set[FD_Flags_Bits; i32]
Represents file's permission and status bits Represents file's permission and status bits
**Example:** **Example:**
When you're passing a value of this type the recommended usage is: When you're passing a value of this type the recommended usage is:
``` ```
linux.Mode{.S_IXOTH, .S_IROTH} | linux.S_IRWXU | linux.S_IRWXG linux.Mode{.S_IXOTH, .S_IROTH} | linux.S_IRWXU | linux.S_IRWXG
``` ```
This would generate a mode that has full permissions for the This would generate a mode that has full permissions for the
file's owner and group, and only "read" and "execute" bits file's owner and group, and only "read" and "execute" bits
for others. for others.
@@ -151,9 +151,9 @@ when ODIN_ARCH == .amd64 {
size: i64, size: i64,
blksize: uint, blksize: uint,
blocks: u64, blocks: u64,
atim: Time_Spec, atime: Time_Spec,
mtim: Time_Spec, mtime: Time_Spec,
ctim: Time_Spec, ctime: Time_Spec,
ino: Inode, ino: Inode,
} }
} }
@@ -495,16 +495,15 @@ Pid_FD_Flags :: bit_set[Pid_FD_Flags_Bits; i32]
// 1. Odin's bitfields start from 0, whereas signals start from 1 // 1. Odin's bitfields start from 0, whereas signals start from 1
// 2. It's unclear how bitfields act in terms of ABI (are they an array of ints or an array of longs?). // 2. It's unclear how bitfields act in terms of ABI (are they an array of ints or an array of longs?).
// it makes a difference because ARM is big endian. // it makes a difference because ARM is big endian.
@private _SIGSET_NWORDS :: (1024 / (8 * size_of(uint))) @private _SIGSET_NWORDS :: (8 / size_of(uint))
Sig_Set :: [_SIGSET_NWORDS]uint Sig_Set :: [_SIGSET_NWORDS]uint
@private SI_MAX_SIZE :: 128 @private SI_MAX_SIZE :: 128
@private SI_ARCH_PREAMBLE :: 3 * size_of(i32) @private SI_ARCH_PREAMBLE :: 4 * size_of(i32)
@private SI_PAD_SIZE :: (SI_MAX_SIZE - SI_ARCH_PREAMBLE) / size_of(i32) @private SI_PAD_SIZE :: SI_MAX_SIZE - SI_ARCH_PREAMBLE
@private SI_TIMER_PAD_SIZE :: size_of(Uid) - size_of(i32)
Sig_Handler_Fn :: #type proc "c" (sig: Signal) Sig_Handler_Fn :: #type proc "c" (sig: Signal)
Sig_Restore_Fn :: #type proc "c" () Sig_Restore_Fn :: #type proc "c" () -> !
Sig_Info :: struct #packed { Sig_Info :: struct #packed {
signo: Signal, signo: Signal,
@@ -518,8 +517,9 @@ Sig_Info :: struct #packed {
uid: Uid, /* sender's uid */ uid: Uid, /* sender's uid */
}, },
using _timer: struct { using _timer: struct {
timerid: i32, /* timer id */ timerid: i32, /* timer id */
overrun: i32, /* overrun count */ overrun: i32, /* overrun count */
value: Sig_Val, /* timer value */
}, },
/* POSIX.1b signals */ /* POSIX.1b signals */
using _rt: struct { using _rt: struct {
@@ -528,8 +528,8 @@ Sig_Info :: struct #packed {
}, },
/* SIGCHLD */ /* SIGCHLD */
using _sigchld: struct { using _sigchld: struct {
_pid1: Pid, /* which child */ _pid1: Pid, /* which child */
_uid1: Uid, /* sender's uid */ _uid1: Uid, /* sender's uid */
status: i32, /* exit code */ status: i32, /* exit code */
utime: uint, utime: uint,
stime: uint, //clock_t stime: uint, //clock_t
@@ -537,7 +537,24 @@ Sig_Info :: struct #packed {
/* SIGILL, SIGFPE, SIGSEGV, SIGBUS */ /* SIGILL, SIGFPE, SIGSEGV, SIGBUS */
using _sigfault: struct { using _sigfault: struct {
addr: rawptr, /* faulting insn/memory ref. */ addr: rawptr, /* faulting insn/memory ref. */
addr_lsb: i16, /* LSB of the reported address */ using _: struct #raw_union {
trapno: i32, /* Trap number that caused signal */
addr_lsb: i16, /* LSB of the reported address */
using _addr_bnd: struct {
_pad2: u64,
lower: rawptr, /* lower bound during fault */
upper: rawptr, /* upper bound during fault */
},
using _addr_pkey: struct {
_pad3: u64,
pkey: u32, /* protection key on PTE that faulted */
},
using _perf: struct {
perf_data: u64,
perf_type: u32,
perf_flags: u32,
},
},
}, },
/* SIGPOLL */ /* SIGPOLL */
using _sigpoll: struct { using _sigpoll: struct {
@@ -547,12 +564,43 @@ Sig_Info :: struct #packed {
/* SIGSYS */ /* SIGSYS */
using _sigsys: struct { using _sigsys: struct {
call_addr: rawptr, /* calling user insn */ call_addr: rawptr, /* calling user insn */
syscall: i32, /* triggering system call number */ syscall: i32, /* triggering system call number */
arch: u32, /* AUDIT_ARCH_* of syscall */ arch: u32, /* AUDIT_ARCH_* of syscall */
}, },
}, },
} }
#assert(size_of(Sig_Info) == 128)
when ODIN_ARCH == .amd64 || ODIN_ARCH == .arm64 {
#assert(offset_of(Sig_Info, signo) == 0x00)
#assert(offset_of(Sig_Info, errno) == 0x04)
#assert(offset_of(Sig_Info, code) == 0x08)
#assert(offset_of(Sig_Info, pid) == 0x10)
#assert(offset_of(Sig_Info, uid) == 0x14)
#assert(offset_of(Sig_Info, timerid) == 0x10)
#assert(offset_of(Sig_Info, overrun) == 0x14)
#assert(offset_of(Sig_Info, value) == 0x18)
#assert(offset_of(Sig_Info, status) == 0x18)
#assert(offset_of(Sig_Info, utime) == 0x20)
#assert(offset_of(Sig_Info, stime) == 0x28)
#assert(offset_of(Sig_Info, addr) == 0x10)
#assert(offset_of(Sig_Info, addr_lsb) == 0x18)
#assert(offset_of(Sig_Info, trapno) == 0x18)
#assert(offset_of(Sig_Info, lower) == 0x20)
#assert(offset_of(Sig_Info, upper) == 0x28)
#assert(offset_of(Sig_Info, pkey) == 0x20)
#assert(offset_of(Sig_Info, perf_data) == 0x18)
#assert(offset_of(Sig_Info, perf_type) == 0x20)
#assert(offset_of(Sig_Info, perf_flags) == 0x24)
#assert(offset_of(Sig_Info, band) == 0x10)
#assert(offset_of(Sig_Info, fd) == 0x18)
#assert(offset_of(Sig_Info, call_addr) == 0x10)
#assert(offset_of(Sig_Info, syscall) == 0x18)
#assert(offset_of(Sig_Info, arch) == 0x1C)
} else {
// TODO
}
SIGEV_MAX_SIZE :: 64 SIGEV_MAX_SIZE :: 64
SIGEV_PAD_SIZE :: ((SIGEV_MAX_SIZE-size_of(i32)*2+size_of(Sig_Val))/size_of(i32)) SIGEV_PAD_SIZE :: ((SIGEV_MAX_SIZE-size_of(i32)*2+size_of(Sig_Val))/size_of(i32))
@@ -583,12 +631,20 @@ Sig_Stack :: struct {
size: uintptr, size: uintptr,
} }
Sig_Action_Special :: enum uint {
SIG_DFL = 0,
SIG_IGN = 1,
SIG_ERR = ~uint(0),
}
Sig_Action_Flags :: bit_set[Sig_Action_Flag; uint]
Sig_Action :: struct($T: typeid) { Sig_Action :: struct($T: typeid) {
using _u: struct #raw_union { using _u: struct #raw_union {
handler: Sig_Handler_Fn, handler: Sig_Handler_Fn,
sigaction: #type proc "c" (sig: Signal, si: ^Sig_Info, ctx: ^T), sigaction: #type proc "c" (sig: Signal, si: ^Sig_Info, ctx: ^T),
special: Sig_Action_Special,
}, },
flags: uint, flags: Sig_Action_Flags,
restorer: Sig_Restore_Fn, restorer: Sig_Restore_Fn,
mask: Sig_Set, mask: Sig_Set,
} }
@@ -733,7 +789,7 @@ RLimit :: struct {
/* /*
Structure representing how much of each resource got used. Structure representing how much of each resource got used.
*/ */
RUsage :: struct { RUsage :: struct {
utime: Time_Val, utime: Time_Val,
stime: Time_Val, stime: Time_Val,
@@ -813,7 +869,7 @@ when size_of(int) == 8 || ODIN_ARCH == .i386 {
cpid: Pid, cpid: Pid,
lpid: Pid, lpid: Pid,
nattach: uint, nattach: uint,
_: [2]uint, _: [2]uint,
} }
} }