/*
- Bacula® - The Network Backup Solution
-
- Copyright (C) 2000-2009 Free Software Foundation Europe e.V.
-
- The main author of Bacula is Kern Sibbald, with contributions from
- many others, a complete list can be found in the file AUTHORS.
- This program is Free Software; you can redistribute it and/or
- modify it under the terms of version two of the GNU General Public
- License as published by the Free Software Foundation and included
- in the file LICENSE.
-
- This program is distributed in the hope that it will be useful, but
- WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, write to the Free Software
- Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
- 02110-1301, USA.
-
- Bacula® is a registered trademark of Kern Sibbald.
- The licensor of Bacula is the Free Software Foundation Europe
- (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zürich,
- Switzerland, email:ftf@fsfeurope.org.
+ Bacula(R) - The Network Backup Solution
+
+ Copyright (C) 2000-2015 Kern Sibbald
+
+ The original author of Bacula is Kern Sibbald, with contributions
+ from many others, a complete list can be found in the file AUTHORS.
+
+ You may use this file and others of this release according to the
+ license defined in the LICENSE file, which includes the Affero General
+ Public License, v3.0 ("AGPLv3") and some additional permissions and
+ terms pursuant to its AGPLv3 Section 7.
+
+ This notice must be preserved when any source code is
+ conveyed and/or propagated.
+
+ Bacula(R) is a registered trademark of Kern Sibbald.
*/
-/*
+/**
* Bacula Director -- restore.c -- responsible for restoring files
*
- * Kern Sibbald, November MM
+ * Written by Kern Sibbald, November MM
*
* This routine is run as a separate thread.
*
* to do the restore.
* Update the DB according to what files where restored????
*
- * Version $Id$
*/
#include "bacula.h"
#include "dird.h"
+#include "lib/ini.h"
/* Commands sent to File daemon */
-static char restorecmd[] = "restore replace=%c prelinks=%d where=%s\n";
-static char restorecmdR[] = "restore replace=%c prelinks=%d regexwhere=%s\n";
-static char storaddr[] = "storage address=%s port=%d ssl=0 Authorization=%s\n";
+static char restorecmd[] = "restore %sreplace=%c prelinks=%d where=%s\n";
+static char restorecmdR[] = "restore %sreplace=%c prelinks=%d regexwhere=%s\n";
+static char storaddr[] = "storage address=%s port=%d ssl=%d Authorization=%s\n";
/* Responses received from File daemon */
static char OKrestore[] = "2000 OK restore\n";
{
char replace, *where, *cmd;
char empty = '\0';
+ char files[100];
/* Build the restore command */
} else {
replace = REPLACE_ALWAYS; /* always replace */
}
-
+
if (jcr->RegexWhere) {
where = jcr->RegexWhere; /* override */
cmd = restorecmdR;
} else { /* nothing was specified */
where = ∅ /* use default */
- cmd = restorecmd;
+ cmd = restorecmd;
}
-
+
jcr->prefix_links = jcr->job->PrefixLinks;
bash_spaces(where);
- Mmsg(ret, cmd, replace, jcr->prefix_links, where);
+ if (jcr->FDVersion < 7) {
+ Mmsg(ret, cmd, "", replace, jcr->prefix_links, where);
+ } else {
+ snprintf(files, sizeof(files), "files=%d ", jcr->ExpectedFiles);
+ Mmsg(ret, cmd, files, replace, jcr->prefix_links, where);
+ }
unbash_spaces(where);
}
#define UA_CMD_SIZE 1000
-/* Open the bootstrap file and find the first Storage=
+/**
+ * Open the bootstrap file and find the first Storage=
* Returns ok if able to open
- * It fills the storage name (should be the first line)
- * and the file descriptor to the bootstrap file,
+ * It fills the storage name (should be the first line)
+ * and the file descriptor to the bootstrap file,
* it should be used for next operations, and need to be closed
* at the end.
*/
-static bool open_bootstrap_file(JCR *jcr, struct bootstrap_info &info)
+static bool open_bootstrap_file(JCR *jcr, bootstrap_info &info)
{
FILE *bs;
UAContext *ua;
}
strncpy(info.storage, jcr->rstore->name(), MAX_NAME_LENGTH);
- bs = fopen(jcr->RestoreBootstrap, "rb");
+ bs = bfopen(jcr->RestoreBootstrap, "rb");
if (!bs) {
berrno be;
Jmsg(jcr, M_FATAL, 0, _("Could not open bootstrap file %s: ERR=%s\n"),
jcr->RestoreBootstrap, be.bstrerror());
- set_jcr_job_status(jcr, JS_ErrorTerminated);
+ jcr->setJobStatus(JS_ErrorTerminated);
return false;
}
return true;
}
-/*
+/**
* This function compare the given storage name with the
* the current one. We compare the name and the address:port.
* Returns true if we use the same storage.
if (jcr->FDVersion < 2) {
return true;
}
- /* we are in init loop ? shoudn't fall here */
+ /* we are in init loop ? shoudn't fail here */
if (!*new_one) {
return true;
}
}
new_store = (STORE *)GetResWithName(R_STORAGE, new_one);
if (!new_store) {
- Jmsg(jcr, M_FATAL, 0,
+ Jmsg(jcr, M_WARNING, 0,
_("Could not get storage resource '%s'.\n"), new_one);
- set_jcr_job_status(jcr, JS_ErrorTerminated);
- return false;
+ /* If not storage found, use last one */
+ return true;
}
/* if Port and Hostname/IP are same, we are talking to the same
* Storage Daemon
return true;
}
-/*
+/**
* Check if the current line contains Storage="xxx", and compare the
- * result to the current storage. We use UAContext to analyse the bsr
+ * result to the current storage. We use UAContext to analyse the bsr
* string.
*
* Returns true if we need to change the storage, and it set the new
- * Storage resource name in "storage" arg.
+ * Storage resource name in "storage" arg.
*/
-static bool check_for_new_storage(JCR *jcr, struct bootstrap_info &info)
+static bool check_for_new_storage(JCR *jcr, bootstrap_info &info)
{
UAContext *ua = info.ua;
parse_ua_args(ua);
return false;
}
-/*
+/**
* Send bootstrap file to Storage daemon section by section.
*/
static bool send_bootstrap_file(JCR *jcr, BSOCK *sock,
- struct bootstrap_info &info)
+ bootstrap_info &info)
{
boffset_t pos;
const char *bootstrap = "bootstrap\n";
while(fgets(ua->cmd, UA_CMD_SIZE, bs)) {
if (check_for_new_storage(jcr, info)) {
/* Otherwise, we need to contact another storage daemon.
- * Reset bs to the beginning of the current segment.
+ * Reset bs to the beginning of the current segment.
*/
fseeko(bs, pos, SEEK_SET);
break;
return true;
}
-/*
+#define MAX_TRIES 6 * 360 /* 6 hours */
+
+/**
* Change the read storage resource for the current job.
*/
-static void select_rstore(JCR *jcr, struct bootstrap_info &info)
+static bool select_rstore(JCR *jcr, bootstrap_info &info)
{
USTORE ustore;
+ int i;
+
+
if (!strcmp(jcr->rstore->name(), info.storage)) {
- return;
+ return true; /* same SD nothing to change */
}
if (!(ustore.store = (STORE *)GetResWithName(R_STORAGE,info.storage))) {
Jmsg(jcr, M_FATAL, 0,
_("Could not get storage resource '%s'.\n"), info.storage);
- set_jcr_job_status(jcr, JS_ErrorTerminated);
- return;
- }
-
- if (jcr->store_bsock) {
- jcr->store_bsock->destroy();
- jcr->store_bsock = NULL;
+ jcr->setJobStatus(JS_ErrorTerminated);
+ return false;
}
-
+
+ /*
+ * This releases the store_bsock between calls to the SD.
+ * I think.
+ */
+ free_bsock(jcr->store_bsock);
+
+ /*
+ * release current read storage and get a new one
+ */
+ dec_read_store(jcr);
free_rstorage(jcr);
set_rstorage(jcr, &ustore);
+ jcr->setJobStatus(JS_WaitSD);
+ /*
+ * Wait for up to 6 hours to increment read stoage counter
+ */
+ for (i=0; i < MAX_TRIES; i++) {
+ /* try to get read storage counter incremented */
+ if (inc_read_store(jcr)) {
+ jcr->setJobStatus(JS_Running);
+ return true;
+ }
+ bmicrosleep(10, 0); /* sleep 10 secs */
+ if (job_canceled(jcr)) {
+ free_rstorage(jcr);
+ return false;
+ }
+ }
+ /* Failed to inc_read_store() */
+ free_rstorage(jcr);
+ Jmsg(jcr, M_FATAL, 0,
+ _("Could not acquire read storage lock for \"%s\""), info.storage);
+ return false;
}
-/*
- * Clean the struct bootstrap_info struct
+/*
+ * Clean the bootstrap_info struct
*/
-static void close_bootstrap_file(struct bootstrap_info &info)
+static void close_bootstrap_file(bootstrap_info &info)
{
if (info.bs) {
fclose(info.bs);
}
}
-/*
- * Take a bootstrap and for each different storage, we change the storage
- * resource and start a new restore session between the client and the storage
- *
+/**
+ * The bootstrap is stored in a file, so open the file, and loop
+ * through it processing each storage device in turn. If the
+ * storage is different from the prior one, we open a new connection
+ * to the new storage and do a restore for that part.
+ * This permits handling multiple storage daemons for a single
+ * restore. E.g. your Full is stored on tape, and Incrementals
+ * on disk.
*/
bool restore_bootstrap(JCR *jcr)
{
- BSOCK *fd=NULL, *sd;
- bool end_loop=false;
- bool first_time=true;
- struct bootstrap_info info;
+ int tls_need = BNET_TLS_NONE;
+ BSOCK *fd = NULL;
+ BSOCK *sd;
+ char *store_address;
+ uint32_t store_port;
+ bool first_time = true;
+ bootstrap_info info;
POOL_MEM restore_cmd(PM_MESSAGE);
- bool ret=false;
+ bool ret = false;
+
- /* this command is used for each part */
- build_restore_command(jcr, restore_cmd);
-
+ /* Open the bootstrap file */
if (!open_bootstrap_file(jcr, info)) {
goto bail_out;
}
- while (!end_loop && !feof(info.bs)) {
-
- select_rstore(jcr, info);
+ /* Read the bootstrap file */
+ while (!feof(info.bs)) {
- /*
+ if (!select_rstore(jcr, info)) {
+ goto bail_out;
+ }
+
+ /**
* Open a message channel connection with the Storage
* daemon. This is to let him know that our client
* will be contacting him for a backup session.
*
*/
Dmsg0(10, "Open connection with storage daemon\n");
- set_jcr_job_status(jcr, JS_WaitSD);
+ jcr->setJobStatus(JS_WaitSD);
/*
* Start conversation with Storage daemon
*/
/*
* Start conversation with File daemon
*/
- set_jcr_job_status(jcr, JS_WaitFD);
+ jcr->setJobStatus(JS_WaitFD);
jcr->keep_sd_auth_key = true; /* don't clear the sd_auth_key now */
if (!connect_to_file_daemon(jcr, 10, FDConnectTimeout, 1)) {
goto bail_out;
}
-
fd = jcr->file_bsock;
+ build_restore_command(jcr, restore_cmd);
}
- set_jcr_job_status(jcr, JS_WaitSD);
+ jcr->setJobStatus(JS_Running);
/*
* Send the bootstrap file -- what Volumes/files to restore
goto bail_out;
}
- if (!sd->fsend("run")) {
- goto bail_out;
+ if (jcr->sd_calls_client) {
+ /*
+ * SD must call "client" i.e. FD
+ */
+ if (jcr->FDVersion < 10) {
+ Jmsg(jcr, M_FATAL, 0, _("The File daemon does not support SDCallsClient.\n"));
+ goto bail_out;
+ }
+ if (!send_client_addr_to_sd(jcr)) {
+ goto bail_out;
+ }
+ if (!run_storage_and_start_message_thread(jcr, sd)) {
+ goto bail_out;
+ }
+
+ store_address = jcr->rstore->address; /* dummy */
+ store_port = 0; /* flag that SD calls FD */
+
+ } else {
+ /*
+ * Default case where FD must call the SD
+ */
+ if (!run_storage_and_start_message_thread(jcr, sd)) {
+ goto bail_out;
+ }
+
+ /*
+ * send Storage daemon address to the File daemon,
+ * then wait for File daemon to make connection
+ * with Storage daemon.
+ */
+ if (jcr->rstore->SDDport == 0) {
+ jcr->rstore->SDDport = jcr->rstore->SDport;
+ }
+
+ store_address = get_storage_address(jcr->client, jcr->rstore);
+ store_port = jcr->rstore->SDDport;
}
- /*
- * Now start a Storage daemon message thread
- */
- if (!start_storage_daemon_message_thread(jcr)) {
- goto bail_out;
+
+ /* TLS Requirement */
+ if (jcr->rstore->tls_enable) {
+ if (jcr->rstore->tls_require) {
+ tls_need = BNET_TLS_REQUIRED;
+ } else {
+ tls_need = BNET_TLS_OK;
+ }
}
- Dmsg0(50, "Storage daemon connection OK\n");
/*
- * send Storage daemon address to the File daemon,
- * then wait for File daemon to make connection
- * with Storage daemon.
+ * Send storage address to FD
+ * if port==0 FD must wait for SD to call it.
*/
- if (jcr->rstore->SDDport == 0) {
- jcr->rstore->SDDport = jcr->rstore->SDport;
- }
- fd->fsend(storaddr, jcr->rstore->address, jcr->rstore->SDDport,
- jcr->sd_auth_key);
+ fd->fsend(storaddr, store_address, store_port, tls_need, jcr->sd_auth_key);
memset(jcr->sd_auth_key, 0, strlen(jcr->sd_auth_key));
-
Dmsg1(6, "dird>filed: %s\n", fd->msg);
if (!response(jcr, fd, OKstore, "Storage", DISPLAY_ERROR)) {
goto bail_out;
}
+ /* Declare the job started to start the MaxRunTime check */
+ jcr->setJobStarted();
+
+ /* Only pass "global" commands to the FD once */
if (first_time) {
+ first_time = false;
if (!send_runscripts_commands(jcr)) {
goto bail_out;
}
- first_time=false;
+ if (!send_component_info(jcr)) {
+ Pmsg0(000, "FAIL: Send component info\n");
+ goto bail_out;
+ }
+ if (!send_restore_objects(jcr)) {
+ Pmsg0(000, "FAIL: Send restore objects\n");
+ goto bail_out;
+ }
}
fd->fsend("%s", restore_cmd.c_str());
-
if (!response(jcr, fd, OKrestore, "Restore", DISPLAY_ERROR)) {
goto bail_out;
}
if (jcr->FDVersion < 2) { /* Old FD */
- end_loop=true; /* we do only one loop */
-
+ break; /* we do only one loop */
} else {
if (!response(jcr, fd, OKstoreend, "Store end", DISPLAY_ERROR)) {
goto bail_out;
return ret;
}
-/*
+/**
* Do a restore of the specified files
*
* Returns: 0 on failure
/* Print Job Start message */
Jmsg(jcr, M_INFO, 0, _("Start Restore Job %s\n"), jcr->Job);
+ if (jcr->client) {
+ jcr->sd_calls_client = jcr->client->sd_calls_client;
+ }
+
+ /* Read the bootstrap file and do the restore */
if (!restore_bootstrap(jcr)) {
goto bail_out;
}
return false;
}
-bool do_restore_init(JCR *jcr)
+/* Create a Plugin Config RestoreObject, will be sent
+ * at restore time to the Plugin
+ */
+static void plugin_create_restoreobject(JCR *jcr, plugin_config_item *elt)
+{
+ ROBJECT_DBR ro;
+ memset(&ro, 0, sizeof(ro));
+ ro.FileIndex = 1;
+ ro.JobId = jcr->JobId;
+ ro.FileType = FT_PLUGIN_CONFIG_FILLED;
+ ro.object_index = 1;
+ ro.object_full_len = ro.object_len = strlen(elt->content);
+ ro.object_compression = 0;
+ ro.plugin_name = elt->plugin_name;
+ ro.object_name = (char*)INI_RESTORE_OBJECT_NAME;
+ ro.object = elt->content;
+ db_create_restore_object_record(jcr, jcr->db, &ro);
+ Dmsg1(50, "Creating restore object for %s\n", elt->plugin_name);
+}
+
+bool do_restore_init(JCR *jcr)
{
+ /* Will add RestoreObject used for the Plugin configuration */
+ if (jcr->plugin_config) {
+
+ plugin_config_item *elt;
+ foreach_alist(elt, jcr->plugin_config) {
+ plugin_create_restoreobject(jcr, elt);
+ free_plugin_config_item(elt);
+ }
+
+ delete jcr->plugin_config;
+ jcr->plugin_config = NULL;
+ }
free_wstorage(jcr);
return true;
}
-/*
+/**
* Release resources allocated during restore.
*
*/
void restore_cleanup(JCR *jcr, int TermCode)
{
char sdt[MAX_TIME_LENGTH], edt[MAX_TIME_LENGTH];
- char ec1[30], ec2[30], ec3[30];
+ char ec1[30], ec2[30], ec3[30], ec4[30], elapsed[50];
char term_code[100], fd_term_msg[100], sd_term_msg[100];
const char *term_msg;
int msg_type = M_INFO;
double kbps;
+ utime_t RunTime;
Dmsg0(20, "In restore_cleanup\n");
update_job_end(jcr, TermCode);
+ if (jcr->component_fd) {
+ fclose(jcr->component_fd);
+ jcr->component_fd = NULL;
+ }
+ if (jcr->component_fname && *jcr->component_fname) {
+ unlink(jcr->component_fname);
+ }
+ free_and_null_pool_memory(jcr->component_fname);
+
if (jcr->unlink_bsr && jcr->RestoreBootstrap) {
unlink(jcr->RestoreBootstrap);
jcr->unlink_bsr = false;
if (job_canceled(jcr)) {
cancel_storage_daemon_job(jcr);
- }
+ }
switch (TermCode) {
case JS_Terminated:
if (jcr->ExpectedFiles > jcr->jr.JobFiles) {
term_msg = _("Restore OK -- warning file count mismatch");
+
+ } else if (jcr->JobErrors > 0 || jcr->SDErrors > 0) {
+ term_msg = _("Restore OK -- with errors");
+
} else {
term_msg = _("Restore OK");
}
msg_type = M_ERROR; /* Generate error message */
if (jcr->store_bsock) {
jcr->store_bsock->signal(BNET_TERMINATE);
- if (jcr->SD_msg_chan) {
+ if (jcr->SD_msg_chan_started) {
pthread_cancel(jcr->SD_msg_chan);
}
}
term_msg = _("Restore Canceled");
if (jcr->store_bsock) {
jcr->store_bsock->signal(BNET_TERMINATE);
- if (jcr->SD_msg_chan) {
+ if (jcr->SD_msg_chan_started) {
pthread_cancel(jcr->SD_msg_chan);
}
}
}
bstrftimes(sdt, sizeof(sdt), jcr->jr.StartTime);
bstrftimes(edt, sizeof(edt), jcr->jr.EndTime);
- if (jcr->jr.EndTime - jcr->jr.StartTime > 0) {
- kbps = (double)jcr->jr.JobBytes / (1000 * (jcr->jr.EndTime - jcr->jr.StartTime));
- } else {
- kbps = 0;
+
+ RunTime = jcr->jr.EndTime - jcr->jr.StartTime;
+ if (RunTime <= 0) {
+ RunTime = 1;
}
+ kbps = (double)jcr->jr.JobBytes / (1000.0 * (double)RunTime);
if (kbps < 0.05) {
kbps = 0;
}
jobstatus_to_ascii(jcr->FDJobStatus, fd_term_msg, sizeof(fd_term_msg));
jobstatus_to_ascii(jcr->SDJobStatus, sd_term_msg, sizeof(sd_term_msg));
- Jmsg(jcr, msg_type, 0, _("%s %s %s (%s): %s\n"
+ Jmsg(jcr, msg_type, 0, _("%s %s %s (%s):\n"
" Build OS: %s %s %s\n"
" JobId: %d\n"
" Job: %s\n"
" Restore Client: %s\n"
" Start time: %s\n"
" End time: %s\n"
+" Elapsed time: %s\n"
" Files Expected: %s\n"
" Files Restored: %s\n"
-" Bytes Restored: %s\n"
+" Bytes Restored: %s (%sB)\n"
" Rate: %.1f KB/s\n"
" FD Errors: %d\n"
" FD termination status: %s\n"
" SD termination status: %s\n"
" Termination: %s\n\n"),
- BACULA, my_name, VERSION, LSMDATE, edt,
+ BACULA, my_name, VERSION, LSMDATE,
HOST_OS, DISTNAME, DISTVER,
jcr->jr.JobId,
jcr->jr.Job,
jcr->client->name(),
sdt,
edt,
+ edit_utime(RunTime, elapsed, sizeof(elapsed)),
edit_uint64_with_commas((uint64_t)jcr->ExpectedFiles, ec1),
edit_uint64_with_commas((uint64_t)jcr->jr.JobFiles, ec2),
- edit_uint64_with_commas(jcr->jr.JobBytes, ec3),
+ edit_uint64_with_commas(jcr->jr.JobBytes, ec3), edit_uint64_with_suffix(jcr->jr.JobBytes, ec4),
(float)kbps,
jcr->JobErrors,
fd_term_msg,