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