mikpe.pdp10-tools/lib/pdp10-stdio.c

440 lines
13 KiB
C

/*
* pdp10-stdio.c
* Copyright (C) 2013-2015 Mikael Pettersson
*
* This file is part of pdp10-tools.
*
* pdp10-tools is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* pdp10-tools is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with pdp10-tools. If not, see <http://www.gnu.org/licenses/>.
*/
/*
* Provide stdio.h-like interface for I/O to and from files with 9-bit logical bytes (nonets),
* represented by native files with 8-bit physical bytes (octets).
*
* Theory of operation:
*
* - The state of a pdp10 file is composed of: a FILE* for an underlying octet file,
* the current read/write position in the nonet file, a 16-bit shift register buffering
* partial octets (writes) or partial nonets (reads), a counter indicating the number
* of bits in the shift register (which may be negative after a call to pdp10_fseek),
* and a boolean flag indicating if there may be unwritten buffered output.
*
* - Write streams: pdp10_fputc adds 9 bits to shiftreg and 9 to shiftreg_nr_bits, then each
* complete group of 8 bits in shiftreg is shifted out and written to the octet file.
* Between pdp10_fputc calls shiftreg contains between 0 and 7 bits, inclusive, during a
* pdp10_fputc it may temporarily contain up to 7+9 == 16 bits.
*
* - Read streams: pdp10_fgetc reads an octet from the octet file and adds 8 bits to shiftreg
* and 8 to shiftreg_nr_bits; this is repeated once more if needed to make shiftreg
* contains at least 9 bits. Then 9 bits are shifted out of shiftreg and returned.
* Between pdp10_fgetc calls shiftreg contains between 0 and 7 bits, inclusive, during an
* fgetc it may contain up to 8+8 == 16 bits.
*
* - An output operation (pdp10_fputc or pdp10_fwrite) may not be directly followed by an
* input operation (pdp10_fgetc or pdp10_fread) without an intervening call to pdp10_fflush
* or pdp10_fseeko, and an input operation may not be directly followed by an output
* operation without an intervening call to pdp10_fseeko, unless the input operation
* encountered end-of-file. (Same restriction as ANSI/ISO C.)
*
* - A pdp10_fseeko repositions the octet file to the closest octet boundary at or before the
* requested nonet boundary, and sets shiftreg_nr_bits to the bit difference, as a number
* between 0 and -7, inclusive. A subsequent pdp10_fgetc or pdp10_fputc detects this
* special state and reinitializes shiftreg as appropriate for that I/O direction.
*/
#include <errno.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "pdp10-stdio.h"
struct pdp10_file {
FILE *octet_fp;
off_t nonet_pos; /* current read or write nonet offset */
unsigned int shiftreg; /* contains 0 to 16 buffered bits */
int shiftreg_nr_bits;
int writing; /* non-zero if shiftreg may contain pending output data */
};
static PDP10_FILE *pdp10_fpopen(FILE *octet_fp, const char *mode)
{
PDP10_FILE *pdp10fp;
int oerrno;
if (!octet_fp) {
return NULL;
} else if (mode[0] == 'a') { /* "a+" won't work, and "a" is not yet implemented */
errno = EINVAL;
} else {
pdp10fp = malloc(sizeof *pdp10fp);
if (pdp10fp) {
pdp10fp->octet_fp = octet_fp;
pdp10fp->nonet_pos = 0;
pdp10fp->shiftreg = 0;
pdp10fp->shiftreg_nr_bits = 0;
pdp10fp->writing = 0;
return pdp10fp;
}
}
oerrno = errno;
fclose(octet_fp);
errno = oerrno;
return NULL;
}
PDP10_FILE *pdp10_fdopen(int fd, const char *mode)
{
off_t octet_pos;
octet_pos = lseek(fd, 0, SEEK_CUR);
if (octet_pos == -1)
return NULL;
/* XXX: for simplicity we require that octet_pos is zero,
otherwise we could follow up with a pdp10_fseeko() call */
if (octet_pos != 0) {
errno = EINVAL;
return NULL;
}
return pdp10_fpopen(fdopen(fd, mode), mode);
}
PDP10_FILE *pdp10_fopen(const char *path, const char *mode)
{
return pdp10_fpopen(fopen(path, mode), mode);
}
static int pdp10_flush_buffered_write(PDP10_FILE *pdp10fp)
{
int octet_ch;
if (!pdp10fp->writing)
return 0;
if (pdp10fp->shiftreg_nr_bits <= 0)
return 0;
/* read the next octet which we will partially overwrite */
if (fseeko(pdp10fp->octet_fp, 0, SEEK_CUR) == -1)
return EOF;
octet_ch = fgetc(pdp10fp->octet_fp);
/* rewind by one octet, or by zero octets if we read EOF above */
if (fseeko(pdp10fp->octet_fp, octet_ch == EOF ? 0 : -1, SEEK_CUR) == -1)
return EOF;
if (octet_ch == EOF)
octet_ch = 0;
octet_ch &= (1 << (8 - pdp10fp->shiftreg_nr_bits)) - 1;
octet_ch |= (pdp10fp->shiftreg << (8 - pdp10fp->shiftreg_nr_bits)) & 0xFF;
if (fputc(octet_ch, pdp10fp->octet_fp) == EOF)
return EOF;
/* rewind by one octet to permit further writes; XXX: this is unnecessary
when the flush is called from fclose() or fseeko() */
if (fseeko(pdp10fp->octet_fp, -1, SEEK_CUR) == -1)
return EOF;
return 0;
}
int pdp10_fflush(PDP10_FILE *pdp10fp)
{
if (pdp10_flush_buffered_write(pdp10fp) == EOF)
return EOF;
return fflush(pdp10fp->octet_fp);
}
int pdp10_fclose(PDP10_FILE *pdp10fp)
{
int status;
FILE *octet_fp;
status = pdp10_flush_buffered_write(pdp10fp);
octet_fp = pdp10fp->octet_fp;
free(pdp10fp);
if (fclose(octet_fp) == EOF)
status = EOF;
return status;
}
static int pdp10_fgetc_one_octet(PDP10_FILE *pdp10fp)
{
int octet_ch;
octet_ch = fgetc(pdp10fp->octet_fp);
if (octet_ch == EOF)
return -1; /* incomplete nonets are discarded */
/* XXX: big-endian conversion */
pdp10fp->shiftreg = (pdp10fp->shiftreg << 8) | (octet_ch & 0xFF);
pdp10fp->shiftreg_nr_bits += 8;
return 0;
}
int pdp10_fgetc(PDP10_FILE *pdp10fp)
{
uint16_t nonet_ch;
pdp10fp->writing = 0;
if (pdp10fp->shiftreg_nr_bits < 9) {
/*
* There are three cases to consider here:
*
* 1. 1 <= shiftreg_nr_bits <= 8.
* We have a partially filled nonet in the buffer.
* We'll read one octet.
*
* 2. shiftreg_nr_bits == 0.
* The last read took us to a 72-bit boundary, emptying the buffer.
* We'll read two octets.
*
* 3. -7 <= shiftreg_nr_bits <= -1.
* An fseek placed octet_pos 1 to 7 bits before nonet_pos.
* We'll read two octets, but the first -shiftreg_nr_bits
* bits will be discarded.
*
* Either way we read one or two octets, append them to the buffer,
* and increment shiftreg_nr_bits by the number of bits read.
*
* An EOF during read permits the next operation to be a write, without
* an intervening fflush() or fseeko(). Therefore we must reposition
* octet_pos before nonet_pos if an EOF occurs here.
*/
if (pdp10_fgetc_one_octet(pdp10fp) < 0
|| (pdp10fp->shiftreg_nr_bits < 9
&& pdp10_fgetc_one_octet(pdp10fp) < 0)) {
if (pdp10fp->shiftreg_nr_bits > 0) {
/* if this fseeko() fails then presumably subsequent fseeko()s
will also fail; if not, then data may not be read or written
where we expect it to be XXX */
(void)fseeko(pdp10fp->octet_fp, -1, SEEK_CUR);
pdp10fp->shiftreg_nr_bits -= 8;
}
return EOF;
}
}
/* XXX: big-endian conversion */
nonet_ch = (pdp10fp->shiftreg >> (pdp10fp->shiftreg_nr_bits - 9)) & 0x1FF;
pdp10fp->shiftreg_nr_bits -= 9;
pdp10fp->nonet_pos += 1;
return nonet_ch;
}
static int pdp10_fputc_one_octet(PDP10_FILE *pdp10fp)
{
unsigned char rest_bits;
unsigned char octet_ch;
rest_bits = pdp10fp->shiftreg_nr_bits - 8;
octet_ch = (pdp10fp->shiftreg >> rest_bits) & 0xFF;
if (fputc((char)octet_ch, pdp10fp->octet_fp) == EOF)
return -1;
pdp10fp->shiftreg_nr_bits = rest_bits;
return 0;
}
int pdp10_fputc(uint16_t nonet_ch, PDP10_FILE *pdp10fp)
{
if (pdp10fp->shiftreg_nr_bits < 0) {
int octet_ch;
/*
* -7 <= shiftreg_nr_bits <= -1.
* An fseek placed octet_pos 1 to 7 bits before nonet_pos.
* We will peek at the octet at octet_pos, and preload shiftreg with the
* -shiftreg_nr_bits high bits from the octet.
*/
/* read the next octet, which we will partially overwrite */
#if 0 /* XXX: the pdp10_fseek did that already */
if (fseeko(pdp10fp->octet_fp, 0, SEEK_CUR) == -1)
return EOF;
#endif
octet_ch = fgetc(pdp10fp->octet_fp);
/* rewind by one octet, or by zero octets if we read EOF above */
if (fseeko(pdp10fp->octet_fp, octet_ch == EOF ? 0 : -1, SEEK_CUR) == -1)
return EOF;
if (octet_ch == EOF)
octet_ch = 0;
pdp10fp->shiftreg_nr_bits = -pdp10fp->shiftreg_nr_bits;
pdp10fp->shiftreg = (octet_ch & 0xFF) >> (8 - pdp10fp->shiftreg_nr_bits);
}
pdp10fp->writing = 1;
pdp10fp->shiftreg = (pdp10fp->shiftreg << 9) | (nonet_ch & 0x1FF);
pdp10fp->shiftreg_nr_bits += 9;
if (pdp10_fputc_one_octet(pdp10fp) < 0)
return EOF;
if (pdp10fp->shiftreg_nr_bits == 8
&& pdp10_fputc_one_octet(pdp10fp) < 0)
return EOF;
pdp10fp->nonet_pos += 1;
return nonet_ch & 0x1FF;
}
int pdp10_fseeko(PDP10_FILE *pdp10fp, off_t offset, int whence)
{
off_t octet_pos, nonet_pos;
if (pdp10_flush_buffered_write(pdp10fp) == EOF)
return -1;
switch (whence) {
case PDP10_SEEK_SET:
nonet_pos = 0;
break;
case PDP10_SEEK_CUR:
nonet_pos = pdp10fp->nonet_pos;
break;
case PDP10_SEEK_END:
if (fseeko(pdp10fp->octet_fp, 0, SEEK_END) == -1)
return -1;
octet_pos = ftello(pdp10fp->octet_fp);
if (octet_pos == -1)
return -1;
/*
* Compute 'nonet_pos = (octet_pos * 8) / 9;' without
* overflowing the intermediate term.
*
* Let octet_pos = A * 9 + B, where A = octet_pos / 9 and B = octet_pos % 9.
*
* (octet_pos * 8) / 9
* == ((A * 9 + B) * 8) / 9
* == (A * 9 * 8 + B * 8) / 9
* == A * 8 + (B * 8) / 9
* == (octet_pos / 9) * 8 + ((octet_pos % 9) * 8) / 9
*/
nonet_pos = (octet_pos / 9) * 8 + ((octet_pos % 9) * 8) / 9;
break;
default:
errno = EINVAL;
return -1;
}
nonet_pos += offset;
/*
* Compute 'octet_pos = (nonet_pos * 9) / 8;' without
* overflowing the intermediate term.
*
* Let nonet_pos = C * 8 + D, where C = nonet_pos / 8 and D = nonet_pos % 8.
*
* (nonet_pos * 9) / 8
* == ((C * 8 + D) * 9) / 8
* == (C * 8 * 9 + D * 9) / 8
* == C * 9 + (D * 9) / 8
* == (nonet_pos / 8) * 9 + ((nonet_pos % 8) * 9) / 8
*/
octet_pos = (nonet_pos / 8) * 9 + ((nonet_pos % 8) * 9) / 8;
if (fseeko(pdp10fp->octet_fp, octet_pos, SEEK_SET) == -1)
return -1;
pdp10fp->nonet_pos = nonet_pos;
/*
* Now octet_pos will be from 0 to 7 bits before nonet_pos.
* Depending on whether the next I/O is a read or a write,
* different actions need to be taken. Set shiftreg_nr_bits
* to the negation of the number of "slack" bits to signal
* this case.
*/
pdp10fp->shiftreg = 0;
pdp10fp->shiftreg_nr_bits = -(nonet_pos % 8);
pdp10fp->writing = 0;
return 0;
}
off_t pdp10_ftello(PDP10_FILE *pdp10fp)
{
return pdp10fp->nonet_pos;
}
/*
* On an octet-based host, in-core data structures representing nonet-based
* target data will in fact contain oversize octet-based host data. For
* example, 9/18/36-bit target integers are typically stored in 16/32/64-bit
* host integers.
*
* This means that I/O of aggreate structures must be avoided, and instead
* be performed on each primitive data field individually, using explicit
* marshalling code for multi-nonet primitive data types.
*
* To detect mistakes in I/O, fread and fwrite only accepts strings (size == 1)
* and single marshalled primitive data values (nmemb == 1, size == 1, 2, or 4).
*/
static int pdp10_freadwrite_bad_params(size_t size, size_t nmemb)
{
return !(size == 1 || (nmemb == 1 && (size == 2 || size == 4)));
}
size_t pdp10_fread(uint16_t *ptr, size_t size, size_t nmemb, PDP10_FILE *pdp10fp)
{
size_t i, nr_nonets;
int nonet_ch;
if (size == 0 || nmemb == 0)
return nmemb;
if (pdp10_freadwrite_bad_params(size, nmemb)) {
errno = EINVAL;
return 0;
}
nr_nonets = size * nmemb;
for (i = 0; i < nr_nonets; ++i) {
nonet_ch = pdp10_fgetc(pdp10fp);
if (nonet_ch == EOF)
break;
ptr[i] = nonet_ch & 0x1FF;
}
return i / size;
}
size_t pdp10_fwrite(const uint16_t *ptr, size_t size, size_t nmemb, PDP10_FILE *pdp10fp)
{
size_t i, nr_nonets;
if (size == 0 || nmemb == 0)
return nmemb;
if (pdp10_freadwrite_bad_params(size, nmemb)) {
errno = EINVAL;
return 0;
}
nr_nonets = size * nmemb;
for (i = 0; i < nr_nonets; ++i)
if (pdp10_fputc(ptr[i] & 0x1FF, pdp10fp) == EOF)
break;
return i / size;
}