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 #if defined(HAVE_CYGWIN) || defined(HAVE_WIN32)
35 static void do_extract(char *fname);
36 static bool record_cb(DCR *dcr, DEV_RECORD *rec);
38 static DEVICE *dev = NULL;
43 static BSR *bsr = NULL;
44 static bool extract = false;
45 static int non_support_data = 0;
46 static long total = 0;
49 static uint32_t num_files = 0;
50 static uint32_t compress_buf_size = 70000;
51 static POOLMEM *compress_buf;
52 static int prog_name_msg = 0;
53 static int win32_data_msg = 0;
54 static char *VolumeName = NULL;
56 static char *wbuf; /* write buffer address */
57 static uint32_t wsize; /* write size */
58 static uint64_t fileAddr = 0; /* file write address */
60 #define CONFIG_FILE "bacula-sd.conf"
61 char *configfile = NULL;
62 STORES *me = NULL; /* our Global resource */
63 bool forge_on = false;
64 pthread_mutex_t device_release_mutex = PTHREAD_MUTEX_INITIALIZER;
65 pthread_cond_t wait_device_release = PTHREAD_COND_INITIALIZER;
70 "Copyright (C) 2000-2005 Kern Sibbald.\n"
71 "\nVersion: %s (%s)\n\n"
72 "Usage: bextract <options> <bacula-archive-device-name> <directory-to-store-files>\n"
73 " -b <file> specify a bootstrap file\n"
74 " -c <file> specify a configuration file\n"
75 " -d <nn> set debug level to nn\n"
76 " -e <file> exclude list\n"
77 " -i <file> include list\n"
78 " -p proceed inspite of I/O errors\n"
80 " -V <volumes> specify Volume names (separated by |)\n"
81 " -? print this message\n\n"), VERSION, BDATE);
86 int main (int argc, char *argv[])
93 setlocale(LC_ALL, "");
94 bindtextdomain("bacula", LOCALEDIR);
97 working_directory = "/tmp";
98 my_name_is(argc, argv, "bextract");
99 init_msg(NULL, NULL); /* setup message handler */
101 ff = init_find_files();
104 while ((ch = getopt(argc, argv, "b:c:d:e:i:pvV:?")) != -1) {
106 case 'b': /* bootstrap file */
107 bsr = parse_bsr(NULL, optarg);
108 // dump_bsr(bsr, true);
111 case 'c': /* specify config file */
112 if (configfile != NULL) {
115 configfile = bstrdup(optarg);
118 case 'd': /* debug level */
119 debug_level = atoi(optarg);
120 if (debug_level <= 0)
124 case 'e': /* exclude list */
125 if ((fd = fopen(optarg, "r")) == NULL) {
127 Pmsg2(0, _("Could not open exclude file: %s, ERR=%s\n"),
128 optarg, be.strerror());
131 while (fgets(line, sizeof(line), fd) != NULL) {
132 strip_trailing_junk(line);
133 Dmsg1(900, "add_exclude %s\n", line);
134 add_fname_to_exclude_list(ff, line);
139 case 'i': /* include list */
140 if ((fd = fopen(optarg, "r")) == NULL) {
142 Pmsg2(0, _("Could not open include file: %s, ERR=%s\n"),
143 optarg, be.strerror());
146 while (fgets(line, sizeof(line), fd) != NULL) {
147 strip_trailing_junk(line);
148 Dmsg1(900, "add_include %s\n", line);
149 add_fname_to_include_list(ff, 0, line);
163 case 'V': /* Volume name */
177 Pmsg0(0, _("Wrong number of arguments: \n"));
181 if (configfile == NULL) {
182 configfile = bstrdup(CONFIG_FILE);
185 parse_config(configfile);
187 if (!got_inc) { /* If no include file, */
188 add_fname_to_include_list(ff, 0, "/"); /* include everything */
198 Pmsg1(000, _("%d Program Name and/or Program Data Stream records ignored.\n"),
201 if (win32_data_msg) {
202 Pmsg1(000, _("%d Win32 data or Win32 gzip data stream records. Ignored.\n"),
205 term_include_exclude_files(ff);
210 static void do_extract(char *devname)
213 jcr = setup_jcr("bextract", devname, bsr, VolumeName, 1); /* acquire for read */
217 dev = jcr->read_dcr->dev;
223 /* Make sure where directory exists and that it is a directory */
224 if (stat(where, &statp) < 0) {
226 Emsg2(M_ERROR_TERM, 0, _("Cannot stat %s. It must exist. ERR=%s\n"),
227 where, be.strerror());
229 if (!S_ISDIR(statp.st_mode)) {
230 Emsg1(M_ERROR_TERM, 0, _("%s must be a directory.\n"), where);
234 jcr->where = bstrdup(where);
237 compress_buf = get_memory(compress_buf_size);
239 read_records(dcr, record_cb, mount_next_read_volume);
240 /* If output file is still open, it was the last one in the
241 * archive since we just hit an end of file, so close the file.
243 if (is_bopen(&bfd)) {
244 set_attributes(jcr, attr, &bfd);
251 printf(_("%u files restored.\n"), num_files);
256 * Called here for each record from read_records()
258 static bool record_cb(DCR *dcr, DEV_RECORD *rec)
263 if (rec->FileIndex < 0) {
264 return true; /* we don't want labels */
267 /* File Attributes stream */
269 switch (rec->Stream) {
270 case STREAM_UNIX_ATTRIBUTES:
271 case STREAM_UNIX_ATTRIBUTES_EX:
273 /* If extracting, it was from previous stream, so
274 * close the output file.
277 if (!is_bopen(&bfd)) {
278 Emsg0(M_ERROR, 0, _("Logic error output file should be open but is not.\n"));
280 set_attributes(jcr, attr, &bfd);
284 if (!unpack_attributes_record(jcr, rec->Stream, rec->data, attr)) {
285 Emsg0(M_ERROR_TERM, 0, _("Cannot continue.\n"));
288 if (attr->file_index != rec->FileIndex) {
289 Emsg2(M_ERROR_TERM, 0, _("Record header file index %ld not equal record index %ld\n"),
290 rec->FileIndex, attr->file_index);
293 if (file_is_included(ff, attr->fname) && !file_is_excluded(ff, attr->fname)) {
295 attr->data_stream = decode_stat(attr->attr, &attr->statp, &attr->LinkFI);
296 if (!is_restore_stream_supported(attr->data_stream)) {
297 if (!non_support_data++) {
298 Jmsg(jcr, M_ERROR, 0, _("%s stream not supported on this Client.\n"),
299 stream_to_ascii(attr->data_stream));
306 build_attr_output_fnames(jcr, attr);
309 stat = create_file(jcr, attr, &bfd, REPLACE_ALWAYS);
316 print_ls_output(jcr, attr);
321 set_attributes(jcr, attr, &bfd);
322 print_ls_output(jcr, attr);
330 /* Data stream and extracting */
331 case STREAM_FILE_DATA:
332 case STREAM_SPARSE_DATA:
333 case STREAM_WIN32_DATA:
336 if (rec->Stream == STREAM_SPARSE_DATA) {
339 wbuf = rec->data + SPARSE_FADDR_SIZE;
340 wsize = rec->data_len - SPARSE_FADDR_SIZE;
341 ser_begin(rec->data, SPARSE_FADDR_SIZE);
343 if (fileAddr != faddr) {
345 if (blseek(&bfd, (off_t)fileAddr, SEEK_SET) < 0) {
347 Emsg2(M_ERROR_TERM, 0, _("Seek error on %s: %s\n"),
348 attr->ofname, be.strerror());
353 wsize = rec->data_len;
356 Dmsg2(8, "Write %u bytes, total=%u\n", wsize, total);
357 if ((uint32_t)bwrite(&bfd, wbuf, wsize) != wsize) {
359 Emsg2(M_ERROR_TERM, 0, _("Write error on %s: %s\n"),
360 attr->ofname, be.strerror());
366 /* GZIP data stream */
367 case STREAM_GZIP_DATA:
368 case STREAM_SPARSE_GZIP_DATA:
369 case STREAM_WIN32_GZIP_DATA:
375 if (rec->Stream == STREAM_SPARSE_GZIP_DATA) {
379 wbuf = rec->data + SPARSE_FADDR_SIZE;
380 wsize = rec->data_len - SPARSE_FADDR_SIZE;
381 ser_begin(rec->data, SPARSE_FADDR_SIZE);
383 if (fileAddr != faddr) {
385 if (blseek(&bfd, (off_t)fileAddr, SEEK_SET) < 0) {
387 Emsg3(M_ERROR, 0, _("Seek to %s error on %s: ERR=%s\n"),
388 edit_uint64(fileAddr, ec1), attr->ofname, be.strerror());
395 wsize = rec->data_len;
397 compress_len = compress_buf_size;
398 if ((stat=uncompress((Bytef *)compress_buf, &compress_len,
399 (const Bytef *)wbuf, (uLong)wsize) != Z_OK)) {
400 Emsg1(M_ERROR, 0, _("Uncompression error. ERR=%d\n"), stat);
405 Dmsg2(100, "Write uncompressed %d bytes, total before write=%d\n", compress_len, total);
406 if ((uLongf)bwrite(&bfd, compress_buf, (size_t)compress_len) != compress_len) {
408 Pmsg0(0, _("===Write error===\n"));
409 Emsg2(M_ERROR, 0, _("Write error on %s: %s\n"),
410 attr->ofname, be.strerror());
414 total += compress_len;
415 fileAddr += compress_len;
416 Dmsg2(100, "Compress len=%d uncompressed=%d\n", rec->data_len,
421 Emsg0(M_ERROR, 0, _("GZIP data stream found, but GZIP not configured!\n"));
428 case STREAM_MD5_DIGEST:
429 case STREAM_SHA1_DIGEST:
430 case STREAM_SHA256_DIGEST:
431 case STREAM_SHA512_DIGEST:
434 case STREAM_SIGNED_DIGEST:
435 case STREAM_ENCRYPTED_SESSION_DATA:
436 // TODO landonf: Investigate crypto support in the storage daemon
439 case STREAM_PROGRAM_NAMES:
440 case STREAM_PROGRAM_DATA:
441 if (!prog_name_msg) {
442 Pmsg0(000, _("Got Program Name or Data Stream. Ignored.\n"));
448 /* If extracting, wierd stream (not 1 or 2), close output file anyway */
450 if (!is_bopen(&bfd)) {
451 Emsg0(M_ERROR, 0, _("Logic error output file should be open but is not.\n"));
453 set_attributes(jcr, attr, &bfd);
456 Jmsg(jcr, M_ERROR, 0, _("Unknown stream=%d ignored. This shouldn't happen!\n"),
464 /* Dummies to replace askdir.c */
465 bool dir_get_volume_info(DCR *dcr, enum get_vol_info_rw writing) { return 1;}
466 bool dir_find_next_appendable_volume(DCR *dcr) { return 1;}
467 bool dir_update_volume_info(DCR *dcr, bool relabel) { return 1; }
468 bool dir_create_jobmedia_record(DCR *dcr) { return 1; }
469 bool dir_ask_sysop_to_create_appendable_volume(DCR *dcr) { return 1; }
470 bool dir_update_file_attributes(DCR *dcr, DEV_RECORD *rec) { return 1;}
471 bool dir_send_job_status(JCR *jcr) {return 1;}
472 VOLRES *new_volume(DCR *dcr, const char *VolumeName) { return NULL; }
473 bool free_volume(DEVICE *dev) { return true; }
474 void free_unused_volume(DCR *dcr) { }
477 bool dir_ask_sysop_to_mount_volume(DCR *dcr)
479 DEVICE *dev = dcr->dev;
480 fprintf(stderr, _("Mount Volume \"%s\" on device %s and press return when ready: "),
481 dcr->VolumeName, dev->print_name());