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:
parent
45086db988
commit
38840b710d
13
util/amlcout.py
Normal file
13
util/amlcout.py
Normal 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
184
util/emlink.c
Normal 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
15
util/intsize.c
Normal 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
306
util/istext.c
Normal 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
12
util/istext.h
Normal 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
908
util/magrst.c
Normal 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
396
util/magsav.c
Normal 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
12
util/makefile
Normal 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
155
util/mtread.c
Normal 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
145
util/mtwrite.c
Normal 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
123
util/ptextu.c
Normal 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
16
util/smad.py
Normal 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
27
util/smag.py
Normal 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
16
util/strip8.c
Normal 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
128
util/untap.c
Normal 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
145
util/untap16.c
Normal 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
128
util/untap_vin.c
Normal 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
58
util/utextp.c
Normal 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++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user