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