File size: 5,187 Bytes
78d2150
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
#include "../../unity/unity.h"

#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>

// The function under test is static in this translation unit and thus directly callable:
// static bool mode_changed (int dir_fd, char const *file, char const *file_full_name,
//                           mode_t old_mode, mode_t new_mode);

// Access the existing file-scope static 'force_silent' defined in the program source.
extern bool force_silent; // The variable is defined above in the same translation unit.

// Helpers
static mode_t get_mode(const char *path) {
    struct stat st;
    TEST_ASSERT_EQUAL_INT_MESSAGE(0, stat(path, &st), "stat on temp file failed");
    return st.st_mode;
}

static void make_temp_path(char *buf, size_t buflen) {
    // Create a unique template in /tmp
    snprintf(buf, buflen, "/tmp/chmod_mode_changed_%ld_XXXXXX", (long)getpid());
}

static void create_temp_file(char *path_buf, size_t buflen) {
    make_temp_path(path_buf, buflen);
    int fd = mkstemp(path_buf);
    TEST_ASSERT_MESSAGE(fd >= 0, "mkstemp failed");
    // Close immediately; we only need the file to exist for stat/chmod
    close(fd);
}

static void remove_temp_file(const char *path) {
    // Best-effort cleanup
    unlink(path);
}

void setUp(void) {
    /* Setup code here, or leave empty */
}

void tearDown(void) {
    /* Cleanup code here, or leave empty */
}

// 1) No special bits, no change => expect false
void test_mode_changed_no_special_no_change(void) {
    char path[256];
    create_temp_file(path, sizeof(path));

    mode_t old_mode = get_mode(path);
    mode_t new_mode = old_mode; // No change, and no special bits added

    bool changed = mode_changed(AT_FDCWD, path, path, old_mode, new_mode);
    TEST_ASSERT_FALSE(changed);

    remove_temp_file(path);
}

// 2) No special bits, with a permission bit change => expect true
void test_mode_changed_no_special_with_change(void) {
    char path[256];
    create_temp_file(path, sizeof(path));

    mode_t old_mode = get_mode(path);
    // Flip owner execute bit (does not include suid/sgid/sticky)
    mode_t new_mode = old_mode ^ S_IXUSR;

    bool changed = mode_changed(AT_FDCWD, path, path, old_mode, new_mode);
    TEST_ASSERT_TRUE(changed);

    remove_temp_file(path);
}

// 3) Special bits present; fstatat fails (nonexistent file) => expect false
void test_mode_changed_special_fstatat_failure(void) {
    // Suppress error diagnostics from the function for this test
    bool prev_silent = force_silent;
    force_silent = true;

    const char *missing = "/this/path/does/not/exist/___definitely___";
    mode_t old_mode = 0;
    // Set a special bit to trigger the fstatat path
    mode_t new_mode = old_mode | S_ISUID;

    bool changed = mode_changed(AT_FDCWD, missing, missing, old_mode, new_mode);
    TEST_ASSERT_FALSE(changed);

    force_silent = prev_silent;
}

// 4) Special bits present; fstatat succeeds; no actual change to file => expect false
void test_mode_changed_special_no_actual_change(void) {
    char path[256];
    create_temp_file(path, sizeof(path));

    mode_t old_mode = get_mode(path);
    // Trigger special-bit path; function will use fstatat to get actual (unchanged) mode
    mode_t new_mode = old_mode | S_ISUID;

    bool changed = mode_changed(AT_FDCWD, path, path, old_mode, new_mode);
    TEST_ASSERT_FALSE(changed);

    remove_temp_file(path);
}

// 5) Special bits present; fstatat succeeds; actual mode changed via chmod => expect true
void test_mode_changed_special_actual_change(void) {
    char path[256];
    create_temp_file(path, sizeof(path));

    mode_t old_mode = get_mode(path);
    // Change a permission bit on the actual file (mask to permissions only for chmod)
    mode_t changed_perm = (old_mode ^ S_IXUSR) & 07777;
    int rc = chmod(path, changed_perm);
    TEST_ASSERT_EQUAL_INT_MESSAGE(0, rc, "chmod to alter file mode failed");

    // Trigger special-bit path; function will pick up the actual new mode from stat
    mode_t new_mode_param = old_mode | S_ISUID;

    bool changed = mode_changed(AT_FDCWD, path, path, old_mode, new_mode_param);
    TEST_ASSERT_TRUE(changed);

    // Restore original perms (best effort)
    chmod(path, old_mode & 07777);
    remove_temp_file(path);
}

// 6) No special bits, difference only in file type bits should be ignored => expect false
void test_mode_changed_ignores_file_type_bits(void) {
    // Craft synthetic modes: permission bits equal, file type bits different.
    mode_t old_mode = S_IFREG | 0644;
    mode_t new_mode = S_IFDIR | 0644; // Different type, same perms; no special bits

    bool changed = mode_changed(AT_FDCWD, "unused", "unused", old_mode, new_mode);
    TEST_ASSERT_FALSE(changed);
}

int main(void) {
    UNITY_BEGIN();

    RUN_TEST(test_mode_changed_no_special_no_change);
    RUN_TEST(test_mode_changed_no_special_with_change);
    RUN_TEST(test_mode_changed_special_fstatat_failure);
    RUN_TEST(test_mode_changed_special_no_actual_change);
    RUN_TEST(test_mode_changed_special_actual_change);
    RUN_TEST(test_mode_changed_ignores_file_type_bits);

    return UNITY_END();
}