3 * Dumb program to extract files from a Bacula backup.
11 Copyright (C) 2000-2006 Kern Sibbald
13 This program is free software; you can redistribute it and/or
14 modify it under the terms of the GNU General Public License
15 version 2 as amended with additional clauses defined in the
16 file LICENSE in the main source directory.
18 This program is distributed in the hope that it will be useful,
19 but WITHOUT ANY WARRANTY; without even the implied warranty of
20 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 the file LICENSE for additional details.
27 #include "findlib/find.h"
29 static void do_extract(char *fname);
30 static bool record_cb(DCR *dcr, DEV_RECORD *rec);
32 static DEVICE *dev = NULL;
37 static BSR *bsr = NULL;
38 static bool extract = false;
39 static int non_support_data = 0;
40 static long total = 0;
43 static uint32_t num_files = 0;
44 static uint32_t compress_buf_size = 70000;
45 static POOLMEM *compress_buf;
46 static int prog_name_msg = 0;
47 static int win32_data_msg = 0;
48 static char *VolumeName = NULL;
50 static char *wbuf; /* write buffer address */
51 static uint32_t wsize; /* write size */
52 static uint64_t fileAddr = 0; /* file write address */
54 #define CONFIG_FILE "bacula-sd.conf"
55 char *configfile = NULL;
56 STORES *me = NULL; /* our Global resource */
57 bool forge_on = false;
58 pthread_mutex_t device_release_mutex = PTHREAD_MUTEX_INITIALIZER;
59 pthread_cond_t wait_device_release = PTHREAD_COND_INITIALIZER;
64 "Copyright (C) 2000-%s Kern Sibbald.\n"
65 "\nVersion: %s (%s)\n\n"
66 "Usage: bextract <options> <bacula-archive-device-name> <directory-to-store-files>\n"
67 " -b <file> specify a bootstrap file\n"
68 " -c <file> specify a configuration file\n"
69 " -d <nn> set debug level to nn\n"
70 " -e <file> exclude list\n"
71 " -i <file> include list\n"
72 " -p proceed inspite of I/O errors\n"
74 " -V <volumes> specify Volume names (separated by |)\n"
75 " -? print this message\n\n"), BYEAR, VERSION, BDATE);
80 int main (int argc, char *argv[])
87 setlocale(LC_ALL, "");
88 bindtextdomain("bacula", LOCALEDIR);
92 working_directory = "/tmp";
93 my_name_is(argc, argv, "bextract");
94 init_msg(NULL, NULL); /* setup message handler */
98 ff = init_find_files();
101 while ((ch = getopt(argc, argv, "b:c:d:e:i:pvV:?")) != -1) {
103 case 'b': /* bootstrap file */
104 bsr = parse_bsr(NULL, optarg);
105 // dump_bsr(bsr, true);
108 case 'c': /* specify config file */
109 if (configfile != NULL) {
112 configfile = bstrdup(optarg);
115 case 'd': /* debug level */
116 debug_level = atoi(optarg);
117 if (debug_level <= 0)
121 case 'e': /* exclude list */
122 if ((fd = fopen(optarg, "rb")) == NULL) {
124 Pmsg2(0, _("Could not open exclude file: %s, ERR=%s\n"),
125 optarg, be.strerror());
128 while (fgets(line, sizeof(line), fd) != NULL) {
129 strip_trailing_junk(line);
130 Dmsg1(900, "add_exclude %s\n", line);
131 add_fname_to_exclude_list(ff, line);
136 case 'i': /* include list */
137 if ((fd = fopen(optarg, "rb")) == NULL) {
139 Pmsg2(0, _("Could not open include file: %s, ERR=%s\n"),
140 optarg, be.strerror());
143 while (fgets(line, sizeof(line), fd) != NULL) {
144 strip_trailing_junk(line);
145 Dmsg1(900, "add_include %s\n", line);
146 add_fname_to_include_list(ff, 0, line);
160 case 'V': /* Volume name */
174 Pmsg0(0, _("Wrong number of arguments: \n"));
178 if (configfile == NULL) {
179 configfile = bstrdup(CONFIG_FILE);
182 parse_config(configfile);
184 if (!got_inc) { /* If no include file, */
185 add_fname_to_include_list(ff, 0, "/"); /* include everything */
195 Pmsg1(000, _("%d Program Name and/or Program Data Stream records ignored.\n"),
198 if (win32_data_msg) {
199 Pmsg1(000, _("%d Win32 data or Win32 gzip data stream records. Ignored.\n"),
202 term_include_exclude_files(ff);
207 static void do_extract(char *devname)
210 jcr = setup_jcr("bextract", devname, bsr, VolumeName, 1); /* acquire for read */
214 dev = jcr->read_dcr->dev;
220 /* Make sure where directory exists and that it is a directory */
221 if (stat(where, &statp) < 0) {
223 Emsg2(M_ERROR_TERM, 0, _("Cannot stat %s. It must exist. ERR=%s\n"),
224 where, be.strerror());
226 if (!S_ISDIR(statp.st_mode)) {
227 Emsg1(M_ERROR_TERM, 0, _("%s must be a directory.\n"), where);
231 jcr->where = bstrdup(where);
234 compress_buf = get_memory(compress_buf_size);
236 read_records(dcr, record_cb, mount_next_read_volume);
237 /* If output file is still open, it was the last one in the
238 * archive since we just hit an end of file, so close the file.
240 if (is_bopen(&bfd)) {
241 set_attributes(jcr, attr, &bfd);
248 printf(_("%u files restored.\n"), num_files);
253 * Called here for each record from read_records()
255 static bool record_cb(DCR *dcr, DEV_RECORD *rec)
260 if (rec->FileIndex < 0) {
261 return true; /* we don't want labels */
264 /* File Attributes stream */
266 switch (rec->Stream) {
267 case STREAM_UNIX_ATTRIBUTES:
268 case STREAM_UNIX_ATTRIBUTES_EX:
270 /* If extracting, it was from previous stream, so
271 * close the output file.
274 if (!is_bopen(&bfd)) {
275 Emsg0(M_ERROR, 0, _("Logic error output file should be open but is not.\n"));
277 set_attributes(jcr, attr, &bfd);
281 if (!unpack_attributes_record(jcr, rec->Stream, rec->data, attr)) {
282 Emsg0(M_ERROR_TERM, 0, _("Cannot continue.\n"));
285 if (attr->file_index != rec->FileIndex) {
286 Emsg2(M_ERROR_TERM, 0, _("Record header file index %ld not equal record index %ld\n"),
287 rec->FileIndex, attr->file_index);
290 if (file_is_included(ff, attr->fname) && !file_is_excluded(ff, attr->fname)) {
292 attr->data_stream = decode_stat(attr->attr, &attr->statp, &attr->LinkFI);
293 if (!is_restore_stream_supported(attr->data_stream)) {
294 if (!non_support_data++) {
295 Jmsg(jcr, M_ERROR, 0, _("%s stream not supported on this Client.\n"),
296 stream_to_ascii(attr->data_stream));
303 build_attr_output_fnames(jcr, attr);
306 stat = create_file(jcr, attr, &bfd, REPLACE_ALWAYS);
313 print_ls_output(jcr, attr);
318 set_attributes(jcr, attr, &bfd);
319 print_ls_output(jcr, attr);
327 /* Data stream and extracting */
328 case STREAM_FILE_DATA:
329 case STREAM_SPARSE_DATA:
330 case STREAM_WIN32_DATA:
333 if (rec->Stream == STREAM_SPARSE_DATA) {
336 wbuf = rec->data + SPARSE_FADDR_SIZE;
337 wsize = rec->data_len - SPARSE_FADDR_SIZE;
338 ser_begin(rec->data, SPARSE_FADDR_SIZE);
340 if (fileAddr != faddr) {
342 if (blseek(&bfd, (off_t)fileAddr, SEEK_SET) < 0) {
344 Emsg2(M_ERROR_TERM, 0, _("Seek error on %s: %s\n"),
345 attr->ofname, be.strerror());
350 wsize = rec->data_len;
353 Dmsg2(8, "Write %u bytes, total=%u\n", wsize, total);
354 if ((uint32_t)bwrite(&bfd, wbuf, wsize) != wsize) {
356 Emsg2(M_ERROR_TERM, 0, _("Write error on %s: %s\n"),
357 attr->ofname, be.strerror());
363 /* GZIP data stream */
364 case STREAM_GZIP_DATA:
365 case STREAM_SPARSE_GZIP_DATA:
366 case STREAM_WIN32_GZIP_DATA:
372 if (rec->Stream == STREAM_SPARSE_GZIP_DATA) {
376 wbuf = rec->data + SPARSE_FADDR_SIZE;
377 wsize = rec->data_len - SPARSE_FADDR_SIZE;
378 ser_begin(rec->data, SPARSE_FADDR_SIZE);
380 if (fileAddr != faddr) {
382 if (blseek(&bfd, (off_t)fileAddr, SEEK_SET) < 0) {
384 Emsg3(M_ERROR, 0, _("Seek to %s error on %s: ERR=%s\n"),
385 edit_uint64(fileAddr, ec1), attr->ofname, be.strerror());
392 wsize = rec->data_len;
394 compress_len = compress_buf_size;
395 if ((stat=uncompress((Bytef *)compress_buf, &compress_len,
396 (const Bytef *)wbuf, (uLong)wsize) != Z_OK)) {
397 Emsg1(M_ERROR, 0, _("Uncompression error. ERR=%d\n"), stat);
402 Dmsg2(100, "Write uncompressed %d bytes, total before write=%d\n", compress_len, total);
403 if ((uLongf)bwrite(&bfd, compress_buf, (size_t)compress_len) != compress_len) {
405 Pmsg0(0, _("===Write error===\n"));
406 Emsg2(M_ERROR, 0, _("Write error on %s: %s\n"),
407 attr->ofname, be.strerror());
411 total += compress_len;
412 fileAddr += compress_len;
413 Dmsg2(100, "Compress len=%d uncompressed=%d\n", rec->data_len,
418 Emsg0(M_ERROR, 0, _("GZIP data stream found, but GZIP not configured!\n"));
425 case STREAM_MD5_DIGEST:
426 case STREAM_SHA1_DIGEST:
427 case STREAM_SHA256_DIGEST:
428 case STREAM_SHA512_DIGEST:
431 case STREAM_SIGNED_DIGEST:
432 case STREAM_ENCRYPTED_SESSION_DATA:
433 // TODO landonf: Investigate crypto support in the storage daemon
436 case STREAM_PROGRAM_NAMES:
437 case STREAM_PROGRAM_DATA:
438 if (!prog_name_msg) {
439 Pmsg0(000, _("Got Program Name or Data Stream. Ignored.\n"));
445 /* If extracting, wierd stream (not 1 or 2), close output file anyway */
447 if (!is_bopen(&bfd)) {
448 Emsg0(M_ERROR, 0, _("Logic error output file should be open but is not.\n"));
450 set_attributes(jcr, attr, &bfd);
453 Jmsg(jcr, M_ERROR, 0, _("Unknown stream=%d ignored. This shouldn't happen!\n"),
461 /* Dummies to replace askdir.c */
462 bool dir_find_next_appendable_volume(DCR *dcr) { return 1;}
463 bool dir_update_volume_info(DCR *dcr, bool relabel) { return 1; }
464 bool dir_create_jobmedia_record(DCR *dcr) { return 1; }
465 bool dir_ask_sysop_to_create_appendable_volume(DCR *dcr) { return 1; }
466 bool dir_update_file_attributes(DCR *dcr, DEV_RECORD *rec) { return 1;}
467 bool dir_send_job_status(JCR *jcr) {return 1;}
470 bool dir_ask_sysop_to_mount_volume(DCR *dcr)
472 DEVICE *dev = dcr->dev;
473 fprintf(stderr, _("Mount Volume \"%s\" on device %s and press return when ready: "),
474 dcr->VolumeName, dev->print_name());
480 bool dir_get_volume_info(DCR *dcr, enum get_vol_info_rw writing)
482 Dmsg0(100, "Fake dir_get_volume_info\n");
483 bstrncpy(dcr->VolCatInfo.VolCatName, dcr->VolumeName, sizeof(dcr->VolCatInfo.VolCatName));
484 dcr->VolCatInfo.VolCatParts = find_num_dvd_parts(dcr);
485 Dmsg2(500, "Vol=%s num_parts=%d\n", dcr->VolCatInfo.VolCatName, dcr->VolCatInfo.VolCatParts);