///|
fn parse_mode(text : String) -> Int raise {
let digits = if text.has_prefix("0o") || text.has_prefix("0O") {
text[2:].to_owned()
} else {
text
}
if digits == "" {
fail("empty mode")
}
let mut value = 0
for digit in digits {
if !(digit is ('0'..='7')) {
fail("invalid mode")
}
value = value * 8 + digit.to_int() - '0'.to_int()
if value > 0o7777 {
fail("mode out of range")
}
}
value
}
///|
fn mode_text(mode : Int) -> String {
let digits = "01234567"
let out = StringBuilder()
out.write_char('0')
let first_shift = if mode > 0o777 { 0 } else { 1 }
for shift in first_shift..<=3 {
let value = (mode >> ((3 - shift) * 3)) & 0x7
out.write_char(digits.get_char(value).unwrap())
}
out.to_string()
}
///|
priv suberror ChmodError {
ChmodError(String)
}
///|
async fn chmod_path(
path : String,
mode : Int,
recursive : Bool,
verbose : Bool,
) -> Unit {
match @fs.kind(path, follow_symlink=false) {
SymLink => raise ChmodError("refusing to chmod symbolic link: \{path}")
Directory => {
@fs.chmod(path, mode)
if verbose {
@stdio.stdout.write("mode of '\{path}' changed to \{mode_text(mode)}\n")
}
if recursive {
let entries = @fs.readdir(
path,
include_hidden=true,
include_special=false,
sort=true,
)
for entry in entries {
chmod_path(
@path.Path(path).join(Path(entry)).to_string(),
mode,
true,
verbose,
)
}
}
}
_ => {
@fs.chmod(path, mode)
if verbose {
@stdio.stdout.write("mode of '\{path}' changed to \{mode_text(mode)}\n")
}
}
}
}
///|
async fn main {
let args = @env.args()[1:]
let parsed = @cli.parse(args, [
@cli.flag("recursive", short='R'),
@cli.flag("verbose", short='v'),
@cli.flag("help"),
]) catch {
@cli.CliError(option~, message~, ..) => {
@stdio.stderr.write("chmod: \{message}: '\{option}'\n")
@sys.exit(2)
return
}
}
if parsed.contains("help") {
@stdio.stdout.write("Usage: chmod [-R] [-v] MODE FILE...\n")
return
}
let selected_mode = match parsed.operands.get(0) {
Some(value) =>
parse_mode(value) catch {
_ => {
@stdio.stderr.write("chmod: invalid mode: {value}\n")
@sys.exit(2)
return
}
}
None => {
@stdio.stderr.write("chmod: missing mode\n")
@sys.exit(2)
return
}
}
let operands = parsed.operands[1:]
if operands.is_empty() {
@stdio.stderr.write("chmod: missing operand\n")
@sys.exit(2)
return
}
if !@platform.permission_mutation_supported() {
@stdio.stderr.write("chmod: unsupported capability: permission mutation\n")
@sys.exit(1)
return
}
let recursive = parsed.contains("recursive")
let verbose = parsed.contains("verbose")
let mut failed = false
for path in operands {
chmod_path(path, selected_mode, recursive, verbose) catch {
err => {
@stdio.stderr.write("chmod: cannot change mode: \{err}\n")
failed = true
}
}
}
if failed {
@sys.exit(1)
}
}