1
0
mirror of https://github.com/prirun/p50em.git synced 2026-01-11 23:42:56 +00:00

util: new directory for "Prime on Unix" utility programs

This commit is contained in:
Jim 2020-03-10 14:49:22 -04:00
parent 45086db988
commit 38840b710d
18 changed files with 2787 additions and 0 deletions

13
util/amlcout.py Normal file
View File

@ -0,0 +1,13 @@
# Send bytes 0-0xff to Prime emulator to verify transparent connection
# NOTE: Enable the printf at storech: in devamlc to perform the test
import socket
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client_socket.connect(('localhost', 9000))
data = ''
for i in xrange(256):
data += chr(i)
data += chr(255)
client_socket.send(data)
client_socket.close()

184
util/emlink.c Normal file
View File

@ -0,0 +1,184 @@
/* emlink.c, Jim Wilcoxson, February 1, 2006
Simple telnet-like client to connect to a system & port in raw mode.
This is designed to connect to the Prime emulator as a full-duplex
terminal, without having to mess with a lot of telnet configuration
and/or security issues.
Usage: emlink <system name/address> <port>
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <termios.h>
#define ESCAPE 035
main (int argc, char **argv) {
int port;
int sockfd;
int i;
struct hostent *server;
struct sockaddr_in addr;
unsigned int addrlen;
static struct termios terminfo,resetinfo;
static fd_set fdread,fdexcp;
struct timeval timeout;
unsigned char ch;
int ttydev;
int ttyflags, newflags;
int n,n2;
#define BUFCHARS 16*1024
unsigned char buf[BUFCHARS];
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1) {
perror ("Unable to create socket");
exit(1);
}
sscanf(argv[2],"%d", &port);
printf ("Connecting to %s port %d...\n", argv[1], port);
server = gethostbyname(argv[1]);
if (server == NULL) {
fprintf(stderr,"ERROR, no such host\n");
exit(1);
}
bzero((char *) &addr, sizeof(addr));
addr.sin_family = AF_INET;
bcopy((char *)server->h_addr, (char *)&addr.sin_addr.s_addr, server->h_length);
addr.sin_port = htons(port);
if (connect(sockfd, (void *) &addr,(socklen_t) sizeof(addr)) < 0) {
fprintf(stderr,"Error connecting to server\n");
exit(1);
}
/* setup terminal in raw mode */
ttydev = 0;
if (fcntl(ttydev, F_GETFL, ttyflags) == -1) {
perror(" unable to get tty flags");
exit(1);
}
if (tcgetattr(ttydev, &terminfo) == -1) {
perror(" unable to get tty attributes");
exit(1);
}
resetinfo = terminfo;
terminfo.c_iflag = 0;
terminfo.c_lflag = 0;
terminfo.c_cflag = 0;
terminfo.c_oflag = 0;
#if 0
terminfo.c_cc[VMIN] = 0;
terminfo.c_cc[VTIME] = 0;
#endif
if (tcsetattr(ttydev, TCSANOW, &terminfo) == -1) {
perror(" unable to set tty attributes");
exit(1);
}
printf("Connected! Use ^] to disconnect.\r\n");
/* read/write loop, with tty in raw mode:
read from stdin, write to socket
read from socket, write to stdout
*/
FD_ZERO(&fdread);
FD_ZERO(&fdexcp);
while (1) {
/* wait until socket or stdin have data */
FD_SET(0, &fdread); /* stdin */
FD_SET(0, &fdexcp);
FD_SET(sockfd, &fdread); /* socket */
FD_SET(sockfd, &fdexcp);
#if 0
timeout.tv_sec = 100;
timeout.tv_usec = 0;
#endif
n = select(sockfd+1, &fdread, NULL, &fdexcp, NULL);
if (n == -1) {
if (errno == EINTR)
continue;
tcsetattr(ttydev, TCSANOW, &resetinfo);
perror("Unable to do read select");
exit(1);
}
if (n == 0)
continue;
if (FD_ISSET(0, &fdexcp)) {
tcsetattr(ttydev, TCSANOW, &resetinfo);
fprintf(stderr,"Exception on tty\n");
exit(1);
}
if (FD_ISSET(0, &fdread)) {
n = read(0, buf, sizeof(buf));
if (n == -1) {
tcsetattr(ttydev, TCSANOW, &resetinfo);
fprintf(stderr,"Error reading from stdin\n");
exit(1);
}
if (n == 0) {
tcsetattr(ttydev, TCSANOW, &resetinfo);
fprintf(stderr,"TTY disconnected\n");
exit(1);
}
if (n > 0) {
for (i=0; i<n; i++)
if (buf[i] == ESCAPE) {
tcsetattr(ttydev, TCSANOW, &resetinfo);
fprintf(stderr,"\r\nUser disconnect\n");
exit(1);
}
n2 = write(sockfd, buf, n); /* send stdin data to socket */
if (n2 != n) {
tcsetattr(ttydev, TCSANOW, &resetinfo);
fprintf(stderr,"Only wrote %d of %d bytes to socket\n", n2, n);
exit(1);
}
}
}
if (FD_ISSET(sockfd, &fdexcp)) {
tcsetattr(ttydev, TCSANOW, &resetinfo);
fprintf(stderr,"Exception on socket\n");
exit(1);
}
if (FD_ISSET(sockfd, &fdread)) {
n = read(sockfd, buf, sizeof(buf));
if (n == -1) {
tcsetattr(ttydev, TCSANOW, &resetinfo);
fprintf(stderr,"Error reading from socket\n");
exit(1);
}
if (n == 0) {
tcsetattr(ttydev, TCSANOW, &resetinfo);
fprintf(stderr,"Remote disconnect\n");
exit(1);
}
if (n > 0) {
n2 = write(1, buf, n); /* send socket data to stdout */
if (n2 != n) {
tcsetattr(ttydev, TCSANOW, &resetinfo);
fprintf(stderr,"Only wrote %d of %d bytes to stdout\n", n2, n);
exit(1);
}
}
}
}
if (tcsetattr(ttydev, TCSANOW, &resetinfo) == -1)
fprintf(stderr,"Unable to reset terminal\n");
}

15
util/intsize.c Normal file
View File

@ -0,0 +1,15 @@
/* show size of C integers */
#include <stdio.h>
main() {
int i;
short s;
long l;
long long ll;
printf("size of int is %d\n", sizeof(i));
printf("size of short is %d\n", sizeof(s));
printf("size of long is %d\n", sizeof(l));
printf("size of long long is %d\n", sizeof(ll));
}

306
util/istext.c Normal file
View File

@ -0,0 +1,306 @@
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
/* this function decides if this is a Prime text file or binary file
by checking the contents of the first tape buffer. The conditions
for a text file are:
- it can't be inside a segdir (checked before calling here)
- it has to be a SAM or DAM file - COMO files are DAM files :(
- it has to have at least 1 newline in the first buffer, usually
4090+ bytes; text files with an initial line longer than this have
to be converted after the restore using ptextu
- lines have to start on word boundaries, with zero padding after nl
(NOTE: como files have space padding, so allow that too)
- only text characters, nl, ff, :001 at the beginning of a line,
:221 (compression) and :211 (tab) are allowed in text files
*/
/* max length of extension including . and ending null byte */
#define MAXEXTENSION 10
static struct {
char ext[MAXEXTENSION];
int ftype;
} exttype[] = {
{".basic", 1},
{".cbl", 1},
{".c", 1},
{".cc", 1},
{".ci", 1},
{".cobol", 1},
{".comi", 1},
{".como", 1},
{".cpl", 1},
{".f77", 1},
{".ftn", 1},
{".ibas", 1},
{".ins", 1},
{".list", 1},
{".map", 1},
{".mod", 1},
{".pascal", 1},
{".plp", 1},
{".pl1", 1},
{".pl1g", 1},
{".pma", 1},
{".rpg", 1},
{".runi", 1},
{".runo", 1},
{".spl", 1},
{".sr", 1},
{".vrpg", 1},
{".bin", 0},
{".dl", 0},
{".save", 0},
};
#define EXTENTRIES sizeof(exttype)/sizeof(exttype[0])
int isptext(char *path, int filetype, unsigned char *buf, int len) {
int i, hasnl, skipline;
unsigned char ch;
unsigned char extension[MAXEXTENSION];
/* scan backward to get file extension */
extension[0] = 0;
for (i=strlen(path)-1; i >= 0; i--)
if (path[i] == '.') {
strncpy(extension, path+i, sizeof(extension)-1);
break;
}
if (extension[0] == '.')
for (i=0; i < EXTENTRIES; i++)
if (strcasecmp(extension, exttype[i].ext) == 0)
return exttype[i].ftype;
if (filetype == 0 || (filetype == 1 && strcasecmp(extension,".como") == 0))
;
else {
#ifdef DEBUG
fprintf(stderr, "Filetype %d can't be a text file\n", filetype);
#endif
return 0;
}
hasnl = 0;
skipline = 0;
for (i=0; i<len; i++) {
ch = buf[i];
if (ch == 0212) {
skipline = 0;
hasnl = 1;
if ((i&1) == 0) /* nl is in the left byte */
if (buf[++i] != 0 && buf[i] != 0240)
return 0; /* unusual padding = not a text file */
} else if (skipline) /* skipping this line? */
continue;
else if (ch == 0221) /* space compression */
i++; /* skip the compression count */
else if (ch == 0001) /* spooler pagination control lines; skip 'em */
skipline = 1;
else if ((ch & 0x7f) == 014 || ch == 0211 || (0240 <= ch /* && ch <= 0377 */))
;
else {
#ifdef DEBUG
fprintf(stderr,"Character %o at position %d kept this from being a text file.\n", ch, i);
#endif
return 0;
}
}
return hasnl; /* buffer has to contain a newline to be a text file */
}
/* writes a buffer of Prime text, converting it to Unix text. The
2-character space compression sequences may cross buffers; "state"
is used to track this:
state=0 means no compression pending
state=1 means the next buffer character is the compression count
Before calling convtext for a new file, state must be initialized
to zero in the caller, then left alone after that.
*/
int ptextu(int fd, unsigned char *buf, int len, int *state) {
int i, n;
unsigned char ch;
/* NOTE: one interation through the text conversion loop could add up
to 255 spaces because of text compression, so some slop is added
to the size of the output buffer in the declaration */
#define OBUFMAX 4096
unsigned char obuf[OBUFMAX+256];
n = 0; /* next output buffer postion */
for (i=0; i<len; i++) {
ch = buf[i];
if (*state == 1) { /* expand spaces */
while (ch--)
obuf[n++] = ' ';
*state = 0;
} else if (ch == 0221) /* start of compression sequence */
*state = 1;
else {
obuf[n++] = (ch & 0x7f);
if (ch == 0212 && (i&1) == 0)
i++;
}
if (n >= OBUFMAX) {
if (fd != -1)
if (write(fd, obuf, n) != n) {
fprintf(stderr,"File write error text conversion, n=%d\n", n);
exit(1);
}
n = 0;
}
}
if (n > 0 && fd != -1 && write(fd, obuf, n) != n) {
fprintf(stderr,"File write error text conversion, n=%d\n", n);
exit(1);
}
return len;
}
/* scans buffer contents to see if it qualifies as a Unix text file that
should be converted to Prime text. Text files can only have:
- printable ASCII characters
- tab
- newline
- carriage return
- form feed
*/
int isutext(char *path, unsigned char *buf, int len) {
int i, hasnl, skipline;
unsigned char ch;
unsigned char extension[MAXEXTENSION];
/* scan backward to get file extension */
extension[0] = 0;
for (i=strlen(path)-1; i >= 0; i--)
if (path[i] == '.') {
strncpy(extension, path+i, sizeof(extension)-1);
break;
}
if (extension[0] == '.')
for (i=0; i < EXTENTRIES; i++)
if (strcasecmp(extension, exttype[i].ext) == 0)
return exttype[i].ftype;
/* extension didn't determine type; check file contents */
hasnl = 0;
for (i=0; i<len; i++) {
ch = buf[i];
if (ch == '\n') {
skipline = 0;
hasnl = 1;
} else if (skipline) /* skipping this line? */
continue;
else if (ch == '\f' || ch == '\t' || ch == '\r' || (040 <= ch && ch <= 0177 ))
;
else {
#ifdef DEBUG
fprintf(stderr,"Character %o at position %d kept this from being a text file.\n", ch, i);
#endif
return 0;
}
}
return hasnl; /* buffer has to contain a newline to be a text file */
}
#if 0
/* writes a buffer of Unix or Windows text, converting it to Prime
uncompressed text.
The state argument is a structure that needs to be initialized
before each new file:
state->oddbyte = 0;
state->col = 0;
state->spaces = 0;
It's possible that a string of spaces could be lost at the end of
the file, but this is very unlikely since text files are supposed
to end with a newline.
*/
int utextp(unsigned char *buf, int len, utextp_t *state) {
int i, n, nsp;
unsigned char ch;
n = 0; /* next output buffer position */
for (i=0; i<len; i++) {
ch = buf[i];
if (ch == ' ')
state->spaces++;
else if (ch == '\t') {
nsp = 8 - (state->col & 7);
state->spaces += nsp;
state->col += nsp;
} else {
while (state->spaces) { /* dump held-up spaces for non-space */
if (state->spaces < 3) {
state->obuf[n++] = 0240;
state->spaces--;
state->oddbyte = ~state->oddbyte;
} else {
nsp = state->spaces;
if (nsp > 255) /* can only handle 255 at once! */
nsp = 255;
state->obuf[n++] = 0221;
state->obuf[n++] = nsp;
state->spaces = state->spaces - nsp;
}
}
if (ch == '\r') /* ignore carriage returns (Windoze) */
continue;
state->obuf[n++] = ch | 0x80;
if (ch == 0212 && !state->oddbyte)
state->obuf[n++] = 0; /* pad line to a word boundary */
else
state->oddbyte = ~state->oddbyte;
}
if (n >= OBUFMAX) {
if (fd != -1)
if (write(fd, state->obuf, n) != n) {
fprintf(stderr,"File write error text conversion, n=%d\n", n);
exit(1);
}
n = 0;
}
}
if (n > 0 && fd != -1 && write(fd, state->obuf, n) != n) {
fprintf(stderr,"File write error text conversion, n=%d\n", n);
exit(1);
}
return len;
}
#endif
/* converts a fixed-length string in place from Prime to regular ascii */
void pasciiu(char *p, int len) {
int i;
for (i=0; i<len; i++)
p[i] &= 0x7f;
}
/* converts a fixed-length string in place from regular to Prime ascii */
void uasciip(char *p, int len) {
int i;
for (i=0; i<len; i++)
p[i] |= 0x80;
}

