coreutils / tests /chmod /tests_for_process_file.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 <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <limits.h>
#include <stdbool.h>
/* Globals from the program (accessible since this file is included into the TU):
- struct mode_change *change;
- mode_t umask_value;
- bool recurse;
- int dereference;
- bool force_silent;
- bool diagnose_surprises;
- enum Verbosity verbosity;
- Functions: mode_compile, xfts_open, fts_read, fts_set, fts_close, process_file
*/
static mode_t saved_umask;
/* Utility: create a temporary directory, return malloc'd path */
static char* make_temp_dir(void)
{
char tmpl[] = "/tmp/chmod_ut_XXXXXX";
char *path = malloc(sizeof(tmpl));
if (!path) return NULL;
memcpy(path, tmpl, sizeof(tmpl));
if (!mkdtemp(path))
{
free(path);
return NULL;
}
return path;
}
/* Utility: join dir and name into a newly malloc'd path */
static char* path_join(const char* dir, const char* name)
{
size_t ld = strlen(dir), ln = strlen(name);
size_t need = ld + 1 + ln + 1;
char *res = malloc(need);
if (!res) return NULL;
memcpy(res, dir, ld);
res[ld] = '/';
memcpy(res + ld + 1, name, ln);
res[ld + 1 + ln] = '\0';
return res;
}
/* Utility: create a regular file with specified mode, returns 0 on success */
static int create_file_with_mode(const char* path, mode_t mode)
{
int fd = open(path, O_CREAT | O_TRUNC | O_WRONLY, mode);
if (fd < 0) return -1;
if (close(fd) != 0) return -1;
/* Ensure exact mode (umask 0 in setUp, but call chmod just in case) */
if (chmod(path, mode) != 0) return -1;
return 0;
}
/* Utility: create symlink */
static int create_symlink_to(const char* target, const char* linkpath)
{
/* Remove pre-existing */
unlink(linkpath);
return symlink(target, linkpath);
}
/* Utility: stat mode of a path (referent) */
static mode_t get_mode_stat(const char* path)
{
struct stat st;
if (stat(path, &st) != 0) return (mode_t)~0;
return st.st_mode;
}
/* Utility: lstat mode of a path (symlink itself) */
static mode_t get_mode_lstat(const char* path)
{
struct stat st;
if (lstat(path, &st) != 0) return (mode_t)~0;
return st.st_mode;
}
/* Utility: open xfts for a single path and return first entry */
static FTSENT* open_single_fts(const char* path, FTS **out_fts, int flags)
{
char *paths[2];
paths[0] = (char*)path;
paths[1] = NULL;
FTS *fts = xfts_open(paths, flags, NULL);
if (!fts)
{
*out_fts = NULL;
return NULL;
}
FTSENT *ent = fts_read(fts);
*out_fts = fts;
return ent;
}
void setUp(void) {
/* Ensure environment messages are English for stable assertions */
setenv("LC_ALL", "C", 1);
setenv("LANG", "C", 1);
/* Make umask predictable */
saved_umask = umask(0);
umask_value = 0;
/* Defaults for globals used by process_file */
recurse = false;
dereference = -1; /* default */
force_silent = false;
diagnose_surprises = false;
verbosity = V_off;
}
void tearDown(void) {
/* Restore process umask */
umask(saved_umask);
}
/* Test 1: Regular file mode change should succeed and set execute for user */
void test_process_file_regular_add_exec(void)
{
char *dir = make_temp_dir();
TEST_ASSERT_NOT_NULL(dir);
char *file = path_join(dir, "f1");
TEST_ASSERT_NOT_NULL(file);
TEST_ASSERT_EQUAL_INT(0, create_file_with_mode(file, 0644));
/* Prepare change: add u+x */
change = mode_compile("u+x", 0);
FTS *fts = NULL;
FTSENT *ent = open_single_fts(file, &fts, FTS_PHYSICAL);
TEST_ASSERT_NOT_NULL_MESSAGE(ent, "fts_read should return an entry for the file");
TEST_ASSERT_NOT_NULL(fts);
bool ok = process_file(fts, ent);
/* Close FTS before assertions further to avoid side effects */
TEST_ASSERT_EQUAL_INT(0, fts_close(fts));
TEST_ASSERT_TRUE(ok);
mode_t m = get_mode_stat(file);
TEST_ASSERT_NOT_EQUAL((mode_t)~0, m);
TEST_ASSERT_TRUE(m & S_IXUSR);
unlink(file);
rmdir(dir);
free(file);
free(dir);
}
/* Test 2: Symlink with no-dereference should not change referent */
void test_process_file_symlink_no_deref_noop(void)
{
char *dir = make_temp_dir();
TEST_ASSERT_NOT_NULL(dir);
char *tgt = path_join(dir, "target");
char *lnk = path_join(dir, "link");
TEST_ASSERT_NOT_NULL(tgt);
TEST_ASSERT_NOT_NULL(lnk);
TEST_ASSERT_EQUAL_INT(0, create_file_with_mode(tgt, 0644));
TEST_ASSERT_EQUAL_INT(0, create_symlink_to(tgt, lnk));
mode_t before = get_mode_stat(tgt);
TEST_ASSERT_NOT_EQUAL((mode_t)~0, before);
/* change: a+x, but do not dereference symlinks */
change = mode_compile("a+x", 0);
dereference = 0; /* --no-dereference */
FTS *fts = NULL;
FTSENT *ent = open_single_fts(lnk, &fts, FTS_PHYSICAL);
TEST_ASSERT_NOT_NULL(ent);
TEST_ASSERT_NOT_NULL(fts);
bool ok = process_file(fts, ent);
TEST_ASSERT_EQUAL_INT(0, fts_close(fts));
TEST_ASSERT_TRUE(ok);
mode_t after = get_mode_stat(tgt);
TEST_ASSERT_NOT_EQUAL((mode_t)~0, after);
TEST_ASSERT_EQUAL_HEX32(before & 07777, after & 07777);
unlink(lnk);
unlink(tgt);
rmdir(dir);
free(lnk);
free(tgt);
free(dir);
}
/* Test 3: Symlink with dereference should change the referent */
void test_process_file_symlink_with_deref_changes_target(void)
{
char *dir = make_temp_dir();
TEST_ASSERT_NOT_NULL(dir);
char *tgt = path_join(dir, "target2");
char *lnk = path_join(dir, "link2");
TEST_ASSERT_NOT_NULL(tgt);
TEST_ASSERT_NOT_NULL(lnk);
TEST_ASSERT_EQUAL_INT(0, create_file_with_mode(tgt, 0644));
TEST_ASSERT_EQUAL_INT(0, create_symlink_to(tgt, lnk));
change = mode_compile("a+x", 0);
dereference = 1; /* --dereference */
verbosity = V_off;
FTS *fts = NULL;
FTSENT *ent = open_single_fts(lnk, &fts, FTS_PHYSICAL);
TEST_ASSERT_NOT_NULL(ent);
TEST_ASSERT_NOT_NULL(fts);
bool ok = process_file(fts, ent);
TEST_ASSERT_EQUAL_INT(0, fts_close(fts));
TEST_ASSERT_TRUE(ok);
mode_t m = get_mode_stat(tgt);
TEST_ASSERT_NOT_EQUAL((mode_t)~0, m);
TEST_ASSERT_TRUE(m & S_IXUSR);
unlink(lnk);
unlink(tgt);
rmdir(dir);
free(lnk);
free(tgt);
free(dir);
}
/* Test 4: No-op change when mode already has requested bits */
void test_process_file_no_change_needed(void)
{
char *dir = make_temp_dir();
TEST_ASSERT_NOT_NULL(dir);
char *file = path_join(dir, "already_exec");
TEST_ASSERT_NOT_NULL(file);
TEST_ASSERT_EQUAL_INT(0, create_file_with_mode(file, 0755));
mode_t before = get_mode_stat(file);
TEST_ASSERT_NOT_EQUAL((mode_t)~0, before);
change = mode_compile("u+x", 0);
verbosity = V_off;
FTS *fts = NULL;
FTSENT *ent = open_single_fts(file, &fts, FTS_PHYSICAL);
TEST_ASSERT_NOT_NULL(ent);
TEST_ASSERT_NOT_NULL(fts);
bool ok = process_file(fts, ent);
TEST_ASSERT_EQUAL_INT(0, fts_close(fts));
TEST_ASSERT_TRUE(ok);
mode_t after = get_mode_stat(file);
TEST_ASSERT_NOT_EQUAL((mode_t)~0, after);
TEST_ASSERT_EQUAL_HEX32(before & 07777, after & 07777);
unlink(file);
rmdir(dir);
free(file);
free(dir);
}
/* Test 5: Verbosity high prints a diagnostic for a change */
void test_process_file_verbose_outputs_message(void)
{
char *dir = make_temp_dir();
TEST_ASSERT_NOT_NULL(dir);
char *file = path_join(dir, "verbose_file");
TEST_ASSERT_NOT_NULL(file);
TEST_ASSERT_EQUAL_INT(0, create_file_with_mode(file, 0644));
change = mode_compile("u+x", 0);
verbosity = V_high;
/* Setup stdout capture */
int saved_stdout = dup(STDOUT_FILENO);
TEST_ASSERT_MESSAGE(saved_stdout >= 0, "dup stdout failed");
int pipefd[2];
TEST_ASSERT_EQUAL_INT_MESSAGE(0, pipe(pipefd), "pipe failed");
fflush(stdout);
if (dup2(pipefd[1], STDOUT_FILENO) < 0) {
/* Restore and fail */
dup2(saved_stdout, STDOUT_FILENO);
close(saved_stdout);
close(pipefd[0]); close(pipefd[1]);
TEST_FAIL_MESSAGE("dup2 failed to redirect stdout");
}
FTS *fts = NULL;
FTSENT *ent = open_single_fts(file, &fts, FTS_PHYSICAL);
TEST_ASSERT_NOT_NULL(ent);
TEST_ASSERT_NOT_NULL(fts);
/* Call target while stdout is redirected.
Do not use Unity asserts here. */
bool ok_call = process_file(fts, ent);
ignore_value(fts_close(fts)); /* Close FTS even if we don't assert now */
fflush(stdout);
/* Restore stdout */
dup2(saved_stdout, STDOUT_FILENO);
close(saved_stdout);
close(pipefd[1]);
/* Now safe to assert */
TEST_ASSERT_TRUE(ok_call);
char buf[1024];
ssize_t n = read(pipefd[0], buf, sizeof(buf) - 1);
close(pipefd[0]);
TEST_ASSERT_TRUE_MESSAGE(n > 0, "no output captured");
if (n > 0) buf[n] = '\0'; else buf[0] = '\0';
/* Expect message like: "mode of <file> changed from ..." */
TEST_ASSERT_NOT_NULL_MESSAGE(strstr(buf, "mode of"), "Verbose output missing 'mode of'");
TEST_ASSERT_NOT_NULL_MESSAGE(strstr(buf, "changed"), "Verbose output missing 'changed'");
unlink(file);
rmdir(dir);
free(file);
free(dir);
}
int main(void)
{
UNITY_BEGIN();
RUN_TEST(test_process_file_regular_add_exec);
RUN_TEST(test_process_file_symlink_no_deref_noop);
RUN_TEST(test_process_file_symlink_with_deref_changes_target);
RUN_TEST(test_process_file_no_change_needed);
RUN_TEST(test_process_file_verbose_outputs_message);
return UNITY_END();
}