3 * Dumb program to extract files from a Bacula backup.
11 Bacula® - The Network Backup Solution
13 Copyright (C) 2000-2006 Free Software Foundation Europe e.V.
15 The main author of Bacula is Kern Sibbald, with contributions from
16 many others, a complete list can be found in the file AUTHORS.
17 This program is Free Software; you can redistribute it and/or
18 modify it under the terms of version two of the GNU General Public
19 License as published by the Free Software Foundation and included
22 This program is distributed in the hope that it will be useful, but
23 WITHOUT ANY WARRANTY; without even the implied warranty of
24 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
25 General Public License for more details.
27 You should have received a copy of the GNU General Public License
28 along with this program; if not, write to the Free Software
29 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
32 Bacula® is a registered trademark of John Walker.
33 The licensor of Bacula is the Free Software Foundation Europe
34 (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zürich,
35 Switzerland, email:ftf@fsfeurope.org.
40 #include "findlib/find.h"
42 static void do_extract(char *fname);
43 static bool record_cb(DCR *dcr, DEV_RECORD *rec);
45 static DEVICE *dev = NULL;
50 static BSR *bsr = NULL;
51 static bool extract = false;
52 static int non_support_data = 0;
53 static long total = 0;
56 static uint32_t num_files = 0;
57 static uint32_t compress_buf_size = 70000;
58 static POOLMEM *compress_buf;
59 static int prog_name_msg = 0;
60 static int win32_data_msg = 0;
61 static char *VolumeName = NULL;
63 static char *wbuf; /* write buffer address */
64 static uint32_t wsize; /* write size */
65 static uint64_t fileAddr = 0; /* file write address */
67 #define CONFIG_FILE "bacula-sd.conf"
68 char *configfile = NULL;
69 STORES *me = NULL; /* our Global resource */
70 bool forge_on = false;
71 pthread_mutex_t device_release_mutex = PTHREAD_MUTEX_INITIALIZER;
72 pthread_cond_t wait_device_release = PTHREAD_COND_INITIALIZER;
78 "\nVersion: %s (%s)\n\n"
79 "Usage: bextract <options> <bacula-archive-device-name> <directory-to-store-files>\n"
80 " -b <file> specify a bootstrap file\n"
81 " -c <file> specify a configuration file\n"
82 " -d <nn> set debug level to nn\n"
83 " -e <file> exclude list\n"
84 " -i <file> include list\n"
85 " -p proceed inspite of I/O errors\n"
87 " -V <volumes> specify Volume names (separated by |)\n"
88 " -? print this message\n\n"), 2000, VERSION, BDATE);
93 int main (int argc, char *argv[])
100 setlocale(LC_ALL, "");
101 bindtextdomain("bacula", LOCALEDIR);
102 textdomain("bacula");
105 working_directory = "/tmp";
106 my_name_is(argc, argv, "bextract");
107 init_msg(NULL, NULL); /* setup message handler */
111 ff = init_find_files();
114 while ((ch = getopt(argc, argv, "b:c:d:e:i:pvV:?")) != -1) {
116 case 'b': /* bootstrap file */
117 bsr = parse_bsr(NULL, optarg);
118 // dump_bsr(bsr, true);
121 case 'c': /* specify config file */
122 if (configfile != NULL) {
125 configfile = bstrdup(optarg);
128 case 'd': /* debug level */
129 debug_level = atoi(optarg);
130 if (debug_level <= 0)
134 case 'e': /* exclude list */
135 if ((fd = fopen(optarg, "rb")) == NULL) {
137 Pmsg2(0, _("Could not open exclude file: %s, ERR=%s\n"),
138 optarg, be.bstrerror());
141 while (fgets(line, sizeof(line), fd) != NULL) {
142 strip_trailing_junk(line);
143 Dmsg1(900, "add_exclude %s\n", line);
144 add_fname_to_exclude_list(ff, line);
149 case 'i': /* include list */
150 if ((fd = fopen(optarg, "rb")) == NULL) {
152 Pmsg2(0, _("Could not open include file: %s, ERR=%s\n"),
153 optarg, be.bstrerror());
156 while (fgets(line, sizeof(line), fd) != NULL) {
157 strip_trailing_junk(line);
158 Dmsg1(900, "add_include %s\n", line);
159 add_fname_to_include_list(ff, 0, line);
173 case 'V': /* Volume name */
187 Pmsg0(0, _("Wrong number of arguments: \n"));
191 if (configfile == NULL) {
192 configfile = bstrdup(CONFIG_FILE);
195 parse_config(configfile);
197 if (!got_inc) { /* If no include file, */
198 add_fname_to_include_list(ff, 0, "/"); /* include everything */
208 Pmsg1(000, _("%d Program Name and/or Program Data Stream records ignored.\n"),
211 if (win32_data_msg) {
212 Pmsg1(000, _("%d Win32 data or Win32 gzip data stream records. Ignored.\n"),
215 term_include_exclude_files(ff);
220 static void do_extract(char *devname)
224 enable_backup_privileges(NULL, 1);
226 jcr = setup_jcr("bextract", devname, bsr, VolumeName, 1); /* acquire for read */
230 dev = jcr->read_dcr->dev;
236 /* Make sure where directory exists and that it is a directory */
237 if (stat(where, &statp) < 0) {
239 Emsg2(M_ERROR_TERM, 0, _("Cannot stat %s. It must exist. ERR=%s\n"),
240 where, be.bstrerror());
242 if (!S_ISDIR(statp.st_mode)) {
243 Emsg1(M_ERROR_TERM, 0, _("%s must be a directory.\n"), where);
247 jcr->where = bstrdup(where);
250 compress_buf = get_memory(compress_buf_size);
252 read_records(dcr, record_cb, mount_next_read_volume);
253 /* If output file is still open, it was the last one in the
254 * archive since we just hit an end of file, so close the file.
256 if (is_bopen(&bfd)) {
257 set_attributes(jcr, attr, &bfd);
264 printf(_("%u files restored.\n"), num_files);
269 * Called here for each record from read_records()
271 static bool record_cb(DCR *dcr, DEV_RECORD *rec)
276 if (rec->FileIndex < 0) {
277 return true; /* we don't want labels */
280 /* File Attributes stream */
282 switch (rec->Stream) {
283 case STREAM_UNIX_ATTRIBUTES:
284 case STREAM_UNIX_ATTRIBUTES_EX:
286 /* If extracting, it was from previous stream, so
287 * close the output file.
290 if (!is_bopen(&bfd)) {
291 Emsg0(M_ERROR, 0, _("Logic error output file should be open but is not.\n"));
293 set_attributes(jcr, attr, &bfd);
297 if (!unpack_attributes_record(jcr, rec->Stream, rec->data, attr)) {
298 Emsg0(M_ERROR_TERM, 0, _("Cannot continue.\n"));
301 if (attr->file_index != rec->FileIndex) {
302 Emsg2(M_ERROR_TERM, 0, _("Record header file index %ld not equal record index %ld\n"),
303 rec->FileIndex, attr->file_index);
306 if (file_is_included(ff, attr->fname) && !file_is_excluded(ff, attr->fname)) {
308 attr->data_stream = decode_stat(attr->attr, &attr->statp, &attr->LinkFI);
309 if (!is_restore_stream_supported(attr->data_stream)) {
310 if (!non_support_data++) {
311 Jmsg(jcr, M_ERROR, 0, _("%s stream not supported on this Client.\n"),
312 stream_to_ascii(attr->data_stream));
319 build_attr_output_fnames(jcr, attr);
322 stat = create_file(jcr, attr, &bfd, REPLACE_ALWAYS);
329 print_ls_output(jcr, attr);
334 set_attributes(jcr, attr, &bfd);
335 print_ls_output(jcr, attr);
343 /* Data stream and extracting */
344 case STREAM_FILE_DATA:
345 case STREAM_SPARSE_DATA:
346 case STREAM_WIN32_DATA:
349 if (rec->Stream == STREAM_SPARSE_DATA) {
352 wbuf = rec->data + SPARSE_FADDR_SIZE;
353 wsize = rec->data_len - SPARSE_FADDR_SIZE;
354 ser_begin(rec->data, SPARSE_FADDR_SIZE);
356 if (fileAddr != faddr) {
358 if (blseek(&bfd, (boffset_t)fileAddr, SEEK_SET) < 0) {
360 Emsg2(M_ERROR_TERM, 0, _("Seek error on %s: %s\n"),
361 attr->ofname, be.bstrerror());
366 wsize = rec->data_len;
369 Dmsg2(8, "Write %u bytes, total=%u\n", wsize, total);
370 if ((uint32_t)bwrite(&bfd, wbuf, wsize) != wsize) {
372 Emsg2(M_ERROR_TERM, 0, _("Write error on %s: %s\n"),
373 attr->ofname, be.bstrerror());
379 /* GZIP data stream */
380 case STREAM_GZIP_DATA:
381 case STREAM_SPARSE_GZIP_DATA:
382 case STREAM_WIN32_GZIP_DATA:
388 if (rec->Stream == STREAM_SPARSE_GZIP_DATA) {
392 wbuf = rec->data + SPARSE_FADDR_SIZE;
393 wsize = rec->data_len - SPARSE_FADDR_SIZE;
394 ser_begin(rec->data, SPARSE_FADDR_SIZE);
396 if (fileAddr != faddr) {
398 if (blseek(&bfd, (boffset_t)fileAddr, SEEK_SET) < 0) {
400 Emsg3(M_ERROR, 0, _("Seek to %s error on %s: ERR=%s\n"),
401 edit_uint64(fileAddr, ec1), attr->ofname, be.bstrerror());
408 wsize = rec->data_len;
410 compress_len = compress_buf_size;
411 if ((stat=uncompress((Bytef *)compress_buf, &compress_len,
412 (const Bytef *)wbuf, (uLong)wsize) != Z_OK)) {
413 Emsg1(M_ERROR, 0, _("Uncompression error. ERR=%d\n"), stat);
418 Dmsg2(100, "Write uncompressed %d bytes, total before write=%d\n", compress_len, total);
419 if ((uLongf)bwrite(&bfd, compress_buf, (size_t)compress_len) != compress_len) {
421 Pmsg0(0, _("===Write error===\n"));
422 Emsg2(M_ERROR, 0, _("Write error on %s: %s\n"),
423 attr->ofname, be.bstrerror());
427 total += compress_len;
428 fileAddr += compress_len;
429 Dmsg2(100, "Compress len=%d uncompressed=%d\n", rec->data_len,
434 Emsg0(M_ERROR, 0, _("GZIP data stream found, but GZIP not configured!\n"));
441 case STREAM_MD5_DIGEST:
442 case STREAM_SHA1_DIGEST:
443 case STREAM_SHA256_DIGEST:
444 case STREAM_SHA512_DIGEST:
447 case STREAM_SIGNED_DIGEST:
448 case STREAM_ENCRYPTED_SESSION_DATA:
449 // TODO landonf: Investigate crypto support in the storage daemon
452 case STREAM_PROGRAM_NAMES:
453 case STREAM_PROGRAM_DATA:
454 if (!prog_name_msg) {
455 Pmsg0(000, _("Got Program Name or Data Stream. Ignored.\n"));
461 /* If extracting, wierd stream (not 1 or 2), close output file anyway */
463 if (!is_bopen(&bfd)) {
464 Emsg0(M_ERROR, 0, _("Logic error output file should be open but is not.\n"));
466 set_attributes(jcr, attr, &bfd);
469 Jmsg(jcr, M_ERROR, 0, _("Unknown stream=%d ignored. This shouldn't happen!\n"),
477 /* Dummies to replace askdir.c */
478 bool dir_find_next_appendable_volume(DCR *dcr) { return 1;}
479 bool dir_update_volume_info(DCR *dcr, bool relabel) { return 1; }
480 bool dir_create_jobmedia_record(DCR *dcr) { return 1; }
481 bool dir_ask_sysop_to_create_appendable_volume(DCR *dcr) { return 1; }
482 bool dir_update_file_attributes(DCR *dcr, DEV_RECORD *rec) { return 1;}
483 bool dir_send_job_status(JCR *jcr) {return 1;}
486 bool dir_ask_sysop_to_mount_volume(DCR *dcr)
488 DEVICE *dev = dcr->dev;
489 fprintf(stderr, _("Mount Volume \"%s\" on device %s and press return when ready: "),
490 dcr->VolumeName, dev->print_name());
496 bool dir_get_volume_info(DCR *dcr, enum get_vol_info_rw writing)
498 Dmsg0(100, "Fake dir_get_volume_info\n");
499 bstrncpy(dcr->VolCatInfo.VolCatName, dcr->VolumeName, sizeof(dcr->VolCatInfo.VolCatName));
500 dcr->VolCatInfo.VolCatParts = find_num_dvd_parts(dcr);
501 Dmsg2(500, "Vol=%s num_parts=%d\n", dcr->VolCatInfo.VolCatName, dcr->VolCatInfo.VolCatParts);