///|
priv suberror MktempError {
MktempError(String)
}
///|
fn random_hex(length : Int) -> String raise MktempError {
guard @env.rand(length) is Some(bytes) else {
raise MktempError("mktemp: secure random source is unavailable")
}
let digits = "0123456789abcdef"
let output = StringBuilder()
for byte in bytes {
output.write_char(digits[byte.to_int() & 0x0F].unsafe_to_char())
}
output.to_string()
}
///|
fn fill_template(template : String) -> String raise MktempError {
let chars : Array[Char] = template.iter().collect()
let mut first = chars.length()
while first > 0 && chars[first - 1] == 'X' {
first -= 1
}
if chars.length() - first < 3 {
raise MktempError("mktemp: template must end in at least 3 X's")
}
let replacement = random_hex(chars.length() - first)
let output = StringBuilder()
for index, char in chars {
if index < first {
output.write_char(char)
} else {
output.write_char(replacement[index - first].unsafe_to_char())
}
}
output.to_string()
}
///|
fn default_template(directory : Bool) -> String {
let root = @env.get_env_var("TMPDIR").unwrap_or("/tmp")
if directory {
root + "/tmp.XXXXXX"
} else {
root + "/tmp.XXXXXX"
}
}
///|
async fn create_file(path : String) -> Bool raise MktempError {
try {
let file = @fs.open(
path,
mode=WriteOnly,
create_mode=CreateNew,
permission=0o600,
)
file.close()
true
} catch {
@os_error.OSError(_) as error if error.is_EEXIST() => false
_ => raise MktempError("mktemp: cannot create temporary file")
}
}
///|
async fn create_directory(path : String) -> Bool raise MktempError {
try {
@fs.mkdir(path, permission=0o700)
true
} catch {
@os_error.OSError(_) as error if error.is_EEXIST() => false
_ => raise MktempError("mktemp: cannot create temporary directory")
}
}
///|
async fn create_unique(
template : String,
directory : Bool,
) -> String raise MktempError {
for _ in 0..<100 {
let candidate = fill_template(template)
let created = if directory {
create_directory(candidate)
} else {
create_file(candidate)
}
if created {
return candidate
}
}
raise MktempError("mktemp: failed to create a unique name after 100 attempts")
}
///|
async fn main {
let args = @env.args()[1:]
let parsed = @cli.parse(args, [
@cli.flag("directory", short='d'),
@cli.flag("help"),
]) catch {
@cli.CliError(option~, message~, ..) => {
@stdio.stderr.write("mktemp: \{message}: '\{option}'\n")
@sys.exit(1)
return
}
}
if parsed.contains("help") {
@stdio.stdout.write(
"Usage: mktemp [-d] [TEMPLATE]\nCreate a uniquely named temporary file or directory.\n",
)
return
}
if parsed.operands.length() > 1 {
@stdio.stderr.write("mktemp: too many templates\n")
@sys.exit(1)
return
}
let directory = parsed.contains("directory")
let template = if parsed.operands.is_empty() {
default_template(directory)
} else {
parsed.operands[0]
}
let result = create_unique(template, directory) catch {
MktempError(message) => {
@stdio.stderr.write("\{message}\n")
@sys.exit(1)
return
}
}
@stdio.stdout.write(result + "\n")
}