From: maximilian attems <max@stro.at>
Date: Mon, 4 Jul 2011 17:51:52 +0200
Subject: [PATCH] [klibc] Fix minimal mv to work across fs
Forwarded: not-needed

This is the use case in initramfs to keep data from /run to /root/run
before calling switch_root().

copy_file() doesn't yet catch EINTR, but that seems less of an issue
in initramfs.

copy_file() and much logic out of copy() is from Ulrich Dangel.
Thank you for the collaboration on this blocker for
http://bugs.debian.org/627808

While we are it require move to have mv at least 2 arguments passed.

Signed-off-by: Ulrich Dangel <uli@spamt.net>
Signed-off-by: maximilian attems <max@stro.at>
[bwh: This is no longer needed by initramfs-tools starting with version 0.121,
 so can be dropped after the stretch release]
---
 usr/utils/mv.c |  234 +++++++++++++++++++++++++++++++++++++++++++++++++++-----
 1 files changed, 216 insertions(+), 18 deletions(-)

Index: klibc-2.0~rc2/usr/utils/mv.c
===================================================================
--- klibc-2.0~rc2.orig/usr/utils/mv.c	2012-02-11 18:50:21.000000000 +0000
+++ klibc-2.0~rc2/usr/utils/mv.c	2012-02-11 19:04:54.000000000 +0000
@@ -1,3 +1,6 @@
+#include <errno.h>
+#include <dirent.h>
+#include <fcntl.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -7,10 +10,204 @@
 
 #include <linux/limits.h>
 
+#define BUFFER_SIZE 32768
+
+/* copy_file - copy a file, used on different fs mv */
+static int copy_file(const char *src, const char *dest, mode_t mode)
+{
+	char buf[BUFFER_SIZE];
+	int sfd, dfd;
+	ssize_t len;
+
+	sfd = open(src, O_RDONLY);
+	if (sfd < 0)
+		return -1;
+
+	dfd = open(dest, O_WRONLY | O_CREAT, mode);
+	if (dfd < 0) {
+		close(sfd);
+		return -1;
+	}
+
+	while ((len = read(sfd, buf, sizeof(buf))) > 0) {
+		len = write(dfd, buf, len);
+		if (len < 0) {
+			close(sfd);
+			close(dfd);
+			return -1;
+		}
+	}
+
+	close(sfd);
+	close(dfd);
+	return 0;
+}
+
+/* copy - recursively copy directories */
+static int copy(char *src, const char *dest)
+{
+	int len;
+	struct stat sb, sf;
+	char target[PATH_MAX];
+	char *p;
+
+	p = strrchr(src, '/');
+	if (p) {
+		p++;
+		/* trailing slashes case */
+		if (strlen(p) == 0) {
+			len = strlen(src) - 1;
+			p = src;
+			while (0 < len && p[len] == '/')
+				p[len--] = '\0';
+			p = strrchr(p, '/');
+			p++;
+		}
+	} else {
+		p = src;
+	}
+
+	memset(&sb, 0, sizeof(struct stat));
+	/* might not exist yet */
+	if (stat(dest, &sb) == 0) {
+		if (S_ISDIR(sb.st_mode)) {
+			len = snprintf(target, PATH_MAX, "%s/%s", dest, p);
+			if (len  >= PATH_MAX)
+				return -1;
+		} else {
+			len = snprintf(target, PATH_MAX, "%s/%s", dest, src);
+			if (len  >= PATH_MAX)
+				return -1;
+		}
+	} else {
+		len = snprintf(target, PATH_MAX, "%s", dest);
+		if (len  >= PATH_MAX)
+			return -1;
+	}
+
+	if (rename(src, target) == 0) {
+		return 0;
+	} else {
+		if (errno != EXDEV)
+			return -1;
+	}
+
+
+	/* cross fs copy */
+	memset(&sf, 0, sizeof(struct stat));
+	if (stat(src, &sf) < 0)
+		return -1;
+	if (!S_ISDIR(sf.st_mode)) {
+		len = copy_file(src, target, sf.st_mode);
+		if (len == 0)
+			return 0;
+		else
+			return -1;
+	}
+
+	DIR *dir;
+	struct dirent *d;
+	char path[PATH_MAX];
+
+	if (mkdir(target, sf.st_mode) < 0)
+		return -1;
+
+	dir = opendir(src);
+	if (!dir) {
+		/* EACCES means we can't read it.
+		 * Might be empty and removable. */
+		if (errno != EACCES)
+			return -1;
+	}
+	while ((d = readdir(dir))) {
+
+		/* Skip . and .. */
+		if (d->d_name[0] == '.' && (d->d_name[1] == '\0'
+		    || (d->d_name[1] == '.' && d->d_name[2] == '\0')))
+			continue;
+
+		/* skip to long path */
+		if (strlen(src) + 1 + strlen(d->d_name) >= PATH_MAX  - 1)
+			continue;
+
+		snprintf(path, sizeof path, "%s/%s", src, d->d_name);
+		if (len  >= sizeof path)
+			return -1;
+
+		memset(&sf, 0, sizeof(struct stat));
+		if (stat(path, &sf) < 0) {
+			closedir(dir);
+			return -1;
+		}
+
+		/* recursively copy files and directories */
+		if (copy(path, target) < 0)
+			return -1;
+	}
+	closedir(dir);
+	return 0;
+}
+
+/* nuke - rm a file or directory recursively */
+static int nuke(const char *src)
+{
+	struct stat sb;
+	DIR *dir;
+	struct dirent *d;
+	char path[PATH_MAX];
+	int len;
+
+	memset(&sb, 0, sizeof(struct stat));
+	/* gone, no work */
+	if (stat(src, &sb) < 0)
+		return 0;
+
+	if (!S_ISDIR(sb.st_mode)) {
+		if (unlink(src) == 0)
+			return 0;
+		else
+			return 1;
+	}
+
+	dir = opendir(src);
+	if (!dir) {
+		/* EACCES means we can't read it.
+		 * Might be empty and removable. */
+		if (errno != EACCES)
+			return -1;
+	}
+	while ((d = readdir(dir))) {
+
+		/* Skip . and .. */
+		if (d->d_name[0] == '.' && (d->d_name[1] == '\0'
+		    || (d->d_name[1] == '.' && d->d_name[2] == '\0')))
+			continue;
+
+		/* skip to long path */
+		if (strlen(src) + 1 + strlen(d->d_name) >= PATH_MAX  - 1)
+			continue;
+
+		len = snprintf(path, sizeof path, "%s/%s", src, d->d_name);
+		if (len  >= sizeof path)
+			return -1;
+
+		memset(&sb, 0, sizeof(struct stat));
+		if (stat(path, &sb) < 0) {
+			closedir(dir);
+			return -1;
+		}
+
+		if (nuke(path) < 0)
+			return -1;
+	}
+	closedir(dir);
+	rmdir(src);
+	return 0;
+}
+
 int main(int argc, char *argv[])
 {
 	int c, f;
-	char *p;
 	struct stat sb;
 
 	f = 0;
@@ -32,11 +229,13 @@
 
 	} while (1);
 
