summaryrefslogtreecommitdiffstatshomepage
path: root/src/lib/util/simh_tape_file.cpp
diff options
context:
space:
mode:
author Miƫtek Bak <mietek@bak.io>2023-09-14 02:27:47 +0200
committer GitHub <noreply@github.com>2023-09-14 07:27:47 +0700
commit5353a8df14d88a378361902a6954ab4c5c1dc5bf (patch)
treefaeb581bddcf4367980e775ae63ca3540ecb73cd /src/lib/util/simh_tape_file.cpp
parent86c6e83b4c2b8aca18db2565e72b03f90a25dda5 (diff)
nscsi: Add SCSI tape device based on SIMH tape image format (#11430)
Diffstat (limited to 'src/lib/util/simh_tape_file.cpp')
-rw-r--r--src/lib/util/simh_tape_file.cpp625
1 files changed, 625 insertions, 0 deletions
diff --git a/src/lib/util/simh_tape_file.cpp b/src/lib/util/simh_tape_file.cpp
new file mode 100644
index 00000000000..06841369ff5
--- /dev/null
+++ b/src/lib/util/simh_tape_file.cpp
@@ -0,0 +1,625 @@
+// license:BSD-3-Clause
+// copyright-holders:Mietek Bak
+
+// best read together with SIMH magtape spec (rev 17 Jan 2022)
+// http://simh.trailing-edge.com/docs/simh_magtape.pdf
+
+#include "multibyte.h"
+#include "simh_tape_file.h"
+
+#include "ioprocs.h"
+
+#include <cassert>
+#include <cstring>
+#include <stdexcept>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+//////////////////////////////////////////////////////////////////////////////
+
+// constants and helpers
+
+enum class simh_marker : u32 {
+ TAPE_MARK = 0x00000000, // filemark; TODO: SIMH doesn't define setmarks
+ ERASE_GAP = 0xfffffffe,
+ EOM = 0xffffffff
+};
+
+inline const bool is_simh_marker_half_gap_forward(const simh_marker marker)
+{
+ // this function is used when we're reading normally (from BOM to EOM); returns true for erase gap markers that have been half overwritten
+ return (const u32)marker == 0xfffeffff;
+}
+
+inline const bool is_simh_marker_half_gap_reverse(const simh_marker marker)
+{
+ // this function is used when we're reading in reverse (from EOM to BOM); returns true for erase gap markers that have been half overwritten
+ return (const u32)marker >= 0xffff0000 && (const u32)marker <= 0xfffffffd;
+}
+
+inline const bool is_simh_marker_eod_forward(const simh_marker marker)
+{
+ // this function is used when we're reading normally (from BOM to EOM); returns true for markers that we consider EOD
+ return marker == simh_marker::ERASE_GAP
+ || is_simh_marker_half_gap_forward(marker)
+ || marker == simh_marker::EOM; // logical EOM
+}
+
+inline const bool is_simh_marker_eod_reverse(const simh_marker marker)
+{
+ // this function is used when we're reading in reverse (from EOM to BOM); returns true for markers that we consider EOD
+ return marker == simh_marker::ERASE_GAP
+ || is_simh_marker_half_gap_reverse(marker)
+ || marker == simh_marker::EOM; // logical EOM
+}
+
+enum class simh_marker_class : u8 {
+ GOOD_DATA_RECORD = 0x0,
+ PRIVATE_DATA_RECORD_1 = 0x1,
+ PRIVATE_DATA_RECORD_2 = 0x2,
+ PRIVATE_DATA_RECORD_3 = 0x3,
+ PRIVATE_DATA_RECORD_4 = 0x4,
+ PRIVATE_DATA_RECORD_5 = 0x5,
+ PRIVATE_DATA_RECORD_6 = 0x6,
+ PRIVATE_MARKER = 0x7,
+ BAD_DATA_RECORD = 0x8,
+ RESERVED_DATA_RECORD_9 = 0x9,
+ RESERVED_DATA_RECORD_A = 0xa,
+ RESERVED_DATA_RECORD_B = 0xb,
+ RESERVED_DATA_RECORD_C = 0xc,
+ RESERVED_DATA_RECORD_D = 0xd,
+ TAPE_DESCRIPTION_DATA_RECORD = 0xe,
+ RESERVED_MARKER = 0xf
+};
+
+inline const simh_marker_class get_simh_marker_class(const simh_marker marker)
+{
+ return (const simh_marker_class)((const u32)marker >> 28);
+}
+
+inline const u32 get_simh_marker_value(const simh_marker marker)
+{
+ return (const u32)marker & 0x0fffffff;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+// construction
+
+simh_tape_file::simh_tape_file(util::random_read_write &file, const u64 file_size, const bool read_only, const bool create)
+ : m_file(file)
+ , m_file_size(file_size)
+ , m_read_only(read_only)
+ , m_pos(0)
+{
+ if (create) {
+ if (read_only) // error: we cannot create read-only file
+ throw std::runtime_error("read-only file");
+
+ write_byte_repeat(0, 0xff, m_file_size); // we assume simh_marker::EOM == 0xffffffff
+ }
+}
+
+simh_tape_file::~simh_tape_file()
+{
+ m_file.finalize();
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+// internal operations
+
+void simh_tape_file::raw_seek(const u64 pos) const
+{
+ std::error_condition err = m_file.seek(pos, SEEK_SET);
+ if (err) // error: we failed to seek to expected byte offset
+ throw std::runtime_error(std::string("failed seek: ") + err.message());
+}
+
+void simh_tape_file::raw_read(u8 *const buf, const u32 len) const
+{
+ size_t actual_len;
+ std::error_condition err = m_file.read(buf, len, actual_len);
+ if (err || actual_len != len) // error: we failed to read expected number of bytes
+ throw std::runtime_error(std::string("failed read: ") + (err ? err.message() : std::string("unexpected length")));
+}
+
+void simh_tape_file::raw_write(const u8 *const buf, const u32 len) const
+{
+ size_t actual_len;
+ std::error_condition err = m_file.write(buf, len, actual_len);
+ if (err || actual_len != len) // error: we failed to write expected number of bytes
+ throw std::runtime_error(std::string("failed write: ") + (err ? err.message() : std::string("unexpected length")));
+}
+
+void simh_tape_file::read_bytes(const u64 pos, u8 *const buf, const u32 len) const
+{
+ raw_seek(pos);
+ raw_read(buf, len);
+}
+
+const u32 simh_tape_file::read_word(const u64 pos) const
+{
+ const u32 tmp_len = 4;
+ u8 tmp_buf[tmp_len];
+ raw_seek(pos);
+ raw_read(tmp_buf, tmp_len);
+ return get_u32le(tmp_buf);
+}
+
+void simh_tape_file::write_bytes(const u64 pos, const u8 *const buf, const u32 len) const
+{
+ raw_seek(pos);
+ raw_write(buf, len);
+}
+
+void simh_tape_file::write_byte_repeat(const u64 pos, const u8 data, const u32 len) const
+{
+ const u32 tmp_len = 4096;
+ u8 tmp_buf[tmp_len];
+ memset(tmp_buf, data, std::min(len, tmp_len));
+ raw_seek(pos);
+ for (u32 i = 0; i < len / tmp_len; i++)
+ raw_write(tmp_buf, tmp_len);
+ raw_write(tmp_buf, len % tmp_len);
+}
+
+void simh_tape_file::write_word(const u64 pos, const u32 data) const
+{
+ const u32 tmp_len = 4;
+ u8 tmp_buf[tmp_len];
+ put_u32le(tmp_buf, data);
+ raw_seek(pos);
+ raw_write(tmp_buf, tmp_len);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+// position-preserving operations
+
+const std::pair<const tape_status, const u32> simh_tape_file::read_position() const
+{
+ // this module only keeps track of current tape position, therefore this function scans from BOM to find and return current block address, taking linear time; TODO: this module could be rewritten to also keep track of current block address, therefore enabling this function to only take constant time
+ assert(m_pos <= m_file_size);
+ assert(m_pos % 2 == 0);
+ if (m_pos == 0) // we're at BOM and next block is 0
+ return std::pair(tape_status::BOM, 0);
+
+ if (m_pos == m_file_size) // we're at physical EOM and there is no next block
+ return std::pair(tape_status::EOM, 0);
+
+ // we need to count how many blocks are between BOM and us
+ u32 blocks_num = 0;
+ u64 tmp_pos = 0;
+ while (tmp_pos < m_pos) {
+ if (tmp_pos + 4 > m_file_size) // error: truncated marker
+ throw std::runtime_error("truncated marker");
+
+ const simh_marker marker = (const simh_marker)read_word(tmp_pos);
+ if (marker == simh_marker::TAPE_MARK) { // we skip filemarks
+ tmp_pos += 4;
+ continue;
+ }
+ if (is_simh_marker_eod_forward(marker)) // error: we reached EOD
+ return std::pair(is_ew() ? tape_status::UNKNOWN_EW : tape_status::UNKNOWN, 0);
+
+ const simh_marker_class marker_class = get_simh_marker_class(marker);
+ const u32 block_len = get_simh_marker_value(marker);
+ const u32 pad_len = block_len % 2; // pad odd-length blocks with 1 byte
+ const u32 read_len = 4 + block_len + pad_len + 4;
+ switch (marker_class) {
+ case simh_marker_class::PRIVATE_MARKER: // we skip other markers
+ case simh_marker_class::RESERVED_MARKER:
+ tmp_pos += 4;
+ break;
+ case simh_marker_class::GOOD_DATA_RECORD: // we try to count data blocks
+ if (tmp_pos + read_len > m_file_size) // error: truncated block
+ throw std::runtime_error("truncated block");
+
+ // we counted another block
+ tmp_pos += read_len;
+ blocks_num++;
+ break;
+ default: // we try to skip other blocks
+ if (tmp_pos + read_len > m_file_size) // error: truncated block
+ throw std::runtime_error("truncated block");
+
+ tmp_pos += read_len;
+ }
+ }
+ if (m_pos < tmp_pos) // we're inside some block
+ return std::pair(is_ew() ? tape_status::UNKNOWN_EW : tape_status::UNKNOWN, 0);
+
+ // we're right after some block (at EOD, or at filemark, or at another block)
+ assert(tmp_pos == m_pos);
+ return std::pair(is_ew() ? tape_status::EW : tape_status::OK, blocks_num);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+// non-destructive operations
+
+void simh_tape_file::rewind(const bool eom)
+{
+ assert(m_pos <= m_file_size);
+ assert(m_pos % 2 == 0);
+ m_pos = eom ? m_file_size : 0;
+}
+
+const tape_status simh_tape_file::locate_block(const u32 req_block_addr)
+{
+ assert(m_pos <= m_file_size);
+ assert(m_pos % 2 == 0);
+ u32 blocks_num = 0;
+ m_pos = 0;
+ while (m_pos < m_file_size) {
+ if (m_pos + 4 > m_file_size) // error: truncated marker
+ throw std::runtime_error("truncated marker");
+
+ const simh_marker marker = (const simh_marker)read_word(m_pos);
+ if (marker == simh_marker::TAPE_MARK) { // we skip filemarks
+ m_pos += 4;
+ continue;
+ }
+ if (is_simh_marker_eod_forward(marker)) // error: we reached EOD
+ return is_ew() ? tape_status::EOD_EW : tape_status::EOD;
+
+ const simh_marker_class marker_class = get_simh_marker_class(marker);
+ const u32 block_len = get_simh_marker_value(marker);
+ const u32 pad_len = block_len % 2; // pad odd-length blocks with 1 byte
+ const u32 read_len = 4 + block_len + pad_len + 4;
+ switch (marker_class) {
+ case simh_marker_class::PRIVATE_MARKER: // we skip other markers
+ case simh_marker_class::RESERVED_MARKER:
+ m_pos += 4;
+ break;
+ case simh_marker_class::GOOD_DATA_RECORD: // we try to count data blocks
+ if (m_pos + read_len > m_file_size) // error: truncated block
+ throw std::runtime_error("truncated block");
+
+ if (blocks_num == req_block_addr) // success: we located requested block
+ return tape_status::OK;
+
+ m_pos += read_len;
+ blocks_num++;
+ break;
+ default: // we try to skip other blocks
+ if (m_pos + read_len > m_file_size) // error: truncated block
+ throw std::runtime_error("truncated block");
+
+ m_pos += read_len;
+ }
+ }
+ // error: we reached physical EOM
+ assert(m_pos == m_file_size);
+ return tape_status::EOM;
+}
+
+const tape_status simh_tape_file::space_eod()
+{
+ assert(m_pos <= m_file_size);
+ assert(m_pos % 2 == 0);
+ while (m_pos < m_file_size) {
+ if (m_pos + 4 > m_file_size) // error: truncated marker
+ throw std::runtime_error("truncated marker");
+
+ const simh_marker marker = (const simh_marker)read_word(m_pos);
+ if (marker == simh_marker::TAPE_MARK) { // we skip filemarks
+ m_pos += 4;
+ continue;
+ }
+ if (is_simh_marker_eod_forward(marker)) // success: we reached EOD
+ return tape_status::OK;
+
+ const simh_marker_class marker_class = get_simh_marker_class(marker);
+ const u32 block_len = get_simh_marker_value(marker);
+ const u32 pad_len = block_len % 2; // pad odd-length blocks with 1 byte
+ const u32 read_len = 4 + block_len + pad_len + 4;
+ switch (marker_class) {
+ case simh_marker_class::PRIVATE_MARKER: // we skip other markers
+ case simh_marker_class::RESERVED_MARKER:
+ m_pos += 4;
+ break;
+ case simh_marker_class::GOOD_DATA_RECORD: // we try to skip all blocks
+ default:
+ if (m_pos + read_len > m_file_size) // error: truncated block
+ throw std::runtime_error("truncated block");
+
+ m_pos += read_len;
+ }
+ }
+ // error: we reached physical EOM
+ assert(m_pos == m_file_size);
+ return tape_status::EOM;
+}
+
+const std::pair<const tape_status, const u32> simh_tape_file::space_blocks(const u32 req_blocks_num)
+{
+ assert(m_pos <= m_file_size);
+ assert(m_pos % 2 == 0);
+ assert(req_blocks_num > 0);
+ u32 blocks_num = 0;
+ while (m_pos < m_file_size) {
+ if (m_pos + 4 > m_file_size) // error: truncated marker
+ throw std::runtime_error("truncated marker");
+
+ const simh_marker marker = (const simh_marker)read_word(m_pos);
+ if (marker == simh_marker::TAPE_MARK) { // error: we reached filemark
+ m_pos += 4;
+ return std::pair(is_ew() ? tape_status::FILEMARK_EW : tape_status::FILEMARK, blocks_num);
+ }
+ if (is_simh_marker_eod_forward(marker)) // error: we reached EOD
+ return std::pair(is_ew() ? tape_status::EOD_EW : tape_status::EOD, blocks_num);
+
+ const simh_marker_class marker_class = get_simh_marker_class(marker);
+ const u32 block_len = get_simh_marker_value(marker);
+ const u32 pad_len = block_len % 2; // pad odd-length blocks with 1 byte
+ const u32 read_len = 4 + block_len + pad_len + 4;
+ switch (marker_class) {
+ case simh_marker_class::PRIVATE_MARKER: // we skip other markers
+ case simh_marker_class::RESERVED_MARKER:
+ m_pos += 4;
+ break;
+ case simh_marker_class::GOOD_DATA_RECORD: // we try to count data blocks
+ if (m_pos + read_len > m_file_size) // error: truncated block
+ throw std::runtime_error("truncated block");
+
+ m_pos += read_len;
+ blocks_num++;
+ if (blocks_num == req_blocks_num) // success: we're done
+ return std::pair(tape_status::OK, blocks_num);
+
+ break;
+ default: // we try to skip other blocks
+ if (m_pos + read_len > m_file_size) // error: truncated block
+ throw std::runtime_error("truncated block");
+
+ m_pos += read_len;
+ }
+ }
+ // error: we reached physical EOM
+ assert(m_pos == m_file_size);
+ return std::pair(tape_status::EOM, blocks_num);
+}
+
+const std::pair<const tape_status, const u32> simh_tape_file::space_filemarks(const u32 req_filemarks_num, const bool setmarks, const bool sequential)
+{
+ assert(m_pos <= m_file_size);
+ assert(m_pos % 2 == 0);
+ assert(req_filemarks_num > 0);
+ assert(!setmarks); // TODO: SIMH doesn't define setmarks
+ assert(!sequential); // TODO: support spacing over sequential filemarks, once we have good way to test it
+ u32 filemarks_num = 0;
+ while (m_pos < m_file_size) {
+ if (m_pos + 4 > m_file_size) // error: truncated marker
+ throw std::runtime_error("truncated marker");
+
+ const simh_marker marker = (const simh_marker)read_word(m_pos);
+ if (marker == simh_marker::TAPE_MARK) { // we count filemarks
+ m_pos += 4;
+ filemarks_num++;
+ if (filemarks_num == req_filemarks_num) // success: we're done
+ return std::pair(tape_status::OK, filemarks_num);
+
+ continue;
+ }
+ if (is_simh_marker_eod_forward(marker)) // error: we reached EOD
+ return std::pair(is_ew() ? tape_status::EOD_EW : tape_status::EOD, filemarks_num);
+
+ const simh_marker_class marker_class = get_simh_marker_class(marker);
+ const u32 block_len = get_simh_marker_value(marker);
+ const u32 pad_len = block_len % 2; // pad odd-length blocks with 1 byte
+ const u32 read_len = 4 + block_len + pad_len + 4;
+ switch (marker_class) {
+ case simh_marker_class::PRIVATE_MARKER: // we skip other markers
+ case simh_marker_class::RESERVED_MARKER:
+ m_pos += 4;
+ break;
+ case simh_marker_class::GOOD_DATA_RECORD: // we try to skip all blocks
+ default:
+ if (m_pos + read_len > m_file_size) // error: truncated block
+ throw std::runtime_error("truncated block");
+
+ m_pos += read_len;
+ }
+ }
+ // error: we reached physical EOM
+ assert(m_pos == m_file_size);
+ return std::pair(tape_status::EOM, filemarks_num);
+}
+
+const std::pair<const tape_status, const u32> simh_tape_file::space_blocks_reverse(const u32 req_blocks_num)
+{
+ assert(m_pos <= m_file_size);
+ assert(m_pos % 2 == 0);
+ assert(req_blocks_num > 0);
+ u32 blocks_num = 0;
+ while (m_pos > 0) {
+ if (m_pos - 4 < 0) // error: truncated marker
+ throw std::runtime_error("truncated marker");
+
+ const simh_marker marker = (const simh_marker)read_word(m_pos - 4);
+ if (marker == simh_marker::TAPE_MARK) { // error: we reached filemark
+ m_pos -= 4;
+ return std::pair(is_ew() ? tape_status::FILEMARK_EW : tape_status::FILEMARK, blocks_num);
+ }
+ if (is_simh_marker_eod_reverse(marker)) // error: we reached EOD
+ return std::pair(is_ew() ? tape_status::EOD_EW : tape_status::EOD, blocks_num);
+
+ const simh_marker_class marker_class = get_simh_marker_class(marker);
+ const u32 block_len = get_simh_marker_value(marker);
+ const u32 pad_len = block_len % 2; // pad odd-length blocks with 1 byte
+ const u32 read_len = 4 + block_len + pad_len + 4;
+ switch (marker_class) {
+ case simh_marker_class::PRIVATE_MARKER: // we skip other markers
+ case simh_marker_class::RESERVED_MARKER:
+ m_pos -= 4;
+ break;
+ case simh_marker_class::GOOD_DATA_RECORD: // we try to count data blocks
+ if (m_pos - read_len < 0) // error: truncated block
+ throw std::runtime_error("truncated block");
+
+ m_pos -= read_len;
+ blocks_num++;
+ if (blocks_num == req_blocks_num) // success: we're done
+ return std::pair(tape_status::OK, blocks_num);
+
+ break;
+ default: // we try to skip other blocks
+ if (m_pos - read_len < 0) // error: truncated block
+ throw std::runtime_error("truncated block");
+
+ m_pos -= read_len;
+ }
+ }
+ // error: we reached BOM
+ assert(m_pos == 0);
+ return std::pair(tape_status::BOM, blocks_num);
+}
+
+const std::pair<const tape_status, const u32> simh_tape_file::space_filemarks_reverse(const u32 req_filemarks_num, const bool setmarks, const bool sequential)
+{
+ assert(m_pos <= m_file_size);
+ assert(m_pos % 2 == 0);
+ assert(req_filemarks_num > 0);
+ assert(!setmarks); // TODO: SIMH doesn't define setmarks
+ assert(!sequential); // TODO: support spacing over sequential filemarks, once we have good way to test it
+ u32 filemarks_num = 0;
+ while (m_pos > 0) {
+ if (m_pos - 4 < 0) // error: truncated marker
+ throw std::runtime_error("truncated marker");
+
+ const simh_marker marker = (const simh_marker)read_word(m_pos - 4);
+ if (marker == simh_marker::TAPE_MARK) { // we count filemarks
+ m_pos -= 4;
+ filemarks_num++;
+ if (filemarks_num == req_filemarks_num) // success: we're done
+ return std::pair(tape_status::OK, filemarks_num);
+
+ continue;
+ }
+ if (is_simh_marker_eod_reverse(marker)) // error: we reached EOD
+ return std::pair(is_ew() ? tape_status::EOD_EW : tape_status::EOD, filemarks_num);
+
+ const simh_marker_class marker_class = get_simh_marker_class(marker);
+ const u32 block_len = get_simh_marker_value(marker);
+ const u32 pad_len = block_len % 2; // pad odd-length blocks with 1 byte
+ const u32 read_len = 4 + block_len + pad_len + 4;
+ switch (marker_class) {
+ case simh_marker_class::PRIVATE_MARKER: // we skip other markers
+ case simh_marker_class::RESERVED_MARKER:
+ m_pos -= 4;
+ break;
+ case simh_marker_class::GOOD_DATA_RECORD: // we try to skip all blocks
+ default:
+ if (m_pos - read_len < 0) // error: truncated block
+ throw std::runtime_error("truncated block");
+
+ m_pos -= read_len;
+ }
+ }
+ // error: we reached BOM
+ assert(m_pos == 0);
+ return std::pair(tape_status::BOM, filemarks_num);
+}
+
+const std::pair<const tape_status, const u32> simh_tape_file::read_block(u8 *buf, const u32 buf_size)
+{
+ assert(m_pos <= m_file_size);
+ assert(m_pos % 2 == 0);
+ while (m_pos < m_file_size) {
+ if (m_pos + 4 > m_file_size) // error: truncated marker
+ throw std::runtime_error("truncated marker");
+
+ const simh_marker marker = (const simh_marker)read_word(m_pos);
+ if (marker == simh_marker::TAPE_MARK) { // error: we reached filemark
+ m_pos += 4;
+ return std::pair(is_ew() ? tape_status::FILEMARK_EW : tape_status::FILEMARK, 0);
+ }
+ if (is_simh_marker_eod_forward(marker)) // error: we reached EOD
+ return std::pair(is_ew() ? tape_status::EOD_EW : tape_status::EOD, 0);
+
+ const simh_marker_class marker_class = get_simh_marker_class(marker);
+ const u32 block_len = get_simh_marker_value(marker);
+ const u32 pad_len = block_len % 2; // pad odd-length blocks with 1 byte
+ const u32 read_len = 4 + block_len + pad_len + 4;
+ switch (marker_class) {
+ case simh_marker_class::PRIVATE_MARKER: // we skip other markers
+ case simh_marker_class::RESERVED_MARKER:
+ m_pos += 4;
+ break;
+ case simh_marker_class::GOOD_DATA_RECORD: // we try to read data blocks
+ if (m_pos + read_len > m_file_size) // error: truncated block
+ throw std::runtime_error("truncated block");
+
+ if (block_len > buf_size) // error: block length too big
+ throw std::runtime_error("block length too big");
+
+ // success: we read another block
+ read_bytes(m_pos + 4, buf, block_len);
+ m_pos += read_len;
+ return std::pair(tape_status::OK, block_len);
+
+ default: // we try to skip other blocks
+ if (m_pos + read_len > m_file_size) // error: truncated block
+ throw std::runtime_error("truncated block");
+
+ m_pos += read_len;
+ }
+ }
+ // error: we reached physical EOM
+ assert(m_pos == m_file_size);
+ return std::pair(tape_status::EOM, 0);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+// destructive operations
+
+void simh_tape_file::erase(const bool eom)
+{
+ assert(!m_read_only);
+ assert(m_pos <= m_file_size);
+ assert(m_pos % 2 == 0);
+ const u32 write_len = m_file_size - m_pos; // we always erase entire remainder of tape
+ write_byte_repeat(m_pos, 0xff, write_len); // we assume simh_marker::EOM == 0xffffffff
+ m_pos += write_len;
+}
+
+const tape_status simh_tape_file::write_block(const u8 *const buf, const u32 req_block_len)
+{
+ assert(!m_read_only);
+ assert(m_pos <= m_file_size);
+ assert(m_pos % 2 == 0);
+ const u32 pad_len = req_block_len % 2; // pad odd-length blocks with 1 byte
+ const u32 write_len = 4 + req_block_len + pad_len + 4;
+ if (m_pos + write_len >= m_file_size) // error: we reached physical EOM
+ return tape_status::EOM;
+
+ write_word(m_pos, req_block_len);
+ write_bytes(m_pos + 4, buf, req_block_len);
+ write_byte_repeat(m_pos + 4 + req_block_len, 0, pad_len);
+ write_word(m_pos + 4 + req_block_len + pad_len, req_block_len);
+ m_pos += write_len;
+ return is_ew() ? tape_status::EW : tape_status::OK; // success: we wrote another block
+}
+
+const tape_status simh_tape_file::write_filemarks(const u32 req_filemarks_num, const bool setmarks)
+{
+ assert(!m_read_only);
+ assert(m_pos <= m_file_size);
+ assert(m_pos % 2 == 0);
+ assert(!setmarks); // TODO: SIMH doesn't define setmarks
+ const u32 write_len = req_filemarks_num * 4;
+ if (m_pos + write_len >= m_file_size) // error: we reached physical EOM
+ return tape_status::EOM;
+
+ for (u32 i = 0; i < write_len; i += 4)
+ write_word(m_pos + i, (const u32)simh_marker::TAPE_MARK);
+ m_pos += write_len;
+ return is_ew() ? tape_status::EW : tape_status::OK; // success: we wrote all filemarks
+}
+
+//////////////////////////////////////////////////////////////////////////////