diff options
Diffstat (limited to 'core/os/file.odin')
| -rw-r--r-- | core/os/file.odin | 570 |
1 files changed, 570 insertions, 0 deletions
diff --git a/core/os/file.odin b/core/os/file.odin new file mode 100644 index 000000000..61582c528 --- /dev/null +++ b/core/os/file.odin @@ -0,0 +1,570 @@ +package os + +import "core:io" +import "core:time" +import "base:runtime" + +/* + Type representing a file handle. + + This struct represents an OS-specific file-handle, which can be one of + the following: + - File + - Directory + - Pipe + - Named pipe + - Block Device + - Character device + - Symlink + - Socket + + See `File_Type` enum for more information on file types. +*/ +File :: struct { + impl: rawptr, + stream: File_Stream, +} + +/* + Type representing the type of a file handle. + + **Note(windows)**: Socket handles can not be distinguished from + files, as they are just a normal file handle that is being treated by + a special driver. Windows also makes no distinction between block and + character devices. +*/ +File_Type :: enum { + // The type of a file could not be determined for the current platform. + Undetermined, + // Represents a regular file. + Regular, + // Represents a directory. + Directory, + // Represents a symbolic link. + Symlink, + // Represents a named pipe (FIFO). + Named_Pipe, + // Represents a socket. + // **Note(windows)**: Not returned on windows + Socket, + // Represents a block device. + // **Note(windows)**: On windows represents all devices. + Block_Device, + // Represents a character device. + // **Note(windows)**: Not returned on windows + Character_Device, +} + +// Represents the file flags for a file handle +File_Flags :: distinct bit_set[File_Flag; uint] +File_Flag :: enum { + Read, + Write, + Append, + Create, + Excl, + Sync, + Trunc, + Sparse, + Inheritable, + Non_Blocking, + Unbuffered_IO, +} + +O_RDONLY :: File_Flags{.Read} +O_WRONLY :: File_Flags{.Write} +O_RDWR :: File_Flags{.Read, .Write} +O_APPEND :: File_Flags{.Append} +O_CREATE :: File_Flags{.Create} +O_EXCL :: File_Flags{.Excl} +O_SYNC :: File_Flags{.Sync} +O_TRUNC :: File_Flags{.Trunc} +O_SPARSE :: File_Flags{.Sparse} + +/* + If specified, the file handle is inherited upon the creation of a child + process. By default all handles are created non-inheritable. + + **Note**: The standard file handles (stderr, stdout and stdin) are always + initialized as inheritable. +*/ +O_INHERITABLE :: File_Flags{.Inheritable} + +Permissions :: distinct bit_set[Permission_Flag; u32] +Permission_Flag :: enum u32 { + Execute_Other = 0, + Write_Other = 1, + Read_Other = 2, + + 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) +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) +} + +// @(require_results) +// open_buffered :: proc(name: string, buffer_size: uint, flags := File_Flags{.Read}, perm := 0o777) -> (^File, Error) { +// if buffer_size == 0 { +// return _open(name, flags, perm) +// } +// 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()) + if err != nil { + panic(error_string(err)) + } + 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) +} + +/* + Close a file and its stream. + + Any further use of the file or its stream should be considered to be in the + same class of bugs as a use-after-free. +*/ +close :: proc(f: ^File) -> Error { + if f != nil { + if f.stream.procedure == nil { + return .Unsupported + } + _, err := f.stream.procedure(f, .Close, nil, 0, nil, runtime.nil_allocator()) + return err + } + 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 { + if f.stream.procedure == nil { + return 0, .Unsupported + } + return f.stream.procedure(f, .Seek, nil, offset, whence, runtime.nil_allocator()) + } + 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 { + if f.stream.procedure == nil { + return 0, .Unsupported + } + n64: i64 + n64, err = f.stream.procedure(f, .Read, p, 0, nil, runtime.nil_allocator()) + return int(n64), err + } + 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 { + if f.stream.procedure == nil { + return 0, .Unsupported + } + n64: i64 + n64, err = f.stream.procedure(f, .Read_At, p, offset, nil, runtime.nil_allocator()) + return int(n64), err + } + 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 { + if f.stream.procedure == nil { + return 0, .Unsupported + } + n64: i64 + n64, err = f.stream.procedure(f, .Write, p, 0, nil, runtime.nil_allocator()) + return int(n64), err + } + 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 { + if f.stream.procedure == nil { + return 0, .Unsupported + } + n64: i64 + n64, err = f.stream.procedure(f, .Write_At, p, offset, nil, runtime.nil_allocator()) + return int(n64), err + } + 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 { + if f.stream.procedure == nil { + return 0, .Unsupported + } + n, err = f.stream.procedure(f, .Size, nil, 0, nil, runtime.nil_allocator()) + if err == .Unsupported { + n = 0 + curr := seek(f, 0, .Current) or_return + end := seek(f, 0, .End) or_return + seek(f, curr, .Start) or_return + n = end + } + return + } + return 0, .Invalid_File +} + +/* + `flush` flushes a file `f` +*/ +flush :: proc(f: ^File) -> Error { + if f != nil { + if f.stream.procedure == nil { + return .Unsupported + } + _, err := f.stream.procedure(f, .Flush, nil, 0, nil, runtime.nil_allocator()) + return err + } + 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) +} + + +chdir :: change_directory + +/* + Changes the current working directory to the named directory. +*/ +change_directory :: proc(name: string) -> Error { + return _chdir(name) +} + +chmod :: change_mode + +/* + 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 + +/* + 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) +} + + +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({}) + fi, err := stat(path, temp_allocator) + if err != nil { + return false + } + return fi.type == .Regular +} + +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({}) + fi, err := stat(path, temp_allocator) + if err != nil { + return false + } + return fi.type == .Directory +} + +/* + `copy_file` copies a file from `src_path` to `dst_path` and returns an error if any was encountered. +*/ +@(require_results) +is_tty :: proc "contextless" (f: ^File) -> bool { + return _is_tty(f) +} + +copy_file :: proc(dst_path, src_path: string) -> Error { + when #defined(_copy_file_native) { + return _copy_file_native(dst_path, src_path) + } else { + return _copy_file(dst_path, src_path) + } +} + +@(private) +_copy_file :: proc(dst_path, src_path: string) -> Error { + src := open(src_path) or_return + defer close(src) + + info := fstat(src, file_allocator()) or_return + defer file_info_delete(info, file_allocator()) + if info.type == .Directory { + return .Invalid_File + } + + 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)) + return err +}
\ No newline at end of file |