]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/findlib/find.c
a9f2d6e7739e9332ec6f5b7e386d36c976949b85
[bacula/bacula] / bacula / src / findlib / find.c
1 /*
2    Bacula(R) - The Network Backup Solution
3
4    Copyright (C) 2000-2015 Kern Sibbald
5    Copyright (C) 2000-2014 Free Software Foundation Europe e.V.
6
7    The original author of Bacula is Kern Sibbald, with contributions
8    from many others, a complete list can be found in the file AUTHORS.
9
10    You may use this file and others of this release according to the
11    license defined in the LICENSE file, which includes the Affero General
12    Public License, v3.0 ("AGPLv3") and some additional permissions and
13    terms pursuant to its AGPLv3 Section 7.
14
15    This notice must be preserved when any source code is 
16    conveyed and/or propagated.
17
18    Bacula(R) is a registered trademark of Kern Sibbald.
19 */
20 /*
21  * Main routine for finding files on a file system.
22  *  The heart of the work to find the files on the
23  *    system is done in find_one.c. Here we have the
24  *    higher level control as well as the matching
25  *    routines for the new syntax Options resource.
26  *
27  *  Kern E. Sibbald, MM
28  *
29  */
30
31
32 #include "bacula.h"
33 #include "find.h"
34
35 static const int dbglvl = 450;
36
37 int32_t name_max;              /* filename max length */
38 int32_t path_max;              /* path name max length */
39
40 #ifdef DEBUG
41 #undef bmalloc
42 #define bmalloc(x) sm_malloc(__FILE__, __LINE__, x)
43 #endif
44 static int our_callback(JCR *jcr, FF_PKT *ff, bool top_level);
45
46 static const int fnmode = 0;
47
48 /*
49  * Initialize the find files "global" variables
50  */
51 FF_PKT *init_find_files()
52 {
53   FF_PKT *ff;
54
55   ff = (FF_PKT *)bmalloc(sizeof(FF_PKT));
56   memset(ff, 0, sizeof(FF_PKT));
57
58   ff->sys_fname = get_pool_memory(PM_FNAME);
59
60    /* Get system path and filename maximum lengths */
61    path_max = pathconf(".", _PC_PATH_MAX);
62    if (path_max < 2048) {
63       path_max = 2048;
64    }
65
66    name_max = pathconf(".", _PC_NAME_MAX);
67    if (name_max < 2048) {
68       name_max = 2048;
69    }
70    path_max++;                        /* add for EOS */
71    name_max++;                        /* add for EOS */
72
73   Dmsg1(dbglvl, "init_find_files ff=%p\n", ff);
74   return ff;
75 }
76
77 /*
78  * Set find_files options. For the moment, we only
79  * provide for full/incremental saves, and setting
80  * of save_time. For additional options, see above
81  */
82 void
83 set_find_options(FF_PKT *ff, int incremental, time_t save_time)
84 {
85   Dmsg0(dbglvl, "Enter set_find_options()\n");
86   ff->incremental = incremental;
87   ff->save_time = save_time;
88   Dmsg0(dbglvl, "Leave set_find_options()\n");
89 }
90
91 void
92 set_find_changed_function(FF_PKT *ff, bool check_fct(JCR *jcr, FF_PKT *ff))
93 {
94    Dmsg0(dbglvl, "Enter set_find_changed_function()\n");
95    ff->check_fct = check_fct;
96 }
97
98 void
99 set_find_snapshot_function(FF_PKT *ff, 
100                            bool convert_path(JCR *jcr, FF_PKT *ff, dlist *filelist, dlistString *node))
101 {
102    ff->snapshot_convert_fct = convert_path;
103 }
104
105 /*
106  * Call this subroutine with a callback subroutine as the first
107  * argument and a packet as the second argument, this packet
108  * will be passed back to the callback subroutine as the last
109  * argument.
110  *
111  */
112 int
113 find_files(JCR *jcr, FF_PKT *ff, int file_save(JCR *jcr, FF_PKT *ff_pkt, bool top_level),
114            int plugin_save(JCR *jcr, FF_PKT *ff_pkt, bool top_level))
115 {
116    ff->file_save = file_save;
117    ff->plugin_save = plugin_save;
118
119    /* This is the new way */
120    findFILESET *fileset = ff->fileset;
121    if (fileset) {
122       int i, j;
123       /* TODO: We probably need be move the initialization in the fileset loop,
124        * at this place flags options are "concatenated" accross Include {} blocks
125        * (not only Options{} blocks inside a Include{})
126        */
127       ff->flags = 0;
128       for (i=0; i<fileset->include_list.size(); i++) {
129          findINCEXE *incexe = (findINCEXE *)fileset->include_list.get(i);
130          fileset->incexe = incexe;
131
132          /* Here, we reset some values between two different Include{} */
133          strcpy(ff->VerifyOpts, "V");
134          strcpy(ff->AccurateOpts, "Cmcs");  /* mtime+ctime+size by default */
135          strcpy(ff->BaseJobOpts, "Jspug5"); /* size+perm+user+group+chk  */
136          ff->plugin = NULL;
137          ff->opt_plugin = false;
138
139          /*
140           * By setting all options, we in effect OR the global options
141           *   which is what we want.
142           */
143          for (j=0; j<incexe->opts_list.size(); j++) {
144             findFOPTS *fo = (findFOPTS *)incexe->opts_list.get(j);
145             ff->flags |= fo->flags;
146             ff->Compress_algo = fo->Compress_algo;
147             ff->Compress_level = fo->Compress_level;
148             ff->strip_path = fo->strip_path;
149             ff->fstypes = fo->fstype;
150             ff->drivetypes = fo->drivetype;
151             if (fo->plugin != NULL) {
152                ff->plugin = fo->plugin; /* TODO: generate a plugin event ? */
153                ff->opt_plugin = true;
154             }
155             bstrncat(ff->VerifyOpts, fo->VerifyOpts, sizeof(ff->VerifyOpts)); /* TODO: Concat or replace? */
156             if (fo->AccurateOpts[0]) {
157                bstrncpy(ff->AccurateOpts, fo->AccurateOpts, sizeof(ff->AccurateOpts));
158             }
159             if (fo->BaseJobOpts[0]) {
160                bstrncpy(ff->BaseJobOpts, fo->BaseJobOpts, sizeof(ff->BaseJobOpts));
161             }
162          }
163          Dmsg4(50, "Verify=<%s> Accurate=<%s> BaseJob=<%s> flags=<%d>\n",
164                ff->VerifyOpts, ff->AccurateOpts, ff->BaseJobOpts, ff->flags);
165          dlistString *node;
166          foreach_dlist(node, &incexe->name_list) {
167             char *fname = node->c_str();
168             Dmsg1(dbglvl, "F %s\n", fname);
169
170             ff->top_fname = fname;
171             /* Convert the filename if needed */
172             if (ff->snapshot_convert_fct) {
173                ff->snapshot_convert_fct(jcr, ff, &incexe->name_list, node);
174             }
175
176             if (find_one_file(jcr, ff, our_callback, ff->top_fname, (dev_t)-1, true) == 0) {
177                return 0;                  /* error return */
178             }
179
180             if (job_canceled(jcr)) {
181                return 0;
182             }
183          }
184          foreach_dlist(node, &incexe->plugin_list) {
185             char *fname = node->c_str();
186             if (!plugin_save) {
187                Jmsg(jcr, M_FATAL, 0, _("Plugin: \"%s\" not found.\n"), fname);
188                return 0;
189             }
190             Dmsg1(dbglvl, "PluginCommand: %s\n", fname);
191             ff->top_fname = fname;
192             ff->cmd_plugin = true;
193
194             /* Make sure that opt plugin is not set
195              * The current implementation doesn't allow option plugin
196              * and command plugin to run at the same time
197              */
198             ff->opt_plugin = false;
199             ff->plugin = NULL;
200
201             plugin_save(jcr, ff, true);
202             ff->cmd_plugin = false;
203             if (job_canceled(jcr)) {
204                return 0;
205             }
206          }
207       }
208    }
209    return 1;
210 }
211
212 /*
213  * Test if the currently selected directory (in ff->fname) is
214  *  explicitly in the Include list or explicitly in the Exclude
215  *  list.
216  */
217 bool is_in_fileset(FF_PKT *ff)
218 {
219    dlistString *node;
220    char *fname;
221    int i;
222    findINCEXE *incexe;
223    findFILESET *fileset = ff->fileset;
224    if (fileset) {
225       for (i=0; i<fileset->include_list.size(); i++) {
226          incexe = (findINCEXE *)fileset->include_list.get(i);
227          foreach_dlist(node, &incexe->name_list) {
228             fname = node->c_str();
229             Dmsg2(dbglvl, "Inc fname=%s ff->fname=%s\n", fname, ff->fname);
230             if (strcmp(fname, ff->fname) == 0) {
231                return true;
232             }
233          }
234       }
235       for (i=0; i<fileset->exclude_list.size(); i++) {
236          incexe = (findINCEXE *)fileset->exclude_list.get(i);
237          foreach_dlist(node, &incexe->name_list) {
238             fname = node->c_str();
239             Dmsg2(dbglvl, "Exc fname=%s ff->fname=%s\n", fname, ff->fname);
240             if (strcmp(fname, ff->fname) == 0) {
241                return true;
242             }
243          }
244       }
245    }
246    return false;
247 }
248
249
250 bool accept_file(FF_PKT *ff)
251 {
252    int i, j, k;
253    int fnm_flags;
254    findFILESET *fileset = ff->fileset;
255    findINCEXE *incexe = fileset->incexe;
256    const char *basename;
257    int (*match_func)(const char *pattern, const char *string, int flags);
258
259    Dmsg1(dbglvl, "enter accept_file: fname=%s\n", ff->fname);
260    if (ff->flags & FO_ENHANCEDWILD) {
261 //    match_func = enh_fnmatch;
262       match_func = fnmatch;
263       if ((basename = last_path_separator(ff->fname)) != NULL)
264          basename++;
265       else
266          basename = ff->fname;
267    } else {
268       match_func = fnmatch;
269       basename = ff->fname;
270    }
271
272    for (j = 0; j < incexe->opts_list.size(); j++) {
273       findFOPTS *fo = (findFOPTS *)incexe->opts_list.get(j);
274       ff->flags = fo->flags;
275       ff->Compress_algo = fo->Compress_algo;
276       ff->Compress_level = fo->Compress_level;
277       ff->fstypes = fo->fstype;
278       ff->drivetypes = fo->drivetype;
279
280       fnm_flags = (ff->flags & FO_IGNORECASE) ? FNM_CASEFOLD : 0;
281       fnm_flags |= (ff->flags & FO_ENHANCEDWILD) ? FNM_PATHNAME : 0;
282
283       if (S_ISDIR(ff->statp.st_mode)) {
284          for (k=0; k<fo->wilddir.size(); k++) {
285             if (match_func((char *)fo->wilddir.get(k), ff->fname, fnmode|fnm_flags) == 0) {
286                if (ff->flags & FO_EXCLUDE) {
287                   Dmsg2(dbglvl, "Exclude wilddir: %s file=%s\n", (char *)fo->wilddir.get(k),
288                      ff->fname);
289                   return false;       /* reject dir */
290                }
291                return true;           /* accept dir */
292             }
293          }
294       } else {
295          for (k=0; k<fo->wildfile.size(); k++) {
296             if (match_func((char *)fo->wildfile.get(k), ff->fname, fnmode|fnm_flags) == 0) {
297                if (ff->flags & FO_EXCLUDE) {
298                   Dmsg2(dbglvl, "Exclude wildfile: %s file=%s\n", (char *)fo->wildfile.get(k),
299                      ff->fname);
300                   return false;       /* reject file */
301                }
302                return true;           /* accept file */
303             }
304          }
305
306          for (k=0; k<fo->wildbase.size(); k++) {
307             if (match_func((char *)fo->wildbase.get(k), basename, fnmode|fnm_flags) == 0) {
308                if (ff->flags & FO_EXCLUDE) {
309                   Dmsg2(dbglvl, "Exclude wildbase: %s file=%s\n", (char *)fo->wildbase.get(k),
310                      basename);
311                   return false;       /* reject file */
312                }
313                return true;           /* accept file */
314             }
315          }
316       }
317       for (k=0; k<fo->wild.size(); k++) {
318          if (match_func((char *)fo->wild.get(k), ff->fname, fnmode|fnm_flags) == 0) {
319             if (ff->flags & FO_EXCLUDE) {
320                Dmsg2(dbglvl, "Exclude wild: %s file=%s\n", (char *)fo->wild.get(k),
321                   ff->fname);
322                return false;          /* reject file */
323             }
324             return true;              /* accept file */
325          }
326       }
327       if (S_ISDIR(ff->statp.st_mode)) {
328          for (k=0; k<fo->regexdir.size(); k++) {
329             const int nmatch = 30;
330             regmatch_t pmatch[nmatch];
331             if (regexec((regex_t *)fo->regexdir.get(k), ff->fname, nmatch, pmatch,  0) == 0) {
332                if (ff->flags & FO_EXCLUDE) {
333                   return false;       /* reject file */
334                }
335                return true;           /* accept file */
336             }
337          }
338       } else {
339          for (k=0; k<fo->regexfile.size(); k++) {
340             const int nmatch = 30;
341             regmatch_t pmatch[nmatch];
342             if (regexec((regex_t *)fo->regexfile.get(k), ff->fname, nmatch, pmatch,  0) == 0) {
343                if (ff->flags & FO_EXCLUDE) {
344                   return false;       /* reject file */
345                }
346                return true;           /* accept file */
347             }
348          }
349       }
350       for (k=0; k<fo->regex.size(); k++) {
351          const int nmatch = 30;
352          regmatch_t pmatch[nmatch];
353          if (regexec((regex_t *)fo->regex.get(k), ff->fname, nmatch, pmatch,  0) == 0) {
354             if (ff->flags & FO_EXCLUDE) {
355                return false;          /* reject file */
356             }
357             return true;              /* accept file */
358          }
359       }
360       /*
361        * If we have an empty Options clause with exclude, then
362        *  exclude the file
363        */
364       if (ff->flags & FO_EXCLUDE &&
365           fo->regex.size() == 0     && fo->wild.size() == 0 &&
366           fo->regexdir.size() == 0  && fo->wilddir.size() == 0 &&
367           fo->regexfile.size() == 0 && fo->wildfile.size() == 0 &&
368           fo->wildbase.size() == 0) {
369          return false;              /* reject file */
370       }
371    }
372
373    /* Now apply the Exclude { } directive */
374    for (i=0; i<fileset->exclude_list.size(); i++) {
375       findINCEXE *incexe = (findINCEXE *)fileset->exclude_list.get(i);
376       for (j=0; j<incexe->opts_list.size(); j++) {
377          findFOPTS *fo = (findFOPTS *)incexe->opts_list.get(j);
378          fnm_flags = (fo->flags & FO_IGNORECASE) ? FNM_CASEFOLD : 0;
379          for (k=0; k<fo->wild.size(); k++) {
380             if (fnmatch((char *)fo->wild.get(k), ff->fname, fnmode|fnm_flags) == 0) {
381                Dmsg1(dbglvl, "Reject wild1: %s\n", ff->fname);
382                return false;          /* reject file */
383             }
384          }
385       }
386       fnm_flags = (incexe->current_opts != NULL && incexe->current_opts->flags & FO_IGNORECASE)
387              ? FNM_CASEFOLD : 0;
388       dlistString *node;
389       foreach_dlist(node, &incexe->name_list) {
390          char *fname = node->c_str();
391          if (fnmatch(fname, ff->fname, fnmode|fnm_flags) == 0) {
392             Dmsg1(dbglvl, "Reject wild2: %s\n", ff->fname);
393             return false;          /* reject file */
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
403  *    the file is included.
404  */
405 static int our_callback(JCR *jcr, FF_PKT *ff, bool top_level)
406 {
407    if (top_level) {
408       return ff->file_save(jcr, ff, top_level);   /* accept file */
409    }
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->file_save(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 (accept_file(ff)) {
437          return ff->file_save(jcr, ff, top_level);
438       } else {
439          Dmsg1(dbglvl, "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 /*
451  * Terminate find_files() and release
452  * all allocated memory
453  */
454 int
455 term_find_files(FF_PKT *ff)
456 {
457    int hard_links;
458
459    free_pool_memory(ff->sys_fname);
460    if (ff->fname_save) {
461       free_pool_memory(ff->fname_save);
462    }
463    if (ff->link_save) {
464       free_pool_memory(ff->link_save);
465    }
466    if (ff->ignoredir_fname) {
467       free_pool_memory(ff->ignoredir_fname);
468    }
469    if (ff->snap_fname) {
470       free_pool_memory(ff->snap_fname);
471    }
472    if (ff->snap_top_fname) {
473       free_pool_memory(ff->snap_top_fname);
474    }
475    if (ff->mtab_list) {
476       delete ff->mtab_list;
477    }
478    hard_links = term_find_one(ff);
479    free(ff);
480    return hard_links;
481 }