1
0
mirror of synced 2026-01-17 16:33:13 +00:00
2020-09-09 15:11:45 -07:00

341 lines
13 KiB
C++

#include "tap_file.h"
// Tape behavior as far as I can tell:
// There are two physical markers on the tape: one for beginning of tape, one for end of tape.
// These markers are detected by photo-detectors and any operation that passes them aborts and sets the appropriate flags
// Files are broken into records. Each record is separated by *empty* tape. Seek and read operations stop at empty tape.
// A special end-of-file marker can be written to the tape. These are special 'records' in the sense that you need an
// extra read operation to pass them. Seek operations abort at EOF markers.
//
// The TAP file is orgnaized into blocks, where both the beginning and the end of the block is marked by a 32-bit length
// indicator. The EOF marker is a simple 32-bit 0 value.
//
// The consequence of this is that read and seek operations should normally stop in-between blocks.
//
// A special convention is to mark the end of tape by a double EOF marker. This is not handled by the HW (and thus TapFile_c)
// but recognized in SW.
// mCurRecordValid mCurRecordEof
// false false - we're sitting between two records
// false true - we're sitting before an EOF marker
// true true - we're sitting after an EOF marker <-- in this case mCurRecordStart and mCurRecordLeft are set up to be valid
// true false - we're sitting inside a data record
boost::uintmax_t TapFile_c::GetFileSize() const {
if (!mFileSize.is_initialized()) mFileSize = boost::filesystem::file_size(mFileName);
return mFileSize.get();
}
TapFile_c::State_e TapFile_c::GetState() {
// if (mCurRecordValid && mCurRecordStart == GetFileSize()) return State_e::EndOfTape;
// if (fail() && !boost::filesystem::exists(mFileName)) return State_e::EndOfTape;
if (!boost::filesystem::exists(mFileName)) {
if (mLastCmdDirection != Direction::Forward) return State_e::BeginningOfTape;
return State_e::EndOfTape;
}
if (!is_open()) return State_e::BeginningOfTape;
if (eof()) return State_e::EndOfTape;
if (tellg() == streampos(0)) return State_e::BeginningOfTape;
if (mCurRecordEof) return State_e::EndOfFile;
if (!mCurRecordValid) return State_e::EndOfRecord;
if (mCurRecordLeft == 0 && mLastCmdDirection == Direction::Backward) return State_e::EndOfRecord;
return State_e::Normal;
}
// TODO: figure out what the expected behavior at BOT is. Right now it skips over the first record, but maybe it should just open the first record
void TapFile_c::SeekToNextRecord() {
mLastCmdDirection = Direction::Forward;
if (!boost::filesystem::exists(mFileName)) return;
// If we have a valid record, skip to the next record
// If we don't have a valid record (beginning of tape condition or EOF marker) skip to the next record
// Valid record case: skip to the end of it
if (mCurRecordValid && !mCurRecordEof) {
seekg(mCurRecordLeft + 4, ios_base::cur);
if (fail()) throw Generic_x("Can't read skip to end of record");
mCurRecordValid = false;
return;
}
// We've sitting between records (after an EOF marker or after a data record), so skip through the next record and set the EOF state according to the skipped record
uint32_t RecordSize;
if (eof()) return;
read((char*)(&RecordSize), 4);
if (eof()) return;
if (fail()) throw Generic_x("Can't read beginning of record");
if (RecordSize > 0) {
seekg(RecordSize, ios_base::cur);
if (fail()) throw Generic_x("Can't seek to end of record");
uint32_t EndRecordSize;
read((char*)(&EndRecordSize), 4);
if (fail() && !eof()) throw Generic_x("Can't read end of record");
if (EndRecordSize != RecordSize) throw Generic_x("Record size mismatch at end");
}
mCurRecordEof = RecordSize == 0;
if (mCurRecordEof) {
mCurRecordValid = true;
mCurRecordLeft = 0;
mCurRecordStart = tellg();
}
else {
mCurRecordValid = false;
}
}
void TapFile_c::SeekToPrevRecord() {
mLastCmdDirection = Direction::Backward;
if (!boost::filesystem::exists(mFileName)) return;
if (eof()) clear();
// If we have a valid record, skip to in-between the current and the previous record
// If we are at the beginning of tape don't do anything
// If we don't have a valid record, skip back a full record, stop in-between records.
// If we're on an EOF record, skip to the end of the previous record
// BOT case
if ((tellg() == streamoff(0))) {
Reset();
mCurRecordEof = false;
return;
}
// Valid record case
if (mCurRecordValid) {
// if we're in middle of a record, we should seek to the end of the previous one (if any)
// We do it in two steps: we seek between the two records, then we get to the end of the previous one. <-- actually we shouldn't do this. Just stay between the two records
// There's one exception though: if we've been after the EOF marker we should just jump in front of it
seekg(mCurRecordStart - 4, ios_base::beg);
if (fail()) throw Generic_x("Can't skip to end of record");
Reset();
if (mCurRecordEof) {
#ifndef NO_ASSERTS
uint32_t RecordSize;
read((char*)(&RecordSize), 4);
if (fail()) throw Generic_x("Can't read EOF marker");
CRAY_ASSERT(RecordSize == 0);
seekg(-4, ios_base::cur);
#endif
return;
}
return;
}
// We're either sitting before an EOF marker or between two data records...
mCurRecordEof = false;
// Since we don't have a valid record, open the previous record if any
if (!(tellg() == streamoff(0))) {
seekg(-4, ios_base::cur); // Seek back to the end-of-record marker of the previous record
//streamoff CurPos = tellg();
if (fail()) throw Generic_x("Can't seek to end of record");
uint32_t EndRecordSize;
read((char*)(&EndRecordSize), 4);
if (fail()) throw Generic_x("Can't read end of record");
if (EndRecordSize != 0) {
// Previous record is not an EOF marker, so we have to skip the whole record
seekg(-streamoff(EndRecordSize + 8), ios_base::cur); // Seek back to the beginning of the record
if (fail()) throw Generic_x("Can't seek to beginning of record");
// Make sure the record size at the beginning matches the end
uint32_t RecordSize;
read((char*)(&RecordSize), 4);
if (fail()) throw Generic_x("Can't read beginning of record");
if (EndRecordSize != RecordSize) throw Generic_x("Record size mismatch");
// We seek back in-between the records
Reset();
seekg(-4, ios_base::cur);
}
else {
// Set up stream state to the end of the record
mCurRecordEof = true;
mCurRecordValid = false;
seekg(-4, ios_base::cur); // Seek back before the EOF marker
if (fail()) throw Generic_x("Can't seek to beginning of EOF marker");
}
}
}
// - if more then whole record is read in one move, we should position to the point AFTER the EOR marker, and set current record to nothing
// - if exactly the whole record is read, we should position to right BEFORE the EOR marker and set the current record to valid, but 0 bytes left
// - Consequently we might need one EXTRA read before we can determine the EOR condition
std::vector<uint8_t> TapFile_c::Read(uint32_t aMaxSize) {
mLastCmdDirection = Direction::Forward;
if (!boost::filesystem::exists(mFileName)) {
return std::vector<uint8_t>();
}
CRAY_ASSERT(aMaxSize > 0);
// Reading when no current record is open or we're after an EOF marker: open the next record
if (!mCurRecordValid || mCurRecordEof) {
// We don't have a valid record, so let's see what's in the file...
uint32_t RecordSize;
if (eof()) return std::vector<uint8_t>();
read((char*)(&RecordSize), 4);
if (eof()) return std::vector<uint8_t>();
if (fail()) throw Generic_x("Can't read beginning of record");
// Verify record size
if (RecordSize > 0) {
streampos CurPos = tellg();
seekg(RecordSize, ios_base::cur);
if (fail()) throw Generic_x("Can't seek to end of record");
uint32_t EndRecordSize;
read((char*)(&EndRecordSize), 4);
if (fail() && !eof()) throw Generic_x("Can't read end of record");
if (EndRecordSize != RecordSize) throw Generic_x("Record size mismatch at end");
seekg(CurPos, ios_base::beg);
if (fail()) throw Generic_x("Can't seek back to beginning of record");
}
mCurRecordLeft = RecordSize;
mCurRecordStart = tellg();
mCurRecordValid = true;
mCurRecordEof = RecordSize == 0;
// We've just opened an EOF record: return 0 bytes, and leave the record open
if (mCurRecordEof) {
return std::vector<uint8_t>(); // This is an EOF condition
}
}
CRAY_ASSERT(mCurRecordValid);
CRAY_ASSERT(!mCurRecordEof);
size_t ReadSize = std::min(aMaxSize, mCurRecordLeft);
std::vector<uint8_t> RetVal(ReadSize);
if (ReadSize != 0) read((char*)(&(RetVal[0])), ReadSize);
if (fail()) throw Generic_x("Can't read from file");
// Read past through the just opened record: close the record
if (aMaxSize >= mCurRecordLeft) {
// Read the end record marker
uint32_t EndRecordSize;
read((char*)(&EndRecordSize), 4);
if (fail()) throw Generic_x("Can't read from end of record");
// Set current record to invalid (so status is EOR)
mCurRecordValid = false;
}
mCurRecordLeft -= uint32_t(ReadSize);
return RetVal;
}
// mCurRecordValid mCurRecordEof
// false false - we're sitting between two records
// false true - we're sitting before an EOF marker
// true true - we're sitting after an EOF marker <-- in this case mCurRecordStart and mCurRecordLeft are set up to be valid
// true false - we're sitting inside a data record
void TapFile_c::WriteRecordInternal(const std::vector<uint8_t> &aData) {
mFileSize.reset();
if (!boost::filesystem::exists(mFileName)) {
close();
ios_base::openmode Mode = mMode;
open(mFileName, ios_base::out | ios_base::binary);
write(nullptr, 0);
close();
open(mFileName, Mode);
}
uint32_t RecordSize = uint32_t(aData.size());
if (!eof()) {
if (mCurRecordValid && !mCurRecordEof) {
// Writing when in the middle of a record: truncate at the beginning of the record
seekg(mCurRecordStart - 4, ios_base::beg);
if (fail()) throw Generic_x("Can't seek to beginning of record");
Truncate();
}
else {
// Writing when no current record is open or we're after an EOF marker: truncate unless record length is the same
uint32_t DiskRecordSize;
read((char*)&DiskRecordSize, 4);
if (!eof()) {
if (fail()) throw Generic_x("Can't read beginning of record");
if (DiskRecordSize != 0) {
seekg(DiskRecordSize, ios_base::cur);
if (fail()) throw Generic_x("Can't seek to end of record");
uint32_t EndDiskRecordSize;
read((char*)&EndDiskRecordSize, 4);
if (fail()) throw Generic_x("Can't read end of record");
if (DiskRecordSize != EndDiskRecordSize) throw Generic_x("Record size mismatch at end");
seekg(-streamoff(DiskRecordSize + 4), ios_base::cur);
if (fail()) throw Generic_x("Can't seek to beginning of record");
}
seekg(-4, ios_base::cur);
if (fail()) throw Generic_x("Can't seek to beginning of record");
// We truncate at the begining of the current record, unless record sizes match
if (DiskRecordSize != RecordSize) {
Truncate();
}
}
}
}
if (eof()) clear();
seekp(tellg(), ios_base::beg);
if (eof()) clear();
write((char*)(&RecordSize), 4);
mCurRecordStart = tellp();
mCurRecordLeft = 0;
if (fail()) throw Generic_x("Can't write beginning of record to file");
if (RecordSize != 0) {
write((char*)(&(aData[0])), RecordSize);
if (fail()) throw Generic_x("Can't write record data to file");
write((char*)(&RecordSize), 4);
if (fail()) throw Generic_x("Can't write end of record to file");
}
streamoff Pos = tellp();
// Re-create the proper internal state: the read is positioned at the end of the currenty written record with or without EOF depending on what we just did
seekg(Pos, ios_base::beg);
seekp(Pos, ios_base::beg);
if (fail()) throw Generic_x("Can't reposition read after write");
mCurRecordEof = RecordSize == 0;
mCurRecordValid = mCurRecordEof;
}
void TapFile_c::WriteRecord(const std::vector<uint8_t> &aData) {
mLastCmdDirection = Direction::Forward;
CRAY_ASSERT(aData.size() != 0);
WriteRecordInternal(aData);
}
void TapFile_c::WriteEndOfFile() {
mLastCmdDirection = Direction::Forward;
WriteRecordInternal(std::vector<uint8_t>());
}
void TapFile_c::SeekToBeginningOfTape() {
mLastCmdDirection = Direction::Backward;
if (eof()) clear();
Reset();
mCurRecordEof = false;
seekg(0, ios_base::beg);
}
void TapFile_c::Reset() {
mCurRecordLeft = 0;
mCurRecordStart = 0;
mCurRecordValid = false;
}
void TapFile_c::Truncate() {
Direction OldDir = mLastCmdDirection;
streamoff CurPos = tellg();
close();
mFileSize.reset();
if (CurPos != 0) {
boost::filesystem::resize_file(mFileName, CurPos);
open(mFileName, mMode);
}
else {
ios_base::openmode Mode = mMode;
open(mFileName, ios_base::out | ios_base::binary);
write(nullptr, 0);
close();
open(mFileName, Mode);
}
seekg(CurPos, ios_base::beg);
mLastCmdDirection = OldDir;
}
void TapFile_c::ReOpen() {
if (is_open()) return;
open(mFileName, mMode);
}
/*********
uint32_t TapFile_c::GetCurrentRecordSize() {
if (!mCurRecordValid) return 0;
if (mCurRecordEof) return 0;
return uint32_t(tellg() - mCurRecordStart) + mCurRecordLeft;
}
***********/