1 /*
2    BAREOS® - Backup Archiving REcovery Open Sourced
3 
4    Copyright (C) 2000-2012 Free Software Foundation Europe e.V.
5    Copyright (C) 2011-2012 Planets Communications B.V.
6    Copyright (C) 2013-2020 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  * Kern Sibbald, MM
25  */
26 /**
27  * @file
28  * Dumb program to do an "ls" of a Bareos 2.0 mortal file.
29  */
30 
31 #include "include/bareos.h"
32 #include "stored/stored.h"
33 #include "stored/stored_globals.h"
34 #include "lib/berrno.h"
35 #include "lib/crypto_cache.h"
36 #include "findlib/find.h"
37 #include "stored/acquire.h"
38 #include "stored/butil.h"
39 #include "stored/jcr_private.h"
40 #include "stored/label.h"
41 #include "stored/match_bsr.h"
42 #include "stored/mount.h"
43 #include "stored/read_record.h"
44 #include "findlib/match.h"
45 #include "lib/address_conf.h"
46 #include "lib/attribs.h"
47 #include "lib/bsignal.h"
48 #include "lib/parse_bsr.h"
49 #include "include/jcr.h"
50 #include "lib/parse_conf.h"
51 
52 namespace storagedaemon {
53 extern bool ParseSdConfig(const char* configfile, int exit_code);
54 }
55 
56 using namespace storagedaemon;
57 
58 static void DoBlocks(char* infname);
59 static void do_jobs(char* infname);
60 static void do_ls(char* fname);
61 static void do_close(JobControlRecord* jcr);
62 static void GetSessionRecord(Device* dev,
63                              DeviceRecord* rec,
64                              Session_Label* sessrec);
65 static bool RecordCb(DeviceControlRecord* dcr, DeviceRecord* rec);
66 
67 static Device* dev;
68 static DeviceControlRecord* dcr;
69 static bool dump_label = false;
70 static bool list_blocks = false;
71 static bool list_jobs = false;
72 static DeviceRecord* rec;
73 static JobControlRecord* jcr;
74 static Session_Label sessrec;
75 static uint32_t num_files = 0;
76 static Attributes* attr;
77 
78 static FindFilesPacket* ff;
79 static BootStrapRecord* bsr = NULL;
80 
usage()81 static void usage()
82 {
83   kBareosVersionStrings.PrintCopyrightWithFsfAndPlanets(stderr, 2000);
84   fprintf(
85       stderr,
86       _("Usage: bls [options] <device-name>\n"
87         "       -b <file>       specify a bootstrap file\n"
88         "       -c <path>       specify a Storage configuration file or "
89         "directory\n"
90         "       -D <director>   specify a director name specified in the "
91         "Storage\n"
92         "                       configuration file for the Key Encryption Key "
93         "selection\n"
94         "       -d <nn>         set debug level to <nn>\n"
95         "       -dt             print timestamp in debug output\n"
96         "       -e <file>       exclude list\n"
97         "       -i <file>       include list\n"
98         "       -j              list jobs\n"
99         "       -k              list blocks\n"
100         "    (no j or k option) list saved files\n"
101         "       -L              dump label\n"
102         "       -p              proceed inspite of errors\n"
103         "       -v              be verbose (can be specified multiple times)\n"
104         "       -V              specify Volume names (separated by |)\n"
105         "       -?              print this message\n\n"));
106   exit(1);
107 }
108 
main(int argc,char * argv[])109 int main(int argc, char* argv[])
110 {
111   int i, ch;
112   FILE* fd;
113   char line[1000];
114   char* VolumeName = NULL;
115   char* bsrName = NULL;
116   char* DirectorName = NULL;
117   bool ignore_label_errors = false;
118   DirectorResource* director = NULL;
119 
120   setlocale(LC_ALL, "");
121   tzset();
122   bindtextdomain("bareos", LOCALEDIR);
123   textdomain("bareos");
124   InitStackDump();
125 
126   working_directory = "/tmp";
127   MyNameIs(argc, argv, "bls");
128   InitMsg(NULL, NULL); /* initialize message handler */
129 
130   OSDependentInit();
131 
132   ff = init_find_files();
133 
134   while ((ch = getopt(argc, argv, "b:c:D:d:e:i:jkLpvV:?")) != -1) {
135     switch (ch) {
136       case 'b':
137         bsrName = optarg;
138         break;
139 
140       case 'c': /* specify config file */
141         if (configfile != NULL) { free(configfile); }
142         configfile = strdup(optarg);
143         break;
144 
145       case 'D': /* specify director name */
146         if (DirectorName != NULL) { free(DirectorName); }
147         DirectorName = strdup(optarg);
148         break;
149 
150       case 'd': /* debug level */
151         if (*optarg == 't') {
152           dbg_timestamp = true;
153         } else {
154           debug_level = atoi(optarg);
155           if (debug_level <= 0) { debug_level = 1; }
156         }
157         break;
158 
159       case 'e': /* exclude list */
160         if ((fd = fopen(optarg, "rb")) == NULL) {
161           BErrNo be;
162           Pmsg2(0, _("Could not open exclude file: %s, ERR=%s\n"), optarg,
163                 be.bstrerror());
164           exit(1);
165         }
166         while (fgets(line, sizeof(line), fd) != NULL) {
167           StripTrailingJunk(line);
168           Dmsg1(100, "add_exclude %s\n", line);
169           AddFnameToExcludeList(ff, line);
170         }
171         fclose(fd);
172         break;
173 
174       case 'i': /* include list */
175         if ((fd = fopen(optarg, "rb")) == NULL) {
176           BErrNo be;
177           Pmsg2(0, _("Could not open include file: %s, ERR=%s\n"), optarg,
178                 be.bstrerror());
179           exit(1);
180         }
181         while (fgets(line, sizeof(line), fd) != NULL) {
182           StripTrailingJunk(line);
183           Dmsg1(100, "add_include %s\n", line);
184           AddFnameToIncludeList(ff, 0, line);
185         }
186         fclose(fd);
187         break;
188 
189       case 'j':
190         list_jobs = true;
191         break;
192 
193       case 'k':
194         list_blocks = true;
195         break;
196 
197       case 'L':
198         dump_label = true;
199         break;
200 
201       case 'p':
202         ignore_label_errors = true;
203         forge_on = true;
204         break;
205 
206       case 'v':
207         verbose++;
208         break;
209 
210       case 'V': /* Volume name */
211         VolumeName = optarg;
212         break;
213 
214       case '?':
215       default:
216         usage();
217 
218     } /* end switch */
219   }   /* end while */
220   argc -= optind;
221   argv += optind;
222 
223   if (!argc) {
224     Pmsg0(0, _("No archive name specified\n"));
225     usage();
226   }
227 
228   my_config = InitSdConfig(configfile, M_ERROR_TERM);
229   ParseSdConfig(configfile, M_ERROR_TERM);
230 
231   if (DirectorName) {
232     foreach_res (director, R_DIRECTOR) {
233       if (bstrcmp(director->resource_name_, DirectorName)) { break; }
234     }
235     if (!director) {
236       Emsg2(
237           M_ERROR_TERM, 0,
238           _("No Director resource named %s defined in %s. Cannot continue.\n"),
239           DirectorName, configfile);
240     }
241   }
242 
243   LoadSdPlugins(me->plugin_directory, me->plugin_names);
244 
245   ReadCryptoCache(me->working_directory, "bareos-sd",
246                   GetFirstPortHostOrder(me->SDaddrs));
247 
248   if (ff->included_files_list == NULL) { AddFnameToIncludeList(ff, 0, "/"); }
249 
250   for (i = 0; i < argc; i++) {
251     if (bsrName) { bsr = libbareos::parse_bsr(NULL, bsrName); }
252     dcr = new DeviceControlRecord;
253     jcr = SetupJcr("bls", argv[i], bsr, director, dcr, VolumeName,
254                    true); /* read device */
255     if (!jcr) { exit(1); }
256     jcr->impl->ignore_label_errors = ignore_label_errors;
257     dev = jcr->impl->dcr->dev;
258     if (!dev) { exit(1); }
259     dcr = jcr->impl->dcr;
260     rec = new_record();
261     attr = new_attr(jcr);
262     /*
263      * Assume that we have already read the volume label.
264      * If on second or subsequent volume, adjust buffer pointer
265      */
266     if (dev->VolHdr.PrevVolumeName[0] != 0) { /* second volume */
267       Pmsg1(0,
268             _("\n"
269               "Warning, this Volume is a continuation of Volume %s\n"),
270             dev->VolHdr.PrevVolumeName);
271     }
272 
273     if (list_blocks) {
274       DoBlocks(argv[i]);
275     } else if (list_jobs) {
276       do_jobs(argv[i]);
277     } else {
278       do_ls(argv[i]);
279     }
280     do_close(jcr);
281   }
282   if (bsr) { libbareos::FreeBsr(bsr); }
283   TermIncludeExcludeFiles(ff);
284   TermFindFiles(ff);
285   return 0;
286 }
287 
do_close(JobControlRecord * jcr)288 static void do_close(JobControlRecord* jcr)
289 {
290   FreeAttr(attr);
291   FreeRecord(rec);
292   CleanDevice(jcr->impl->dcr);
293   dev->term();
294   FreeDeviceControlRecord(jcr->impl->dcr);
295   FreeJcr(jcr);
296 }
297 
298 /* List just block information */
DoBlocks(char * infname)299 static void DoBlocks(char* infname)
300 {
301   DeviceBlock* block = dcr->block;
302   char buf1[100], buf2[100];
303   for (;;) {
304     switch (dcr->ReadBlockFromDevice(NO_BLOCK_NUMBER_CHECK)) {
305       case DeviceControlRecord::ReadStatus::Ok:
306         // no special handling required
307         break;
308       case DeviceControlRecord::ReadStatus::EndOfTape:
309         if (!MountNextReadVolume(dcr)) {
310           Jmsg(jcr, M_INFO, 0,
311                _("Got EOM at file %u on device %s, Volume \"%s\"\n"), dev->file,
312                dev->print_name(), dcr->VolumeName);
313           return;
314         }
315         /* Read and discard Volume label */
316         DeviceRecord* record;
317         record = new_record();
318         dcr->ReadBlockFromDevice(NO_BLOCK_NUMBER_CHECK);
319         ReadRecordFromBlock(dcr, record);
320         GetSessionRecord(dev, record, &sessrec);
321         FreeRecord(record);
322         Jmsg(jcr, M_INFO, 0, _("Mounted Volume \"%s\".\n"), dcr->VolumeName);
323         break;
324       case DeviceControlRecord::ReadStatus::EndOfFile:
325         Jmsg(jcr, M_INFO, 0, _("End of file %u on device %s, Volume \"%s\"\n"),
326              dev->file, dev->print_name(), dcr->VolumeName);
327         Dmsg0(20, "read_record got eof. try again\n");
328         continue;
329       default:
330         Dmsg1(100, "!read_block(): ERR=%s\n", dev->bstrerror());
331         if (dev->IsShortBlock()) {
332           Jmsg(jcr, M_INFO, 0, "%s", dev->errmsg);
333           continue;
334         } else {
335           /* I/O error */
336           DisplayTapeErrorStatus(jcr, dev);
337           return;
338         }
339     }
340     if (!MatchBsrBlock(bsr, block)) {
341       Dmsg5(100, "reject Blk=%u blen=%u bVer=%d SessId=%u SessTim=%u\n",
342             block->BlockNumber, block->block_len, block->BlockVer,
343             block->VolSessionId, block->VolSessionTime);
344       continue;
345     }
346     Dmsg5(100, "Blk=%u blen=%u bVer=%d SessId=%u SessTim=%u\n",
347           block->BlockNumber, block->block_len, block->BlockVer,
348           block->VolSessionId, block->VolSessionTime);
349     if (verbose == 1) {
350       ReadRecordFromBlock(dcr, rec);
351       Pmsg9(-1,
352             _("File:blk=%u:%u blk_num=%u blen=%u First rec FI=%s SessId=%u "
353               "SessTim=%u Strm=%s rlen=%d\n"),
354             dev->file, dev->block_num, block->BlockNumber, block->block_len,
355             FI_to_ascii(buf1, rec->FileIndex), rec->VolSessionId,
356             rec->VolSessionTime,
357             stream_to_ascii(buf2, rec->Stream, rec->FileIndex), rec->data_len);
358       rec->remainder = 0;
359     } else if (verbose > 1) {
360       DumpBlock(block, "");
361     } else {
362       printf(_("Block: %d size=%d\n"), block->BlockNumber, block->block_len);
363     }
364   }
365   return;
366 }
367 
368 /**
369  * We are only looking for labels or in particular Job Session records
370  */
jobs_cb(DeviceControlRecord * dcr,DeviceRecord * rec)371 static bool jobs_cb(DeviceControlRecord* dcr, DeviceRecord* rec)
372 {
373   if (rec->FileIndex < 0) { DumpLabelRecord(dcr->dev, rec, verbose); }
374   rec->remainder = 0;
375   return true;
376 }
377 
378 /* Do list job records */
do_jobs(char * infname)379 static void do_jobs(char* infname)
380 {
381   ReadRecords(dcr, jobs_cb, MountNextReadVolume);
382 }
383 
384 /* Do an ls type listing of an archive */
do_ls(char * infname)385 static void do_ls(char* infname)
386 {
387   if (dump_label) {
388     DumpVolumeLabel(dev);
389     return;
390   }
391   ReadRecords(dcr, RecordCb, MountNextReadVolume);
392   printf("%u files and directories found.\n", num_files);
393 }
394 
395 /**
396  * Called here for each record from ReadRecords()
397  */
RecordCb(DeviceControlRecord * dcr,DeviceRecord * rec)398 static bool RecordCb(DeviceControlRecord* dcr, DeviceRecord* rec)
399 {
400   PoolMem record_str(PM_MESSAGE);
401 
402   if (rec->FileIndex < 0) {
403     GetSessionRecord(dev, rec, &sessrec);
404     return true;
405   }
406 
407   /*
408    * File Attributes stream
409    */
410   switch (rec->maskedStream) {
411     case STREAM_UNIX_ATTRIBUTES:
412     case STREAM_UNIX_ATTRIBUTES_EX:
413       if (!UnpackAttributesRecord(jcr, rec->Stream, rec->data, rec->data_len,
414                                   attr)) {
415         if (!forge_on) {
416           Emsg0(M_ERROR_TERM, 0, _("Cannot continue.\n"));
417         } else {
418           Emsg0(M_ERROR, 0, _("Attrib unpack error!\n"));
419         }
420         num_files++;
421         return true;
422       }
423 
424       attr->data_stream = DecodeStat(attr->attr, &attr->statp,
425                                      sizeof(attr->statp), &attr->LinkFI);
426       BuildAttrOutputFnames(jcr, attr);
427 
428       if (FileIsIncluded(ff, attr->fname) && !FileIsExcluded(ff, attr->fname)) {
429         if (!verbose) {
430           PrintLsOutput(jcr, attr);
431         } else {
432           Pmsg1(-1, "%s\n", record_to_str(record_str, jcr, rec));
433         }
434         num_files++;
435       }
436       break;
437     default:
438       if (verbose) { Pmsg1(-1, "%s\n", record_to_str(record_str, jcr, rec)); }
439       break;
440   }
441 
442   /* debug output of record */
443   DumpRecord("", rec);
444 
445   return true;
446 }
447 
GetSessionRecord(Device * dev,DeviceRecord * rec,Session_Label * sessrec)448 static void GetSessionRecord(Device* dev,
449                              DeviceRecord* rec,
450                              Session_Label* sessrec)
451 {
452   const char* rtype;
453   Session_Label empty_Session_Label;
454   *sessrec = empty_Session_Label;
455   jcr->JobId = 0;
456   switch (rec->FileIndex) {
457     case PRE_LABEL:
458       rtype = _("Fresh Volume Label");
459       break;
460     case VOL_LABEL:
461       rtype = _("Volume Label");
462       UnserVolumeLabel(dev, rec);
463       break;
464     case SOS_LABEL:
465       rtype = _("Begin Job Session");
466       UnserSessionLabel(sessrec, rec);
467       jcr->JobId = sessrec->JobId;
468       break;
469     case EOS_LABEL:
470       rtype = _("End Job Session");
471       break;
472     case 0:
473     case EOM_LABEL:
474       rtype = _("End of Medium");
475       break;
476     case EOT_LABEL:
477       rtype = _("End of Physical Medium");
478       break;
479     case SOB_LABEL:
480       rtype = _("Start of object");
481       break;
482     case EOB_LABEL:
483       rtype = _("End of object");
484       break;
485     default:
486       rtype = _("Unknown");
487       Dmsg1(10, "FI rtype=%d unknown\n", rec->FileIndex);
488       break;
489   }
490   Dmsg5(10,
491         "%s Record: VolSessionId=%d VolSessionTime=%d JobId=%d DataLen=%d\n",
492         rtype, rec->VolSessionId, rec->VolSessionTime, rec->Stream,
493         rec->data_len);
494   if (verbose) {
495     Pmsg5(
496         -1,
497         _("%s Record: VolSessionId=%d VolSessionTime=%d JobId=%d DataLen=%d\n"),
498         rtype, rec->VolSessionId, rec->VolSessionTime, rec->Stream,
499         rec->data_len);
500   }
501 }
502