From 4cfeb32aa836bc315aa74cf5fb38e0330407f79d Mon Sep 17 00:00:00 2001
From: Uwe Seimet <Uwe.Seimet@seimet.de>
Date: Wed, 15 Sep 2021 17:35:48 +0200
Subject: [PATCH] Added support for copying image files

---
 src/raspberrypi/rascsi.cpp             | 63 +++++++++++++++++++++++++-
 src/raspberrypi/rascsi_interface.proto |  7 ++-
 2 files changed, 66 insertions(+), 4 deletions(-)

diff --git a/src/raspberrypi/rascsi.cpp b/src/raspberrypi/rascsi.cpp
index 1a5bd334..f7a41189 100644
--- a/src/raspberrypi/rascsi.cpp
+++ b/src/raspberrypi/rascsi.cpp
@@ -31,6 +31,7 @@
 #include "spdlog/spdlog.h"
 #include "spdlog/sinks/stdout_color_sinks.h"
 #include <spdlog/async.h>
+#include <sys/sendfile.h>
 #include <string>
 #include <sstream>
 #include <iostream>
@@ -811,11 +812,11 @@ bool RenameImage(int fd, const PbCommand& command)
 
 	string src = command.params().Get(0);
 	if (src.find('/') != string::npos) {
-		return ReturnStatus(fd, false, "The image filename '" + src + "' must not contain a path");
+		return ReturnStatus(fd, false, "The current filename '" + src + "' must not contain a path");
 	}
 	string dst = command.params().Get(1);
 	if (dst.find('/') != string::npos) {
-		return ReturnStatus(fd, false, "The image filename '" + dst + "' must not contain a path");
+		return ReturnStatus(fd, false, "The new filename '" + dst + "' must not contain a path");
 	}
 
 	src = default_image_folder + "/" + src;
@@ -835,6 +836,61 @@ bool RenameImage(int fd, const PbCommand& command)
 	return ReturnStatus(fd);
 }
 
+bool CopyImage(int fd, const PbCommand& command)
+{
+	if (command.params().size() < 2 || command.params().Get(0).empty() || command.params().Get(1).empty()) {
+		return ReturnStatus(fd, false, "Can't copy image file: Missing filename");
+	}
+
+	string src = command.params().Get(0);
+	if (src.find('/') != string::npos) {
+		return ReturnStatus(fd, false, "The current filename '" + src + "' must not contain a path");
+	}
+	string dst = command.params().Get(1);
+	if (dst.find('/') != string::npos) {
+		return ReturnStatus(fd, false, "The new filename '" + dst + "' must not contain a path");
+	}
+
+	src = default_image_folder + "/" + src;
+	dst = default_image_folder + "/" + dst;
+
+	struct stat st;
+	if (!stat(dst.c_str(), &st)) {
+		return ReturnStatus(fd, false, "Image file '" + dst + "' already exists");
+	}
+
+	int fd_src = open(src.c_str(), O_RDONLY, 0);
+	if (fd_src == -1) {
+		return ReturnStatus(fd, false, "Can't open source image file '" + src + "': " + string(strerror(errno)));
+	}
+
+	struct stat st_src;
+    if (fstat(fd_src, &st_src) == -1) {
+		return ReturnStatus(fd, false, "Can't read source image file '" + src + "': " + string(strerror(errno)));
+    }
+
+	int fd_dst = open(dst.c_str(), O_WRONLY | O_CREAT, st_src.st_mode);
+	if (fd_dst == -1) {
+		close (fd_dst);
+
+		return ReturnStatus(fd, false, "Can't open destination image file '" + dst + "': " + string(strerror(errno)));
+	}
+
+    if (sendfile(fd_dst, fd_src, 0, st_src.st_size) == -1) {
+        close(fd_dst);
+        close(fd_src);
+
+        return ReturnStatus(fd, false, "Can't copy image file '" + src + "' to '" + dst + "': " + string(strerror(errno)));
+	}
+
+    close(fd_dst);
+    close(fd_src);
+
+	LOGINFO("%s", string("Copied image file '" + src + "' to '" + dst + "'").c_str());
+
+	return ReturnStatus(fd);
+}
+
 void DetachAll()
 {
 	Device *map[devices.size()];
@@ -1293,6 +1349,9 @@ bool ProcessCmd(const int fd, const PbCommand& command)
 		case RENAME_IMAGE:
 			return RenameImage(fd, command);
 
+		case COPY_IMAGE:
+			return CopyImage(fd, command);
+
 		default:
 			// This is a device-specific command handled below
 			break;
diff --git a/src/raspberrypi/rascsi_interface.proto b/src/raspberrypi/rascsi_interface.proto
index 6aa23a82..4b16e92b 100644
--- a/src/raspberrypi/rascsi_interface.proto
+++ b/src/raspberrypi/rascsi_interface.proto
@@ -68,9 +68,12 @@ enum PbOperation {
     // Delete an image file. PbCommand.params(0) contains the filename.
     // The filename is relative to the default image folder and must not contain a slash.
     DELETE_IMAGE = 16;
-    // Rename an image file. PbCommand.params(0) contains the old filename, PbCommand.params(1) the new name.
-    // The filename is relative to the default image folder and must not contain a slash.
+    // Rename an image file. PbCommand.params(0) contains the current filename, PbCommand.params(1) the new name.
+    // The filenames are relative to the default image folder and must not contain a slash.
     RENAME_IMAGE = 17;
+    // Copy an image file. PbCommand.params(0) contains the source filename, PbCommand.params(1) the destination name.
+    // The filenames are relative to the default image folder and must not contain a slash.
+    COPY_IMAGE = 18;
 }
 
 // The properties supported by a device