12
util/istext.h Normal file
View File

@ -0,0 +1,12 @@
/* istext.h, Jim Wilcoxson, April 5, 2007
Text conversion routines for the Prime emulator.
*/
#define OBUFMAX 4096
typedef struct {
int oddbyte; /* true if next char written is in right byte */
int col; /* column # of next byte to be written, 0-based */
int spaces; /* # of held-back spaces */
unsigned char obuf[OBUFMAX];
} utextp_t;

908
util/magrst.c Normal file
View File

@ -0,0 +1,908 @@
/* magrst.c, Jim Wilcoxson, March 19, 2005
Reads both old and new (drb) format Magsav tape files on Unix.
Still to do:
- nested segdirs haven't been tested
- .TAP files aren't handled directly - need to untap first
- file types are lost, so Unix Magsav has to guess the file type
- filename translation (slashes, etc.) is not well thought out
- make a hidden symlink for max entries in a segdir (emulation of segdir)
- acls and category acls aren't handled
- roam, rbf, and cam entries aren't handled (who cares!)
- print a warning/error if reels are out of order
- no partial restores
- not tested on multi-reel backups
- does a zero-length file have a data record?
- needs error check for path and buf overflows
- should have a force-overwrite option, and not overwrite otherwise
- an option to save the boot program to disk?
- an option to just display an index with gory details?
- an option to not lowercase filenames?
- ptimestampu should check to see if current timezone observes DST
- isptext maybe should ensure :001 is at the beginning of the line
- a single non-text character prevents text conversion; use a percentage?
- when weirdness happens, set "skipping" more often instead of bombing
- test that skipping=1 actually works
- have a list of suffixes that are most likely text (.cpl, .list, etc)
and a list that are most likely binary (.bin, .run, etc.). If tasting
the file shows a different type, prompt user (or write both types and
let them choose later, in the emulator)
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h> /* mkdir */
#include <sys/types.h> /* mkdir */
#include <errno.h>
#include <time.h> /* mktime */
#include <utime.h> /* utimes */
/* Magsav and "new" Magsav (aka drb) record ids.
NOTE: magsav recid's are positive on the tape! */
#define MS_DATA -1
#define MS_NAME -2
#define MS_START_LOG_TAPE -4
#define MS_END_LOG_TAPE -5
#define DRB_START_LOG_TAPE 1
#define DRB_START_OBJ 2
#define DRB_ACL_DATA 3
#define DRB_ACAT_DATA 4
#define DRB_FILE_DATA 5
#define DRB_ROAM_DATA 6
#define DRB_END_OBJ 7
#define DRB_END_LOG_TAPE 8
#define DRB_DIR_OBJ 1
#define DRB_FILE_OBJ 2
#define DRB_SEGDIR_OBJ 3
#define DRB_ACAT_OBJ 4
#define DRB_ROAM_OBJ 5
#define DRB_ROAM_SEGDIR_OBJ 6
#define DRB_SEG_SUBFILE_OBJ 7
#define DRB_SEG_SUBDIR_OBJ 8
/* writes a buffer of Prime text, converting it to Unix text.
The 2-character space compression sequences may cross tape
buffers; "state" is used to track this:
state=0 means no compression pending
state=1 means the next buffer character is the compression count
Before calling convtext for a new file, state must be initialized
to zero in the caller, then left alone after that.
*/
int convtext(int fd, unsigned char *buf, int len, int *state) {
int i, n;
unsigned char ch;
/* NOTE: one interation through the text conversion loop could add up
to 255 spaces because of text compression, so some slop is added
to the size of the output buffer in the declaration */
#define OBUFMAX 4096
unsigned char obuf[OBUFMAX+256];
n = 0; /* next output buffer postion */
for (i=0; i<len; i++) {
ch = buf[i];
if (*state == 1) { /* expand spaces */
while (ch--)
obuf[n++] = ' ';
*state = 0;
} else if (ch == 0221) /* start of compression sequence */
*state = 1;
else {
obuf[n++] = (ch & 0x7f);
if (ch == 0212 && (i&1) == 0)
i++;
}
if (n >= OBUFMAX) {
if (fd > 0 && write(fd, obuf, n) != n) {
fprintf(stderr,"File write error text conversion, n=%d\n", n);
exit(1);
}
n = 0;
}
}
if (fd > 0 && n > 0 && write(fd, obuf, n) != n) {
fprintf(stderr,"File write error text conversion, n=%d\n", n);
exit(1);
}
return len;
}
/* this function takes a Prime filesystem timestamp (a 32-bit integer)
and returns a Unix timestamp. Since Primos doesn't store timezone
information in the timestamp, the current timezone is used.
Format of a Prime FS timestamp is:
left 16 bits: YYYYYYYMMMMDDDDD, year is mod 100
right 16 bits: seconds since midnight divided by 4, ie, 0-21599
*/
time_t ptimestampu(unsigned int ptime) {
int i;
time_t unixtime;
struct tm tms;
i = ptime >> 25; /* year mod 100 */
if (i < 75) /* assume 2000 if year >= 75 */
i += 100;
tms.tm_year = i; /* mktime wants years since 1900 */
tms.tm_mon = (ptime >> 21) & 0xf;
tms.tm_mday = (ptime >> 16) & 0x1f;
/* convert secs since midnight/4 to hours, minutes, and seconds */
i = (ptime & 0xffff) * 4;
tms.tm_hour = i/3600;
tms.tm_min = (i%3600)/60;
tms.tm_sec = i%60;
tms.tm_isdst = 1; /* use current timezone's DST flag? */
unixtime = mktime(&tms);
if (unixtime == -1) {
fprintf(stderr,"Unable to convert Prime timestamp:\n");
fprintf(stderr," year=%d, mon=%d, day=%d, hour=%d, min=%d, sec=%d\n", tms.tm_year, tms.tm_mon, tms.tm_mday, tms.tm_hour, tms.tm_min, tms.tm_sec);
}
return unixtime;
}
/* read a short (16-bit) integer in big-endian format into native format */
unsigned short readshort () {
return getchar()<<8 | getchar();
}
/* read a long (32-bit) integer in big-endian format into native format */
int readlong () {
int n,ch;
return getchar()<<24 | getchar()<<16 | getchar()<<8 | getchar();
}
main (int argc, char** argv) {
int verbose; /* verbose level */
int nowrite; /* true if indexing only */
int binary; /* true = don't translate text files */
int text; /* true = only restore text files */
int drb; /* true if this is a drb save */
int firstrec; /* true if first record */
int fp; /* current record's file position */
int logrecno; /* Magsav logical record number */
int explogrecno; /* expected logical record number (Magsav) */
int expblockno; /* expected block number (drb) */
int nwords;
int i,ch;
int recid; /* record id */
int wordsleft; /* number of words left in current record */
int tapeformat; /* pre-19, 19 w/o ACLS, 19 w/ACLS, etc. */
int objtype; /* drb object type */
int lrecversion; /* drb logical record version */
int filetype = -1; /* Primos file type being restored */
unsigned int dtm,dta,dtc,lsrdtm; /* date modified, accessed, created, parent dtm */
int fd; /* Unix fd for writing output files */
int nwritten;
int ecwskip; /* words to skip at end of entry */
int skipping; /* true if skipping the current object */
int textfile; /* true if converting a text file */
int textstate; /* tracks text file compression state */
int maxentries; /* maximum entries in a segdir */
int segentry; /* this file's entry in a segdir */
int insegdir; /* true if inside a segdir */
unsigned char path[4096]; /* object pathname */
struct { /* directory stack info */
char path[4096]; /* pathname */
unsigned int dta; /* access time */
unsigned int dtm; /* modification time */
} dirstack[64];
int dirlevel; /* last entry in dirstack, -1 if empty */
unsigned char *p;
unsigned char buf[16*2048]; /* tape buffer (limit of 16K words on Prime) */
struct utimbuf ut;
int reel; /* reel number (old magsav) */
short bootskiprecno, bootskipreclen, bootskiprecid;
drb = 0; /* assume it's an old magsav tape initially */
skipping = 1; /* might allow us to correctly start w/reel 2 */
fd = -1; /* make sure it doesn't look like a file is open */
wordsleft = 0; /* words left in this logical record */
verbose = 0;
nowrite = 0;
binary = 0;
text = 0;
/* any args? */
for (i=1; i<argc; i++) {
if (strcmp(argv[i],"-vv") == 0)
verbose = 2;
else if (strcmp(argv[i],"-v") == 0)
verbose = 1;
else if (strcmp(argv[i],"-nw") == 0)
nowrite = 1;
else if (strcmp(argv[i],"-binary") == 0)
binary = 1;
else if (strcmp(argv[i],"-text") == 0)
text = 1;
else if (strcmp(argv[i],"-h") == 0 || strcmp(argv[i],"-help") == 0) {
fprintf(stdout, "Usage: magrst [-v] [-vv] [-nw] [-binary] [-text]\n");
fprintf(stdout, "Reads old and new style magrst data from stdin.\n");
exit(0);
}
}
explogrecno = 1;
expblockno = 0;
while (1) {
/* if too many words were consumed, it's an error; if not enough
were consumed, skip them now. This is at the top of the while
loop so that continues can be used inside the loop to skip stuff. */
if (wordsleft < 0) {
fprintf(stderr, "Tape parse error at position %d, recid = %d, wordsleft=%d\n", ftell(stdin), recid, wordsleft);
exit(1);
} else if (wordsleft > 0) {
if (verbose >= 2)
fprintf(stderr, "WARNING: %d words left unread at position %d\n", wordsleft, ftell(stdin));
if (fread(buf, wordsleft*2, 1, stdin) != 1) {
fprintf(stderr, "fread read error at position %d\n", fp);
exit(1);
}
}
wordsleft = 0;
/* now we're at some kind of record header, in theory */
fp = ftell(stdin); /* might give -1 on some OS's */
/* read the tape header; for a newer-format (drb) tape, the first
4 bytes are "LSR " */
if (fread(buf, 4, 1, stdin) != 1)
if (feof(stdin)) {
if (verbose >= 1) fprintf(stderr,"End of file at position %d\n", fp);
exit(0);
} else {
fprintf(stderr, "Error reading header at position %d\n", fp);
exit(1);
}
buf[4]=0;
/* some drb tapes have ANSI labels; skip them */
if (strcmp(buf,"VOL1") == 0) {
drb = 1;
expblockno++;
if (verbose >= 1)
fprintf(stderr,"Skipping %s label record at position %d\n", buf, fp);
fread(buf,76,1,stdin);
/* try to find start of VOL2 record. This is the way we bypass
the boot program, which immediately follows VOL1 if a drb tape has
a boot program. Assumes the string VOL2 doesn't occur in the boot
program. */
strcpy(buf,"VOL2");
i=0;
while(i < 4) {
ch = getchar();
if (ch == buf[i])
i++;
else
i = 0;
}
}
if (strncmp(buf,"VOL",3) == 0 || strncmp(buf,"UVL",3) == 0 || strncmp(buf,"HDR",3) == 0 || strncmp(buf,"UHL",3) == 0 || strncmp(buf,"EOF",3) == 0 || strncmp(buf,"EOV",3) == 0 || strncmp(buf,"UTL",3) == 0 ) {
drb = 1;
expblockno++;
if (verbose >= 1)
fprintf(stderr,"Skipping %s label record at position %d\n", buf, fp);
fread(buf,76,1,stdin);
continue;
}
/* "LSR " header might be in either Prime ASCII or standard ASCII */
if (strcmp(buf,"\314\323\322\240") == 0 || strcmp(buf,"LSR ") == 0) {
drb = 1;
if (verbose >= 2) fprintf(stderr, "drb: POS %d, ", fp);
i = readshort();
if (i != 1) {
fprintf(stderr, "Logical tape version is %d, but expected 1\n", i);
exit(1);
}
i = readshort();
if (verbose >= 2) fprintf(stderr, "save %d, ", i);
i = readlong();
if (verbose >= 2) lsrdtm = ptimestampu(i);
i = readlong();
if (verbose >= 2) fprintf(stderr, "block #%d, ", i);
if (i != expblockno)
fprintf(stderr," ************ EXPECTED BLOCK %d, READ BLOCK %d\n", expblockno, i);
expblockno = i+1;
i = readshort();
if (verbose >= 2) fprintf(stderr, "size %d, ", i);
wordsleft = i-10;
i = readshort();
if (verbose >= 2) fprintf(stderr, "lrecs %d, ", i);
if (verbose >= 2) fprintf(stderr, "%s", ctime((time_t *)&lsrdtm));
/* read the logical record header following LSR, then fall through */
if (fread(buf, 4, 1, stdin) != 1) {
fprintf(stderr, "Error reading header following LSR at position %d\n", fp);
exit(1);
}
}
/* every drb logical record has a 4-byte header:
byte 1: record id
byte 2: version
bytes 3-4: record length, including the 4-byte header
old magsav logical records have a 3-word header:
word 1: logical record number in logical tape, starting with 1
word 2: record length, including the 3-word header
word 3: record id
*/
/* if this is an old magsav record, get the 3-word header.
Otherwise it's a drb record with a 2-word header. Either way, the
record size is always in word 2 */
nwords = buf[2]<<8 | buf[3];
if (nwords > 16*1024) {
fprintf(stderr, "Record size is %d words at position %d\n", nwords, fp);
exit(1);
}
if (drb) {
recid = buf[0];
lrecversion = buf[1];
if (lrecversion != 1 && lrecversion != 128) {
fprintf(stderr, "Logical record version is %d, but expected 1 at position %d\n", lrecversion, fp);
exit(1);
}
if (verbose >= 2)
fprintf(stderr,"drb: recid=%d, lrecversion=%d, nwords=%d\n", recid, lrecversion, nwords);
wordsleft = nwords-2;
} else {
logrecno = buf[0]<<8 | buf[1];
recid = -readshort(); /* NOTE: make magsav recid's negative! */
bootskipdone:
wordsleft = nwords-3;
if (verbose >= 2)
fprintf(stderr,"magrst: recno %d, %d words, recid %d\n", logrecno, nwords, recid);
if (logrecno != explogrecno) {
fprintf(stderr,"********** magrst: expected logrec %d, saw %d\n", explogrecno, logrecno);
explogrecno = logrecno;
}
explogrecno++;
}
/* if there is a file open and this isn't a continuation of it, close
the file now, set the file timestamp */
if (fd >= 0 && recid != MS_DATA && recid != DRB_FILE_DATA) {
if (fd > 0)
close(fd);
fd = -1;
/* now go back up the directory stack and set dtm/dta in reverse
order */
for (i = dirlevel; i>= 0; i--) {
ut.actime = ptimestampu(dirstack[i].dta);
ut.modtime = ptimestampu(dirstack[i].dtm);
if (ut.modtime >= 0) {
if (verbose >= 2)
fprintf(stderr,"Setting timestamp on %s to %d\n", dirstack[i].path, dirstack[i].dtm);
if (ut.actime < 0)
ut.actime = ut.modtime;
if (utime(dirstack[i].path, &ut) == -1) {
fprintf(stderr,"Error setting timestamp for %s:", dirstack[i].path);
perror(NULL);
}
}
}
}
if (recid == MS_START_LOG_TAPE) {
fprintf(stderr,"\nStart of logical tape at position %d\n", fp);
/* word 4: tape format */
tapeformat = readshort(); wordsleft--;
if (tapeformat == 0x8000)
fprintf(stderr,"Tape format: pre rev 19\n");
else if (tapeformat == 0xC000)
fprintf(stderr,"Tape format: rev 19 w/o ACLs\n");
else if (tapeformat == 0xE000)
fprintf(stderr,"Tape format: rev 19 with ACLs\n");
else if (tapeformat == 0xD000)
fprintf(stderr,"Tape format: rev 20 w/o ACLs\n");
else if (tapeformat == 0xF000)
fprintf(stderr,"Tape format: rev 20 with ACLs\n");
else if (tapeformat == 0xA000)
fprintf(stderr,"Tape format: rev 22 w/o ACLs\n");
else if (tapeformat == 0xB000)
fprintf(stderr,"Tape format: rev 22 with ACLs\n");
else
fprintf(stderr,"Tape format: 0x%x\n", tapeformat);
/* words 5-7: date as MMDDYY */
if (fread(buf, 6, 1, stdin) != 1) {
fprintf(stderr, "error reading date at position %d\n", fp);
exit(1);
}
buf[6] = 0;
pasciiu(buf,6);
wordsleft -= 3;
fprintf(stderr, "Date: %6s\n", buf);
/* word 8: user version */
i = readshort(); wordsleft--;
fprintf(stderr, "User version: %d\n", i);
/* word 9: reel number */
reel = readshort(); wordsleft--; /* reel number */
fprintf(stderr, "Reel: %d\n", reel);
/* words 10-12: tape name */
if (fread(buf, 6, 1, stdin) != 1) {
fprintf(stderr, "error reading tape name at position %d\n", fp);
exit(1);
}
buf[6] = 0;
wordsleft -= 3;
pasciiu(buf,6);
fprintf(stderr, "Tape name: %6s\n", buf);
/* the "start logical tape" record length doesn't include the size
of the boot program, if present. Magsav adds the boot program
to the first record on reel 1 of a backup. AFAIK, it doesn't
write the boot record on reel 2 of a continued backup. At rev
19 and 20, it appears to add the boot record to every logical
tape header, even if it isn't the first one on the tape.
If the boot program is present, the first record will be 1024
bytes for rev 19 and earlier tapes. Later revs of magsav
store much longer boots - for example, rev 20.2 stores over
4K bytes in the first tape record. Without record markers, it's
not possible to tell how long the physical tape record is, so
we need to add some code to heuristically determine the
length of the boot program by looking for logical record 1's
header in the data stream.
*/
if (reel == 1) {
if (verbose >= 1)
fprintf(stderr, "Skipping boot at BOT, nwords=%d...\n", nwords);
/* the first record is at least 1024 bytes (512 words). The
start logical tape header size (nwords) doesn't include the
boot, so skip 512-nwords first */
#if 0
if (fread(buf, 512-nwords, 2, stdin) != 512-nwords) {
perror("Error skipping boot");
exit(1);
}
#endif
wordsleft = 4096;
/* look for logical record 1, record length n (usually 27),
and record id MS_NAME (remember, we negate old magsav
recid's to distinguish them from drb */
while (1) {
bootskiprecno = readshort(); wordsleft--;
if (bootskiprecno != 1)
continue;
skipcont:
bootskipreclen = readshort(); wordsleft--;
if (bootskipreclen < 1) /* not part of header */
continue;
/* rec lengths must be 3 words or greater, but if it's
a 1, it might be the recno, so backup */
if (bootskipreclen == 1) {
bootskiprecno = bootskipreclen;
goto skipcont;
}
#if 1
/* might want to add this later as an additional check;
for example, a reclen of 4 would cause this loop to
exit, but it's probably not logical record 1 because
MS_NAME records have more data than this */
if (bootskipreclen < 27 || bootskipreclen > 100)
continue;
#endif
bootskiprecid = readshort(); wordsleft--;
if (-bootskiprecid == MS_NAME) {
explogrecno = 1;
logrecno = 1;
recid = MS_NAME;
nwords = bootskipreclen;
goto bootskipdone;
}
/* this word might be the start of logical record 1 */
if (bootskiprecid == 1) {
bootskiprecno = bootskiprecid;
goto skipcont;
}
}
fprintf(stderr,"Unable to find logical reccord 1\n");
exit(1);
}
} else if (recid == MS_NAME || recid == DRB_START_OBJ) {
skipping = 0;
insegdir = 0;
if (recid == DRB_START_OBJ) {
/* drb stores all possible attributes in the same record layout,
even though most attributes only apply to certain object types */
objtype = readshort();
if (verbose >= 2)
fprintf(stderr,"Start object type %d at position %d\n", objtype, fp);
i = readlong(); /* dtm */
if (objtype == DRB_DIR_OBJ || objtype == DRB_FILE_OBJ || objtype == DRB_SEGDIR_OBJ)
dtm = i;
if (objtype == DRB_SEG_SUBFILE_OBJ || objtype == DRB_SEG_SUBDIR_OBJ)
insegdir = 1;
i = readlong(); /* dtc */
i = readlong(); /* dta */
if (objtype == DRB_DIR_OBJ || objtype == DRB_FILE_OBJ || objtype == DRB_SEGDIR_OBJ)
dta = i;
i = readshort(); /* file protection */
i = readlong(); /* segdir entry class */
i = readshort(); /* file object file type: 1=SAM, 2=DAM, 3=CAM */
filetype = 0; /* assume it's a regular SAM file for now */
i = readshort(); /* CAM extent info */
i = readlong(); /* max quota (dir) */
if (fread(buf, 12, 1, stdin) != 1) { /* owner & non-owner pass */
fprintf(stderr, "error reading owner/non-owner at %d\n", fp);
exit(1);
}
i = readshort(); /* dir type, 1=PW, 2=ACL (dir) */
if (objtype == DRB_DIR_OBJ) /* use filesystem & old magsav types */
if (i == 1)
filetype = 4;
else if (i == 2)
filetype = 5;
else {
fprintf(stderr, "Skipping drb dir type %d at position %d\n", i, fp);
skipping = 1;
}
i = readshort(); /* file bits (rwlock, etc.) */
i = readshort(); /* roam segdir type (roam) */
i = readshort(); /* regular segdir type, 1=SAM, 2=DAM (segdir) */
if (objtype == DRB_SEGDIR_OBJ)
if (i == 1)
filetype = 2;
else if (i == 2)
filetype = 3;
else {
fprintf(stderr, "Skipping drb segdir type %d at position %d\n", i, fp);
skipping = 1;
}
maxentries = readlong(); /* max entries (segdir) */
i = readshort(); /* dummy */
i = readshort(); /* object level */
i = readshort(); /* 0=regular, 1=roam */
i = readshort(); /* seg level: 1=subfile, 2=file under seg subdir */
i = readshort(); /* object name length in bytes */
if (i < 1 || i > 32) {
fprintf(stderr, "object length = %d at %d\n", i, fp);
exit(1);
}
if (fread(buf, 32, 1, stdin) != 1) { /* object name */
fprintf(stderr, "error reading object name at %d\n", fp);
exit(1);
}
buf[i] = 0;
i = readshort(); /* object pathname length (bytes) */
wordsleft -= 48;
if (fread(path, (i+1)/2*2, 1, stdin) != 1) { /* object name */
fprintf(stderr, "error reading object name at %d\n", fp);
exit(1);
}
wordsleft -= (i+1)/2;
path[i] = 0;
} else if (recid == MS_NAME) {
p = path; /* pointer to accumulate pathname */
while (wordsleft > 0) {
#if 0
if (wordsleft % 24 != 0) {
fprintf(stderr, "name parse error, position %d, wordsleft=%d\n", fp, wordsleft);
exit(1);
}
#endif
/* for a segdir, entries look like this:
word 1: type of subfile (parent or this file?)
word 2: zero
word 3: segdir subfile entry number
word 4: protection (?)
words 5-19: unused
word 20: subfile type (parent or this file?)
words 21-24: unused
NOTE: Segment directories can be nested. Need a test tape
to implement the restore for these.
For non-segdirs, entries look like this:
word 1: entry control word (right byte = length in words)
words 2-17: filename
word 18: owner/non-owner protection bits
word 19: acl protection; bit 1 set if non-default ACL protection
word 20: file type:
left byte:
:100000 = special file (BOOT/DSKRAT)
:040000 = clear if file has been changed since last backup
:020000 = set if modified by Primos II (timestamp is inaccurate)
:010000 = set for special BOOT, MFD, BADSPT, and DSKRAT files
bits 5-6 are the file's read/write lock:
00 = use system rwlock (dflt)
01 = N readers or 1 writer (excl)
10 = N readers and 1 writer (updt)
11 = N readers and N writers (none)
NOTE: UFD's don't have rwlocks; segdirs do.
Segment subfiles have the same rwlock as the (top-level?) segdir
right byte:
0 = SAM (sequential) file
1 = DAM (direct access) file
2 = SEGSAM (sequential segment directory)
3 = SEGDAM (direct seqment directory)
4 = password directory
5 = ACL directory
6 = category ACL
7 = CAM (contiguous) file
*/
/* ecw */
i = readshort(); wordsleft--;
if (i & 0xFF < 24) {
fprintf(stderr, "ecw = %d/%d (size != 24) at position %d\n", i>>8, i&0xff, fp);
exit(1);
}
ecwskip = (i & 0xFF) - 22;
ecwskip = 2; /* only skip words 23 & 24? */
/* words 2-17: filename (regular entry) */
if (fread(p, 32, 1, stdin) != 1) {
fprintf(stderr, "error reading file name at position %d\n", fp);
exit(1);
}
wordsleft -= 16;
/* for segment subfiles (word 2 is zero), add the entry number */
if (*p == 0 && *(p+1) == 0) {
segentry = *(p+2)<<8 | *(p+3);
sprintf(p, "%d/", segentry);
p = path + strlen(path);
insegdir = 1;
} else {
for (i=0; i<32; i++) { /* find the end of the filename */
if (*p == 0240) { /* space w/parity */
break;
}
p++;
}
*p++ = '>';
*p = 0;
}
/* word 18: owner/non-owner protection bits */
i = readshort(); wordsleft--;
/* word 19: acl protection; bit 1 set if non-default ACL */
i = readshort(); wordsleft--;
filetype = readshort(); wordsleft--;
filetype &= 0xff;
/* word 21-22: date/time modified */
dta = 0;
dtm = readlong(); wordsleft -= 2;
/* words 23 to (ecw length) are dummy */
while (ecwskip > 0) {
i = readshort();
wordsleft--;
ecwskip--;
}
}
*(--p) = 0;
}
/* pathname postprocessing: strip parity, change > to /, change / to S */
for (p=path; *p; p++) {
*p = *p & 0x7f;
if ('A' <= *p && *p <= 'Z') /* lowercase the name for Unix */
*p = *p+('a'-'A');
if (*p == '/')
*p = 'S';
if (*p == '>')
*p = '/';
}
/* if path starts with <DISKNAME>BLAH, strip the leading < */
if (path[0] == '<')
strcpy(path,path+1);
fprintf(stderr,"%s\n", path);
if (fd > 0) {
fprintf(stderr,"fd should be zero or -1??\n");
exit(1);
}
/* create the parent directories */
if (nowrite == 0) {
dirlevel = -1;
for (p=path; *p != 0; p++)
if (*p == '/') {
*p = 0;
if (mkdir(path, 0755) == -1 && errno != EEXIST) {
fprintf(stderr,"Creating directory %s\n", path);
perror(" Error is");
exit(1);
}
dirlevel++;
strcpy(dirstack[dirlevel].path, path);
*p = '/';
}
dirlevel++;
strcpy(dirstack[dirlevel].path, path);
dirstack[dirlevel].dtm = dtm;
dirstack[dirlevel].dta = dta;
//fprintf(stderr,"Saved dtm for %s as %d\n", dirstack[dirlevel].path, dirstack[dirlevel].dtm);
}
} else if (recid == MS_DATA && (filetype == 4 || filetype == 5)) {
/* Magsav data record following a password or ACL directory:
word 1: always 8
words 2-4: owner password
words 5-7: non-owner password
word 8: always zero
word 9: zero for pre-quota saves (and last word)
words 9-10: "QT$$" for quota saves
word 11: 0 if quota directory, 1 if non-quota
words 12-19: q$read return array; word 14 is max quota
*/
i = readshort(); wordsleft--;
if (i != 8) {
fprintf(stderr,"Expected 8 but got %d for ufd at position %d\n", i, fp);
exit(1);
}
} else if (recid == MS_DATA && (filetype == 2 || filetype == 3)) {
/* Magsav data record following a segment directory:
word 1: maximum entries in the segdir */
maxentries = readshort(); wordsleft--;
#if 0
fprintf(stderr,"Segdir %s, maxentries=%d\n", path, maxentries);
#endif
/* below occurs when reel 2 starts with file data and is restored alone */
} else if ((recid == MS_DATA || recid == DRB_FILE_DATA) && filetype == -1) {
fprintf(stderr,"Skipping file data at position %d\n", fp);
} else if ((recid == MS_DATA || recid == DRB_FILE_DATA) && (filetype == 0 || filetype == 1)) { /* SAM or DAM data */
if (drb) { /* read file data size in bytes */
i = readlong(); wordsleft -= 2;
if (verbose >= 2)
fprintf(stderr, "File data size = %d bytes\n", i);
}
if (fread(buf, wordsleft*2, 1, stdin) != 1) {
fprintf(stderr, "error reading file data at position %d\n", fp);
exit(1);
}
if (nowrite == 0) {
if (fd < 0) { /* first data record; open output file */
textstate = 0;
if (!binary && !insegdir && isptext(path,filetype,buf,wordsleft*2))
textfile = 1;
else
textfile = 0;
/* open the file for writing (should check for overwrite) */
fd = creat(path, 0644);
if (fd == -1) {
fprintf(stderr,"Opening file %s\n", path);
perror(" Error is");
}
if (!textfile && text) { /* don't restore binary files */
close(fd);
fd = 0;
}
}
if (textfile)
nwritten = convtext(fd, buf, 2*wordsleft, &textstate);
else if (fd > 0) {
nwritten = write(fd, buf, 2*wordsleft);
if (nwritten != 2*wordsleft) {
fprintf(stderr,"Writing file %s\n", path);
perror(" Error is");
}
}
}
wordsleft = 0;
} else if (recid == DRB_FILE_DATA) {
i = readlong(); wordsleft -= 2;
fprintf(stderr,"Unrecognized drb filetype = %d ignored\n", filetype);
} else if (recid == DRB_END_OBJ) {
i = readshort(); wordsleft--;
if (i == 0x8000)
fprintf(stderr, "WARNING: File may be incomplete (open?), status = 0x%04x\n", i);
else if (i & 0xF800) {
fprintf(stderr, "WARNING: File save status = 0x%04x\n", i);
}
} else if (recid == MS_END_LOG_TAPE || recid == DRB_END_LOG_TAPE) {
fprintf(stderr,"End of logical tape at position %d\n", fp);
#if 0
exit(0);
#endif
}
}
}

