dos33fsprogs/utils/prodos-utils/prodos.c
2021-08-03 13:33:29 -04:00

1404 lines
33 KiB
C

#include <stdio.h>
#include <stdlib.h> /* exit() */
#include <string.h> /* strncpy() */
#include <sys/stat.h> /* struct stat */
#include <fcntl.h> /* O_RDONLY */
#include <unistd.h> /* lseek() */
#include <ctype.h> /* toupper() */
#include <errno.h>
#include "version.h"
#include "prodos.h"
static int ignore_errors=0;
int debug=1;
/* Read volume directory into a buffer */
static int prodos_read_voldir(int fd, struct voldir_t *voldir,
int interleave, int image_offset) {
int result;
unsigned char voldir_buffer[PRODOS_BYTES_PER_BLOCK];
voldir->interleave=interleave;
voldir->image_offset=image_offset;
/* read in VOLDIR KEY Block*/
voldir->fd=fd;
result=prodos_read_block(voldir,voldir_buffer,PRODOS_VOLDIR_KEY_BLOCK);
if (result<0) {
fprintf(stderr,"Error reading VOLDIR\n");
return -1;
}
voldir->fd=fd;
voldir->storage_type=(voldir_buffer[0x4]>>4)&0xf;
voldir->name_length=(voldir_buffer[0x4]&0xf);
if (voldir->storage_type!=0xf) {
fprintf(stderr,"ERROR! Expected storage type F\n");
}
memcpy(voldir->volume_name,&voldir_buffer[0x5],voldir->name_length);
voldir->volume_name[voldir->name_length]=0;
voldir->creation_time=(voldir_buffer[0x1c]<<16)|
(voldir_buffer[0x1d]<<24)|
(voldir_buffer[0x1e]<<0)|
(voldir_buffer[0x1f]<<8);
voldir->version=voldir_buffer[0x20];
voldir->min_version=voldir_buffer[0x21];
voldir->access=voldir_buffer[0x22];
voldir->entry_length=voldir_buffer[0x23];
if (voldir->entry_length!=PRODOS_FILE_DESC_LEN) {
printf("Error! Unexpected desc len %d\n",
voldir->entry_length);
}
voldir->entries_per_block=voldir_buffer[0x24];
voldir->file_count=voldir_buffer[0x25]|(voldir_buffer[0x26]<<8);
voldir->bit_map_pointer=voldir_buffer[0x27]|(voldir_buffer[0x28]<<8);
voldir->total_blocks=voldir_buffer[0x29]|(voldir_buffer[0x2A]<<8);
voldir->next_block=voldir_buffer[0x2]|(voldir_buffer[0x3]<<8);
return 0;
}
/* Given filename, return voldir/offset */
static int prodos_lookup_file(struct voldir_t *voldir,
char *filename) {
int voldir_block,voldir_offset;
struct file_entry_t file_entry;
unsigned char voldir_buffer[PRODOS_BYTES_PER_BLOCK];
int result,file;
voldir_block=PRODOS_VOLDIR_KEY_BLOCK;
voldir_offset=1; /* skip the header */
while(1) {
/* Read in Block */
result=prodos_read_block(voldir,
voldir_buffer,voldir_block);
if (result<0) {
fprintf(stderr,"Error on I/O\n");
return -1;
}
for(file=voldir_offset;
file<voldir->entries_per_block;file++) {
prodos_populate_filedesc(
voldir_buffer+4+file*PRODOS_FILE_DESC_LEN,
&file_entry);
/* FIXME: case insensitive? */
if (!strncmp(filename,(char *)file_entry.file_name,15)) {
return (voldir_block<<8)|file;
}
}
voldir_offset=0;
voldir_block=voldir_buffer[2]|(voldir_buffer[3]<<8);
if (voldir_block==0) break;
}
return -1;
}
/* Given filename, return voldir/offset */
/* FIXME: allocate new voldir block if all full */
static int prodos_allocate_directory_entry(struct voldir_t *voldir) {
int voldir_block,voldir_offset;
struct file_entry_t file_entry;
unsigned char voldir_buffer[PRODOS_BYTES_PER_BLOCK];
int result,file;
voldir_block=PRODOS_VOLDIR_KEY_BLOCK;
voldir_offset=1; /* skip the header */
while(1) {
/* Read in Block */
result=prodos_read_block(voldir,
voldir_buffer,voldir_block);
if (result<0) {
fprintf(stderr,"Error on I/O\n");
return -1;
}
for(file=voldir_offset;
file<voldir->entries_per_block;file++) {
prodos_populate_filedesc(
voldir_buffer+4+file*PRODOS_FILE_DESC_LEN,
&file_entry);
if (file_entry.storage_type==PRODOS_FILE_DELETED) {
return (voldir_block<<8)|file;
}
}
voldir_offset=0;
voldir_block=voldir_buffer[2]|(voldir_buffer[3]<<8);
if (voldir_block==0) break;
}
return -1;
}
/* Checks if "filename" exists */
/* returns file type */
static int prodos_check_file_exists(struct voldir_t *voldir,
char *filename) {
int result;
result=prodos_lookup_file(voldir,filename);
return result;
}
static int prodos_free_block(struct voldir_t *voldir,int block) {
int result;
/* mark as free using VOLDIR */
result=prodos_voldir_free_block(voldir,block);
if (result<0) {
fprintf(stderr,"Error on I/O\n");
}
return 0;
}
static int prodos_allocate_block(struct voldir_t *voldir) {
int found_block=0;
/* Find an empty block */
found_block=prodos_voldir_find_free_block(voldir);
if (debug) printf("Found free block %x\n",found_block);
if (found_block<0) {
fprintf(stderr,"ERROR: prodos_allocate_sector: Disk full!\n");
return -1;
}
prodos_voldir_reserve_block(voldir,found_block);
return found_block;
}
static int prodos_writeout_filedesc(struct voldir_t *voldir,
struct file_entry_t *file_entry,
unsigned char *dest) {
/* clear it out */
memset(dest,0,PRODOS_FILE_DESC_LEN);
dest[0x00]=(file_entry->storage_type<<4)|(file_entry->name_length&0xf);
memcpy(&dest[0x01],&file_entry->file_name[0],file_entry->name_length);
dest[0x10]=file_entry->file_type;
dest[0x11]=file_entry->key_pointer&0xff;
dest[0x12]=(file_entry->key_pointer>>8)&0xff;
dest[0x13]=file_entry->blocks_used&0xff;
dest[0x14]=(file_entry->blocks_used>>8)&0xff;
dest[0x15]=file_entry->eof&0xff;
dest[0x16]=(file_entry->eof>>8)&0xff;
dest[0x17]=(file_entry->eof>>16)&0xff;
dest[0x18]=(file_entry->creation_time>>16)&0xff;
dest[0x19]=(file_entry->creation_time>>24)&0xff;
dest[0x1a]=(file_entry->creation_time>>0)&0xff;
dest[0x1b]=(file_entry->creation_time>>8)&0xff;
dest[0x1c]=file_entry->version;
dest[0x1d]=file_entry->min_version;
dest[0x1e]=file_entry->access;
dest[0x1f]=file_entry->aux_type&0xff;
dest[0x20]=(file_entry->aux_type>>8)&0xff;
dest[0x21]=(file_entry->last_mod>>16)&0xff;
dest[0x22]=(file_entry->last_mod>>24)&0xff;
dest[0x23]=(file_entry->last_mod>>0)&0xff;
dest[0x24]=(file_entry->last_mod>>8)&0xff;
dest[0x25]=file_entry->header_pointer&0xff;
dest[0x26]=(file_entry->header_pointer>>8)&0xff;
return 0;
}
#define ERROR_MYSTERY 1
#define ERROR_INVALID_FILENAME 2
#define ERROR_FILE_NOT_FOUND 3
#define ERROR_NO_SPACE 4
#define ERROR_IMAGE_NOT_FOUND 5
#define ERROR_CATALOG_FULL 6
#define ERROR_FILE_TOO_BIG 7
/* creates file apple_filename on the image from local file filename */
/* returns ?? */
static int prodos_add_file(struct voldir_t *voldir,
int fd, char *type,
int address, int length,
char *filename, char *apple_filename) {
int free_blocks,file_size,needed_blocks,total_blocks,file_type;
int block,i,j,needed_limit;
struct stat file_info;
int input_fd;
int result;
unsigned char key_buffer[PRODOS_BYTES_PER_BLOCK];
unsigned char index_buffer[PRODOS_BYTES_PER_BLOCK];
unsigned char data_buffer[PRODOS_BYTES_PER_BLOCK];
int key_block,index,inode;
struct file_entry_t file;
/* check for valid filename */
/* Filename rules for Prodos: */
/* Only letters, numbers, and periods */
/* no special chars */
/* Traditionally only uppercase (for II+). Upper/lowercase */
/* data later stored in the VERSION/MAX_VERSION fields */
for(i=0;i<strlen(apple_filename);i++) {
if ( (!isalnum(apple_filename[i])) &&
(apple_filename[i]!='.') ) {
fprintf(stderr,"Warning! Invalid char in filename!\n");
//return ERROR_INVALID_FILENAME;
}
if ( (isalpha(apple_filename[i])) &&
(islower(apple_filename[i])) ) {
fprintf(stderr,"Warning, lowercase filename support not really implemented\n");
//return ERROR_INVALID_FILENAME;
}
}
/* Determine size of file to upload */
if (stat(filename,&file_info)<0) {
fprintf(stderr,"Error! %s not found!\n",filename);
return ERROR_FILE_NOT_FOUND;
}
file_size=(int)file_info.st_size;
/* We need to round up to nearest block size */
needed_blocks=1+((file_size-1)/PRODOS_BYTES_PER_BLOCK);
if (debug) printf("Filesize: %d (need %d blocks)\n",
file_size,needed_blocks);
if (needed_blocks==0) {
fprintf(stderr,"Error! invalid blocksize %d\n",needed_blocks);
return -ERROR_MYSTERY;
}
else if (needed_blocks==1) {
/* seedling */
if (debug) printf("File seedling\n");
file_type=PRODOS_FILE_SEEDLING;
total_blocks=needed_blocks;
}
else if (needed_blocks<=256) {
/* sapling */
if (debug) printf("File sapling\n");
file_type=PRODOS_FILE_SAPLING;
total_blocks=needed_blocks+1; /* for index block */
}
else if (needed_blocks<=65536) {
/* tree */
if (debug) printf("File tree\n");
file_type=PRODOS_FILE_TREE;
total_blocks=needed_blocks+1 /* for key index block */
+(1+needed_blocks/256); /* for index blocks */
/* FIXME: -1? */
}
else {
fprintf(stderr,"Error, file too big: %d\n",file_size);
return -ERROR_FILE_TOO_BIG;
}
/* Get free space on device */
free_blocks=prodos_voldir_free_space(voldir);
/* Check for free space */
if (total_blocks>free_blocks) {
fprintf(stderr,"Error! Not enough free space "
"on disk image (need %d have %d)\n",
total_blocks,free_blocks);
return ERROR_NO_SPACE;
}
/* Open the local file */
input_fd=open(filename,O_RDONLY);
if (input_fd<0) {
fprintf(stderr,"Error! could not open %s\n",filename);
return ERROR_IMAGE_NOT_FOUND;
}
if (file_type==PRODOS_FILE_SEEDLING) {
block=prodos_allocate_block(voldir);
key_block=block;
memset(data_buffer,0,PRODOS_BYTES_PER_BLOCK);
result=read(input_fd,data_buffer,PRODOS_BYTES_PER_BLOCK);
if (result<0) {
fprintf(stderr,"Error reading\n");
return -ERROR_MYSTERY;
}
prodos_write_block(voldir,data_buffer,block);
}
if (file_type==PRODOS_FILE_SAPLING) {
/* allocate index */
index=prodos_allocate_block(voldir);
key_block=index;
memset(index_buffer,0,PRODOS_BYTES_PER_BLOCK);
for(i=0;i<needed_blocks;i++) {
block=prodos_allocate_block(voldir);
index_buffer[i]=block&0xff;
index_buffer[i+256]=(block>>8)&0xff;
memset(data_buffer,0,PRODOS_BYTES_PER_BLOCK);
result=read(input_fd,data_buffer,PRODOS_BYTES_PER_BLOCK);
if (result<0) {
fprintf(stderr,"Error reading\n");
return -ERROR_MYSTERY;
}
prodos_write_block(voldir,data_buffer,block);
}
prodos_write_block(voldir,index_buffer,index);
}
if (file_type==PRODOS_FILE_TREE) {
/* allocate key index */
key_block=prodos_allocate_block(voldir);
memset(key_buffer,0,PRODOS_BYTES_PER_BLOCK);
for(j=0;j<(1+needed_blocks/256);j++) {
index=prodos_allocate_block(voldir);
memset(index_buffer,0,PRODOS_BYTES_PER_BLOCK);
key_buffer[j]=index&0xff;
key_buffer[j+256]=(index>>8)&0xff;
if (j==needed_blocks/256) {
needed_limit=needed_blocks%256;
}
else {
needed_limit=256;
}
for(i=0;i<needed_limit;i++) {
block=prodos_allocate_block(voldir);
index_buffer[i]=block&0xff;
index_buffer[i+256]=(block>>8)&0xff;
memset(data_buffer,0,PRODOS_BYTES_PER_BLOCK);
result=read(input_fd,data_buffer,PRODOS_BYTES_PER_BLOCK);
if (result<0) {
fprintf(stderr,"Error reading\n");
return -ERROR_MYSTERY;
}
prodos_write_block(voldir,data_buffer,block);
}
prodos_write_block(voldir,index_buffer,index);
}
prodos_write_block(voldir,key_buffer,key_block);
}
close(input_fd);
/* now that file is on disk, hook up the directory image */
memset(&file,0,sizeof(struct file_entry_t));
/* FIXME */
file.storage_type=file_type;
file.name_length=strlen(apple_filename);
memcpy(file.file_name,apple_filename,file.name_length);
file.file_type=0;
file.key_pointer=key_block;
file.blocks_used=total_blocks; /* includes index blocks */
file.eof=file_size;
file.creation_time=0;
file.version=0;
file.min_version=0;
file.access=0;
file.aux_type=0;
file.last_mod=0;
file.header_pointer=PRODOS_VOLDIR_KEY_BLOCK;
inode=prodos_allocate_directory_entry(voldir);
if (inode<0) {
return inode;
}
/* read in existing voldir entry */
result=prodos_read_block(voldir,data_buffer,inode>>8);
/* copy in new data */
prodos_writeout_filedesc(voldir,&file,
data_buffer+4+(inode&0xff)*PRODOS_FILE_DESC_LEN);
/* write back existing voldir entry */
result=prodos_write_block(voldir,data_buffer,inode>>8);
return 0;
}
int prodos_get_file_entry(struct voldir_t *voldir,
int inode, struct file_entry_t *file) {
unsigned char voldir_buffer[PRODOS_BYTES_PER_BLOCK];
int block,offset;
int result;
block=(inode>>8);
offset=(inode&0xff);
result=prodos_read_block(voldir,voldir_buffer,block);
if (result<0) {
return result;
}
prodos_populate_filedesc(voldir_buffer+4+offset*PRODOS_FILE_DESC_LEN,
file);
return 0;
}
/* load a file from the disk image. */
/* inode = voldirblock<<8 | entry */
static int prodos_load_file(struct voldir_t *voldir,
int inode,char *filename) {
int output_fd;
unsigned char data[PRODOS_BYTES_PER_BLOCK];
unsigned char index_block[PRODOS_BYTES_PER_BLOCK];
unsigned char master_index_block[PRODOS_BYTES_PER_BLOCK];
int result,chunk,chunk_block,index,blocks_left,read_blocks;
struct file_entry_t file;
/* FIXME! Warn if overwriting file! */
output_fd=open(filename,O_WRONLY|O_CREAT|O_TRUNC,0666);
if (output_fd<0) {
fprintf(stderr,"Error! could not open %s for local save\n",
filename);
return -1;
}
if (prodos_get_file_entry(voldir,inode,&file)<0) {
fprintf(stderr,"Error opening inode %x\n",inode);
return -1;
}
switch(file.storage_type) {
case PRODOS_FILE_SEEDLING:
/* Just a single block */
if (debug) fprintf(stderr,"Loading %d bytes from "
"block $%x\n",
file.eof,file.key_pointer);
result=prodos_read_block(voldir,data,
file.key_pointer);
if (result<0) {
return result;
}
result=write(output_fd,data,file.eof);
if (result!=file.eof) {
fprintf(stderr,"Error writing file!\n");
return -1;
}
break;
case PRODOS_FILE_SAPLING:
/* Index block points to up to 256 blocks */
/* Addresses are stored low-byte (256 bytes) then hi-byte */
/* Address of zero means file hole, all zeros */
if (debug) fprintf(stderr,"Loading index "
"block $%x\n",
file.key_pointer);
result=prodos_read_block(voldir,index_block,
file.key_pointer);
if (result<0) {
return result;
}
for(chunk=0;chunk<file.blocks_used;chunk++) {
chunk_block=(index_block[chunk])|(index_block[chunk+256]<<8);
if (chunk_block==0) {
/* FILE hole */
lseek(output_fd,
PRODOS_BYTES_PER_BLOCK,
SEEK_CUR);
}
else {
result=prodos_read_block(voldir,data,
chunk_block);
if (result<0) {
return result;
}
}
result=write(output_fd,data,PRODOS_BYTES_PER_BLOCK);
if (result!=PRODOS_BYTES_PER_BLOCK) {
fprintf(stderr,"Error writing file!\n");
return -1;
}
}
/* truncate to actual size of file */
ftruncate(output_fd,file.eof);
break;
case PRODOS_FILE_TREE:
/* Master Index block points to up to 256 index blocks */
/* Addresses are stored low-byte (256 bytes) then hi-byte */
/* Index block points to up to 256 blocks */
/* Addresses are stored low-byte (256 bytes) then hi-byte */
/* Address of zero means file hole, all zeros */
blocks_left=file.blocks_used;
if (debug) fprintf(stderr,"Loading master index "
"block $%x\n",
file.key_pointer);
result=prodos_read_block(voldir,master_index_block,
file.key_pointer);
if (result<0) {
return result;
}
for(index=0;index<file.blocks_used/256;index++) {
result=prodos_read_block(voldir,
index_block,
(master_index_block[index])|
(master_index_block[256+index]<<8));
if (result<0) {
return result;
}
if (blocks_left<256) {
read_blocks=blocks_left;
}
else {
read_blocks=256;
}
for(chunk=0;chunk<read_blocks;chunk++) {
chunk_block=(index_block[chunk])|
(index_block[chunk+256]<<8);
result=prodos_read_block(voldir,data,
chunk_block);
if (result<0) {
return result;
}
result=write(output_fd,data,PRODOS_BYTES_PER_BLOCK);
if (result!=PRODOS_BYTES_PER_BLOCK) {
fprintf(stderr,"Error writing file!\n");
return -1;
}
}
}
/* truncate to actual size of file */
ftruncate(output_fd,file.eof);
break;
case PRODOS_FILE_SUBDIR:
case PRODOS_FILE_DELETED:
case PRODOS_FILE_SUBDIR_HDR:
case PRODOS_FILE_VOLUME_HDR:
default:
fprintf(stderr,"Error! "
"Cannot load this type of file: %x\n",
file.storage_type);
break;
}
close(output_fd);
return 0;
}
/* rename a file */
/* FIXME: validate the new filename is valid */
static int prodos_rename_file(struct voldir_t *voldir,
char *old_filename,char *new_filename) {
int result;
int inode;
struct file_entry_t file;
unsigned char data_buffer[PRODOS_BYTES_PER_BLOCK];
int newlen;
/* get the voldir/entry for file */
inode=prodos_lookup_file(voldir,old_filename);
if (inode<0) {
fprintf(stderr,"Error! %s not found!\n",
old_filename);
return -1;
}
/* get the file entry */
if (prodos_get_file_entry(voldir,inode,&file)<0) {
fprintf(stderr,"Error opening inode %x\n",inode);
return -1;
}
/* change the filename */
newlen=strlen(new_filename);
memset(file.file_name,0,15);
memcpy(file.file_name,new_filename,newlen);
file.name_length=newlen;
/* read in existing voldir entry */
result=prodos_read_block(voldir,data_buffer,inode>>8);
/* copy in new data */
prodos_writeout_filedesc(voldir,&file,
data_buffer+4+(inode&0xff)*PRODOS_FILE_DESC_LEN);
/* write back existing voldir entry */
result=prodos_write_block(voldir,data_buffer,inode>>8);
if (result<0) {
fprintf(stderr,"I/O Error!\n");
return result;
}
return 0;
}
static int prodos_delete_file(struct voldir_t *voldir,char *apple_filename) {
#if 0
int i;
int catalog_track,catalog_sector,catalog_entry;
int ts_track,ts_sector;
char file_type;
int result;
unsigned char catalog_buffer[PRODOS_BYTES_PER_BLOCK];
/* unpack file/track/sector info */
catalog_entry=fsl>>16;
catalog_track=(fsl>>8)&0xff;
catalog_sector=(fsl&0xff);
/* Load in the catalog table for the file */
lseek(fd,DISK_OFFSET(catalog_track,catalog_sector),SEEK_SET);
result=read(fd,catalog_buffer,PRODOS_BYTES_PER_BLOCK);
file_type=catalog_buffer[CATALOG_FILE_LIST+
(catalog_entry*CATALOG_ENTRY_SIZE)
+FILE_TYPE];
if (file_type&0x80) {
fprintf(stderr,"File is locked! Unlock before deleting!\n");
exit(1);
}
/* get pointer to t/s list */
ts_track=catalog_buffer[CATALOG_FILE_LIST+
catalog_entry*CATALOG_ENTRY_SIZE+FILE_TS_LIST_T];
ts_sector=catalog_buffer[CATALOG_FILE_LIST+
catalog_entry*CATALOG_ENTRY_SIZE+FILE_TS_LIST_S];
keep_deleting:
/* load in the t/s list info */
lseek(fd,DISK_OFFSET(ts_track,ts_sector),SEEK_SET);
result=read(fd,catalog_buffer,PRODOS_BYTES_PER_BLOCK);
/* Free each sector listed by t/s list */
for(i=0;i<TSL_MAX_NUMBER;i++) {
/* If t/s = 0/0 then no need to clear */
if ((catalog_buffer[TSL_LIST+2*i]==0) &&
(catalog_buffer[TSL_LIST+2*i+1]==0)) {
}
else {
prodos_free_sector(voldir,fd,catalog_buffer[TSL_LIST+2*i],
catalog_buffer[TSL_LIST+2*i+1]);
}
}
/* free the t/s list */
prodos_free_sector(voldir,fd,ts_track,ts_sector);
/* Point to next t/s list */
ts_track=catalog_buffer[TSL_NEXT_TRACK];
ts_sector=catalog_buffer[TSL_NEXT_SECTOR];
/* If more tsl lists, keep looping */
if ((ts_track==0x0) && (ts_sector==0x0)) {
}
else {
goto keep_deleting;
}
/* Erase file from catalog entry */
/* First reload proper catalog sector */
lseek(fd,DISK_OFFSET(catalog_track,catalog_sector),SEEK_SET);
result=read(fd,catalog_buffer,PRODOS_BYTES_PER_BLOCK);
/* save track as last char of name, for undelete purposes */
catalog_buffer[CATALOG_FILE_LIST+(catalog_entry*CATALOG_ENTRY_SIZE)+
(FILE_NAME+FILE_NAME_SIZE-1)]=
catalog_buffer[CATALOG_FILE_LIST+(catalog_entry*CATALOG_ENTRY_SIZE)];
/* Actually delete the file */
/* by setting the track value to FF which indicates deleted file */
catalog_buffer[CATALOG_FILE_LIST+(catalog_entry*CATALOG_ENTRY_SIZE)]=0xff;
/* re seek to catalog position and write out changes */
lseek(fd,DISK_OFFSET(catalog_track,catalog_sector),SEEK_SET);
result=write(fd,catalog_buffer,PRODOS_BYTES_PER_BLOCK);
if (result<0) fprintf(stderr,"Error on I/O\n");
#endif
return 0;
}
static void display_help(char *name, int version_only) {
printf("\nprodos version %s\n",VERSION);
printf("by Vince Weaver <vince@deater.net>\n");
printf("\n");
if (version_only) return;
printf("Usage: %s [-h] [i interleave] [-y] [-x] disk_image COMMAND [options]\n",name);
printf("\t-h : this help message\n");
printf("\t-i : interleave (prodos or dos33)\n");
printf("\t-y : always answer yes for warning questions\n");
printf("\t-x : ignore errors (useful for making invalid filenames)\n");
printf("\n");
printf(" Where disk_image is a valid PRODOS disk image\n"
" and COMMAND is one of the following:\n");
printf("\tCATALOG [dir]\n");
printf("\tLOAD apple_file <local_file>\n");
printf("\tSAVE [-t type] [-a addr] [-l len] local_file <apple_file>\n");
printf("\tDELETE apple_file\n");
printf("\tRENAME apple_file_old apple_file_new\n");
printf("\tDUMP\n");
printf("\tVOLUME volume_name\n");
printf("\tTYPE TODO: set type\n");
printf("\tAUX TODO: set aux\n");
printf("\tTIMESTAMP TODO: set timestamp\n");
printf("\tACCESS TODO: set access\n");
printf("\n");
return;
}
#define COMMAND_LOAD 0
#define COMMAND_SAVE 1
#define COMMAND_CATALOG 2
#define COMMAND_DELETE 3
#define COMMAND_RENAME 4
#define COMMAND_DUMP 5
#define COMMAND_SHOWFREE 6
#define COMMAND_VOLNAME 7
#define MAX_COMMAND 8
#define COMMAND_UNKNOWN 255
static struct command_type {
int type;
char name[32];
} commands[MAX_COMMAND] = {
{COMMAND_LOAD,"LOAD"},
{COMMAND_SAVE,"SAVE"},
{COMMAND_CATALOG,"CATALOG"},
{COMMAND_DELETE,"DELETE"},
{COMMAND_RENAME,"RENAME"},
{COMMAND_DUMP,"DUMP"},
{COMMAND_SHOWFREE,"SHOWFREE"},
{COMMAND_VOLNAME,"VOLNAME"},
};
static int lookup_command(char *name) {
int which=COMMAND_UNKNOWN,i;
for(i=0;i<MAX_COMMAND;i++) {
if(!strncmp(name,commands[i].name,strlen(commands[i].name))) {
which=commands[i].type;
break;
}
}
return which;
}
static int truncate_filename(char *out, char *in) {
int truncated=0;
/* Truncate filename if too long */
if (strlen(in)>15) {
fprintf(stderr,"Warning! Truncating %s to 15 chars\n",in);
truncated=1;
}
strncpy(out,in,15);
out[15]='\0';
return truncated;
}
static int prodos_sync_voldir(struct voldir_t *voldir) {
unsigned char newvoldir[PRODOS_BYTES_PER_BLOCK];
memset(newvoldir,0,PRODOS_BYTES_PER_BLOCK);
newvoldir[0x4]=(voldir->storage_type<<4)|(voldir->name_length&0xf);
memcpy(&newvoldir[0x5],voldir->volume_name,voldir->name_length);
/* FIXME: probably endianess issues */
newvoldir[0x1c]=(voldir->creation_time>>16)&0xff;
newvoldir[0x1d]=(voldir->creation_time>>24)&0xff;
newvoldir[0x1e]=(voldir->creation_time>>0)&0xff;
newvoldir[0x1f]=(voldir->creation_time>>8)&0xff;
newvoldir[0x20]=voldir->version;
newvoldir[0x21]=voldir->min_version;
newvoldir[0x22]=voldir->access;
newvoldir[0x23]=voldir->entry_length;
newvoldir[0x24]=voldir->entries_per_block;
newvoldir[0x25]=voldir->file_count&0xff;
newvoldir[0x26]=(voldir->file_count>>8)&0xff;
newvoldir[0x27]=voldir->bit_map_pointer&0xff;
newvoldir[0x28]=(voldir->bit_map_pointer>>8)&0xff;
newvoldir[0x29]=voldir->total_blocks&0xff;
newvoldir[0x2A]=(voldir->total_blocks>>8)&0xff;
newvoldir[0x2]=voldir->next_block&0xff;
newvoldir[0x3]=(voldir->next_block>>8)&0xff;
prodos_write_block(voldir,newvoldir,PRODOS_VOLDIR_KEY_BLOCK);
return 0;
}
static int change_volume_name(struct voldir_t *voldir, char *volname) {
int volname_len;
volname_len=strlen(volname);
if (volname_len>15) {
printf("Warning! Volume name %s is too long, truncating\n",
volname);
volname_len=15;
}
memcpy(voldir->volume_name,volname,15);
voldir->name_length=volname_len;
prodos_sync_voldir(voldir);
return 0;
}
int main(int argc, char **argv) {
char image[BUFSIZ];
char type[4];
int prodos_fd=0,i;
int interleave=PRODOS_INTERLEAVE_PRODOS,arg_interleave=0;
int image_offset=0;
int command,file_exists;
char temp_string[BUFSIZ];
char apple_filename[31],new_filename[31];
char local_filename[BUFSIZ];
char *result_string;
int always_yes=0;
char *temp,*endptr;
int c;
int inode;
int address=0, length=0;
struct voldir_t voldir;
/* Check command line arguments */
while ((c = getopt (argc, argv,"a:i:l:t:dhvxy"))!=-1) {
switch (c) {
case 'd':
fprintf(stderr,"DEBUG enabled\n");
debug=1;
break;
case 'a':
address=strtol(optarg,&endptr,0);
if (debug) fprintf(stderr,"Address=%d\n",address);
break;
case 't':
if (strlen(optarg)!=3) {
fprintf(stderr,"Type %s too long, should be 3 chars\n",optarg);
return -1;
}
for(i=0;i<3;i++) {
type[i]=toupper(optarg[i]);
}
type[3]=0;
if (debug) fprintf(stderr,"Type=%s\n",type);
break;
case 'i':
if (!strncmp(optarg,"prodos",6)) {
arg_interleave=1;
}
if (!strncmp(optarg,"dos33",5)) {
arg_interleave=2;
}
if (debug) fprintf(stderr,"Interleave=%d\n",arg_interleave);
break;
case 'l':
length=strtol(optarg,&endptr,0);
if (debug) fprintf(stderr,"Length=%d\n",address);
break;
case 'v':
display_help(argv[0],1);
return 0;
case 'h': display_help(argv[0],0);
return 0;
case 'x':
ignore_errors=1;
break;
case 'y':
always_yes=1;
break;
}
}
if (optind==argc) {
fprintf(stderr,"ERROR! Must specify disk image!\n\n");
return -1;
}
/* get argument 1, which is image name */
strncpy(image,argv[optind],BUFSIZ-1);
prodos_fd=open(image,O_RDWR);
if (prodos_fd<0) {
fprintf(stderr,"Error opening disk_image: %s\n",image);
return -1;
}
if (debug) {
printf("checking extension: %s\n",&image[strlen(image)-4]);
}
/* Try to autodetect interleave based on filename */
if (strlen(image)>4) {
if (!strncmp(&image[strlen(image)-4],".dsk",4)) {
if (debug) printf("Detected DOS33 interleave\n");
interleave=PRODOS_INTERLEAVE_DOS33;
}
/* FIXME: detect this based on magic number */
else if (!strncmp(&image[strlen(image)-4],".2mg",4)) {
char header[64];
int image_format;
read(prodos_fd,header,64);
image_offset= (header[24])|
(header[25]<<8)|
(header[26]<<16)|
(header[27]<<24);
image_format=(header[12])|
(header[13]<<8)|
(header[14]<<16)|
(header[15]<<24);
if (image_format==0) {
interleave=PRODOS_INTERLEAVE_DOS33;
}
else if (image_format==1) {
interleave=PRODOS_INTERLEAVE_PRODOS;
}
else {
fprintf(stderr,"Unsupported 2MG format\n");
return -1;
}
if (debug) {
char string[5];
printf("Detected 2MG format\n");
memcpy(string,header,4);
string[4]=0;
printf("magic: %s\n",string);
memcpy(string,header+4,4);
string[4]=0;
printf("creator: %s\n",string);
printf("Header size: %d\n",
(header[8]|(header[9]<<8)));
printf("Version: %d\n",
(header[10]|(header[11]<<8)));
printf("Flags: $%X\n",
(header[16])|
(header[17]<<8)|
(header[18]<<16)|
(header[19]<<24));
printf("ProDOS blocks: $%X\n",
(header[20])|
(header[21]<<8)|
(header[22]<<16)|
(header[23]<<24));
printf("Image offset: $%X\n",image_offset);
printf("Bytes of data: %d\n",
(header[28])|
(header[29]<<8)|
(header[30]<<16)|
(header[31]<<24));
printf("Offset to comment: $%X\n",
(header[32])|
(header[33]<<8)|
(header[34]<<16)|
(header[35]<<24));
printf("Length of comment: %d\n",
(header[36])|
(header[37]<<8)|
(header[38]<<16)|
(header[39]<<24));
printf("Offset to creator comment: $%X\n",
(header[40])|
(header[41]<<8)|
(header[42]<<16)|
(header[43]<<24));
printf("Length of creator comment: %d\n",
(header[44])|
(header[45]<<8)|
(header[46]<<16)|
(header[47]<<24));
}
}
}
/* override inteleave if set */
if (arg_interleave) {
interleave=arg_interleave-1;
}
prodos_read_voldir(prodos_fd,&voldir,interleave,image_offset);
/* Move to next argument */
optind++;
if (optind==argc) {
fprintf(stderr,"ERROR! Must specify command!\n\n");
return -2;
}
/* Grab command */
strncpy(temp_string,argv[optind],BUFSIZ-1);
/* Make command be uppercase */
for(i=0;i<strlen(temp_string);i++) {
temp_string[i]=toupper(temp_string[i]);
}
/* Move to next argument */
optind++;
command=lookup_command(temp_string);
switch(command) {
case COMMAND_UNKNOWN:
fprintf(stderr,"ERROR! Unknown command %s\n",temp_string);
fprintf(stderr,"\tTry \"%s -h\" for help.\n\n",argv[0]);
goto exit_and_close;
break;
/* Load a file from disk image to local machine */
case COMMAND_LOAD:
/* check and make sure we have apple_filename */
if (argc==optind) {
fprintf(stderr,"Error! Need apple file_name\n");
fprintf(stderr,"%s %s LOAD apple_filename\n",
argv[0],image);
goto exit_and_close;
}
truncate_filename(apple_filename,argv[optind]);
if (debug) printf("\tApple filename: %s\n",apple_filename);
/* get output filename */
optind++;
if (argc>optind) {
if (debug) printf("Using %s for filename\n",
local_filename);
strncpy(local_filename,argv[optind],BUFSIZ-1);
}
else {
if (debug) printf("Using %s for filename\n",
apple_filename);
strncpy(local_filename,apple_filename,31);
}
if (debug) printf("\tOutput filename: %s\n",local_filename);
/* get the voldir/entry for file */
inode=prodos_lookup_file(&voldir,apple_filename);
if (inode<0) {
fprintf(stderr,"Error! %s not found!\n",
apple_filename);
goto exit_and_close;
}
/* Load the file */
prodos_load_file(&voldir,inode,local_filename);
break;
case COMMAND_CATALOG:
prodos_catalog(prodos_fd,&voldir);
break;
case COMMAND_SAVE:
if (debug) printf("\ttype=%s\n",type);
if (argc==optind) {
fprintf(stderr,"Error! Need file_name\n");
fprintf(stderr,"%s %s SAVE "
"file_name apple_filename\n\n",
argv[0],image);
goto exit_and_close;
}
strncpy(local_filename,argv[optind],BUFSIZ-1);
optind++;
if (debug) printf("\tLocal filename: %s\n",local_filename);
if (argc>optind) {
/* apple filename specified */
truncate_filename(apple_filename,argv[optind]);
}
else {
/* If no filename specified for apple name */
/* Then use the input name. Note, we strip */
/* everything up to the last slash so useless */
/* path info isn't used */
temp=local_filename+(strlen(local_filename)-1);
while(temp!=local_filename) {
temp--;
if (*temp == '/') {
temp++;
break;
}
}
truncate_filename(apple_filename,temp);
}
if (debug) printf("\tApple filename: %s\n",apple_filename);
file_exists=prodos_check_file_exists(&voldir,apple_filename);
if (file_exists>=0) {
fprintf(stderr,"Warning! %s exists!\n",apple_filename);
if (!always_yes) {
printf("Over-write (y/n)?");
result_string=fgets(temp_string,BUFSIZ,stdin);
if ((result_string==NULL) || (temp_string[0]!='y')) {
printf("Exiting early...\n");
goto exit_and_close;
}
}
fprintf(stderr,"Deleting previous version...\n");
prodos_delete_file(&voldir,apple_filename);
}
prodos_add_file(&voldir,prodos_fd,type,
address, length,
local_filename,apple_filename);
break;
case COMMAND_DELETE:
if (argc==optind) {
fprintf(stderr,"Error! Need file_name\n");
fprintf(stderr,"%s %s DELETE apple_filename\n",
argv[0],image);
goto exit_and_close;
}
truncate_filename(apple_filename,argv[optind]);
file_exists=prodos_check_file_exists(&voldir,apple_filename);
if (file_exists<0) {
fprintf(stderr, "Error! File %s does not exist\n",
apple_filename);
goto exit_and_close;
}
prodos_delete_file(&voldir,apple_filename);
break;
case COMMAND_DUMP:
printf("Dumping %s!\n",image);
prodos_dump(&voldir,prodos_fd);
break;
case COMMAND_SHOWFREE:
printf("Showing Free %s!\n",image);
prodos_showfree(&voldir,prodos_fd);
break;
case COMMAND_RENAME:
/* check and make sure we have apple_filename */
if (argc==optind) {
fprintf(stderr,"Error! Need two filenames\n");
fprintf(stderr,"%s %s RENAME apple_filename_old "
"apple_filename_new\n",
argv[0],image);
goto exit_and_close;
}
/* Truncate filename if too long */
truncate_filename(apple_filename,argv[optind]);
optind++;
if (argc==optind) {
fprintf(stderr,"Error! Need two filenames\n");
fprintf(stderr,"%s %s RENAME apple_filename_old "
"apple_filename_new\n",
argv[0],image);
goto exit_and_close;
}
truncate_filename(new_filename,argv[optind]);
/* get the entry/track/sector for file */
file_exists=prodos_check_file_exists(&voldir,apple_filename);
if (file_exists<0) {
fprintf(stderr,"Error! %s not found!\n",
apple_filename);
goto exit_and_close;
}
prodos_rename_file(&voldir,apple_filename,new_filename);
break;
/* Change the volume name */
case COMMAND_VOLNAME:
/* check and make sure we have a volume name */
if (argc==optind) {
fprintf(stderr,"Error! Need apple volume_name\n");
fprintf(stderr,"%s %s VOLUME volume_name\n",
argv[0],image);
goto exit_and_close;
}
change_volume_name(&voldir,argv[optind]);
break;
default:
fprintf(stderr,"Sorry, unsupported command %s\n\n",temp_string);
goto exit_and_close;
}
exit_and_close:
close(prodos_fd);
return 0;
}