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