]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/findlib/find.c
Make new prune algo to work with backup
[bacula/bacula] / bacula / src / findlib / find.c
1 /*
2    Bacula® - The Network Backup Solution
3
4    Copyright (C) 2000-2010 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 two of the GNU 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 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
125    szDrives[0] = 0; /* make empty */
126    int nCount = 0;
127     
128    findFILESET *fileset = ff->fileset;
129    if (fileset) {
130       int i;
131       dlistString *node;
132       
133       for (i=0; i<fileset->include_list.size(); i++) {
134          findINCEXE *incexe = (findINCEXE *)fileset->include_list.get(i);
135          
136          /* look through all files and check */
137          foreach_dlist(node, &incexe->name_list) {
138             char *fname = node->c_str();
139             /* fname should match x:/ */
140             if (strlen(fname) >= 2 && B_ISALPHA(fname[0]) 
141                && fname[1] == ':') {
142                
143                /* always add in uppercase */
144                char ch = toupper(fname[0]);
145                /* if not found in string, add drive letter */
146                if (!strchr(szDrives,ch)) {
147                   szDrives[nCount] = ch;
148                   szDrives[nCount+1] = 0;
149                   nCount++;
150                }                                
151             }            
152          }
153       }
154    }
155    return nCount;
156 }
157
158 /*
159  * Call this subroutine with a callback subroutine as the first
160  * argument and a packet as the second argument, this packet
161  * will be passed back to the callback subroutine as the last
162  * argument.
163  *
164  */
165 int
166 find_files(JCR *jcr, FF_PKT *ff, int file_save(JCR *jcr, FF_PKT *ff_pkt, bool top_level),
167            int plugin_save(JCR *jcr, FF_PKT *ff_pkt, bool top_level)) 
168 {
169    ff->file_save = file_save;
170    ff->plugin_save = plugin_save;
171
172    /* This is the new way */
173    findFILESET *fileset = ff->fileset;
174    if (fileset) {
175       int i, j;
176       ff->flags = 0;
177       ff->VerifyOpts[0] = 'V';
178       ff->VerifyOpts[1] = 0;
179       strcpy(ff->AccurateOpts, "Cmcs");  /* mtime+ctime+size by default */
180       strcpy(ff->BaseJobOpts, "Jspug5"); /* size+perm+user+group+chk  */
181       for (i=0; i<fileset->include_list.size(); i++) {
182          findINCEXE *incexe = (findINCEXE *)fileset->include_list.get(i);
183          fileset->incexe = incexe;
184          /*
185           * By setting all options, we in effect OR the global options
186           *   which is what we want.
187           */
188          for (j=0; j<incexe->opts_list.size(); j++) {
189             findFOPTS *fo = (findFOPTS *)incexe->opts_list.get(j);
190             ff->flags |= fo->flags;
191             ff->GZIP_level = fo->GZIP_level;
192             ff->strip_path = fo->strip_path;
193             ff->fstypes = fo->fstype;
194             ff->drivetypes = fo->drivetype;
195             bstrncat(ff->VerifyOpts, fo->VerifyOpts, sizeof(ff->VerifyOpts));
196             if (fo->AccurateOpts[0]) {
197                bstrncpy(ff->AccurateOpts, fo->AccurateOpts, sizeof(ff->AccurateOpts));
198             }
199             if (fo->BaseJobOpts[0]) {
200                bstrncpy(ff->BaseJobOpts, fo->BaseJobOpts, sizeof(ff->BaseJobOpts));
201             }
202          }
203          Dmsg3(50, "Verify=<%s> Accurate=<%s> BaseJob=<%s>\n", ff->VerifyOpts, ff->AccurateOpts, ff->BaseJobOpts);
204          dlistString *node;
205          foreach_dlist(node, &incexe->name_list) {
206             char *fname = node->c_str();
207             Dmsg1(dbglvl, "F %s\n", fname);
208             ff->top_fname = fname;
209             if (find_one_file(jcr, ff, our_callback, ff->top_fname, (dev_t)-1, true) == 0) {
210                return 0;                  /* error return */
211             }
212             if (job_canceled(jcr)) {
213                return 0;
214             }
215          }
216          foreach_dlist(node, &incexe->plugin_list) {
217             char *fname = node->c_str();
218             if (!plugin_save) {
219                Jmsg(jcr, M_FATAL, 0, _("Plugin: \"%s\" not found.\n"), fname);
220                return 0;
221             }
222             Dmsg1(dbglvl, "PluginCommand: %s\n", fname);
223             ff->top_fname = fname;
224             ff->cmd_plugin = true;
225             plugin_save(jcr, ff, true);
226             ff->cmd_plugin = false;
227             if (job_canceled(jcr)) {
228                return 0;
229             }
230          }
231       }
232    }
233    return 1;
234 }
235
236 /*
237  * Test if the currently selected directory (in ff->fname) is
238  *  explicitly in the Include list or explicitly in the Exclude 
239  *  list.
240  */
241 bool is_in_fileset(FF_PKT *ff)
242 {
243    dlistString *node;
244    char *fname;
245    int i;
246    findINCEXE *incexe;
247    findFILESET *fileset = ff->fileset;
248    if (fileset) {
249       for (i=0; i<fileset->include_list.size(); i++) {
250          incexe = (findINCEXE *)fileset->include_list.get(i);
251          foreach_dlist(node, &incexe->name_list) {
252             fname = node->c_str();
253             Dmsg2(dbglvl, "Inc fname=%s ff->fname=%s\n", fname, ff->fname);
254             if (strcmp(fname, ff->fname) == 0) {
255                return true;
256             }
257          }
258       }
259       for (i=0; i<fileset->exclude_list.size(); i++) {
260          incexe = (findINCEXE *)fileset->exclude_list.get(i);
261          foreach_dlist(node, &incexe->name_list) {
262             fname = node->c_str();
263             Dmsg2(dbglvl, "Exc fname=%s ff->fname=%s\n", fname, ff->fname);
264             if (strcmp(fname, ff->fname) == 0) {
265                return true;
266             }
267          }
268       }
269    }
270    return false;
271 }
272
273
274 static bool accept_file(FF_PKT *ff)
275 {
276    int i, j, k;
277    int fnm_flags;
278    findFILESET *fileset = ff->fileset;
279    findINCEXE *incexe = fileset->incexe;
280    const char *basename;
281    int (*match_func)(const char *pattern, const char *string, int flags);
282
283    Dmsg1(dbglvl, "enter accept_file: fname=%s\n", ff->fname);
284    if (ff->flags & FO_ENHANCEDWILD) {
285 //    match_func = enh_fnmatch;
286       match_func = fnmatch;
287       if ((basename = last_path_separator(ff->fname)) != NULL)
288          basename++;
289       else
290          basename = ff->fname;
291    } else {
292       match_func = fnmatch;
293       basename = ff->fname;
294    }
295
296    for (j = 0; j < incexe->opts_list.size(); j++) {
297       findFOPTS *fo = (findFOPTS *)incexe->opts_list.get(j);
298       ff->flags = fo->flags;
299       ff->GZIP_level = fo->GZIP_level;
300       ff->fstypes = fo->fstype;
301       ff->drivetypes = fo->drivetype;
302
303       fnm_flags = (ff->flags & FO_IGNORECASE) ? FNM_CASEFOLD : 0;
304       fnm_flags |= (ff->flags & FO_ENHANCEDWILD) ? FNM_PATHNAME : 0;
305
306       if (S_ISDIR(ff->statp.st_mode)) {
307          for (k=0; k<fo->wilddir.size(); k++) {
308             if (match_func((char *)fo->wilddir.get(k), ff->fname, fnmode|fnm_flags) == 0) {
309                if (ff->flags & FO_EXCLUDE) {
310                   Dmsg2(dbglvl, "Exclude wilddir: %s file=%s\n", (char *)fo->wilddir.get(k),
311                      ff->fname);
312                   return false;       /* reject dir */
313                }
314                return true;           /* accept dir */
315             }
316          }
317       } else {
318          for (k=0; k<fo->wildfile.size(); k++) {
319             if (match_func((char *)fo->wildfile.get(k), ff->fname, fnmode|fnm_flags) == 0) {
320                if (ff->flags & FO_EXCLUDE) {
321                   Dmsg2(dbglvl, "Exclude wildfile: %s file=%s\n", (char *)fo->wildfile.get(k),
322                      ff->fname);
323                   return false;       /* reject file */
324                }
325                return true;           /* accept file */
326             }
327          }
328
329          for (k=0; k<fo->wildbase.size(); k++) {
330             if (match_func((char *)fo->wildbase.get(k), basename, fnmode|fnm_flags) == 0) {
331                if (ff->flags & FO_EXCLUDE) {
332                   Dmsg2(dbglvl, "Exclude wildbase: %s file=%s\n", (char *)fo->wildbase.get(k),
333                      basename);
334                   return false;       /* reject file */
335                }
336                return true;           /* accept file */
337             }
338          }
339       }
340       for (k=0; k<fo->wild.size(); k++) {
341          if (match_func((char *)fo->wild.get(k), ff->fname, fnmode|fnm_flags) == 0) {
342             if (ff->flags & FO_EXCLUDE) {
343                Dmsg2(dbglvl, "Exclude wild: %s file=%s\n", (char *)fo->wild.get(k),
344                   ff->fname);
345                return false;          /* reject file */
346             }
347             return true;              /* accept file */
348          }
349       }
350       if (S_ISDIR(ff->statp.st_mode)) {
351          for (k=0; k<fo->regexdir.size(); k++) {
352             const int nmatch = 30;
353             regmatch_t pmatch[nmatch];
354             if (regexec((regex_t *)fo->regexdir.get(k), ff->fname, nmatch, pmatch,  0) == 0) {
355                if (ff->flags & FO_EXCLUDE) {
356                   return false;       /* reject file */
357                }
358                return true;           /* accept file */
359             }
360          }
361       } else {
362          for (k=0; k<fo->regexfile.size(); k++) {
363             const int nmatch = 30;
364             regmatch_t pmatch[nmatch];
365             if (regexec((regex_t *)fo->regexfile.get(k), ff->fname, nmatch, pmatch,  0) == 0) {
366                if (ff->flags & FO_EXCLUDE) {
367                   return false;       /* reject file */
368                }
369                return true;           /* accept file */
370             }
371          }
372       }
373       for (k=0; k<fo->regex.size(); k++) {
374          const int nmatch = 30;
375          regmatch_t pmatch[nmatch];
376          if (regexec((regex_t *)fo->regex.get(k), ff->fname, nmatch, pmatch,  0) == 0) {
377             if (ff->flags & FO_EXCLUDE) {
378                return false;          /* reject file */
379             }
380             return true;              /* accept file */
381          }
382       }
383       /*
384        * If we have an empty Options clause with exclude, then
385        *  exclude the file
386        */
387       if (ff->flags & FO_EXCLUDE &&
388           fo->regex.size() == 0     && fo->wild.size() == 0 &&
389           fo->regexdir.size() == 0  && fo->wilddir.size() == 0 &&
390           fo->regexfile.size() == 0 && fo->wildfile.size() == 0 &&
391           fo->wildbase.size() == 0) {
392          return false;              /* reject file */
393       }
394    }
395
396    /* Now apply the Exclude { } directive */
397    for (i=0; i<fileset->exclude_list.size(); i++) {
398       findINCEXE *incexe = (findINCEXE *)fileset->exclude_list.get(i);
399       for (j=0; j<incexe->opts_list.size(); j++) {
400          findFOPTS *fo = (findFOPTS *)incexe->opts_list.get(j);
401          fnm_flags = (fo->flags & FO_IGNORECASE) ? FNM_CASEFOLD : 0;
402          for (k=0; k<fo->wild.size(); k++) {
403             if (fnmatch((char *)fo->wild.get(k), ff->fname, fnmode|fnm_flags) == 0) {
404                Dmsg1(dbglvl, "Reject wild1: %s\n", ff->fname);
405                return false;          /* reject file */
406             }
407          }
408       }
409       fnm_flags = (incexe->current_opts != NULL && incexe->current_opts->flags & FO_IGNORECASE)
410              ? FNM_CASEFOLD : 0;
411       dlistString *node;
412       foreach_dlist(node, &incexe->name_list) {
413          char *fname = node->c_str();
414          if (fnmatch(fname, ff->fname, fnmode|fnm_flags) == 0) {
415             Dmsg1(dbglvl, "Reject wild2: %s\n", ff->fname);
416             return false;          /* reject file */
417          }
418       }
419    }
420    return true;
421 }
422
423 /*
424  * The code comes here for each file examined.
425  * We filter the files, then call the user's callback if
426  *    the file is included.
427  */
428 static int our_callback(JCR *jcr, FF_PKT *ff, bool top_level)
429 {
430    if (top_level) {
431       return ff->file_save(jcr, ff, top_level);   /* accept file */
432    }
433    switch (ff->type) {
434    case FT_NOACCESS:
435    case FT_NOFOLLOW:
436    case FT_NOSTAT:
437    case FT_NOCHG:
438    case FT_ISARCH:
439    case FT_NORECURSE:
440    case FT_NOFSCHG:
441    case FT_INVALIDFS:
442    case FT_INVALIDDT:
443    case FT_NOOPEN:
444    case FT_REPARSE:
445 //    return ff->file_save(jcr, ff, top_level);
446
447    /* These items can be filtered */
448    case FT_LNKSAVED:
449    case FT_REGE:
450    case FT_REG:
451    case FT_LNK:
452    case FT_DIRBEGIN:
453    case FT_DIREND:
454    case FT_RAW:
455    case FT_FIFO:
456    case FT_SPEC:
457    case FT_DIRNOCHG:
458       if (accept_file(ff)) {
459          return ff->file_save(jcr, ff, top_level);
460       } else {
461          Dmsg1(dbglvl, "Skip file %s\n", ff->fname);
462          return -1;                   /* ignore this file */
463       }
464
465    default:
466       Dmsg1(000, "Unknown FT code %d\n", ff->type);
467       return 0;
468    }
469 }
470
471
472 /*
473  * Terminate find_files() and release
474  * all allocated memory
475  */
476 int
477 term_find_files(FF_PKT *ff)
478 {
479    int hard_links;
480
481    free_pool_memory(ff->sys_fname);
482    if (ff->fname_save) {
483       free_pool_memory(ff->fname_save);
484    }
485    if (ff->link_save) {
486       free_pool_memory(ff->link_save);
487    }
488    hard_links = term_find_one(ff);
489    free(ff);
490    return hard_links;
491 }