396
util/magsav.c Normal file
View File

@ -0,0 +1,396 @@
/* magsav.c, Jim Wilcoxson, February 23, 2007
Unix version of magsav, to create Prime tape files from Unix file systems.
This program creates old Magsav format tapes, because they're simpler.
Structure of an old Magsav tape:
1. Every record has a 3-word header
word 1: logical record number in logical tape, starting with 1
word 2: record length, including the 3-word header
word 3: record id:
4 = start logical tape
2 = name record (block of ufd entries)
1 = data record
5 = end logical tape
1. start logical tape record:
word 4: tape format (see magrst; we use 0xC000: rev 19 w/o ACLS)
word 5-7: MMDDYY date (text)
word 8: user version number (short int)
word 9: reel
word 10-12: tape name (text)
2. tape file mark
3. Name record, data record(s), Name record, data record(s), ...
4. end logical tape record
5. 2 tape file marks
*/
#define TAP_FILE_MARK 0
#define MS_DATA 1
#define MS_NAME 2
#define MS_START_LOG_TAPE 4
#define MS_END_LOG_TAPE 5
#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h> /* mkdir */
#include <sys/types.h> /* mkdir */
#include <errno.h>
#include <time.h> /* mktime */
#include <utime.h> /* utimes */
#include <dirent.h>
#include "istext.h"
#define MAXDIRENTRIES 32
#define MAXEXTENSION 10
static struct {
char ext[MAXEXTENSION];
int ftype;
} exttype[] = {
{".como", 1},
{".run", 1},
};
#define EXTENTRIES sizeof(exttype)/sizeof(exttype[0])
/* global vars */
FILE *outfile;
int verbose = 0;
unsigned short lrecl;
struct {
unsigned short ibuf[24];
} ds[MAXDIRENTRIES];
int dl = -1; /* # of entries in ds; -1=empty */
helpexit() {
fprintf(stderr, "Usage: magsav [-v] [-vv] -f <output file> [file1] [file2] ...\n");
fprintf(stderr, "Saves file1-filen in Prime MAGSAV format to output file.\n");
exit(0);
}
/* this function takes a Unix timestamp and converts it to a Prime
filesystem timestamp (a 32-bit integer).
Format of a Prime FS timestamp is:
left 16 bits: YYYYYYYMMMMDDDDD, year is mod 100
right 16 bits: seconds since midnight divided by 4, ie, 0-21599
*/
unsigned int utimestampp(time_t unixtime) {
int i;
unsigned short pdate;
unsigned short ptime;
struct tm *tms;
/* break down Unix timestamp */
tms = localtime(&unixtime);
i = tms->tm_year;
if (i > 127)
i = 127; /* dont' let it overflow! */
pdate = (tms->tm_year<<9) | ((tms->tm_mon+1)<<5) | tms->tm_mday;
ptime = (tms->tm_hour*3600 + tms->tm_min*60 + tms->tm_sec)/4;
return (pdate<<16) | ptime;
}
/* write a Magsav tape record in .TAP format:
- 4 bytes for .TAP format record length (bytes following this)
- 3 word (6 byte) Magsav tape record header
- Magsav record
- 4 bytes for .TAP format ending record length
*/
mtwrite (int recid, unsigned short *ibuf, int nw) {
int tapbytes;
unsigned char tapbuf[4];
short mshdr[3];
if (recid == TAP_FILE_MARK)
tapbytes = 0;
else
tapbytes = (nw+3)*2;
tapbuf[0] = tapbytes & 0xFF;
tapbuf[1] = (tapbytes>>8) & 0xFF;
tapbuf[2] = (tapbytes>>16) & 0xFF;
tapbuf[3] = (tapbytes>>24) & 0xFF;
if (fwrite(tapbuf, 1, 4, outfile) != 4) {
perror("Writing .TAP leading record length");
exit(1);
}
if (recid != TAP_FILE_MARK) {
mshdr[0] = htons(lrecl);
mshdr[1] = htons(nw+3);
mshdr[2] = htons(recid);
if (fwrite(mshdr, 2, 3, outfile) != 3) {
perror("Writing MAGSAV record header");
exit(1);
}
if (fwrite(ibuf, 2, nw, outfile) != nw) {
perror("Writing MAGSAV record buffer");
exit(1);
}
if (fwrite(tapbuf, 1, 4, outfile) != 4) {
perror("Writing .TAP trailing record length");
exit(1);
}
}
lrecl++;
}
savename() {
mtwrite(MS_NAME, (unsigned short *)&ds, (dl+1)*24);
}
pushname(char *name, struct stat sb) {
int i,j;
char *p, ch;
int filetype;
unsigned char extension[8];
dl++;
if (dl > MAXDIRENTRIES) {
fprintf(stderr, "directory stack overflowflow!\n");
exit(1);
}
if ((sb.st_mode & S_IFMT) == S_IFDIR)
filetype = 5; /* ACL directory type */
else {
/* scan backward to get file extension */
filetype = 0; /* assume SAM file type */
extension[0] = 0;
for (i=strlen(name)-1; i >= 0; i--)
if (name[i] == '.') {
strncpy(extension, name+i, sizeof(extension)-1);
break;
}
if (extension[0] == '.')
for (i=0; i < EXTENTRIES; i++)
if (strcasecmp(extension, exttype[i].ext) == 0)
filetype = exttype[i].ftype;
}
/* ECW: left byte=dir entry type, right byte = size in words, incl. ECW
Directory entry types:
0 = old directory header
1 = directory header
2 = vacant entry
3 = file entry
4 = access category
5 = ACL
6 = directory index block
*/
ds[dl].ibuf[0] = htons((3<<8) | 24);
p = (char *)(ds[dl].ibuf+1); /* point to name part */
j = 0;
for (i=0; i<32; i++) { /* copy name, upcase, fix parity */
ch = name[j++];
if (ch == 0)
ch = ' ';
else if (ch == 'S')
ch = '/';
else if ('a' <= ch && ch <= 'z')
ch = ch - 'a' + 'A';
*p++ = ch | 0x80;
}
ds[dl].ibuf[17] = htons(0xFF00); /* owner/non-owner protection */
ds[dl].ibuf[18] = 0; /* ACL protection */
ds[dl].ibuf[19] = htons(filetype);
*(int *)(ds[dl].ibuf+20) = htonl(utimestampp(sb.st_mtime));
ds[dl].ibuf[22] = 0; /* reserved */
ds[dl].ibuf[23] = 0;
}
popname() {
if (dl >= 0)
dl--;
else {
fprintf(stderr, "directory stack underflow!\n");
exit(1);
}
}
savefile(char *path, char *name, struct stat sb) {
char buf[4090];
int i, n, fd, nw;
int first, text;
utextp_t state;
if ((fd=open(path, O_RDONLY)) == -1) {
perror(NULL);
return;
}
pushname(name, sb);
savename();
popname();
state.oddbyte = 0;
state.col = 0;
state.spaces = 0;
first = 1;
while ((n=read(fd, buf, sizeof(buf))) > 0) {
if (first)
text = isutext(path, buf, n);
if (n & 1) {
fprintf(stderr,"Warning: odd-length file padded with newline or zero\n");
if (text)
buf[n++] = '\n';
else
buf[n++] = 0;
}
if (text) {
}
nw = (n+1)/2;
mtwrite(MS_DATA, (unsigned short *)buf, nw);
}
close(fd);
}
savedir(char *path, char *name, struct stat sb) {
unsigned short ibuf[9];
DIR *dp;
struct dirent *de;
int i;
if ((dp=opendir(path)) == NULL) {
perror(NULL);
return;
}
pushname(name, sb);
savename();
/* write MAGSAV directory data record */
ibuf[0] = 8;
for (i=1; i<3; i++)
ibuf[i] = ((' '<<8) | ' ') | 0x8080; /* owner pw */
ibuf[4] = ibuf[5] = ibuf[6] = 0; /* non-owner password */
ibuf[7] = 0;
ibuf[8] = 0;
mtwrite(MS_DATA, ibuf, 9);
/* write all directory entries */
while ((de=readdir(dp)) != NULL) {
if (strcmp(de->d_name,".") == 0 || strcmp(de->d_name,"..") == 0)
continue;
save(path, de->d_name);
}
/* all done */
closedir(dp);
popname();
}
save(char *parent, char *name) {
char path[1024];
struct stat sb;
path[0] = 0;
strcpy(path, parent);
strcat(path, "/");
strcat(path, name);
printf("%s\n", path);
if (stat(path, &sb) == -1) {
perror("Stat error");
return;
}
if ((sb.st_mode & S_IFMT) == S_IFDIR)
savedir(path, name, sb);
else if ((sb.st_mode & S_IFMT) == S_IFREG)
savefile(path, name, sb);
else
fprintf(stderr, "Ignored %s: can't save this file type\n", name);
}
main (int argc, char** argv) {
int argx;
union {
unsigned short i[16];
unsigned char c[32];
} buf;
int i;
/* init */
outfile = NULL;
/* any args? */
for (argx=1; argx<argc; argx++) {
if (strcmp(argv[argx],"-vv") == 0)
verbose = 2;
else if (strcmp(argv[argx],"-v") == 0)
verbose = 1;
else if (strcmp(argv[argx],"-f") == 0) {
argx++;
if (argx < argc) {
if ((outfile=fopen(argv[argx], "w")) == NULL) {
perror("Unable to open output file");
exit(1);
}
} else {
fprintf(stderr, "No output file following -f\n");
helpexit();
}
} else if (argv[argx][0] == '-')
helpexit();
else
break;
}
/* if no output file specified, give help */
if (!outfile)
helpexit();
/* write "start logical tape" record */
lrecl = 1;
buf.i[0] = htons(0xC000);
strcpy(buf.c+2, "010199"); uasciip(buf.c+2, 6);
buf.i[4] = 0;
buf.i[5] = htons(1);
strcpy(buf.c+12, "MAGSAV"); uasciip(buf.c+12, 6);
mtwrite(MS_START_LOG_TAPE, buf.i, 9);
mtwrite(TAP_FILE_MARK, 0, 0);
/* write files */
lrecl = 1;
for (;argx < argc; argx++) {
save(".", argv[argx]);
}
/* end logical tape */
mtwrite(MS_END_LOG_TAPE, 0, 0);
mtwrite(TAP_FILE_MARK, 0, 0);
mtwrite(TAP_FILE_MARK, 0, 0);
fclose(outfile);
}

