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