123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552 |
- //+private
- package os2
- import "base:runtime"
- import "core:io"
- import "core:time"
- import "core:sync"
- import "core:sys/linux"
- File_Impl :: struct {
- file: File,
- name: string,
- fd: linux.Fd,
- allocator: runtime.Allocator,
- buffer: []byte,
- rw_mutex: sync.RW_Mutex, // read write calls
- p_mutex: sync.Mutex, // pread pwrite calls
- }
- _stdin := File{
- stream = {
- procedure = _file_stream_proc,
- },
- fstat = _fstat,
- }
- _stdout := File{
- stream = {
- procedure = _file_stream_proc,
- },
- fstat = _fstat,
- }
- _stderr := File{
- stream = {
- procedure = _file_stream_proc,
- },
- fstat = _fstat,
- }
- @init
- _standard_stream_init :: proc() {
- @static stdin_impl := File_Impl {
- name = "/proc/self/fd/0",
- fd = 0,
- }
- @static stdout_impl := File_Impl {
- name = "/proc/self/fd/1",
- fd = 1,
- }
- @static stderr_impl := File_Impl {
- name = "/proc/self/fd/2",
- fd = 2,
- }
- stdin_impl.allocator = file_allocator()
- stdout_impl.allocator = file_allocator()
- stderr_impl.allocator = file_allocator()
- _stdin.impl = &stdin_impl
- _stdout.impl = &stdout_impl
- _stderr.impl = &stderr_impl
- // cannot define these initially because cyclic reference
- _stdin.stream.data = &stdin_impl
- _stdout.stream.data = &stdout_impl
- _stderr.stream.data = &stderr_impl
- stdin = &_stdin
- stdout = &_stdout
- stderr = &_stderr
- }
- _open :: proc(name: string, flags: File_Flags, perm: int) -> (f: ^File, err: Error) {
- TEMP_ALLOCATOR_GUARD()
- name_cstr := temp_cstring(name) or_return
- // Just default to using O_NOCTTY because needing to open a controlling
- // terminal would be incredibly rare. This has no effect on files while
- // allowing us to open serial devices.
- sys_flags: linux.Open_Flags = {.NOCTTY, .CLOEXEC}
- switch flags & (O_RDONLY|O_WRONLY|O_RDWR) {
- case O_RDONLY:
- case O_WRONLY: sys_flags += {.WRONLY}
- case O_RDWR: sys_flags += {.RDWR}
- }
- if .Append in flags { sys_flags += {.APPEND} }
- if .Create in flags { sys_flags += {.CREAT} }
- if .Excl in flags { sys_flags += {.EXCL} }
- if .Sync in flags { sys_flags += {.DSYNC} }
- if .Trunc in flags { sys_flags += {.TRUNC} }
- if .Inheritable in flags { sys_flags -= {.CLOEXEC} }
- fd, errno := linux.open(name_cstr, sys_flags, transmute(linux.Mode)u32(perm))
- if errno != .NONE {
- return nil, _get_platform_error(errno)
- }
- return _new_file(uintptr(fd), name)
- }
- _new_file :: proc(fd: uintptr, _: string = "") -> (f: ^File, err: Error) {
- impl := new(File_Impl, file_allocator()) or_return
- defer if err != nil {
- free(impl, file_allocator())
- }
- impl.file.impl = impl
- impl.fd = linux.Fd(fd)
- impl.allocator = file_allocator()
- impl.name = _get_full_path(impl.fd, file_allocator()) or_return
- impl.file.stream = {
- data = impl,
- procedure = _file_stream_proc,
- }
- impl.file.fstat = _fstat
- return &impl.file, nil
- }
- @(require_results)
- _open_buffered :: proc(name: string, buffer_size: uint, flags := File_Flags{.Read}, perm := 0o777) -> (f: ^File, err: Error) {
- assert(buffer_size > 0)
- f, err = _open(name, flags, perm)
- if f != nil && err == nil {
- impl := (^File_Impl)(f.impl)
- impl.buffer = make([]byte, buffer_size, file_allocator())
- f.stream.procedure = _file_stream_buffered_proc
- }
- return
- }
- _destroy :: proc(f: ^File_Impl) -> Error {
- if f == nil {
- return nil
- }
- a := f.allocator
- err0 := delete(f.name, a)
- err1 := delete(f.buffer, a)
- err2 := free(f, a)
- err0 or_return
- err1 or_return
- err2 or_return
- return nil
- }
- _close :: proc(f: ^File_Impl) -> Error {
- if f == nil{
- return nil
- }
- errno := linux.close(f.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 {
- if f == nil || f.impl == nil {
- return ~uintptr(0)
- }
- impl := (^File_Impl)(f.impl)
- return uintptr(impl.fd)
- }
- _name :: proc(f: ^File) -> string {
- return (^File_Impl)(f.impl).name if f != nil && f.impl != nil else ""
- }
- _seek :: proc(f: ^File_Impl, offset: i64, whence: io.Seek_From) -> (ret: i64, err: Error) {
- // We have to handle this here, because Linux returns EINVAL for both
- // invalid offsets and invalid whences.
- switch whence {
- case .Start, .Current, .End:
- break
- case:
- return 0, .Invalid_Whence
- }
- n, errno := linux.lseek(f.fd, offset, linux.Seek_Whence(whence))
- #partial switch errno {
- case .EINVAL:
- return 0, .Invalid_Offset
- case .NONE:
- return n, nil
- case:
- return -1, _get_platform_error(errno)
- }
- }
- _read :: proc(f: ^File_Impl, p: []byte) -> (i64, Error) {
- if len(p) == 0 {
- return 0, nil
- }
- n, errno := linux.read(f.fd, p[:])
- if errno != .NONE {
- return -1, _get_platform_error(errno)
- }
- return i64(n), io.Error.EOF if n == 0 else nil
- }
- _read_at :: proc(f: ^File_Impl, p: []byte, offset: i64) -> (i64, Error) {
- if len(p) == 0 {
- return 0, nil
- }
- if offset < 0 {
- return 0, .Invalid_Offset
- }
- n, errno := linux.pread(f.fd, p[:], offset)
- if errno != .NONE {
- return -1, _get_platform_error(errno)
- }
- if n == 0 {
- return 0, .EOF
- }
- return i64(n), nil
- }
- _write :: proc(f: ^File_Impl, p: []byte) -> (i64, Error) {
- if len(p) == 0 {
- return 0, nil
- }
- n, errno := linux.write(f.fd, p[:])
- if errno != .NONE {
- return -1, _get_platform_error(errno)
- }
- return i64(n), nil
- }
- _write_at :: proc(f: ^File_Impl, p: []byte, offset: i64) -> (i64, Error) {
- if len(p) == 0 {
- return 0, nil
- }
- if offset < 0 {
- return 0, .Invalid_Offset
- }
- n, errno := linux.pwrite(f.fd, p[:], offset)
- if errno != .NONE {
- return -1, _get_platform_error(errno)
- }
- return i64(n), nil
- }
- _file_size :: proc(f: ^File_Impl) -> (n: i64, err: Error) {
- // TODO: Identify 0-sized "pseudo" files and return No_Size. This would
- // eliminate the need for the _read_entire_pseudo_file procs.
- s: linux.Stat = ---
- errno := linux.fstat(f.fd, &s)
- if errno != .NONE {
- return -1, _get_platform_error(errno)
- }
- if s.mode & linux.S_IFMT == linux.S_IFREG {
- return i64(s.size), nil
- }
- return 0, .No_Size
- }
- _sync :: proc(f: ^File) -> Error {
- impl := (^File_Impl)(f.impl)
- return _get_platform_error(linux.fsync(impl.fd))
- }
- _flush :: proc(f: ^File_Impl) -> Error {
- return _get_platform_error(linux.fsync(f.fd))
- }
- _truncate :: proc(f: ^File, size: i64) -> Error {
- impl := (^File_Impl)(f.impl)
- return _get_platform_error(linux.ftruncate(impl.fd, size))
- }
- _remove :: proc(name: string) -> Error {
- is_dir_fd :: proc(fd: linux.Fd) -> bool {
- s: linux.Stat
- if linux.fstat(fd, &s) != .NONE {
- return false
- }
- return linux.S_ISDIR(s.mode)
- }
- TEMP_ALLOCATOR_GUARD()
- name_cstr := temp_cstring(name) or_return
- fd, errno := linux.open(name_cstr, {.NOFOLLOW})
- #partial switch (errno) {
- case .ELOOP:
- /* symlink */
- case .NONE:
- defer linux.close(fd)
- if is_dir_fd(fd) {
- return _get_platform_error(linux.rmdir(name_cstr))
- }
- case:
- return _get_platform_error(errno)
- }
- return _get_platform_error(linux.unlink(name_cstr))
- }
- _rename :: proc(old_name, new_name: string) -> Error {
- TEMP_ALLOCATOR_GUARD()
- old_name_cstr := temp_cstring(old_name) or_return
- new_name_cstr := temp_cstring(new_name) or_return
- return _get_platform_error(linux.rename(old_name_cstr, new_name_cstr))
- }
- _link :: proc(old_name, new_name: string) -> Error {
- TEMP_ALLOCATOR_GUARD()
- old_name_cstr := temp_cstring(old_name) or_return
- new_name_cstr := temp_cstring(new_name) or_return
- return _get_platform_error(linux.link(old_name_cstr, new_name_cstr))
- }
- _symlink :: proc(old_name, new_name: string) -> Error {
- TEMP_ALLOCATOR_GUARD()
- old_name_cstr := temp_cstring(old_name) or_return
- new_name_cstr := temp_cstring(new_name) or_return
- return _get_platform_error(linux.symlink(old_name_cstr, new_name_cstr))
- }
- _read_link_cstr :: proc(name_cstr: cstring, allocator: runtime.Allocator) -> (string, Error) {
- bufsz : uint = 256
- buf := make([]byte, bufsz, allocator)
- for {
- sz, errno := linux.readlink(name_cstr, buf[:])
- if errno != .NONE {
- delete(buf, allocator)
- return "", _get_platform_error(errno)
- } else if sz == int(bufsz) {
- bufsz *= 2
- delete(buf, allocator)
- buf = make([]byte, bufsz, allocator)
- } else {
- return string(buf[:sz]), nil
- }
- }
- }
- _read_link :: proc(name: string, allocator: runtime.Allocator) -> (s: string, e: Error) {
- TEMP_ALLOCATOR_GUARD()
- name_cstr := temp_cstring(name) or_return
- return _read_link_cstr(name_cstr, allocator)
- }
- _chdir :: proc(name: string) -> Error {
- TEMP_ALLOCATOR_GUARD()
- name_cstr := temp_cstring(name) or_return
- return _get_platform_error(linux.chdir(name_cstr))
- }
- _fchdir :: proc(f: ^File) -> Error {
- impl := (^File_Impl)(f.impl)
- return _get_platform_error(linux.fchdir(impl.fd))
- }
- _chmod :: proc(name: string, mode: int) -> Error {
- TEMP_ALLOCATOR_GUARD()
- name_cstr := temp_cstring(name) or_return
- return _get_platform_error(linux.chmod(name_cstr, transmute(linux.Mode)(u32(mode))))
- }
- _fchmod :: proc(f: ^File, mode: int) -> Error {
- impl := (^File_Impl)(f.impl)
- return _get_platform_error(linux.fchmod(impl.fd, transmute(linux.Mode)(u32(mode))))
- }
- // NOTE: will throw error without super user priviledges
- _chown :: proc(name: string, uid, gid: int) -> Error {
- TEMP_ALLOCATOR_GUARD()
- name_cstr := temp_cstring(name) or_return
- return _get_platform_error(linux.chown(name_cstr, linux.Uid(uid), linux.Gid(gid)))
- }
- // NOTE: will throw error without super user priviledges
- _lchown :: proc(name: string, uid, gid: int) -> Error {
- TEMP_ALLOCATOR_GUARD()
- name_cstr := temp_cstring(name) or_return
- return _get_platform_error(linux.lchown(name_cstr, linux.Uid(uid), linux.Gid(gid)))
- }
- // NOTE: will throw error without super user priviledges
- _fchown :: proc(f: ^File, uid, gid: int) -> Error {
- impl := (^File_Impl)(f.impl)
- return _get_platform_error(linux.fchown(impl.fd, linux.Uid(uid), linux.Gid(gid)))
- }
- _chtimes :: proc(name: string, atime, mtime: time.Time) -> Error {
- TEMP_ALLOCATOR_GUARD()
- name_cstr := temp_cstring(name) or_return
- times := [2]linux.Time_Spec {
- {
- uint(atime._nsec) / uint(time.Second),
- uint(atime._nsec) % uint(time.Second),
- },
- {
- uint(mtime._nsec) / uint(time.Second),
- uint(mtime._nsec) % uint(time.Second),
- },
- }
- return _get_platform_error(linux.utimensat(linux.AT_FDCWD, name_cstr, ×[0], nil))
- }
- _fchtimes :: proc(f: ^File, atime, mtime: time.Time) -> Error {
- times := [2]linux.Time_Spec {
- {
- uint(atime._nsec) / uint(time.Second),
- uint(atime._nsec) % uint(time.Second),
- },
- {
- uint(mtime._nsec) / uint(time.Second),
- uint(mtime._nsec) % uint(time.Second),
- },
- }
- impl := (^File_Impl)(f.impl)
- return _get_platform_error(linux.utimensat(impl.fd, nil, ×[0], nil))
- }
- _exists :: proc(name: string) -> bool {
- TEMP_ALLOCATOR_GUARD()
- name_cstr, _ := temp_cstring(name)
- return linux.access(name_cstr, linux.F_OK) == .NONE
- }
- /* For reading Linux system files that stat to size 0 */
- _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) {
- TEMP_ALLOCATOR_GUARD()
- name_cstr := clone_to_cstring(name, temp_allocator()) or_return
- 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")
- _file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, offset: i64, whence: io.Seek_From) -> (n: i64, err: io.Error) {
- f := (^File_Impl)(stream_data)
- ferr: Error
- switch mode {
- case .Read:
- n, ferr = _read(f, p)
- err = error_to_io_error(ferr)
- return
- case .Read_At:
- n, ferr = _read_at(f, p, offset)
- err = error_to_io_error(ferr)
- return
- case .Write:
- n, ferr = _write(f, p)
- err = error_to_io_error(ferr)
- return
- case .Write_At:
- n, ferr = _write_at(f, p, offset)
- err = error_to_io_error(ferr)
- return
- case .Seek:
- n, ferr = _seek(f, offset, whence)
- err = error_to_io_error(ferr)
- return
- case .Size:
- n, ferr = _file_size(f)
- err = error_to_io_error(ferr)
- return
- case .Flush:
- ferr = _flush(f)
- err = error_to_io_error(ferr)
- return
- case .Close, .Destroy:
- ferr = _close(f)
- err = error_to_io_error(ferr)
- return
- case .Query:
- return io.query_utility({.Read, .Read_At, .Write, .Write_At, .Seek, .Size, .Flush, .Close, .Destroy, .Query})
- }
- return 0, .Empty
- }
- @(private="package")
- _file_stream_buffered_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, offset: i64, whence: io.Seek_From) -> (n: i64, err: io.Error) {
- f := (^File_Impl)(stream_data)
- ferr: Error
- switch mode {
- case .Read:
- n, ferr = _read(f, p)
- err = error_to_io_error(ferr)
- return
- case .Read_At:
- n, ferr = _read_at(f, p, offset)
- err = error_to_io_error(ferr)
- return
- case .Write:
- n, ferr = _write(f, p)
- err = error_to_io_error(ferr)
- return
- case .Write_At:
- n, ferr = _write_at(f, p, offset)
- err = error_to_io_error(ferr)
- return
- case .Seek:
- n, ferr = _seek(f, offset, whence)
- err = error_to_io_error(ferr)
- return
- case .Size:
- n, ferr = _file_size(f)
- err = error_to_io_error(ferr)
- return
- case .Flush:
- ferr = _flush(f)
- err = error_to_io_error(ferr)
- return
- case .Close, .Destroy:
- ferr = _close(f)
- err = error_to_io_error(ferr)
- return
- case .Query:
- return io.query_utility({.Read, .Read_At, .Write, .Write_At, .Seek, .Size, .Flush, .Close, .Destroy, .Query})
- }
- return 0, .Empty
- }
|