12
util/makefile Normal file
View File

@ -0,0 +1,12 @@
magrst: # Unix version of Prime's magrst
rm -rf magrst.o
cc -o magrst magrst.c istext.c
magsav: # Unix version of Prime's magsav
rm -rf magsav.o
cc -o magsav magsav.c istext.c

155
util/mtread.c Normal file
View File

@ -0,0 +1,155 @@
/* mtread.c, Jim Wilcoxson, February 4, 2007
Reads a magtape connected to a Linux system and writes records in .TAP
format. The entire tape is read until:
- a read error occurs
- the EOT is encountered
- two file marks are read (logical EOT)
*/
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mtio.h>
main (int argc, char **argv) {
char *outfile;
int tapefd, fdout;
struct mtop mt_cmd;
struct mtget mt_status;
struct mtpos mt_pos;
int filenr, relrecnr;
int n,n2;
int outpos;
int mark;
char buf[20000];
if (argc < 2) {
printf("Usage: mtread <output file>\n");
exit(1);
}
outpos = 0;
filenr = 1;
relrecnr = 0;
mark = 0;
outfile = argv[1];
if ((fdout=open(outfile, O_WRONLY+O_CREAT+O_EXCL, 0770)) == -1) {
perror("Error opening output file");
exit(1);
}
if ((tapefd=open("/dev/st0", O_RDONLY)) == -1) {
perror("Error opening tape drive");
goto done;
}
#if 0
/* set tape to do fast EOM. This can be useful if a tape is "loose"
on the reel, to tighten up the tape */
mt_cmd.mt_op = MTSETDRVBUFFER;
mt_cmd.mt_count = MT_ST_SETBOOLEANS | MT_ST_FAST_MTEOM;
if (ioctl(tapefd, MTIOCTOP, &mt_cmd) != 0) {
perror("Error setting fast EOM");
goto done;
}
/* skip to EOM */
mt_cmd.mt_op = MTEOM;
mt_cmd.mt_count = 0;
if (ioctl(tapefd, MTIOCTOP, &mt_cmd) != 0) {
perror("Error skipping to EOM");
goto done;
}
#endif
/* rewind tape */
#if 1
mt_cmd.mt_op = MTREW;
mt_cmd.mt_count = 0;
if (ioctl(tapefd, MTIOCTOP, &mt_cmd) != 0) {
perror("Error rewinding");
goto done;
}
#endif
/* set tape blocksize to 0 */
mt_cmd.mt_op = MTSETDRVBUFFER;
mt_cmd.mt_count = 0;
if (ioctl(tapefd, MTIOCTOP, &mt_cmd) != 0) {
perror("Error setting variable blocksize");
goto done;
}
while(1) {
n=read(tapefd, buf+4, sizeof(buf)-8);
relrecnr++;
if (n == -1) {
#if 1
if (mark == 2) {
printf("Read error after double tape mark, assuming tape EOF\n");
goto done;
}
#endif
perror("Read error");
printf("File %d, record %d: read error\n", filenr, relrecnr);
*(int *)buf = 0;
buf[3] = 0x80;
if (write(fdout, buf, 4) != 4) {
perror("Error writing error mark");
goto done;
}
#if 0
goto done;
#else
printf("Hit Enter to continue...");
getchar();
#endif
} else if (n == 0) {
printf("File mark %d at outpos %d\n", filenr, outpos);
*(int *)buf = 0;
if (write(fdout, buf, 4) != 4) {
perror("Error writing file mark");
goto done;
}
mark += 1;
filenr++;
relrecnr = 0;
} else {
*(int *)buf = n;
*(int *)(buf+n+4) = n;
mark = 0;
#if 0
printf("File %d record %d outpos %d size %d\n", filenr, relrecnr, outpos, n);
#endif
if ((n2=write(fdout, buf, n+8)) != n+8) {
if (n2 == -1) {
perror("Error writing output file");
goto done;
}
printf("Error writing output file, read %d, wrote %d\n", n, n2);
goto done;
}
outpos += n+8;
}
}
done:
if (outpos == 0)
unlink(outfile);
sleep(2);
mt_cmd.mt_op = MTREW;
mt_cmd.mt_count = 0;
if (ioctl(tapefd, MTIOCTOP, &mt_cmd) != 0) {
perror("Error rewinding");
}
close(tapefd);
exit(1);
}

