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