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