aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoseph Hunkeler <jhunkeler@gmail.com>2023-11-20 00:31:20 -0500
committerJoseph Hunkeler <jhunkeler@gmail.com>2023-11-20 00:31:20 -0500
commit0340f7765a6aa78de6e6a3ea8c3878713201e94e (patch)
tree52a8fdb50706f8387013f67faad87c76c3974198
parent62edbefaffeb2f9d3a52193c2e0d54c77acbcc40 (diff)
downloadstasis-0340f7765a6aa78de6e6a3ea8c3878713201e94e.tar.gz
Add copy.c, copy.h
* copytree() * mkdirs() * copy2()
-rw-r--r--include/copy.h19
-rw-r--r--src/copy.c191
2 files changed, 210 insertions, 0 deletions
diff --git a/include/copy.h b/include/copy.h
new file mode 100644
index 0000000..9e8bc11
--- /dev/null
+++ b/include/copy.h
@@ -0,0 +1,19 @@
+#ifndef OMC_COPY_H
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <dirent.h>
+#include <errno.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include "omc.h"
+
+#define CT_OWNER 1 << 1
+#define CT_PERM 1 << 2
+
+int copytree(const char *srcdir, const char *destdir, unsigned op);
+int mkdirs(const char *_path, mode_t mode);
+int copy2(const char *src, const char *dest, unsigned op);
+
+#endif // OMC_COPY_H \ No newline at end of file
diff --git a/src/copy.c b/src/copy.c
new file mode 100644
index 0000000..fa623d2
--- /dev/null
+++ b/src/copy.c
@@ -0,0 +1,191 @@
+#include "copy.h"
+
+int copy2(const char *src, const char *dest, unsigned int op) {
+ size_t bytes_read;
+ size_t bytes_written;
+ char buf[OMC_BUFSIZ];
+ struct stat src_stat, dnamest;
+ FILE *fp1, *fp2;
+
+ if (lstat(src, &src_stat) < 0) {
+ perror(src);
+ return -1;
+ }
+
+ if (access(dest, F_OK) == 0) {
+ unlink(dest);
+ }
+
+ char dname[1024] = {0};
+ strcpy(dname, dest);
+ char *dname_endptr;
+
+ dname_endptr = strrchr(dname, '/');
+ if (dname_endptr != NULL) {
+ *dname_endptr = '\0';
+ }
+
+ stat(dname, &dnamest);
+ if (S_ISLNK(src_stat.st_mode)) {
+ char lpath[1024] = {0};
+ if (readlink(src, lpath, sizeof(lpath)) < 0) {
+ perror(src);
+ return -1;
+ }
+ if (symlink(lpath, dest) < 0) {
+ // silent
+ return -1;
+ }
+ } else if (S_ISREG(src_stat.st_mode) && src_stat.st_nlink > 2 && src_stat.st_dev == dnamest.st_dev) {
+ if (link(src, dest) < 0) {
+ perror(src);
+ return -1;
+ }
+ } else if (S_ISFIFO(src_stat.st_mode) || S_ISBLK(src_stat.st_mode) || S_ISCHR(src_stat.st_mode) || S_ISSOCK(src_stat.st_mode)) {
+ if (mknod(dest, src_stat.st_mode, src_stat.st_rdev) < 0) {
+ perror(src);
+ return -1;
+ }
+ } else if (S_ISREG(src_stat.st_mode)) {
+ fp1 = fopen(src, "rb");
+ if (!fp1) {
+ perror(src);
+ return -1;
+ }
+
+ fp2 = fopen(dest, "w+b");
+ if (!fp2) {
+ perror(dest);
+ return -1;
+ }
+
+ bytes_written = 0;
+ while ((bytes_read = fread(buf, sizeof(char), sizeof(buf), fp1)) != 0) {
+ bytes_written += fwrite(buf, sizeof(char), bytes_read, fp2);
+ }
+ fclose(fp1);
+ fclose(fp2);
+
+ if (bytes_written != (size_t) src_stat.st_size) {
+ fprintf(stderr, "%s: SHORT WRITE (expected %zu bytes, but wrote %zu bytes)\n", dest, src_stat.st_size, bytes_written);
+ return -1;
+ }
+
+ if (op & CT_OWNER && chown(dest, src_stat.st_uid, src_stat.st_gid) < 0) {
+ perror(dest);
+ }
+
+ if (op & CT_PERM && chmod(dest, src_stat.st_mode) < 0) {
+ perror(dest);
+ }
+ } else {
+ errno = EOPNOTSUPP;
+ return -1;
+ }
+ return 0;
+}
+
+int mkdirs(const char *_path, mode_t mode) {
+ int status;
+ char *token;
+ char pathbuf[1024] = {0};
+ char *path;
+ path = pathbuf;
+ strcpy(path, _path);
+ errno = 0;
+
+ char result[1024] = {0};
+ status = 0;
+ while ((token = strsep(&path, "/")) != NULL && !status) {
+ if (token[0] == '.')
+ continue;
+ strcat(result, token);
+ strcat(result, "/");
+ status = mkdir(result, mode);
+ }
+ return status;
+}
+
+int copytree(const char *srcdir, const char *destdir, unsigned int op) {
+ DIR *dir;
+ struct dirent *d;
+ struct stat st;
+ mode_t mode;
+ mode_t umask_value;
+ errno = 0;
+
+ dir = opendir(srcdir);
+ if (!dir) {
+ return -1;
+ }
+
+ umask_value = umask(0);
+ umask(umask_value);
+
+ if (lstat(srcdir, &st) < 0) {
+ perror(srcdir);
+ closedir(dir);
+ return -1;
+ }
+
+ if (op & CT_PERM) {
+ mode = st.st_mode;
+ } else {
+ mode = 0777 & ~umask_value;
+ }
+
+ // Source directory is not writable so modify the mode for the destination
+ if (mode & ~S_IWUSR) {
+ mode |= S_IWUSR;
+ }
+
+ if (access(destdir, F_OK) < 0) {
+ if (mkdirs(destdir, mode) < 0) {
+ perror(destdir);
+ closedir(dir);
+ return -1;
+ }
+ }
+
+ memset(&st, 0, sizeof(st));
+ while ((d = readdir(dir)) != NULL) {
+ char src[1024] = {0};
+ char dest[1024] = {0};
+ if (strcmp(d->d_name, ".") == 0 || strcmp(d->d_name, "..") == 0) {
+ continue;
+ }
+ strcat(src, srcdir);
+ strcat(src, "/");
+ strcat(src, d->d_name);
+
+ strcat(dest, destdir);
+ strcat(dest, "/");
+ strcat(dest, d->d_name);
+
+ if (lstat(src, &st) < 0) {
+ perror(srcdir);
+ closedir(dir);
+ return -1;
+ }
+
+ if (d->d_type == DT_DIR) {
+ if (strncmp(src, dest, strlen(src)) == 0) {
+ closedir(dir);
+ return -1;
+ }
+
+ if (access(dest, F_OK) < 0) {
+ if (mkdir(dest, mode) < 0) {
+ perror(dest);
+ closedir(dir);
+ return -1;
+ }
+ }
+
+ copytree(src, dest, op);
+ }
+ copy2(src, dest, op);
+ }
+ closedir(dir);
+ return 0;
+}