1 /*
2    BAREOS® - Backup Archiving REcovery Open Sourced
3 
4    Copyright (C) 2000-2008 Free Software Foundation Europe e.V.
5    Copyright (C) 2016-2016 Bareos GmbH & Co. KG
6 
7    This program is Free Software; you can redistribute it and/or
8    modify it under the terms of version three of the GNU Affero General Public
9    License as published by the Free Software Foundation and included
10    in the file LICENSE.
11 
12    This program is distributed in the hope that it will be useful, but
13    WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15    Affero General Public License for more details.
16 
17    You should have received a copy of the GNU Affero General Public License
18    along with this program; if not, write to the Free Software
19    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
20    02110-1301, USA.
21 */
22 /*
23  * Kern Sibbald, MM
24  */
25 /**
26  * @file
27  * Test program for find files
28  */
29 
30 #include "include/bareos.h"
31 #include "dird/dird.h"
32 #include "findlib/find.h"
33 #include "lib/mntent_cache.h"
34 #include "ch.h"
35 
36 #if defined(HAVE_WIN32)
37 #define isatty(fd) (fd == 0)
38 #endif
39 
40 using namespace directordaemon;
41 
42 /* Dummy functions */
GeneratePluginEvent(JobControlRecord * jcr,bEventType eventType,void * value)43 void GeneratePluginEvent(JobControlRecord* jcr,
44                          bEventType eventType,
45                          void* value)
46 {
47 }
48 extern bool ParseDirConfig(const char* configfile, int exit_code);
49 
50 /* Global variables */
51 static int num_files = 0;
52 static int max_file_len = 0;
53 static int max_path_len = 0;
54 static int trunc_fname = 0;
55 static int trunc_path = 0;
56 static int attrs = 0;
57 
58 static JobControlRecord* jcr;
59 
60 static int PrintFile(JobControlRecord* jcr, FindFilesPacket* ff, bool);
61 static void CountFiles(FindFilesPacket* ff);
62 static bool CopyFileset(FindFilesPacket* ff, JobControlRecord* jcr);
63 static void SetOptions(findFOPTS* fo, const char* opts);
64 
usage()65 static void usage()
66 {
67   fprintf(
68       stderr,
69       _("\n"
70         "Usage: testfind [-d debug_level] [-] [pattern1 ...]\n"
71         "       -a          print extended attributes (Win32 debug)\n"
72         "       -d <nn>     set debug level to <nn>\n"
73         "       -dt         print timestamp in debug output\n"
74         "       -c          specify config file containing FileSet resources\n"
75         "       -f          specify which FileSet to use\n"
76         "       -?          print this message.\n"
77         "\n"
78         "Patterns are used for file inclusion -- normally directories.\n"
79         "Debug level >= 1 prints each file found.\n"
80         "Debug level >= 10 prints path/file for catalog.\n"
81         "Errors are always printed.\n"
82         "Files/paths truncated is the number of files/paths with len > 255.\n"
83         "Truncation is only in the catalog.\n"
84         "\n"));
85 
86   exit(1);
87 }
88 
89 
main(int argc,char * const * argv)90 int main(int argc, char* const* argv)
91 {
92   FindFilesPacket* ff;
93   const char* configfile = "bareos-dir.conf";
94   const char* fileset_name = "Windows-Full-Set";
95   int ch, hard_links;
96 
97   OSDependentInit();
98 
99   setlocale(LC_ALL, "");
100   tzset();
101   bindtextdomain("bareos", LOCALEDIR);
102   textdomain("bareos");
103 
104   while ((ch = getopt(argc, argv, "ac:d:f:?")) != -1) {
105     switch (ch) {
106       case 'a': /* print extended attributes *debug* */
107         attrs = 1;
108         break;
109 
110       case 'c': /* set debug level */
111         configfile = optarg;
112         break;
113 
114       case 'd': /* set debug level */
115         if (*optarg == 't') {
116           dbg_timestamp = true;
117         } else {
118           debug_level = atoi(optarg);
119           if (debug_level <= 0) { debug_level = 1; }
120         }
121         break;
122 
123       case 'f': /* exclude patterns */
124         fileset_name = optarg;
125         break;
126 
127       case '?':
128       default:
129         usage();
130     }
131   }
132 
133   argc -= optind;
134   argv += optind;
135 
136   my_config = InitDirConfig(configfile, M_ERROR_TERM);
137   my_config->ParseConfig();
138 
139   MessagesResource* msg;
140 
141   foreach_res (msg, R_MSGS) {
142     InitMsg(NULL, msg);
143   }
144 
145   jcr = NewDirectorJcr();  // Ueb: null
146   jcr->impl->res.fileset =
147       (FilesetResource*)my_config->GetResWithName(R_FILESET, fileset_name);
148 
149   if (jcr->impl->res.fileset == NULL) {
150     fprintf(stderr, "%s: Fileset not found\n", fileset_name);
151 
152     FilesetResource* var;
153 
154     fprintf(stderr, "Valid FileSets:\n");
155 
156     foreach_res (var, R_FILESET) {
157       fprintf(stderr, "    %s\n", var->resource_name_);
158     }
159 
160     exit(1);
161   }
162 
163   ff = init_find_files();
164 
165   CopyFileset(ff, jcr);
166 
167   FindFiles(jcr, ff, PrintFile, NULL);
168 
169   FreeJcr(jcr);
170   if (my_config) {
171     delete my_config;
172     my_config = NULL;
173   }
174 
175   RecentJobResultsList::Cleanup();
176   CleanupJcrChain();
177 
178   /* Clean up fileset */
179   findFILESET* fileset = ff->fileset;
180 
181   if (fileset) {
182     int i, j, k;
183     /* Delete FileSet Include lists */
184     for (i = 0; i < fileset->include_list.size(); i++) {
185       findIncludeExcludeItem* incexe =
186           (findIncludeExcludeItem*)fileset->include_list.get(i);
187       for (j = 0; j < incexe->opts_list.size(); j++) {
188         findFOPTS* fo = (findFOPTS*)incexe->opts_list.get(j);
189         for (k = 0; k < fo->regex.size(); k++) {
190           regfree((regex_t*)fo->regex.get(k));
191         }
192         fo->regex.destroy();
193         fo->regexdir.destroy();
194         fo->regexfile.destroy();
195         fo->wild.destroy();
196         fo->wilddir.destroy();
197         fo->wildfile.destroy();
198         fo->wildbase.destroy();
199         fo->fstype.destroy();
200         fo->Drivetype.destroy();
201       }
202       incexe->opts_list.destroy();
203       incexe->name_list.destroy();
204     }
205     fileset->include_list.destroy();
206 
207     /* Delete FileSet Exclude lists */
208     for (i = 0; i < fileset->exclude_list.size(); i++) {
209       findIncludeExcludeItem* incexe =
210           (findIncludeExcludeItem*)fileset->exclude_list.get(i);
211       for (j = 0; j < incexe->opts_list.size(); j++) {
212         findFOPTS* fo = (findFOPTS*)incexe->opts_list.get(j);
213         fo->regex.destroy();
214         fo->regexdir.destroy();
215         fo->regexfile.destroy();
216         fo->wild.destroy();
217         fo->wilddir.destroy();
218         fo->wildfile.destroy();
219         fo->wildbase.destroy();
220         fo->fstype.destroy();
221         fo->Drivetype.destroy();
222       }
223       incexe->opts_list.destroy();
224       incexe->name_list.destroy();
225     }
226     fileset->exclude_list.destroy();
227     free(fileset);
228   }
229   ff->fileset = NULL;
230   hard_links = TermFindFiles(ff);
231 
232   printf(_("\n"
233            "Total files    : %d\n"
234            "Max file length: %d\n"
235            "Max path length: %d\n"
236            "Files truncated: %d\n"
237            "Paths truncated: %d\n"
238            "Hard links     : %d\n"),
239          num_files, max_file_len, max_path_len, trunc_fname, trunc_path,
240          hard_links);
241 
242   FlushMntentCache();
243 
244   TermMsg();
245 
246   CloseMemoryPool();
247   exit(0);
248 }
249 
PrintFile(JobControlRecord * jcr,FindFilesPacket * ff,bool top_level)250 static int PrintFile(JobControlRecord* jcr, FindFilesPacket* ff, bool top_level)
251 {
252   switch (ff->type) {
253     case FT_LNKSAVED:
254       if (debug_level == 1) {
255         printf("%s\n", ff->fname);
256       } else if (debug_level > 1) {
257         printf("Lnka: %s -> %s\n", ff->fname, ff->link);
258       }
259       break;
260     case FT_REGE:
261       if (debug_level == 1) {
262         printf("%s\n", ff->fname);
263       } else if (debug_level > 1) {
264         printf("Empty: %s\n", ff->fname);
265       }
266       CountFiles(ff);
267       break;
268     case FT_REG:
269       if (debug_level == 1) {
270         printf("%s\n", ff->fname);
271       } else if (debug_level > 1) {
272         printf(_("Reg: %s\n"), ff->fname);
273       }
274       CountFiles(ff);
275       break;
276     case FT_LNK:
277       if (debug_level == 1) {
278         printf("%s\n", ff->fname);
279       } else if (debug_level > 1) {
280         printf("Lnk: %s -> %s\n", ff->fname, ff->link);
281       }
282       CountFiles(ff);
283       break;
284     case FT_DIRBEGIN:
285       return 1;
286     case FT_NORECURSE:
287     case FT_NOFSCHG:
288     case FT_INVALIDFS:
289     case FT_INVALIDDT:
290     case FT_DIREND:
291       if (debug_level) {
292         char errmsg[100] = "";
293         if (ff->type == FT_NORECURSE) {
294           bstrncpy(errmsg, _("\t[will not descend: recursion turned off]"),
295                    sizeof(errmsg));
296         } else if (ff->type == FT_NOFSCHG) {
297           bstrncpy(errmsg,
298                    _("\t[will not descend: file system change not allowed]"),
299                    sizeof(errmsg));
300         } else if (ff->type == FT_INVALIDFS) {
301           bstrncpy(errmsg, _("\t[will not descend: disallowed file system]"),
302                    sizeof(errmsg));
303         } else if (ff->type == FT_INVALIDDT) {
304           bstrncpy(errmsg, _("\t[will not descend: disallowed drive type]"),
305                    sizeof(errmsg));
306         }
307         printf("%s%s%s\n", (debug_level > 1 ? "Dir: " : ""), ff->fname, errmsg);
308       }
309       ff->type = FT_DIREND;
310       CountFiles(ff);
311       break;
312     case FT_SPEC:
313       if (debug_level == 1) {
314         printf("%s\n", ff->fname);
315       } else if (debug_level > 1) {
316         printf("Spec: %s\n", ff->fname);
317       }
318       CountFiles(ff);
319       break;
320     case FT_NOACCESS:
321       printf(_("Err: Could not access %s: %s\n"), ff->fname, strerror(errno));
322       break;
323     case FT_NOFOLLOW:
324       printf(_("Err: Could not follow ff->link %s: %s\n"), ff->fname,
325              strerror(errno));
326       break;
327     case FT_NOSTAT:
328       printf(_("Err: Could not stat %s: %s\n"), ff->fname, strerror(errno));
329       break;
330     case FT_NOCHG:
331       printf(_("Skip: File not saved. No change. %s\n"), ff->fname);
332       break;
333     case FT_ISARCH:
334       printf(_("Err: Attempt to backup archive. Not saved. %s\n"), ff->fname);
335       break;
336     case FT_NOOPEN:
337       printf(_("Err: Could not open directory %s: %s\n"), ff->fname,
338              strerror(errno));
339       break;
340     default:
341       printf(_("Err: Unknown file ff->type %d: %s\n"), ff->type, ff->fname);
342       break;
343   }
344   if (attrs) {
345     char attr[200];
346     encode_attribsEx(NULL, attr, ff);
347     if (*attr != 0) { printf("AttrEx=%s\n", attr); }
348     //    set_attribsEx(NULL, ff->fname, NULL, NULL, ff->type, attr);
349   }
350   return 1;
351 }
352 
CountFiles(FindFilesPacket * ar)353 static void CountFiles(FindFilesPacket* ar)
354 {
355   int fnl, pnl;
356   char *l, *p;
357   PoolMem file(PM_FNAME);
358   PoolMem spath(PM_FNAME);
359 
360   num_files++;
361 
362   /* Find path without the filename.
363    * I.e. everything after the last / is a "filename".
364    * OK, maybe it is a directory name, but we treat it like
365    * a filename. If we don't find a / then the whole name
366    * must be a path name (e.g. c:).
367    */
368   for (p = l = ar->fname; *p; p++) {
369     if (IsPathSeparator(*p)) { l = p; /* set pos of last slash */ }
370   }
371   if (IsPathSeparator(*l)) { /* did we find a slash? */
372     l++;                     /* yes, point to filename */
373   } else {                   /* no, whole thing must be path name */
374     l = p;
375   }
376 
377   /* If filename doesn't exist (i.e. root directory), we
378    * simply create a blank name consisting of a single
379    * space. This makes handling zero length filenames
380    * easier.
381    */
382   fnl = p - l;
383   if (fnl > max_file_len) { max_file_len = fnl; }
384   if (fnl > 255) {
385     printf(_("===== Filename truncated to 255 chars: %s\n"), l);
386     fnl = 255;
387     trunc_fname++;
388   }
389 
390   if (fnl > 0) {
391     PmStrcpy(file, l); /* copy filename */
392   } else {
393     PmStrcpy(file, " "); /* blank filename */
394   }
395 
396   pnl = l - ar->fname;
397   if (pnl > max_path_len) { max_path_len = pnl; }
398   if (pnl > 255) {
399     printf(_("========== Path name truncated to 255 chars: %s\n"), ar->fname);
400     pnl = 255;
401     trunc_path++;
402   }
403 
404   PmStrcpy(spath, ar->fname);
405   if (pnl == 0) {
406     PmStrcpy(spath, " ");
407     printf(_("========== Path length is zero. File=%s\n"), ar->fname);
408   }
409   if (debug_level >= 10) {
410     printf(_("Path: %s\n"), spath.c_str());
411     printf(_("File: %s\n"), file.c_str());
412   }
413 }
414 
CopyFileset(FindFilesPacket * ff,JobControlRecord * jcr)415 static bool CopyFileset(FindFilesPacket* ff, JobControlRecord* jcr)
416 {
417   FilesetResource* jcr_fileset = jcr->impl->res.fileset;
418   int num;
419   bool include = true;
420 
421   findFILESET* fileset;
422   findFOPTS* current_opts;
423 
424   fileset = (findFILESET*)malloc(sizeof(findFILESET));
425   memset(fileset, 0, sizeof(findFILESET));
426   ff->fileset = fileset;
427 
428   fileset->state = state_none;
429   fileset->include_list.init(1, true);
430   fileset->exclude_list.init(1, true);
431 
432   for (;;) {
433     if (include) {
434       num = jcr_fileset->num_includes;
435     } else {
436       num = jcr_fileset->num_excludes;
437     }
438     for (int i = 0; i < num; i++) {
439       IncludeExcludeItem* ie;
440       int j, k;
441 
442       if (include) {
443         ie = jcr_fileset->include_items[i];
444 
445         /* New include */
446         fileset->incexe =
447             (findIncludeExcludeItem*)malloc(sizeof(findIncludeExcludeItem));
448         memset(fileset->incexe, 0, sizeof(findIncludeExcludeItem));
449         fileset->incexe->opts_list.init(1, true);
450         fileset->incexe->name_list.init(0, 0);
451         fileset->include_list.append(fileset->incexe);
452       } else {
453         ie = jcr_fileset->exclude_items[i];
454 
455         /* New exclude */
456         fileset->incexe =
457             (findIncludeExcludeItem*)malloc(sizeof(findIncludeExcludeItem));
458         memset(fileset->incexe, 0, sizeof(findIncludeExcludeItem));
459         fileset->incexe->opts_list.init(1, true);
460         fileset->incexe->name_list.init(0, 0);
461         fileset->exclude_list.append(fileset->incexe);
462       }
463 
464       for (j = 0; j < ie->num_opts; j++) {
465         FileOptions* fo = ie->opts_list[j];
466 
467         current_opts = (findFOPTS*)malloc(sizeof(findFOPTS));
468         memset(current_opts, 0, sizeof(findFOPTS));
469         fileset->incexe->current_opts = current_opts;
470         fileset->incexe->opts_list.append(current_opts);
471 
472         current_opts->regex.init(1, true);
473         current_opts->regexdir.init(1, true);
474         current_opts->regexfile.init(1, true);
475         current_opts->wild.init(1, true);
476         current_opts->wilddir.init(1, true);
477         current_opts->wildfile.init(1, true);
478         current_opts->wildbase.init(1, true);
479         current_opts->fstype.init(1, true);
480         current_opts->Drivetype.init(1, true);
481 
482         SetOptions(current_opts, fo->opts);
483 
484         for (k = 0; k < fo->regex.size(); k++) {
485           // fd->fsend("R %s\n", fo->regex.get(k));
486           current_opts->regex.append(strdup((const char*)fo->regex.get(k)));
487         }
488         for (k = 0; k < fo->regexdir.size(); k++) {
489           // fd->fsend("RD %s\n", fo->regexdir.get(k));
490           current_opts->regexdir.append(
491               strdup((const char*)fo->regexdir.get(k)));
492         }
493         for (k = 0; k < fo->regexfile.size(); k++) {
494           // fd->fsend("RF %s\n", fo->regexfile.get(k));
495           current_opts->regexfile.append(
496               strdup((const char*)fo->regexfile.get(k)));
497         }
498         for (k = 0; k < fo->wild.size(); k++) {
499           current_opts->wild.append(strdup((const char*)fo->wild.get(k)));
500         }
501         for (k = 0; k < fo->wilddir.size(); k++) {
502           current_opts->wilddir.append(strdup((const char*)fo->wilddir.get(k)));
503         }
504         for (k = 0; k < fo->wildfile.size(); k++) {
505           current_opts->wildfile.append(
506               strdup((const char*)fo->wildfile.get(k)));
507         }
508         for (k = 0; k < fo->wildbase.size(); k++) {
509           current_opts->wildbase.append(
510               strdup((const char*)fo->wildbase.get(k)));
511         }
512         for (k = 0; k < fo->fstype.size(); k++) {
513           current_opts->fstype.append(strdup((const char*)fo->fstype.get(k)));
514         }
515         for (k = 0; k < fo->Drivetype.size(); k++) {
516           current_opts->Drivetype.append(
517               strdup((const char*)fo->Drivetype.get(k)));
518         }
519       }
520 
521       for (j = 0; j < ie->name_list.size(); j++) {
522         fileset->incexe->name_list.append(
523             strdup((const char*)ie->name_list.get(j)));
524       }
525     }
526 
527     if (!include) { /* If we just did excludes */
528       break;        /*   all done */
529     }
530 
531     include = false; /* Now do excludes */
532   }
533 
534   return true;
535 }
536 
SetOptions(findFOPTS * fo,const char * opts)537 static void SetOptions(findFOPTS* fo, const char* opts)
538 {
539   int j;
540   const char* p;
541 
542   for (p = opts; *p; p++) {
543     switch (*p) {
544       case 'a': /* alway replace */
545       case '0': /* no option */
546         break;
547       case 'e':
548         SetBit(FO_EXCLUDE, fo->flags);
549         break;
550       case 'f':
551         SetBit(FO_MULTIFS, fo->flags);
552         break;
553       case 'h': /* no recursion */
554         SetBit(FO_NO_RECURSION, fo->flags);
555         break;
556       case 'H': /* no hard link handling */
557         SetBit(FO_NO_HARDLINK, fo->flags);
558         break;
559       case 'i':
560         SetBit(FO_IGNORECASE, fo->flags);
561         break;
562       case 'M': /* MD5 */
563         SetBit(FO_MD5, fo->flags);
564         break;
565       case 'n':
566         SetBit(FO_NOREPLACE, fo->flags);
567         break;
568       case 'p': /* use portable data format */
569         SetBit(FO_PORTABLE, fo->flags);
570         break;
571       case 'R': /* Resource forks and Finder Info */
572         SetBit(FO_HFSPLUS, fo->flags);
573       case 'r': /* read fifo */
574         SetBit(FO_READFIFO, fo->flags);
575         break;
576       case 'S':
577         switch (*(p + 1)) {
578           case ' ':
579             /* Old director did not specify SHA variant */
580             SetBit(FO_SHA1, fo->flags);
581             break;
582           case '1':
583             SetBit(FO_SHA1, fo->flags);
584             p++;
585             break;
586 #ifdef HAVE_SHA2
587           case '2':
588             SetBit(FO_SHA256, fo->flags);
589             p++;
590             break;
591           case '3':
592             SetBit(FO_SHA512, fo->flags);
593             p++;
594             break;
595 #endif
596           default:
597             /* Automatically downgrade to SHA-1 if an unsupported
598              * SHA variant is specified */
599             SetBit(FO_SHA1, fo->flags);
600             p++;
601             break;
602         }
603         break;
604       case 's':
605         SetBit(FO_SPARSE, fo->flags);
606         break;
607       case 'm':
608         SetBit(FO_MTIMEONLY, fo->flags);
609         break;
610       case 'k':
611         SetBit(FO_KEEPATIME, fo->flags);
612         break;
613       case 'A':
614         SetBit(FO_ACL, fo->flags);
615         break;
616       case 'V': /* verify options */
617         /* Copy Verify Options */
618         for (j = 0; *p && *p != ':'; p++) {
619           fo->VerifyOpts[j] = *p;
620           if (j < (int)sizeof(fo->VerifyOpts) - 1) { j++; }
621         }
622         fo->VerifyOpts[j] = 0;
623         break;
624       case 'w':
625         SetBit(FO_IF_NEWER, fo->flags);
626         break;
627       case 'W':
628         SetBit(FO_ENHANCEDWILD, fo->flags);
629         break;
630       case 'Z': /* compression */
631         p++;    /* skip Z */
632         if (*p >= '0' && *p <= '9') {
633           SetBit(FO_COMPRESS, fo->flags);
634           fo->Compress_algo = COMPRESS_GZIP;
635           fo->Compress_level = *p - '0';
636         } else if (*p == 'o') {
637           SetBit(FO_COMPRESS, fo->flags);
638           fo->Compress_algo = COMPRESS_LZO1X;
639           fo->Compress_level = 1; /* not used with LZO */
640         }
641         Dmsg2(200, "Compression alg=%d level=%d\n", fo->Compress_algo,
642               fo->Compress_level);
643         break;
644       case 'x':
645         SetBit(FO_NO_AUTOEXCL, fo->flags);
646         break;
647       case 'X':
648         SetBit(FO_XATTR, fo->flags);
649         break;
650       default:
651         Emsg1(M_ERROR, 0, _("Unknown include/exclude option: %c\n"), *p);
652         break;
653     }
654   }
655 }
656