]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/findlib/find.c
Allow plugin to backup a directory
[bacula/bacula] / bacula / src / findlib / find.c
1 /*
2    Bacula® - The Network Backup Solution
3
4    Copyright (C) 2000-2012 Free Software Foundation Europe e.V.
5
6    The main author of Bacula is Kern Sibbald, with contributions from
7    many others, a complete list can be found in the file AUTHORS.
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    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    Bacula® is a registered trademark of Kern Sibbald.
24    The licensor of Bacula is the Free Software Foundation Europe
25    (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zürich,
26    Switzerland, email:ftf@fsfeurope.org.
27 */
28 /*
29  * Main routine for finding files on a file system.
30  *  The heart of the work to find the files on the
31  *    system is done in find_one.c. Here we have the
32  *    higher level control as well as the matching
33  *    routines for the new syntax Options resource.
34  *
35  *  Kern E. Sibbald, MM
36  *
37  */
38
39
40 #include "bacula.h"
41 #include "find.h"
42
43 static const int dbglvl = 450;
44
45 int32_t name_max;              /* filename max length */
46 int32_t path_max;              /* path name max length */
47
48 #ifdef DEBUG
49 #undef bmalloc
50 #define bmalloc(x) sm_malloc(__FILE__, __LINE__, x)
51 #endif
52 static int our_callback(JCR *jcr, FF_PKT *ff, bool top_level);
53 static bool accept_file(FF_PKT *ff);
54
55 static const int fnmode = 0;
56
57 /*
58  * Initialize the find files "global" variables
59  */
60 FF_PKT *init_find_files()
61 {
62   FF_PKT *ff;
63
64   ff = (FF_PKT *)bmalloc(sizeof(FF_PKT));
65   memset(ff, 0, sizeof(FF_PKT));
66
67   ff->sys_fname = get_pool_memory(PM_FNAME);
68
69    /* Get system path and filename maximum lengths */
70    path_max = pathconf(".", _PC_PATH_MAX);
71    if (path_max < 2048) {
72       path_max = 2048;
73    }
74
75    name_max = pathconf(".", _PC_NAME_MAX);
76    if (name_max < 2048) {
77       name_max = 2048;
78    }
79    path_max++;                        /* add for EOS */
80    name_max++;                        /* add for EOS */
81
82   Dmsg1(dbglvl, "init_find_files ff=%p\n", ff);
83   return ff;
84 }
85
86 /*
87  * Set find_files options. For the moment, we only
88  * provide for full/incremental saves, and setting
89  * of save_time. For additional options, see above
90  */
91 void
92 set_find_options(FF_PKT *ff, int incremental, time_t save_time)
93 {
94   Dmsg0(dbglvl, "Enter set_find_options()\n");
95   ff->incremental = incremental;
96   ff->save_time = save_time;
97   Dmsg0(dbglvl, "Leave set_find_options()\n");
98 }
99
100 void
101 set_find_changed_function(FF_PKT *ff, bool check_fct(JCR *jcr, FF_PKT *ff))
102 {
103    Dmsg0(dbglvl, "Enter set_find_changed_function()\n");
104    ff->check_fct = check_fct;
105 }
106
107 /*
108  * For VSS we need to know which windows drives
109  * are used, because we create a snapshot of all used
110  * drives before operation
111  *
112  * the function returns the number of used drives and
113  * fills "drives" with up to 26 (A..Z) drive names
114  *
115  */
116 int
117 get_win32_driveletters(FF_PKT *ff, char* szDrives)
118 {
119    /* szDrives must be at least 27 bytes long */
120
121 #if !defined(HAVE_WIN32)
122    return 0;
123 #endif
124    int nCount;
125    /*
126     * Can be already filled by plugin, so check that all
127     *   letters are in upper case. There should be no duplicates.
128     */
129    for (nCount = 0; nCount < 27 && szDrives[nCount] ; nCount++) {
130       szDrives[nCount] = toupper(szDrives[nCount]);
131    }
132
133    findFILESET *fileset = ff->fileset;
134    if (fileset) {
135       int i;
136       dlistString *node;
137       
138       for (i=0; i<fileset->include_list.size(); i++) {
139          findINCEXE *incexe = (findINCEXE *)fileset->include_list.get(i);
140          
141          /* look through all files and check */
142          foreach_dlist(node, &incexe->name_list) {
143             char *fname = node->c_str();
144             /* fname should match x:/ */
145             if (strlen(fname) >= 2 && B_ISALPHA(fname[0]) 
146                && fname[1] == ':') {
147                
148                /* always add in uppercase */
149                char ch = toupper(fname[0]);
150                /* if not found in string, add drive letter */
151                if (!strchr(szDrives,ch)) {
152                   szDrives[nCount] = ch;
153                   szDrives[nCount+1] = 0;
154                   nCount++;
155                }                                
156             }            
157          }
158       }
159    }
160    return nCount;
161 }
162
163 /*
164  * Call this subroutine with a callback subroutine as the first
165  * argument and a packet as the second argument, this packet
166  * will be passed back to the callback subroutine as the last
167  * argument.
168  *
169  */
170 int
171 find_files(JCR *jcr, FF_PKT *ff, int file_save(JCR *jcr, FF_PKT *ff_pkt, bool top_level),
172            int plugin_save(JCR *jcr, FF_PKT *ff_pkt, bool top_level)) 
173 {
174    ff->file_save = file_save;
175    ff->plugin_save = plugin_save;
176
177    /* This is the new way */
178    findFILESET *fileset = ff->fileset;
179    if (fileset) {
180       int i, j;
181       /* TODO: We probably need be move the initialization in the fileset loop,
182        * at this place flags options are "concatenated" accross Include {} blocks
183        * (not only Options{} blocks inside a Include{})
184        */
185       ff->flags = 0;
186       for (i=0; i<fileset->include_list.size(); i++) {
187          findINCEXE *incexe = (findINCEXE *)fileset->include_list.get(i);
188          fileset->incexe = incexe;
189
190          strcpy(ff->VerifyOpts, "V");
191          strcpy(ff->AccurateOpts, "Cmcs");  /* mtime+ctime+size by default */
192          strcpy(ff->BaseJobOpts, "Jspug5"); /* size+perm+user+group+chk  */
193
194          /*
195           * By setting all options, we in effect OR the global options
196           *   which is what we want.
197           */
198          for (j=0; j<incexe->opts_list.size(); j++) {
199             findFOPTS *fo = (findFOPTS *)incexe->opts_list.get(j);
200             ff->flags |= fo->flags;
201             ff->Compress_algo = fo->Compress_algo;
202             ff->Compress_level = fo->Compress_level;
203             ff->strip_path = fo->strip_path;
204             ff->fstypes = fo->fstype;
205             ff->drivetypes = fo->drivetype;
206             ff->plugin = fo->plugin; /* TODO: generate a plugin event ? */
207             ff->opt_plugin = (ff->plugin != NULL)? true : false;
208             bstrncat(ff->VerifyOpts, fo->VerifyOpts, sizeof(ff->VerifyOpts)); /* TODO: Concat or replace? */
209             if (fo->AccurateOpts[0]) {
210                bstrncpy(ff->AccurateOpts, fo->AccurateOpts, sizeof(ff->AccurateOpts));
211             }
212             if (fo->BaseJobOpts[0]) {
213                bstrncpy(ff->BaseJobOpts, fo->BaseJobOpts, sizeof(ff->BaseJobOpts));
214             }
215          }
216          Dmsg4(50, "Verify=<%s> Accurate=<%s> BaseJob=<%s> flags=<%d>\n", 
217                ff->VerifyOpts, ff->AccurateOpts, ff->BaseJobOpts, ff->flags);
218          dlistString *node;
219          foreach_dlist(node, &incexe->name_list) {
220             char *fname = node->c_str();
221             Dmsg1(dbglvl, "F %s\n", fname);
222             ff->top_fname = fname;
223             if (find_one_file(jcr, ff, our_callback, ff->top_fname, (dev_t)-1, true) == 0) {
224                return 0;                  /* error return */
225             }
226             if (job_canceled(jcr)) {
227                return 0;
228             }
229          }
230          foreach_dlist(node, &incexe->plugin_list) {
231             char *fname = node->c_str();
232             if (!plugin_save) {
233                Jmsg(jcr, M_FATAL, 0, _("Plugin: \"%s\" not found.\n"), fname);
234                return 0;
235             }
236             Dmsg1(dbglvl, "PluginCommand: %s\n", fname);
237             ff->top_fname = fname;
238             ff->cmd_plugin = true;
239             plugin_save(jcr, ff, true);
240             ff->cmd_plugin = false;
241             if (job_canceled(jcr)) {
242                return 0;
243             }
244          }
245       }
246    }
247    return 1;
248 }
249
250 /*
251  * Test if the currently selected directory (in ff->fname) is
252  *  explicitly in the Include list or explicitly in the Exclude 
253  *  list.
254  */
255 bool is_in_fileset(FF_PKT *ff)
256 {
257    dlistString *node;
258    char *fname;
259    int i;
260    findINCEXE *incexe;
261    findFILESET *fileset = ff->fileset;
262    if (fileset) {
263       for (i=0; i<fileset->include_list.size(); i++) {
264          incexe = (findINCEXE *)fileset->include_list.get(i);
265          foreach_dlist(node, &incexe->name_list) {
266             fname = node->c_str();
267             Dmsg2(dbglvl, "Inc fname=%s ff->fname=%s\n", fname, ff->fname);
268             if (strcmp(fname, ff->fname) == 0) {
269                return true;
270             }
271          }
272       }
273       for (i=0; i<fileset->exclude_list.size(); i++) {
274          incexe = (findINCEXE *)fileset->exclude_list.get(i);
275          foreach_dlist(node, &incexe->name_list) {
276             fname = node->c_str();
277             Dmsg2(dbglvl, "Exc fname=%s ff->fname=%s\n", fname, ff->fname);
278             if (strcmp(fname, ff->fname) == 0) {
279                return true;
280             }
281          }
282       }
283    }
284    return false;
285 }
286
287
288 static bool accept_file(FF_PKT *ff)
289 {
290    int i, j, k;
291    int fnm_flags;
292    findFILESET *fileset = ff->fileset;
293    findINCEXE *incexe = fileset->incexe;
294    const char *basename;
295    int (*match_func)(const char *pattern, const char *string, int flags);
296
297    Dmsg1(dbglvl, "enter accept_file: fname=%s\n", ff->fname);
298    if (ff->flags & FO_ENHANCEDWILD) {
299 //    match_func = enh_fnmatch;
300       match_func = fnmatch;
301       if ((basename = last_path_separator(ff->fname)) != NULL)
302          basename++;
303       else
304          basename = ff->fname;
305    } else {
306       match_func = fnmatch;
307       basename = ff->fname;
308    }
309
310    for (j = 0; j < incexe->opts_list.size(); j++) {
311       findFOPTS *fo = (findFOPTS *)incexe->opts_list.get(j);
312       ff->flags = fo->flags;
313       ff->Compress_algo = fo->Compress_algo;
314       ff->Compress_level = fo->Compress_level;
315       ff->fstypes = fo->fstype;
316       ff->drivetypes = fo->drivetype;
317
318       fnm_flags = (ff->flags & FO_IGNORECASE) ? FNM_CASEFOLD : 0;
319       fnm_flags |= (ff->flags & FO_ENHANCEDWILD) ? FNM_PATHNAME : 0;
320
321       if (S_ISDIR(ff->statp.st_mode)) {
322          for (k=0; k<fo->wilddir.size(); k++) {
323             if (match_func((char *)fo->wilddir.get(k), ff->fname, fnmode|fnm_flags) == 0) {
324                if (ff->flags & FO_EXCLUDE) {
325                   Dmsg2(dbglvl, "Exclude wilddir: %s file=%s\n", (char *)fo->wilddir.get(k),
326                      ff->fname);
327                   return false;       /* reject dir */
328                }
329                return true;           /* accept dir */
330             }
331          }
332       } else {
333          for (k=0; k<fo->wildfile.size(); k++) {
334             if (match_func((char *)fo->wildfile.get(k), ff->fname, fnmode|fnm_flags) == 0) {
335                if (ff->flags & FO_EXCLUDE) {
336                   Dmsg2(dbglvl, "Exclude wildfile: %s file=%s\n", (char *)fo->wildfile.get(k),
337                      ff->fname);
338                   return false;       /* reject file */
339                }
340                return true;           /* accept file */
341             }
342          }
343
344          for (k=0; k<fo->wildbase.size(); k++) {
345             if (match_func((char *)fo->wildbase.get(k), basename, fnmode|fnm_flags) == 0) {
346                if (ff->flags & FO_EXCLUDE) {
347                   Dmsg2(dbglvl, "Exclude wildbase: %s file=%s\n", (char *)fo->wildbase.get(k),
348                      basename);
349                   return false;       /* reject file */
350                }
351                return true;           /* accept file */
352             }
353          }
354       }
355       for (k=0; k<fo->wild.size(); k++) {
356          if (match_func((char *)fo->wild.get(k), ff->fname, fnmode|fnm_flags) == 0) {
357             if (ff->flags & FO_EXCLUDE) {
358                Dmsg2(dbglvl, "Exclude wild: %s file=%s\n", (char *)fo->wild.get(k),
359                   ff->fname);
360                return false;          /* reject file */
361             }
362             return true;              /* accept file */
363          }
364       }
365       if (S_ISDIR(ff->statp.st_mode)) {
366          for (k=0; k<fo->regexdir.size(); k++) {
367             const int nmatch = 30;
368             regmatch_t pmatch[nmatch];
369             if (regexec((regex_t *)fo->regexdir.get(k), ff->fname, nmatch, pmatch,  0) == 0) {
370                if (ff->flags & FO_EXCLUDE) {
371                   return false;       /* reject file */
372                }
373                return true;           /* accept file */
374             }
375          }
376       } else {
377          for (k=0; k<fo->regexfile.size(); k++) {
378             const int nmatch = 30;
379             regmatch_t pmatch[nmatch];
380             if (regexec((regex_t *)fo->regexfile.get(k), ff->fname, nmatch, pmatch,  0) == 0) {
381                if (ff->flags & FO_EXCLUDE) {
382                   return false;       /* reject file */
383                }
384                return true;           /* accept file */
385             }
386          }
387       }
388       for (k=0; k<fo->regex.size(); k++) {
389          const int nmatch = 30;
390          regmatch_t pmatch[nmatch];
391          if (regexec((regex_t *)fo->regex.get(k), ff->fname, nmatch, pmatch,  0) == 0) {
392             if (ff->flags & FO_EXCLUDE) {
393                return false;          /* reject file */
394             }
395             return true;              /* accept file */
396          }
397       }
398       /*
399        * If we have an empty Options clause with exclude, then
400        *  exclude the file
401        */
402       if (ff->flags & FO_EXCLUDE &&
403           fo->regex.size() == 0     && fo->wild.size() == 0 &&
404           fo->regexdir.size() == 0  && fo->wilddir.size() == 0 &&
405           fo->regexfile.size() == 0 && fo->wildfile.size() == 0 &&
406           fo->wildbase.size() == 0) {
407          return false;              /* reject file */
408       }
409    }
410
411    /* Now apply the Exclude { } directive */
412    for (i=0; i<fileset->exclude_list.size(); i++) {
413       findINCEXE *incexe = (findINCEXE *)fileset->exclude_list.get(i);
414       for (j=0; j<incexe->opts_list.size(); j++) {
415          findFOPTS *fo = (findFOPTS *)incexe->opts_list.get(j);
416          fnm_flags = (fo->flags & FO_IGNORECASE) ? FNM_CASEFOLD : 0;
417          for (k=0; k<fo->wild.size(); k++) {
418             if (fnmatch((char *)fo->wild.get(k), ff->fname, fnmode|fnm_flags) == 0) {
419                Dmsg1(dbglvl, "Reject wild1: %s\n", ff->fname);
420                return false;          /* reject file */
421             }
422          }
423       }
424       fnm_flags = (incexe->current_opts != NULL && incexe->current_opts->flags & FO_IGNORECASE)
425              ? FNM_CASEFOLD : 0;
426       dlistString *node;
427       foreach_dlist(node, &incexe->name_list) {
428          char *fname = node->c_str();
429          if (fnmatch(fname, ff->fname, fnmode|fnm_flags) == 0) {
430             Dmsg1(dbglvl, "Reject wild2: %s\n", ff->fname);
431             return false;          /* reject file */
432          }
433       }
434    }
435    return true;
436 }
437
438 /*
439  * The code comes here for each file examined.
440  * We filter the files, then call the user's callback if
441  *    the file is included.
442  */
443 static int our_callback(JCR *jcr, FF_PKT *ff, bool top_level)
444 {
445    if (top_level) {
446       return ff->file_save(jcr, ff, top_level);   /* accept file */
447    }
448    switch (ff->type) {
449    case FT_NOACCESS:
450    case FT_NOFOLLOW:
451    case FT_NOSTAT:
452    case FT_NOCHG:
453    case FT_ISARCH:
454    case FT_NORECURSE:
455    case FT_NOFSCHG:
456    case FT_INVALIDFS:
457    case FT_INVALIDDT:
458    case FT_NOOPEN:
459 //    return ff->file_save(jcr, ff, top_level);
460
461    /* These items can be filtered */
462    case FT_LNKSAVED:
463    case FT_REGE:
464    case FT_REG:
465    case FT_LNK:
466    case FT_DIRBEGIN:
467    case FT_DIREND:
468    case FT_RAW:
469    case FT_FIFO:
470    case FT_SPEC:
471    case FT_DIRNOCHG:
472    case FT_REPARSE:
473    case FT_JUNCTION:
474       if (accept_file(ff)) {
475          return ff->file_save(jcr, ff, top_level);
476       } else {
477          Dmsg1(dbglvl, "Skip file %s\n", ff->fname);
478          return -1;                   /* ignore this file */
479       }
480
481    default:
482       Dmsg1(000, "Unknown FT code %d\n", ff->type);
483       return 0;
484    }
485 }
486
487
488 /*
489  * Terminate find_files() and release
490  * all allocated memory
491  */
492 int
493 term_find_files(FF_PKT *ff)
494 {
495    int hard_links;
496
497    free_pool_memory(ff->sys_fname);
498    if (ff->fname_save) {
499       free_pool_memory(ff->fname_save);
500    }
501    if (ff->link_save) {
502       free_pool_memory(ff->link_save);
503    }
504    hard_links = term_find_one(ff);
505    free(ff);
506    return hard_links;
507 }