1 /*
2    BAREOS® - Backup Archiving REcovery Open Sourced
3 
4    Copyright (C) 2011-2011 Free Software Foundation Europe e.V.
5    Copyright (C) 2011-2012 Planets Communications B.V.
6    Copyright (C) 2013-2016 Bareos GmbH & Co. KG
7 
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    Affero 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 /*
24  * Written by Marco van Wieringen, November 2011
25  */
26 /**
27  * @file
28  * Detect fileset shadowing e.g. when an include entry pulls in data
29  * which is already being backuped by another include pattern. Currently
30  * we support both local and global shadowing. Where local shadowing is
31  * when the shadowing occurs within one include block and global when
32  * between multiple include blocks.
33  */
34 
35 #include "include/bareos.h"
36 #include "find.h"
37 #include "findlib/shadowing.h"
38 
39 /**
40  * Check if a certain fileset include pattern shadows another pattern.
41  */
check_include_pattern_shadowing(JobControlRecord * jcr,const char * pattern1,const char * pattern2,bool recursive)42 static inline bool check_include_pattern_shadowing(JobControlRecord *jcr,
43                                                    const char *pattern1,
44                                                    const char *pattern2,
45                                                    bool recursive)
46 {
47    int len1, len2;
48    bool retval = false;
49    struct stat st1, st2;
50 
51    /*
52     * See if one directory shadows the other or if two
53     * files are hardlinked.
54     */
55    if (lstat(pattern1, &st1) != 0) {
56       BErrNo be;
57       Jmsg(jcr, M_WARNING, 0,
58            _("Cannot stat file %s: ERR=%s\n"),
59            pattern1, be.bstrerror());
60       goto bail_out;
61    }
62 
63    if (lstat(pattern2, &st2) != 0) {
64       BErrNo be;
65       Jmsg(jcr, M_WARNING, 0,
66            _("Cannot stat file %s: ERR=%s\n"),
67            pattern2, be.bstrerror());
68       goto bail_out;
69    }
70 
71    if (S_ISDIR(st1.st_mode) && S_ISDIR(st2.st_mode)) {
72       /*
73        * Only check shadowing of directories when recursion is turned on.
74        */
75       if (recursive ) {
76          len1 = strlen(pattern1);
77          len2 = strlen(pattern2);
78 
79          /*
80           * See if one pattern shadows the other.
81           */
82          if (((len1 < len2 && pattern1[len1] == '\0' && IsPathSeparator(pattern2[len1])) ||
83               (len1 > len2 && IsPathSeparator(pattern1[len2]) && pattern1[len1] == '\0')) &&
84              bstrncmp(pattern1, pattern2, MIN(len1, len2))) {
85             /*
86              * If both directories have the same st_dev they shadow
87              * each other e.g. are not on separate filesystems.
88              */
89             if (st1.st_dev == st2.st_dev) {
90                retval = true;
91             }
92          }
93       }
94    } else {
95       /*
96        * See if the two files are hardlinked.
97        */
98       if (st1.st_dev == st2.st_dev &&
99           st1.st_ino == st2.st_ino) {
100          retval = true;
101       }
102    }
103 
104 bail_out:
105    return retval;
106 }
107 
108 /**
109  * See if recursion is on or off for a specific include block.
110  * We use the data from the default options block.
111  * e.g. the last option block in the include block.
112  */
IncludeBlockIsRecursive(findIncludeExcludeItem * incexe)113 static inline bool IncludeBlockIsRecursive(findIncludeExcludeItem *incexe)
114 {
115    int i;
116    findFOPTS *fo;
117    bool recursive = true;
118 
119    for (i = 0; i < incexe->opts_list.size(); i++) {
120       fo = (findFOPTS *)incexe->opts_list.get(i);
121 
122       recursive = !BitIsSet(FO_NO_RECURSION, fo->flags);
123    }
124 
125    return recursive;
126 }
127 
128 /**
129  * See if an options block of an include block has any wildcard
130  * or regex settings which are not used for excluding.
131  */
IncludeBlockHasPatterns(findIncludeExcludeItem * incexe)132 static inline bool IncludeBlockHasPatterns(findIncludeExcludeItem *incexe)
133 {
134    int i;
135    bool has_find_patterns = false;
136    findFOPTS *fo;
137 
138    for (i = 0; i < incexe->opts_list.size(); i++) {
139       fo = (findFOPTS *)incexe->opts_list.get(i);
140 
141       /*
142        * See if this is an exclude block.
143        * e.g. exclude = yes is set then we
144        * should still check for shadowing.
145        */
146       if (BitIsSet(FO_EXCLUDE, fo->flags)) {
147          continue;
148       }
149 
150       /*
151        * See if the include block has any interesting
152        * wildcard matching options. We consider the following
153        * as making the shadowing match to difficult:
154        * - regex = entries
155        * - regexdir = entries
156        * - wild = entries
157        * - wildir = entries
158        *
159        * For filename shadowing we only check if files are
160        * hardlinked so we don't take into consideration
161        * - regexfile = entries
162        * - wildfile = entries
163        */
164       if (fo->regex.size() > 0 ||
165           fo->regexdir.size() > 0 ||
166           fo->wild.size() > 0 ||
167           fo->wilddir.size() > 0) {
168          has_find_patterns = true;
169       }
170    }
171 
172    return has_find_patterns;
173 }
174 
175 /**
176  * For this include block lookup the shadow checking type requested.
177  * We use the data from the default options block.
178  * e.g. the last option block in the include block.
179  */
IncludeBlockGetShadowType(findIncludeExcludeItem * incexe)180 static inline b_fileset_shadow_type IncludeBlockGetShadowType(findIncludeExcludeItem *incexe)
181 {
182    int i;
183    findFOPTS *fo;
184    b_fileset_shadow_type shadow_type = check_shadow_none;
185 
186    for (i = 0; i < incexe->opts_list.size(); i++) {
187       fo = (findFOPTS *)incexe->opts_list.get(i);
188 
189       shadow_type = fo->shadow_type;
190    }
191    return shadow_type;
192 }
193 
194 /**
195  * See if there is any local shadowing within an include block.
196  */
check_local_fileset_shadowing(JobControlRecord * jcr,findIncludeExcludeItem * incexe,bool remove)197 static void check_local_fileset_shadowing(JobControlRecord *jcr,
198                                           findIncludeExcludeItem *incexe,
199                                           bool remove)
200 {
201    dlistString *str1, *str2, *next;
202    bool recursive;
203 
204    /*
205     * See if this is a recursive include block.
206     */
207    recursive = IncludeBlockIsRecursive(incexe);
208 
209    /*
210     * Loop over all entries in the name_list
211     * and compare them against all next entries
212     * after the one we are currently examining.
213     * This way we only check shadowing only once.
214     */
215    str1 = (dlistString *)incexe->name_list.first();
216    while (str1) {
217       str2 = (dlistString *)incexe->name_list.next(str1);
218       while (str1 && str2) {
219          if (check_include_pattern_shadowing(jcr,
220                                              str1->c_str(),
221                                              str2->c_str(),
222                                              recursive)) {
223             /*
224              * See what entry shadows the other, the longest entry
225              * shadow the shorter one.
226              */
227             if (strlen(str1->c_str()) < strlen(str2->c_str())) {
228                if (remove) {
229                   /*
230                    * Pattern2 is longer then Pattern1 e.g. the include block patterns
231                    * are probably sorted right. This is the easiest case where we just
232                    * remove the entry from the list and continue.
233                    */
234                   Jmsg(jcr, M_WARNING, 0,
235                        _("Fileset include block entry %s shadows %s removing it from fileset\n"),
236                        str2->c_str(), str1->c_str());
237                   next = (dlistString *)incexe->name_list.next(str2);
238                   incexe->name_list.remove(str2);
239                   str2 = next;
240                   continue;
241                } else {
242                   Jmsg(jcr, M_WARNING, 0,
243                        _("Fileset include block entry %s shadows %s\n"),
244                        str2->c_str(), str1->c_str());
245                }
246             } else {
247                if (remove) {
248                   /*
249                    * Pattern1 is longer then Pattern2 e.g. the include block patterns
250                    * are not sorted right and probably reverse. This is a bit more difficult.
251                    * We remove the first pattern from the list and restart the shadow scan.
252                    * By setting str1 to NULL we force a rescan as the next method of the dlist
253                    * will start at the first entry of the dlist again.
254                    */
255                   Jmsg(jcr, M_WARNING, 0,
256                        _("Fileset include block entry %s shadows %s removing it from fileset\n"),
257                        str1->c_str(), str2->c_str());
258                   incexe->name_list.remove(str1);
259                   str1 = NULL;
260                   continue;
261                } else {
262                   Jmsg(jcr, M_WARNING, 0,
263                        _("Fileset include block entry %s shadows %s\n"),
264                        str1->c_str(), str2->c_str());
265                }
266             }
267          }
268          str2 = (dlistString *)incexe->name_list.next(str2);
269       }
270       str1 = (dlistString *)incexe->name_list.next(str1);
271    }
272 }
273 
274 /**
275  * See if there is any local shadowing within an include block or
276  * any global shadowing between include blocks.
277  */
check_global_fileset_shadowing(JobControlRecord * jcr,findFILESET * fileset,bool remove)278 static inline void check_global_fileset_shadowing(JobControlRecord *jcr,
279                                                   findFILESET *fileset,
280                                                   bool remove)
281 {
282    int i, j;
283    bool local_recursive, global_recursive;
284    findIncludeExcludeItem *current, *compare_against;
285    dlistString *str1, *str2, *next;
286 
287    /*
288     * Walk all the include blocks and see if there
289     * is any shadowing between the different sets.
290     */
291    for (i = 0; i < fileset->include_list.size(); i++) {
292       current = (findIncludeExcludeItem *)fileset->include_list.get(i);
293 
294       /*
295        * See if there is any local shadowing.
296        */
297       check_local_fileset_shadowing(jcr, current, remove);
298 
299       /*
300        * Only check global shadowing against this include block
301        * when it doesn't have any patterns. Testing if a fileset
302        * shadows the other with patterns is next to impossible
303        * without comparing the matching criteria which can be
304        * in so many forms we forget it all together. When you
305        * are smart enough to create include/exclude patterns
306        * we also don't provide you with basic stop gap measures.
307        */
308       if (IncludeBlockHasPatterns(current)) {
309          continue;
310       }
311 
312       /*
313        * Now compare this block against any include block after this one.
314        * We can shortcut as we don't have to start at the beginning of
315        * the list again because we compare all sets against each other
316        * this way anyhow. e.g. we start with set 1 against 2 .. x and
317        * then 2 against 3 .. x (No need to compare 2 against 1 again
318        * as we did that in the first run already.
319        *
320        * See if this is a recursive include block.
321        */
322       local_recursive = IncludeBlockIsRecursive(current);
323       for (j = i + 1; j < fileset->include_list.size(); j++) {
324          compare_against = (findIncludeExcludeItem *)fileset->include_list.get(j);
325 
326          /*
327           * Only check global shadowing against this include block
328           * when it doesn't have any patterns.
329           */
330          if (IncludeBlockHasPatterns(compare_against)) {
331             continue;
332          }
333 
334          /*
335           * See if both include blocks are recursive.
336           */
337          global_recursive = (local_recursive &&
338                              IncludeBlockIsRecursive(compare_against));
339 
340          /*
341           * Walk over the filename list and compare it
342           * against the other entry from the other list.
343           */
344          str1 = (dlistString *)current->name_list.first();
345          while (str1) {
346             str2 = (dlistString *)compare_against->name_list.first();
347             while (str1 && str2) {
348                if (check_include_pattern_shadowing(jcr,
349                                                    str1->c_str(),
350                                                    str2->c_str(),
351                                                    global_recursive)) {
352                   /*
353                    * See what entry shadows the other, the longest entry
354                    * shadow the shorter one.
355                    */
356                   if (strlen(str1->c_str()) < strlen(str2->c_str())) {
357                      if (remove) {
358                         /*
359                          * Pattern2 is longer then Pattern1 e.g. the include block patterns
360                          * are probably sorted right. This is the easiest case where we just
361                          * remove the entry from the list and continue.
362                          */
363                         Jmsg(jcr, M_WARNING, 0,
364                              _("Fileset include block entry %s shadows %s removing it from fileset\n"),
365                              str2->c_str(), str1->c_str());
366                         next = (dlistString *)compare_against->name_list.next(str2);
367                         compare_against->name_list.remove(str2);
368                         str2 = next;
369                         continue;
370                      } else {
371                         Jmsg(jcr, M_WARNING, 0,
372                              _("Fileset include block entry %s shadows %s\n"),
373                              str2->c_str(), str1->c_str());
374                      }
375                   } else {
376                      if (remove) {
377                         /*
378                          * Pattern1 is longer then Pattern2 e.g. the include block patterns
379                          * are not sorted right and probably reverse. This is a bit more
380                          * difficult. We remove the first pattern from the list and restart
381                          * the shadow scan. By setting str1 to NULL we force a rescan as the
382                          * next method of the dlist will start at the first entry of the
383                          * dlist again.
384                          */
385                         Jmsg(jcr, M_WARNING, 0,
386                              _("Fileset include block entry %s shadows %s removing it from fileset\n"),
387                              str1->c_str(), str2->c_str());
388                         current->name_list.remove(str1);
389                         str1 = NULL;
390                         continue;
391                      } else {
392                         Jmsg(jcr, M_WARNING, 0,
393                              _("Fileset include block entry %s shadows %s\n"),
394                              str1->c_str(), str2->c_str());
395                      }
396                   }
397                }
398                str2 = (dlistString *)compare_against->name_list.next(str2);
399             }
400             str1 = (dlistString *)current->name_list.next(str1);
401          }
402       }
403    }
404 }
405 
CheckIncludeListShadowing(JobControlRecord * jcr,findFILESET * fileset)406 void CheckIncludeListShadowing(JobControlRecord *jcr, findFILESET *fileset)
407 {
408    int i;
409    findIncludeExcludeItem *incexe;
410    b_fileset_shadow_type shadow_type;
411 
412    /*
413     * Walk the list of include blocks.
414     */
415    for (i = 0; i < fileset->include_list.size(); i++) {
416       incexe = (findIncludeExcludeItem *)fileset->include_list.get(i);
417 
418       /*
419        * See if the shadow check option is enabled for this
420        * include block. If not just continue with the next include block.
421        */
422       shadow_type = IncludeBlockGetShadowType(incexe);
423       switch (shadow_type) {
424       case check_shadow_none:
425          continue;
426       case check_shadow_local_warn:
427       case check_shadow_local_remove:
428          /*
429           * Check only for local shadowing within the same include block.
430           */
431          check_local_fileset_shadowing(jcr, incexe,
432                                        shadow_type == check_shadow_local_remove);
433          break;
434       case check_shadow_global_warn:
435       case check_shadow_global_remove:
436          /*
437           * Check global shadowing over more then one include block.
438           * We only need to perform the global check once because we
439           * visit all entries in that scan so return after returning
440           * from the function.
441           */
442          check_global_fileset_shadowing(jcr, fileset,
443                                         shadow_type == check_shadow_global_remove);
444          return;
445       }
446    }
447 }
448