/* * Do a backup of the specified FileSet * * Returns: false on failure * true on success */ bool do_backup(JCR *jcr) { int stat; int tls_need = BNET_TLS_NONE; BSOCK *fd; STORE *store; char ed1[100]; if (jcr->get_JobLevel() == L_VIRTUAL_FULL) { return do_vbackup(jcr); } /* Print Job Start message */ Jmsg(jcr, M_INFO, 0, _("Start Backup JobId %s, Job=%s\n"), edit_uint64(jcr->JobId, ed1), jcr->Job); set_jcr_job_status(jcr, JS_Running); Dmsg2(100, "JobId=%d JobLevel=%c\n", jcr->jr.JobId, jcr->jr.JobLevel); if (!db_update_job_start_record(jcr, jcr->db, &jcr->jr)) { Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db)); return false; } /* * Open a message channel connection with the Storage * daemon. This is to let him know that our client * will be contacting him for a backup session. * */ Dmsg0(110, "Open connection with storage daemon\n"); set_jcr_job_status(jcr, JS_WaitSD); /* * Start conversation with Storage daemon */ if (!connect_to_storage_daemon(jcr, 10, SDConnectTimeout, 1)) { return false; } /* * Now start a job with the Storage daemon */ if (!start_storage_daemon_job(jcr, NULL, jcr->wstorage)) { return false; } /* * Start the job prior to starting the message thread below * to avoid two threads from using the BSOCK structure at * the same time. */ if (!bnet_fsend(jcr->store_bsock, "run")) { return false; } /* * Now start a Storage daemon message thread. Note, * this thread is used to provide the catalog services * for the backup job, including inserting the attributes * into the catalog. See catalog_update() in catreq.c */ if (!start_storage_daemon_message_thread(jcr)) { return false; } Dmsg0(150, "Storage daemon connection OK\n"); set_jcr_job_status(jcr, JS_WaitFD); if (!connect_to_file_daemon(jcr, 10, FDConnectTimeout, 1)) { goto bail_out; } set_jcr_job_status(jcr, JS_Running); fd = jcr->file_bsock; if (!send_include_list(jcr)) { goto bail_out; } if (!send_exclude_list(jcr)) { goto bail_out; } if (!send_level_command(jcr)) { goto bail_out; } /* * send Storage daemon address to the File daemon */ store = jcr->wstore; if (store->SDDport == 0) { store->SDDport = store->SDport; } /* TLS Requirement */ if (store->tls_enable) { if (store->tls_require) { tls_need = BNET_TLS_REQUIRED; } else { tls_need = BNET_TLS_OK; } } fd->fsend(storaddr, store->address, store->SDDport, tls_need); if (!response(jcr, fd, OKstore, "Storage", DISPLAY_ERROR)) { goto bail_out; } if (!send_runscripts_commands(jcr)) { goto bail_out; } /* * We re-update the job start record so that the start * time is set after the run before job. This avoids * that any files created by the run before job will * be saved twice. They will be backed up in the current * job, but not in the next one unless they are changed. * Without this, they will be backed up in this job and * in the next job run because in that case, their date * is after the start of this run. */ jcr->start_time = time(NULL); jcr->jr.StartTime = jcr->start_time; if (!db_update_job_start_record(jcr, jcr->db, &jcr->jr)) { Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db)); } /* * If backup is in accurate mode, we send the list of * all files to FD. */ if (!send_accurate_current_files(jcr)) { goto bail_out; } /* Send backup command */ fd->fsend(backupcmd); if (!response(jcr, fd, OKbackup, "backup", DISPLAY_ERROR)) { goto bail_out; } /* Pickup Job termination data */ stat = wait_for_job_termination(jcr); db_write_batch_file_records(jcr); /* used by bulk batch file insert */ if (stat == JS_Terminated) { backup_cleanup(jcr, stat); return true; } return false; /* Come here only after starting SD thread */ bail_out: set_jcr_job_status(jcr, JS_ErrorTerminated); Dmsg1(400, "wait for sd. use=%d\n", jcr->use_count()); /* Cancel SD */ wait_for_job_termination(jcr, FDConnectTimeout); Dmsg1(400, "after wait for sd. use=%d\n", jcr->use_count()); return false; }
/* * Do a verification of the specified files against the Catlaog * * Returns: false on failure * true on success */ bool do_verify(JCR *jcr) { const char *level; BSOCK *fd; int stat; char ed1[100]; JOB_DBR jr; JobId_t verify_jobid = 0; const char *Name; free_wstorage(jcr); /* we don't write */ memset(&jcr->previous_jr, 0, sizeof(jcr->previous_jr)); /* * Find JobId of last job that ran. Note, we do this when * the job actually starts running, not at schedule time, * so that we find the last job that terminated before * this job runs rather than before it is scheduled. This * permits scheduling a Backup and Verify at the same time, * but with the Verify at a lower priority. * * For VERIFY_CATALOG we want the JobId of the last INIT. * For VERIFY_VOLUME_TO_CATALOG, we want the JobId of the * last backup Job. */ if (jcr->get_JobLevel() == L_VERIFY_CATALOG || jcr->get_JobLevel() == L_VERIFY_VOLUME_TO_CATALOG || jcr->get_JobLevel() == L_VERIFY_DISK_TO_CATALOG) { memcpy(&jr, &jcr->jr, sizeof(jr)); if (jcr->verify_job && (jcr->get_JobLevel() == L_VERIFY_VOLUME_TO_CATALOG || jcr->get_JobLevel() == L_VERIFY_DISK_TO_CATALOG)) { Name = jcr->verify_job->name(); } else { Name = NULL; } Dmsg1(100, "find last jobid for: %s\n", NPRT(Name)); if (!db_find_last_jobid(jcr, jcr->db, Name, &jr)) { if (jcr->get_JobLevel() == L_VERIFY_CATALOG) { Jmsg(jcr, M_FATAL, 0, _( "Unable to find JobId of previous InitCatalog Job.\n" "Please run a Verify with Level=InitCatalog before\n" "running the current Job.\n")); } else { Jmsg(jcr, M_FATAL, 0, _( "Unable to find JobId of previous Job for this client.\n")); } return false; } verify_jobid = jr.JobId; Dmsg1(100, "Last full jobid=%d\n", verify_jobid); } /* * Now get the job record for the previous backup that interests * us. We use the verify_jobid that we found above. */ if (jcr->get_JobLevel() == L_VERIFY_CATALOG || jcr->get_JobLevel() == L_VERIFY_VOLUME_TO_CATALOG || jcr->get_JobLevel() == L_VERIFY_DISK_TO_CATALOG) { jcr->previous_jr.JobId = verify_jobid; if (!db_get_job_record(jcr, jcr->db, &jcr->previous_jr)) { Jmsg(jcr, M_FATAL, 0, _("Could not get job record for previous Job. ERR=%s"), db_strerror(jcr->db)); return false; } if (!(jcr->previous_jr.JobStatus == JS_Terminated || jcr->previous_jr.JobStatus == JS_Warnings)) { Jmsg(jcr, M_FATAL, 0, _("Last Job %d did not terminate normally. JobStatus=%c\n"), verify_jobid, jcr->previous_jr.JobStatus); return false; } Jmsg(jcr, M_INFO, 0, _("Verifying against JobId=%d Job=%s\n"), jcr->previous_jr.JobId, jcr->previous_jr.Job); } /* * If we are verifying a Volume, we need the Storage * daemon, so open a connection, otherwise, just * create a dummy authorization key (passed to * File daemon but not used). */ if (jcr->get_JobLevel() == L_VERIFY_VOLUME_TO_CATALOG) { if (!create_restore_bootstrap_file(jcr)) { return false; } } else { jcr->sd_auth_key = bstrdup("dummy"); /* dummy Storage daemon key */ } if (jcr->get_JobLevel() == L_VERIFY_DISK_TO_CATALOG && jcr->verify_job) { jcr->fileset = jcr->verify_job->fileset; } Dmsg2(100, "ClientId=%u JobLevel=%c\n", jcr->previous_jr.ClientId, jcr->get_JobLevel()); if (!db_update_job_start_record(jcr, jcr->db, &jcr->jr)) { Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db)); return false; } /* Print Job Start message */ Jmsg(jcr, M_INFO, 0, _("Start Verify JobId=%s Level=%s Job=%s\n"), edit_uint64(jcr->JobId, ed1), level_to_str(jcr->get_JobLevel()), jcr->Job); if (jcr->get_JobLevel() == L_VERIFY_VOLUME_TO_CATALOG) { /* * Start conversation with Storage daemon */ set_jcr_job_status(jcr, JS_Blocked); if (!connect_to_storage_daemon(jcr, 10, SDConnectTimeout, 1)) { return false; } /* * Now start a job with the Storage daemon */ if (!start_storage_daemon_job(jcr, jcr->rstorage, NULL)) { return false; } if (!jcr->store_bsock->fsend("run")) { return false; } /* * Now start a Storage daemon message thread */ if (!start_storage_daemon_message_thread(jcr)) { return false; } Dmsg0(50, "Storage daemon connection OK\n"); } /* * OK, now connect to the File daemon * and ask him for the files. */ set_jcr_job_status(jcr, JS_Blocked); if (!connect_to_file_daemon(jcr, 10, FDConnectTimeout, 1)) { goto bail_out; } set_jcr_job_status(jcr, JS_Running); fd = jcr->file_bsock; Dmsg0(30, ">filed: Send include list\n"); if (!send_include_list(jcr)) { goto bail_out; } Dmsg0(30, ">filed: Send exclude list\n"); if (!send_exclude_list(jcr)) { goto bail_out; } /* * Send Level command to File daemon, as well * as the Storage address if appropriate. */ switch (jcr->get_JobLevel()) { case L_VERIFY_INIT: level = "init"; break; case L_VERIFY_CATALOG: level = "catalog"; break; case L_VERIFY_VOLUME_TO_CATALOG: /* * send Storage daemon address to the File daemon */ if (jcr->rstore->SDDport == 0) { jcr->rstore->SDDport = jcr->rstore->SDport; } bnet_fsend(fd, storaddr, jcr->rstore->address, jcr->rstore->SDDport); if (!response(jcr, fd, OKstore, "Storage", DISPLAY_ERROR)) { goto bail_out; } /* * Send the bootstrap file -- what Volumes/files to restore */ if (!send_bootstrap_file(jcr, fd) || !response(jcr, fd, OKbootstrap, "Bootstrap", DISPLAY_ERROR)) { goto bail_out; } if (!jcr->RestoreBootstrap) { Jmsg0(jcr, M_FATAL, 0, _("Deprecated feature ... use bootstrap.\n")); goto bail_out; } level = "volume"; break; case L_VERIFY_DATA: level = "data"; break; case L_VERIFY_DISK_TO_CATALOG: level="disk_to_catalog"; break; default: Jmsg2(jcr, M_FATAL, 0, _("Unimplemented Verify level %d(%c)\n"), jcr->get_JobLevel(), jcr->get_JobLevel()); goto bail_out; } if (!send_runscripts_commands(jcr)) { goto bail_out; } /* * Send verify command/level to File daemon */ fd->fsend(verifycmd, level); if (!response(jcr, fd, OKverify, "Verify", DISPLAY_ERROR)) { goto bail_out; } /* * Now get data back from File daemon and * compare it to the catalog or store it in the * catalog depending on the run type. */ /* Compare to catalog */ switch (jcr->get_JobLevel()) { case L_VERIFY_CATALOG: Dmsg0(10, "Verify level=catalog\n"); jcr->sd_msg_thread_done = true; /* no SD msg thread, so it is done */ jcr->SDJobStatus = JS_Terminated; get_attributes_and_compare_to_catalog(jcr, jcr->previous_jr.JobId); break; case L_VERIFY_VOLUME_TO_CATALOG: Dmsg0(10, "Verify level=volume\n"); get_attributes_and_compare_to_catalog(jcr, jcr->previous_jr.JobId); break; case L_VERIFY_DISK_TO_CATALOG: Dmsg0(10, "Verify level=disk_to_catalog\n"); jcr->sd_msg_thread_done = true; /* no SD msg thread, so it is done */ jcr->SDJobStatus = JS_Terminated; get_attributes_and_compare_to_catalog(jcr, jcr->previous_jr.JobId); break; case L_VERIFY_INIT: /* Build catalog */ Dmsg0(10, "Verify level=init\n"); jcr->sd_msg_thread_done = true; /* no SD msg thread, so it is done */ jcr->SDJobStatus = JS_Terminated; get_attributes_and_put_in_catalog(jcr); db_end_transaction(jcr, jcr->db); /* terminate any open transaction */ db_write_batch_file_records(jcr); break; default: Jmsg1(jcr, M_FATAL, 0, _("Unimplemented verify level %d\n"), jcr->get_JobLevel()); goto bail_out; } stat = wait_for_job_termination(jcr); verify_cleanup(jcr, stat); return true; bail_out: return false; }
int apcsmart_ups_entry_point(UPSINFO *ups, int command, void *data) { int retries = 5; /* Number of retries if reason is NA (see below) */ char ans[20]; switch (command) { case DEVICE_CMD_SET_DUMB_MODE: /* Set dumb mode for a smart UPS */ write(ups->fd, "R", 1); /* enter dumb mode */ *ans = 0; getline(ans, sizeof(ans), ups); printf("Going dumb: %s\n", ans); break; case DEVICE_CMD_GET_SELFTEST_MSG: /* Results of last self test */ if (ups->UPS_Cap[CI_ST_STAT]) { ups->testresult = decode_testresult( smart_poll(ups->UPS_Cmd[CI_ST_STAT], ups)); } break; case DEVICE_CMD_CHECK_SELFTEST: Dmsg0(80, "Checking self test.\n"); /* * XXX * * One day we will do this test inside the driver and not as an * entry point. */ /* Reason for last transfer to batteries */ if (ups->UPS_Cap[CI_WHY_BATT]) { ups->lastxfer = XFER_NA; while (ups->lastxfer == XFER_NA && retries--) { ups->lastxfer = decode_lastxfer( smart_poll(ups->UPS_Cmd[CI_WHY_BATT], ups)); if (ups->lastxfer == XFER_NA) { Dmsg0(80, "Transfer reason still not available.\n"); if (retries > 0) sleep(2); /* debounce */ /* * Be careful because if we go out of here without * knowing the reason of transfer (i.e. the reason * is "NA", apcupsd will think this is a power failure * even if it is a self test. Not much of a problem * as this should not happen. * We allow 5 retries for reading reason from UPS before * giving up. */ } else if (ups->lastxfer == XFER_SELFTEST) { ups->SelfTest = time(NULL); Dmsg1(80, "Self Test time: %s", ctime(&ups->SelfTest)); } } } break; default: return FAILURE; break; } return SUCCESS; }
void b_free_jcr(const char *file, int line, JCR *jcr) { struct s_last_job *je; Dmsg3(dbglvl, "Enter free_jcr jid=%u from %s:%d\n", jcr->JobId, file, line); #else void free_jcr(JCR *jcr) { struct s_last_job *je; Dmsg3(dbglvl, "Enter free_jcr jid=%u use_count=%d Job=%s\n", jcr->JobId, jcr->use_count(), jcr->Job); #endif lock_jcr_chain(); jcr->dec_use_count(); /* decrement use count */ if (jcr->use_count() < 0) { Jmsg2(jcr, M_ERROR, 0, _("JCR use_count=%d JobId=%d\n"), jcr->use_count(), jcr->JobId); } if (jcr->JobId > 0) { Dmsg3(dbglvl, "Dec free_jcr jid=%u use_count=%d Job=%s\n", jcr->JobId, jcr->use_count(), jcr->Job); } if (jcr->use_count() > 0) { /* if in use */ unlock_jcr_chain(); return; } if (jcr->JobId > 0) { Dmsg3(dbglvl, "remove jcr jid=%u use_count=%d Job=%s\n", jcr->JobId, jcr->use_count(), jcr->Job); } remove_jcr(jcr); /* remove Jcr from chain */ unlock_jcr_chain(); dequeue_messages(jcr); close_msg(jcr); /* close messages for this job */ job_end_pop(jcr); /* pop and call hooked routines */ Dmsg1(dbglvl, "End job=%d\n", jcr->JobId); /* Keep some statistics */ switch (jcr->getJobType()) { case JT_BACKUP: case JT_VERIFY: case JT_RESTORE: case JT_MIGRATE: case JT_COPY: case JT_ADMIN: /* Keep list of last jobs, but not Console where JobId==0 */ if (jcr->JobId > 0) { lock_last_jobs_list(); num_jobs_run++; je = (struct s_last_job *)malloc(sizeof(struct s_last_job)); memset(je, 0, sizeof(struct s_last_job)); /* zero in case unset fields */ je->Errors = jcr->JobErrors; je->JobType = jcr->getJobType(); je->JobId = jcr->JobId; je->VolSessionId = jcr->VolSessionId; je->VolSessionTime = jcr->VolSessionTime; bstrncpy(je->Job, jcr->Job, sizeof(je->Job)); je->JobFiles = jcr->JobFiles; je->JobBytes = jcr->JobBytes; je->JobStatus = jcr->JobStatus; je->JobLevel = jcr->getJobLevel(); je->start_time = jcr->start_time; je->end_time = time(NULL); if (!last_jobs) { init_last_jobs_list(); } last_jobs->append(je); if (last_jobs->size() > max_last_jobs) { je = (struct s_last_job *)last_jobs->first(); last_jobs->remove(je); free(je); } unlock_last_jobs_list(); } break; default: break; } if (jcr->daemon_free_jcr) { jcr->daemon_free_jcr(jcr); /* call daemon free routine */ } free_common_jcr(jcr); Dmsg0(dbglvl, "Exit free_jcr\n"); } /* * Remove jcr from thread specific data, but * but make sure it is us who are attached. */ void remove_jcr_from_tsd(JCR *jcr) { JCR *tjcr = get_jcr_from_tsd(); if (tjcr == jcr) { set_jcr_in_tsd(INVALID_JCR); } }
/* * Release resources allocated during backup. * */ void verify_cleanup(JCR *jcr, int TermCode) { char sdt[50], edt[50]; char ec1[30], ec2[30]; char term_code[100], fd_term_msg[100], sd_term_msg[100]; const char *term_msg; int msg_type; JobId_t JobId; const char *Name; // Dmsg1(100, "Enter verify_cleanup() TermCod=%d\n", TermCode); Dmsg3(900, "JobLevel=%c Expected=%u JobFiles=%u\n", jcr->get_JobLevel(), jcr->ExpectedFiles, jcr->JobFiles); if (jcr->get_JobLevel() == L_VERIFY_VOLUME_TO_CATALOG && jcr->ExpectedFiles != jcr->JobFiles) { TermCode = JS_ErrorTerminated; } JobId = jcr->jr.JobId; update_job_end(jcr, TermCode); if (job_canceled(jcr)) { cancel_storage_daemon_job(jcr); } if (jcr->unlink_bsr && jcr->RestoreBootstrap) { unlink(jcr->RestoreBootstrap); jcr->unlink_bsr = false; } msg_type = M_INFO; /* by default INFO message */ switch (TermCode) { case JS_Terminated: term_msg = _("Verify OK"); break; case JS_FatalError: case JS_ErrorTerminated: term_msg = _("*** Verify Error ***"); msg_type = M_ERROR; /* Generate error message */ break; case JS_Error: term_msg = _("Verify warnings"); break; case JS_Canceled: term_msg = _("Verify Canceled"); break; case JS_Differences: term_msg = _("Verify Differences"); break; default: term_msg = term_code; bsnprintf(term_code, sizeof(term_code), _("Inappropriate term code: %d %c\n"), TermCode, TermCode); break; } bstrftimes(sdt, sizeof(sdt), jcr->jr.StartTime); bstrftimes(edt, sizeof(edt), jcr->jr.EndTime); if (jcr->verify_job) { Name = jcr->verify_job->hdr.name; } else { Name = ""; } jobstatus_to_ascii(jcr->FDJobStatus, fd_term_msg, sizeof(fd_term_msg)); if (jcr->get_JobLevel() == L_VERIFY_VOLUME_TO_CATALOG) { jobstatus_to_ascii(jcr->SDJobStatus, sd_term_msg, sizeof(sd_term_msg)); Jmsg(jcr, msg_type, 0, _("%s %s %s (%s): %s\n" " Build OS: %s %s %s\n" " JobId: %d\n" " Job: %s\n" " FileSet: %s\n" " Verify Level: %s\n" " Client: %s\n" " Verify JobId: %d\n" " Verify Job: %s\n" " Start time: %s\n" " End time: %s\n" " Files Expected: %s\n" " Files Examined: %s\n" " Non-fatal FD errors: %d\n" " FD termination status: %s\n" " SD termination status: %s\n" " Termination: %s\n\n"), BACULA, my_name, VERSION, LSMDATE, edt, HOST_OS, DISTNAME, DISTVER, jcr->jr.JobId, jcr->jr.Job, jcr->fileset->hdr.name, level_to_str(jcr->get_JobLevel()), jcr->client->hdr.name, jcr->previous_jr.JobId, Name, sdt, edt, edit_uint64_with_commas(jcr->ExpectedFiles, ec1), edit_uint64_with_commas(jcr->JobFiles, ec2), jcr->JobErrors, fd_term_msg, sd_term_msg, term_msg); } else { Jmsg(jcr, msg_type, 0, _("%s %s %s (%s): %s\n" " Build: %s %s %s\n" " JobId: %d\n" " Job: %s\n" " FileSet: %s\n" " Verify Level: %s\n" " Client: %s\n" " Verify JobId: %d\n" " Verify Job: %s\n" " Start time: %s\n" " End time: %s\n" " Files Examined: %s\n" " Non-fatal FD errors: %d\n" " FD termination status: %s\n" " Termination: %s\n\n"), BACULA, my_name, VERSION, LSMDATE, edt, HOST_OS, DISTNAME, DISTVER, jcr->jr.JobId, jcr->jr.Job, jcr->fileset->hdr.name, level_to_str(jcr->get_JobLevel()), jcr->client->name(), jcr->previous_jr.JobId, Name, sdt, edt, edit_uint64_with_commas(jcr->JobFiles, ec1), jcr->JobErrors, fd_term_msg, term_msg); } Dmsg0(100, "Leave verify_cleanup()\n"); }
/* * Do a verification of the specified files against the Catlaog * * Returns: false on failure * true on success */ bool do_verify(JCR *jcr) { int JobLevel; const char *level; BSOCK *fd = NULL; BSOCK *sd = NULL; int status; char ed1[100]; JOB_DBR jr; JobId_t verify_jobid = 0; const char *Name; free_wstorage(jcr); /* we don't write */ memset(&jcr->previous_jr, 0, sizeof(jcr->previous_jr)); /* * Find JobId of last job that ran. Note, we do this when * the job actually starts running, not at schedule time, * so that we find the last job that terminated before * this job runs rather than before it is scheduled. This * permits scheduling a Backup and Verify at the same time, * but with the Verify at a lower priority. * * For VERIFY_CATALOG we want the JobId of the last INIT. * For VERIFY_VOLUME_TO_CATALOG, we want the JobId of the * last backup Job. */ JobLevel = jcr->getJobLevel(); switch (JobLevel) { case L_VERIFY_CATALOG: case L_VERIFY_VOLUME_TO_CATALOG: case L_VERIFY_DISK_TO_CATALOG: memcpy(&jr, &jcr->jr, sizeof(jr)); if (jcr->res.verify_job && (JobLevel == L_VERIFY_VOLUME_TO_CATALOG || JobLevel == L_VERIFY_DISK_TO_CATALOG)) { Name = jcr->res.verify_job->name(); } else { Name = NULL; } Dmsg1(100, "find last jobid for: %s\n", NPRT(Name)); /* * See if user supplied a jobid= as run argument or from menu */ if (jcr->VerifyJobId) { verify_jobid = jcr->VerifyJobId; Dmsg1(100, "Supplied jobid=%d\n", verify_jobid); } else { if (!db_find_last_jobid(jcr, jcr->db, Name, &jr)) { if (JobLevel == L_VERIFY_CATALOG) { Jmsg(jcr, M_FATAL, 0, _( "Unable to find JobId of previous InitCatalog Job.\n" "Please run a Verify with Level=InitCatalog before\n" "running the current Job.\n")); } else { Jmsg(jcr, M_FATAL, 0, _( "Unable to find JobId of previous Job for this client.\n")); } return false; } verify_jobid = jr.JobId; } Dmsg1(100, "Last full jobid=%d\n", verify_jobid); /* * Now get the job record for the previous backup that interests * us. We use the verify_jobid that we found above. */ jcr->previous_jr.JobId = verify_jobid; if (!db_get_job_record(jcr, jcr->db, &jcr->previous_jr)) { Jmsg(jcr, M_FATAL, 0, _("Could not get job record for previous Job. ERR=%s"), db_strerror(jcr->db)); return false; } if (!(jcr->previous_jr.JobStatus == JS_Terminated || jcr->previous_jr.JobStatus == JS_Warnings)) { Jmsg(jcr, M_FATAL, 0, _("Last Job %d did not terminate normally. JobStatus=%c\n"), verify_jobid, jcr->previous_jr.JobStatus); return false; } Jmsg(jcr, M_INFO, 0, _("Verifying against JobId=%d Job=%s\n"), jcr->previous_jr.JobId, jcr->previous_jr.Job); } /* * If we are verifying a Volume, we need the Storage * daemon, so open a connection, otherwise, just * create a dummy authorization key (passed to * File daemon but not used). */ switch (JobLevel) { case L_VERIFY_VOLUME_TO_CATALOG: /* * Note: negative status is an error, zero status, means * no files were backed up, so skip calling SD and * client. */ status = create_restore_bootstrap_file(jcr); if (status < 0) { /* error */ return false; } else if (status == 0) { /* No files, nothing to do */ verify_cleanup(jcr, JS_Terminated); /* clean up */ return true; /* get out */ } if (jcr->res.verify_job) { jcr->res.fileset = jcr->res.verify_job->fileset; } break; default: jcr->sd_auth_key = bstrdup("dummy"); /* dummy Storage daemon key */ break; } Dmsg2(100, "ClientId=%u JobLevel=%c\n", jcr->previous_jr.ClientId, JobLevel); if (!db_update_job_start_record(jcr, jcr->db, &jcr->jr)) { Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db)); return false; } /* * Print Job Start message */ Jmsg(jcr, M_INFO, 0, _("Start Verify JobId=%s Level=%s Job=%s\n"), edit_uint64(jcr->JobId, ed1), level_to_str(JobLevel), jcr->Job); switch (JobLevel) { case L_VERIFY_VOLUME_TO_CATALOG: /* * Start conversation with Storage daemon */ jcr->setJobStatus(JS_Blocked); if (!connect_to_storage_daemon(jcr, 10, me->SDConnectTimeout, true)) { return false; } sd = jcr->store_bsock; /* * Now start a job with the Storage daemon */ if (!start_storage_daemon_job(jcr, jcr->res.rstorage, NULL, /* send_bsr */ true)) { return false; } jcr->passive_client = jcr->res.client->passive; if (!jcr->passive_client) { /* * Start the Job in the SD. */ if (!sd->fsend("run")) { return false; } /* * Now start a Storage daemon message thread */ if (!start_storage_daemon_message_thread(jcr)) { return false; } Dmsg0(50, "Storage daemon connection OK\n"); } /* * OK, now connect to the File daemon and ask him for the files. */ jcr->setJobStatus(JS_Blocked); if (!connect_to_file_daemon(jcr, 10, me->FDConnectTimeout, true)) { goto bail_out; } send_job_info(jcr); fd = jcr->file_bsock; /* * Check if the file daemon supports passive client mode. */ if (jcr->passive_client && jcr->FDVersion < FD_VERSION_51) { Jmsg(jcr, M_FATAL, 0, _("Client \"%s\" doesn't support passive client mode. " "Please upgrade your client or disable compat mode.\n"), jcr->res.client->name()); goto bail_out; } break; default: /* * OK, now connect to the File daemon and ask him for the files. */ jcr->setJobStatus(JS_Blocked); if (!connect_to_file_daemon(jcr, 10, me->FDConnectTimeout, true)) { goto bail_out; } send_job_info(jcr); fd = jcr->file_bsock; break; } jcr->setJobStatus(JS_Running); Dmsg0(30, ">filed: Send include list\n"); if (!send_include_list(jcr)) { goto bail_out; } Dmsg0(30, ">filed: Send exclude list\n"); if (!send_exclude_list(jcr)) { goto bail_out; } /* * Send Level command to File daemon, as well as the Storage address if appropriate. */ switch (JobLevel) { case L_VERIFY_INIT: level = "init"; break; case L_VERIFY_CATALOG: level = "catalog"; break; case L_VERIFY_VOLUME_TO_CATALOG: if (!jcr->RestoreBootstrap) { Jmsg0(jcr, M_FATAL, 0, _("Deprecated feature ... use bootstrap.\n")); goto bail_out; } if (!jcr->passive_client) { int tls_need = BNET_TLS_NONE; STORERES *store = jcr->res.rstore; /* * Send Storage daemon address to the File daemon */ if (store->SDDport == 0) { store->SDDport = store->SDport; } /* * TLS Requirement */ if (store->tls.enable) { if (store->tls.require) { tls_need = BNET_TLS_REQUIRED; } else { tls_need = BNET_TLS_OK; } } fd->fsend(storaddrcmd, store->address, store->SDDport, tls_need, jcr->sd_auth_key); if (!response(jcr, fd, OKstore, "Storage", DISPLAY_ERROR)) { goto bail_out; } } else { int tls_need = BNET_TLS_NONE; CLIENTRES *client = jcr->res.client; /* * TLS Requirement */ if (client->tls.enable) { if (client->tls.require) { tls_need = BNET_TLS_REQUIRED; } else { tls_need = BNET_TLS_OK; } } /* * Tell the SD to connect to the FD. */ sd->fsend(passiveclientcmd, client->address, client->FDport, tls_need); if (!response(jcr, sd, OKpassiveclient, "Passive client", DISPLAY_ERROR)) { goto bail_out; } /* * Start the Job in the SD. */ if (!sd->fsend("run")) { goto bail_out; } /* * Now start a Storage daemon message thread */ if (!start_storage_daemon_message_thread(jcr)) { goto bail_out; } Dmsg0(50, "Storage daemon connection OK\n"); } level = "volume"; break; case L_VERIFY_DATA: level = "data"; break; case L_VERIFY_DISK_TO_CATALOG: level="disk_to_catalog"; break; default: Jmsg2(jcr, M_FATAL, 0, _("Unimplemented Verify level %d(%c)\n"), JobLevel, JobLevel); goto bail_out; } if (!send_runscripts_commands(jcr)) { goto bail_out; } /* * Send verify command/level to File daemon */ fd->fsend(verifycmd, level); if (!response(jcr, fd, OKverify, "Verify", DISPLAY_ERROR)) { goto bail_out; } /* * Now get data back from File daemon and * compare it to the catalog or store it in the * catalog depending on the run type. */ switch (JobLevel) { case L_VERIFY_CATALOG: /* * Verify from catalog */ Dmsg0(10, "Verify level=catalog\n"); jcr->sd_msg_thread_done = true; /* no SD msg thread, so it is done */ jcr->SDJobStatus = JS_Terminated; get_attributes_and_compare_to_catalog(jcr, jcr->previous_jr.JobId); break; case L_VERIFY_VOLUME_TO_CATALOG: /* * Verify Volume to catalog entries */ Dmsg0(10, "Verify level=volume\n"); get_attributes_and_compare_to_catalog(jcr, jcr->previous_jr.JobId); break; case L_VERIFY_DISK_TO_CATALOG: /* * Verify Disk attributes to catalog */ Dmsg0(10, "Verify level=disk_to_catalog\n"); jcr->sd_msg_thread_done = true; /* no SD msg thread, so it is done */ jcr->SDJobStatus = JS_Terminated; get_attributes_and_compare_to_catalog(jcr, jcr->previous_jr.JobId); break; case L_VERIFY_INIT: /* * Build catalog */ Dmsg0(10, "Verify level=init\n"); jcr->sd_msg_thread_done = true; /* no SD msg thread, so it is done */ jcr->SDJobStatus = JS_Terminated; get_attributes_and_put_in_catalog(jcr); db_end_transaction(jcr, jcr->db); /* terminate any open transaction */ db_write_batch_file_records(jcr); break; default: Jmsg1(jcr, M_FATAL, 0, _("Unimplemented verify level %d\n"), JobLevel); goto bail_out; } status = wait_for_job_termination(jcr); verify_cleanup(jcr, status); return true; bail_out: if (jcr->file_bsock) { jcr->file_bsock->signal(BNET_TERMINATE); jcr->file_bsock->close(); delete jcr->file_bsock; jcr->file_bsock = NULL; } return false; }
int vtape::tape_op(struct mtop *mt_com) { int result=0; int count = mt_com->mt_count; if (!online) { errno = ENOMEDIUM; return -1; } switch (mt_com->mt_op) { case MTRESET: case MTNOP: case MTSETDRVBUFFER: break; default: case MTRAS1: case MTRAS2: case MTRAS3: case MTSETDENSITY: errno = ENOTTY; result = -1; break; case MTFSF: /* Forward space over mt_count filemarks. */ do { result = fsf(); } while (--count > 0 && result == 0); break; case MTBSF: /* Backward space over mt_count filemarks. */ do { result = bsf(); } while (--count > 0 && result == 0); break; case MTFSR: /* Forward space over mt_count records (tape blocks). */ /* file number = 1 block number = 0 file number = 1 block number = 1 mt: /dev/lto2: Erreur d'entree/sortie file number = 2 block number = 0 */ /* tester si on se trouve a la fin du fichier */ result = fsr(mt_com->mt_count); break; case MTBSR: /* Backward space over mt_count records (tape blocks). */ result = bsr(mt_com->mt_count); break; case MTWEOF: /* Write mt_count filemarks. */ do { result = weof(); } while (result == 0 && --count > 0); break; case MTREW: /* Rewind. */ Dmsg0(dbglevel, "rewind vtape\n"); check_eof(); atEOF = atEOD = false; atBOT = true; current_file = 0; current_block = 0; lseek(fd, 0, SEEK_SET); result = !read_fm(VT_READ_EOF); break; case MTOFFL: /* put tape offline */ result = offline(); break; case MTRETEN: /* Re-tension tape. */ result = 0; break; case MTBSFM: /* not used by bareos */ errno = EIO; result = -1; break; case MTFSFM: /* not used by bareos */ errno = EIO; result = -1; break; case MTEOM:/* Go to the end of the recorded media (for appending files). */ while (next_FM) { lseek(fd, next_FM, SEEK_SET); if (read_fm(VT_READ_EOF)) { current_file++; } } boffset_t l; while (::read(fd, &l, sizeof(l)) > 0) { if (l) { lseek(fd, l, SEEK_CUR); } else { ASSERT(0); } Dmsg0(dbglevel, "skip 1 block\n"); } current_block = -1; atEOF = false; atEOD = true; /* file number = 3 block number = -1 */ /* Can be at EOM */ break; case MTERASE: /* not used by bareos */ atEOD = true; atEOF = false; atEOT = false; current_file = 0; current_block = -1; lseek(fd, 0, SEEK_SET); read_fm(VT_READ_EOF); truncate_file(); break; case MTSETBLK: break; case MTSEEK: break; case MTTELL: break; case MTFSS: break; case MTBSS: break; case MTWSM: break; case MTLOCK: break; case MTUNLOCK: break; case MTLOAD: break; case MTUNLOAD: break; case MTCOMPRESSION: break; case MTSETPART: break; case MTMKPART: break; } return result == 0 ? 0 : -1; }
/* * 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": (dev->is_dvd()?"DVD":"disk")); /* * With the reservation system, this should not happen */ if (dev->can_read()) { Mmsg2(jcr->errmsg, "Want to append but %s device %s is busy reading.\n", dev->print_type(), dev->print_name()); Jmsg(jcr, M_FATAL, 0, jcr->errmsg); Dmsg0(50, jcr->errmsg); 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() && strcmp(dcr->VolCatInfo.VolCatStatus, "Recycle") != 0) { 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 */ Mmsg2(jcr->errmsg, _("Could not ready %s device %s for append.\n"), dev->print_type(), dev->print_name()); Jmsg(jcr, M_FATAL, 0, jcr->errmsg); Dmsg0(50, jcr->errmsg); } 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); } if (generate_plugin_event(jcr, bsdEventDeviceOpen, dcr) != bRC_OK) { Mmsg0(jcr->errmsg, _("generate_plugin_event(bsdEventDeviceOpen) Failed\n")); Jmsg(jcr, M_FATAL, 0, jcr->errmsg); Dmsg0(50, jcr->errmsg); goto get_out; } 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()); ok = dir_update_volume_info(dcr, false, false); /* send Volume info to Director */ 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; }
/********************************************************************* * 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: false if failed for any reason * true 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); if (generate_plugin_event(jcr, bsdEventDeviceOpen, dcr) != bRC_OK) { Jmsg(jcr, M_FATAL, 0, _("generate_plugin_event(bsdEventDeviceOpen) Failed\n")); goto get_out; } 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] && strcmp(dcr->media_type, dev->device->media_type) != 0) { RCTX rctx; DIRSTORE *store; int stat; Jmsg4(jcr, M_INFO, 0, _("Changing read device. Want Media Type=\"%s\" have=\"%s\"\n" " %s device=%s\n"), dcr->media_type, dev->device->media_type, dev->print_type(), dev->print_name()); Dmsg4(rdbglvl, "Changing read device. Want Media Type=\"%s\" have=\"%s\"\n" " %s device=%s\n", dcr->media_type, dev->device->media_type, dev->print_type(), dev->print_name()); generate_plugin_event(jcr, bsdEventDeviceClose, dcr); 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 storage 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 */ stat = search_res_for_device(rctx); release_reserve_messages(jcr); /* release queued messages */ unlock_reservations(); if (stat == 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 %s device %s chosen.\n"), dev->print_type(), dev->print_name()); Dmsg2(50, "Media Type change. New read %s device %s chosen.\n", dev->print_type(), dev->print_name()); if (generate_plugin_event(jcr, bsdEventDeviceOpen, dcr) != bRC_OK) { Jmsg(jcr, M_FATAL, 0, _("generate_plugin_event(bsdEventDeviceOpen) Failed\n")); goto get_out; } 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 VolType */ Dmsg1(rdbglvl, "dir_get_volume_info vol=%s\n", dcr->VolumeName); if (!dir_get_volume_info(dcr, 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(SD_READ); dcr->do_load(SD_READ); 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, "open vol=%s\n", dcr->VolumeName); if (!dev->open(dcr, OPEN_READ_ONLY)) { if (!dev->poll) { Jmsg4(jcr, M_WARNING, 0, _("Read open %s device %s Volume \"%s\" failed: ERR=%s\n"), dev->print_type(), 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_TYPE_ERROR: Jmsg(jcr, M_FATAL, 0, dev->errmsg); goto get_out; 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(); 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(); free_volume(dev); } /* Call autochanger only once unless ask_sysop called */ if (try_autochanger) { int stat; Dmsg2(rdbglvl, "calling autoload Vol=%s Slot=%d\n", dcr->VolumeName, dcr->VolCatInfo.Slot); stat = autoload_device(dcr, SD_READ, NULL); if (stat > 0) { try_autochanger = false; continue; /* try reading volume mounted */ } } /* Mount a specific volume and no other */ Dmsg0(rdbglvl, "calling dir_ask_sysop\n"); if (!dir_ask_sysop_to_mount_volume(dcr, SD_READ)) { goto get_out; /* error return */ } /* Volume info is always needed because of VolType */ Dmsg1(150, "dir_get_volume_info vol=%s\n", dcr->VolumeName); if (!dir_get_volume_info(dcr, 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) { Jmsg2(jcr, M_FATAL, 0, _("Too many errors trying to mount %s device %s for reading.\n"), dev->print_type(), 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 %s device %s.\n"), dcr->VolumeName, dev->print_type(), dev->print_name()); get_out: dev->Lock(); dcr->clear_reserved(); /* If failed and not writing plugin close device */ if (!ok && dev->num_writers == 0 && dev->num_reserved() == 0) { generate_plugin_event(jcr, bsdEventDeviceClose, dcr); } /* * 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; }
int list_result(void *vctx, int nb_col, char **row) { SQL_FIELD *field; int i, col_len, max_len = 0; int num_fields; char buf[2000], ewc[30]; LIST_CTX *pctx = (LIST_CTX *)vctx; DB_LIST_HANDLER *send = pctx->send; e_list_type type = pctx->type; B_DB *mdb = pctx->mdb; void *ctx = pctx->ctx; JCR *jcr = pctx->jcr; num_fields = sql_num_fields(mdb); switch (type) { case NF_LIST: case RAW_LIST: /* * No need to calculate things like maximum field lenght for * unformated or raw output. */ break; case HORZ_LIST: case VERT_LIST: if (!pctx->once) { pctx->once = true; Dmsg1(800, "list_result starts looking at %d fields\n", num_fields); /* * Determine column display widths */ sql_field_seek(mdb, 0); for (i = 0; i < num_fields; i++) { Dmsg1(800, "list_result processing field %d\n", i); field = sql_fetch_field(mdb); if (!field) { break; } col_len = cstrlen(field->name); if (type == VERT_LIST) { if (col_len > max_len) { max_len = col_len; } } else { if (sql_field_is_numeric(mdb, field->type) && (int)field->max_length > 0) { /* fixup for commas */ field->max_length += (field->max_length - 1) / 3; } if (col_len < (int)field->max_length) { col_len = field->max_length; } if (col_len < 4 && !sql_field_is_not_null(mdb, field->flags)) { col_len = 4; /* 4 = length of the word "NULL" */ } field->max_length = col_len; /* reset column info */ } } pctx->num_rows++; Dmsg0(800, "list_result finished first loop\n"); if (type == VERT_LIST) { break; } Dmsg1(800, "list_result starts second loop looking at %d fields\n", num_fields); /* * Keep the result to display the same line at the end of the table */ list_dashes(mdb, last_line_handler, pctx); send(ctx, pctx->line); send(ctx, "|"); sql_field_seek(mdb, 0); for (i = 0; i < num_fields; i++) { Dmsg1(800, "list_result looking at field %d\n", i); field = sql_fetch_field(mdb); if (!field) { break; } max_len = max_length(field->max_length); bsnprintf(buf, sizeof(buf), " %-*s |", max_len, field->name); send(ctx, buf); } send(ctx, "\n"); list_dashes(mdb, send, ctx); } break; default: break; } switch (type) { case NF_LIST: case RAW_LIST: Dmsg1(800, "list_result starts third loop looking at %d fields\n", num_fields); sql_field_seek(mdb, 0); for (i = 0; i < num_fields; i++) { field = sql_fetch_field(mdb); if (!field) { break; } if (row[i] == NULL) { bsnprintf(buf, sizeof(buf), " %s", "NULL"); } else { bsnprintf(buf, sizeof(buf), " %s", row[i]); } send(ctx, buf); } if (type != RAW_LIST) { send(ctx, "\n"); } break; case HORZ_LIST: Dmsg1(800, "list_result starts third loop looking at %d fields\n", num_fields); sql_field_seek(mdb, 0); send(ctx, "|"); for (i = 0; i < num_fields; i++) { field = sql_fetch_field(mdb); if (!field) { break; } max_len = max_length(field->max_length); if (row[i] == NULL) { bsnprintf(buf, sizeof(buf), " %-*s |", max_len, "NULL"); } else if (sql_field_is_numeric(mdb, field->type) && !jcr->gui && is_an_integer(row[i])) { bsnprintf(buf, sizeof(buf), " %*s |", max_len, add_commas(row[i], ewc)); } else { bsnprintf(buf, sizeof(buf), " %-*s |", max_len, row[i]); } send(ctx, buf); } send(ctx, "\n"); break; case VERT_LIST: Dmsg1(800, "list_result starts vertical list at %d fields\n", num_fields); sql_field_seek(mdb, 0); for (i = 0; i < num_fields; i++) { field = sql_fetch_field(mdb); if (!field) { break; } if (row[i] == NULL) { bsnprintf(buf, sizeof(buf), " %*s: %s\n", max_len, field->name, "NULL"); } else if (sql_field_is_numeric(mdb, field->type) && !jcr->gui && is_an_integer(row[i])) { bsnprintf(buf, sizeof(buf), " %*s: %s\n", max_len, field->name, add_commas(row[i], ewc)); } else { bsnprintf(buf, sizeof(buf), " %*s: %s\n", max_len, field->name, row[i]); } send(ctx, buf); } send(ctx, "\n"); break; default: break; } return 0; }
/* * If full_list is set, we list vertically, otherwise, we * list on one line horizontally. * Return number of rows */ int list_result(JCR *jcr, B_DB *mdb, DB_LIST_HANDLER *send, void *ctx, e_list_type type) { SQL_FIELD *field; SQL_ROW row; int i, col_len, max_len = 0; int num_fields; char buf[2000], ewc[30]; Dmsg0(800, "list_result starts\n"); if (sql_num_rows(mdb) == 0) { send(ctx, _("No results to list.\n")); return sql_num_rows(mdb); } num_fields = sql_num_fields(mdb); switch (type) { case NF_LIST: case RAW_LIST: /* * No need to calculate things like column widths for * unformated or raw output. */ break; case HORZ_LIST: case VERT_LIST: Dmsg1(800, "list_result starts looking at %d fields\n", num_fields); /* * Determine column display widths */ sql_field_seek(mdb, 0); for (i = 0; i < num_fields; i++) { Dmsg1(800, "list_result processing field %d\n", i); field = sql_fetch_field(mdb); if (!field) { break; } col_len = cstrlen(field->name); if (type == VERT_LIST) { if (col_len > max_len) { max_len = col_len; } } else { if (sql_field_is_numeric(mdb, field->type) && (int)field->max_length > 0) { /* fixup for commas */ field->max_length += (field->max_length - 1) / 3; } if (col_len < (int)field->max_length) { col_len = field->max_length; } if (col_len < 4 && !sql_field_is_not_null(mdb, field->flags)) { col_len = 4; /* 4 = length of the word "NULL" */ } field->max_length = col_len; /* reset column info */ } } break; } Dmsg0(800, "list_result finished first loop\n"); switch (type) { case NF_LIST: case RAW_LIST: Dmsg1(800, "list_result starts second loop looking at %d fields\n", num_fields); while ((row = sql_fetch_row(mdb)) != NULL) { sql_field_seek(mdb, 0); for (i = 0; i < num_fields; i++) { field = sql_fetch_field(mdb); if (!field) { break; } if (row[i] == NULL) { bsnprintf(buf, sizeof(buf), " %s", "NULL"); } else { bsnprintf(buf, sizeof(buf), " %s", row[i]); } send(ctx, buf); } if (type != RAW_LIST) { send(ctx, "\n"); } } break; case HORZ_LIST: Dmsg1(800, "list_result starts second loop looking at %d fields\n", num_fields); list_dashes(mdb, send, ctx); send(ctx, "|"); sql_field_seek(mdb, 0); for (i = 0; i < num_fields; i++) { Dmsg1(800, "list_result looking at field %d\n", i); field = sql_fetch_field(mdb); if (!field) { break; } max_len = max_length(field->max_length); bsnprintf(buf, sizeof(buf), " %-*s |", max_len, field->name); send(ctx, buf); } send(ctx, "\n"); list_dashes(mdb, send, ctx); Dmsg1(800, "list_result starts third loop looking at %d fields\n", num_fields); while ((row = sql_fetch_row(mdb)) != NULL) { sql_field_seek(mdb, 0); send(ctx, "|"); for (i = 0; i < num_fields; i++) { field = sql_fetch_field(mdb); if (!field) { break; } max_len = max_length(field->max_length); if (row[i] == NULL) { bsnprintf(buf, sizeof(buf), " %-*s |", max_len, "NULL"); } else if (sql_field_is_numeric(mdb, field->type) && !jcr->gui && is_an_integer(row[i])) { bsnprintf(buf, sizeof(buf), " %*s |", max_len, add_commas(row[i], ewc)); } else { bsnprintf(buf, sizeof(buf), " %-*s |", max_len, row[i]); } send(ctx, buf); } send(ctx, "\n"); } list_dashes(mdb, send, ctx); break; case VERT_LIST: Dmsg1(800, "list_result starts vertical list at %d fields\n", num_fields); while ((row = sql_fetch_row(mdb)) != NULL) { sql_field_seek(mdb, 0); for (i = 0; i < num_fields; i++) { field = sql_fetch_field(mdb); if (!field) { break; } if (row[i] == NULL) { bsnprintf(buf, sizeof(buf), " %*s: %s\n", max_len, field->name, "NULL"); } else if (sql_field_is_numeric(mdb, field->type) && !jcr->gui && is_an_integer(row[i])) { bsnprintf(buf, sizeof(buf), " %*s: %s\n", max_len, field->name, add_commas(row[i], ewc)); } else { bsnprintf(buf, sizeof(buf), " %*s: %s\n", max_len, field->name, row[i]); } send(ctx, buf); } send(ctx, "\n"); } break; } return sql_num_rows(mdb); }
/* * 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 stat = 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 */ stat = dev->next_vol_timedwait(&timeout); Dmsg2(dbglvl, "Wokeup from sleep on device stat=%d blocked=%s\n", stat, 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 (stat == EINVAL) { berrno be; Jmsg1(jcr, M_FATAL, 0, _("pthread timedwait error. ERR=%s\n"), be.bstrerror(stat)); stat = 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"); stat = 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 */ stat = 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"); stat = W_MOUNT; break; } /* * If we did not timeout, then some event happened, so * return to check if state changed. */ if (stat != ETIMEDOUT) { berrno be; Dmsg2(dbglvl, "Wake return. stat=%d. ERR=%s\n", stat, be.bstrerror(stat)); stat = 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 stat; }
static bRC do_set_scsi_encryption_key(void *value) { DCR *dcr; DEVICE *dev; DEVRES *device; DIRRES *director; char StoredVolEncrKey[MAX_NAME_LENGTH]; char VolEncrKey[MAX_NAME_LENGTH]; /* * Unpack the arguments passed in. */ dcr = (DCR *)value; if (!dcr) { return bRC_Error; } dev = dcr->dev; if (!dev) { return bRC_Error; } device = dev->device; if (!device) { return bRC_Error; } *StoredVolEncrKey = '\0'; if (!get_volume_encryption_key(dcr, StoredVolEncrKey)) { return bRC_Error; } /* * See if a volume encryption key is available. */ if (!*StoredVolEncrKey) { Dmsg0(dbglvl, "scsicrypto-sd: No encryption key to load on device\n"); return bRC_OK; } /* * See if device supports hardware encryption. */ if (!device->drive_crypto_enabled) { Dmsg0(dbglvl, "scsicrypto-sd: Trying to load encryption key on drive without support\n"); Emsg0(M_ERROR, 0, _("scsicrypto-sd: Trying to load encryption key on drive without support\n")); return bRC_Error; } /* * The key passed from the director to the storage daemon is always base64 encoded. */ base64_to_bin(VolEncrKey, sizeof(VolEncrKey), StoredVolEncrKey, strlen(StoredVolEncrKey)); /* * See if we have an key encryption key in the config then the passed key * has been wrapped using RFC3394 key wrapping. We first copy the current * wrapped key into a temporary variable for unwrapping. */ if (dcr->jcr && dcr->jcr->director) { director = dcr->jcr->director; if (director->keyencrkey) { char WrappedVolEncrKey[MAX_NAME_LENGTH]; memcpy(WrappedVolEncrKey, VolEncrKey, MAX_NAME_LENGTH); memset(VolEncrKey, 0, MAX_NAME_LENGTH); if (aes_unwrap((unsigned char *)director->keyencrkey, DEFAULT_PASSPHRASE_LENGTH / 8, (unsigned char *)WrappedVolEncrKey, (unsigned char *)VolEncrKey) != 0) { Dmsg1(dbglvl, "scsicrypto-sd: Failed to unwrap encryption key using %s\n", director->keyencrkey); Emsg0(M_ERROR, 0, _("scsicrypto-sd: Failed to unwrap encryption key, probably wrong KeyEncryptionKey in config\n")); return bRC_Error; } } } Dmsg1(dbglvl, "scsicrypto-sd: Loading new crypto key %s\n", VolEncrKey); P(crypto_operation_mutex); if (set_scsi_encryption_key(dev->fd(), dev->dev_name, VolEncrKey)) { dev->set_crypto_enabled(); V(crypto_operation_mutex); return bRC_OK; } else { V(crypto_operation_mutex); return bRC_Error; } }
/* * Release resources allocated during backup. */ void backup_cleanup(JCR *jcr, int TermCode) { char sdt[50], edt[50], schedt[50]; char ec1[30], ec2[30], ec3[30], ec4[30], ec5[30], compress[50]; char ec6[30], ec7[30], ec8[30], elapsed[50]; char term_code[100], fd_term_msg[100], sd_term_msg[100]; const char *term_msg; int msg_type = M_INFO; MEDIA_DBR mr; CLIENT_DBR cr; double kbps, compression; utime_t RunTime; if (jcr->get_JobLevel() == L_VIRTUAL_FULL) { vbackup_cleanup(jcr, TermCode); return; } Dmsg2(100, "Enter backup_cleanup %d %c\n", TermCode, TermCode); memset(&mr, 0, sizeof(mr)); memset(&cr, 0, sizeof(cr)); update_job_end(jcr, TermCode); if (!db_get_job_record(jcr, jcr->db, &jcr->jr)) { Jmsg(jcr, M_WARNING, 0, _("Error getting Job record for Job report: ERR=%s"), db_strerror(jcr->db)); set_jcr_job_status(jcr, JS_ErrorTerminated); } bstrncpy(cr.Name, jcr->client->name(), sizeof(cr.Name)); if (!db_get_client_record(jcr, jcr->db, &cr)) { Jmsg(jcr, M_WARNING, 0, _("Error getting Client record for Job report: ERR=%s"), db_strerror(jcr->db)); } bstrncpy(mr.VolumeName, jcr->VolumeName, sizeof(mr.VolumeName)); if (!db_get_media_record(jcr, jcr->db, &mr)) { Jmsg(jcr, M_WARNING, 0, _("Error getting Media record for Volume \"%s\": ERR=%s"), mr.VolumeName, db_strerror(jcr->db)); set_jcr_job_status(jcr, JS_ErrorTerminated); } update_bootstrap_file(jcr); switch (jcr->JobStatus) { case JS_Terminated: if (jcr->JobErrors || jcr->SDErrors) { term_msg = _("Backup OK -- with warnings"); } else { term_msg = _("Backup OK"); } break; case JS_Warnings: term_msg = _("Backup OK -- with warnings"); break; case JS_FatalError: case JS_ErrorTerminated: term_msg = _("*** Backup Error ***"); msg_type = M_ERROR; /* Generate error message */ if (jcr->store_bsock) { jcr->store_bsock->signal(BNET_TERMINATE); if (jcr->SD_msg_chan) { pthread_cancel(jcr->SD_msg_chan); } } break; case JS_Canceled: term_msg = _("Backup Canceled"); if (jcr->store_bsock) { jcr->store_bsock->signal(BNET_TERMINATE); if (jcr->SD_msg_chan) { pthread_cancel(jcr->SD_msg_chan); } } break; default: term_msg = term_code; sprintf(term_code, _("Inappropriate term code: %c\n"), jcr->JobStatus); break; } bstrftimes(schedt, sizeof(schedt), jcr->jr.SchedTime); bstrftimes(sdt, sizeof(sdt), jcr->jr.StartTime); bstrftimes(edt, sizeof(edt), jcr->jr.EndTime); RunTime = jcr->jr.EndTime - jcr->jr.StartTime; if (RunTime <= 0) { kbps = 0; } else { kbps = ((double)jcr->jr.JobBytes) / (1000.0 * (double)RunTime); } if (!db_get_job_volume_names(jcr, jcr->db, jcr->jr.JobId, &jcr->VolumeName)) { /* * Note, if the job has erred, most likely it did not write any * tape, so suppress this "error" message since in that case * it is normal. Or look at it the other way, only for a * normal exit should we complain about this error. */ if (jcr->JobStatus == JS_Terminated && jcr->jr.JobBytes) { Jmsg(jcr, M_ERROR, 0, "%s", db_strerror(jcr->db)); } jcr->VolumeName[0] = 0; /* none */ } if (jcr->ReadBytes == 0) { bstrncpy(compress, "None", sizeof(compress)); } else { compression = (double)100 - 100.0 * ((double)jcr->JobBytes / (double)jcr->ReadBytes); if (compression < 0.5) { bstrncpy(compress, "None", sizeof(compress)); } else { bsnprintf(compress, sizeof(compress), "%.1f %%", compression); } } jobstatus_to_ascii(jcr->FDJobStatus, fd_term_msg, sizeof(fd_term_msg)); jobstatus_to_ascii(jcr->SDJobStatus, sd_term_msg, sizeof(sd_term_msg)); // bmicrosleep(15, 0); /* for debugging SIGHUP */ Jmsg(jcr, msg_type, 0, _("%s %s %s (%s): %s\n" " Build OS: %s %s %s\n" " JobId: %d\n" " Job: %s\n" " Backup Level: %s%s\n" " Client: \"%s\" %s\n" " FileSet: \"%s\" %s\n" " Pool: \"%s\" (From %s)\n" " Catalog: \"%s\" (From %s)\n" " Storage: \"%s\" (From %s)\n" " Scheduled time: %s\n" " Start time: %s\n" " End time: %s\n" " Elapsed time: %s\n" " Priority: %d\n" " FD Files Written: %s\n" " SD Files Written: %s\n" " FD Bytes Written: %s (%sB)\n" " SD Bytes Written: %s (%sB)\n" " Rate: %.1f KB/s\n" " Software Compression: %s\n" " VSS: %s\n" " Encryption: %s\n" " Accurate: %s\n" " Volume name(s): %s\n" " Volume Session Id: %d\n" " Volume Session Time: %d\n" " Last Volume Bytes: %s (%sB)\n" " Non-fatal FD errors: %d\n" " SD Errors: %d\n" " FD termination status: %s\n" " SD termination status: %s\n" " Termination: %s\n\n"), BACULA, my_name, VERSION, LSMDATE, edt, HOST_OS, DISTNAME, DISTVER, jcr->jr.JobId, jcr->jr.Job, level_to_str(jcr->get_JobLevel()), jcr->since, jcr->client->name(), cr.Uname, jcr->fileset->name(), jcr->FSCreateTime, jcr->pool->name(), jcr->pool_source, jcr->catalog->name(), jcr->catalog_source, jcr->wstore->name(), jcr->wstore_source, schedt, sdt, edt, edit_utime(RunTime, elapsed, sizeof(elapsed)), jcr->JobPriority, edit_uint64_with_commas(jcr->jr.JobFiles, ec1), edit_uint64_with_commas(jcr->SDJobFiles, ec2), edit_uint64_with_commas(jcr->jr.JobBytes, ec3), edit_uint64_with_suffix(jcr->jr.JobBytes, ec4), edit_uint64_with_commas(jcr->SDJobBytes, ec5), edit_uint64_with_suffix(jcr->SDJobBytes, ec6), kbps, compress, jcr->VSS?_("yes"):_("no"), jcr->Encrypt?_("yes"):_("no"), jcr->accurate?_("yes"):_("no"), jcr->VolumeName, jcr->VolSessionId, jcr->VolSessionTime, edit_uint64_with_commas(mr.VolBytes, ec7), edit_uint64_with_suffix(mr.VolBytes, ec8), jcr->JobErrors, jcr->SDErrors, fd_term_msg, sd_term_msg, term_msg); Dmsg0(100, "Leave backup_cleanup()\n"); }
/* * Store Messages Destination information */ static void store_msgs(LEX *lc, RES_ITEM *item, int index, int pass) { int token; char *cmd; POOLMEM *dest; int dest_len; URES *res_all = (URES *)my_config->m_res_all; Dmsg2(900, "store_msgs pass=%d code=%d\n", pass, item->code); if (pass == 1) { switch (item->code) { case MD_STDOUT: case MD_STDERR: case MD_SYSLOG: /* syslog */ case MD_CONSOLE: case MD_CATALOG: scan_types(lc, (MSGSRES *)(item->value), item->code, NULL, NULL); break; case MD_OPERATOR: /* send to operator */ case MD_DIRECTOR: /* send to Director */ case MD_MAIL: /* mail */ case MD_MAIL_ON_ERROR: /* mail if Job errors */ case MD_MAIL_ON_SUCCESS: /* mail if Job succeeds */ if (item->code == MD_OPERATOR) { cmd = res_all->res_msgs.operator_cmd; } else { cmd = res_all->res_msgs.mail_cmd; } dest = get_pool_memory(PM_MESSAGE); dest[0] = 0; dest_len = 0; /* * Pick up comma separated list of destinations */ for ( ;; ) { token = lex_get_token(lc, T_NAME); /* scan destination */ dest = check_pool_memory_size(dest, dest_len + lc->str_len + 2); if (dest[0] != 0) { pm_strcat(dest, " "); /* separate multiple destinations with space */ dest_len++; } pm_strcat(dest, lc->str); dest_len += lc->str_len; Dmsg2(900, "store_msgs newdest=%s: dest=%s:\n", lc->str, NPRT(dest)); token = lex_get_token(lc, T_SKIP_EOL); if (token == T_COMMA) { continue; /* get another destination */ } if (token != T_EQUALS) { scan_err1(lc, _("expected an =, got: %s"), lc->str); return; } break; } Dmsg1(900, "mail_cmd=%s\n", NPRT(cmd)); scan_types(lc, (MSGSRES *)(item->value), item->code, dest, cmd); free_pool_memory(dest); Dmsg0(900, "done with dest codes\n"); break; case MD_FILE: /* file */ case MD_APPEND: /* append */ dest = get_pool_memory(PM_MESSAGE); /* * Pick up a single destination */ token = lex_get_token(lc, T_NAME); /* scan destination */ pm_strcpy(dest, lc->str); dest_len = lc->str_len; token = lex_get_token(lc, T_SKIP_EOL); Dmsg1(900, "store_msgs dest=%s:\n", NPRT(dest)); if (token != T_EQUALS) { scan_err1(lc, _("expected an =, got: %s"), lc->str); return; } scan_types(lc, (MSGSRES *)(item->value), item->code, dest, NULL); free_pool_memory(dest); Dmsg0(900, "done with dest codes\n"); break; default: scan_err1(lc, _("Unknown item code: %d\n"), item->code); return; } } scan_to_eol(lc); set_bit(index, res_all->hdr.item_present); Dmsg0(900, "Done store_msgs\n"); }
static void list_running_jobs_plain(STATUS_PKT *sp) { JCR *njcr; int len, sec, bps; bool found = false; POOL_MEM msg(PM_MESSAGE); char dt[MAX_TIME_LENGTH], b1[32], b2[32], b3[32], b4[32]; /* * List running jobs */ Dmsg0(1000, "Begin status jcr loop.\n"); len = Mmsg(msg, _("\nRunning Jobs:\n")); sendit(msg, len, sp); const char *vss = ""; #ifdef WIN32_VSS if (g_pVSSClient && g_pVSSClient->IsInitialized()) { vss = "VSS "; } #endif foreach_jcr(njcr) { bstrftime_nc(dt, sizeof(dt), njcr->start_time); if (njcr->JobId == 0) { len = Mmsg(msg, _("Director connected at: %s\n"), dt); } else { len = Mmsg(msg, _("JobId %d Job %s is running.\n"), njcr->JobId, njcr->Job); sendit(msg, len, sp); len = Mmsg(msg, _(" %s%s %s Job started: %s\n"), vss, level_to_str(njcr->getJobLevel()), job_type_to_str(njcr->getJobType()), dt); } sendit(msg, len, sp); if (njcr->JobId == 0) { continue; } sec = time(NULL) - njcr->start_time; if (sec <= 0) { sec = 1; } bps = (int)(njcr->JobBytes / sec); len = Mmsg(msg, _(" Files=%s Bytes=%s Bytes/sec=%s Errors=%d\n" " Bwlimit=%s\n"), edit_uint64_with_commas(njcr->JobFiles, b1), edit_uint64_with_commas(njcr->JobBytes, b2), edit_uint64_with_commas(bps, b3), njcr->JobErrors, edit_uint64_with_commas(njcr->max_bandwidth, b4)); sendit(msg, len, sp); len = Mmsg(msg, _(" Files Examined=%s\n"), edit_uint64_with_commas(njcr->num_files_examined, b1)); sendit(msg, len, sp); if (njcr->JobFiles > 0) { njcr->lock(); len = Mmsg(msg, _(" Processing file: %s\n"), njcr->last_fname); njcr->unlock(); sendit(msg, len, sp); } found = true; if (njcr->store_bsock) { len = Mmsg(msg, " SDReadSeqNo=%" lld " fd=%d\n", njcr->store_bsock->read_seqno, njcr->store_bsock->m_fd); sendit(msg, len, sp); } else { len = Mmsg(msg, _(" SDSocket closed.\n")); sendit(msg, len, sp); } } endeach_jcr(njcr); if (!found) { len = Mmsg(msg, _("No Jobs running.\n")); sendit(msg, len, sp); } len = pm_strcpy(msg, _("====\n")); sendit(msg, len, sp); }
/* * Store a size in bytes */ static void store_int_unit(LEX *lc, RES_ITEM *item, int index, int pass, bool size32, enum unit_type type) { int token; uint64_t uvalue; char bsize[500]; URES *res_all = (URES *)my_config->m_res_all; Dmsg0(900, "Enter store_unit\n"); token = lex_get_token(lc, T_SKIP_EOL); errno = 0; switch (token) { case T_NUMBER: case T_IDENTIFIER: case T_UNQUOTED_STRING: bstrncpy(bsize, lc->str, sizeof(bsize)); /* save first part */ /* * If terminated by space, scan and get modifier */ while (lc->ch == ' ') { token = lex_get_token(lc, T_ALL); switch (token) { case T_NUMBER: case T_IDENTIFIER: case T_UNQUOTED_STRING: bstrncat(bsize, lc->str, sizeof(bsize)); break; } } switch (type) { case STORE_SIZE: if (!size_to_uint64(bsize, &uvalue)) { scan_err1(lc, _("expected a size number, got: %s"), lc->str); return; } break; case STORE_SPEED: if (!speed_to_uint64(bsize, &uvalue)) { scan_err1(lc, _("expected a speed number, got: %s"), lc->str); return; } break; default: scan_err0(lc, _("unknown unit type encountered")); return; } if (size32) { *(item->ui32value) = (uint32_t)uvalue; } else { switch (type) { case STORE_SIZE: *(item->i64value) = uvalue; break; case STORE_SPEED: *(item->ui64value) = uvalue; break; } } break; default: scan_err2(lc, _("expected a %s, got: %s"), (type == STORE_SIZE)?_("size"):_("speed"), lc->str); return; } if (token != T_EOL) { scan_to_eol(lc); } set_bit(index, res_all->hdr.item_present); Dmsg0(900, "Leave store_unit\n"); }
/* * Prune File records from the database. For any Job which * is older than the retention period, we unconditionally delete * all File records for that Job. This is simple enough that no * temporary tables are needed. We simply make an in memory list of * the JobIds meeting the prune conditions, then delete all File records * pointing to each of those JobIds. * * This routine assumes you want the pruning to be done. All checking * must be done before calling this routine. */ int prune_files(UAContext *ua, CLIENT *client) { struct del_ctx del; struct s_count_ctx cnt; POOL_MEM query(PM_MESSAGE); utime_t now, period; CLIENT_DBR cr; char ed1[50], ed2[50]; db_lock(ua->db); memset(&cr, 0, sizeof(cr)); memset(&del, 0, sizeof(del)); bstrncpy(cr.Name, client->hdr.name, sizeof(cr.Name)); if (!db_create_client_record(ua->jcr, ua->db, &cr)) { db_unlock(ua->db); return 0; } period = client->FileRetention; now = (utime_t)time(NULL); /* Select Jobs -- for counting */ Mmsg(query, count_select_job, edit_int64(now - period, ed1), edit_int64(cr.ClientId, ed2)); Dmsg3(050, "select now=%u period=%u sql=%s\n", (uint32_t)now, (uint32_t)period, query.c_str()); cnt.count = 0; if (!db_sql_query(ua->db, query.c_str(), del_count_handler, (void *)&cnt)) { ua->error_msg("%s", db_strerror(ua->db)); Dmsg0(050, "Count failed\n"); goto bail_out; } if (cnt.count == 0) { if (ua->verbose) { ua->warning_msg(_("No Files found to prune.\n")); } goto bail_out; } if (cnt.count < MAX_DEL_LIST_LEN) { del.max_ids = cnt.count + 1; } else { del.max_ids = MAX_DEL_LIST_LEN; } del.tot_ids = 0; del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids); /* Now process same set but making a delete list */ Mmsg(query, select_job, edit_int64(now - period, ed1), edit_int64(cr.ClientId, ed2)); db_sql_query(ua->db, query.c_str(), file_delete_handler, (void *)&del); purge_files_from_job_list(ua, del); edit_uint64_with_commas(del.num_del, ed1); ua->info_msg(_("Pruned Files from %s Jobs for client %s from catalog.\n"), ed1, client->name()); bail_out: db_unlock(ua->db); if (del.JobId) { free(del.JobId); } return 1; }
/* * When a filemark is encountered while reading, the following happens. If * there are data remaining in the buffer when the filemark is found, the * buffered data is returned. The next read returns zero bytes. The following * read returns data from the next file. The end of recorded data is signaled * by returning zero bytes for two consecutive read calls. The third read * returns an error. */ ssize_t vtape::d_read(int, void *buffer, size_t count) { ASSERT(online); ASSERT(current_file >= 0); ssize_t nb; uint32_t s; Dmsg2(dbglevel*2, "read %i:%i\n", current_file, current_block); if (atEOT || atEOD) { errno = EIO; return -1; } if (atEOF) { if (!next_FM) { atEOD = true; atEOF = false; current_block=-1; return 0; } atEOF=false; } check_eof(); atEOD = atBOT = false; /* reading size of data */ nb = ::read(fd, &s, sizeof(uint32_t)); if (nb <= 0) { atEOF = true; /* TODO: check this */ return 0; } if (s > count) { /* not enough buffer to read block */ Dmsg2(dbglevel, "Need more buffer to read next block %i > %i\n",s,count); lseek(fd, s, SEEK_CUR); errno = ENOMEM; return -1; } if (!s) { /* EOF */ atEOF = true; if (read_fm(VT_SKIP_EOF)) { current_file++; } return 0; } /* reading data itself */ nb = ::read(fd, buffer, s); if (nb != (ssize_t)s) { /* read error */ errno=EIO; atEOT=true; current_block = -1; Dmsg0(dbglevel, "EOT during reading\n"); return -1; } /* read ok */ if (current_block >= 0) { current_block++; } return nb; }
/* * Pruning Jobs is a bit more complicated than purging Files * because we delete Job records only if there is a more current * backup of the FileSet. Otherwise, we keep the Job record. * In other words, we never delete the only Job record that * contains a current backup of a FileSet. This prevents the * Volume from being recycled and destroying a current backup. * * For Verify Jobs, we do not delete the last InitCatalog. * * For Restore Jobs there are no restrictions. */ int prune_jobs(UAContext *ua, CLIENT *client, int JobType) { struct del_ctx del; POOL_MEM query(PM_MESSAGE); utime_t now, period; CLIENT_DBR cr; char ed1[50], ed2[50]; db_lock(ua->db); memset(&cr, 0, sizeof(cr)); memset(&del, 0, sizeof(del)); bstrncpy(cr.Name, client->name(), sizeof(cr.Name)); if (!db_create_client_record(ua->jcr, ua->db, &cr)) { db_unlock(ua->db); return 0; } period = client->JobRetention; now = (utime_t)time(NULL); /* Drop any previous temporary tables still there */ drop_temp_tables(ua); /* Create temp tables and indicies */ if (!create_temp_tables(ua)) { goto bail_out; } /* * Select all files that are older than the JobRetention period * and stuff them into the "DeletionCandidates" table. */ edit_int64(now - period, ed1); Mmsg(query, insert_delcand, (char)JobType, ed1, edit_int64(cr.ClientId, ed2)); if (!db_sql_query(ua->db, query.c_str(), NULL, (void *)NULL)) { if (ua->verbose) { ua->error_msg("%s", db_strerror(ua->db)); } Dmsg0(050, "insert delcand failed\n"); goto bail_out; } del.max_ids = 100; del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids); del.PurgedFiles = (char *)malloc(del.max_ids); /* ed1 = JobTDate */ edit_int64(cr.ClientId, ed2); switch (JobType) { case JT_BACKUP: Mmsg(query, select_backup_del, ed1, ed2); break; case JT_RESTORE: Mmsg(query, select_restore_del, ed1, ed2); break; case JT_VERIFY: Mmsg(query, select_verify_del, ed1, ed2); break; case JT_ADMIN: Mmsg(query, select_admin_del, ed1, ed2); break; case JT_COPY: Mmsg(query, select_copy_del, ed1, ed2); break; case JT_MIGRATE: Mmsg(query, select_migrate_del, ed1, ed2); break; } Dmsg1(150, "Query=%s\n", query.c_str()); if (!db_sql_query(ua->db, query.c_str(), job_delete_handler, (void *)&del)) { ua->error_msg("%s", db_strerror(ua->db)); } purge_job_list_from_catalog(ua, del); if (del.num_del > 0) { ua->info_msg(_("Pruned %d %s for client %s from catalog.\n"), del.num_del, del.num_del==1?_("Job"):_("Jobs"), client->name()); } else if (ua->verbose) { ua->info_msg(_("No Jobs found to prune.\n")); } bail_out: drop_temp_tables(ua); db_unlock(ua->db); if (del.JobId) { free(del.JobId); } if (del.PurgedFiles) { free(del.PurgedFiles); } return 1; }
/* * Create a Job Control Record and link it into JCR chain * Returns newly allocated JCR * Note, since each daemon has a different JCR, he passes * us the size. */ JCR *new_jcr(int size, JCR_free_HANDLER *daemon_free_jcr) { JCR *jcr; MQUEUE_ITEM *item = NULL; struct sigaction sigtimer; int status; Dmsg0(dbglvl, "Enter new_jcr\n"); status = pthread_once(&key_once, create_jcr_key); if (status != 0) { berrno be; Jmsg1(NULL, M_ABORT, 0, _("pthread_once failed. ERR=%s\n"), be.bstrerror(status)); } jcr = (JCR *)malloc(size); memset(jcr, 0, size); /* Note for the director, this value is changed in jobq.c */ jcr->my_thread_id = pthread_self(); jcr->msg_queue = New(dlist(item, &item->link)); if ((status = pthread_mutex_init(&jcr->msg_queue_mutex, NULL)) != 0) { berrno be; Jmsg(NULL, M_ABORT, 0, _("Could not init msg_queue mutex. ERR=%s\n"), be.bstrerror(status)); } jcr->job_end_push.init(1, false); jcr->sched_time = time(NULL); jcr->initial_sched_time = jcr->sched_time; jcr->daemon_free_jcr = daemon_free_jcr; /* plug daemon free routine */ jcr->init_mutex(); jcr->inc_use_count(); jcr->VolumeName = get_pool_memory(PM_FNAME); jcr->VolumeName[0] = 0; jcr->errmsg = get_pool_memory(PM_MESSAGE); jcr->errmsg[0] = 0; jcr->comment = get_pool_memory(PM_FNAME); jcr->comment[0] = 0; /* Setup some dummy values */ bstrncpy(jcr->Job, "*System*", sizeof(jcr->Job)); jcr->JobId = 0; jcr->setJobType(JT_SYSTEM); /* internal job until defined */ jcr->setJobLevel(L_NONE); jcr->setJobStatus(JS_Created); /* ready to run */ sigtimer.sa_flags = 0; sigtimer.sa_handler = timeout_handler; sigfillset(&sigtimer.sa_mask); sigaction(TIMEOUT_SIGNAL, &sigtimer, NULL); /* * Locking jobs is a global lock that is needed * so that the Director can stop new jobs from being * added to the jcr chain while it processes a new * conf file and does the job_end_push(). */ lock_jobs(); lock_jcr_chain(); if (!jcrs) { jcrs = New(dlist(jcr, &jcr->link)); } jcrs->append(jcr); unlock_jcr_chain(); unlock_jobs(); return jcr; }
/* * This function returns a truth value depending on the state of soft quotas. * The function compares the total jobbytes against the soft quota. * * If the quotas are not strict (the default) it checks the jobbytes value against * the quota limit previously when running in burst mode during the grace period. * * It checks if we have exceeded our grace time. * * If the value is true, the quota is reached and termination of the job should occur. * * Returns: true on reaching soft quota * false on not reaching soft quota. */ bool quota_check_softquotas(JCR *jcr) { bool retval = false; uint64_t now = (uint64_t)time(NULL); /* * Do not check if the softquota is not set */ if (jcr->res.client->SoftQuota == 0) { goto bail_out; } Dmsg1(dbglvl, "Checking soft quotas for JobId %d\n", jcr->JobId); if (!jcr->HasQuota) { if (jcr->res.client->QuotaIncludeFailedJobs) { if (!db_get_quota_jobbytes(jcr, jcr->db, &jcr->jr, jcr->res.client->JobRetention)) { Jmsg(jcr, M_WARNING, 0, _("Error getting Quota value: ERR=%s"), db_strerror(jcr->db)); goto bail_out; } Dmsg0(dbglvl, "Quota Includes Failed Jobs\n"); } else { if (!db_get_quota_jobbytes_nofailed(jcr, jcr->db, &jcr->jr, jcr->res.client->JobRetention)) { Jmsg(jcr, M_WARNING, 0, _("Error getting Quota value: ERR=%s"), db_strerror(jcr->db)); goto bail_out; } Jmsg(jcr, M_INFO, 0, _("Quota does NOT include Failed Jobs\n")); } jcr->HasQuota = true; } Dmsg2(dbglvl, "Quota for %s is %llu\n", jcr->jr.Name, jcr->jr.JobSumTotalBytes); Dmsg2(dbglvl, "QuotaLimit for %s is %llu\n", jcr->jr.Name, jcr->res.client->QuotaLimit); Dmsg2(dbglvl, "HardQuota for %s is %llu\n", jcr->jr.Name, jcr->res.client->HardQuota); Dmsg2(dbglvl, "SoftQuota for %s is %llu\n", jcr->jr.Name, jcr->res.client->SoftQuota); Dmsg2(dbglvl, "SoftQuota Grace Period for %s is %d\n", jcr->jr.Name, jcr->res.client->SoftQuotaGracePeriod); Dmsg2(dbglvl, "SoftQuota Grace Time for %s is %d\n", jcr->jr.Name, jcr->res.client->GraceTime); if (jcr->jr.JobSumTotalBytes > jcr->res.client->SoftQuota) { /* * Only warn once about softquotas in the job * Check if gracetime has been set */ if (jcr->res.client->GraceTime == 0 && jcr->res.client->SoftQuotaGracePeriod) { Dmsg1(dbglvl, "update_quota_gracetime: %d\n", now); if (!db_update_quota_gracetime(jcr, jcr->db, &jcr->jr)) { Jmsg(jcr, M_WARNING, 0, _("Error setting Quota gracetime: ERR=%s"), db_strerror(jcr->db)); } else { Jmsg(jcr, M_ERROR, 0, _("Softquota Exceeded, Grace Period starts now.\n")); } jcr->res.client->GraceTime = now; goto bail_out; } else if (jcr->res.client->SoftQuotaGracePeriod && (now - (uint64_t)jcr->res.client->GraceTime) < (uint64_t)jcr->res.client->SoftQuotaGracePeriod) { Jmsg(jcr, M_ERROR, 0, _("Softquota Exceeded, will be enforced after Grace Period expires.\n")); } else if (jcr->res.client->SoftQuotaGracePeriod && (now - (uint64_t)jcr->res.client->GraceTime) > (uint64_t)jcr->res.client->SoftQuotaGracePeriod) { /* * If gracetime has expired update else check more if not set softlimit yet then set and bail out. */ if (jcr->res.client->QuotaLimit < 1) { if (!db_update_quota_softlimit(jcr, jcr->db, &jcr->jr)) { Jmsg(jcr, M_WARNING, 0, _("Error setting Quota Softlimit: ERR=%s"), db_strerror(jcr->db)); } Jmsg(jcr, M_WARNING, 0, _("Softquota Exceeded and Grace Period expired.\n")); Jmsg(jcr, M_INFO, 0, _("Setting Burst Quota to %d Bytes.\n"), jcr->jr.JobSumTotalBytes); jcr->res.client->QuotaLimit = jcr->jr.JobSumTotalBytes; retval = true; goto bail_out; } else { /* * If we use strict quotas enforce the pure soft quota limit. */ if (jcr->res.client->StrictQuotas && jcr->jr.JobSumTotalBytes > jcr->res.client->SoftQuota) { Dmsg0(dbglvl, "Softquota Exceeded, enforcing Strict Quota Limit.\n"); retval = true; goto bail_out; } else if (!jcr->res.client->StrictQuotas && jcr->jr.JobSumTotalBytes >= jcr->res.client->QuotaLimit) { /* * If strict quotas turned off use the last known limit */ Jmsg(jcr, M_WARNING, 0, _("Softquota Exceeded, enforcing Burst Quota Limit.\n")); retval = true; goto bail_out; } } } } bail_out: return retval; }
bool CONFIG::parse_config_file(const char *cf, void *caller_ctx, LEX_ERROR_HANDLER *scan_error, LEX_WARNING_HANDLER *scan_warning, int32_t err_type) { bool result = true; LEX *lc = NULL; int token, i, pass; int res_type = 0; enum parse_state state = p_none; RES_TABLE *res_table = NULL; RES_ITEM *items = NULL; RES_ITEM *item = NULL; int level = 0; /* * Make two passes. The first builds the name symbol table, * and the second picks up the items. */ Dmsg0(900, "Enter parse_config()\n"); for (pass = 1; pass <= 2; pass++) { Dmsg1(900, "parse_config pass %d\n", pass); if ((lc = lex_open_file(lc, cf, scan_error, scan_warning)) == NULL) { berrno be; /* * We must create a lex packet to print the error */ lc = (LEX *)malloc(sizeof(LEX)); memset(lc, 0, sizeof(LEX)); if (scan_error) { lc->scan_error = scan_error; } else { lex_set_default_error_handler(lc); } if (scan_warning) { lc->scan_warning = scan_warning; } else { lex_set_default_warning_handler(lc); } lex_set_error_handler_error_type(lc, err_type) ; scan_err2(lc, _("Cannot open config file \"%s\": %s\n"), cf, be.bstrerror()); free(lc); return 0; } lex_set_error_handler_error_type(lc, err_type); lc->error_counter = 0; lc->caller_ctx = caller_ctx; while ((token=lex_get_token(lc, T_ALL)) != T_EOF) { Dmsg3(900, "parse state=%d pass=%d got token=%s\n", state, pass, lex_tok_to_str(token)); switch (state) { case p_none: if (token == T_EOL) { break; } else if (token == T_UTF8_BOM) { /* * We can assume the file is UTF-8 as we have seen a UTF-8 BOM */ break; } else if (token == T_UTF16_BOM) { scan_err0(lc, _("Currently we cannot handle UTF-16 source files. " "Please convert the conf file to UTF-8\n")); goto bail_out; } else if (token != T_IDENTIFIER) { scan_err1(lc, _("Expected a Resource name identifier, got: %s"), lc->str); goto bail_out; } res_table = get_resource_table(lc->str); if(res_table && res_table->items) { items = res_table->items; state = p_resource; res_type = res_table->rcode; init_resource(res_type, items, pass); } if (state == p_none) { scan_err1(lc, _("expected resource name, got: %s"), lc->str); goto bail_out; } break; case p_resource: switch (token) { case T_BOB: level++; break; case T_IDENTIFIER: if (level != 1) { scan_err1(lc, _("not in resource definition: %s"), lc->str); goto bail_out; } i = get_resource_item_index(items, lc->str); if (i>=0) { item = &items[i]; /* * If the CFG_ITEM_NO_EQUALS flag is set we do NOT * scan for = after the keyword */ if (!(item->flags & CFG_ITEM_NO_EQUALS)) { token = lex_get_token(lc, T_SKIP_EOL); Dmsg1 (900, "in T_IDENT got token=%s\n", lex_tok_to_str(token)); if (token != T_EQUALS) { scan_err1(lc, _("expected an equals, got: %s"), lc->str); goto bail_out; } } /* * See if we are processing a deprecated keyword if so warn the user about it. */ if (item->flags & CFG_ITEM_DEPRECATED) { scan_warn2(lc, _("using deprecated keyword %s on line %d"), item->name, lc->line_no); /* * As we only want to warn we continue parsing the config. So no goto bail_out here. */ } Dmsg1(800, "calling handler for %s\n", item->name); /* * Call item handler */ if (!store_resource(item->type, lc, item, i, pass)) { /* * None of the generic types fired if there is a registered callback call that now. */ if (m_store_res) { m_store_res(lc, item, i, pass); } } } else { Dmsg2(900, "level=%d id=%s\n", level, lc->str); Dmsg1(900, "Keyword = %s\n", lc->str); scan_err1(lc, _("Keyword \"%s\" not permitted in this resource.\n" "Perhaps you left the trailing brace off of the previous resource."), lc->str); goto bail_out; } break; case T_EOB: level--; state = p_none; Dmsg0(900, "T_EOB => define new resource\n"); if (((URES *)m_res_all)->hdr.name == NULL) { scan_err0(lc, _("Name not specified for resource")); goto bail_out; } /* save resource */ if (!save_resource(res_type, items, pass)) { scan_err0(lc, _("save_resource failed")); goto bail_out; }; break; case T_EOL: break; default: scan_err2(lc, _("unexpected token %d %s in resource definition"), token, lex_tok_to_str(token)); goto bail_out; } break; default: scan_err1(lc, _("Unknown parser state %d\n"), state); goto bail_out; } } if (state != p_none) { scan_err0(lc, _("End of conf file reached with unclosed resource.")); goto bail_out; } if (debug_level >= 900 && pass == 2) { int i; for (i = m_r_first; i <= m_r_last; i++) { dump_resource(i, m_res_head[i-m_r_first], prtmsg, NULL, false); } } if (lc->error_counter > 0) { result = false; } lc = lex_close_file(lc); } Dmsg0(900, "Leave parse_config_file()\n"); return result; bail_out: if (lc) { lc = lex_close_file(lc); } return false; }
/* * Create the file, or the directory * * fname is the original filename * ofile is the output filename (may be in a different directory) * * Returns: CF_SKIP if file should be skipped * CF_ERROR on error * CF_EXTRACT file created and data to restore * CF_CREATED file created no data to restore * * Note, we create the file here, except for special files, * we do not set the attributes because we want to first * write the file, then when the writing is done, set the * attributes. * * So, we return with the file descriptor open for normal files. */ int create_file(JCR *jcr, ATTR *attr, BFILE *bfd, int replace) { mode_t new_mode, parent_mode; int flags; uid_t uid; gid_t gid; int pnl; bool exists = false; struct stat mstatp; #ifndef HAVE_WIN32 bool isOnRoot; #endif bfd->reparse_point = false; if (is_win32_stream(attr->data_stream)) { set_win32_backup(bfd); } else { set_portable_backup(bfd); } new_mode = attr->statp.st_mode; Dmsg3(200, "type=%d newmode=%x file=%s\n", attr->type, new_mode, attr->ofname); parent_mode = S_IWUSR | S_IXUSR | new_mode; gid = attr->statp.st_gid; uid = attr->statp.st_uid; #ifdef HAVE_WIN32 if (!bfd->use_backup_api) { /* * Eliminate invalid windows filename characters from foreign filenames */ char *ch = (char *)attr->ofname; if (ch[0] != 0 && ch[1] != 0) { ch += 2; while (*ch) { switch (*ch) { case ':': case '<': case '>': case '*': case '?': case '|': *ch = '_'; break; } ch++; } } } #endif Dmsg2(400, "Replace=%c %d\n", (char)replace, replace); if (lstat(attr->ofname, &mstatp) == 0) { exists = true; switch (replace) { case REPLACE_IFNEWER: if (attr->statp.st_mtime <= mstatp.st_mtime) { Qmsg(jcr, M_INFO, 0, _("File skipped. Not newer: %s\n"), attr->ofname); return CF_SKIP; } break; case REPLACE_IFOLDER: if (attr->statp.st_mtime >= mstatp.st_mtime) { Qmsg(jcr, M_INFO, 0, _("File skipped. Not older: %s\n"), attr->ofname); return CF_SKIP; } break; case REPLACE_NEVER: /* * Set attributes if we created this directory */ if (attr->type == FT_DIREND && path_list_lookup(jcr->path_list, attr->ofname)) { break; } Qmsg(jcr, M_INFO, 0, _("File skipped. Already exists: %s\n"), attr->ofname); return CF_SKIP; case REPLACE_ALWAYS: break; } } switch (attr->type) { case FT_RAW: /* Raw device to be written */ case FT_FIFO: /* FIFO to be written to */ case FT_LNKSAVED: /* Hard linked, file already saved */ case FT_LNK: case FT_SPEC: /* Fifo, ... to be backed up */ case FT_REGE: /* Empty file */ case FT_REG: /* Regular file */ /* * Note, we do not delete FT_RAW because these are device files * or FIFOs that should already exist. If we blow it away, * we may blow away a FIFO that is being used to read the * restore data, or we may blow away a partition definition. */ if (exists && attr->type != FT_RAW && attr->type != FT_FIFO) { /* Get rid of old copy */ Dmsg1(400, "unlink %s\n", attr->ofname); if (secure_erase(jcr, attr->ofname) == -1) { berrno be; Qmsg(jcr, M_ERROR, 0, _("File %s already exists and could not be replaced. ERR=%s.\n"), attr->ofname, be.bstrerror()); /* Continue despite error */ } } /* * Here we do some preliminary work for all the above * types to create the path to the file if it does * not already exist. Below, we will split to * do the file type specific work */ pnl = separate_path_and_file(jcr, attr->fname, attr->ofname); if (pnl < 0) { return CF_ERROR; } /* * If path length is <= 0 we are making a file in the root * directory. Assume that the directory already exists. */ if (pnl > 0) { char savechr; savechr = attr->ofname[pnl]; attr->ofname[pnl] = 0; /* terminate path */ if (!path_already_seen(jcr, attr->ofname, pnl)) { Dmsg1(400, "Make path %s\n", attr->ofname); /* * If we need to make the directory, ensure that it is with * execute bit set (i.e. parent_mode), and preserve what already * exists. Normally, this should do nothing. */ if (!makepath(attr, attr->ofname, parent_mode, parent_mode, uid, gid, 1)) { Dmsg1(10, "Could not make path. %s\n", attr->ofname); attr->ofname[pnl] = savechr; /* restore full name */ return CF_ERROR; } } attr->ofname[pnl] = savechr; /* restore full name */ } /* * Now we do the specific work for each file type */ switch(attr->type) { case FT_REGE: case FT_REG: Dmsg1(100, "Create=%s\n", attr->ofname); flags = O_WRONLY | O_CREAT | O_TRUNC | O_BINARY; /* O_NOFOLLOW; */ if (IS_CTG(attr->statp.st_mode)) { flags |= O_CTG; /* set contiguous bit if needed */ } if (is_bopen(bfd)) { Qmsg1(jcr, M_ERROR, 0, _("bpkt already open fid=%d\n"), bfd->fid); bclose(bfd); } if (bopen(bfd, attr->ofname, flags, 0, attr->statp.st_rdev) < 0) { berrno be; be.set_errno(bfd->berrno); Qmsg2(jcr, M_ERROR, 0, _("Could not create %s: ERR=%s\n"), attr->ofname, be.bstrerror()); Dmsg2(100,"Could not create %s: ERR=%s\n", attr->ofname, be.bstrerror()); return CF_ERROR; } return CF_EXTRACT; #ifndef HAVE_WIN32 /* None of these exist in MS Windows */ case FT_RAW: /* Bareos raw device e.g. /dev/sda1 */ case FT_FIFO: /* Bareos fifo to save data */ case FT_SPEC: flags = O_WRONLY | O_BINARY; isOnRoot = bstrcmp(attr->fname, attr->ofname) ? 1 : 0; if (S_ISFIFO(attr->statp.st_mode)) { Dmsg1(400, "Restore fifo: %s\n", attr->ofname); if (mkfifo(attr->ofname, attr->statp.st_mode) != 0 && errno != EEXIST) { berrno be; Qmsg2(jcr, M_ERROR, 0, _("Cannot make fifo %s: ERR=%s\n"), attr->ofname, be.bstrerror()); return CF_ERROR; } } else if (S_ISSOCK(attr->statp.st_mode)) { Dmsg1(200, "Skipping restore of socket: %s\n", attr->ofname); #ifdef S_IFDOOR /* Solaris high speed RPC mechanism */ } else if (S_ISDOOR(attr->statp.st_mode)) { Dmsg1(200, "Skipping restore of door file: %s\n", attr->ofname); #endif #ifdef S_IFPORT /* Solaris event port for handling AIO */ } else if (S_ISPORT(attr->statp.st_mode)) { Dmsg1(200, "Skipping restore of event port file: %s\n", attr->ofname); #endif } else if ((S_ISBLK(attr->statp.st_mode) || S_ISCHR(attr->statp.st_mode)) && !exists && isOnRoot) { /* * Fatal: Restoring a device on root-file system, but device node does not exist. * Should not create a dump file. */ Qmsg1(jcr, M_ERROR, 0, _("Device restore on root failed, device %s missing.\n"), attr->fname); return CF_ERROR; } else if (S_ISBLK(attr->statp.st_mode) || S_ISCHR(attr->statp.st_mode)) { Dmsg1(400, "Restoring a device as a file: %s\n", attr->ofname); flags = O_WRONLY | O_CREAT | O_TRUNC | O_BINARY; } else { Dmsg1(400, "Restore node: %s\n", attr->ofname); if (mknod(attr->ofname, attr->statp.st_mode, attr->statp.st_rdev) != 0 && errno != EEXIST) { berrno be; Qmsg2(jcr, M_ERROR, 0, _("Cannot make node %s: ERR=%s\n"), attr->ofname, be.bstrerror()); return CF_ERROR; } } /* * Here we are going to attempt to restore to a FIFO, which * means that the FIFO must already exist, AND there must * be some process already attempting to read from the * FIFO, so we open it write-only. */ if (attr->type == FT_RAW || attr->type == FT_FIFO) { btimer_t *tid; Dmsg1(400, "FT_RAW|FT_FIFO %s\n", attr->ofname); /* * Timeout open() in 60 seconds */ if (attr->type == FT_FIFO) { Dmsg0(400, "Set FIFO timer\n"); tid = start_thread_timer(jcr, pthread_self(), 60); } else { tid = NULL; } if (is_bopen(bfd)) { Qmsg1(jcr, M_ERROR, 0, _("bpkt already open fid=%d\n"), bfd->fid); } Dmsg2(400, "open %s flags=0x%x\n", attr->ofname, flags); if ((bopen(bfd, attr->ofname, flags, 0, 0)) < 0) { berrno be; be.set_errno(bfd->berrno); Qmsg2(jcr, M_ERROR, 0, _("Could not open %s: ERR=%s\n"), attr->ofname, be.bstrerror()); Dmsg2(400, "Could not open %s: ERR=%s\n", attr->ofname, be.bstrerror()); stop_thread_timer(tid); return CF_ERROR; } stop_thread_timer(tid); return CF_EXTRACT; } Dmsg1(400, "FT_SPEC %s\n", attr->ofname); return CF_CREATED; case FT_LNKSAVED: /* Hard linked, file already saved */ Dmsg2(130, "Hard link %s => %s\n", attr->ofname, attr->olname); if (link(attr->olname, attr->ofname) != 0) { berrno be; #ifdef HAVE_CHFLAGS struct stat s; /* * If using BSD user flags, maybe has a file flag preventing this. * So attempt to disable, retry link, and reset flags. * Note that BSD securelevel may prevent disabling flag. */ if (stat(attr->olname, &s) == 0 && s.st_flags != 0) { if (chflags(attr->olname, 0) == 0) { if (link(attr->olname, attr->ofname) != 0) { /* * Restore original file flags even when linking failed */ if (chflags(attr->olname, s.st_flags) < 0) { Qmsg2(jcr, M_ERROR, 0, _("Could not restore file flags for file %s: ERR=%s\n"), attr->olname, be.bstrerror()); } #endif /* HAVE_CHFLAGS */ Qmsg3(jcr, M_ERROR, 0, _("Could not hard link %s -> %s: ERR=%s\n"), attr->ofname, attr->olname, be.bstrerror()); Dmsg3(200, "Could not hard link %s -> %s: ERR=%s\n", attr->ofname, attr->olname, be.bstrerror()); return CF_ERROR; #ifdef HAVE_CHFLAGS } /* * Finally restore original file flags */ if (chflags(attr->olname, s.st_flags) < 0) { Qmsg2(jcr, M_ERROR, 0, _("Could not restore file flags for file %s: ERR=%s\n"), attr->olname, be.bstrerror()); } } else { Qmsg2(jcr, M_ERROR, 0, _("Could not reset file flags for file %s: ERR=%s\n"), attr->olname, be.bstrerror()); } } else { Qmsg3(jcr, M_ERROR, 0, _("Could not hard link %s -> %s: ERR=%s\n"), attr->ofname, attr->olname, be.bstrerror()); return CF_ERROR; } #endif /* HAVE_CHFLAGS */ } return CF_CREATED; #endif /* HAVE_WIN32 */ #ifdef HAVE_WIN32 case FT_LNK: /* * Handle Windows Symlink-Like Reparse Points * - Directory Symlinks * - File Symlinks * - Volume Mount Points * - Junctions */ Dmsg2(130, "FT_LNK should restore: %s -> %s\n", attr->ofname, attr->olname); if (attr->statp.st_rdev & FILE_ATTRIBUTE_VOLUME_MOUNT_POINT) { /* * We do not restore volume mount points */ Dmsg0(130, "Skipping Volume Mount Point\n"); return CF_SKIP; } if (win32_symlink(attr->olname, attr->ofname, attr->statp.st_rdev) != 0 && errno != EEXIST) { berrno be; Qmsg3(jcr, M_ERROR, 0, _("Could not symlink %s -> %s: ERR=%s\n"), attr->ofname, attr->olname, be.bstrerror()); return CF_ERROR; } return CF_CREATED; #else case FT_LNK: /* * Unix/Linux symlink handling */ Dmsg2(130, "FT_LNK should restore: %s -> %s\n", attr->ofname, attr->olname); if (symlink(attr->olname, attr->ofname) != 0 && errno != EEXIST) { berrno be; Qmsg3(jcr, M_ERROR, 0, _("Could not symlink %s -> %s: ERR=%s\n"), attr->ofname, attr->olname, be.bstrerror()); return CF_ERROR; } return CF_CREATED; #endif } /* End inner switch */ case FT_REPARSE: case FT_JUNCTION: bfd->reparse_point = true; /* * Fall through wanted */ case FT_DIRBEGIN: case FT_DIREND: Dmsg2(200, "Make dir mode=%o dir=%s\n", new_mode, attr->ofname); if (!makepath(attr, attr->ofname, new_mode, parent_mode, uid, gid, 0)) { return CF_ERROR; } /* * If we are using the Win32 Backup API, we open the directory so * that the security info will be read and saved. */ if (!is_portable_backup(bfd)) { if (is_bopen(bfd)) { Qmsg1(jcr, M_ERROR, 0, _("bpkt already open fid=%d\n"), bfd->fid); } if (bopen(bfd, attr->ofname, O_WRONLY | O_BINARY, 0, attr->statp.st_rdev) < 0) { berrno be; be.set_errno(bfd->berrno); #ifdef HAVE_WIN32 /* * Check for trying to create a drive, if so, skip */ if (attr->ofname[1] == ':' && IsPathSeparator(attr->ofname[2]) && attr->ofname[3] == '\0') { return CF_SKIP; } #endif Qmsg2(jcr, M_ERROR, 0, _("Could not open %s: ERR=%s\n"), attr->ofname, be.bstrerror()); return CF_ERROR; } return CF_EXTRACT; } else { return CF_CREATED; } case FT_DELETED: Qmsg2(jcr, M_INFO, 0, _("Original file %s have been deleted: type=%d\n"), attr->fname, attr->type); break; /* * The following should not occur */ case FT_NOACCESS: case FT_NOFOLLOW: case FT_NOSTAT: case FT_DIRNOCHG: case FT_NOCHG: case FT_ISARCH: case FT_NORECURSE: case FT_NOFSCHG: case FT_NOOPEN: Qmsg2(jcr, M_ERROR, 0, _("Original file %s not saved: type=%d\n"), attr->fname, attr->type); break; default: Qmsg2(jcr, M_ERROR, 0, _("Unknown file type %d; not restored: %s\n"), attr->type, attr->fname); break; } return CF_ERROR; }
/* * This routine is called only during a Verify */ void get_attributes_and_compare_to_catalog(JCR *jcr, JobId_t JobId) { BSOCK *fd; int n, len; FILE_DBR fdbr; struct stat statf; /* file stat */ struct stat statc; /* catalog stat */ char buf[MAXSTRING]; POOLMEM *fname = get_pool_memory(PM_MESSAGE); int do_Digest = CRYPTO_DIGEST_NONE; int32_t file_index = 0; memset(&fdbr, 0, sizeof(FILE_DBR)); fd = jcr->file_bsock; fdbr.JobId = JobId; jcr->FileIndex = 0; Dmsg0(20, "bdird: waiting to receive file attributes\n"); /* * Get Attributes and Signature from File daemon * We expect: * FileIndex * Stream * Options or Digest (MD5/SHA1) * Filename * Attributes * Link name ??? */ while ((n=bget_dirmsg(fd)) >= 0 && !job_canceled(jcr)) { int stream; char *attr, *p, *fn; char Opts_Digest[MAXSTRING]; /* Verify Opts or MD5/SHA1 digest */ if (job_canceled(jcr)) { return; } fname = check_pool_memory_size(fname, fd->msglen); jcr->fname = check_pool_memory_size(jcr->fname, fd->msglen); Dmsg1(200, "Atts+Digest=%s\n", fd->msg); if ((len = sscanf(fd->msg, "%ld %d %100s", &file_index, &stream, fname)) != 3) { Jmsg3(jcr, M_FATAL, 0, _("bird<filed: bad attributes, expected 3 fields got %d\n" " mslen=%d msg=%s\n"), len, fd->msglen, fd->msg); return; } /* * We read the Options or Signature into fname * to prevent overrun, now copy it to proper location. */ bstrncpy(Opts_Digest, fname, sizeof(Opts_Digest)); p = fd->msg; skip_nonspaces(&p); /* skip FileIndex */ skip_spaces(&p); skip_nonspaces(&p); /* skip Stream */ skip_spaces(&p); skip_nonspaces(&p); /* skip Opts_Digest */ p++; /* skip space */ fn = fname; while (*p != 0) { *fn++ = *p++; /* copy filename */ } *fn = *p++; /* term filename and point to attribs */ attr = p; /* * Got attributes stream, decode it */ if (stream == STREAM_UNIX_ATTRIBUTES || stream == STREAM_UNIX_ATTRIBUTES_EX) { int32_t LinkFIf, LinkFIc; Dmsg2(400, "file_index=%d attr=%s\n", file_index, attr); jcr->JobFiles++; jcr->FileIndex = file_index; /* remember attribute file_index */ jcr->previous_jr.FileIndex = file_index; decode_stat(attr, &statf, &LinkFIf); /* decode file stat packet */ do_Digest = CRYPTO_DIGEST_NONE; jcr->fn_printed = false; pm_strcpy(jcr->fname, fname); /* move filename into JCR */ Dmsg2(040, "dird<filed: stream=%d %s\n", stream, jcr->fname); Dmsg1(020, "dird<filed: attr=%s\n", attr); /* * Find equivalent record in the database */ fdbr.FileId = 0; if (!db_get_file_attributes_record(jcr, jcr->db, jcr->fname, &jcr->previous_jr, &fdbr)) { Jmsg(jcr, M_INFO, 0, _("New file: %s\n"), jcr->fname); Dmsg1(020, _("File not in catalog: %s\n"), jcr->fname); set_jcr_job_status(jcr, JS_Differences); continue; } else { /* * mark file record as visited by stuffing the * current JobId, which is unique, into the MarkId field. */ db_mark_file_record(jcr, jcr->db, fdbr.FileId, jcr->JobId); } Dmsg3(400, "Found %s in catalog. inx=%d Opts=%s\n", jcr->fname, file_index, Opts_Digest); decode_stat(fdbr.LStat, &statc, &LinkFIc); /* decode catalog stat */ /* * Loop over options supplied by user and verify the * fields he requests. */ for (p=Opts_Digest; *p; p++) { char ed1[30], ed2[30]; switch (*p) { case 'i': /* compare INODEs */ if (statc.st_ino != statf.st_ino) { prt_fname(jcr); Jmsg(jcr, M_INFO, 0, _(" st_ino differ. Cat: %s File: %s\n"), edit_uint64((uint64_t)statc.st_ino, ed1), edit_uint64((uint64_t)statf.st_ino, ed2)); set_jcr_job_status(jcr, JS_Differences); } break; case 'p': /* permissions bits */ if (statc.st_mode != statf.st_mode) { prt_fname(jcr); Jmsg(jcr, M_INFO, 0, _(" st_mode differ. Cat: %x File: %x\n"), (uint32_t)statc.st_mode, (uint32_t)statf.st_mode); set_jcr_job_status(jcr, JS_Differences); } break; case 'n': /* number of links */ if (statc.st_nlink != statf.st_nlink) { prt_fname(jcr); Jmsg(jcr, M_INFO, 0, _(" st_nlink differ. Cat: %d File: %d\n"), (uint32_t)statc.st_nlink, (uint32_t)statf.st_nlink); set_jcr_job_status(jcr, JS_Differences); } break; case 'u': /* user id */ if (statc.st_uid != statf.st_uid) { prt_fname(jcr); Jmsg(jcr, M_INFO, 0, _(" st_uid differ. Cat: %u File: %u\n"), (uint32_t)statc.st_uid, (uint32_t)statf.st_uid); set_jcr_job_status(jcr, JS_Differences); } break; case 'g': /* group id */ if (statc.st_gid != statf.st_gid) { prt_fname(jcr); Jmsg(jcr, M_INFO, 0, _(" st_gid differ. Cat: %u File: %u\n"), (uint32_t)statc.st_gid, (uint32_t)statf.st_gid); set_jcr_job_status(jcr, JS_Differences); } break; case 's': /* size */ if (statc.st_size != statf.st_size) { prt_fname(jcr); Jmsg(jcr, M_INFO, 0, _(" st_size differ. Cat: %s File: %s\n"), edit_uint64((uint64_t)statc.st_size, ed1), edit_uint64((uint64_t)statf.st_size, ed2)); set_jcr_job_status(jcr, JS_Differences); } break; case 'a': /* access time */ if (statc.st_atime != statf.st_atime) { prt_fname(jcr); Jmsg(jcr, M_INFO, 0, _(" st_atime differs\n")); set_jcr_job_status(jcr, JS_Differences); } break; case 'm': if (statc.st_mtime != statf.st_mtime) { prt_fname(jcr); Jmsg(jcr, M_INFO, 0, _(" st_mtime differs\n")); set_jcr_job_status(jcr, JS_Differences); } break; case 'c': /* ctime */ if (statc.st_ctime != statf.st_ctime) { prt_fname(jcr); Jmsg(jcr, M_INFO, 0, _(" st_ctime differs\n")); set_jcr_job_status(jcr, JS_Differences); } break; case 'd': /* file size decrease */ if (statc.st_size > statf.st_size) { prt_fname(jcr); Jmsg(jcr, M_INFO, 0, _(" st_size decrease. Cat: %s File: %s\n"), edit_uint64((uint64_t)statc.st_size, ed1), edit_uint64((uint64_t)statf.st_size, ed2)); set_jcr_job_status(jcr, JS_Differences); } break; case '5': /* compare MD5 */ Dmsg1(500, "set Do_MD5 for %s\n", jcr->fname); do_Digest = CRYPTO_DIGEST_MD5; break; case '1': /* compare SHA1 */ do_Digest = CRYPTO_DIGEST_SHA1; break; case ':': case 'V': default: break; } } /* * Got Digest Signature from Storage daemon * It came across in the Opts_Digest field. */ } else if (crypto_digest_stream_type(stream) != CRYPTO_DIGEST_NONE) { Dmsg2(400, "stream=Digest inx=%d Digest=%s\n", file_index, Opts_Digest); /* * When ever we get a digest it MUST have been * preceded by an attributes record, which sets attr_file_index */ if (jcr->FileIndex != (uint32_t)file_index) { Jmsg2(jcr, M_FATAL, 0, _("MD5/SHA1 index %d not same as attributes %d\n"), file_index, jcr->FileIndex); return; } if (do_Digest != CRYPTO_DIGEST_NONE) { db_escape_string(jcr, jcr->db, buf, Opts_Digest, strlen(Opts_Digest)); if (strcmp(buf, fdbr.Digest) != 0) { prt_fname(jcr); Jmsg(jcr, M_INFO, 0, _(" %s differs. File=%s Cat=%s\n"), stream_to_ascii(stream), buf, fdbr.Digest); set_jcr_job_status(jcr, JS_Differences); } do_Digest = CRYPTO_DIGEST_NONE; } } jcr->JobFiles = file_index; } if (is_bnet_error(fd)) { berrno be; Jmsg2(jcr, M_FATAL, 0, _("bdird<filed: bad attributes from filed n=%d : %s\n"), n, be.bstrerror()); return; } /* Now find all the files that are missing -- i.e. all files in * the database where the MarkId != current JobId */ jcr->fn_printed = false; bsnprintf(buf, sizeof(buf), "SELECT Path.Path,Filename.Name FROM File,Path,Filename " "WHERE File.JobId=%d " "AND File.MarkId!=%d AND File.PathId=Path.PathId " "AND File.FilenameId=Filename.FilenameId", JobId, jcr->JobId); /* missing_handler is called for each file found */ db_sql_query(jcr->db, buf, missing_handler, (void *)jcr); if (jcr->fn_printed) { set_jcr_job_status(jcr, JS_Differences); } free_pool_memory(fname); }
/* * 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 stat; char ec1[50]; Dmsg0(100, "Despooling data\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)); set_jcr_job_status(jcr, 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)); set_jcr_job_status(jcr, JS_DataDespooling); } set_jcr_job_status(jcr, JS_DataDespooling); dir_send_job_status(jcr); 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(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 = new_dcr(jcr, NULL, rdev); 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); for ( ; ok; ) { if (job_canceled(jcr)) { ok = false; break; } stat = read_block_from_spool_file(rdcr); if (stat == RB_EOT) { break; } else if (stat == RB_ERROR) { ok = false; break; } ok = write_block_to_device(dcr); 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()); } Dmsg3(800, "Write block ok=%d FI=%d LI=%d\n", ok, block->FirstIndex, block->LastIndex); } if (!dir_create_jobmedia_record(dcr)) { Jmsg2(jcr, M_FATAL, 0, _("Could not create JobMedia record for Volume=\"%s\" Job=%s\n"), dcr->VolCatInfo.VolCatName, jcr->Job); } /* 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(dcr->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(dcr->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->dev = NULL; free_dcr(rdcr); free(rdev); dcr->spooling = true; /* turn on spooling again */ dcr->despooling = false; /* * We are done, so unblock the device, but if we have done a * commit, leave it locked so that the job cleanup does not * need to wait to release the device (no re-acquire of the lock). */ dcr->dlock(); unblock_device(dcr->dev); /* If doing a commit, leave the device locked -- unlocked in release_device() */ if (!commit) { dcr->dunlock(); } set_jcr_job_status(jcr, JS_Running); dir_send_job_status(jcr); return ok; }
/* * If s == NULL we are just waiting on FD for status changes. * If s != NULL we are asking the UPS to tell us the value of something. * * If s == NULL there is a much more fine-grained locking. */ int getline(char *s, int len, UPSINFO *ups) { int i = 0; int ending = 0; char c; int retval; int wait; if (s != NULL) wait = TIMER_FAST; /* 1 sec, expect fast response */ else wait = ups->wait_time; #ifdef HAVE_MINGW /* Set read() timeout since we have no select() support. */ { COMMTIMEOUTS ct; HANDLE h = (HANDLE)_get_osfhandle(ups->fd); ct.ReadIntervalTimeout = MAXDWORD; ct.ReadTotalTimeoutMultiplier = MAXDWORD; ct.ReadTotalTimeoutConstant = wait * 1000; ct.WriteTotalTimeoutMultiplier = 0; ct.WriteTotalTimeoutConstant = 0; SetCommTimeouts(h, &ct); } #endif while (!ending) { #if !defined(HAVE_MINGW) fd_set rfds; struct timeval tv; FD_ZERO(&rfds); FD_SET(ups->fd, &rfds); tv.tv_sec = wait; tv.tv_usec = 0; errno = 0; retval = select((ups->fd) + 1, &rfds, NULL, NULL, &tv); switch (retval) { case 0: /* No chars available in TIMER seconds. */ return FAILURE; case -1: if (errno == EINTR || errno == EAGAIN) { /* assume SIGCHLD */ continue; } else if (errno == EBADF) { return FAILURE; /* We're probably shutting down */ } Error_abort1("Select error on UPS FD. %s\n", strerror(errno)); break; default: break; } #endif do { retval = read(ups->fd, &c, 1); } while (retval == -1 && (errno == EAGAIN || errno == EINTR)); if (retval == 0) { return FAILURE; } switch (c) { /* * Here we can be called in two ways: * * s == NULL * The shm lock is not held so we must hold it here. * * s != NULL * We are called from a routine that have * already held the shm lock so no need to hold it * another time. Simply update the UPS structure * fields and the shm will be updated when * write_unlock is called by the calling * routine. * * If something changes on the UPS, a special character is * sent over the serial line but no \n\r sequence is sent: * only a single character. This way if s == NULL, if we * receive a character like this we must return immediately * and not wait for a string completion. */ case UPS_ON_BATT: /* UPS_ON_BATT = '!' */ if (s == NULL) write_lock(ups); ups->clear_online(); Dmsg0(80, "Got UPS ON BATT.\n"); if (s == NULL) { write_unlock(ups); ending = 1; } break; case UPS_REPLACE_BATTERY: /* UPS_REPLACE_BATTERY = '#' */ if (s == NULL) write_lock(ups); ups->set_replacebatt(); Dmsg0(80, "Got UPS REPLACE_BATT.\n"); if (s == NULL) { write_unlock(ups); ending = 1; } break; case UPS_ON_LINE: /* UPS_ON_LINE = '$' */ if (s == NULL) write_lock(ups); ups->set_online(); Dmsg0(80, "Got UPS ON LINE.\n"); if (s == NULL) { write_unlock(ups); ending = 1; } break; case BATT_LOW: /* BATT_LOW = '%' */ if (s == NULL) write_lock(ups); ups->set_battlow(); Dmsg0(80, "Got UPS BATT_LOW.\n"); if (s == NULL) { write_unlock(ups); ending = 1; } break; case BATT_OK: /* BATT_OK = '+' */ if (s == NULL) write_lock(ups); ups->clear_battlow(); Dmsg0(80, "Got UPS BATT_OK.\n"); if (s == NULL) { write_unlock(ups); ending = 1; } break; case UPS_EPROM_CHANGE: /* UPS_EPROM_CHANGE = '|' */ case UPS_TRAILOR: /* UPS_TRAILOR = ':' */ break; /* NOTE: The UPS terminates what it sends to us * with a \r\n. Thus the line feed signals the * end of what we are to receive. */ case UPS_LF: /* UPS_LF = '\n' */ if (s != NULL) ending = 1; /* This what we waited for */ break; case UPS_CR: /* UPS_CR = '\r' */ break; default: if (s != NULL) { if (i + 1 < len) s[i++] = c; else ending = 1; /* no more room in buffer */ } break; } } if (s != NULL) { s[i] = '\0'; } return SUCCESS; }
int main (int argc, char *argv[]) { int ch; bool ok; char *iVolumeName = NULL; char *oVolumeName = NULL; char *DirectorName = NULL; DIRRES *director = NULL; bool ignore_label_errors = false; DCR *in_dcr, *out_dcr; setlocale(LC_ALL, ""); bindtextdomain("bareos", LOCALEDIR); textdomain("bareos"); init_stack_dump(); my_name_is(argc, argv, "bcopy"); lmgr_init_thread(); init_msg(NULL, NULL); while ((ch = getopt(argc, argv, "b:c:D:d:i:o:pvw:?")) != -1) { switch (ch) { case 'b': bsr = parse_bsr(NULL, optarg); break; case 'c': /* specify config file */ if (configfile != NULL) { free(configfile); } configfile = bstrdup(optarg); break; case 'D': /* specify director name */ if (DirectorName != NULL) { free(DirectorName); } DirectorName = bstrdup(optarg); break; case 'd': /* debug level */ if (*optarg == 't') { dbg_timestamp = true; } else { debug_level = atoi(optarg); if (debug_level <= 0) { debug_level = 1; } } break; case 'i': /* input Volume name */ iVolumeName = optarg; break; case 'o': /* output Volume name */ oVolumeName = optarg; break; case 'p': ignore_label_errors = true; forge_on = true; break; case 'v': verbose++; break; case 'w': wd = optarg; break; case '?': default: usage(); } } argc -= optind; argv += optind; if (argc != 2) { Pmsg0(0, _("Wrong number of arguments: \n")); usage(); } OSDependentInit(); working_directory = wd; if (configfile == NULL) { configfile = bstrdup(CONFIG_FILE); } my_config = new_config_parser(); parse_sd_config(my_config, configfile, M_ERROR_TERM); if (DirectorName) { foreach_res(director, R_DIRECTOR) { if (bstrcmp(director->hdr.name, DirectorName)) { break; } } if (!director) { Emsg2(M_ERROR_TERM, 0, _("No Director resource named %s defined in %s. Cannot continue.\n"), DirectorName, configfile); } } load_sd_plugins(me->plugin_directory, me->plugin_names); read_crypto_cache(me->working_directory, "bareos-sd", get_first_port_host_order(me->SDaddrs)); /* * Setup and acquire input device for reading */ Dmsg0(100, "About to setup input jcr\n"); in_dcr = New(DCR); in_jcr = setup_jcr("bcopy", argv[0], bsr, director, in_dcr, iVolumeName, true); /* read device */ if (!in_jcr) { exit(1); } in_jcr->ignore_label_errors = ignore_label_errors; in_dev = in_jcr->dcr->dev; if (!in_dev) { exit(1); } /* * Setup output device for writing */ Dmsg0(100, "About to setup output jcr\n"); out_dcr = New(DCR); out_jcr = setup_jcr("bcopy", argv[1], bsr, director, out_dcr, oVolumeName, false); /* write device */ if (!out_jcr) { exit(1); } out_dev = out_jcr->dcr->dev; if (!out_dev) { exit(1); } Dmsg0(100, "About to acquire device for writing\n"); /* * For we must now acquire the device for writing */ out_dev->rLock(false); if (!out_dev->open(out_jcr->dcr, OPEN_READ_WRITE)) { Emsg1(M_FATAL, 0, _("dev open failed: %s\n"), out_dev->errmsg); out_dev->Unlock(); exit(1); } out_dev->Unlock(); if (!acquire_device_for_append(out_jcr->dcr)) { free_jcr(in_jcr); exit(1); } out_block = out_jcr->dcr->block; ok = read_records(in_jcr->dcr, record_cb, mount_next_read_volume); if (ok || out_dev->can_write()) { if (!out_jcr->dcr->write_block_to_device()) { Pmsg0(000, _("Write of last block failed.\n")); } } Pmsg2(000, _("%u Jobs copied. %u records copied.\n"), jobs, records); in_dev->term(); out_dev->term(); free_jcr(in_jcr); free_jcr(out_jcr); return 0; }
/* * Write a Volume Label * !!! Note, this is ONLY used for writing * a fresh volume label. Any data * after the label will be destroyed, * in fact, we write the label 5 times !!!! * * This routine should be used only when labeling a blank tape. */ bool write_new_volume_label_to_dev(DCR *dcr, const char *VolName, const char *PoolName, bool relabel) { JCR *jcr = dcr->jcr; DEVICE *dev = dcr->dev; DEV_BLOCK *block = dcr->block; Dmsg0(150, "write_volume_label()\n"); if (*VolName == 0) { Pmsg0(0, "=== ERROR: write_new_volume_label_to_dev called with NULL VolName\n"); goto bail_out; } if (relabel) { volume_unused(dcr); /* mark current volume unused */ /* Truncate device */ if (!dev->truncate(dcr)) { goto bail_out; } if (!dev->is_tape()) { dev->close(dcr); /* make sure file closed for rename */ } } /* Set the new filename for open, ... */ dev->setVolCatName(VolName); dcr->setVolCatName(VolName); Dmsg1(150, "New VolName=%s\n", VolName); if (!dev->open(dcr, OPEN_READ_WRITE)) { /* If device is not tape, attempt to create it */ if (dev->is_tape() || !dev->open(dcr, CREATE_READ_WRITE)) { Jmsg3(jcr, M_WARNING, 0, _("Open device %s Volume \"%s\" failed: ERR=%s\n"), dev->print_name(), dcr->VolumeName, dev->bstrerror()); goto bail_out; } } Dmsg1(150, "Label type=%d\n", dev->label_type); /* * Let any stored plugin know that we are about to write a new label to the volume. */ if (generate_plugin_event(jcr, bsdEventLabelWrite, dcr) != bRC_OK) { Dmsg0(200, "Error from bsdEventLabelWrite plugin event.\n"); goto bail_out; } for ( ;; ) { empty_block(block); if (!dev->rewind(dcr)) { Dmsg2(130, "Bad status on %s from rewind: ERR=%s\n", dev->print_name(), dev->print_errmsg()); if (!forge_on) { goto bail_out; } } /* Temporarily mark in append state to enable writing */ dev->set_append(); /* Create PRE_LABEL */ create_volume_label(dev, VolName, PoolName); /* * If we have already detected an ANSI label, re-read it * to skip past it. Otherwise, we write a new one if * so requested. */ if (dev->label_type != B_BAREOS_LABEL) { if (read_ansi_ibm_label(dcr) != VOL_OK) { dev->rewind(dcr); goto bail_out; } } else if (!write_ansi_ibm_labels(dcr, ANSI_VOL_LABEL, VolName)) { goto bail_out; } create_volume_label_record(dcr, dev, dcr->rec); dcr->rec->Stream = 0; dcr->rec->maskedStream = 0; if (!write_record_to_block(dcr, dcr->rec)) { Dmsg2(130, "Bad Label write on %s: ERR=%s\n", dev->print_name(), dev->print_errmsg()); goto bail_out; } else { Dmsg2(130, "Wrote label of %d bytes to %s\n", dcr->rec->data_len, dev->print_name()); } Dmsg0(130, "Call write_block_to_dev()\n"); if (!dcr->write_block_to_dev()) { Dmsg2(130, "Bad Label write on %s: ERR=%s\n", dev->print_name(), dev->print_errmsg()); goto bail_out; } break; } dev = dcr->dev; Dmsg0(130, " Wrote block to device\n"); if (dev->weof(1)) { dev->set_labeled(); write_ansi_ibm_labels(dcr, ANSI_EOF_LABEL, dev->VolHdr.VolumeName); } if (debug_level >= 20) { dump_volume_label(dev); } Dmsg0(100, "Call reserve_volume\n"); if (reserve_volume(dcr, VolName) == NULL) { Mmsg2(jcr->errmsg, _("Could not reserve volume %s on %s\n"), dev->VolHdr.VolumeName, dev->print_name()); Dmsg1(100, "%s", jcr->errmsg); goto bail_out; } dev = dcr->dev; /* may have changed in reserve_volume */ dev->clear_append(); /* remove append since this is PRE_LABEL */ return true; bail_out: volume_unused(dcr); dev->clear_volhdr(); dev->clear_append(); /* remove append since this is PRE_LABEL */ return false; }
/* * Read a Record from the block * * Returns: false if nothing read or if the continuation record does not match. * In both of these cases, a block read must be done. * true if at least the record header was read, this * routine may have to be called again with a new * block if the entire record was not read. */ bool read_record_from_block(DCR *dcr, DEV_RECORD *rec) { ser_declare; uint32_t remlen; uint32_t VolSessionId; uint32_t VolSessionTime; int32_t FileIndex; int32_t Stream; uint32_t data_bytes; uint32_t rhl; char buf1[100], buf2[100]; remlen = dcr->block->binbuf; /* * Clear state flags */ clear_all_bits(REC_STATE_MAX, rec->state_bits); if (dcr->block->dev->is_tape()) { set_bit(REC_ISTAPE, rec->state_bits); } rec->Block = ((DEVICE *)(dcr->block->dev))->EndBlock; rec->File = ((DEVICE *)(dcr->block->dev))->EndFile; /* * Get the header. There is always a full header, otherwise we find it in the next block. */ Dmsg3(450, "Block=%d Ver=%d size=%u\n", dcr->block->BlockNumber, dcr->block->BlockVer, dcr->block->block_len); if (dcr->block->BlockVer == 1) { rhl = RECHDR1_LENGTH; } else { rhl = RECHDR2_LENGTH; } if (remlen >= rhl) { Dmsg4(450, "Enter read_record_block: remlen=%d data_len=%d rem=%d blkver=%d\n", remlen, rec->data_len, rec->remainder, dcr->block->BlockVer); unser_begin(dcr->block->bufp, WRITE_RECHDR_LENGTH); if (dcr->block->BlockVer == 1) { unser_uint32(VolSessionId); unser_uint32(VolSessionTime); } else { VolSessionId = dcr->block->VolSessionId; VolSessionTime = dcr->block->VolSessionTime; } unser_int32(FileIndex); unser_int32(Stream); unser_uint32(data_bytes); dcr->block->bufp += rhl; dcr->block->binbuf -= rhl; remlen -= rhl; /* * If we are looking for more (remainder!=0), we reject anything * where the VolSessionId and VolSessionTime don't agree */ if (rec->remainder && (rec->VolSessionId != VolSessionId || rec->VolSessionTime != VolSessionTime)) { set_bit(REC_NO_MATCH, rec->state_bits); Dmsg0(450, "remainder and VolSession doesn't match\n"); return false; /* This is from some other Session */ } /* * If Stream is negative, it means that this is a continuation * of a previous partially written record. */ if (Stream < 0) { /* continuation record? */ Dmsg1(500, "Got negative Stream => continuation. remainder=%d\n", rec->remainder); set_bit(REC_CONTINUATION, rec->state_bits); if (!rec->remainder) { /* if we didn't read previously */ rec->data_len = 0; /* return data as if no continuation */ } else if (rec->Stream != -Stream) { set_bit(REC_NO_MATCH, rec->state_bits); return false; /* This is from some other Session */ } rec->Stream = -Stream; /* set correct Stream */ rec->maskedStream = rec->Stream & STREAMMASK_TYPE; } else { /* Regular record */ rec->Stream = Stream; rec->maskedStream = rec->Stream & STREAMMASK_TYPE; rec->data_len = 0; /* transfer to beginning of data */ } rec->VolSessionId = VolSessionId; rec->VolSessionTime = VolSessionTime; rec->FileIndex = FileIndex; if (FileIndex > 0) { if (dcr->block->FirstIndex == 0) { dcr->block->FirstIndex = FileIndex; } dcr->block->LastIndex = FileIndex; } Dmsg6(450, "rd_rec_blk() got FI=%s SessId=%d Strm=%s len=%u\n" "remlen=%d data_len=%d\n", FI_to_ascii(buf1, rec->FileIndex), rec->VolSessionId, stream_to_ascii(buf2, rec->Stream, rec->FileIndex), data_bytes, remlen, rec->data_len); } else { /* * No more records in this block because the number * of remaining bytes are less than a record header * length, so return empty handed, but indicate that * he must read again. By returning, we allow the * higher level routine to fetch the next block and * then reread. */ Dmsg0(450, "read_record_block: nothing\n"); set_bit(REC_NO_HEADER, rec->state_bits); set_bit(REC_BLOCK_EMPTY, rec->state_bits); empty_block(dcr->block); /* mark block empty */ return false; } /* Sanity check */ if (data_bytes >= MAX_BLOCK_LENGTH) { /* * Something is wrong, force read of next block, abort * continuing with this block. */ set_bit(REC_NO_HEADER, rec->state_bits); set_bit(REC_BLOCK_EMPTY, rec->state_bits); empty_block(dcr->block); Jmsg2(dcr->jcr, M_WARNING, 0, _("Sanity check failed. maxlen=%d datalen=%d. Block discarded.\n"), MAX_BLOCK_LENGTH, data_bytes); return false; } rec->data = check_pool_memory_size(rec->data, rec->data_len + data_bytes); /* * At this point, we have read the header, now we * must transfer as much of the data record as * possible taking into account: 1. A partial * data record may have previously been transferred, * 2. The current block may not contain the whole data * record. */ if (remlen >= data_bytes) { /* * Got whole record */ memcpy(rec->data+rec->data_len, dcr->block->bufp, data_bytes); dcr->block->bufp += data_bytes; dcr->block->binbuf -= data_bytes; rec->data_len += data_bytes; } else { /* * Partial record */ memcpy(rec->data+rec->data_len, dcr->block->bufp, remlen); dcr->block->bufp += remlen; dcr->block->binbuf -= remlen; rec->data_len += remlen; rec->remainder = 1; /* partial record transferred */ Dmsg1(450, "read_record_block: partial xfered=%d\n", rec->data_len); set_bit(REC_PARTIAL_RECORD, rec->state_bits); set_bit(REC_BLOCK_EMPTY, rec->state_bits); return true; } rec->remainder = 0; Dmsg4(450, "Rtn full rd_rec_blk FI=%s SessId=%d Strm=%s len=%d\n", FI_to_ascii(buf1, rec->FileIndex), rec->VolSessionId, stream_to_ascii(buf2, rec->Stream, rec->FileIndex), rec->data_len); return true; /* transferred full record */ }