/* * Find JobId of last job that ran. E.g. for * VERIFY_CATALOG we want the JobId of the last INIT. * For VERIFY_VOLUME_TO_CATALOG, we want the JobId of the last Job. * * Returns: true on success * false on failure */ bool db_find_last_jobid(JCR *jcr, B_DB *mdb, const char *Name, JOB_DBR *jr) { SQL_ROW row; char ed1[50]; /* Find last full */ db_lock(mdb); Dmsg2(100, "JobLevel=%d JobType=%d\n", jr->JobLevel, jr->JobType); if (jr->JobLevel == L_VERIFY_CATALOG) { Mmsg(mdb->cmd, "SELECT JobId FROM Job WHERE Type='V' AND Level='%c' AND " " JobStatus IN ('T','W') AND Name='%s' AND " "ClientId=%s ORDER BY StartTime DESC LIMIT 1", L_VERIFY_INIT, jr->Name, edit_int64(jr->ClientId, ed1)); } else if (jr->JobLevel == L_VERIFY_VOLUME_TO_CATALOG || jr->JobLevel == L_VERIFY_DISK_TO_CATALOG || jr->JobType == JT_BACKUP) { if (Name) { Mmsg(mdb->cmd, "SELECT JobId FROM Job WHERE Type='B' AND JobStatus IN ('T','W') AND " "Name='%s' ORDER BY StartTime DESC LIMIT 1", Name); } else { Mmsg(mdb->cmd, "SELECT JobId FROM Job WHERE Type='B' AND JobStatus IN ('T','W') AND " "ClientId=%s ORDER BY StartTime DESC LIMIT 1", edit_int64(jr->ClientId, ed1)); } } else { Mmsg1(&mdb->errmsg, _("Unknown Job level=%d\n"), jr->JobLevel); db_unlock(mdb); return false; } Dmsg1(100, "Query: %s\n", mdb->cmd); if (!QUERY_DB(jcr, mdb, mdb->cmd)) { db_unlock(mdb); return false; } if ((row = sql_fetch_row(mdb)) == NULL) { Mmsg1(&mdb->errmsg, _("No Job found for: %s.\n"), mdb->cmd); sql_free_result(mdb); db_unlock(mdb); return false; } jr->JobId = str_to_int64(row[0]); sql_free_result(mdb); Dmsg1(100, "db_get_last_jobid: got JobId=%d\n", jr->JobId); if (jr->JobId <= 0) { Mmsg1(&mdb->errmsg, _("No Job found for: %s\n"), mdb->cmd); db_unlock(mdb); return false; } db_unlock(mdb); return true; }
/* * Backward space a record * * Returns: false on failure * true on success */ bool generic_tape_device::bsr(int num) { struct mtop mt_com; int status; if (!is_open()) { dev_errno = EBADF; Mmsg0(errmsg, _("Bad call to bsr_dev. Device not open\n")); Emsg0(M_FATAL, 0, errmsg); return false; } if (!has_cap(CAP_BSR)) { Mmsg1(errmsg, _("ioctl MTBSR not permitted on %s.\n"), prt_name); return false; } Dmsg0(100, "bsr_dev\n"); block_num -= num; clear_eof(); clear_eot(); mt_com.mt_op = MTBSR; mt_com.mt_count = num; status = d_ioctl(m_fd, MTIOCTOP, (char *)&mt_com); if (status < 0) { berrno be; clrerror(mt_com.mt_op); Mmsg2(errmsg, _("ioctl MTBSR error on %s. ERR=%s.\n"), prt_name, be.bstrerror()); } return status == 0; }
/* * Get record max. Query is already in mdb->cmd * No locking done * * Returns: -1 on failure * count on success */ int get_sql_record_max(JCR *jcr, B_DB *mdb) { SQL_ROW row; int retval = 0; if (QUERY_DB(jcr, mdb, mdb->cmd)) { if ((row = sql_fetch_row(mdb)) == NULL) { Mmsg1(&mdb->errmsg, _("error fetching row: %s\n"), sql_strerror(mdb)); retval = -1; } else { retval = str_to_int64(row[0]); } sql_free_result(mdb); } else { Mmsg1(&mdb->errmsg, _("error fetching row: %s\n"), sql_strerror(mdb)); retval = -1; } return retval; }
/* * Delete Pool record, must also delete all associated * Media records. * * Returns: false on error * true on success * PoolId = number of Pools deleted (should be 1) * NumVols = number of Media records deleted */ bool db_delete_pool_record(JCR *jcr, B_DB *mdb, POOL_DBR *pr) { bool retval = false; SQL_ROW row; int num_rows; char esc[MAX_ESCAPE_NAME_LENGTH]; db_lock(mdb); mdb->db_escape_string(jcr, esc, pr->Name, strlen(pr->Name)); Mmsg(mdb->cmd, "SELECT PoolId FROM Pool WHERE Name='%s'", esc); Dmsg1(10, "selectpool: %s\n", mdb->cmd); pr->PoolId = pr->NumVols = 0; if (QUERY_DB(jcr, mdb, mdb->cmd)) { num_rows = sql_num_rows(mdb); if (num_rows == 0) { Mmsg(mdb->errmsg, _("No pool record %s exists\n"), pr->Name); sql_free_result(mdb); goto bail_out; } else if (num_rows != 1) { Mmsg(mdb->errmsg, _("Expecting one pool record, got %d\n"), num_rows); sql_free_result(mdb); goto bail_out; } if ((row = sql_fetch_row(mdb)) == NULL) { Mmsg1(mdb->errmsg, _("Error fetching row %s\n"), sql_strerror(mdb)); goto bail_out; } pr->PoolId = str_to_int64(row[0]); sql_free_result(mdb); } /* Delete Media owned by this pool */ Mmsg(mdb->cmd, "DELETE FROM Media WHERE Media.PoolId = %d", pr->PoolId); pr->NumVols = DELETE_DB(jcr, mdb, mdb->cmd); Dmsg1(200, "Deleted %d Media records\n", pr->NumVols); /* Delete Pool */ Mmsg(mdb->cmd, "DELETE FROM Pool WHERE Pool.PoolId = %d", pr->PoolId); pr->PoolId = DELETE_DB(jcr, mdb, mdb->cmd); Dmsg1(200, "Deleted %d Pool records\n", pr->PoolId); retval = true; bail_out: db_unlock(mdb); return retval; }
/* * Given a full filename, split it into its path * and filename parts. They are returned in pool memory * in the mdb structure. */ void split_path_and_file(JCR *jcr, B_DB *mdb, const char *fname) { const char *p, *f; /* Find path without the filename. * I.e. everything after the last / is a "filename". * OK, maybe it is a directory name, but we treat it like * a filename. If we don't find a / then the whole name * must be a path name (e.g. c:). */ for (p=f=fname; *p; p++) { if (IsPathSeparator(*p)) { f = p; /* set pos of last slash */ } } if (IsPathSeparator(*f)) { /* did we find a slash? */ f++; /* yes, point to filename */ } else { f = p; /* no, whole thing must be path name */ } /* If filename doesn't exist (i.e. root directory), we * simply create a blank name consisting of a single * space. This makes handling zero length filenames * easier. */ mdb->fnl = p - f; if (mdb->fnl > 0) { mdb->fname = check_pool_memory_size(mdb->fname, mdb->fnl+1); memcpy(mdb->fname, f, mdb->fnl); /* copy filename */ mdb->fname[mdb->fnl] = 0; } else { mdb->fname[0] = 0; mdb->fnl = 0; } mdb->pnl = f - fname; if (mdb->pnl > 0) { mdb->path = check_pool_memory_size(mdb->path, mdb->pnl+1); memcpy(mdb->path, fname, mdb->pnl); mdb->path[mdb->pnl] = 0; } else { Mmsg1(&mdb->errmsg, _("Path length is zero. File=%s\n"), fname); Jmsg(jcr, M_ERROR, 0, "%s", mdb->errmsg); mdb->path[0] = 0; mdb->pnl = 0; } Dmsg2(500, "split path=%s file=%s\n", mdb->path, mdb->fname); }
/* * Foward space num records * * Returns: false on failure * true on success */ bool generic_tape_device::fsr(int num) { struct mtop mt_com; int status; if (!is_open()) { dev_errno = EBADF; Mmsg0(errmsg, _("Bad call to fsr. Device not open\n")); Emsg0(M_FATAL, 0, errmsg); return false; } if (!has_cap(CAP_FSR)) { Mmsg1(errmsg, _("ioctl MTFSR not permitted on %s.\n"), prt_name); return false; } Dmsg1(100, "fsr %d\n", num); mt_com.mt_op = MTFSR; mt_com.mt_count = num; status = d_ioctl(m_fd, MTIOCTOP, (char *)&mt_com); if (status == 0) { clear_eof(); block_num += num; } else { berrno be; struct mtget mt_stat; clrerror(mt_com.mt_op); Dmsg1(100, "FSF fail: ERR=%s\n", be.bstrerror()); if (dev_get_os_pos(this, &mt_stat)) { Dmsg4(100, "Adjust from %d:%d to %d:%d\n", file, block_num, mt_stat.mt_fileno, mt_stat.mt_blkno); file = mt_stat.mt_fileno; block_num = mt_stat.mt_blkno; } else { if (at_eof()) { set_eot(); } else { set_ateof(); } } Mmsg3(errmsg, _("ioctl MTFSR %d error on %s. ERR=%s.\n"), num, prt_name, be.bstrerror()); } return status == 0; }
bool win32_fifo_device::eod(DCR *dcr) { if (m_fd < 0) { dev_errno = EBADF; Mmsg1(errmsg, _("Bad call to eod. Device %s not open\n"), prt_name); return false; } Dmsg0(100, "Enter eod\n"); if (at_eot()) { return true; } clear_eof(); /* remove EOF flag */ block_num = file = 0; file_size = 0; file_addr = 0; return true; }
/* * Position device to end of medium (end of data) * * Returns: true on succes * false on error */ bool DEVICE::eod(DCR *dcr) { boffset_t pos; if (m_fd < 0) { dev_errno = EBADF; Mmsg1(errmsg, _("Bad call to eod. Device %s not open\n"), print_name()); return false; } if (is_vtl()) { return true; } Dmsg0(100, "Enter eod\n"); if (at_eot()) { return true; } clear_eof(); /* remove EOF flag */ block_num = file = 0; file_size = 0; file_addr = 0; pos = lseek(dcr, (boffset_t)0, SEEK_END); Dmsg1(200, "====== Seek to %lld\n", pos); if (pos >= 0) { update_pos(dcr); set_eot(); return true; } dev_errno = errno; berrno be; Mmsg2(errmsg, _("lseek error on %s. ERR=%s.\n"), print_name(), be.bstrerror()); Dmsg0(100, errmsg); return false; }
/* * Find JobId of last job that ran. E.g. for * VERIFY_CATALOG we want the JobId of the last INIT. * For VERIFY_VOLUME_TO_CATALOG, we want the JobId of the last Job. * * Returns: true on success * false on failure */ bool db_find_last_jobid(JCR *jcr, B_DB *mdb, const char *Name, JOB_DBR *jr) { bool retval = false; SQL_ROW row; char ed1[50]; char esc_name[MAX_ESCAPE_NAME_LENGTH]; db_lock(mdb); /* Find last full */ Dmsg2(100, "JobLevel=%d JobType=%d\n", jr->JobLevel, jr->JobType); if (jr->JobLevel == L_VERIFY_CATALOG) { mdb->db_escape_string(jcr, esc_name, jr->Name, strlen(jr->Name)); Mmsg(mdb->cmd, "SELECT JobId FROM Job WHERE Type='V' AND Level='%c' AND " " JobStatus IN ('T','W') AND Name='%s' AND " "ClientId=%s ORDER BY StartTime DESC LIMIT 1", L_VERIFY_INIT, esc_name, edit_int64(jr->ClientId, ed1)); } else if (jr->JobLevel == L_VERIFY_VOLUME_TO_CATALOG || jr->JobLevel == L_VERIFY_DISK_TO_CATALOG || jr->JobType == JT_BACKUP) { if (Name) { mdb->db_escape_string(jcr, esc_name, (char*)Name, MIN(strlen(Name), sizeof(esc_name))); Mmsg(mdb->cmd, "SELECT JobId FROM Job WHERE Type='B' AND JobStatus IN ('T','W') AND " "Name='%s' ORDER BY StartTime DESC LIMIT 1", esc_name); } else { Mmsg(mdb->cmd, "SELECT JobId FROM Job WHERE Type='B' AND JobStatus IN ('T','W') AND " "ClientId=%s ORDER BY StartTime DESC LIMIT 1", edit_int64(jr->ClientId, ed1)); } } else { Mmsg1(&mdb->errmsg, _("Unknown Job level=%d\n"), jr->JobLevel); goto bail_out; } Dmsg1(100, "Query: %s\n", mdb->cmd); if (!QUERY_DB(jcr, mdb, mdb->cmd)) { goto bail_out; } if ((row = sql_fetch_row(mdb)) == NULL) { Mmsg1(&mdb->errmsg, _("No Job found for: %s.\n"), mdb->cmd); sql_free_result(mdb); goto bail_out; } jr->JobId = str_to_int64(row[0]); sql_free_result(mdb); Dmsg1(100, "db_get_last_jobid: got JobId=%d\n", jr->JobId); if (jr->JobId <= 0) { Mmsg1(&mdb->errmsg, _("No Job found for: %s\n"), mdb->cmd); goto bail_out; } retval = true; bail_out: db_unlock(mdb); return retval; }
/* * We read an ANSI label and compare the Volume name. We require * a VOL1 record of 80 characters followed by a HDR1 record containing * BACULA.DATA in the filename field. We then read up to 3 more * header records (they are not required) and an EOF, at which * point, all is good. * * Returns: * VOL_OK Volume name OK * VOL_NO_LABEL No ANSI label on Volume * VOL_IO_ERROR I/O error on read * VOL_NAME_ERROR Wrong name in VOL1 record * VOL_LABEL_ERROR Probably an ANSI label, but something wrong * */ int read_ansi_ibm_label(DCR *dcr) { DEVICE * volatile dev = dcr->dev; JCR *jcr = dcr->jcr; char label[80]; /* tape label */ int stat, i; char *VolName = dcr->VolumeName; bool ok = false; /* * Read VOL1, HDR1, HDR2 labels, but ignore the data * If tape read the following EOF mark, on disk do * not read. */ Dmsg0(100, "Read ansi label.\n"); if (!dev->is_tape()) { return VOL_OK; } dev->label_type = B_BACULA_LABEL; /* assume Bacula label */ /* Read a maximum of 5 records VOL1, HDR1, ... HDR4 */ for (i=0; i < 6; i++) { do { stat = dev->read(label, sizeof(label)); } while (stat == -1 && errno == EINTR); if (stat < 0) { berrno be; dev->clrerror(-1); Dmsg1(100, "Read device got: ERR=%s\n", be.bstrerror()); Mmsg2(jcr->errmsg, _("Read error on device %s in ANSI label. ERR=%s\n"), dev->dev_name, be.bstrerror()); Jmsg(jcr, M_ERROR, 0, "%s", dev->errmsg); dev->VolCatInfo.VolCatErrors++; return VOL_IO_ERROR; } if (stat == 0) { if (dev->at_eof()) { dev->set_eot(); /* second eof, set eot bit */ Dmsg0(100, "EOM on ANSI label\n"); Mmsg0(jcr->errmsg, _("Insane! End of tape while reading ANSI label.\n")); return VOL_LABEL_ERROR; /* at EOM this shouldn't happen */ } else { dev->set_ateof(); /* set eof state */ } } switch (i) { case 0: /* Want VOL1 label */ if (stat == 80) { if (strncmp("VOL1", label, 4) == 0) { ok = true; dev->label_type = B_ANSI_LABEL; Dmsg0(100, "Got ANSI VOL1 label\n"); } else { /* Try EBCDIC */ ebcdic_to_ascii(label, label, sizeof(label)); if (strncmp("VOL1", label, 4) == 0) { ok = true;; dev->label_type = B_IBM_LABEL; Dmsg0(100, "Found IBM label.\n"); Dmsg0(100, "Got IBM VOL1 label\n"); } } } if (!ok) { Dmsg0(100, "No VOL1 label\n"); Mmsg0(jcr->errmsg, _("No VOL1 label while reading ANSI/IBM label.\n")); return VOL_NO_LABEL; /* No ANSI label */ } /* Compare Volume Names allow special wild card */ if (VolName && *VolName && *VolName != '*') { if (!same_label_names(VolName, &label[4])) { char *p = &label[4]; char *q; free_volume(dev); /* Store new Volume name */ q = dev->VolHdr.VolumeName; for (int i=0; *p != ' ' && i < 6; i++) { *q++ = *p++; } *q = 0; Dmsg0(100, "Call reserve_volume\n"); /* ***FIXME*** why is this reserve_volume() needed???? KES */ reserve_volume(dcr, dev->VolHdr.VolumeName); dev = dcr->dev; /* may have changed in reserve_volume */ Dmsg2(100, "Wanted ANSI Vol %s got %6s\n", VolName, dev->VolHdr.VolumeName); Mmsg2(jcr->errmsg, _("Wanted ANSI Volume \"%s\" got \"%s\"\n"), VolName, dev->VolHdr.VolumeName); return VOL_NAME_ERROR; } } break; case 1: if (dev->label_type == B_IBM_LABEL) { ebcdic_to_ascii(label, label, sizeof(label)); } if (stat != 80 || strncmp("HDR1", label, 4) != 0) { Dmsg0(100, "No HDR1 label\n"); Mmsg0(jcr->errmsg, _("No HDR1 label while reading ANSI label.\n")); return VOL_LABEL_ERROR; } if (strncmp("BACULA.DATA", &label[4], 11) != 0) { Dmsg1(100, "HD1 not Bacula label. Wanted BACULA.DATA got %11s\n", &label[4]); Mmsg1(jcr->errmsg, _("ANSI/IBM Volume \"%s\" does not belong to Bacula.\n"), dev->VolHdr.VolumeName); return VOL_NAME_ERROR; /* Not a Bacula label */ } Dmsg0(100, "Got HDR1 label\n"); break; case 2: if (dev->label_type == B_IBM_LABEL) { ebcdic_to_ascii(label, label, sizeof(label)); } if (stat != 80 || strncmp("HDR2", label, 4) != 0) { Dmsg0(100, "No HDR2 label\n"); Mmsg0(jcr->errmsg, _("No HDR2 label while reading ANSI/IBM label.\n")); return VOL_LABEL_ERROR; } Dmsg0(100, "Got ANSI HDR2 label\n"); break; default: if (stat == 0) { Dmsg0(100, "ANSI label OK\n"); return VOL_OK; } if (dev->label_type == B_IBM_LABEL) { ebcdic_to_ascii(label, label, sizeof(label)); } if (stat != 80 || strncmp("HDR", label, 3) != 0) { Dmsg0(100, "Unknown or bad ANSI/IBM label record.\n"); Mmsg0(jcr->errmsg, _("Unknown or bad ANSI/IBM label record.\n")); return VOL_LABEL_ERROR; } Dmsg0(100, "Got HDR label\n"); break; } } Dmsg0(100, "Too many records in ANSI/IBM label.\n"); Mmsg0(jcr->errmsg, _("Too many records in while reading ANSI/IBM label.\n")); return VOL_LABEL_ERROR; }
/********************************************************************* * Acquire device for reading. * The drive should have previously been reserved by calling * reserve_device_for_read(). We read the Volume label from the block and * leave the block pointers just after the label. * * Returns: NULL if failed for any reason * dcr if successful */ bool acquire_device_for_read(DCR *dcr) { DEVICE *dev; JCR *jcr = dcr->jcr; bool ok = false; bool tape_previously_mounted; VOL_LIST *vol; bool try_autochanger = true; int i; int vol_label_status; int retry = 0; Enter(rdbglvl); dev = dcr->dev; dev->Lock_read_acquire(); Dmsg2(rdbglvl, "dcr=%p dev=%p\n", dcr, dcr->dev); Dmsg2(rdbglvl, "MediaType dcr=%s dev=%s\n", dcr->media_type, dev->device->media_type); dev->dblock(BST_DOING_ACQUIRE); if (dev->num_writers > 0) { Jmsg2(jcr, M_FATAL, 0, _("Acquire read: num_writers=%d not zero. Job %d canceled.\n"), dev->num_writers, jcr->JobId); goto get_out; } /* Find next Volume, if any */ vol = jcr->VolList; if (!vol) { char ed1[50]; Jmsg(jcr, M_FATAL, 0, _("No volumes specified for reading. Job %s canceled.\n"), edit_int64(jcr->JobId, ed1)); goto get_out; } jcr->CurReadVolume++; for (i=1; i<jcr->CurReadVolume; i++) { vol = vol->next; } if (!vol) { Jmsg(jcr, M_FATAL, 0, _("Logic error: no next volume to read. Numvol=%d Curvol=%d\n"), jcr->NumReadVolumes, jcr->CurReadVolume); goto get_out; /* should not happen */ } set_dcr_from_vol(dcr, vol); Dmsg2(rdbglvl, "Want Vol=%s Slot=%d\n", vol->VolumeName, vol->Slot); /* * If the MediaType requested for this volume is not the * same as the current drive, we attempt to find the same * device that was used to write the orginal volume. If * found, we switch to using that device. * * N.B. A lot of routines rely on the dcr pointer not changing * read_records.c even has multiple dcrs cached, so we take care * here to release all important parts of the dcr and re-acquire * them such as the block pointer (size may change), but we do * not release the dcr. */ Dmsg2(rdbglvl, "MediaType dcr=%s dev=%s\n", dcr->media_type, dev->device->media_type); if (dcr->media_type[0] && !bstrcmp(dcr->media_type, dev->device->media_type)) { RCTX rctx; DIRSTORE *store; int status; Jmsg3(jcr, M_INFO, 0, _("Changing read device. Want Media Type=\"%s\" have=\"%s\"\n" " device=%s\n"), dcr->media_type, dev->device->media_type, dev->print_name()); Dmsg3(rdbglvl, "Changing read device. Want Media Type=\"%s\" have=\"%s\"\n" " device=%s\n", dcr->media_type, dev->device->media_type, dev->print_name()); dev->dunblock(DEV_UNLOCKED); lock_reservations(); memset(&rctx, 0, sizeof(RCTX)); rctx.jcr = jcr; jcr->read_dcr = dcr; jcr->reserve_msgs = New(alist(10, not_owned_by_alist)); rctx.any_drive = true; rctx.device_name = vol->device; store = new DIRSTORE; memset(store, 0, sizeof(DIRSTORE)); store->name[0] = 0; /* No dir name */ bstrncpy(store->media_type, vol->MediaType, sizeof(store->media_type)); bstrncpy(store->pool_name, dcr->pool_name, sizeof(store->pool_name)); bstrncpy(store->pool_type, dcr->pool_type, sizeof(store->pool_type)); store->append = false; rctx.store = store; clean_device(dcr); /* clean up the dcr */ /* * Search for a new device */ status = search_res_for_device(rctx); release_reserve_messages(jcr); /* release queued messages */ unlock_reservations(); if (status == 1) { /* found new device to use */ /* * Switching devices, so acquire lock on new device, * then release the old one. */ dcr->dev->Lock_read_acquire(); /* lock new one */ dev->Unlock_read_acquire(); /* release old one */ dev = dcr->dev; /* get new device pointer */ dev->dblock(BST_DOING_ACQUIRE); dcr->VolumeName[0] = 0; Jmsg(jcr, M_INFO, 0, _("Media Type change. New read device %s chosen.\n"), dev->print_name()); Dmsg1(50, "Media Type change. New read device %s chosen.\n", dev->print_name()); bstrncpy(dcr->VolumeName, vol->VolumeName, sizeof(dcr->VolumeName)); dcr->setVolCatName(vol->VolumeName); bstrncpy(dcr->media_type, vol->MediaType, sizeof(dcr->media_type)); dcr->VolCatInfo.Slot = vol->Slot; dcr->VolCatInfo.InChanger = vol->Slot > 0; bstrncpy(dcr->pool_name, store->pool_name, sizeof(dcr->pool_name)); bstrncpy(dcr->pool_type, store->pool_type, sizeof(dcr->pool_type)); } else { /* error */ Jmsg1(jcr, M_FATAL, 0, _("No suitable device found to read Volume \"%s\"\n"), vol->VolumeName); Dmsg1(rdbglvl, "No suitable device found to read Volume \"%s\"\n", vol->VolumeName); goto get_out; } } Dmsg2(rdbglvl, "MediaType dcr=%s dev=%s\n", dcr->media_type, dev->device->media_type); dev->clear_unload(); if (dev->vol && dev->vol->is_swapping()) { dev->vol->set_slot(vol->Slot); Dmsg3(rdbglvl, "swapping: slot=%d Vol=%s dev=%s\n", dev->vol->get_slot(), dev->vol->vol_name, dev->print_name()); } init_device_wait_timers(dcr); tape_previously_mounted = dev->can_read() || dev->can_append() || dev->is_labeled(); // tape_initially_mounted = tape_previously_mounted; /* Volume info is always needed because of VolParts */ Dmsg1(rdbglvl, "dir_get_volume_info vol=%s\n", dcr->VolumeName); if (!dcr->dir_get_volume_info(GET_VOL_INFO_FOR_READ)) { Dmsg2(rdbglvl, "dir_get_vol_info failed for vol=%s: %s\n", dcr->VolumeName, jcr->errmsg); Jmsg1(jcr, M_WARNING, 0, "Read acquire: %s", jcr->errmsg); } dev->set_load(); /* set to load volume */ for ( ;; ) { /* If not polling limit retries */ if (!dev->poll && retry++ > 10) { break; } dev->clear_labeled(); /* force reread of label */ if (job_canceled(jcr)) { char ed1[50]; Mmsg1(dev->errmsg, _("Job %s canceled.\n"), edit_int64(jcr->JobId, ed1)); Jmsg(jcr, M_INFO, 0, dev->errmsg); goto get_out; /* error return */ } dcr->do_unload(); dcr->do_swapping(false/*!is_writing*/); dcr->do_load(false /*!is_writing*/); set_dcr_from_vol(dcr, vol); /* refresh dcr with desired volume info */ /* * This code ensures that the device is ready for * reading. If it is a file, it opens it. * If it is a tape, it checks the volume name */ Dmsg1(rdbglvl, "stored: open vol=%s\n", dcr->VolumeName); if (!dev->open(dcr, OPEN_READ_ONLY)) { if (!dev->poll) { Jmsg3(jcr, M_WARNING, 0, _("Read open device %s Volume \"%s\" failed: ERR=%s\n"), dev->print_name(), dcr->VolumeName, dev->bstrerror()); } goto default_path; } Dmsg1(rdbglvl, "opened dev %s OK\n", dev->print_name()); /* Read Volume Label */ Dmsg0(rdbglvl, "calling read-vol-label\n"); vol_label_status = read_dev_volume_label(dcr); switch (vol_label_status) { case VOL_OK: Dmsg0(rdbglvl, "Got correct volume.\n"); ok = true; dev->VolCatInfo = dcr->VolCatInfo; /* structure assignment */ break; /* got it */ case VOL_IO_ERROR: Dmsg0(rdbglvl, "IO Error\n"); /* * Send error message generated by read_dev_volume_label() * only we really had a tape mounted. This supresses superfluous * error messages when nothing is mounted. */ if (tape_previously_mounted) { Jmsg(jcr, M_WARNING, 0, "Read acquire: %s", jcr->errmsg); } goto default_path; case VOL_NAME_ERROR: Dmsg3(rdbglvl, "Vol name=%s want=%s drv=%s.\n", dev->VolHdr.VolumeName, dcr->VolumeName, dev->print_name()); if (dev->is_volume_to_unload()) { goto default_path; } dev->set_unload(); /* force unload of unwanted tape */ if (!unload_autochanger(dcr, -1)) { /* at least free the device so we can re-open with correct volume */ dev->close(dcr); free_volume(dev); } dev->set_load(); /* Fall through */ default: Jmsg1(jcr, M_WARNING, 0, "Read acquire: %s", jcr->errmsg); default_path: Dmsg0(rdbglvl, "default path\n"); tape_previously_mounted = true; /* * If the device requires mount, close it, so the device can be ejected. */ if (dev->requires_mount()) { dev->close(dcr); free_volume(dev); } /* Call autochanger only once unless ask_sysop called */ if (try_autochanger) { int status; Dmsg2(rdbglvl, "calling autoload Vol=%s Slot=%d\n", dcr->VolumeName, dcr->VolCatInfo.Slot); status = autoload_device(dcr, 0, NULL); if (status > 0) { try_autochanger = false; continue; /* try reading volume mounted */ } } /* Mount a specific volume and no other */ Dmsg0(rdbglvl, "calling dir_ask_sysop\n"); if (!dcr->dir_ask_sysop_to_mount_volume(ST_READREADY)) { goto get_out; /* error return */ } /* Volume info is always needed because of VolParts */ Dmsg1(150, "dir_get_volume_info vol=%s\n", dcr->VolumeName); if (!dcr->dir_get_volume_info(GET_VOL_INFO_FOR_READ)) { Dmsg2(150, "dir_get_vol_info failed for vol=%s: %s\n", dcr->VolumeName, jcr->errmsg); Jmsg1(jcr, M_WARNING, 0, "Read acquire: %s", jcr->errmsg); } dev->set_load(); /* set to load volume */ try_autochanger = true; /* permit trying the autochanger again */ continue; /* try reading again */ } /* end switch */ break; } /* end for loop */ if (!ok) { Jmsg1(jcr, M_FATAL, 0, _("Too many errors trying to mount device %s for reading.\n"), dev->print_name()); goto get_out; } dev->clear_append(); dev->set_read(); jcr->sendJobStatus(JS_Running); Jmsg(jcr, M_INFO, 0, _("Ready to read from volume \"%s\" on device %s.\n"), dcr->VolumeName, dev->print_name()); get_out: dev->Lock(); dcr->clear_reserved(); /* * Normally we are blocked, but in at least one error case above * we are not blocked because we unsuccessfully tried changing * devices. */ if (dev->is_blocked()) { dev->dunblock(DEV_LOCKED); } else { dev->Unlock(); /* dunblock() unlock the device too */ } Dmsg2(rdbglvl, "dcr=%p dev=%p\n", dcr, dcr->dev); Dmsg2(rdbglvl, "MediaType dcr=%s dev=%s\n", dcr->media_type, dev->device->media_type); dev->Unlock_read_acquire(); Leave(rdbglvl); return ok; }
/* * Open a volume using libdroplet. */ int object_store_device::d_open(const char *pathname, int flags, int mode) { dpl_status_t status; dpl_vfile_flag_t dpl_flags; dpl_option_t dpl_options; #if 1 Mmsg1(errmsg, _("Object Storage devices are not yet supported, please disable %s\n"), dev_name); return -1; #endif /* * Initialize the droplet library when its not done previously. */ P(mutex); if (droplet_reference_count == 0) { status = dpl_init(); if (status != DPL_SUCCESS) { V(mutex); return -1; } dpl_set_log_func(object_store_logfunc); droplet_reference_count++; } V(mutex); if (!m_object_configstring) { int len; char *bp, *next_option; bool done; if (!dev_options) { Mmsg0(errmsg, _("No device options configured\n")); Emsg0(M_FATAL, 0, errmsg); return -1; } m_object_configstring = bstrdup(dev_options); bp = m_object_configstring; while (bp) { next_option = strchr(bp, ','); if (next_option) { *next_option++ = '\0'; } done = false; for (int i = 0; !done && device_options[i].name; i++) { /* * Try to find a matching device option. */ if (bstrncasecmp(bp, device_options[i].name, device_options[i].compare_size)) { switch (device_options[i].type) { case argument_profile: m_profile = bp + device_options[i].compare_size; done = true; break; case argument_bucket: m_object_bucketname = bp + device_options[i].compare_size; done = true; break; default: break; } } } if (!done) { Mmsg1(errmsg, _("Unable to parse device option: %s\n"), bp); Emsg0(M_FATAL, 0, errmsg); goto bail_out; } bp = next_option; } if (!m_profile) { Mmsg0(errmsg, _("No droplet profile configured\n")); Emsg0(M_FATAL, 0, errmsg); goto bail_out; } /* * Strip any .profile prefix from the libdroplet profile name. */ len = strlen(m_profile); if (len > 8 && bstrcasecmp(m_profile + (len - 8), ".profile")) { m_profile[len - 8] = '\0'; } } /* * See if we need to setup a new context for this device. */ if (!m_ctx) { char *bp; /* * See if this is a path. */ bp = strrchr(m_object_configstring, '/'); if (!bp) { /* * Only a profile name. */ m_ctx = dpl_ctx_new(NULL, m_object_configstring); } else { if (bp == m_object_configstring) { /* * Profile in root of filesystem */ m_ctx = dpl_ctx_new("/", bp + 1); } else { /* * Profile somewhere else. */ *bp++ = '\0'; m_ctx = dpl_ctx_new(m_object_configstring, bp); } } /* * If we failed to allocate a new context fail the open. */ if (!m_ctx) { Mmsg1(errmsg, _("Failed to create a new context using config %s\n"), dev_options); return -1; } /* * Login if that is needed for this backend. */ status = dpl_login(m_ctx); switch (status) { case DPL_SUCCESS: break; case DPL_ENOTSUPP: /* * Backend doesn't support login which is fine. */ break; default: Mmsg2(errmsg, _("Failed to login for voume %s using dpl_login(): ERR=%s.\n"), getVolCatName(), dpl_status_str(status)); return -1; } /* * If a bucketname was defined set it in the context. */ if (m_object_bucketname) { m_ctx->cur_bucket = m_object_bucketname; } } /* * See if we don't have a file open already. */ if (m_vfd) { dpl_close(m_vfd); m_vfd = NULL; } /* * Create some options for libdroplet. * * DPL_OPTION_NOALLOC - we provide the buffer to copy the data into * no need to let the library allocate memory we * need to free after copying the data. */ memset(&dpl_options, 0, sizeof(dpl_options)); dpl_options.mask |= DPL_OPTION_NOALLOC; if (flags & O_CREAT) { dpl_flags = DPL_VFILE_FLAG_CREAT | DPL_VFILE_FLAG_RDWR; status = dpl_open(m_ctx, /* context */ getVolCatName(), /* locator */ dpl_flags, /* flags */ &dpl_options, /* options */ NULL, /* condition */ NULL, /* metadata */ NULL, /* sysmd */ NULL, /* query_params */ NULL, /* stream_status */ &m_vfd); } else { dpl_flags = DPL_VFILE_FLAG_RDWR; status = dpl_open(m_ctx, /* context */ getVolCatName(), /* locator */ dpl_flags, /* flags */ &dpl_options, /* options */ NULL, /* condition */ NULL, /* metadata */ NULL, /* sysmd */ NULL, /* query_params */ NULL, /* stream_status */ &m_vfd); } switch (status) { case DPL_SUCCESS: m_offset = 0; return 0; default: Mmsg2(errmsg, _("Failed to open %s using dpl_open(): ERR=%s.\n"), getVolCatName(), dpl_status_str(status)); m_vfd = NULL; return droplet_errno_to_system_errno(status); } bail_out: return -1; }
/* * Open the next part file. * - Close the fd * - Increment part number * - Reopen the device */ int dvd_open_next_part(DCR *dcr) { DEVICE *dev = dcr->dev; Dmsg6(29, "Enter: == open_next_part part=%d npart=%d dev=%s vol=%s mode=%d file_addr=%d\n", dev->part, dev->num_dvd_parts, dev->print_name(), dev->getVolCatName(), dev->openmode, dev->file_addr); if (!dev->is_dvd()) { Dmsg1(100, "Device %s is not dvd!!!!\n", dev->print_name()); return -1; } /* When appending, do not open a new part if the current is empty */ if (dev->can_append() && (dev->part > dev->num_dvd_parts) && (dev->part_size == 0)) { Dmsg0(29, "open_next_part exited immediately (dev->part_size == 0).\n"); return dev->fd(); } dev->close_part(dcr); /* close current part */ /* * If we have a spooled part open, write it to the * DVD before opening the next part. */ if (dev->is_part_spooled()) { Dmsg2(100, "Before open next write previous. part=%d num_parts=%d\n", dev->part, dev->num_dvd_parts); if (!dvd_write_part(dcr)) { Dmsg0(29, "Error in dvd_write part.\n"); return -1; } } dev->part_start += dev->part_size; dev->part++; Dmsg2(29, "Inc part=%d num_dvd_parts=%d\n", dev->part, dev->num_dvd_parts); /* Are we working on a part past what is written in the DVD? */ if (dev->num_dvd_parts < dev->part) { POOL_MEM archive_name(PM_FNAME); struct stat buf; /* * First check what is on DVD. If our part is there, we * are in trouble, so bail out. * NB: This is however not a problem if we are writing the first part. * It simply means that we are over writing an existing volume... */ if (dev->num_dvd_parts > 0) { make_mounted_dvd_filename(dev, archive_name); /* makes dvd name */ Dmsg1(100, "Check if part on DVD: %s\n", archive_name.c_str()); if (stat(archive_name.c_str(), &buf) == 0) { /* bad news bail out */ dev->set_part_spooled(false); Mmsg1(&dev->errmsg, _("Next Volume part already exists on DVD. Cannot continue: %s\n"), archive_name.c_str()); return -1; } } #ifdef neeeded Dmsg2(400, "num_dvd_parts=%d part=%d\n", dev->num_dvd_parts, dev->part); make_spooled_dvd_filename(dev, archive_name); /* makes spool name */ /* Check if the next part exists in spool directory . */ Dmsg1(100, "Check if part on spool: %s\n", archive_name.c_str()); if ((stat(archive_name.c_str(), &buf) == 0) || (errno != ENOENT)) { Dmsg1(29, "======= Part %s is in the way, deleting it...\n", archive_name.c_str()); /* Then try to unlink it */ if (unlink(archive_name.c_str()) < 0) { berrno be; dev->set_part_spooled(false); dev->dev_errno = errno; Mmsg2(dev->errmsg, _("open_next_part can't unlink existing part %s, ERR=%s\n"), archive_name.c_str(), be.bstrerror()); return -1; } } #endif } Dmsg2(400, "Call dev->open(vol=%s, mode=%d)\n", dcr->getVolCatName(), dev->openmode); /* Open next part. Note, this sets part_size for part opened. */ if (dev->open(dcr, OPEN_READ_ONLY) < 0) { return -1; } dev->set_labeled(); /* all next parts are "labeled" */ return dev->fd(); }
/* * If implemented in system, clear the tape error status. */ void generic_tape_device::clrerror(int func) { const char *msg = NULL; char buf[100]; dev_errno = errno; /* save errno */ if (errno == EIO) { VolCatInfo.VolCatErrors++; } if (errno == ENOTTY || errno == ENOSYS) { /* Function not implemented */ switch (func) { case -1: break; /* ignore message printed later */ case MTWEOF: msg = "WTWEOF"; clear_cap(CAP_EOF); /* turn off feature */ break; #ifdef MTEOM case MTEOM: msg = "WTEOM"; clear_cap(CAP_EOM); /* turn off feature */ break; #endif case MTFSF: msg = "MTFSF"; clear_cap(CAP_FSF); /* turn off feature */ break; case MTBSF: msg = "MTBSF"; clear_cap(CAP_BSF); /* turn off feature */ break; case MTFSR: msg = "MTFSR"; clear_cap(CAP_FSR); /* turn off feature */ break; case MTBSR: msg = "MTBSR"; clear_cap(CAP_BSR); /* turn off feature */ break; case MTREW: msg = "MTREW"; break; #ifdef MTSETBLK case MTSETBLK: msg = "MTSETBLK"; break; #endif #ifdef MTSETDRVBUFFER case MTSETDRVBUFFER: msg = "MTSETDRVBUFFER"; break; #endif #ifdef MTRESET case MTRESET: msg = "MTRESET"; break; #endif #ifdef MTSETBSIZ case MTSETBSIZ: msg = "MTSETBSIZ"; break; #endif #ifdef MTSRSZ case MTSRSZ: msg = "MTSRSZ"; break; #endif #ifdef MTLOAD case MTLOAD: msg = "MTLOAD"; break; #endif #ifdef MTLOCK case MTLOCK: msg = "MTLOCK"; break; #endif #ifdef MTUNLOCK case MTUNLOCK: msg = "MTUNLOCK"; break; #endif case MTOFFL: msg = "MTOFFL"; break; #ifdef MTIOCLRERR case MTIOCLRERR: msg = "MTIOCLRERR"; break; #endif #ifdef MTIOCERRSTAT case MTIOCERRSTAT: msg = "MTIOCERRSTAT"; break; #endif #ifdef MTCSE case MTCSE: msg = "MTCSE"; break; #endif default: bsnprintf(buf, sizeof(buf), _("unknown func code %d"), func); msg = buf; break; } if (msg != NULL) { dev_errno = ENOSYS; Mmsg1(errmsg, _("I/O function \"%s\" not supported on this device.\n"), msg); Emsg0(M_ERROR, 0, errmsg); } } /* * Now we try different methods of clearing the error status on the drive * so that it is not locked for further operations. */ /* * On some systems such as NetBSD, this clears all errors */ get_os_tape_file(); /* * OS specific clear function. */ os_clrerror(this); }
/* * Position device to end of medium (end of data) * * Returns: true on succes * false on error */ bool generic_tape_device::eod(DCR *dcr) { struct mtop mt_com; bool ok = true; int32_t os_file; if (m_fd < 0) { dev_errno = EBADF; Mmsg1(errmsg, _("Bad call to eod. Device %s not open\n"), prt_name); return false; } #if defined (__digital__) && defined (__unix__) return fsf(VolCatInfo.VolCatFiles); #endif Dmsg0(100, "Enter eod\n"); if (at_eot()) { return true; } clear_eof(); /* remove EOF flag */ block_num = file = 0; file_size = 0; file_addr = 0; #ifdef MTEOM if (has_cap(CAP_FASTFSF) && !has_cap(CAP_EOM)) { Dmsg0(100,"Using FAST FSF for EOM\n"); /* * If unknown position, rewind */ if (get_os_tape_file() < 0) { if (!rewind(NULL)) { Dmsg0(100, "Rewind error\n"); return false; } } mt_com.mt_op = MTFSF; /* * ***FIXME*** fix code to handle case that INT16_MAX is not large enough. */ mt_com.mt_count = INT16_MAX; /* use big positive number */ if (mt_com.mt_count < 0) { mt_com.mt_count = INT16_MAX; /* brain damaged system */ } } if (has_cap(CAP_MTIOCGET) && (has_cap(CAP_FASTFSF) || has_cap(CAP_EOM))) { if (has_cap(CAP_EOM)) { Dmsg0(100,"Using EOM for EOM\n"); mt_com.mt_op = MTEOM; mt_com.mt_count = 1; } if (d_ioctl(m_fd, MTIOCTOP, (char *)&mt_com) < 0) { berrno be; clrerror(mt_com.mt_op); Dmsg1(50, "ioctl error: %s\n", be.bstrerror()); update_pos(dcr); Mmsg2(errmsg, _("ioctl MTEOM error on %s. ERR=%s.\n"), prt_name, be.bstrerror()); Dmsg0(100, errmsg); return false; } os_file = get_os_tape_file(); if (os_file < 0) { berrno be; clrerror(-1); Mmsg2(errmsg, _("ioctl MTIOCGET error on %s. ERR=%s.\n"), prt_name, be.bstrerror()); Dmsg0(100, errmsg); return false; } Dmsg1(100, "EOD file=%d\n", os_file); set_ateof(); file = os_file; } else { #endif /* * Rewind then use FSF until EOT reached */ if (!rewind(NULL)) { Dmsg0(100, "Rewind error.\n"); return false; } /* * Move file by file to the end of the tape */ int file_num; for (file_num=file; !at_eot(); file_num++) { Dmsg0(200, "eod: doing fsf 1\n"); if (!fsf(1)) { Dmsg0(100, "fsf error.\n"); return false; } /* * Avoid infinite loop by ensuring we advance. */ if (!at_eot() && file_num == (int)file) { Dmsg1(100, "fsf did not advance from file %d\n", file_num); set_ateof(); os_file = get_os_tape_file(); if (os_file >= 0) { Dmsg2(100, "Adjust file from %d to %d\n", file_num, os_file); file = os_file; } break; } } #ifdef MTEOM } #endif /* * Some drivers leave us after second EOF when doing MTEOM, so we must backup * so that appending overwrites the second EOF. */ if (has_cap(CAP_BSFATEOM)) { /* * Backup over EOF */ ok = bsf(1); /* * If BSF worked and fileno is known (not -1), set file */ os_file = get_os_tape_file(); if (os_file >= 0) { Dmsg2(100, "BSFATEOF adjust file from %d to %d\n", file , os_file); file = os_file; } else { file++; /* wing it -- not correct on all OSes */ } } else { update_pos(dcr); /* update position */ } Dmsg1(200, "EOD dev->file=%d\n", file); return ok; }
/* * Rewind the device. * * Returns: true on success * false on failure */ bool generic_tape_device::rewind(DCR *dcr) { struct mtop mt_com; unsigned int i; bool first = true; Dmsg3(400, "rewind res=%d fd=%d %s\n", num_reserved(), m_fd, prt_name); state &= ~(ST_EOT | ST_EOF | ST_WEOT); /* Remove EOF/EOT flags */ block_num = file = 0; file_size = 0; file_addr = 0; if (m_fd < 0) { return false; } mt_com.mt_op = MTREW; mt_com.mt_count = 1; /* * If we get an I/O error on rewind, it is probably because * the drive is actually busy. We loop for (about 5 minutes) * retrying every 5 seconds. */ for (i = max_rewind_wait; ; i -= 5) { if (d_ioctl(m_fd, MTIOCTOP, (char *)&mt_com) < 0) { berrno be; clrerror(mt_com.mt_op); if (i == max_rewind_wait) { Dmsg1(200, "Rewind error, %s. retrying ...\n", be.bstrerror()); } /* * This is a gross hack, because if the user has the * device mounted (i.e. open), then uses mtx to load * a tape, the current open file descriptor is invalid. * So, we close the drive and re-open it. */ if (first && dcr) { int oo_mode = open_mode; d_close(m_fd); clear_opened(); open(dcr, oo_mode); if (m_fd < 0) { return false; } first = false; continue; } #ifdef HAVE_SUN_OS if (dev_errno == EIO) { Mmsg1(errmsg, _("No tape loaded or drive offline on %s.\n"), prt_name); return false; } #else if (dev_errno == EIO && i > 0) { Dmsg0(200, "Sleeping 5 seconds.\n"); bmicrosleep(5, 0); continue; } #endif Mmsg2(errmsg, _("Rewind error on %s. ERR=%s.\n"), prt_name, be.bstrerror()); return false; } break; } return true; }
static inline DEVICE *m_init_dev(JCR *jcr, DEVRES *device, bool new_init) { struct stat statp; int errstat; DCR *dcr = NULL; DEVICE *dev = NULL; uint32_t max_bs; Dmsg1(400, "max_block_size in device res is %u\n", device->max_block_size); /* * If no device type specified, try to guess */ if (!device->dev_type) { /* * Check that device is available */ if (stat(device->device_name, &statp) < 0) { berrno be; Jmsg2(jcr, M_ERROR, 0, _("Unable to stat device %s: ERR=%s\n"), device->device_name, be.bstrerror()); return NULL; } if (S_ISDIR(statp.st_mode)) { device->dev_type = B_FILE_DEV; } else if (S_ISCHR(statp.st_mode)) { device->dev_type = B_TAPE_DEV; } else if (S_ISFIFO(statp.st_mode)) { device->dev_type = B_FIFO_DEV; } else if (!bit_is_set(CAP_REQMOUNT, device->cap_bits)) { Jmsg2(jcr, M_ERROR, 0, _("%s is an unknown device type. Must be tape or directory, st_mode=%x\n"), device->device_name, statp.st_mode); return NULL; } } /* * See what type of device is wanted. */ switch (device->dev_type) { /* * When using dynamic loading use the init_backend_dev() function * for any type of device not being of the type file. */ #ifndef HAVE_DYNAMIC_SD_BACKENDS #ifdef HAVE_GFAPI case B_GFAPI_DEV: dev = New(gfapi_device); break; #endif #ifdef HAVE_OBJECTSTORE case B_OBJECT_STORE_DEV: dev = New(object_store_device); break; #endif #ifdef HAVE_RADOS case B_RADOS_DEV: dev = New(rados_device); break; #endif #ifdef HAVE_CEPHFS case B_CEPHFS_DEV: dev = New(cephfs_device); break; #endif #ifdef HAVE_ELASTO case B_ELASTO_DEV: dev = New(elasto_device); break; #endif #ifdef HAVE_WIN32 case B_TAPE_DEV: dev = New(win32_tape_device); break; case B_FIFO_DEV: dev = New(win32_fifo_device); break; #else case B_TAPE_DEV: dev = New(unix_tape_device); break; case B_FIFO_DEV: dev = New(unix_fifo_device); break; #endif #endif /* HAVE_DYNAMIC_SD_BACKENDS */ #ifdef HAVE_WIN32 case B_FILE_DEV: dev = New(win32_file_device); break; #else case B_FILE_DEV: dev = New(unix_file_device); break; #endif default: #ifdef HAVE_DYNAMIC_SD_BACKENDS dev = init_backend_dev(jcr, device->dev_type); #endif break; } if (!dev) { Jmsg2(jcr, M_ERROR, 0, _("%s has an unknown device type %d\n"), device->device_name, device->dev_type); return NULL; } dev->clear_slot(); /* unknown */ /* * Copy user supplied device parameters from Resource */ dev->dev_name = get_memory(strlen(device->device_name) + 1); pm_strcpy(dev->dev_name, device->device_name); if (device->device_options) { dev->dev_options = get_memory(strlen(device->device_options) + 1); pm_strcpy(dev->dev_options, device->device_options); } dev->prt_name = get_memory(strlen(device->device_name) + strlen(device->name()) + 20); /* * We edit "Resource-name" (physical-name) */ Mmsg(dev->prt_name, "\"%s\" (%s)", device->name(), device->device_name); Dmsg1(400, "Allocate dev=%s\n", dev->print_name()); copy_bits(CAP_MAX, device->cap_bits, dev->capabilities); /* * current block sizes */ dev->min_block_size = device->min_block_size; dev->max_block_size = device->max_block_size; dev->max_volume_size = device->max_volume_size; dev->max_file_size = device->max_file_size; dev->max_concurrent_jobs = device->max_concurrent_jobs; dev->volume_capacity = device->volume_capacity; dev->max_rewind_wait = device->max_rewind_wait; dev->max_open_wait = device->max_open_wait; dev->max_open_vols = device->max_open_vols; dev->vol_poll_interval = device->vol_poll_interval; dev->max_spool_size = device->max_spool_size; dev->drive = device->drive; dev->drive_index = device->drive_index; dev->autoselect = device->autoselect; dev->norewindonclose = device->norewindonclose; dev->dev_type = device->dev_type; dev->device = device; /* * Sanity check */ if (dev->vol_poll_interval && dev->vol_poll_interval < 60) { dev->vol_poll_interval = 60; } device->dev = dev; if (dev->is_fifo()) { dev->set_cap(CAP_STREAM); /* set stream device */ } /* * If the device requires mount : * - Check that the mount point is available * - Check that (un)mount commands are defined */ if (dev->is_file() && dev->requires_mount()) { if (!device->mount_point || stat(device->mount_point, &statp) < 0) { berrno be; dev->dev_errno = errno; Jmsg2(jcr, M_ERROR_TERM, 0, _("Unable to stat mount point %s: ERR=%s\n"), device->mount_point, be.bstrerror()); } if (!device->mount_command || !device->unmount_command) { Jmsg0(jcr, M_ERROR_TERM, 0, _("Mount and unmount commands must defined for a device which requires mount.\n")); } } /* * Sanity check */ if (dev->max_block_size == 0) { max_bs = DEFAULT_BLOCK_SIZE; } else { max_bs = dev->max_block_size; } if (dev->min_block_size > max_bs) { Jmsg(jcr, M_ERROR_TERM, 0, _("Min block size > max on device %s\n"), dev->print_name()); } if (dev->max_block_size > MAX_BLOCK_LENGTH) { Jmsg3(jcr, M_ERROR, 0, _("Block size %u on device %s is too large, using default %u\n"), dev->max_block_size, dev->print_name(), DEFAULT_BLOCK_SIZE); dev->max_block_size = 0; } if (dev->max_block_size % TAPE_BSIZE != 0) { Jmsg3(jcr, M_WARNING, 0, _("Max block size %u not multiple of device %s block size=%d.\n"), dev->max_block_size, dev->print_name(), TAPE_BSIZE); } if (dev->max_volume_size != 0 && dev->max_volume_size < (dev->max_block_size << 4)) { Jmsg(jcr, M_ERROR_TERM, 0, _("Max Vol Size < 8 * Max Block Size for device %s\n"), dev->print_name()); } dev->errmsg = get_pool_memory(PM_EMSG); *dev->errmsg = 0; if ((errstat = dev->init_mutex()) != 0) { berrno be; dev->dev_errno = errstat; Mmsg1(dev->errmsg, _("Unable to init mutex: ERR=%s\n"), be.bstrerror(errstat)); Jmsg0(jcr, M_ERROR_TERM, 0, dev->errmsg); } if ((errstat = pthread_cond_init(&dev->wait, NULL)) != 0) { berrno be; dev->dev_errno = errstat; Mmsg1(dev->errmsg, _("Unable to init cond variable: ERR=%s\n"), be.bstrerror(errstat)); Jmsg0(jcr, M_ERROR_TERM, 0, dev->errmsg); } if ((errstat = pthread_cond_init(&dev->wait_next_vol, NULL)) != 0) { berrno be; dev->dev_errno = errstat; Mmsg1(dev->errmsg, _("Unable to init cond variable: ERR=%s\n"), be.bstrerror(errstat)); Jmsg0(jcr, M_ERROR_TERM, 0, dev->errmsg); } if ((errstat = pthread_mutex_init(&dev->spool_mutex, NULL)) != 0) { berrno be; dev->dev_errno = errstat; Mmsg1(dev->errmsg, _("Unable to init spool mutex: ERR=%s\n"), be.bstrerror(errstat)); Jmsg0(jcr, M_ERROR_TERM, 0, dev->errmsg); } if ((errstat = dev->init_acquire_mutex()) != 0) { berrno be; dev->dev_errno = errstat; Mmsg1(dev->errmsg, _("Unable to init acquire mutex: ERR=%s\n"), be.bstrerror(errstat)); Jmsg0(jcr, M_ERROR_TERM, 0, dev->errmsg); } if ((errstat = dev->init_read_acquire_mutex()) != 0) { berrno be; dev->dev_errno = errstat; Mmsg1(dev->errmsg, _("Unable to init read acquire mutex: ERR=%s\n"), be.bstrerror(errstat)); Jmsg0(jcr, M_ERROR_TERM, 0, dev->errmsg); } dev->set_mutex_priorities(); #ifdef xxx if ((errstat = rwl_init(&dev->lock)) != 0) { berrno be; dev->dev_errno = errstat; Mmsg1(dev->errmsg, _("Unable to init mutex: ERR=%s\n"), be.bstrerror(errstat)); Jmsg0(jcr, M_ERROR_TERM, 0, dev->errmsg); } #endif dev->clear_opened(); dev->attached_dcrs = New(dlist(dcr, &dcr->dev_link)); Dmsg2(100, "init_dev: tape=%d dev_name=%s\n", dev->is_tape(), dev->dev_name); dev->initiated = true; Dmsg3(100, "dev=%s dev_max_bs=%u max_bs=%u\n", dev->dev_name, dev->device->max_block_size, dev->max_block_size); return dev; }
/* * If implemented in system, clear the tape * error status. */ void DEVICE::clrerror(int func) { const char *msg = NULL; char buf[100]; dev_errno = errno; /* save errno */ if (errno == EIO) { VolCatInfo.VolCatErrors++; } if (!is_tape()) { return; } if (errno == ENOTTY || errno == ENOSYS) { /* Function not implemented */ switch (func) { case -1: break; /* ignore message printed later */ case MTWEOF: msg = "WTWEOF"; clear_cap(CAP_EOF); /* turn off feature */ break; #ifdef MTEOM case MTEOM: msg = "WTEOM"; clear_cap(CAP_EOM); /* turn off feature */ break; #endif case MTFSF: msg = "MTFSF"; clear_cap(CAP_FSF); /* turn off feature */ break; case MTBSF: msg = "MTBSF"; clear_cap(CAP_BSF); /* turn off feature */ break; case MTFSR: msg = "MTFSR"; clear_cap(CAP_FSR); /* turn off feature */ break; case MTBSR: msg = "MTBSR"; clear_cap(CAP_BSR); /* turn off feature */ break; case MTREW: msg = "MTREW"; break; #ifdef MTSETBLK case MTSETBLK: msg = "MTSETBLK"; break; #endif #ifdef MTSETDRVBUFFER case MTSETDRVBUFFER: msg = "MTSETDRVBUFFER"; break; #endif #ifdef MTRESET case MTRESET: msg = "MTRESET"; break; #endif #ifdef MTSETBSIZ case MTSETBSIZ: msg = "MTSETBSIZ"; break; #endif #ifdef MTSRSZ case MTSRSZ: msg = "MTSRSZ"; break; #endif #ifdef MTLOAD case MTLOAD: msg = "MTLOAD"; break; #endif #ifdef MTUNLOCK case MTUNLOCK: msg = "MTUNLOCK"; break; #endif case MTOFFL: msg = "MTOFFL"; break; default: bsnprintf(buf, sizeof(buf), _("unknown func code %d"), func); msg = buf; break; } if (msg != NULL) { dev_errno = ENOSYS; Mmsg1(errmsg, _("I/O function \"%s\" not supported on this device.\n"), msg); Emsg0(M_ERROR, 0, errmsg); } } /* * Now we try different methods of clearing the error * status on the drive so that it is not locked for * further operations. */ /* On some systems such as NetBSD, this clears all errors */ get_os_tape_file(); /* Found on Solaris */ #ifdef MTIOCLRERR { d_ioctl(m_fd, MTIOCLRERR); Dmsg0(200, "Did MTIOCLRERR\n"); } #endif /* Typically on FreeBSD */ #ifdef MTIOCERRSTAT { berrno be; /* Read and clear SCSI error status */ union mterrstat mt_errstat; Dmsg2(200, "Doing MTIOCERRSTAT errno=%d ERR=%s\n", dev_errno, be.bstrerror(dev_errno)); d_ioctl(m_fd, MTIOCERRSTAT, (char *)&mt_errstat); } #endif /* Clear Subsystem Exception TRU64 */ #ifdef MTCSE { struct mtop mt_com; mt_com.mt_op = MTCSE; mt_com.mt_count = 1; /* Clear any error condition on the tape */ d_ioctl(m_fd, MTIOCTOP, (char *)&mt_com); Dmsg0(200, "Did MTCSE\n"); } #endif }
/* * Open a volume using gfapi. */ int gfapi_device::d_open(const char *pathname, int flags, int mode) { int status; POOL_MEM virtual_filename(PM_FNAME); /* * Parse the gluster URI. */ if (!m_gfapi_volume) { m_gfapi_volume = bstrdup(dev_name); if (!parse_gfapi_devicename(m_gfapi_volume, &m_transport, &m_servername, &m_volumename, &m_basedir, &m_serverport)) { Mmsg1(errmsg, _("Unable to parse device URI %s.\n"), dev_name); Emsg0(M_FATAL, 0, errmsg); goto bail_out; } } /* * See if we need to setup a Gluster context. */ if (!m_glfs) { m_glfs = glfs_new(m_volumename); if (!m_glfs) { Mmsg1(errmsg, _("Unable to create new Gluster context for volumename %s.\n"), m_volumename); Emsg0(M_FATAL, 0, errmsg); goto bail_out; } status = glfs_set_volfile_server(m_glfs, (m_transport) ? m_transport : "tcp", m_servername, m_serverport); if (status < 0) { Mmsg3(errmsg, _("Unable to initialize Gluster management server for transport %s, servername %s, serverport %d\n"), (m_transport) ? m_transport : "tcp", m_servername, m_serverport); Emsg0(M_FATAL, 0, errmsg); goto bail_out; } status = glfs_init(m_glfs); if (status < 0) { Mmsg1(errmsg, _("Unable to initialize Gluster for volumename %s.\n"), m_volumename); Emsg0(M_FATAL, 0, errmsg); goto bail_out; } } /* * See if we don't have a file open already. */ if (m_gfd) { glfs_close(m_gfd); m_gfd = NULL; } /* * See if we store in an explicit directory. */ if (m_basedir) { struct stat st; /* * Make sure the dir exists if one is defined. */ Mmsg(virtual_filename, "/%s", m_basedir); if (glfs_stat(m_glfs, virtual_filename.c_str(), &st) != 0) { switch (errno) { case ENOENT: if (!gfapi_makedir(m_glfs, virtual_filename.c_str())) { Mmsg1(errmsg, _("Specified glusterfs direcory %s cannot be created.\n"), virtual_filename.c_str()); Emsg0(M_FATAL, 0, errmsg); goto bail_out; } break; default: goto bail_out; } } else { if (!S_ISDIR(st.st_mode)) { Mmsg1(errmsg, _("Specified glusterfs direcory %s is not a directory.\n"), virtual_filename.c_str()); Emsg0(M_FATAL, 0, errmsg); goto bail_out; } } Mmsg(virtual_filename, "/%s/%s", m_basedir, getVolCatName()); } else { Mmsg(virtual_filename, "%s", getVolCatName()); } /* * See if the O_CREAT flag is set as glfs_open doesn't support that flag and you have to call glfs_creat then. */ if (flags & O_CREAT) { m_gfd = glfs_creat(m_glfs, virtual_filename.c_str(), flags, mode); } else { m_gfd = glfs_open(m_glfs, virtual_filename.c_str(), flags); } if (!m_gfd) { goto bail_out; } return 0; bail_out: /* * Cleanup the Gluster context. */ if (m_glfs) { glfs_fini(m_glfs); m_glfs = NULL; } return -1; }
/* * Foward space a file * * Returns: true on success * false on failure */ bool generic_tape_device::fsf(int num) { int32_t os_file = 0; struct mtop mt_com; int status = 0; if (!is_open()) { dev_errno = EBADF; Mmsg0(errmsg, _("Bad call to fsf. Device not open\n")); Emsg0(M_FATAL, 0, errmsg); return false; } if (at_eot()) { dev_errno = 0; Mmsg1(errmsg, _("Device %s at End of Tape.\n"), prt_name); return false; } if (at_eof()) { Dmsg0(200, "ST_EOF set on entry to FSF\n"); } Dmsg0(100, "fsf\n"); block_num = 0; /* * If Fast forward space file is set, then we * use MTFSF to forward space and MTIOCGET * to get the file position. We assume that * the SCSI driver will ensure that we do not * forward space past the end of the medium. */ if (has_cap(CAP_FSF) && has_cap(CAP_MTIOCGET) && has_cap(CAP_FASTFSF)) { int my_errno = 0; mt_com.mt_op = MTFSF; mt_com.mt_count = num; status = d_ioctl(m_fd, MTIOCTOP, (char *)&mt_com); if (status < 0) { my_errno = errno; /* save errno */ } else if ((os_file=get_os_tape_file()) < 0) { my_errno = errno; /* save errno */ } if (my_errno != 0) { berrno be; set_eot(); Dmsg0(200, "Set ST_EOT\n"); clrerror(mt_com.mt_op); Mmsg2(errmsg, _("ioctl MTFSF error on %s. ERR=%s.\n"), prt_name, be.bstrerror(my_errno)); Dmsg1(200, "%s", errmsg); return false; } Dmsg1(200, "fsf file=%d\n", os_file); set_ateof(); file = os_file; return true; /* * Here if CAP_FSF is set, and virtually all drives * these days support it, we read a record, then forward * space one file. Using this procedure, which is slow, * is the only way we can be sure that we don't read * two consecutive EOF marks, which means End of Data. */ } else if (has_cap(CAP_FSF)) { POOLMEM *rbuf; int rbuf_len; Dmsg0(200, "FSF has cap_fsf\n"); if (max_block_size == 0) { rbuf_len = DEFAULT_BLOCK_SIZE; } else { rbuf_len = max_block_size; } rbuf = get_memory(rbuf_len); mt_com.mt_op = MTFSF; mt_com.mt_count = 1; while (num-- && !at_eot()) { Dmsg0(100, "Doing read before fsf\n"); if ((status = this->read((char *)rbuf, rbuf_len)) < 0) { if (errno == ENOMEM) { /* tape record exceeds buf len */ status = rbuf_len; /* This is OK */ /* * On IBM drives, they return ENOSPC at EOM instead of EOF status */ } else if (at_eof() && errno == ENOSPC) { status = 0; } else if (has_cap(CAP_IOERRATEOM) && at_eof() && errno == EIO) { if (has_cap(CAP_IBMLINTAPE)) { Dmsg0(100, "Got EIO on read, checking lin_tape sense data\n"); if (check_scsi_at_eod(m_fd)) { Dmsg0(100, "Sense data confirms it's EOD\n"); status = 0; } else { Dmsg0(100, "Not at EOD, might be a real error. Check sense trace from lin_taped logs.\n"); set_eot(); clrerror(-1); Mmsg1(errmsg, _("read error on %s. ERR=Input/Output error.\n"), prt_name); break; } } else { Dmsg0(100, "Got EIO on read, assuming that's due to EOD\n"); status = 0; } } else { berrno be; set_eot(); clrerror(-1); Dmsg2(100, "Set ST_EOT read errno=%d. ERR=%s\n", dev_errno, be.bstrerror()); Mmsg2(errmsg, _("read error on %s. ERR=%s.\n"), prt_name, be.bstrerror()); Dmsg1(100, "%s", errmsg); break; } } if (status == 0) { /* EOF */ Dmsg1(100, "End of File mark from read. File=%d\n", file+1); /* * Two reads of zero means end of tape */ if (at_eof()) { set_eot(); Dmsg0(100, "Set ST_EOT\n"); break; } else { set_ateof(); continue; } } else { /* Got data */ clear_eot(); clear_eof(); } Dmsg0(100, "Doing MTFSF\n"); status = d_ioctl(m_fd, MTIOCTOP, (char *)&mt_com); if (status < 0) { /* error => EOT */ berrno be; set_eot(); Dmsg0(100, "Set ST_EOT\n"); clrerror(mt_com.mt_op); Mmsg2(errmsg, _("ioctl MTFSF error on %s. ERR=%s.\n"), prt_name, be.bstrerror()); Dmsg0(100, "Got < 0 for MTFSF\n"); Dmsg1(100, "%s", errmsg); } else { set_ateof(); } } free_memory(rbuf); /* * No FSF, so use FSR to simulate it */ } else { Dmsg0(200, "Doing FSR for FSF\n"); while (num-- && !at_eot()) { fsr(INT32_MAX); /* returns -1 on EOF or EOT */ } if (at_eot()) { dev_errno = 0; Mmsg1(errmsg, _("Device %s at End of Tape.\n"), prt_name); status = -1; } else { status = 0; } } Dmsg1(200, "Return %d from FSF\n", status); if (at_eof()) { Dmsg0(200, "ST_EOF set on exit FSF\n"); } if (at_eot()) { Dmsg0(200, "ST_EOT set on exit FSF\n"); } Dmsg1(200, "Return from FSF file=%d\n", file); return status == 0; }
/* * Find Available Media (Volume) for Pool * * Find a Volume for a given PoolId, MediaType, and Status. * The unwanted_volumes variable lists the VolumeNames which we should skip if any. * * Returns: 0 on failure * numrows on success */ int db_find_next_volume(JCR *jcr, B_DB *mdb, int item, bool InChanger, MEDIA_DBR *mr, const char *unwanted_volumes) { char ed1[50]; int num_rows = 0; SQL_ROW row = NULL; bool find_oldest = false; bool found_candidate = false; char esc_type[MAX_ESCAPE_NAME_LENGTH]; char esc_status[MAX_ESCAPE_NAME_LENGTH]; db_lock(mdb); mdb->db_escape_string(jcr, esc_type, mr->MediaType, strlen(mr->MediaType)); mdb->db_escape_string(jcr, esc_status, mr->VolStatus, strlen(mr->VolStatus)); if (item == -1) { find_oldest = true; item = 1; } retry_fetch: if (find_oldest) { /* * Find oldest volume(s) */ Mmsg(mdb->cmd, "SELECT MediaId,VolumeName,VolJobs,VolFiles,VolBlocks," "VolBytes,VolMounts,VolErrors,VolWrites,MaxVolBytes,VolCapacityBytes," "MediaType,VolStatus,PoolId,VolRetention,VolUseDuration,MaxVolJobs," "MaxVolFiles,Recycle,Slot,FirstWritten,LastWritten,InChanger," "EndFile,EndBlock,LabelType,LabelDate,StorageId," "Enabled,LocationId,RecycleCount,InitialWrite," "ScratchPoolId,RecyclePoolId,VolReadTime,VolWriteTime," "ActionOnPurge,EncryptionKey,MinBlocksize,MaxBlocksize " "FROM Media WHERE PoolId=%s AND MediaType='%s' AND VolStatus IN ('Full'," "'Recycle','Purged','Used','Append') AND Enabled=1 " "ORDER BY LastWritten LIMIT %d", edit_int64(mr->PoolId, ed1), esc_type, item); } else { POOL_MEM changer(PM_FNAME); const char *order; /* * Find next available volume */ if (InChanger) { Mmsg(changer, "AND InChanger=1 AND StorageId=%s", edit_int64(mr->StorageId, ed1)); } if (bstrcmp(mr->VolStatus, "Recycle") || bstrcmp(mr->VolStatus, "Purged")) { order = "AND Recycle=1 ORDER BY LastWritten ASC,MediaId"; /* take oldest that can be recycled */ } else { order = sql_media_order_most_recently_written[db_get_type_index(mdb)]; /* take most recently written */ } Mmsg(mdb->cmd, "SELECT MediaId,VolumeName,VolJobs,VolFiles,VolBlocks," "VolBytes,VolMounts,VolErrors,VolWrites,MaxVolBytes,VolCapacityBytes," "MediaType,VolStatus,PoolId,VolRetention,VolUseDuration,MaxVolJobs," "MaxVolFiles,Recycle,Slot,FirstWritten,LastWritten,InChanger," "EndFile,EndBlock,LabelType,LabelDate,StorageId," "Enabled,LocationId,RecycleCount,InitialWrite," "ScratchPoolId,RecyclePoolId,VolReadTime,VolWriteTime," "ActionOnPurge,EncryptionKey,MinBlocksize,MaxBlocksize " "FROM Media WHERE PoolId=%s AND MediaType='%s' AND Enabled=1 " "AND VolStatus='%s' " "%s " "%s LIMIT %d", edit_int64(mr->PoolId, ed1), esc_type, esc_status, changer.c_str(), order, item); } Dmsg1(100, "fnextvol=%s\n", mdb->cmd); if (!QUERY_DB(jcr, mdb, mdb->cmd)) { goto bail_out; } num_rows = sql_num_rows(mdb); if (item > num_rows || item < 1) { Dmsg2(050, "item=%d got=%d\n", item, num_rows); Mmsg2(&mdb->errmsg, _("Request for Volume item %d greater than max %d or less than 1\n"), item, num_rows); num_rows = 0; goto bail_out; } for (int i = 0 ; i < item; i++) { if ((row = sql_fetch_row(mdb)) == NULL) { Dmsg1(050, "Fail fetch item=%d\n", i); Mmsg1(&mdb->errmsg, _("No Volume record found for item %d.\n"), i); sql_free_result(mdb); num_rows = 0; goto bail_out; } /* * See if this is not on the unwanted volumes list. */ if (unwanted_volumes && is_on_unwanted_volumes_list(row[1], unwanted_volumes)) { continue; } /* * Return fields in Media Record */ mr->MediaId = str_to_int64(row[0]); bstrncpy(mr->VolumeName, (row[1] != NULL) ? row[1] : "", sizeof(mr->VolumeName)); mr->VolJobs = str_to_int64(row[2]); mr->VolFiles = str_to_int64(row[3]); mr->VolBlocks = str_to_int64(row[4]); mr->VolBytes = str_to_uint64(row[5]); mr->VolMounts = str_to_int64(row[6]); mr->VolErrors = str_to_int64(row[7]); mr->VolWrites = str_to_int64(row[8]); mr->MaxVolBytes = str_to_uint64(row[9]); mr->VolCapacityBytes = str_to_uint64(row[10]); bstrncpy(mr->MediaType, (row[11] != NULL) ? row[11] : "", sizeof(mr->MediaType)); bstrncpy(mr->VolStatus, (row[12] != NULL) ? row[12] : "", sizeof(mr->VolStatus)); mr->PoolId = str_to_int64(row[13]); mr->VolRetention = str_to_uint64(row[14]); mr->VolUseDuration = str_to_uint64(row[15]); mr->MaxVolJobs = str_to_int64(row[16]); mr->MaxVolFiles = str_to_int64(row[17]); mr->Recycle = str_to_int64(row[18]); mr->Slot = str_to_int64(row[19]); bstrncpy(mr->cFirstWritten, (row[20] != NULL) ? row[20] : "", sizeof(mr->cFirstWritten)); mr->FirstWritten = (time_t)str_to_utime(mr->cFirstWritten); bstrncpy(mr->cLastWritten, (row[21] != NULL) ? row[21] : "", sizeof(mr->cLastWritten)); mr->LastWritten = (time_t)str_to_utime(mr->cLastWritten); mr->InChanger = str_to_uint64(row[22]); mr->EndFile = str_to_uint64(row[23]); mr->EndBlock = str_to_uint64(row[24]); mr->LabelType = str_to_int64(row[25]); bstrncpy(mr->cLabelDate, (row[26] != NULL) ? row[26] : "", sizeof(mr->cLabelDate)); mr->LabelDate = (time_t)str_to_utime(mr->cLabelDate); mr->StorageId = str_to_int64(row[27]); mr->Enabled = str_to_int64(row[28]); mr->LocationId = str_to_int64(row[29]); mr->RecycleCount = str_to_int64(row[30]); bstrncpy(mr->cInitialWrite, (row[31] != NULL) ? row[31] : "", sizeof(mr->cInitialWrite)); mr->InitialWrite = (time_t)str_to_utime(mr->cInitialWrite); mr->ScratchPoolId = str_to_int64(row[32]); mr->RecyclePoolId = str_to_int64(row[33]); mr->VolReadTime = str_to_int64(row[34]); mr->VolWriteTime = str_to_int64(row[35]); mr->ActionOnPurge = str_to_int64(row[36]); bstrncpy(mr->EncrKey, (row[37] != NULL) ? row[37] : "", sizeof(mr->EncrKey)); mr->MinBlocksize = str_to_int32(row[38]); mr->MaxBlocksize = str_to_int32(row[39]); sql_free_result(mdb); found_candidate = true; break; } if (!found_candidate && find_oldest) { item++; goto retry_fetch; } bail_out: db_unlock(mdb); Dmsg1(050, "Rtn numrows=%d\n", num_rows); return num_rows; }
/* * Find job start time if JobId specified, otherwise * find last Job start time Incremental and Differential saves. * * StartTime is returned in stime * Job name is returned in job (MAX_NAME_LENGTH) * * Returns: 0 on failure * 1 on success, jr is unchanged, but stime and job are set */ bool db_find_job_start_time(JCR *jcr, B_DB *mdb, JOB_DBR *jr, POOLMEM **stime, char *job) { bool retval = false; SQL_ROW row; char ed1[50], ed2[50]; char esc_name[MAX_ESCAPE_NAME_LENGTH]; db_lock(mdb); mdb->db_escape_string(jcr, esc_name, jr->Name, strlen(jr->Name)); pm_strcpy(stime, "0000-00-00 00:00:00"); /* default */ job[0] = 0; /* If no Id given, we must find corresponding job */ if (jr->JobId == 0) { /* Differential is since last Full backup */ Mmsg(mdb->cmd, "SELECT StartTime, Job FROM Job WHERE JobStatus IN ('T','W') AND Type='%c' AND " "Level='%c' AND Name='%s' AND ClientId=%s AND FileSetId=%s " "ORDER BY StartTime DESC LIMIT 1", jr->JobType, L_FULL, esc_name, edit_int64(jr->ClientId, ed1), edit_int64(jr->FileSetId, ed2)); if (jr->JobLevel == L_DIFFERENTIAL) { /* SQL cmd for Differential backup already edited above */ /* Incremental is since last Full, Incremental, or Differential */ } else if (jr->JobLevel == L_INCREMENTAL) { /* * For an Incremental job, we must first ensure * that a Full backup was done (cmd edited above) * then we do a second look to find the most recent * backup */ if (!QUERY_DB(jcr, mdb, mdb->cmd)) { Mmsg2(&mdb->errmsg, _("Query error for start time request: ERR=%s\nCMD=%s\n"), sql_strerror(mdb), mdb->cmd); goto bail_out; } if ((row = sql_fetch_row(mdb)) == NULL) { sql_free_result(mdb); Mmsg(mdb->errmsg, _("No prior Full backup Job record found.\n")); goto bail_out; } sql_free_result(mdb); /* Now edit SQL command for Incremental Job */ Mmsg(mdb->cmd, "SELECT StartTime, Job FROM Job WHERE JobStatus IN ('T','W') AND Type='%c' AND " "Level IN ('%c','%c','%c') AND Name='%s' AND ClientId=%s " "AND FileSetId=%s ORDER BY StartTime DESC LIMIT 1", jr->JobType, L_INCREMENTAL, L_DIFFERENTIAL, L_FULL, esc_name, edit_int64(jr->ClientId, ed1), edit_int64(jr->FileSetId, ed2)); } else { Mmsg1(mdb->errmsg, _("Unknown level=%d\n"), jr->JobLevel); goto bail_out; } } else { Dmsg1(100, "Submitting: %s\n", mdb->cmd); Mmsg(mdb->cmd, "SELECT StartTime, Job FROM Job WHERE Job.JobId=%s", edit_int64(jr->JobId, ed1)); } if (!QUERY_DB(jcr, mdb, mdb->cmd)) { pm_strcpy(stime, ""); /* set EOS */ Mmsg2(&mdb->errmsg, _("Query error for start time request: ERR=%s\nCMD=%s\n"), sql_strerror(mdb), mdb->cmd); goto bail_out; } if ((row = sql_fetch_row(mdb)) == NULL) { Mmsg2(&mdb->errmsg, _("No Job record found: ERR=%s\nCMD=%s\n"), sql_strerror(mdb), mdb->cmd); sql_free_result(mdb); goto bail_out; } Dmsg2(100, "Got start time: %s, job: %s\n", row[0], row[1]); pm_strcpy(stime, row[0]); bstrncpy(job, row[1], MAX_NAME_LENGTH); sql_free_result(mdb); retval = true; bail_out: db_unlock(mdb); return retval; }
/* * Find Available Media (Volume) for Pool * * Find a Volume for a given PoolId, MediaType, and Status. * * Returns: 0 on failure * numrows on success */ int BDB::bdb_find_next_volume(JCR *jcr, int item, bool InChanger, MEDIA_DBR *mr) { SQL_ROW row = NULL; int numrows; const char *order; char esc_type[MAX_ESCAPE_NAME_LENGTH]; char esc_status[MAX_ESCAPE_NAME_LENGTH]; char ed1[50]; bdb_lock(); bdb_escape_string(jcr, esc_type, mr->MediaType, strlen(mr->MediaType)); bdb_escape_string(jcr, esc_status, mr->VolStatus, strlen(mr->VolStatus)); if (item == -1) { /* find oldest volume */ /* Find oldest volume */ Mmsg(cmd, "SELECT MediaId,VolumeName,VolJobs,VolFiles,VolBlocks," "VolBytes,VolMounts,VolErrors,VolWrites,MaxVolBytes,VolCapacityBytes," "MediaType,VolStatus,PoolId,VolRetention,VolUseDuration,MaxVolJobs," "MaxVolFiles,Recycle,Slot,FirstWritten,LastWritten,InChanger," "EndFile,EndBlock,VolParts,LabelType,LabelDate,StorageId," "Enabled,LocationId,RecycleCount,InitialWrite," "ScratchPoolId,RecyclePoolId,VolReadTime,VolWriteTime,ActionOnPurge " "FROM Media WHERE PoolId=%s AND MediaType='%s' AND VolStatus IN ('Full'," "'Recycle','Purged','Used','Append') AND Enabled=1 " "ORDER BY LastWritten LIMIT 1", edit_int64(mr->PoolId, ed1), esc_type); item = 1; } else { POOL_MEM changer(PM_FNAME); POOL_MEM voltype(PM_FNAME); POOL_MEM exclude(PM_FNAME); /* Find next available volume */ if (InChanger) { Mmsg(changer, " AND InChanger=1 AND StorageId=%s ", edit_int64(mr->StorageId, ed1)); } /* Volumes will be automatically excluded from the query, we just take the * first one of the list */ if (mr->exclude_list && *mr->exclude_list) { item = 1; Mmsg(exclude, " AND MediaId NOT IN (%s) ", mr->exclude_list); } if (strcmp(mr->VolStatus, "Recycle") == 0 || strcmp(mr->VolStatus, "Purged") == 0) { order = "AND Recycle=1 ORDER BY LastWritten ASC,MediaId"; /* take oldest that can be recycled */ } else { order = sql_media_order_most_recently_written[bdb_get_type_index()]; /* take most recently written */ } Mmsg(cmd, "SELECT MediaId,VolumeName,VolJobs,VolFiles,VolBlocks," "VolBytes,VolMounts,VolErrors,VolWrites,MaxVolBytes,VolCapacityBytes," "MediaType,VolStatus,PoolId,VolRetention,VolUseDuration,MaxVolJobs," "MaxVolFiles,Recycle,Slot,FirstWritten,LastWritten,InChanger," "EndFile,EndBlock,VolParts,LabelType,LabelDate,StorageId," "Enabled,LocationId,RecycleCount,InitialWrite," "ScratchPoolId,RecyclePoolId,VolReadTime,VolWriteTime,ActionOnPurge " "FROM Media WHERE PoolId=%s AND MediaType='%s' AND Enabled=1 " "AND VolStatus='%s' " "%s " "%s " "%s " "%s LIMIT %d", edit_int64(mr->PoolId, ed1), esc_type, esc_status, voltype.c_str(), changer.c_str(), exclude.c_str(), order, item); } Dmsg1(100, "fnextvol=%s\n", cmd); if (!QueryDB(jcr, cmd)) { bdb_unlock(); return 0; } numrows = sql_num_rows(); if (item > numrows || item < 1) { Dmsg2(050, "item=%d got=%d\n", item, numrows); Mmsg2(&errmsg, _("Request for Volume item %d greater than max %d or less than 1\n"), item, numrows); bdb_unlock(); return 0; } /* Note, we previously seeked to the row using: * sql_data_seek(item-1); * but this failed on PostgreSQL, so now we loop * over all the records. This should not be too horrible since * the maximum Volumes we look at in any case is 20. */ while (item-- > 0) { if ((row = sql_fetch_row()) == NULL) { Dmsg1(050, "Fail fetch item=%d\n", item+1); Mmsg1(&errmsg, _("No Volume record found for item %d.\n"), item); sql_free_result(); bdb_unlock(); return 0; } } /* Return fields in Media Record */ mr->MediaId = str_to_int64(row[0]); bstrncpy(mr->VolumeName, row[1]!=NULL?row[1]:"", sizeof(mr->VolumeName)); mr->VolJobs = str_to_int64(row[2]); mr->VolFiles = str_to_int64(row[3]); mr->VolBlocks = str_to_int64(row[4]); mr->VolBytes = str_to_uint64(row[5]); mr->VolMounts = str_to_int64(row[6]); mr->VolErrors = str_to_int64(row[7]); mr->VolWrites = str_to_int64(row[8]); mr->MaxVolBytes = str_to_uint64(row[9]); mr->VolCapacityBytes = str_to_uint64(row[10]); bstrncpy(mr->MediaType, row[11]!=NULL?row[11]:"", sizeof(mr->MediaType)); bstrncpy(mr->VolStatus, row[12]!=NULL?row[12]:"", sizeof(mr->VolStatus)); mr->PoolId = str_to_int64(row[13]); mr->VolRetention = str_to_uint64(row[14]); mr->VolUseDuration = str_to_uint64(row[15]); mr->MaxVolJobs = str_to_int64(row[16]); mr->MaxVolFiles = str_to_int64(row[17]); mr->Recycle = str_to_int64(row[18]); mr->Slot = str_to_int64(row[19]); bstrncpy(mr->cFirstWritten, row[20]!=NULL?row[20]:"", sizeof(mr->cFirstWritten)); mr->FirstWritten = (time_t)str_to_utime(mr->cFirstWritten); bstrncpy(mr->cLastWritten, row[21]!=NULL?row[21]:"", sizeof(mr->cLastWritten)); mr->LastWritten = (time_t)str_to_utime(mr->cLastWritten); mr->InChanger = str_to_uint64(row[22]); mr->EndFile = str_to_uint64(row[23]); mr->EndBlock = str_to_uint64(row[24]); mr->VolType = str_to_int64(row[25]); /* formerly VolParts */ mr->LabelType = str_to_int64(row[26]); bstrncpy(mr->cLabelDate, row[27]!=NULL?row[27]:"", sizeof(mr->cLabelDate)); mr->LabelDate = (time_t)str_to_utime(mr->cLabelDate); mr->StorageId = str_to_int64(row[28]); mr->Enabled = str_to_int64(row[29]); mr->LocationId = str_to_int64(row[30]); mr->RecycleCount = str_to_int64(row[31]); bstrncpy(mr->cInitialWrite, row[32]!=NULL?row[32]:"", sizeof(mr->cInitialWrite)); mr->InitialWrite = (time_t)str_to_utime(mr->cInitialWrite); mr->ScratchPoolId = str_to_int64(row[33]); mr->RecyclePoolId = str_to_int64(row[34]); mr->VolReadTime = str_to_int64(row[35]); mr->VolWriteTime = str_to_int64(row[36]); mr->ActionOnPurge = str_to_int64(row[37]); sql_free_result(); bdb_unlock(); Dmsg1(050, "Rtn numrows=%d\n", numrows); return numrows; }
DEVICE *m_init_dev(JCR *jcr, DEVRES *device) { struct stat statp; int errstat; DCR *dcr = NULL; DEVICE *dev = NULL; uint32_t max_bs; /* If no device type specified, try to guess */ if (!device->dev_type) { /* Check that device is available */ if (stat(device->device_name, &statp) < 0) { berrno be; Jmsg2(jcr, M_ERROR, 0, _("Unable to stat device %s: ERR=%s\n"), device->device_name, be.bstrerror()); return NULL; } if (S_ISDIR(statp.st_mode)) { device->dev_type = B_FILE_DEV; } else if (S_ISCHR(statp.st_mode)) { device->dev_type = B_TAPE_DEV; } else if (S_ISFIFO(statp.st_mode)) { device->dev_type = B_FIFO_DEV; #ifdef USE_VTAPE /* must set DeviceType = Vtape * in normal mode, autodetection is disabled */ } else if (S_ISREG(statp.st_mode)) { device->dev_type = B_VTAPE_DEV; #endif } else if (!(device->cap_bits & CAP_REQMOUNT)) { Jmsg2(jcr, M_ERROR, 0, _("%s is an unknown device type. Must be tape or directory\n" " or have RequiresMount=yes for DVD. st_mode=%x\n"), device->device_name, statp.st_mode); return NULL; } else { device->dev_type = B_DVD_DEV; } if (strcmp(device->device_name, "/dev/null") == 0) { device->dev_type = B_NULL_DEV; } } switch (device->dev_type) { case B_DVD_DEV: Jmsg0(jcr, M_FATAL, 0, _("DVD support is now deprecated.\n")); return NULL; case B_VTAPE_DEV: dev = New(vtape); break; #ifdef USE_FTP case B_FTP_DEV: dev = New(ftp_device); break; #endif case B_TAPE_DEV: dev = New(tape_dev); break; case B_FILE_DEV: case B_FIFO_DEV: case B_NULL_DEV: dev = New(file_dev); break; default: return NULL; } dev->clear_slot(); /* unknown */ /* Copy user supplied device parameters from Resource */ dev->dev_name = get_memory(strlen(device->device_name)+1); pm_strcpy(dev->dev_name, device->device_name); dev->prt_name = get_memory(strlen(device->device_name) + strlen(device->hdr.name) + 20); /* We edit "Resource-name" (physical-name) */ Mmsg(dev->prt_name, "\"%s\" (%s)", device->hdr.name, device->device_name); Dmsg1(400, "Allocate dev=%s\n", dev->print_name()); dev->capabilities = device->cap_bits; dev->min_free_space = device->min_free_space; dev->min_block_size = device->min_block_size; dev->max_block_size = device->max_block_size; dev->max_volume_size = device->max_volume_size; dev->max_file_size = device->max_file_size; dev->max_concurrent_jobs = device->max_concurrent_jobs; dev->volume_capacity = device->volume_capacity; dev->max_rewind_wait = device->max_rewind_wait; dev->max_open_wait = device->max_open_wait; dev->vol_poll_interval = device->vol_poll_interval; dev->max_spool_size = device->max_spool_size; dev->drive_index = device->drive_index; dev->enabled = device->enabled; dev->autoselect = device->autoselect; dev->read_only = device->read_only; dev->dev_type = device->dev_type; dev->device = device; if (dev->is_tape()) { /* No parts on tapes */ dev->max_part_size = 0; } else { dev->max_part_size = device->max_part_size; } /* Sanity check */ if (dev->vol_poll_interval && dev->vol_poll_interval < 60) { dev->vol_poll_interval = 60; } if (!device->dev) { /* The first time we create a DEVICE from the DEVRES, we keep a pointer * to the DEVICE accessible from the DEVRES. */ device->dev = dev; } if (dev->is_fifo()) { dev->capabilities |= CAP_STREAM; /* set stream device */ } /* If the device requires mount : * - Check that the mount point is available * - Check that (un)mount commands are defined */ if (dev->is_file() && dev->requires_mount()) { if (!device->mount_point || stat(device->mount_point, &statp) < 0) { berrno be; dev->dev_errno = errno; Jmsg2(jcr, M_ERROR_TERM, 0, _("Unable to stat mount point %s: ERR=%s\n"), device->mount_point, be.bstrerror()); } if (!device->mount_command || !device->unmount_command) { Jmsg0(jcr, M_ERROR_TERM, 0, _("Mount and unmount commands must defined for a device which requires mount.\n")); } } /* Keep the device ID in the DEVICE struct to identify the hardware */ if (dev->is_file() && stat(dev->archive_name(), &statp) == 0) { dev->devno = statp.st_dev; } /* Sanity check */ if (dev->max_block_size == 0) { max_bs = DEFAULT_BLOCK_SIZE; } else { max_bs = dev->max_block_size; } if (dev->min_block_size > max_bs) { Jmsg(jcr, M_ERROR_TERM, 0, _("Min block size > max on device %s\n"), dev->print_name()); } if (dev->max_block_size > 4096000) { Jmsg3(jcr, M_ERROR, 0, _("Block size %u on device %s is too large, using default %u\n"), dev->max_block_size, dev->print_name(), DEFAULT_BLOCK_SIZE); dev->max_block_size = 0; } if (dev->max_block_size % TAPE_BSIZE != 0) { Jmsg3(jcr, M_WARNING, 0, _("Max block size %u not multiple of device %s block size=%d.\n"), dev->max_block_size, dev->print_name(), TAPE_BSIZE); } if (dev->max_volume_size != 0 && dev->max_volume_size < (dev->max_block_size << 4)) { Jmsg(jcr, M_ERROR_TERM, 0, _("Max Vol Size < 8 * Max Block Size for device %s\n"), dev->print_name()); } dev->errmsg = get_pool_memory(PM_EMSG); *dev->errmsg = 0; if ((errstat = dev->init_mutex()) != 0) { berrno be; dev->dev_errno = errstat; Mmsg1(dev->errmsg, _("Unable to init mutex: ERR=%s\n"), be.bstrerror(errstat)); Jmsg0(jcr, M_ERROR_TERM, 0, dev->errmsg); } if ((errstat = pthread_cond_init(&dev->wait, NULL)) != 0) { berrno be; dev->dev_errno = errstat; Mmsg1(dev->errmsg, _("Unable to init cond variable: ERR=%s\n"), be.bstrerror(errstat)); Jmsg0(jcr, M_ERROR_TERM, 0, dev->errmsg); } if ((errstat = pthread_cond_init(&dev->wait_next_vol, NULL)) != 0) { berrno be; dev->dev_errno = errstat; Mmsg1(dev->errmsg, _("Unable to init cond variable: ERR=%s\n"), be.bstrerror(errstat)); Jmsg0(jcr, M_ERROR_TERM, 0, dev->errmsg); } if ((errstat = pthread_mutex_init(&dev->spool_mutex, NULL)) != 0) { berrno be; dev->dev_errno = errstat; Mmsg1(dev->errmsg, _("Unable to init spool mutex: ERR=%s\n"), be.bstrerror(errstat)); Jmsg0(jcr, M_ERROR_TERM, 0, dev->errmsg); } if ((errstat = dev->init_acquire_mutex()) != 0) { berrno be; dev->dev_errno = errstat; Mmsg1(dev->errmsg, _("Unable to init acquire mutex: ERR=%s\n"), be.bstrerror(errstat)); Jmsg0(jcr, M_ERROR_TERM, 0, dev->errmsg); } if ((errstat = dev->init_read_acquire_mutex()) != 0) { berrno be; dev->dev_errno = errstat; Mmsg1(dev->errmsg, _("Unable to init read acquire mutex: ERR=%s\n"), be.bstrerror(errstat)); Jmsg0(jcr, M_ERROR_TERM, 0, dev->errmsg); } if ((errstat = dev->init_volcat_mutex()) != 0) { berrno be; dev->dev_errno = errstat; Mmsg1(dev->errmsg, _("Unable to init volcat mutex: ERR=%s\n"), be.bstrerror(errstat)); Jmsg0(jcr, M_ERROR_TERM, 0, dev->errmsg); } if ((errstat = dev->init_dcrs_mutex()) != 0) { berrno be; dev->dev_errno = errstat; Mmsg1(dev->errmsg, _("Unable to init dcrs mutex: ERR=%s\n"), be.bstrerror(errstat)); Jmsg0(jcr, M_ERROR_TERM, 0, dev->errmsg); } dev->set_mutex_priorities(); #ifdef xxx if ((errstat = rwl_init(&dev->lock)) != 0) { berrno be; dev->dev_errno = errstat; Mmsg1(dev->errmsg, _("Unable to init mutex: ERR=%s\n"), be.bstrerror(errstat)); Jmsg0(jcr, M_ERROR_TERM, 0, dev->errmsg); } #endif dev->clear_opened(); dev->attached_dcrs = New(dlist(dcr, &dcr->dev_link)); Dmsg2(100, "init_dev: tape=%d dev_name=%s\n", dev->is_tape(), dev->dev_name); dev->initiated = true; return dev; }
/* * Note!!!! Part numbers now begin at 1. The part number is * suppressed from the first part, which is just the Volume * name. Each subsequent part is the Volumename.partnumber. * * Write a part (Vol, Vol.2, ...) from the spool to the DVD * This routine does not update the part number, so normally, you * should call open_next_part() * * It is also called from truncate_dvd to "blank" the medium, as * well as from block.c when the DVD is full to write the last part. */ bool dvd_write_part(DCR *dcr) { DEVICE *dev = dcr->dev; POOL_MEM archive_name(PM_FNAME); /* * Don't write empty part files. * This is only useful when growisofs does not support write beyond * the 4GB boundary. * Example : * - 3.9 GB on the volume, dvd-freespace reports 0.4 GB free * - Write 0.2 GB on the volume, Bacula thinks it could still * append data, it creates a new empty part. * - dvd-freespace reports 0 GB free, as the 4GB boundary has * been crossed * - Bacula thinks he must finish to write to the device, so it * tries to write the last part (0-byte), but dvd-writepart fails... * * There is one exception: when recycling a volume, we write a blank part * file, so, then, we need to accept to write it. */ if (dev->part_size == 0 && !dev->truncating) { Dmsg2(29, "dvd_write_part: device is %s, won't write blank part %d\n", dev->print_name(), dev->part); /* Delete spool file */ make_spooled_dvd_filename(dev, archive_name); unlink(archive_name.c_str()); dev->set_part_spooled(false); Dmsg1(29, "========= unlink(%s)\n", archive_name.c_str()); Dsm_check(400); return true; } POOL_MEM ocmd(PM_FNAME); POOL_MEM results(PM_MESSAGE); char* icmd; int status; int timeout; char ed1[50]; dev->clear_freespace_ok(); /* need to update freespace */ Dsm_check(400); Dmsg3(29, "dvd_write_part: device is %s, part is %d, is_mounted=%d\n", dev->print_name(), dev->part, dev->is_mounted()); icmd = dev->device->write_part_command; dev->edit_mount_codes(ocmd, icmd); /* * original line follows * timeout = dev->max_open_wait + (dev->max_part_size/(1350*1024/2)); * I modified this for a longer timeout; pre-formatting, blanking and * writing can take quite a while */ /* Explanation of the timeout value, when writing the first part, * by Arno Lehmann : * 9 GB, write speed 1x: 6990 seconds (almost 2 hours...) * Overhead: 900 seconds (starting, initializing, finalizing,probably * reloading 15 minutes) * Sum: 15780. * A reasonable last-exit timeout would be 16000 seconds. Quite long - * almost 4.5 hours, but hopefully, that timeout will only ever be needed * in case of a serious emergency. */ if (dev->part == 1) { timeout = 16000; } else { timeout = dev->max_open_wait + (dev->part_size/(1350*1024/4)); } Dmsg2(20, "Write part: cmd=%s timeout=%d\n", ocmd.c_str(), timeout); status = run_program_full_output(ocmd.c_str(), timeout, results.addr()); Dmsg2(20, "Write part status=%d result=%s\n", status, results.c_str()); dev->blank_dvd = false; if (status != 0) { Jmsg2(dcr->jcr, M_FATAL, 0, _("Error writing part %d to the DVD: ERR=%s\n"), dev->part, results.c_str()); Mmsg1(dev->errmsg, _("Error while writing current part to the DVD: %s"), results.c_str()); Dmsg1(100, "%s\n", dev->errmsg); dev->dev_errno = EIO; if (!dev->truncating) { dcr->mark_volume_in_error(); } Dsm_check(400); return false; } Jmsg(dcr->jcr, M_INFO, 0, _("Part %d (%lld bytes) written to DVD.\n"), dev->part, dev->part_size); Dmsg3(400, "dvd_write_part: Part %d (%lld bytes) written to DVD\nResults: %s\n", dev->part, dev->part_size, results.c_str()); dev->num_dvd_parts++; /* there is now one more part on DVD */ dev->VolCatInfo.VolCatParts = dev->num_dvd_parts; dcr->VolCatInfo.VolCatParts = dev->num_dvd_parts; Dmsg1(100, "Update num_parts=%d\n", dev->num_dvd_parts); /* Delete spool file */ make_spooled_dvd_filename(dev, archive_name); unlink(archive_name.c_str()); dev->set_part_spooled(false); Dmsg1(29, "========= unlink(%s)\n", archive_name.c_str()); Dsm_check(400); /* growisofs umounted the device, so remount it (it will update the free space) */ dev->clear_mounted(); dev->mount(1); Jmsg(dcr->jcr, M_INFO, 0, _("Remaining free space %s on %s\n"), edit_uint64_with_commas(dev->free_space, ed1), dev->print_name()); Dsm_check(400); return true; }