1 /*
2    Bacula(R) - The Network Backup Solution
3 
4    Copyright (C) 2000-2020 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  */
init_find_files()50 FF_PKT *init_find_files()
51 {
52   FF_PKT *ff;
53 
54   /* bmalloc returns zeroed buffer */
55   ff = (FF_PKT *)bmalloc(sizeof(FF_PKT));
56 
57    /* Get system path and filename maximum lengths */
58    path_max = pathconf(".", _PC_PATH_MAX);
59    if (path_max < 2048) {
60       path_max = 2048;
61    }
62 
63    name_max = pathconf(".", _PC_NAME_MAX);
64    if (name_max < 2048) {
65       name_max = 2048;
66    }
67    path_max++;                        /* add for EOS */
68    name_max++;                        /* add for EOS */
69 
70   Dmsg1(dbglvl, "init_find_files ff=%p\n", ff);
71   return ff;
72 }
73 
74 /*
75  * Set find_files options. For the moment, we only
76  * provide for full/incremental saves, and setting
77  * of save_time. For additional options, see above
78  */
79 void
set_find_options(FF_PKT * ff,int incremental,time_t save_time)80 set_find_options(FF_PKT *ff, int incremental, time_t save_time)
81 {
82   Dmsg0(dbglvl, "Enter set_find_options()\n");
83   ff->incremental = incremental;
84   ff->save_time = save_time;
85   Dmsg0(dbglvl, "Leave set_find_options()\n");
86 }
87 
88 void
set_find_changed_function(FF_PKT * ff,bool check_fct (JCR * jcr,FF_PKT * ff))89 set_find_changed_function(FF_PKT *ff, bool check_fct(JCR *jcr, FF_PKT *ff))
90 {
91    Dmsg0(dbglvl, "Enter set_find_changed_function()\n");
92    ff->check_fct = check_fct;
93 }
94 
95 void
set_find_snapshot_function(FF_PKT * ff,bool convert_path (JCR * jcr,FF_PKT * ff,dlist * filelist,dlistString * node))96 set_find_snapshot_function(FF_PKT *ff,
97                            bool convert_path(JCR *jcr, FF_PKT *ff, dlist *filelist, dlistString *node))
98 {
99    ff->snapshot_convert_fct = convert_path;
100 }
101 
102 /*
103  * Call this subroutine with a callback subroutine as the first
104  * argument and a packet as the second argument, this packet
105  * will be passed back to the callback subroutine as the last
106  * argument.
107  *
108  */
109 int
find_files(JCR * jcr,FF_PKT * ff,int file_save (JCR * jcr,FF_PKT * ff_pkt,bool top_level),int plugin_save (JCR * jcr,FF_PKT * ff_pkt,bool top_level))110 find_files(JCR *jcr, FF_PKT *ff, int file_save(JCR *jcr, FF_PKT *ff_pkt, bool top_level),
111            int plugin_save(JCR *jcr, FF_PKT *ff_pkt, bool top_level))
112 {
113    ff->file_save = file_save;
114    ff->plugin_save = plugin_save;
115 
116    /* This is the new way */
117    findFILESET *fileset = ff->fileset;
118    if (fileset) {
119       int i, j;
120       /* TODO: We probably need be move the initialization in the fileset loop,
121        * at this place flags options are "concatenated" accross Include {} blocks
122        * (not only Options{} blocks inside a Include{})
123        */
124       ff->flags = 0;
125       for (i=0; i<fileset->include_list.size(); i++) {
126          findINCEXE *incexe = (findINCEXE *)fileset->include_list.get(i);
127          fileset->incexe = incexe;
128 
129          /* Here, we reset some values between two different Include{} */
130          strcpy(ff->VerifyOpts, "V");
131          strcpy(ff->AccurateOpts, "Cmcs");  /* mtime+ctime+size by default */
132          strcpy(ff->BaseJobOpts, "Jspug5"); /* size+perm+user+group+chk  */
133          ff->plugin = NULL;
134          ff->opt_plugin = false;
135 
136          /*
137           * By setting all options, we in effect OR the global options
138           *   which is what we want.
139           */
140          for (j=0; j<incexe->opts_list.size(); j++) {
141             findFOPTS *fo = (findFOPTS *)incexe->opts_list.get(j);
142             /* TODO options are "simply" reset by Options block that come next
143              * For example :
144              * Options { Dedup = storage }
145              * Options { IgnoreCase = yes }
146              * at the end the "Dedup = storage" is ignored
147              * ATTN: some plugins use AddOptions() that create extra Option block
148              * Also see accept_file() below that could suffer of the same problem
149              */
150             ff->flags |= fo->flags;
151             /* If the compress option was set in the previous block, overwrite the
152              * algorithm only if defined
153              */
154             if ((ff->flags & FO_COMPRESS) && fo->Compress_algo != 0) {
155                ff->Compress_algo = fo->Compress_algo;
156                ff->Compress_level = fo->Compress_level;
157             }
158             if (fo->flags & FO_DEDUPLICATION) {
159                /* fix #2334 but see TODO above*/
160                ff->Dedup_level = fo->Dedup_level;
161             }
162             ff->strip_path = fo->strip_path;
163             ff->fstypes = fo->fstype;
164             ff->drivetypes = fo->drivetype;
165             if (fo->plugin != NULL) {
166                ff->plugin = fo->plugin; /* TODO: generate a plugin event ? */
167                ff->opt_plugin = true;
168             }
169             bstrncat(ff->VerifyOpts, fo->VerifyOpts, sizeof(ff->VerifyOpts)); /* TODO: Concat or replace? */
170             if (fo->AccurateOpts[0]) {
171                bstrncpy(ff->AccurateOpts, fo->AccurateOpts, sizeof(ff->AccurateOpts));
172             }
173             if (fo->BaseJobOpts[0]) {
174                bstrncpy(ff->BaseJobOpts, fo->BaseJobOpts, sizeof(ff->BaseJobOpts));
175             }
176          }
177          Dmsg4(50, "Verify=<%s> Accurate=<%s> BaseJob=<%s> flags=<%lld>\n",
178                ff->VerifyOpts, ff->AccurateOpts, ff->BaseJobOpts, ff->flags);
179          dlistString *node;
180          foreach_dlist(node, &incexe->name_list) {
181             POOL_MEM fname(PM_FNAME);
182             fname.strcpy(node->c_str());
183 #ifdef HAVE_WIN32
184             win32_normalize_fileset_path(fname.addr());
185 #endif
186             Dmsg1(dbglvl, "F %s\n", fname.c_str());
187             ff->top_fname = fname.c_str();
188             /* Convert the filename if needed */
189             if (ff->snapshot_convert_fct) {
190                ff->snapshot_convert_fct(jcr, ff, &incexe->name_list, node);
191             }
192 
193             if (find_one_file(jcr, ff, our_callback, fname.c_str(), ff->top_fname, (dev_t)-1, true) == 0) {
194                return 0;                  /* error return */
195             }
196 
197             if (job_canceled(jcr)) {
198                return 0;
199             }
200          }
201          foreach_dlist(node, &incexe->plugin_list) {
202             char *fname = node->c_str();
203             if (!plugin_save) {
204                Jmsg(jcr, M_FATAL, 0, _("Plugin: \"%s\" not found.\n"), fname);
205                return 0;
206             }
207             Dmsg1(dbglvl, "PluginCommand: %s\n", fname);
208             ff->top_fname = fname;
209             ff->cmd_plugin = true;
210 
211             /* Make sure that opt plugin is not set
212              * The current implementation doesn't allow option plugin
213              * and command plugin to run at the same time
214              */
215             ff->opt_plugin = false;
216             ff->plugin = NULL;
217 
218             plugin_save(jcr, ff, true);
219             ff->cmd_plugin = false;
220             if (job_canceled(jcr)) {
221                return 0;
222             }
223          }
224       }
225    }
226    return 1;
227 }
228 
229 /*
230  * Test if the currently selected directory (in ff->fname) is
231  *  explicitly in the Include list or explicitly in the Exclude
232  *  list.
233  */
is_in_fileset(FF_PKT * ff)234 bool is_in_fileset(FF_PKT *ff)
235 {
236    dlistString *node;
237    char *fname;
238    int i;
239    findINCEXE *incexe;
240    findFILESET *fileset = ff->fileset;
241    if (fileset) {
242       for (i=0; i<fileset->include_list.size(); i++) {
243          incexe = (findINCEXE *)fileset->include_list.get(i);
244          foreach_dlist(node, &incexe->name_list) {
245             fname = node->c_str();
246             Dmsg2(dbglvl, "Inc fname=%s ff->fname=%s\n", fname, ff->fname);
247             if (strcmp(fname, ff->fname) == 0) {
248                return true;
249             }
250          }
251       }
252       for (i=0; i<fileset->exclude_list.size(); i++) {
253          incexe = (findINCEXE *)fileset->exclude_list.get(i);
254          foreach_dlist(node, &incexe->name_list) {
255             fname = node->c_str();
256             Dmsg2(dbglvl, "Exc fname=%s ff->fname=%s\n", fname, ff->fname);
257             if (strcmp(fname, ff->fname) == 0) {
258                return true;
259             }
260          }
261       }
262    }
263    return false;
264 }
265 
266 
accept_file(FF_PKT * ff)267 bool accept_file(FF_PKT *ff)
268 {
269    int i, j, k;
270    int fnm_flags;
271    findFILESET *fileset = ff->fileset;
272    findINCEXE *incexe = fileset->incexe;
273    const char *basename;
274    int (*match_func)(const char *pattern, const char *string, int flags);
275 
276    Dmsg1(dbglvl, "enter accept_file: fname=%s\n", ff->fname);
277    if (ff->flags & FO_ENHANCEDWILD) {
278 //    match_func = enh_fnmatch;
279       match_func = fnmatch;
280       if ((basename = last_path_separator(ff->fname)) != NULL)
281          basename++;
282       else
283          basename = ff->fname;
284    } else {
285       match_func = fnmatch;
286       basename = ff->fname;
287    }
288 
289    for (j = 0; j < incexe->opts_list.size(); j++) {
290       findFOPTS *fo = (findFOPTS *)incexe->opts_list.get(j);
291       ff->flags = fo->flags;
292       ff->Compress_algo = fo->Compress_algo;
293       ff->Compress_level = fo->Compress_level;
294       if (fo->flags & FO_DEDUPLICATION) {
295          /* fix #2334 but see TODO in find_file() above */
296          ff->Dedup_level = fo->Dedup_level;
297       }
298       ff->fstypes = fo->fstype;
299       ff->drivetypes = fo->drivetype;
300 
301       fnm_flags = (ff->flags & FO_IGNORECASE) ? FNM_CASEFOLD : 0;
302       fnm_flags |= (ff->flags & FO_ENHANCEDWILD) ? FNM_PATHNAME : 0;
303 
304       if (S_ISDIR(ff->statp.st_mode)) {
305          for (k=0; k<fo->wilddir.size(); k++) {
306             if (match_func((char *)fo->wilddir.get(k), ff->fname, fnmode|fnm_flags) == 0) {
307                if (ff->flags & FO_EXCLUDE) {
308                   Dmsg2(dbglvl, "Exclude wilddir: %s file=%s\n", (char *)fo->wilddir.get(k),
309                      ff->fname);
310                   return false;       /* reject dir */
311                }
312                return true;           /* accept dir */
313             }
314          }
315       } else {
316          for (k=0; k<fo->wildfile.size(); k++) {
317             if (match_func((char *)fo->wildfile.get(k), ff->fname, fnmode|fnm_flags) == 0) {
318                if (ff->flags & FO_EXCLUDE) {
319                   Dmsg2(dbglvl, "Exclude wildfile: %s file=%s\n", (char *)fo->wildfile.get(k),
320                      ff->fname);
321                   return false;       /* reject file */
322                }
323                return true;           /* accept file */
324             }
325          }
326 
327          for (k=0; k<fo->wildbase.size(); k++) {
328             if (match_func((char *)fo->wildbase.get(k), basename, fnmode|fnm_flags) == 0) {
329                if (ff->flags & FO_EXCLUDE) {
330                   Dmsg2(dbglvl, "Exclude wildbase: %s file=%s\n", (char *)fo->wildbase.get(k),
331                      basename);
332                   return false;       /* reject file */
333                }
334                return true;           /* accept file */
335             }
336          }
337       }
338       for (k=0; k<fo->wild.size(); k++) {
339          if (match_func((char *)fo->wild.get(k), ff->fname, fnmode|fnm_flags) == 0) {
340             if (ff->flags & FO_EXCLUDE) {
341                Dmsg2(dbglvl, "Exclude wild: %s file=%s\n", (char *)fo->wild.get(k),
342                   ff->fname);
343                return false;          /* reject file */
344             }
345             return true;              /* accept file */
346          }
347       }
348       if (S_ISDIR(ff->statp.st_mode)) {
349          for (k=0; k<fo->regexdir.size(); k++) {
350             const int nmatch = 30;
351             regmatch_t pmatch[nmatch];
352             if (regexec((regex_t *)fo->regexdir.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       } else {
360          for (k=0; k<fo->regexfile.size(); k++) {
361             const int nmatch = 30;
362             regmatch_t pmatch[nmatch];
363             if (regexec((regex_t *)fo->regexfile.get(k), ff->fname, nmatch, pmatch,  0) == 0) {
364                if (ff->flags & FO_EXCLUDE) {
365                   return false;       /* reject file */
366                }
367                return true;           /* accept file */
368             }
369          }
370       }
371       for (k=0; k<fo->regex.size(); k++) {
372          const int nmatch = 30;
373          regmatch_t pmatch[nmatch];
374          if (regexec((regex_t *)fo->regex.get(k), ff->fname, nmatch, pmatch,  0) == 0) {
375             if (ff->flags & FO_EXCLUDE) {
376                return false;          /* reject file */
377             }
378             return true;              /* accept file */
379          }
380       }
381       /*
382        * If we have an empty Options clause with exclude, then
383        *  exclude the file
384        */
385       if (ff->flags & FO_EXCLUDE &&
386           fo->regex.size() == 0     && fo->wild.size() == 0 &&
387           fo->regexdir.size() == 0  && fo->wilddir.size() == 0 &&
388           fo->regexfile.size() == 0 && fo->wildfile.size() == 0 &&
389           fo->wildbase.size() == 0) {
390          return false;              /* reject file */
391       }
392    }
393 
394    /* Now apply the Exclude { } directive */
395    for (i=0; i<fileset->exclude_list.size(); i++) {
396       findINCEXE *incexe = (findINCEXE *)fileset->exclude_list.get(i);
397       /* FIXME: I don't think we can have wild exclusion inside a Exclude {} */
398       for (j=0; j<incexe->opts_list.size(); j++) {
399          findFOPTS *fo = (findFOPTS *)incexe->opts_list.get(j);
400          fnm_flags = (fo->flags & FO_IGNORECASE) ? FNM_CASEFOLD : 0;
401          for (k=0; k<fo->wild.size(); k++) {
402             if (fnmatch((char *)fo->wild.get(k), ff->fname, fnmode|fnm_flags) == 0) {
403                Dmsg1(dbglvl, "Reject wild1: %s\n", ff->fname);
404                return false;          /* reject file */
405             }
406          }
407       }
408       /* FIXME: I don't think we can set Options{} inside an Exclude{}, so it is
409        * not possible to have the IGNORECASE flag. The Exclude must match the case.
410        * One solution would be to look the Include flag for the current file. A very
411        * old version of the code was using FNM_CASEFOLD by default.
412        */
413       fnm_flags = (incexe->current_opts != NULL && incexe->current_opts->flags & FO_IGNORECASE)
414              ? FNM_CASEFOLD : 0;
415       dlistString *node;
416       foreach_dlist(node, &incexe->name_list) {
417          char *fname = node->c_str();
418          if (fnmatch(fname, ff->fname, fnmode|fnm_flags) == 0) {
419             Dmsg1(dbglvl, "Reject wild2: %s\n", ff->fname);
420             return false;          /* reject file */
421          }
422       }
423    }
424    return true;
425 }
426 
427 /*
428  * The code comes here for each file examined.
429  * We filter the files, then call the user's callback if
430  *    the file is included.
431  */
our_callback(JCR * jcr,FF_PKT * ff,bool top_level)432 static int our_callback(JCR *jcr, FF_PKT *ff, bool top_level)
433 {
434    if (top_level) {
435       return ff->file_save(jcr, ff, top_level);   /* accept file */
436    }
437    switch (ff->type) {
438    case FT_NOACCESS:
439    case FT_NOFOLLOW:
440    case FT_NOSTAT:
441    case FT_NOCHG:
442    case FT_ISARCH:
443    case FT_NORECURSE:
444    case FT_NOFSCHG:
445    case FT_INVALIDFS:
446    case FT_INVALIDDT:
447    case FT_NOOPEN:
448 //    return ff->file_save(jcr, ff, top_level);
449 
450    /* These items can be filtered */
451    case FT_LNKSAVED:
452    case FT_REGE:
453    case FT_REG:
454    case FT_LNK:
455    case FT_DIRBEGIN:
456    case FT_DIREND:
457    case FT_RAW:
458    case FT_FIFO:
459    case FT_SPEC:
460    case FT_DIRNOCHG:
461    case FT_REPARSE:
462    case FT_JUNCTION:
463       if (accept_file(ff)) {
464          return ff->file_save(jcr, ff, top_level);
465       } else {
466          Dmsg1(dbglvl, "Skip file %s\n", ff->fname);
467          return -1;                   /* ignore this file */
468       }
469 
470    default:
471       Dmsg1(000, "Unknown FT code %d\n", ff->type);
472       return 0;
473    }
474 }
475 
476 
477 /*
478  * Terminate find_files() and release
479  * all allocated memory
480  */
481 int
term_find_files(FF_PKT * ff)482 term_find_files(FF_PKT *ff)
483 {
484    int hard_links;
485 
486    if (ff->fname_save) {
487       free_pool_memory(ff->fname_save);
488    }
489    if (ff->link_save) {
490       free_pool_memory(ff->link_save);
491    }
492    if (ff->ignoredir_fname) {
493       free_pool_memory(ff->ignoredir_fname);
494    }
495    if (ff->snap_top_fname) {
496       free_pool_memory(ff->snap_top_fname);
497    }
498    if (ff->mtab_list) {
499       delete ff->mtab_list;
500    }
501    hard_links = term_find_one(ff);
502    free(ff);
503    return hard_links;
504 }
505