Branch data Line data Source code
1 : : #include "sute.h"
2 : : #include <sys/stat.h>
3 : : #include <unistd.h>
4 : : #include <limits.h>
5 : :
6 : : /**
7 : : * @brief Create the temporary directory used by one file access test
8 : : *
9 : : * The directory path is stored in @p tmpdir. A read-only view of the same path
10 : : * is returned through @p tmpdir_path_out for use with standard filesystem calls
11 : : *
12 : : * @param[out] tmpdir Memory descriptor that receives the directory path
13 : : * @param[out] tmpdir_path_out Receives a read-only view of the stored path
14 : : * @return SUCCESS when the directory was created and both outputs are ready,
15 : : * otherwise FAILURE
16 : : */
17 : 5 : static Return test0026_prepare_tmpdir(
18 : : memory *tmpdir,
19 : : const char **tmpdir_path_out)
20 : : {
21 : : /* Status returned by this function through provide()
22 : : Default value assumes successful completion */
23 : 5 : Return status = SUCCESS;
24 : :
25 : 5 : status = create_tmpdir(tmpdir);
26 : 5 : *tmpdir_path_out = m_text(tmpdir);
27 : :
28 : 5 : provide(status);
29 : : }
30 : :
31 : : /**
32 : : * @brief Verify that an existing relative path is reported as accessible
33 : : *
34 : : * @details
35 : : * Creates a readable temporary file, opens its parent directory, and passes
36 : : * only the file name to file_check_access(). Because the file exists and can
37 : : * be read, the function must return FILE_ACCESS_ALLOWED
38 : : */
39 : 1 : static Return test0026_1(void)
40 : : {
41 : 1 : INITTEST;
42 : :
43 : 1 : m_create(char,tmpdir,MEMORY_STRING);
44 : 1 : m_create(char,relative_path,MEMORY_STRING);
45 : 1 : const char *tmpdir_path = "";
46 : :
47 : 1 : ASSERT(SUCCESS == test0026_prepare_tmpdir(tmpdir,&tmpdir_path));
48 : 1 : ASSERT(SUCCESS == m_copy_literal(relative_path,"test0026_accessible.txt"));
49 : :
50 : 1 : char absolute_file_path[PATH_MAX] = "";
51 : 1 : FILE *file = NULL;
52 : 1 : m_create(char,absolute_path,MEMORY_STRING);
53 : 1 : const int written = snprintf(absolute_file_path,
54 : : sizeof(absolute_file_path),
55 : : "%s/%s",
56 : : tmpdir_path,
57 : 1 : m_text(relative_path));
58 : :
59 : 1 : ASSERT(written > 0 && (size_t)written < sizeof(absolute_file_path));
60 : :
61 : 1 : ASSERT(SUCCESS == m_copy_string(absolute_path,(size_t)written + 1U,absolute_file_path));
62 : 1 : ASSERT(SUCCESS == open_file_stream(absolute_path,"wb",&file));
63 : 1 : ASSERT(file != NULL);
64 : 1 : ASSERT(fclose(file) == 0);
65 : :
66 : 1 : int directory_fd = -1;
67 : :
68 : 1 : ASSERT(directory_open(tmpdir,&directory_fd) == FILE_ACCESS_ALLOWED);
69 : 1 : ASSERT(file_check_access(directory_fd,relative_path,R_OK) == FILE_ACCESS_ALLOWED);
70 : :
71 : 1 : const int close_status = close(directory_fd);
72 : 1 : const int remove_file_status = remove(absolute_file_path);
73 : 1 : call(m_del(absolute_path));
74 : 1 : const int remove_tmpdir_status = rmdir(tmpdir_path);
75 : :
76 : 1 : ASSERT(close_status == 0);
77 : 1 : ASSERT(remove_file_status == 0);
78 : 1 : ASSERT(remove_tmpdir_status == 0);
79 : :
80 : 1 : call(m_del(relative_path));
81 : 1 : call(m_del(tmpdir));
82 : :
83 : 1 : RETURN_STATUS;
84 : : }
85 : :
86 : : /**
87 : : * @brief Verify that a missing file is reported as FILE_NOT_FOUND
88 : : *
89 : : * @details
90 : : * Builds a path inside a temporary directory without creating a file at that
91 : : * location. The access check must distinguish this normal missing-file case
92 : : * from permission problems and unexpected filesystem errors
93 : : */
94 : 1 : static Return test0026_2(void)
95 : : {
96 : 1 : INITTEST;
97 : :
98 : 1 : m_create(char,tmpdir,MEMORY_STRING);
99 : 1 : m_create(char,relative_path,MEMORY_STRING);
100 : 1 : const char *tmpdir_path = "";
101 : :
102 : 1 : ASSERT(SUCCESS == test0026_prepare_tmpdir(tmpdir,&tmpdir_path));
103 : 1 : ASSERT(SUCCESS == m_copy_literal(relative_path,"test0026_missing.txt"));
104 : :
105 : 1 : int directory_fd = -1;
106 : :
107 : 1 : ASSERT(directory_open(tmpdir,&directory_fd) == FILE_ACCESS_ALLOWED);
108 : 1 : ASSERT(file_check_access(directory_fd,relative_path,R_OK) == FILE_NOT_FOUND);
109 : :
110 : 1 : const int close_status = close(directory_fd);
111 : 1 : const int remove_tmpdir_status = rmdir(tmpdir_path);
112 : :
113 : 1 : ASSERT(close_status == 0);
114 : 1 : ASSERT(remove_tmpdir_status == 0);
115 : :
116 : 1 : call(m_del(relative_path));
117 : 1 : call(m_del(tmpdir));
118 : :
119 : 1 : RETURN_STATUS;
120 : : }
121 : :
122 : : /**
123 : : * @brief Verify that an unreadable path is reported as FILE_ACCESS_DENIED
124 : : *
125 : : * @details
126 : : * Creates a file and then removes every permission from its parent directory.
127 : : * The file still exists, but the process cannot pass through the directory to
128 : : * reach it. file_check_access() must therefore return FILE_ACCESS_DENIED
129 : : * rather than FILE_NOT_FOUND
130 : : */
131 : 1 : static Return test0026_3(void)
132 : : {
133 : 1 : INITTEST;
134 : :
135 : 1 : m_create(char,tmpdir,MEMORY_STRING);
136 : 1 : m_create(char,relative_path,MEMORY_STRING);
137 : 1 : const char *tmpdir_path = "";
138 : :
139 : 1 : ASSERT(SUCCESS == test0026_prepare_tmpdir(tmpdir,&tmpdir_path));
140 : 1 : ASSERT(SUCCESS == m_copy_literal(relative_path,"test0026_locked_dir/file.txt"));
141 : :
142 : 1 : char locked_dir[PATH_MAX] = "";
143 : 1 : const int dir_written = snprintf(locked_dir,sizeof(locked_dir),"%s/%s",tmpdir_path,"test0026_locked_dir");
144 : :
145 : 1 : ASSERT(dir_written > 0 && (size_t)dir_written < sizeof(locked_dir));
146 : :
147 : 1 : ASSERT(mkdir(locked_dir,0700) == 0);
148 : :
149 : 1 : char locked_file_path[PATH_MAX] = "";
150 : 1 : FILE *file = NULL;
151 : 1 : m_create(char,locked_path,MEMORY_STRING);
152 : 1 : const int file_written = snprintf(locked_file_path,
153 : : sizeof(locked_file_path),
154 : : "%s/%s",
155 : : tmpdir_path,
156 : 1 : m_text(relative_path));
157 : :
158 : 1 : ASSERT(file_written > 0 && (size_t)file_written < sizeof(locked_file_path));
159 : :
160 : 1 : ASSERT(SUCCESS == m_copy_string(locked_path,(size_t)file_written + 1U,locked_file_path));
161 : 1 : ASSERT(SUCCESS == open_file_stream(locked_path,"wb",&file));
162 : 1 : ASSERT(file != NULL);
163 : 1 : ASSERT(fclose(file) == 0);
164 : :
165 : 1 : ASSERT(chmod(locked_dir,0000) == 0);
166 : :
167 : 1 : int directory_fd = -1;
168 : :
169 : 1 : ASSERT(directory_open(tmpdir,&directory_fd) == FILE_ACCESS_ALLOWED);
170 : 1 : ASSERT(file_check_access(directory_fd,relative_path,R_OK) == FILE_ACCESS_DENIED);
171 : :
172 : 1 : const int close_status = close(directory_fd);
173 : 1 : const int restore_status = chmod(locked_dir,0700);
174 : 1 : const int remove_file_status = remove(locked_file_path);
175 : 1 : const int remove_dir_status = rmdir(locked_dir);
176 : :
177 : 1 : call(m_del(locked_path));
178 : 1 : const int remove_tmpdir_status = rmdir(tmpdir_path);
179 : :
180 : 1 : ASSERT(close_status == 0);
181 : 1 : ASSERT(restore_status == 0);
182 : 1 : ASSERT(remove_file_status == 0);
183 : 1 : ASSERT(remove_dir_status == 0);
184 : 1 : ASSERT(remove_tmpdir_status == 0);
185 : :
186 : 1 : call(m_del(relative_path));
187 : 1 : call(m_del(tmpdir));
188 : :
189 : 1 : RETURN_STATUS;
190 : : }
191 : :
192 : : /**
193 : : * @brief Verify that an unexpected access failure is reported as FILE_ACCESS_ERROR
194 : : *
195 : : * @details
196 : : * Creates a real file and uses the test access-status hook to force
197 : : * FILE_ACCESS_ERROR for that relative path. Because this error means neither
198 : : * "missing" nor "permission denied", file_check_access() must return
199 : : * FILE_ACCESS_ERROR
200 : : */
201 : 1 : static Return test0026_4(void)
202 : : {
203 : 1 : INITTEST;
204 : :
205 : 1 : m_create(char,tmpdir,MEMORY_STRING);
206 : 1 : m_create(char,relative_path,MEMORY_STRING);
207 : 1 : const char *tmpdir_path = "";
208 : :
209 : 1 : ASSERT(SUCCESS == test0026_prepare_tmpdir(tmpdir,&tmpdir_path));
210 : 1 : ASSERT(SUCCESS == m_copy_literal(relative_path,"test0026_error.txt"));
211 : :
212 : 1 : char error_path[PATH_MAX] = "";
213 : 1 : FILE *file = NULL;
214 : 1 : m_create(char,error_target,MEMORY_STRING);
215 : 1 : const int written = snprintf(error_path,
216 : : sizeof(error_path),
217 : : "%s/%s",
218 : : tmpdir_path,
219 : 1 : m_text(relative_path));
220 : :
221 : 1 : ASSERT(written > 0 && (size_t)written < sizeof(error_path));
222 : :
223 : 1 : ASSERT(SUCCESS == m_copy_string(error_target,(size_t)written + 1U,error_path));
224 : 1 : ASSERT(SUCCESS == open_file_stream(error_target,"wb",&file));
225 : 1 : ASSERT(file != NULL);
226 : 1 : ASSERT(fclose(file) == 0);
227 : :
228 : 1 : int directory_fd = -1;
229 : :
230 : 1 : ASSERT(directory_open(tmpdir,&directory_fd) == FILE_ACCESS_ALLOWED);
231 : 1 : ASSERT(file_check_access(directory_fd,relative_path,R_OK) == FILE_ACCESS_ALLOWED);
232 : :
233 : 1 : ASSERT(SUCCESS == set_environment_variable("TESTITALL_TEST_ENV_FILE_ACCESS_SUFFIX",m_text(relative_path)));
234 : 1 : ASSERT(SUCCESS == set_environment_variable("TESTITALL_TEST_ENV_FILE_ACCESS_STATUS","FILE_ACCESS_ERROR"));
235 : 1 : ASSERT(file_check_access(directory_fd,relative_path,R_OK) == FILE_ACCESS_ERROR);
236 : 1 : ASSERT(SUCCESS == set_environment_variable("TESTITALL_TEST_ENV_FILE_ACCESS_SUFFIX",""));
237 : 1 : ASSERT(SUCCESS == set_environment_variable("TESTITALL_TEST_ENV_FILE_ACCESS_STATUS",""));
238 : :
239 : 1 : const int close_status = close(directory_fd);
240 : 1 : const int remove_file_status = remove(error_path);
241 : 1 : call(m_del(error_target));
242 : 1 : const int remove_tmpdir_status = rmdir(tmpdir_path);
243 : :
244 : 1 : ASSERT(close_status == 0);
245 : 1 : ASSERT(remove_file_status == 0);
246 : 1 : ASSERT(remove_tmpdir_status == 0);
247 : :
248 : 1 : call(m_del(relative_path));
249 : 1 : call(m_del(tmpdir));
250 : :
251 : 1 : RETURN_STATUS;
252 : : }
253 : :
254 : : /**
255 : : * @brief Verify access through a directory that can be searched but not listed
256 : : *
257 : : * Creates a readable file and changes its parent directory mode to 0111. This
258 : : * keeps permission to pass through the directory when a child name is already
259 : : * known, but removes permission to list the directory contents. Opening the
260 : : * directory and checking the known relative file name must still succeed,
261 : : * proving that this workflow does not require directory read permission
262 : : */
263 : 1 : static Return test0026_5(void)
264 : : {
265 : 1 : INITTEST;
266 : :
267 : 1 : m_create(char,tmpdir,MEMORY_STRING);
268 : 1 : m_create(char,relative_path,MEMORY_STRING);
269 : 1 : const char *tmpdir_path = "";
270 : :
271 : 1 : ASSERT(SUCCESS == test0026_prepare_tmpdir(tmpdir,&tmpdir_path));
272 : 1 : ASSERT(SUCCESS == m_copy_literal(relative_path,"test0026_5_search_only.txt"));
273 : :
274 : 1 : char absolute_file_path[PATH_MAX] = "";
275 : 1 : const int written = snprintf(absolute_file_path,
276 : : sizeof(absolute_file_path),
277 : : "%s/%s",
278 : : tmpdir_path,
279 : 1 : m_text(relative_path));
280 : :
281 : 1 : ASSERT(written > 0 && (size_t)written < sizeof(absolute_file_path));
282 : :
283 : 1 : FILE *file = fopen(absolute_file_path,"wb");
284 : :
285 : 1 : ASSERT(file != NULL);
286 : 1 : ASSERT(fclose(file) == 0);
287 : 1 : ASSERT(chmod(tmpdir_path,0111) == 0);
288 : :
289 : 1 : int directory_fd = -1;
290 : :
291 : 1 : ASSERT(directory_open(tmpdir,&directory_fd) == FILE_ACCESS_ALLOWED);
292 : 1 : ASSERT(file_check_access(directory_fd,relative_path,R_OK) == FILE_ACCESS_ALLOWED);
293 : :
294 : 1 : const int close_status = close(directory_fd);
295 : 1 : const int restore_status = chmod(tmpdir_path,0700);
296 : 1 : const int remove_file_status = remove(absolute_file_path);
297 : 1 : const int remove_tmpdir_status = rmdir(tmpdir_path);
298 : :
299 : 1 : ASSERT(close_status == 0);
300 : 1 : ASSERT(restore_status == 0);
301 : 1 : ASSERT(remove_file_status == 0);
302 : 1 : ASSERT(remove_tmpdir_status == 0);
303 : :
304 : 1 : call(m_del(tmpdir));
305 : 1 : call(m_del(relative_path));
306 : :
307 : 1 : RETURN_STATUS;
308 : : }
309 : :
310 : : /**
311 : : * @brief Test directory-relative file access checks
312 : : *
313 : : * @details
314 : : * Covers readable, missing, permission-denied, and unexpected-error results for
315 : : * paths checked relative to an opened directory. It also verifies that a
316 : : * directory can be opened once and reused to check relative file names,
317 : : * including a directory that can be searched but cannot be listed
318 : : */
319 : 1 : Return test0026(void)
320 : : {
321 : 1 : INITTEST;
322 : :
323 : 1 : TEST(test0026_1,"file_check_access(): readable path relative to a directory descriptor");
324 : 1 : TEST(test0026_2,"file_check_access(): missing path relative to a directory descriptor");
325 : 1 : TEST(test0026_3,"file_check_access(): inaccessible path relative to a directory descriptor");
326 : 1 : TEST(test0026_4,"file_check_access(): forced access-check failure");
327 : 1 : TEST(test0026_5,"file_check_access(): readable path below a search-only directory");
328 : :
329 : 1 : RETURN_STATUS;
330 : : }
|