Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions docs/_docs/user-guide/eldritch.md
Original file line number Diff line number Diff line change
Expand Up @@ -671,6 +671,12 @@ The **file.append** method appends the `content` to file at `path`. If no file e

The **file.compress** method compresses a file using the gzip algorithm. If the destination file doesn't exist it will be created. If the source file doesn't exist an error will be thrown. If the source path is a directory the contents will be placed in a tar archive and then compressed.

### file.chmod

`file.chmod(path: str, mode: int) -> None`

The **file.chmod** method changes the permissions of a file. The `mode` should typically be specified in octal (e.g. `0o755`). On Windows, this method only considers the `0o200` (owner writable) bit to determine whether to toggle the read-only attribute of the file, similar to Golang's `os.Chmod`.

### file.copy

`file.copy(src: str, dst: str) -> None`
Expand Down
4 changes: 2 additions & 2 deletions implants/golem/tests/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ fn test_golem_main_basic_non_interactive() -> anyhow::Result<()> {
cmd.assert()
.success()
.stdout(predicate::str::contains(r#"HELLO"#))
.stdout(predicate::str::contains(r#""append", "compress""#));
.stdout(predicate::str::contains(r#""append", "chmod", "compress""#));

Ok(())
}
Expand All @@ -64,7 +64,7 @@ fn test_golem_main_loaded_files() -> anyhow::Result<()> {
cmd.arg(GOLEM_CLI_TEST_DIR);
cmd.assert()
.success()
.stdout(predicate::str::contains(r#"["append", "compress""#));
.stdout(predicate::str::contains(r#"["append", "chmod", "compress""#));
Ok(())
}

Expand Down
4 changes: 4 additions & 0 deletions implants/lib/eldritch/stdlib/eldritch-libfile/src/fake.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,10 @@ impl FileLibrary for FileLibraryFake {
Ok(())
}

fn chmod(&self, _path: String, _mode: i64) -> Result<(), String> {
Ok(())
}

fn copy(&self, src: String, dst: String) -> Result<(), String> {
let mut root = self.root.lock();
let src_parts = Self::normalize_path(&src);
Expand Down
14 changes: 14 additions & 0 deletions implants/lib/eldritch/stdlib/eldritch-libfile/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,20 @@ pub trait FileLibrary {
/// - Returns an error string if the source doesn't exist or copy fails.
fn copy(&self, src: String, dst: String) -> Result<(), String>;

#[eldritch_method]
/// Changes the permissions of a file.
///
/// **Parameters**
/// - `path` (`str`): The file path.
/// - `mode` (`u32`): The permission mode (e.g., 0o755). On Windows, only the 0o200 (owner writable) bit is considered.
///
/// **Returns**
/// - `None`
///
/// **Errors**
/// - Returns an error string if chmod fails.
fn chmod(&self, path: String, mode: i64) -> Result<(), String>;

#[eldritch_method]
/// Decompresses a GZIP file.
///
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
use alloc::string::String;
use alloc::string::ToString;

pub fn chmod(path: String, mode: i64) -> Result<(), String> {
chmod_impl(path, mode as u32)
}

#[cfg(unix)]
fn chmod_impl(path: String, mode: u32) -> Result<(), String> {
use std::fs;
use std::os::unix::fs::PermissionsExt;

let mut perms = fs::metadata(&path)
.map_err(|e| e.to_string())?
.permissions();
perms.set_mode(mode);
fs::set_permissions(&path, perms).map_err(|e| e.to_string())
}

#[cfg(windows)]
fn chmod_impl(path: String, mode: u32) -> Result<(), String> {
use std::fs;

let mut perms = fs::metadata(&path)
.map_err(|e| e.to_string())?
.permissions();

// Windows logic: only check the 0o200 bit (owner writable)
let is_writable = (mode & 0o200) != 0;
perms.set_readonly(!is_writable);

fs::set_permissions(&path, perms).map_err(|e| e.to_string())
}

#[cfg(not(any(unix, windows)))]
fn chmod_impl(_path: String, _mode: u32) -> Result<(), String> {
Err("chmod is not supported on this platform".to_string())
}

#[cfg(test)]
mod tests {
use super::*;
use std::fs;
#[cfg(unix)]
use std::os::unix::fs::PermissionsExt;
use tempfile::NamedTempFile;

#[test]
#[cfg(unix)]
fn test_chmod_unix() {
let file = NamedTempFile::new().unwrap();
let path = file.path().to_str().unwrap().to_string();

chmod(path.clone(), 0o755).unwrap();

let perms = fs::metadata(&path).unwrap().permissions();
assert_eq!(perms.mode() & 0o777, 0o755);
}

#[test]
#[cfg(windows)]
fn test_chmod_windows() {
let file = NamedTempFile::new().unwrap();
let path = file.path().to_str().unwrap().to_string();

// Try setting writable (clear readonly)
chmod(path.clone(), 0o600).unwrap();
let perms = fs::metadata(&path).unwrap().permissions();
assert_eq!(perms.readonly(), false);

// Try setting readonly
chmod(path.clone(), 0o400).unwrap();
let perms = fs::metadata(&path).unwrap().permissions();
assert_eq!(perms.readonly(), true);
}
}
5 changes: 5 additions & 0 deletions implants/lib/eldritch/stdlib/eldritch-libfile/src/std/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use eldritch_core::Value;
use eldritch_macros::eldritch_library_impl;

pub mod append_impl;
pub mod chmod_impl;
pub mod compress_impl;
pub mod copy_impl;
pub mod decompress_impl;
Expand Down Expand Up @@ -46,6 +47,10 @@ impl FileLibrary for StdFileLibrary {
compress_impl::compress(src, dst)
}

fn chmod(&self, path: String, mode: i64) -> Result<(), String> {
chmod_impl::chmod(path, mode)
}

fn copy(&self, src: String, dst: String) -> Result<(), String> {
copy_impl::copy(src, dst)
}
Expand Down
4 changes: 4 additions & 0 deletions tavern/internal/www/src/assets/eldritch-docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,10 @@
"signature": "file.compress(src: str, dst: str) -> None",
"description": "The **file.compress** method compresses a file using the gzip algorithm. If the destination file doesn't exist it will be created. If the source file doesn't exist an error will be thrown. If the source path is a directory the contents will be placed in a tar archive and then compressed."
},
"file.chmod": {
"signature": "file.chmod(path: str, mode: int) -> None",
"description": "The **file.chmod** method changes the permissions of a file. The `mode` should typically be specified in octal (e.g. `0o755`). On Windows, this method only considers the `0o200` (owner writable) bit to determine whether to toggle the read-only attribute of the file, similar to Golang's `os.Chmod`."
},
"file.copy": {
"signature": "file.copy(src: str, dst: str) -> None",
"description": "The **file.copy** method copies a file from `src` path to `dst` path. If `dst` file doesn't exist it will be created."
Expand Down
Loading