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