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