1 /*
2  * Process files recursively, using an optional pattern
3  *
4  * This program is distributed under the GNU General Public License, version
5  * 2.1. A copy of this license is included with this source.
6  *
7  * Copyright (C) 2002 Magnus Holmgren
8  */
9 #ifdef HAVE_CONFIG_H
10 #include "config.h"
11 #endif
12 
13 #ifdef ENABLE_RECURSIVE
14 
15 #include <stdio.h>
16 #include <string.h>
17 #include <sys/stat.h>
18 #include <sys/types.h>
19 #include "i18n.h"
20 #include "misc.h"
21 #include "recurse.h"
22 
23 #ifdef _WIN32
24 #include <direct.h>
25 #include <windows.h>
26 #else
27 #include <errno.h>
28 #include <dirent.h>
29 #include <stdlib.h>
30 #include <unistd.h>
31 #endif
32 
33 
34 #ifdef _WIN32
35 #define PATH_SEPARATOR "\\"
36 #define PATH_SEPARATOR_CHAR '\\'
37 #else
38 #define PATH_SEPARATOR "/"
39 #define PATH_SEPARATOR_CHAR '/'
40 #endif
41 
42 
43 typedef struct directory
44 {
45 	const char* name;       /* Name of the read file (or directory) */
46 	int read_error;         /* True if an error has occured */
47 
48 	/* The following stuff is used internally */
49 #ifdef _WIN32
50 	WIN32_FIND_DATA find_data;
51 	HANDLE find_handle;
52 #else
53 	DIR* dir;
54 	struct dirent* entry;
55 #endif
56 	const char* full_path;
57 } DIRECTORY;
58 
59 
60 /**
61  * See if a path refers to a directory.
62  *
63  * \param path  path to examine.
64  * \returns  1 if path is a directory, 0 if it isn't a directory, and -1 for
65  * any errors.
66  */
is_dir(const char * path)67 static int is_dir(const char* path)
68 {
69 	struct stat stat_buf;
70 
71 	if (stat(path, &stat_buf) != 0) {
72 		return -1;
73 	}
74 
75 	/* Is this the proper way to check for a directory? */
76 	return (stat_buf.st_mode & S_IFDIR) ? 1 : 0;
77 }
78 
79 
80 /**
81  * \brief See if a string contains wildcard characters.
82  *
83  * See if a string contains wildcard characters, as used by match().
84  *
85  * \param string  text string to examine.
86  * \return  1 if the string contains pattern characters, 0 otherwise.
87  */
contains_pattern(const char * string)88 static int contains_pattern(const char* string)
89 {
90 	while (*string) {
91 		switch (*string) {
92 			case '?':
93 			case '*':
94 				return 1;
95 
96 			case '\\':
97 				/* Accept a terminating \ as a literal \ */
98 				if (string[1])
99 					string++;
100 				/* Fall through */
101 
102 			default:
103 				string++;
104 				break;
105 		}
106 	}
107 
108 	return 0;
109 }
110 
111 
112 /**
113  * \brief Compare two characters for equality.
114  *
115  * Compare two characters for equality. Placed as a separate function since
116  * case should be considered on some platforms.
117  *
118  * \param c1  first character to compare.
119  * \param c2  second character to compare.
120  * \return  1 if the characters are equal, 0 otherwise.
121  */
equal(const char c1,const char c2)122 inline static int equal(const char c1, const char c2)
123 {
124 #ifdef _WIN32
125 	return (toupper(c1) == toupper(c2)) ? 1 : 0;
126 #else
127 	return (c1 == c2) ? 1 : 0;
128 #endif
129 }
130 
131 
132 /**
133  * \brief Match a text against a pattern.
134  *
135  * Match a text against a pattern. The pattern may contain the wildcards '*'
136  * and '?'. '*' matches zero or more characters while '?' matches exactly one
137  * character. Wildcards can be escaped by preceding them with a '\'. To match
138  * a '\' character, escape it (i.e., "\\").
139  *
140  * Using '\' as an escape character makes this function unsuitable to match
141  * full pathnames on Win32 (or DOS) platforms. Matching the last part (i.e.,
142  * after the last '\' (or '/', depending on platform)) works fine though.
143  *
144  * This function is case sensitive on some platforms.
145  *
146  * \param pattern  the pattern strings will be matched against.
147  * \param text     string to match against pattern.
148  * \return  1 if the text matches the pattern and 0 otherwise.
149  */
match(const char * pattern,const char * text)150 static int match(const char* pattern, const char* text)
151 {
152 	const char* last_pattern = NULL;
153 	const char* last_text = NULL;
154 
155 	while (*text) {
156 		switch (*pattern) {
157 			case '?':
158 				/* Just accept any difference */
159 				++pattern;
160 				++text;
161 				break;
162 
163 			case '*':
164 				/* Search for the text following the '*' */
165 				last_pattern = ++pattern;
166 				last_text = text;
167 				break;
168 
169 			case '\\':
170 				/* Accept a terminating \ as a literal \ */
171 				if (pattern[1])
172 					++pattern;
173 				/* Fall through */
174 
175 			default:
176 				if (!equal(*pattern++, *text++)) {
177 					if (last_pattern != NULL) {
178 						/* Accept difference and repeat search for the text
179 						 * following the '*'
180 						 */
181 						pattern = last_pattern;
182 						text = ++last_text;
183 					}
184 					else
185 						return 0;
186 				}
187 				break;
188 		}
189 	}
190 
191 	/* Only a match if pattern is exhausted (we know text is) */
192 	return (*pattern) ? 0 : 1;
193 }
194 
195 
196 #ifdef _WIN32
197 /**
198  * \brief Display a file (or other I/O) Win32 error message.
199  *
200  * Display a file (or other I/O) error message that was caused by a
201  * Win32-specific function call. First a message is formatted
202  * and printed, followed by the error text, terminated by a line feed.
203  *
204  * \param message  message format to display.
205  * \param ...      printf-arguments used to format the message.
206  */
file_error_win(const char * message,...)207 static void file_error_win(const char *message, ...)
208 {
209 	char *error;
210 	va_list args;
211 
212 	va_start(args, message);
213 	vfprintf(stderr, message, args);
214 	va_end(args);
215 
216 	FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
217 		FORMAT_MESSAGE_IGNORE_INSERTS, NULL, GetLastError(),
218 		MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) &error, 0,
219 		NULL);
220 
221 	fprintf(stderr, error);
222 	fprintf(stderr, "\n");
223 	LocalFree(error);
224 }
225 
226 
227 /**
228  * Open the current directory for scanning.
229  *
230  * \param full_path  the directory name to dislpay in case of errors.
231  * \return  directory structure, where the name field contains the name of
232  *          the first entry in the directory. If an error occured, NULL is
233  *          returned (and a message has been printed).
234  */
open_dir(const char * full_path)235 static DIRECTORY *open_dir(const char *full_path)
236 {
237 	DIRECTORY *result;
238 
239 	result = (DIRECTORY *) calloc(1, sizeof(DIRECTORY));
240 
241 	if (result != NULL) {
242 		result->find_handle = FindFirstFile("*", &result->find_data);
243 
244 		if (result->find_handle != INVALID_HANDLE_VALUE) {
245 			result->full_path = full_path;
246 			result->name = result->find_data.cFileName;
247 			return result;
248 		}
249 
250 		/* Could get "no more files" here, but not likely, due to "." and ".." */
251 
252 		free(result);
253 		file_error_win(_("Couldn't scan directory '%s': "), full_path);
254 		return NULL;
255 	}
256 
257 	fprintf(stderr, "Out of memory\n");
258 	return NULL;
259 }
260 
261 
262 /**
263  * Get the next file or folder in the directory.
264  *
265  * \param directory  pointer returned by open_dir. If the call is successful,
266  *                   the name field is updated with the new name.
267  * \return  0 for success and -1 for failure (in which case a message has been
268  *          printed).
269  */
read_dir(DIRECTORY * directory)270 static int read_dir(DIRECTORY *directory)
271 {
272 	if (FindNextFile(directory->find_handle, &directory->find_data)) {
273 		/* Probably not needed, but... */
274 		directory->name = directory->find_data.cFileName;
275 		return 0;
276 	}
277 	else {
278 		if (GetLastError() != ERROR_NO_MORE_FILES) {
279 			file_error_win(_("Couldn't scan directory '%s': "), directory->full_path);
280 			directory->read_error = 1;
281 		}
282 	}
283 
284 	return -1;
285 }
286 
287 
288 /**
289  * \brief  Close the scanning of a directory.
290  *
291  * Close the scanning of a directory. No more calls to read_dir() can be made
292  * after this call.
293  *
294  * \param  directory  directory to stop scanning.
295  * \return  0 for success and -1 for failure (in which case a message has been
296  *          printed).
297  */
close_dir(DIRECTORY * directory)298 static int close_dir(DIRECTORY *directory)
299 {
300 	int result = -1;
301 
302 	if (directory != NULL) {
303 		if (FindClose(directory->find_handle) == 0) {
304 			/* What could cause this? Do we need to take some action here? */
305 			file_error_win(_("Couldn't close directory '%s': "), directory->full_path);
306 		}
307 		else
308 			result = 0;
309 		free(directory);
310 	}
311 
312 	return result;
313 }
314 
315 #else /* WIN32 */
316 
317 /**
318  * Open the current directory for scanning.
319  *
320  * \param full_path  the directory name to dislpay in case of errors.
321  * \return  directory structure, where the name field contains the name of
322  *          the first entry in the directory. If an error occured, NULL is
323  *          returned (and a message has been printed).
324  */
open_dir(const char * full_path)325 static DIRECTORY *open_dir(const char *full_path)
326 {
327 	DIRECTORY *result;
328 
329 	result = (DIRECTORY *) calloc(1, sizeof(DIRECTORY));
330 
331 	if (result != NULL) {
332 		result->full_path = full_path;
333 		result->dir = opendir(".");
334 
335 		if (result->dir != NULL) {
336 			result->entry = readdir(result->dir);
337 
338 			if (result->entry != NULL) {
339 				result->name = result->entry->d_name;
340 				return result;
341 			}
342 		}
343 
344 		/* Could get "no more files" here, but not likely, due to "." and ".." */
345 
346 		free(result);
347 		file_error(_("Couldn't scan directory '%s': "), result->full_path);
348 		return NULL;
349 	}
350 
351 	fprintf(stderr, _("Out of memory\n"));
352 	return NULL;
353 }
354 
355 
356 /**
357  * Get the next file or folder in the directory.
358  *
359  * \param directory  pointer returned by open_dir. If the call is successful,
360  *                   the name field is updated with the new name.
361  * \return  0 for success and -1 for failure (in which case a message has been
362  *          printed).
363  */
read_dir(DIRECTORY * directory)364 static int read_dir(DIRECTORY *directory)
365 {
366 	directory->entry = readdir(directory->dir);
367 
368 	if (directory->entry != NULL) {
369 		directory->name = directory->entry->d_name;
370 		return 0;
371 	}
372 
373 	/* I think this is the right way to check for errors... */
374 	directory->read_error = (errno != 0);
375 
376 	if (directory->read_error)
377 	        file_error(_("Couldn't scan directory '%s': "), directory->full_path);
378 
379 	return -1;
380 }
381 
382 
383 /**
384  * \brief  Close the scanning of a directory.
385  *
386  * Close the scanning of a directory. No more calls to read_dir() can be made
387  * after this call.
388  *
389  * \param  directory  directory to stop scanning.
390  * \return  0 for success and -1 for failure (in which case a message has been
391  *          printed).
392  */
close_dir(DIRECTORY * directory)393 static int close_dir(DIRECTORY *directory)
394 {
395 	int result = -1;
396 
397 	if (directory != NULL) {
398 		if (closedir(directory->dir) != 0)
399 			file_error(_("Couldn't close directory '%s': "), directory->full_path);
400 		else
401 			result = 0;
402 
403 		free(directory);
404 	}
405 
406 	return result;
407 }
408 #endif /* WIN32 */
409 
410 
411 /**
412  * \brief Process all files in a directory.
413  *
414  * Process all files in a directory. If settings->album is set, assume the
415  * files make up one album. If settings->recursive is set, process all
416  * subdirectories as well.
417  *
418  * \param current   "display name" (i.e., not neccessarily the full name) of
419  *                  the current path, used to display the name of the
420  *                  directory being processed. path will be appended to
421  *                  current and passed on recursively, if needed.
422  * \param path      name of folder to process.
423  * \param settings  settings and global variables.
424  * \return  0 if successful and -1 if an error occured (in which case a
425  *          message has been printed).
426  */
process_directory(const char * current,const char * path,SETTINGS * settings)427 static int process_directory(const char* current, const char* path, SETTINGS* settings)
428 {
429 	char* full_path;
430 	char* old_path;
431 	int result = -1;
432 
433 	old_path = getcwd(NULL, 1024);
434 
435 	if (old_path == NULL) {
436 		file_error(_("Couldn't get name of current directory: "));
437 		return result;
438 	}
439 
440 	full_path = malloc(strlen(current) + strlen(path) + 2);
441 
442 	if (full_path == NULL) {
443 		free(old_path);
444 		fprintf(stderr, _("Out of memory"));
445 		return result;
446 	}
447 
448 	strcpy(full_path, current);
449 
450 	if (strlen(full_path) > 0)
451 		strcat(full_path, PATH_SEPARATOR);
452 
453 	strcat(full_path, path);
454 
455 	if (chdir(path) == 0) {
456 		DIRECTORY* directory;
457 		FILE_LIST* file_list = NULL;
458 
459 		directory = open_dir(".");
460 
461 		if (directory != NULL) {
462 			result = 0;
463 
464 			do {
465 				int dir;
466 
467 				/* Skip "special" directories */
468 				if (!strcmp(directory->name, ".") || !strcmp(directory->name, ".."))
469 					continue;
470 
471 				dir = is_dir(directory->name);
472 
473 				if (dir > 0) {
474 					if (settings->recursive)
475 						result = process_directory(full_path, directory->name, settings);
476 				}
477 				else if (!dir) {
478 					if(match(settings->pattern, directory->name) && (add_to_list(&file_list, directory->name) < 0))
479 						result = -1;
480 				}
481 				else {
482 					file_error(_("Couldn't find '%s': "), path);
483 					result = -1;
484 				}
485 			} while ((result == 0) && (read_dir(directory) == 0));
486 
487 			if (directory->read_error)
488 				result = -1;
489 
490 			close_dir(directory);
491 		}
492 
493 		if ((result == 0) && (file_list != NULL)) {
494 			fprintf(stderr, _("\nProcessing directory '%s':\n"), full_path);
495 
496 			result = process_files(file_list, settings, full_path);
497 		}
498 
499 		free_list(file_list);
500 
501 		if ((result == 0) && (chdir(old_path) != 0)) {
502 			file_error(_("Couldn't go back to folder '%s': "), old_path);
503 			result = 0;
504 		}
505 	}
506 	else
507 		file_error(_("Couldn't go to folder '%s': "), full_path);
508 
509 	free(old_path);
510 	free(full_path);
511 	return result;
512 }
513 
514 
515 /**
516  * \brief Process an argument.
517  *
518  * Process an argument. Check for wildcard at end of path, then process the
519  * file or folder specified.
520  *
521  * \param path      path argument to process.
522  * \param settings  settings and global variables.
523  * \return  0 if successful and -1 if an error occured (in which case a
524  *          message has been printed).
525  */
process_argument(const char * path,SETTINGS * settings)526 int process_argument(const char* path, SETTINGS* settings)
527 {
528 	char* buffer = strdup(path);
529 	char* my_path;
530 	int my_path_len;
531 	int dir;
532 	int result = -1;
533 
534 	if (buffer == NULL) {
535 		fprintf(stderr, _("Out of memory\n"));
536 		return result;
537 	}
538 
539 	my_path = buffer;
540 	/* Check for wildcards */
541 	settings->pattern = last_path(my_path);
542 
543 	if (contains_pattern(settings->pattern)) {
544 		/* Strip last part of path */
545 		if (settings->pattern > my_path) {
546 			/* Not using [-1] to avoid compiler warning */
547 			settings->pattern--;
548 			*settings->pattern++ = '\0';
549 		}
550 		else
551 			my_path = "";
552 	}
553 	else
554 		settings->pattern = NULL;
555 
556 	my_path_len = strlen(my_path);
557 
558 	if (my_path_len == 0)
559 		my_path = ".";
560 	else if (my_path[my_path_len - 1] == PATH_SEPARATOR_CHAR)
561 		/* On Win32, "path" and "path\." are okay, but not "path\"... */
562 		my_path[my_path_len - 1] = '\0';
563 
564 	dir = is_dir(my_path);
565 
566 	if (dir > 0) {
567 		/* Finish off any files before processing folder */
568 		if (process_files(settings->file_list, settings, ".") == 0) {
569 			if (settings->pattern == NULL)
570 				settings->pattern = "*";
571 
572 			result = process_directory("", my_path, settings);
573 		}
574 
575 		free_list(settings->file_list);
576 		settings->file_list = NULL;
577 	}
578 	else if (dir == 0) {
579 		if (settings->pattern)
580 			/* A pattern was specified, but a file was found as "folder part" */
581 			fprintf(stderr, _("'%s' is a file, not a folder\n"), my_path);
582 		else
583 			result = add_to_list(&settings->file_list, my_path);
584 	}
585 	else
586 		file_error(_("Couldn't find '%s': "), my_path);
587 
588 	free(buffer);
589 	return result;
590 }
591 
592 #endif /* ENABLE_RECURSIVE */
593