2 Bacula® - The Network Backup Solution
4 Copyright (C) 2000-2008 Free Software Foundation Europe e.V.
6 The main author of Bacula is Kern Sibbald, with contributions from
7 many others, a complete list can be found in the file AUTHORS.
8 This program is Free Software; you can redistribute it and/or
9 modify it under the terms of version two of the GNU General Public
10 License as published by the Free Software Foundation and included
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 General Public License for more details.
18 You should have received a copy of the GNU 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
23 Bacula® is a registered trademark of John Walker.
24 The licensor of Bacula is the Free Software Foundation Europe
25 (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zürich,
26 Switzerland, email:ftf@fsfeurope.org.
30 * Dumb program to extract files from a Bacula backup.
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 if (*optarg == 't') {
130 dbg_timestamp = true;
132 debug_level = atoi(optarg);
133 if (debug_level <= 0) {
139 case 'e': /* exclude list */
140 if ((fd = fopen(optarg, "rb")) == NULL) {
142 Pmsg2(0, _("Could not open exclude file: %s, ERR=%s\n"),
143 optarg, be.bstrerror());
146 while (fgets(line, sizeof(line), fd) != NULL) {
147 strip_trailing_junk(line);
148 Dmsg1(900, "add_exclude %s\n", line);
149 add_fname_to_exclude_list(ff, line);
154 case 'i': /* include list */
155 if ((fd = fopen(optarg, "rb")) == NULL) {
157 Pmsg2(0, _("Could not open include file: %s, ERR=%s\n"),
158 optarg, be.bstrerror());
161 while (fgets(line, sizeof(line), fd) != NULL) {
162 strip_trailing_junk(line);
163 Dmsg1(900, "add_include %s\n", line);
164 add_fname_to_include_list(ff, 0, line);
178 case 'V': /* Volume name */
192 Pmsg0(0, _("Wrong number of arguments: \n"));
196 if (configfile == NULL) {
197 configfile = bstrdup(CONFIG_FILE);
200 parse_config(configfile);
202 if (!got_inc) { /* If no include file, */
203 add_fname_to_include_list(ff, 0, "/"); /* include everything */
213 Pmsg1(000, _("%d Program Name and/or Program Data Stream records ignored.\n"),
216 if (win32_data_msg) {
217 Pmsg1(000, _("%d Win32 data or Win32 gzip data stream records. Ignored.\n"),
220 term_include_exclude_files(ff);
225 static void do_extract(char *devname)
229 enable_backup_privileges(NULL, 1);
231 jcr = setup_jcr("bextract", devname, bsr, VolumeName, 1); /* acquire for read */
235 dev = jcr->read_dcr->dev;
241 /* Make sure where directory exists and that it is a directory */
242 if (stat(where, &statp) < 0) {
244 Emsg2(M_ERROR_TERM, 0, _("Cannot stat %s. It must exist. ERR=%s\n"),
245 where, be.bstrerror());
247 if (!S_ISDIR(statp.st_mode)) {
248 Emsg1(M_ERROR_TERM, 0, _("%s must be a directory.\n"), where);
252 jcr->where = bstrdup(where);
253 attr = new_attr(jcr);
255 compress_buf = get_memory(compress_buf_size);
257 read_records(dcr, record_cb, mount_next_read_volume);
258 /* If output file is still open, it was the last one in the
259 * archive since we just hit an end of file, so close the file.
261 if (is_bopen(&bfd)) {
262 set_attributes(jcr, attr, &bfd);
269 printf(_("%u files restored.\n"), num_files);
274 * Called here for each record from read_records()
276 static bool record_cb(DCR *dcr, DEV_RECORD *rec)
281 if (rec->FileIndex < 0) {
282 return true; /* we don't want labels */
285 /* File Attributes stream */
287 switch (rec->Stream) {
288 case STREAM_UNIX_ATTRIBUTES:
289 case STREAM_UNIX_ATTRIBUTES_EX:
291 /* If extracting, it was from previous stream, so
292 * close the output file.
295 if (!is_bopen(&bfd)) {
296 Emsg0(M_ERROR, 0, _("Logic error output file should be open but is not.\n"));
298 set_attributes(jcr, attr, &bfd);
302 if (!unpack_attributes_record(jcr, rec->Stream, rec->data, attr)) {
303 Emsg0(M_ERROR_TERM, 0, _("Cannot continue.\n"));
306 if (attr->file_index != rec->FileIndex) {
307 Emsg2(M_ERROR_TERM, 0, _("Record header file index %ld not equal record index %ld\n"),
308 rec->FileIndex, attr->file_index);
311 if (file_is_included(ff, attr->fname) && !file_is_excluded(ff, attr->fname)) {
313 attr->data_stream = decode_stat(attr->attr, &attr->statp, &attr->LinkFI);
314 if (!is_restore_stream_supported(attr->data_stream)) {
315 if (!non_support_data++) {
316 Jmsg(jcr, M_ERROR, 0, _("%s stream not supported on this Client.\n"),
317 stream_to_ascii(attr->data_stream));
324 build_attr_output_fnames(jcr, attr);
327 stat = create_file(jcr, attr, &bfd, REPLACE_ALWAYS);
334 print_ls_output(jcr, attr);
339 set_attributes(jcr, attr, &bfd);
340 print_ls_output(jcr, attr);
348 /* Data stream and extracting */
349 case STREAM_FILE_DATA:
350 case STREAM_SPARSE_DATA:
351 case STREAM_WIN32_DATA:
354 if (rec->Stream == STREAM_SPARSE_DATA) {
357 wbuf = rec->data + SPARSE_FADDR_SIZE;
358 wsize = rec->data_len - SPARSE_FADDR_SIZE;
359 ser_begin(rec->data, SPARSE_FADDR_SIZE);
361 if (fileAddr != faddr) {
363 if (blseek(&bfd, (boffset_t)fileAddr, SEEK_SET) < 0) {
365 Emsg2(M_ERROR_TERM, 0, _("Seek error on %s: %s\n"),
366 attr->ofname, be.bstrerror());
371 wsize = rec->data_len;
374 Dmsg2(8, "Write %u bytes, total=%u\n", wsize, total);
375 if ((uint32_t)bwrite(&bfd, wbuf, wsize) != wsize) {
377 Emsg2(M_ERROR_TERM, 0, _("Write error on %s: %s\n"),
378 attr->ofname, be.bstrerror());
384 /* GZIP data stream */
385 case STREAM_GZIP_DATA:
386 case STREAM_SPARSE_GZIP_DATA:
387 case STREAM_WIN32_GZIP_DATA:
393 if (rec->Stream == STREAM_SPARSE_GZIP_DATA) {
397 wbuf = rec->data + SPARSE_FADDR_SIZE;
398 wsize = rec->data_len - SPARSE_FADDR_SIZE;
399 ser_begin(rec->data, SPARSE_FADDR_SIZE);
401 if (fileAddr != faddr) {
403 if (blseek(&bfd, (boffset_t)fileAddr, SEEK_SET) < 0) {
405 Emsg3(M_ERROR, 0, _("Seek to %s error on %s: ERR=%s\n"),
406 edit_uint64(fileAddr, ec1), attr->ofname, be.bstrerror());
413 wsize = rec->data_len;
415 compress_len = compress_buf_size;
416 if ((stat=uncompress((Bytef *)compress_buf, &compress_len,
417 (const Bytef *)wbuf, (uLong)wsize) != Z_OK)) {
418 Emsg1(M_ERROR, 0, _("Uncompression error. ERR=%d\n"), stat);
423 Dmsg2(100, "Write uncompressed %d bytes, total before write=%d\n", compress_len, total);
424 if ((uLongf)bwrite(&bfd, compress_buf, (size_t)compress_len) != compress_len) {
426 Pmsg0(0, _("===Write error===\n"));
427 Emsg2(M_ERROR, 0, _("Write error on %s: %s\n"),
428 attr->ofname, be.bstrerror());
432 total += compress_len;
433 fileAddr += compress_len;
434 Dmsg2(100, "Compress len=%d uncompressed=%d\n", rec->data_len,
439 Emsg0(M_ERROR, 0, _("GZIP data stream found, but GZIP not configured!\n"));
446 case STREAM_MD5_DIGEST:
447 case STREAM_SHA1_DIGEST:
448 case STREAM_SHA256_DIGEST:
449 case STREAM_SHA512_DIGEST:
452 case STREAM_SIGNED_DIGEST:
453 case STREAM_ENCRYPTED_SESSION_DATA:
454 // TODO landonf: Investigate crypto support in the storage daemon
457 case STREAM_PROGRAM_NAMES:
458 case STREAM_PROGRAM_DATA:
459 if (!prog_name_msg) {
460 Pmsg0(000, _("Got Program Name or Data Stream. Ignored.\n"));
466 /* If extracting, wierd stream (not 1 or 2), close output file anyway */
468 if (!is_bopen(&bfd)) {
469 Emsg0(M_ERROR, 0, _("Logic error output file should be open but is not.\n"));
471 set_attributes(jcr, attr, &bfd);
474 Jmsg(jcr, M_ERROR, 0, _("Unknown stream=%d ignored. This shouldn't happen!\n"),
482 /* Dummies to replace askdir.c */
483 bool dir_find_next_appendable_volume(DCR *dcr) { return 1;}
484 bool dir_update_volume_info(DCR *dcr, bool relabel, bool update_LastWritten) { return 1; }
485 bool dir_create_jobmedia_record(DCR *dcr) { return 1; }
486 bool dir_ask_sysop_to_create_appendable_volume(DCR *dcr) { return 1; }
487 bool dir_update_file_attributes(DCR *dcr, DEV_RECORD *rec) { return 1;}
488 bool dir_send_job_status(JCR *jcr) {return 1;}
491 bool dir_ask_sysop_to_mount_volume(DCR *dcr, int /*mode*/)
493 DEVICE *dev = dcr->dev;
494 fprintf(stderr, _("Mount Volume \"%s\" on device %s and press return when ready: "),
495 dcr->VolumeName, dev->print_name());
501 bool dir_get_volume_info(DCR *dcr, enum get_vol_info_rw writing)
503 Dmsg0(100, "Fake dir_get_volume_info\n");
504 bstrncpy(dcr->VolCatInfo.VolCatName, dcr->VolumeName, sizeof(dcr->VolCatInfo.VolCatName));
505 dcr->VolCatInfo.VolCatParts = find_num_dvd_parts(dcr);
506 Dmsg2(500, "Vol=%s num_parts=%d\n", dcr->VolCatInfo.VolCatName, dcr->VolCatInfo.VolCatParts);