Hi tech@,

I frequently want to move a file from one place to another and shred
the original via the rm(1) -P option.  On several occasions I have
forgotten about wanting to shred the original file when using mv(1)
instead (out of habit) and ended up losing the ability to do so easily
because mv(1) unlinks the original in the cross-filesystem case.

This patch adds a -P option to mv(1) that does the same thing rm -P
does in all the cases where it would normally just unlink the source
file.

Maybe someone else has shared my "d'oh!" moment after typing mv?
Maybe this isn't worth it and should just be dealt with using a shell
alias, but to me this feels cleaner.

Feedback most welcome as always.

Pax, -A
--
https://haqistan.net/~attila | attila@{stalphonsos.com,haqistan.net}
pgp: 0x62A729CF | C2CE 2487 03AC 4C2F 101D  09C1 4068 D5D5 62A7 29CF

diff --git a/bin/mv/mv.1 b/bin/mv/mv.1
index b269c67..571eb6a 100644
--- a/bin/mv/mv.1
+++ b/bin/mv/mv.1
@@ -41,10 +41,10 @@
 .Nd move files
 .Sh SYNOPSIS
 .Nm mv
-.Op Fl fiv
+.Op Fl fiPv
 .Ar source target
 .Nm mv
-.Op Fl fiv
+.Op Fl fiPv
 .Ar source ... directory
 .Sh DESCRIPTION
 In its first form, the
@@ -103,6 +103,16 @@ The
 option overrides any previous
 .Fl f
 options.
+.It Fl P
+Overwrite regular files before deleting them,
+in the case where the source and destination are on
+different file systems.
+Files are overwritten once with a random pattern.
+Files with multiple links will be unlinked but not overwritten,
+as the with
+.Xr rm 1
+.Fl P
+option.
 .It Fl v
 Display the source and destination after each move.
 .El
diff --git a/bin/mv/mv.c b/bin/mv/mv.c
index cf8e07b..8831925 100644
--- a/bin/mv/mv.c
+++ b/bin/mv/mv.c
@@ -49,9 +49,11 @@
 #include <pwd.h>
 #include <grp.h>
 
+#define MAXIMUM(a, b)	(((a) > (b)) ? (a) : (b))
+
 extern char *__progname;
 
-int fflg, iflg, vflg;
+int fflg, iflg, vflg, Pflg;
 int stdin_ok;
 
 extern int cpmain(int argc, char **argv);
@@ -60,6 +62,9 @@ extern int rmmain(int argc, char **argv);
 int	mvcopy(char *, char *);
 int	do_move(char *, char *);
 int	fastcopy(char *, char *, struct stat *);
+int	do_unlink(char *, struct stat *);
+int	rm_overwrite(char *, struct stat *);
+int	pass(int, off_t, char *, size_t);
 void	usage(void);
 
 int
@@ -71,7 +76,7 @@ main(int argc, char *argv[])
 	int ch;
 	char path[PATH_MAX];
 
-	while ((ch = getopt(argc, argv, "ifv")) != -1)
+	while ((ch = getopt(argc, argv, "ifvP")) != -1)
 		switch (ch) {
 		case 'i':
 			fflg = 0;
@@ -84,6 +89,9 @@ main(int argc, char *argv[])
 		case 'v':
 			vflg = 1;
 			break;
+		case 'P':
+			Pflg = 1;
+			break;
 		default:
 			usage();
 		}
@@ -244,7 +252,7 @@ do_move(char *from, char *to)
 	 *	current source file...
 	 */
 	if (!lstat(to, &sb)) {
-		if ((S_ISDIR(sb.st_mode)) ? rmdir(to) : unlink(to)) {
+		if ((S_ISDIR(sb.st_mode)) ? rmdir(to) : do_unlink(to, &sb)) {
 			warn("can't remove %s", to);
 			return (1);
 		}
@@ -299,7 +307,7 @@ fastcopy(char *from, char *to, struct stat *sbp)
 		}
 	if (nread < 0) {
 		warn("%s", from);
-err:		if (unlink(to))
+err:		if (do_unlink(to, NULL))
 			warn("%s: remove", to);
 		(void)close(from_fd);
 		(void)close(to_fd);
@@ -341,7 +349,7 @@ err:		if (unlink(to))
 		return (1);
 	}
 
-	if (unlink(from)) {
+	if (do_unlink(from, sbp)) {
 		warn("%s: remove", from);
 		return (1);
 	}
@@ -384,11 +392,99 @@ mvcopy(char *from, char *to)
 	return (0);
 }
 
+int
+do_unlink(char *file, struct stat *sbp)
+{
+	struct stat sb;
+
+	if (sbp == NULL) {
+		if (lstat(file, &sb)) {
+			warn("%s", file);
+			return (1);
+		}
+		sbp = &sb;
+	}
+	if (Pflg && !rm_overwrite(file, sbp))
+		return (1);
+	if (unlink(file)) {
+		return (1);
+	}
+	return (0);
+}
+
+int
+rm_overwrite(char *file, struct stat *sbp)
+{
+	struct stat sb, sb2;
+	struct statfs fsb;
+	size_t bsize;
+	int fd;
+	char *buf = NULL;
+
+	fd = -1;
+	if (sbp == NULL) {
+		if (lstat(file, &sb))
+			goto err;
+		sbp = &sb;
+	}
+	if (!S_ISREG(sbp->st_mode))
+		return (1);
+	if (sbp->st_nlink > 1) {
+		warnx("%s (inode %llu): not overwritten due to multiple links",
+		    file, (unsigned long long)sbp->st_ino);
+		return (0);
+	}
+	if ((fd = open(file, O_WRONLY|O_NONBLOCK|O_NOFOLLOW, 0)) == -1)
+		goto err;
+	if (fstat(fd, &sb2))
+		goto err;
+	if (sb2.st_dev != sbp->st_dev || sb2.st_ino != sbp->st_ino ||
+	    !S_ISREG(sb2.st_mode)) {
+		errno = EPERM;
+		goto err;
+	}
+	if (fstatfs(fd, &fsb) == -1)
+		goto err;
+	bsize = MAXIMUM(fsb.f_iosize, 1024U);
+	if ((buf = malloc(bsize)) == NULL)
+		err(1, "%s: malloc", file);
+
+	if (!pass(fd, sbp->st_size, buf, bsize))
+		goto err;
+	if (fsync(fd))
+		goto err;
+	close(fd);
+	free(buf);
+	if (vflg)
+		(void)fprintf(stderr, "# overwrote %s\n", file);
+	return (1);
+
+err:
+	warn("%s", file);
+	close(fd);
+	free(buf);
+	return (0);
+}
+
+int
+pass(int fd, off_t len, char *buf, size_t bsize)
+{
+	size_t wlen;
+
+	for (; len > 0; len -= wlen) {
+		wlen = len < bsize ? len : bsize;
+		arc4random_buf(buf, wlen);
+		if (write(fd, buf, wlen) != wlen)
+			return (0);
+	}
+	return (1);
+}
+
 void
 usage(void)
 {
-	(void)fprintf(stderr, "usage: %s [-fiv] source target\n", __progname);
-	(void)fprintf(stderr, "       %s [-fiv] source ... directory\n",
+	(void)fprintf(stderr, "usage: %s [-fiPv] source target\n", __progname);
+	(void)fprintf(stderr, "       %s [-fiPv] source ... directory\n",
 	    __progname);
 	exit(1);
 }

Reply via email to