/* * Read Data and send to File Daemon * * Returns: false on failure * true on success */ bool do_read_data(JCR *jcr) { BSOCK *fd = jcr->file_bsock; DCR *dcr = jcr->read_dcr; bool ok = true; Dmsg0(20, "Start read data.\n"); if (!bnet_set_buffer_size(fd, dcr->device->max_network_buffer_size, BNET_SETBUF_WRITE)) { return false; } if (jcr->NumReadVolumes == 0) { Jmsg(jcr, M_FATAL, 0, _("No Volume names found for restore.\n")); fd->fsend(FD_error); return false; } Dmsg2(200, "Found %d volumes names to restore. First=%s\n", jcr->NumReadVolumes, jcr->VolList->VolumeName); /* * Ready device for reading */ if (!acquire_device_for_read(dcr)) { fd->fsend(FD_error); return false; } /* * Let any SD plugin know now its time to setup the record translation infra. */ if (generate_plugin_event(jcr, bsdEventSetupRecordTranslation, dcr) != bRC_OK) { jcr->setJobStatus(JS_ErrorTerminated); return false; } /* * Tell File daemon we will send data */ fd->fsend(OK_data); jcr->sendJobStatus(JS_Running); ok = read_records(dcr, record_cb, mount_next_read_volume); /* * Send end of data to FD */ fd->signal(BNET_EOD); if (!release_device(jcr->read_dcr)) { ok = false; } Dmsg0(30, "Done reading.\n"); return ok; }
/* * Read Data and send to File Daemon * Returns: false on failure * true on success */ bool do_read_data(JCR *jcr) { BSOCK *fd = jcr->file_bsock; bool ok = true; DCR *dcr = jcr->read_dcr; Dmsg0(20, "Start read data.\n"); if (!bnet_set_buffer_size(fd, dcr->device->max_network_buffer_size, BNET_SETBUF_WRITE)) { return false; } if (jcr->NumReadVolumes == 0) { Jmsg(jcr, M_FATAL, 0, _("No Volume names found for restore.\n")); fd->fsend(FD_error); return false; } Dmsg2(200, "Found %d volumes names to restore. First=%s\n", jcr->NumReadVolumes, jcr->VolList->VolumeName); /* Ready device for reading */ if (!acquire_device_for_read(dcr)) { fd->fsend(FD_error); return false; } /* Tell File daemon we will send data */ fd->fsend(OK_data); jcr->sendJobStatus(JS_Running); ok = read_records(dcr, record_cb, mount_next_read_volume); /* Send end of data to FD */ fd->signal(BNET_EOD); if (!release_device(jcr->read_dcr)) { ok = false; } Dmsg0(30, "Done reading.\n"); return ok; }
/* * Execute a command from the UA */ bool do_a_dot_command(UAContext *ua) { int i; int len; bool ok = false; bool found = false; BSOCK *user = ua->UA_sock; Dmsg1(1400, "Dot command: %s\n", user->msg); if (ua->argc == 0) { return false; } len = strlen(ua->argk[0]); if (len == 1) { if (ua->api) user->signal(BNET_CMD_BEGIN); if (ua->api) user->signal(BNET_CMD_OK); return true; /* no op */ } for (i=0; i<comsize; i++) { /* search for command */ if (bstrncasecmp(ua->argk[0], _(commands[i].key), len)) { /* Check if this command is authorized in RunScript */ if (ua->runscript && !commands[i].use_in_rs) { ua->error_msg(_("Can't use %s command in a runscript"), ua->argk[0]); break; } bool gui = ua->gui; /* Check if command permitted, but "quit" is always OK */ if (!bstrcmp(ua->argk[0], NT_(".quit")) && !acl_access_ok(ua, Command_ACL, ua->argk[0], len)) { break; } Dmsg1(100, "Cmd: %s\n", ua->cmd); ua->gui = true; if (ua->api) user->signal(BNET_CMD_BEGIN); ok = (*commands[i].func)(ua, ua->cmd); /* go execute command */ if (ua->api) user->signal(ok?BNET_CMD_OK:BNET_CMD_FAILED); ua->gui = gui; found = true; break; } } if (!found) { ua->error_msg("%s%s", ua->argk[0], _(": is an invalid command.\n")); ok = false; } return ok; }
/* * Cancel a running job on a storage daemon. Used by the interactive cancel * command to cancel a JobId on a Storage Daemon this can be used when the * Director already removed the Job and thinks it finished but the Storage * Daemon still thinks its active. */ bool cancel_storage_daemon_job(UAContext *ua, STORERES *store, char *JobId) { BSOCK *sd; JCR *control_jcr; control_jcr = new_control_jcr("*JobCancel*", JT_SYSTEM); control_jcr->res.wstore = store; if (!connect_to_storage_daemon(control_jcr, 10, me->SDConnectTimeout, true)) { ua->error_msg(_("Failed to connect to Storage daemon.\n")); } Dmsg0(200, "Connected to storage daemon\n"); sd = control_jcr->store_bsock; sd->fsend("cancel Job=%s\n", JobId); while (sd->recv() >= 0) { ua->send_msg("%s", sd->msg); } sd->signal(BNET_TERMINATE); sd->close(); control_jcr->store_bsock = NULL; free_jcr(control_jcr); return true; }
static void do_client_cmd(UAContext *ua, CLIENTRES *client, const char *cmd) { BSOCK *fd; /* Connect to File daemon */ ua->jcr->res.client = client; /* Try to connect for 15 seconds */ ua->send_msg(_("Connecting to Client %s at %s:%d\n"), client->name(), client->address, client->FDport); if (!connect_to_file_daemon(ua->jcr, 1, 15, false, false)) { ua->error_msg(_("Failed to connect to Client.\n")); return; } Dmsg0(120, "Connected to file daemon\n"); fd = ua->jcr->file_bsock; fd->fsend("%s", cmd); if (fd->recv() >= 0) { ua->send_msg("%s", fd->msg); } fd->signal(BNET_TERMINATE); fd->close(); ua->jcr->file_bsock = NULL; return; }
/* * resolve a host on a storage daemon */ bool do_storage_resolve(UAContext *ua, STORERES *store) { BSOCK *sd; USTORERES lstore; lstore.store = store; pm_strcpy(lstore.store_source, _("unknown source")); set_wstorage(ua->jcr, &lstore); if (!(sd = open_sd_bsock(ua))) { return false; } for (int i = 1; i < ua->argc; i++) { if (!*ua->argk[i]) { continue; } sd->fsend("resolve %s", ua->argk[i]); while (sd->recv() >= 0) { ua->send_msg("%s", sd->msg); } } sd->signal(BNET_TERMINATE); sd->close(); ua->jcr->store_bsock = NULL; return true; }
/* * Run a File daemon Job -- File daemon already authorized * Director sends us this command. * * Basic task here is: * - Read a command from the File daemon * - Execute it * */ void run_job(JCR *jcr) { BSOCK *dir = jcr->dir_bsock; char ec1[30]; dir->set_jcr(jcr); Dmsg1(120, "Start run Job=%s\n", jcr->Job); dir->fsend(Job_start, jcr->Job); jcr->start_time = time(NULL); jcr->run_time = jcr->start_time; jcr->sendJobStatus(JS_Running); do_fd_commands(jcr); jcr->end_time = time(NULL); dequeue_messages(jcr); /* send any queued messages */ jcr->setJobStatus(JS_Terminated); generate_plugin_event(jcr, bsdEventJobEnd); dir->fsend(Job_end, jcr->Job, jcr->JobStatus, jcr->JobFiles, edit_uint64(jcr->JobBytes, ec1), jcr->JobErrors); dir->signal(BNET_EOD); /* send EOD to Director daemon */ free_plugins(jcr); /* release instantiated plugins */ }
/** * Save OSX specific resource forks and finder info. */ static inline bool save_rsrc_and_finder(b_save_ctx &bsctx) { char flags[FOPTS_BYTES]; int rsrc_stream; BSOCK *sd = bsctx.jcr->store_bsock; bool retval = false; if (bsctx.ff_pkt->hfsinfo.rsrclength > 0) { if (bopen_rsrc(&bsctx.ff_pkt->bfd, bsctx.ff_pkt->fname, O_RDONLY | O_BINARY, 0) < 0) { bsctx.ff_pkt->ff_errno = errno; berrno be; Jmsg(bsctx.jcr, M_NOTSAVED, -1, _(" Cannot open resource fork for \"%s\": ERR=%s.\n"), bsctx.ff_pkt->fname, be.bstrerror()); bsctx.jcr->JobErrors++; if (is_bopen(&bsctx.ff_pkt->bfd)) { bclose(&bsctx.ff_pkt->bfd); } } else { int status; memcpy(flags, bsctx.ff_pkt->flags, sizeof(flags)); clear_bit(FO_COMPRESS, bsctx.ff_pkt->flags); clear_bit(FO_SPARSE, bsctx.ff_pkt->flags); clear_bit(FO_OFFSETS, bsctx.ff_pkt->flags); rsrc_stream = bit_is_set(FO_ENCRYPT, flags) ? STREAM_ENCRYPTED_MACOS_FORK_DATA : STREAM_MACOS_FORK_DATA; status = send_data(bsctx.jcr, rsrc_stream, bsctx.ff_pkt, bsctx.digest, bsctx.signing_digest); memcpy(bsctx.ff_pkt->flags, flags, sizeof(flags)); bclose(&bsctx.ff_pkt->bfd); if (!status) { goto bail_out; } } } Dmsg1(300, "Saving Finder Info for \"%s\"\n", bsctx.ff_pkt->fname); sd->fsend("%ld %d 0", bsctx.jcr->JobFiles, STREAM_HFSPLUS_ATTRIBUTES); Dmsg1(300, "filed>stored:header %s", sd->msg); pm_memcpy(sd->msg, bsctx.ff_pkt->hfsinfo.fndrinfo, 32); sd->msglen = 32; if (bsctx.digest) { crypto_digest_update(bsctx.digest, (uint8_t *)sd->msg, sd->msglen); } if (bsctx.signing_digest) { crypto_digest_update(bsctx.signing_digest, (uint8_t *)sd->msg, sd->msglen); } sd->send(); sd->signal(BNET_EOD); retval = true; bail_out: return retval; }
/* * Terminate the signing digest and send it to the Storage daemon */ static inline bool terminate_signing_digest(b_save_ctx &bsctx) { uint32_t size = 0; bool retval = false; SIGNATURE *signature = NULL; BSOCK *sd = bsctx.jcr->store_bsock; if ((signature = crypto_sign_new(bsctx.jcr)) == NULL) { Jmsg(bsctx.jcr, M_FATAL, 0, _("Failed to allocate memory for crypto signature.\n")); goto bail_out; } if (!crypto_sign_add_signer(signature, bsctx.signing_digest, bsctx.jcr->crypto.pki_keypair)) { Jmsg(bsctx.jcr, M_FATAL, 0, _("An error occurred while signing the stream.\n")); goto bail_out; } /* * Get signature size */ if (!crypto_sign_encode(signature, NULL, &size)) { Jmsg(bsctx.jcr, M_FATAL, 0, _("An error occurred while signing the stream.\n")); goto bail_out; } /* * Grow the bsock buffer to fit our message if necessary */ if (sizeof_pool_memory(sd->msg) < (int32_t)size) { sd->msg = realloc_pool_memory(sd->msg, size); } /* * Send our header */ sd->fsend("%ld %ld 0", bsctx.jcr->JobFiles, STREAM_SIGNED_DIGEST); Dmsg1(300, "filed>stored:header %s", sd->msg); /* * Encode signature data */ if (!crypto_sign_encode(signature, (uint8_t *)sd->msg, &size)) { Jmsg(bsctx.jcr, M_FATAL, 0, _("An error occurred while signing the stream.\n")); goto bail_out; } sd->msglen = size; sd->send(); sd->signal(BNET_EOD); /* end of checksum */ retval = true; bail_out: if (signature) { crypto_sign_free(signature); } return retval; }
/* * Now talk to the SD and do what he says */ static void do_sd_commands(JCR *jcr) { int i, status; bool found, quit; BSOCK *sd = jcr->store_bsock; sd->set_jcr(jcr); quit = false; while (!quit) { /* * Read command coming from the Storage daemon */ status = sd->recv(); if (is_bnet_stop(sd)) { /* hardeof or error */ break; /* connection terminated */ } if (status <= 0) { continue; /* ignore signals and zero length msgs */ } Dmsg1(110, "<stored: %s", sd->msg); found = false; for (i = 0; sd_cmds[i].cmd; i++) { if (bstrncmp(sd_cmds[i].cmd, sd->msg, strlen(sd_cmds[i].cmd))) { found = true; /* indicate command found */ jcr->errmsg[0] = 0; if (!sd_cmds[i].func(jcr)) { /* do command */ /* * Note sd->msg command may be destroyed by comm activity */ if (!job_canceled(jcr)) { if (jcr->errmsg[0]) { Jmsg1(jcr, M_FATAL, 0, _("Command error with SD, hanging up. %s\n"), jcr->errmsg); } else { Jmsg0(jcr, M_FATAL, 0, _("Command error with SD, hanging up.\n")); } jcr->setJobStatus(JS_ErrorTerminated); } quit = true; } break; } } if (!found) { /* command not found */ if (!job_canceled(jcr)) { Jmsg1(jcr, M_FATAL, 0, _("SD command not found: %s\n"), sd->msg); Dmsg1(110, "<stored: Command not found: %s\n", sd->msg); } sd->fsend(serrmsg); break; } } sd->signal(BNET_TERMINATE); /* signal to SD job is done */ }
/* * This is an information message that should probably be put * into the status line of a GUI program. */ void UAContext::info_msg(const char *fmt, ...) { BSOCK *bs = UA_sock; va_list arg_ptr; if (bs && api) bs->signal(BNET_INFO_MSG); va_start(arg_ptr, fmt); bmsg(this, fmt, arg_ptr); va_end(arg_ptr); }
/* * Now talk to the FD and do what he says */ void do_fd_commands(JCR *jcr) { int i; bool found, quit; BSOCK *fd = jcr->file_bsock; fd->set_jcr(jcr); for (quit=false; !quit;) { int status; /* Read command coming from the File daemon */ status = fd->recv(); if (is_bnet_stop(fd)) { /* hardeof or error */ break; /* connection terminated */ } if (status <= 0) { continue; /* ignore signals and zero length msgs */ } Dmsg1(110, "<filed: %s", fd->msg); found = false; for (i=0; fd_cmds[i].cmd; i++) { if (bstrncmp(fd_cmds[i].cmd, fd->msg, strlen(fd_cmds[i].cmd))) { found = true; /* indicate command found */ jcr->errmsg[0] = 0; if (!fd_cmds[i].func(jcr)) { /* do command */ /* Note fd->msg command may be destroyed by comm activity */ if (!job_canceled(jcr)) { if (jcr->errmsg[0]) { Jmsg1(jcr, M_FATAL, 0, _("Command error with FD, hanging up. %s\n"), jcr->errmsg); } else { Jmsg0(jcr, M_FATAL, 0, _("Command error with FD, hanging up.\n")); } jcr->setJobStatus(JS_ErrorTerminated); } quit = true; } break; } } if (!found) { /* command not found */ if (!job_canceled(jcr)) { Jmsg1(jcr, M_FATAL, 0, _("FD command not found: %s\n"), fd->msg); Dmsg1(110, "<filed: Command not found: %s\n", fd->msg); } fd->fsend(ferrmsg); break; } } fd->signal(BNET_TERMINATE); /* signal to FD job is done */ }
void cancel_storage_daemon_job(JCR *jcr) { if (jcr->sd_canceled) { return; /* cancel only once */ } UAContext *ua = new_ua_context(jcr); JCR *control_jcr = new_control_jcr("*JobCancel*", JT_SYSTEM); BSOCK *sd; ua->jcr = control_jcr; if (jcr->store_bsock) { if (!ua->jcr->wstorage) { if (jcr->rstorage) { copy_wstorage(ua->jcr, jcr->rstorage, _("Job resource")); } else { copy_wstorage(ua->jcr, jcr->wstorage, _("Job resource")); } } else { USTORE store; if (jcr->rstorage) { store.store = jcr->rstore; } else { store.store = jcr->wstore; } set_wstorage(ua->jcr, &store); } if (!connect_to_storage_daemon(ua->jcr, 10, SDConnectTimeout, 1)) { goto bail_out; } Dmsg0(200, "Connected to storage daemon\n"); sd = ua->jcr->store_bsock; sd->fsend("cancel Job=%s\n", jcr->Job); while (sd->recv() >= 0) { } sd->signal(BNET_TERMINATE); sd->close(); ua->jcr->store_bsock = NULL; jcr->sd_canceled = true; jcr->store_bsock->set_timed_out(); jcr->store_bsock->set_terminated(); sd_msg_thread_send_signal(jcr, TIMEOUT_SIGNAL); jcr->my_thread_send_signal(TIMEOUT_SIGNAL); } bail_out: free_jcr(control_jcr); free_ua_context(ua); }
/* * Cancel a running job on a storage daemon. The silent flag sets * if we need to be silent or not e.g. when doing an interactive cancel * or a system invoked one. */ bool cancel_storage_daemon_job(UAContext *ua, JCR *jcr, bool silent) { BSOCK *sd; USTORERES store; if (!ua->jcr->wstorage) { if (jcr->rstorage) { copy_wstorage(ua->jcr, jcr->rstorage, _("Job resource")); } else { copy_wstorage(ua->jcr, jcr->wstorage, _("Job resource")); } } else { if (jcr->rstorage) { store.store = jcr->res.rstore; } else { store.store = jcr->res.wstore; } set_wstorage(ua->jcr, &store); } if (!connect_to_storage_daemon(ua->jcr, 10, me->SDConnectTimeout, true)) { if (!silent) { ua->error_msg(_("Failed to connect to Storage daemon.\n")); } return false; } Dmsg0(200, "Connected to storage daemon\n"); sd = ua->jcr->store_bsock; sd->fsend(canceljobcmd, jcr->Job); while (sd->recv() >= 0) { if (!silent) { ua->send_msg("%s", sd->msg); } } sd->signal(BNET_TERMINATE); sd->close(); delete ua->jcr->store_bsock; ua->jcr->store_bsock = NULL; if (silent) { jcr->sd_canceled = true; } jcr->store_bsock->set_timed_out(); jcr->store_bsock->set_terminated(); sd_msg_thread_send_signal(jcr, TIMEOUT_SIGNAL); jcr->my_thread_send_signal(TIMEOUT_SIGNAL); return true; }
/* * Get the status of a remote storage daemon. */ void do_native_storage_status(UAContext *ua, STORERES *store, char *cmd) { BSOCK *sd; USTORERES lstore; lstore.store = store; pm_strcpy(lstore.store_source, _("unknown source")); set_wstorage(ua->jcr, &lstore); /* * Try connecting for up to 15 seconds */ if (!ua->api) { ua->send_msg(_("Connecting to Storage daemon %s at %s:%d\n"), store->name(), store->address, store->SDport); } if (!connect_to_storage_daemon(ua->jcr, 10, me->SDConnectTimeout, false)) { ua->send_msg(_("\nFailed to connect to Storage daemon %s.\n====\n"), store->name()); if (ua->jcr->store_bsock) { ua->jcr->store_bsock->close(); delete ua->jcr->store_bsock; ua->jcr->store_bsock = NULL; } return; } Dmsg0(20, _("Connected to storage daemon\n")); sd = ua->jcr->store_bsock; if (cmd) { sd->fsend(dotstatuscmd, cmd); } else { sd->fsend(statuscmd); } while (sd->recv() >= 0) { ua->send_msg("%s", sd->msg); } sd->signal( BNET_TERMINATE); sd->close(); delete ua->jcr->store_bsock; ua->jcr->store_bsock = NULL; return; }
bool finish_cmd(JCR *jcr) { BSOCK *dir = jcr->dir_bsock; char ec1[30]; /* * See if the Job has a certain protocol. Some protocols allow the * finish cmd some do not (Native backup for example does NOT) */ switch (jcr->getJobProtocol()) { case PT_NDMP: Dmsg1(200, "Finish_cmd: %s", jcr->dir_bsock->msg); jcr->end_time = time(NULL); dequeue_messages(jcr); /* send any queued messages */ jcr->setJobStatus(JS_Terminated); switch (jcr->getJobType()) { case JT_BACKUP: end_of_ndmp_backup(jcr); break; case JT_RESTORE: end_of_ndmp_restore(jcr); break; default: break; } generate_plugin_event(jcr, bsdEventJobEnd); dir->fsend(Job_end, jcr->Job, jcr->JobStatus, jcr->JobFiles, edit_uint64(jcr->JobBytes, ec1), jcr->JobErrors); dir->signal(BNET_EOD); /* send EOD to Director daemon */ free_plugins(jcr); /* release instantiated plugins */ Dmsg2(800, "Done jid=%d %p\n", jcr->JobId, jcr); return false; /* Continue DIR session ? */ default: Dmsg1(200, "Finish_cmd: %s", jcr->dir_bsock->msg); Jmsg2(jcr, M_FATAL, 0, _("Hey!!!! JobId %u Job %s tries to use finish cmd while not part of protocol.\n"), (uint32_t)jcr->JobId, jcr->Job); return false; /* Continue DIR session ? */ } }
/* * Read Close session command * Close the read session */ static bool read_close_session(JCR *jcr) { BSOCK *fd = jcr->file_bsock; Dmsg1(120, "Read close session: %s\n", fd->msg); if (!jcr->session_opened) { fd->fsend(NOT_opened); return false; } /* Send final close msg to File daemon */ fd->fsend(OK_close, jcr->JobStatus); Dmsg1(160, ">filed: %s\n", fd->msg); fd->signal(BNET_EOD); /* send EOD to File daemon */ jcr->session_opened = false; return true; }
/* * Append Close session command * Close the append session and send back Statistics * (need to fix statistics) */ static bool append_close_session(JCR *jcr) { BSOCK *fd = jcr->file_bsock; Dmsg1(120, "<filed: %s", fd->msg); if (!jcr->session_opened) { pm_strcpy(jcr->errmsg, _("Attempt to close non-open session.\n")); fd->fsend(NOT_opened); return false; } /* Send final statistics to File daemon */ fd->fsend(OK_close, jcr->JobStatus); Dmsg1(120, ">filed: %s", fd->msg); fd->signal(BNET_EOD); /* send EOD to File daemon */ jcr->session_opened = false; return true; }
/* * Run a File daemon Job -- File daemon already authorized * Director sends us this command. * * Basic task here is: * - Read a command from the File daemon * - Execute it * */ void run_job(JCR *jcr) { BSOCK *dir = jcr->dir_bsock; char ec1[30]; dir->set_jcr(jcr); Dmsg1(120, "Start run Job=%s\n", jcr->Job); dir->fsend(Job_start, jcr->Job); jcr->start_time = time(NULL); jcr->run_time = jcr->start_time; set_jcr_job_status(jcr, JS_Running); dir_send_job_status(jcr); /* update director */ do_fd_commands(jcr); jcr->end_time = time(NULL); dequeue_messages(jcr); /* send any queued messages */ set_jcr_job_status(jcr, JS_Terminated); generate_daemon_event(jcr, "JobEnd"); dir->fsend(Job_end, jcr->Job, jcr->JobStatus, jcr->JobFiles, edit_uint64(jcr->JobBytes, ec1), jcr->JobErrors); dir->signal(BNET_EOD); /* send EOD to Director daemon */ return; }
/* * Terminate any digest and send it to Storage daemon */ static inline bool terminate_digest(b_save_ctx &bsctx) { uint32_t size; bool retval = false; BSOCK *sd = bsctx.jcr->store_bsock; sd->fsend("%ld %d 0", bsctx.jcr->JobFiles, bsctx.digest_stream); Dmsg1(300, "filed>stored:header %s", sd->msg); size = CRYPTO_DIGEST_MAX_SIZE; /* * Grow the bsock buffer to fit our message if necessary */ if (sizeof_pool_memory(sd->msg) < (int32_t)size) { sd->msg = realloc_pool_memory(sd->msg, size); } if (!crypto_digest_finalize(bsctx.digest, (uint8_t *)sd->msg, &size)) { Jmsg(bsctx.jcr, M_FATAL, 0, _("An error occurred finalizing signing the stream.\n")); goto bail_out; } /* * Keep the checksum if this file is a hardlink */ if (bsctx.ff_pkt->linked) { ff_pkt_set_link_digest(bsctx.ff_pkt, bsctx.digest_stream, sd->msg, size); } sd->msglen = size; sd->send(); sd->signal(BNET_EOD); /* end of checksum */ retval = true; bail_out: return retval; }
/** * Find all the requested files and send them * to the Storage daemon. * * Note, we normally carry on a one-way * conversation from this point on with the SD, simply blasting * data to him. To properly know what is going on, we * also run a "heartbeat" monitor which reads the socket and * reacts accordingly (at the moment it has nothing to do * except echo the heartbeat to the Director). */ bool blast_data_to_storage_daemon(JCR *jcr, char *addr, crypto_cipher_t cipher) { BSOCK *sd; bool ok = true; sd = jcr->store_bsock; jcr->setJobStatus(JS_Running); Dmsg1(300, "filed: opened data connection %d to stored\n", sd->m_fd); LockRes(); CLIENTRES *client = (CLIENTRES *)GetNextRes(R_CLIENT, NULL); UnlockRes(); uint32_t buf_size; if (client) { buf_size = client->max_network_buffer_size; } else { buf_size = 0; /* use default */ } if (!sd->set_buffer_size(buf_size, BNET_SETBUF_WRITE)) { jcr->setJobStatus(JS_ErrorTerminated); Jmsg(jcr, M_FATAL, 0, _("Cannot set buffer size FD->SD.\n")); return false; } jcr->buf_size = sd->msglen; if (!adjust_compression_buffers(jcr)) { return false; } if (!crypto_session_start(jcr, cipher)) { return false; } set_find_options((FF_PKT *)jcr->ff, jcr->incremental, jcr->mtime); /** * In accurate mode, we overload the find_one check function */ if (jcr->accurate) { set_find_changed_function((FF_PKT *)jcr->ff, accurate_check_file); } start_heartbeat_monitor(jcr); if (have_acl) { jcr->acl_data = (acl_data_t *)malloc(sizeof(acl_data_t)); memset(jcr->acl_data, 0, sizeof(acl_data_t)); jcr->acl_data->u.build = (acl_build_data_t *)malloc(sizeof(acl_build_data_t)); memset(jcr->acl_data->u.build, 0, sizeof(acl_build_data_t)); jcr->acl_data->u.build->content = get_pool_memory(PM_MESSAGE); } if (have_xattr) { jcr->xattr_data = (xattr_data_t *)malloc(sizeof(xattr_data_t)); memset(jcr->xattr_data, 0, sizeof(xattr_data_t)); jcr->xattr_data->u.build = (xattr_build_data_t *)malloc(sizeof(xattr_build_data_t)); memset(jcr->xattr_data->u.build, 0, sizeof(xattr_build_data_t)); jcr->xattr_data->u.build->content = get_pool_memory(PM_MESSAGE); } /** * Subroutine save_file() is called for each file */ if (!find_files(jcr, (FF_PKT *)jcr->ff, save_file, plugin_save)) { ok = false; /* error */ jcr->setJobStatus(JS_ErrorTerminated); } if (have_acl && jcr->acl_data->u.build->nr_errors > 0) { Jmsg(jcr, M_WARNING, 0, _("Encountered %ld acl errors while doing backup\n"), jcr->acl_data->u.build->nr_errors); } if (have_xattr && jcr->xattr_data->u.build->nr_errors > 0) { Jmsg(jcr, M_WARNING, 0, _("Encountered %ld xattr errors while doing backup\n"), jcr->xattr_data->u.build->nr_errors); } close_vss_backup_session(jcr); accurate_finish(jcr); /* send deleted or base file list to SD */ stop_heartbeat_monitor(jcr); sd->signal(BNET_EOD); /* end of sending data */ if (have_acl && jcr->acl_data) { free_pool_memory(jcr->acl_data->u.build->content); free(jcr->acl_data->u.build); free(jcr->acl_data); jcr->acl_data = NULL; } if (have_xattr && jcr->xattr_data) { free_pool_memory(jcr->xattr_data->u.build->content); free(jcr->xattr_data->u.build); free(jcr->xattr_data); jcr->xattr_data = NULL; } if (jcr->big_buf) { free(jcr->big_buf); jcr->big_buf = NULL; } cleanup_compression(jcr); crypto_session_end(jcr); Dmsg1(100, "end blast_data ok=%d\n", ok); return ok; }
/** * Called here by find() for each file included. * This is a callback. The original is find_files() above. * * Send the file and its data to the Storage daemon. * * Returns: 1 if OK * 0 if error * -1 to ignore file/directory (not used here) */ int save_file(JCR *jcr, FF_PKT *ff_pkt, bool top_level) { bool do_read = false; bool plugin_started = false; bool do_plugin_set = false; int status, data_stream; int rtnstat = 0; b_save_ctx bsctx; bool has_file_data = false; struct save_pkt sp; /* use by option plugin */ BSOCK *sd = jcr->store_bsock; if (jcr->is_canceled() || jcr->is_incomplete()) { return 0; } jcr->num_files_examined++; /* bump total file count */ switch (ff_pkt->type) { case FT_LNKSAVED: /* Hard linked, file already saved */ Dmsg2(130, "FT_LNKSAVED hard link: %s => %s\n", ff_pkt->fname, ff_pkt->link); break; case FT_REGE: Dmsg1(130, "FT_REGE saving: %s\n", ff_pkt->fname); has_file_data = true; break; case FT_REG: Dmsg1(130, "FT_REG saving: %s\n", ff_pkt->fname); has_file_data = true; break; case FT_LNK: Dmsg2(130, "FT_LNK saving: %s -> %s\n", ff_pkt->fname, ff_pkt->link); break; case FT_RESTORE_FIRST: Dmsg1(100, "FT_RESTORE_FIRST saving: %s\n", ff_pkt->fname); break; case FT_PLUGIN_CONFIG: Dmsg1(100, "FT_PLUGIN_CONFIG saving: %s\n", ff_pkt->fname); break; case FT_DIRBEGIN: jcr->num_files_examined--; /* correct file count */ return 1; /* not used */ case FT_NORECURSE: Jmsg(jcr, M_INFO, 1, _(" Recursion turned off. Will not descend from %s into %s\n"), ff_pkt->top_fname, ff_pkt->fname); ff_pkt->type = FT_DIREND; /* Backup only the directory entry */ break; case FT_NOFSCHG: /* Suppress message for /dev filesystems */ if (!is_in_fileset(ff_pkt)) { Jmsg(jcr, M_INFO, 1, _(" %s is a different filesystem. Will not descend from %s into it.\n"), ff_pkt->fname, ff_pkt->top_fname); } ff_pkt->type = FT_DIREND; /* Backup only the directory entry */ break; case FT_INVALIDFS: Jmsg(jcr, M_INFO, 1, _(" Disallowed filesystem. Will not descend from %s into %s\n"), ff_pkt->top_fname, ff_pkt->fname); ff_pkt->type = FT_DIREND; /* Backup only the directory entry */ break; case FT_INVALIDDT: Jmsg(jcr, M_INFO, 1, _(" Disallowed drive type. Will not descend into %s\n"), ff_pkt->fname); break; case FT_REPARSE: case FT_JUNCTION: case FT_DIREND: Dmsg1(130, "FT_DIREND: %s\n", ff_pkt->link); break; case FT_SPEC: Dmsg1(130, "FT_SPEC saving: %s\n", ff_pkt->fname); if (S_ISSOCK(ff_pkt->statp.st_mode)) { Jmsg(jcr, M_SKIPPED, 1, _(" Socket file skipped: %s\n"), ff_pkt->fname); return 1; } break; case FT_RAW: Dmsg1(130, "FT_RAW saving: %s\n", ff_pkt->fname); has_file_data = true; break; case FT_FIFO: Dmsg1(130, "FT_FIFO saving: %s\n", ff_pkt->fname); break; case FT_NOACCESS: { berrno be; Jmsg(jcr, M_NOTSAVED, 0, _(" Could not access \"%s\": ERR=%s\n"), ff_pkt->fname, be.bstrerror(ff_pkt->ff_errno)); jcr->JobErrors++; return 1; } case FT_NOFOLLOW: { berrno be; Jmsg(jcr, M_NOTSAVED, 0, _(" Could not follow link \"%s\": ERR=%s\n"), ff_pkt->fname, be.bstrerror(ff_pkt->ff_errno)); jcr->JobErrors++; return 1; } case FT_NOSTAT: { berrno be; Jmsg(jcr, M_NOTSAVED, 0, _(" Could not stat \"%s\": ERR=%s\n"), ff_pkt->fname, be.bstrerror(ff_pkt->ff_errno)); jcr->JobErrors++; return 1; } case FT_DIRNOCHG: case FT_NOCHG: Jmsg(jcr, M_SKIPPED, 1, _(" Unchanged file skipped: %s\n"), ff_pkt->fname); return 1; case FT_ISARCH: Jmsg(jcr, M_NOTSAVED, 0, _(" Archive file not saved: %s\n"), ff_pkt->fname); return 1; case FT_NOOPEN: { berrno be; Jmsg(jcr, M_NOTSAVED, 0, _(" Could not open directory \"%s\": ERR=%s\n"), ff_pkt->fname, be.bstrerror(ff_pkt->ff_errno)); jcr->JobErrors++; return 1; } case FT_DELETED: Dmsg1(130, "FT_DELETED: %s\n", ff_pkt->fname); break; default: Jmsg(jcr, M_NOTSAVED, 0, _(" Unknown file type %d; not saved: %s\n"), ff_pkt->type, ff_pkt->fname); jcr->JobErrors++; return 1; } Dmsg1(130, "filed: sending %s to stored\n", ff_pkt->fname); /* * Setup backup signing context. */ memset(&bsctx, 0, sizeof(b_save_ctx)); bsctx.digest_stream = STREAM_NONE; bsctx.jcr = jcr; bsctx.ff_pkt = ff_pkt; /* * Digests and encryption are only useful if there's file data */ if (has_file_data) { if (!setup_encryption_digests(bsctx)) { goto good_rtn; } } /* * Initialize the file descriptor we use for data and other streams. */ binit(&ff_pkt->bfd); if (bit_is_set(FO_PORTABLE, ff_pkt->flags)) { set_portable_backup(&ff_pkt->bfd); /* disable Win32 BackupRead() */ } /* * Option and cmd plugin are not compatible together */ if (ff_pkt->cmd_plugin) { do_plugin_set = true; } else if (ff_pkt->opt_plugin) { /* * Ask the option plugin what to do with this file */ switch (plugin_option_handle_file(jcr, ff_pkt, &sp)) { case bRC_OK: Dmsg2(10, "Option plugin %s will be used to backup %s\n", ff_pkt->plugin, ff_pkt->fname); jcr->opt_plugin = true; jcr->plugin_sp = &sp; plugin_update_ff_pkt(ff_pkt, &sp); do_plugin_set = true; break; case bRC_Skip: Dmsg2(10, "Option plugin %s decided to skip %s\n", ff_pkt->plugin, ff_pkt->fname); goto good_rtn; case bRC_Core: Dmsg2(10, "Option plugin %s decided to let bareos handle %s\n", ff_pkt->plugin, ff_pkt->fname); break; default: goto bail_out; } } if (do_plugin_set) { /* * Tell bfile that it needs to call plugin */ if (!set_cmd_plugin(&ff_pkt->bfd, jcr)) { goto bail_out; } send_plugin_name(jcr, sd, true); /* signal start of plugin data */ plugin_started = true; } /* * Send attributes -- must be done after binit() */ if (!encode_and_send_attributes(jcr, ff_pkt, data_stream)) { goto bail_out; } /* * Meta data only for restore object */ if (IS_FT_OBJECT(ff_pkt->type)) { goto good_rtn; } /* * Meta data only for deleted files */ if (ff_pkt->type == FT_DELETED) { goto good_rtn; } /* * Set up the encryption context and send the session data to the SD */ if (has_file_data && jcr->crypto.pki_encrypt) { if (!crypto_session_send(jcr, sd)) { goto bail_out; } } /* * For a command plugin use the setting from the plugins savepkt no_read field * which is saved in the ff_pkt->no_read variable. do_read is the inverted * value of this variable as no_read == TRUE means do_read == FALSE */ if (ff_pkt->cmd_plugin) { do_read = !ff_pkt->no_read; } else { /* * Open any file with data that we intend to save, then save it. * * Note, if is_win32_backup, we must open the Directory so that * the BackupRead will save its permissions and ownership streams. */ if (ff_pkt->type != FT_LNKSAVED && S_ISREG(ff_pkt->statp.st_mode)) { #ifdef HAVE_WIN32 do_read = !is_portable_backup(&ff_pkt->bfd) || ff_pkt->statp.st_size > 0; #else do_read = ff_pkt->statp.st_size > 0; #endif } else if (ff_pkt->type == FT_RAW || ff_pkt->type == FT_FIFO || ff_pkt->type == FT_REPARSE || ff_pkt->type == FT_JUNCTION || (!is_portable_backup(&ff_pkt->bfd) && ff_pkt->type == FT_DIREND)) { do_read = true; } } Dmsg2(150, "type=%d do_read=%d\n", ff_pkt->type, do_read); if (do_read) { btimer_t *tid; int noatime; if (ff_pkt->type == FT_FIFO) { tid = start_thread_timer(jcr, pthread_self(), 60); } else { tid = NULL; } noatime = bit_is_set(FO_NOATIME, ff_pkt->flags) ? O_NOATIME : 0; ff_pkt->bfd.reparse_point = (ff_pkt->type == FT_REPARSE || ff_pkt->type == FT_JUNCTION); if (bopen(&ff_pkt->bfd, ff_pkt->fname, O_RDONLY | O_BINARY | noatime, 0, ff_pkt->statp.st_rdev) < 0) { ff_pkt->ff_errno = errno; berrno be; Jmsg(jcr, M_NOTSAVED, 0, _(" Cannot open \"%s\": ERR=%s.\n"), ff_pkt->fname, be.bstrerror()); jcr->JobErrors++; if (tid) { stop_thread_timer(tid); tid = NULL; } goto good_rtn; } if (tid) { stop_thread_timer(tid); tid = NULL; } status = send_data(jcr, data_stream, ff_pkt, bsctx.digest, bsctx.signing_digest); if (bit_is_set(FO_CHKCHANGES, ff_pkt->flags)) { has_file_changed(jcr, ff_pkt); } bclose(&ff_pkt->bfd); if (!status) { goto bail_out; } } if (have_darwin_os) { /* * Regular files can have resource forks and Finder Info */ if (ff_pkt->type != FT_LNKSAVED && (S_ISREG(ff_pkt->statp.st_mode) && bit_is_set(FO_HFSPLUS, ff_pkt->flags))) { if (!save_rsrc_and_finder(bsctx)) { goto bail_out; } } } /* * Save ACLs when requested and available for anything not being a symlink. */ if (have_acl) { if (bit_is_set(FO_ACL, ff_pkt->flags) && ff_pkt->type != FT_LNK) { if (!do_backup_acl(jcr, ff_pkt)) { goto bail_out; } } } /* * Save Extended Attributes when requested and available for all files. */ if (have_xattr) { if (bit_is_set(FO_XATTR, ff_pkt->flags)) { if (!do_backup_xattr(jcr, ff_pkt)) { goto bail_out; } } } /* * Terminate the signing digest and send it to the Storage daemon */ if (bsctx.signing_digest) { if (!terminate_signing_digest(bsctx)) { goto bail_out; } } /* * Terminate any digest and send it to Storage daemon */ if (bsctx.digest) { if (!terminate_digest(bsctx)) { goto bail_out; } } /* * Check if original file has a digest, and send it */ if (ff_pkt->type == FT_LNKSAVED && ff_pkt->digest) { Dmsg2(300, "Link %s digest %d\n", ff_pkt->fname, ff_pkt->digest_len); sd->fsend("%ld %d 0", jcr->JobFiles, ff_pkt->digest_stream); sd->msg = check_pool_memory_size(sd->msg, ff_pkt->digest_len); memcpy(sd->msg, ff_pkt->digest, ff_pkt->digest_len); sd->msglen = ff_pkt->digest_len; sd->send(); sd->signal(BNET_EOD); /* end of hardlink record */ } good_rtn: rtnstat = jcr->is_canceled() ? 0 : 1; /* good return if not canceled */ bail_out: if (jcr->is_incomplete() || jcr->is_canceled()) { rtnstat = 0; } if (plugin_started) { send_plugin_name(jcr, sd, false); /* signal end of plugin data */ } if (ff_pkt->opt_plugin) { jcr->plugin_sp = NULL; /* sp is local to this function */ jcr->opt_plugin = false; } if (bsctx.digest) { crypto_digest_free(bsctx.digest); } if (bsctx.signing_digest) { crypto_digest_free(bsctx.signing_digest); } return rtnstat; }
/* * Run a Storage daemon replicate Job -- Wait for remote Storage daemon * to connect and authenticate it we then will get a wakeup sign using * the job_start_wait conditional * * Director sends us this command. * * Basic task here is: * - Read a command from the Storage daemon * - Execute it */ bool do_listen_run(JCR *jcr) { char ec1[30]; int errstat = 0; struct timeval tv; struct timezone tz; struct timespec timeout; BSOCK *dir = jcr->dir_bsock; jcr->sendJobStatus(JS_WaitSD); /* wait for SD to connect */ gettimeofday(&tv, &tz); timeout.tv_nsec = tv.tv_usec * 1000; timeout.tv_sec = tv.tv_sec + me->client_wait; Dmsg3(50, "%s waiting %d sec for SD to contact SD key=%s\n", jcr->Job, (int)(timeout.tv_sec-time(NULL)), jcr->sd_auth_key); Dmsg2(800, "Wait SD for jid=%d %p\n", jcr->JobId, jcr); /* * Wait for the Storage daemon to contact us to start the Job, * when he does, we will be released, unless the 30 minutes expires. */ P(mutex); while (!jcr->authenticated && !job_canceled(jcr)) { errstat = pthread_cond_timedwait(&jcr->job_start_wait, &mutex, &timeout); if (errstat == ETIMEDOUT || errstat == EINVAL || errstat == EPERM) { break; } Dmsg1(800, "=== Auth cond errstat=%d\n", errstat); } Dmsg3(50, "Auth=%d canceled=%d errstat=%d\n", jcr->authenticated, job_canceled(jcr), errstat); V(mutex); Dmsg2(800, "Auth fail or cancel for jid=%d %p\n", jcr->JobId, jcr); Dmsg1(120, "Start run Job=%s\n", jcr->Job); dir->fsend(Job_start, jcr->Job); jcr->start_time = time(NULL); jcr->run_time = jcr->start_time; jcr->sendJobStatus(JS_Running); /* * See if we need to limit the inbound bandwidth. */ if (me->max_bandwidth_per_job && jcr->store_bsock) { jcr->store_bsock->set_bwlimit(me->max_bandwidth_per_job); if (me->allow_bw_bursting) { jcr->store_bsock->set_bwlimit_bursting(); } } do_sd_commands(jcr); jcr->end_time = time(NULL); dequeue_messages(jcr); /* send any queued messages */ jcr->setJobStatus(JS_Terminated); generate_plugin_event(jcr, bsdEventJobEnd); dir->fsend(Job_end, jcr->Job, jcr->JobStatus, jcr->JobFiles, edit_uint64(jcr->JobBytes, ec1), jcr->JobErrors); dir->signal(BNET_EOD); /* send EOD to Director daemon */ free_plugins(jcr); /* release instantiated plugins */ /* * After a listen cmd we are done e.g. return false. */ return false; }
/* * Called here for each record from read_records() * This function is used when we do a external clone of a Job e.g. * this SD is the reading SD. And a remote SD is the writing SD. * * Returns: true if OK * false if error */ static bool clone_record_to_remote_sd(DCR *dcr, DEV_RECORD *rec) { POOLMEM *msgsave; JCR *jcr = dcr->jcr; char buf1[100], buf2[100]; BSOCK *sd = jcr->store_bsock; bool send_eod, send_header; #ifdef xxx Dmsg5(000, "on entry JobId=%d FI=%s SessId=%d Strm=%s len=%d\n", jcr->JobId, FI_to_ascii(buf1, rec->FileIndex), rec->VolSessionId, stream_to_ascii(buf2, rec->Stream, rec->FileIndex), rec->data_len); #endif /* * If label discard it */ if (rec->FileIndex < 0) { return true; } /* * See if this is the first record being processed. */ if (rec->last_FileIndex == 0) { /* * Initialize the last counters so we can compare * things in the next run through here. */ rec->last_VolSessionId = rec->VolSessionId; rec->last_VolSessionTime = rec->VolSessionTime; rec->last_FileIndex = rec->FileIndex; rec->last_Stream = rec->Stream; jcr->JobFiles = 1; /* * Need to send both a new header only. */ send_eod = false; send_header = true; } else { /* * See if we are changing file or stream type. */ if (rec->VolSessionId != rec->last_VolSessionId || rec->VolSessionTime != rec->last_VolSessionTime || rec->FileIndex != rec->last_FileIndex || rec->Stream != rec->last_Stream) { /* * See if we are changing the FileIndex e.g. * start processing the next file in the backup stream. */ if (rec->FileIndex != rec->last_FileIndex) { jcr->JobFiles++; } /* * Keep track of the new state. */ rec->last_VolSessionId = rec->VolSessionId; rec->last_VolSessionTime = rec->VolSessionTime; rec->last_FileIndex = rec->FileIndex; rec->last_Stream = rec->Stream; /* * Need to send both a EOD and a new header. */ send_eod = true; send_header = true; } else { send_eod = false; send_header = false; } } /* * Send a EOD when needed. */ if (send_eod) { if (!sd->signal(BNET_EOD)) { /* indicate end of file data */ if (!jcr->is_job_canceled()) { Jmsg1(jcr, M_FATAL, 0, _("Network send error to SD. ERR=%s\n"), sd->bstrerror()); } return false; } } /* * Send a header when needed. */ if (send_header) { if (!sd->fsend("%ld %d 0", rec->FileIndex, rec->Stream)) { if (!jcr->is_job_canceled()) { Jmsg1(jcr, M_FATAL, 0, _("Network send error to SD. ERR=%s\n"), sd->bstrerror()); } return false; } } /* * Send the record data. * We don't want to copy the data from the record to the socket structure * so we save the original msg pointer and use the record data pointer for * sending and restore the original msg pointer when done. */ msgsave = sd->msg; sd->msg = rec->data; sd->msglen = rec->data_len; if (!sd->send()) { sd->msg = msgsave; sd->msglen = 0; if (!jcr->is_job_canceled()) { Jmsg1(jcr, M_FATAL, 0, _("Network send error to SD. ERR=%s\n"), sd->bstrerror()); } return false; } jcr->JobBytes += sd->msglen; sd->msg = msgsave; Dmsg5(500, "wrote_record JobId=%d FI=%s SessId=%d Strm=%s len=%d\n", jcr->JobId, FI_to_ascii(buf1, rec->FileIndex), rec->VolSessionId, stream_to_ascii(buf2, rec->Stream, rec->FileIndex), rec->data_len); return true; }
bool encode_and_send_attributes(JCR *jcr, FF_PKT *ff_pkt, int &data_stream) { BSOCK *sd = jcr->store_bsock; POOL_MEM attribs(PM_NAME), attribsExBuf(PM_NAME); char *attribsEx = NULL; int attr_stream; int comp_len; bool status; int hangup = get_hangup(); #ifdef FD_NO_SEND_TEST return true; #endif Dmsg1(300, "encode_and_send_attrs fname=%s\n", ff_pkt->fname); /** Find what data stream we will use, then encode the attributes */ if ((data_stream = select_data_stream(ff_pkt, me->compatible)) == STREAM_NONE) { /* This should not happen */ Jmsg0(jcr, M_FATAL, 0, _("Invalid file flags, no supported data stream type.\n")); return false; } encode_stat(attribs.c_str(), &ff_pkt->statp, sizeof(ff_pkt->statp), ff_pkt->LinkFI, data_stream); /** Now possibly extend the attributes */ if (IS_FT_OBJECT(ff_pkt->type)) { attr_stream = STREAM_RESTORE_OBJECT; } else { attribsEx = attribsExBuf.c_str(); attr_stream = encode_attribsEx(jcr, attribsEx, ff_pkt); } Dmsg3(300, "File %s\nattribs=%s\nattribsEx=%s\n", ff_pkt->fname, attribs.c_str(), attribsEx); jcr->lock(); jcr->JobFiles++; /* increment number of files sent */ ff_pkt->FileIndex = jcr->JobFiles; /* return FileIndex */ pm_strcpy(jcr->last_fname, ff_pkt->fname); jcr->unlock(); /* * Debug code: check if we must hangup */ if (hangup && (jcr->JobFiles > (uint32_t)hangup)) { jcr->setJobStatus(JS_Incomplete); Jmsg1(jcr, M_FATAL, 0, "Debug hangup requested after %d files.\n", hangup); set_hangup(0); return false; } /** * Send Attributes header to Storage daemon * <file-index> <stream> <info> */ if (!sd->fsend("%ld %d 0", jcr->JobFiles, attr_stream)) { if (!jcr->is_canceled() && !jcr->is_incomplete()) { Jmsg1(jcr, M_FATAL, 0, _("Network send error to SD. ERR=%s\n"), sd->bstrerror()); } return false; } Dmsg1(300, ">stored: attrhdr %s", sd->msg); /** * Send file attributes to Storage daemon * File_index * File type * Filename (full path) * Encoded attributes * Link name (if type==FT_LNK or FT_LNKSAVED) * Encoded extended-attributes (for Win32) * Delta Sequence Number * * or send Restore Object to Storage daemon * File_index * File_type * Object_index * Object_len (possibly compressed) * Object_full_len (not compressed) * Object_compression * Plugin_name * Object_name * Binary Object data * * For a directory, link is the same as fname, but with trailing * slash. For a linked file, link is the link. */ if (!IS_FT_OBJECT(ff_pkt->type) && ff_pkt->type != FT_DELETED) { /* already stripped */ strip_path(ff_pkt); } switch (ff_pkt->type) { case FT_JUNCTION: case FT_LNK: case FT_LNKSAVED: Dmsg3(300, "Link %d %s to %s\n", jcr->JobFiles, ff_pkt->fname, ff_pkt->link); status = sd->fsend("%ld %d %s%c%s%c%s%c%s%c%u%c", jcr->JobFiles, ff_pkt->type, ff_pkt->fname, 0, attribs.c_str(), 0, ff_pkt->link, 0, attribsEx, 0, ff_pkt->delta_seq, 0); break; case FT_DIREND: case FT_REPARSE: /* Here link is the canonical filename (i.e. with trailing slash) */ status = sd->fsend("%ld %d %s%c%s%c%c%s%c%u%c", jcr->JobFiles, ff_pkt->type, ff_pkt->link, 0, attribs.c_str(), 0, 0, attribsEx, 0, ff_pkt->delta_seq, 0); break; case FT_PLUGIN_CONFIG: case FT_RESTORE_FIRST: comp_len = ff_pkt->object_len; ff_pkt->object_compression = 0; if (ff_pkt->object_len > 1000) { /* * Big object, compress it */ comp_len = compressBound(ff_pkt->object_len); POOLMEM *comp_obj = get_memory(comp_len); /* * FIXME: check Zdeflate error */ Zdeflate(ff_pkt->object, ff_pkt->object_len, comp_obj, comp_len); if (comp_len < ff_pkt->object_len) { ff_pkt->object = comp_obj; ff_pkt->object_compression = 1; /* zlib level 9 compression */ } else { /* * Uncompressed object smaller, use it */ comp_len = ff_pkt->object_len; } Dmsg2(100, "Object compressed from %d to %d bytes\n", ff_pkt->object_len, comp_len); } sd->msglen = Mmsg(sd->msg, "%d %d %d %d %d %d %s%c%s%c", jcr->JobFiles, ff_pkt->type, ff_pkt->object_index, comp_len, ff_pkt->object_len, ff_pkt->object_compression, ff_pkt->fname, 0, ff_pkt->object_name, 0); sd->msg = check_pool_memory_size(sd->msg, sd->msglen + comp_len + 2); memcpy(sd->msg + sd->msglen, ff_pkt->object, comp_len); /* * Note we send one extra byte so Dir can store zero after object */ sd->msglen += comp_len + 1; status = sd->send(); if (ff_pkt->object_compression) { free_and_null_pool_memory(ff_pkt->object); } break; case FT_REG: status = sd->fsend("%ld %d %s%c%s%c%c%s%c%d%c", jcr->JobFiles, ff_pkt->type, ff_pkt->fname, 0, attribs.c_str(), 0, 0, attribsEx, 0, ff_pkt->delta_seq, 0); break; default: status = sd->fsend("%ld %d %s%c%s%c%c%s%c%u%c", jcr->JobFiles, ff_pkt->type, ff_pkt->fname, 0, attribs.c_str(), 0, 0, attribsEx, 0, ff_pkt->delta_seq, 0); break; } if (!IS_FT_OBJECT(ff_pkt->type) && ff_pkt->type != FT_DELETED) { unstrip_path(ff_pkt); } Dmsg2(300, ">stored: attr len=%d: %s\n", sd->msglen, sd->msg); if (!status && !jcr->is_job_canceled()) { Jmsg1(jcr, M_FATAL, 0, _("Network send error to SD. ERR=%s\n"), sd->bstrerror()); } sd->signal(BNET_EOD); /* indicate end of attributes data */ return status; }
/** * Send data read from an already open file descriptor. * * We return 1 on sucess and 0 on errors. * * ***FIXME*** * We use ff_pkt->statp.st_size when FO_SPARSE to know when to stop reading. * Currently this is not a problem as the only other stream, resource forks, * are not handled as sparse files. */ static int send_data(JCR *jcr, int stream, FF_PKT *ff_pkt, DIGEST *digest, DIGEST *signing_digest) { b_ctx bctx; BSOCK *sd = jcr->store_bsock; #ifdef FD_NO_SEND_TEST return 1; #endif /* * Setup backup context. */ memset(&bctx, 0, sizeof(b_ctx)); bctx.jcr = jcr; bctx.ff_pkt = ff_pkt; bctx.msgsave = sd->msg; /* save the original sd buffer */ bctx.rbuf = sd->msg; /* read buffer */ bctx.wbuf = sd->msg; /* write buffer */ bctx.rsize = jcr->buf_size; /* read buffer size */ bctx.cipher_input = (uint8_t *)bctx.rbuf; /* encrypt uncompressed data */ bctx.digest = digest; /* encryption digest */ bctx.signing_digest = signing_digest; /* signing digest */ Dmsg1(300, "Saving data, type=%d\n", ff_pkt->type); if (!setup_compression_context(bctx)) { goto bail_out; } if (!setup_encryption_context(bctx)) { goto bail_out; } /* * Send Data header to Storage daemon * <file-index> <stream> <info> */ if (!sd->fsend("%ld %d 0", jcr->JobFiles, stream)) { if (!jcr->is_job_canceled()) { Jmsg1(jcr, M_FATAL, 0, _("Network send error to SD. ERR=%s\n"), sd->bstrerror()); } goto bail_out; } Dmsg1(300, ">stored: datahdr %s", sd->msg); /* * Make space at beginning of buffer for fileAddr because this * same buffer will be used for writing if compression is off. */ if (bit_is_set(FO_SPARSE, ff_pkt->flags) || bit_is_set(FO_OFFSETS, ff_pkt->flags)) { bctx.rbuf += OFFSET_FADDR_SIZE; bctx.rsize -= OFFSET_FADDR_SIZE; #ifdef HAVE_FREEBSD_OS /* * To read FreeBSD partitions, the read size must be a multiple of 512. */ bctx.rsize = (bctx.rsize / 512) * 512; #endif } /* * A RAW device read on win32 only works if the buffer is a multiple of 512 */ #ifdef HAVE_WIN32 if (S_ISBLK(ff_pkt->statp.st_mode)) { bctx.rsize = (bctx.rsize / 512) * 512; } if (ff_pkt->statp.st_rdev & FILE_ATTRIBUTE_ENCRYPTED) { if (!send_encrypted_data(bctx)) { goto bail_out; } } else { if (!send_plain_data(bctx)) { goto bail_out; } } #else if (!send_plain_data(bctx)) { goto bail_out; } #endif if (sd->msglen < 0) { /* error */ berrno be; Jmsg(jcr, M_ERROR, 0, _("Read error on file %s. ERR=%s\n"), ff_pkt->fname, be.bstrerror(ff_pkt->bfd.berrno)); if (jcr->JobErrors++ > 1000) { /* insanity check */ Jmsg(jcr, M_FATAL, 0, _("Too many errors. JobErrors=%d.\n"), jcr->JobErrors); } } else if (bit_is_set(FO_ENCRYPT, ff_pkt->flags)) { /* * For encryption, we must call finalize to push out any buffered data. */ if (!crypto_cipher_finalize(bctx.cipher_ctx, (uint8_t *)jcr->crypto.crypto_buf, &bctx.encrypted_len)) { /* * Padding failed. Shouldn't happen. */ Jmsg(jcr, M_FATAL, 0, _("Encryption padding error\n")); goto bail_out; } /* * Note, on SSL pre-0.9.7, there is always some output */ if (bctx.encrypted_len > 0) { sd->msglen = bctx.encrypted_len; /* set encrypted length */ sd->msg = jcr->crypto.crypto_buf; /* set correct write buffer */ if (!sd->send()) { if (!jcr->is_job_canceled()) { Jmsg1(jcr, M_FATAL, 0, _("Network send error to SD. ERR=%s\n"), sd->bstrerror()); } goto bail_out; } Dmsg1(130, "Send data to SD len=%d\n", sd->msglen); jcr->JobBytes += sd->msglen; /* count bytes saved possibly compressed/encrypted */ sd->msg = bctx.msgsave; /* restore bnet buffer */ } } if (!sd->signal(BNET_EOD)) { /* indicate end of file data */ if (!jcr->is_job_canceled()) { Jmsg1(jcr, M_FATAL, 0, _("Network send error to SD. ERR=%s\n"), sd->bstrerror()); } goto bail_out; } /* * Free the cipher context */ if (bctx.cipher_ctx) { crypto_cipher_free(bctx.cipher_ctx); } return 1; bail_out: /* * Free the cipher context */ if (bctx.cipher_ctx) { crypto_cipher_free(bctx.cipher_ctx); } sd->msg = bctx.msgsave; /* restore bnet buffer */ sd->msglen = 0; return 0; }
/* * Handle Director User Agent commands */ static void *handle_UA_client_request(void *arg) { int status; UAContext *ua; JCR *jcr; BSOCK *user = (BSOCK *)arg; pthread_detach(pthread_self()); jcr = new_control_jcr("-Console-", JT_CONSOLE); ua = new_ua_context(jcr); ua->UA_sock = user; set_jcr_in_tsd(INVALID_JCR); user->recv(); /* Get first message */ if (!authenticate_user_agent(ua)) { goto getout; } while (!ua->quit) { if (ua->api) { user->signal(BNET_MAIN_PROMPT); } status = user->recv(); if (status >= 0) { pm_strcpy(ua->cmd, ua->UA_sock->msg); parse_ua_args(ua); if (ua->argc > 0 && ua->argk[0][0] == '.') { do_a_dot_command(ua); } else { do_a_command(ua); } dequeue_messages(ua->jcr); if (!ua->quit) { if (console_msg_pending && acl_access_ok(ua, Command_ACL, "messages")) { if (ua->auto_display_messages) { pm_strcpy(ua->cmd, "messages"); qmessages_cmd(ua, ua->cmd); ua->user_notified_msg_pending = false; } else if (!ua->gui && !ua->user_notified_msg_pending && console_msg_pending) { if (ua->api) { user->signal(BNET_MSGS_PENDING); } else { bsendmsg(ua, _("You have messages.\n")); } ua->user_notified_msg_pending = true; } } if (!ua->api) { user->signal(BNET_EOD); /* send end of command */ } } } else if (is_bnet_stop(user)) { ua->quit = true; } else { /* signal */ user->signal(BNET_POLL); } } getout: close_db(ua); free_ua_context(ua); free_jcr(jcr); user->close(); delete user; return NULL; }
/* * Read Data and commit to new job. */ bool do_mac_run(JCR *jcr) { DEVICE *dev; char ec1[50]; const char *Type; bool ok = true; BSOCK *dir = jcr->dir_bsock; switch(jcr->getJobType()) { case JT_MIGRATE: Type = "Migration"; break; case JT_ARCHIVE: Type = "Archive"; break; case JT_COPY: Type = "Copy"; break; case JT_BACKUP: Type = "Virtual Backup"; break; default: Type = "Unknown"; break; } Dmsg0(20, "Start read data.\n"); if (jcr->NumReadVolumes == 0) { Jmsg(jcr, M_FATAL, 0, _("No Volume names found for %s.\n"), Type); goto bail_out; } /* * Check autoinflation/autodeflation settings. */ check_auto_xflation(jcr); /* * See if we perform both read and write or read only. */ if (jcr->remote_replicate) { BSOCK *sd; if (!jcr->read_dcr) { Jmsg(jcr, M_FATAL, 0, _("Read device not properly initialized.\n")); goto bail_out; } Dmsg1(100, "read_dcr=%p\n", jcr->read_dcr); Dmsg3(200, "Found %d volumes names for %s. First=%s\n", jcr->NumReadVolumes, Type, jcr->VolList->VolumeName); /* * Ready devices for reading. */ if (!acquire_device_for_read(jcr->read_dcr)) { ok = false; goto bail_out; } Dmsg2(200, "===== After acquire pos %u:%u\n", jcr->read_dcr->dev->file, jcr->read_dcr->dev->block_num); jcr->sendJobStatus(JS_Running); /* * Set network buffering. */ sd = jcr->store_bsock; if (!sd->set_buffer_size(me->max_network_buffer_size, BNET_SETBUF_WRITE)) { Jmsg(jcr, M_FATAL, 0, _("Cannot set buffer size SD->SD.\n")); ok = false; goto bail_out; } /* * Let the remote SD know we are about to start the replication. */ sd->fsend(start_replicate); Dmsg1(110, ">stored: %s", sd->msg); /* * Expect to receive back the Ticket number. */ if (bget_msg(sd) >= 0) { Dmsg1(110, "<stored: %s", sd->msg); if (sscanf(sd->msg, OK_start_replicate, &jcr->Ticket) != 1) { Jmsg(jcr, M_FATAL, 0, _("Bad response to start replicate: %s\n"), sd->msg); goto bail_out; } Dmsg1(110, "Got Ticket=%d\n", jcr->Ticket); } else { Jmsg(jcr, M_FATAL, 0, _("Bad response from stored to start replicate command\n")); goto bail_out; } /* * Let the remote SD know we are now really going to send the data. */ sd->fsend(replicate_data, jcr->Ticket); Dmsg1(110, ">stored: %s", sd->msg); /* * Expect to get response to the replicate data cmd from Storage daemon */ if (!response(jcr, sd, OK_data, "replicate data")) { ok = false; goto bail_out; } /* * Read all data and send it to remote SD. */ ok = read_records(jcr->read_dcr, clone_record_to_remote_sd, mount_next_read_volume); /* * Send the last EOD to close the last data transfer and a next EOD to * signal the remote we are done. */ if (!sd->signal(BNET_EOD) || !sd->signal(BNET_EOD)) { if (!jcr->is_job_canceled()) { Jmsg1(jcr, M_FATAL, 0, _("Network send error to SD. ERR=%s\n"), sd->bstrerror()); } goto bail_out; } /* * Expect to get response that the replicate data succeeded. */ if (!response(jcr, sd, OK_replicate, "replicate data")) { ok = false; goto bail_out; } /* * End replicate session. */ sd->fsend(end_replicate); Dmsg1(110, ">stored: %s", sd->msg); /* * Expect to get response to the end replicate cmd from Storage daemon */ if (!response(jcr, sd, OK_end_replicate, "end replicate")) { ok = false; goto bail_out; } /* Inform Storage daemon that we are done */ sd->signal(BNET_TERMINATE); } else { if (!jcr->read_dcr || !jcr->dcr) { Jmsg(jcr, M_FATAL, 0, _("Read and write devices not properly initialized.\n")); goto bail_out; } Dmsg2(100, "read_dcr=%p write_dcr=%p\n", jcr->read_dcr, jcr->dcr); Dmsg3(200, "Found %d volumes names for %s. First=%s\n", jcr->NumReadVolumes, Type, jcr->VolList->VolumeName); /* * Ready devices for reading and writing. */ if (!acquire_device_for_read(jcr->read_dcr) || !acquire_device_for_append(jcr->dcr)) { ok = false; goto bail_out; } Dmsg2(200, "===== After acquire pos %u:%u\n", jcr->dcr->dev->file, jcr->dcr->dev->block_num); jcr->sendJobStatus(JS_Running); if (!begin_data_spool(jcr->dcr) ) { ok = false; goto bail_out; } if (!begin_attribute_spool(jcr)) { ok = false; goto bail_out; } jcr->dcr->VolFirstIndex = jcr->dcr->VolLastIndex = 0; jcr->run_time = time(NULL); set_start_vol_position(jcr->dcr); jcr->JobFiles = 0; /* * Read all data and make a local clone of it. */ ok = read_records(jcr->read_dcr, clone_record_internally, mount_next_read_volume); } bail_out: if (!ok) { jcr->setJobStatus(JS_ErrorTerminated); } if (!jcr->remote_replicate && jcr->dcr) { /* * Don't use time_t for job_elapsed as time_t can be 32 or 64 bits, * and the subsequent Jmsg() editing will break */ int32_t job_elapsed; dev = jcr->dcr->dev; Dmsg1(100, "ok=%d\n", ok); if (ok || dev->can_write()) { /* * Flush out final partial block of this session */ if (!jcr->dcr->write_block_to_device()) { Jmsg2(jcr, M_FATAL, 0, _("Fatal append error on device %s: ERR=%s\n"), dev->print_name(), dev->bstrerror()); Dmsg0(100, _("Set ok=FALSE after write_block_to_device.\n")); ok = false; } Dmsg2(200, "Flush block to device pos %u:%u\n", dev->file, dev->block_num); } if (!ok) { discard_data_spool(jcr->dcr); } else { /* * Note: if commit is OK, the device will remain blocked */ commit_data_spool(jcr->dcr); } job_elapsed = time(NULL) - jcr->run_time; if (job_elapsed <= 0) { job_elapsed = 1; } Jmsg(jcr, M_INFO, 0, _("Elapsed time=%02d:%02d:%02d, Transfer rate=%s Bytes/second\n"), job_elapsed / 3600, job_elapsed % 3600 / 60, job_elapsed % 60, edit_uint64_with_suffix(jcr->JobBytes / job_elapsed, ec1)); /* * Release the device -- and send final Vol info to DIR */ release_device(jcr->dcr); if (!ok || job_canceled(jcr)) { discard_attribute_spool(jcr); } else { commit_attribute_spool(jcr); } } if (jcr->read_dcr) { if (!release_device(jcr->read_dcr)) { ok = false; } } jcr->sendJobStatus(); /* update director */ Dmsg0(30, "Done reading.\n"); jcr->end_time = time(NULL); dequeue_messages(jcr); /* send any queued messages */ if (ok) { jcr->setJobStatus(JS_Terminated); } generate_plugin_event(jcr, bsdEventJobEnd); dir->fsend(Job_end, jcr->Job, jcr->JobStatus, jcr->JobFiles, edit_uint64(jcr->JobBytes, ec1), jcr->JobErrors); Dmsg4(100, Job_end, jcr->Job, jcr->JobStatus, jcr->JobFiles, ec1); dir->signal(BNET_EOD); /* send EOD to Director daemon */ free_plugins(jcr); /* release instantiated plugins */ return false; /* Continue DIR session ? */ }
/* * 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; }
/* * Implement Bareos bconsole command purge action * purge action= pool= volume= storage= devicetype= */ static int action_on_purge_cmd(UAContext *ua, const char *cmd) { bool allpools = false; int drive = -1; int nb = 0; uint32_t *results = NULL; const char *action = "all"; STORERES *store = NULL; POOLRES *pool = NULL; MEDIA_DBR mr; POOL_DBR pr; BSOCK *sd = NULL; memset(&pr, 0, sizeof(pr)); /* Look at arguments */ for (int i=1; i<ua->argc; i++) { if (bstrcasecmp(ua->argk[i], NT_("allpools"))) { allpools = true; } else if (bstrcasecmp(ua->argk[i], NT_("volume")) && is_name_valid(ua->argv[i], NULL)) { bstrncpy(mr.VolumeName, ua->argv[i], sizeof(mr.VolumeName)); } else if (bstrcasecmp(ua->argk[i], NT_("devicetype")) && ua->argv[i]) { bstrncpy(mr.MediaType, ua->argv[i], sizeof(mr.MediaType)); } else if (bstrcasecmp(ua->argk[i], NT_("drive")) && ua->argv[i]) { drive = atoi(ua->argv[i]); } else if (bstrcasecmp(ua->argk[i], NT_("action")) && is_name_valid(ua->argv[i], NULL)) { action=ua->argv[i]; } } /* Choose storage */ ua->jcr->res.wstore = store = get_storage_resource(ua, false); if (!store) { goto bail_out; } switch (store->Protocol) { case APT_NDMPV2: case APT_NDMPV3: case APT_NDMPV4: ua->warning_msg(_("Storage has non-native protocol.\n")); goto bail_out; default: break; } if (!open_db(ua)) { Dmsg0(100, "Can't open db\n"); goto bail_out; } if (!allpools) { /* force pool selection */ pool = get_pool_resource(ua); if (!pool) { Dmsg0(100, "Can't get pool resource\n"); goto bail_out; } bstrncpy(pr.Name, pool->name(), sizeof(pr.Name)); if (!db_get_pool_record(ua->jcr, ua->db, &pr)) { Dmsg0(100, "Can't get pool record\n"); goto bail_out; } mr.PoolId = pr.PoolId; } /* * Look for all Purged volumes that can be recycled, are enabled and * have more the 10,000 bytes. */ mr.Recycle = 1; mr.Enabled = 1; mr.VolBytes = 10000; set_storageid_in_mr(store, &mr); bstrncpy(mr.VolStatus, "Purged", sizeof(mr.VolStatus)); if (!db_get_media_ids(ua->jcr, ua->db, &mr, &nb, &results)) { Dmsg0(100, "No results from db_get_media_ids\n"); goto bail_out; } if (!nb) { ua->send_msg(_("No Volumes found to perform %s action.\n"), action); goto bail_out; } if ((sd = open_sd_bsock(ua)) == NULL) { Dmsg0(100, "Can't open connection to sd\n"); goto bail_out; } /* * Loop over the candidate Volumes and actually truncate them */ for (int i=0; i < nb; i++) { mr.clear(); mr.MediaId = results[i]; if (db_get_media_record(ua->jcr, ua->db, &mr)) { /* TODO: ask for drive and change Pool */ if (bstrcasecmp("truncate", action) || bstrcasecmp("all", action)) { do_truncate_on_purge(ua, &mr, pr.Name, store->dev_name(), drive, sd); } } else { Dmsg1(0, "Can't find MediaId=%lld\n", (uint64_t) mr.MediaId); } } bail_out: close_db(ua); if (sd) { sd->signal(BNET_TERMINATE); sd->close(); ua->jcr->store_bsock = NULL; } ua->jcr->res.wstore = NULL; if (results) { free(results); } return 1; }