2 Bacula(R) - The Network Backup Solution
4 Copyright (C) 2000-2015 Kern Sibbald
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.
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.
14 This notice must be preserved when any source code is
15 conveyed and/or propagated.
17 Bacula(R) is a registered trademark of Kern Sibbald.
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.
34 static const int dbglvl = 450;
36 int32_t name_max; /* filename max length */
37 int32_t path_max; /* path name max length */
41 #define bmalloc(x) sm_malloc(__FILE__, __LINE__, x)
43 static int our_callback(JCR *jcr, FF_PKT *ff, bool top_level);
45 static const int fnmode = 0;
48 * Initialize the find files "global" variables
50 FF_PKT *init_find_files()
54 ff = (FF_PKT *)bmalloc(sizeof(FF_PKT));
55 memset(ff, 0, sizeof(FF_PKT));
57 ff->sys_fname = get_pool_memory(PM_FNAME);
59 /* Get system path and filename maximum lengths */
60 path_max = pathconf(".", _PC_PATH_MAX);
61 if (path_max < 2048) {
65 name_max = pathconf(".", _PC_NAME_MAX);
66 if (name_max < 2048) {
69 path_max++; /* add for EOS */
70 name_max++; /* add for EOS */
72 Dmsg1(dbglvl, "init_find_files ff=%p\n", ff);
77 * Set find_files options. For the moment, we only
78 * provide for full/incremental saves, and setting
79 * of save_time. For additional options, see above
82 set_find_options(FF_PKT *ff, int incremental, time_t save_time)
84 Dmsg0(dbglvl, "Enter set_find_options()\n");
85 ff->incremental = incremental;
86 ff->save_time = save_time;
87 Dmsg0(dbglvl, "Leave set_find_options()\n");
91 set_find_changed_function(FF_PKT *ff, bool check_fct(JCR *jcr, FF_PKT *ff))
93 Dmsg0(dbglvl, "Enter set_find_changed_function()\n");
94 ff->check_fct = check_fct;
98 set_find_snapshot_function(FF_PKT *ff,
99 bool convert_path(JCR *jcr, FF_PKT *ff, dlist *filelist, dlistString *node))
101 ff->snapshot_convert_fct = convert_path;
105 * Call this subroutine with a callback subroutine as the first
106 * argument and a packet as the second argument, this packet
107 * will be passed back to the callback subroutine as the last
112 find_files(JCR *jcr, FF_PKT *ff, int file_save(JCR *jcr, FF_PKT *ff_pkt, bool top_level),
113 int plugin_save(JCR *jcr, FF_PKT *ff_pkt, bool top_level))
115 ff->file_save = file_save;
116 ff->plugin_save = plugin_save;
118 /* This is the new way */
119 findFILESET *fileset = ff->fileset;
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{})
127 for (i=0; i<fileset->include_list.size(); i++) {
128 findINCEXE *incexe = (findINCEXE *)fileset->include_list.get(i);
129 fileset->incexe = incexe;
131 /* Here, we reset some values between two different Include{} */
132 strcpy(ff->VerifyOpts, "V");
133 strcpy(ff->AccurateOpts, "Cmcs"); /* mtime+ctime+size by default */
134 strcpy(ff->BaseJobOpts, "Jspug5"); /* size+perm+user+group+chk */
136 ff->opt_plugin = false;
139 * By setting all options, we in effect OR the global options
140 * which is what we want.
142 for (j=0; j<incexe->opts_list.size(); j++) {
143 findFOPTS *fo = (findFOPTS *)incexe->opts_list.get(j);
144 /* TODO options are "simply" reset by Options block that come next
146 * Options { IgnoreCase = yes }
147 * ATTN: some plugins use AddOptions() that create extra Option block
148 * Also see accept_file() below that could suffer of the same problem
150 ff->flags |= fo->flags;
151 /* If the compress option was set in the previous block, overwrite the
152 * algorithm only if defined
154 if ((ff->flags & FO_COMPRESS) && fo->Compress_algo != 0) {
155 ff->Compress_algo = fo->Compress_algo;
156 ff->Compress_level = fo->Compress_level;
158 ff->strip_path = fo->strip_path;
159 ff->fstypes = fo->fstype;
160 ff->drivetypes = fo->drivetype;
161 if (fo->plugin != NULL) {
162 ff->plugin = fo->plugin; /* TODO: generate a plugin event ? */
163 ff->opt_plugin = true;
165 bstrncat(ff->VerifyOpts, fo->VerifyOpts, sizeof(ff->VerifyOpts)); /* TODO: Concat or replace? */
166 if (fo->AccurateOpts[0]) {
167 bstrncpy(ff->AccurateOpts, fo->AccurateOpts, sizeof(ff->AccurateOpts));
169 if (fo->BaseJobOpts[0]) {
170 bstrncpy(ff->BaseJobOpts, fo->BaseJobOpts, sizeof(ff->BaseJobOpts));
173 Dmsg4(50, "Verify=<%s> Accurate=<%s> BaseJob=<%s> flags=<%lld>\n",
174 ff->VerifyOpts, ff->AccurateOpts, ff->BaseJobOpts, ff->flags);
176 foreach_dlist(node, &incexe->name_list) {
177 char *fname = node->c_str();
178 Dmsg1(dbglvl, "F %s\n", fname);
180 ff->top_fname = fname;
181 /* Convert the filename if needed */
182 if (ff->snapshot_convert_fct) {
183 ff->snapshot_convert_fct(jcr, ff, &incexe->name_list, node);
186 if (find_one_file(jcr, ff, our_callback, ff->top_fname, (dev_t)-1, true) == 0) {
187 return 0; /* error return */
190 if (job_canceled(jcr)) {
194 foreach_dlist(node, &incexe->plugin_list) {
195 char *fname = node->c_str();
197 Jmsg(jcr, M_FATAL, 0, _("Plugin: \"%s\" not found.\n"), fname);
200 Dmsg1(dbglvl, "PluginCommand: %s\n", fname);
201 ff->top_fname = fname;
202 ff->cmd_plugin = true;
204 /* Make sure that opt plugin is not set
205 * The current implementation doesn't allow option plugin
206 * and command plugin to run at the same time
208 ff->opt_plugin = false;
211 plugin_save(jcr, ff, true);
212 ff->cmd_plugin = false;
213 if (job_canceled(jcr)) {
223 * Test if the currently selected directory (in ff->fname) is
224 * explicitly in the Include list or explicitly in the Exclude
227 bool is_in_fileset(FF_PKT *ff)
233 findFILESET *fileset = ff->fileset;
235 for (i=0; i<fileset->include_list.size(); i++) {
236 incexe = (findINCEXE *)fileset->include_list.get(i);
237 foreach_dlist(node, &incexe->name_list) {
238 fname = node->c_str();
239 Dmsg2(dbglvl, "Inc fname=%s ff->fname=%s\n", fname, ff->fname);
240 if (strcmp(fname, ff->fname) == 0) {
245 for (i=0; i<fileset->exclude_list.size(); i++) {
246 incexe = (findINCEXE *)fileset->exclude_list.get(i);
247 foreach_dlist(node, &incexe->name_list) {
248 fname = node->c_str();
249 Dmsg2(dbglvl, "Exc fname=%s ff->fname=%s\n", fname, ff->fname);
250 if (strcmp(fname, ff->fname) == 0) {
260 bool accept_file(FF_PKT *ff)
264 findFILESET *fileset = ff->fileset;
265 findINCEXE *incexe = fileset->incexe;
266 const char *basename;
267 int (*match_func)(const char *pattern, const char *string, int flags);
269 Dmsg1(dbglvl, "enter accept_file: fname=%s\n", ff->fname);
270 if (ff->flags & FO_ENHANCEDWILD) {
271 // match_func = enh_fnmatch;
272 match_func = fnmatch;
273 if ((basename = last_path_separator(ff->fname)) != NULL)
276 basename = ff->fname;
278 match_func = fnmatch;
279 basename = ff->fname;
282 for (j = 0; j < incexe->opts_list.size(); j++) {
283 findFOPTS *fo = (findFOPTS *)incexe->opts_list.get(j);
284 ff->flags = fo->flags;
285 ff->Compress_algo = fo->Compress_algo;
286 ff->Compress_level = fo->Compress_level;
287 ff->fstypes = fo->fstype;
288 ff->drivetypes = fo->drivetype;
290 fnm_flags = (ff->flags & FO_IGNORECASE) ? FNM_CASEFOLD : 0;
291 fnm_flags |= (ff->flags & FO_ENHANCEDWILD) ? FNM_PATHNAME : 0;
293 if (S_ISDIR(ff->statp.st_mode)) {
294 for (k=0; k<fo->wilddir.size(); k++) {
295 if (match_func((char *)fo->wilddir.get(k), ff->fname, fnmode|fnm_flags) == 0) {
296 if (ff->flags & FO_EXCLUDE) {
297 Dmsg2(dbglvl, "Exclude wilddir: %s file=%s\n", (char *)fo->wilddir.get(k),
299 return false; /* reject dir */
301 return true; /* accept dir */
305 for (k=0; k<fo->wildfile.size(); k++) {
306 if (match_func((char *)fo->wildfile.get(k), ff->fname, fnmode|fnm_flags) == 0) {
307 if (ff->flags & FO_EXCLUDE) {
308 Dmsg2(dbglvl, "Exclude wildfile: %s file=%s\n", (char *)fo->wildfile.get(k),
310 return false; /* reject file */
312 return true; /* accept file */
316 for (k=0; k<fo->wildbase.size(); k++) {
317 if (match_func((char *)fo->wildbase.get(k), basename, fnmode|fnm_flags) == 0) {
318 if (ff->flags & FO_EXCLUDE) {
319 Dmsg2(dbglvl, "Exclude wildbase: %s file=%s\n", (char *)fo->wildbase.get(k),
321 return false; /* reject file */
323 return true; /* accept file */
327 for (k=0; k<fo->wild.size(); k++) {
328 if (match_func((char *)fo->wild.get(k), ff->fname, fnmode|fnm_flags) == 0) {
329 if (ff->flags & FO_EXCLUDE) {
330 Dmsg2(dbglvl, "Exclude wild: %s file=%s\n", (char *)fo->wild.get(k),
332 return false; /* reject file */
334 return true; /* accept file */
337 if (S_ISDIR(ff->statp.st_mode)) {
338 for (k=0; k<fo->regexdir.size(); k++) {
339 const int nmatch = 30;
340 regmatch_t pmatch[nmatch];
341 if (regexec((regex_t *)fo->regexdir.get(k), ff->fname, nmatch, pmatch, 0) == 0) {
342 if (ff->flags & FO_EXCLUDE) {
343 return false; /* reject file */
345 return true; /* accept file */
349 for (k=0; k<fo->regexfile.size(); k++) {
350 const int nmatch = 30;
351 regmatch_t pmatch[nmatch];
352 if (regexec((regex_t *)fo->regexfile.get(k), ff->fname, nmatch, pmatch, 0) == 0) {
353 if (ff->flags & FO_EXCLUDE) {
354 return false; /* reject file */
356 return true; /* accept file */
360 for (k=0; k<fo->regex.size(); k++) {
361 const int nmatch = 30;
362 regmatch_t pmatch[nmatch];
363 if (regexec((regex_t *)fo->regex.get(k), ff->fname, nmatch, pmatch, 0) == 0) {
364 if (ff->flags & FO_EXCLUDE) {
365 return false; /* reject file */
367 return true; /* accept file */
371 * If we have an empty Options clause with exclude, then
374 if (ff->flags & FO_EXCLUDE &&
375 fo->regex.size() == 0 && fo->wild.size() == 0 &&
376 fo->regexdir.size() == 0 && fo->wilddir.size() == 0 &&
377 fo->regexfile.size() == 0 && fo->wildfile.size() == 0 &&
378 fo->wildbase.size() == 0) {
379 return false; /* reject file */
383 /* Now apply the Exclude { } directive */
384 for (i=0; i<fileset->exclude_list.size(); i++) {
385 findINCEXE *incexe = (findINCEXE *)fileset->exclude_list.get(i);
386 for (j=0; j<incexe->opts_list.size(); j++) {
387 findFOPTS *fo = (findFOPTS *)incexe->opts_list.get(j);
388 fnm_flags = (fo->flags & FO_IGNORECASE) ? FNM_CASEFOLD : 0;
389 for (k=0; k<fo->wild.size(); k++) {
390 if (fnmatch((char *)fo->wild.get(k), ff->fname, fnmode|fnm_flags) == 0) {
391 Dmsg1(dbglvl, "Reject wild1: %s\n", ff->fname);
392 return false; /* reject file */
396 fnm_flags = (incexe->current_opts != NULL && incexe->current_opts->flags & FO_IGNORECASE)
399 foreach_dlist(node, &incexe->name_list) {
400 char *fname = node->c_str();
401 if (fnmatch(fname, ff->fname, fnmode|fnm_flags) == 0) {
402 Dmsg1(dbglvl, "Reject wild2: %s\n", ff->fname);
403 return false; /* reject file */
411 * The code comes here for each file examined.
412 * We filter the files, then call the user's callback if
413 * the file is included.
415 static int our_callback(JCR *jcr, FF_PKT *ff, bool top_level)
418 return ff->file_save(jcr, ff, top_level); /* accept file */
431 // return ff->file_save(jcr, ff, top_level);
433 /* These items can be filtered */
446 if (accept_file(ff)) {
447 return ff->file_save(jcr, ff, top_level);
449 Dmsg1(dbglvl, "Skip file %s\n", ff->fname);
450 return -1; /* ignore this file */
454 Dmsg1(000, "Unknown FT code %d\n", ff->type);
461 * Terminate find_files() and release
462 * all allocated memory
465 term_find_files(FF_PKT *ff)
469 free_pool_memory(ff->sys_fname);
470 if (ff->fname_save) {
471 free_pool_memory(ff->fname_save);
474 free_pool_memory(ff->link_save);
476 if (ff->ignoredir_fname) {
477 free_pool_memory(ff->ignoredir_fname);
479 if (ff->snap_fname) {
480 free_pool_memory(ff->snap_fname);
482 if (ff->snap_top_fname) {
483 free_pool_memory(ff->snap_top_fname);
486 delete ff->mtab_list;
488 hard_links = term_find_one(ff);