static inline int ndmp_wait_for_job_termination(JCR *jcr) { jcr->setJobStatus(JS_Running); /* * Force cancel in SD if failing, but not for Incomplete jobs * so that we let the SD despool. */ Dmsg4(100, "cancel=%d FDJS=%d JS=%d SDJS=%d\n", jcr->is_canceled(), jcr->FDJobStatus, jcr->JobStatus, jcr->SDJobStatus); if (jcr->is_canceled() || (!jcr->res.job->RescheduleIncompleteJobs)) { Dmsg3(100, "FDJS=%d JS=%d SDJS=%d\n", jcr->FDJobStatus, jcr->JobStatus, jcr->SDJobStatus); cancel_storage_daemon_job(jcr); } /* * Note, the SD stores in jcr->JobFiles/ReadBytes/JobBytes/JobErrors */ wait_for_storage_daemon_termination(jcr); jcr->FDJobStatus = JS_Terminated; if (jcr->JobStatus != JS_Terminated) { return jcr->JobStatus; } if (jcr->FDJobStatus != JS_Terminated) { return jcr->FDJobStatus; } return jcr->SDJobStatus; }
/* * Do a virtual backup, which consolidates all previous backups into * a sort of synthetic Full. * * Returns: false on failure * true on success */ bool do_native_vbackup(JCR *jcr) { char ed1[100]; BSOCK *sd; char *p; db_list_ctx jobids; if (!jcr->res.rstorage) { Jmsg(jcr, M_FATAL, 0, _("No storage for reading given.\n")); return false; } if (!jcr->res.wstorage) { Jmsg(jcr, M_FATAL, 0, _("No storage for writing given.\n")); return false; } Dmsg2(100, "rstorage=%p wstorage=%p\n", jcr->res.rstorage, jcr->res.wstorage); Dmsg2(100, "Read store=%s, write store=%s\n", ((STORERES *)jcr->res.rstorage->first())->name(), ((STORERES *)jcr->res.wstorage->first())->name()); /* * Print Job Start message */ Jmsg(jcr, M_INFO, 0, _("Start Virtual Backup JobId %s, Job=%s\n"), edit_uint64(jcr->JobId, ed1), jcr->Job); if (!jcr->accurate) { Jmsg(jcr, M_WARNING, 0, _("This Job is not an Accurate backup so is not equivalent to a Full backup.\n")); } db_accurate_get_jobids(jcr, jcr->db, &jcr->jr, &jobids); Dmsg1(10, "Accurate jobids=%s\n", jobids.list); if (jobids.count == 0) { Jmsg(jcr, M_FATAL, 0, _("No previous Jobs found.\n")); return false; } /* * Now we find the last job that ran and store it's info in * the previous_jr record. We will set our times to the * values from that job so that anything changed after that * time will be picked up on the next backup. */ p = strrchr(jobids.list, ','); /* find last jobid */ if (p != NULL) { p++; } else { p = jobids.list; } memset(&jcr->previous_jr, 0, sizeof(jcr->previous_jr)); jcr->previous_jr.JobId = str_to_int64(p); Dmsg1(10, "Previous JobId=%s\n", p); if (!db_get_job_record(jcr, jcr->db, &jcr->previous_jr)) { Jmsg(jcr, M_FATAL, 0, _("Error getting Job record for previous Job: ERR=%s"), db_strerror(jcr->db)); return false; } if (!create_bootstrap_file(jcr, jobids.list)) { Jmsg(jcr, M_FATAL, 0, _("Could not get or create the FileSet record.\n")); return false; } /* * 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(110, "Open connection with storage daemon\n"); jcr->setJobStatus(JS_WaitSD); /* * Start conversation with Storage daemon */ if (!connect_to_storage_daemon(jcr, 10, me->SDConnectTimeout, true)) { return false; } sd = jcr->store_bsock; /* * Now start a job with the Storage daemon */ if (!start_storage_daemon_job(jcr, jcr->res.rstorage, jcr->res.wstorage, /* send_bsr */ true)) { return false; } Dmsg0(100, "Storage daemon connection OK\n"); /* * We re-update the job start record so that the start * time is set after the run before job. This avoids * that any files created by the run before job will * be saved twice. They will be backed up in the current * job, but not in the next one unless they are changed. * Without this, they will be backed up in this job and * in the next job run because in that case, their date * is after the start of this run. */ jcr->start_time = time(NULL); jcr->jr.StartTime = jcr->start_time; jcr->jr.JobTDate = jcr->start_time; jcr->setJobStatus(JS_Running); /* * Update job start record */ if (!db_update_job_start_record(jcr, jcr->db, &jcr->jr)) { Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db)); return false; } /* * Declare the job started to start the MaxRunTime check */ jcr->setJobStarted(); /* * Start the job prior to starting the message thread below * to avoid two threads from using the BSOCK structure at * the same time. */ if (!sd->fsend("run")) { return false; } /* * Now start a Storage daemon message thread */ if (!start_storage_daemon_message_thread(jcr)) { return false; } jcr->setJobStatus(JS_Running); /* * Pickup Job termination data * Note, the SD stores in jcr->JobFiles/ReadBytes/JobBytes/JobErrors */ wait_for_storage_daemon_termination(jcr); jcr->setJobStatus(jcr->SDJobStatus); db_write_batch_file_records(jcr); /* used by bulk batch file insert */ if (!jcr->is_JobStatus(JS_Terminated)) { return false; } native_vbackup_cleanup(jcr, jcr->JobStatus); return true; }
/* * 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. */ static inline bool do_ndmp_restore_bootstrap(JCR *jcr) { int cnt; BSOCK *sd; BSR *bsr; NIS *nis = NULL; int32_t current_fi; bootstrap_info info; BSR_FINDEX *fileindex; struct ndm_session ndmp_sess; struct ndm_job_param ndmp_job; bool session_initialized = false; bool retval = false; int NdmpLoglevel; if (jcr->res.client->ndmp_loglevel > me->ndmp_loglevel) { NdmpLoglevel = jcr->res.client->ndmp_loglevel; } else { NdmpLoglevel = me->ndmp_loglevel; } /* * We first parse the BSR ourself so we know what to restore. */ jcr->bsr = parse_bsr(jcr, jcr->RestoreBootstrap); if (!jcr->bsr) { Jmsg(jcr, M_FATAL, 0, _("Error parsing bootstrap file.\n")); goto bail_out; } /* * Setup all paired read storage. */ set_paired_storage(jcr); if (!jcr->res.pstore) { Jmsg(jcr, M_FATAL, 0, _("Read storage %s doesn't point to storage definition with paired storage option.\n"), jcr->res.rstore->name()); goto bail_out; } /* * Open the bootstrap file */ if (!open_bootstrap_file(jcr, info)) { goto bail_out; } nis = (NIS *)malloc(sizeof(NIS)); memset(nis, 0, sizeof(NIS)); /* * Read the bootstrap file */ bsr = jcr->bsr; while (!feof(info.bs)) { if (!select_next_rstore(jcr, info)) { goto cleanup; } /* * Initialize the ndmp restore job. We build the generic job once per storage daemon * and reuse the job definition for each seperate sub-restore we perform as * part of the whole job. We only free the env_table between every sub-restore. */ if (!ndmp_build_client_job(jcr, jcr->res.client, jcr->res.pstore, NDM_JOB_OP_EXTRACT, &ndmp_job)) { goto cleanup; } /* * 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"); jcr->setJobStatus(JS_WaitSD); /* * Start conversation with Storage daemon */ if (!connect_to_storage_daemon(jcr, 10, me->SDConnectTimeout, true)) { goto cleanup; } sd = jcr->store_bsock; /* * Now start a job with the Storage daemon */ if (!start_storage_daemon_job(jcr, jcr->res.rstorage, NULL)) { goto cleanup; } jcr->setJobStatus(JS_Running); /* * Send the bootstrap file -- what Volumes/files to restore */ if (!send_bootstrap_file(jcr, sd, info) || !response(jcr, sd, OKbootstrap, "Bootstrap", DISPLAY_ERROR)) { goto cleanup; } if (!sd->fsend("run")) { goto cleanup; } /* * Now start a Storage daemon message thread */ if (!start_storage_daemon_message_thread(jcr)) { goto cleanup; } Dmsg0(50, "Storage daemon connection OK\n"); /* * Walk over each bsr record */ cnt = 0; for (bsr = jcr->bsr; bsr; bsr = bsr->next) { /* * Walk each fileindex of the current BSR record. Each different fileindex is * a separate NDMP stream. */ for (fileindex = bsr->FileIndex; fileindex; fileindex = fileindex->next) { for (current_fi = fileindex->findex; current_fi <= fileindex->findex2; current_fi++) { /* * See if this is the first Restore NDMP stream or not. For NDMP we can have multiple Backup * runs as part of the same Job. When we are restoring data from a Native Storage Daemon * we let it know to expect a next restore session. It will generate a new authorization * key so we wait for the nextrun_ready conditional variable to be raised by the msg_thread. */ if (jcr->store_bsock && cnt > 0) { jcr->store_bsock->fsend("nextrun"); P(mutex); pthread_cond_wait(&jcr->nextrun_ready, &mutex); V(mutex); } /* * Perform the actual NDMP job. * Initialize a new NDMP session */ memset(&ndmp_sess, 0, sizeof(ndmp_sess)); ndmp_sess.conn_snooping = (me->ndmp_snooping) ? 1 : 0; ndmp_sess.control_agent_enabled = 1; ndmp_sess.param = (struct ndm_session_param *)malloc(sizeof(struct ndm_session_param)); memset(ndmp_sess.param, 0, sizeof(struct ndm_session_param)); ndmp_sess.param->log.deliver = ndmp_loghandler; ndmp_sess.param->log_level = native_to_ndmp_loglevel(NdmpLoglevel, debug_level, nis); nis->jcr = jcr; ndmp_sess.param->log.ctx = nis; ndmp_sess.param->log_tag = bstrdup("DIR-NDMP"); /* * Initialize the session structure. */ if (ndma_session_initialize(&ndmp_sess)) { goto cleanup_ndmp; } session_initialized = true; /* * Copy the actual job to perform. */ jcr->jr.FileIndex = current_fi; if (bsr->sessid && bsr->sesstime) { jcr->jr.VolSessionId = bsr->sessid->sessid; jcr->jr.VolSessionTime = bsr->sesstime->sesstime; } else { Jmsg(jcr, M_FATAL, 0, _("Wrong BSR missing sessid and/or sesstime\n")); goto cleanup_ndmp; } memcpy(&ndmp_sess.control_acb->job, &ndmp_job, sizeof(struct ndm_job_param)); if (!fill_restore_environment(jcr, current_fi, &ndmp_sess.control_acb->job)) { Jmsg(jcr, M_ERROR, 0, _("ERROR in fill_restore_environment\n")); goto cleanup_ndmp; } ndma_job_auto_adjust(&ndmp_sess.control_acb->job); if (!ndmp_validate_job(jcr, &ndmp_sess.control_acb->job)) { Jmsg(jcr, M_ERROR, 0, _("ERROR in ndmp_validate_job\n")); goto cleanup_ndmp; } /* * Commission the session for a run. */ if (ndma_session_commission(&ndmp_sess)) { Jmsg(jcr, M_ERROR, 0, _("ERROR in ndma_session_commission\n")); goto cleanup_ndmp; } /* * Setup the DMA. */ if (ndmca_connect_control_agent(&ndmp_sess)) { Jmsg(jcr, M_ERROR, 0, _("ERROR in ndmca_connect_control_agent\n")); goto cleanup_ndmp; } ndmp_sess.conn_open = 1; ndmp_sess.conn_authorized = 1; /* * Let the DMA perform its magic. */ if (ndmca_control_agent(&ndmp_sess) != 0) { Jmsg(jcr, M_ERROR, 0, _("ERROR in ndmca_control_agent\n")); goto cleanup_ndmp; } /* * See if there were any errors during the restore. */ if (!extract_post_restore_stats(jcr, &ndmp_sess)) { Jmsg(jcr, M_ERROR, 0, _("ERROR in extract_post_restore_stats\n")); goto cleanup_ndmp; } /* * Reset the NDMP session states. */ ndma_session_decommission(&ndmp_sess); /* * Cleanup the job after it has run. */ ndma_destroy_env_list(&ndmp_sess.control_acb->job.env_tab); ndma_destroy_env_list(&ndmp_sess.control_acb->job.result_env_tab); ndma_destroy_nlist(&ndmp_sess.control_acb->job.nlist_tab); /* * Release any tape device name allocated. */ if (ndmp_sess.control_acb->job.tape_device) { free(ndmp_sess.control_acb->job.tape_device); ndmp_sess.control_acb->job.tape_device = NULL; } /* * Destroy the session. */ ndma_session_destroy(&ndmp_sess); /* * Free the param block. */ free(ndmp_sess.param->log_tag); free(ndmp_sess.param); ndmp_sess.param = NULL; /* * Reset the initialized state so we don't try to cleanup again. */ session_initialized = false; /* * Keep track that we finished this part of the restore. */ cnt++; } } } /* * Tell the storage daemon we are done. */ jcr->store_bsock->fsend("finish"); wait_for_storage_daemon_termination(jcr); } /* * Jump to the generic cleanup done for every Job. */ retval = true; goto cleanup; cleanup_ndmp: /* * Only need to cleanup when things are initialized. */ if (session_initialized) { ndma_destroy_env_list(&ndmp_sess.control_acb->job.env_tab); ndma_destroy_env_list(&ndmp_sess.control_acb->job.result_env_tab); ndma_destroy_nlist(&ndmp_sess.control_acb->job.nlist_tab); if (ndmp_sess.control_acb->job.tape_device) { free(ndmp_sess.control_acb->job.tape_device); } /* * Destroy the session. */ ndma_session_destroy(&ndmp_sess); } if (ndmp_sess.param) { free(ndmp_sess.param->log_tag); free(ndmp_sess.param); } cleanup: if (nis) { free(nis); } free_paired_storage(jcr); close_bootstrap_file(info); bail_out: free_tree(jcr->restore_tree_root); jcr->restore_tree_root = NULL; return retval; }
/** * 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. */ static inline bool do_native_restore_bootstrap(JCR *jcr) { STORERES *store; bootstrap_info info; BSOCK *fd = NULL; BSOCK *sd = NULL; bool first_time = true; POOL_MEM restore_cmd(PM_MESSAGE); /* * 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; } /* * Read the bootstrap file */ jcr->passive_client = jcr->res.client->passive; while (!feof(info.bs)) { if (!select_next_rstore(jcr, info)) { goto bail_out; } store = jcr->res.rstore; /** * 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"); jcr->setJobStatus(JS_WaitSD); /* * Start conversation with Storage daemon */ if (!connect_to_storage_daemon(jcr, 10, me->SDConnectTimeout, true)) { goto bail_out; } sd = jcr->store_bsock; /* * Now start a job with the Storage daemon */ if (!start_storage_daemon_job(jcr, jcr->rstorage, NULL)) { goto bail_out; } if (first_time) { /* * Start conversation with File daemon */ 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, me->FDConnectTimeout, true, true)) { goto bail_out; } fd = jcr->file_bsock; /* * Check if the file daemon supports passive client mode. */ if (jcr->passive_client && jcr->FDVersion < FD_VERSION_51) { Jmsg(jcr, M_FATAL, 0, _("Client \"%s\" doesn't support passive client mode. " "Please upgrade your client or disable compat mode.\n"), jcr->res.client->name()); goto bail_out; } } jcr->setJobStatus(JS_Running); /* * Send the bootstrap file -- what Volumes/files to restore */ if (!send_bootstrap_file(jcr, sd, info) || !response(jcr, sd, OKbootstrap, "Bootstrap", DISPLAY_ERROR)) { goto bail_out; } if (!jcr->passive_client) { int tls_need = BNET_TLS_NONE; /* * When the client is not in passive mode we can put the SD in * listen mode for the FD connection. And ask the FD to connect * to the SD. */ if (!sd->fsend("run")) { goto bail_out; } /* * Now start a Storage daemon message thread */ if (!start_storage_daemon_message_thread(jcr)) { goto bail_out; } 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. */ if (store->SDDport == 0) { store->SDDport = store->SDport; } /* * TLS Requirement */ if (store->tls_enable) { if (store->tls_require) { tls_need = BNET_TLS_REQUIRED; } else { tls_need = BNET_TLS_OK; } } fd->fsend(storaddrcmd, store->address, store->SDDport, 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; } } else { int tls_need = BNET_TLS_NONE; CLIENTRES *client = jcr->res.client; /* * In passive mode we tell the FD what authorization key to use * and the ask the SD to initiate the connection. */ fd->fsend(setauthorizationcmd, 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, OKAuthorization, "Setauthorization", DISPLAY_ERROR)) { goto bail_out; } /* * TLS Requirement */ tls_need = BNET_TLS_NONE; if (client->tls_enable) { if (client->tls_require) { tls_need = BNET_TLS_REQUIRED; } else { tls_need = BNET_TLS_OK; } } /* * Tell the SD to connect to the FD. */ sd->fsend(passiveclientcmd, client->address, client->FDport, tls_need); if (!response(jcr, sd, OKpassiveclient, "Passive client", DISPLAY_ERROR)) { goto bail_out; } /* * Start the Job in the SD. */ if (!sd->fsend("run")) { goto bail_out; } /* * Now start a Storage daemon message thread */ if (!start_storage_daemon_message_thread(jcr)) { goto bail_out; } Dmsg0(50, "Storage daemon connection OK\n"); } /* * 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; } if (!send_restore_objects(jcr)) { Dmsg0(000, "FAIL: Send restore objects\n"); goto bail_out; } /* * Only FD version 52 and later understand the sending of plugin options. */ if (jcr->FDVersion >= FD_VERSION_52) { if (!send_plugin_options(jcr)) { Dmsg0(000, "FAIL: Send plugin options\n"); goto bail_out; } } else { /* * Plugin options specified and not a FD that understands the new protocol keyword. */ if (jcr->plugin_options) { Jmsg(jcr, M_FATAL, 0, _("Client \"%s\" doesn't support plugin option passing. " "Please upgrade your client or disable compat mode.\n"), jcr->res.client->name()); goto bail_out; } } } fd->fsend("%s", restore_cmd.c_str()); if (!response(jcr, fd, OKrestore, "Restore", DISPLAY_ERROR)) { goto bail_out; } if (jcr->FDVersion < FD_VERSION_2) { /* Old FD */ break; /* we do only one loop */ } else { if (!response(jcr, fd, OKstoreend, "Store end", DISPLAY_ERROR)) { goto bail_out; } wait_for_storage_daemon_termination(jcr); } } /* the whole boostrap has been send */ if (fd && jcr->FDVersion >= FD_VERSION_2) { fd->fsend("endrestore"); } close_bootstrap_file(info); return true; bail_out: if (jcr->file_bsock) { jcr->file_bsock->signal(BNET_TERMINATE); jcr->file_bsock->close(); delete jcr->file_bsock; jcr->file_bsock = NULL; } close_bootstrap_file(info); return false; }
/* * Here we wait for the File daemon to signal termination, * then we wait for the Storage daemon. When both * are done, we return the job status. * Also used by restore.c */ int wait_for_job_termination(JCR *jcr, int timeout) { int32_t n = 0; BSOCK *fd = jcr->file_bsock; bool fd_ok = false; uint32_t JobFiles, JobErrors; uint32_t JobWarnings = 0; uint64_t ReadBytes = 0; uint64_t JobBytes = 0; int VSS = 0; int Encrypt = 0; btimer_t *tid=NULL; set_jcr_job_status(jcr, JS_Running); if (fd) { if (timeout) { tid = start_bsock_timer(fd, timeout); /* TODO: New timeout directive??? */ } /* Wait for Client to terminate */ while ((n = bget_dirmsg(fd)) >= 0) { if (!fd_ok && (sscanf(fd->msg, EndJob, &jcr->FDJobStatus, &JobFiles, &ReadBytes, &JobBytes, &JobErrors, &VSS, &Encrypt) == 7 || sscanf(fd->msg, OldEndJob, &jcr->FDJobStatus, &JobFiles, &ReadBytes, &JobBytes, &JobErrors) == 5)) { fd_ok = true; set_jcr_job_status(jcr, jcr->FDJobStatus); Dmsg1(100, "FDStatus=%c\n", (char)jcr->JobStatus); } else { Jmsg(jcr, M_WARNING, 0, _("Unexpected Client Job message: %s\n"), fd->msg); } if (job_canceled(jcr)) { break; } } if (tid) { stop_bsock_timer(tid); } if (is_bnet_error(fd)) { Jmsg(jcr, M_FATAL, 0, _("Network error with FD during %s: ERR=%s\n"), job_type_to_str(jcr->get_JobType()), fd->bstrerror()); } fd->signal(BNET_TERMINATE); /* tell Client we are terminating */ } /* Force cancel in SD if failing */ if (job_canceled(jcr) || !fd_ok) { cancel_storage_daemon_job(jcr); } /* Note, the SD stores in jcr->JobFiles/ReadBytes/JobBytes/JobErrors */ wait_for_storage_daemon_termination(jcr); /* Return values from FD */ if (fd_ok) { jcr->JobFiles = JobFiles; jcr->JobErrors += JobErrors; /* Keep total errors */ jcr->ReadBytes = ReadBytes; jcr->JobBytes = JobBytes; jcr->JobWarnings = JobWarnings; jcr->VSS = VSS; jcr->Encrypt = Encrypt; } else { Jmsg(jcr, M_FATAL, 0, _("No Job status returned from FD.\n")); } // Dmsg4(100, "fd_ok=%d FDJS=%d JS=%d SDJS=%d\n", fd_ok, jcr->FDJobStatus, // jcr->JobStatus, jcr->SDJobStatus); /* Return the first error status we find Dir, FD, or SD */ if (!fd_ok || is_bnet_error(fd)) { /* if fd not set, that use !fd_ok */ jcr->FDJobStatus = JS_ErrorTerminated; } if (jcr->JobStatus != JS_Terminated) { return jcr->JobStatus; } if (jcr->FDJobStatus != JS_Terminated) { return jcr->FDJobStatus; } return jcr->SDJobStatus; }