coreutils / tests /mv /tests_for_do_move.c
AryaWu's picture
Upload folder using huggingface_hub
78d2150 verified
#include "../../unity/unity.h"
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <dirent.h>
#include <errno.h>
#include <limits.h>
/* Helpers: filesystem utilities for tests */
static char *path_join(const char *a, const char *b)
{
size_t la = strlen(a);
size_t lb = strlen(b);
bool need_slash = (la > 0 && a[la-1] != '/');
size_t len = la + (need_slash ? 1 : 0) + lb + 1;
char *res = (char *)malloc(len);
if (!res) return NULL;
strcpy(res, a);
if (need_slash) strcat(res, "/");
strcat(res, b);
return res;
}
static char *make_temp_dir(void)
{
char tmpl[] = "/tmp/mv_do_move_test_XXXXXX";
char *buf = strdup(tmpl);
if (!buf) return NULL;
if (!mkdtemp(buf)) {
free(buf);
return NULL;
}
return buf; /* caller must free with rm_rf or free if empty */
}
static int write_file(const char *path, const char *content)
{
int fd = open(path, O_CREAT | O_WRONLY | O_TRUNC, 0600);
if (fd < 0) return -1;
ssize_t len = (content ? (ssize_t)strlen(content) : 0);
ssize_t w = 0;
if (len > 0) {
w = write(fd, content, (size_t)len);
if (w != len) {
int e = errno;
close(fd);
errno = e;
return -1;
}
}
if (close(fd) < 0) return -1;
return 0;
}
static int read_file(const char *path, char *buf, size_t bufsz, ssize_t *out_len)
{
int fd = open(path, O_RDONLY);
if (fd < 0) return -1;
ssize_t r = read(fd, buf, bufsz);
int e = errno;
close(fd);
if (r < 0) { errno = e; return -1; }
if (out_len) *out_len = r;
return 0;
}
static int ensure_dir(const char *path, mode_t mode)
{
if (mkdir(path, mode) == 0) return 0;
if (errno == EEXIST) return 0;
return -1;
}
static bool path_exists(const char *path)
{
struct stat st;
return stat(path, &st) == 0;
}
static bool is_dir(const char *path)
{
struct stat st;
if (stat(path, &st) != 0) return false;
return S_ISDIR(st.st_mode);
}
/* Recursive remove for cleanup */
static int rm_rf(const char *path)
{
struct stat st;
if (lstat(path, &st) != 0) {
if (errno == ENOENT) return 0;
return -1;
}
if (S_ISDIR(st.st_mode)) {
DIR *d = opendir(path);
if (!d) return -1;
struct dirent *de;
while ((de = readdir(d)) != NULL) {
if (strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0)
continue;
char *child = path_join(path, de->d_name);
if (!child) { closedir(d); return -1; }
if (rm_rf(child) != 0) {
free(child);
closedir(d);
return -1;
}
free(child);
}
closedir(d);
if (rmdir(path) != 0) return -1;
} else {
if (unlink(path) != 0) return -1;
}
return 0;
}
/* For content checks */
static void assert_file_has_content(const char *path, const char *expected)
{
char buf[4096];
ssize_t n = -1;
TEST_ASSERT_TRUE_MESSAGE(path_exists(path), "Expected file to exist");
TEST_ASSERT_EQUAL_INT_MESSAGE(0, read_file(path, buf, sizeof buf, &n), "Failed to read file");
size_t exp_len = strlen(expected);
TEST_ASSERT_EQUAL_size_t_MESSAGE(exp_len, (size_t)n, "File length mismatch");
TEST_ASSERT_EQUAL_INT_MESSAGE(0, memcmp(buf, expected, (size_t)n), "File content mismatch");
}
/* Unity fixtures */
void setUp(void) { /* empty */ }
void tearDown(void) { /* empty */ }
/* Access do_move and cp_option_init provided in mv.c translation unit */
extern bool do_move(char const *source, char const *dest,
int dest_dirfd, char const *dest_relname,
const struct cp_options *x);
/* cp_option_init is static in mv.c, but since this test file is included into
the same translation unit, we can declare it here to call it. */
static void cp_option_init(struct cp_options *x);
static void test_do_move_rename_file_same_dir(void)
{
char *tmp = make_temp_dir();
TEST_ASSERT_NOT_NULL(tmp);
char *src = path_join(tmp, "a.txt");
char *dst = path_join(tmp, "b.txt");
TEST_ASSERT_NOT_NULL(src);
TEST_ASSERT_NOT_NULL(dst);
TEST_ASSERT_EQUAL_INT(0, write_file(src, "hello world"));
struct cp_options opt;
cp_option_init(&opt);
bool ok = do_move(src, dst, AT_FDCWD, dst, &opt);
TEST_ASSERT_TRUE_MESSAGE(ok, "do_move should succeed for simple rename");
TEST_ASSERT_FALSE_MESSAGE(path_exists(src), "Source should be gone after rename");
assert_file_has_content(dst, "hello world");
free(src);
free(dst);
TEST_ASSERT_EQUAL_INT(0, rm_rf(tmp));
free(tmp);
}
static void test_do_move_rename_directory_into_dir(void)
{
char *tmp = make_temp_dir();
TEST_ASSERT_NOT_NULL(tmp);
char *srcdir = path_join(tmp, "srcdir");
char *inner = path_join(srcdir, "file");
char *destroot = path_join(tmp, "destroot");
char *destpath = path_join(destroot, "srcdir");
TEST_ASSERT_NOT_NULL(srcdir && inner && destroot && destpath);
TEST_ASSERT_EQUAL_INT(0, ensure_dir(srcdir, 0700));
TEST_ASSERT_EQUAL_INT(0, write_file(inner, "data"));
TEST_ASSERT_EQUAL_INT(0, ensure_dir(destroot, 0700));
struct cp_options opt;
cp_option_init(&opt);
bool ok = do_move(srcdir, destpath, AT_FDCWD, destpath, &opt);
TEST_ASSERT_TRUE_MESSAGE(ok, "Moving a directory into another directory should succeed");
TEST_ASSERT_FALSE_MESSAGE(path_exists(srcdir), "Source directory should be removed");
TEST_ASSERT_TRUE_MESSAGE(is_dir(destpath), "Destination directory should exist");
char *moved_inner = path_join(destpath, "file");
TEST_ASSERT_NOT_NULL(moved_inner);
assert_file_has_content(moved_inner, "data");
free(moved_inner);
free(srcdir);
free(inner);
free(destroot);
free(destpath);
TEST_ASSERT_EQUAL_INT(0, rm_rf(tmp));
free(tmp);
}
static void test_do_move_copy_into_self_directory_fails(void)
{
char *tmp = make_temp_dir();
TEST_ASSERT_NOT_NULL(tmp);
char *parent = path_join(tmp, "parent");
char *child = path_join(parent, "child");
TEST_ASSERT_NOT_NULL(parent && child);
TEST_ASSERT_EQUAL_INT(0, ensure_dir(parent, 0700));
TEST_ASSERT_EQUAL_INT(0, ensure_dir(child, 0700));
/* Try to move parent into its subdirectory: should fail */
struct cp_options opt;
cp_option_init(&opt);
bool ok = do_move(parent, child, AT_FDCWD, child, &opt);
TEST_ASSERT_FALSE_MESSAGE(ok, "Moving a directory into its own subdirectory must fail");
/* Ensure original 'parent' still exists and we did not create a recursive loop */
TEST_ASSERT_TRUE_MESSAGE(is_dir(parent), "Parent directory should still exist");
/* Ensure child still exists and does not contain another 'parent' (avoid recursion) */
char *grand = path_join(child, "parent");
TEST_ASSERT_NOT_NULL(grand);
TEST_ASSERT_FALSE_MESSAGE(path_exists(grand), "Should not create nested parent inside child");
free(grand);
free(parent);
free(child);
TEST_ASSERT_EQUAL_INT(0, rm_rf(tmp));
free(tmp);
}
static void test_do_move_rename_fails_copy_then_rm_fails_due_to_perms(void)
{
char *tmp = make_temp_dir();
TEST_ASSERT_NOT_NULL(tmp);
char *srcdir = path_join(tmp, "srcdir");
char *srcfile = path_join(srcdir, "file.txt");
char *dstfile = path_join(tmp, "dest.txt");
TEST_ASSERT_NOT_NULL(srcdir && srcfile && dstfile);
TEST_ASSERT_EQUAL_INT(0, ensure_dir(srcdir, 0700));
TEST_ASSERT_EQUAL_INT(0, write_file(srcfile, "perm-test"));
/* Remove write permission on source directory to block rename and later removal */
TEST_ASSERT_EQUAL_INT(0, chmod(srcdir, 0555));
struct cp_options opt;
cp_option_init(&opt);
bool ok = do_move(srcfile, dstfile, AT_FDCWD, dstfile, &opt);
/* We expect failure since rm() should not be able to remove source due to permissions. */
TEST_ASSERT_FALSE_MESSAGE(ok, "do_move should fail when it cannot remove the source after copying");
/* Source should still exist (cannot remove due to directory perms) */
TEST_ASSERT_TRUE_MESSAGE(path_exists(srcfile), "Source file should still exist");
/* Destination may or may not exist depending on copy() behavior, but if it does, ensure content */
if (path_exists(dstfile)) {
assert_file_has_content(dstfile, "perm-test");
}
/* Restore permissions to allow cleanup */
TEST_ASSERT_EQUAL_INT(0, chmod(srcdir, 0700));
free(srcdir);
free(srcfile);
free(dstfile);
TEST_ASSERT_EQUAL_INT(0, rm_rf(tmp));
free(tmp);
}
static void test_do_move_with_dest_dirfd_and_relname(void)
{
char *tmp = make_temp_dir();
TEST_ASSERT_NOT_NULL(tmp);
char *src = path_join(tmp, "a");
char *dst_full = path_join(tmp, "rel_b");
TEST_ASSERT_NOT_NULL(src && dst_full);
TEST_ASSERT_EQUAL_INT(0, write_file(src, "dirfd-test"));
int dfd = open(tmp, O_RDONLY | O_DIRECTORY);
TEST_ASSERT_TRUE_MESSAGE(dfd >= 0, "Failed to open directory fd");
struct cp_options opt;
cp_option_init(&opt);
/* Use dirfd with relative destination name */
bool ok = do_move(src, dst_full, dfd, "rel_b", &opt);
TEST_ASSERT_TRUE_MESSAGE(ok, "do_move should succeed using dirfd/relname destination");
TEST_ASSERT_FALSE_MESSAGE(path_exists(src), "Source should be gone");
assert_file_has_content(dst_full, "dirfd-test");
close(dfd);
free(src);
free(dst_full);
TEST_ASSERT_EQUAL_INT(0, rm_rf(tmp));
free(tmp);
}
int main(void)
{
UNITY_BEGIN();
RUN_TEST(test_do_move_rename_file_same_dir);
RUN_TEST(test_do_move_rename_directory_into_dir);
RUN_TEST(test_do_move_copy_into_self_directory_fails);
RUN_TEST(test_do_move_rename_fails_copy_then_rm_fails_due_to_perms);
RUN_TEST(test_do_move_with_dest_dirfd_and_relname);
return UNITY_END();
}