Merge tag 'dev-2025-11'

This commit is contained in:
ed
2025-11-05 22:27:46 -05:00
213 changed files with 4956 additions and 966 deletions
+14
View File
@@ -6,6 +6,10 @@ import "core:strings"
read_dir :: read_directory
/*
Reads the file `f` (assuming it is a directory) and returns the unsorted directory entries.
This returns up to `n` entries OR all of them if `n <= 0`.
*/
@(require_results)
read_directory :: proc(f: ^File, n: int, allocator: runtime.Allocator) -> (files: []File_Info, err: Error) {
if f == nil {
@@ -47,11 +51,18 @@ read_directory :: proc(f: ^File, n: int, allocator: runtime.Allocator) -> (files
}
/*
Reads the file `f` (assuming it is a directory) and returns all of the unsorted directory entries.
*/
@(require_results)
read_all_directory :: proc(f: ^File, allocator: runtime.Allocator) -> (fi: []File_Info, err: Error) {
return read_directory(f, -1, allocator)
}
/*
Reads the named directory by path (assuming it is a directory) and returns the unsorted directory entries.
This returns up to `n` entries OR all of them if `n <= 0`.
*/
@(require_results)
read_directory_by_path :: proc(path: string, n: int, allocator: runtime.Allocator) -> (fi: []File_Info, err: Error) {
f := open(path) or_return
@@ -59,6 +70,9 @@ read_directory_by_path :: proc(path: string, n: int, allocator: runtime.Allocato
return read_directory(f, n, allocator)
}
/*
Reads the named directory by path (assuming it is a directory) and returns all of the unsorted directory entries.
*/
@(require_results)
read_all_directory_by_path :: proc(path: string, allocator: runtime.Allocator) -> (fi: []File_Info, err: Error) {
return read_directory_by_path(path, -1, allocator)
+1 -1
View File
@@ -18,7 +18,7 @@ find_data_to_file_info :: proc(base_path: string, d: ^win32.WIN32_FIND_DATAW, al
temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
path := concatenate({base_path, `\`, win32_wstring_to_utf8(cstring16(raw_data(d.cFileName[:])), temp_allocator) or_else ""}, allocator) or_return
handle := win32.HANDLE(_open_internal(path, {.Read}, 0o666) or_else 0)
handle := win32.HANDLE(_open_internal(path, {.Read}, Permissions_Read_Write_All) or_else 0)
defer win32.CloseHandle(handle)
fi.fullpath = path
+24
View File
@@ -3,6 +3,10 @@ package os2
import "core:io"
import "base:runtime"
/*
General errors that are common within this package which cannot
be categorized by `io.Error` nor `runtime.Allocator_Error`.
*/
General_Error :: enum u32 {
None,
@@ -33,8 +37,12 @@ General_Error :: enum u32 {
Unsupported,
}
// A platform specific error
Platform_Error :: _Platform_Error
/*
`Error` is a union of different classes of errors that could be returned from procedures in this package.
*/
Error :: union #shared_nil {
General_Error,
io.Error,
@@ -46,6 +54,7 @@ Error :: union #shared_nil {
ERROR_NONE :: Error{}
// Attempts to convert an `Error` into a platform specific error as an integer. `ok` is false if not possible
@(require_results)
is_platform_error :: proc(ferr: Error) -> (err: i32, ok: bool) {
v := ferr.(Platform_Error) or_else {}
@@ -53,6 +62,7 @@ is_platform_error :: proc(ferr: Error) -> (err: i32, ok: bool) {
}
// Attempts to return the error `ferr` as a string without any allocation
@(require_results)
error_string :: proc(ferr: Error) -> string {
if ferr == nil {
@@ -112,6 +122,9 @@ error_string :: proc(ferr: Error) -> string {
return "unknown error"
}
/*
`print_error` is a utility procedure which will print an error `ferr` to a specified file `f`.
*/
print_error :: proc(f: ^File, ferr: Error, msg: string) {
temp_allocator := TEMP_ALLOCATOR_GUARD({})
err_str := error_string(ferr)
@@ -127,3 +140,14 @@ print_error :: proc(f: ^File, ferr: Error, msg: string) {
buf[length - 1] = '\n'
write(f, buf)
}
// Attempts to convert an `Error` `ferr` into an `io.Error`
@(private)
error_to_io_error :: proc(ferr: Error) -> io.Error {
if ferr == nil {
return .None
}
return ferr.(io.Error) or_else .Unknown
}
+198 -13
View File
@@ -56,6 +56,7 @@ File_Type :: enum {
Character_Device,
}
// Represents the file flags for a file handle
File_Flags :: distinct bit_set[File_Flag; uint]
File_Flag :: enum {
Read,
@@ -90,17 +91,77 @@ O_SPARSE :: File_Flags{.Sparse}
*/
O_INHERITABLE :: File_Flags{.Inheritable}
stdin: ^File = nil // OS-Specific
stdout: ^File = nil // OS-Specific
stderr: ^File = nil // OS-Specific
Permissions :: distinct bit_set[Permission_Flag; u32]
Permission_Flag :: enum u32 {
Execute_Other = 0,
Write_Other = 1,
Read_Other = 2,
@(require_results)
create :: proc(name: string) -> (^File, Error) {
return open(name, {.Read, .Write, .Create}, 0o777)
Execute_Group = 3,
Write_Group = 4,
Read_Group = 5,
Execute_User = 6,
Write_User = 7,
Read_User = 8,
}
Permissions_Execute_All :: Permissions{.Execute_User, .Execute_Group, .Execute_Other}
Permissions_Write_All :: Permissions{.Write_User, .Write_Group, .Write_Other}
Permissions_Read_All :: Permissions{.Read_User, .Read_Group, .Read_Other}
Permissions_Read_Write_All :: Permissions_Read_All + Permissions_Write_All
Permissions_All :: Permissions_Read_All + Permissions_Write_All + Permissions_Execute_All
Permissions_Default_File :: Permissions_Read_All + Permissions_Write_All
Permissions_Default_Directory :: Permissions_Read_All + Permissions_Write_All + Permissions_Execute_All
Permissions_Default :: Permissions_Default_Directory
perm :: proc{
perm_number,
}
/*
`perm_number` converts an integer value `perm` to the bit set `Permissions`
*/
@(require_results)
open :: proc(name: string, flags := File_Flags{.Read}, perm := 0o777) -> (^File, Error) {
perm_number :: proc "contextless" (perm: int) -> Permissions {
return transmute(Permissions)u32(perm & 0o777)
}
// `stdin` is an open file pointing to the standard input file stream
stdin: ^File = nil // OS-Specific
// `stdout` is an open file pointing to the standard output file stream
stdout: ^File = nil // OS-Specific
// `stderr` is an open file pointing to the standard error file stream
stderr: ^File = nil // OS-Specific
/*
`create` creates or truncates a named file `name`.
If the file already exists, it is truncated.
If the file does not exist, it is created with the `Permissions_Default_File` permissions.
If successful, a `^File` is return which can be used for I/O.
And error is returned if any is encountered.
*/
@(require_results)
create :: proc(name: string) -> (^File, Error) {
return open(name, {.Read, .Write, .Create, .Trunc}, Permissions_Default_File)
}
/*
`open` is a generalized open call, which defaults to opening for reading.
If the file does not exist, and the `{.Create}` flag is passed, it is created with the permissions `perm`,
and please note that the containing directory must exist otherwise and an error will be returned.
If successful, a `^File` is return which can be used for I/O.
And error is returned if any is encountered.
*/
@(require_results)
open :: proc(name: string, flags := File_Flags{.Read}, perm := Permissions_Default) -> (^File, Error) {
return _open(name, flags, perm)
}
@@ -112,7 +173,10 @@ open :: proc(name: string, flags := File_Flags{.Read}, perm := 0o777) -> (^File,
// return _open_buffered(name, buffer_size, flags, perm)
// }
/*
`new_file` returns a new `^File` with the given file descriptor `handle` and `name`.
The return value will only be `nil` IF the `handle` is not a valid file descriptor.
*/
@(require_results)
new_file :: proc(handle: uintptr, name: string) -> ^File {
file, err := _new_file(handle, name, file_allocator())
@@ -122,16 +186,25 @@ new_file :: proc(handle: uintptr, name: string) -> ^File {
return file
}
/*
`clone` returns a new `^File` based on the passed file `f` with the same underlying file descriptor.
*/
@(require_results)
clone :: proc(f: ^File) -> (^File, Error) {
return _clone(f)
}
/*
`fd` returns the file descriptor of the file `f` passed. If the file is not valid, an invalid handle will be returned.
*/
@(require_results)
fd :: proc(f: ^File) -> uintptr {
return _fd(f)
}
/*
`name` returns the name of the file. The lifetime of this string lasts as long as the file handle itself.
*/
@(require_results)
name :: proc(f: ^File) -> string {
return _name(f)
@@ -150,6 +223,16 @@ close :: proc(f: ^File) -> Error {
return nil
}
/*
seek sets the offsets for the next read or write on a file to a specified `offset`,
according to what `whence` is set.
`.Start` is relative to the origin of the file.
`.Current` is relative to the current offset.
`.End` is relative to the end.
It returns the new offset and an error, if any is encountered.
Prefer `read_at` or `write_at` if the offset does not want to be changed.
*/
seek :: proc(f: ^File, offset: i64, whence: io.Seek_From) -> (ret: i64, err: Error) {
if f != nil {
return io.seek(f.stream, offset, whence)
@@ -157,6 +240,11 @@ seek :: proc(f: ^File, offset: i64, whence: io.Seek_From) -> (ret: i64, err: Err
return 0, .Invalid_File
}
/*
`read` reads up to len(p) bytes from the file `f`, and then stores them in `p`.
It returns the number of bytes read and an error, if any is encountered.
At the end of a file, it returns `0, io.EOF`.
*/
read :: proc(f: ^File, p: []byte) -> (n: int, err: Error) {
if f != nil {
return io.read(f.stream, p)
@@ -164,6 +252,12 @@ read :: proc(f: ^File, p: []byte) -> (n: int, err: Error) {
return 0, .Invalid_File
}
/*
`read_at` reads up to len(p) bytes from the file `f` at the byte offset `offset`, and then stores them in `p`.
It returns the number of bytes read and an error, if any is encountered.
`read_at` always returns a non-nil error when `n < len(p)`.
At the end of a file, the error is `io.EOF`.
*/
read_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: int, err: Error) {
if f != nil {
return io.read_at(f.stream, p, offset)
@@ -171,6 +265,11 @@ read_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: int, err: Error) {
return 0, .Invalid_File
}
/*
`write` writes `len(p)` bytes from `p` to the file `f`. It returns the number of bytes written to
and an error, if any is encountered.
`write` returns a non-nil error when `n != len(p)`.
*/
write :: proc(f: ^File, p: []byte) -> (n: int, err: Error) {
if f != nil {
return io.write(f.stream, p)
@@ -178,6 +277,11 @@ write :: proc(f: ^File, p: []byte) -> (n: int, err: Error) {
return 0, .Invalid_File
}
/*
`write_at` writes `len(p)` bytes from `p` to the file `f` starting at byte offset `offset`.
It returns the number of bytes written to and an error, if any is encountered.
`write_at` returns a non-nil error when `n != len(p)`.
*/
write_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: int, err: Error) {
if f != nil {
return io.write_at(f.stream, p, offset)
@@ -185,6 +289,9 @@ write_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: int, err: Error) {
return 0, .Invalid_File
}
/*
`file_size` returns the length of the file `f` in bytes and an error, if any is encountered.
*/
file_size :: proc(f: ^File) -> (n: i64, err: Error) {
if f != nil {
return io.size(f.stream)
@@ -192,6 +299,9 @@ file_size :: proc(f: ^File) -> (n: i64, err: Error) {
return 0, .Invalid_File
}
/*
`flush` flushes a file `f`
*/
flush :: proc(f: ^File) -> Error {
if f != nil {
return io.flush(f.stream)
@@ -199,31 +309,54 @@ flush :: proc(f: ^File) -> Error {
return nil
}
/*
`sync` commits the current contents of the file `f` to stable storage.
This usually means flushing the file system's in-memory copy to disk.
*/
sync :: proc(f: ^File) -> Error {
return _sync(f)
}
/*
`truncate` changes the size of the file `f` to `size` in bytes.
This can be used to shorten or lengthen a file.
It does not change the "offset" of the file.
*/
truncate :: proc(f: ^File, size: i64) -> Error {
return _truncate(f, size)
}
/*
`remove` removes a named file or (empty) directory.
*/
remove :: proc(name: string) -> Error {
return _remove(name)
}
/*
`rename` renames (moves) `old_path` to `new_path`.
*/
rename :: proc(old_path, new_path: string) -> Error {
return _rename(old_path, new_path)
}
/*
`link` creates a `new_name` as a hard link to the `old_name` file.
*/
link :: proc(old_name, new_name: string) -> Error {
return _link(old_name, new_name)
}
/*
`symlink` creates a `new_name` as a symbolic link to the `old_name` file.
*/
symlink :: proc(old_name, new_name: string) -> Error {
return _symlink(old_name, new_name)
}
/*
`read_link` returns the destinction of the named symbolic link `name`.
*/
read_link :: proc(name: string, allocator: runtime.Allocator) -> (string, Error) {
return _read_link(name,allocator)
}
@@ -231,36 +364,65 @@ read_link :: proc(name: string, allocator: runtime.Allocator) -> (string, Error)
chdir :: change_directory
/*
Changes the current working directory to the named directory.
*/
change_directory :: proc(name: string) -> Error {
return _chdir(name)
}
chmod :: change_mode
change_mode :: proc(name: string, mode: int) -> Error {
/*
Changes the mode/permissions of the named file to `mode`.
If the file is a symbolic link, it changes the mode of the link's target.
On Windows, only `{.Write_User}` of `mode` is used, and controls whether or not
the file has a read-only attribute. Use `{.Read_User}` for a read-only file and
`{.Read_User, .Write_User}` for a readable & writable file.
*/
change_mode :: proc(name: string, mode: Permissions) -> Error {
return _chmod(name, mode)
}
chown :: change_owner
/*
Changes the numeric `uid` and `gid` of a named file. If the file is a symbolic link,
it changes the `uid` and `gid` of the link's target.
On Windows, it always returns an error.
*/
change_owner :: proc(name: string, uid, gid: int) -> Error {
return _chown(name, uid, gid)
}
fchdir :: fchange_directory
/*
Changes the current working directory to the file, which must be a directory.
*/
fchange_directory :: proc(f: ^File) -> Error {
return _fchdir(f)
}
fchmod :: fchange_mode
fchange_mode :: proc(f: ^File, mode: int) -> Error {
/*
Changes the current `mode` permissions of the file `f`.
*/
fchange_mode :: proc(f: ^File, mode: Permissions) -> Error {
return _fchmod(f, mode)
}
fchown :: fchange_owner
/*
Changes the numeric `uid` and `gid` of the file `f`. If the file is a symbolic link,
it changes the `uid` and `gid` of the link's target.
On Windows, it always returns an error.
*/
fchange_owner :: proc(f: ^File, uid, gid: int) -> Error {
return _fchown(f, uid, gid)
}
@@ -268,27 +430,45 @@ fchange_owner :: proc(f: ^File, uid, gid: int) -> Error {
lchown :: change_owner_do_not_follow_links
/*
Changes the numeric `uid` and `gid` of the file `f`. If the file is a symbolic link,
it changes the `uid` and `gid` of the lin itself.
On Windows, it always returns an error.
*/
change_owner_do_not_follow_links :: proc(name: string, uid, gid: int) -> Error {
return _lchown(name, uid, gid)
}
chtimes :: change_times
/*
Changes the access `atime` and modification `mtime` times of a named file.
*/
change_times :: proc(name: string, atime, mtime: time.Time) -> Error {
return _chtimes(name, atime, mtime)
}
fchtimes :: fchange_times
/*
Changes the access `atime` and modification `mtime` times of the file `f`.
*/
fchange_times :: proc(f: ^File, atime, mtime: time.Time) -> Error {
return _fchtimes(f, atime, mtime)
}
/*
`exists` returns whether or not a named file exists.
*/
@(require_results)
exists :: proc(path: string) -> bool {
return _exists(path)
}
/*
`is_file` returns whether or not the type of a named file is a `File_Type.Regular` file.
*/
@(require_results)
is_file :: proc(path: string) -> bool {
temp_allocator := TEMP_ALLOCATOR_GUARD({})
@@ -301,6 +481,9 @@ is_file :: proc(path: string) -> bool {
is_dir :: is_directory
/*
Returns whether or not the type of a named file is a `File_Type.Directory` file.
*/
@(require_results)
is_directory :: proc(path: string) -> bool {
temp_allocator := TEMP_ALLOCATOR_GUARD({})
@@ -311,7 +494,9 @@ is_directory :: proc(path: string) -> bool {
return fi.type == .Directory
}
/*
`copy_file` copies a file from `src_path` to `dst_path` and returns an error if any was encountered.
*/
copy_file :: proc(dst_path, src_path: string) -> Error {
when #defined(_copy_file_native) {
return _copy_file_native(dst_path, src_path)
@@ -331,7 +516,7 @@ _copy_file :: proc(dst_path, src_path: string) -> Error {
return .Invalid_File
}
dst := open(dst_path, {.Read, .Write, .Create, .Trunc}, info.mode & 0o777) or_return
dst := open(dst_path, {.Read, .Write, .Create, .Trunc}, info.mode & Permissions_All) or_return
defer close(dst)
_, err := io.copy(to_writer(dst), to_reader(src))
+11 -11
View File
@@ -65,7 +65,7 @@ _standard_stream_init :: proc "contextless" () {
stderr = new_std(&files[2], 2, "/proc/self/fd/2")
}
_open :: proc(name: string, flags: File_Flags, perm: int) -> (f: ^File, err: Error) {
_open :: proc(name: string, flags: File_Flags, perm: Permissions) -> (f: ^File, err: Error) {
temp_allocator := TEMP_ALLOCATOR_GUARD({})
name_cstr := clone_to_cstring(name, temp_allocator) or_return
@@ -88,7 +88,7 @@ _open :: proc(name: string, flags: File_Flags, perm: int) -> (f: ^File, err: Err
if .Trunc in flags { sys_flags += {.TRUNC} }
if .Inheritable in flags { sys_flags -= {.CLOEXEC} }
fd, errno := linux.open(name_cstr, sys_flags, transmute(linux.Mode)u32(perm))
fd, errno := linux.open(name_cstr, sys_flags, transmute(linux.Mode)transmute(u32)perm)
if errno != .NONE {
return nil, _get_platform_error(errno)
}
@@ -132,7 +132,7 @@ _clone :: proc(f: ^File) -> (clone: ^File, err: Error) {
@(require_results)
_open_buffered :: proc(name: string, buffer_size: uint, flags := File_Flags{.Read}, perm := 0o777) -> (f: ^File, err: Error) {
_open_buffered :: proc(name: string, buffer_size: uint, flags := File_Flags{.Read}, perm: Permissions) -> (f: ^File, err: Error) {
assert(buffer_size > 0)
f, err = _open(name, flags, perm)
if f != nil && err == nil {
@@ -198,7 +198,7 @@ _seek :: proc(f: ^File_Impl, offset: i64, whence: io.Seek_From) -> (ret: i64, er
case .NONE:
return n, nil
case:
return -1, _get_platform_error(errno)
return 0, _get_platform_error(errno)
}
}
@@ -209,7 +209,7 @@ _read :: proc(f: ^File_Impl, p: []byte) -> (i64, Error) {
n, errno := linux.read(f.fd, p[:min(len(p), MAX_RW)])
if errno != .NONE {
return -1, _get_platform_error(errno)
return 0, _get_platform_error(errno)
}
return i64(n), io.Error.EOF if n == 0 else nil
}
@@ -223,7 +223,7 @@ _read_at :: proc(f: ^File_Impl, p: []byte, offset: i64) -> (i64, Error) {
}
n, errno := linux.pread(f.fd, p[:min(len(p), MAX_RW)], offset)
if errno != .NONE {
return -1, _get_platform_error(errno)
return 0, _get_platform_error(errno)
}
if n == 0 {
return 0, .EOF
@@ -276,7 +276,7 @@ _file_size :: proc(f: ^File_Impl) -> (n: i64, err: Error) {
s: linux.Stat = ---
errno := linux.fstat(f.fd, &s)
if errno != .NONE {
return -1, _get_platform_error(errno)
return 0, _get_platform_error(errno)
}
if s.mode & linux.S_IFMT == linux.S_IFREG {
@@ -369,15 +369,15 @@ _fchdir :: proc(f: ^File) -> Error {
return _get_platform_error(linux.fchdir(impl.fd))
}
_chmod :: proc(name: string, mode: int) -> Error {
_chmod :: proc(name: string, mode: Permissions) -> Error {
temp_allocator := TEMP_ALLOCATOR_GUARD({})
name_cstr := clone_to_cstring(name, temp_allocator) or_return
return _get_platform_error(linux.chmod(name_cstr, transmute(linux.Mode)(u32(mode))))
return _get_platform_error(linux.chmod(name_cstr, transmute(linux.Mode)transmute(u32)mode))
}
_fchmod :: proc(f: ^File, mode: int) -> Error {
_fchmod :: proc(f: ^File, mode: Permissions) -> Error {
impl := (^File_Impl)(f.impl)
return _get_platform_error(linux.fchmod(impl.fd, transmute(linux.Mode)(u32(mode))))
return _get_platform_error(linux.fchmod(impl.fd, transmute(linux.Mode)transmute(u32)mode))
}
// NOTE: will throw error without super user priviledges
+21 -14
View File
@@ -46,7 +46,7 @@ init_std_files :: proc "contextless" () {
stderr = new_std(&files[2], posix.STDERR_FILENO, "/dev/stderr")
}
_open :: proc(name: string, flags: File_Flags, perm: int) -> (f: ^File, err: Error) {
_open :: proc(name: string, flags: File_Flags, perm: Permissions) -> (f: ^File, err: Error) {
if name == "" {
err = .Invalid_Path
return
@@ -72,7 +72,7 @@ _open :: proc(name: string, flags: File_Flags, perm: int) -> (f: ^File, err: Err
temp_allocator := TEMP_ALLOCATOR_GUARD({})
cname := clone_to_cstring(name, temp_allocator) or_return
fd := posix.open(cname, sys_flags, transmute(posix.mode_t)posix._mode_t(perm))
fd := posix.open(cname, sys_flags, transmute(posix.mode_t)posix._mode_t(transmute(u32)perm))
if fd < 0 {
err = _get_platform_error()
return
@@ -284,17 +284,17 @@ _fchdir :: proc(f: ^File) -> Error {
return nil
}
_fchmod :: proc(f: ^File, mode: int) -> Error {
if posix.fchmod(__fd(f), transmute(posix.mode_t)posix._mode_t(mode)) != .OK {
_fchmod :: proc(f: ^File, mode: Permissions) -> Error {
if posix.fchmod(__fd(f), transmute(posix.mode_t)posix._mode_t(transmute(u32)mode)) != .OK {
return _get_platform_error()
}
return nil
}
_chmod :: proc(name: string, mode: int) -> (err: Error) {
_chmod :: proc(name: string, mode: Permissions) -> (err: Error) {
temp_allocator := TEMP_ALLOCATOR_GUARD({})
cname := clone_to_cstring(name, temp_allocator) or_return
if posix.chmod(cname, transmute(posix.mode_t)posix._mode_t(mode)) != .OK {
if posix.chmod(cname, transmute(posix.mode_t)posix._mode_t(transmute(u32)mode)) != .OK {
return _get_platform_error()
}
return nil
@@ -382,12 +382,14 @@ _file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte,
}
to_read := uint(min(len(p), MAX_RW))
n = i64(posix.read(fd, raw_data(p), to_read))
_n := i64(posix.read(fd, raw_data(p), to_read))
switch {
case n == 0:
case _n == 0:
err = .EOF
case n < 0:
case _n < 0:
err = .Unknown
case:
n = _n
}
return
@@ -402,12 +404,14 @@ _file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte,
}
to_read := uint(min(len(p), MAX_RW))
n = i64(posix.pread(fd, raw_data(p), to_read, posix.off_t(offset)))
_n := i64(posix.pread(fd, raw_data(p), to_read, posix.off_t(offset)))
switch {
case n == 0:
case _n == 0:
err = .EOF
case n < 0:
case _n < 0:
err = .Unknown
case:
n = _n
}
return
@@ -460,15 +464,18 @@ _file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte,
return
}
n = i64(posix.lseek(fd, posix.off_t(offset), posix.Whence(whence)))
if n < 0 {
_n := i64(posix.lseek(fd, posix.off_t(offset), posix.Whence(whence)))
if _n < 0 {
#partial switch posix.get_errno() {
case .EINVAL:
err = .Invalid_Offset
case:
err = .Unknown
}
return
}
n = _n
return
case .Size:
+1 -1
View File
@@ -8,7 +8,7 @@ import "core:sys/posix"
_posix_absolute_path :: proc(fd: posix.FD, name: string, allocator: runtime.Allocator) -> (path: cstring, err: Error) {
temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
cname := clone_to_cstring(name, temp_allocator)
cname := clone_to_cstring(name, temp_allocator) or_return
buf: [posix.PATH_MAX]byte
path = posix.realpath(cname, raw_data(buf[:]))
+12 -9
View File
@@ -2,6 +2,7 @@ package os2
import "core:io"
// Converts a file `f` into an `io.Stream`
to_stream :: proc(f: ^File) -> (s: io.Stream) {
if f != nil {
assert(f.stream.procedure != nil)
@@ -10,14 +11,16 @@ to_stream :: proc(f: ^File) -> (s: io.Stream) {
return
}
/*
This is an alias of `to_stream` which converts a file `f` to an `io.Stream`.
It can be useful to indicate what the stream is meant to be used for as a writer,
even if it has no logical difference.
*/
to_writer :: to_stream
/*
This is an alias of `to_stream` which converts a file `f` to an `io.Stream`.
It can be useful to indicate what the stream is meant to be used for as a reader,
even if it has no logical difference.
*/
to_reader :: to_stream
@(private)
error_to_io_error :: proc(ferr: Error) -> io.Error {
if ferr == nil {
return .None
}
return ferr.(io.Error) or_else .Unknown
}
+94 -19
View File
@@ -4,10 +4,18 @@ import "base:runtime"
import "core:strconv"
import "core:unicode/utf8"
/*
`write_string` writes a string `s` to file `f`.
Returns the number of bytes written and an error, if any is encountered.
*/
write_string :: proc(f: ^File, s: string) -> (n: int, err: Error) {
return write(f, transmute([]byte)s)
}
/*
`write_strings` writes a variadic list of strings `strings` to file `f`.
Returns the number of bytes written and an error, if any is encountered.
*/
write_strings :: proc(f: ^File, strings: ..string) -> (n: int, err: Error) {
for s in strings {
m: int
@@ -19,11 +27,18 @@ write_strings :: proc(f: ^File, strings: ..string) -> (n: int, err: Error) {
}
return
}
/*
`write_byte` writes a byte `b` to file `f`.
Returns the number of bytes written and an error, if any is encountered.
*/
write_byte :: proc(f: ^File, b: byte) -> (n: int, err: Error) {
return write(f, []byte{b})
}
/*
`write_rune` writes a rune `r` as an UTF-8 encoded string to file `f`.
Returns the number of bytes written and an error, if any is encountered.
*/
write_rune :: proc(f: ^File, r: rune) -> (n: int, err: Error) {
if r < utf8.RUNE_SELF {
return write_byte(f, byte(r))
@@ -34,6 +49,10 @@ write_rune :: proc(f: ^File, r: rune) -> (n: int, err: Error) {
return write(f, b[:n])
}
/*
`write_encoded_rune` writes a rune `r` as an UTF-8 encoded string which with escaped control codes to file `f`.
Returns the number of bytes written and an error, if any is encountered.
*/
write_encoded_rune :: proc(f: ^File, r: rune) -> (n: int, err: Error) {
wrap :: proc(m: int, merr: Error, n: ^int, err: ^Error) -> bool {
n^ += m
@@ -73,6 +92,31 @@ write_encoded_rune :: proc(f: ^File, r: rune) -> (n: int, err: Error) {
return
}
/*
`write_ptr` is a utility procedure that writes the bytes points at `data` with length `len`.
It is equivalent to: `write(f, ([^]byte)(data)[:len])`
*/
write_ptr :: proc(f: ^File, data: rawptr, len: int) -> (n: int, err: Error) {
return write(f, ([^]byte)(data)[:len])
}
/*
`read_ptr` is a utility procedure that reads the bytes points at `data` with length `len`.
It is equivalent to: `read(f, ([^]byte)(data)[:len])`
*/
read_ptr :: proc(f: ^File, data: rawptr, len: int) -> (n: int, err: Error) {
return read(f, ([^]byte)(data)[:len])
}
/*
`read_at_least` reads from `f` into `buf` until it has read at least `min` bytes.
It returns the number of bytes copied and an error if fewer bytes were read.
The error is only an `io.EOF` if no bytes were read.
*/
read_at_least :: proc(f: ^File, buf: []byte, min: int) -> (n: int, err: Error) {
if len(buf) < min {
return 0, .Short_Buffer
@@ -88,17 +132,17 @@ read_at_least :: proc(f: ^File, buf: []byte, min: int) -> (n: int, err: Error) {
return
}
/*
`read_full` reads exactly `len(buf)` bytes from `f` into `buf`.
It returns the number of bytes copied and an error if fewer bytes were read.
The error is only an `io.EOF` if no bytes were read.
It is equivalent to `read_at_least(f, buf, len(buf))`.
*/
read_full :: proc(f: ^File, buf: []byte) -> (n: int, err: Error) {
return read_at_least(f, buf, len(buf))
}
write_ptr :: proc(f: ^File, data: rawptr, len: int) -> (n: int, err: Error) {
return write(f, ([^]byte)(data)[:len])
}
read_ptr :: proc(f: ^File, data: rawptr, len: int) -> (n: int, err: Error) {
return read(f, ([^]byte)(data)[:len])
}
read_entire_file :: proc{
@@ -106,18 +150,23 @@ read_entire_file :: proc{
read_entire_file_from_file,
}
/*
`read_entire_file_from_path` reads the entire named file `name` into memory allocated with `allocator`.
A slice of bytes and an error is returned, if any error is encountered.
*/
@(require_results)
read_entire_file_from_path :: proc(name: string, allocator: runtime.Allocator) -> (data: []byte, err: Error) {
f, ferr := open(name)
if ferr != nil {
return nil, ferr
}
read_entire_file_from_path :: proc(name: string, allocator: runtime.Allocator, loc := #caller_location) -> (data: []byte, err: Error) {
f := open(name) or_return
defer close(f)
return read_entire_file_from_file(f, allocator)
return read_entire_file_from_file(f, allocator, loc)
}
/*
`read_entire_file_from_file` reads the entire file `f` into memory allocated with `allocator`.
A slice of bytes and an error is returned, if any error is encountered.
*/
@(require_results)
read_entire_file_from_file :: proc(f: ^File, allocator: runtime.Allocator) -> (data: []byte, err: Error) {
read_entire_file_from_file :: proc(f: ^File, allocator: runtime.Allocator, loc := #caller_location) -> (data: []byte, err: Error) {
size: int
has_size := false
if size64, serr := file_size(f); serr == nil {
@@ -129,7 +178,7 @@ read_entire_file_from_file :: proc(f: ^File, allocator: runtime.Allocator) -> (d
if has_size && size > 0 {
total: int
data = make([]byte, size, allocator) or_return
data = make([]byte, size, allocator, loc) or_return
for total < len(data) {
n: int
n, err = read(f, data[total:])
@@ -145,13 +194,13 @@ read_entire_file_from_file :: proc(f: ^File, allocator: runtime.Allocator) -> (d
return
} else {
buffer: [1024]u8
out_buffer := make([dynamic]u8, 0, 0, allocator)
out_buffer := make([dynamic]u8, 0, 0, allocator, loc)
total := 0
for {
n: int
n, err = read(f, buffer[:])
total += n
append_elems(&out_buffer, ..buffer[:n]) or_return
append_elems(&out_buffer, ..buffer[:n], loc=loc) or_return
if err != nil {
if err == .EOF || err == .Broken_Pipe {
err = nil
@@ -163,8 +212,23 @@ read_entire_file_from_file :: proc(f: ^File, allocator: runtime.Allocator) -> (d
}
}
/*
`write_entire_file` writes the contents of `data` into named file `name`.
It defaults with the permssions `perm := Permissions_Read_All + {.Write_User}`, and `truncate`s by default.
An error is returned if any is encountered.
*/
write_entire_file :: proc{
write_entire_file_from_bytes,
write_entire_file_from_string,
}
/*
`write_entire_file_from_bytes` writes the contents of `data` into named file `name`.
It defaults with the permssions `perm := Permissions_Read_All + {.Write_User}`, and `truncate`s by default.
An error is returned if any is encountered.
*/
@(require_results)
write_entire_file :: proc(name: string, data: []byte, perm: int = 0o644, truncate := true) -> Error {
write_entire_file_from_bytes :: proc(name: string, data: []byte, perm := Permissions_Read_All + {.Write_User}, truncate := true) -> Error {
flags := O_WRONLY|O_CREATE
if truncate {
flags |= O_TRUNC
@@ -177,3 +241,14 @@ write_entire_file :: proc(name: string, data: []byte, perm: int = 0o644, truncat
return err
}
/*
`write_entire_file_from_string` writes the contents of `data` into named file `name`.
It defaults with the permssions `perm := Permissions_Read_All + {.Write_User}`, and `truncate`s by default.
An error is returned if any is encountered.
*/
@(require_results)
write_entire_file_from_string :: proc(name: string, data: string, perm := Permissions_Read_All + {.Write_User}, truncate := true) -> Error {
return write_entire_file(name, transmute([]byte)data, perm, truncate)
}
+7 -5
View File
@@ -51,8 +51,8 @@ init_std_files :: proc "contextless" () {
}
@(init)
init_preopens :: proc() {
strip_prefixes :: proc(path: string) -> string {
init_preopens :: proc "contextless" () {
strip_prefixes :: proc "contextless" (path: string) -> string {
path := path
loop: for len(path) > 0 {
switch {
@@ -69,6 +69,8 @@ init_preopens :: proc() {
return path
}
context = runtime.default_context()
n: int
n_loop: for fd := wasi.fd_t(3); ; fd += 1 {
_, err := wasi.fd_prestat_get(fd)
@@ -171,7 +173,7 @@ match_preopen :: proc(path: string) -> (wasi.fd_t, string, bool) {
return match.fd, relative, true
}
_open :: proc(name: string, flags: File_Flags, perm: int) -> (f: ^File, err: Error) {
_open :: proc(name: string, flags: File_Flags, perm: Permissions) -> (f: ^File, err: Error) {
dir_fd, relative, ok := match_preopen(name)
if !ok {
return nil, .Invalid_Path
@@ -373,11 +375,11 @@ _fchdir :: proc(f: ^File) -> Error {
return .Unsupported
}
_fchmod :: proc(f: ^File, mode: int) -> Error {
_fchmod :: proc(f: ^File, mode: Permissions) -> Error {
return .Unsupported
}
_chmod :: proc(name: string, mode: int) -> Error {
_chmod :: proc(name: string, mode: Permissions) -> Error {
return .Unsupported
}
+7 -8
View File
@@ -12,7 +12,6 @@ import win32 "core:sys/windows"
INVALID_HANDLE :: ~uintptr(0)
S_IWRITE :: 0o200
_ERROR_BAD_NETPATH :: 53
MAX_RW :: 1<<30
@@ -81,7 +80,7 @@ _handle :: proc "contextless" (f: ^File) -> win32.HANDLE {
return win32.HANDLE(_fd(f))
}
_open_internal :: proc(name: string, flags: File_Flags, perm: int) -> (handle: uintptr, err: Error) {
_open_internal :: proc(name: string, flags: File_Flags, perm: Permissions) -> (handle: uintptr, err: Error) {
if len(name) == 0 {
err = .Not_Exist
return
@@ -122,7 +121,7 @@ _open_internal :: proc(name: string, flags: File_Flags, perm: int) -> (handle: u
}
attrs: u32 = win32.FILE_ATTRIBUTE_NORMAL|win32.FILE_FLAG_BACKUP_SEMANTICS
if perm & S_IWRITE == 0 {
if .Write_User not_in perm {
attrs = win32.FILE_ATTRIBUTE_READONLY
if create_mode == win32.CREATE_ALWAYS {
// NOTE(bill): Open has just asked to create a file in read-only mode.
@@ -150,7 +149,7 @@ _open_internal :: proc(name: string, flags: File_Flags, perm: int) -> (handle: u
}
_open :: proc(name: string, flags: File_Flags, perm: int) -> (f: ^File, err: Error) {
_open :: proc(name: string, flags: File_Flags, perm: Permissions) -> (f: ^File, err: Error) {
flags := flags if flags != nil else {.Read}
handle := _open_internal(name, flags, perm) or_return
return _new_file(handle, name, file_allocator())
@@ -193,7 +192,7 @@ _new_file :: proc(handle: uintptr, name: string, allocator: runtime.Allocator) -
@(require_results)
_open_buffered :: proc(name: string, buffer_size: uint, flags := File_Flags{.Read}, perm := 0o777) -> (f: ^File, err: Error) {
_open_buffered :: proc(name: string, buffer_size: uint, flags := File_Flags{.Read}, perm: Permissions) -> (f: ^File, err: Error) {
assert(buffer_size > 0)
flags := flags if flags != nil else {.Read}
handle := _open_internal(name, flags, perm) or_return
@@ -744,7 +743,7 @@ _fchdir :: proc(f: ^File) -> Error {
return nil
}
_fchmod :: proc(f: ^File, mode: int) -> Error {
_fchmod :: proc(f: ^File, mode: Permissions) -> Error {
if f == nil || f.impl == nil {
return nil
}
@@ -753,7 +752,7 @@ _fchmod :: proc(f: ^File, mode: int) -> Error {
return _get_platform_error()
}
attrs := d.dwFileAttributes
if mode & S_IWRITE != 0 {
if .Write_User in mode {
attrs &~= win32.FILE_ATTRIBUTE_READONLY
} else {
attrs |= win32.FILE_ATTRIBUTE_READONLY
@@ -780,7 +779,7 @@ _chdir :: proc(name: string) -> Error {
return nil
}
_chmod :: proc(name: string, mode: int) -> Error {
_chmod :: proc(name: string, mode: Permissions) -> Error {
f := open(name, {.Write}) or_return
defer close(f)
return _fchmod(f, mode)
+3
View File
@@ -2,6 +2,9 @@ package os2
import "base:runtime"
/*
Returns the default `heap_allocator` for this specific platform.
*/
@(require_results)
heap_allocator :: proc() -> runtime.Allocator {
return runtime.Allocator{
+5 -1
View File
@@ -34,7 +34,7 @@ _mkdir_all :: proc(path: string, perm: int) -> Error {
return .Exist
}
clean_path := clean_path(path, temp_allocator)
clean_path := clean_path(path, temp_allocator) or_return
return internal_mkdir_all(clean_path)
internal_mkdir_all :: proc(path: string) -> Error {
@@ -114,3 +114,7 @@ _get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err
return concatenate({"/", arg}, allocator)
}
_get_absolute_path :: proc(path: string, allocator: runtime.Allocator) -> (absolute_path: string, err: Error) {
return "", .Unsupported
}
+1 -1
View File
@@ -35,7 +35,7 @@ delete_args :: proc "contextless" () {
Exit the current process.
*/
exit :: proc "contextless" (code: int) -> ! {
_exit(code)
runtime.exit(code)
}
/*
+2 -6
View File
@@ -13,11 +13,6 @@ import "core:sys/linux"
PIDFD_UNASSIGNED :: ~uintptr(0)
@(private="package")
_exit :: proc "contextless" (code: int) -> ! {
linux.exit_group(i32(code))
}
@(private="package")
_get_uid :: proc() -> int {
return int(linux.getuid())
@@ -427,7 +422,8 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
strings.write_string(&exe_builder, executable_name)
exe_path = strings.to_cstring(&exe_builder) or_return
if linux.access(exe_path, linux.X_OK) == .NONE {
stat := linux.Stat{}
if linux.stat(exe_path, &stat) == .NONE && .IFREG in stat.mode && .IXUSR in stat.mode {
found = true
break
}
-4
View File
@@ -10,10 +10,6 @@ import "core:strings"
import kq "core:sys/kqueue"
import "core:sys/posix"
_exit :: proc "contextless" (code: int) -> ! {
posix.exit(i32(code))
}
_get_uid :: proc() -> int {
return int(posix.getuid())
}
+1 -5
View File
@@ -4,11 +4,7 @@ package os2
import "base:runtime"
import "core:time"
import "core:sys/wasm/wasi"
_exit :: proc "contextless" (code: int) -> ! {
wasi.proc_exit(wasi.exitcode_t(code))
}
// import "core:sys/wasm/wasi"
_get_uid :: proc() -> int {
return 0
-5
View File
@@ -7,11 +7,6 @@ import "core:strings"
import win32 "core:sys/windows"
import "core:time"
@(private="package")
_exit :: proc "contextless" (code: int) -> ! {
win32.ExitProcess(u32(code))
}
@(private="package")
_get_uid :: proc() -> int {
return -1
+29 -5
View File
@@ -6,13 +6,16 @@ import "core:time"
Fstat_Callback :: proc(f: ^File, allocator: runtime.Allocator) -> (File_Info, Error)
/*
`File_Info` describes a file and is returned from `stat`, `fstat`, and `lstat`.
*/
File_Info :: struct {
fullpath: string,
name: string,
fullpath: string, // fullpath of the file
name: string, // base name of the file
inode: u128, // might be zero if cannot be determined
size: i64 `fmt:"M"`,
mode: int `fmt:"o"`,
inode: u128, // might be zero if cannot be determined
size: i64 `fmt:"M"`, // length in bytes for regular files; system-dependent for other file types
mode: Permissions, // file permission flags
type: File_Type,
creation_time: time.Time,
@@ -49,6 +52,10 @@ fstat :: proc(f: ^File, allocator: runtime.Allocator) -> (File_Info, Error) {
return {}, .Invalid_Callback
}
/*
`stat` returns a `File_Info` describing the named file from the file system.
The resulting `File_Info` must be deleted with `file_info_delete`.
*/
@(require_results)
stat :: proc(name: string, allocator: runtime.Allocator) -> (File_Info, Error) {
return _stat(name, allocator)
@@ -56,12 +63,21 @@ stat :: proc(name: string, allocator: runtime.Allocator) -> (File_Info, Error) {
lstat :: stat_do_not_follow_links
/*
Returns a `File_Info` describing the named file from the file system.
If the file is a symbolic link, the `File_Info` returns describes the symbolic link,
rather than following the link.
The resulting `File_Info` must be deleted with `file_info_delete`.
*/
@(require_results)
stat_do_not_follow_links :: proc(name: string, allocator: runtime.Allocator) -> (File_Info, Error) {
return _lstat(name, allocator)
}
/*
Returns true if two `File_Info`s are equivalent.
*/
@(require_results)
same_file :: proc(fi1, fi2: File_Info) -> bool {
return _same_file(fi1, fi2)
@@ -71,6 +87,10 @@ same_file :: proc(fi1, fi2: File_Info) -> bool {
last_write_time :: modification_time
last_write_time_by_name :: modification_time_by_path
/*
Returns the modification time of the file `f`.
The resolution of the timestamp is system-dependent.
*/
@(require_results)
modification_time :: proc(f: ^File) -> (time.Time, Error) {
temp_allocator := TEMP_ALLOCATOR_GUARD({})
@@ -78,6 +98,10 @@ modification_time :: proc(f: ^File) -> (time.Time, Error) {
return fi.modification_time, err
}
/*
Returns the modification time of the named file `path`.
The resolution of the timestamp is system-dependent.
*/
@(require_results)
modification_time_by_path :: proc(path: string) -> (time.Time, Error) {
temp_allocator := TEMP_ALLOCATOR_GUARD({})
+1 -1
View File
@@ -26,7 +26,7 @@ _fstat_internal :: proc(fd: linux.Fd, allocator: runtime.Allocator) -> (fi: File
case linux.S_IFREG: type = .Regular
case linux.S_IFSOCK: type = .Socket
}
mode := int(0o7777 & transmute(u32)s.mode)
mode := transmute(Permissions)(0o7777 & transmute(u32)s.mode)
// TODO: As of Linux 4.11, the new statx syscall can retrieve creation_time
fi = File_Info {
+1 -1
View File
@@ -14,7 +14,7 @@ internal_stat :: proc(stat: posix.stat_t, fullpath: string) -> (fi: File_Info) {
fi.inode = u128(stat.st_ino)
fi.size = i64(stat.st_size)
fi.mode = int(transmute(posix._mode_t)(stat.st_mode - posix.S_IFMT))
fi.mode = transmute(Permissions)u32(transmute(posix._mode_t)(stat.st_mode - posix.S_IFMT))
fi.type = .Undetermined
switch {
+4 -4
View File
@@ -211,11 +211,11 @@ _file_type_from_create_file :: proc(wname: win32.wstring, create_file_attributes
return file_type(h)
}
_file_type_mode_from_file_attributes :: proc(file_attributes: win32.DWORD, h: win32.HANDLE, ReparseTag: win32.DWORD) -> (type: File_Type, mode: int) {
_file_type_mode_from_file_attributes :: proc(file_attributes: win32.DWORD, h: win32.HANDLE, ReparseTag: win32.DWORD) -> (type: File_Type, mode: Permissions) {
if file_attributes & win32.FILE_ATTRIBUTE_READONLY != 0 {
mode |= 0o444
mode += Permissions_Write_All
} else {
mode |= 0o666
mode += Permissions_Read_Write_All
}
is_sym := false
@@ -229,7 +229,7 @@ _file_type_mode_from_file_attributes :: proc(file_attributes: win32.DWORD, h: wi
type = .Symlink
} else if file_attributes & win32.FILE_ATTRIBUTE_DIRECTORY != 0 {
type = .Directory
mode |= 0o111
mode += Permissions_Execute_All
} else if h != nil {
type = file_type(h)
}
+15 -2
View File
@@ -7,7 +7,7 @@ MAX_ATTEMPTS :: 1<<13 // Should be enough for everyone, right?
// Creates a new temperatory file in the directory `dir`.
//
// Opens the file for reading and writing, with 0o666 permissions, and returns the new `^File`.
// Opens the file for reading and writing, with `Permissions_Read_Write_All` permissions, and returns the new `^File`.
// The filename is generated by taking a pattern, and adding a randomized string to the end.
// If the pattern includes an "*", the random string replaces the last "*".
// If `dir` is an empty string, `temp_directory()` will be used.
@@ -26,7 +26,7 @@ create_temp_file :: proc(dir, pattern: string) -> (f: ^File, err: Error) {
attempts := 0
for {
name := concatenate_strings_from_buffer(name_buf[:], prefix, random_string(rand_buf[:]), suffix)
f, err = open(name, {.Read, .Write, .Create, .Excl}, 0o666)
f, err = open(name, {.Read, .Write, .Create, .Excl}, Permissions_Read_Write_All)
if err == .Exist {
close(f)
attempts += 1
@@ -80,6 +80,19 @@ make_directory_temp :: proc(dir, pattern: string, allocator: runtime.Allocator)
}
temp_dir :: temp_directory
/*
Returns the default directory to use for temporary files.
On Unix systems, it typically returns $TMPDIR if non-empty, otherwlse `/tmp`.
On Windows, it uses `GetTempPathW`, returning the first non-empty value from one of the following:
* `%TMP%`
* `%TEMP%`
* `%USERPROFILE %`
* or the Windows directory
See https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-gettemppathw for more information.
On wasi, it returns `/tmp`.
*/
@(require_results)
temp_directory :: proc(allocator: runtime.Allocator) -> (string, Error) {
return _temp_dir(allocator)
+12 -12
View File
@@ -2,7 +2,6 @@
package os2
import "base:runtime"
import "core:encoding/ini"
import "core:strings"
_user_cache_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
@@ -157,18 +156,19 @@ _xdg_user_dirs_lookup :: proc(xdg_key: string, allocator: runtime.Allocator) ->
user_dirs_path := concatenate({config_dir, "/user-dirs.dirs"}, temp_allocator) or_return
content := read_entire_file(user_dirs_path, temp_allocator) or_return
it := ini.Iterator{
section = "",
_src = string(content),
options = ini.Options{
comment = "#",
key_lower_case = false,
},
}
xdg_dirs := string(content)
for line in strings.split_lines_iterator(&xdg_dirs) {
if len(line) > 0 && line[0] == '#' {
continue
}
for k, v in ini.iterate(&it) {
if k == xdg_key {
return replace_environment_placeholders(v, allocator), nil
equals := strings.index(line, "=")
if equals > -1 {
if line[:equals] == xdg_key {
// Unquote to return a bare path string as we do on Windows
val := strings.trim(line[equals+1:], "\"")
return replace_environment_placeholders(val, allocator), nil
}
}
}
return