diff --git a/docs/paths.md b/docs/paths.md index 6e8d6fc..14ec783 100644 --- a/docs/paths.md +++ b/docs/paths.md @@ -37,33 +37,68 @@ intended. Paths can be created from text with slashes using ## Path Methods +- [`func accessed(path:Path, follow_symlinks=yes -> Moment?)`](#accessed) - [`func append(path: Path, text: Text, permissions: Int32 = 0o644[32] -> Void)`](#append) - [`func append_bytes(path: Path, bytes: [Byte], permissions: Int32 = 0o644[32] -> Void)`](#append_bytes) - [`func base_name(path: Path -> Text)`](#base_name) - [`func by_line(path: Path -> func(->Text?)?)`](#by_line) +- [`func can_execute(path:Path -> Bool)`](#can_execute) +- [`func can_read(path:Path -> Bool)`](#can_read) +- [`func can_write(path:Path -> Bool)`](#can_write) +- [`func changed(path:Path, follow_symlinks=yes -> Moment?)`](#changed) +- [`func child(path: Path, child:Text -> Path)`](#child) - [`func children(path: Path, include_hidden=no -> [Path])`](#children) - [`func create_directory(path: Path, permissions=0o755[32] -> Void)`](#create_directory) - [`func exists(path: Path -> Bool)`](#exists) - [`func extension(path: Path, full=yes -> Text)`](#extension) - [`func files(path: Path, include_hidden=no -> [Path])`](#files) +- [`func from_components(components:[Text] -> Path)`](#from_components) - [`func glob(path: Path -> [Path])`](#glob) +- [`func group(path: Path, follow_symlinks=yes -> Text?)`](#group) - [`func is_directory(path: Path, follow_symlinks=yes -> Bool)`](#is_directory) - [`func is_file(path: Path, follow_symlinks=yes -> Bool)`](#is_file) - [`func is_socket(path: Path, follow_symlinks=yes -> Bool)`](#is_socket) - [`func is_symlink(path: Path -> Bool)`](#is_symlink) +- [`func modified(path:Path, follow_symlinks=yes -> Moment?)`](#modified) +- [`func owner(path: Path, follow_symlinks=yes -> Text?)`](#owner) - [`func parent(path: Path -> Path)`](#parent) - [`func read(path: Path -> Text?)`](#read) - [`func read_bytes(path: Path -> [Byte]?)`](#read_bytes) -- [`func relative(path: Path, relative_to=(./) -> Path)`](#relative) +- [`func relative_to(path: Path, relative_to=(./) -> Path)`](#relative_to) - [`func remove(path: Path, ignore_missing=no -> Void)`](#remove) - [`func resolved(path: Path, relative_to=(./) -> Path)`](#resolved) +- [`func set_owner(path:Path, owner=none:Text, group=none:Text, follow_symlinks=yes)`](#set_owner) - [`func subdirectories(path: Path, include_hidden=no -> [Path])`](#subdirectories) - [`func unique_directory(path: Path -> Path)`](#unique_directory) - [`func write(path: Path, text: Text, permissions=0o644[32] -> Void)`](#write) -- [`func write(path: Path, bytes: [Byte], permissions=0o644[32] -> Void)`](#write_bytes) +- [`func write_bytes(path: Path, bytes: [Byte], permissions=0o644[32] -> Void)`](#write_bytes) - [`func write_unique(path: Path, text: Text -> Path)`](#write_unique) - [`func write_unique_bytes(path: Path, bytes: [Byte] -> Path)`](#write_unique_bytes) +### `accessed` +Gets the file access time of a file. + +```tomo +func accessed(path:Path, follow_symlinks: Bool = yes -> Moment?) +``` + +- `path`: The path of the file whose access time you want. +- `follow_symlinks`: Whether to follow symbolic links (default is `yes`). + +**Returns:** +A [Moment](moments.md) representing when the file or directory was last +accessed, or `none` if no such file or directory exists. + +**Example:** +```tomo +>> (./file.txt):accessed() += Sun 16 Mar 2025 03:43:53 PM EDT EDT : Moment? +>> (./not-a-file):accessed() += none : Moment? +``` + +--- + ### `append` Appends the given text to the file at the specified path, creating the file if it doesn't already exist. Failure to write will result in a runtime error. @@ -74,7 +109,7 @@ func append(path: Path, text: Text, permissions: Int32 = 0o644[32] -> Void) - `path`: The path of the file to append to. - `text`: The text to append to the file. -- `permissions` (optional): The permissions to set on the file if it is being created (default is `0o644`). +- `permissions`: The permissions to set on the file if it is being created (default is `0o644`). **Returns:** Nothing. @@ -96,7 +131,7 @@ func append_bytes(path: Path, bytes: [Byte], permissions: Int32 = 0o644[32] -> V - `path`: The path of the file to append to. - `bytes`: The bytes to append to the file. -- `permissions` (optional): The permissions to set on the file if it is being created (default is `0o644`). +- `permissions`: The permissions to set on the file if it is being created (default is `0o644`). **Returns:** Nothing. @@ -158,6 +193,127 @@ for line in (/dev/stdin):by_line()!: --- +### `can_execute` +Returns whether or not a file can be executed by the current user/group. + +```tomo +func can_execute(path: Path -> Bool) +``` + +- `path`: The path of the file to check. + +**Returns:** +`yes` if the file or directory exists and the current user has execute permissions, otherwise `no`. + +**Example:** +```tomo +>> (/bin/sh):can_execute() += yes +>> (/usr/include/stdlib.h):can_execute() += no +>> (/non/existant/file):can_execute() += no +``` + +--- + +### `can_read` +Returns whether or not a file can be read by the current user/group. + +```tomo +func can_read(path: Path -> Bool) +``` + +- `path`: The path of the file to check. + +**Returns:** +`yes` if the file or directory exists and the current user has read permissions, otherwise `no`. + +**Example:** +```tomo +>> (/usr/include/stdlib.h):can_read() += yes +>> (/etc/shadow):can_read() += no +>> (/non/existant/file):can_read() += no +``` + +--- + +### `can_write` +Returns whether or not a file can be written by the current user/group. + +```tomo +func can_write(path: Path -> Bool) +``` + +- `path`: The path of the file to check. + +**Returns:** +`yes` if the file or directory exists and the current user has write permissions, otherwise `no`. + +**Example:** +```tomo +>> (/tmp):can_write() += yes +>> (/etc/passwd):can_write() += no +>> (/non/existant/file):can_write() += no +``` + +--- + +### `changed` +Gets the file change time of a file. + +**Note:** this is the +["ctime"](https://en.wikipedia.org/wiki/Stat_(system_call)#ctime) of a file, +which is _not_ the file creation time. + +```tomo +func changed(path:Path, follow_symlinks: Bool = yes -> Moment?) +``` + +- `path`: The path of the file whose change time you want. +- `follow_symlinks`: Whether to follow symbolic links (default is `yes`). + +**Returns:** +A [Moment](moments.md) representing when the file or directory was last +changed, or `none` if no such file or directory exists. + +**Example:** +```tomo +>> (./file.txt):changed() += Sun 16 Mar 2025 03:43:53 PM EDT EDT : Moment? +>> (./not-a-file):changed() += none : Moment? +``` + +--- + +### `child` +Return a path that is a child of another path. + +```tomo +func child(path: Path, child: Text -> [Path]) +``` + +- `path`: The path of a directory. +- `child`: The name of a child file or directory. + +**Returns:** +A new path representing the child. + +**Example:** +```tomo +>> (./directory):child("file.txt") += (./directory/file.txt) +``` + +--- + ### `children` Returns a list of children (files and directories) within the directory at the specified path. Optionally includes hidden files. @@ -166,7 +322,7 @@ func children(path: Path, include_hidden=no -> [Path]) ``` - `path`: The path of the directory. -- `include_hidden` (optional): Whether to include hidden files, which start with a `.` (default is `no`). +- `include_hidden`: Whether to include hidden files, which start with a `.` (default is `no`). **Returns:** A list of paths for the children. @@ -188,7 +344,7 @@ func create_directory(path: Path, permissions=0o755[32] -> Void) ``` - `path`: The path of the directory to create. -- `permissions` (optional): The permissions to set on the new directory (default is `0o644`). +- `permissions`: The permissions to set on the new directory (default is `0o644`). **Returns:** Nothing. @@ -224,11 +380,11 @@ func exists(path: Path -> Bool) Returns the file extension of the file at the specified path. Optionally returns the full extension. ```tomo -func extension(path: Path, full=yes -> Text) +func extension(path: Path, full:Bool = yes -> Text) ``` - `path`: The path of the file. -- `full` (optional): Whether to return everything after the first `.` in the +- `full`: Whether to return everything after the first `.` in the base name, or only the last part of the extension (default is `yes`). **Returns:** @@ -253,11 +409,11 @@ no file extension. Returns a list of files within the directory at the specified path. Optionally includes hidden files. ```tomo -func files(path: Path, include_hidden=no -> [Path]) +func files(path: Path, include_hidden: Bool = no -> [Path]) ``` - `path`: The path of the directory. -- `include_hidden` (optional): Whether to include hidden files (default is `no`). +- `include_hidden`: Whether to include hidden files (default is `no`). **Returns:** A list of file paths. @@ -270,6 +426,30 @@ A list of file paths. --- +### `from_components` +Returns a path built from an array of path components. + +```tomo +func from_components(components: [Text] -> Path) +``` + +- `components`: An array of path components. + +**Returns:** +A path representing the given components. + +**Example:** +```tomo +>> Path.from_components(["/", "usr", "include"]) += /usr/include +>> Path.from_components(["foo.txt"]) += ./foo.txt +>> Path.from_components(["~", ".local"]) += ~/.local +``` + +--- + ### `glob` Perform a globbing operation and return an array of matching paths. Some glob specific details: @@ -312,6 +492,29 @@ A list of file paths that match the glob. --- +### `group` +Get the owning group of a file or directory. + +```tomo +func group(path: Path, follow_symlinks: Bool = yes -> Text?) +``` + +- `path`: The path whose owning group to get. +- `follow_symlinks`: Whether to follow symbolic links (default is `yes`). + +**Returns:** +The name of the group which owns the file or directory, or `none` if the path does not exist. + +**Example:** +```tomo +>> (/bin):group() += "root" +>> (/non/existent/file):group() += none:Text +``` + +--- + ### `is_directory` Checks if the path represents a directory. Optionally follows symbolic links. @@ -320,7 +523,7 @@ func is_directory(path: Path, follow_symlinks=yes -> Bool) ``` - `path`: The path to check. -- `follow_symlinks` (optional): Whether to follow symbolic links (default is `yes`). +- `follow_symlinks`: Whether to follow symbolic links (default is `yes`). **Returns:** `True` if the path is a directory, `False` otherwise. @@ -344,7 +547,7 @@ func is_file(path: Path, follow_symlinks=yes -> Bool) ``` - `path`: The path to check. -- `follow_symlinks` (optional): Whether to follow symbolic links (default is `yes`). +- `follow_symlinks`: Whether to follow symbolic links (default is `yes`). **Returns:** `True` if the path is a file, `False` otherwise. @@ -368,7 +571,7 @@ func is_socket(path: Path, follow_symlinks=yes -> Bool) ``` - `path`: The path to check. -- `follow_symlinks` (optional): Whether to follow symbolic links (default is `yes`). +- `follow_symlinks`: Whether to follow symbolic links (default is `yes`). **Returns:** `True` if the path is a socket, `False` otherwise. @@ -401,6 +604,53 @@ func is_symlink(path: Path -> Bool) --- +### `modified` +Gets the file modification time of a file. + +```tomo +func modified(path:Path, follow_symlinks: Bool = yes -> Moment?) +``` + +- `path`: The path of the file whose modification time you want. +- `follow_symlinks`: Whether to follow symbolic links (default is `yes`). + +**Returns:** +A [Moment](moments.md) representing when the file or directory was last +modified, or `none` if no such file or directory exists. + +**Example:** +```tomo +>> (./file.txt):modified() += Sun 16 Mar 2025 03:43:53 PM EDT EDT : Moment? +>> (./not-a-file):modified() += none : Moment? +``` + +--- + +### `owner` +Get the owning user of a file or directory. + +```tomo +func owner(path: Path, follow_symlinks: Bool = yes -> Text?) +``` + +- `path`: The path whose owner to get. +- `follow_symlinks`: Whether to follow symbolic links (default is `yes`). + +**Returns:** +The name of the user who owns the file or directory, or `none` if the path does not exist. + +**Example:** +```tomo +>> (/bin):owner() += "root" +>> (/non/existent/file):owner() += none:Text +``` + +--- + ### `parent` Returns the parent directory of the file or directory at the specified path. @@ -471,15 +721,15 @@ returned. --- -### `relative` +### `relative_to` Returns the path relative to a given base path. By default, the base path is the current directory. ```tomo -func relative(path: Path, relative_to=(./) -> Path) +func relative_to(path: Path, relative_to=(./) -> Path) ``` - `path`: The path to convert. -- `relative_to` (optional): The base path for the relative path (default is `./`). +- `relative_to`: The base path for the relative path (default is `./`). **Returns:** The relative path. @@ -500,7 +750,7 @@ func remove(path: Path, ignore_missing=no -> Void) ``` - `path`: The path to remove. -- `ignore_missing` (optional): Whether to ignore errors if the file or directory does not exist (default is `no`). +- `ignore_missing`: Whether to ignore errors if the file or directory does not exist (default is `no`). **Returns:** Nothing. @@ -520,7 +770,7 @@ func resolved(path: Path, relative_to=(./) -> Path) ``` - `path`: The path to resolve. -- `relative_to` (optional): The base path for resolution (default is `./`). +- `relative_to`: The base path for resolution (default is `./`). **Returns:** The resolved absolute path. @@ -536,6 +786,28 @@ The resolved absolute path. --- +### `set_owner` +Set the owning user and/or group for a path. + +```tomo +func set_owner(path:Path, owner: Text? = none:Text, group: Text? = none:Text, follow_symlinks: Bool = yes) +``` + +- `path`: The path to change the permissions for. +- `owner`: If non-none, the new user to assign to be the owner of the file (default: `none`). +- `group`: If non-none, the new group to assign to be the owner of the file (default: `none`). +- `follow_symlinks`: Whether to follow symbolic links (default is `yes`). + +**Returns:** +Nothing. If a path does not exist, a failure will be raised. + +**Example:** +```tomo +(./file.txt):set_owner(owner="root", group="wheel") +``` + +--- + ### `subdirectories` Returns a list of subdirectories within the directory at the specified path. Optionally includes hidden subdirectories. @@ -544,7 +816,7 @@ func subdirectories(path: Path, include_hidden=no -> [Path]) ``` - `path`: The path of the directory. -- `include_hidden` (optional): Whether to include hidden subdirectories (default is `no`). +- `include_hidden`: Whether to include hidden subdirectories (default is `no`). **Returns:** A list of subdirectory paths. @@ -595,7 +867,7 @@ func write(path: Path, text: Text, permissions=0o644[32] -> Void) - `path`: The path of the file to write to. - `text`: The text to write to the file. -- `permissions` (optional): The permissions to set on the file if it is created (default is `0o644`). +- `permissions`: The permissions to set on the file if it is created (default is `0o644`). **Returns:** Nothing. @@ -618,7 +890,7 @@ func write(path: Path, bytes: [Byte], permissions=0o644[32] -> Void) - `path`: The path of the file to write to. - `bytes`: An array of bytes to write to the file. -- `permissions` (optional): The permissions to set on the file if it is created (default is `0o644`). +- `permissions`: The permissions to set on the file if it is created (default is `0o644`). **Returns:** Nothing. diff --git a/environment.c b/environment.c index 7212d26..78525b0 100644 --- a/environment.c +++ b/environment.c @@ -334,45 +334,45 @@ env_t *new_compilation_unit(CORD libname) {"year", "Moment$year", "func(moment:Moment,timezone=none:Text -> Int)"}, )}, {"Path", PATH_TYPE, "Path_t", "Path$info", TypedArray(ns_entry_t, + {"accessed", "Path$accessed", "func(path:Path, follow_symlinks=yes -> Moment?)"}, {"append", "Path$append", "func(path:Path, text:Text, permissions=Int32(0o644))"}, {"append_bytes", "Path$append_bytes", "func(path:Path, bytes:[Byte], permissions=Int32(0o644))"}, {"base_name", "Path$base_name", "func(path:Path -> Text)"}, {"by_line", "Path$by_line", "func(path:Path -> func(->Text?)?)"}, + {"can_execute", "Path$can_execute", "func(path:Path -> Bool)"}, + {"can_read", "Path$can_read", "func(path:Path -> Bool)"}, + {"can_write", "Path$can_write", "func(path:Path -> Bool)"}, + {"changed", "Path$changed", "func(path:Path, follow_symlinks=yes -> Moment?)"}, {"child", "Path$with_component", "func(path:Path, child:Text -> Path)"}, {"children", "Path$children", "func(path:Path, include_hidden=no -> [Path])"}, - {"components", "Path$components", "func(path:Path -> [Text])"}, {"concatenated_with", "Path$concat", "func(a,b:Path -> Path)"}, {"create_directory", "Path$create_directory", "func(path:Path, permissions=Int32(0o755))"}, - {"escape_int", "Int$value_as_text", "func(i:Int -> Path)"}, - {"escape_path", "Path$escape_path", "func(path:Path -> Path)"}, - {"escape_text", "Path$escape_text", "func(text:Text -> Path)"}, {"exists", "Path$exists", "func(path:Path -> Bool)"}, {"extension", "Path$extension", "func(path:Path, full=yes -> Text)"}, {"files", "Path$children", "func(path:Path, include_hidden=no -> [Path])"}, {"from_components", "Path$from_components", "func(components:[Text] -> Path)"}, {"glob", "Path$glob", "func(path:Path -> [Path])"}, + {"group", "Path$group", "func(path:Path, follow_symlinks=yes -> Text?)"}, {"is_directory", "Path$is_directory", "func(path:Path, follow_symlinks=yes -> Bool)"}, {"is_file", "Path$is_file", "func(path:Path, follow_symlinks=yes -> Bool)"}, {"is_pipe", "Path$is_pipe", "func(path:Path, follow_symlinks=yes -> Bool)"}, {"is_socket", "Path$is_socket", "func(path:Path, follow_symlinks=yes -> Bool)"}, {"is_symlink", "Path$is_symlink", "func(path:Path -> Bool)"}, + {"modified", "Path$modified", "func(path:Path, follow_symlinks=yes -> Moment?)"}, + {"owner", "Path$owner", "func(path:Path, follow_symlinks=yes -> Text?)"}, {"parent", "Path$parent", "func(path:Path -> Path)"}, {"read", "Path$read", "func(path:Path -> Text?)"}, {"read_bytes", "Path$read_bytes", "func(path:Path, limit=none:Int -> [Byte]?)"}, - {"relative", "Path$relative", "func(path:Path, relative_to=(./) -> Path)"}, {"relative_to", "Path$relative_to", "func(path:Path, relative_to:Path -> Path)"}, {"remove", "Path$remove", "func(path:Path, ignore_missing=no)"}, {"resolved", "Path$resolved", "func(path:Path, relative_to=(./) -> Path)"}, + {"set_owner", "Path$set_owner", "func(path:Path, owner=none:Text, group=none:Text, follow_symlinks=yes)"}, {"subdirectories", "Path$children", "func(path:Path, include_hidden=no -> [Path])"}, {"unique_directory", "Path$unique_directory", "func(path:Path -> Path)"}, {"write", "Path$write", "func(path:Path, text:Text, permissions=Int32(0o644))"}, {"write_bytes", "Path$write_bytes", "func(path:Path, bytes:[Byte], permissions=Int32(0o644))"}, {"write_unique", "Path$write_unique", "func(path:Path, text:Text -> Path)"}, {"write_unique_bytes", "Path$write_unique_bytes", "func(path:Path, bytes:[Byte] -> Path)"}, - - {"modified", "Path$modified", "func(path:Path, follow_symlinks=yes -> Moment?)"}, - {"accessed", "Path$accessed", "func(path:Path, follow_symlinks=yes -> Moment?)"}, - {"changed", "Path$changed", "func(path:Path, follow_symlinks=yes -> Moment?)"}, )}, // RNG must come after Path so we can read bytes from /dev/urandom {"RNG", RNG_TYPE, "RNG_t", "RNG", TypedArray(ns_entry_t, diff --git a/stdlib/paths.c b/stdlib/paths.c index 6290815..813c200 100644 --- a/stdlib/paths.c +++ b/stdlib/paths.c @@ -4,6 +4,8 @@ #include #include #include +#include +#include #include #include #include @@ -215,6 +217,27 @@ public bool Path$is_symlink(Path_t path) return (sb.st_mode & S_IFMT) == S_IFLNK; } +public bool Path$can_read(Path_t path) +{ + path = Path$_expand_home(path); + const char *path_str = Path$as_c_string(path); + return (euidaccess(path_str, R_OK) == 0); +} + +public bool Path$can_write(Path_t path) +{ + path = Path$_expand_home(path); + const char *path_str = Path$as_c_string(path); + return (euidaccess(path_str, W_OK) == 0); +} + +public bool Path$can_execute(Path_t path) +{ + path = Path$_expand_home(path); + const char *path_str = Path$as_c_string(path); + return (euidaccess(path_str, X_OK) == 0); +} + public OptionalMoment_t Path$modified(Path_t path, bool follow_symlinks) { struct stat sb; @@ -340,6 +363,45 @@ public OptionalText_t Path$read(Path_t path) return Text$from_bytes(bytes); } +public OptionalText_t Path$owner(Path_t path, bool follow_symlinks) +{ + struct stat sb; + int status = path_stat(path, follow_symlinks, &sb); + if (status != 0) return NONE_TEXT; + struct passwd *pw = getpwuid(sb.st_uid); + return pw ? Text$from_str(pw->pw_name) : NONE_TEXT; +} + +public OptionalText_t Path$group(Path_t path, bool follow_symlinks) +{ + struct stat sb; + int status = path_stat(path, follow_symlinks, &sb); + if (status != 0) return NONE_TEXT; + struct group *gr = getgrgid(sb.st_uid); + return gr ? Text$from_str(gr->gr_name) : NONE_TEXT; +} + +public void Path$set_owner(Path_t path, OptionalText_t owner, OptionalText_t group, bool follow_symlinks) +{ + uid_t owner_id = (uid_t)-1; + if (owner.length >= 0) { + struct passwd *pwd = getpwnam(Text$as_c_string(owner)); + if (pwd == NULL) fail("Not a valid user: %k", &owner); + owner_id = pwd->pw_uid; + } + + gid_t group_id = (gid_t)-1; + if (group.length >= 0) { + struct group *grp = getgrnam(Text$as_c_string(group)); + if (grp == NULL) fail("Not a valid group: %k", &group); + group_id = grp->gr_gid; + } + const char *path_str = Path$as_c_string(path); + int result = follow_symlinks ? chown(path_str, owner_id, group_id) : lchown(path_str, owner_id, group_id); + if (result < 0) + fail("Could not set owner!"); +} + public void Path$remove(Path_t path, bool ignore_missing) { path = Path$_expand_home(path); @@ -515,6 +577,7 @@ public Path_t Path$with_component(Path_t path, Text_t component) }; ARRAY_INCREF(result.components); Array$insert(&result.components, &component, I(0), sizeof(Text_t)); + clean_components(&result.components); return result; } @@ -690,7 +753,7 @@ public Text_t Path$as_text(const void *obj, bool color, const TypeInfo_t *type) text = Text$concat(path->components.length > 0 ? Text("~/") : Text("~"), text); else if (path->type == PATH_ABSOLUTE) text = Text$concat(Text("/"), text); - else if (path->type == PATH_RELATIVE && path->components.length > 0 && !Text$equal_values(*(Text_t*)(path->components.data), Text(".."))) + else if (path->type == PATH_RELATIVE && (path->components.length == 0 || !Text$equal_values(*(Text_t*)(path->components.data), Text("..")))) text = Text$concat(path->components.length > 0 ? Text("./") : Text("."), text); if (color) diff --git a/stdlib/paths.h b/stdlib/paths.h index 3efe10d..b971cdf 100644 --- a/stdlib/paths.h +++ b/stdlib/paths.h @@ -24,6 +24,9 @@ bool Path$is_directory(Path_t path, bool follow_symlinks); bool Path$is_pipe(Path_t path, bool follow_symlinks); bool Path$is_socket(Path_t path, bool follow_symlinks); bool Path$is_symlink(Path_t path); +bool Path$can_read(Path_t path); +bool Path$can_write(Path_t path); +bool Path$can_execute(Path_t path); OptionalMoment_t Path$modified(Path_t path, bool follow_symlinks); OptionalMoment_t Path$accessed(Path_t path, bool follow_symlinks); OptionalMoment_t Path$changed(Path_t path, bool follow_symlinks); @@ -33,6 +36,9 @@ void Path$append(Path_t path, Text_t text, int permissions); void Path$append_bytes(Path_t path, Array_t bytes, int permissions); OptionalText_t Path$read(Path_t path); OptionalArray_t Path$read_bytes(Path_t path, OptionalInt_t limit); +void Path$set_owner(Path_t path, OptionalText_t owner, OptionalText_t group, bool follow_symlinks); +OptionalText_t Path$owner(Path_t path, bool follow_symlinks); +OptionalText_t Path$group(Path_t path, bool follow_symlinks); void Path$remove(Path_t path, bool ignore_missing); void Path$create_directory(Path_t path, int permissions); Array_t Path$children(Path_t path, bool include_hidden);