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.
26 #include "findlib/find.h"
28 #if defined(HAVE_CYGWIN) || defined(HAVE_WIN32)
34 static void do_extract(char *fname);
35 static bool record_cb(DCR *dcr, DEV_RECORD *rec);
37 static DEVICE *dev = NULL;
42 static BSR *bsr = NULL;
43 static bool extract = false;
44 static int non_support_data = 0;
45 static long total = 0;
48 static uint32_t num_files = 0;
49 static uint32_t compress_buf_size = 70000;
50 static POOLMEM *compress_buf;
51 static int prog_name_msg = 0;
52 static int win32_data_msg = 0;
53 static char *VolumeName = NULL;
55 static char *wbuf; /* write buffer address */
56 static uint32_t wsize; /* write size */
57 static uint64_t fileAddr = 0; /* file write address */
59 #define CONFIG_FILE "bacula-sd.conf"
60 char *configfile = NULL;
61 STORES *me = NULL; /* our Global resource */
62 bool forge_on = false;
63 pthread_mutex_t device_release_mutex = PTHREAD_MUTEX_INITIALIZER;
64 pthread_cond_t wait_device_release = PTHREAD_COND_INITIALIZER;
69 "Copyright (C) 2000-2005 Kern Sibbald.\n"
70 "\nVersion: " VERSION " (" BDATE ")\n\n"
71 "Usage: bextract <options> <bacula-archive-device-name> <directory-to-store-files>\n"
72 " -b <file> specify a bootstrap file\n"
73 " -c <file> specify a configuration file\n"
74 " -d <nn> set debug level to nn\n"
75 " -e <file> exclude list\n"
76 " -i <file> include list\n"
77 " -p proceed inspite of I/O errors\n"
79 " -V <volumes> specify Volume names (separated by |)\n"
80 " -? print this message\n\n");
85 int main (int argc, char *argv[])
92 working_directory = "/tmp";
93 my_name_is(argc, argv, "bextract");
94 init_msg(NULL, NULL); /* setup message handler */
96 ff = init_find_files();
99 while ((ch = getopt(argc, argv, "b:c:d:e:i:pvV:?")) != -1) {
101 case 'b': /* bootstrap file */
102 bsr = parse_bsr(NULL, optarg);
103 // dump_bsr(bsr, true);
106 case 'c': /* specify config file */
107 if (configfile != NULL) {
110 configfile = bstrdup(optarg);
113 case 'd': /* debug level */
114 debug_level = atoi(optarg);
115 if (debug_level <= 0)
119 case 'e': /* exclude list */
120 if ((fd = fopen(optarg, "r")) == NULL) {
122 Pmsg2(0, "Could not open exclude file: %s, ERR=%s\n",
123 optarg, be.strerror());
126 while (fgets(line, sizeof(line), fd) != NULL) {
127 strip_trailing_junk(line);
128 Dmsg1(900, "add_exclude %s\n", line);
129 add_fname_to_exclude_list(ff, line);
134 case 'i': /* include list */
135 if ((fd = fopen(optarg, "r")) == NULL) {
137 Pmsg2(0, "Could not open include file: %s, ERR=%s\n",
138 optarg, be.strerror());
141 while (fgets(line, sizeof(line), fd) != NULL) {
142 strip_trailing_junk(line);
143 Dmsg1(900, "add_include %s\n", line);
144 add_fname_to_include_list(ff, 0, line);
158 case 'V': /* Volume name */
172 Pmsg0(0, "Wrong number of arguments: \n");
176 if (configfile == NULL) {
177 configfile = bstrdup(CONFIG_FILE);
180 parse_config(configfile);
182 if (!got_inc) { /* If no include file, */
183 add_fname_to_include_list(ff, 0, "/"); /* include everything */
193 Pmsg1(000, "%d Program Name and/or Program Data Stream records ignored.\n",
196 if (win32_data_msg) {
197 Pmsg1(000, "%d Win32 data or Win32 gzip data stream records. Ignored.\n",
200 term_include_exclude_files(ff);
205 static void do_extract(char *devname)
208 jcr = setup_jcr("bextract", devname, bsr, VolumeName, 1); /* acquire for read */
218 /* Make sure where directory exists and that it is a directory */
219 if (stat(where, &statp) < 0) {
221 Emsg2(M_ERROR_TERM, 0, "Cannot stat %s. It must exist. ERR=%s\n",
222 where, be.strerror());
224 if (!S_ISDIR(statp.st_mode)) {
225 Emsg1(M_ERROR_TERM, 0, "%s must be a directory.\n", where);
229 jcr->where = bstrdup(where);
232 compress_buf = get_memory(compress_buf_size);
234 read_records(dcr, record_cb, mount_next_read_volume);
235 /* If output file is still open, it was the last one in the
236 * archive since we just hit an end of file, so close the file.
238 if (is_bopen(&bfd)) {
239 set_attributes(jcr, attr, &bfd);
246 printf("%u files restored.\n", num_files);
251 * Called here for each record from read_records()
253 static bool record_cb(DCR *dcr, DEV_RECORD *rec)
258 if (rec->FileIndex < 0) {
259 return true; /* we don't want labels */
262 /* File Attributes stream */
264 switch (rec->Stream) {
265 case STREAM_UNIX_ATTRIBUTES:
266 case STREAM_UNIX_ATTRIBUTES_EX:
268 /* If extracting, it was from previous stream, so
269 * close the output file.
272 if (!is_bopen(&bfd)) {
273 Emsg0(M_ERROR, 0, _("Logic error output file should be open but is not.\n"));
275 set_attributes(jcr, attr, &bfd);
279 if (!unpack_attributes_record(jcr, rec->Stream, rec->data, attr)) {
280 Emsg0(M_ERROR_TERM, 0, _("Cannot continue.\n"));
283 if (attr->file_index != rec->FileIndex) {
284 Emsg2(M_ERROR_TERM, 0, _("Record header file index %ld not equal record index %ld\n"),
285 rec->FileIndex, attr->file_index);
288 if (file_is_included(ff, attr->fname) && !file_is_excluded(ff, attr->fname)) {
290 attr->data_stream = decode_stat(attr->attr, &attr->statp, &attr->LinkFI);
291 if (!is_stream_supported(attr->data_stream)) {
292 if (!non_support_data++) {
293 Jmsg(jcr, M_ERROR, 0, _("%s stream not supported on this Client.\n"),
294 stream_to_ascii(attr->data_stream));
301 build_attr_output_fnames(jcr, attr);
304 stat = create_file(jcr, attr, &bfd, REPLACE_ALWAYS);
311 print_ls_output(jcr, attr);
316 set_attributes(jcr, attr, &bfd);
317 print_ls_output(jcr, attr);
325 /* Data stream and extracting */
326 case STREAM_FILE_DATA:
327 case STREAM_SPARSE_DATA:
328 case STREAM_WIN32_DATA:
331 if (rec->Stream == STREAM_SPARSE_DATA) {
334 wbuf = rec->data + SPARSE_FADDR_SIZE;
335 wsize = rec->data_len - SPARSE_FADDR_SIZE;
336 ser_begin(rec->data, SPARSE_FADDR_SIZE);
338 if (fileAddr != faddr) {
340 if (blseek(&bfd, (off_t)fileAddr, SEEK_SET) < 0) {
342 Emsg2(M_ERROR_TERM, 0, _("Seek error on %s: %s\n"),
343 attr->ofname, be.strerror());
348 wsize = rec->data_len;
351 Dmsg2(8, "Write %u bytes, total=%u\n", wsize, total);
352 if ((uint32_t)bwrite(&bfd, wbuf, wsize) != wsize) {
354 Emsg2(M_ERROR_TERM, 0, _("Write error on %s: %s\n"),
355 attr->ofname, be.strerror());
361 /* GZIP data stream */
362 case STREAM_GZIP_DATA:
363 case STREAM_SPARSE_GZIP_DATA:
364 case STREAM_WIN32_GZIP_DATA:
370 if (rec->Stream == STREAM_SPARSE_GZIP_DATA) {
374 wbuf = rec->data + SPARSE_FADDR_SIZE;
375 wsize = rec->data_len - SPARSE_FADDR_SIZE;
376 ser_begin(rec->data, SPARSE_FADDR_SIZE);
378 if (fileAddr != faddr) {
380 if (blseek(&bfd, (off_t)fileAddr, SEEK_SET) < 0) {
382 Emsg3(M_ERROR, 0, _("Seek to %s error on %s: ERR=%s\n"),
383 edit_uint64(fileAddr, ec1), attr->ofname, be.strerror());
390 wsize = rec->data_len;
392 compress_len = compress_buf_size;
393 if ((stat=uncompress((Bytef *)compress_buf, &compress_len,
394 (const Bytef *)wbuf, (uLong)wsize) != Z_OK)) {
395 Emsg1(M_ERROR, 0, _("Uncompression error. ERR=%d\n"), stat);
400 Dmsg2(100, "Write uncompressed %d bytes, total before write=%d\n", compress_len, total);
401 if ((uLongf)bwrite(&bfd, compress_buf, (size_t)compress_len) != compress_len) {
403 Pmsg0(0, "===Write error===\n");
404 Emsg2(M_ERROR, 0, _("Write error on %s: %s\n"),
405 attr->ofname, be.strerror());
409 total += compress_len;
410 fileAddr += compress_len;
411 Dmsg2(100, "Compress len=%d uncompressed=%d\n", rec->data_len,
416 Emsg0(M_ERROR, 0, "GZIP data stream found, but GZIP not configured!\n");
423 case STREAM_MD5_SIGNATURE:
424 case STREAM_SHA1_SIGNATURE:
427 case STREAM_PROGRAM_NAMES:
428 case STREAM_PROGRAM_DATA:
429 if (!prog_name_msg) {
430 Pmsg0(000, "Got Program Name or Data Stream. Ignored.\n");
436 /* If extracting, wierd stream (not 1 or 2), close output file anyway */
438 if (!is_bopen(&bfd)) {
439 Emsg0(M_ERROR, 0, "Logic error output file should be open but is not.\n");
441 set_attributes(jcr, attr, &bfd);
444 Jmsg(jcr, M_ERROR, 0, _("Unknown stream=%d ignored. This shouldn't happen!\n"),
452 /* Dummies to replace askdir.c */
453 bool dir_get_volume_info(DCR *dcr, enum get_vol_info_rw writing) { return 1;}
454 bool dir_find_next_appendable_volume(DCR *dcr) { return 1;}
455 bool dir_update_volume_info(DCR *dcr, bool relabel) { return 1; }
456 bool dir_create_jobmedia_record(DCR *dcr) { return 1; }
457 bool dir_ask_sysop_to_create_appendable_volume(DCR *dcr) { return 1; }
458 bool dir_update_file_attributes(DCR *dcr, DEV_RECORD *rec) { return 1;}
459 bool dir_send_job_status(JCR *jcr) {return 1;}
462 bool dir_ask_sysop_to_mount_volume(DCR *dcr)
464 DEVICE *dev = dcr->dev;
465 fprintf(stderr, "Mount Volume \"%s\" on device %s and press return when ready: ",
466 dcr->VolumeName, dev->print_name());