145
util/mtwrite.c Normal file
View File

@ -0,0 +1,145 @@
/* mtwrite.c, Jim Wilcoxson, July 14, 2011
Writes a .TAP disk file to a magtape
*/
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mtio.h>
#define BUFSIZE 20000
/* in the tap format, record lengths are always stored little-endian */
int readint_le () {
int n,ch;
n = getchar() | getchar()<<8 | getchar()<<16 | getchar()<<24;
if (feof(stdin)) {
fprintf(stderr,"End of file reading record length, position = %d\n", ftell(stdin));
exit(0);
}
return n;
}
main (int argc, char **argv) {
char *infile;
int tapefd, fdin;
struct mtop mt_cmd;
struct mtget mt_status;
struct mtpos mt_pos;
int filenr, relrecnr;
int reclen, reclen2;
int n,n2;
int outpos;
int mark;
char buf[BUFSIZE];
filenr = 1;
relrecnr = 0;
mark = 0;
if ((tapefd=open("/dev/st0", O_RDWR)) == -1) {
perror("Error opening tape drive");
goto done;
}
/* set tape blocksize to 0 */
mt_cmd.mt_op = MTSETDRVBUFFER;
mt_cmd.mt_count = 0;
if (ioctl(tapefd, MTIOCTOP, &mt_cmd) != 0) {
perror("Error setting variable blocksize");
goto done;
}
/* set buffering */
mt_cmd.mt_op = MTSETDRVBUFFER;
mt_cmd.mt_count = 1;
if (ioctl(tapefd, MTIOCTOP, &mt_cmd) != 0) {
perror("Error setting buffering params");
goto done;
}
mt_cmd.mt_op = MTSETDRVBUFFER;
mt_cmd.mt_count = 1;
mt_cmd.mt_count |= MT_ST_BOOLEANS | MT_ST_BUFFER_WRITES | MT_ST_ASYNC_WRITES;
if (ioctl(tapefd, MTIOCTOP, &mt_cmd) != 0) {
perror("Error setting buffering params");
goto done;
}
#if 0
/* set density: 1=800bpi; 2=1600bpi; 3=6250bpi */
mt_cmd.mt_op = MTSETDENSITY;
mt_cmd.mt_count = 2;
if (ioctl(tapefd, MTIOCTOP, &mt_cmd) != 0) {
perror("Error setting variable blocksize");
goto done;
}
#endif
while(1) {
relrecnr++;
reclen = readint_le();
if (reclen == 0xFFFFFFFF) /* end of medium */
goto done;
else if (reclen & 0x80000000) { /* error in record */
printf("File %d, record %d contains error; writing zero record\n", filenr, relrecnr);
*(int *)buf = 0;
buf[0] = 4;
*(int *)(buf+4) = 0;
*(int *)(buf+8) = *(int *)buf;
if (write(tapefd, buf, 12) != 12) {
perror("Error writing error record");
goto done;
}
} else if (reclen == 0) {
printf("File mark %d\n", filenr);
mt_cmd.mt_op = MTWEOF;
mt_cmd.mt_count = 1;
if (ioctl(tapefd, MTIOCTOP, &mt_cmd) != 0) {
perror("Error writing tape mark");
goto done;
}
filenr++;
relrecnr = 0;
} else {
reclen = reclen & 0x00FFFFFF;
if (reclen > BUFSIZE) {
fprintf(stderr,"Record too big at record %d: increase BUFSIZE to %d\n", relrecnr, reclen);
exit(1);
}
n=fread(buf, 1, reclen, stdin);
if (n != reclen) {
fprintf(stderr,"Read error at record %d, expected %d, got %d bytes\n", relrecnr, reclen, n);
exit(1);
}
reclen2 = readint_le();
if (reclen2 != reclen) {
fprintf(stderr,"Read error at record %d, expected trailing reclen %d, got %d\n", relrecnr, reclen, reclen2);
exit(1);
}
if ((n2=write(tapefd, buf, reclen)) != reclen) {
if (n2 == -1) {
perror("Error writing tape device");
goto done;
}
printf("Error writing tape device, read %d, wrote %d\n", reclen, n2);
goto done;
}
}
}
done:
sleep(2);
mt_cmd.mt_op = MTREW;
mt_cmd.mt_count = 0;
if (ioctl(tapefd, MTIOCTOP, &mt_cmd) != 0) {
perror("Error rewinding");
}
close(tapefd);
exit(1);
}

