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