1 /* Copyright (c) 2016-2018 Dovecot authors, see the included COPYING file */
2 
3 #include "test-lib.h"
4 #include "path-util.h"
5 #include "unlink-directory.h"
6 #include "str.h"
7 
8 #include <unistd.h>
9 #include <fcntl.h>
10 #include <stdlib.h>
11 #include <sys/stat.h>
12 
13 #define TEMP_DIRNAME ".test-path-util"
14 
15 static const char *tmpdir;
16 static const char *cwd;
17 static const char *link1;
18 static const char *link2;
19 static const char *link3;
20 static const char *link4;
21 
test_local_path(void)22 static void test_local_path(void)
23 {
24 	const char *expected = t_strconcat(cwd, "/README.md", NULL);
25 	const char *npath = NULL, *error = NULL;
26 	test_assert(t_normpath_to("README.md", cwd, &npath, &error) == 0);
27 	test_assert_strcmp(npath, expected);
28 }
29 
test_absolute_path_no_change(void)30 static void test_absolute_path_no_change(void)
31 {
32 	const char *npath = NULL, *error = NULL;
33 	test_assert(t_normpath_to("/", "/", &npath, &error) == 0);
34 	test_assert_strcmp(npath, "/");
35 
36 	test_assert(t_normpath_to(cwd, cwd, &npath, &error) == 0);
37 	test_assert_strcmp(npath, cwd);
38 }
39 
path_height(const char * p)40 static int path_height(const char *p)
41 {
42 	int n;
43 	for (n = 0; *p != '\0'; ++p)
44 		n += *p == '/';
45 	return n;
46 }
47 
test_travel_to_root(void)48 static void test_travel_to_root(void)
49 {
50 	int l = path_height(cwd);
51 	const char *npath = cwd;
52 	for (npath = cwd; l != 0; l--) {
53 		const char *error;
54 		test_assert_idx(t_normpath_to("../", npath, &npath, &error) == 0, l);
55 	}
56 	test_assert_strcmp(npath, "/");
57 }
58 
test_extra_slashes(void)59 static void test_extra_slashes(void)
60 {
61 	const char *npath = NULL, *error = NULL;
62 	test_assert(t_normpath_to(".", cwd, &npath, &error) == 0);
63 	test_assert_strcmp(npath, cwd);
64 
65 	test_assert(t_normpath_to("./", cwd, &npath, &error) == 0);
66 	test_assert_strcmp(npath, cwd);
67 
68 	test_assert(t_normpath_to(".////", cwd, &npath, &error) == 0);
69 	test_assert_strcmp(npath, cwd);
70 }
71 
test_nonexistent_path(void)72 static void test_nonexistent_path(void)
73 {
74 	const char *npath = NULL, *error = NULL;
75 	const char *expected = t_strconcat(cwd, "/nonexistent", NULL);
76 	test_assert(t_normpath_to("nonexistent", cwd, &npath, &error) == 0);
77 	test_assert_strcmp(npath, expected);
78 	test_assert(t_realpath_to("nonexistent", cwd, &npath, &error) == -1);
79 	test_assert(error != NULL);
80 }
81 
test_relative_dotdot(void)82 static void test_relative_dotdot(void)
83 {
84 	const char *rel_path = "../"TEMP_DIRNAME;
85 	const char *npath = NULL, *error = NULL;
86 	test_assert(t_normpath_to(rel_path, tmpdir, &npath, &error) == 0);
87 	test_assert_strcmp(npath, tmpdir);
88 
89 	test_assert(t_normpath_to("..", tmpdir, &npath, &error) == 0);
90 	test_assert_strcmp(npath, cwd);
91 
92 	test_assert(t_normpath_to("../", tmpdir, &npath, &error) == 0);
93 	test_assert_strcmp(npath, cwd);
94 
95 	test_assert(t_normpath_to("../.", tmpdir, &npath, &error) == 0);
96 	test_assert_strcmp(npath, cwd);
97 }
98 
test_link1(void)99 static void test_link1(void)
100 {
101 	const char *old_dir, *npath = NULL, *error = NULL;
102 	test_assert(t_realpath_to(link1, "/", &npath, &error) == 0);
103 	test_assert_strcmp(npath, tmpdir);
104 
105 	/* .../link1/link1/child */
106 	test_assert(t_realpath_to(t_strconcat(link1, "/link1/child", NULL),
107 				  "/", &npath, &error) == 0);
108 	test_assert_strcmp(npath, t_strconcat(tmpdir, "/child", NULL));
109 
110 	/* relative link1/link1/child */
111 	if (t_get_working_dir(&old_dir, &error) < 0)
112 		i_fatal("t_get_working_dir() failed: %s", error);
113 	if (chdir(tmpdir) < 0)
114 		i_fatal("chdir(%s) failed: %m", tmpdir);
115 	test_assert(t_realpath(t_strconcat("link1", "/link1/child", NULL),
116 			       &npath, &error) == 0);
117 	if (chdir(old_dir) < 0)
118 		i_fatal("chdir(%s) failed: %m", old_dir);
119 }
120 
test_link4(void)121 static void test_link4(void)
122 {
123 	const char *npath = NULL, *error = NULL;
124 
125 	test_assert(t_realpath_to(t_strconcat(link1, "/link4/child", NULL),
126 				  "/", &npath, &error) == 0);
127 	test_assert_strcmp(npath, t_strconcat(tmpdir, "/child", NULL));
128 }
129 
test_link_loop(void)130 static void test_link_loop(void)
131 {
132 	const char *npath = NULL, *error = NULL;
133 	errno = 0;
134 	test_assert(t_realpath_to(link2, "/", &npath, &error) == -1);
135 	test_assert(errno == ELOOP);
136 	test_assert(error != NULL);
137 }
138 
test_abspath_vs_normpath(void)139 static void test_abspath_vs_normpath(void)
140 {
141 	const char *abs = t_abspath_to("../../bin", "/usr/lib/");
142 	test_assert_strcmp(abs, "/usr/lib//../../bin");
143 
144 	const char *norm = NULL, *error = NULL;
145 	test_assert(t_normpath_to("../../bin", "/usr///lib/", &norm, &error) == 0);
146 	test_assert_strcmp(norm, "/bin");
147 }
148 
create_links(const char * tmpdir)149 static void create_links(const char *tmpdir)
150 {
151 	link1 = t_strconcat(tmpdir, "/link1", NULL);
152 	if (symlink(tmpdir, link1) < 0)
153 		i_fatal("symlink(%s, %s) failed: %m", tmpdir, link1);
154 
155 	const char *link1_child = t_strconcat(link1, "/child", NULL);
156 	int fd = creat(link1_child, 0600);
157 	if (fd == -1)
158 		i_fatal("creat(%s) failed: %m", link1_child);
159 	i_close_fd(&fd);
160 
161 	/* link2 and link3 point to each other to create a loop */
162 	link2 = t_strconcat(tmpdir, "/link2", NULL);
163 	link3 = t_strconcat(tmpdir, "/link3", NULL);
164 	if (symlink(link3, link2) < 0)
165 		i_fatal("symlink(%s, %s) failed: %m", link3, link2);
166 	if (symlink(link2, link3) < 0)
167 		i_fatal("symlink(%s, %s) failed: %m", link2, link3);
168 
169 	/* link4 points to link1 */
170 	link4 = t_strconcat(tmpdir, "/link4", NULL);
171 	if (symlink("link1", link4) < 0)
172 		i_fatal("symlink(link1, %s) failed: %m", link4);
173 }
174 
test_link_alloc(void)175 static void test_link_alloc(void)
176 {
177 #define COMPONENT_COMPONENT "/component-component"
178 	const char *o_tmpdir;
179 
180 	/* idea here is to make sure component-component
181 	   would optimally hit to the nearest_power value.
182 
183 	   it has to be big enough to cause requirement for
184 	   allocation in t_realpath. */
185 	string_t *basedir = t_str_new(256);
186 	str_append(basedir, cwd);
187 	str_append(basedir, "/"TEMP_DIRNAME);
188 	size_t len = nearest_power(I_MAX(127, str_len(basedir) + strlen(COMPONENT_COMPONENT) + 1)) -
189 			strlen(COMPONENT_COMPONENT);
190 
191 	while(str_len(basedir) < len) {
192 		str_append(basedir, COMPONENT_COMPONENT);
193 		(void)mkdir(str_c(basedir), 0700);
194 	}
195 	o_tmpdir = tmpdir;
196 	tmpdir = str_c(basedir);
197 
198 	create_links(tmpdir);
199 
200 	test_link1();
201 	test_link_loop();
202 
203 	tmpdir = o_tmpdir;
204 }
205 
test_link_alloc2(void)206 static void test_link_alloc2(void)
207 {
208 	const char *o_tmpdir;
209 
210 	/* try enough different sized base directory lengths so the code
211 	   hits the different reallocations and tests for off-by-one errors */
212 	string_t *basedir = t_str_new(256);
213 	str_append(basedir, cwd);
214 	str_append(basedir, "/"TEMP_DIRNAME);
215 	str_append_c(basedir, '/');
216 	size_t base_len = str_len(basedir);
217 
218 	o_tmpdir = tmpdir;
219 	/* path_normalize() initially allocates 128 bytes, so we'll test paths
220 	   up to that length+1. */
221 	unsigned char buf[128+1];
222 	memset(buf, 'x', sizeof(buf));
223 	for (size_t i = 1; i <= sizeof(buf); i++) {
224 		str_truncate(basedir, base_len);
225 		str_append_data(basedir, buf, i);
226 		tmpdir = str_c(basedir);
227 		(void)mkdir(str_c(basedir), 0700);
228 
229 		create_links(tmpdir);
230 		test_link1();
231 		test_link_loop();
232 	}
233 	tmpdir = o_tmpdir;
234 }
235 
test_cleanup(void)236 static void test_cleanup(void)
237 {
238 	const char *error;
239 
240 	if (unlink_directory(tmpdir, UNLINK_DIRECTORY_FLAG_RMDIR, &error) < 0)
241 		i_error("unlink_directory() failed: %s", error);
242 }
243 
test_init(void)244 static void test_init(void)
245 {
246 	const char *error;
247 	test_assert(t_get_working_dir(&cwd, &error) == 0);
248 	tmpdir = t_strconcat(cwd, "/"TEMP_DIRNAME, NULL);
249 
250 	test_cleanup();
251 	if (mkdir(tmpdir, 0700) < 0) {
252 		i_fatal("mkdir: %m");
253 	}
254 
255 	create_links(tmpdir);
256 }
257 
test_path_util(void)258 void test_path_util(void)
259 {
260 	test_begin("test_path_util");
261 	alarm(20);
262 	test_init();
263 	test_local_path();
264 	test_absolute_path_no_change();
265 	test_travel_to_root();
266 	test_extra_slashes();
267 	test_nonexistent_path();
268 	test_relative_dotdot();
269 	test_link1();
270 	test_link4();
271 	test_link_loop();
272 	test_abspath_vs_normpath();
273 	test_link_alloc();
274 	test_link_alloc2();
275 	test_cleanup();
276 	alarm(0);
277 	test_end();
278 }
279