/* * Get response from FD or SD to a command we * sent. Check that the response agrees with what we expect. * * Returns: false on failure * true on success */ bool response(JCR *jcr, BSOCK *bs, char *resp, const char *cmd, e_prtmsg prtmsg) { int n; if (is_bnet_error(bs)) { return false; } if ((n = bget_dirmsg(bs)) >= 0) { if (bstrcmp(bs->msg, resp)) { return true; } if (prtmsg == DISPLAY_ERROR) { Jmsg(jcr, M_FATAL, 0, _("Bad response to %s command: wanted %s, got %s\n"), cmd, resp, bs->msg); } return false; } Jmsg(jcr, M_FATAL, 0, _("Socket error on %s command: ERR=%s\n"), cmd, bnet_strerror(bs)); return false; }
/* * Ask the autochanger to perform a mount, umount or release operation. */ bool do_autochanger_volume_operation(UAContext *ua, STORERES *store, const char *operation, int drive, int slot) { BSOCK *sd = NULL; bool retval = true; char dev_name[MAX_NAME_LENGTH]; if (!(sd = open_sd_bsock(ua))) { return false; } bstrncpy(dev_name, store->dev_name(), sizeof(dev_name)); bash_spaces(dev_name); if (slot > 0) { sd->fsend(changervolopslotcmd, operation, dev_name, drive, slot); } else { sd->fsend(changervolopcmd, operation, dev_name, drive); } /* * We use bget_dirmsg here and not bnet_recv because as part of * the mount request the stored can request catalog information for * any plugin who listens to the bsdEventLabelVerified event. * As we don't want to loose any non protocol data e.g. errors * without a 3xxx prefix we set the allow_any_message of * bget_dirmsg to true and as such is behaves like a normal * bnet_recv for any non protocol messages. */ while (bget_dirmsg(sd, true) >= 0) { ua->send_msg("%s", sd->msg); } close_sd_bsock(ua); return retval; }
/* * Common routine for both label and relabel */ static int do_label(UAContext *ua, const char *cmd, bool relabel) { USTORERES store; BSOCK *sd; char dev_name[MAX_NAME_LENGTH]; MEDIA_DBR mr, omr; POOL_DBR pr; bool print_reminder = true; bool label_barcodes = false; bool label_encrypt = false; int ok = FALSE; int i, j; int drive; bool media_record_exists = false; static const char *barcode_keywords[] = { "barcode", "barcodes", NULL }; memset(&pr, 0, sizeof(pr)); if (!open_client_db(ua)) { return 1; } /* * Look for one of the barcode keywords */ if (!relabel && (i = find_arg_keyword(ua, barcode_keywords)) >= 0) { /* * Now find the keyword in the list */ if ((j = find_arg(ua, barcode_keywords[i])) > 0) { *ua->argk[j] = 0; /* zap barcode keyword */ } label_barcodes = true; } /* * Look for the encrypt keyword */ if ((i = find_arg(ua, "encrypt")) > 0) { *ua->argk[i] = 0; /* zap encrypt keyword */ label_encrypt = true; } store.store = get_storage_resource(ua, true, label_barcodes); if (!store.store) { return 1; } switch (store.store->Protocol) { case APT_NDMPV2: case APT_NDMPV3: case APT_NDMPV4: /* * See if the user selected a NDMP storage device but its * handled by a native Bareos storage daemon e.g. we have * a paired_storage pointer. */ if (store.store->paired_storage) { store.store = store.store->paired_storage; } else { ua->warning_msg(_("Storage has non-native protocol.\n")); return 1; } break; default: break; } pm_strcpy(store.store_source, _("command line")); set_wstorage(ua->jcr, &store); drive = get_storage_drive(ua, store.store); if (label_barcodes) { label_from_barcodes(ua, drive, label_encrypt); return 1; } /* * If relabel get name of Volume to relabel */ if (relabel) { /* * Check for oldvolume=name */ i = find_arg_with_value(ua, "oldvolume"); if (i >= 0) { bstrncpy(omr.VolumeName, ua->argv[i], sizeof(omr.VolumeName)); if (db_get_media_record(ua->jcr, ua->db, &omr)) { goto checkVol; } ua->error_msg("%s", db_strerror(ua->db)); } /* * No keyword or Vol not found, ask user to select */ if (!select_media_dbr(ua, &omr)) { return 1; } /* * Require Volume to be Purged or Recycled */ checkVol: if (!bstrcmp(omr.VolStatus, "Purged") && !bstrcmp(omr.VolStatus, "Recycle")) { ua->error_msg(_("Volume \"%s\" has VolStatus %s. It must be Purged or Recycled before relabeling.\n"), omr.VolumeName, omr.VolStatus); return 1; } } /* * Check for volume=NewVolume */ i = find_arg_with_value(ua, "volume"); if (i >= 0) { pm_strcpy(ua->cmd, ua->argv[i]); goto checkName; } /* * Get a new Volume name */ for ( ;; ) { media_record_exists = false; if (!get_cmd(ua, _("Enter new Volume name: "))) { return 1; } checkName: if (!is_volume_name_legal(ua, ua->cmd)) { continue; } /* * Search by Media name so set VolumeName and clear MediaId. */ mr.MediaId = 0; bstrncpy(mr.VolumeName, ua->cmd, sizeof(mr.VolumeName)); /* * If VolBytes are zero the Volume is not labeled */ if (db_get_media_record(ua->jcr, ua->db, &mr)) { if (mr.VolBytes != 0) { ua->error_msg(_("Media record for new Volume \"%s\" already exists.\n"), mr.VolumeName); continue; } media_record_exists = true; } break; /* Got it */ } /* * If autochanger, request slot */ i = find_arg_with_value(ua, "slot"); if (i >= 0) { mr.Slot = atoi(ua->argv[i]); if (mr.Slot < 0) { mr.Slot = 0; } mr.InChanger = mr.Slot > 0; /* if slot give assume in changer */ } else if (store.store->autochanger) { if (!get_pint(ua, _("Enter slot (0 or Enter for none): "))) { return 1; } mr.Slot = ua->pint32_val; if (mr.Slot < 0) { mr.Slot = 0; } mr.InChanger = mr.Slot > 0; /* if slot give assume in changer */ } set_storageid_in_mr(store.store, &mr); bstrncpy(mr.MediaType, store.store->media_type, sizeof(mr.MediaType)); /* * Must select Pool if not already done */ if (pr.PoolId == 0) { memset(&pr, 0, sizeof(pr)); if (!select_pool_dbr(ua, &pr)) { return 1; } } /* * See if we need to generate a new passphrase for hardware encryption. */ if (label_encrypt) { ua->info_msg(_("Generating new hardware encryption key\n")); if (!generate_new_encryption_key(ua, &mr)) { return 1; } } ok = send_label_request(ua, &mr, &omr, &pr, relabel, media_record_exists, drive); if (ok) { sd = ua->jcr->store_bsock; if (relabel) { /* * Delete the old media record */ if (!db_delete_media_record(ua->jcr, ua->db, &omr)) { ua->error_msg(_("Delete of Volume \"%s\" failed. ERR=%s"), omr.VolumeName, db_strerror(ua->db)); } else { ua->info_msg(_("Old volume \"%s\" deleted from catalog.\n"), omr.VolumeName); /* * Update the number of Volumes in the pool */ pr.NumVols--; if (!db_update_pool_record(ua->jcr, ua->db, &pr)) { ua->error_msg("%s", db_strerror(ua->db)); } } } if (ua->automount) { bstrncpy(dev_name, store.store->dev_name(), sizeof(dev_name)); ua->info_msg(_("Requesting to mount %s ...\n"), dev_name); bash_spaces(dev_name); sd->fsend("mount %s drive=%d", dev_name, drive); unbash_spaces(dev_name); /* * We use bget_dirmsg here and not bnet_recv because as part of * the mount request the stored can request catalog information for * any plugin who listens to the bsdEventLabelVerified event. * As we don't want to loose any non protocol data e.g. errors * without a 3xxx prefix we set the allow_any_message of * bget_dirmsg to true and as such is behaves like a normal * bnet_recv for any non protocol messages. */ while (bget_dirmsg(sd, true) >= 0) { ua->send_msg("%s", sd->msg); /* * Here we can get * 3001 OK mount. Device=xxx or * 3001 Mounted Volume vvvv * 3002 Device "DVD-Writer" (/dev/hdc) is mounted. * 3906 is cannot mount non-tape * So for those, no need to print a reminder */ if (bstrncmp(sd->msg, "3001 ", 5) || bstrncmp(sd->msg, "3002 ", 5) || bstrncmp(sd->msg, "3906 ", 5)) { print_reminder = false; } } } } if (print_reminder) { ua->info_msg(_("Do not forget to mount the drive!!!\n")); } close_sd_bsock(ua); return 1; }
/* * NOTE! This routine opens the SD socket but leaves it open */ static bool send_label_request(UAContext *ua, MEDIA_DBR *mr, MEDIA_DBR *omr, POOL_DBR *pr, bool relabel, bool media_record_exists, int drive) { BSOCK *sd; char dev_name[MAX_NAME_LENGTH]; bool ok = false; uint64_t VolBytes = 0; if (!(sd=open_sd_bsock(ua))) { return false; } bstrncpy(dev_name, ua->jcr->res.wstore->dev_name(), sizeof(dev_name)); bash_spaces(dev_name); bash_spaces(mr->VolumeName); bash_spaces(mr->MediaType); bash_spaces(pr->Name); if (relabel) { bash_spaces(omr->VolumeName); sd->fsend("relabel %s OldName=%s NewName=%s PoolName=%s " "MediaType=%s Slot=%d drive=%d MinBlocksize=%d MaxBlocksize=%d", dev_name, omr->VolumeName, mr->VolumeName, pr->Name, mr->MediaType, mr->Slot, drive, /* * if relabeling, keep blocksize settings */ omr->MinBlocksize, omr->MaxBlocksize); ua->send_msg(_("Sending relabel command from \"%s\" to \"%s\" ...\n"), omr->VolumeName, mr->VolumeName); } else { sd->fsend("label %s VolumeName=%s PoolName=%s MediaType=%s " "Slot=%d drive=%d MinBlocksize=%d MaxBlocksize=%d", dev_name, mr->VolumeName, pr->Name, mr->MediaType, mr->Slot, drive, /* * if labeling, use blocksize defined in pool */ pr->MinBlocksize, pr->MaxBlocksize); ua->send_msg(_("Sending label command for Volume \"%s\" Slot %d ...\n"), mr->VolumeName, mr->Slot); Dmsg8(100, "label %s VolumeName=%s PoolName=%s MediaType=%s " "Slot=%d drive=%d MinBlocksize=%d MaxBlocksize=%d\n", dev_name, mr->VolumeName, pr->Name, mr->MediaType, mr->Slot, drive, pr->MinBlocksize, pr->MaxBlocksize); } /* * We use bget_dirmsg here and not bnet_recv because as part of * the label request the stored can request catalog information for * any plugin who listens to the bsdEventLabelVerified event. * As we don't want to loose any non protocol data e.g. errors * without a 3xxx prefix we set the allow_any_message of * bget_dirmsg to true and as such is behaves like a normal * bnet_recv for any non protocol messages. */ while (bget_dirmsg(sd, true) >= 0) { ua->send_msg("%s", sd->msg); if (sscanf(sd->msg, "3000 OK label. VolBytes=%llu ", &VolBytes) == 1) { ok = true; } } unbash_spaces(mr->VolumeName); unbash_spaces(mr->MediaType); unbash_spaces(pr->Name); mr->LabelDate = time(NULL); mr->set_label_date = true; if (ok) { if (media_record_exists) { /* we update it */ mr->VolBytes = VolBytes; mr->InChanger = mr->Slot > 0; /* if slot give assume in changer */ set_storageid_in_mr(ua->jcr->res.wstore, mr); if (!db_update_media_record(ua->jcr, ua->db, mr)) { ua->error_msg("%s", db_strerror(ua->db)); ok = false; } } else { /* create the media record */ set_pool_dbr_defaults_in_media_dbr(mr, pr); mr->VolBytes = VolBytes; mr->InChanger = mr->Slot > 0; /* if slot give assume in changer */ mr->Enabled = 1; set_storageid_in_mr(ua->jcr->res.wstore, mr); if (db_create_media_record(ua->jcr, ua->db, mr)) { ua->info_msg(_("Catalog record for Volume \"%s\", Slot %d successfully created.\n"), mr->VolumeName, mr->Slot); /* * Update number of volumes in pool */ pr->NumVols++; if (!db_update_pool_record(ua->jcr, ua->db, pr)) { ua->error_msg("%s", db_strerror(ua->db)); } } else { ua->error_msg("%s", db_strerror(ua->db)); ok = false; } } } else { ua->error_msg(_("Label command failed for Volume %s.\n"), mr->VolumeName); } return ok; }
/* * 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, *sd; int stat; char ed1[100]; JOB_DBR jr; JobId_t verify_jobid = 0; char *store_address; uint32_t store_port; 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->getJobLevel() == L_VERIFY_CATALOG || jcr->getJobLevel() == L_VERIFY_VOLUME_TO_CATALOG || jcr->getJobLevel() == L_VERIFY_DISK_TO_CATALOG || jcr->getJobLevel() == L_VERIFY_DATA) { memcpy(&jr, &jcr->jr, sizeof(jr)); if (jcr->verify_job && (jcr->getJobLevel() == L_VERIFY_VOLUME_TO_CATALOG || jcr->getJobLevel() == L_VERIFY_DISK_TO_CATALOG || jcr->getJobLevel() == L_VERIFY_DATA)) { Name = jcr->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->RestoreJobId) { verify_jobid = jcr->RestoreJobId; Dmsg1(100, "Supplied jobid=%d\n", verify_jobid); } else { if (!db_find_last_jobid(jcr, jcr->db, Name, &jr)) { if (jcr->getJobLevel() == 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->getJobLevel() == L_VERIFY_CATALOG || jcr->getJobLevel() == L_VERIFY_VOLUME_TO_CATALOG || jcr->getJobLevel() == L_VERIFY_DISK_TO_CATALOG || jcr->getJobLevel() == L_VERIFY_DATA) { 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->getJobLevel() == L_VERIFY_VOLUME_TO_CATALOG || jcr->getJobLevel() == L_VERIFY_DATA) { int stat; /* * Note: negative status is an error, zero status, means * no files were backed up, so skip calling SD and * client. */ stat = create_restore_bootstrap_file(jcr); if (stat < 0) { /* error */ return false; } else if (stat == 0) { /* No files, nothing to do */ verify_cleanup(jcr, JS_Terminated); /* clean up */ return true; /* get out */ } } else { jcr->sd_auth_key = bstrdup("dummy"); /* dummy Storage daemon key */ } /* Pass the original fileset to the client */ if (jcr->getJobLevel() == L_VERIFY_DATA) { FILESET_DBR fdbr; memset(&fdbr, 0, sizeof(fdbr)); fdbr.FileSetId = jcr->previous_jr.FileSetId; if (!db_get_fileset_record(jcr, jcr->db, &fdbr)) { Jmsg(jcr, M_FATAL, 0, _("Could not get fileset record from previous Job. ERR=%s"), db_strerror(jcr->db)); return false; } jcr->fileset = (FILESET *)GetResWithName(R_FILESET, fdbr.FileSet); if (!jcr->fileset) { if (jcr->verify_job) { jcr->fileset = jcr->verify_job->fileset; Jmsg(jcr, M_WARNING, 0, _("Could not find FileSet resource \"%s\" from previous Job\n"), fdbr.FileSet); Jmsg(jcr, M_INFO, 0, _("Using FileSet \"%\"\n"), jcr->fileset->name()); } else { Jmsg(jcr, M_FATAL, 0, _("Could not get FileSet resource for verify Job.")); return false; } } Dmsg1(50, "FileSet = %s\n", jcr->fileset->name()); } /* Pass the current fileset to the client */ if (jcr->getJobLevel() == 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->getJobLevel()); 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->getJobLevel()), jcr->Job); if (jcr->getJobLevel() == L_VERIFY_VOLUME_TO_CATALOG || jcr->getJobLevel() == L_VERIFY_DATA) { /* * Start conversation with Storage daemon */ jcr->setJobStatus(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; } sd = jcr->store_bsock; jcr->sd_calls_client = jcr->client->sd_calls_client; /* * Send the bootstrap file -- what Volumes/files to restore */ if (!send_bootstrap_file(jcr, sd) || !response(jcr, sd, OKbootstrap, "Bootstrap", DISPLAY_ERROR)) { goto bail_out; } if (!jcr->sd_calls_client) { if (!run_storage_and_start_message_thread(jcr, sd)) { return false; } } } /* * OK, now connect to the File daemon * and ask him for the files. */ jcr->setJobStatus(JS_Blocked); if (!connect_to_file_daemon(jcr, 10, FDConnectTimeout, 1)) { goto bail_out; } jcr->setJobStatus(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->getJobLevel()) { case L_VERIFY_INIT: level = "init"; break; case L_VERIFY_CATALOG: level = "catalog"; break; case L_VERIFY_DATA: case L_VERIFY_VOLUME_TO_CATALOG: if (jcr->sd_calls_client) { if (jcr->FDVersion < 10) { Jmsg(jcr, M_FATAL, 0, _("The File daemon does not support SDCallsClient.\n")); goto bail_out; } if (!send_client_addr_to_sd(jcr)) { goto bail_out; } if (!run_storage_and_start_message_thread(jcr, jcr->store_bsock)) { return false; } store_address = jcr->rstore->address; /* dummy */ store_port = 0; /* flag that SD calls FD */ } else { /* * send Storage daemon address to the File daemon */ if (jcr->rstore->SDDport == 0) { jcr->rstore->SDDport = jcr->rstore->SDport; } store_address = get_storage_address(jcr->client, jcr->rstore); store_port = jcr->rstore->SDDport; } if (!send_store_addr_to_fd(jcr, jcr->rstore, store_address, store_port)) { goto bail_out; } if (!jcr->RestoreBootstrap) { Jmsg0(jcr, M_FATAL, 0, _("Deprecated feature ... use bootstrap.\n")); goto bail_out; } if (jcr->getJobLevel() == L_VERIFY_VOLUME_TO_CATALOG) { level = "volume"; } else { 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->getJobLevel(), jcr->getJobLevel()); 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->getJobLevel()) { 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; case L_VERIFY_DATA: /* Nothing special to do */ bget_dirmsg(fd); /* eat EOD */ break; default: Jmsg1(jcr, M_FATAL, 0, _("Unimplemented verify level %d\n"), jcr->getJobLevel()); goto bail_out; } stat = wait_for_job_termination(jcr); verify_cleanup(jcr, stat); return true; bail_out: return false; }
/* * 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)) { int32_t stream, full_stream; char *attr, *p, *fn; char Opts_Digest[MAXSTRING]; /* Verify Opts or MD5/SHA1 digest */ if (job_canceled(jcr)) { free_pool_memory(fname); 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, &full_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); free_pool_memory(fname); return; } stream = full_stream & STREAMMASK_TYPE; Dmsg4(30, "Got hdr: FilInx=%d FullStream=%d Stream=%d fname=%s.\n", file_index, full_stream, stream, fname); /* * 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, sizeof(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); jcr->setJobStatus(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, sizeof(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)); jcr->setJobStatus(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); jcr->setJobStatus(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); jcr->setJobStatus(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); jcr->setJobStatus(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); jcr->setJobStatus(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)); jcr->setJobStatus(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")); jcr->setJobStatus(JS_Differences); } break; case 'm': if (statc.st_mtime != statf.st_mtime) { prt_fname(jcr); Jmsg(jcr, M_INFO, 0, _(" st_mtime differs\n")); jcr->setJobStatus(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")); jcr->setJobStatus(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)); jcr->setJobStatus(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); free_pool_memory(fname); 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); jcr->setJobStatus(JS_Differences); } do_Digest = CRYPTO_DIGEST_NONE; } } jcr->JobFiles = file_index; } if (fd->is_error()) { berrno be; Jmsg2(jcr, M_FATAL, 0, _("bdird<filed: bad attributes from filed n=%d : %s\n"), n, be.bstrerror()); free_pool_memory(fname); 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.FileIndex > 0 " "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) { jcr->setJobStatus(JS_Differences); } free_pool_memory(fname); }
/* * Here we wait for the File daemon to signal termination, * then we wait for the Storage daemon. When both * are done, we return the job status. * Also used by restore.c */ int wait_for_job_termination(JCR *jcr, int timeout) { int32_t n = 0; BSOCK *fd = jcr->file_bsock; bool fd_ok = false; uint32_t JobFiles, JobErrors; uint32_t JobWarnings = 0; uint64_t ReadBytes = 0; uint64_t JobBytes = 0; int VSS = 0; int Encrypt = 0; btimer_t *tid=NULL; set_jcr_job_status(jcr, JS_Running); if (fd) { if (timeout) { tid = start_bsock_timer(fd, timeout); /* TODO: New timeout directive??? */ } /* Wait for Client to terminate */ while ((n = bget_dirmsg(fd)) >= 0) { if (!fd_ok && (sscanf(fd->msg, EndJob, &jcr->FDJobStatus, &JobFiles, &ReadBytes, &JobBytes, &JobErrors, &VSS, &Encrypt) == 7 || sscanf(fd->msg, OldEndJob, &jcr->FDJobStatus, &JobFiles, &ReadBytes, &JobBytes, &JobErrors) == 5)) { fd_ok = true; set_jcr_job_status(jcr, jcr->FDJobStatus); Dmsg1(100, "FDStatus=%c\n", (char)jcr->JobStatus); } else { Jmsg(jcr, M_WARNING, 0, _("Unexpected Client Job message: %s\n"), fd->msg); } if (job_canceled(jcr)) { break; } } if (tid) { stop_bsock_timer(tid); } if (is_bnet_error(fd)) { Jmsg(jcr, M_FATAL, 0, _("Network error with FD during %s: ERR=%s\n"), job_type_to_str(jcr->get_JobType()), fd->bstrerror()); } fd->signal(BNET_TERMINATE); /* tell Client we are terminating */ } /* Force cancel in SD if failing */ if (job_canceled(jcr) || !fd_ok) { cancel_storage_daemon_job(jcr); } /* Note, the SD stores in jcr->JobFiles/ReadBytes/JobBytes/JobErrors */ wait_for_storage_daemon_termination(jcr); /* Return values from FD */ if (fd_ok) { jcr->JobFiles = JobFiles; jcr->JobErrors += JobErrors; /* Keep total errors */ jcr->ReadBytes = ReadBytes; jcr->JobBytes = JobBytes; jcr->JobWarnings = JobWarnings; jcr->VSS = VSS; jcr->Encrypt = Encrypt; } else { Jmsg(jcr, M_FATAL, 0, _("No Job status returned from FD.\n")); } // Dmsg4(100, "fd_ok=%d FDJS=%d JS=%d SDJS=%d\n", fd_ok, jcr->FDJobStatus, // jcr->JobStatus, jcr->SDJobStatus); /* Return the first error status we find Dir, FD, or SD */ if (!fd_ok || is_bnet_error(fd)) { /* if fd not set, that use !fd_ok */ jcr->FDJobStatus = JS_ErrorTerminated; } if (jcr->JobStatus != JS_Terminated) { return jcr->JobStatus; } if (jcr->FDJobStatus != JS_Terminated) { return jcr->FDJobStatus; } return jcr->SDJobStatus; }