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