1 /*
2    BAREOS® - Backup Archiving REcovery Open Sourced
3 
4    Copyright (C) 2000-2012 Free Software Foundation Europe e.V.
5    Copyright (C) 2011-2012 Planets Communications B.V.
6    Copyright (C) 2013-2019 Bareos GmbH & Co. KG
7 
8    This program is Free Software; you can redistribute it and/or
9    modify it under the terms of version three of the GNU Affero General Public
10    License as published by the Free Software Foundation and included
11    in the file LICENSE.
12 
13    This program is distributed in the hope that it will be useful, but
14    WITHOUT ANY WARRANTY; without even the implied warranty of
15    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16    Affero General Public License for more details.
17 
18    You should have received a copy of the GNU Affero General Public License
19    along with this program; if not, write to the Free Software
20    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21    02110-1301, USA.
22 */
23 /*
24  * Kern E. Sibbald, MM
25  */
26 /**
27  * @file
28  * Main routine for finding files on a file system.
29  * The heart of the work to find the files on the
30  * system is done in find_one.c. Here we have the
31  * higher level control as well as the matching
32  * routines for the new syntax Options resource.
33  */
34 
35 #include "include/bareos.h"
36 #include "include/jcr.h"
37 #include "find.h"
38 #include "findlib/find_one.h"
39 #include "lib/util.h"
40 
41 static const int debuglevel = 450;
42 
43 int32_t name_max; /* filename max length */
44 int32_t path_max; /* path name max length */
45 
46 static int OurCallback(JobControlRecord* jcr,
47                        FindFilesPacket* ff,
48                        bool top_level);
49 
50 static const int fnmode = 0;
51 
52 /**
53  * Initialize the find files "global" variables
54  */
init_find_files()55 FindFilesPacket* init_find_files()
56 {
57   FindFilesPacket* ff;
58 
59   ff = (FindFilesPacket*)malloc(sizeof(FindFilesPacket));
60   FindFilesPacket empty_ff;
61   *ff = empty_ff;
62 
63   ff->sys_fname = GetPoolMemory(PM_FNAME);
64 
65   /* Get system path and filename maximum lengths */
66   path_max = pathconf(".", _PC_PATH_MAX);
67   if (path_max < 2048) { path_max = 2048; }
68 
69   name_max = pathconf(".", _PC_NAME_MAX);
70   if (name_max < 2048) { name_max = 2048; }
71   path_max++; /* add for EOS */
72   name_max++; /* add for EOS */
73 
74   Dmsg1(debuglevel, "init_find_files ff=%p\n", ff);
75   return ff;
76 }
77 
78 /**
79  * Set FindFiles options. For the moment, we only
80  * provide for full/incremental saves, and setting
81  * of save_time. For additional options, see above
82  */
SetFindOptions(FindFilesPacket * ff,bool incremental,time_t save_time)83 void SetFindOptions(FindFilesPacket* ff, bool incremental, time_t save_time)
84 {
85   Dmsg0(debuglevel, "Enter SetFindOptions()\n");
86   ff->incremental = incremental;
87   ff->save_time = save_time;
88   Dmsg0(debuglevel, "Leave SetFindOptions()\n");
89 }
90 
SetFindChangedFunction(FindFilesPacket * ff,bool CheckFct (JobControlRecord * jcr,FindFilesPacket * ff))91 void SetFindChangedFunction(FindFilesPacket* ff,
92                             bool CheckFct(JobControlRecord* jcr,
93                                           FindFilesPacket* ff))
94 {
95   Dmsg0(debuglevel, "Enter SetFindChangedFunction()\n");
96   ff->CheckFct = CheckFct;
97 }
98 
99 /**
100  * Call this subroutine with a callback subroutine as the first
101  * argument and a packet as the second argument, this packet
102  * will be passed back to the callback subroutine as the last
103  * argument.
104  */
FindFiles(JobControlRecord * jcr,FindFilesPacket * ff,int FileSave (JobControlRecord * jcr,FindFilesPacket * ff_pkt,bool top_level),int PluginSave (JobControlRecord * jcr,FindFilesPacket * ff_pkt,bool top_level))105 int FindFiles(JobControlRecord* jcr,
106               FindFilesPacket* ff,
107               int FileSave(JobControlRecord* jcr,
108                            FindFilesPacket* ff_pkt,
109                            bool top_level),
110               int PluginSave(JobControlRecord* jcr,
111                              FindFilesPacket* ff_pkt,
112                              bool top_level))
113 {
114   ff->FileSave = FileSave;
115   ff->PluginSave = PluginSave;
116 
117   /* This is the new way */
118   findFILESET* fileset = ff->fileset;
119   if (fileset) {
120     int i, j;
121     /*
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     ClearAllBits(FO_MAX, ff->flags);
127     for (i = 0; i < fileset->include_list.size(); i++) {
128       dlistString* node;
129       findIncludeExcludeItem* incexe
130           = (findIncludeExcludeItem*)fileset->include_list.get(i);
131       fileset->incexe = incexe;
132 
133       /*
134        * Here, we reset some values between two different Include{}
135        */
136       strcpy(ff->VerifyOpts, "V");
137       strcpy(ff->AccurateOpts, "Cmcs");  /* mtime+ctime+size by default */
138       strcpy(ff->BaseJobOpts, "Jspug5"); /* size+perm+user+group+chk  */
139       ff->plugin = NULL;
140       ff->opt_plugin = false;
141 
142       /*
143        * By setting all options, we in effect OR the global options which is
144        * what we want.
145        */
146       for (j = 0; j < incexe->opts_list.size(); j++) {
147         findFOPTS* fo;
148 
149         fo = (findFOPTS*)incexe->opts_list.get(j);
150         CopyBits(FO_MAX, fo->flags, ff->flags);
151         ff->Compress_algo = fo->Compress_algo;
152         ff->Compress_level = fo->Compress_level;
153         ff->StripPath = fo->StripPath;
154         ff->size_match = fo->size_match;
155         ff->fstypes = fo->fstype;
156         ff->drivetypes = fo->Drivetype;
157         if (fo->plugin != NULL) {
158           ff->plugin = fo->plugin; /* TODO: generate a plugin event ? */
159           ff->opt_plugin = true;
160         }
161         bstrncat(ff->VerifyOpts, fo->VerifyOpts,
162                  sizeof(ff->VerifyOpts)); /* TODO: Concat or replace? */
163         if (fo->AccurateOpts[0]) {
164           bstrncpy(ff->AccurateOpts, fo->AccurateOpts,
165                    sizeof(ff->AccurateOpts));
166         }
167         if (fo->BaseJobOpts[0]) {
168           bstrncpy(ff->BaseJobOpts, fo->BaseJobOpts, sizeof(ff->BaseJobOpts));
169         }
170       }
171       Dmsg4(50, "Verify=<%s> Accurate=<%s> BaseJob=<%s> flags=<%d>\n",
172             ff->VerifyOpts, ff->AccurateOpts, ff->BaseJobOpts, ff->flags);
173 
174       foreach_dlist (node, &incexe->name_list) {
175         char* fname = node->c_str();
176 
177         Dmsg1(debuglevel, "F %s\n", fname);
178         ff->top_fname = fname;
179         if (FindOneFile(jcr, ff, OurCallback, ff->top_fname, (dev_t)-1, true)
180             == 0) {
181           return 0; /* error return */
182         }
183         if (JobCanceled(jcr)) { return 0; }
184       }
185 
186       foreach_dlist (node, &incexe->plugin_list) {
187         char* fname = node->c_str();
188 
189         if (!PluginSave) {
190           Jmsg(jcr, M_FATAL, 0, _("Plugin: \"%s\" not found.\n"), fname);
191           return 0;
192         }
193         Dmsg1(debuglevel, "PluginCommand: %s\n", fname);
194         ff->top_fname = fname;
195         ff->cmd_plugin = true;
196         PluginSave(jcr, ff, true);
197         ff->cmd_plugin = false;
198         if (JobCanceled(jcr)) { return 0; }
199       }
200     }
201   }
202   return 1;
203 }
204 
205 /**
206  * Test if the currently selected directory (in ff->fname) is
207  * explicitly in the Include list or explicitly in the Exclude list.
208  */
IsInFileset(FindFilesPacket * ff)209 bool IsInFileset(FindFilesPacket* ff)
210 {
211   int i;
212   char* fname;
213   dlistString* node;
214   findIncludeExcludeItem* incexe;
215   findFILESET* fileset = ff->fileset;
216 
217   if (fileset) {
218     for (i = 0; i < fileset->include_list.size(); i++) {
219       incexe = (findIncludeExcludeItem*)fileset->include_list.get(i);
220       foreach_dlist (node, &incexe->name_list) {
221         fname = node->c_str();
222         Dmsg2(debuglevel, "Inc fname=%s ff->fname=%s\n", fname, ff->fname);
223         if (bstrcmp(fname, ff->fname)) { return true; }
224       }
225     }
226     for (i = 0; i < fileset->exclude_list.size(); i++) {
227       incexe = (findIncludeExcludeItem*)fileset->exclude_list.get(i);
228       foreach_dlist (node, &incexe->name_list) {
229         fname = node->c_str();
230         Dmsg2(debuglevel, "Exc fname=%s ff->fname=%s\n", fname, ff->fname);
231         if (bstrcmp(fname, ff->fname)) { return true; }
232       }
233     }
234   }
235 
236   return false;
237 }
238 
AcceptFile(FindFilesPacket * ff)239 bool AcceptFile(FindFilesPacket* ff)
240 {
241   int i, j, k;
242   int fnm_flags;
243   const char* basename;
244   findFILESET* fileset = ff->fileset;
245   findIncludeExcludeItem* incexe = fileset->incexe;
246   int (*match_func)(const char* pattern, const char* string, int flags);
247 
248   Dmsg1(debuglevel, "enter AcceptFile: fname=%s\n", ff->fname);
249   if (BitIsSet(FO_ENHANCEDWILD, ff->flags)) {
250     match_func = fnmatch;
251     if ((basename = last_path_separator(ff->fname)) != NULL)
252       basename++;
253     else
254       basename = ff->fname;
255   } else {
256     match_func = fnmatch;
257     basename = ff->fname;
258   }
259 
260   for (j = 0; j < incexe->opts_list.size(); j++) {
261     findFOPTS* fo;
262 
263     fo = (findFOPTS*)incexe->opts_list.get(j);
264     CopyBits(FO_MAX, fo->flags, ff->flags);
265     ff->Compress_algo = fo->Compress_algo;
266     ff->Compress_level = fo->Compress_level;
267     ff->fstypes = fo->fstype;
268     ff->drivetypes = fo->Drivetype;
269 
270     fnm_flags = BitIsSet(FO_IGNORECASE, ff->flags) ? FNM_CASEFOLD : 0;
271     fnm_flags |= BitIsSet(FO_ENHANCEDWILD, ff->flags) ? FNM_PATHNAME : 0;
272 
273     if (S_ISDIR(ff->statp.st_mode)) {
274       for (k = 0; k < fo->wilddir.size(); k++) {
275         if (match_func((char*)fo->wilddir.get(k), ff->fname, fnmode | fnm_flags)
276             == 0) {
277           if (BitIsSet(FO_EXCLUDE, ff->flags)) {
278             Dmsg2(debuglevel, "Exclude wilddir: %s file=%s\n",
279                   (char*)fo->wilddir.get(k), ff->fname);
280             return false; /* reject dir */
281           }
282           return true; /* accept dir */
283         }
284       }
285     } else {
286       for (k = 0; k < fo->wildfile.size(); k++) {
287         if (match_func((char*)fo->wildfile.get(k), ff->fname,
288                        fnmode | fnm_flags)
289             == 0) {
290           if (BitIsSet(FO_EXCLUDE, ff->flags)) {
291             Dmsg2(debuglevel, "Exclude wildfile: %s file=%s\n",
292                   (char*)fo->wildfile.get(k), ff->fname);
293             return false; /* reject file */
294           }
295           return true; /* accept file */
296         }
297       }
298 
299       for (k = 0; k < fo->wildbase.size(); k++) {
300         if (match_func((char*)fo->wildbase.get(k), basename, fnmode | fnm_flags)
301             == 0) {
302           if (BitIsSet(FO_EXCLUDE, ff->flags)) {
303             Dmsg2(debuglevel, "Exclude wildbase: %s file=%s\n",
304                   (char*)fo->wildbase.get(k), basename);
305             return false; /* reject file */
306           }
307           return true; /* accept file */
308         }
309       }
310     }
311 
312     for (k = 0; k < fo->wild.size(); k++) {
313       if (match_func((char*)fo->wild.get(k), ff->fname, fnmode | fnm_flags)
314           == 0) {
315         if (BitIsSet(FO_EXCLUDE, ff->flags)) {
316           Dmsg2(debuglevel, "Exclude wild: %s file=%s\n",
317                 (char*)fo->wild.get(k), ff->fname);
318           return false; /* reject file */
319         }
320         return true; /* accept file */
321       }
322     }
323 
324     if (S_ISDIR(ff->statp.st_mode)) {
325       for (k = 0; k < fo->regexdir.size(); k++) {
326         if (regexec((regex_t*)fo->regexdir.get(k), ff->fname, 0, NULL, 0)
327             == 0) {
328           if (BitIsSet(FO_EXCLUDE, ff->flags)) {
329             return false; /* reject file */
330           }
331           return true; /* accept file */
332         }
333       }
334     } else {
335       for (k = 0; k < fo->regexfile.size(); k++) {
336         if (regexec((regex_t*)fo->regexfile.get(k), ff->fname, 0, NULL, 0)
337             == 0) {
338           if (BitIsSet(FO_EXCLUDE, ff->flags)) {
339             return false; /* reject file */
340           }
341           return true; /* accept file */
342         }
343       }
344     }
345 
346     for (k = 0; k < fo->regex.size(); k++) {
347       if (regexec((regex_t*)fo->regex.get(k), ff->fname, 0, NULL, 0) == 0) {
348         if (BitIsSet(FO_EXCLUDE, ff->flags)) { return false; /* reject file */ }
349         return true; /* accept file */
350       }
351     }
352 
353     /*
354      * If we have an empty Options clause with exclude, then exclude the file
355      */
356     if (BitIsSet(FO_EXCLUDE, ff->flags) && fo->regex.size() == 0
357         && fo->wild.size() == 0 && fo->regexdir.size() == 0
358         && fo->wilddir.size() == 0 && fo->regexfile.size() == 0
359         && fo->wildfile.size() == 0 && fo->wildbase.size() == 0) {
360       Dmsg1(debuglevel, "Empty options, rejecting: %s\n", ff->fname);
361       return false; /* reject file */
362     }
363   }
364 
365   /*
366    * Now apply the Exclude { } directive
367    */
368   for (i = 0; i < fileset->exclude_list.size(); i++) {
369     dlistString* node;
370     findIncludeExcludeItem* incexe
371         = (findIncludeExcludeItem*)fileset->exclude_list.get(i);
372 
373     for (j = 0; j < incexe->opts_list.size(); j++) {
374       findFOPTS* fo = (findFOPTS*)incexe->opts_list.get(j);
375       fnm_flags = BitIsSet(FO_IGNORECASE, fo->flags) ? FNM_CASEFOLD : 0;
376       for (k = 0; k < fo->wild.size(); k++) {
377         if (fnmatch((char*)fo->wild.get(k), ff->fname, fnmode | fnm_flags)
378             == 0) {
379           Dmsg1(debuglevel, "Reject wild1: %s\n", ff->fname);
380           return false; /* reject file */
381         }
382       }
383     }
384     fnm_flags = (incexe->current_opts != NULL
385                  && BitIsSet(FO_IGNORECASE, incexe->current_opts->flags))
386                     ? FNM_CASEFOLD
387                     : 0;
388     foreach_dlist (node, &incexe->name_list) {
389       char* fname = node->c_str();
390 
391       if (fnmatch(fname, ff->fname, fnmode | fnm_flags) == 0) {
392         Dmsg1(debuglevel, "Reject wild2: %s\n", ff->fname);
393         return false; /* reject file */
394       }
395     }
396   }
397 
398   return true;
399 }
400 
401 /**
402  * The code comes here for each file examined.
403  * We filter the files, then call the user's callback if the file is included.
404  */
OurCallback(JobControlRecord * jcr,FindFilesPacket * ff,bool top_level)405 static int OurCallback(JobControlRecord* jcr,
406                        FindFilesPacket* ff,
407                        bool top_level)
408 {
409   if (top_level) { return ff->FileSave(jcr, ff, top_level); /* accept file */ }
410   switch (ff->type) {
411     case FT_NOACCESS:
412     case FT_NOFOLLOW:
413     case FT_NOSTAT:
414     case FT_NOCHG:
415     case FT_ISARCH:
416     case FT_NORECURSE:
417     case FT_NOFSCHG:
418     case FT_INVALIDFS:
419     case FT_INVALIDDT:
420     case FT_NOOPEN:
421       //    return ff->FileSave(jcr, ff, top_level);
422 
423     /* These items can be filtered */
424     case FT_LNKSAVED:
425     case FT_REGE:
426     case FT_REG:
427     case FT_LNK:
428     case FT_DIRBEGIN:
429     case FT_DIREND:
430     case FT_RAW:
431     case FT_FIFO:
432     case FT_SPEC:
433     case FT_DIRNOCHG:
434     case FT_REPARSE:
435     case FT_JUNCTION:
436       if (AcceptFile(ff)) {
437         return ff->FileSave(jcr, ff, top_level);
438       } else {
439         Dmsg1(debuglevel, "Skip file %s\n", ff->fname);
440         return -1; /* ignore this file */
441       }
442 
443     default:
444       Dmsg1(000, "Unknown FT code %d\n", ff->type);
445       return 0;
446   }
447 }
448 
449 /**
450  * Terminate FindFiles() and release all allocated memory
451  */
TermFindFiles(FindFilesPacket * ff)452 int TermFindFiles(FindFilesPacket* ff)
453 {
454   int hard_links = 0;
455 
456   if (ff) {
457     FreePoolMemory(ff->sys_fname);
458     if (ff->fname_save) { FreePoolMemory(ff->fname_save); }
459     if (ff->link_save) { FreePoolMemory(ff->link_save); }
460     if (ff->ignoredir_fname) { FreePoolMemory(ff->ignoredir_fname); }
461     hard_links = TermFindOne(ff);
462     free(ff);
463   }
464 
465   return hard_links;
466 }
467 
468 /**
469  * Allocate a new include/exclude block.
470  */
allocate_new_incexe(void)471 findIncludeExcludeItem* allocate_new_incexe(void)
472 {
473   findIncludeExcludeItem* incexe;
474 
475   incexe = (findIncludeExcludeItem*)malloc(sizeof(findIncludeExcludeItem));
476   *incexe = findIncludeExcludeItem{};
477   incexe->opts_list.init(1, true);
478   incexe->name_list.init();
479   incexe->plugin_list.init();
480 
481   return incexe;
482 }
483 
484 /**
485  * Define a new Exclude block in the FileSet
486  */
new_exclude(findFILESET * fileset)487 findIncludeExcludeItem* new_exclude(findFILESET* fileset)
488 {
489   /*
490    * New exclude
491    */
492   fileset->incexe = allocate_new_incexe();
493   fileset->exclude_list.append(fileset->incexe);
494 
495   return fileset->incexe;
496 }
497 
498 /**
499  * Define a new Include block in the FileSet
500  */
new_include(findFILESET * fileset)501 findIncludeExcludeItem* new_include(findFILESET* fileset)
502 {
503   /*
504    * New include
505    */
506   fileset->incexe = allocate_new_incexe();
507   fileset->include_list.append(fileset->incexe);
508 
509   return fileset->incexe;
510 }
511 
512 /**
513  * Define a new preInclude block in the FileSet.
514  * That is the include is prepended to the other
515  * Includes. This is used for plugin exclusions.
516  */
new_preinclude(findFILESET * fileset)517 findIncludeExcludeItem* new_preinclude(findFILESET* fileset)
518 {
519   /*
520    * New pre-include
521    */
522   fileset->incexe = allocate_new_incexe();
523   fileset->include_list.prepend(fileset->incexe);
524 
525   return fileset->incexe;
526 }
527 
528 /**
529  * Create a new exclude block and prepend it to the list of exclude blocks.
530  */
new_preexclude(findFILESET * fileset)531 findIncludeExcludeItem* new_preexclude(findFILESET* fileset)
532 {
533   /*
534    * New pre-exclude
535    */
536   fileset->incexe = allocate_new_incexe();
537   fileset->exclude_list.prepend(fileset->incexe);
538 
539   return fileset->incexe;
540 }
541 
start_options(FindFilesPacket * ff)542 findFOPTS* start_options(FindFilesPacket* ff)
543 {
544   int state = ff->fileset->state;
545   findIncludeExcludeItem* incexe = ff->fileset->incexe;
546 
547   if (state != state_options) {
548     ff->fileset->state = state_options;
549     findFOPTS* fo = (findFOPTS*)malloc(sizeof(findFOPTS));
550     *fo = findFOPTS{};
551     fo->regex.init(1, true);
552     fo->regexdir.init(1, true);
553     fo->regexfile.init(1, true);
554     fo->wild.init(1, true);
555     fo->wilddir.init(1, true);
556     fo->wildfile.init(1, true);
557     fo->wildbase.init(1, true);
558     fo->base.init(1, true);
559     fo->fstype.init(1, true);
560     fo->Drivetype.init(1, true);
561     incexe->current_opts = fo;
562     incexe->opts_list.append(fo);
563   }
564 
565   return incexe->current_opts;
566 }
567 
568 /**
569  * Used by plugins to define a new options block
570  */
NewOptions(FindFilesPacket * ff,findIncludeExcludeItem * incexe)571 void NewOptions(FindFilesPacket* ff, findIncludeExcludeItem* incexe)
572 {
573   findFOPTS* fo;
574 
575   fo = (findFOPTS*)malloc(sizeof(findFOPTS));
576   *fo = findFOPTS{};
577   fo->regex.init(1, true);
578   fo->regexdir.init(1, true);
579   fo->regexfile.init(1, true);
580   fo->wild.init(1, true);
581   fo->wilddir.init(1, true);
582   fo->wildfile.init(1, true);
583   fo->wildbase.init(1, true);
584   fo->base.init(1, true);
585   fo->fstype.init(1, true);
586   fo->Drivetype.init(1, true);
587   incexe->current_opts = fo;
588   incexe->opts_list.prepend(fo);
589   ff->fileset->state = state_options;
590 }
591