Verified Commit 3af1f011 authored by Karel Koci's avatar Karel Koci 🤘 Committed by Karel Koci
Browse files

WIP: Implement move and copy in C

parent 73bc26bf
Pipeline #72707 failed with stages
in 45 seconds
......@@ -36,12 +36,127 @@ static THREAD_LOCAL char *err_path = NULL;
static bool preserve_error(const char *path) {
stderrno = errno;
if (err_path)
free(err_path);
free(err_path);
err_path = strdup(path);
return false;
}
static bool copy_path_internal(const char *source, const char *target);
static bool copy_file(const char *source, struct stat *st, const char *target) {
int src_fd = open(source, O_RDONLY);
int fd = open(target, O_WRONLY | O_CREAT | O_EXCL, S_IWUSR);
char buf[BUFSIZ];
while (true) {
size_t read = read(src_fd, buf, BUFSIZ);
switch (read) {
case 0:
break;
case -1:
return preserve_error(source);
}
if (write(fd, buf, read) == -1)
return preserve_error(target);
}
close(src_fd);
if (fchmod(fd, st->st_mode) == -1)
WARN("Failed to set permissions for file: %s: %s", target, strerror(errno));
if (fchown(fd, st->st_uid, st->st_gid) == -1)
WARN("Failed to set ownership for file: %s: %s", target, strerror(errno));
close(fd);
}
static bool copy_link(const char *source, struct stat *st, const char *target) {
char link_target[st->st_size + 1];
assert(readlink(source, &link_target, st->st_size) == st->st_size); // TODO possibly better error handling?
if (symlink(link_target, target) == -1)
return preserve_error(target);
if (lchown(target, st->st_uid, st->st_gid) == -1)
WARN("Failed to set ownership for symlink: %s: %s", target, strerror(errno));
return true;
}
static bool copy_directory(const char *source, struct stat *st, const char *target) {
// TODO create target directory first
if (mkdir(target, st->st_mode) == -1)
return preserve_error(target);
if (chown(target, st->st_uid, st->st_gid) == -1)
WARN("Failed to set ownership for directory: %s: %s", target, strerror(errno));
if ((DIR *dir = opendir(source)) == NULL)
return preserve_error(path);
struct dirent *ent;
while ((ent = readdir(dir))) {
if (is_dot_dotdot(ent->d_name))
continue;
if (ent->d_type == DT_DIR) {
if (!remove_recursive(aprintf("%s/%s", path, ent->d_name)))
return false;
} else {
if (unlinkat(dirfd(dir), ent->d_name, 0) != 0)
return preserve_error(aprintf("%s/%s", path, ent->d_name));
}
}
closedir(dir);
// TODO
}
static bool copy_path_internal(const char *source, const char *target) {
struct stat st;
if (lstat(source, &st) == -1) {
// TODO error
}
switch (st.st_mode & S_IFMT) {
case S_IFREG:
return copy_file(source, &st, target);
case S_IFLNK:
return copy_link(source, &st, target);
case S_IFDIR:
return copy_directory(source, &st, target);
case S_IFBLK:
case S_IFCHR:
mknod(target, st.st_mode, st.st_rdev);
chown(target, st.st_uid, st.st_gid);
return true;
case S_IFIFO:
WARN("copy_path: FIFO (named pipe) is not supported.");
return true;
case S_IFSOCK:
WARN("copy_path: UNIX domain socket is not supported.");
return true;
default:
DIE("copy_path: unknown node type: %d", st.st_mode & S_IFMT);
}
}
bool copy_path(const char *source, const char *target) {
// Unconditionally remove target, that makes it easier for us
remove_recursive(target);
// TODO possibly merge and update instead of remove and copy. That can be
// cleaner solution for running programs.
last_operation = "Copy";
return copy_path_internal(source, target);
}
bool move_path(const char *source, const char *target) {
last_operation = "Move";
if (rename(source, target) == -1) {
switch (errno) {
case EFAULT:
return copy_path(source, target) && remove_recursive(source);
case EISDIR:
case ENOTDIR:
return remove_recursive(target) && move_path(source, target);
default:
return preserve_error(source);
}
}
return true;
}
// Matches . and .. file names (used to ignore current and upper directory entry)
static bool is_dot_dotdot(const char *name) {
return name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'));
......
......@@ -21,6 +21,14 @@
#include <stdbool.h>
#include <lua.h>
// Copy given path to given target. It copies mode, ownership and timestamps.
// If target exists then it is overwritten.
bool copy_path(const char *source, const char *target) __attribute__((nonnull));
// Move given path to given target. It preserves mode, ownership and timestamps.
// If target exists then it is overwritten.
bool move_path(const char *source, const char *target) __attribute__((nonnull));
// Make sure that path does not exist and if so remove it recursively
// path: path to be recursively removed
// Returns true on success otherwise false. On error you can call path_utils_error
......
......@@ -44,6 +44,51 @@ static void tmp_link(const char *root, const char *path, const char *target) {
ck_assert(!symlink(target, aprintf("%s/%s", root, path)));
}
START_TEST(path_move_file) {
char *path = tmpdir_template("path_move_file");
int file = mkstemp(path);
close(file); // only create file (no content required
char *new_path = aprintf("%s.new", path);
ck_assert(path_exists(path));
ck_assert(!path_exists(new_path));
move_path(path, new_path);
ck_assert(!path_exists(path));
ck_assert(path_exists(new_path));
ck_assert(remove_recursive(new_path));
free(path);
}
END_TEST
START_TEST(path_move_dir) {
char *path = tmpdir_template("path_move_dir");
ck_assert(mkdtemp(path));
char *link = aprintf("%s/some_link", path);
ck_assert(!symlink("/dev/null", link));
char *new_path = aprintf("%s.new", path);
char *new_link = aprintf("%s.new/some_link", link);
ck_assert(path_exists(path));
ck_assert(!path_exists(new_path));
ck_assert(!path_exists(new_link));
move_path(path, new_path);
ck_assert(!path_exists(path));
ck_assert(!path_exists(link));
ck_assert(path_exists(new_link));
ck_assert(remove_recursive(new_path));
free(path);
}
END_TEST
START_TEST(remove_recursive_file) {
char *path = tmpdir_template("remove_recursive_file");
int file = mkstemp(path);
......@@ -226,6 +271,7 @@ END_TEST
Suite *gen_test_suite(void) {
Suite *result = suite_create("path_utils");
TCase *tcases = tcase_create("tcase");
tcase_add_test(tcases, path_move_file);
tcase_add_test(tcases, remove_recursive_file);
tcase_add_test(tcases, remove_recursive_link);
tcase_add_test(tcases, remove_recursive_dir);
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment