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