123
util/ptextu.c Normal file
View File

@ -0,0 +1,123 @@
/* ptextu.c, J. Wilcoxson, March 16, 2005
Reads a Prime text file from stdin and writes a Unix text file to stdout.
Prime text files have 3 unique features:
- the text characters all have the high bit set, including newline
- text files may be compressed or uncompressed - compressed is
typical. Compressed files represent runs of 3-255 spaces as a 2-byte
sequence: 0221 (control-q w/parity) followed by the number of actual
spaces in the next byte. Uncompressed files are typically used in
record-oriented programs, where individual records may need to be
randomly updated or positioned. Compressed text files can't be
updated in general, other than to append or truncate, because the line
length may change if the number of spaces changes. When the Prime
compressed file input routines are used, blanks are expanded. Any
Prime program that handles compressed text files will also work with
uncompressed text files (just slower), but programs expecting
uncompressed files won't work with compressed files since blank
expansion doesn't happen.
- all text file lines must start on a 16-bit word boundary. To
enforce this restriction, when a newline character occurs in the left
byte of a 16-bit word, ie, it's in an odd file position (with counting
starting at 1), it is followed by a dummy character, typically a zero,
that isn't part of the text file. This null has to be skipped for
Unix since Unix treats every byte as part of the text file.
Notes:
- if the Prime text file ends with a compression character, that
character is not written to the output file. This is an invalid Prime
text file.
Here is an octal dump of a Prime text file. Lines 1, 2, and 4 needed
padding after the newline, but not line 3 or 5. Line 6 begins with the
compression character sequence, representing 3 spaces in 2 characters,
and has the same sequence later in the line.
0000000 354 351 363 364 346 272 240 360 362 357 343 273 212 \0 212 \0
0000020 245 351 356 343 354 365 344 345 240 247 363 371 363 343 357 355
0000040 276 353 345 371 363 256 351 356 363 256 360 354 261 247 273 212
0000060 212 \0 344 343 354 212 221 003 345 362 362 360 362 244 221 003
0000100 345 356 364 362 371 240 250 342 351 356 254 240 342 351 356 254
0000120 240 343 350 341 362 250 252 251 254 240 342 351 356 254 240 343
0000140 350 341 362 250 252 251 254 240 342 351 356 251 254 212 221 003
0000160 354 351 363 364 346 364 221 003 345 356 364 362 371 240 357 360
0000200 364 351 357 356 363 240 250 366 341 362 351 341 342 354 345 251
0000220 254 212 221 003 364 356 357 365 341 221 004 345 356 364 362 371
0000240 240 250 343 350 341 362 250 252 251 254 240 342 351 356 251 273
0000260 212 \0 212 \0 344 343 354 212 221 003 261 240 342 365 346 254
0000300 212 \0 221 006 262 240 354 345 356 240 342 351 356 254 212 \0
0000320 221 006 262 240 363 364 362 240 343 350 341 362 250 265 260 260
0000340 260 251 254 212 221 003 360 357 363 221 003 342 351 356 250 263
0000360 261 251 254 212 221 003 343 357 344 345 240 240 342 351 356 273
0000400 212 \0 212 \0 221 003 360 357 363 240 275 240 260 273 212 \0
0000420 221 003 344 357 240 365 356 364 351 354 240 250 342 365 346 256
0000440 354 345 356 240 276 240 260 251 273 212 221 006 343 341 354 354
0000460 240 354 351 363 364 346 364 240 250 342 365 346 254 240 262 265
0000500 260 260 254 240 360 357 363 254 240 343 357 344 345 251 273 212
0000520 221 006 351 346 240 343 357 344 345 240 336 275 240 260 240 364
0000540 350 345 356 212 221 \t 343 341 354 354 240 345 362 362 360 362
0000560 244 240 250 353 244 356 362 364 356 254 240 343 357 344 345 254
0000600 240 247 247 254 240 260 254 240 247 314 311 323 324 306 247 254
0000620 240 265 251 273 212 \0 221 006 343 341 354 354 240 364 356 357
0000640 365 341 240 250 342 365 346 256 363 364 362 254 240 341 342 363
0000660 240 250 342 365 346 256 354 345 356 251 251 273 212 \0 221 006
0000700 345 356 344 273 212 \0 212 \0 345 356 344 273 212 \0
0000716
--- Here is the text version: ---
listf: proc;
%include 'syscom>keys.ins.pl1';
dcl
errpr$ entry (bin, bin, char(*), bin, char(*), bin),
listft entry options (variable),
tnoua entry (char(*), bin);
dcl
1 buf,
2 len bin,
2 str char(5000),
pos bin(31),
code bin;
pos = 0;
do until (buf.len > 0);
call listft (buf, 2500, pos, code);
if code ^= 0 then
call errpr$ (k$nrtn, code, '', 0, 'LISTF', 5);
call tnoua (buf.str, abs (buf.len));
end;
end;
--- End of Unix text file ---
*/
#include <stdio.h>
main () {
int n,ch;
n = 0;
while ((ch=getchar()) != EOF) {
n++;
if (ch == 0221) {
n++;
if ((ch=getchar()) != EOF) /* avoid adding tons of blanks here */
while (ch--)
putchar(' ');
} else {
ch &= 0x7f; /* turn off high bit for Unix text */
putchar(ch);
if (ch == '\n' && n&1) { /* skip pad byte following newline */
getchar();
n++;
}
}
}
}

