Filesystem Operations
Part of: Reef Language Reference Last reviewed on version: 0.8.0
This guide covers Reef's filesystem module family (fs.*) for native file operations. These modules replace shell-out patterns in programs like coral.
Result/Option migration (Phase C): as of the Result/Option migration, every
fs.* function that can fail returns Result[T, core.error.Error] (or, for
values that may be absent rather than failed, Option[T]) instead of a
sentinel like -1, "", or false. Import core.result and core.error
(and core.option where noted) alongside the fs.* module you're using:
import core.result as result
import core.error as error
import core.option as option
Overview
The fs module family provides four focused modules:
| Module | Purpose | Shell Equivalent |
|---|---|---|
fs.stat |
File type queries and metadata | stat, test -f/-d/-L |
fs.perm |
Permission modification and testing | chmod, chown, test -r/-w/-x |
fs.link |
Symbolic and hard link operations | ln -s, readlink, ln |
fs.ops |
Copy, remove, rename (single and recursive) | cp, cp -rp, rm, rm -rf, mv |
These complement existing I/O modules:
| Module | Purpose |
|---|---|
io.file |
Read/write file contents (readFile, writeFile) |
io.dir |
Directory creation and listing (create_dir, create_dir_all, list_dir) |
File Metadata (fs.stat)
Query file type and metadata via stat/lstat.
import fs.stat
import core.result as result
import core.error as error
proc main()
let path = "/etc/hosts"
// Type checks
if stat.is_file(path)
println("regular file")
end if
if stat.is_directory("/tmp")
println("directory")
end if
if stat.is_symlink("/usr/bin/python3")
println("symlink")
end if
// Existence check
if stat.exists(path)
println("exists")
end if
// Metadata — each of these returns a Result, since the underlying
// stat()/lstat() call can fail (e.g. ENOENT, EACCES).
let size_r = stat.file_size(path)
if result.is_err(size_r)
let msg = error.error_message(result.unwrap_err(size_r))
println("stat failed: ${msg}")
else
let size = result.unwrap_ok(size_r)
println("size: ${size}")
end if
let mode = result.unwrap_or(stat.file_mode(path), 0)
let uid = result.unwrap_or(stat.file_uid(path), -1)
let gid = result.unwrap_or(stat.file_gid(path), -1)
let mtime = result.unwrap_or(stat.file_mtime(path), 0i64)
end main
Function Reference
| Function | Return | Description |
|---|---|---|
is_file(path) |
bool |
Regular file (not symlink/dir/device) |
is_directory(path) |
bool |
Directory |
is_symlink(path) |
bool |
Symbolic link (uses lstat) |
is_pipe(path) |
bool |
Named pipe (FIFO) |
is_socket(path) |
bool |
Unix domain socket file |
is_block_device(path) |
bool |
Block device |
is_char_device(path) |
bool |
Character device |
exists(path) |
bool |
Path exists (any type) |
file_size(path) |
Result[int64, Error] |
Size in bytes; Err if stat fails |
file_mode(path) |
Result[int, Error] |
Permission bits; Err if stat fails |
file_uid(path) |
Result[int, Error] |
Owner UID; Err if stat fails |
file_gid(path) |
Result[int, Error] |
Owner GID; Err if stat fails |
file_mtime(path) |
Result[int64, Error] |
Modification time as Unix timestamp; Err if stat fails |
uid_name(uid) |
Option[string] |
Username for UID; None if not resolvable |
gid_name(gid) |
Option[string] |
Group name for GID; None if not resolvable |
file_uid_name(path) |
Result[Option[string], Error] |
Owner username; Err if stat fails, Ok(None) if UID unresolvable |
file_gid_name(path) |
Result[Option[string], Error] |
Owner group name; Err if stat fails, Ok(None) if GID unresolvable |
Permissions (fs.perm)
Modify and test file permissions.
import fs.perm
import io.file
import core.result as result
import core.error as error
proc main()
let path = "/tmp/myfile.txt"
if result.is_err(file.writeFile(path, "hello"))
return
end if
// Set specific permission mode (octal 0644 = decimal 420)
if result.is_err(perm.chmod(path, 420))
println("chmod failed")
end if
// Test permissions
if perm.is_readable(path)
println("readable")
end if
if perm.is_writable(path)
println("writable")
end if
// Convenience: add execute bits for all
perm.set_executable(path)
// Convenience: remove write bits for all
perm.set_readonly(path)
// Change ownership (requires root)
let chown_r = perm.chown(path, 0, 0)
if result.is_err(chown_r)
let msg = error.error_message(result.unwrap_err(chown_r))
println("chown failed: ${msg}")
end if
// Change ownership of symlink itself
perm.lchown("/tmp/mylink", 1000, 1000)
end main
All five mutating functions above (chmod, chown, lchown, set_executable,
set_readonly) return Result[bool, Error] — Ok(true) on success, Err
carrying the failure reason (e.g. PermissionDenied, NotFound). Discarding
the result (as set_executable/set_readonly do above) is fine for
best-effort calls; check it when the caller needs to know whether the change
actually happened.
Permission Constants
POSIX permission bit constants are available as functions:
import fs.perm
proc main()
// Build custom mode: rwxr-xr-x = 0755
let mode = perm.S_IRUSR() + perm.S_IWUSR() + perm.S_IXUSR()
+ perm.S_IRGRP() + perm.S_IXGRP()
+ perm.S_IROTH() + perm.S_IXOTH()
perm.chmod("/tmp/myfile", mode)
end main
| Constant | Octal | Description |
|---|---|---|
S_IRUSR() |
0400 | Owner read |
S_IWUSR() |
0200 | Owner write |
S_IXUSR() |
0100 | Owner execute |
S_IRGRP() |
0040 | Group read |
S_IWGRP() |
0020 | Group write |
S_IXGRP() |
0010 | Group execute |
S_IROTH() |
0004 | Others read |
S_IWOTH() |
0002 | Others write |
S_IXOTH() |
0001 | Others execute |
S_ISUID() |
4000 | Set-user-ID |
S_ISGID() |
2000 | Set-group-ID |
S_ISVTX() |
1000 | Sticky bit |
Function Reference
| Function | Return | Description |
|---|---|---|
chmod(path, mode) |
Result[bool, Error] |
Set permission bits |
chown(path, uid, gid) |
Result[bool, Error] |
Change owner/group |
lchown(path, uid, gid) |
Result[bool, Error] |
Change symlink owner/group (no dereference) |
set_executable(path) |
Result[bool, Error] |
Add execute bits for user/group/other |
set_readonly(path) |
Result[bool, Error] |
Remove write bits for user/group/other |
is_readable(path) |
bool |
Check read access for current user |
is_writable(path) |
bool |
Check write access for current user |
is_executable(path) |
bool |
Check execute access for current user |
Links (fs.link)
Create and read symbolic and hard links.
import fs.link
import fs.stat
import core.result as result
import core.error as error
proc main()
// Create a symbolic link
if result.is_err(link.symlink("/etc/hosts", "/tmp/hosts_link"))
println("symlink failed")
return
end if
// Read symlink target
let target_r = link.readlink("/tmp/hosts_link")
if result.is_err(target_r)
let msg = error.error_message(result.unwrap_err(target_r))
println("readlink failed: ${msg}")
else
let target = result.unwrap_ok(target_r)
println("target: ${target}") // target: /etc/hosts
end if
// Verify it's a symlink
if stat.is_symlink("/tmp/hosts_link")
println("is symlink")
end if
// Create a hard link (same inode)
link.hardlink("/tmp/file.txt", "/tmp/file_hard.txt")
end main
Function Reference
| Function | Return | Description |
|---|---|---|
symlink(target, link_path) |
Result[bool, Error] |
Create symbolic link |
readlink(path) |
Result[string, Error] |
Read symlink target |
hardlink(old_path, new_path) |
Result[bool, Error] |
Create hard link |
File Operations (fs.ops)
Copy, remove, and rename files and directory trees.
Single File Operations
import fs.ops
import io.file
import core.result as result
import core.error as error
proc main()
if result.is_err(file.writeFile("/tmp/src.txt", "hello"))
return
end if
// Copy file (content only, default permissions)
if result.is_err(ops.copy_file("/tmp/src.txt", "/tmp/dst.txt"))
println("copy failed")
end if
// Copy file preserving permissions (like cp -p)
ops.copy_file_preserve("/tmp/src.txt", "/tmp/dst2.txt")
// Copy a symlink without dereferencing
ops.copy_symlink("/tmp/mylink", "/tmp/mylink_copy")
// Remove a single file (like rm -f, silent on ENOENT)
ops.remove_file("/tmp/dst.txt")
// Rename/move a file
let rename_r = ops.rename("/tmp/dst2.txt", "/tmp/moved.txt")
if result.is_err(rename_r)
let msg = error.error_message(result.unwrap_err(rename_r))
println("rename failed: ${msg}")
end if
end main
Recursive Operations
import fs.ops
import io.file
import io.dir
import core.result as result
import core.error as error
proc main()
// Build a directory tree
dir.create_dir_all("/tmp/myproject/src/lib")
file.writeFile("/tmp/myproject/src/main.reef", "proc main() end main")
file.writeFile("/tmp/myproject/src/lib/util.reef", "// utils")
// Copy entire tree (preserves permissions and symlinks)
if result.is_err(ops.copy_tree("/tmp/myproject", "/tmp/myproject_backup"))
println("copy_tree failed")
return
end if
// Remove entire tree (like rm -rf)
let rm_r = ops.remove_tree("/tmp/myproject_backup")
if result.is_err(rm_r)
let msg = error.error_message(result.unwrap_err(rm_r))
println("remove_tree failed: ${msg}")
end if
end main
Safety
remove_tree has built-in safety checks that now surface as Err(error(InvalidInput, ...))
rather than silently failing:
- Refuses to remove
"/"(root directory) - Refuses paths with fewer than 2 components (e.g.,
"/tmp"is rejected) - Maximum recursion depth of 256
copy_tree also has a recursion depth limit of 256 and copies symlinks as symlinks (no dereferencing).
Function Reference
| Function | Return | Description |
|---|---|---|
copy_file(src, dst) |
Result[bool, Error] |
Copy file contents (default permissions) |
copy_file_preserve(src, dst) |
Result[bool, Error] |
Copy file preserving permissions |
copy_symlink(src, dst) |
Result[bool, Error] |
Copy symlink without dereferencing |
remove_file(path) |
Result[bool, Error] |
Remove single file |
rename(old_path, new_path) |
Result[bool, Error] |
Rename/move file or directory |
remove_tree(path) |
Result[bool, Error] |
Recursively remove directory tree; Err(InvalidInput) on an unsafe path |
copy_tree(src, dst) |
Result[bool, Error] |
Recursively copy directory tree |
Common Patterns
Check Before Operate
import fs.stat
import fs.ops
import core.result as result
import core.error as error
proc safe_copy(src: string, dst: string)
if not stat.exists(src)
println("source not found: ${src}")
return
end if
if stat.exists(dst)
println("destination exists: ${dst}")
return
end if
let copy_r = ops.copy_file(src, dst)
if result.is_err(copy_r)
let msg = error.error_message(result.unwrap_err(copy_r))
println("copy failed: ${msg}")
end if
end safe_copy
Deploy Script (Replace Shell-Outs)
import fs.stat
import fs.perm
import fs.ops
import io.dir
proc deploy(src_dir: string, dst_dir: string)
// Create destination if needed
if not stat.is_directory(dst_dir)
dir.create_dir_all(dst_dir)
end if
// Copy project tree
ops.copy_tree(src_dir, dst_dir)
// Make scripts executable
perm.set_executable(dst_dir + "/bin/start.sh")
println("deployed to ${dst_dir}")
end deploy
Backup With Timestamp
import fs.ops
import fs.stat
import time.time as time
import core.convert as convert
proc backup(path: string)
if not stat.exists(path)
return
end if
let backup_path = path + "." + convert.to_string(time.time_now()) + ".bak"
if stat.is_directory(path)
ops.copy_tree(path, backup_path)
else
ops.copy_file_preserve(path, backup_path)
end if
end backup
Module Relationship
fs.stat ─── metadata queries (read-only)
fs.perm ─── permission changes (chmod/chown)
fs.link ─── symlink/hardlink operations
fs.ops ─── copy/remove/rename (destructive operations)
io.file ─── file content read/write
io.dir ─── directory create/list
All fs.* modules delegate to C runtime functions in reef_fs.c via the FFI hub at sys.platform.runtime.