/* Deprecated. Do not use. */ void get_current_time(struct date_time *dt) { struct tm tm; time_t now; now = time(NULL); (void)gmtime_r(&now, &tm); Dmsg6(200, "m=%d d=%d y=%d h=%d m=%d s=%d\n", tm.tm_mon+1, tm.tm_mday, tm.tm_year+1900, tm.tm_hour, tm.tm_min, tm.tm_sec); tm_encode(dt, &tm); #ifdef DEBUG Dmsg2(200, "jday=%f jmin=%f\n", dt->julian_day_number, dt->julian_day_fraction); tm_decode(dt, &tm); Dmsg6(200, "m=%d d=%d y=%d h=%d m=%d s=%d\n", tm.tm_mon+1, tm.tm_mday, tm.tm_year+1900, tm.tm_hour, tm.tm_min, tm.tm_sec); #endif }
static inline bool write_data_to_block(DEV_BLOCK *block, DEV_RECORD *rec) { rec->remlen = block->buf_len - block->binbuf; /* * Write as much of data as possible */ if (rec->remlen >= rec->remainder) { memcpy(block->bufp, rec->data + (rec->data_len - rec->remainder), rec->remainder); block->bufp += rec->remainder; block->binbuf += rec->remainder; } else { memcpy(block->bufp, rec->data + (rec->data_len - rec->remainder), rec->remlen); #ifdef xxxxxSMCHECK if (!sm_check_rtn(__FILE__, __LINE__, False)) { /* * We damaged a buffer */ Dmsg6(0, "Damaged block FI=%s SessId=%d Strm=%s len=%d\n" "rem=%d remainder=%d\n", FI_to_ascii(buf1, rec->FileIndex), rec->VolSessionId, stream_to_ascii(buf2, rec->Stream, rec->FileIndex), rec->data_len, rec->remlen, rec->remainder); Dmsg5(0, "Damaged block: bufp=%x binbuf=%d buf_len=%d rem=%d moved=%d\n", block->bufp, block->binbuf, block->buf_len, block->buf_len-block->binbuf, rec->remlen); Dmsg2(0, "Damaged block: buf=%x binbuffrombuf=%d \n", block->buf, block->bufp-block->buf); Emsg0(M_ABORT, 0, _("Damaged buffer\n")); } #endif block->bufp += rec->remlen; block->binbuf += rec->remlen; rec->remainder -= rec->remlen; return false; /* did partial transfer */ } return true; }
void catalog_request(JCR *jcr, BSOCK *bs) { MEDIA_DBR mr, sdmr; JOBMEDIA_DBR jm; char Job[MAX_NAME_LENGTH]; char pool_name[MAX_NAME_LENGTH]; int index, ok, label, writing; POOLMEM *omsg; POOL_DBR pr; uint32_t Stripe, Copy; uint64_t MediaId; utime_t VolFirstWritten; utime_t VolLastWritten; memset(&sdmr, 0, sizeof(sdmr)); memset(&jm, 0, sizeof(jm)); Dsm_check(100); /* * Request to find next appendable Volume for this Job */ Dmsg1(100, "catreq %s", bs->msg); if (!jcr->db) { omsg = get_memory(bs->msglen+1); pm_strcpy(omsg, bs->msg); bs->fsend(_("1990 Invalid Catalog Request: %s"), omsg); Jmsg1(jcr, M_FATAL, 0, _("Invalid Catalog request; DB not open: %s"), omsg); free_memory(omsg); return; } /* * Find next appendable medium for SD */ if (sscanf(bs->msg, Find_media, &Job, &index, &pool_name, &mr.MediaType) == 4) { memset(&pr, 0, sizeof(pr)); bstrncpy(pr.Name, pool_name, sizeof(pr.Name)); unbash_spaces(pr.Name); ok = db_get_pool_record(jcr, jcr->db, &pr); if (ok) { mr.PoolId = pr.PoolId; set_storageid_in_mr(jcr->res.wstore, &mr); mr.ScratchPoolId = pr.ScratchPoolId; ok = find_next_volume_for_append(jcr, &mr, index, fnv_create_vol, fnv_prune); Dmsg3(050, "find_media ok=%d idx=%d vol=%s\n", ok, index, mr.VolumeName); } /* * Send Find Media response to Storage daemon */ if (ok) { send_volume_info_to_storage_daemon(jcr, bs, &mr); } else { bs->fsend(_("1901 No Media.\n")); Dmsg0(500, "1901 No Media.\n"); } /* * Request to find specific Volume information */ } else if (sscanf(bs->msg, Get_Vol_Info, &Job, &mr.VolumeName, &writing) == 3) { Dmsg1(100, "CatReq GetVolInfo Vol=%s\n", mr.VolumeName); /* * Find the Volume */ unbash_spaces(mr.VolumeName); if (db_get_media_record(jcr, jcr->db, &mr)) { const char *reason = NULL; /* detailed reason for rejection */ /* * If we are reading, accept any volume (reason == NULL) * If we are writing, check if the Volume is valid * for this job, and do a recycle if necessary */ if (writing) { /* * SD wants to write this Volume, so make * sure it is suitable for this job, i.e. * Pool matches, and it is either Append or Recycle * and Media Type matches and Pool allows any volume. */ if (mr.PoolId != jcr->jr.PoolId) { reason = _("not in Pool"); } else if (!bstrcmp(mr.MediaType, jcr->res.wstore->media_type)) { reason = _("not correct MediaType"); } else { /* * Now try recycling if necessary * reason set non-NULL if we cannot use it */ check_if_volume_valid_or_recyclable(jcr, &mr, &reason); } } if (!reason && mr.Enabled != 1) { reason = _("is not Enabled"); } if (reason == NULL) { /* * Send Find Media response to Storage daemon */ send_volume_info_to_storage_daemon(jcr, bs, &mr); } else { /* Not suitable volume */ bs->fsend(_("1998 Volume \"%s\" catalog status is %s, %s.\n"), mr.VolumeName, mr.VolStatus, reason); } } else { bs->fsend(_("1997 Volume \"%s\" not in catalog.\n"), mr.VolumeName); Dmsg1(100, "1997 Volume \"%s\" not in catalog.\n", mr.VolumeName); } /* * Request to update Media record. Comes typically at the end * of a Storage daemon Job Session, when labeling/relabeling a * Volume, or when an EOF mark is written. */ } else if (sscanf(bs->msg, Update_media, &Job, &sdmr.VolumeName, &sdmr.VolJobs, &sdmr.VolFiles, &sdmr.VolBlocks, &sdmr.VolBytes, &sdmr.VolMounts, &sdmr.VolErrors, &sdmr.VolWrites, &sdmr.MaxVolBytes, &VolLastWritten, &sdmr.VolStatus, &sdmr.Slot, &label, &sdmr.InChanger, &sdmr.VolReadTime, &sdmr.VolWriteTime, &VolFirstWritten) == 18) { db_lock(jcr->db); Dmsg3(400, "Update media %s oldStat=%s newStat=%s\n", sdmr.VolumeName, mr.VolStatus, sdmr.VolStatus); bstrncpy(mr.VolumeName, sdmr.VolumeName, sizeof(mr.VolumeName)); /* copy Volume name */ unbash_spaces(mr.VolumeName); if (!db_get_media_record(jcr, jcr->db, &mr)) { Jmsg(jcr, M_ERROR, 0, _("Unable to get Media record for Volume %s: ERR=%s\n"), mr.VolumeName, db_strerror(jcr->db)); bs->fsend(_("1991 Catalog Request for vol=%s failed: %s"), mr.VolumeName, db_strerror(jcr->db)); goto bail_out; } /* Set first written time if this is first job */ if (mr.FirstWritten == 0) { if (VolFirstWritten == 0) { mr.FirstWritten = jcr->start_time; /* use Job start time as first write */ } else { mr.FirstWritten = VolFirstWritten; } mr.set_first_written = true; } /* If we just labeled the tape set time */ if (label || mr.LabelDate == 0) { mr.LabelDate = jcr->start_time; mr.set_label_date = true; if (mr.InitialWrite == 0) { mr.InitialWrite = jcr->start_time; } Dmsg2(400, "label=%d labeldate=%d\n", label, mr.LabelDate); } else { /* * Insanity check for VolFiles get set to a smaller value */ if (sdmr.VolFiles < mr.VolFiles) { Jmsg(jcr, M_FATAL, 0, _("Volume Files at %u being set to %u" " for Volume \"%s\". This is incorrect.\n"), mr.VolFiles, sdmr.VolFiles, mr.VolumeName); bs->fsend(_("1992 Update Media error. VolFiles=%u, CatFiles=%u\n"), sdmr.VolFiles, mr.VolFiles); goto bail_out; } } Dmsg2(400, "Update media: BefVolJobs=%u After=%u\n", mr.VolJobs, sdmr.VolJobs); /* * Check if the volume has been written by the job, * and update the LastWritten field if needed. */ if (mr.VolBlocks != sdmr.VolBlocks && VolLastWritten != 0) { mr.LastWritten = VolLastWritten; } /* * Update to point to the last device used to write the Volume. * However, do so only if we are writing the tape, i.e. * the number of VolWrites has increased. */ if (jcr->res.wstore && sdmr.VolWrites > mr.VolWrites) { Dmsg2(050, "Update StorageId old=%d new=%d\n", mr.StorageId, jcr->res.wstore->StorageId); /* Update StorageId after write */ set_storageid_in_mr(jcr->res.wstore, &mr); } else { /* Nothing written, reset same StorageId */ set_storageid_in_mr(NULL, &mr); } /* Copy updated values to original media record */ mr.VolJobs = sdmr.VolJobs; mr.VolFiles = sdmr.VolFiles; mr.VolBlocks = sdmr.VolBlocks; mr.VolBytes = sdmr.VolBytes; mr.VolMounts = sdmr.VolMounts; mr.VolErrors = sdmr.VolErrors; mr.VolWrites = sdmr.VolWrites; mr.Slot = sdmr.Slot; mr.InChanger = sdmr.InChanger; bstrncpy(mr.VolStatus, sdmr.VolStatus, sizeof(mr.VolStatus)); mr.VolReadTime = sdmr.VolReadTime; mr.VolWriteTime = sdmr.VolWriteTime; Dmsg2(400, "db_update_media_record. Stat=%s Vol=%s\n", mr.VolStatus, mr.VolumeName); /* * Update the database, then before sending the response to the * SD, check if the Volume has expired. */ if (!db_update_media_record(jcr, jcr->db, &mr)) { Jmsg(jcr, M_FATAL, 0, _("Catalog error updating Media record. %s"), db_strerror(jcr->db)); bs->fsend(_("1993 Update Media error\n")); Dmsg0(400, "send error\n"); } else { (void)has_volume_expired(jcr, &mr); send_volume_info_to_storage_daemon(jcr, bs, &mr); } bail_out: db_unlock(jcr->db); Dmsg1(400, ">CatReq response: %s", bs->msg); Dmsg1(400, "Leave catreq jcr 0x%x\n", jcr); return; /* * Request to create a JobMedia record */ } else if (sscanf(bs->msg, Create_job_media, &Job, &jm.FirstIndex, &jm.LastIndex, &jm.StartFile, &jm.EndFile, &jm.StartBlock, &jm.EndBlock, &Copy, &Stripe, &MediaId) == 10) { if (jcr->mig_jcr) { jm.JobId = jcr->mig_jcr->JobId; } else { jm.JobId = jcr->JobId; } jm.MediaId = MediaId; Dmsg6(400, "create_jobmedia JobId=%d MediaId=%d SF=%d EF=%d FI=%d LI=%d\n", jm.JobId, jm.MediaId, jm.StartFile, jm.EndFile, jm.FirstIndex, jm.LastIndex); if (!db_create_jobmedia_record(jcr, jcr->db, &jm)) { Jmsg(jcr, M_FATAL, 0, _("Catalog error creating JobMedia record. %s"), db_strerror(jcr->db)); bs->fsend(_("1992 Create JobMedia error\n")); } else { Dmsg0(400, "JobMedia record created\n"); bs->fsend(OK_create); } } else { omsg = get_memory(bs->msglen+1); pm_strcpy(omsg, bs->msg); bs->fsend(_("1990 Invalid Catalog Request: %s"), omsg); Jmsg1(jcr, M_FATAL, 0, _("Invalid Catalog request: %s"), omsg); free_memory(omsg); } Dmsg1(400, ">CatReq response: %s", bs->msg); Dmsg1(400, "Leave catreq jcr 0x%x\n", jcr); return; }
/* * Read the header record */ static bool read_header(DCR *dcr, DEV_BLOCK *block, DEV_RECORD *rec) { ser_declare; uint32_t VolSessionId; uint32_t VolSessionTime; int32_t FileIndex; int32_t Stream; uint32_t rhl; char buf1[100], buf2[100]; Dmsg0(dbgep, "=== rpath 1 read_header\n"); /* Clear state flags */ rec->state_bits = 0; if (block->dev->is_tape()) { rec->state_bits |= REC_ISTAPE; } rec->Block = ((DEVICE *)block->dev)->EndBlock; rec->File = ((DEVICE *)block->dev)->EndFile; /* * Get the header. There is always a full header, * otherwise we find it in the next block. */ Dmsg3(read_dbglvl, "Block=%d Ver=%d block_len=%u\n", block->BlockNumber, block->BlockVer, block->block_len); if (block->BlockVer == 1) { rhl = RECHDR1_LENGTH; } else { rhl = RECHDR2_LENGTH; } if (rec->remlen >= rhl) { Dmsg0(dbgep, "=== rpath 2 begin unserial header\n"); Dmsg4(read_dbglvl, "read_header: remlen=%d data_len=%d rem=%d blkver=%d\n", rec->remlen, rec->data_len, rec->remainder, block->BlockVer); unser_begin(block->bufp, WRITE_RECHDR_LENGTH); if (block->BlockVer == 1) { unser_uint32(VolSessionId); unser_uint32(VolSessionTime); } else { VolSessionId = block->VolSessionId; VolSessionTime = block->VolSessionTime; } unser_int32(FileIndex); unser_int32(Stream); unser_uint32(rec->data_bytes); block->bufp += rhl; block->binbuf -= rhl; rec->remlen -= rhl; /* If we are looking for more (remainder!=0), we reject anything * where the VolSessionId and VolSessionTime don't agree */ if (rec->remainder && (rec->VolSessionId != VolSessionId || rec->VolSessionTime != VolSessionTime)) { rec->state_bits |= REC_NO_MATCH; Dmsg0(read_dbglvl, "remainder and VolSession doesn't match\n"); Dmsg0(dbgep, "=== rpath 4 VolSession no match\n"); return false; /* This is from some other Session */ } /* if Stream is negative, it means that this is a continuation * of a previous partially written record. */ if (Stream < 0) { /* continuation record? */ Dmsg0(dbgep, "=== rpath 5 negative stream\n"); Dmsg1(read_dbglvl, "Got negative Stream => continuation. remainder=%d\n", rec->remainder); rec->state_bits |= REC_CONTINUATION; if (!rec->remainder) { /* if we didn't read previously */ Dmsg0(dbgep, "=== rpath 6 no remainder\n"); rec->data_len = 0; /* return data as if no continuation */ } else if (rec->Stream != -Stream) { Dmsg0(dbgep, "=== rpath 7 wrong cont stream\n"); rec->state_bits |= REC_NO_MATCH; return false; /* This is from some other Session */ } rec->Stream = -Stream; /* set correct Stream */ rec->maskedStream = rec->Stream & STREAMMASK_TYPE; } else { /* Regular record */ Dmsg0(dbgep, "=== rpath 8 normal stream\n"); rec->Stream = Stream; rec->maskedStream = rec->Stream & STREAMMASK_TYPE; rec->data_len = 0; /* transfer to beginning of data */ } rec->VolSessionId = VolSessionId; rec->VolSessionTime = VolSessionTime; rec->FileIndex = FileIndex; if (FileIndex > 0) { Dmsg0(dbgep, "=== rpath 9 FileIndex>0\n"); if (block->FirstIndex == 0) { Dmsg0(dbgep, "=== rpath 10 FirstIndex\n"); block->FirstIndex = FileIndex; } block->LastIndex = rec->FileIndex; } Dmsg6(read_dbglvl, "read_header: FI=%s SessId=%d Strm=%s len=%u rec->remlen=%d data_len=%d\n", FI_to_ascii(buf1, rec->FileIndex), rec->VolSessionId, stream_to_ascii(buf2, rec->Stream, rec->FileIndex), rec->data_bytes, rec->remlen, rec->data_len); } else { Dmsg0(dbgep, "=== rpath 11a block out of records\n"); /* * No more records in this block because the number * of remaining bytes are less than a record header * length, so return empty handed, but indicate that * he must read again. By returning, we allow the * higher level routine to fetch the next block and * then reread. */ Dmsg0(read_dbglvl, "read_header: End of block\n"); rec->state_bits |= (REC_NO_HEADER | REC_BLOCK_EMPTY); empty_block(block); /* mark block empty */ return false; } /* Sanity check */ if (rec->data_bytes >= MAX_BLOCK_LENGTH) { Dmsg0(dbgep, "=== rpath 11b maxlen too big\n"); /* * Something is wrong, force read of next block, abort * continuing with this block. */ rec->state_bits |= (REC_NO_HEADER | REC_BLOCK_EMPTY); empty_block(block); Jmsg2(dcr->jcr, M_WARNING, 0, _("Sanity check failed. maxlen=%d datalen=%d. Block discarded.\n"), MAX_BLOCK_LENGTH, rec->data_bytes); return false; } rec->data = check_pool_memory_size(rec->data, rec->data_len+rec->data_bytes); rec->rstate = st_data; return true; }
/* * BSR + EOF => begin of EOF + EIO * BSR + BSR + EOF => last block * current_block = -1 */ int vtape::bsr(int count) { ASSERT(online); ASSERT(current_file >= 0); ASSERT(count == 1); ASSERT(fd >= 0); check_eof(); if (!count) { return 0; } int ret=0; int last_f=0; int last_b=0; boffset_t last=-1, last2=-1; boffset_t orig = lseek(fd, 0, SEEK_CUR); int orig_f = current_file; int orig_b = current_block; Dmsg4(dbglevel, "bsr(%i) cur_blk=%i orig=%lli cur_FM=%lli\n", count, current_block, orig, cur_FM); /* begin of tape, do nothing */ if (atBOT) { errno = EIO; return -1; } /* at EOF 0:-1 BOT=0 EOD=0 EOF=0 ERR: Input/output error */ if (atEOF) { lseek(fd, cur_FM, SEEK_SET); atEOF = false; if (current_file > 0) { current_file--; } current_block=-1; errno = EIO; return -1; } /* * First, go to cur/last_FM and read all blocks to find the good one */ if (cur_FM == orig) { /* already just before EOF */ lseek(fd, last_FM, SEEK_SET); } else { lseek(fd, cur_FM, SEEK_SET); } ret = read_fm(VT_READ_EOF); do { if (!atEOF) { last2 = last; /* keep track of the 2 last blocs position */ last = lseek(fd, 0, SEEK_CUR); last_f = current_file; last_b = current_block; Dmsg6(dbglevel, "EOF=%i last2=%lli last=%lli < orig=%lli %i:%i\n", atEOF, last2, last, orig, current_file, current_block); } ret = fsr(1); } while ((lseek(fd, 0, SEEK_CUR) < orig) && (ret == 0)); if (last2 > 0 && atEOF) { /* we take the previous position */ lseek(fd, last2, SEEK_SET); current_file = last_f; current_block = last_b - 1; Dmsg3(dbglevel, "1 set offset2=%lli %i:%i\n", last, current_file, current_block); } else if (last > 0) { lseek(fd, last, SEEK_SET); current_file = last_f; current_block = last_b; Dmsg3(dbglevel, "2 set offset=%lli %i:%i\n", last, current_file, current_block); } else { lseek(fd, orig, SEEK_SET); current_file = orig_f; current_block = orig_b; return -1; } Dmsg2(dbglevel, "bsr %i:%i\n", current_file, current_block); errno=0; atEOT = atEOF = atEOD = false; atBOT = (lseek(fd, 0, SEEK_CUR) - (sizeof(uint32_t)+2*sizeof(boffset_t))) == 0; if (orig_b == -1) { current_block = orig_b; } return 0; }
/* * Read a Record from the block * * Returns: false if nothing read or if the continuation record does not match. * In both of these cases, a block read must be done. * true if at least the record header was read, this * routine may have to be called again with a new * block if the entire record was not read. */ bool read_record_from_block(DCR *dcr, DEV_RECORD *rec) { ser_declare; uint32_t remlen; uint32_t VolSessionId; uint32_t VolSessionTime; int32_t FileIndex; int32_t Stream; uint32_t data_bytes; uint32_t rhl; char buf1[100], buf2[100]; remlen = dcr->block->binbuf; /* * Clear state flags */ clear_all_bits(REC_STATE_MAX, rec->state_bits); if (dcr->block->dev->is_tape()) { set_bit(REC_ISTAPE, rec->state_bits); } rec->Block = ((DEVICE *)(dcr->block->dev))->EndBlock; rec->File = ((DEVICE *)(dcr->block->dev))->EndFile; /* * Get the header. There is always a full header, otherwise we find it in the next block. */ Dmsg3(450, "Block=%d Ver=%d size=%u\n", dcr->block->BlockNumber, dcr->block->BlockVer, dcr->block->block_len); if (dcr->block->BlockVer == 1) { rhl = RECHDR1_LENGTH; } else { rhl = RECHDR2_LENGTH; } if (remlen >= rhl) { Dmsg4(450, "Enter read_record_block: remlen=%d data_len=%d rem=%d blkver=%d\n", remlen, rec->data_len, rec->remainder, dcr->block->BlockVer); unser_begin(dcr->block->bufp, WRITE_RECHDR_LENGTH); if (dcr->block->BlockVer == 1) { unser_uint32(VolSessionId); unser_uint32(VolSessionTime); } else { VolSessionId = dcr->block->VolSessionId; VolSessionTime = dcr->block->VolSessionTime; } unser_int32(FileIndex); unser_int32(Stream); unser_uint32(data_bytes); dcr->block->bufp += rhl; dcr->block->binbuf -= rhl; remlen -= rhl; /* * If we are looking for more (remainder!=0), we reject anything * where the VolSessionId and VolSessionTime don't agree */ if (rec->remainder && (rec->VolSessionId != VolSessionId || rec->VolSessionTime != VolSessionTime)) { set_bit(REC_NO_MATCH, rec->state_bits); Dmsg0(450, "remainder and VolSession doesn't match\n"); return false; /* This is from some other Session */ } /* * If Stream is negative, it means that this is a continuation * of a previous partially written record. */ if (Stream < 0) { /* continuation record? */ Dmsg1(500, "Got negative Stream => continuation. remainder=%d\n", rec->remainder); set_bit(REC_CONTINUATION, rec->state_bits); if (!rec->remainder) { /* if we didn't read previously */ rec->data_len = 0; /* return data as if no continuation */ } else if (rec->Stream != -Stream) { set_bit(REC_NO_MATCH, rec->state_bits); return false; /* This is from some other Session */ } rec->Stream = -Stream; /* set correct Stream */ rec->maskedStream = rec->Stream & STREAMMASK_TYPE; } else { /* Regular record */ rec->Stream = Stream; rec->maskedStream = rec->Stream & STREAMMASK_TYPE; rec->data_len = 0; /* transfer to beginning of data */ } rec->VolSessionId = VolSessionId; rec->VolSessionTime = VolSessionTime; rec->FileIndex = FileIndex; if (FileIndex > 0) { if (dcr->block->FirstIndex == 0) { dcr->block->FirstIndex = FileIndex; } dcr->block->LastIndex = FileIndex; } Dmsg6(450, "rd_rec_blk() got FI=%s SessId=%d Strm=%s len=%u\n" "remlen=%d data_len=%d\n", FI_to_ascii(buf1, rec->FileIndex), rec->VolSessionId, stream_to_ascii(buf2, rec->Stream, rec->FileIndex), data_bytes, remlen, rec->data_len); } else { /* * No more records in this block because the number * of remaining bytes are less than a record header * length, so return empty handed, but indicate that * he must read again. By returning, we allow the * higher level routine to fetch the next block and * then reread. */ Dmsg0(450, "read_record_block: nothing\n"); set_bit(REC_NO_HEADER, rec->state_bits); set_bit(REC_BLOCK_EMPTY, rec->state_bits); empty_block(dcr->block); /* mark block empty */ return false; } /* Sanity check */ if (data_bytes >= MAX_BLOCK_LENGTH) { /* * Something is wrong, force read of next block, abort * continuing with this block. */ set_bit(REC_NO_HEADER, rec->state_bits); set_bit(REC_BLOCK_EMPTY, rec->state_bits); empty_block(dcr->block); Jmsg2(dcr->jcr, M_WARNING, 0, _("Sanity check failed. maxlen=%d datalen=%d. Block discarded.\n"), MAX_BLOCK_LENGTH, data_bytes); return false; } rec->data = check_pool_memory_size(rec->data, rec->data_len + data_bytes); /* * At this point, we have read the header, now we * must transfer as much of the data record as * possible taking into account: 1. A partial * data record may have previously been transferred, * 2. The current block may not contain the whole data * record. */ if (remlen >= data_bytes) { /* * Got whole record */ memcpy(rec->data+rec->data_len, dcr->block->bufp, data_bytes); dcr->block->bufp += data_bytes; dcr->block->binbuf -= data_bytes; rec->data_len += data_bytes; } else { /* * Partial record */ memcpy(rec->data+rec->data_len, dcr->block->bufp, remlen); dcr->block->bufp += remlen; dcr->block->binbuf -= remlen; rec->data_len += remlen; rec->remainder = 1; /* partial record transferred */ Dmsg1(450, "read_record_block: partial xfered=%d\n", rec->data_len); set_bit(REC_PARTIAL_RECORD, rec->state_bits); set_bit(REC_BLOCK_EMPTY, rec->state_bits); return true; } rec->remainder = 0; Dmsg4(450, "Rtn full rd_rec_blk FI=%s SessId=%d Strm=%s len=%d\n", FI_to_ascii(buf1, rec->FileIndex), rec->VolSessionId, stream_to_ascii(buf2, rec->Stream, rec->FileIndex), rec->data_len); return true; /* transferred full record */ }
/* * We get the slot list from the Storage daemon. * If listall is set we run an 'autochanger listall' cmd * otherwise an 'autochanger list' cmd * If scan is set and listall is not, we return all slots found, * otherwise, we return only slots with valid barcodes (Volume names) * * Input (output of mxt-changer list): * * 0:vol2 Slot num:Volume Name * * Input (output of mxt-changer listall): * * Drive content: D:Drive num:F:Slot loaded:Volume Name * D:0:F:2:vol2 or D:Drive num:E * D:1:F:42:vol42 * D:3:E * * Slot content: * S:1:F:vol1 S:Slot num:F:Volume Name * S:2:E or S:Slot num:E * S:3:F:vol4 * * Import/Export tray slots: * I:10:F:vol10 I:Slot num:F:Volume Name * I:11:E or I:Slot num:E * I:12:F:vol40 * * If a drive is loaded, the slot *should* be empty */ dlist *get_vol_list_from_SD(UAContext *ua, STORERES *store, bool listall, bool scan) { int nr_fields; char *bp; char dev_name[MAX_NAME_LENGTH]; char *field1, *field2, *field3, *field4, *field5; vol_list_t *vl = NULL; dlist *vol_list; BSOCK *sd = NULL; if (!(sd = open_sd_bsock(ua))) { return NULL; } bstrncpy(dev_name, store->dev_name(), sizeof(dev_name)); bash_spaces(dev_name); /* * Ask for autochanger list of volumes */ if (listall) { sd->fsend(changerlistallcmd , dev_name); } else { sd->fsend(changerlistcmd, dev_name); } vol_list = New(dlist(vl, &vl->link)); /* * Read and organize list of Volumes */ while (bnet_recv(sd) >= 0) { strip_trailing_junk(sd->msg); /* * Check for returned SD messages */ if (sd->msg[0] == '3' && B_ISDIGIT(sd->msg[1]) && B_ISDIGIT(sd->msg[2]) && B_ISDIGIT(sd->msg[3]) && sd->msg[4] == ' ') { ua->send_msg("%s\n", sd->msg); /* pass them on to user */ continue; } /* * Parse the message. list gives max 2 fields listall max 5. * We always make sure all fields are initialized to either * a value or NULL. * * For autochanger list the following mapping is used: * - field1 == slotnr * - field2 == volumename * * For autochanger listall the following mapping is used: * - field1 == type * - field2 == slotnr * - field3 == content (E for Empty, F for Full) * - field4 == loaded (loaded slot if type == D) * - field4 == volumename (if type == S or I) * - field5 == volumename (if type == D) */ field1 = sd->msg; field2 = strchr(sd->msg, ':'); if (field2) { *field2++ = '\0'; if (listall) { field3 = strchr(field2, ':'); if (field3) { *field3++ = '\0'; field4 = strchr(field3, ':'); if (field4) { *field4++ = '\0'; field5 = strchr(field4, ':'); if (field5) { *field5++ = '\0'; nr_fields = 5; } else { nr_fields = 4; } } else { nr_fields = 3; field5 = NULL; } } else { nr_fields = 2; field4 = NULL; field5 = NULL; } } else { nr_fields = 2; field3 = NULL; field4 = NULL; field5 = NULL; } } else { nr_fields = 1; field3 = NULL; field4 = NULL; field5 = NULL; } /* * See if this is a parsable string from either list or listall * e.g. at least f1:f2 */ if (!field1 && !field2) { goto parse_error; } vl = (vol_list_t *)malloc(sizeof(vol_list_t)); memset(vl, 0, sizeof(vol_list_t)); if (scan && !listall) { /* * Scanning -- require only valid slot */ vl->Slot = atoi(field1); if (vl->Slot <= 0) { ua->error_msg(_("Invalid Slot number: %s\n"), sd->msg); free(vl); continue; } vl->Type = slot_type_normal; if (strlen(field2) > 0) { vl->Content = slot_content_full; vl->VolName = bstrdup(field2); } else { vl->Content = slot_content_empty; } vl->Index = INDEX_SLOT_OFFSET + vl->Slot; } else if (!listall) { /* * Not scanning and not listall. */ if (strlen(field2) == 0) { free(vl); continue; } if (!is_an_integer(field1) || (vl->Slot = atoi(field1)) <= 0) { ua->error_msg(_("Invalid Slot number: %s\n"), field1); free(vl); continue; } if (!is_volume_name_legal(ua, field2)) { ua->error_msg(_("Invalid Volume name: %s\n"), field2); free(vl); continue; } vl->Type = slot_type_normal; vl->Content = slot_content_full; vl->VolName = bstrdup(field2); vl->Index = INDEX_SLOT_OFFSET + vl->Slot; } else { /* * Listall. */ if (!field3) { goto parse_error; } switch (*field1) { case 'D': vl->Type = slot_type_drive; break; case 'S': vl->Type = slot_type_normal; break; case 'I': vl->Type = slot_type_import; break; default: vl->Type = slot_type_unknown; break; } /* * For drives the Slot is the actual drive number. * For any other type its the actual slot number. */ switch (vl->Type) { case slot_type_drive: if (!is_an_integer(field2) || (vl->Slot = atoi(field2)) < 0) { ua->error_msg(_("Invalid Drive number: %s\n"), field2); free(vl); continue; } vl->Index = INDEX_DRIVE_OFFSET + vl->Slot; if (vl->Index >= INDEX_MAX_DRIVES) { ua->error_msg(_("Drive number %d greater then INDEX_MAX_DRIVES(%d) please increase define\n"), vl->Slot, INDEX_MAX_DRIVES); free(vl); continue; } break; default: if (!is_an_integer(field2) || (vl->Slot = atoi(field2)) <= 0) { ua->error_msg(_("Invalid Slot number: %s\n"), field2); free(vl); continue; } vl->Index = INDEX_SLOT_OFFSET + vl->Slot; break; } switch (*field3) { case 'E': vl->Content = slot_content_empty; break; case 'F': vl->Content = slot_content_full; switch (vl->Type) { case slot_type_normal: case slot_type_import: if (field4) { vl->VolName = bstrdup(field4); } break; case slot_type_drive: if (field4) { vl->Loaded = atoi(field4); } if (field5) { vl->VolName = bstrdup(field5); } break; default: break; } break; default: vl->Content = slot_content_unknown; break; } } if (vl->VolName) { Dmsg6(100, "Add index = %d slot=%d loaded=%d type=%d content=%d Vol=%s to SD list.\n", vl->Index, vl->Slot, vl->Loaded, vl->Type, vl->Content, NPRT(vl->VolName)); } else { Dmsg5(100, "Add index = %d slot=%d loaded=%d type=%d content=%d Vol=NULL to SD list.\n", vl->Index, vl->Slot, vl->Loaded, vl->Type, vl->Content); } vol_list->binary_insert(vl, compare_vol_list_entry); continue; parse_error: /* * We encountered a parse error, see how many replacements * we done of ':' with '\0' by looking at the nr_fields * variable and undo those. Number of undo's are nr_fields - 1 */ while (nr_fields > 1 && (bp = strchr(sd->msg, '\0')) != NULL) { *bp = ':'; nr_fields--; } ua->error_msg(_("Illegal output from autochanger %s: %s\n"), (listall) ? _("listall") : _("list"), sd->msg); free(vl); continue; } close_sd_bsock(ua); if (vol_list->size() == 0) { delete vol_list; vol_list = NULL; } return vol_list; }
/* * 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(); }
/* * 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", dev_name, omr->VolumeName, mr->VolumeName, pr->Name, mr->MediaType, mr->Slot, drive); 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", dev_name, mr->VolumeName, pr->Name, mr->MediaType, mr->Slot, drive); ua->send_msg(_("Sending label command for Volume \"%s\" Slot %d ...\n"), mr->VolumeName, mr->Slot); Dmsg6(100, "label %s VolumeName=%s PoolName=%s MediaType=%s Slot=%d drive=%d\n", dev_name, mr->VolumeName, pr->Name, mr->MediaType, mr->Slot, drive); } /* * 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; }
/* Write session label * Returns: false on failure * true on success */ bool write_session_label(DCR *dcr, int label) { JCR *jcr = dcr->jcr; DEVICE *dev = dcr->dev; DEV_RECORD *rec; DEV_BLOCK *block = dcr->block; char buf1[100], buf2[100]; rec = new_record(); Dmsg1(130, "session_label record=%x\n", rec); switch (label) { case SOS_LABEL: set_start_vol_position(dcr); break; case EOS_LABEL: if (dev->is_tape()) { dcr->EndBlock = dev->EndBlock; dcr->EndFile = dev->EndFile; } else { dcr->EndBlock = (uint32_t)dev->file_addr; dcr->EndFile = (uint32_t)(dev->file_addr >> 32); } break; default: Jmsg1(jcr, M_ABORT, 0, _("Bad Volume session label = %d\n"), label); break; } create_session_label(dcr, rec, label); rec->FileIndex = label; /* * We guarantee that the session record can totally fit * into a block. If not, write the block, and put it in * the next block. Having the sesssion record totally in * one block makes reading them much easier (no need to * read the next block). */ if (!can_write_record_to_block(block, rec)) { Dmsg0(150, "Cannot write session label to block.\n"); if (!dcr->write_block_to_device()) { Dmsg0(130, "Got session label write_block_to_dev error.\n"); free_record(rec); return false; } } if (!write_record_to_block(dcr, rec)) { free_record(rec); return false; } Dmsg6(150, "Write sesson_label record JobId=%d FI=%s SessId=%d Strm=%s len=%d " "remainder=%d\n", jcr->JobId, FI_to_ascii(buf1, rec->FileIndex), rec->VolSessionId, stream_to_ascii(buf2, rec->Stream, rec->FileIndex), rec->data_len, rec->remainder); free_record(rec); Dmsg2(150, "Leave write_session_label Block=%ud File=%ud\n", dev->get_block_num(), dev->get_file()); return true; }
/* * 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, int relabel, bool media_record_exists, int drive) { BSOCK *sd; char dev_name[MAX_NAME_LENGTH]; bool ok = false; bool is_dvd = false; uint64_t VolBytes = 0; if (!(sd=open_sd_bsock(ua))) { return false; } bstrncpy(dev_name, ua->jcr->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", dev_name, omr->VolumeName, mr->VolumeName, pr->Name, mr->MediaType, mr->Slot, drive); 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", dev_name, mr->VolumeName, pr->Name, mr->MediaType, mr->Slot, drive); ua->send_msg(_("Sending label command for Volume \"%s\" Slot %d ...\n"), mr->VolumeName, mr->Slot); Dmsg6(100, "label %s VolumeName=%s PoolName=%s MediaType=%s Slot=%d drive=%d\n", dev_name, mr->VolumeName, pr->Name, mr->MediaType, mr->Slot, drive); } while (sd->recv() >= 0) { int dvd; ua->send_msg("%s", sd->msg); if (sscanf(sd->msg, "3000 OK label. VolBytes=%llu DVD=%d ", &VolBytes, &dvd) == 2) { is_dvd = dvd; ok = true; } } unbash_spaces(mr->VolumeName); unbash_spaces(mr->MediaType); unbash_spaces(pr->Name); mr->LabelDate = time(NULL); mr->set_label_date = true; if (is_dvd) { /* We know that a freshly labelled DVD has 1 VolParts */ /* This does not apply to auto-labelled DVDs. */ mr->VolParts = 1; } if (ok) { if (media_record_exists) { /* we update it */ mr->VolBytes = VolBytes; mr->InChanger = mr->Slot > 0; /* if slot give assume in changer */ mr->StorageId = ua->jcr->wstore->StorageId; 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->StorageId = ua->jcr->wstore->StorageId; mr->Enabled = 1; 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; }
/* * Write a Record to the block * * Returns: false on failure (none or partially written) * true on success (all bytes written) * * and remainder returned in packet. * * We require enough room for the header, and we deal with * two special cases. 1. Only part of the record may have * been transferred the last time (when remainder is * non-zero), and 2. The remaining bytes to write may not * all fit into the block. */ bool write_record_to_block(DCR *dcr, DEV_RECORD *rec) { bool retval = false; char buf1[100], buf2[100]; DEV_BLOCK *block = dcr->block; /* * After this point the record is in nrec not rec e.g. its either converted * or is just a pointer to the same as the rec pointer being passed in. */ while (1) { ASSERT(block->binbuf == (uint32_t)(block->bufp - block->buf)); ASSERT(block->buf_len >= block->binbuf); Dmsg6(890, "write_record_to_block() FI=%s SessId=%d Strm=%s len=%d\n" "rem=%d remainder=%d\n", FI_to_ascii(buf1, rec->FileIndex), rec->VolSessionId, stream_to_ascii(buf2, rec->Stream, rec->FileIndex), rec->data_len, rec->remlen, rec->remainder); switch (rec->state) { case st_none: /* * Figure out what to do */ rec->state = st_header; continue; /* go to next state */ case st_header: /* * Write header * * If rec->remainder is non-zero, we have been called a * second (or subsequent) time to finish writing a record * that did not previously fit into the block. */ if (!write_header_to_block(block, rec)) { goto bail_out; /* write block then come back here */ } rec->state = st_data; /* after header, now write data */ continue; /* * Write continuation header */ case st_header_cont: write_continue_header_to_block(block, rec); rec->state = st_data; if (rec->remlen == 0) { goto bail_out; /* Partial transfer */ } continue; /* * Write normal data */ case st_data: /* * Write data * * Part of it may have already been transferred, and we * may not have enough room to transfer the whole this time. */ if (rec->remainder > 0) { if (!write_data_to_block(block, rec)) { rec->state = st_header_cont; goto bail_out; } } rec->remainder = 0; /* did whole transfer */ rec->state = st_none; retval = true; goto bail_out; default: Dmsg0(000, "Something went wrong. Default state.\n"); rec->state = st_none; retval = true; goto bail_out; } } bail_out: return retval; }