16
util/smad.py Normal file
View File

@ -0,0 +1,16 @@
# decode a Prime pdev number on the command line
import sys
if len(sys.argv) < 2:
raise Exception, 'Usage: smad <Prime physical device number>'
pdev = int(sys.argv[1], 8)
if not (pdev & 0x10):
raise Exception, 'Not a valid pdev'
cont = (pdev & 0xE0) >> 5
print 'Controller %d @ \'%d' % (cont, [24, 26, 25, 22, 45, 27, 46, 23][cont])
print 'Unit %d' % ((pdev & 0xf) >> 1)
print 'Head offset %d' % ((pdev >> 12) * 2)
print 'Surfaces %d' % (((pdev >> 7) & 0x1E) + (pdev & 1))

27
util/smag.py Normal file
View File

@ -0,0 +1,27 @@
# create Prime pdev numbers from prompts
import sys
caddr = [24, 26, 25, 22, 45, 27, 46, 23]
def getn(prompt, valid):
while 1:
try:
n = int(input(prompt + '? '))
except Exception:
sys.exit(0)
if valid(n):
return n
while 1:
pdev = 0x10
h = getn('Head offset (0-30)', lambda h: 0 <= h <= 30 and not h & 1)
pdev |= h >> 1 << 12
s = getn('Surfaces (0-31)', lambda s: 0 <= s <= 31)
pdev |= (s & 1) | ((s & 0x1e) << 7)
c = getn('Controller address %s' % repr(caddr),
lambda c: c in caddr)
pdev |= caddr.index(c) << 5
u = getn('Unit (0-7)', lambda u: 0 <= u <= 7)
pdev |= u << 1
print 'Prime pdev \'%o' % pdev
print

16
util/strip8.c Normal file
View File

@ -0,0 +1,16 @@
/* strip8.c, J. Wilcoxson, March 16, 2005
Reads a Prime data file and strips the high bit to make text readable.
NOTE: this program is not useful for production data, because Prime
text files have a specific format. Use prtoux to convert Prime text
files to Unix text files.
*/
#include <stdio.h>
main () {
int ch;
while ((ch = getchar()) != EOF)
putchar(ch & 0x7f);
}

128
util/untap.c Normal file
View File

@ -0,0 +1,128 @@
/* untap.c, J. Wilcoxson, March 19, 2005
Reads a tap magtape file and removes the tap information.
tap reference: http://simh.trailing-edge.com/docs/simh_magtape.pdf
*/
#include <stdlib.h>
#include <stdio.h>
#define BUFSIZE 128*1024
/* in the tap format, record lengths are always stored little-endian */
int readint_le () {
int n,ch;
n = getchar() | getchar()<<8 | getchar()<<16 | getchar()<<24;
if (feof(stdin)) {
fprintf(stderr,"End of file reading record length, position = %d\n", ftell(stdin));
exit(0);
}
return n;
}
main (int argc, char** argv) {
int fp; /* input file position, zero based */
int recno; /* record number, 1st record is 1 */
int reclen; /* length of record (bytes) */
int i,ch;
int ones; /* count of held-back "all ones" bytes */
int allff; /* true if record was all 1 bits */
int verbose;
unsigned char buf[BUFSIZE];
unsigned char bufhdr[5]; /* hack to display Prime 4-char tape hdr */
/* init */
recno = 0;
ones = 0;
fp = 0;
verbose = 0;
/* check args */
for (i=1; i<argc; i++) {
if (strcmp(argv[i],"-vv") == 0)
verbose = 2;
else if (strcmp(argv[i],"-v") == 0)
verbose = 1;
}
while (1) {
recno++;
if (feof(stdin)) {
if (verbose >= 1)
fprintf(stderr,"End of file at record %d\n", recno);
exit(0);
}
fp=ftell(stdin);
reclen = readint_le();
if (reclen == 0xFFFFFFFF) { /* end of medium */
if (verbose >= 1)
fprintf(stderr,"End of medium at record %d, position %d\n", recno, fp);
exit(0);
}
if (reclen == 0) { /* tape mark */
if (verbose >= 1)
fprintf(stderr,"Tape mark at record %d, position %d\n", recno, fp);
continue;
}
if (reclen & 0x80000000) { /* error in record */
fprintf(stderr,"Record %d flagged as error, position %d\n", recno, fp);
#if 0
if (recno > 1)
exit(1);
#endif
continue;
}
reclen = reclen & 0x00FFFFFF;
if (reclen > BUFSIZE) {
fprintf(stderr,"Record too big at record %d, position %d: increase BUFSIZE to %d\n", recno, fp, reclen);
exit(1);
}
allff = 1;
for (i=0; i<reclen; i++) {
buf[i] = getchar();
if (buf[i] != 0xff)
allff = 0;
}
i = readint_le();
if (i != reclen) {
fprintf(stderr,"Record length mismatch at record %d, position %d: %d != %d\n", recno, fp, reclen, i);
exit(1);
}
if (reclen&1) {
fprintf(stderr,"WARNING: Record %d has odd length of %d, position %d; record discarded!\n", recno, reclen, fp);
continue;
}
/* hack to display Prime drb record headers */
if (verbose >= 2) {
for (i=0; i<4; i++)
bufhdr[i] = buf[i] & 0x7f;
bufhdr[4] = 0;
fprintf(stderr,"Record %d, position %d, length %d, hdr %s\n", recno, fp, reclen, bufhdr);
}
/* sometimes tap files end with a stream of 0xff bytes when there
is no more data on the tape. To prevent writing this trailing
stream of garbage, keep a count of records full of 0xff bytes
and write them out when real data is seen again */
if (allff) {
if (verbose >= 2)
fprintf(stderr,"Record %d is all ones, position %d\n", recno, fp);
ones += reclen;
} else {
while (ones-- > 0)
putchar(0xff);
ones = 0;
for (i=0; i<reclen; i++)
putchar(buf[i]);
}
}
}

