/* * 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 */ }
bool run_cmd(JCR *jcr) { struct timeval tv; struct timezone tz; struct timespec timeout; int errstat = 0; Dsm_check(200); Dmsg1(200, "Run_cmd: %s\n", jcr->dir_bsock->msg); /* If we do not need the FD, we are doing a migrate, copy, or virtual * backup. */ if (jcr->no_client_used()) { do_mac(jcr); return false; } jcr->sendJobStatus(JS_WaitFD); /* wait for FD 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 FD to contact SD key=%s\n", jcr->Job, (int)(timeout.tv_sec-time(NULL)), jcr->sd_auth_key); Dmsg2(800, "Wait FD for jid=%d %p\n", jcr->JobId, jcr); /* * Wait for the File 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); memset(jcr->sd_auth_key, 0, strlen(jcr->sd_auth_key)); if (jcr->authenticated && !job_canceled(jcr)) { Dmsg2(800, "Running jid=%d %p\n", jcr->JobId, jcr); run_job(jcr); /* Run the job */ } Dmsg2(800, "Done jid=%d %p\n", jcr->JobId, jcr); return false; }
/* * 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 */ }
/* * This routine is meant to be called once the first pass * to ensure that we have a candidate volume to mount. * Otherwise, we ask the sysop to created one. */ bool DCR::find_a_volume() { DCR *dcr = this; if (!is_suitable_volume_mounted()) { bool have_vol = false; /* Do we have a candidate volume? */ if (dev->vol) { bstrncpy(VolumeName, dev->vol->vol_name, sizeof(VolumeName)); have_vol = dir_get_volume_info(this, GET_VOL_INFO_FOR_WRITE); } /* * Get Director's idea of what tape we should have mounted. * in dcr->VolCatInfo */ if (!have_vol) { Dmsg0(200, "Before dir_find_next_appendable_volume.\n"); while (!dir_find_next_appendable_volume(dcr)) { Dmsg0(200, "not dir_find_next\n"); if (job_canceled(jcr)) { unlock_volumes(); return false; } unlock_volumes(); if (!dir_ask_sysop_to_create_appendable_volume(dcr)) { return false; } lock_volumes(); Dmsg0(150, "Again dir_find_next_append...\n"); } } } return true; }
/* * Called here by find() for each file included. * */ static int tally_file(JCR *jcr, FF_PKT *ff_pkt, bool top_level) { ATTR attr; if (job_canceled(jcr)) { return 0; } switch (ff_pkt->type) { case FT_LNKSAVED: /* Hard linked, file already saved */ case FT_REGE: case FT_REG: case FT_LNK: case FT_NORECURSE: case FT_NOFSCHG: case FT_INVALIDFS: case FT_INVALIDDT: case FT_REPARSE: case FT_JUNCTION: case FT_DIREND: case FT_SPEC: case FT_RAW: case FT_FIFO: break; case FT_DIRBEGIN: case FT_NOACCESS: case FT_NOFOLLOW: case FT_NOSTAT: case FT_DIRNOCHG: case FT_NOCHG: case FT_ISARCH: case FT_NOOPEN: default: return 1; } if (ff_pkt->type != FT_LNKSAVED && S_ISREG(ff_pkt->statp.st_mode)) { if (ff_pkt->statp.st_size > 0) { jcr->JobBytes += ff_pkt->statp.st_size; } #ifdef HAVE_DARWIN_OS if (ff_pkt->flags & FO_HFSPLUS) { if (ff_pkt->hfsinfo.rsrclength > 0) { jcr->JobBytes += ff_pkt->hfsinfo.rsrclength; } jcr->JobBytes += 32; /* Finder info */ } #endif } jcr->num_files_examined++; jcr->JobFiles++; /* increment number of files seen */ if (jcr->listing) { memcpy(&attr.statp, &ff_pkt->statp, sizeof(struct stat)); attr.type = ff_pkt->type; attr.ofname = (POOLMEM *)ff_pkt->fname; attr.olname = (POOLMEM *)ff_pkt->link; print_ls_output(jcr, &attr); } return 1; }
/* * Cleanup a NDMP restore session. */ void ndmp_restore_cleanup(JCR *jcr, int TermCode) { char term_code[100]; const char *term_msg; int msg_type = M_INFO; Dmsg0(20, "In ndmp_restore_cleanup\n"); update_job_end(jcr, TermCode); if (jcr->unlink_bsr && jcr->RestoreBootstrap) { secure_erase(jcr, 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 { term_msg = _("Restore OK"); } break; case JS_Warnings: term_msg = _("Restore OK -- with warnings"); break; case JS_FatalError: case JS_ErrorTerminated: term_msg = _("*** Restore Error ***"); msg_type = M_ERROR; /* Generate error message */ if (jcr->store_bsock) { jcr->store_bsock->signal(BNET_TERMINATE); if (jcr->SD_msg_chan_started) { pthread_cancel(jcr->SD_msg_chan); } } break; case JS_Canceled: term_msg = _("Restore Canceled"); if (jcr->store_bsock) { jcr->store_bsock->signal(BNET_TERMINATE); if (jcr->SD_msg_chan_started) { pthread_cancel(jcr->SD_msg_chan); } } break; default: term_msg = term_code; sprintf(term_code, _("Inappropriate term code: %c\n"), TermCode); break; } generate_restore_summary(jcr, msg_type, term_msg); Dmsg0(20, "Leaving ndmp_restore_cleanup\n"); }
/* * Foreach files in currrent list, send "/path/fname\0LStat" to FD */ static int accurate_list_handler(void *ctx, int num_fields, char **row) { JCR *jcr = (JCR *)ctx; if (job_canceled(jcr)) { return 1; } if (row[2] > 0) { /* discard when file_index == 0 */ jcr->file_bsock->fsend("%s%s%c%s", row[0], row[1], 0, row[4]); } return 0; }
/* * Check if MaxRunSchedTime has expired and if the job can be * canceled. */ static bool job_check_maxrunschedtime(JCR *jcr) { if (jcr->MaxRunSchedTime == 0 || job_canceled(jcr)) { return false; } if ((watchdog_time - jcr->sched_time) < jcr->MaxRunSchedTime) { Dmsg3(200, "Job %p (%s) with MaxRunSchedTime %d not expired\n", jcr, jcr->Job, jcr->MaxRunSchedTime); return false; } return true; }
/* * Despool spooled attributes */ bool BSOCK::despool(void update_attr_spool_size(ssize_t size), ssize_t tsize) { int32_t pktsiz; size_t nbytes; ssize_t last = 0, size = 0; int count = 0; JCR *jcr = get_jcr(); if (lseek(m_spool_fd, 0, SEEK_SET) == -1) { Qmsg(jcr, M_FATAL, 0, _("attr spool I/O error.\n")); return false; } #if defined(HAVE_POSIX_FADVISE) && defined(POSIX_FADV_WILLNEED) posix_fadvise(m_spool_fd, 0, 0, POSIX_FADV_WILLNEED); #endif while ((nbytes = read(m_spool_fd, (char *)&pktsiz, sizeof(int32_t))) == sizeof(int32_t)) { size += sizeof(int32_t); msglen = ntohl(pktsiz); if (msglen > 0) { if (msglen > (int32_t)sizeof_pool_memory(msg)) { msg = realloc_pool_memory(msg, msglen + 1); } nbytes = read(m_spool_fd, msg, msglen); if (nbytes != (size_t)msglen) { berrno be; Dmsg2(400, "nbytes=%d msglen=%d\n", nbytes, msglen); Qmsg1(get_jcr(), M_FATAL, 0, _("read attr spool error. ERR=%s\n"), be.bstrerror()); update_attr_spool_size(tsize - last); return false; } size += nbytes; if ((++count & 0x3F) == 0) { update_attr_spool_size(size - last); last = size; } } send(); if (jcr && job_canceled(jcr)) { return false; } } update_attr_spool_size(tsize - last); return true; }
/* * We are called here for each record that matches the above * SQL query -- that is for each file contained in the Catalog * that was not marked earlier. This means that the file in * question is a missing file (in the Catalog but not on Disk). */ static int missing_handler(void *ctx, int num_fields, char **row) { JCR *jcr = (JCR *)ctx; if (job_canceled(jcr)) { return 1; } if (!jcr->fn_printed) { Qmsg(jcr, M_WARNING, 0, _("The following files are in the Catalog but not on %s:\n"), jcr->getJobLevel() == L_VERIFY_VOLUME_TO_CATALOG ? "the Volume(s)" : "disk"); jcr->fn_printed = true; } Qmsg(jcr, M_INFO, 0, " %s%s\n", row[0]?row[0]:"", row[1]?row[1]:""); return 0; }
/* * Try to connect to host for max_retry_time at retry_time intervals. * Note, you must have called the constructor prior to calling * this routine. */ bool BSOCK::connect(JCR * jcr, int retry_interval, utime_t max_retry_time, utime_t heart_beat, const char *name, char *host, char *service, int port, int verbose) { bool ok = false; int i; int fatal = 0; time_t begin_time = time(NULL); time_t now; btimer_t *tid = NULL; /* Try to trap out of OS call when time expires */ if (max_retry_time) { tid = start_thread_timer(jcr, pthread_self(), (uint32_t)max_retry_time); } for (i = 0; !open(jcr, name, host, service, port, heart_beat, &fatal); i -= retry_interval) { berrno be; if (fatal || (jcr && job_canceled(jcr))) { goto bail_out; } Dmsg4(100, "Unable to connect to %s on %s:%d. ERR=%s\n", name, host, port, be.bstrerror()); if (i < 0) { i = 60 * 5; /* complain again in 5 minutes */ if (verbose) Qmsg4(jcr, M_WARNING, 0, _( "Could not connect to %s on %s:%d. ERR=%s\n" "Retrying ...\n"), name, host, port, be.bstrerror()); } bmicrosleep(retry_interval, 0); now = time(NULL); if (begin_time + max_retry_time <= now) { Qmsg4(jcr, M_FATAL, 0, _("Unable to connect to %s on %s:%d. ERR=%s\n"), name, host, port, be.bstrerror()); goto bail_out; } } ok = true; bail_out: if (tid) { stop_thread_timer(tid); } return ok; }
static void job_monitor_watchdog(watchdog_t *self) { JCR *control_jcr, *jcr; control_jcr = (JCR *)self->data; Dsm_check(100); Dmsg1(800, "job_monitor_watchdog %p called\n", self); foreach_jcr(jcr) { bool cancel = false; if (jcr->JobId == 0 || job_canceled(jcr) || jcr->no_maxtime) { Dmsg2(800, "Skipping JCR=%p Job=%s\n", jcr, jcr->Job); continue; } /* check MaxWaitTime */ if (job_check_maxwaittime(jcr)) { jcr->setJobStatus(JS_Canceled); Qmsg(jcr, M_FATAL, 0, _("Max wait time exceeded. Job canceled.\n")); cancel = true; /* check MaxRunTime */ } else if (job_check_maxruntime(jcr)) { jcr->setJobStatus(JS_Canceled); Qmsg(jcr, M_FATAL, 0, _("Max run time exceeded. Job canceled.\n")); cancel = true; /* check MaxRunSchedTime */ } else if (job_check_maxrunschedtime(jcr)) { jcr->setJobStatus(JS_Canceled); Qmsg(jcr, M_FATAL, 0, _("Max run sched time exceeded. Job canceled.\n")); cancel = true; } if (cancel) { Dmsg3(800, "Cancelling JCR %p jobid %d (%s)\n", jcr, jcr->JobId, jcr->Job); UAContext *ua = new_ua_context(jcr); ua->jcr = control_jcr; cancel_job(ua, jcr); free_ua_context(ua); Dmsg2(800, "Have cancelled JCR %p Job=%d\n", jcr, jcr->JobId); } } /* Keep reference counts correct */ endeach_jcr(jcr); }
/* * Send current file list to FD * DIR -> FD : accurate files=xxxx * DIR -> FD : /path/to/file\0Lstat * DIR -> FD : /path/to/dir/\0Lstat * ... * DIR -> FD : EOD */ bool send_accurate_current_files(JCR *jcr) { POOL_MEM buf; if (!jcr->accurate || job_canceled(jcr) || jcr->get_JobLevel()==L_FULL) { return true; } POOLMEM *jobids = get_pool_memory(PM_FNAME); db_accurate_get_jobids(jcr, jcr->db, &jcr->jr, jobids); if (*jobids == 0) { free_pool_memory(jobids); Jmsg(jcr, M_FATAL, 0, _("Cannot find previous jobids.\n")); return false; } Jmsg(jcr, M_INFO, 0, _("Sending Accurate information.\n")); /* to be able to allocate the right size for htable */ POOLMEM *nb = get_pool_memory(PM_FNAME); *nb = 0; /* clear buffer */ Mmsg(buf, "SELECT sum(JobFiles) FROM Job WHERE JobId IN (%s)",jobids); db_sql_query(jcr->db, buf.c_str(), db_get_int_handler, nb); Dmsg2(200, "jobids=%s nb=%s\n", jobids, nb); jcr->file_bsock->fsend("accurate files=%s\n", nb); if (!db_open_batch_connexion(jcr, jcr->db)) { Jmsg0(jcr, M_FATAL, 0, "Can't get dedicate sql connexion"); return false; } db_get_file_list(jcr, jcr->db_batch, jobids, accurate_list_handler, (void *)jcr); /* TODO: close the batch connexion ? (can be used very soon) */ free_pool_memory(jobids); free_pool_memory(nb); jcr->file_bsock->signal(BNET_EOD); return true; }
/* * Get response from Storage daemon to a command we sent. * Check that the response is OK. * * Returns: false on failure * true on success */ static bool response(JCR *jcr, BSOCK *sd, char *resp, const char *cmd) { if (sd->errors) { return false; } if (bget_msg(sd) > 0) { Dmsg1(110, "<stored: %s", sd->msg); if (bstrcmp(sd->msg, resp)) { return true; } } if (job_canceled(jcr)) { return false; /* if canceled avoid useless error messages */ } if (is_bnet_error(sd)) { Jmsg2(jcr, M_FATAL, 0, _("Comm error with SD. bad response to %s. ERR=%s\n"), cmd, bnet_strerror(sd)); } else { Jmsg3(jcr, M_FATAL, 0, _("Bad response to %s command. Wanted %s, got %s\n"), cmd, resp, sd->msg); } return false; }
/* * Check if maxruntime has expired and if the job can be * canceled. */ static bool job_check_maxruntime(JCR *jcr) { bool cancel = false; JOBRES *job = jcr->res.job; utime_t run_time; if (job_canceled(jcr) || !jcr->job_started) { return false; } if (job->MaxRunTime == 0 && job->FullMaxRunTime == 0 && job->IncMaxRunTime == 0 && job->DiffMaxRunTime == 0) { return false; } run_time = watchdog_time - jcr->start_time; Dmsg7(200, "check_maxruntime %llu-%u=%llu >= %llu|%llu|%llu|%llu\n", watchdog_time, jcr->start_time, run_time, job->MaxRunTime, job->FullMaxRunTime, job->IncMaxRunTime, job->DiffMaxRunTime); if (jcr->getJobLevel() == L_FULL && job->FullMaxRunTime != 0 && run_time >= job->FullMaxRunTime) { Dmsg0(200, "check_maxwaittime: FullMaxcancel\n"); cancel = true; } else if (jcr->getJobLevel() == L_DIFFERENTIAL && job->DiffMaxRunTime != 0 && run_time >= job->DiffMaxRunTime) { Dmsg0(200, "check_maxwaittime: DiffMaxcancel\n"); cancel = true; } else if (jcr->getJobLevel() == L_INCREMENTAL && job->IncMaxRunTime != 0 && run_time >= job->IncMaxRunTime) { Dmsg0(200, "check_maxwaittime: IncMaxcancel\n"); cancel = true; } else if (job->MaxRunTime > 0 && run_time >= job->MaxRunTime) { Dmsg0(200, "check_maxwaittime: Maxcancel\n"); cancel = true; } return cancel; }
/** * Request to mount specific Volume * * Entered with device blocked and dcr->VolumeName is desired * volume. * Leaves with device blocked. * * Returns: true on success (operator issues a mount command) * false on failure * Note, must create dev->errmsg on error return. * */ bool dir_ask_sysop_to_mount_volume(DCR *dcr, int mode) { int status = W_TIMEOUT; DEVICE *dev = dcr->dev; JCR *jcr = dcr->jcr; Dmsg0(dbglvl, "enter dir_ask_sysop_to_mount_volume\n"); if (!dcr->VolumeName[0]) { Mmsg0(dev->errmsg, _("Cannot request another volume: no volume name given.\n")); return false; } ASSERT(dev->blocked()); for ( ;; ) { if (job_canceled(jcr)) { Mmsg(dev->errmsg, _("Job %s canceled while waiting for mount on Storage Device %s.\n"), jcr->Job, dev->print_name()); return false; } /* * If we are not polling, and the wait timeout or the * user explicitly did a mount, send him the message. * Otherwise skip it. */ if (!dev->poll && (status == W_TIMEOUT || status == W_MOUNT)) { const char *msg; if (mode == ST_APPENDREADY) { msg = _("Please mount append Volume \"%s\" or label a new one for:\n" " Job: %s\n" " Storage: %s\n" " Pool: %s\n" " Media type: %s\n"); } else { msg = _("Please mount read Volume \"%s\" for:\n" " Job: %s\n" " Storage: %s\n" " Pool: %s\n" " Media type: %s\n"); } Jmsg(jcr, M_MOUNT, 0, msg, dcr->VolumeName, jcr->Job, dev->print_name(), dcr->pool_name, dcr->media_type); Dmsg3(dbglvl, "Mount \"%s\" on device \"%s\" for Job %s\n", dcr->VolumeName, dev->print_name(), jcr->Job); } jcr->sendJobStatus(JS_WaitMount); status = wait_for_sysop(dcr); /* wait on device */ Dmsg1(dbglvl, "Back from wait_for_sysop status=%d\n", status); if (dev->poll) { Dmsg1(dbglvl, "Poll timeout in mount vol on device %s\n", dev->print_name()); Dmsg1(dbglvl, "Blocked=%s\n", dev->print_blocked()); goto get_out; } if (status == W_TIMEOUT) { if (!double_dev_wait_time(dev)) { Mmsg(dev->errmsg, _("Max time exceeded waiting to mount Storage Device %s for Job %s\n"), dev->print_name(), jcr->Job); Jmsg(jcr, M_FATAL, 0, "%s", dev->errmsg); Dmsg1(dbglvl, "Gave up waiting on device %s\n", dev->print_name()); return false; /* exceeded maximum waits */ } continue; } if (status == W_ERROR) { berrno be; Mmsg(dev->errmsg, _("pthread error in mount_volume\n")); Jmsg(jcr, M_FATAL, 0, "%s", dev->errmsg); return false; } Dmsg1(dbglvl, "Someone woke me for device %s\n", dev->print_name()); break; } get_out: jcr->sendJobStatus(JS_Running); Dmsg0(dbglvl, "leave dir_ask_sysop_to_mount_volume\n"); return true; }
/* * If release is set, we rewind the current volume, * which we no longer want, and ask the user (console) * to mount the next volume. * * Continue trying until we get it, and then ensure * that we can write on it. * * This routine returns a 0 only if it is REALLY * impossible to get the requested Volume. * */ bool DCR::mount_next_write_volume() { int retry = 0; bool ask = false, recycle, autochanger; bool do_find = true; int mode; DCR *dcr = this; Dmsg2(150, "Enter mount_next_volume(release=%d) dev=%s\n", dev->must_unload(), dev->print_name()); init_device_wait_timers(dcr); lock_volumes(); /* * Attempt to mount the next volume. If something non-fatal goes * wrong, we come back here to re-try (new op messages, re-read * Volume, ...) */ mount_next_vol: Dmsg1(150, "mount_next_vol retry=%d\n", retry); /* Ignore retry if this is poll request */ if (!dev->poll && retry++ > 4) { /* Last ditch effort before giving up, force operator to respond */ VolCatInfo.Slot = 0; unlock_volumes(); if (!dir_ask_sysop_to_mount_volume(dcr, ST_APPEND)) { Jmsg(jcr, M_FATAL, 0, _("Too many errors trying to mount device %s.\n"), dev->print_name()); goto no_lock_bail_out; } lock_volumes(); Dmsg1(150, "Continue after dir_ask_sysop_to_mount. must_load=%d\n", dev->must_load()); } if (job_canceled(jcr)) { Jmsg(jcr, M_FATAL, 0, _("Job %d canceled.\n"), jcr->JobId); goto bail_out; } recycle = false; if (retry >= 2) { do_find = false; } if (dev->must_unload()) { ask = true; /* ask operator to mount tape */ do_find = true; /* re-find a volume after unload */ } do_unload(); do_swapping(true /*is_writing*/); do_load(true /*is_writing*/); if (do_find && !find_a_volume()) { goto no_lock_bail_out; } if (job_canceled(jcr)) { goto bail_out; } Dmsg3(150, "After find_next_append. Vol=%s Slot=%d Parts=%d\n", VolCatInfo.VolCatName, VolCatInfo.Slot, VolCatInfo.VolCatParts); /* * Get next volume and ready it for append * This code ensures that the device is ready for * writing. We start from the assumption that there * may not be a tape mounted. * * If the device is a file, we create the output * file. If it is a tape, we check the volume name * and move the tape to the end of data. * */ if (autoload_device(dcr, true/*writing*/, NULL) > 0) { autochanger = true; ask = false; } else { autochanger = false; VolCatInfo.Slot = 0; ask = retry >= 2; } Dmsg1(150, "autoload_dev returns %d\n", autochanger); /* * If we autochanged to correct Volume or (we have not just * released the Volume AND we can automount) we go ahead * and read the label. If there is no tape in the drive, * we will fail, recurse and ask the operator the next time. */ if (!dev->must_unload() && dev->is_tape() && dev->has_cap(CAP_AUTOMOUNT)) { Dmsg0(250, "(1)Ask=0\n"); ask = false; /* don't ask SYSOP this time */ } /* Don't ask if not removable */ if (!dev->is_removable()) { Dmsg0(250, "(2)Ask=0\n"); ask = false; } Dmsg2(250, "Ask=%d autochanger=%d\n", ask, autochanger); if (ask) { unlock_volumes(); if (!dir_ask_sysop_to_mount_volume(dcr, ST_APPEND)) { Dmsg0(150, "Error return ask_sysop ...\n"); goto no_lock_bail_out; } lock_volumes(); } if (job_canceled(jcr)) { goto bail_out; } Dmsg3(150, "want vol=%s devvol=%s dev=%s\n", VolumeName, dev->VolHdr.VolumeName, dev->print_name()); if (dev->poll && dev->has_cap(CAP_CLOSEONPOLL)) { dev->close(); } /* Ensure the device is open */ if (dev->has_cap(CAP_STREAM)) { mode = OPEN_WRITE_ONLY; } else { mode = OPEN_READ_WRITE; } /* Try autolabel if enabled */ if (dev->open(dcr, mode) < 0) { try_autolabel(false); /* try to create a new volume label */ } while (dev->open(dcr, mode) < 0) { Dmsg1(150, "open_device failed: ERR=%s\n", dev->bstrerror()); if ((dev->is_file() && dev->is_removable()) || dev->is_dvd()) { bool ok = true; Dmsg0(150, "call scan_dir_for_vol\n"); if (dev->is_dvd()) { if (!dev->mount(0)) { ok = false; } } if (ok && dev->scan_dir_for_volume(dcr)) { if (dev->open(dcr, mode) >= 0) { break; /* got a valid volume */ } } if (ok && dev->is_dvd()) { dev->unmount(0); } } if (try_autolabel(false) == try_read_vol) { break; /* created a new volume label */ } Dmsg0(50, "set_unload\n"); dev->set_unload(); /* force ask sysop */ ask = true; Dmsg0(150, "goto mount_next_vol\n"); goto mount_next_vol; } /* * Now check the volume label to make sure we have the right tape mounted */ read_volume: switch (check_volume_label(ask, autochanger)) { case check_next_vol: Dmsg0(50, "set_unload\n"); dev->set_unload(); /* want a different Volume */ Dmsg0(150, "goto mount_next_vol\n"); goto mount_next_vol; case check_read_vol: goto read_volume; case check_error: goto bail_out; case check_ok: break; } /* * See if we have a fresh tape or a tape with data. * * Note, if the LabelType is PRE_LABEL, it was labeled * but never written. If so, rewrite the label but set as * VOL_LABEL. We rewind and return the label (reconstructed) * in the block so that in the case of a new tape, data can * be appended just after the block label. If we are writing * a second volume, the calling routine will write the label * before writing the overflow block. * * If the tape is marked as Recycle, we rewrite the label. */ recycle = strcmp(dev->VolCatInfo.VolCatStatus, "Recycle") == 0; if (dev->VolHdr.LabelType == PRE_LABEL || recycle) { if (!rewrite_volume_label(dcr, recycle)) { mark_volume_in_error(); goto mount_next_vol; } } else { /* * OK, at this point, we have a valid Bacula label, but * we need to position to the end of the volume, since we are * just now putting it into append mode. */ Dmsg0(200, "Device previously written, moving to end of data\n"); Jmsg(jcr, M_INFO, 0, _("Volume \"%s\" previously written, moving to end of data.\n"), VolumeName); if (!dev->eod(dcr)) { Jmsg(jcr, M_ERROR, 0, _("Unable to position to end of data on device %s: ERR=%s\n"), dev->print_name(), dev->bstrerror()); mark_volume_in_error(); goto mount_next_vol; } if (!is_eod_valid()) { Dmsg0(150, "goto mount_next_vol\n"); goto mount_next_vol; } dev->VolCatInfo.VolCatMounts++; /* Update mounts */ Dmsg1(150, "update volinfo mounts=%d\n", dev->VolCatInfo.VolCatMounts); if (!dir_update_volume_info(dcr, false, false)) { goto bail_out; } /* Return an empty block */ empty_block(block); /* we used it for reading so set for write */ } dev->set_append(); Dmsg1(150, "set APPEND, normal return from mount_next_write_volume. dev=%s\n", dev->print_name()); unlock_volumes(); return true; bail_out: unlock_volumes(); no_lock_bail_out: return false; }
int run_scripts(JCR *jcr, alist *runscripts, const char *label, alist *allowed_script_dirs) { RUNSCRIPT *script; bool runit; int when; Dmsg2(200, "runscript: running all RUNSCRIPT object (%s) JobStatus=%c\n", label, jcr->JobStatus); if (strstr(label, NT_("Before"))) { when = SCRIPT_Before; } else if (bstrcmp(label, NT_("ClientAfterVSS"))) { when = SCRIPT_AfterVSS; } else { when = SCRIPT_After; } if (runscripts == NULL) { Dmsg0(100, "runscript: WARNING RUNSCRIPTS list is NULL\n"); return 0; } foreach_alist(script, runscripts) { Dmsg2(200, "runscript: try to run %s:%s\n", NPRT(script->target), NPRT(script->command)); runit = false; if ((script->when & SCRIPT_Before) && (when & SCRIPT_Before)) { if ((script->on_success && (jcr->JobStatus == JS_Running || jcr->JobStatus == JS_Created)) || (script->on_failure && (job_canceled(jcr) || jcr->JobStatus == JS_Differences))) { Dmsg4(200, "runscript: Run it because SCRIPT_Before (%s,%i,%i,%c)\n", script->command, script->on_success, script->on_failure, jcr->JobStatus ); runit = true; } } if ((script->when & SCRIPT_AfterVSS) && (when & SCRIPT_AfterVSS)) { if ((script->on_success && (jcr->JobStatus == JS_Blocked)) || (script->on_failure && job_canceled(jcr))) { Dmsg4(200, "runscript: Run it because SCRIPT_AfterVSS (%s,%i,%i,%c)\n", script->command, script->on_success, script->on_failure, jcr->JobStatus ); runit = true; } } if ((script->when & SCRIPT_After) && (when & SCRIPT_After)) { if ((script->on_success && (jcr->JobStatus == JS_Terminated || jcr->JobStatus == JS_Warnings)) || (script->on_failure && (job_canceled(jcr) || jcr->JobStatus == JS_Differences))) { Dmsg4(200, "runscript: Run it because SCRIPT_After (%s,%i,%i,%c)\n", script->command, script->on_success, script->on_failure, jcr->JobStatus ); runit = true; } } if (!script->is_local()) { runit = false; } /* * We execute it */ if (runit) { if (!script_dir_allowed(jcr, script, allowed_script_dirs)) { Dmsg1(200, "runscript: Not running script %s because its not in one of the allowed scripts dirs\n", script->command); Jmsg(jcr, M_ERROR, 0, _("Runscript: run %s \"%s\" could not execute, " "not in one of the allowed scripts dirs\n"), label, script->command); jcr->setJobStatus(JS_ErrorTerminated); goto bail_out; } script->run(jcr, label); } }
/** * Request the sysop to create an appendable volume * * Entered with device blocked. * Leaves with device blocked. * * Returns: true on success (operator issues a mount command) * false on failure * Note, must create dev->errmsg on error return. * * On success, dcr->VolumeName and dcr->VolCatInfo contain * information on suggested volume, but this may not be the * same as what is actually mounted. * * When we return with success, the correct tape may or may not * actually be mounted. The calling routine must read it and * verify the label. */ bool dir_ask_sysop_to_create_appendable_volume(DCR *dcr) { int status = W_TIMEOUT; DEVICE *dev = dcr->dev; JCR *jcr = dcr->jcr; bool got_vol = false; if (job_canceled(jcr)) { return false; } Dmsg0(dbglvl, "enter dir_ask_sysop_to_create_appendable_volume\n"); ASSERT(dev->blocked()); for ( ;; ) { if (job_canceled(jcr)) { Mmsg(dev->errmsg, _("Job %s canceled while waiting for mount on Storage Device \"%s\".\n"), jcr->Job, dev->print_name()); Jmsg(jcr, M_INFO, 0, "%s", dev->errmsg); return false; } got_vol = dir_find_next_appendable_volume(dcr); /* get suggested volume */ if (got_vol) { goto get_out; } else { if (status == W_TIMEOUT || status == W_MOUNT) { Mmsg(dev->errmsg, _( "Job %s is waiting. Cannot find any appendable volumes.\n" "Please use the \"label\" command to create a new Volume for:\n" " Storage: %s\n" " Pool: %s\n" " Media type: %s\n"), jcr->Job, dev->print_name(), dcr->pool_name, dcr->media_type); Jmsg(jcr, M_MOUNT, 0, "%s", dev->errmsg); Dmsg1(dbglvl, "%s", dev->errmsg); } } jcr->sendJobStatus(JS_WaitMedia); status = wait_for_sysop(dcr); Dmsg1(dbglvl, "Back from wait_for_sysop status=%d\n", status); if (dev->poll) { Dmsg1(dbglvl, "Poll timeout in create append vol on device %s\n", dev->print_name()); continue; } if (status == W_TIMEOUT) { if (!double_dev_wait_time(dev)) { Mmsg(dev->errmsg, _("Max time exceeded waiting to mount Storage Device %s for Job %s\n"), dev->print_name(), jcr->Job); Jmsg(jcr, M_FATAL, 0, "%s", dev->errmsg); Dmsg1(dbglvl, "Gave up waiting on device %s\n", dev->print_name()); return false; /* exceeded maximum waits */ } continue; } if (status == W_ERROR) { berrno be; Mmsg0(dev->errmsg, _("pthread error in mount_next_volume.\n")); Jmsg(jcr, M_FATAL, 0, "%s", dev->errmsg); return false; } Dmsg1(dbglvl, "Someone woke me for device %s\n", dev->print_name()); } get_out: jcr->sendJobStatus(JS_Running); Dmsg0(dbglvl, "leave dir_ask_sysop_to_mount_create_appendable_volume\n"); return true; }
/********************************************************************* * Acquire device for reading. * The drive should have previously been reserved by calling * reserve_device_for_read(). We read the Volume label from the block and * leave the block pointers just after the label. * * Returns: NULL if failed for any reason * dcr if successful */ bool acquire_device_for_read(DCR *dcr) { DEVICE *dev; JCR *jcr = dcr->jcr; bool ok = false; bool tape_previously_mounted; VOL_LIST *vol; bool try_autochanger = true; int i; int vol_label_status; int retry = 0; Enter(rdbglvl); dev = dcr->dev; dev->Lock_read_acquire(); Dmsg2(rdbglvl, "dcr=%p dev=%p\n", dcr, dcr->dev); Dmsg2(rdbglvl, "MediaType dcr=%s dev=%s\n", dcr->media_type, dev->device->media_type); dev->dblock(BST_DOING_ACQUIRE); if (dev->num_writers > 0) { Jmsg2(jcr, M_FATAL, 0, _("Acquire read: num_writers=%d not zero. Job %d canceled.\n"), dev->num_writers, jcr->JobId); goto get_out; } /* Find next Volume, if any */ vol = jcr->VolList; if (!vol) { char ed1[50]; Jmsg(jcr, M_FATAL, 0, _("No volumes specified for reading. Job %s canceled.\n"), edit_int64(jcr->JobId, ed1)); goto get_out; } jcr->CurReadVolume++; for (i=1; i<jcr->CurReadVolume; i++) { vol = vol->next; } if (!vol) { Jmsg(jcr, M_FATAL, 0, _("Logic error: no next volume to read. Numvol=%d Curvol=%d\n"), jcr->NumReadVolumes, jcr->CurReadVolume); goto get_out; /* should not happen */ } set_dcr_from_vol(dcr, vol); Dmsg2(rdbglvl, "Want Vol=%s Slot=%d\n", vol->VolumeName, vol->Slot); /* * If the MediaType requested for this volume is not the * same as the current drive, we attempt to find the same * device that was used to write the orginal volume. If * found, we switch to using that device. * * N.B. A lot of routines rely on the dcr pointer not changing * read_records.c even has multiple dcrs cached, so we take care * here to release all important parts of the dcr and re-acquire * them such as the block pointer (size may change), but we do * not release the dcr. */ Dmsg2(rdbglvl, "MediaType dcr=%s dev=%s\n", dcr->media_type, dev->device->media_type); if (dcr->media_type[0] && !bstrcmp(dcr->media_type, dev->device->media_type)) { RCTX rctx; DIRSTORE *store; int status; Jmsg3(jcr, M_INFO, 0, _("Changing read device. Want Media Type=\"%s\" have=\"%s\"\n" " device=%s\n"), dcr->media_type, dev->device->media_type, dev->print_name()); Dmsg3(rdbglvl, "Changing read device. Want Media Type=\"%s\" have=\"%s\"\n" " device=%s\n", dcr->media_type, dev->device->media_type, dev->print_name()); dev->dunblock(DEV_UNLOCKED); lock_reservations(); memset(&rctx, 0, sizeof(RCTX)); rctx.jcr = jcr; jcr->read_dcr = dcr; jcr->reserve_msgs = New(alist(10, not_owned_by_alist)); rctx.any_drive = true; rctx.device_name = vol->device; store = new DIRSTORE; memset(store, 0, sizeof(DIRSTORE)); store->name[0] = 0; /* No dir name */ bstrncpy(store->media_type, vol->MediaType, sizeof(store->media_type)); bstrncpy(store->pool_name, dcr->pool_name, sizeof(store->pool_name)); bstrncpy(store->pool_type, dcr->pool_type, sizeof(store->pool_type)); store->append = false; rctx.store = store; clean_device(dcr); /* clean up the dcr */ /* * Search for a new device */ status = search_res_for_device(rctx); release_reserve_messages(jcr); /* release queued messages */ unlock_reservations(); if (status == 1) { /* found new device to use */ /* * Switching devices, so acquire lock on new device, * then release the old one. */ dcr->dev->Lock_read_acquire(); /* lock new one */ dev->Unlock_read_acquire(); /* release old one */ dev = dcr->dev; /* get new device pointer */ dev->dblock(BST_DOING_ACQUIRE); dcr->VolumeName[0] = 0; Jmsg(jcr, M_INFO, 0, _("Media Type change. New read device %s chosen.\n"), dev->print_name()); Dmsg1(50, "Media Type change. New read device %s chosen.\n", dev->print_name()); bstrncpy(dcr->VolumeName, vol->VolumeName, sizeof(dcr->VolumeName)); dcr->setVolCatName(vol->VolumeName); bstrncpy(dcr->media_type, vol->MediaType, sizeof(dcr->media_type)); dcr->VolCatInfo.Slot = vol->Slot; dcr->VolCatInfo.InChanger = vol->Slot > 0; bstrncpy(dcr->pool_name, store->pool_name, sizeof(dcr->pool_name)); bstrncpy(dcr->pool_type, store->pool_type, sizeof(dcr->pool_type)); } else { /* error */ Jmsg1(jcr, M_FATAL, 0, _("No suitable device found to read Volume \"%s\"\n"), vol->VolumeName); Dmsg1(rdbglvl, "No suitable device found to read Volume \"%s\"\n", vol->VolumeName); goto get_out; } } Dmsg2(rdbglvl, "MediaType dcr=%s dev=%s\n", dcr->media_type, dev->device->media_type); dev->clear_unload(); if (dev->vol && dev->vol->is_swapping()) { dev->vol->set_slot(vol->Slot); Dmsg3(rdbglvl, "swapping: slot=%d Vol=%s dev=%s\n", dev->vol->get_slot(), dev->vol->vol_name, dev->print_name()); } init_device_wait_timers(dcr); tape_previously_mounted = dev->can_read() || dev->can_append() || dev->is_labeled(); // tape_initially_mounted = tape_previously_mounted; /* Volume info is always needed because of VolParts */ Dmsg1(rdbglvl, "dir_get_volume_info vol=%s\n", dcr->VolumeName); if (!dcr->dir_get_volume_info(GET_VOL_INFO_FOR_READ)) { Dmsg2(rdbglvl, "dir_get_vol_info failed for vol=%s: %s\n", dcr->VolumeName, jcr->errmsg); Jmsg1(jcr, M_WARNING, 0, "Read acquire: %s", jcr->errmsg); } dev->set_load(); /* set to load volume */ for ( ;; ) { /* If not polling limit retries */ if (!dev->poll && retry++ > 10) { break; } dev->clear_labeled(); /* force reread of label */ if (job_canceled(jcr)) { char ed1[50]; Mmsg1(dev->errmsg, _("Job %s canceled.\n"), edit_int64(jcr->JobId, ed1)); Jmsg(jcr, M_INFO, 0, dev->errmsg); goto get_out; /* error return */ } dcr->do_unload(); dcr->do_swapping(false/*!is_writing*/); dcr->do_load(false /*!is_writing*/); set_dcr_from_vol(dcr, vol); /* refresh dcr with desired volume info */ /* * This code ensures that the device is ready for * reading. If it is a file, it opens it. * If it is a tape, it checks the volume name */ Dmsg1(rdbglvl, "stored: open vol=%s\n", dcr->VolumeName); if (!dev->open(dcr, OPEN_READ_ONLY)) { if (!dev->poll) { Jmsg3(jcr, M_WARNING, 0, _("Read open device %s Volume \"%s\" failed: ERR=%s\n"), dev->print_name(), dcr->VolumeName, dev->bstrerror()); } goto default_path; } Dmsg1(rdbglvl, "opened dev %s OK\n", dev->print_name()); /* Read Volume Label */ Dmsg0(rdbglvl, "calling read-vol-label\n"); vol_label_status = read_dev_volume_label(dcr); switch (vol_label_status) { case VOL_OK: Dmsg0(rdbglvl, "Got correct volume.\n"); ok = true; dev->VolCatInfo = dcr->VolCatInfo; /* structure assignment */ break; /* got it */ case VOL_IO_ERROR: Dmsg0(rdbglvl, "IO Error\n"); /* * Send error message generated by read_dev_volume_label() * only we really had a tape mounted. This supresses superfluous * error messages when nothing is mounted. */ if (tape_previously_mounted) { Jmsg(jcr, M_WARNING, 0, "Read acquire: %s", jcr->errmsg); } goto default_path; case VOL_NAME_ERROR: Dmsg3(rdbglvl, "Vol name=%s want=%s drv=%s.\n", dev->VolHdr.VolumeName, dcr->VolumeName, dev->print_name()); if (dev->is_volume_to_unload()) { goto default_path; } dev->set_unload(); /* force unload of unwanted tape */ if (!unload_autochanger(dcr, -1)) { /* at least free the device so we can re-open with correct volume */ dev->close(dcr); free_volume(dev); } dev->set_load(); /* Fall through */ default: Jmsg1(jcr, M_WARNING, 0, "Read acquire: %s", jcr->errmsg); default_path: Dmsg0(rdbglvl, "default path\n"); tape_previously_mounted = true; /* * If the device requires mount, close it, so the device can be ejected. */ if (dev->requires_mount()) { dev->close(dcr); free_volume(dev); } /* Call autochanger only once unless ask_sysop called */ if (try_autochanger) { int status; Dmsg2(rdbglvl, "calling autoload Vol=%s Slot=%d\n", dcr->VolumeName, dcr->VolCatInfo.Slot); status = autoload_device(dcr, 0, NULL); if (status > 0) { try_autochanger = false; continue; /* try reading volume mounted */ } } /* Mount a specific volume and no other */ Dmsg0(rdbglvl, "calling dir_ask_sysop\n"); if (!dcr->dir_ask_sysop_to_mount_volume(ST_READREADY)) { goto get_out; /* error return */ } /* Volume info is always needed because of VolParts */ Dmsg1(150, "dir_get_volume_info vol=%s\n", dcr->VolumeName); if (!dcr->dir_get_volume_info(GET_VOL_INFO_FOR_READ)) { Dmsg2(150, "dir_get_vol_info failed for vol=%s: %s\n", dcr->VolumeName, jcr->errmsg); Jmsg1(jcr, M_WARNING, 0, "Read acquire: %s", jcr->errmsg); } dev->set_load(); /* set to load volume */ try_autochanger = true; /* permit trying the autochanger again */ continue; /* try reading again */ } /* end switch */ break; } /* end for loop */ if (!ok) { Jmsg1(jcr, M_FATAL, 0, _("Too many errors trying to mount device %s for reading.\n"), dev->print_name()); goto get_out; } dev->clear_append(); dev->set_read(); jcr->sendJobStatus(JS_Running); Jmsg(jcr, M_INFO, 0, _("Ready to read from volume \"%s\" on device %s.\n"), dcr->VolumeName, dev->print_name()); get_out: dev->Lock(); dcr->clear_reserved(); /* * Normally we are blocked, but in at least one error case above * we are not blocked because we unsuccessfully tried changing * devices. */ if (dev->is_blocked()) { dev->dunblock(DEV_LOCKED); } else { dev->Unlock(); /* dunblock() unlock the device too */ } Dmsg2(rdbglvl, "dcr=%p dev=%p\n", dcr, dcr->dev); Dmsg2(rdbglvl, "MediaType dcr=%s dev=%s\n", dcr->media_type, dev->device->media_type); dev->Unlock_read_acquire(); Leave(rdbglvl); return ok; }
/* * First prove our identity to the Storage daemon, then * make him prove his identity. */ int authenticate_storagedaemon(JCR *jcr) { BSOCK *sd = jcr->store_bsock; int tls_local_need = BNET_TLS_NONE; int tls_remote_need = BNET_TLS_NONE; int compatible = true; bool auth_success = false; btimer_t *tid = start_bsock_timer(sd, AUTH_TIMEOUT); /* TLS Requirement */ if (have_tls && me->tls_enable) { if (me->tls_require) { tls_local_need = BNET_TLS_REQUIRED; } else { tls_local_need = BNET_TLS_OK; } } if (me->tls_authenticate) { tls_local_need = BNET_TLS_REQUIRED; } if (job_canceled(jcr)) { auth_success = false; /* force quick exit */ goto auth_fatal; } /* Respond to SD challenge */ auth_success = cram_md5_respond(sd, jcr->sd_auth_key, &tls_remote_need, &compatible); if (job_canceled(jcr)) { auth_success = false; /* force quick exit */ goto auth_fatal; } if (!auth_success) { Dmsg1(dbglvl, "cram_respond failed for %s\n", sd->who()); } else { /* Now challenge him */ auth_success = cram_md5_challenge(sd, jcr->sd_auth_key, tls_local_need, compatible); if (!auth_success) { Dmsg1(dbglvl, "cram_challenge failed for %s\n", sd->who()); } } if (!auth_success) { Jmsg(jcr, M_FATAL, 0, _("Authorization key rejected by Storage daemon.\n" "Please see " MANUAL_AUTH_URL " for help.\n")); goto auth_fatal; } /* Verify that the remote host is willing to meet our TLS requirements */ if (tls_remote_need < tls_local_need && tls_local_need != BNET_TLS_OK && tls_remote_need != BNET_TLS_OK) { Jmsg(jcr, M_FATAL, 0, _("Authorization problem: Remote server did not" " advertize required TLS support.\n")); Dmsg2(dbglvl, "remote_need=%d local_need=%d\n", tls_remote_need, tls_local_need); auth_success = false; goto auth_fatal; } /* Verify that we are willing to meet the remote host's requirements */ if (tls_remote_need > tls_local_need && tls_local_need != BNET_TLS_OK && tls_remote_need != BNET_TLS_OK) { Jmsg(jcr, M_FATAL, 0, _("Authorization problem: Remote server requires TLS.\n")); Dmsg2(dbglvl, "remote_need=%d local_need=%d\n", tls_remote_need, tls_local_need); auth_success = false; goto auth_fatal; } if (tls_local_need >= BNET_TLS_OK && tls_remote_need >= BNET_TLS_OK) { /* Engage TLS! Full Speed Ahead! */ if (!bnet_tls_client(me->tls_ctx, sd, NULL)) { Jmsg(jcr, M_FATAL, 0, _("TLS negotiation failed.\n")); auth_success = false; goto auth_fatal; } if (me->tls_authenticate) { /* tls authentication only? */ sd->free_tls(); /* yes, shutdown tls */ } } auth_fatal: /* Destroy session key */ memset(jcr->sd_auth_key, 0, strlen(jcr->sd_auth_key)); stop_bsock_timer(tid); /* Single thread all failures to avoid DOS */ if (!auth_success) { P(mutex); bmicrosleep(6, 0); V(mutex); } return auth_success; }
/* * NB! This routine locks the device, but if committing will * not unlock it. If not committing, it will be unlocked. */ static bool despool_data(DCR *dcr, bool commit) { DEVICE *rdev; DCR *rdcr; bool ok = true; DEV_BLOCK *block; JCR *jcr = dcr->jcr; int status; char ec1[50]; BSOCK *dir = jcr->dir_bsock; Dmsg0(100, "Despooling data\n"); if (jcr->dcr->job_spool_size == 0) { Jmsg(jcr, M_WARNING, 0, _("Despooling zero bytes. Your disk is probably FULL!\n")); } /* * Commit means that the job is done, so we commit, otherwise, we * are despooling because of user spool size max or some error * (e.g. filesystem full). */ if (commit) { Jmsg(jcr, M_INFO, 0, _("Committing spooled data to Volume \"%s\". Despooling %s bytes ...\n"), jcr->dcr->VolumeName, edit_uint64_with_commas(jcr->dcr->job_spool_size, ec1)); jcr->setJobStatus(JS_DataCommitting); } else { Jmsg(jcr, M_INFO, 0, _("Writing spooled data to Volume. Despooling %s bytes ...\n"), edit_uint64_with_commas(jcr->dcr->job_spool_size, ec1)); jcr->setJobStatus(JS_DataDespooling); } jcr->sendJobStatus(JS_DataDespooling); dcr->despool_wait = true; dcr->spooling = false; /* * We work with device blocked, but not locked so that other threads * e.g. reservations can lock the device structure. */ dcr->dblock(BST_DESPOOLING); dcr->despool_wait = false; dcr->despooling = true; /* * This is really quite kludgy and should be fixed some time. * We create a dev structure to read from the spool file * in rdev and rdcr. */ rdev = (DEVICE *)malloc(sizeof(DEVICE)); memset(rdev, 0, sizeof(DEVICE)); rdev->dev_name = get_memory(strlen(spool_name)+1); bstrncpy(rdev->dev_name, spool_name, sizeof_pool_memory(rdev->dev_name)); rdev->errmsg = get_pool_memory(PM_EMSG); *rdev->errmsg = 0; rdev->max_block_size = dcr->dev->max_block_size; rdev->min_block_size = dcr->dev->min_block_size; rdev->device = dcr->dev->device; rdcr = dcr->get_new_spooling_dcr(); setup_new_dcr_device(jcr, rdcr, rdev, NULL); rdcr->spool_fd = dcr->spool_fd; block = dcr->block; /* save block */ dcr->block = rdcr->block; /* make read and write block the same */ Dmsg1(800, "read/write block size = %d\n", block->buf_len); lseek(rdcr->spool_fd, 0, SEEK_SET); /* rewind */ #if defined(HAVE_POSIX_FADVISE) && defined(POSIX_FADV_WILLNEED) posix_fadvise(rdcr->spool_fd, 0, 0, POSIX_FADV_WILLNEED); #endif /* Add run time, to get current wait time */ int32_t despool_start = time(NULL) - jcr->run_time; set_new_file_parameters(dcr); while (ok) { if (job_canceled(jcr)) { ok = false; break; } status = read_block_from_spool_file(rdcr); if (status == RB_EOT) { break; } else if (status == RB_ERROR) { ok = false; break; } ok = dcr->write_block_to_device(); if (!ok) { Jmsg2(jcr, M_FATAL, 0, _("Fatal append error on device %s: ERR=%s\n"), dcr->dev->print_name(), dcr->dev->bstrerror()); Dmsg2(000, "Fatal append error on device %s: ERR=%s\n", dcr->dev->print_name(), dcr->dev->bstrerror()); /* Force in case Incomplete set */ jcr->forceJobStatus(JS_FatalError); } Dmsg3(800, "Write block ok=%d FI=%d LI=%d\n", ok, block->FirstIndex, block->LastIndex); } /* * If this Job is incomplete, we need to backup the FileIndex * to the last correctly saved file so that the JobMedia * LastIndex is correct. */ if (jcr->is_JobStatus(JS_Incomplete)) { dcr->VolLastIndex = dir->get_FileIndex(); Dmsg1(100, "======= Set FI=%ld\n", dir->get_FileIndex()); } if (!dcr->dir_create_jobmedia_record(false)) { Jmsg2(jcr, M_FATAL, 0, _("Could not create JobMedia record for Volume=\"%s\" Job=%s\n"), dcr->getVolCatName(), jcr->Job); jcr->forceJobStatus(JS_FatalError); /* override any Incomplete */ } /* Set new file/block parameters for current dcr */ set_new_file_parameters(dcr); /* * Subtracting run_time give us elapsed time - wait_time since * we started despooling. Note, don't use time_t as it is 32 or 64 * bits depending on the OS and doesn't edit with %d */ int32_t despool_elapsed = time(NULL) - despool_start - jcr->run_time; if (despool_elapsed <= 0) { despool_elapsed = 1; } Jmsg(jcr, M_INFO, 0, _("Despooling elapsed time = %02d:%02d:%02d, Transfer rate = %s Bytes/second\n"), despool_elapsed / 3600, despool_elapsed % 3600 / 60, despool_elapsed % 60, edit_uint64_with_suffix(jcr->dcr->job_spool_size / despool_elapsed, ec1)); dcr->block = block; /* reset block */ lseek(rdcr->spool_fd, 0, SEEK_SET); /* rewind */ if (ftruncate(rdcr->spool_fd, 0) != 0) { berrno be; Jmsg(jcr, M_ERROR, 0, _("Ftruncate spool file failed: ERR=%s\n"), be.bstrerror()); /* Note, try continuing despite ftruncate problem */ } P(mutex); if (spool_stats.data_size < dcr->job_spool_size) { spool_stats.data_size = 0; } else { spool_stats.data_size -= dcr->job_spool_size; } V(mutex); P(dcr->dev->spool_mutex); dcr->dev->spool_size -= dcr->job_spool_size; dcr->job_spool_size = 0; /* zap size in input dcr */ V(dcr->dev->spool_mutex); free_memory(rdev->dev_name); free_pool_memory(rdev->errmsg); /* Be careful to NULL the jcr and free rdev after free_dcr() */ rdcr->jcr = NULL; rdcr->set_dev(NULL); free_dcr(rdcr); free(rdev); dcr->spooling = true; /* turn on spooling again */ dcr->despooling = false; /* * Note, if committing we leave the device blocked. It will be removed in * release_device(); */ if (!commit) { dcr->dev->dunblock(); } jcr->sendJobStatus(JS_Running); return ok; }
/* * See who is connecting and lookup the authentication information. * First make him prove his identity and then prove our identity to the Remote daemon. */ static inline bool two_way_authenticate(int rcode, BSOCK *bs, JCR* jcr) { POOLMEM *dirname = get_pool_memory(PM_MESSAGE); DIRRES *director = NULL; int tls_local_need = BNET_TLS_NONE; int tls_remote_need = BNET_TLS_NONE; bool compatible = true; /* Want md5 compatible DIR */ bool auth_success = false; alist *verify_list = NULL; btimer_t *tid = NULL; if (rcode != R_DIRECTOR) { Dmsg1(dbglvl, "I only authenticate directors, not %d\n", rcode); Jmsg1(jcr, M_FATAL, 0, _("I only authenticate directors, not %d\n"), rcode); goto auth_fatal; } if (bs->msglen < 25 || bs->msglen > 500) { Dmsg2(dbglvl, "Bad Hello command from Director at %s. Len=%d.\n", bs->who(), bs->msglen); char addr[64]; char *who = bnet_get_peer(bs, addr, sizeof(addr)) ? bs->who() : addr; Jmsg2(jcr, M_FATAL, 0, _("Bad Hello command from Director at %s. Len=%d.\n"), who, bs->msglen); goto auth_fatal; } dirname = check_pool_memory_size(dirname, bs->msglen); if (sscanf(bs->msg, "Hello Director %s calling", dirname) != 1) { char addr[64]; char *who = bnet_get_peer(bs, addr, sizeof(addr)) ? bs->who() : addr; bs->msg[100] = 0; Dmsg2(dbglvl, "Bad Hello command from Director at %s: %s\n", bs->who(), bs->msg); Jmsg2(jcr, M_FATAL, 0, _("Bad Hello command from Director at %s: %s\n"), who, bs->msg); goto auth_fatal; } unbash_spaces(dirname); foreach_res(director, R_DIRECTOR) { if (bstrcmp(director->hdr.name, dirname)) break; } if (!director) { char addr[64]; char *who = bnet_get_peer(bs, addr, sizeof(addr)) ? bs->who() : addr; Jmsg2(jcr, M_FATAL, 0, _("Connection from unknown Director %s at %s rejected.\n"), dirname, who); goto auth_fatal; } if (have_tls) { /* * TLS Requirement */ if (director->tls_enable) { if (director->tls_require) { tls_local_need = BNET_TLS_REQUIRED; } else { tls_local_need = BNET_TLS_OK; } } if (director->tls_authenticate) { tls_local_need = BNET_TLS_REQUIRED; } if (director->tls_verify_peer) { verify_list = director->tls_allowed_cns; } } /* * Timeout Hello after 10 min */ tid = start_bsock_timer(bs, AUTH_TIMEOUT); /* * Sanity check. */ ASSERT(director->password.encoding == p_encoding_md5); /* * Challenge the director */ auth_success = cram_md5_challenge(bs, director->password.value, tls_local_need, compatible); if (job_canceled(jcr)) { auth_success = false; goto auth_fatal; /* quick exit */ } if (auth_success) { auth_success = cram_md5_respond(bs, director->password.value, &tls_remote_need, &compatible); if (!auth_success) { char addr[64]; char *who = bnet_get_peer(bs, addr, sizeof(addr)) ? bs->who() : addr; Dmsg1(dbglvl, "cram_get_auth failed for %s\n", who); } } else { char addr[64]; char *who = bnet_get_peer(bs, addr, sizeof(addr)) ? bs->who() : addr; Dmsg1(dbglvl, "cram_auth failed for %s\n", who); } if (!auth_success) { Emsg1(M_FATAL, 0, _("Incorrect password given by Director at %s.\n"), bs->who()); goto auth_fatal; } /* * Verify that the remote host is willing to meet our TLS requirements */ if (tls_remote_need < tls_local_need && tls_local_need != BNET_TLS_OK && tls_remote_need != BNET_TLS_OK) { Jmsg0(jcr, M_FATAL, 0, _("Authorization problem: Remote server did not" " advertize required TLS support.\n")); Dmsg2(dbglvl, "remote_need=%d local_need=%d\n", tls_remote_need, tls_local_need); auth_success = false; goto auth_fatal; } /* * Verify that we are willing to meet the remote host's requirements */ if (tls_remote_need > tls_local_need && tls_local_need != BNET_TLS_OK && tls_remote_need != BNET_TLS_OK) { Jmsg0(jcr, M_FATAL, 0, _("Authorization problem: Remote server requires TLS.\n")); Dmsg2(dbglvl, "remote_need=%d local_need=%d\n", tls_remote_need, tls_local_need); auth_success = false; goto auth_fatal; } if (tls_local_need >= BNET_TLS_OK && tls_remote_need >= BNET_TLS_OK) { /* * Engage TLS! Full Speed Ahead! */ if (!bnet_tls_server(director->tls_ctx, bs, verify_list)) { Jmsg0(jcr, M_FATAL, 0, _("TLS negotiation failed.\n")); auth_success = false; goto auth_fatal; } if (director->tls_authenticate) { /* authentication only? */ bs->free_tls(); /* shutodown tls */ } } auth_fatal: if (tid) { stop_bsock_timer(tid); tid = NULL; } free_pool_memory(dirname); jcr->director = director; /* * Single thread all failures to avoid DOS */ if (!auth_success) { P(mutex); bmicrosleep(6, 0); V(mutex); } return auth_success; }
/* * Verify attributes of the requested files on the Volume * */ void do_verify_volume(JCR *jcr) { BSOCK *sd, *dir; POOLMEM *fname; /* original file name */ POOLMEM *lname; /* link name */ int32_t stream; uint32_t size; uint32_t VolSessionId, VolSessionTime, file_index; uint32_t record_file_index; char digest[BASE64_SIZE(CRYPTO_DIGEST_MAX_SIZE)]; int type, stat; sd = jcr->store_bsock; if (!sd) { Jmsg(jcr, M_FATAL, 0, _("Storage command not issued before Verify.\n")); jcr->setJobStatus(JS_FatalError); return; } dir = jcr->dir_bsock; jcr->setJobStatus(JS_Running); LockRes(); CLIENT *client = (CLIENT *)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_FatalError); return; } jcr->buf_size = sd->msglen; fname = get_pool_memory(PM_FNAME); lname = get_pool_memory(PM_FNAME); /* * Get a record from the Storage daemon */ while (bget_msg(sd) >= 0 && !job_canceled(jcr)) { /* * First we expect a Stream Record Header */ if (sscanf(sd->msg, rec_header, &VolSessionId, &VolSessionTime, &file_index, &stream, &size) != 5) { Jmsg1(jcr, M_FATAL, 0, _("Record header scan error: %s\n"), sd->msg); goto bail_out; } Dmsg3(30, "Got hdr: FilInx=%d Stream=%d size=%d.\n", file_index, stream, size); /* * Now we expect the Stream Data */ if (bget_msg(sd) < 0) { Jmsg1(jcr, M_FATAL, 0, _("Data record error. ERR=%s\n"), sd->bstrerror()); goto bail_out; } if (size != ((uint32_t)sd->msglen)) { Jmsg2(jcr, M_FATAL, 0, _("Actual data size %d not same as header %d\n"), sd->msglen, size); goto bail_out; } Dmsg2(30, "Got stream data %s, len=%d\n", stream_to_ascii(stream), sd->msglen); /* File Attributes stream */ switch (stream) { case STREAM_UNIX_ATTRIBUTES: case STREAM_UNIX_ATTRIBUTES_EX: char *ap, *lp, *fp; Dmsg0(400, "Stream=Unix Attributes.\n"); if ((int)sizeof_pool_memory(fname) < sd->msglen) { fname = realloc_pool_memory(fname, sd->msglen + 1); } if ((int)sizeof_pool_memory(lname) < sd->msglen) { lname = realloc_pool_memory(lname, sd->msglen + 1); } *fname = 0; *lname = 0; /* * An Attributes record consists of: * File_index * Type (FT_types) * Filename * Attributes * Link name (if file linked i.e. FT_LNK) * Extended Attributes (if Win32) */ if (sscanf(sd->msg, "%d %d", &record_file_index, &type) != 2) { Jmsg(jcr, M_FATAL, 0, _("Error scanning record header: %s\n"), sd->msg); Dmsg0(0, "\nError scanning header\n"); goto bail_out; } Dmsg2(30, "Got Attr: FilInx=%d type=%d\n", record_file_index, type); ap = sd->msg; while (*ap++ != ' ') /* skip record file index */ ; while (*ap++ != ' ') /* skip type */ ; /* Save filename and position to attributes */ fp = fname; while (*ap != 0) { *fp++ = *ap++; /* copy filename to fname */ } *fp = *ap++; /* terminate filename & point to attribs */ Dmsg2(100, "File=%s Attr=%s\n", fname, ap); /* Skip to Link name */ if (type == FT_LNK || type == FT_LNKSAVED) { lp = ap; while (*lp++ != 0) { ; } pm_strcat(lname, lp); /* "save" link name */ } else { *lname = 0; } jcr->lock(); jcr->JobFiles++; jcr->num_files_examined++; pm_strcpy(jcr->last_fname, fname); /* last file examined */ jcr->unlock(); /* * Send file attributes to Director * File_index * Stream * Verify Options * Filename (full path) * Encoded attributes * Link name (if type==FT_LNK) * For a directory, link is the same as fname, but with trailing * slash. For a linked file, link is the link. */ /* Send file attributes to Director */ Dmsg2(200, "send ATTR inx=%d fname=%s\n", jcr->JobFiles, fname); if (type == FT_LNK || type == FT_LNKSAVED) { stat = dir->fsend("%d %d %s %s%c%s%c%s%c", jcr->JobFiles, STREAM_UNIX_ATTRIBUTES, "pinsug5", fname, 0, ap, 0, lname, 0); /* for a deleted record, we set fileindex=0 */ } else if (type == FT_DELETED) { stat = dir->fsend("%d %d %s %s%c%s%c%c", 0, STREAM_UNIX_ATTRIBUTES, "pinsug5", fname, 0, ap, 0, 0); } else { stat = dir->fsend("%d %d %s %s%c%s%c%c", jcr->JobFiles, STREAM_UNIX_ATTRIBUTES, "pinsug5", fname, 0, ap, 0, 0); } Dmsg2(200, "bfiled>bdird: attribs len=%d: msg=%s\n", dir->msglen, dir->msg); if (!stat) { Jmsg(jcr, M_FATAL, 0, _("Network error in send to Director: ERR=%s\n"), dir->bstrerror()); goto bail_out; } break; case STREAM_MD5_DIGEST: bin_to_base64(digest, sizeof(digest), (char *)sd->msg, CRYPTO_DIGEST_MD5_SIZE, true); Dmsg2(400, "send inx=%d MD5=%s\n", jcr->JobFiles, digest); dir->fsend("%d %d %s *MD5-%d*", jcr->JobFiles, STREAM_MD5_DIGEST, digest, jcr->JobFiles); Dmsg2(20, "bfiled>bdird: MD5 len=%d: msg=%s\n", dir->msglen, dir->msg); break; case STREAM_SHA1_DIGEST: bin_to_base64(digest, sizeof(digest), (char *)sd->msg, CRYPTO_DIGEST_SHA1_SIZE, true); Dmsg2(400, "send inx=%d SHA1=%s\n", jcr->JobFiles, digest); dir->fsend("%d %d %s *SHA1-%d*", jcr->JobFiles, STREAM_SHA1_DIGEST, digest, jcr->JobFiles); Dmsg2(20, "bfiled>bdird: SHA1 len=%d: msg=%s\n", dir->msglen, dir->msg); break; case STREAM_SHA256_DIGEST: bin_to_base64(digest, sizeof(digest), (char *)sd->msg, CRYPTO_DIGEST_SHA256_SIZE, true); Dmsg2(400, "send inx=%d SHA256=%s\n", jcr->JobFiles, digest); dir->fsend("%d %d %s *SHA256-%d*", jcr->JobFiles, STREAM_SHA256_DIGEST, digest, jcr->JobFiles); Dmsg2(20, "bfiled>bdird: SHA256 len=%d: msg=%s\n", dir->msglen, dir->msg); break; case STREAM_SHA512_DIGEST: bin_to_base64(digest, sizeof(digest), (char *)sd->msg, CRYPTO_DIGEST_SHA512_SIZE, true); Dmsg2(400, "send inx=%d SHA512=%s\n", jcr->JobFiles, digest); dir->fsend("%d %d %s *SHA512-%d*", jcr->JobFiles, STREAM_SHA512_DIGEST, digest, jcr->JobFiles); Dmsg2(20, "bfiled>bdird: SHA512 len=%d: msg=%s\n", dir->msglen, dir->msg); break; /* * Restore stream object is counted, but not restored here */ case STREAM_RESTORE_OBJECT: jcr->lock(); jcr->JobFiles++; jcr->num_files_examined++; jcr->unlock(); break; /* Ignore everything else */ default: break; } /* end switch */ } /* end while bnet_get */ jcr->setJobStatus(JS_Terminated); goto ok_out; bail_out: jcr->setJobStatus(JS_ErrorTerminated); ok_out: if (jcr->compress_buf) { free(jcr->compress_buf); jcr->compress_buf = NULL; } free_pool_memory(fname); free_pool_memory(lname); Dmsg2(050, "End Verify-Vol. Files=%d Bytes=%" lld "\n", jcr->JobFiles, jcr->JobBytes); }
int DCR::check_volume_label(bool &ask, bool &autochanger) { int vol_label_status; /* * If we are writing to a stream device, ASSUME the volume label * is correct. */ if (dev->has_cap(CAP_STREAM)) { vol_label_status = VOL_OK; create_volume_label(dev, VolumeName, "Default", false /* not DVD */); dev->VolHdr.LabelType = PRE_LABEL; } else { vol_label_status = read_dev_volume_label(this); } if (job_canceled(jcr)) { goto check_bail_out; } Dmsg2(150, "Want dirVol=%s dirStat=%s\n", VolumeName, VolCatInfo.VolCatStatus); /* * At this point, dev->VolCatInfo has what is in the drive, if anything, * and dcr->VolCatInfo has what the Director wants. */ switch (vol_label_status) { case VOL_OK: Dmsg1(150, "Vol OK name=%s\n", dev->VolHdr.VolumeName); dev->VolCatInfo = VolCatInfo; /* structure assignment */ break; /* got a Volume */ case VOL_NAME_ERROR: VOLUME_CAT_INFO dcrVolCatInfo, devVolCatInfo; char saveVolumeName[MAX_NAME_LENGTH]; Dmsg2(150, "Vol NAME Error Have=%s, want=%s\n", dev->VolHdr.VolumeName, VolumeName); if (dev->is_volume_to_unload()) { ask = true; goto check_next_volume; } /* If not removable, Volume is broken */ if (!dev->is_removable()) { Jmsg(jcr, M_WARNING, 0, _("Volume \"%s\" not on device %s.\n"), VolumeName, dev->print_name()); mark_volume_in_error(); goto check_next_volume; } /* * OK, we got a different volume mounted. First save the * requested Volume info (dcr) structure, then query if * this volume is really OK. If not, put back the desired * volume name, mark it not in changer and continue. */ dcrVolCatInfo = VolCatInfo; /* structure assignment */ devVolCatInfo = dev->VolCatInfo; /* structure assignment */ /* Check if this is a valid Volume in the pool */ bstrncpy(saveVolumeName, VolumeName, sizeof(saveVolumeName)); bstrncpy(VolumeName, dev->VolHdr.VolumeName, sizeof(VolumeName)); if (!dir_get_volume_info(this, GET_VOL_INFO_FOR_WRITE)) { POOL_MEM vol_info_msg; pm_strcpy(vol_info_msg, jcr->dir_bsock->msg); /* save error message */ /* Restore desired volume name, note device info out of sync */ /* This gets the info regardless of the Pool */ bstrncpy(VolumeName, dev->VolHdr.VolumeName, sizeof(VolumeName)); if (autochanger && !dir_get_volume_info(this, GET_VOL_INFO_FOR_READ)) { /* * If we get here, we know we cannot write on the Volume, * and we know that we cannot read it either, so it * is not in the autochanger. */ mark_volume_not_inchanger(); } dev->VolCatInfo = devVolCatInfo; /* structure assignment */ dev->set_unload(); /* unload this volume */ Jmsg(jcr, M_WARNING, 0, _("Director wanted Volume \"%s\".\n" " Current Volume \"%s\" not acceptable because:\n" " %s"), dcrVolCatInfo.VolCatName, dev->VolHdr.VolumeName, vol_info_msg.c_str()); ask = true; /* Restore saved DCR before continuing */ bstrncpy(VolumeName, saveVolumeName, sizeof(VolumeName)); VolCatInfo = dcrVolCatInfo; /* structure assignment */ goto check_next_volume; } /* * This was not the volume we expected, but it is OK with * the Director, so use it. */ Dmsg1(150, "Got new Volume name=%s\n", VolumeName); dev->VolCatInfo = VolCatInfo; /* structure assignment */ Dmsg1(100, "Call reserve_volume=%s\n", dev->VolHdr.VolumeName); if (reserve_volume(this, dev->VolHdr.VolumeName) == NULL) { Jmsg2(jcr, M_WARNING, 0, _("Could not reserve volume %s on %s\n"), dev->VolHdr.VolumeName, dev->print_name()); ask = true; goto check_next_volume; } break; /* got a Volume */ /* * At this point, we assume we have a blank tape mounted. */ case VOL_IO_ERROR: if (dev->is_dvd()) { Jmsg(jcr, M_FATAL, 0, "%s", jcr->errmsg); mark_volume_in_error(); goto check_bail_out; /* we could not write on DVD */ } /* Fall through wanted */ case VOL_NO_LABEL: switch (try_autolabel(true)) { case try_next_vol: goto check_next_volume; case try_read_vol: goto check_read_volume; case try_error: goto check_bail_out; case try_default: break; } /* NOTE! Fall-through wanted. */ case VOL_NO_MEDIA: default: Dmsg0(200, "VOL_NO_MEDIA or default.\n"); /* Send error message */ if (!dev->poll) { } else { Dmsg1(200, "Msg suppressed by poll: %s\n", jcr->errmsg); } ask = true; /* Needed, so the medium can be changed */ if (dev->requires_mount()) { dev->close(); } goto check_next_volume; } return check_ok; check_next_volume: return check_next_vol; check_bail_out: return check_error; check_read_volume: return check_read_vol; }
/* * Write a block to the spool file * * Returns: true on success or EOT * false on hard error */ bool write_block_to_spool_file(DCR *dcr) { uint32_t wlen, hlen; /* length to write */ bool despool = false; DEV_BLOCK *block = dcr->block; if (job_canceled(dcr->jcr)) { return false; } ASSERT(block->binbuf == ((uint32_t) (block->bufp - block->buf))); if (block->binbuf <= WRITE_BLKHDR_LENGTH) { /* Does block have data in it? */ return true; } hlen = sizeof(spool_hdr); wlen = block->binbuf; P(dcr->dev->spool_mutex); dcr->job_spool_size += hlen + wlen; dcr->dev->spool_size += hlen + wlen; if ((dcr->max_job_spool_size > 0 && dcr->job_spool_size >= dcr->max_job_spool_size) || (dcr->dev->max_spool_size > 0 && dcr->dev->spool_size >= dcr->dev->max_spool_size)) { despool = true; } V(dcr->dev->spool_mutex); P(mutex); spool_stats.data_size += hlen + wlen; if (spool_stats.data_size > spool_stats.max_data_size) { spool_stats.max_data_size = spool_stats.data_size; } V(mutex); if (despool) { char ec1[30], ec2[30]; if (dcr->max_job_spool_size > 0) { Jmsg(dcr->jcr, M_INFO, 0, _("User specified Job spool size reached: " "JobSpoolSize=%s MaxJobSpoolSize=%s\n"), edit_uint64_with_commas(dcr->job_spool_size, ec1), edit_uint64_with_commas(dcr->max_job_spool_size, ec2)); } else { Jmsg(dcr->jcr, M_INFO, 0, _("User specified Device spool size reached: " "DevSpoolSize=%s MaxDevSpoolSize=%s\n"), edit_uint64_with_commas(dcr->dev->spool_size, ec1), edit_uint64_with_commas(dcr->dev->max_spool_size, ec2)); } if (!despool_data(dcr, false)) { Pmsg0(000, _("Bad return from despool in write_block.\n")); return false; } /* Despooling cleared these variables so reset them */ P(dcr->dev->spool_mutex); dcr->job_spool_size += hlen + wlen; dcr->dev->spool_size += hlen + wlen; V(dcr->dev->spool_mutex); Jmsg(dcr->jcr, M_INFO, 0, _("Spooling data again ...\n")); } if (!write_spool_header(dcr)) { return false; } if (!write_spool_data(dcr)) { return false; } Dmsg2(800, "Wrote block FI=%d LI=%d\n", block->FirstIndex, block->LastIndex); empty_block(block); return true; }
/* * First prove our identity to the Remote daemon and then make him prove his identity. */ static inline bool two_way_authenticate(BSOCK *bs, JCR *jcr, bool initiate, const char *what) { int tls_local_need = BNET_TLS_NONE; int tls_remote_need = BNET_TLS_NONE; bool compatible = true; bool auth_success = false; alist *verify_list = NULL; btimer_t *tid = NULL; /* * TLS Requirement */ if (have_tls && me->tls_enable) { if (me->tls_require) { tls_local_need = BNET_TLS_REQUIRED; } else { tls_local_need = BNET_TLS_OK; } } if (me->tls_authenticate) { tls_local_need = BNET_TLS_REQUIRED; } if (job_canceled(jcr)) { auth_success = false; /* force quick exit */ goto auth_fatal; } /* * Timeout Hello after 10 min */ tid = start_bsock_timer(bs, AUTH_TIMEOUT); /* * See if we initiate the challenge or respond to a challenge. */ if (initiate) { /* * Challenge SD */ auth_success = cram_md5_challenge(bs, jcr->sd_auth_key, tls_local_need, compatible); if (auth_success) { /* * Respond to his challenge */ auth_success = cram_md5_respond(bs, jcr->sd_auth_key, &tls_remote_need, &compatible); if (!auth_success) { Dmsg1(dbglvl, "Respond cram-get-auth failed with %s\n", bs->who()); } } else { Dmsg1(dbglvl, "Challenge cram-auth failed with %s\n", bs->who()); } } else { /* * Respond to challenge */ auth_success = cram_md5_respond(bs, jcr->sd_auth_key, &tls_remote_need, &compatible); if (job_canceled(jcr)) { auth_success = false; /* force quick exit */ goto auth_fatal; } if (!auth_success) { Dmsg1(dbglvl, "cram_respond failed for %s\n", bs->who()); } else { /* * Challenge SD. */ auth_success = cram_md5_challenge(bs, jcr->sd_auth_key, tls_local_need, compatible); if (!auth_success) { Dmsg1(dbglvl, "cram_challenge failed for %s\n", bs->who()); } } } if (!auth_success) { Jmsg(jcr, M_FATAL, 0, _("Authorization key rejected by %s daemon.\n" "Please see %s for help.\n"), what, MANUAL_AUTH_URL); goto auth_fatal; } /* * Verify that the remote host is willing to meet our TLS requirements */ if (tls_remote_need < tls_local_need && tls_local_need != BNET_TLS_OK && tls_remote_need != BNET_TLS_OK) { Jmsg(jcr, M_FATAL, 0, _("Authorization problem: Remote server did not" " advertize required TLS support.\n")); Dmsg2(dbglvl, "remote_need=%d local_need=%d\n", tls_remote_need, tls_local_need); auth_success = false; goto auth_fatal; } /* * Verify that we are willing to meet the remote host's requirements */ if (tls_remote_need > tls_local_need && tls_local_need != BNET_TLS_OK && tls_remote_need != BNET_TLS_OK) { Jmsg(jcr, M_FATAL, 0, _("Authorization problem: Remote server requires TLS.\n")); Dmsg2(dbglvl, "remote_need=%d local_need=%d\n", tls_remote_need, tls_local_need); auth_success = false; goto auth_fatal; } if (tls_local_need >= BNET_TLS_OK && tls_remote_need >= BNET_TLS_OK) { /* * See if we are handshaking a passive client connection. */ if (initiate) { verify_list = me->tls_allowed_cns; } /* * Engage TLS! Full Speed Ahead! */ if (!bnet_tls_client(me->tls_ctx, bs, verify_list)) { Jmsg(jcr, M_FATAL, 0, _("TLS negotiation failed.\n")); auth_success = false; goto auth_fatal; } if (me->tls_authenticate) { /* tls authentication only? */ bs->free_tls(); /* yes, shutdown tls */ } } auth_fatal: /* * Destroy session key */ memset(jcr->sd_auth_key, 0, strlen(jcr->sd_auth_key)); stop_bsock_timer(tid); /* * Single thread all failures to avoid DOS */ if (!auth_success) { P(mutex); bmicrosleep(6, 0); V(mutex); } return auth_success; }
/* * Acquire device for writing. We permit multiple writers. * If this is the first one, we read the label. * * Returns: NULL if failed for any reason * dcr if successful. * Note, normally reserve_device_for_append() is called * before this routine. */ DCR *acquire_device_for_append(DCR *dcr) { DEVICE *dev = dcr->dev; JCR *jcr = dcr->jcr; bool ok = false; bool have_vol = false; Enter(200); init_device_wait_timers(dcr); dev->Lock_acquire(); /* only one job at a time */ dev->Lock(); Dmsg1(100, "acquire_append device is %s\n", dev->is_tape() ? "tape" : "disk"); /* * With the reservation system, this should not happen */ if (dev->can_read()) { Jmsg1(jcr, M_FATAL, 0, _("Want to append, but device %s is busy reading.\n"), dev->print_name()); Dmsg1(200, "Want to append but device %s is busy reading.\n", dev->print_name()); goto get_out; } dev->clear_unload(); /* * have_vol defines whether or not mount_next_write_volume should * ask the Director again about what Volume to use. */ if (dev->can_append() && dcr->is_suitable_volume_mounted() && !bstrcmp(dcr->VolCatInfo.VolCatStatus, "Recycle")) { Dmsg0(190, "device already in append.\n"); /* * At this point, the correct tape is already mounted, so * we do not need to do mount_next_write_volume(), unless * we need to recycle the tape. */ if (dev->num_writers == 0) { dev->VolCatInfo = dcr->VolCatInfo; /* structure assignment */ } have_vol = dcr->is_tape_position_ok(); } if (!have_vol) { dev->rLock(true); block_device(dev, BST_DOING_ACQUIRE); dev->Unlock(); Dmsg1(190, "jid=%u Do mount_next_write_vol\n", (uint32_t)jcr->JobId); if (!dcr->mount_next_write_volume()) { if (!job_canceled(jcr)) { /* Reduce "noise" -- don't print if job canceled */ Jmsg(jcr, M_FATAL, 0, _("Could not ready device %s for append.\n"), dev->print_name()); Dmsg1(200, "Could not ready device %s for append.\n", dev->print_name()); } dev->Lock(); unblock_device(dev); goto get_out; } Dmsg2(190, "Output pos=%u:%u\n", dcr->dev->file, dcr->dev->block_num); dev->Lock(); unblock_device(dev); } dev->num_writers++; /* we are now a writer */ if (jcr->NumWriteVolumes == 0) { jcr->NumWriteVolumes = 1; } dev->VolCatInfo.VolCatJobs++; /* increment number of jobs on vol */ Dmsg4(100, "=== nwriters=%d nres=%d vcatjob=%d dev=%s\n", dev->num_writers, dev->num_reserved(), dev->VolCatInfo.VolCatJobs, dev->print_name()); dcr->dir_update_volume_info(false, false); /* send Volume info to Director */ ok = true; get_out: /* Don't plugin close here, we might have multiple writers */ dcr->clear_reserved(); dev->Unlock(); dev->Unlock_acquire(); Leave(200); return ok ? dcr : NULL; }
/* * Wait for SysOp to mount a tape on a specific device * * Returns: W_ERROR, W_TIMEOUT, W_POLL, W_MOUNT, or W_WAKE */ int wait_for_sysop(DCR *dcr) { struct timeval tv; struct timezone tz; struct timespec timeout; time_t last_heartbeat = 0; time_t first_start = time(NULL); int status = 0; int add_wait; bool unmounted; DEVICE *dev = dcr->dev; JCR *jcr = dcr->jcr; dev->Lock(); Dmsg1(dbglvl, "Enter blocked=%s\n", dev->print_blocked()); /* * Since we want to mount a tape, make sure current one is * not marked as using this drive. */ volume_unused(dcr); unmounted = dev->is_device_unmounted(); dev->poll = false; /* * Wait requested time (dev->rem_wait_sec). However, we also wake up every * HB_TIME seconds and send a heartbeat to the FD and the Director * to keep stateful firewalls from closing them down while waiting * for the operator. */ add_wait = dev->rem_wait_sec; if (me->heartbeat_interval && add_wait > me->heartbeat_interval) { add_wait = me->heartbeat_interval; } /* If the user did not unmount the tape and we are polling, ensure * that we poll at the correct interval. */ if (!unmounted && dev->vol_poll_interval && add_wait > dev->vol_poll_interval) { add_wait = dev->vol_poll_interval; } if (!unmounted) { Dmsg1(dbglvl, "blocked=%s\n", dev->print_blocked()); dev->dev_prev_blocked = dev->blocked(); dev->set_blocked(BST_WAITING_FOR_SYSOP); /* indicate waiting for mount */ } for ( ; !job_canceled(jcr); ) { time_t now, start, total_waited; gettimeofday(&tv, &tz); timeout.tv_nsec = tv.tv_usec * 1000; timeout.tv_sec = tv.tv_sec + add_wait; Dmsg4(dbglvl, "I'm going to sleep on device %s. HB=%d rem_wait=%d add_wait=%d\n", dev->print_name(), (int)me->heartbeat_interval, dev->rem_wait_sec, add_wait); start = time(NULL); /* Wait required time */ status = pthread_cond_timedwait(&dev->wait_next_vol, &dev->m_mutex, &timeout); Dmsg2(dbglvl, "Wokeup from sleep on device status=%d blocked=%s\n", status, dev->print_blocked()); now = time(NULL); total_waited = now - first_start; dev->rem_wait_sec -= (now - start); /* Note, this always triggers the first time. We want that. */ if (me->heartbeat_interval) { if (now - last_heartbeat >= me->heartbeat_interval) { /* send heartbeats */ if (jcr->file_bsock) { jcr->file_bsock->signal(BNET_HEARTBEAT); Dmsg0(dbglvl, "Send heartbeat to FD.\n"); } if (jcr->dir_bsock) { jcr->dir_bsock->signal(BNET_HEARTBEAT); } last_heartbeat = now; } } if (status == EINVAL) { berrno be; Jmsg1(jcr, M_FATAL, 0, _("pthread timedwait error. ERR=%s\n"), be.bstrerror(status)); status = W_ERROR; /* error */ break; } /* * Continue waiting if operator is labeling volumes */ if (dev->blocked() == BST_WRITING_LABEL) { continue; } if (dev->rem_wait_sec <= 0) { /* on exceeding wait time return */ Dmsg0(dbglvl, "Exceed wait time.\n"); status = W_TIMEOUT; break; } /* * Check if user unmounted the device while we were waiting */ unmounted = dev->is_device_unmounted(); if (!unmounted && dev->vol_poll_interval && (total_waited >= dev->vol_poll_interval)) { Dmsg1(dbglvl, "poll return in wait blocked=%s\n", dev->print_blocked()); dev->poll = true; /* returning a poll event */ status = W_POLL; break; } /* * Check if user mounted the device while we were waiting */ if (dev->blocked() == BST_MOUNT) { /* mount request ? */ Dmsg0(dbglvl, "Mounted return.\n"); status = W_MOUNT; break; } /* * If we did not timeout, then some event happened, so * return to check if state changed. */ if (status != ETIMEDOUT) { berrno be; Dmsg2(dbglvl, "Wake return. status=%d. ERR=%s\n", status, be.bstrerror(status)); status = W_WAKE; /* someone woke us */ break; } /* * At this point, we know we woke up because of a timeout, * that was due to a heartbeat, because any other reason would * have caused us to return, so update the wait counters and continue. */ add_wait = dev->rem_wait_sec; if (me->heartbeat_interval && add_wait > me->heartbeat_interval) { add_wait = me->heartbeat_interval; } /* If the user did not unmount the tape and we are polling, ensure * that we poll at the correct interval. */ if (!unmounted && dev->vol_poll_interval && add_wait > dev->vol_poll_interval - total_waited) { add_wait = dev->vol_poll_interval - total_waited; } if (add_wait < 0) { add_wait = 0; } } if (!unmounted) { dev->set_blocked(dev->dev_prev_blocked); /* restore entry state */ Dmsg1(dbglvl, "set %s\n", dev->print_blocked()); } Dmsg1(dbglvl, "Exit blocked=%s\n", dev->print_blocked()); dev->Unlock(); return status; }
/* * This job is done, so release the device. From a Unix standpoint, * the device remains open. * * Note, if we were spooling, we may enter with the device blocked. * We unblock at the end, only if it was us who blocked the * device. * */ bool release_device(DCR *dcr) { utime_t now; JCR *jcr = dcr->jcr; DEVICE *dev = dcr->dev; bool ok = true; char tbuf[100]; int was_blocked = BST_NOT_BLOCKED; /* * Capture job statistics now that we are done using this device. */ now = (utime_t)time(NULL); update_job_statistics(jcr, now); dev->Lock(); if (!dev->is_blocked()) { block_device(dev, BST_RELEASING); } else { was_blocked = dev->blocked(); dev->set_blocked(BST_RELEASING); } lock_volumes(); Dmsg2(100, "release_device device %s is %s\n", dev->print_name(), dev->is_tape() ? "tape" : "disk"); /* * If device is reserved, job never started, so release the reserve here */ dcr->clear_reserved(); if (dev->can_read()) { VOLUME_CAT_INFO *vol = &dev->VolCatInfo; dev->clear_read(); /* clear read bit */ Dmsg2(150, "dir_update_vol_info. label=%d Vol=%s\n", dev->is_labeled(), vol->VolCatName); if (dev->is_labeled() && vol->VolCatName[0] != 0) { dcr->dir_update_volume_info(false, false); /* send Volume info to Director */ remove_read_volume(jcr, dcr->VolumeName); volume_unused(dcr); } } else if (dev->num_writers > 0) { /* * Note if WEOT is set, we are at the end of the tape and may not be positioned correctly, * so the job_media_record and update_vol_info have already been done, * which means we skip them here. */ dev->num_writers--; Dmsg1(100, "There are %d writers in release_device\n", dev->num_writers); if (dev->is_labeled()) { Dmsg2(200, "dir_create_jobmedia. Release vol=%s dev=%s\n", dev->getVolCatName(), dev->print_name()); if (!dev->at_weot() && !dcr->dir_create_jobmedia_record(false)) { Jmsg2(jcr, M_FATAL, 0, _("Could not create JobMedia record for Volume=\"%s\" Job=%s\n"), dcr->getVolCatName(), jcr->Job); } /* * If no more writers, and no errors, and wrote something, write an EOF */ if (!dev->num_writers && dev->can_write() && dev->block_num > 0) { dev->weof(1); write_ansi_ibm_labels(dcr, ANSI_EOF_LABEL, dev->VolHdr.VolumeName); } if (!dev->at_weot()) { dev->VolCatInfo.VolCatFiles = dev->file; /* set number of files */ /* * Note! do volume update before close, which zaps VolCatInfo */ dcr->dir_update_volume_info(false, false); /* send Volume info to Director */ Dmsg2(200, "dir_update_vol_info. Release vol=%s dev=%s\n", dev->getVolCatName(), dev->print_name()); } if (dev->num_writers == 0) { /* if not being used */ volume_unused(dcr); /* we obviously are not using the volume */ } } } else { /* * If we reach here, it is most likely because the job has failed, * since the device is not in read mode and there are no writers. * It was probably reserved. */ volume_unused(dcr); } Dmsg3(100, "%d writers, %d reserve, dev=%s\n", dev->num_writers, dev->num_reserved(), dev->print_name()); /* * If no writers, close if file or !CAP_ALWAYS_OPEN */ if (dev->num_writers == 0 && (!dev->is_tape() || !dev->has_cap(CAP_ALWAYSOPEN))) { dev->close(dcr); free_volume(dev); } unlock_volumes(); /* * Fire off Alert command and include any output */ if (!job_canceled(jcr)) { if (!dcr->device->drive_tapealert_enabled && dcr->device->alert_command) { int status = 1; POOLMEM *alert, *line; BPIPE *bpipe; alert = get_pool_memory(PM_FNAME); line = get_pool_memory(PM_FNAME); alert = edit_device_codes(dcr, alert, dcr->device->alert_command, ""); /* * Wait maximum 5 minutes */ bpipe = open_bpipe(alert, 60 * 5, "r"); if (bpipe) { while (bfgets(line, bpipe->rfd)) { Jmsg(jcr, M_ALERT, 0, _("Alert: %s"), line); } status = close_bpipe(bpipe); } else { status = errno; } if (status != 0) { berrno be; Jmsg(jcr, M_ALERT, 0, _("3997 Bad alert command: %s: ERR=%s.\n"), alert, be.bstrerror(status)); } Dmsg1(400, "alert status=%d\n", status); free_pool_memory(alert); free_pool_memory(line); } else { /* * If all reservations are cleared for this device raise an event that SD plugins can register to. */ if (dev->num_reserved() == 0) { generate_plugin_event(jcr, bsdEventDeviceReleased, dcr); } } } pthread_cond_broadcast(&dev->wait_next_vol); Dmsg2(100, "JobId=%u broadcast wait_device_release at %s\n", (uint32_t)jcr->JobId, bstrftimes(tbuf, sizeof(tbuf), (utime_t)time(NULL))); release_device_cond(); /* * If we are the thread that blocked the device, then unblock it */ if (pthread_equal(dev->no_wait_id, pthread_self())) { dev->dunblock(true); } else { /* * Otherwise, reset the prior block status and unlock */ dev->set_blocked(was_blocked); dev->Unlock(); } if (dcr->keep_dcr) { detach_dcr_from_dev(dcr); } else { free_dcr(dcr); } Dmsg2(100, "Device %s released by JobId=%u\n", dev->print_name(), (uint32_t)jcr->JobId); return ok; }