commit: 54b0cd7abcba5c5a4502a7c44f84cd0e32dd94e4
parent 7f45e61983d77cadaaebadcfdfa2b924b9cfdf70
Author: Haelwenn (lanodan) Monnier <contact@hacktivis.me>
Date:   Tue, 26 Mar 2024 10:09:46 +0100
cmd/rmdir: new
Diffstat:
6 files changed, 182 insertions(+), 2 deletions(-)
diff --git a/cmd/rm.c b/cmd/rm.c
@@ -192,6 +192,7 @@ do_unlinkat(int fd, char *name, char *acc_path)
 
 	return err;
 }
+
 void
 usage()
 {
diff --git a/cmd/rmdir.1 b/cmd/rmdir.1
@@ -0,0 +1,41 @@
+.\" utils-std: Collection of commonly available Unix tools
+.\" Copyright 2017 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me>
+.\" SPDX-License-Identifier: MPL-2.0
+.Dd 2024-03-26
+.Dt RMDIR 1
+.Os
+.Sh NAME
+.Nm rmdir
+.Nd remove directories
+.Sh SYNOPSIS
+.Nm
+.Op Fl pv
+.Ar directory...
+.Sh DESCRIPTION
+The
+.Nm
+utility removes each given
+.Ar directory .
+.Sh OPTIONS
+.Bl -tag -width aa
+.It Fl p
+Remove all parents directories present in the
+.Ar directory
+argument, but not beyond.
+This is effectively the opposite of
+.Cm mkdir
+.Fl p
+.Ar directory
+.It Fl v
+Verbose, print each action done
+.El
+.Sh EXIT STATUS
+.Ex -std
+.Sh SEE ALSO
+.Xr rm 1
+.Sh STANDARDS
+.Nm
+should be compliant with
+.St -p1003.1-2008
+.Sh AUTHORS
+.An Haelwenn (lanodan) Monnier Aq Mt contact+utils@hacktivis.me
diff --git a/cmd/rmdir.c b/cmd/rmdir.c
@@ -0,0 +1,98 @@
+// utils-std: Collection of commonly available Unix tools
+// SPDX-FileCopyrightText: 2017 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me>
+// SPDX-License-Identifier: MPL-2.0
+
+#define _POSIX_C_SOURCE 200809L
+#include <errno.h>
+#include <stdbool.h>
+#include <stdio.h>  // fprintf
+#include <stdlib.h> // abort
+#include <string.h> // strerror, strrchr
+#include <unistd.h> // getopt, rmdir
+
+void
+usage()
+{
+	fprintf(stderr, "Usage: rmdir [-pv] directory...\n");
+}
+
+int
+main(int argc, char *argv[])
+{
+	bool parents = false, verbose = false;
+
+	int c = -1;
+	while((c = getopt(argc, argv, ":pv")) != -1)
+	{
+		switch(c)
+		{
+		case 'p':
+			parents = true;
+			break;
+		case 'v':
+			verbose = true;
+			break;
+		case ':':
+			fprintf(stderr, "rmdir: Error: Missing operand for option: '-%c'\n", optopt);
+			usage();
+			return 1;
+		case '?':
+			fprintf(stderr, "rmdir: Error: Unrecognised option: '-%c'\n", optopt);
+			usage();
+			return 1;
+		default:
+			abort();
+		}
+	}
+
+	argc -= optind;
+	argv += optind;
+
+	if(argc == 0)
+	{
+		fprintf(stderr, "rmdir: missing operand\n");
+		usage();
+		return 1;
+	}
+
+	int err = 0;
+
+	for(int i = 0; i < argc; i++)
+	{
+		errno = 0;
+
+		char *path = argv[i];
+		if(rmdir(path) < 0)
+		{
+			fprintf(stderr, "rmdir: Failed removing '%s': %s\n", path, strerror(errno));
+			err = 1;
+			continue;
+		}
+		if(verbose) fprintf(stderr, "rmdir: Removed '%s'\n", path);
+
+		if(!parents) continue;
+
+		while(true)
+		{
+			char *sep = strrchr(path, '/');
+			if(sep == NULL) break;
+
+			*sep = 0;
+
+			if(*path == 0) break;
+
+			errno = 0;
+			if(rmdir(path) < 0)
+			{
+				if(errno == ENOTDIR) break;
+
+				fprintf(stderr, "rmdir: Failed removing '%s': %s\n", path, strerror(errno));
+				err = 1;
+				break;
+			}
+			if(verbose) fprintf(stderr, "rmdir: Removed '%s'\n", path);
+		}
+	}
+
+	return err;
+}
diff --git a/coreutils.txt b/coreutils.txt
@@ -65,7 +65,7 @@ pwd: Done
 readlink: ?
 realpath: Done
 rm: Todo
-rmdir: No
+rmdir: Done
 runcon: No. (SELinux-specific util, wtf)
 seq: Done
 sha1sum: No
diff --git a/lsb_commands.txt b/lsb_commands.txt
@@ -103,7 +103,7 @@ pwd: Done
 remove_initd: ?
 renice: Maybe
 rm: Todo
-rmdir: No
+rmdir: Done
 sed: out of scope
 sendmail: out of scope
 seq: Done
diff --git a/test-cmd/rmdir.t b/test-cmd/rmdir.t
@@ -0,0 +1,40 @@
+#!/usr/bin/env cram
+# SPDX-FileCopyrightText: 2017 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me>
+# SPDX-License-Identifier: MPL-2.0
+
+  $ export PATH="$TESTDIR/../cmd:$PATH"
+
+  $ test "$(command -v rmdir)" = "$TESTDIR/../cmd/rmdir"
+
+  $ mkdir -p no_p/bar
+  $ rmdir no_p/bar
+  $ test -d no_p
+
+  $ mkdir -p p/bar
+  $ rmdir -p p/bar
+  $ test ! -e p
+
+  $ mkdir -p v_no_p/bar
+  $ rmdir -v v_no_p/bar
+  rmdir: Removed 'v_no_p/bar'
+  $ test -d v_no_p
+
+  $ mkdir -p v_p/bar
+  $ rmdir -pv v_p/bar
+  rmdir: Removed 'v_p/bar'
+  rmdir: Removed 'v_p'
+  $ test ! -e v_p
+
+  $ touch file
+  $ rmdir file
+  rmdir: Failed removing 'file': Not a directory
+  [1]
+  $ rmdir -p file
+  rmdir: Failed removing 'file': Not a directory
+  [1]
+  $ rmdir -v file
+  rmdir: Failed removing 'file': Not a directory
+  [1]
+  $ rmdir -pv file
+  rmdir: Failed removing 'file': Not a directory
+  [1]