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