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