]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/findlib/find.c
573a37ea77748490c4cb4704dd478b6839eeac15
[bacula/bacula] / bacula / src / findlib / find.c
1 /*
2    Bacula(R) - The Network Backup Solution
3
4    Copyright (C) 2000-2015 Kern Sibbald
5
6    The original author of Bacula is Kern Sibbald, with contributions
7    from many others, a complete list can be found in the file AUTHORS.
8
9    You may use this file and others of this release according to the
10    license defined in the LICENSE file, which includes the Affero General
11    Public License, v3.0 ("AGPLv3") and some additional permissions and
12    terms pursuant to its AGPLv3 Section 7.
13
14    This notice must be preserved when any source code is 
15    conveyed and/or propagated.
16
17    Bacula(R) is a registered trademark of Kern Sibbald.
18 */
19 /*
20  * Main routine for finding files on a file system.
21  *  The heart of the work to find the files on the
22  *    system is done in find_one.c. Here we have the
23  *    higher level control as well as the matching
24  *    routines for the new syntax Options resource.
25  *
26  *  Kern E. Sibbald, MM
27  *
28  */
29
30
31 #include "bacula.h"
32 #include "find.h"
33
34 static const int dbglvl = 450;
35
36 int32_t name_max;              /* filename max length */
37 int32_t path_max;              /* path name max length */
38
39 #ifdef DEBUG
40 #undef bmalloc
41 #define bmalloc(x) sm_malloc(__FILE__, __LINE__, x)
42 #endif
43 static int our_callback(JCR *jcr, FF_PKT *ff, bool top_level);
44
45 static const int fnmode = 0;
46
47 /*
48  * Initialize the find files "global" variables
49  */
50 FF_PKT *init_find_files()
51 {
52   FF_PKT *ff;
53
54   ff = (FF_PKT *)bmalloc(sizeof(FF_PKT));
55   memset(ff, 0, sizeof(FF_PKT));
56
57   ff->sys_fname = get_pool_memory(PM_FNAME);
58
59    /* Get system path and filename maximum lengths */
60    path_max = pathconf(".", _PC_PATH_MAX);
61    if (path_max < 2048) {
62       path_max = 2048;
63    }
64
65    name_max = pathconf(".", _PC_NAME_MAX);
66    if (name_max < 2048) {
67       name_max = 2048;
68    }
69    path_max++;                        /* add for EOS */
70    name_max++;                        /* add for EOS */
71
72   Dmsg1(dbglvl, "init_find_files ff=%p\n", ff);
73   return ff;
74 }
75
76 /*
77  * Set find_files options. For the moment, we only
78  * provide for full/incremental saves, and setting
79  * of save_time. For additional options, see above
80  */
81 void
82 set_find_options(FF_PKT *ff, int incremental, time_t save_time)
83 {
84   Dmsg0(dbglvl, "Enter set_find_options()\n");
85   ff->incremental = incremental;
86   ff->save_time = save_time;
87   Dmsg0(dbglvl, "Leave set_find_options()\n");
88 }
89
90 void
91 set_find_changed_function(FF_PKT *ff, bool check_fct(JCR *jcr, FF_PKT *ff))
92 {
93    Dmsg0(dbglvl, "Enter set_find_changed_function()\n");
94    ff->check_fct = check_fct;
95 }
96
97 void
98 set_find_snapshot_function(FF_PKT *ff, 
99                            bool convert_path(JCR *jcr, FF_PKT *ff, dlist *filelist, dlistString *node))
100 {
101    ff->snapshot_convert_fct = convert_path;
102 }
103
104 /*
105  * Call this subroutine with a callback subroutine as the first
106  * argument and a packet as the second argument, this packet
107  * will be passed back to the callback subroutine as the last
108  * argument.
109  *
110  */
111 int
112 find_files(JCR *jcr, FF_PKT *ff, int file_save(JCR *jcr, FF_PKT *ff_pkt, bool top_level),
113            int plugin_save(JCR *jcr, FF_PKT *ff_pkt, bool top_level))
114 {
115    ff->file_save = file_save;
116    ff->plugin_save = plugin_save;
117
118    /* This is the new way */
119    findFILESET *fileset = ff->fileset;
120    if (fileset) {
121       int i, j;
122       /* TODO: We probably need be move the initialization in the fileset loop,
123        * at this place flags options are "concatenated" accross Include {} blocks
124        * (not only Options{} blocks inside a Include{})
125        */
126       ff->flags = 0;
127       for (i=0; i<fileset->include_list.size(); i++) {
128          findINCEXE *incexe = (findINCEXE *)fileset->include_list.get(i);
129          fileset->incexe = incexe;
130
131          /* Here, we reset some values between two different Include{} */
132          strcpy(ff->VerifyOpts, "V");
133          strcpy(ff->AccurateOpts, "Cmcs");  /* mtime+ctime+size by default */
134          strcpy(ff->BaseJobOpts, "Jspug5"); /* size+perm+user+group+chk  */
135          ff->plugin = NULL;
136          ff->opt_plugin = false;
137
138          /*
139           * By setting all options, we in effect OR the global options
140           *   which is what we want.
141           */
142          for (j=0; j<incexe->opts_list.size(); j++) {
143             findFOPTS *fo = (findFOPTS *)incexe->opts_list.get(j);
144             ff->flags |= fo->flags;
145             ff->Compress_algo = fo->Compress_algo;
146             ff->Compress_level = fo->Compress_level;
147             ff->strip_path = fo->strip_path;
148             ff->fstypes = fo->fstype;
149             ff->drivetypes = fo->drivetype;
150             if (fo->plugin != NULL) {
151                ff->plugin = fo->plugin; /* TODO: generate a plugin event ? */
152                ff->opt_plugin = true;
153             }
154             bstrncat(ff->VerifyOpts, fo->VerifyOpts, sizeof(ff->VerifyOpts)); /* TODO: Concat or replace? */
155             if (fo->AccurateOpts[0]) {
156                bstrncpy(ff->AccurateOpts, fo->AccurateOpts, sizeof(ff->AccurateOpts));
157             }
158             if (fo->BaseJobOpts[0]) {
159                bstrncpy(ff->BaseJobOpts, fo->BaseJobOpts, sizeof(ff->BaseJobOpts));
160             }
161          }
162          Dmsg4(50, "Verify=<%s> Accurate=<%s> BaseJob=<%s> flags=<%d>\n",
163                ff->VerifyOpts, ff->AccurateOpts, ff->BaseJobOpts, ff->flags);
164          dlistString *node;
165          foreach_dlist(node, &incexe->name_list) {
166             char *fname = node->c_str();
167             Dmsg1(dbglvl, "F %s\n", fname);
168
169             ff->top_fname = fname;
170             /* Convert the filename if needed */
171             if (ff->snapshot_convert_fct) {
172                ff->snapshot_convert_fct(jcr, ff, &incexe->name_list, node);
173             }
174
175             if (find_one_file(jcr, ff, our_callback, ff->top_fname, (dev_t)-1, true) == 0) {
176                return 0;                  /* error return */
177             }
178
179             if (job_canceled(jcr)) {
180                return 0;
181             }
182          }
183          foreach_dlist(node, &incexe->plugin_list) {
184             char *fname = node->c_str();
185             if (!plugin_save) {
186                Jmsg(jcr, M_FATAL, 0, _("Plugin: \"%s\" not found.\n"), fname);
187                return 0;
188             }
189             Dmsg1(dbglvl, "PluginCommand: %s\n", fname);
190             ff->top_fname = fname;
191             ff->cmd_plugin = true;
192
193             /* Make sure that opt plugin is not set
194              * The current implementation doesn't allow option plugin
195              * and command plugin to run at the same time
196              */
197             ff->opt_plugin = false;
198             ff->plugin = NULL;
199
200             plugin_save(jcr, ff, true);
201             ff->cmd_plugin = false;
202             if (job_canceled(jcr)) {
203                return 0;
204             }
205          }
206       }
207    }
208    return 1;
209 }
210
211 /*
212  * Test if the currently selected directory (in ff->fname) is
213  *  explicitly in the Include list or explicitly in the Exclude
214  *  list.
215  */
216 bool is_in_fileset(FF_PKT *ff)
217 {
218    dlistString *node;
219    char *fname;
220    int i;
221    findINCEXE *incexe;
222    findFILESET *fileset = ff->fileset;
223    if (fileset) {
224       for (i=0; i<fileset->include_list.size(); i++) {
225          incexe = (findINCEXE *)fileset->include_list.get(i);
226          foreach_dlist(node, &incexe->name_list) {
227             fname = node->c_str();
228             Dmsg2(dbglvl, "Inc fname=%s ff->fname=%s\n", fname, ff->fname);
229             if (strcmp(fname, ff->fname) == 0) {
230                return true;
231             }
232          }
233       }
234       for (i=0; i<fileset->exclude_list.size(); i++) {
235          incexe = (findINCEXE *)fileset->exclude_list.get(i);
236          foreach_dlist(node, &incexe->name_list) {
237             fname = node->c_str();
238             Dmsg2(dbglvl, "Exc fname=%s ff->fname=%s\n", fname, ff->fname);
239             if (strcmp(fname, ff->fname) == 0) {
240                return true;
241             }
242          }
243       }
244    }
245    return false;
246 }
247
248
249 bool accept_file(FF_PKT *ff)
250 {
251    int i, j, k;
252    int fnm_flags;
253    findFILESET *fileset = ff->fileset;
254    findINCEXE *incexe = fileset->incexe;
255    const char *basename;
256    int (*match_func)(const char *pattern, const char *string, int flags);
257
258    Dmsg1(dbglvl, "enter accept_file: fname=%s\n", ff->fname);
259    if (ff->flags & FO_ENHANCEDWILD) {
260 //    match_func = enh_fnmatch;
261       match_func = fnmatch;
262       if ((basename = last_path_separator(ff->fname)) != NULL)
263          basename++;
264       else
265          basename = ff->fname;
266    } else {
267       match_func = fnmatch;
268       basename = ff->fname;
269    }
270
271    for (j = 0; j < incexe->opts_list.size(); j++) {
272       findFOPTS *fo = (findFOPTS *)incexe->opts_list.get(j);
273       ff->flags = fo->flags;
274       ff->Compress_algo = fo->Compress_algo;
275       ff->Compress_level = fo->Compress_level;
276       ff->fstypes = fo->fstype;
277       ff->drivetypes = fo->drivetype;
278
279       fnm_flags = (ff->flags & FO_IGNORECASE) ? FNM_CASEFOLD : 0;
280       fnm_flags |= (ff->flags & FO_ENHANCEDWILD) ? FNM_PATHNAME : 0;
281
282       if (S_ISDIR(ff->statp.st_mode)) {
283          for (k=0; k<fo->wilddir.size(); k++) {
284             if (match_func((char *)fo->wilddir.get(k), ff->fname, fnmode|fnm_flags) == 0) {
285                if (ff->flags & FO_EXCLUDE) {
286                   Dmsg2(dbglvl, "Exclude wilddir: %s file=%s\n", (char *)fo->wilddir.get(k),
287                      ff->fname);
288                   return false;       /* reject dir */
289                }
290                return true;           /* accept dir */
291             }
292          }
293       } else {
294          for (k=0; k<fo->wildfile.size(); k++) {
295             if (match_func((char *)fo->wildfile.get(k), ff->fname, fnmode|fnm_flags) == 0) {
296                if (ff->flags & FO_EXCLUDE) {
297                   Dmsg2(dbglvl, "Exclude wildfile: %s file=%s\n", (char *)fo->wildfile.get(k),
298                      ff->fname);
299                   return false;       /* reject file */
300                }
301                return true;           /* accept file */
302             }
303          }
304
305          for (k=0; k<fo->wildbase.size(); k++) {
306             if (match_func((char *)fo->wildbase.get(k), basename, fnmode|fnm_flags) == 0) {
307                if (ff->flags & FO_EXCLUDE) {
308                   Dmsg2(dbglvl, "Exclude wildbase: %s file=%s\n", (char *)fo->wildbase.get(k),
309                      basename);
310                   return false;       /* reject file */
311                }
312                return true;           /* accept file */
313             }
314          }
315       }
316       for (k=0; k<fo->wild.size(); k++) {
317          if (match_func((char *)fo->wild.get(k), ff->fname, fnmode|fnm_flags) == 0) {
318             if (ff->flags & FO_EXCLUDE) {
319                Dmsg2(dbglvl, "Exclude wild: %s file=%s\n", (char *)fo->wild.get(k),
320                   ff->fname);
321                return false;          /* reject file */
322             }
323             return true;              /* accept file */
324          }
325       }
326       if (S_ISDIR(ff->statp.st_mode)) {
327          for (k=0; k<fo->regexdir.size(); k++) {
328             const int nmatch = 30;
329             regmatch_t pmatch[nmatch];
330             if (regexec((regex_t *)fo->regexdir.get(k), ff->fname, nmatch, pmatch,  0) == 0) {
331                if (ff->flags & FO_EXCLUDE) {
332                   return false;       /* reject file */
333                }
334                return true;           /* accept file */
335             }
336          }
337       } else {
338          for (k=0; k<fo->regexfile.size(); k++) {
339             const int nmatch = 30;
340             regmatch_t pmatch[nmatch];
341             if (regexec((regex_t *)fo->regexfile.get(k), ff->fname, nmatch, pmatch,  0) == 0) {
342                if (ff->flags & FO_EXCLUDE) {
343                   return false;       /* reject file */
344                }
345                return true;           /* accept file */
346             }
347          }
348       }
349       for (k=0; k<fo->regex.size(); k++) {
350          const int nmatch = 30;
351          regmatch_t pmatch[nmatch];
352          if (regexec((regex_t *)fo->regex.get(k), ff->fname, nmatch, pmatch,  0) == 0) {
353             if (ff->flags & FO_EXCLUDE) {
354                return false;          /* reject file */
355             }
356             return true;              /* accept file */
357          }
358       }
359       /*
360        * If we have an empty Options clause with exclude, then
361        *  exclude the file
362        */
363       if (ff->flags & FO_EXCLUDE &&
364           fo->regex.size() == 0     && fo->wild.size() == 0 &&
365           fo->regexdir.size() == 0  && fo->wilddir.size() == 0 &&
366           fo->regexfile.size() == 0 && fo->wildfile.size() == 0 &&
367           fo->wildbase.size() == 0) {
368          return false;              /* reject file */
369       }
370    }
371
372    /* Now apply the Exclude { } directive */
373    for (i=0; i<fileset->exclude_list.size(); i++) {
374       findINCEXE *incexe = (findINCEXE *)fileset->exclude_list.get(i);
375       for (j=0; j<incexe->opts_list.size(); j++) {
376          findFOPTS *fo = (findFOPTS *)incexe->opts_list.get(j);
377          fnm_flags = (fo->flags & FO_IGNORECASE) ? FNM_CASEFOLD : 0;
378          for (k=0; k<fo->wild.size(); k++) {
379             if (fnmatch((char *)fo->wild.get(k), ff->fname, fnmode|fnm_flags) == 0) {
380                Dmsg1(dbglvl, "Reject wild1: %s\n", ff->fname);
381                return false;          /* reject file */
382             }
383          }
384       }
385       fnm_flags = (incexe->current_opts != NULL && incexe->current_opts->flags & FO_IGNORECASE)
386              ? FNM_CASEFOLD : 0;
387       dlistString *node;
388       foreach_dlist(node, &incexe->name_list) {
389          char *fname = node->c_str();
390          if (fnmatch(fname, ff->fname, fnmode|fnm_flags) == 0) {
391             Dmsg1(dbglvl, "Reject wild2: %s\n", ff->fname);
392             return false;          /* reject file */
393          }
394       }
395    }
396    return true;
397 }
398
399 /*
400  * The code comes here for each file examined.
401  * We filter the files, then call the user's callback if
402  *    the file is included.
403  */
404 static int our_callback(JCR *jcr, FF_PKT *ff, bool top_level)
405 {
406    if (top_level) {
407       return ff->file_save(jcr, ff, top_level);   /* accept file */
408    }
409    switch (ff->type) {
410    case FT_NOACCESS:
411    case FT_NOFOLLOW:
412    case FT_NOSTAT:
413    case FT_NOCHG:
414    case FT_ISARCH:
415    case FT_NORECURSE:
416    case FT_NOFSCHG:
417    case FT_INVALIDFS:
418    case FT_INVALIDDT:
419    case FT_NOOPEN:
420 //    return ff->file_save(jcr, ff, top_level);
421
422    /* These items can be filtered */
423    case FT_LNKSAVED:
424    case FT_REGE:
425    case FT_REG:
426    case FT_LNK:
427    case FT_DIRBEGIN:
428    case FT_DIREND:
429    case FT_RAW:
430    case FT_FIFO:
431    case FT_SPEC:
432    case FT_DIRNOCHG:
433    case FT_REPARSE:
434    case FT_JUNCTION:
435       if (accept_file(ff)) {
436          return ff->file_save(jcr, ff, top_level);
437       } else {
438          Dmsg1(dbglvl, "Skip file %s\n", ff->fname);
439          return -1;                   /* ignore this file */
440       }
441
442    default:
443       Dmsg1(000, "Unknown FT code %d\n", ff->type);
444       return 0;
445    }
446 }
447
448
449 /*
450  * Terminate find_files() and release
451  * all allocated memory
452  */
453 int
454 term_find_files(FF_PKT *ff)
455 {
456    int hard_links;
457
458    free_pool_memory(ff->sys_fname);
459    if (ff->fname_save) {
460       free_pool_memory(ff->fname_save);
461    }
462    if (ff->link_save) {
463       free_pool_memory(ff->link_save);
464    }
465    if (ff->ignoredir_fname) {
466       free_pool_memory(ff->ignoredir_fname);
467    }
468    if (ff->snap_fname) {
469       free_pool_memory(ff->snap_fname);
470    }
471    if (ff->snap_top_fname) {
472       free_pool_memory(ff->snap_top_fname);
473    }
474    if (ff->mtab_list) {
475       delete ff->mtab_list;
476    }
477    hard_links = term_find_one(ff);
478    free(ff);
479    return hard_links;
480 }