-	if (optind == argc) {
+	/* not enough arguments */
+	if (argc - optind < 2) {
 		fprintf(stderr, "Usage: %s [-f] source dest\n", argv[0]);
 		return 1;
 	}
 
+	/* check on many archs if destination is a directory to mv in */
 	memset(&sb, 0, sizeof(struct stat));
 	if (stat(argv[argc - 1], &sb) < 0 && argc - optind > 2) {
 		if (!(S_ISDIR(sb.st_mode))) {
@@ -47,23 +246,22 @@
 		}
 	}
 
-	for (c = optind; c < argc - 1; c++) {
-		char target[PATH_MAX];
-
-		p = strrchr(argv[c], '/');
-		p++;
-
-		if (S_ISDIR(sb.st_mode))
-			snprintf(target, PATH_MAX, "%s/%s", argv[argc - 1], p);
-		else
-			snprintf(target, PATH_MAX, "%s", argv[argc - 1]);
-
-		if (f)
-			unlink(target);
-
-		if (rename(argv[c], target) == -1)
-			perror(target);
-	}
+	/* remove destination */
+	if (f)
+		nuke(argv[argc - 1]);
+
+	/* the mv action */
+	for (c = optind; c < argc - 1; c++)
+		if (copy(argv[c], argv[argc - 1]) < 0) {
+			perror("Could not copy file");
+			return -1;
+		}
 
+	/* Only rm after sucessfull rename */
+	for (c = optind; c < argc - 1; c++)
+		if (nuke(argv[c]) < 0) {
+			perror("Could not rm file");
+			return -1;
+		}
 	return 0;
 }
