///|
fn directory_not_empty(error : @os_error.OSError) -> Bool {
let @os_error.OSError(code, ..) = error
// Public OSError carries the host error number: ENOTEMPTY on POSIX,
// ERROR_DIR_NOT_EMPTY on Windows. EEXIST is the other POSIX spelling.
error.is_EEXIST() ||
code ==
(match @async.platform {
Linux => 39
MacOS => 66
Windows => 145
})
}
///|
async fn ignored_removal_failure(
path : String,
error : @os_error.OSError,
) -> Bool {
if directory_not_empty(error) {
return true
}
let @os_error.OSError(code, ..) = error
// GNU also checks nonemptiness after permission, read-only and busy errors.
// Never let that check turn a non-directory or symbolic link into success.
let may_mask_nonempty = error.is_EACCES() ||
(!(@async.platform is Windows) && (code == 1 || code == 16 || code == 30))
if !may_mask_nonempty {
return false
}
try
@fs.kind(path, follow_symlink=false) == Directory &&
!@fs.readdir(path, include_hidden=true, include_special=false, sort=false).is_empty()
catch {
_ => false
}
}
///|
async fn remove_directory(
path : String,
ignore_non_empty : Bool,
verbose : Bool,
) -> Bool {
if verbose {
@stdio.stdout.write("rmdir: removing directory, '\{path}'\n")
}
@fs.rmdir(path) catch {
@os_error.OSError(_) as error => {
if ignore_non_empty && ignored_removal_failure(path, error) {
return false
}
raise error
}
error => raise error
}
true
}
///|
async fn main {
let metadata_args = @env.args()[1:]
if metadata_args.length() == 1 && metadata_args[0] == "--version" {
@stdio.stdout.write("rmdir 0.2.5\n")
return
}
let args = @env.args()[1:]
let parsed = @cli.parse(args, [
@cli.flag("parents", short='p'),
@cli.flag("ignore-fail-on-non-empty", short='I'),
@cli.flag("verbose", short='v'),
@cli.flag("help"),
]) catch {
@cli.CliError(option~, message~, ..) => {
@stdio.stderr.write("rmdir: \{message}: '\{option}'\n")
@sys.exit(1)
return
}
}
if parsed.contains("help") {
@stdio.stdout.write(
"Usage: rmdir [-p] [-I] [-v] DIRECTORY...\n --help display this help and exit\n --version display version and exit\n -p, --parents remove empty parents, stopping at the first failure\n -v, --verbose report each removal attempt before it runs\n --ignore-fail-on-non-empty suppress only failures attributable to nonemptiness\n -I is a project short alias of --ignore-fail-on-non-empty, not a GNU option.\nOrdinary option/operand errors exit 1; documented capability refusals retain their own status.\n",
)
return
}
let directories = parsed.operands
let parents = parsed.contains("parents")
let ignore_non_empty = parsed.contains("ignore-fail-on-non-empty")
let verbose = parsed.contains("verbose")
if directories.is_empty() {
@stdio.stderr.write("rmdir: missing operand\n")
@sys.exit(1)
return
}
let mut failed = false
for directory in directories {
let mut current = directory
let mut keep_going = true
while keep_going {
let removed = remove_directory(current, ignore_non_empty, verbose) catch {
err => {
@stdio.stderr.write("rmdir: failed to remove '\{current}': \{err}\n")
failed = true
break
}
}
if !removed || !parents {
break
}
let parent = @fsops.dirname(current)
keep_going = parent != current &&
parent != "." &&
!protects_working_directory(parent)
current = parent
}
}
if failed {
@sys.exit(1)
}
}