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,
276                        fnmode | fnm_flags) == 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) == 0) {
289           if (BitIsSet(FO_EXCLUDE, ff->flags)) {
290             Dmsg2(debuglevel, "Exclude wildfile: %s file=%s\n",
291                   (char*)fo->wildfile.get(k), ff->fname);
292             return false; /* reject file */
293           }
294           return true; /* accept file */
295         }
296       }
297 
298       for (k = 0; k < fo->wildbase.size(); k++) {
299         if (match_func((char*)fo->wildbase.get(k), basename,
300                        fnmode | fnm_flags) == 0) {
301           if (BitIsSet(FO_EXCLUDE, ff->flags)) {
302             Dmsg2(debuglevel, "Exclude wildbase: %s file=%s\n",
303                   (char*)fo->wildbase.get(k), basename);
304             return false; /* reject file */
305           }
306           return true; /* accept file */
307         }
308       }
309     }
310 
311     for (k = 0; k < fo->wild.size(); k++) {
312       if (match_func((char*)fo->wild.get(k), ff->fname, fnmode | fnm_flags) ==
313           0) {
314         if (BitIsSet(FO_EXCLUDE, ff->flags)) {
315           Dmsg2(debuglevel, "Exclude wild: %s file=%s\n",
316                 (char*)fo->wild.get(k), ff->fname);
317           return false; /* reject file */
318         }
319         return true; /* accept file */
320       }
321     }
322 
323     if (S_ISDIR(ff->statp.st_mode)) {
324       for (k = 0; k < fo->regexdir.size(); k++) {
325         if (regexec((regex_t*)fo->regexdir.get(k), ff->fname, 0, NULL, 0) ==
326             0) {
327           if (BitIsSet(FO_EXCLUDE, ff->flags)) {
328             return false; /* reject file */
329           }
330           return true; /* accept file */
331         }
332       }
333     } else {
334       for (k = 0; k < fo->regexfile.size(); k++) {
335         if (regexec((regex_t*)fo->regexfile.get(k), ff->fname, 0, NULL, 0) ==
336             0) {
337           if (BitIsSet(FO_EXCLUDE, ff->flags)) {
338             return false; /* reject file */
339           }
340           return true; /* accept file */
341         }
342       }
343     }
344 
345     for (k = 0; k < fo->regex.size(); k++) {
346       if (regexec((regex_t*)fo->regex.get(k), ff->fname, 0, NULL, 0) == 0) {
347         if (BitIsSet(FO_EXCLUDE, ff->flags)) { return false; /* reject file */ }
348         return true; /* accept file */
349       }
350     }
351 
352     /*
353      * If we have an empty Options clause with exclude, then exclude the file
354      */
355     if (BitIsSet(FO_EXCLUDE, ff->flags) && fo->regex.size() == 0 &&
356         fo->wild.size() == 0 && fo->regexdir.size() == 0 &&
357         fo->wilddir.size() == 0 && fo->regexfile.size() == 0 &&
358         fo->wildfile.size() == 0 && fo->wildbase.size() == 0) {
359       Dmsg1(debuglevel, "Empty options, rejecting: %s\n", ff->fname);
360       return false; /* reject file */
361     }
362   }
363 
364   /*
365    * Now apply the Exclude { } directive
366    */
367   for (i = 0; i < fileset->exclude_list.size(); i++) {
368     dlistString* node;
369     findIncludeExcludeItem* incexe =
370         (findIncludeExcludeItem*)fileset->exclude_list.get(i);
371 
372     for (j = 0; j < incexe->opts_list.size(); j++) {
373       findFOPTS* fo = (findFOPTS*)incexe->opts_list.get(j);
374       fnm_flags = BitIsSet(FO_IGNORECASE, fo->flags) ? FNM_CASEFOLD : 0;
375       for (k = 0; k < fo->wild.size(); k++) {
376         if (fnmatch((char*)fo->wild.get(k), ff->fname, fnmode | fnm_flags) ==
377             0) {
378           Dmsg1(debuglevel, "Reject wild1: %s\n", ff->fname);
379           return false; /* reject file */
380         }
381       }
382     }
383     fnm_flags = (incexe->current_opts != NULL &&
384                  BitIsSet(FO_IGNORECASE, incexe->current_opts->flags))
385                     ? FNM_CASEFOLD
386                     : 0;
387     foreach_dlist (node, &incexe->name_list) {
388       char* fname = node->c_str();
389 
390       if (fnmatch(fname, ff->fname, fnmode | fnm_flags) == 0) {
391         Dmsg1(debuglevel, "Reject wild2: %s\n", ff->fname);
392         return false; /* reject file */
393       }
394     }
395   }
396 
397   return true;
398 }
399 
400 /**
401  * The code comes here for each file examined.
402  * We filter the files, then call the user's callback if the file is included.
403  */
OurCallback(JobControlRecord * jcr,FindFilesPacket * ff,bool top_level)404 static int OurCallback(JobControlRecord* jcr,
405                        FindFilesPacket* ff,
406                        bool top_level)
407 {
408   if (top_level) { return ff->FileSave(jcr, ff, top_level); /* accept file */ }
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->FileSave(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 (AcceptFile(ff)) {
436         return ff->FileSave(jcr, ff, top_level);
437       } else {
438         Dmsg1(debuglevel, "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  * Terminate FindFiles() and release all allocated memory
450  */
TermFindFiles(FindFilesPacket * ff)451 int TermFindFiles(FindFilesPacket* ff)
452 {
453   int hard_links = 0;
454 
455   if (ff) {
456     FreePoolMemory(ff->sys_fname);
457     if (ff->fname_save) { FreePoolMemory(ff->fname_save); }
458     if (ff->link_save) { FreePoolMemory(ff->link_save); }
459     if (ff->ignoredir_fname) { FreePoolMemory(ff->ignoredir_fname); }
460     hard_links = TermFindOne(ff);
461     free(ff);
462   }
463 
464   return hard_links;
465 }
466 
467 /**
468  * Allocate a new include/exclude block.
469  */
allocate_new_incexe(void)470 findIncludeExcludeItem* allocate_new_incexe(void)
471 {
472   findIncludeExcludeItem* incexe;
473 
474   incexe = (findIncludeExcludeItem*)malloc(sizeof(findIncludeExcludeItem));
475   *incexe = findIncludeExcludeItem{};
476   incexe->opts_list.init(1, true);
477   incexe->name_list.init();
478   incexe->plugin_list.init();
479 
480   return incexe;
481 }
482 
483 /**
484  * Define a new Exclude block in the FileSet
485  */
new_exclude(findFILESET * fileset)486 findIncludeExcludeItem* new_exclude(findFILESET* fileset)
487 {
488   /*
489    * New exclude
490    */
491   fileset->incexe = allocate_new_incexe();
492   fileset->exclude_list.append(fileset->incexe);
493 
494   return fileset->incexe;
495 }
496 
497 /**
498  * Define a new Include block in the FileSet
499  */
new_include(findFILESET * fileset)500 findIncludeExcludeItem* new_include(findFILESET* fileset)
501 {
502   /*
503    * New include
504    */
505   fileset->incexe = allocate_new_incexe();
506   fileset->include_list.append(fileset->incexe);
507 
508   return fileset->incexe;
509 }
510 
511 /**
512  * Define a new preInclude block in the FileSet.
513  * That is the include is prepended to the other
514  * Includes. This is used for plugin exclusions.
515  */
new_preinclude(findFILESET * fileset)516 findIncludeExcludeItem* new_preinclude(findFILESET* fileset)
517 {
518   /*
519    * New pre-include
520    */
521   fileset->incexe = allocate_new_incexe();
522   fileset->include_list.prepend(fileset->incexe);
523 
524   return fileset->incexe;
525 }
526 
527 /**
528  * Create a new exclude block and prepend it to the list of exclude blocks.
529  */
new_preexclude(findFILESET * fileset)530 findIncludeExcludeItem* new_preexclude(findFILESET* fileset)
531 {
532   /*
533    * New pre-exclude
534    */
535   fileset->incexe = allocate_new_incexe();
536   fileset->exclude_list.prepend(fileset->incexe);
537 
538   return fileset->incexe;
539 }
540 
start_options(FindFilesPacket * ff)541 findFOPTS* start_options(FindFilesPacket* ff)
542 {
543   int state = ff->fileset->state;
544   findIncludeExcludeItem* incexe = ff->fileset->incexe;
545 
546   if (state != state_options) {
547     ff->fileset->state = state_options;
548     findFOPTS* fo = (findFOPTS*)malloc(sizeof(findFOPTS));
549     *fo = findFOPTS{};
550     fo->regex.init(1, true);
551     fo->regexdir.init(1, true);
552     fo->regexfile.init(1, true);
553     fo->wild.init(1, true);
554     fo->wilddir.init(1, true);
555     fo->wildfile.init(1, true);
556     fo->wildbase.init(1, true);
557     fo->base.init(1, true);
558     fo->fstype.init(1, true);
559     fo->Drivetype.init(1, true);
560     incexe->current_opts = fo;
561     incexe->opts_list.append(fo);
562   }
563 
564   return incexe->current_opts;
565 }
566 
567 /**
568  * Used by plugins to define a new options block
569  */
NewOptions(FindFilesPacket * ff,findIncludeExcludeItem * incexe)570 void NewOptions(FindFilesPacket* ff, findIncludeExcludeItem* incexe)
571 {
572   findFOPTS* fo;
573 
574   fo = (findFOPTS*)malloc(sizeof(findFOPTS));
575   *fo = findFOPTS{};
576   fo->regex.init(1, true);
577   fo->regexdir.init(1, true);
578   fo->regexfile.init(1, true);
579   fo->wild.init(1, true);
580   fo->wilddir.init(1, true);
581   fo->wildfile.init(1, true);
582   fo->wildbase.init(1, true);
583   fo->base.init(1, true);
584   fo->fstype.init(1, true);
585   fo->Drivetype.init(1, true);
586   incexe->current_opts = fo;
587   incexe->opts_list.prepend(fo);
588   ff->fileset->state = state_options;
589 }
590