dos33fsprogs/utils/prodos-utils/prodos.c

1575 lines
36 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 <time.h>
#include "version.h"
#include "prodos.h"
static int ignore_errors=0;
int debug=0;
/* Given filename, return voldir/offset */
static int prodos_lookup_file(struct voldir_t *voldir,
int subdir_block,
char *filename) {
int voldir_block,voldir_offset;
struct file_entry_t file_entry;
unsigned char voldir_buffer[PRODOS_BYTES_PER_BLOCK];
int result,file;
if (debug) printf("\t*** Looking for %s in block $%X\n",filename,subdir_block);
voldir_block=subdir_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) continue;
if (debug) printf("\tTrying $%X = %s\n",
(voldir_block<<8)|file,
file_entry.file_name);
/* FIXME: case insensitive? */
if (!strncmp(filename,(char *)file_entry.file_name,15)) {
if (debug) printf("*** MATCH: %s = %s at inode $%X\n",
filename,file_entry.file_name,
(voldir_block<<8)|file);
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 dir_block fine an empty entry */
/* FIXME: allocate new voldir block if all full */
static int prodos_allocate_directory_entry(
struct voldir_t *voldir,int dir_block) {
int voldir_block,voldir_offset;
struct file_entry_t file_entry;
unsigned char voldir_buffer[PRODOS_BYTES_PER_BLOCK];
int result,file;
if (debug) printf("*** Looking for empty file entry in $%X\n",dir_block);
voldir_block=dir_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 (debug) printf("\tTrying $%X status %X\n",
(voldir_block<<8)|file,
file_entry.storage_type);
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,
int dir_block,
char *filename) {
int result;
result=prodos_lookup_file(voldir,dir_block,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
#define PRODOS_NUM_FILE_TYPES 5
static struct prodos_file_type {
int type;
char name[4];
} file_types[PRODOS_NUM_FILE_TYPES] = {
{PRODOS_TYPE_TXT,"TXT"},
{PRODOS_TYPE_BIN,"BIN"},
{PRODOS_TYPE_BAS,"BAS"},
{PRODOS_TYPE_VAR,"VAR"},
{PRODOS_TYPE_SYS,"SYS"},
};
/* creates file apple_filename on the image from local file filename */
/* returns ?? */
static int prodos_add_file(struct voldir_t *voldir,
int dir_block, char *type,
int address, int length,
char *filename, char *apple_filename) {
int free_blocks,file_size,needed_blocks,total_blocks,storage_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;
int file_type=0;
/* 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;
}
}
/* get file type */
for(i=0;i<PRODOS_NUM_FILE_TYPES;i++) {
if (!strncmp(type,file_types[i].name,3)) {
file_type=file_types[i].type;
if (debug) printf("Found type %s=%d\n",type,file_type);
}
}
/* 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");
storage_type=PRODOS_FILE_SEEDLING;
total_blocks=needed_blocks;
}
else if (needed_blocks<=256) {
/* sapling */
if (debug) printf("File sapling\n");
storage_type=PRODOS_FILE_SAPLING;
total_blocks=needed_blocks+1; /* for index block */
}
else if (needed_blocks<=65536) {
/* tree */
if (debug) printf("File tree\n");
storage_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 (storage_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 (storage_type==PRODOS_FILE_SAPLING) {
/* allocate index */
index=prodos_allocate_block(voldir);
key_block=index;
if (debug) printf("**** ALLOCATING SAPLING key_block=$%X\n",key_block);
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 (storage_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=storage_type;
file.name_length=strlen(apple_filename);
memcpy(file.file_name,apple_filename,file.name_length);
file.file_type=file_type;
file.key_pointer=key_block;
file.blocks_used=total_blocks; /* includes index blocks */
file.eof=file_size;
file.creation_time=prodos_time(time(NULL));
file.version=0;
file.min_version=0;
file.access=0xe3; // 0x21?
if (file.file_type==PRODOS_TYPE_BIN) {
file.aux_type=address;
}
else {
file.aux_type=0;
}
file.last_mod=prodos_time(time(NULL));
file.header_pointer=dir_block;
inode=prodos_allocate_directory_entry(voldir,dir_block);
if (inode<0) {
return inode;
}
if (debug) printf("Found inode $%x\n",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);
/* update file count */
if (debug) printf("Updating file count...\n");
voldir->file_count++;
prodos_sync_voldir(voldir);
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;
}
static int prodos_get_directory(struct voldir_t *voldir,char *string) {
char path[4096];
int pointer=0,path_pointer=0;
int inode,final_pointer=-1;
struct file_entry_t file;
int subdir_block=PRODOS_VOLDIR_KEY_BLOCK;
if (debug) printf("Looking up directory %s\n",string);
while(1) {
path_pointer=0;
/* see if leading '/' */
/* also handles the plain "/" case */
/* FIXME: eat multiple //// */
if (string[pointer]=='/') {
pointer++;
/* special case "/" */
if (string[pointer]==0) return subdir_block;
}
while(string[pointer]!=0) {
if (string[pointer]=='/') break;
path[path_pointer]=string[pointer];
pointer++;
path_pointer++;
}
path[path_pointer]=0; /* NUL terminate */
if (debug) printf("Found subdir %s\n",path);
/* get the voldir/entry for file */
inode=prodos_lookup_file(voldir,subdir_block,path);
if (inode<0) {
fprintf(stderr,"Error! %s not found!\n",
path);
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;
}
if (file.storage_type!=PRODOS_FILE_SUBDIR) {
fprintf(stderr,"Error! %s is not a subdir!\n",path);
return -1;
}
else {
final_pointer=file.key_pointer;
}
subdir_block=final_pointer;
if (string[pointer]==0) break;
}
return final_pointer;
}
/* 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,PRODOS_VOLDIR_KEY_BLOCK,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,
int dir_block,char *apple_filename) {
unsigned char data_buffer[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,mblock;
struct file_entry_t file;
int inode;
if (debug) printf("*** DELETING FILE %s\n",apple_filename);
/* get the voldir/entry for file */
inode=prodos_lookup_file(voldir,dir_block,apple_filename);
if (debug) printf("\t*** Found inode $%X for file %s\n",inode,apple_filename);
if (inode<0) {
fprintf(stderr,"Error! %s not found!\n",
apple_filename);
return -1;
}
result=prodos_read_block(voldir,data_buffer,inode>>8);
if (prodos_get_file_entry(voldir,inode,&file)<0) {
fprintf(stderr,"Error opening inode %x\n",inode);
return -1;
}
if (debug) printf("*** PERMANENTLY DELETING %s (inode: $%X)\n",
file.file_name,inode);
/******************************/
/* delete all the file blocks */
/******************************/
switch(file.storage_type) {
case PRODOS_FILE_SEEDLING:
/* Just a single block */
if (debug) fprintf(stderr,"*** SEEDLING Deleting block $%x\n",
file.key_pointer);
result=prodos_free_block(voldir,file.key_pointer);
if (result<0) {
return result;
}
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) printf("*** SAPLING "
"Freeing 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 */
}
else {
result=prodos_free_block(voldir,chunk_block);
if (result<0) {
return result;
}
}
}
result=prodos_free_block(voldir,file.key_pointer);
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,"*** TREE: "
"Deleting 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++) {
mblock=(master_index_block[index])|
(master_index_block[256+index]<<8);
result=prodos_read_block(voldir,
index_block,
mblock);
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_free_block(voldir,chunk_block);
if (result<0) {
return result;
}
}
result=prodos_free_block(voldir,mblock);
}
result=prodos_free_block(voldir,file.key_pointer);
break;
case PRODOS_FILE_SUBDIR:
case PRODOS_FILE_DELETED:
case PRODOS_FILE_SUBDIR_HDR:
case PRODOS_FILE_VOLUME_HDR:
default:
fprintf(stderr,"Error! "
"Cannot delete this type of file: %x\n",
file.storage_type);
break;
}
/* now that file is gone, disconnect the directory image */
/* should we clear it out? */
/* makes undelete harder */
/* ProDOS 1.0.1 clears filename but leaves rest */
// memset(&file,0,sizeof(struct file_entry_t));
file.storage_type=PRODOS_FILE_DELETED;
file.name_length=0;
memset(file.file_name,0,PRODOS_FILENAME_LEN);
/* 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);
/* update file count */
if (debug) printf("Updating file count...\n");
voldir->file_count--;
prodos_sync_voldir(voldir);
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_name]\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("\tMKDIR dir_name\n");
printf("\tRMDIR dir_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 COMMAND_MKDIR 8
#define COMMAND_RMDIR 9
#define MAX_COMMAND 10
#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"},
{COMMAND_MKDIR,"MKDIR"},
{COMMAND_RMDIR,"RMDIR"},
};
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 *path, char *in) {
int truncated=0;
int last_slash=0;
int i;
for(i=0;i<strlen(in);i++) {
if (in[i]=='/') last_slash=i+1;
}
strncpy(path,in,last_slash);
/* Truncate filename if too long */
if (strlen(in+last_slash)>PRODOS_FILENAME_LEN) {
fprintf(stderr,"Warning! Truncating %s to 15 chars\n",in+last_slash);
truncated=1;
}
strncpy(out,in+last_slash,PRODOS_FILENAME_LEN);
out[PRODOS_FILENAME_LEN]='\0';
return truncated;
}
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 apple_path[BUFSIZ];
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;
int dir_block;
/* 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_init_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,apple_path,argv[optind]);
if (debug) printf("\tApple filename: %s, path %s\n",
apple_filename,apple_path);
/* 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);
if (apple_path[0]==0) {
dir_block=PRODOS_VOLDIR_KEY_BLOCK;
}
else {
dir_block=prodos_get_directory(&voldir,apple_path);
if (dir_block<0) {
fprintf(stderr,"Error, couldn't open directory %s\n",argv[optind]);
return -1;
}
}
/* get the voldir/entry for file */
inode=prodos_lookup_file(&voldir,
dir_block,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:
if (argc>optind) {
dir_block=prodos_get_directory(&voldir,argv[optind]);
if (dir_block<0) {
fprintf(stderr,"Error, couldn't open directory %s\n",argv[optind]);
return -1;
}
prodos_catalog(&voldir,dir_block,argv[optind]);
}
else {
/* use root dir */
dir_block=PRODOS_VOLDIR_KEY_BLOCK;
prodos_catalog(&voldir,dir_block,NULL);
}
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,apple_path,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,apple_path,temp);
}
if (apple_path[0]==0) {
dir_block=PRODOS_VOLDIR_KEY_BLOCK;
}
else {
dir_block=prodos_get_directory(&voldir,apple_path);
if (dir_block<0) {
fprintf(stderr,"Error, couldn't open directory %s\n",argv[optind]);
return -1;
}
}
if (debug) printf("\tApple filename: %s, path: %s\n",
apple_filename,apple_path);
file_exists=prodos_check_file_exists(&voldir,
dir_block,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,dir_block,apple_filename);
}
prodos_add_file(&voldir,dir_block,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,apple_path,argv[optind]);
if (apple_path[0]==0) {
dir_block=PRODOS_VOLDIR_KEY_BLOCK;
}
else {
dir_block=prodos_get_directory(&voldir,apple_path);
if (dir_block<0) {
fprintf(stderr,"Error, couldn't open directory %s\n",argv[optind]);
return -1;
}
}
file_exists=prodos_check_file_exists(&voldir,
dir_block,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,dir_block,apple_filename);
break;
case COMMAND_DUMP:
printf("Dumping %s!\n",image);
prodos_dump(&voldir);
break;
case COMMAND_SHOWFREE:
printf("Showing Free %s!\n",image);
prodos_showfree(&voldir);
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,apple_path,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,apple_path,argv[optind]);
if (apple_path[0]==0) {
dir_block=PRODOS_VOLDIR_KEY_BLOCK;
}
else {
dir_block=prodos_get_directory(&voldir,apple_path);
if (dir_block<0) {
fprintf(stderr,"Error, couldn't open directory %s\n",argv[optind]);
return -1;
}
}
/* get the entry/track/sector for file */
file_exists=prodos_check_file_exists(&voldir,
dir_block,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;
}
prodos_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;
}