3 * Dumb program to extract files from a Bacula backup.
11 Copyright (C) 2000-2005 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 ammended 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: " VERSION " (" BDATE ")\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");
86 int main (int argc, char *argv[])
93 working_directory = "/tmp";
94 my_name_is(argc, argv, "bextract");
95 init_msg(NULL, NULL); /* setup message handler */
97 ff = init_find_files();
100 while ((ch = getopt(argc, argv, "b:c:d:e:i:pvV:?")) != -1) {
102 case 'b': /* bootstrap file */
103 bsr = parse_bsr(NULL, optarg);
104 // dump_bsr(bsr, true);
107 case 'c': /* specify config file */
108 if (configfile != NULL) {
111 configfile = bstrdup(optarg);
114 case 'd': /* debug level */
115 debug_level = atoi(optarg);
116 if (debug_level <= 0)
120 case 'e': /* exclude list */
121 if ((fd = fopen(optarg, "r")) == NULL) {
123 Pmsg2(0, "Could not open exclude file: %s, ERR=%s\n",
124 optarg, be.strerror());
127 while (fgets(line, sizeof(line), fd) != NULL) {
128 strip_trailing_junk(line);
129 Dmsg1(900, "add_exclude %s\n", line);
130 add_fname_to_exclude_list(ff, line);
135 case 'i': /* include list */
136 if ((fd = fopen(optarg, "r")) == NULL) {
138 Pmsg2(0, "Could not open include file: %s, ERR=%s\n",
139 optarg, be.strerror());
142 while (fgets(line, sizeof(line), fd) != NULL) {
143 strip_trailing_junk(line);
144 Dmsg1(900, "add_include %s\n", line);
145 add_fname_to_include_list(ff, 0, line);
159 case 'V': /* Volume name */
173 Pmsg0(0, "Wrong number of arguments: \n");
177 if (configfile == NULL) {
178 configfile = bstrdup(CONFIG_FILE);
181 parse_config(configfile);
183 if (!got_inc) { /* If no include file, */
184 add_fname_to_include_list(ff, 0, "/"); /* include everything */
194 Pmsg1(000, "%d Program Name and/or Program Data Stream records ignored.\n",
197 if (win32_data_msg) {
198 Pmsg1(000, "%d Win32 data or Win32 gzip data stream records. Ignored.\n",
201 term_include_exclude_files(ff);
206 static void do_extract(char *devname)
209 jcr = setup_jcr("bextract", devname, bsr, VolumeName, 1); /* acquire for read */
219 /* Make sure where directory exists and that it is a directory */
220 if (stat(where, &statp) < 0) {
222 Emsg2(M_ERROR_TERM, 0, "Cannot stat %s. It must exist. ERR=%s\n",
223 where, be.strerror());
225 if (!S_ISDIR(statp.st_mode)) {
226 Emsg1(M_ERROR_TERM, 0, "%s must be a directory.\n", where);
230 jcr->where = bstrdup(where);
233 compress_buf = get_memory(compress_buf_size);
235 read_records(dcr, record_cb, mount_next_read_volume);
236 /* If output file is still open, it was the last one in the
237 * archive since we just hit an end of file, so close the file.
239 if (is_bopen(&bfd)) {
240 set_attributes(jcr, attr, &bfd);
247 printf("%u files restored.\n", num_files);
252 * Called here for each record from read_records()
254 static bool record_cb(DCR *dcr, DEV_RECORD *rec)
259 if (rec->FileIndex < 0) {
260 return true; /* we don't want labels */
263 /* File Attributes stream */
265 switch (rec->Stream) {
266 case STREAM_UNIX_ATTRIBUTES:
267 case STREAM_UNIX_ATTRIBUTES_EX:
269 /* If extracting, it was from previous stream, so
270 * close the output file.
273 if (!is_bopen(&bfd)) {
274 Emsg0(M_ERROR, 0, _("Logic error output file should be open but is not.\n"));
276 set_attributes(jcr, attr, &bfd);
280 if (!unpack_attributes_record(jcr, rec->Stream, rec->data, attr)) {
281 Emsg0(M_ERROR_TERM, 0, _("Cannot continue.\n"));
284 if (attr->file_index != rec->FileIndex) {
285 Emsg2(M_ERROR_TERM, 0, _("Record header file index %ld not equal record index %ld\n"),
286 rec->FileIndex, attr->file_index);
289 if (file_is_included(ff, attr->fname) && !file_is_excluded(ff, attr->fname)) {
291 attr->data_stream = decode_stat(attr->attr, &attr->statp, &attr->LinkFI);
292 if (!is_stream_supported(attr->data_stream)) {
293 if (!non_support_data++) {
294 Jmsg(jcr, M_ERROR, 0, _("%s stream not supported on this Client.\n"),
295 stream_to_ascii(attr->data_stream));
302 build_attr_output_fnames(jcr, attr);
305 stat = create_file(jcr, attr, &bfd, REPLACE_ALWAYS);
312 print_ls_output(jcr, attr);
317 set_attributes(jcr, attr, &bfd);
318 print_ls_output(jcr, attr);
326 /* Data stream and extracting */
327 case STREAM_FILE_DATA:
328 case STREAM_SPARSE_DATA:
329 case STREAM_WIN32_DATA:
332 if (rec->Stream == STREAM_SPARSE_DATA) {
335 wbuf = rec->data + SPARSE_FADDR_SIZE;
336 wsize = rec->data_len - SPARSE_FADDR_SIZE;
337 ser_begin(rec->data, SPARSE_FADDR_SIZE);
339 if (fileAddr != faddr) {
341 if (blseek(&bfd, (off_t)fileAddr, SEEK_SET) < 0) {
343 Emsg2(M_ERROR_TERM, 0, _("Seek error on %s: %s\n"),
344 attr->ofname, be.strerror());
349 wsize = rec->data_len;
352 Dmsg2(8, "Write %u bytes, total=%u\n", wsize, total);
353 if ((uint32_t)bwrite(&bfd, wbuf, wsize) != wsize) {
355 Emsg2(M_ERROR_TERM, 0, _("Write error on %s: %s\n"),
356 attr->ofname, be.strerror());
362 /* GZIP data stream */
363 case STREAM_GZIP_DATA:
364 case STREAM_SPARSE_GZIP_DATA:
365 case STREAM_WIN32_GZIP_DATA:
371 if (rec->Stream == STREAM_SPARSE_GZIP_DATA) {
375 wbuf = rec->data + SPARSE_FADDR_SIZE;
376 wsize = rec->data_len - SPARSE_FADDR_SIZE;
377 ser_begin(rec->data, SPARSE_FADDR_SIZE);
379 if (fileAddr != faddr) {
381 if (blseek(&bfd, (off_t)fileAddr, SEEK_SET) < 0) {
383 Emsg3(M_ERROR, 0, _("Seek to %s error on %s: ERR=%s\n"),
384 edit_uint64(fileAddr, ec1), attr->ofname, be.strerror());
391 wsize = rec->data_len;
393 compress_len = compress_buf_size;
394 if ((stat=uncompress((Bytef *)compress_buf, &compress_len,
395 (const Bytef *)wbuf, (uLong)wsize) != Z_OK)) {
396 Emsg1(M_ERROR, 0, _("Uncompression error. ERR=%d\n"), stat);
401 Dmsg2(100, "Write uncompressed %d bytes, total before write=%d\n", compress_len, total);
402 if ((uLongf)bwrite(&bfd, compress_buf, (size_t)compress_len) != compress_len) {
404 Pmsg0(0, "===Write error===\n");
405 Emsg2(M_ERROR, 0, _("Write error on %s: %s\n"),
406 attr->ofname, be.strerror());
410 total += compress_len;
411 fileAddr += compress_len;
412 Dmsg2(100, "Compress len=%d uncompressed=%d\n", rec->data_len,
417 Emsg0(M_ERROR, 0, "GZIP data stream found, but GZIP not configured!\n");
424 case STREAM_MD5_SIGNATURE:
425 case STREAM_SHA1_SIGNATURE:
428 case STREAM_PROGRAM_NAMES:
429 case STREAM_PROGRAM_DATA:
430 if (!prog_name_msg) {
431 Pmsg0(000, "Got Program Name or Data Stream. Ignored.\n");
437 /* If extracting, wierd stream (not 1 or 2), close output file anyway */
439 if (!is_bopen(&bfd)) {
440 Emsg0(M_ERROR, 0, "Logic error output file should be open but is not.\n");
442 set_attributes(jcr, attr, &bfd);
445 Jmsg(jcr, M_ERROR, 0, _("Unknown stream=%d ignored. This shouldn't happen!\n"),
453 /* Dummies to replace askdir.c */
454 bool dir_get_volume_info(DCR *dcr, enum get_vol_info_rw writing) { return 1;}
455 bool dir_find_next_appendable_volume(DCR *dcr) { return 1;}
456 bool dir_update_volume_info(DCR *dcr, bool relabel) { return 1; }
457 bool dir_create_jobmedia_record(DCR *dcr) { return 1; }
458 bool dir_ask_sysop_to_create_appendable_volume(DCR *dcr) { return 1; }
459 bool dir_update_file_attributes(DCR *dcr, DEV_RECORD *rec) { return 1;}
460 bool dir_send_job_status(JCR *jcr) {return 1;}
461 VOLRES *new_volume(const char *VolumeName, DEVICE *dev) { return NULL; }
462 bool free_volume(DEVICE *dev) { return true; }
465 bool dir_ask_sysop_to_mount_volume(DCR *dcr)
467 DEVICE *dev = dcr->dev;
468 fprintf(stderr, "Mount Volume \"%s\" on device %s and press return when ready: ",
469 dcr->VolumeName, dev->print_name());