coreutils / tests /chmod /tests_for_process_files.c
AryaWu's picture
Upload folder using huggingface_hub
78d2150 verified
#include "../../unity/unity.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <limits.h>
/* The program includes the definitions we need (process_files, globals, and
modechange.h declarations) before including this test file. */
/* Helper: create a unique temporary directory. Returns malloc'ed path. */
static char *make_temp_dir(void)
{
char *tmpl = malloc(64);
TEST_ASSERT_NOT_NULL_MESSAGE(tmpl, "malloc failed for tempdir");
snprintf(tmpl, 64, "/tmp/chmod_pf_%ld_XXXXXX", (long)getpid());
if (!mkdtemp(tmpl)) {
free(tmpl);
TEST_FAIL_MESSAGE("mkdtemp failed");
}
return tmpl; /* Caller must free and rmdir */
}
/* Helper: join directory and name into a newly-allocated path */
static char *join_path(const char *dir, const char *name)
{
size_t dl = strlen(dir), nl = strlen(name);
size_t need = dl + 1 + nl + 1;
char *p = malloc(need);
TEST_ASSERT_NOT_NULL_MESSAGE(p, "malloc failed in join_path");
memcpy(p, dir, dl);
p[dl] = '/';
memcpy(p + dl + 1, name, nl);
p[dl + 1 + nl] = '\0';
return p;
}
/* Helper: create an empty file with specified mode. Returns malloc'ed path. */
static char *create_file_with_mode(const char *dir, const char *name, mode_t mode)
{
char *p = join_path(dir, name);
int fd = open(p, O_WRONLY | O_CREAT | O_TRUNC, mode);
if (fd < 0) {
free(p);
TEST_FAIL_MESSAGE("open/create file failed");
}
close(fd);
return p;
}
/* Helper: create a directory with specified mode. Returns malloc'ed path. */
static char *create_dir_with_mode(const char *parent, const char *name, mode_t mode)
{
char *p = join_path(parent, name);
if (mkdir(p, mode) != 0) {
free(p);
TEST_FAIL_MESSAGE("mkdir failed");
}
return p;
}
/* Helper: create symlink. Returns malloc'ed link path. */
static char *create_symlink(const char *dir, const char *linkname, const char *target_relative)
{
char *linkpath = join_path(dir, linkname);
if (symlink(target_relative, linkpath) != 0) {
free(linkpath);
TEST_FAIL_MESSAGE("symlink failed");
}
return linkpath;
}
/* Remember the process umask to restore after each test */
static mode_t saved_process_umask;
/* Unity setup/teardown */
void setUp(void) {
/* Silence error diagnostics during failure-path tests to keep output clean. */
force_silent = true;
diagnose_surprises = false;
verbosity = V_off;
root_dev_ino = NULL;
recurse = false;
dereference = -1; /* default semantics */
umask_value = 0; /* don't mask off requested bits in mode_adjust */
/* Compile a default change: add user execute bit. */
change = mode_compile("u+x", 0);
TEST_ASSERT_NOT_NULL_MESSAGE(change, "mode_compile returned NULL");
/* Ensure predictable file creation permissions in tests. */
saved_process_umask = umask(0);
}
void tearDown(void) {
/* Restore the process umask. */
umask(saved_process_umask);
}
/* Compute FTS flags for use with xfts_open */
static int test_fts_flags(void)
{
return FTS_CWDFD | FTS_PHYSICAL | FTS_NOCHDIR;
}
/* Test 1: Regular file should be processed successfully and return true. */
void test_process_files_regular_file_success(void)
{
char *td = make_temp_dir();
char *file = create_file_with_mode(td, "file.txt", 0600);
char *files[] = { file, NULL };
int flags = test_fts_flags();
bool ok = process_files(files, flags);
TEST_ASSERT_TRUE_MESSAGE(ok, "process_files should succeed for a regular file");
/* Verify that the file now has user execute bit set (from change "u+x"). */
struct stat st;
TEST_ASSERT_EQUAL_INT_MESSAGE(0, stat(file, &st), "stat failed on created file");
TEST_ASSERT_TRUE_MESSAGE((st.st_mode & S_IXUSR) != 0, "u+x not applied to regular file");
/* Cleanup */
unlink(file);
rmdir(td);
free(file);
free(td);
}
/* Test 2: Nonexistent path should cause process_files to return false. */
void test_process_files_nonexistent_returns_false(void)
{
char *td = make_temp_dir();
char *missing = join_path(td, "no_such_file_or_dir");
char *files[] = { missing, NULL };
int flags = test_fts_flags();
bool ok = process_files(files, flags);
TEST_ASSERT_FALSE_MESSAGE(ok, "process_files should fail for a nonexistent path");
/* Cleanup */
free(missing);
rmdir(td);
free(td);
}
/* Test 3: Mixed inputs (one missing, one valid) should return false overall. */
void test_process_files_mixed_inputs_accumulate_failure(void)
{
char *td = make_temp_dir();
char *missing = join_path(td, "nope");
char *file = create_file_with_mode(td, "ok.txt", 0600);
char *files[] = { missing, file, NULL };
int flags = test_fts_flags();
bool ok = process_files(files, flags);
TEST_ASSERT_FALSE_MESSAGE(ok, "process_files should return false if any input fails");
/* Cleanup */
unlink(file);
free(file);
free(missing);
rmdir(td);
free(td);
}
/* Test 4: Recursive directory traversal should change modes in nested entries and return true. */
void test_process_files_recursive_directory_changes_nested(void)
{
/* Use a change that visibly affects both directories and files. */
change = mode_compile("o+x", 0);
TEST_ASSERT_NOT_NULL_MESSAGE(change, "mode_compile returned NULL for o+x");
recurse = true;
char *td = make_temp_dir();
char *subdir = create_dir_with_mode(td, "d", 0700); /* o+x is initially off */
char *nested = create_file_with_mode(subdir, "f.txt", 0600); /* o+x off */
char *files[] = { subdir, NULL };
int flags = test_fts_flags();
bool ok = process_files(files, flags);
TEST_ASSERT_TRUE_MESSAGE(ok, "process_files should succeed with recursion");
/* Verify directory has o+x set */
struct stat st;
TEST_ASSERT_EQUAL_INT_MESSAGE(0, stat(subdir, &st), "stat failed on directory");
TEST_ASSERT_TRUE_MESSAGE((st.st_mode & S_IXOTH) != 0, "o+x not applied to directory");
/* Verify nested file has o+x set */
TEST_ASSERT_EQUAL_INT_MESSAGE(0, stat(nested, &st), "stat failed on nested file");
TEST_ASSERT_TRUE_MESSAGE((st.st_mode & S_IXOTH) != 0, "o+x not applied to nested file");
/* Cleanup */
unlink(nested);
rmdir(subdir);
rmdir(td);
free(nested);
free(subdir);
free(td);
}
/* Optional: Test symlink handling on top-level with default dereference (-1)
to ensure success. This also exercises symlink path where fchmodat may act
on referent for top-level entries. */
void test_process_files_symlink_top_level_success(void)
{
char *td = make_temp_dir();
char *target = create_file_with_mode(td, "t.txt", 0600);
/* Link points to target in the same directory (relative path). */
char *linkpath = create_symlink(td, "l", "t.txt");
char *files[] = { linkpath, NULL };
int flags = test_fts_flags();
bool ok = process_files(files, flags);
TEST_ASSERT_TRUE_MESSAGE(ok, "process_files should succeed for a top-level symlink");
/* Verify referent got user execute bit set (from default 'u+x'). */
struct stat st;
TEST_ASSERT_EQUAL_INT_MESSAGE(0, stat(target, &st), "stat failed for symlink target");
TEST_ASSERT_TRUE_MESSAGE((st.st_mode & S_IXUSR) != 0, "u+x not applied to symlink target");
/* Cleanup */
unlink(linkpath);
unlink(target);
rmdir(td);
free(linkpath);
free(target);
free(td);
}
int main(void)
{
UNITY_BEGIN();
RUN_TEST(test_process_files_regular_file_success);
RUN_TEST(test_process_files_nonexistent_returns_false);
RUN_TEST(test_process_files_mixed_inputs_accumulate_failure);
RUN_TEST(test_process_files_recursive_directory_changes_nested);
RUN_TEST(test_process_files_symlink_top_level_success);
return UNITY_END();
}