1 #ifdef _WIN32
2 
3 #define RM_RETRY_COUNT	5
4 #define RM_RETRY_DELAY	10
5 
6 #ifdef __MINGW32__
7 
8 /* These security-enhanced functions are not available
9  * in MinGW, so just use the vanilla ones */
10 #define wcscpy_s(a, b, c) wcscpy((a), (c))
11 #define wcscat_s(a, b, c) wcscat((a), (c))
12 
13 #endif /* __MINGW32__ */
14 
15 static int
fs__dotordotdot(WCHAR * _tocheck)16 fs__dotordotdot(WCHAR *_tocheck)
17 {
18 	return _tocheck[0] == '.' &&
19 		(_tocheck[1] == '\0' ||
20 		 (_tocheck[1] == '.' && _tocheck[2] == '\0'));
21 }
22 
23 static int
fs_rmdir_rmdir(WCHAR * _wpath)24 fs_rmdir_rmdir(WCHAR *_wpath)
25 {
26 	unsigned retries = 1;
27 
28 	while (!RemoveDirectoryW(_wpath)) {
29 		/* Only retry when we have retries remaining, and the
30 		 * error was ERROR_DIR_NOT_EMPTY. */
31 		if (retries++ > RM_RETRY_COUNT ||
32 			ERROR_DIR_NOT_EMPTY != GetLastError())
33 			return -1;
34 
35 		/* Give whatever has a handle to a child item some time
36 		 * to release it before trying again */
37 		Sleep(RM_RETRY_DELAY * retries * retries);
38 	}
39 
40 	return 0;
41 }
42 
43 static void
fs_rmdir_helper(WCHAR * _wsource)44 fs_rmdir_helper(WCHAR *_wsource)
45 {
46 	WCHAR buffer[MAX_PATH];
47 	HANDLE find_handle;
48 	WIN32_FIND_DATAW find_data;
49 	size_t buffer_prefix_len;
50 
51 	/* Set up the buffer and capture the length */
52 	wcscpy_s(buffer, MAX_PATH, _wsource);
53 	wcscat_s(buffer, MAX_PATH, L"\\");
54 	buffer_prefix_len = wcslen(buffer);
55 
56 	/* FindFirstFile needs a wildcard to match multiple items */
57 	wcscat_s(buffer, MAX_PATH, L"*");
58 	find_handle = FindFirstFileW(buffer, &find_data);
59 	cl_assert(INVALID_HANDLE_VALUE != find_handle);
60 
61 	do {
62 		/* FindFirstFile/FindNextFile gives back . and ..
63 		 * entries at the beginning */
64 		if (fs__dotordotdot(find_data.cFileName))
65 			continue;
66 
67 		wcscpy_s(buffer + buffer_prefix_len, MAX_PATH - buffer_prefix_len, find_data.cFileName);
68 
69 		if (FILE_ATTRIBUTE_DIRECTORY & find_data.dwFileAttributes)
70 			fs_rmdir_helper(buffer);
71 		else {
72 			/* If set, the +R bit must be cleared before deleting */
73 			if (FILE_ATTRIBUTE_READONLY & find_data.dwFileAttributes)
74 				cl_assert(SetFileAttributesW(buffer, find_data.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY));
75 
76 			cl_assert(DeleteFileW(buffer));
77 		}
78 	}
79 	while (FindNextFileW(find_handle, &find_data));
80 
81 	/* Ensure that we successfully completed the enumeration */
82 	cl_assert(ERROR_NO_MORE_FILES == GetLastError());
83 
84 	/* Close the find handle */
85 	FindClose(find_handle);
86 
87 	/* Now that the directory is empty, remove it */
88 	cl_assert(0 == fs_rmdir_rmdir(_wsource));
89 }
90 
91 static int
fs_rm_wait(WCHAR * _wpath)92 fs_rm_wait(WCHAR *_wpath)
93 {
94 	unsigned retries = 1;
95 	DWORD last_error;
96 
97 	do {
98 		if (INVALID_FILE_ATTRIBUTES == GetFileAttributesW(_wpath))
99 			last_error = GetLastError();
100 		else
101 			last_error = ERROR_SUCCESS;
102 
103 		/* Is the item gone? */
104 		if (ERROR_FILE_NOT_FOUND == last_error ||
105 			ERROR_PATH_NOT_FOUND == last_error)
106 			return 0;
107 
108 		Sleep(RM_RETRY_DELAY * retries * retries);
109 	}
110 	while (retries++ <= RM_RETRY_COUNT);
111 
112 	return -1;
113 }
114 
115 static void
fs_rm(const char * _source)116 fs_rm(const char *_source)
117 {
118 	WCHAR wsource[MAX_PATH];
119 	DWORD attrs;
120 
121 	/* The input path is UTF-8. Convert it to wide characters
122 	 * for use with the Windows API */
123 	cl_assert(MultiByteToWideChar(CP_UTF8,
124 				MB_ERR_INVALID_CHARS,
125 				_source,
126 				-1, /* Indicates NULL termination */
127 				wsource,
128 				MAX_PATH));
129 
130 	/* Does the item exist? If not, we have no work to do */
131 	attrs = GetFileAttributesW(wsource);
132 
133 	if (INVALID_FILE_ATTRIBUTES == attrs)
134 		return;
135 
136 	if (FILE_ATTRIBUTE_DIRECTORY & attrs)
137 		fs_rmdir_helper(wsource);
138 	else {
139 		/* The item is a file. Strip the +R bit */
140 		if (FILE_ATTRIBUTE_READONLY & attrs)
141 			cl_assert(SetFileAttributesW(wsource, attrs & ~FILE_ATTRIBUTE_READONLY));
142 
143 		cl_assert(DeleteFileW(wsource));
144 	}
145 
146 	/* Wait for the DeleteFile or RemoveDirectory call to complete */
147 	cl_assert(0 == fs_rm_wait(wsource));
148 }
149 
150 static void
fs_copydir_helper(WCHAR * _wsource,WCHAR * _wdest)151 fs_copydir_helper(WCHAR *_wsource, WCHAR *_wdest)
152 {
153 	WCHAR buf_source[MAX_PATH], buf_dest[MAX_PATH];
154 	HANDLE find_handle;
155 	WIN32_FIND_DATAW find_data;
156 	size_t buf_source_prefix_len, buf_dest_prefix_len;
157 
158 	wcscpy_s(buf_source, MAX_PATH, _wsource);
159 	wcscat_s(buf_source, MAX_PATH, L"\\");
160 	buf_source_prefix_len = wcslen(buf_source);
161 
162 	wcscpy_s(buf_dest, MAX_PATH, _wdest);
163 	wcscat_s(buf_dest, MAX_PATH, L"\\");
164 	buf_dest_prefix_len = wcslen(buf_dest);
165 
166 	/* Get an enumerator for the items in the source. */
167 	wcscat_s(buf_source, MAX_PATH, L"*");
168 	find_handle = FindFirstFileW(buf_source, &find_data);
169 	cl_assert(INVALID_HANDLE_VALUE != find_handle);
170 
171 	/* Create the target directory. */
172 	cl_assert(CreateDirectoryW(_wdest, NULL));
173 
174 	do {
175 		/* FindFirstFile/FindNextFile gives back . and ..
176 		 * entries at the beginning */
177 		if (fs__dotordotdot(find_data.cFileName))
178 			continue;
179 
180 		wcscpy_s(buf_source + buf_source_prefix_len, MAX_PATH - buf_source_prefix_len, find_data.cFileName);
181 		wcscpy_s(buf_dest + buf_dest_prefix_len, MAX_PATH - buf_dest_prefix_len, find_data.cFileName);
182 
183 		if (FILE_ATTRIBUTE_DIRECTORY & find_data.dwFileAttributes)
184 			fs_copydir_helper(buf_source, buf_dest);
185 		else
186 			cl_assert(CopyFileW(buf_source, buf_dest, TRUE));
187 	}
188 	while (FindNextFileW(find_handle, &find_data));
189 
190 	/* Ensure that we successfully completed the enumeration */
191 	cl_assert(ERROR_NO_MORE_FILES == GetLastError());
192 
193 	/* Close the find handle */
194 	FindClose(find_handle);
195 }
196 
197 static void
fs_copy(const char * _source,const char * _dest)198 fs_copy(const char *_source, const char *_dest)
199 {
200 	WCHAR wsource[MAX_PATH], wdest[MAX_PATH];
201 	DWORD source_attrs, dest_attrs;
202 	HANDLE find_handle;
203 	WIN32_FIND_DATAW find_data;
204 
205 	/* The input paths are UTF-8. Convert them to wide characters
206 	 * for use with the Windows API. */
207 	cl_assert(MultiByteToWideChar(CP_UTF8,
208 				MB_ERR_INVALID_CHARS,
209 				_source,
210 				-1,
211 				wsource,
212 				MAX_PATH));
213 
214 	cl_assert(MultiByteToWideChar(CP_UTF8,
215 				MB_ERR_INVALID_CHARS,
216 				_dest,
217 				-1,
218 				wdest,
219 				MAX_PATH));
220 
221 	/* Check the source for existence */
222 	source_attrs = GetFileAttributesW(wsource);
223 	cl_assert(INVALID_FILE_ATTRIBUTES != source_attrs);
224 
225 	/* Check the target for existence */
226 	dest_attrs = GetFileAttributesW(wdest);
227 
228 	if (INVALID_FILE_ATTRIBUTES != dest_attrs) {
229 		/* Target exists; append last path part of source to target.
230 		 * Use FindFirstFile to parse the path */
231 		find_handle = FindFirstFileW(wsource, &find_data);
232 		cl_assert(INVALID_HANDLE_VALUE != find_handle);
233 		wcscat_s(wdest, MAX_PATH, L"\\");
234 		wcscat_s(wdest, MAX_PATH, find_data.cFileName);
235 		FindClose(find_handle);
236 
237 		/* Check the new target for existence */
238 		cl_assert(INVALID_FILE_ATTRIBUTES == GetFileAttributesW(wdest));
239 	}
240 
241 	if (FILE_ATTRIBUTE_DIRECTORY & source_attrs)
242 		fs_copydir_helper(wsource, wdest);
243 	else
244 		cl_assert(CopyFileW(wsource, wdest, TRUE));
245 }
246 
247 void
cl_fs_cleanup(void)248 cl_fs_cleanup(void)
249 {
250 	fs_rm(fixture_path(_clar_path, "*"));
251 }
252 
253 #else
254 
255 #include <errno.h>
256 #include <string.h>
257 
258 static int
shell_out(char * const argv[])259 shell_out(char * const argv[])
260 {
261 	int status, piderr;
262 	pid_t pid;
263 
264 	pid = fork();
265 
266 	if (pid < 0) {
267 		fprintf(stderr,
268 			"System error: `fork()` call failed (%d) - %s\n",
269 			errno, strerror(errno));
270 		exit(-1);
271 	}
272 
273 	if (pid == 0) {
274 		execv(argv[0], argv);
275 	}
276 
277 	do {
278 		piderr = waitpid(pid, &status, WUNTRACED);
279 	} while (piderr < 0 && (errno == EAGAIN || errno == EINTR));
280 
281 	return WEXITSTATUS(status);
282 }
283 
284 static void
fs_copy(const char * _source,const char * dest)285 fs_copy(const char *_source, const char *dest)
286 {
287 	char *argv[5];
288 	char *source;
289 	size_t source_len;
290 
291 	source = strdup(_source);
292 	source_len = strlen(source);
293 
294 	if (source[source_len - 1] == '/')
295 		source[source_len - 1] = 0;
296 
297 	argv[0] = "/bin/cp";
298 	argv[1] = "-R";
299 	argv[2] = source;
300 	argv[3] = (char *)dest;
301 	argv[4] = NULL;
302 
303 	cl_must_pass_(
304 		shell_out(argv),
305 		"Failed to copy test fixtures to sandbox"
306 	);
307 
308 	free(source);
309 }
310 
311 static void
fs_rm(const char * source)312 fs_rm(const char *source)
313 {
314 	char *argv[4];
315 
316 	argv[0] = "/bin/rm";
317 	argv[1] = "-Rf";
318 	argv[2] = (char *)source;
319 	argv[3] = NULL;
320 
321 	cl_must_pass_(
322 		shell_out(argv),
323 		"Failed to cleanup the sandbox"
324 	);
325 }
326 
327 void
cl_fs_cleanup(void)328 cl_fs_cleanup(void)
329 {
330 	clar_unsandbox();
331 	clar_sandbox();
332 }
333 #endif
334