145
util/untap16.c Normal file
View File

@ -0,0 +1,145 @@
/* untap.c, J. Wilcoxson, March 19, 2005
Reads a tap magtape file and removes the tap information.
tap reference: http://simh.trailing-edge.com/docs/simh_magtape.pdf
NOTE: for this modified version, a 16-bit Prime word length is used
to delimit records.
*/
#include <stdlib.h>
#include <stdio.h>
#define BUFSIZE 16*1024
/* reads a 16-bit, big-endian record length, which is the number of 16-bit
words in the tape record. The upper 3 bits are apparently flag bits,
and these are moved the the upper 3 bits of the 32-bit word */
int readint_be () {
int n,ch;
n = (getchar()<<8 | getchar());
n = ((n & 0xE000) << 16) | ((n & 0x1FFF) * 2);
if (feof(stdin)) {
fprintf(stderr,"End of file reading record length, position = %d\n", ftell(stdin));
exit(0);
}
return n;
}
main (int argc, char** argv) {
int fp; /* input file position, zero based */
int recno; /* record number, 1st record is 1 */
int reclen; /* length of record (bytes) */
int flag1,flag2; /* leading & trailing flags */
int i,ch;
int ones; /* count of held-back "all ones" bytes */
int allff; /* true if record was all 1 bits */
int verbose;
unsigned char buf[BUFSIZE];
unsigned char bufhdr[5]; /* hack to display Prime 4-char tape hdr */
/* init */
recno = 0;
ones = 0;
fp = 0;
verbose = 0;
/* check args */
for (i=1; i<argc; i++) {
if (strcmp(argv[i],"-vv") == 0)
verbose = 2;
else if (strcmp(argv[i],"-v") == 0)
verbose = 1;
}
while (1) {
recno++;
if (feof(stdin)) {
if (verbose >= 1)
fprintf(stderr,"End of file at record %d\n", recno);
exit(0);
}
fp=ftell(stdin);
reclen = readint_be();
if (reclen == 0xFFFFFFFF) { /* end of medium */
if (verbose >= 1)
fprintf(stderr,"End of medium at record %d, position %d\n", recno, fp);
exit(0);
}
if (reclen == 0) { /* tape mark */
if (verbose >= 1)
fprintf(stderr,"Tape mark at record %d, position %d\n", recno, fp);
continue;
}
flag1 = reclen & 0xF0000000;
if (flag1 != 0) fprintf(stderr,"Leading flags are 0x%08x, position %d\n", flag1, fp);
reclen = reclen & 0x0FFFFFFF;
if (reclen > BUFSIZE) {
fprintf(stderr,"Record too big at record %d, position %d: increase BUFSIZE to %d\n", recno, fp, reclen);
exit(1);
}
allff = 1;
for (i=0; i<reclen; i++) {
buf[i] = getchar();
if (buf[i] != 0xff)
allff = 0;
}
i = readint_be();
flag2 = i & 0xF0000000;
if (flag2 != 0 && flag2 != 0x60000000) fprintf(stderr,"Trailing flags are 0x%08x, position %d\n", flag2, fp);
i = i & 0x0FFFFFFF;
if (i != reclen) {
fprintf(stderr,"Record length mismatch at record %d, position %d: %d != %d\n", recno, fp, reclen, i);
exit(1);
}
if (reclen&1) {
fprintf(stderr,"WARNING: Record %d has odd length of %d, position %d; record discarded!\n", recno, reclen, fp);
continue;
}
if (flag1 & 0x80000000) { /* error in record */
fprintf(stderr,"Record %d flagged as error, position %d; IGNORED!\n", recno, fp);
continue;
}
/* hack to display Prime drb record headers */
if (verbose >= 2) {
for (i=0; i<4; i++)
bufhdr[i] = buf[i] & 0x7f;
bufhdr[4] = 0;
fprintf(stderr,"Record %d, position %d, length %d, hdr %s\n", recno, fp, reclen, bufhdr);
}
/* sometimes tap files end with a stream of 0xff bytes when there
is no more data on the tape. To prevent writing this trailing
stream of garbage, keep a count of records full of 0xff bytes
and write them out when real data is seen again */
if (allff) {
if (verbose >= 2)
fprintf(stderr,"Record %d is all ones, position %d\n", recno, fp);
ones += reclen;
} else {
while (ones-- > 0)
putchar(0xff);
ones = 0;
for (i=0; i<reclen; i++)
putchar(buf[i]);
}
/* if flag bits are 0x60000000, then this is the last logical record in
a block and there is a 16-bit thing following */
if (flag2 == 0x60000000) {
for (i=0; i<16; i++)
buf[i] = getchar();
}
}
}

128
util/untap_vin.c Normal file
View File

@ -0,0 +1,128 @@
/* untap.c, J. Wilcoxson, March 19, 2005
Reads a tap magtape file and removes the tap information.
tap reference: http://simh.trailing-edge.com/docs/simh_magtape.pdf
*/
#include <stdlib.h>
#include <stdio.h>
#define BUFSIZE 128*1024
/* in the tap format, record lengths are always stored little-endian */
int readint_le () {
int n,ch;
n = getchar() | getchar()<<8;
if (feof(stdin)) {
fprintf(stderr,"End of file reading record length, position = %d\n", ftell(stdin));
exit(0);
}
return n;
}
main (int argc, char** argv) {
int fp; /* input file position, zero based */
int recno; /* record number, 1st record is 1 */
int reclen; /* length of record (bytes) */
int i,ch;
int ones; /* count of held-back "all ones" bytes */
int allff; /* true if record was all 1 bits */
int verbose;
unsigned char buf[BUFSIZE];
unsigned char bufhdr[5]; /* hack to display Prime 4-char tape hdr */
/* init */
recno = 0;
ones = 0;
fp = 0;
verbose = 0;
/* check args */
for (i=1; i<argc; i++) {
if (strcmp(argv[i],"-vv") == 0)
verbose = 2;
else if (strcmp(argv[i],"-v") == 0)
verbose = 1;
}
while (1) {
recno++;
if (feof(stdin)) {
if (verbose >= 1)
fprintf(stderr,"End of file at record %d\n", recno);
exit(0);
}
fp=ftell(stdin);
reclen = readint_le();
if (reclen == 0) { /* end of medium */
if (verbose >= 1)
fprintf(stderr,"Zero rec length at record %d, position %d\n", recno, fp);
exit(0);
}
if (reclen == 0xFFFF) { /* tape mark */
if (verbose >= 1)
fprintf(stderr,"Tape mark at record %d, position %d\n", recno, fp);
continue;
}
if (reclen & 0x80000000) { /* error in record */
fprintf(stderr,"Record %d flagged as error, position %d\n", recno, fp);
#if 0
if (recno > 1)
exit(1);
#endif
continue;
}
reclen = reclen & 0x00FFFFFF;
if (reclen > BUFSIZE) {
fprintf(stderr,"Record too big at record %d, position %d: increase BUFSIZE to %d\n", recno, fp, reclen);
exit(1);
}
allff = 1;
for (i=0; i<reclen; i++) {
buf[i] = getchar();
if (buf[i] != 0xff)
allff = 0;
}
i = readint_le();
if (i != reclen) {
fprintf(stderr,"Record length mismatch at record %d, position %d: %d != %d\n", recno, fp, reclen, i);
exit(1);
}
if (reclen&1) {
fprintf(stderr,"WARNING: Record %d has odd length of %d, position %d; record discarded!\n", recno, reclen, fp);
continue;
}
/* hack to display Prime drb record headers */
if (verbose >= 2) {
for (i=0; i<4; i++)
bufhdr[i] = buf[i] & 0x7f;
bufhdr[4] = 0;
fprintf(stderr,"Record %d, position %d, length %d, hdr %s\n", recno, fp, reclen, bufhdr);
}
/* sometimes tap files end with a stream of 0xff bytes when there
is no more data on the tape. To prevent writing this trailing
stream of garbage, keep a count of records full of 0xff bytes
and write them out when real data is seen again */
if (allff) {
if (verbose >= 2)
fprintf(stderr,"Record %d is all ones, position %d\n", recno, fp);
ones += reclen;
} else {
while (ones-- > 0)
putchar(0xff);
ones = 0;
for (i=0; i<reclen; i++)
putchar(buf[i]);
}
}
}

58
util/utextp.c Normal file
View File

@ -0,0 +1,58 @@
/* utextp.c, Jim Wilcoxson, March 16, 2005
Reads a Unix text file and converts it to a compressed Prime text file.
This means:
- turn high bit on
- expand Unix tabs to spaces
- change multiple spaces to rcp (Prime text compression)
- ignore carriage returns (Windoze)
- ensure each lines begins on a 16-bit word boundary
*/
#include <stdio.h>
main () {
int n,i,ch,col,space,nsp;
n = 0;
col = 0;
space = 0;
while ((ch=getchar()) != EOF) {
if (ch == '\t') { /* expand Unix tabs */
nsp = 8 - (col & 7);
space += nsp;
col += nsp;
} else if (ch == ' ') {
space++;
col++;
} else if (ch == '\r')
;
else { /* not a space character */
while (space) { /* dump held-back spaces first */
if (space < 3) { /* write regular spaces */
putchar(' ');
space--;
n++;
} else { /* write compressed spaces */
putchar(0221);
if (space > 255) /* limited to 255 spaces per compression */
nsp = 255;
else
nsp = space;
putchar(nsp);
n = n+2;
space = space - nsp;
}
}
putchar(ch | 0x80); /* write the text char w/parity */
col++;
n++;
if (ch == '\n') {
col = 0;
if (n&1) { /* Prime lines must start on a 16-bit word boundary */
putchar(0);
n++;
}
}
}
}
}