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