/*
* Copyright (C) 2008,2010,2012,2018,2019,2020,2021 Klaus Michael Indlekofer. All rights reserved.
*
* m.indlekofer@gmx.de
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
*/



#include "commoninclude.h"
#include "akaiutil_io.h"
#include "akaiutil.h"
#include "akaiutil_file.h"
#include "akaiutil_take.h"



/* disks */
struct disk_s disk[DISK_NUM_MAX]; /* disks, Note: one for each disk, system-wide */
u_int disk_num; /* number of disks */

/* partitions */
struct part_s part[PART_NUM_MAX]; /* partitions, Note: one for each partition, system-wide */
u_int part_num; /* number of partitions */

/* current directory */
struct disk_s *curdiskp; /* current disk pointer into disk[], NULL if system-level */
						 /* Note: unique, fixed mapping to each file */
struct part_s *curpartp; /* current partition pointer into part[], NULL if disk-level */
						 /* Note: unique, fixed mapping to each partition */
struct vol_s *curvolp; /* current volume pointer into curvol_buf, NULL if disk- or partition-level */
					   /* Note: neither fixed nor unique mapping to each volume!!! */
struct vol_s curvol_buf; /* current volume buffer, Note: only one buffer, might change volume identity!!! */

/* for operations with current directory, Note: one instance only, not recursive!!! */
struct disk_s *savecurdiskp;
struct part_s *savecurpartp;
struct vol_s *savecurvolp;
struct vol_s savecurvol_buf;
int savecurvolmodflag; /* modifier flag */
char savecurvolname[AKAI_NAME_LEN+1]; /* +1 for '\0' */
u_int savecurvolindex;

char dirnamebuf[DIRNAMEBUF_LEN+1]; /* +1 for '\0' */

/* filter tags */
u_char curfiltertag[AKAI_FILE_TAGNUM];



int
open_disk(char *name,int readonly,OFF64_T startoff,u_int pseudodisksize,u_int pseudodisknum)
{
	int fd;
#ifdef _VISUALCPP
	int fldrn;
	int fldrtype;
#endif /* _VISUALCPP */
	u_int disksize;
	u_int i;

	if (name==NULL){
		return -1;
	}
	if (disk_num>=DISK_NUM_MAX){
		return -1;
	}

	/* Note: pseudodisksize>AKAI_DISKSIZE_MAX is allowed */
	/* Note: pseudodisksize==0 is allowed and means: pseudo-disk size is not specified */

	if (pseudodisknum>PSEUDODISK_NUM_MAX){
		pseudodisknum=PSEUDODISK_NUM_MAX; /* limit */
	}
	/* Note: pseudodisknum==0 is allowed and means: max. number of pseudo-disks is not specified */

	fd=-1;

#ifdef _VISUALCPP
	fldrn=-1;
	fldrtype=FLDR_TYPE_FLH; /* XXX */
	/* check if name corresponds to floppy drive */
	if (strcasecmp(name,"floppyla:")==0){
		fldrn=0;
		fldrtype=FLDR_TYPE_FLL;
	}else if (strcasecmp(name,"floppylb:")==0){
		fldrn=1;
		fldrtype=FLDR_TYPE_FLL;
	}else if (strcasecmp(name,"floppyha:")==0){
		fldrn=0;
		fldrtype=FLDR_TYPE_FLH;
	}else if (strcasecmp(name,"floppyhb:")==0){
		fldrn=1;
		fldrtype=FLDR_TYPE_FLH;
	}
	if (fldrn>=0){ /* is floppy drive? */
		/* Note: ignore startoff,pseudodisksize,pseudodisknum if floppy drive */
		/* Note: fldr_open() does not consider readonly flag */
		if (fldr_open(fldrn,fldrtype)<0){
			PRINTF_ERR("disk%u: cannot open \"%s\"\n",disk_num,name);
			/* discard disk, keep old disk_num */
			return -1;
		}
		PRINTF_OUT("disk%u: \"%s\"\n",disk_num,name);

		disk[disk_num].index=disk_num; /* index */
		disk[disk_num].fd=-1; /* no file descriptor */
		disk[disk_num].fldrn=fldrn; /* floppy drive number */
		disk[disk_num].readonly=readonly;
		disk[disk_num].startoff=0; /* no start offset (Note: startoff is ignored by io_blks_direct() if floppy drive) */
		if (fldrtype==FLDR_TYPE_FLH){
			disk[disk_num].totsize=AKAI_FLH_SIZE*AKAI_FL_BLOCKSIZE; /* disk size in bytes */
			disk[disk_num].type=DISK_TYPE_FLH;
		}else{
			disk[disk_num].totsize=AKAI_FLL_SIZE*AKAI_FL_BLOCKSIZE; /* disk size in bytes */
			disk[disk_num].type=DISK_TYPE_FLL;
		}
		disk[disk_num].blksize=AKAI_FL_BLOCKSIZE; /* blocksize */
		/* disk size in blocks */
		disk[disk_num].bsize=disk[disk_num].totsize/disk[disk_num].blksize;

		disk_num++; /* found one */

		return 0;
	}else{
		fldrn=-1; /* no floppy drive */
	}
#endif /* _VISUALCPP */

	if (readonly){
		fd=OPEN(name,O_RDONLY|O_BINARY,0);
	}else{
		fd=OPEN(name,O_RDWR|O_BINARY,0666);
	}
	if (fd<0){
		PERROR("open");
		PRINTF_ERR("disk%u: cannot open \"%s\"\n",disk_num,name);
		/* discard disk, keep old disk_num */
		return -1;
	}
	for (i=0;(disk_num<DISK_NUM_MAX)&&(i<PSEUDODISK_NUM_MAX);){
		if ((pseudodisksize>0)&&(pseudodisknum>0)){ /* pseudo-disk size and max. number of pseudo-disks given? */
			/* take given pseudodisksize as usable disk size */
			/* Note: don't determine usable disk size of current pseudo-disk */
			/* Note: in particular useful for disk with bad blocks */
			disksize=pseudodisksize;
		}else{
			/* determine usable disk size of current pseudo-disk, use pseudodisksize as upper limit (if >0) */
			/* Note: possibly not useful for disk with bad blocks */
			disksize=akai_disksize(fd,startoff,pseudodisksize);
			if ((i>0)&&(disksize==0)){ /* not first pseudo-disk and empty? */
				break; /* done */
			}
		}
		if ((pseudodisksize>0)||(pseudodisknum>0)||(startoff>0)){
			PRINTF_OUT("disk%u: \"%s\" (start: byte 0x%02x%08x)\n",
				disk_num,
				name,
				(u_int)(startoff>>32),
				(u_int)(0xffffffff&startoff));
		}else{
			PRINTF_OUT("disk%u: \"%s\"\n",disk_num,name);
		}
		if (disksize>AKAI_DISKSIZE_MAX){
			disksize=AKAI_DISKSIZE_MAX; /* limit for usable disk size */
		}
		if ((pseudodisksize>0)&&(disksize<pseudodisksize)){
			PRINTF_OUT("disk%u: usable disk size is limited to %u KB\n",disk_num,disksize/1024);
		}

		disk[disk_num].index=disk_num; /* index */
		disk[disk_num].fd=fd;
#ifdef _VISUALCPP
		disk[disk_num].fldrn=-1; /* no floppy drive */
#endif /* _VISUALCPP */
		disk[disk_num].readonly=readonly;
		disk[disk_num].startoff=startoff; /* start offset in bytes */
		disk[disk_num].totsize=disksize; /* disk size in bytes */
		disk[disk_num].type=DISK_TYPE_HD; /* XXX first guess for type */
		disk[disk_num].blksize=AKAI_HD_BLOCKSIZE; /* XXX first guess for blocksize */
		/* disk size in blocks */
		disk[disk_num].bsize=disk[disk_num].totsize/disk[disk_num].blksize;

		disk_num++; /* found one */

		if ((pseudodisksize==0) /* pseudo-disk size not given? */
			&&((pseudodisknum==0)||(disksize==0))){ /* and: max. number of pseudo-disks not given or usable disk size==0? */
			break; /* done */
		}

		/* next pseudo-disk */
		i++;
		if ((pseudodisknum>0)&&(i>=pseudodisknum)){ /* max. number of pseudo-disks given and done? */
			break; /* done */
		}
		if (pseudodisksize>0){ /* pseudo-disk size given? */
			/* increment pseudo-disk start offset by pseudodisksize (which can be larger than the usable disk size) */
			startoff+=(OFF64_T)pseudodisksize;
		}else{
			/* pseudo-disk size is not given */
			/* increment pseudo-disk start offset by disksize (usable disk size, determined above) */
			/* Note: now, disksize>0 (see check above) */
			startoff+=disksize;
		}
	}

	return 0;
}



void
close_alldisks(void)
{
	int fd;
#ifdef _VISUALCPP
	int fldrn;
#endif /* _VISUALCPP */
	u_int i;

	while (disk_num>0){
		disk_num--;
		fd=disk[disk_num].fd;
		if (fd>=0){ /* opened? */
			/* close */
			CLOSE(disk[disk_num].fd);
			/* mark all disks with same fd as closed */
			for (i=0;i<=disk_num;i++){
				if (disk[i].fd==fd){
					disk[i].fd=-1; /* closed */
				}
			}
		}
#ifdef _VISUALCPP
		fldrn=disk[disk_num].fldrn;
		if ((fldrn>=0)&&(fldrn<FLDRNUM)&&(fldr_h[fldrn]!=INVALID_HANDLE_VALUE)){ /* floppy drive opened? */
			/* close */
			CloseHandle(fldr_h[fldrn]);
			fldr_h[fldrn]=INVALID_HANDLE_VALUE; /* closed */
		}
		disk[disk_num].fldrn=-1; /* no floppy drive */
#endif /* _VISUALCPP */
	}
}



/* determine usable disk size in bytes */
/* Note: possibly not useful for disk with bad blocks */
u_int
akai_disksize(int fd,OFF64_T startoff,u_int disksizemax)
{
	u_int off,siz;
	static u_char bbuf[AKAI_DISKSIZE_GRAN];

	if (fd<0){
		return 0;
	}

	/* Note: disksizemax==0 is allowed and means: max. usable disk size is not specified */
	if ((disksizemax==0)||(disksizemax>AKAI_DISKSIZE_MAX)){
		disksizemax=AKAI_DISKSIZE_MAX; /* limit for usable disk size */
	}
	/* XXX 32bit overflow protection for off<<=1 below */
	if (disksizemax>0x80000000){
		disksizemax=0x80000000;
	}
	/* determine start offset >= disksizemax, must be power of 2 */
	for (off=1;off<disksizemax;off<<=1)
		;
	siz=off;
	for (;;){
		siz>>=1;
		/* can read 1 block at off ? */
		if ((off>=disksizemax) /* beyond max. disk limit? */
			||(LSEEK64(fd,startoff+((OFF64_T)off),SEEK_SET)<0)
			||(READ(fd,bbuf,AKAI_DISKSIZE_GRAN)!=(int)AKAI_DISKSIZE_GRAN)){
			/* block at off is not readable -> off is too high */
#ifdef DEBUG
			PRINTF_ERR("akai_disksize: off=%08x siz=%08x high\n",off,siz);
#endif
			if (siz<AKAI_DISKSIZE_GRAN){
				if (off==0){ /* even block at 0 is not readable? */
					/* no blocks are readable */
					return 0; /* exit */
				}
				off-=AKAI_DISKSIZE_GRAN; /* block at off is not readable */
				/* Note: must check if block at new off is readable */
			}else{
				off-=siz;
			}
		}else{
			/* block at off is readable -> off is OK or too low */
#ifdef DEBUG
			PRINTF_ERR("akai_disksize: off=%08x siz=%08x low\n",off,siz);
#endif
			if (siz<AKAI_DISKSIZE_GRAN){
				break; /* end of iteration */
			}
			off+=siz;
		}
	}
	/* now, off is at last readable disk block */

	/* return value in bytes */
	if (off<AKAI_DISKSIZE_MAX){
		off+=AKAI_DISKSIZE_GRAN; /* !!! */
	}else{
		off=AKAI_DISKSIZE_MAX;
	}
	if (off>disksizemax){
		off=disksizemax;
	}
#ifdef DEBUG
	PRINTF_ERR("akai_disksize done: size=%08x (%u)\n",off,off);
#endif
	return off;
}



int
akai_io_blks(struct part_s *pp,u_char *buf,u_int bstart,u_int bsize,int cachealloc,int mode)
{

	if ((pp==NULL)||(pp->diskp==NULL)){
		return -1;
	}

	if ((pp->diskp->readonly)&&(mode==IO_BLKS_WRITE)){
		PRINTF_ERR("disk%u: read-only, cannot write\n",pp->diskp->index);
		return -1;
	}

	if (((bstart+bsize)>pp->bsize)
		||((pp->bstart+bstart+bsize)>pp->diskp->bsize)){
		PRINTF_ERR("disk%u: cannot %s outside partition/disk\n",
			pp->diskp->index,
			(mode==IO_BLKS_WRITE)?"write":"read");
		return -1;
	}

	return io_blks(pp->diskp->fd,
#ifdef _VISUALCPP
				   pp->diskp->fldrn,
#endif /* _VISUALCPP */
				   pp->diskp->startoff,
				   buf,
				   pp->bstart+bstart,
				   bsize,
				   pp->blksize,
				   cachealloc,mode);
}



/* open external file read-only */
int
akai_openreadonly_extfile(char *name)
{
	int fd;
	u_int nlen;
	char *nbuf;
	u_int k;

	if (name==NULL){
		return -1;
	}

	/* try given name */
	fd=OPEN(name,O_RDONLY|O_BINARY,0);
	if (fd>=0){ /* success? */
		return fd;
	}
	/* no success with name */

	/* try again with modified name: ' ' instead of '_' for keyboard entry */
	nlen=(u_int)strlen(name);
	if (nlen==0){
		return -1;
	}
	/* Note: use own file name buffer for (possible) modification, don't modify string in name */
	nbuf=(char *)malloc(nlen+1); /* +1 for '\0' */
	if (nbuf==NULL){
		PERROR("malloc");
		return -1;
	}
	for (k=0;k<nlen;k++){
		if (name[k]=='_'){
			nbuf[k]=' ';
		}else{
			nbuf[k]=name[k];
		}
	}
	nbuf[k]='\0';
	fd=OPEN(nbuf,O_RDONLY|O_BINARY,0);
	free(nbuf);
	return fd;
}



/* check external WAV file name (and modify given file name if required) */
/* Note: assumes that wavname may be modified */
int
akai_check_extwavname(char *wavname)
{
	u_int nlen;
	int fd;
	u_int k;

	if (wavname==NULL){
		return -1;
	}

	/* check if correct suffix in WAV file name */
	nlen=(u_int)strlen(wavname);
	if ((nlen<4)||(strncasecmp(wavname+nlen-4,".wav",4)!=0)){
		PRINTF_ERR("invalid WAV file name\n");
		return -1;
	}
#if 1
	/* wildcard characters are not allowed */
	for (k=0;k<nlen-4;k++){
		if ((wavname[k]=='*')||(wavname[k]=='?')){
			PRINTF_ERR("invalid WAV file name\n");
			return -1;
		}
	}
#endif
	/* check if external file with given file name can be opened */
	fd=OPEN(wavname,O_RDONLY|O_BINARY,0);
	if (fd<0){ /* no success? */
		/* modify name: ' ' instead of '_' for keyboard entry */
		/* Note: assume that string in wavname may be modified */
		for (k=0;k<nlen-4;k++){
			if (wavname[k]=='_'){
				wavname[k]=' ';
			}
		}
		/* check if external file with (possibly) modified file name can be opened */
		fd=OPEN(wavname,O_RDONLY|O_BINARY,0);
		if (fd<0){ /* no success? */
			PRINTF_ERR("cannot access WAV file\n");
			return -1;
		}
	}
	CLOSE(fd);

	return 0; /* OK */
}



/* count free and bad blocks in partition */
void
akai_countfree_part(struct part_s *pp)
{
	u_int i;
	u_int n;

	if (pp==NULL){
		return;
	}

	pp->bfree=0;
	pp->bbad=0;

	if ((!pp->valid)||(pp->fat==NULL)){
		return;
	}

	if (pp->type==PART_TYPE_DD){
		/* S1100/S3000 harddisk DD partition */
		/* Note: start at cluster 1 in order to skip reserved system cluster 0 which contains partition header */
		for (i=1;i<pp->csize;i++){
			n=(pp->fat[i][1]<<8)+pp->fat[i][0];
			if (n==AKAI_DDFAT_CODE_FREE){
				pp->bfree+=AKAI_DDPART_CBLKS; /* +1 cluster */
			}else if (n==AKAI_DDFAT_CODE_BAD){
				pp->bbad+=AKAI_DDPART_CBLKS; /* +1 cluster */
#if 1
			}else if (((n>=pp->bsize)||(n<pp->bsyssize))
					  /* Note: here, n!=AKAI_DDFAT_CODE_FREE, see check above */
					  &&(n!=AKAI_DDFAT_CODE_SYS)
					  &&(n!=AKAI_DDFAT_CODE_END)){ /* invalid? */
				pp->bbad+=AKAI_DDPART_CBLKS; /* +1 cluster */
#endif
			}
		}
		return;
	}

	/* Note: start at block pp->bsyssize in order to skip reserved system blocks */
	/*       => also avoids problem due to AKAI_FAT_CODE_SYS900FL==AKAI_FAT_CODE_FREE */
	for (i=pp->bsyssize;i<pp->bsize;i++){
		n=(pp->fat[i][1]<<8)+pp->fat[i][0];
		if (n==AKAI_FAT_CODE_FREE){
			pp->bfree++;
		}else if (n==AKAI_FAT_CODE_BAD){
			pp->bbad++;
#if 1
		}else if (((n>=pp->bsize)||(n<pp->bsyssize))
				  /* Note: here, n!=AKAI_FAT_CODE_FREE, see check above */
				  /* Note: here, n!=AKAI_FAT_CODE_BAD, see check above */
				  &&(n!=AKAI_FAT_CODE_SYS900FL) /* XXX same as AKAI_FAT_CODE_FREE */
				  &&(n!=AKAI_FAT_CODE_SYS900HD)
				  &&(n!=AKAI_FAT_CODE_SYS)
				  &&(n!=AKAI_FAT_CODE_DIREND900HD)
				  &&(n!=AKAI_FAT_CODE_DIREND1000HD)
				  &&(n!=AKAI_FAT_CODE_DIREND3000)
				  &&(n!=AKAI_FAT_CODE_FILEEND900)
				  &&(n!=AKAI_FAT_CODE_FILEEND)){ /* invalid? */
			pp->bbad++;
#endif
		}
	}
}



int
akai_check_fatblk(u_int blk,u_int bsize,u_int bsyssize)
{

	if (  (blk==AKAI_FAT_CODE_FREE)
		||(blk==AKAI_FAT_CODE_BAD)
		||(blk==AKAI_FAT_CODE_SYS900FL) /* XXX same as AKAI_FAT_CODE_FREE */
		||(blk==AKAI_FAT_CODE_SYS900HD)
		||(blk==AKAI_FAT_CODE_SYS)
		||(blk==AKAI_FAT_CODE_DIREND900HD)
		||(blk==AKAI_FAT_CODE_DIREND1000HD)
		||(blk==AKAI_FAT_CODE_DIREND3000)
		||(blk==AKAI_FAT_CODE_FILEEND900)
		||(blk==AKAI_FAT_CODE_FILEEND)
		||(blk>=bsize)
		||(blk<bsyssize)){
		return -1; /* special code or invalid */
	}

	return 0; /* OK */
}



int
print_fatchain(struct part_s *pp,u_int blk)
{
	u_int i;

	if ((pp==NULL)||(!pp->valid)||(pp->fat==NULL)){
		return -1;
	}
	if (pp->type==PART_TYPE_DD){
		return -1;
	}

	PRINTF_OUT("lblk    pblk\n");
	PRINTF_OUT("--------------\n");
	for (i=0;i<pp->bsize;i++){ /* XXX to avoid loop */
		if (akai_check_fatblk(blk,pp->bsize,pp->bsyssize)<0){
			break; /* end of chain */
		}
		PRINTF_OUT("0x%04x  0x%04x\n",i,blk);
		/* next block */
		blk=(pp->fat[blk][1]<<8)+pp->fat[blk][0];
	}
	PRINTF_OUT("--------------\n");
#if 0
	PRINTF_OUT("END=    0x%04x\n",blk);
#endif

	if (i==pp->bsize){ /* XXX stuck in loop? */
		return 1;
	}

	return 0;
}



/* Note: if writeflag==0, free block counter of partition is not updated!!! */
int
akai_free_fatchain(struct part_s *pp,u_int bstart,int writeflag)
{
	int ret;
	u_int fblk,nblk;
	u_int bc;
	u_int i;
	u_int hdsiz;

	if ((pp==NULL)||(!pp->valid)||(pp->fat==NULL)||(bstart>pp->bsize)){
		return -1;
	}
	if (pp->type==PART_TYPE_DD){
		return -1;
	}

	ret=0; /* OK so far */

	/* free FAT chain */
	fblk=bstart;
	bc=0; /* block counter */
	for (i=0;i<pp->bsize;i++){ /* XXX to avoid loop */
		/* check fblk */
		if ( /* Note: don't check for AKAI_FAT_CODE_SYS900FL here since it is ==AKAI_FAT_CODE_FREE !!! */
			  (fblk==AKAI_FAT_CODE_SYS900HD)
			||(fblk==AKAI_FAT_CODE_SYS)
			||(fblk==AKAI_FAT_CODE_DIREND900HD)
			||(fblk==AKAI_FAT_CODE_DIREND1000HD)
			||(fblk==AKAI_FAT_CODE_DIREND3000)
			||(fblk==AKAI_FAT_CODE_FILEEND900)
			||(fblk==AKAI_FAT_CODE_FILEEND)){ /* valid end of chain? */
			break; /* done */
		}
		if (  (fblk==AKAI_FAT_CODE_FREE)
			||(fblk==AKAI_FAT_CODE_BAD)
			||(fblk>=pp->bsize)){ /* invalid? */
			PRINTF_ERR("invalid block in chain\n");
			ret=-1;
			break;
		}
		/* next block */
		nblk=(pp->fat[fblk][1]<<8)+pp->fat[fblk][0];
		/* free block in FAT */
		pp->fat[fblk][1]=0xff&(AKAI_FAT_CODE_FREE>>8);
		pp->fat[fblk][0]=0xff&AKAI_FAT_CODE_FREE;
		bc++;
		/* advance */
		fblk=nblk;
	}

	if (writeflag){
		/* update free block counter */
		pp->bfree+=bc;

		/* write new FAT to partition */
		if ((pp->type==PART_TYPE_FLL)||(pp->type==PART_TYPE_FLH)){
			/* write floppy header */
			if (pp->type==PART_TYPE_FLL){
				hdsiz=AKAI_FLLHEAD_BLKS; /* floppy header */
			}else{
				hdsiz=AKAI_FLHHEAD_BLKS; /* floppy header */
			}
			if (akai_io_blks(pp,(u_char *)&pp->head.flh,
							 0,
							 hdsiz,
							 1,IO_BLKS_WRITE)<0){ /* 1: allocate cache if possible */
				return -1;
			}
		}else if (pp->type==PART_TYPE_HD9){
			/* copy first FAT entries */
			bcopy((u_char *)&pp->head.hd9.fatblk,(u_char *)&pp->head.hd9.fatblk0,AKAI_HD9FAT0_ENTRIES*2);
			/* write S900 harddisk header */
			if (akai_io_blks(pp,(u_char *)&pp->head.hd9,
							 0,
							 AKAI_HD9HEAD_BLKS,
							 1,IO_BLKS_WRITE)<0){ /* 1: allocate cache if possible */
				return -1;
			}
		}else if (pp->type==PART_TYPE_HD){
			/* write S1000/S3000 partition header */
			if (akai_io_blks(pp,(u_char *)&pp->head.hd,
							 0,
							 AKAI_PARTHEAD_BLKS,
							 1,IO_BLKS_WRITE)<0){ /* 1: allocate cache if possible */
				return -1;
			}
		}
	}

	return ret;
}



/* returns first block in *bstartp */
int
akai_allocate_fatchain(struct part_s *pp,u_int bsize,u_int *bstartp,u_int bcont0,u_int endcode)
{
	int ret;
	u_int fblk,pblk;
	u_int bc;
	u_int hdsiz;

	if ((pp==NULL)||(!pp->valid)||(pp->fat==NULL)){
		return -1;
	}
	if (pp->type==PART_TYPE_DD){
		return -1;
	}
	if (bstartp==NULL){
		return -1;
	}

	if (bcont0>bsize){ /* want more contiguous blocks than total? */
		return -1;
	}

	/* check if enough space left */
	if (bsize>pp->bfree){
		PRINTF_ERR("not enough space left\n");
		return -1;
	}
	/* now there should be enough free blocks if there was no bcont0 constraint */

	/* check code for end of chain */
	if ( /* Note: don't check for AKAI_FAT_CODE_SYS900FL here since it is ==AKAI_FAT_CODE_FREE !!! */
		  (endcode!=AKAI_FAT_CODE_SYS900HD)
		&&(endcode!=AKAI_FAT_CODE_SYS)
		&&(endcode!=AKAI_FAT_CODE_DIREND900HD)
		&&(endcode!=AKAI_FAT_CODE_DIREND1000HD)
		&&(endcode!=AKAI_FAT_CODE_DIREND3000)
		&&(endcode!=AKAI_FAT_CODE_FILEEND900)
		&&(endcode!=AKAI_FAT_CODE_FILEEND)){
		return -1;
	}

	if (bsize==0){
		return 0; /* done */
	}

	/* scan FAT */
	ret=0; /* no error so far */
	bc=0; /* block counter */
	*bstartp=pp->bsize; /* invalid */
	pblk=0;
	/* Note: start at block pp->bsyssize in order to skip reserved system blocks */
	/*       => also avoids problem due to AKAI_FAT_CODE_SYS900FL==AKAI_FAT_CODE_FREE */
	for (fblk=pp->bsyssize;fblk<pp->bsize;fblk++){ /* block */
		/* check if fblk is free */
		if (((pp->fat[fblk][1]<<8)+pp->fat[fblk][0])!=AKAI_FAT_CODE_FREE){
			continue; /* not free, next */
		}
		/* is free */

		/* allocate */
		if (bc==0){ /* is first free block? */
			/* start of chain */
			*bstartp=fblk;
		}else{
			if ((bcont0<=1)||(fblk==(pblk+1))){ /* don't care or contiguous to previous block? */
				/* link chain in previous free block */
				pp->fat[pblk][1]=0xff&(fblk>>8);
				pp->fat[pblk][0]=0xff&fblk;
			}else{
				/* we care and this is a non-contiguous block */
				if (bc<bcont0){ /* not enough so far? */
					/* must discard chain allocated so far */
					/* free chain */
					if (akai_free_fatchain(pp,*bstartp,0)<0){ /* 0: don't write FAT yet */
						ret=-1;
						goto akai_allocate_fatchain_done;
					}
					/* nothing anymore, new start */
					*bstartp=fblk;
					bc=0;
				}else{
					/* enough contiguous blocks, non-contiguous blocks may follow now */
					bcont0=0; /* don't care anymore */
				}
			}
		}
		bc++; /* found one */
		/* temporarily mark end of chain */
		pp->fat[fblk][1]=0xff&(endcode>>8);
		pp->fat[fblk][0]=0xff&endcode;

		if (bc==bsize){ /* enough? */
			/* keep end mark */
			break; /* done */
		}
		pblk=fblk; /* save for next element */
	}

	if (bc!=bsize){ /* not enough? */
		if (*bstartp<pp->bsize){ /* valid chain? */
			/* free chain */
			if (akai_free_fatchain(pp,*bstartp,0)<0){ /* 0: don't write FAT yet */
				ret=-1;
				goto akai_allocate_fatchain_done;
			}
		}
	}else{
		/* enough */
		/* update free block counter */
		pp->bfree-=bc;
	}

akai_allocate_fatchain_done:
	if (ret<0){
		/* XXX in case something went wrong */
		/* count free and bad blocks */
		akai_countfree_part(pp);
	}
	/* write new FAT to partition */
	if ((pp->type==PART_TYPE_FLL)||(pp->type==PART_TYPE_FLH)){
		/* write floppy header */
		if (pp->type==PART_TYPE_FLL){
			hdsiz=AKAI_FLLHEAD_BLKS; /* floppy header */
		}else{
			hdsiz=AKAI_FLHHEAD_BLKS; /* floppy header */
		}
		if (akai_io_blks(pp,(u_char *)&pp->head.flh,
						 0,
						 hdsiz,
						 1,IO_BLKS_WRITE)<0){ /* 1: allocate cache if possible */
			ret=-1;
		}
	}else if (pp->type==PART_TYPE_HD9){
		/* copy first FAT entries */
		bcopy((u_char *)&pp->head.hd9.fatblk,(u_char *)&pp->head.hd9.fatblk0,AKAI_HD9FAT0_ENTRIES*2);
		/* write S900 harddisk header */
		if (akai_io_blks(pp,(u_char *)&pp->head.hd9,
						 0,
						 AKAI_HD9HEAD_BLKS,
						 1,IO_BLKS_WRITE)<0){ /* 1: allocate cache if possible */
			ret=-1;
		}
	}else if (pp->type==PART_TYPE_HD){
		/* write S1000/S3000 partition header */
		if (akai_io_blks(pp,(u_char *)&pp->head.hd,
						 0,
						 AKAI_PARTHEAD_BLKS,
						 1,IO_BLKS_WRITE)<0){ /* 1: allocate cache if possible */
			ret=-1;
		}
	}

	return ret;
}



int
akai_scanbad(struct part_s *pp,int markflag)
{
	static u_char buf[AKAI_DDPART_CBLKS*AKAI_HD_BLOCKSIZE]; /* XXX enough for all partition types */
	u_int hdsiz;
	u_int i,n;
	int modifflag;

	if ((pp==NULL)||(!pp->valid)){
		return -1;
	}

	if (pp->type==PART_TYPE_DD){
		/* S1100/S3000 harddisk DD partition */
		/* scan for bad clusters */
		modifflag=0;
		for (i=0;i<pp->csize;i++){
			PRINTF_OUT("\rcluster 0x%04x",i);
			FLUSH_ALL;
			/* try to read cluster */
			if (akai_io_blks(pp,(u_char *)buf,
							 i*AKAI_DDPART_CBLKS, /* block offset */
							 AKAI_DDPART_CBLKS, /* 1 cluster */
							 0,IO_BLKS_READ)<0){ /* 0: don't alloc cache */
				/* cannot read cluster */
				if (i==0){ /* reserved cluster for system? */
					PRINTF_OUT(" bad (system)\n");
					/* XXX don't mark system cluster as bad */
				}else{
					n=(pp->fat[i][1]<<8)+pp->fat[i][0];
					if ((n==AKAI_DDFAT_CODE_FREE)||(n==AKAI_DDFAT_CODE_BAD)){
						PRINTF_OUT(" bad\n");
						if (markflag&&(n!=AKAI_DDFAT_CODE_BAD)){
							/* mark as bad cluster */
							pp->fat[i][1]=0xff&(AKAI_DDFAT_CODE_BAD>>8);
							pp->fat[i][0]=0xff&AKAI_DDFAT_CODE_BAD;
							modifflag=1;
						}
					}else{
						PRINTF_OUT(" bad (used)\n");
						/* XXX don't mark used cluster as bad */
					}
				}
				FLUSH_ALL;

				if (markflag&&modifflag){
					/* count free and bad blocks */
					akai_countfree_part(pp);

					/* write partition header */
					if (akai_io_blks(pp,(u_char *)&pp->head.dd,
									 0,
									 AKAI_DDPARTHEAD_BLKS,
									 1,IO_BLKS_WRITE)<0){ /* 1: allocate cache if possible */
						return -1;
					}
				}
			}
		}
		return 0;
	}

	/* scan for bad blocks */
	modifflag=0;
	for (i=0;i<pp->bsize;i++){
		PRINTF_OUT("\rblock 0x%04x",i);
		FLUSH_ALL;
#ifdef _VISUALCPP
		if (pp->diskp->fldrn>=0){ /* is floppy drive? */
			/* check if no floppy inserted */
			if (fldr_checkfloppyinserted(pp->diskp->fldrn)!=0){
				PRINTF_OUT("\nerror: no floppy inserted\n");
				FLUSH_ALL;
				return -1;
			}
		}
#endif /* _VISUALCPP */
		/* try to read block */
		if (akai_io_blks(pp,(u_char *)buf,
						 i,
						 1,
						 0,IO_BLKS_READ)<0){ /* 0: don't alloc cache */
			/* cannot read block */
			if (i<pp->bsyssize){ /* reserved block for system? */
				PRINTF_OUT(" bad (system)\n");
				/* XXX don't mark system block as bad */
			}else{
				n=(pp->fat[i][1]<<8)+pp->fat[i][0];
				if ((n==AKAI_FAT_CODE_FREE)||(n==AKAI_FAT_CODE_BAD)){
					PRINTF_OUT(" bad\n");
					if (markflag&&(n!=AKAI_FAT_CODE_BAD)){
						/* mark as bad block */
						pp->fat[i][1]=0xff&(AKAI_FAT_CODE_BAD>>8);
						pp->fat[i][0]=0xff&AKAI_FAT_CODE_BAD;
						modifflag=1;
					}
				}else{
					PRINTF_OUT(" bad (used)\n");
					/* XXX don't mark used block as bad */
				}
			}
			FLUSH_ALL;
		}
	}

	if (markflag&&modifflag){
		/* count free and bad blocks */
		akai_countfree_part(pp);

		/* write new FAT to partition */
		if ((pp->type==PART_TYPE_FLL)||(pp->type==PART_TYPE_FLH)){
			/* write floppy header */
			if (pp->type==PART_TYPE_FLL){
				hdsiz=AKAI_FLLHEAD_BLKS; /* floppy header */
			}else{
				hdsiz=AKAI_FLHHEAD_BLKS; /* floppy header */
			}
			if (akai_io_blks(pp,(u_char *)&pp->head.flh,
							 0,
							 hdsiz,
							 1,IO_BLKS_WRITE)<0){ /* 1: allocate cache if possible */
				return -1;
			}
		}else if (pp->type==PART_TYPE_HD9){
			/* copy first FAT entries */
			bcopy((u_char *)&pp->head.hd9.fatblk,(u_char *)&pp->head.hd9.fatblk0,AKAI_HD9FAT0_ENTRIES*2);
			/* write S900 harddisk header */
			if (akai_io_blks(pp,(u_char *)&pp->head.hd9,
							 0,
							 AKAI_HD9HEAD_BLKS,
							 1,IO_BLKS_WRITE)<0){ /* 1: allocate cache if possible */
				return -1;
			}
		}else if (pp->type==PART_TYPE_HD){
			/* write S1000/S3000 partition header */
			if (akai_io_blks(pp,(u_char *)&pp->head.hd,
							 0,
							 AKAI_PARTHEAD_BLKS,
							 1,IO_BLKS_WRITE)<0){ /* 1: allocate cache if possible */
				return -1;
			}
		}
	}

	return 0;
}



int
akai_read_file(int outfd,u_char *outbuf,struct file_s *fp,u_int begin,u_int end)
{
	u_char fbuf[AKAI_HD_BLOCKSIZE];
	u_int fblk,fchunk,fremain,skipbyte;
	u_char (*fatp)[2];
	int err;

	if ((outfd<0)&&(outbuf==NULL)){
		return -1;
	}

	if (fp==NULL){
		return -1;
	}
	if ((fp->volp==NULL)||(fp->volp->type==AKAI_VOL_TYPE_INACT)){
		return -1;
	}
	if ((fp->volp->partp==NULL)||(!fp->volp->partp->valid)){
		return -1;
	}
	fatp=fp->volp->partp->fat;
	if (fatp==NULL){
		return -1;
	}
	if ((fp->volp->partp->blksize==0)||(fp->volp->partp->blksize>AKAI_HD_BLOCKSIZE)){
		return -1;
	}

	if (fp->type==AKAI_FTYPE_FREE){
		return -1;
	}

	if ((end<begin)||(end>fp->size)){
		PRINTF_ERR("invalid limits\n");
		return -1;
	}

	fblk=fp->bstart; /* start block */
	fremain=end; /* remaining bytes */
	skipbyte=begin; /* bytes to skip */
	for (;fremain>0;){ /* byte counter */
		if (fremain>=fp->volp->partp->blksize){
			fchunk=fp->volp->partp->blksize;
		}else{
			fchunk=fremain;
		}
		/* check fblk */
		if (akai_check_fatblk(fblk,fp->volp->partp->bsize,fp->volp->partp->bsyssize)<0){
			PRINTF_ERR("invalid block in file\n");
			return -1;
		}
		if (skipbyte<fchunk){
			/* read block */
			if (akai_io_blks(fp->volp->partp,(u_char *)fbuf,
							 fblk,
							 1,
							 0,IO_BLKS_READ)<0){ /* 0: don't alloc cache */
				return -1;
			}
			if (outbuf!=NULL){
				/* to buffer */
				bcopy(fbuf+skipbyte,outbuf,fchunk-skipbyte);
				outbuf+=fchunk-skipbyte;
			}else{
				/* write to file */
				err=WRITE(outfd,(void *)(fbuf+skipbyte),fchunk-skipbyte);
				if (err<0){
					PERROR("write");
					return -1;
				}
				if (err!=(int)(fchunk-skipbyte)){
					PRINTF_ERR("write: incomplete\n");
					return -1;
				}
			}
			skipbyte=0; /* done */
		}else{
			skipbyte-=fchunk;
		}
		/* next block */
		fblk=(fatp[fblk][1]<<8)+fatp[fblk][0];
		/* chunk done */
		fremain-=fchunk;
	}

	return 0;
}



int
akai_write_file(int inpfd,u_char *inpbuf,struct file_s *fp,u_int begin,u_int end)
{
	u_char fbuf[AKAI_HD_BLOCKSIZE];
	u_int fblk,fchunk,fremain,skipbyte;
	u_char (*fatp)[2];
	int err;

	if ((inpfd<0)&&(inpbuf==NULL)){
		return -1;
	}

	if (fp==NULL){
		return -1;
	}
	if ((fp->volp==NULL)||(fp->volp->type==AKAI_VOL_TYPE_INACT)){
		return -1;
	}
	if ((fp->volp->partp==NULL)||(!fp->volp->partp->valid)){
		return -1;
	}
	fatp=fp->volp->partp->fat;
	if (fatp==NULL){
		return -1;
	}
	if ((fp->volp->partp->blksize==0)||(fp->volp->partp->blksize>AKAI_HD_BLOCKSIZE)){
		return -1;
	}

	if (fp->type==AKAI_FTYPE_FREE){
		return -1;
	}

	if ((end<begin)||(end>fp->size)){
		PRINTF_ERR("invalid limits\n");
		return -1;
	}

	fblk=fp->bstart; /* start block */
	fremain=end; /* remaining bytes */
	skipbyte=begin; /* bytes to skip */
	for (;fremain>0;){ /* byte counter */
		if (fremain>=fp->volp->partp->blksize){
			fchunk=fp->volp->partp->blksize;
		}else{
			fchunk=fremain;
		}
		/* check fblk */
		if (akai_check_fatblk(fblk,fp->volp->partp->bsize,fp->volp->partp->bsyssize)<0){
			PRINTF_ERR("invalid block in file\n");
			return -1;
		}
		if (skipbyte<fchunk){
			if ((skipbyte>0)||(fchunk<fp->volp->partp->blksize)){ /* need to read? */
				/* read block */
				if (akai_io_blks(fp->volp->partp,(u_char *)fbuf,
								 fblk,
								 1,
								 0,IO_BLKS_READ)<0){ /* 0: don't alloc cache */
					return -1;
				}
			}
			if (inpbuf!=NULL){
				/* from buffer */
				bcopy(inpbuf,fbuf+skipbyte,fchunk-skipbyte);
				inpbuf+=fchunk-skipbyte;
			}else{
				/* read file */
				err=READ(inpfd,(void *)(fbuf+skipbyte),fchunk-skipbyte);
				if (err<0){
					PERROR("read");
					return -1;
				}
				if (err!=(int)(fchunk-skipbyte)){
					PRINTF_ERR("read: incomplete\n");
					return -1;
				}
			}
			/* write block */
			if (akai_io_blks(fp->volp->partp,(u_char *)fbuf,
							 fblk,
							 1,
							 0,IO_BLKS_WRITE)<0){ /* 0: don't alloc cache */
				return -1;
			}
			skipbyte=0; /* done */
		}else{
			skipbyte-=fchunk;
		}
		/* next block */
		fblk=(fatp[fblk][1]<<8)+fatp[fblk][0];
		/* chunk done */
		fremain-=fchunk;
	}

	return 0;
}



int
print_ddfatchain(struct part_s *pp,u_int cstart)
{
	u_int i;
	u_int cl,nextcl;

	if ((pp==NULL)||(!pp->valid)||(pp->fat==NULL)){
		return -1;
	}
	if (pp->type!=PART_TYPE_DD){
		return -1;
	}
	if (cstart>=pp->csize){
		return -1;
	}

	PRINTF_OUT("lcl     pcl\n");
	PRINTF_OUT("--------------\n");
	cl=cstart;
	for (i=0;i<pp->csize;i++){ /* XXX to avoid loop */
		/* check cl */
		if ((cl==AKAI_DDFAT_CODE_FREE)
			||(cl==AKAI_DDFAT_CODE_BAD)
			||(cl==AKAI_DDFAT_CODE_SYS)
			||(cl==AKAI_DDFAT_CODE_END)
			||(cl>=pp->csize)){ /* invalid? */
			break; /* exit */
		}
		/* next cluster */
		nextcl=(pp->fat[cl][1]<<8)+pp->fat[cl][0];
		/* print */
		PRINTF_OUT("0x%04x  0x%04x\n",i,cl);
		/* advance */
		if (nextcl==AKAI_DDFAT_CODE_END){
			break; /* end of chain */
		}
		cl=nextcl;
	}
	PRINTF_OUT("--------------\n");
#if 0
	PRINTF_OUT("END=    0x%04x\n",cl);
#endif

	if (i==pp->csize){ /* XXX stuck in loop? */
		return 1;
	}

	return 0;
}



u_int
akai_count_ddfatchain(struct part_s *pp,u_int cstart)
{
	u_int cc;
	u_int i;
	u_int cl,nextcl;

	if ((pp==NULL)||(!pp->valid)||(pp->fat==NULL)){
		return 0;
	}
	if (pp->type!=PART_TYPE_DD){
		return 0;
	}
	if (cstart>=pp->csize){
		return 0;
	}

	cc=0;
	cl=cstart;
	for (i=0;i<pp->csize;i++){ /* XXX to avoid loop */
		/* check cl */
		if ((cl==AKAI_DDFAT_CODE_FREE)
			||(cl==AKAI_DDFAT_CODE_BAD)
			||(cl==AKAI_DDFAT_CODE_SYS)
			||(cl==AKAI_DDFAT_CODE_END)
			||(cl>=pp->csize)){ /* invalid? */
			break; /* exit */
		}
		/* next cluster */
		nextcl=(pp->fat[cl][1]<<8)+pp->fat[cl][0];
		cc++; /* +1 cluster */
		/* advance */
		if (nextcl==AKAI_DDFAT_CODE_END){
			break; /* end of chain, done */
		}
		cl=nextcl;
	}

	return cc;
}



/* Note: if writeflag==0, free block counter of partition is not updated!!! */
int
akai_free_ddfatchain(struct part_s *pp,u_int cstart,int writeflag)
{
	int ret;
	u_int cl,nextcl;
	u_int cc;
	u_int i;

	if ((pp==NULL)||(!pp->valid)||(pp->fat==NULL)||(cstart>pp->csize)){
		return -1;
	}
	if (pp->type!=PART_TYPE_DD){
		return -1;
	}

	ret=0; /* OK so far */

	/* free DD FAT chain */
	cl=cstart;
	cc=0; /* cluster counter */
	for (i=0;i<pp->csize;i++){ /* XXX to avoid loop */
		/* check cl */
		if ((cl==AKAI_DDFAT_CODE_FREE)
			||(cl==AKAI_DDFAT_CODE_BAD)
			||(cl==AKAI_DDFAT_CODE_SYS)
			||(cl==AKAI_DDFAT_CODE_END)
			||(cl>=pp->csize)){ /* invalid? */
			PRINTF_ERR("invalid cluster in chain\n");
			ret=-1;
			break;
		}
		/* next cluster */
		nextcl=(pp->fat[cl][1]<<8)+pp->fat[cl][0];
		/* free cluster in FAT */
		pp->fat[cl][1]=0xff&(AKAI_DDFAT_CODE_FREE>>8);
		pp->fat[cl][0]=0xff&AKAI_DDFAT_CODE_FREE;
		cc++;
		/* advance */
		if (nextcl==AKAI_DDFAT_CODE_END){
			break; /* end of chain, done */
		}
		cl=nextcl;
	}

	if (writeflag){
		/* update free block counter */
		pp->bfree+=cc*AKAI_DDPART_CBLKS;

		/* write partition header */
		if (akai_io_blks(pp,(u_char *)&pp->head.dd,
						 0,
						 AKAI_DDPARTHEAD_BLKS,
						 1,IO_BLKS_WRITE)<0){ /* 1: allocate cache if possible */
			return -1;
		}
	}

	return ret;
}



/* returns first cluster in *cstartp */
int
akai_allocate_ddfatchain(struct part_s *pp,u_int csize,u_int *cstartp,u_int ccont0)
{
	int ret;
	u_int fcl,pcl;
	u_int cc;

	if ((pp==NULL)||(!pp->valid)||(pp->fat==NULL)){
		return -1;
	}
	if (pp->type!=PART_TYPE_DD){
		return -1;
	}
	if (cstartp==NULL){
		return -1;
	}

	if (ccont0>csize){ /* want more contiguous clusters than total? */
		return -1;
	}

	/* check if enough space left */
	if (csize*AKAI_DDPART_CBLKS>pp->bfree){
		PRINTF_ERR("not enough space left\n");
		return -1;
	}
	/* now there should be enough free clusters if there was no ccont0 constraint */

	if (csize==0){
		return 0; /* done */
	}

	/* scan FAT */
	ret=0; /* no error so far */
	cc=0; /* cluster counter */
	*cstartp=pp->csize; /* invalid */
	pcl=0;
	/* Note: start at cluster 1 in order to skip reserved system cluster 0 which contains partition header */
	for (fcl=1;fcl<pp->csize;fcl++){ /* cluster */
		/* check if fcl is free */
		if (((pp->fat[fcl][1]<<8)+pp->fat[fcl][0])!=AKAI_DDFAT_CODE_FREE){
			continue; /* not free, next */
		}
		/* is free */

		/* allocate */
		if (cc==0){ /* is first free cluster? */
			/* start of chain */
			*cstartp=fcl;
		}else{
			if ((ccont0<=1)||(fcl==(pcl+1))){ /* don't care or contiguous to previous cluster? */
				/* link chain in previous free cluster */
				pp->fat[pcl][1]=0xff&(fcl>>8);
				pp->fat[pcl][0]=0xff&fcl;
			}else{
				/* we care and this is a non-contiguous cluster */
				if (cc<ccont0){ /* not enough so far? */
					/* must discard chain allocated so far */
					/* free chain */
					if (akai_free_ddfatchain(pp,*cstartp,0)<0){ /* 0: don't write FAT yet */
						ret=-1;
						goto akai_allocate_ddfatchain_done;
					}
					/* nothing anymore, new start */
					*cstartp=fcl;
					cc=0;
				}else{
					/* enough contiguous clusters, non-contiguous clusters may follow now */
					ccont0=0; /* don't care anymore */
				}
			}
		}
		cc++; /* found one */
		/* temporarily mark end of chain */
		pp->fat[fcl][1]=0xff&(AKAI_DDFAT_CODE_END>>8);
		pp->fat[fcl][0]=0xff&AKAI_DDFAT_CODE_END;

		if (cc==csize){ /* enough? */
			/* keep end mark */
			break; /* done */
		}
		pcl=fcl; /* save for next element */
	}

	if (cc!=csize){ /* not enough? */
		if (*cstartp<pp->csize){ /* valid chain? */
			/* free chain */
			if (akai_free_ddfatchain(pp,*cstartp,0)<0){ /* 0: don't write FAT yet */
				ret=-1;
				goto akai_allocate_ddfatchain_done;
			}
		}
	}else{
		/* enough */
		/* update free block counter */
		pp->bfree-=cc*AKAI_DDPART_CBLKS;
	}

akai_allocate_ddfatchain_done:
	if (ret<0){
		/* XXX in case something went wrong */
		/* count free and bad blocks */
		akai_countfree_part(pp);
	}
	/* write partition header */
	if (akai_io_blks(pp,(u_char *)&pp->head.dd,
					 0,
					 AKAI_DDPARTHEAD_BLKS,
					 1,IO_BLKS_WRITE)<0){ /* 1: allocate cache if possible */
		ret=-1;
	}

	return ret;
}



int
akai_export_ddfatchain(struct part_s *pp,u_int cstart,u_int bstart,u_int bsize,int outfd,u_char *outbuf)
{
	static u_char buf[AKAI_DDPART_CBLKS*AKAI_HD_BLOCKSIZE];
	u_int bufsiz;
	u_int i,i0,i1;
	u_int cl,nextcl;
	u_int chunkoff,chunksiz;
	int err;

	if ((pp==NULL)||(!pp->valid)||(pp->fat==NULL)){
		return -1;
	}
	if (pp->type!=PART_TYPE_DD){
		return -1;
	}
	if (cstart>=pp->csize){
		return -1;
	}
	if ((outfd<0)&&(outbuf==NULL)){
		return -1;
	}

	if (bsize==0){
		return 0;
	}

	bufsiz=AKAI_DDPART_CBLKS*AKAI_HD_BLOCKSIZE; /* 1 cluster in bytes */

	i0=bstart/bufsiz; /* first cluster for export */
	i1=(bstart+bsize-1)/bufsiz; /* last cluster for export */
	if (i1>=pp->csize){
		return -1;
	}

	cl=cstart;
	for (i=0;i<=i1;i++){
		/* check cl */
		if ((cl==AKAI_DDFAT_CODE_FREE)
			||(cl==AKAI_DDFAT_CODE_BAD)
			||(cl==AKAI_DDFAT_CODE_SYS)
			||(cl==AKAI_DDFAT_CODE_END)
			||(cl>=pp->csize)){ /* invalid? */
			return -1;
		}
		/* next cluster */
		nextcl=(pp->fat[cl][1]<<8)+pp->fat[cl][0];

		if (i>=i0){ /* far enough? */
			/* read cluster */
			if (akai_io_blks(pp,buf,
							 cl*AKAI_DDPART_CBLKS, /* block offset */
							 AKAI_DDPART_CBLKS, /* 1 cluster */
							 1,IO_BLKS_READ)<0){  /* 1: alloc cache if possible */
				return -1;
			}

			if (i==i0){ /* first cluster? */
				chunkoff=bstart-i0*bufsiz;
				chunksiz=bufsiz-chunkoff;
				/* Note: chunkoff and chunksiz <= bufsiz by definition of i0 */
				if (chunksiz>bsize){
					chunksiz=bsize;
				}
			}else if (i==i1){ /* last cluster? */
				chunkoff=0;
				chunksiz=bstart+bsize-i1*bufsiz;
				/* Note: chunksiz <= bufsiz by definition of i1 */
			}else{
				chunkoff=0;
				chunksiz=bufsiz; /* 1 cluster */
			}

			if (outbuf!=NULL){
				/* to buffer */
				bcopy(buf+chunkoff,outbuf,chunksiz);
				outbuf+=chunksiz;
			}else{
				/* write to file */
				err=WRITE(outfd,buf+chunkoff,chunksiz);
				if (err<0){
					PERROR("write");
					return -1;
				}
				if (err!=(int)chunksiz){
					PRINTF_ERR("write: incomplete\n");
					return -1;
				}
			}
		}

		/* advance */
		if (nextcl==AKAI_DDFAT_CODE_END){
			break; /* end of chain, done */
		}
		cl=nextcl;
	}

	return 0;
}



int
akai_import_ddfatchain(struct part_s *pp,u_int cstart,u_int bstart,u_int bsize,int inpfd,u_char *inpbuf)
{
	static u_char buf[AKAI_DDPART_CBLKS*AKAI_HD_BLOCKSIZE];
	u_int bufsiz;
	u_int i,i0,i1;
	u_int cl,nextcl;
	u_int chunkoff,chunksiz;
	int readflag;
	int err;

	if ((pp==NULL)||(!pp->valid)||(pp->fat==NULL)){
		return -1;
	}
	if (pp->type!=PART_TYPE_DD){
		return -1;
	}
	if (cstart>=pp->csize){
		return -1;
	}
	if ((inpfd<0)&&(inpbuf==NULL)){
		return -1;
	}

	if (bsize==0){
		return 0;
	}

	bufsiz=AKAI_DDPART_CBLKS*AKAI_HD_BLOCKSIZE; /* 1 cluster in bytes */

	i0=bstart/bufsiz; /* first cluster for export */
	i1=(bstart+bsize-1)/bufsiz; /* last cluster for export */
	if (i1>=pp->csize){
		return -1;
	}

	cl=cstart;
	for (i=0;i<=i1;i++){
		/* check cl */
		if ((cl==AKAI_DDFAT_CODE_FREE)
			||(cl==AKAI_DDFAT_CODE_BAD)
			||(cl==AKAI_DDFAT_CODE_SYS)
			||(cl==AKAI_DDFAT_CODE_END)
			||(cl>=pp->csize)){ /* invalid? */
			return -1;
		}
		/* next cluster */
		nextcl=(pp->fat[cl][1]<<8)+pp->fat[cl][0];

		if (i>=i0){ /* far enough? */
			if (i==i0){ /* first cluster? */
				chunkoff=bstart-i0*bufsiz;
				chunksiz=bufsiz-chunkoff;
				/* Note: chunkoff and chunksiz <= bufsiz by definition of i0 */
				if (chunksiz>bsize){
					chunksiz=bsize;
				}
				readflag=1;
			}else if (i==i1){ /* last cluster? */
				chunkoff=0;
				chunksiz=bstart+bsize-i1*bufsiz;
				/* Note: chunksiz <= bufsiz by definition of i1 */
				readflag=1;
			}else{
				chunkoff=0;
				chunksiz=bufsiz; /* 1 cluster */
				readflag=0;
			}

			if (readflag){
				/* read cluster */
				if (akai_io_blks(pp,buf,
								 cl*AKAI_DDPART_CBLKS, /* block offset */
								 AKAI_DDPART_CBLKS, /* 1 cluster */
								 1,IO_BLKS_READ)<0){  /* 1: alloc cache if possible */
					return -1;
				}
			}

			if (inpbuf!=NULL){
				/* from buffer */
				bcopy(inpbuf,buf+chunkoff,chunksiz);
				inpbuf+=chunksiz;
			}else{
				/* read from file */
				err=READ(inpfd,buf+chunkoff,chunksiz);
				if (err<0){
					PERROR("read");
					return -1;
				}
				if (err!=(int)chunksiz){
					PRINTF_ERR("read: incomplete\n");
					return -1;
				}
			}

			/* write cluster */
			if (akai_io_blks(pp,buf,
							 cl*AKAI_DDPART_CBLKS, /* block offset */
							 AKAI_DDPART_CBLKS, /* 1 cluster */
							 1,IO_BLKS_WRITE)<0){  /* 1: alloc cache if possible */
				return -1;
			}
		}

		/* advance */
		if (nextcl==AKAI_DDFAT_CODE_END){
			break; /* end of chain, done */
		}
		cl=nextcl;
	}

	return 0;
}



/* convert char for S1000/S3000 */
char
akai2ascii(u_char c)
{
	char a;

	if (c<=9){
		a='0'+c-0;
	}else if (c==10){
		a=' ';
	}else if ((c>=11)&&(c<=36)){
		a='A'+c-11;
	}else if (c==37){
		a='#';
	}else if (c==38){
		a='+';
	}else if (c==39){
		a='-';
	}else if (c==40){
		a='.';
	}else{
		a='.'; /* XXX */
	}

	return a;
}

/* convert char for S900 */
char
akai2ascii900(u_char c)
{
	char a;

	if (((c>='0')&&(c<='9'))
		||(c==' ')
		||((c>='A')&&(c<='Z'))
		||((c>='a')&&(c<='z'))
		||(c=='#')
		||(c=='+')
		||(c=='-')
		||(c=='.')){
		a=c;
	}else if (c=='\0'){
		a=' '; /* XXX */
	}else{
		a='.'; /* XXX */
	}

	return a;
}

void
akai2ascii_name(u_char *aname,char *name,int s900flag)
{
	int i;
	int len;

	if (name==NULL){
		return;
	}
	if (aname==NULL){
		name[0]='\0';
		return;
	}

	if (s900flag){
		len=AKAI_NAME_LEN_S900;
	}else{
		len=AKAI_NAME_LEN;
	}
	for (i=0;i<len;i++){
		if (s900flag){
#if 1
			if (aname[i]=='\0'){ /* end? */
				len=i;
				break; /* done */
			}
#endif
			name[i]=akai2ascii900(aname[i]);
		}else{
			name[i]=akai2ascii(aname[i]);
		}
	}
	name[len]='\0';

	/* remove trailing ' ' */
	for (i=0;i<len;i++){
		if (name[len-i-1]==' '){
			name[len-i-1]='\0';
		}else{
			break;
		}
	}
}

void
akai2ascii_filename(u_char *aname,u_char ft,u_int osver,char *name,int s900flag)
{
	int l;

	if (name==NULL){
		return;
	}

	/* Note: aname==NULL is also OK */
	if (aname==NULL){
		name[0]='\0';
	}else{
		akai2ascii_name(aname,name,s900flag);
	}

	/* type */
	l=(int)strlen(name);
	if (ft==AKAI_SAMPLE900_FTYPE){
		/* S900 sample file */
		if (osver!=0){
			/* S900 compressed sample format */
			sprintf(name+l,".S9C");
		}else{
			/* S900 non-compressed sample format */
			sprintf(name+l,".S9");
		}
	}else if (ft==AKAI_CDSETUP3000_FTYPE){
		/* CD3000 CD-ROM setup file */
		/* Note: not ".T9" */
		sprintf(name+l,".CD");
	}else if (ft==AKAI_CDSAMPLE3000_FTYPE){
		/* CD3000 CD-ROM sample parameters file */
		/* Note: not ".H3" */
		sprintf(name+l,".s+");
	}else if ((ft>=AKAI_S900_FTYPE_MIN)&&(ft<=AKAI_S900_FTYPE_MAX)){
		/* S900 */
		/* Note: 'T'==AKAI_CDSETUP3000_FTYPE was handled above */
		sprintf(name+l,".%c9",(char)('A'+ft-AKAI_S900_FTYPE_MIN));
	}else if ((ft>=AKAI_S1000_FTYPE_MIN)&&(ft<=AKAI_S1000_FTYPE_MAX)){
		/* S1000 */
#if 1
		if ((ft=='p')||(ft=='s')){
			sprintf(name+l,".%c1",(char)('A'+ft-AKAI_S1000_FTYPE_MIN));
		}else
#endif
		{
			sprintf(name+l,".%c",(char)('A'+ft-AKAI_S1000_FTYPE_MIN));
		}
	}else if ((ft>=(u_char)AKAI_S3000_FTYPE_MIN)&&(ft<=(u_char)AKAI_S3000_FTYPE_MAX)){
		/* S3000 */
		/* Note: 'h'+0x80==AKAI_CDSAMPLE3000_FTYPE was handled above */
		sprintf(name+l,".%c3",(char)('A'+ft-AKAI_S3000_FTYPE_MIN));
	}else{
		/* unknown */
		sprintf(name+l,".x%02x",(u_int)ft);
	}
}

/* convert char for S1000/S3000 */
u_char
ascii2akai(char a)
{
	char c;

	if (a=='_'){ /* for keyboard entry */
		a=' ';
	}

	if ((a>='0')&&(a<='9')){
		c=a-'0';
	}else if (a==' '){
		c=10;
	}else if ((a>='A')&&(a<='Z')){
		c=11+a-'A';
	}else if ((a>='a')&&(a<='z')){
		c=11+a-'a';
	}else if (a=='#'){
		c=37;
	}else if (a=='+'){
		c=38;
	}else if (a=='-'){
		c=39;
	}else if (a=='.'){
		c=40;
	}else{
		c=40; /* XXX */
	}

	return c;
}

/* convert char for S900 */
u_char
ascii2akai900(char a)
{
	char c;

	if (a=='_'){ /* for keyboard entry */
		a=' ';
	}

	if (((a>='0')&&(a<='9'))
		||(a==' ')
		||((a>='A')&&(a<='Z'))
		||((a>='a')&&(a<='z'))
		||(a=='#')
		||(a=='+')
		||(a=='-')
		||(a=='.')){
		c=a;
	}else{
		c='.'; /* XXX */
	}

	return c;
}

void
ascii2akai_name(char *name,u_char *aname,int s900flag)
{
	int i;
	u_char blank;
	int len;

	if (aname==NULL){
		return;
	}

	/* fill with default ' ' */
	if (s900flag){
		blank=ascii2akai900(' ');
		len=AKAI_NAME_LEN_S900;
	}else{
		blank=ascii2akai(' ');
		len=AKAI_NAME_LEN;
	}
	for (i=0;i<len;i++){
		if ((name==NULL)||(name[i]=='\0')){ /* nothing or end? */
			aname[i]=blank; /* default */
			name=NULL; /* don't want to go beyond end */
		}else{
			if (s900flag){
				aname[i]=ascii2akai900(name[i]);
			}else{
				aname[i]=ascii2akai(name[i]);
			}
		}
	}
}

u_char
ascii2akai_filename(char *name,u_char *aname,u_int *osverp,int s900flag)
{
	char *tn;
	u_int tl;
	u_int i,l;
	u_int u;
	u_char ft;
	char namebuf[AKAI_NAME_LEN+4+1]; /* +4 for ".<type>", +1 for '\0' */

	if ((aname==NULL)||(osverp==NULL)){
		return 0; /* XXX */
	}

	l=(u_int)strlen(name);
	/* check prior to strcpy */
	if (l>AKAI_NAME_LEN+4){ /* too long for namebuf? */
		/* invalid length */
		return AKAI_FTYPE_FREE;
	}

	/* copy name for possible manipulation below */
	strcpy(namebuf,name);

	/* check length and suffix */
	if ((l>=2)&&(l<=AKAI_NAME_LEN+2)&&(namebuf[l-2]=='.')){
		namebuf[l-2]='\0'; /* separate */
		tn=&namebuf[l-1]; /* type field */
		tl=1; /* type length */
	}else if ((l>=3)&&(l<=AKAI_NAME_LEN+3)&&(namebuf[l-3]=='.')){
		namebuf[l-3]='\0'; /* separate */
		tn=&namebuf[l-2]; /* type field */
		tl=2; /* type length */
	}else if ((l>=4)&&(l<=AKAI_NAME_LEN+4)&&(namebuf[l-4]=='.')){
		namebuf[l-4]='\0'; /* separate */
		tn=&namebuf[l-3]; /* type field */
		tl=3; /* type length */
	}else{
		/* no valid suffix */
		return AKAI_FTYPE_FREE;
	}
	l=(u_int)strlen(namebuf);
	/* remove trailing ' ' or '_' (XXX don't care how many) */
	for (i=0;i<l;i++){
		if ((namebuf[l-i-1]==' ')||(namebuf[l-i-1]=='_')){
			namebuf[l-i-1]='\0';
		}else{
			break;
		}
	}
	/* check name length */
	l=(u_int)strlen(namebuf);
	if (l>(u_int)(s900flag?AKAI_NAME_LEN_S900:AKAI_NAME_LEN)){
		/* invalid length */
		return AKAI_FTYPE_FREE;
	}

	/* type */
	if ((tl==2)&&(strcasecmp(tn,"S9")==0)){
		/* S900 sample file, non-compressed sample format */
		ft=AKAI_SAMPLE900_FTYPE;
		/* Note: set osver, even if not S900 volume */
		*osverp=0; /* non-compressed */
	}else if ((tl==3)&&(strcasecmp(tn,"S9C")==0)){
		/* S900 sample file, compressed sample format */
		ft=AKAI_SAMPLE900_FTYPE;
		/* Note: check/set osver, even if not S900 volume */
		if (*osverp==0){ /* unsuitable osver? */
			*osverp=1; /* XXX non zero */
		}
	}else if ((tl==2)&&(strcasecmp(tn,"CD")==0)){
		/* CD3000 CD-ROM setup file */
		/* Note: AKAI_CDSETUP3000_FTYPE=='T' => ".T9" will also lead to this type */
		ft=AKAI_CDSETUP3000_FTYPE;
		if (s900flag){
			*osverp=0; /* non-compressed */
		}
	}else if ((tl==2)&&(strcasecmp(tn,"s+")==0)){
		/* CD3000 CD-ROM sample parameters file */
		/* Note: AKAI_CDSAMPLE3000_FTYPE=='h'+0x80 => ".H3" will also lead to this type */
		ft=AKAI_CDSAMPLE3000_FTYPE;
		if (s900flag){
			*osverp=0; /* non-compressed */
		}
	}else if ((tl==1)||(tl==2)){
		ft=(u_char)tn[0];
		if ((ft>='A')&&(ft<='Z')){
			ft+='a'-'A'; /* to lower case */
		}
		if ((ft<'a')||(ft>'z')){
			/* invalid suffix */
			ft=AKAI_FTYPE_FREE;
		}
		if ((tl==1)||(tn[1]=='1')){
			/* S1000 */
			ft=AKAI_S1000_FTYPE_MIN+ft-'a';
		}else if (tn[1]=='9'){
			/* S900 */
			ft=AKAI_S900_FTYPE_MIN+ft-'a';
			*osverp=0; /* non-compressed */
		}else if (tn[1]=='3'){
			/* S3000 */
			ft=AKAI_S3000_FTYPE_MIN+ft-'a';
		}else{
			/* invalid suffix */
			ft=AKAI_FTYPE_FREE;
		}
		if (s900flag){
			*osverp=0; /* non-compressed */
		}
	}else{ /* must be tl==3 now */
		if (((tn[0]!='X')&&(tn[0]!='x'))
			||
			(((tn[1]<'0')||(tn[1]>'9'))
			 &&((tn[1]<'A')||(tn[1]>'F'))
			 &&((tn[1]<'a')||(tn[1]>'f')))
			||
			(((tn[2]<'0')||(tn[2]>'9'))
			 &&((tn[2]<'A')||(tn[2]>'F'))
			 &&((tn[2]<'a')||(tn[2]>'f')))){
			/* invalid suffix */
			ft=AKAI_FTYPE_FREE;
		}else{
			u=AKAI_FTYPE_FREE; /* default: invalid */
			sscanf(tn+1,"%x",&u);
			/* Note: if sscanf finds nothing, u remains! */
			ft=(u_char)u;
			/* Note: might still be invalid */
		}
		if (s900flag){
			*osverp=0; /* non-compressed */
		}
	}

	/* name */
	ascii2akai_name(namebuf,aname,s900flag);

#if 1
	if (ft==AKAI_VOLDIR3000FL_FTYPE){ /* S3000 floppy flag? */
		ft=AKAI_FTYPE_FREE; /* invalid */
	}
#endif

	/* Note: caller must check for AKAI_FTYPE_FREE as invalid code */
	return ft;
}



void
akai_vol_info(struct vol_s *vp,u_int ai,int verbose)
{

	if ((vp==NULL)||(vp->type==AKAI_VOL_TYPE_INACT)
		||(vp->partp==NULL)||(!vp->partp->valid)){
		return;
	}

	if (verbose){
#if 1
		if ((vp->partp->type==PART_TYPE_FLL)||(vp->partp->type==PART_TYPE_FLH)){ /* floppy? */
			/* for floppy, print also some info about partition: */
			/* Note: floppy has only one volume within one partition which covers the whole disk */
			PRINTF_OUT("parttype:    ");
			if (vp->partp->type==PART_TYPE_FLL){
				PRINTF_OUT("low-density floppy");
			}else{
				PRINTF_OUT("high-density floppy");
			}
			PRINTF_OUT("\nblksize:     0x%04x bytes\n",vp->partp->blksize);
			PRINTF_OUT("size:        0x%04x blocks (%6u KB)\n",
				vp->partp->bsize,
				(vp->partp->bsize*vp->partp->blksize)/1024); /* in KB */
			PRINTF_OUT("free:        0x%04x blocks (%6u KB) = %5.1lf%%\n",
				vp->partp->bfree,
				(vp->partp->bfree*vp->partp->blksize)/1024, /* in KB */
				(vp->partp->bsize>0)?(100.0*((double)vp->partp->bfree)/((double)vp->partp->bsize)):0.0);
			PRINTF_OUT("bad:         0x%04x blocks (%6u KB) = %5.1lf%%\n",
				vp->partp->bbad,
				(vp->partp->bbad*vp->partp->blksize)/1024, /* in KB */
				(vp->partp->bsize>0)?(100.0*((double)vp->partp->bbad)/((double)vp->partp->bsize)):0.0);
		}
#endif
		PRINTF_OUT("vnr:         %u\n",vp->index+1);
		PRINTF_OUT("vname:       \"%s\"\n",vp->name);
		if (vp->partp->type==PART_TYPE_HD9){
			if (ai>0){
				PRINTF_OUT("alias:       V%u\n",ai);
			}
		}else if (vp->type!=AKAI_VOL_TYPE_S900){
			PRINTF_OUT("load number: ");
			akai_print_lnum(vp->lnum);
			PRINTF_OUT("\n");
		}
		PRINTF_OUT("voltype:     ");
	}else{
		PRINTF_OUT("%3u   %-12s   ",vp->index+1,vp->name);
		if (vp->partp->type==PART_TYPE_HD9){
			PRINTF_OUT("V%-3u",ai);
		}else{
			akai_print_lnum(vp->lnum);
		}
		PRINTF_OUT("    0x%04x  ",vp->dirblk[0]);
		if (vp->type!=AKAI_VOL_TYPE_S900){
			PRINTF_OUT("%2u.%02u  ",0xff&(vp->osver>>8),0xff&vp->osver);
		}else{
			PRINTF_OUT(" -     ");
		}
	}
	switch (vp->type){
	case AKAI_VOL_TYPE_INACT:
		PRINTF_OUT("INACT");
		break;
	case AKAI_VOL_TYPE_S1000:
		PRINTF_OUT("S1000");
		break;
	case AKAI_VOL_TYPE_S3000:
		PRINTF_OUT("S3000");
		break;
	case AKAI_VOL_TYPE_CD3000:
		PRINTF_OUT("CD3000");
		break;
	case AKAI_VOL_TYPE_S900:
		PRINTF_OUT("S900");
		break;
	default:
		PRINTF_OUT("(%02x)",vp->type);
		break;
	}
	PRINTF_OUT("\n");
	if (verbose){
		if (vp->type!=AKAI_VOL_TYPE_S900){
			PRINTF_OUT("osver:       %u.%02u\n",0xff&(vp->osver>>8),0xff&vp->osver);
		}
		PRINTF_OUT("start:       block 0x%04x\n",vp->dirblk[0]);
		PRINTF_OUT("max. files:  %u\n",vp->fimax);
	}
}

void
akai_list_vol(struct vol_s *vp,u_char *filtertagp)
{
	u_int fi;
	struct file_s tmpfile; /* current file */
	u_int totf,totb;

	if ((vp==NULL)||(vp->type==AKAI_VOL_TYPE_INACT)
		||(vp->partp==NULL)||(!vp->partp->valid)
		||(vp->partp->diskp==NULL)){
		return;
	}

	PRINTF_OUT("/disk%u/%c/%s\n",vp->partp->diskp->index,vp->partp->letter,vp->name);
	if (vp->type==AKAI_VOL_TYPE_S900){
		PRINTF_OUT("fnr  fname               size/B  startblk  uncompr\n");
		PRINTF_OUT("--------------------------------------------------\n");
	}else{
		PRINTF_OUT("fnr  fname               size/B  startblk  osver  tags\n");
		PRINTF_OUT("--------------------------------------------------------------\n");
	}

	/* files in volume directory */
	totf=0;
	totb=0;
	for (fi=0;fi<vp->fimax;fi++){
		/* get file */
		if (akai_get_file(vp,&tmpfile,fi)<0){
			continue; /* next file */
		}
		/* test if tag-filter matches */
		if (akai_match_filetags(filtertagp,tmpfile.tag)<0){ /* no match? */
			continue; /* next file */
		}
		totf++;
		totb+=tmpfile.size;
		/* print file info */
		akai_file_info(&tmpfile,0);
	}

	if (vp->type==AKAI_VOL_TYPE_S900){
		PRINTF_OUT("--------------------------------------------------\n");
	}else{
		PRINTF_OUT("--------------------------------------------------------------\n");
	}
	PRINTF_OUT("total: %3u file(s) (max. %3u), %9u bytes\n\n",totf,vp->fimax,totb);
}



int
akai_read_voldir(struct vol_s *vp)
{
	u_int i,imax;
	u_int blk;
	u_char *addr;

	if ((vp==NULL)||(vp->type==AKAI_VOL_TYPE_INACT)||(vp->file==NULL)){
		return -1;
	}
	if ((vp->partp==NULL)||(!vp->partp->valid)||(vp->partp->blksize==0)){
		return -1;
	}

	/* number of blocks */
	imax=vp->fimax*sizeof(struct akai_voldir_entry_s);
	imax=(imax+vp->partp->blksize-1)/vp->partp->blksize; /* round up */
	if (imax>VOL_DIRBLKS){
		return -1;
	}

	/* Note: first file starts at byte 0 in first block */
	addr=(u_char *)vp->file;
	for (i=0;i<imax;i++){
		blk=vp->dirblk[i];
#ifdef DEBUG
		PRINTF_OUT("read dir block %2u: 0x%04x\n",i,blk);
#endif
		/* read volume directory block */
		if (akai_io_blks(vp->partp,addr,
						 blk,
						 1,
						 1,IO_BLKS_READ)<0){ /* 1: alloc cache if possible */
			return -1;
		}

		addr+=vp->partp->blksize; /* next */
	}

	return 0;
}

	
	
int
akai_write_voldir(struct vol_s *vp,u_int fi)
{
	u_int blk0,blk1;
	u_int blk;
	u_char *addr;

	if ((vp==NULL)||(vp->type==AKAI_VOL_TYPE_INACT)||(vp->file==NULL)){
		return -1;
	}
	if ((vp->partp==NULL)||(!vp->partp->valid)||(vp->partp->blksize==0)){
		return -1;
	}

	if (fi>=vp->fimax){
		return -1;
	}

	/* Note: file entry might overlap two adjacent blocks !!! */
	/* block(s) for file fi */
	blk0=fi*sizeof(struct akai_voldir_entry_s);
	blk1=blk0+sizeof(struct akai_voldir_entry_s)-1;
	blk0/=vp->partp->blksize;
	blk1/=vp->partp->blksize;
	if ((blk0>=VOL_DIRBLKS)||(blk1>=VOL_DIRBLKS)){
		return -1;
	}

	/* write volume directory block 0 to partition */
	blk=vp->dirblk[blk0];
	/* Note: first file starts at byte 0 in first block */
	addr=((u_char *)vp->file)+blk0*vp->partp->blksize;
#ifdef DEBUG
	PRINTF_OUT("write dir block %2u: 0x%04x\n",blk0,blk);
#endif
	if (akai_io_blks(vp->partp,addr,
					 blk,
					 1,
					 1,IO_BLKS_WRITE)<0){ /* 1: allocate cache if possible */
		return -1;
	}
	if (blk1!=blk0){
		/* write volume directory block 1 to partition */
		blk=vp->dirblk[blk1];
		addr+=vp->partp->blksize; /* next */
#ifdef DEBUG
		PRINTF_OUT("write dir block %2u: 0x%04x\n",blk1,blk);
#endif
		if (akai_io_blks(vp->partp,addr,
						 blk,
						 1,
						 1,IO_BLKS_WRITE)<0){ /* 1: allocate cache if possible */
			return -1;
		}
	}

	return 0;
}



/* Note: see comment in akaiutil.h for struct vol_s about copying vol_s!!! */
void
akai_copy_structvol(struct vol_s *srcvp,struct vol_s *dstvp)
{

	if ((srcvp==NULL)||(srcvp->partp==NULL)||(!srcvp->partp->valid)){
		return;
	}
	if (dstvp==NULL){
		return;
	}

	/* copy */
	*dstvp=*srcvp;

	/* fix pointers for copy to be self-contained */
	if ((srcvp->partp->type==PART_TYPE_FLL)||(srcvp->partp->type==PART_TYPE_FLH)){
		/* floppy */
		if ((srcvp->type==AKAI_VOL_TYPE_S3000)||(srcvp->type==AKAI_VOL_TYPE_CD3000)){
			/* S3000 */
			/* start of files */
			dstvp->file=&dstvp->dir.s3000fl.file[0];
		}
		/* Note: pointers into floppy header (in partition) remain the same!!! */
	}else if ((srcvp->partp->type==PART_TYPE_HD9)||(srcvp->partp->type==PART_TYPE_HD)){
		/* harddisk */
		if (srcvp->type==AKAI_VOL_TYPE_S900){
			/* S900 */
			/* start of files */
			dstvp->file=&dstvp->dir.s900hd.file[0];
			/* volume parameters */
			dstvp->param=NULL; /* S900 has no volume parameters */
		}else if (srcvp->type==AKAI_VOL_TYPE_S1000){
			/* S1000 */
			/* start of files */
			dstvp->file=&dstvp->dir.s1000hd.file[0];
			/* volume parameters */
			dstvp->param=&dstvp->dir.s1000hd.param;
		}else{
			/* S3000 */
			/* start of files */
			dstvp->file=&dstvp->dir.s3000hd.file[0];
			/* volume parameters */
			dstvp->param=&dstvp->dir.s3000hd.param;
		}
	}
}



/* get volume with index vi */
/* saves result in *vp */
int
akai_get_vol(struct part_s *pp,struct vol_s *vp,u_int vi)
{
	u_int i,j;
	struct akai_flvol_label_s *lp;

	if ((pp==NULL)||(!pp->valid)||(pp->fat==NULL)){
		return -1;
	}
	if (pp->type==PART_TYPE_DD){
		return -1;
	}
	if (vp==NULL){
		return -1;
	}

	/* in partition */
	vp->partp=pp;

	if ((pp->type==PART_TYPE_FLL)||(pp->type==PART_TYPE_FLH)){
		/* S900/S1000/S3000 floppy */
	
		/* fake index */
		if (vi!=0){
			return -1;
		}
		vp->index=vi;

		/* Note: low- and high-density floppy headers are identical */
		/*       up to first AKAI_FAT_ENTRIES_FLL FAT-entries */

		/* floppy volume label */
		if (pp->type==PART_TYPE_FLL){
			lp=&pp->head.fll.label;
		}else{
			lp=&pp->head.flh.label;
		}

		/* load number */
		vp->lnum=AKAI_VOL_LNUM_OFF;

		/* OS version in floppy header */
		vp->osver=(lp->osver[1]<<8)+lp->osver[0];

		/* determine floppy volume type */
		if (pp->head.flh.file[0].type==AKAI_VOLDIR3000FL_FTYPE){ /* S3000 flag? */
			/* S3000 */

			/* volume type */
			vp->type=AKAI_VOL_TYPE_S3000;

			/* number of volume directory entries */
			vp->fimax=AKAI_VOLDIR_ENTRIES_S3000FL;
			/* start of files */
			vp->file=&vp->dir.s3000fl.file[0];
			/* set volume directory blocks */
			if (pp->type==PART_TYPE_FLL){
				j=AKAI_VOLDIR3000FLL_BSTART;
			}else{
				j=AKAI_VOLDIR3000FLH_BSTART;
			}
			for (i=0;i<AKAI_VOLDIR3000FL_BLKS;i++){
				vp->dirblk[i]=j+i;
			}
		}else{
			/* S900 or S1000 */

			/* determine volume type */
			if (vp->osver==AKAI_OSVER_S900VOL){
				/* S900 */
				vp->type=AKAI_VOL_TYPE_S900;
			}else{
				/* S1000 */
				vp->type=AKAI_VOL_TYPE_S1000;
			}

			/* number of volume directory entries */
			vp->fimax=AKAI_VOLDIR_ENTRIES_S1000FL;
			/* start of files */
			/* Note: see comment above: start of files is the same for low- and high density */
			/* Note: S900 and S1000 floppy volume directory is within floppy header */
			vp->file=&pp->head.flh.file[0]; /* Note: in floppy header */
			/* set volume directory blocks */
			if (pp->type==PART_TYPE_FLL){
				j=AKAI_FLLHEAD_BLKS;
			}else{
				j=AKAI_FLHHEAD_BLKS;
			}
			for (i=0;i<j;i++){
				vp->dirblk[i]=i; /* start at offset 0 */
			}
		}
		
		/* name in floppy header */
		akai2ascii_name(lp->name,vp->name,vp->type==AKAI_VOL_TYPE_S900);
		if (vp->name[0]=='\0'){ /* empty volume name? */
			/* use default volume name as fake volume name */
			sprintf(vp->name,"VOLUME %03u",vp->index+1);
		}

		/* volume parameters */
		if (vp->type==AKAI_VOL_TYPE_S900){
			/* S900 */
			vp->param=NULL; /* S900 has no volume parameters */
		}else{
			/* S1000 or S3000 */
			vp->param=&lp->param; /* Note: in floppy header */
		}

		if (vp->type==AKAI_VOL_TYPE_S3000){
			/* S3000 */
			/* read volume directory */
			if (akai_read_voldir(vp)<0){
				PRINTF_ERR("volume %u: cannot read volume directory\n",vp->index+1);
				vp->type=AKAI_VOL_TYPE_INACT; /* XXX mark invalid */
				return -1;
			}
		} /* else: S900 and S1000 floppy volume directory is within floppy header */

		return 0;
	}

	if (pp->type==PART_TYPE_HD9){
		/* S900 harddisk */

		/* index */
		if (vi>=pp->volnummax){
			return -1;
		}
		vp->index=vi;

		/* get volume infos from root directory entry for volume */
		
		/* volume type */
		vp->type=AKAI_VOL_TYPE_S900;
		
		/* load number */
		vp->lnum=AKAI_VOL_LNUM_OFF;
		
		/* OS version */
		/* Note: no floppy volume labels for volumes on harddisk => cannot get OS version */
		vp->osver=AKAI_OSVER_S900VOL;

		/* name */
		/* Note: volume names in root directory use S1000/S3000 char conversion */
		akai2ascii_name(vp->partp->head.hd9.vol[vp->index].name,vp->name,1); /* 1: S900 */
		if (vp->name[0]=='\0'){ /* empty volume name? */
			/* use default volume name as fake volume name */
			sprintf(vp->name,"VOLUME %03u",vp->index+1);
		}

		/* volume directory block */
		vp->dirblk[0]=(vp->partp->head.hd9.vol[vp->index].start[1]<<8)
			+vp->partp->head.hd9.vol[vp->index].start[0];
		if (vp->dirblk[0]==AKAI_VOL_START_INACT){ /* volume inactive? */
			vp->type=AKAI_VOL_TYPE_INACT;
			return -1;
		}
		/* check vp->dirblk[0] */
		if (akai_check_fatblk(vp->dirblk[0],vp->partp->bsize,vp->partp->bsyssize)<0){
			PRINTF_ERR("volume %u: invalid dir block 0: 0x%04x\n",vp->index+1,vp->dirblk[0]);
			vp->type=AKAI_VOL_TYPE_INACT; /* XXX mark invalid */
			return -1;
		}
		/* number of volume directory entries */
		vp->fimax=AKAI_VOLDIR_ENTRIES_S900HD;
		/* start of files */
		vp->file=&vp->dir.s900hd.file[0];
		
		/* volume parameters */
		vp->param=NULL; /* S900 has no volume parameters */
		
		/* read volume directory */
		if (akai_read_voldir(vp)<0){
			PRINTF_ERR("volume %u: cannot read volume directory\n",vp->index+1);
			vp->type=AKAI_VOL_TYPE_INACT; /* XXX mark invalid */
			return -1;
		}
		
		return 0;
	}

	if (pp->type==PART_TYPE_HD){
		/* S1000/S3000 harddisk sampler partition */

		/* index */
		if (vi>=pp->volnummax){
			return -1;
		}
		vp->index=vi;

		/* get volume infos from root directory entry for volume */
		
		/* volume type */
		vp->type=vp->partp->head.hd.vol[vp->index].type;
		
		/* load number */
		vp->lnum=vp->partp->head.hd.vol[vp->index].lnum;
		
		/* OS version */
		/* Note: no floppy volume labels for volumes on harddisk => cannot get OS version */
		/* derive OS version from volume type in root directory entry */
		if ((vp->type==AKAI_VOL_TYPE_S3000)||(vp->type==AKAI_VOL_TYPE_CD3000)){
			vp->osver=AKAI_OSVER_S3000MAX; /* XXX */
		}else{
			/* XXX assume S1000 */
			vp->osver=AKAI_OSVER_S1000MAX; /* XXX */
		}

		/* name */
		/* Note: volume names in root directory entries use S1000/S3000 char conversion */
		akai2ascii_name(vp->partp->head.hd.vol[vp->index].name,vp->name,0); /* 0: not S900 */
		if (vp->name[0]=='\0'){ /* empty volume name? */
			/* use default volume name as fake volume name */
			sprintf(vp->name,"VOLUME %03u",vp->index+1);
		}

		switch (vp->type){
		case AKAI_VOL_TYPE_INACT:
			return -1; /* invalid */
		case AKAI_VOL_TYPE_S1000:
		case AKAI_VOL_TYPE_S3000:
		case AKAI_VOL_TYPE_CD3000:
			break;
		default:
#if 0
			/* XXX assume S1000 */
			vp->type=AKAI_VOL_TYPE_S1000;
			break;
#else
			PRINTF_ERR("volume %u: unknown volume type (0x%04x)\n",vp->index+1,vp->type);
			vp->type=AKAI_VOL_TYPE_INACT; /* XXX mark inactive */
			return -1;
#endif
		}
		
		/* volume directory block(s) */
		vp->dirblk[0]=(vp->partp->head.hd.vol[vp->index].start[1]<<8)
			+vp->partp->head.hd.vol[vp->index].start[0];
		/* check vp->dirblk[0] */
		if (akai_check_fatblk(vp->dirblk[0],vp->partp->bsize,vp->partp->bsyssize)<0){
			PRINTF_ERR("volume %u: invalid dir block 0: 0x%04x\n",vp->index+1,vp->dirblk[0]);
			vp->type=AKAI_VOL_TYPE_INACT; /* XXX mark invalid */
			return -1;
		}
		/* volume directory entries */
		if (vp->type==AKAI_VOL_TYPE_S1000){
			/* S1000 */
			/* number of volume directory entries */
			vp->fimax=AKAI_VOLDIR_ENTRIES_S1000HD;
			/* start of files */
			vp->file=&vp->dir.s1000hd.file[0];
		}else{
			/* should be S3000 */
			vp->dirblk[1]=(vp->partp->fat[vp->dirblk[0]][1]<<8)
				+vp->partp->fat[vp->dirblk[0]][0];
			/* check vp->dirblk[1] */
			if (akai_check_fatblk(vp->dirblk[1],vp->partp->bsize,vp->partp->bsyssize)<0){ /* block 1 not found? */
				/* Note: S3000 format should always have block 1 */
#if 1
				PRINTF_ERR("volume %u: S3000 volume directory block 1 missing, assuming S1000 volume\n",vp->index+1);
				/* XXX assume S1000 */
				vp->type=AKAI_VOL_TYPE_S1000;
				/* number of volume directory entries */
				vp->fimax=AKAI_VOLDIR_ENTRIES_S1000HD;
				/* start of files */
				vp->file=&vp->dir.s1000hd.file[0];
#else
				PRINTF_ERR("volume %u: S3000 volume directory block 1 missing\n",vp->index+1);
				vp->type=AKAI_VOL_TYPE_INACT; /* XXX mark invalid */
				return -1;
#endif
			}else{
				/* S3000 */
				/* vp->dirblk[1] should be volume directory block 1 now */
				/* number of volume directory entries */
				vp->fimax=AKAI_VOLDIR_ENTRIES_S3000HD; /* block 0 and 1 */
				/* start of files */
				vp->file=&vp->dir.s3000hd.file[0];
			}
		}
		
		/* volume parameters */
		if (vp->type==AKAI_VOL_TYPE_S1000){
			/* S1000 harddisk */
			vp->param=&vp->dir.s1000hd.param;
		}else{
			/* S3000 harddisk */
			vp->param=&vp->dir.s3000hd.param;
		}
		
		/* read volume directory */
		if (akai_read_voldir(vp)<0){
			PRINTF_ERR("volume %u: cannot read volume directory\n",vp->index+1);
			vp->type=AKAI_VOL_TYPE_INACT; /* XXX mark invalid */
			return -1;
		}
		
		return 0;
	}

	return -1;
}



/* find first volume with given name */
/* saves result in *vp */
int
akai_find_vol(struct part_s *pp,struct vol_s *vp,char *name)
{
	u_int osver;
	u_int vi;
	u_int ai,aivifound;
	char namebuf[AKAI_NAME_LEN+1]; /* +1 for '\0' */
	char vnamebuf[AKAI_NAME_LEN+1]; /* +1 for '\0' */
	u_int i,j,l;

	if ((pp==NULL)||(!pp->valid)){
		return -1;
	}
	if (pp->type==PART_TYPE_DD){
		return -1;
	}
	if (vp==NULL){
		return -1;
	}
	if (name==NULL){
		return -1;
	}

	/* fix name */
	l=(u_int)strlen(name);
	if (l>AKAI_NAME_LEN){ /* too long for namebuf? */
		return -1;
	}
	for (i=0;i<l;i++){
		if (name[i]=='_'){ /* for keyboard entry */
			namebuf[i]=' ';
		}else{
			namebuf[i]=name[i];
		}
	}
	namebuf[i]='\0';
	/* remove trailing ' ' (XXX don't care how many) */
	for (i=0;i<l;i++){
		if (namebuf[l-i-1]==' '){
			namebuf[l-i-1]='\0';
		}else{
			break;
		}
	}

	/* volumes in partition */
	aivifound=pp->volnummax; /* invalid */
	ai=0;
	for (vi=0;vi<pp->volnummax;vi++){
		if (pp->type==PART_TYPE_FLL){
			/* OS version in floppy header */
			osver=(pp->head.fll.label.osver[1]<<8)+pp->head.fll.label.osver[0];
			/* volume name in floppy header */
			akai2ascii_name(pp->head.fll.label.name,vnamebuf,osver==AKAI_OSVER_S900VOL);
		}else if (pp->type==PART_TYPE_FLH){
			/* OS version in floppy header */
			osver=(pp->head.flh.label.osver[1]<<8)+pp->head.flh.label.osver[0];
			/* volume name in floppy header */
			akai2ascii_name(pp->head.flh.label.name,vnamebuf,osver==AKAI_OSVER_S900VOL);
		}else if (pp->type==PART_TYPE_HD9){
			/* S900 harddisk */
			j=vp->dirblk[0]=(pp->head.hd9.vol[vi].start[1]<<8)
				+pp->head.hd9.vol[vi].start[0];
			if (j==AKAI_VOL_START_INACT){ /* inactive? */
				continue; /* next */
			}
			/* volume name in root directory entry */
			akai2ascii_name(pp->head.hd9.vol[vi].name,vnamebuf,1); /* 1: S900 */
		}else if (pp->type==PART_TYPE_HD){
			/* S1000/S3000 harddisk sampler partition */
			if (pp->head.hd.vol[vi].type==AKAI_VOL_TYPE_INACT){ /* inactive? */
				continue; /* next */
			}
			/* volume name in root directory */
			akai2ascii_name(pp->head.hd.vol[vi].name,vnamebuf,0); /* 0: not S900 */
		}else{
			continue; /* next */
		}
		ai++;

		if (vnamebuf[0]=='\0'){ /* empty volume name? */
			/* use default volume name as fake volume name */
			sprintf(vnamebuf,"VOLUME %03u",vi+1);
		}

		/* compare name (case-insensitive) */
		if (strcasecmp(vnamebuf,namebuf)==0){ /* match? */
			break; /* found */
		}

		if ((pp->type==PART_TYPE_HD9)&&(aivifound>=pp->volnummax)){ /* S900 harddisk and no alias name match yet? */
			/* alias name for volume on S900 harddisk */
			sprintf(vnamebuf,"V%u",ai);
			/* compare name (case-insensitive) */
			if (strcasecmp(vnamebuf,namebuf)==0){ /* match? */
				/* Note: name match takes precendence over alias name match */
				aivifound=vi; /* save for later */
			}
		}
	}
	if (vi==pp->volnummax){ /* no name match? */
		if ((pp->type==PART_TYPE_HD9)&&(aivifound<pp->volnummax)){ /* S900 harddisk and alias name match? */
			vi=aivifound;
		}else{
			return -1;
		}
	}

	/* get volume */
	return akai_get_vol(pp,vp,vi);
}



int
akai_rename_vol(struct vol_s *vp,char *name,u_int lnum,u_int osver,struct akai_volparam_s *parp)
{
	int modflag;
	u_int blk;
	u_char *addr;
	struct akai_flvol_label_s *lp;
	u_int hdsiz;

	if ((vp==NULL)||(vp->type==AKAI_VOL_TYPE_INACT)){
		return -1;
	}
	if ((vp->partp==NULL)||(!vp->partp->valid)){
		return -1;
	}

	if ((vp->partp->type==PART_TYPE_FLL)||(vp->partp->type==PART_TYPE_FLH)){
		/* floppy */

		modflag=0; /* floppy header not modified yet */

		/* floppy volume label */
		if (vp->partp->type==PART_TYPE_FLL){
			lp=&vp->partp->head.fll.label;
			hdsiz=AKAI_FLLHEAD_BLKS;
		}else{
			lp=&vp->partp->head.flh.label;
			hdsiz=AKAI_FLHHEAD_BLKS;
		}

		/* Note: no load number on floppy */

		/* Note: cannot switch volume type S900/S1000/S3000 */
		if (vp->type==AKAI_VOL_TYPE_S900){
			/* S900 floppy */
			/* correct OS version if necessary */
			osver=AKAI_OSVER_S900VOL;
			if (osver!=vp->osver){ /* need to modify? */
				vp->osver=osver;
				/* Note: S900 has no floppy volume label, all zero */
				bzero(lp,sizeof(struct akai_flvol_label_s));
				modflag=1;
			}
		}else{
			/* S1000/S300 floppy */
			/* correct OS version if necessary */
			if (vp->type==AKAI_VOL_TYPE_S1000){
				/* S1000 floppy */
				if ((osver==AKAI_OSVER_S900VOL)||(osver>AKAI_OSVER_S1100MAX)){
					osver=AKAI_OSVER_S1000MAX; /* XXX */
				}
			}else{
				/* S3000 floppy */
				if ((osver==AKAI_OSVER_S900VOL)||(osver>AKAI_OSVER_S3000MAX)){
					osver=AKAI_OSVER_S3000MAX; /* XXX */
				}
			}
			/* OS version */
			if (osver!=vp->osver){ /* new OS version? */
				vp->osver=osver;
				lp->osver[1]=0xff&(osver>>8);
				lp->osver[0]=0xff&osver;
				modflag=1;
			}

			/* name */
			if (name!=NULL){ /* new name? */
				if (strlen(name)>(u_int)AKAI_NAME_LEN){
					PRINTF_ERR("invalid name length\n");
					return -1;
				}
				if (name!=vp->name){ /* not already in vp->name? */
					/* copy */
					strcpy(vp->name,name); /* Note: name has been checked above */
				}
				ascii2akai_name(name,lp->name,0); /* 0: not S900 */
				modflag=1;
			}

			/* volume parameters */
			if ((parp!=NULL)&&(vp->param!=NULL)){ /* new volume parameters? */
				if (parp!=vp->param){ /* not already vp->param? */
					/* copy */
					bcopy(parp,vp->param,sizeof(struct akai_volparam_s));
				} /* else: vp->param[] possibly modified */
				modflag=1;
			}
		}

		if (modflag){
			/* write floppy header */
			if (akai_io_blks(vp->partp,(u_char *)&vp->partp->head.flh,
							 0,
							 hdsiz,
							 1,IO_BLKS_WRITE)<0){ /* 1: allocate cache if possible */
				return -1;
			}
		}
		
		return 0;
	}
	
	if (vp->partp->type==PART_TYPE_HD9){
		/* S900 harddisk */

		/* set vol_s */
		modflag=0; /* root directory not modified yet */
		
		/* Note: no load number on S900 harddisk */

		/* correct type and OS version */
		vp->type=AKAI_VOL_TYPE_S900;
		vp->osver=AKAI_OSVER_S900VOL;
		/* Note: no floppy volume labels for volumes on harddisk => cannot save OS version */

		/* name */
		if (name!=NULL){
			if (strlen(name)>AKAI_NAME_LEN_S900){
				PRINTF_ERR("invalid name length\n");
				return -1;
			}
			if (name!=vp->name){ /* not already in vp->name? */
				/* copy */
				strcpy(vp->name,name); /* Note: name has been checked above */
			}
			/* change root directory entry in partition */
			ascii2akai_name(name,vp->partp->head.hd9.vol[vp->index].name,1); /* 1: S900 */
			modflag=1;
		}
		
		if (modflag){
			/* write new root directory to partition */
			/* copy first FAT entries */
			bcopy((u_char *)&vp->partp->head.hd9.fatblk,(u_char *)&vp->partp->head.hd9.fatblk0,AKAI_HD9FAT0_ENTRIES*2);
			/* write harddisk header */
			if (akai_io_blks(vp->partp,(u_char *)&vp->partp->head.hd9,
							 0,
							 AKAI_HD9HEAD_BLKS,
							 1,IO_BLKS_WRITE)<0){ /* 1: allocate cache if possible */
				return -1;
			}
		}
		
		/* Note: no volume parameters on S900 harddisk */
		
		return 0;
	}

	if (vp->partp->type==PART_TYPE_HD){
		/* S1000/S3000 harddisk sampler partition */

		/* set vol_s */
		modflag=0; /* root directory not modified yet */
		
		/* load number */
		if (lnum!=vp->lnum){ /* need to modify? */
			if ((lnum!=AKAI_VOL_LNUM_OFF)
				&&((lnum<AKAI_VOL_LNUM_MIN)||(lnum>AKAI_VOL_LNUM_MAX))){
				return -1;
			}
			vp->lnum=lnum;
			/* change root directory entry in partition */
			vp->partp->head.hd.vol[vp->index].lnum=lnum;
			modflag=1;
		}

		/* correct type and OS version */
		if ((vp->type==AKAI_VOL_TYPE_S900)||(vp->osver==AKAI_OSVER_S900VOL)){ /* incorrect for partition type? */
			vp->type=AKAI_VOL_TYPE_S1000; /* XXX */
			vp->osver=AKAI_OSVER_S1000MAX; /* XXX */
		}
		/* Note: no floppy volume labels for volumes on harddisk => cannot save OS version */

		/* name */
		if (name!=NULL){
			if (strlen(name)>AKAI_NAME_LEN){
				PRINTF_ERR("invalid name length\n");
				return -1;
			}
			if (name!=vp->name){ /* not already in vp->name? */
				/* copy */
				strcpy(vp->name,name); /* Note: name has been checked above */
			}
			/* change root directory entry in partition */
			ascii2akai_name(name,vp->partp->head.hd.vol[vp->index].name,0); /* 0: not S900 */
			modflag=1;
		}
		
		if (modflag){
			/* write new root directory to partition */
			/* write partition header */
			if (akai_io_blks(vp->partp,(u_char *)&vp->partp->head.hd,
							 0,
							 AKAI_PARTHEAD_BLKS,
							 1,IO_BLKS_WRITE)<0){ /* 1: allocate cache if possible */
				return -1;
			}
		}
		
		/* volume parameters */
		if ((parp!=NULL)&&(vp->param!=NULL)){
			if (parp!=vp->param){ /* not already vp->param? */
				/* copy */
				bcopy(parp,vp->param,sizeof(struct akai_volparam_s));
			} /* else: vp->param[] possibly modified */
			
			/* get volume directory block of volume parameters */
			if ((vp->type==AKAI_VOL_TYPE_S3000)||(vp->type==AKAI_VOL_TYPE_CD3000)){
				blk=vp->dirblk[1];
				addr=((u_char *)&vp->dir)+AKAI_HD_BLOCKSIZE;
			}else{
				blk=vp->dirblk[0];
				addr=(u_char *)&vp->dir;
			}
			
			/* write new volume directory block to partition */
			if (akai_io_blks(vp->partp,addr,
							 blk,
							 1,
							 1,IO_BLKS_WRITE)<0){ /* 1: allocate cache if possible */
				return -1;
			}
		}
		
		return 0;
	}

	return -1;
}



/* uses *vp as output */
int
akai_create_vol(struct part_s *pp,struct vol_s *vp,u_int type,u_int index,char *name,u_int lnum,struct akai_volparam_s *parp)
{
	u_char *addr;
	u_int i,j;

	if ((pp==NULL)||(!pp->valid)||(pp->fat==NULL)){
		return -1;
	}
	if (vp==NULL){
		return -1;
	}

	if ((pp->type!=PART_TYPE_HD9)&&(pp->type!=PART_TYPE_HD)){
		PRINTF_ERR("cannot create additional volume for this partition type\n");
		return -1;
	}
	/* now, harddisk */

	/* partition */
	vp->partp=pp;

	/* type */
	if (pp->type==PART_TYPE_HD9){
		/* S900 */
		if (type!=AKAI_VOL_TYPE_S900){
			PRINTF_ERR("cannot create this volume type for this partition type\n");
			return -1;
		}
	}else if (pp->type==PART_TYPE_HD){
		/* S1000/S3000 */
		if ((type!=AKAI_VOL_TYPE_S1000)&&(type!=AKAI_VOL_TYPE_S3000)&&(type!=AKAI_VOL_TYPE_CD3000)){
			PRINTF_ERR("cannot create this volume type for this partition type\n");
			return -1;
		}
	}
	vp->type=type;

	/* load number */
	if (pp->type==PART_TYPE_HD9){
		/* S900 */
		vp->lnum=AKAI_VOL_LNUM_OFF;
	}else{
		/* S1000/S3000 */
		if ((lnum!=AKAI_VOL_LNUM_OFF)
			&&((lnum<AKAI_VOL_LNUM_MIN)||(lnum>AKAI_VOL_LNUM_MAX))){
			return -1;
		}
		vp->lnum=lnum;
	}

	/* OS version */
	if (vp->type==AKAI_VOL_TYPE_S900){
		/* S900 */
		vp->osver=AKAI_OSVER_S900VOL;
	}else if (vp->type==AKAI_VOL_TYPE_S1000){
		/* S1000 */
		vp->osver=AKAI_OSVER_S1000MAX; /* XXX */
	}else{
		/* S3000 */
		vp->osver=AKAI_OSVER_S3000MAX; /* XXX */
	}

	/* name */
	if ((name!=NULL)&&(name[0]!='\0')){ /* volume name given? */
		if (strlen(name)>(u_int)((vp->type==AKAI_VOL_TYPE_S900)?AKAI_NAME_LEN_S900:AKAI_NAME_LEN)){
			PRINTF_ERR("invalid name length\n");
			return -1;
		}
		strcpy(vp->name,name);
	}

	/* index */
	if (index==AKAI_CREATE_VOL_NOINDEX){ /* no user-supplied volume index? */
		/* find inactive entry in root directory in partition */
		for (i=0;i<pp->volnummax;i++){
			if (pp->type==PART_TYPE_HD9){
				/* S900 */
				j=vp->dirblk[0]=(vp->partp->head.hd9.vol[i].start[1]<<8)
					+vp->partp->head.hd9.vol[i].start[0];
				if (j==AKAI_VOL_START_INACT){ /* inactive? */
					/* index */
					vp->index=i;
					break; /* found one */
				}
			}else{
				/* S1000/S3000 */
				if (vp->partp->head.hd.vol[i].type==AKAI_VOL_TYPE_INACT){ /* inactive? */
					/* index */
					vp->index=i;
					break; /* found one */
				}
			}
		}
		if (i==pp->volnummax){ /* none found? */
			PRINTF_OUT("no inactive volume found");
			return -1;
		}
	}else{
		/* user-supplied index */
		if (index>=pp->volnummax){
			return -1;
		}
		if (pp->type==PART_TYPE_HD9){
			/* S900 */
			j=vp->dirblk[0]=(vp->partp->head.hd9.vol[index].start[1]<<8)
				+vp->partp->head.hd9.vol[index].start[0];
			if (j!=AKAI_VOL_START_INACT){ /* not inactive? */
				PRINTF_OUT("volume not inactive\n");
				return -1;
			}
		}else{
			/* S1000/S3000 */
			if (vp->partp->head.hd.vol[index].type!=AKAI_VOL_TYPE_INACT){ /* not inactive? */
				PRINTF_OUT("volume not inactive\n");
				return -1;
			}
		}
		/* index */
		vp->index=index;
	}

	if ((name==NULL)||(name[0]=='\0')){ /* no volume name given? */
		/* default volume name */
		sprintf(vp->name,"VOLUME %03u",vp->index+1);
	}

	/* allocate volume directory */
	if (vp->type==AKAI_VOL_TYPE_S900){
		/* S900 */
		if (akai_allocate_fatchain(vp->partp,
									AKAI_VOLDIR900HD_BLKS,
									&vp->dirblk[0],
									AKAI_VOLDIR900HD_BLKS,
									AKAI_FAT_CODE_DIREND900HD)<0){ /* end code */
			return -1;
		}
		/* number of volume directory entries */
		vp->fimax=AKAI_VOLDIR_ENTRIES_S900HD;
		/* start of files */
		vp->file=&vp->dir.s900hd.file[0];
	}else if (vp->type==AKAI_VOL_TYPE_S1000){
		/* S1000 */
		if (akai_allocate_fatchain(vp->partp,
									AKAI_VOLDIR1000HD_BLKS,
									&vp->dirblk[0],
									AKAI_VOLDIR1000HD_BLKS,
									AKAI_FAT_CODE_DIREND1000HD)<0){ /* end code */
									return -1;
		}
		/* number of volume directory entries */
		vp->fimax=AKAI_VOLDIR_ENTRIES_S1000HD;
		/* start of files */
		vp->file=&vp->dir.s1000hd.file[0];
	}else{
		/* S3000 */
		if (akai_allocate_fatchain(vp->partp,
									AKAI_VOLDIR3000HD_BLKS,
									&vp->dirblk[0],
									AKAI_VOLDIR3000HD_BLKS, /* XXX want contiguous blocks */
									AKAI_FAT_CODE_DIREND3000)<0){ /* end code */
			return -1;
		}
		/* number of volume directory entries */
		vp->fimax=AKAI_VOLDIR_ENTRIES_S3000HD;
		/* start of files */
		vp->file=&vp->dir.s3000hd.file[0];
	}

	/* initialize volume directory */
	bzero(&vp->dir,sizeof(union akai_voldir_u));
	for (i=0;i<vp->fimax;i++){
		vp->file[i].type=AKAI_FTYPE_FREE;
	}

	/* volume parameters */
	if (vp->type==AKAI_VOL_TYPE_S900){
		/* S900 */
		vp->param=NULL; /* S900 has no volume paramaters */
	}else if (vp->type==AKAI_VOL_TYPE_S1000){
		/* S1000 */
		vp->param=&vp->dir.s1000hd.param;
	}else{
		/* S3000 */
		vp->param=&vp->dir.s3000hd.param;
	}
	if (vp->param!=NULL){
		if (parp!=NULL){
			/* copy volume parameters */
			bcopy(parp,vp->param,sizeof(struct akai_volparam_s));
		}else{
			/* default volume parameters */
			akai_init_volparam(vp->param,(vp->type==AKAI_VOL_TYPE_S3000)||(vp->type==AKAI_VOL_TYPE_CD3000));
		}
	}

	/* write new volume directory block 0 to partition */
	addr=(u_char *)&vp->dir;
	if (akai_io_blks(vp->partp,addr,
					 vp->dirblk[0],
					 1,
					 1,IO_BLKS_WRITE)<0){ /* 1: allocate cache if possible */
		return -1;
	}

	if (vp->fimax>AKAI_VOLDIR_ENTRIES_1BLKHD){ /* second volume directory block? */
		/* block 1 from FAT */
		vp->dirblk[1]=(vp->partp->fat[vp->dirblk[0]][1]<<8)
					  +vp->partp->fat[vp->dirblk[0]][0];
		/* write new volume directory block 1 to partition */
		addr+=AKAI_HD_BLOCKSIZE;
		if (akai_io_blks(vp->partp,addr,
						 vp->dirblk[1],
						 1,
						 1,IO_BLKS_WRITE)<0){ /* 1: allocate cache if possible */
			return -1;
		}
	}

	/* update root directory entry in partition */
	if (pp->type==PART_TYPE_HD9){
		/* S900 */
		/* Note: no OS version nor load number on S900 harddisk */
		ascii2akai_name(vp->name,vp->partp->head.hd9.vol[vp->index].name,1); /* 1: S900 */
		/* start */
		vp->partp->head.hd9.vol[vp->index].start[1]=0xff&(vp->dirblk[0]>>8);
		vp->partp->head.hd9.vol[vp->index].start[0]=0xff&vp->dirblk[0];

		/* write new root directory to harddisk */
		/* copy first FAT entries */
		bcopy((u_char *)&vp->partp->head.hd9.fatblk,(u_char *)&vp->partp->head.hd9.fatblk0,AKAI_HD9FAT0_ENTRIES*2);
		/* write harddisk header */
		if (akai_io_blks(vp->partp,(u_char *)&vp->partp->head.hd9,
						 0,
						 AKAI_HD9HEAD_BLKS,
						 1,IO_BLKS_WRITE)<0){ /* 1: allocate cache if possible */
			return -1;
		}
	}else{
		/* S1000/S3000 */
		vp->partp->head.hd.vol[vp->index].type=(u_char)vp->type;
		/* Note: no floppy volume labels for volumes on harddisk => cannot write OS version */
		/*       akai_get_vol() will derive OS version from volume type in root directory entry */
		vp->partp->head.hd.vol[vp->index].lnum=(u_char)vp->lnum;
		ascii2akai_name(vp->name,vp->partp->head.hd.vol[vp->index].name,0); /* 0: not S900 */
		/* start */
		vp->partp->head.hd.vol[vp->index].start[1]=0xff&(vp->dirblk[0]>>8);
		vp->partp->head.hd.vol[vp->index].start[0]=0xff&vp->dirblk[0];

		/* write new root directory to partition */
		/* write partition header */
		if (akai_io_blks(vp->partp,(u_char *)&vp->partp->head.hd,
						 0,
						 AKAI_PARTHEAD_BLKS,
						 1,IO_BLKS_WRITE)<0){ /* 1: allocate cache if possible */
			return -1;
		}
	}

	return 0;
}



int
akai_wipe_vol(struct vol_s *vp,int delflag)
{
	u_int fi;
	struct file_s tmpfile;

	if ((vp==NULL)||(vp->type==AKAI_VOL_TYPE_INACT)){
		return -1;
	}
	if ((vp->partp==NULL)||(!vp->partp->valid)||(vp->partp->fat==NULL)){
		return -1;
	}

	if (delflag&&(vp->partp->type!=PART_TYPE_HD9)&&(vp->partp->type!=PART_TYPE_HD)){
		PRINTF_ERR("cannot remove floppy volume\n");
		return -1;
	}

	/* delete all files in volume */
	for (fi=0;fi<vp->fimax;fi++){
		if (akai_get_file(vp,&tmpfile,fi)<0){
			continue; /* next file */
		}
		if (akai_delete_file(&tmpfile)<0){
			return -1;
		}
	}

	if (delflag){
		/* now, harddisk */

		/* free FAT block(s) of volume */
		vp->partp->fat[vp->dirblk[0]][1]=0xff&(AKAI_FAT_CODE_FREE>>8);
		vp->partp->fat[vp->dirblk[0]][0]=0xff&AKAI_FAT_CODE_FREE;
		if ((vp->fimax>AKAI_VOLDIR_ENTRIES_1BLKHD)
			&&(akai_check_fatblk(vp->dirblk[1],vp->partp->bsize,vp->partp->bsyssize)==0)){ /* also block 1? */
			vp->partp->fat[vp->dirblk[1]][1]=0xff&(AKAI_FAT_CODE_FREE>>8);
			vp->partp->fat[vp->dirblk[1]][0]=0xff&AKAI_FAT_CODE_FREE;
		}
		/* Note: free and bad block counter will be updated below */

		/* free root directory entry in partition */
		if (vp->partp->type==PART_TYPE_HD9){
			/* S900 */
			vp->partp->head.hd9.vol[vp->index].start[1]=0xff&(AKAI_VOL_START_INACT>>8);
			vp->partp->head.hd9.vol[vp->index].start[0]=0xff&AKAI_VOL_START_INACT;
			/* XXX keep name */

			/* write new FAT and root directory to partition */
			/* copy first FAT entries */
			bcopy((u_char *)&vp->partp->head.hd9.fatblk,(u_char *)&vp->partp->head.hd9.fatblk0,AKAI_HD9FAT0_ENTRIES*2);
			/* write harddisk header */
			if (akai_io_blks(vp->partp,(u_char *)&vp->partp->head.hd9,
							 0,
							 AKAI_HD9HEAD_BLKS,
							 1,IO_BLKS_WRITE)<0){ /* 1: allocate cache if possible */
				return -1;
			}
		}else{
			/* S1000/S3000 */
			vp->partp->head.hd.vol[vp->index].type=AKAI_VOL_TYPE_INACT;
			/* XXX keep name etc. */

			/* write new FAT and root directory to partition */
			/* write partition header */
			if (akai_io_blks(vp->partp,(u_char *)&vp->partp->head.hd,
							 0,
							 AKAI_PARTHEAD_BLKS,
							 1,IO_BLKS_WRITE)<0){ /* 1: allocate cache if possible */
				return -1;
			}
		}
	}

	/* count free and bad blocks */
	akai_countfree_part(vp->partp);

	/* Note: no need to exit or restart now */
	return 0;
}



void
akai_list_volparam(struct akai_volparam_s *parp,int listflag)
{
	u_int i;

	if (parp==NULL){
		return;
	}

	if (listflag){
		PRINTF_OUT("volume parameters:\nidx   val\n---------\n");
		for (i=0;i<sizeof(struct akai_volparam_s);i++){
			/* XXX */
			PRINTF_OUT("%3u  0x%02x",i,(u_int)((u_char *)parp)[i]);
			switch (i){
			case 0:
				PRINTF_OUT("  (program select MIDI channel-1)\n");
				break;
			case 1:
				PRINTF_OUT("  (MIDI OMNI for program select)\n");
				break;
			case 2:
				PRINTF_OUT("  (program select enable)\n");
				break;
			case 3:
				PRINTF_OUT("  (selected program number-1)\n");
				break;
			case 4:
				PRINTF_OUT("  (MIDI OMNI override for play input)\n");
				break;
			case 5:
				PRINTF_OUT("  (MIDI system exclusive channel-1)\n");
				break;
			default:
				PRINTF_OUT("\n");
				break;
			}
		}
		PRINTF_OUT("---------\n\n");
	}

	PRINTF_OUT("volume parameters:\n");
	PRINTF_OUT("program select MIDI channel:       %u\n",((u_int)parp->progselmidich1)+1);
	PRINTF_OUT("MIDI OMNI for program select:      %s\n",parp->progselomni?"ON":"OFF");
	PRINTF_OUT("program select enable:             %s\n",parp->progselenab?"ON":"OFF");
	PRINTF_OUT("selected program number:           %u\n",((u_int)parp->prognr1)+1);
	PRINTF_OUT("MIDI OMNI override for play input: %s\n",parp->playinominovrr?"ON":"OFF");
	PRINTF_OUT("MIDI system exclusive channel:     %u\n",((u_int)parp->sysexch1)+1);
}

void
akai_init_volparam(struct akai_volparam_s *parp,int s3000flag)
{

	if (parp==NULL){
		return;
	}

	/* default volume parameters */
	bzero(parp,sizeof(struct akai_volparam_s));
	if (s3000flag){
		/* S3000 */
		parp->progselomni=0x01; /* XXX */
		parp->progselenab=0x01; /* XXX */
		parp->dummy1[0]=0x32; /* XXX */
		parp->dummy1[1]=0x09; /* XXX */
		parp->dummy1[2]=0x0c; /* XXX */
		parp->dummy1[3]=0xff; /* XXX */
	}else{
		/* S1000 */
		parp->progselomni=0x01; /* XXX */
		parp->progselenab=0x01; /* XXX */
	}
}



void
akai_print_lnum(u_int lnum)
{

	if (lnum==AKAI_VOL_LNUM_OFF){
		PRINTF_OUT("-  ");
	}else{
		PRINTF_OUT("%03u",lnum);
	}
}

u_int
akai_get_lnum(char *name)
{
	u_int lnum;

	if (name==NULL){
		return AKAI_VOL_LNUM_OFF;
	}

	if (strcasecmp(name,"OFF")==0){
		return AKAI_VOL_LNUM_OFF;
	}

	lnum=(u_int)atoi(name);
	if ((lnum<AKAI_VOL_LNUM_MIN)||(lnum>AKAI_VOL_LNUM_MAX)){
		PRINTF_ERR("invalid load number, setting OFF\n");
		return AKAI_VOL_LNUM_OFF;
	}

	return lnum;
}



void
akai_list_tags(struct part_s *pp)
{
	u_int i;
	char namebuf[AKAI_NAME_LEN+1]; /* +1 for '\0' */

	if ((pp==NULL)||(!pp->valid)){
		return;
	}

	if (pp->type!=PART_TYPE_HD){
		PRINTF_OUT("no tags in this partition type\n");
		return;
	}
	/* now, S1000/S3000 harddisk sampler partition */

	/* check magic */
	if (strncmp(AKAI_PARTHEAD_TAGSMAGIC,(char *)pp->head.hd.tagsmagic,4)!=0){
		PRINTF_OUT("no tags present\n");
		return;
	}

	PRINTF_OUT("tag  name\n");
	PRINTF_OUT("-----------------\n");
	for (i=0;i<AKAI_PARTHEAD_TAGNUM;i++){
		/* Note: tag names use S1000/S3000 char conversion */
		akai2ascii_name(pp->head.hd.tag[i],namebuf,0); /* 0: not S900 */
		PRINTF_OUT(" %02u  %s\n",i+1,namebuf); /* XXX default tag name */
	}
	PRINTF_OUT("-----------------\n");
}



int
akai_rename_tag(struct part_s *pp,char *name,u_int ti,int wipeflag)
{
	u_int i;
	char namebuf[AKAI_NAME_LEN+1]; /* +1 for '\0' */

	if ((pp==NULL)||(!pp->valid)){
		return -1;
	}

	if (pp->type!=PART_TYPE_HD){
		PRINTF_OUT("no tags in this partition type\n");
		return -1;
	}
	/* now, S1000/S3000 harddisk sampler partition */

	if (name!=NULL){
		if ((strlen(name)>AKAI_NAME_LEN)||(ti>=AKAI_PARTHEAD_TAGNUM)){
			return -1;
		}
	}

	if (wipeflag){
		bcopy(AKAI_PARTHEAD_TAGSMAGIC,pp->head.hd.tagsmagic,4); /* magic */
		for (i=0;i<AKAI_PARTHEAD_TAGNUM;i++){
			sprintf(namebuf,"TAG %c       ",(char)('A'+i)); /* XXX default tag name */
			/* Note: tag names use S1000/S3000 char conversion */
			ascii2akai_name(namebuf,pp->head.hd.tag[i],0); /* 0: not S900 */
		}
	}

	if (name!=NULL){
		/* check magic */
		if (strncmp(AKAI_PARTHEAD_TAGSMAGIC,(char *)pp->head.hd.tagsmagic,4)!=0){
			PRINTF_OUT("no tags present\n");
			return -1;
		}

		/* set tag */
		/* Note: tag names use S1000/S3000 char conversion */
		ascii2akai_name(name,pp->head.hd.tag[ti],0); /* 0: not S900 */
	}

	/* write new tag(s) to partition */
	/* write partition header */
	return akai_io_blks(pp,(u_char *)&pp->head.hd,
						0,
						AKAI_PARTHEAD_BLKS,
						1,IO_BLKS_WRITE); /* 1: allocate cache if possible */
}



int
copy_tags(struct part_s *srcpp,struct part_s *dstpp)
{

	if ((srcpp==NULL)||(!srcpp->valid)){
		return -1;
	}
	if ((dstpp==NULL)||(!dstpp->valid)){
		return -1;
	}

	if ((srcpp->type!=PART_TYPE_HD)||(dstpp->type!=PART_TYPE_HD)){
		PRINTF_OUT("no tags in this partition type\n");
		return -1;
	}
	/* now, S1000/S3000 harddisk sampler partition */

	/* copy tags */
	bcopy(srcpp->head.hd.tagsmagic,dstpp->head.hd.tagsmagic,4); /* magic */
	bcopy(srcpp->head.hd.tag[0],dstpp->head.hd.tag[0],
		AKAI_PARTHEAD_TAGNUM*AKAI_NAME_LEN); /* tag names */

	/* write new tag(s) to partition */
	/* write partition header */
	return akai_io_blks(dstpp,(u_char *)&dstpp->head.hd,
						0,
						AKAI_PARTHEAD_BLKS,
						1,IO_BLKS_WRITE);/* 1: allocate cache if possible */
}



void
akai_print_cdinfo(struct part_s *pp,int verbose)
{
	u_int c;
	u_int i;
	u_int fnum;
	u_int bn;
	char namebuf[AKAI_NAME_LEN+1]; /* +1 for '\0' */
	char fnamebuf[AKAI_NAME_LEN+4+1]; /* name (ASCII), +4 for ".<type>", +1 for '\0' */
	u_char buf[AKAI_HD_BLOCKSIZE];
	struct akai_cdinfohead_s *hp;
	struct akai_voldir_entry_s *dep;
	u_char *buf2;

	if ((pp==NULL)||(!pp->valid)||(pp->fat==NULL)){
		return;
	}

	if (pp->type!=PART_TYPE_HD){
		PRINTF_OUT("no CD-ROM info in this partition type\n");
		return;
	}
	/* now, S1000/S3000 harddisk sampler partition */

	/* check if S3000 with CD-ROM info: */
	/* check if tags magic */
	if (strncmp(AKAI_PARTHEAD_TAGSMAGIC,(char *)pp->head.hd.tagsmagic,4)!=0){
		PRINTF_OUT("no CD-ROM info in this partition\n");
		return;
	}
	/* check if at least minimum number of blocks for CD-ROM info have FAT code SYS */
	/* Note: S1000 volume also uses SYS for volume directory */
	for (i=AKAI_CDINFO_BLK;i<AKAI_CDINFO_BLK+AKAI_CDINFO_MINSIZB;i++){
		c=(pp->fat[i][1]<<8)+pp->fat[i][0];
		if (c!=AKAI_FAT_CODE_SYS){
			PRINTF_OUT("no CD-ROM info in this partition\n");
			return;
		}
	}

	/* read CD-ROM info header */
	if (akai_io_blks(pp,buf,
					 AKAI_CDINFO_BLK,
					 1,
					 0,IO_BLKS_READ)<0){ /* 0: don't allocate new cache */
		PRINTF_ERR("cannot read CD-ROM info\n");
		return;
	}

	/* CD-ROM info header */
	hp=(struct akai_cdinfohead_s *)buf;

	/* CD-ROM label */
	/* Note: CD-ROM label uses S1000/S3000 char conversion */
	akai2ascii_name(hp->cdlabel,namebuf,0); /* 0: not S900 */
	PRINTF_OUT("CD-ROM label: \"%s\"\n",namebuf);

	/* number of files */
	fnum=(hp->fnum[1]<<8)+hp->fnum[0];
	PRINTF_OUT("\nnumber of files in partition %c: %u\n",pp->letter,fnum);
	/* required number of blocks */
	bn=sizeof(struct akai_cdinfohead_s)+fnum*sizeof(struct akai_voldir_entry_s);
	bn=(bn+AKAI_HD_BLOCKSIZE-1)/AKAI_HD_BLOCKSIZE; /* round up */
	if (AKAI_CDINFO_BLK+bn>pp->bsize){
		PRINTF_ERR("invalid number of files for partition size\n");
		return;
	}
	/* check if all required blocks for CD-ROM info have FAT code SYS */
	/* Note: S1000 volume also uses SYS for volume directory */
	for (i=1;i<bn;i++){
		c=(pp->fat[AKAI_CDINFO_BLK+i][1]<<8)
			+pp->fat[AKAI_CDINFO_BLK+i][0];
		if (c!=AKAI_FAT_CODE_SYS){
			/* truncate number of blocks */
			bn=i;
			/* truncate number of files */
			fnum=((bn*AKAI_HD_BLOCKSIZE)-sizeof(struct akai_cdinfohead_s))/sizeof(struct akai_voldir_entry_s);
			PRINTF_OUT("CD-ROM info is incomplete, truncated to %u files\n",fnum);
			break;
		}
	}

	if (verbose){
		/* print volume sizes */
		PRINTF_OUT("\nvolume files\n------------\n");
		for (i=0;i<pp->volnummax;i++){
			c=(hp->volesiz[i][1]<<8)+hp->volesiz[i][0]; /* size of volume directory entries for files in bytes */
			if (c!=0){ /* used? */
				c/=sizeof(struct akai_voldir_entry_s); /* number of file entries */
				PRINTF_OUT("%03u      %3u\n",i+1,c);
			}
		}
		PRINTF_OUT("------------\n");

		if ((buf2=(u_char *)malloc(bn*AKAI_HD_BLOCKSIZE))==NULL){
			PERROR("malloc");
			return;
		}

		/* read directory of files from CD-ROM info */
		if (akai_io_blks(pp,buf2,
						 AKAI_CDINFO_BLK,
						 bn,
						 0,IO_BLKS_READ)<0){ /* 0: don't allocate new cache */
			PRINTF_ERR("cannot read CD-ROM directory of files\n");
			free(buf2);
			return;
		}

		/* print directory of files */
		PRINTF_OUT("\ndirectory of files:\n---------------------------------------------------------------------\n");
		for (i=0;i<fnum;i++){
			dep=(struct akai_voldir_entry_s *)(buf2+sizeof(struct akai_cdinfohead_s));
			/* name */
			/* Note: file names in CD-ROM info use S1000/S3000 char conversion */
			akai2ascii_filename(dep[i].name,dep[i].type,AKAI_OSVER_S3000MAX,fnamebuf,0); /* 0: not S900 */
			PRINTF_OUT("%-16s  ",fnamebuf);
			if (i%4==3){
				PRINTF_OUT("\n");
			}
		}
		if (i%4!=0){
			PRINTF_OUT("\n");
		}
		PRINTF_OUT("---------------------------------------------------------------------\n");

		free(buf2);
	}
}



int
akai_set_cdinfo(struct part_s *pp,char *cdlabel)
{
	u_int c;
	u_int i,j;
	u_int fnum,fnummax;
	u_int vsiz;
	static u_char buf[AKAI_CDINFO_MINSIZB*AKAI_HD_BLOCKSIZE]; /* XXX minimum size */
	u_char *bp;
	struct akai_cdinfohead_s *hp;
	struct vol_s vol;
	struct file_s file;
	u_char oldcdlabel[AKAI_NAME_LEN];

	if ((pp==NULL)||(!pp->valid)||(pp->fat==NULL)){
		return -1;
	}

	if (pp->type!=PART_TYPE_HD){
		PRINTF_OUT("no CD-ROM info in this partition type\n");
		return -1;
	}
	/* now, S1000/S3000 harddisk sampler partition */

	/* check if S3000 with CD-ROM info: */
	/* check if tags magic */
	if (strncmp(AKAI_PARTHEAD_TAGSMAGIC,(char *)pp->head.hd.tagsmagic,4)!=0){
		PRINTF_OUT("no CD-ROM info in this partition\n");
		return -1;
	}
	/* check if at least minimum number of blocks for CD-ROM info have FAT code SYS */
	/* Note: S1000 volume also uses SYS for volume directory */
	for (i=AKAI_CDINFO_BLK;i<AKAI_CDINFO_BLK+AKAI_CDINFO_MINSIZB;i++){
		c=(pp->fat[i][1]<<8)+pp->fat[i][0];
		if (c!=AKAI_FAT_CODE_SYS){
			PRINTF_OUT("no CD-ROM info in this partition\n");
			return -1;
		}
	}

	/* CD-ROM info header */
	hp=(struct akai_cdinfohead_s *)buf;

	if (cdlabel==NULL){
		/* read old CD-ROM info */
		if (akai_io_blks(pp,buf,
						 AKAI_CDINFO_BLK,
						 1, /* XXX 1 block is enough for header */
						 0,IO_BLKS_READ)<0){ /* 0: don't allocate new cache */
			PRINTF_ERR("cannot read CD-ROM info\n");
			return -1;
		}
		/* save old label */
		bcopy(hp->cdlabel,oldcdlabel,AKAI_NAME_LEN);
	}

	/* wipe CD-ROM info */
	bzero(buf,AKAI_CDINFO_MINSIZB*AKAI_HD_BLOCKSIZE);

	/* max. number of files in CD-ROM info */
	/* XXX minimum size */
	fnummax=((AKAI_CDINFO_MINSIZB*AKAI_HD_BLOCKSIZE)-sizeof(struct akai_cdinfohead_s))/sizeof(struct akai_voldir_entry_s);

	/* read all volumes in partition */
	fnum=0;
	bp=buf+sizeof(struct akai_cdinfohead_s); /* behind header */
	for (i=0;i<pp->volnummax;i++){
		vsiz=0;
		if (akai_get_vol(pp,&vol,i)<0){
			continue; /* ignore error, next */
		}
		/* check all files in volume */
		for (j=0;j<vol.fimax;j++){
			if (akai_get_file(&vol,&file,j)<0){
				continue; /* ignore error, next */
			}
			/* copy volume entry */
			bcopy(&vol.file[j],bp,sizeof(struct akai_voldir_entry_s));
			bp+=sizeof(struct akai_voldir_entry_s);
			vsiz+=sizeof(struct akai_voldir_entry_s);
			fnum++;
			if (fnum>=fnummax){
				break; /* exit, ignore rest */
			}
		}
		/* size of volume directory entries for files in bytes */
		hp->volesiz[i][1]=0xff&(vsiz>>8);
		hp->volesiz[i][0]=0xff&vsiz;
		if (fnum>=fnummax){
			break; /* exit, ignore rest */
		}
	}
	/* total number of files */
	hp->fnum[1]=0xff&(fnum>>8);
	hp->fnum[0]=0xff&fnum;

	/* CD-ROM label */
	if (cdlabel!=NULL){
		/* Note: CD-ROM label uses S1000/S3000 char conversion */
		ascii2akai_name(cdlabel,hp->cdlabel,0); /* 0: not S900 */
	}else{
		/* restore old label */
		bcopy(oldcdlabel,hp->cdlabel,AKAI_NAME_LEN);
	}

	/* write CD-ROM info */
	if (akai_io_blks(pp,buf,
					 AKAI_CDINFO_BLK,
					 AKAI_CDINFO_MINSIZB,
					 0,IO_BLKS_WRITE)<0){ /* 0: don't allocate new cache */
		PRINTF_ERR("cannot write CD-ROM info\n");
		return -1;
	}

	return 0;
}



void
akai_part_info(struct part_s *pp,int verbose)
{

	if (pp==NULL){
		return;
	}

	if (verbose){
		PRINTF_OUT("part:      ");
		if (pp->type==PART_TYPE_DD){
			/* harddisk DD partition */
			if (pp->letter==0){
				PRINTF_OUT("DD\n");
			}else{
				PRINTF_OUT("DD%u\n",(u_int)pp->letter);
			}
		}else{
			/* floppy or harddisk sampler partition */
			PRINTF_OUT("%c\n",pp->letter);
		}
		if (!pp->valid){
			PRINTF_OUT("*** marked invalid ***\n");
		}
		PRINTF_OUT("parttype:  ");
		switch (pp->type){
		case PART_TYPE_FLL:
			PRINTF_OUT("low-density floppy");
			break;
		case PART_TYPE_FLH:
			PRINTF_OUT("high-density floppy");
			break;
		case PART_TYPE_HD9:
			PRINTF_OUT("S900 harddisk");
			break;
		case PART_TYPE_HD:
			PRINTF_OUT("S1000/S3000 harddisk sampler partition");
			break;
		case PART_TYPE_DD:
			PRINTF_OUT("S1100/S3000 harddisk DD parttion");
			break;
		default:
			PRINTF_OUT("\?\?\?");
			break;
		}
		PRINTF_OUT("\nblksize:   0x%04x bytes\n",pp->blksize);
		PRINTF_OUT("start:     block 0x%04x\n",
			pp->bstart);
		PRINTF_OUT("size:      0x%04x blocks (%6u KB)\n",
			pp->bsize,
			(pp->bsize*pp->blksize)/1024); /* in KB */
		PRINTF_OUT("free:      0x%04x blocks (%6u KB) = %5.1lf%%\n",
			pp->bfree,
			(pp->bfree*pp->blksize)/1024, /* in KB */
			(pp->bsize>0)?(100.0*((double)pp->bfree)/((double)pp->bsize)):0.0);
		PRINTF_OUT("bad:       0x%04x blocks (%6u KB) = %5.1lf%%\n",
			pp->bbad,
			(pp->bbad*pp->blksize)/1024, /* in KB */
			(pp->bsize>0)?(100.0*((double)pp->bbad)/((double)pp->bsize)):0.0);
		if (pp->type!=PART_TYPE_DD){
			PRINTF_OUT("max. vols: %u\n",pp->volnummax);
		}
	}else{
		if (pp->type==PART_TYPE_DD){
			/* harddisk DD partition */
			if (pp->letter==0){
				PRINTF_OUT("  DD");
			}else if (pp->letter<=9){
				PRINTF_OUT(" DD%u",(u_int)pp->letter);
			}else{
				PRINTF_OUT("DD%u",(u_int)pp->letter);
			}
		}else{
			/* floppy or harddisk sampler partition */
			PRINTF_OUT("   %c",pp->letter);
		}
		PRINTF_OUT("%c  ",pp->valid?' ':'*');
		switch (pp->type){
		case PART_TYPE_FLL:
			PRINTF_OUT("FLL");
			break;
		case PART_TYPE_FLH:
			PRINTF_OUT("FLH");
			break;
		case PART_TYPE_HD9:
			PRINTF_OUT("HD9");
			break;
		case PART_TYPE_HD:
			PRINTF_OUT(" HD");
			break;
		case PART_TYPE_DD:
			PRINTF_OUT(" DD");
			break;
		default:
			PRINTF_OUT("\?\?\?");
			break;
		}
		PRINTF_OUT("   0x%04x    0x%04x    %5.1lf     0x%04x   %5.1lf  %5.1lf\n",
			pp->bstart,
			pp->bsize,
			((double)pp->bsize*pp->blksize)/((double)1024*1024),
			pp->bfree,
			((double)pp->bfree*pp->blksize)/((double)1024*1024),
			(pp->bsize>0)?(100.0*((double)pp->bfree)/((double)pp->bsize)):0.0);
	}
}

void
akai_list_part(struct part_s *pp,int recflag,u_char *filtertagp)
{
	u_int vi;
	u_int ai;
	struct vol_s tmpvol;

	if ((pp==NULL)||(!pp->valid)||(pp->diskp==NULL)){
		return;
	}

	if (pp->type==PART_TYPE_DD){
		/* harddisk DD partition */
		u_int tott;
		u_int i;

		PRINTF_OUT("/disk%u/DD",pp->diskp->index);
		if (pp->letter!=0){
			PRINTF_OUT("%u",(u_int)pp->letter);
		}
		PRINTF_OUT("\ntnr  tname         cstarts cstarte csizes  csizee     scount  srate  stype\n");
		PRINTF_OUT("---------------------------------------------------------------------------\n");
		/* DD takes in partition */
		tott=0;
		for (i=0;i<AKAI_DDTAKE_MAXNUM;i++){
			if (pp->head.dd.take[i].stat==AKAI_DDTAKESTAT_FREE){ /* free? */
				continue; /* next */
			}
			tott++;
			/* print DD take info */
			akai_ddtake_info(pp,i,0);
		}
		PRINTF_OUT("---------------------------------------------------------------------------\n");
		PRINTF_OUT("total: %3u take(s)\n\n",tott);

		return;
	}

	/* now, floppy or harddisk sampler partition */

	PRINTF_OUT("/disk%u/%c\n",pp->diskp->index,pp->letter);
	PRINTF_OUT("vnr   vname         %s  startblk  osver  type\n",
		(pp->type==PART_TYPE_HD9)?"alias":"lnum");
	PRINTF_OUT("---------------------------------------------------\n");
	/* volumes in partition */
	ai=0;
	for (vi=0;vi<pp->volnummax;vi++){
		/* get volume */
		if (akai_get_vol(pp,&tmpvol,vi)<0){
			continue; /* next volume */
		}
		ai++;
		/* print volume info */
		akai_vol_info(&tmpvol,ai,0);
	}
	PRINTF_OUT("---------------------------------------------------\n");
	PRINTF_OUT("total: %3u volume(s) (max. %3u)\n\n",ai,pp->volnummax);

	if (recflag){
		/* volumes in partition */
		ai=0;
		for (vi=0;vi<pp->volnummax;vi++){
			/* get volume */
			if (akai_get_vol(pp,&tmpvol,vi)<0){
				continue; /* next volume */
			}
			if (tmpvol.type!=AKAI_VOL_TYPE_INACT){
				/* list volume directory */
				akai_list_vol(&tmpvol,filtertagp);
			}
		}
	}
}



/* find partition with given name (letter) */
struct part_s *
akai_find_part(struct disk_s *dp,char *name)
{
	u_int p;
	char namebuf[16]; /* XXX */

	if ((dp==NULL)||(name==NULL)){
		return NULL;
	}

	for (p=0;p<part_num;p++){
		/* Note: allow even invalid partition to be found!!! */

		if (part[p].diskp!=dp){ /* not on this disk? */
			continue; /* next */
		}

		/* name */
		if (part[p].type==PART_TYPE_DD){
			if (part[p].letter==0){
				sprintf(namebuf,"DD");
			}else{
				sprintf(namebuf,"DD%u",(u_int)part[p].letter);
			}
		}else{
			sprintf(namebuf,"%c",part[p].letter);
		}

		/* compare name (case-insensitive) */
		if (strcasecmp(namebuf,name)==0){ /* match? */
			return &part[p]; /* found */
		}
	}

	return NULL; /* not found */
}



void
print_fat(struct part_s *pp)
{
	u_int i,ilim;
	u_int j;

	if ((pp==NULL)||(!pp->valid)||(pp->fat==NULL)){
		return;
	}

	PRINTF_OUT("%s    +0   +1   +2   +3   +4   +5   +6   +7\n----------------------------------------------\n",
		(pp->type==PART_TYPE_DD)?"clu":"blk");
	ilim=(pp->type==PART_TYPE_DD)?pp->csize:pp->bsize;
	for (i=0;i<ilim;i++){
		if ((i%8)==0){
			PRINTF_OUT("%04x: ",i);
		}
		j=(pp->fat[i][1]<<8)+pp->fat[i][0];
		if (pp->type==PART_TYPE_DD){ /* harddisk DD partition? */
			switch (j){
			case AKAI_DDFAT_CODE_FREE:
				PRINTF_OUT(" -   ");
				break;
			case AKAI_DDFAT_CODE_BAD:
				PRINTF_OUT(" BAD ");
				break;
			case AKAI_DDFAT_CODE_SYS:
				PRINTF_OUT(" SYS ");
				break;
			case AKAI_DDFAT_CODE_END:
				PRINTF_OUT(" END ");
				break;
			default:
				PRINTF_OUT(" %04x",j);
				break;
			}
		}else{
			if (i<pp->bsyssize){ /* reserved block for system? */
				PRINTF_OUT(" SYS ");
			}else{
				switch (j){
				case AKAI_FAT_CODE_FREE: /* XXX same as AKAI_FAT_CODE_SYS900FL */
					PRINTF_OUT(" -   ");
					break;
				case AKAI_FAT_CODE_BAD:
					PRINTF_OUT(" BAD ");
					break;
				case AKAI_FAT_CODE_SYS900HD:
				case AKAI_FAT_CODE_SYS: /* XXX same as AKAI_FAT_CODE_DIREND1000HD */
					PRINTF_OUT(" SYS ");
					break;
				case AKAI_FAT_CODE_FILEEND900: /* XXX same as AKAI_FAT_CODE_DIREND900HD, AKAI_FAT_CODE_DIREND3000 */
				case AKAI_FAT_CODE_FILEEND:
					PRINTF_OUT(" END ");
					break;
				default:
					PRINTF_OUT(" %04x",j);
					break;
				}
			}
		}
		if (((i%8)==7)||(i+1==ilim)){
			PRINTF_OUT("\n");
		}
	}
	PRINTF_OUT("----------------------------------------------\n");
}



void
akai_disk_info(struct disk_s *dp,int verbose)
{
	u_int totp;
	u_int totf;
	u_int totb;
	u_int pi;

	if (dp==NULL){
		return;
	}

	/* scan all partitions in system */
	totp=0;
	totf=0;
	totb=0;
	for (pi=0;pi<part_num;pi++){
		if (part[pi].diskp!=dp){ /* not this disk? */
			continue;
		}
		totp++;
		if (part[pi].valid){ /* valid? */
			totf+=part[pi].bfree;
			totb+=part[pi].bbad;
		}
	}
	if (verbose){
		PRINTF_OUT("disk:     %u\n",dp->index);
		PRINTF_OUT("fd:       %i\n",dp->fd);
#ifdef _VISUALCPP
		PRINTF_OUT("fldrn:    %i\n",dp->fldrn);
#endif /* _VISUALCPP */
		PRINTF_OUT("start:    byte 0x%02x%08x\n",
			(u_int)(dp->startoff>>32),
			(u_int)(0xffffffff&dp->startoff));
		PRINTF_OUT("parts:    %u\n",totp);
		PRINTF_OUT("disktype: ");
		switch (dp->type){
		case DISK_TYPE_FLL:
			PRINTF_OUT("low-density floppy");
			break;
		case DISK_TYPE_FLH:
			PRINTF_OUT("high-density floppy");
			break;
		case DISK_TYPE_HD9:
			PRINTF_OUT("S900 harddisk");
			break;
		case DISK_TYPE_HD:
			PRINTF_OUT("S1000/S3000 harddisk");
			break;
		default:
			PRINTF_OUT("\?\?\?");
			break;
		}
		PRINTF_OUT("\nblksize:  0x%04x bytes\n",dp->blksize);
		PRINTF_OUT("total:    0x%04x blocks (%6u KB)\n",
			dp->bsize,
			dp->totsize/1024); /* in KB */
		PRINTF_OUT("free:     0x%04x blocks (%6u KB) = %5.1lf%%\n",
			totf,
			(totf*dp->blksize)/1024, /* in KB */
			(dp->bsize>0)?(100.0*((double)totf)/((double)dp->bsize)):0.0);
		PRINTF_OUT("bad:      0x%04x blocks (%6u KB) = %5.1lf%%\n",
			totb,
			(totb*dp->blksize)/1024, /* in KB */
			(dp->bsize>0)?(100.0*((double)totb)/((double)dp->bsize)):0.0);
	}else{
		PRINTF_OUT(" %3u   ",dp->index);
		switch (dp->type){
		case DISK_TYPE_FLL:
			PRINTF_OUT("FLL");
			break;
		case DISK_TYPE_FLH:
			PRINTF_OUT("FLH");
			break;
		case DISK_TYPE_HD9:
			PRINTF_OUT("HD9");
			break;
		case DISK_TYPE_HD:
			PRINTF_OUT(" HD");
			break;
		default:
			PRINTF_OUT("\?\?\?");
			break;
		}
		PRINTF_OUT("   %3u   0x%04x   0x%04x   %5.1lf     0x%04x   %5.1lf  %5.1lf\n",
			totp,
			dp->blksize,
			dp->bsize,
			((double)dp->bsize*dp->blksize)/((double)1024*1024),
			totf,
			((double)totf*dp->blksize)/((double)1024*1024),
			(dp->bsize>0)?(100.0*((double)totf)/((double)dp->bsize)):0.0);
	}
}

void
akai_list_disk(struct disk_s *dp,int recflag,u_char *filtertagp)
{
	u_int p;
	u_int totp;

	if (dp==NULL){
		return;
	}

	PRINTF_OUT("/disk%u\n",dp->index);
	PRINTF_OUT("part  type startblk size/blks  size/MB  free/blks free/MB free/%%\n");
	PRINTF_OUT("----------------------------------------------------------------\n");
	/* partitions on disk */
	totp=0;
	for (p=0;p<part_num;p++){
		if (part[p].diskp==dp){ /* on this disk? */
			/* print partition info */
			akai_part_info(&part[p],0);
			totp++;
		}
	}
	PRINTF_OUT("----------------------------------------------------------------\n");
	PRINTF_OUT("total: %3u partition(s)\n\n",totp);

	if (recflag){
		/* partitions on disk */
		for (p=0;p<part_num;p++){
			if ((part[p].diskp==dp) /* on this disk? */
				&&(part[p].valid)){
				/* list root directory in partition */
				akai_list_part(&part[p],recflag,filtertagp);
			}
		}
	}
}

void
akai_list_alldisks(int recflag,u_char *filtertagp)
{
	u_int di;

	PRINTF_OUT("/\n");
	PRINTF_OUT("disk  type parts  blksize tot/blks  tot/MB  free/blks free/MB free/%%\n");
	PRINTF_OUT("--------------------------------------------------------------------\n");
	/* disks */
	for (di=0;di<disk_num;di++){
		/* print disk info */
		akai_disk_info(&disk[di],0);
	}
	PRINTF_OUT("--------------------------------------------------------------------\n");
	PRINTF_OUT("total: %3u disk(s)\n\n",disk_num);

	if (recflag){
		/* disks */
		for (di=0;di<disk_num;di++){
			/* list disk */
			akai_list_disk(&disk[di],recflag,filtertagp);
		}
	}
}



void
akai_sort_filetags(u_char *tagp)
{
	u_int i,j;
	u_char b;

	if (tagp==NULL){
		return;
	}

	for (i=0;i<(AKAI_FILE_TAGNUM-1);i++){
		for (j=i+1;j<AKAI_FILE_TAGNUM;j++){
			if (tagp[i]>tagp[j]){
				/* swap */
				b=tagp[i];
				tagp[i]=tagp[j];
				tagp[j]=b;
			}
		}
	}
}



int
akai_match_filetags(u_char *filtertagp,u_char *testtagp)
{
	u_int i,j;

	if (filtertagp==NULL){
		return 0; /* no filter, matches always */
	}
	if (testtagp==NULL){
		return -1; /* no match */
	}

	if (testtagp[0]==AKAI_FILE_TAGS1000){ /* XXX check if testtagp[] stems from S1000 */
		/* now, match only if all entries in filtertagp[] are free */
		for (i=0;i<AKAI_FILE_TAGNUM;i++){
			if ((filtertagp[i]!=AKAI_FILE_TAGFREE)&&(filtertagp[i]!=AKAI_FILE_TAGS1000)){ /* not free? */
				return -1; /* no match */
			}
		}

		return 0; /* match */
	}

	for (i=0;i<AKAI_FILE_TAGNUM;i++){
		if ((filtertagp[i]==AKAI_FILE_TAGFREE)||(filtertagp[i]==AKAI_FILE_TAGS1000)){ /* free? */
			continue; /* next */
		}
		for (j=0;j<AKAI_FILE_TAGNUM;j++){
			if ((testtagp[i]==AKAI_FILE_TAGFREE)||(testtagp[i]==AKAI_FILE_TAGS1000)){ /* free? */
				continue; /* next */
			}
			if (testtagp[j]==filtertagp[i]){ /* found? */
				break; /* found entry i */
			}
		}
		if (j==AKAI_FILE_TAGNUM){ /* not found? */
			return -1; /* no match */
		}
	}
	/* now, all non-free entries of filtertagp[] are found in testtagp[] => match */

	return 0; /* match */
}



int
akai_set_filetag(u_char *tagp,u_int ti)
{
	u_int i;

	if (tagp==NULL){
		return -1;
	}

	if ((ti<1)||(ti>AKAI_PARTHEAD_TAGNUM)){
		PRINTF_ERR("invalid tag index\n");
		return -1;
	}

	/* check if already present */
	for (i=0;i<AKAI_FILE_TAGNUM;i++){
		if (tagp[i]==ti){
			return 0; /* nothing to do */
		}
	}

	/* find free entry */
	for (i=0;i<AKAI_FILE_TAGNUM;i++){
		if ((tagp[i]==AKAI_FILE_TAGFREE)||(tagp[i]==AKAI_FILE_TAGS1000)){ /* free? */
			break; /* found */
		}
	}
	if (i==AKAI_FILE_TAGNUM){ /* none found? */
		PRINTF_OUT("no free tag entry\n");
		return -1;
	}
	/* set tag */
	tagp[i]=ti;

	akai_sort_filetags(tagp);

	return 0;
}



int
akai_clear_filetag(u_char *tagp,u_int ti)
{
	u_int i;

	if (tagp==NULL){
		return -1;
	}

	/* XXX allow to clear invalid tag index */

	if (ti==AKAI_FILE_TAGFREE){
		/* clear all */
		for (i=0;i<AKAI_FILE_TAGNUM;i++){
			tagp[i]=AKAI_FILE_TAGFREE;
		}
	}else if (ti==AKAI_FILE_TAGS1000){
		/* clear all */
		for (i=0;i<AKAI_FILE_TAGNUM;i++){
			tagp[i]=AKAI_FILE_TAGS1000;
		}
	}else{
		/* find entry */
		for (i=0;i<AKAI_FILE_TAGNUM;i++){
			if (tagp[i]==ti){
				/* clear */
				tagp[i]=AKAI_FILE_TAGFREE;
				/* Note: must not break, must find (although invalid) repitions as well */
			}
		}
	}

	akai_sort_filetags(tagp);

	return 0;
}



/* get file with index fi */
/* saves result in *fp */
int
akai_get_file(struct vol_s *vp,struct file_s *fp,u_int fi)
{

	if ((vp==NULL)||(vp->type==AKAI_VOL_TYPE_INACT)||(vp->file==NULL)){
		return -1;
	}
	if ((vp->partp==NULL)||(!vp->partp->valid)){
		return -1;
	}
	if (fp==NULL){
		return -1;
	}

	if (fi>=vp->fimax){
		return -1;
	}

	/* in volume */
	fp->volp=vp;

	/* index */
	fp->index=fi;

	/* type */
	fp->type=vp->file[fi].type;
	if (fp->type==AKAI_FTYPE_FREE){ /* free entry? */
		return -1;
	}

	/* tags */
	bcopy(vp->file[fi].tag,fp->tag,AKAI_FILE_TAGNUM);

	/* osver: */
	/* if S1000/S3000: OS version */
	/* if S900 compressed file: number of un-compressed floppy blocks */
	/* else: zero */
	fp->osver=(vp->file[fi].osver[1]<<8)
			  +vp->file[fi].osver[0];

	/* name */
	akai2ascii_filename(vp->file[fi].name,(u_char)fp->type,fp->osver,fp->name,vp->type==AKAI_VOL_TYPE_S900);

	/* size */
	fp->size=(vp->file[fi].size[2]<<16)
			 +(vp->file[fi].size[1]<<8)
			 +vp->file[fi].size[0];

	/* start */
	fp->bstart=(vp->file[fi].start[1]<<8)
			   +vp->file[fi].start[0];

	return 0;
}



/* find file with given name */
/* saves result in *fp */
int
akai_find_file(struct vol_s *vp,struct file_s *fp,char *name)
{
	u_int fi;
	char namebuf[AKAI_NAME_LEN+4+1]; /* +4 for ".<type>", +1 for '\0' */
	u_int i,j,k,l;

	if ((vp==NULL)||(vp->type==AKAI_VOL_TYPE_INACT)){
		return -1;
	}
	if ((vp->partp==NULL)||(!vp->partp->valid)){
		return -1;
	}
	if (fp==NULL){
		return -1;
	}
	if (name==NULL){
		return -1;
	}

	/* fix name */
	l=(u_int)strlen(name);
	if (l>AKAI_NAME_LEN+4){ /* too long for namebuf? */
		return -1;
	}
	j=l; /* end */
	for (i=0;i<l;i++){
		if (name[i]=='_'){ /* for keyboard entry */
			namebuf[i]=' ';
		}else{
			namebuf[i]=name[i];
			if (name[i]=='.'){
				j=i; /* last '.' so far */
			}
		}
	}
	namebuf[i]='\0';
	/* remove trailing ' ' (XXX don't care how many) before last '.' (or end if no '.') */
	k=j;
	for (i=0;i<j;i++){
		if (namebuf[j-i-1]==' '){
			namebuf[j-i-1]='\0';
			k--;
		}else{
			break;
		}
	}
	if ((k<j)&&(j<l)){
		/* move ".<type>" */
		for (i=0;(k+i<l)&&(j+i<l);i++){
			namebuf[k+i]=namebuf[j+i];
		}
		namebuf[k+i]='\0';
	}

	/* files in volume directory */
	for (fi=0;fi<vp->fimax;fi++){
		/* get file */
		if (akai_get_file(vp,fp,fi)<0){
			continue; /* next file */
		}

		/* compare name (case-insensitive) */
		if (strcasecmp(fp->name,namebuf)==0){ /* match? */
			return 0; /* found */
		}
	}

	return -1; /* not found */
}



int
akai_rename_file(struct file_s *fp,char *name,struct vol_s *vp,u_int dstindex,u_char *tagp,u_int osver)
{
	u_int i;
	u_int diffvolflag;
	u_int fisrc,fidst;
	struct vol_s *volsrcp,*voldstp;
	u_char ft;
	u_char anamebuf[AKAI_NAME_LEN];

	if ((fp==NULL)||(fp->type==AKAI_FTYPE_FREE)){
		return -1;
	}
	if ((fp->volp==NULL)||(fp->volp->type==AKAI_VOL_TYPE_INACT)||(fp->volp->file==NULL)){
		return -1;
	}
	if ((fp->volp->partp==NULL)||(!fp->volp->partp->valid)){
		return -1;
	}

	if ((vp!=NULL)&&((vp->type==AKAI_VOL_TYPE_INACT)||(vp->file==NULL))){
		return -1;
	}
	if ((vp!=NULL)&&(vp->partp!=fp->volp->partp)){ /* if vp specified: not on same partition? */
		PRINTF_OUT("must be in same partition\n");
		return -1;
	}

	/* Note: must not compare pointers (vp and fp->volp) if the buffer behind volume pointer is not unique for each volume!!! */
	/*       use dirblk[0] instead, which is unique if same partition (see check above) */
	if ((vp!=NULL)&&(vp->dirblk[0]!=fp->volp->dirblk[0])){ /* different volume specified? */
		diffvolflag=1;
	}else{
		diffvolflag=0;
	}

	if ((name==NULL)&&((osver!=fp->osver)||diffvolflag)){ /* no new name but different OS version or different volume? */
		/* possibly different char conversion or possibly need to correct osver -> handle as new name below */
		name=fp->name;
	}

	if ((diffvolflag==0)&&(name==NULL)&&(tagp==NULL)){ /* nothing to do? */
		return 0; /* done */
	}

	/* source file */
	fisrc=fp->index;
	volsrcp=fp->volp;

	if (diffvolflag){ /* different volume specified? */
		voldstp=vp;
		if (dstindex==AKAI_CREATE_FILE_NOINDEX){ /* no user-supplied index? */
			/* find free destination volume directory entry */
			for (fidst=0;fidst<voldstp->fimax;fidst++){
				if (voldstp->file[fidst].type==AKAI_FTYPE_FREE){ /* free? */
					break; /* found one */
				}
			}
			if (fidst==voldstp->fimax){ /* none found? */
				PRINTF_OUT("no free file entry found\n");
				return -1;
			}
		}else{
			if (voldstp->file[dstindex].type!=AKAI_FTYPE_FREE){ /* not free? */
				PRINTF_OUT("destination index not free\n");
				return -1;
			}
			fidst=dstindex;
		}
	}else{
		/* same volume */
		voldstp=volsrcp;
		if ((dstindex==AKAI_CREATE_FILE_NOINDEX) /* no user-supplied index? */
			||(dstindex==fisrc)){ /* or same as before? */
			fidst=fisrc; /* keep index */
		}else{
			if (voldstp->file[dstindex].type!=AKAI_FTYPE_FREE){ /* not free? */
				PRINTF_OUT("destination index not free\n");
				return -1;
			}
			fidst=dstindex; /* new index */
		}
	}

	/* name and type */
	ft=0;
	if (name!=NULL){ /* new name? */
		/* new name and type and possibly correct osver */
		ft=ascii2akai_filename(name,anamebuf,&osver,voldstp->type==AKAI_VOL_TYPE_S900);
		if (ft==AKAI_FTYPE_FREE){
			PRINTF_ERR("invalid name\n");
			return -1;
		}
	}

	/* update volume entries (after having checked the name!!!) */

	if (diffvolflag||(fidst!=fisrc)){ /* different volume specified, or different index within same volume? */
		/* create destination volume directory entry */
		bcopy(&volsrcp->file[fisrc],&voldstp->file[fidst],sizeof(struct akai_voldir_entry_s));

		/* new volume */
		fp->volp=voldstp;

		/* new index */
		fp->index=fidst;

		/* free old volume directory entry */
		bzero(&volsrcp->file[fisrc],sizeof(struct akai_voldir_entry_s));
		volsrcp->file[fisrc].type=AKAI_FTYPE_FREE;

		/* write source volume directory */
		if (akai_write_voldir(volsrcp,fisrc)<0){
			return -1;
		}
	}

	/* set destination volume directory entry */

	if (osver!=fp->osver){ /* different OS version? */
		/* new OS version */
		fp->osver=osver;
		voldstp->file[fidst].osver[1]=0xff&(fp->osver>>8);
		voldstp->file[fidst].osver[0]=0xff&fp->osver;
	}

	/* name and type */
	if (name!=NULL){ /* new name/type? */
		/* new name */
		if (vp->type==AKAI_VOL_TYPE_S900){
			bcopy(anamebuf,voldstp->file[fidst].name,AKAI_NAME_LEN_S900);
			bzero(voldstp->file[fidst].name+AKAI_NAME_LEN_S900,AKAI_NAME_LEN-AKAI_NAME_LEN_S900);
		}else{
			bcopy(anamebuf,voldstp->file[fidst].name,AKAI_NAME_LEN);
		}
		if (name!=fp->name){ /* not already in fp->name? */
			/* copy */
			strcpy(fp->name,name); /* Note: name has been checked above */
		}

		/* new type */
		voldstp->file[fidst].type=ft;
		fp->type=ft;
	}
	/* else: keep old name and type */
	/*       No need to copy since whole dir entry has been copied above if necessary */

	/* tags */
	if (vp->type==AKAI_VOL_TYPE_S900){
		/* S900 */
		/* no tags */
		for (i=0;i<AKAI_FILE_TAGNUM;i++){
			fp->tag[i]=AKAI_FILE_TAGFREE;
		}
		bzero(voldstp->file[fidst].tag,AKAI_FILE_TAGNUM);
	}else{
		/* S1000/S3000 */
		if (tagp!=NULL){ /* new tags? */
			/* copy tags */
			bcopy(tagp,voldstp->file[fidst].tag,AKAI_FILE_TAGNUM);
			akai_sort_filetags(voldstp->file[fidst].tag);
			if (tagp!=fp->tag){ /* not already in fp->tag? */
				/* copy */
				bcopy(voldstp->file[fidst].tag,fp->tag,AKAI_FILE_TAGNUM);
				/* XXX no check for validity of tag indices */
			}
		}
		/* else: keep old tags */
		/*       No need to copy since whole dir entry has been copied above if necessary */
	}

	/* write destination volume directory */
	/* Note: the same partition for source and destination */
	if (akai_write_voldir(voldstp,fidst)<0){
		return -1;
	}

	return 0;
}



/* uses *fp as output */
/* size in bytes */
int
akai_create_file(struct vol_s *vp,struct file_s *fp,u_int size,u_int index,char *name,u_int osver,u_char *tagp)
{
	u_char ft;
	u_int bsize;
	u_int fi;
	u_int i;
	u_char anamebuf[AKAI_NAME_LEN];

	if ((vp==NULL)||(vp->type==AKAI_VOL_TYPE_INACT)||(vp->file==NULL)){
		return -1;
	}
	if ((vp->partp==NULL)||(!vp->partp->valid)||(vp->partp->blksize==0)){
		return -1;
	}
	if (fp==NULL){
		return -1;
	}
	if (name==NULL){
		return -1;
	}

	if (size>AKAI_FILE_SIZEMAX){
		PRINTF_ERR("file size is too large\n");
		return -1;
	}

	/* volume */
	fp->volp=vp;

	/* name and type and possibly correct osver */
	ft=ascii2akai_filename(name,anamebuf,&osver,vp->type==AKAI_VOL_TYPE_S900);
	if (ft==AKAI_FTYPE_FREE){
		PRINTF_ERR("invalid name\n");
		return -1;
	}
	/* now, lengths also OK */
	strcpy(fp->name,name);
	fp->type=ft;

	/* osver */
	fp->osver=osver;

	/* index */
	if (index==AKAI_CREATE_FILE_NOINDEX){ /* no user-supplied volume index? */
		/* find free volume directory entry */
		for (fi=0;fi<vp->fimax;fi++){
			if (vp->file[fi].type==AKAI_FTYPE_FREE){ /* free? */
				break; /* found one */
			}
		}
		if (fi==vp->fimax){ /* none found? */
			PRINTF_OUT("no free file entry found\n");
			return -1;
		}
	}else{
		/* user-supplied index */
		fi=index;
		if (fi>vp->fimax){
			return -1;
		}
		if (vp->file[fi].type!=AKAI_FTYPE_FREE){ /* not free? */
			PRINTF_OUT("file entry not free\n");
			return -1;
		}
	}
	fp->index=fi;

	/* size */
	fp->size=size;

	/* tags */
	if (vp->type==AKAI_VOL_TYPE_S900){
		/* S900 */
		/* no tags */
		for (i=0;i<AKAI_FILE_TAGNUM;i++){
			fp->tag[i]=AKAI_FILE_TAGFREE;
		}
	}else{
		/* S1000/S3000 */
		if (tagp!=NULL){
			/* correct and copy */
			/* Note: even if tagp==fp->tag */
			for (i=0;i<AKAI_FILE_TAGNUM;i++){
				if ((tagp[i]>=1)&&(tagp[i]<=AKAI_PARTHEAD_TAGNUM)){
					fp->tag[i]=tagp[i];
				}else{
					/* set free */
					if (fp->osver<=AKAI_OSVER_S1100MAX){
						fp->tag[i]=AKAI_FILE_TAGS1000;
					}else{
						fp->tag[i]=AKAI_FILE_TAGFREE;
					}
				}
			}
			akai_sort_filetags(fp->tag);
		}else{
			/* all free */
			for (i=0;i<AKAI_FILE_TAGNUM;i++){
				if (fp->osver<=AKAI_OSVER_S1100MAX){
					fp->tag[i]=AKAI_FILE_TAGS1000;
				}else{
					fp->tag[i]=AKAI_FILE_TAGFREE;
				}
			}
		}
	}

	/* allocate space */
	bsize=(fp->size+fp->volp->partp->blksize-1)/fp->volp->partp->blksize; /* round up */
	if (akai_allocate_fatchain(vp->partp,bsize,
							   &fp->bstart,
							   1,
							   (vp->type==AKAI_VOL_TYPE_S900)?AKAI_FAT_CODE_FILEEND900:AKAI_FAT_CODE_FILEEND)<0){ /* end code */
		return -1;
	}

	/* create volume directory entry */
	bzero(&vp->file[fi],sizeof(struct akai_voldir_entry_s));
	if (vp->type==AKAI_VOL_TYPE_S900){
		/* S900 */
		/* name */
		bcopy(anamebuf,vp->file[fi].name,AKAI_NAME_LEN_S900);
		bzero(vp->file[fi].name+AKAI_NAME_LEN_S900,AKAI_NAME_LEN-AKAI_NAME_LEN_S900);
		/* no tags */
		bzero(vp->file[fi].tag,AKAI_FILE_TAGNUM);
	}else{
		/* S1000/S3000 */
		/* name */
		bcopy(anamebuf,vp->file[fi].name,AKAI_NAME_LEN);
		/* tags */
		bcopy(fp->tag,vp->file[fi].tag,AKAI_FILE_TAGNUM);
	}
	/* type */
	vp->file[fi].type=fp->type;
	/* size */
	vp->file[fi].size[2]=0xff&(fp->size>>16);
	vp->file[fi].size[1]=0xff&(fp->size>>8);
	vp->file[fi].size[0]=0xff&fp->size;
	/* start */
	vp->file[fi].start[1]=0xff&(fp->bstart>>8);
	vp->file[fi].start[0]=0xff&fp->bstart;
	/* OS version */
	vp->file[fi].osver[1]=0xff&(fp->osver>>8);
	vp->file[fi].osver[0]=0xff&fp->osver;

	/* write volume directory */
	if (akai_write_voldir(vp,fi)<0){
		return -1;
	}

	return 0;
}



void
akai_fvol1000_initfile(struct akai_voldir_entry_s *ep,u_int osver,u_int tag)
{
	u_int i;

	if (ep==NULL){
		return;
	}

	/* OS version */
	ep->osver[1]=0xff&(osver>>8);
	ep->osver[0]=0xff&osver;

	if (osver==AKAI_OSVER_S900VOL){
		/* S900 */
		/* zero name and tags */
		bzero(ep->name,AKAI_NAME_LEN);
		bzero(ep->tag,AKAI_FILE_TAGNUM);
	}else{
		/* S1000/S3000 */
		/* default name */
		ascii2akai_name(AKAI_EMPTY1000_FNAME,ep->name,0); /* 0: not S900 */
		/* default tags */
		for (i=0;i<AKAI_FILE_TAGNUM;i++){
			ep->tag[i]=tag;
		}
	}

	/* type: free entry */
	ep->type=AKAI_FTYPE_FREE;
}

int
akai_delete_file(struct file_s *fp)
{
	u_int fi;

	if ((fp==NULL)||(fp->type==AKAI_FTYPE_FREE)){
		return -1;
	}
	if ((fp->volp==NULL)||(fp->volp->type==AKAI_VOL_TYPE_INACT)||(fp->volp->file==NULL)){
		return -1;
	}
	if ((fp->volp->partp==NULL)||(!fp->volp->partp->valid)){
		return -1;
	}

	/* file index */
	fi=fp->index;

	/* free volume directory entry */
	bzero(&fp->volp->file[fi],sizeof(struct akai_voldir_entry_s));
	if ((fp->volp->partp->type==PART_TYPE_HD9)
		||(fp->volp->partp->type==PART_TYPE_HD)
		||(fp->volp->type==AKAI_VOL_TYPE_S3000)||(fp->volp->type==AKAI_VOL_TYPE_CD3000)){
		/* harddisk and/or S3000 volume */
		fp->volp->file[fi].type=AKAI_FTYPE_FREE;
	}else{
		/* S900/S1000 floppy */
		akai_fvol1000_initfile(&fp->volp->file[fi],fp->volp->osver,AKAI_FILE_TAGS1000);
	}

	/* write volume directory */
	if (akai_write_voldir(fp->volp,fi)<0){
		return -1;
	}

	/* free FAT chain */
	if (akai_free_fatchain(fp->volp->partp,fp->bstart,1)<0){ /* 1: write FAT */
		PRINTF_ERR("failed to free blocks of file, however, volume directory entry deleted\n");
		return -1;
	}

	return 0;
}



int
akai_rename_ddtake(struct part_s *pp,u_int ti,char *name)
{

	if ((pp==NULL)||(!pp->valid)||(pp->fat==NULL)){
		return -1;
	}
	if (pp->type!=PART_TYPE_DD){
		return -1;
	}

	if (ti>=AKAI_DDTAKE_MAXNUM){
		return -1;
	}

	if (name==NULL){
		return -1;
	}

	if (pp->head.dd.take[ti].stat==AKAI_DDTAKESTAT_FREE){ /* free? */
		return -1;
	}

	/* rename */
	ascii2akai_name(name,pp->head.dd.take[ti].name,0); /* 0: not S900 */

	/* write partition header */
	if (akai_io_blks(pp,(u_char *)&pp->head.dd,
					 0,
					 AKAI_DDPARTHEAD_BLKS,
					 1,IO_BLKS_WRITE)<0){ /* 1: allocate cache if possible */
		return -1;
	}

	return 0;
}

int
akai_delete_ddtake(struct part_s *pp,u_int ti)
{
	u_int cstarts,cstarte;

	if ((pp==NULL)||(!pp->valid)||(pp->fat==NULL)){
		return -1;
	}
	if (pp->type!=PART_TYPE_DD){
		return -1;
	}

	if (ti>=AKAI_DDTAKE_MAXNUM){
		return -1;
	}

	if (pp->head.dd.take[ti].stat==AKAI_DDTAKESTAT_FREE){ /* free? */
		return -1;
	}

	/* free DD FAT chains */
	cstarts=(pp->head.dd.take[ti].cstarts[1]<<8)
		    +pp->head.dd.take[ti].cstarts[0];
	if (cstarts!=0){ /* sample not empty? */
		if (akai_free_ddfatchain(pp,cstarts,0)<0){ /* 0: don't write header yet */
			return -1;
		}
	}
	cstarte=(pp->head.dd.take[ti].cstarte[1]<<8)
		    +pp->head.dd.take[ti].cstarte[0];
	if (cstarte!=0){ /* envelope not empty? */
		if (akai_free_ddfatchain(pp,cstarte,0)<0){ /* 0: don't write header yet */
			return -1;
		}
	}

	/* wipe directory entry for take */
	bzero(&pp->head.dd.take[ti],sizeof(struct akai_ddtake_s));
	pp->head.dd.take[ti].stat=AKAI_DDTAKESTAT_FREE; /* free */

	/* count free and bad blocks */
	akai_countfree_part(pp);

	/* write partition header */
	if (akai_io_blks(pp,(u_char *)&pp->head.dd,
					 0,
					 AKAI_DDPARTHEAD_BLKS,
					 1,IO_BLKS_WRITE)<0){ /* 1: allocate cache if possible */
		return -1;
	}

	return 0;
}



void
akai_fix_partheadmagic(struct part_s *pp)
{
	u_int i;
	u_int m,cs;

	/* Note: allow invalid partitions to be fixed!!! */
	if (pp==NULL){
		return;
	}
	if (pp->type!=PART_TYPE_HD){
		return;
	}
	/* now, S1000/S3000 harddisk sampler partition */

	cs=pp->bsize;

	/* set magic */
	for (i=0;i<AKAI_PARTHEAD_MAGICNUM;i++){
		m=0xffff&(i*AKAI_PARTHEAD_MAGICVAL);
		pp->head.hd.magic[i][1]=0xff&(m>>8);
		pp->head.hd.magic[i][0]=0xff&m;
		cs+=m;
	}

	/* set checksum */
	pp->head.hd.chksum[3]=0xff&(cs>>24);
	pp->head.hd.chksum[2]=0xff&(cs>>16);
	pp->head.hd.chksum[1]=0xff&(cs>>8);
	pp->head.hd.chksum[0]=0xff&cs;
}

int
akai_check_partheadmagic(struct part_s *pp)
{
	u_int i;
	u_int m,cs,c;

	/* Note: allow invalid partitions to be checked!!! */
	if (pp==NULL){
		return -1;
	}
	if (pp->type==PART_TYPE_DD){
		return -1;
	}

	cs=pp->bsize;

	/* magic */
	for (i=0;i<AKAI_PARTHEAD_MAGICNUM;i++){
		m=(pp->head.hd.magic[i][1]<<8)
		  +pp->head.hd.magic[i][0];
		if (m!=(0xffff&(i*AKAI_PARTHEAD_MAGICVAL))){
			return -1; /* magic error */
		}
		cs+=m;
	}

	/* checksum */
	c=(pp->head.hd.chksum[3]<<24)
	  +(pp->head.hd.chksum[2]<<16)
	  +(pp->head.hd.chksum[1]<<8)
	  +pp->head.hd.chksum[0];
	if (c!=cs){
		return -2; /* only checksum error */
	}

	return 0; /* OK */
}

void
akai_fix_parttabmagic(struct akai_parttab_s *ptp)
{
	u_int i;
	u_int m;

	if (ptp==NULL){
		return;
	}

	/* set magic */
	for (i=0;i<AKAI_PARTTAB_MAGICNUM;i++){
		m=0xffff&(i*AKAI_PARTTAB_MAGICVAL);
		ptp->magic[i][1]=0xff&(m>>8);
		ptp->magic[i][0]=0xff&m;
	}
}

int
akai_check_parttabmagic(struct akai_parttab_s *ptp)
{
	u_int i;
	u_int m;

	if (ptp==NULL){
		return -1;
	}

	/* magic */
	for (i=0;i<AKAI_PARTTAB_MAGICNUM;i++){
		m=(ptp->magic[i][1]<<8)+ptp->magic[i][0];
		if (m!=(0xffff&(i*AKAI_PARTTAB_MAGICVAL))){
			return -1; /* magic error */
		}
	}

	return 0; /* OK */
}



int
akai_scan_floppy(struct disk_s *dp)
{
	u_int i,n;

	if (dp==NULL){
		return -1;
	}

	/* disk blocksize and total size */
	if (dp->blksize!=AKAI_FL_BLOCKSIZE){ /* wrong blocksize? */
		/* set new blksize */
		dp->blksize=AKAI_FL_BLOCKSIZE;
		/* disk size in blocks */
		dp->bsize=dp->totsize/dp->blksize;
	} /* else: assume that dp->bsize is valid */

#ifdef _VISUALCPP
	if (dp->fldrn>=0){ /* is floppy drive? */
		/* Note: open_disk() has set disk type */
		/* check if no floppy inserted */
		if (fldr_checkfloppyinserted(dp->fldrn)!=0){
			PRINTF_OUT("disk%u: no floppy inserted\n",dp->index);
			/* discard this partition, keep old part_num */
			return -1;
		}
	}else /* no floppy drive */
#endif /* _VISUALCPP */
	{
		/* set disk type */
		/* Note: requires valid dp->bsize (see above) */
		if (dp->bsize>=AKAI_FLH_SIZE){
			dp->type=DISK_TYPE_FLH;
		}else{
			dp->type=DISK_TYPE_FLL;
		}
	}

	/* disk pointer */
	part[part_num].diskp=dp;

	part[part_num].valid=1; /* OK so far */

	/* set partition type = disk type (first guess) */
	part[part_num].type=dp->type;

	/* fake index on disk */
	part[part_num].index=0;

	/* block size */
	part[part_num].blksize=dp->blksize;

	/* start block */
	part[part_num].bstart=0;

	/* size (Note: also needed to read floppy header below) */
	/* first guess: disk size (Note: part[part_num].bstart==0) */
	part[part_num].bsize=dp->bsize;

	/* try to read floppy header at block 0 relative to start */
	/* Note: low- and high-density floppy headers are identical */
	/*       up to first AKAI_FAT_ENTRIES_FLL FAT-entries!!! */
	/* => try to read the largest header (AKAI_FLHHEAD_BLKS blocks) */
	/* XXX bad luck for low-density floppy which has bad block(s) */
	/*     at AKAI_FLLHEAD_BLKS <= block < AKAI_FLHHEAD_BLKS */
	if ((part[part_num].bsize<AKAI_FLHHEAD_BLKS)
		||(akai_io_blks(&part[part_num],(u_char *)&part[part_num].head.flh,
						0,
						AKAI_FLHHEAD_BLKS,
						1,IO_BLKS_READ)<0)){ /* 1: alloc cache if possible */
		PRINTF_OUT("disk%u: cannot read floppy header\n",dp->index);
		/* discard this partition, keep old part_num */
		/* assume end of disk, done */
		return -1;
	}

	/* start of FAT */
	/* Note: see comment above: FAT-start same for low- and high density */
	part[part_num].fat=&part[part_num].head.flh.fatblk[0];

	part[part_num].bsize=AKAI_FLH_SIZE;
	/* count S1000/S3000 system blocks in FAT */
	for (i=0;i<(AKAI_FLHHEAD_BLKS+AKAI_VOLDIR3000FL_BLKS);i++){
		n=(part[part_num].fat[i][1]<<8)+part[part_num].fat[i][0];
		if (n!=AKAI_FAT_CODE_SYS){ /* not reserved for system anymore? */
			break; /* end of system blocks */
		}
	}
	if (i>0){
		/* assume S1000/S3000 */
		/* reserved blocks for system */
		part[part_num].bsyssize=i;
		/* check floppy type */
		if ((dp->bsize>=AKAI_FLL_SIZE)&&(dp->bsize<AKAI_FLH_SIZE)
			&&((i==AKAI_FLLHEAD_BLKS)||(i==(AKAI_FLLHEAD_BLKS+AKAI_VOLDIR3000FL_BLKS)))){ /* S1000 or S3000 low-density? */
			/* if correct, must be low-density floppy now */
			part[part_num].type=PART_TYPE_FLL;
			part[part_num].bsize=AKAI_FLL_SIZE;
			/* Note: see comment above: no need to read low-density header again */
		}else if ((dp->bsize>=AKAI_FLH_SIZE)
				  &&((i==AKAI_FLHHEAD_BLKS)||(i==(AKAI_FLHHEAD_BLKS+AKAI_VOLDIR3000FL_BLKS)))){ /* S1000 or S3000 high-density? */
			/* if correct, must be high-density floppy now */
			part[part_num].type=PART_TYPE_FLH;
			part[part_num].bsize=AKAI_FLH_SIZE;
		}else{
			return -1;
		}
	}else{
		/* count S900 system blocks in FAT */
		for (i=0;i<AKAI_FLHHEAD_BLKS;i++){
			n=(part[part_num].fat[i][1]<<8)+part[part_num].fat[i][0];
			if (n!=AKAI_FAT_CODE_SYS900FL){ /* not reserved for system anymore? */
				break; /* end of system blocks */
			}
		}
		/* reserved blocks for system */
		part[part_num].bsyssize=i;
		/* check floppy type */
		/* Note: since AKAI_FAT_CODE_SYS900FL==AKAI_FAT_CODE_FREE, */
		/*       a low-density floppy may have i>=AKAI_FLLHEAD_BLKS here */
		if ((dp->bsize>=AKAI_FLL_SIZE)&&(dp->bsize<AKAI_FLH_SIZE)&&(i>=AKAI_FLLHEAD_BLKS)){ /* low-density? */
			/* if correct, must be low-density floppy now */
			part[part_num].type=PART_TYPE_FLL;
			part[part_num].bsize=AKAI_FLL_SIZE;
			/* Note: AKAI_FLLHEAD_BLKS system blocks, i may differ */
			part[part_num].bsyssize=AKAI_FLLHEAD_BLKS;
			/* Note: see comment above: no need to read low-density header again */
		}else if ((dp->bsize>=AKAI_FLH_SIZE)&&(i==AKAI_FLHHEAD_BLKS)){ /* high-density? */
			/* if correct, must be high-density floppy now */
			part[part_num].type=PART_TYPE_FLH;
			part[part_num].bsize=AKAI_FLH_SIZE;
		}else{
			return -1;
		}
	}
	/* Note: now, part[part_num].bsize <= dp->bsize */
	/* Note: if floppy drive -> open_disk() has set disk type, may differ from partition type */

	/* count free and bad blocks */
	akai_countfree_part(&part[part_num]);

	/* number of volumes */
	part[part_num].volnummax=1;

	/* fake partition letter */
	part[part_num].letter='A';

	part_num++; /* found one */

	return 0;
}



int
akai_scan_harddisk9(struct disk_s *dp)
{
	u_int i,n;

	if (dp==NULL){
		return -1;
	}
	
	/* set disk type */
	dp->type=DISK_TYPE_HD9;

	/* disk blocksize and total size */
	if (dp->blksize!=AKAI_HD_BLOCKSIZE){ /* wrong blocksize? */
		/* set new blksize */
		dp->blksize=AKAI_HD_BLOCKSIZE;
		/* disk size in blocks */
		dp->bsize=dp->totsize/dp->blksize;
	} /* else: assume that dp->bsize is valid */

	/* disk pointer */
	part[part_num].diskp=dp;

	part[part_num].valid=1; /* OK so far */

	/* set partition type = disk type */
	part[part_num].type=dp->type;

	/* fake index on disk */
	part[part_num].index=0;

	/* block size */
	part[part_num].blksize=dp->blksize;

	/* start block */
	part[part_num].bstart=0;

	/* size (Note: also needed to read harddisk header below) */
	/* first guess: disk size (Note: part[part_num].bstart==0) */
	part[part_num].bsize=dp->bsize;

	/* try to read S900 harddisk header at block 0 relative to start */
	if ((part[part_num].bsize<AKAI_HD9HEAD_BLKS)
		||(akai_io_blks(&part[part_num],(u_char *)&part[part_num].head.hd9,
						0,
						AKAI_HD9HEAD_BLKS,
						1,IO_BLKS_READ)<0)){ /* 1: alloc cache if possible */
		/* discard this partition, keep old part_num */
		PRINTF_OUT("disk%u: cannot read S900 harddisk header\n",dp->index);
		/* assume end of disk, done */
		return -1;
	}

	/* start of FAT */
	part[part_num].fat=&part[part_num].head.hd9.fatblk[0];

	/* count system blocks in FAT */
	for (i=0;i<AKAI_HD9HEAD_BLKS;i++){
		n=(part[part_num].fat[i][1]<<8)+part[part_num].fat[i][0];
		if (n!=AKAI_FAT_CODE_SYS900HD){ /* not reserved for system anymore? */
			/* discard this partition, keep old part_num */
			return -1;
		}
	}

	/* size */
	if (part[part_num].head.hd9.flag1==AKAI_HD9FLAG1_SIZEVALID){
		/* get size from header */
		part[part_num].bsize=(((u_int)part[part_num].head.hd9.size[1])<<8)
			+((u_int)part[part_num].head.hd9.size[0]);
		if (part[part_num].bsize>AKAI_HD9_MAXSIZE){
			PRINTF_OUT("disk%u: invalid size (0x%04x) of harddisk",
				dp->index,part[part_num].bsize);
			part[part_num].bsize=AKAI_HD9_MAXSIZE;
			PRINTF_OUT(", truncated (0x%04x)\n",part[part_num].bsize);
			/* XXX keep valid */
		}
	}else{
		/* default size */
		part[part_num].bsize=AKAI_HD9_DEFSIZE;
	}
	/* Note: part[part_num].bstart==0 */
	if (part[part_num].bsize>dp->bsize){
		PRINTF_OUT("disk%u: expected size (0x%04x) of harddisk is too large",
			dp->index,part[part_num].bsize);
		/* truncate */
		part[part_num].bsize=dp->bsize;
		PRINTF_OUT(", truncated (0x%04x)\n",part[part_num].bsize);
		/* XXX keep valid */
	}

	/* reserved blocks for system */
	part[part_num].bsyssize=AKAI_HD9HEAD_BLKS;

	/* count free and bad blocks */
	akai_countfree_part(&part[part_num]);

	/* number of volumes */
	part[part_num].volnummax=AKAI_HD9ROOTDIR_ENTRIES;

	/* fake partition letter */
	part[part_num].letter='A';

	part_num++; /* found one */

	return 0;
}



int
akai_scan_ddpart(struct disk_s *dp,struct akai_parttab_s *ptp,u_int bstart,u_int pi)
{
	u_int ptcsize;
	u_int ptbsize;
	u_int totsize;
	u_int di,dimax;

	if ((dp==NULL)||(ptp==NULL)){
		return -1;
	}
	if (bstart>dp->bsize){
		return -1;
	}

	/* get number of DD partitions */
	dimax=ptp->ddpartnum;
	if (dimax==0){
		return 0;
	}
	if (dimax>AKAI_DDPART_NUM){
		PRINTF_OUT("disk%u: invalid DD partition number (%u), truncated (%u)\n",
			dp->index,dimax,AKAI_DDPART_NUM);
		dimax=AKAI_PART_NUM; /* XXX truncate */
	}

	totsize=0;
	for (di=0;(di<dimax)&&(part_num<PART_NUM_MAX);){
		/* disk pointer */
		part[part_num].diskp=dp;

		part[part_num].valid=1; /* OK so far */

		/* type */
		part[part_num].type=PART_TYPE_DD;

		/* index on disk */
		part[part_num].index=pi;

		/* block size */
		part[part_num].blksize=dp->blksize;

		/* start block */
		part[part_num].bstart=bstart;

		/* partition size according to partition table */
		ptcsize=(ptp->ddpart[di][1]<<8)+ptp->ddpart[di][0]; /* in clusters */
		ptbsize=ptcsize*AKAI_DDPART_CBLKS; /* in blocks */
		/* check size */
		if (ptbsize==0){
			PRINTF_OUT("disk%u: premature end of DD partitions\n",dp->index);
			/* end of partitions, done */
			/* no further partition, keep old part_num */
			break; /* done */
		}
		if (ptbsize>AKAI_HD_MAXSIZE){
			PRINTF_OUT("disk%u: invalid size (0x%04x) of DD partition %u",
				dp->index,ptbsize,di);
			/* truncate */
			ptbsize=AKAI_HD_MAXSIZE;
			ptcsize=ptbsize/AKAI_DDPART_CBLKS; /* in clusters */
			ptbsize=ptcsize*AKAI_DDPART_CBLKS; /* in blocks */
			PRINTF_OUT(", truncated (0x%04x)\n",ptbsize);
			/* XXX keep valid */
		}
		if ((part[part_num].bstart+ptbsize)>dp->bsize){
			PRINTF_OUT("disk%u: expected size (0x%04x) of DD partition %u is too large",
				dp->index,ptbsize,di);
			/* truncate */
			if (part[part_num].bstart<dp->bsize){
				ptbsize=dp->bsize-part[part_num].bstart;
			}else{
				ptbsize=0;
			}
			ptcsize=ptbsize/AKAI_DDPART_CBLKS; /* in clusters */
			ptbsize=ptcsize*AKAI_DDPART_CBLKS; /* in blocks */
			PRINTF_OUT(", truncated (0x%04x)\n",ptbsize);
			/* XXX keep valid */
			/* Note: this will be the last DD partition */
		}
		part[part_num].bsize=ptbsize;
		part[part_num].csize=ptcsize;

		/* try to read S1100/S3000 harddisk DD partition header at block 0 relative to start */
		if ((part[part_num].bsize<AKAI_DDPARTHEAD_BLKS)
			||(akai_io_blks(&part[part_num],(u_char *)&part[part_num].head.dd,
							0,
							AKAI_DDPARTHEAD_BLKS,
							1,IO_BLKS_READ)<0)){ /* 1: alloc cache if possible */
			/* discard this partition, keep old part_num */
			PRINTF_OUT("disk%u: cannot read header of DD partition %u, discarded\n",
				dp->index,di);
			break; /* assume end of disk, done */
		}

		/* start of FAT */
		part[part_num].fat=&part[part_num].head.dd.fatcl[0];

		/* count free and bad blocks */
		akai_countfree_part(&part[part_num]);

		/* no volumes */
		part[part_num].volnummax=0;

		/* fake partition letter */
		part[part_num].letter=di; /* XXX */

		/* bstart of next DD partition */
		bstart+=ptbsize;
		/* update total size of DD partitions */
		totsize+=ptbsize;

		di++;
		pi++;
		part_num++; /* found one */

		if (bstart>=dp->bsize){
			break; /* done */
		}
	}

	/* check total number of DD partitions */
	if (di!=dimax){
		PRINTF_OUT("disk%u: inconsistent DD partition number (%u instead of %u), ignored\n",
			dp->index,di,dimax);
		/* XXX ignore */
	}

	/* check total size of DD partitions */
	/* total size according to partition table */
	ptcsize=(ptp->ddpart[di][1]<<8)+ptp->ddpart[di][0]; /* in clusters */
	ptbsize=ptcsize*AKAI_DDPART_CBLKS; /* in blocks */
	if (ptbsize!=totsize){
		PRINTF_OUT("disk%u: inconsistent total size of DD partitions (0x%04x instead of 0x%04x), ignored\n",
			dp->index,totsize,ptbsize);
		/* XXX ignore */
	}

	return 0;
}



int
akai_scan_disk(struct disk_s *dp,int floppyenable)
{
	u_int bstart;
	u_int pi,pimax;
	u_int ptbsize;
	struct akai_parttab_s *ptp;
	int retval;

	if (dp==NULL){
		return -1;
	}
	if (dp->totsize==0){
		PRINTF_OUT("disk%u: zero size\n",dp->index);
		return -1;
	}

	/* set disk type */
	/* first guess: S1000/S3000 harddisk */
	dp->type=DISK_TYPE_HD;

	/* set disk blocksize */
	/* first guess: harddisk */
	/* Note: blksize may change now!!! */
	dp->blksize=AKAI_HD_BLOCKSIZE;

	/* disk size in blocks */
	/* Note: uses harddisk blocksize (AKAI_HD_BLOCKSIZE) here */
	dp->bsize=dp->totsize/dp->blksize;
	/* Note: dp->bsize can be 0 here */

	/* scan for S1000/S3000 harddisk sampler partitions or S900 harddisk or S900/S1000/S3000 floppy */
	bstart=0; /* first partition */
	pimax=AKAI_PART_NUM; /* first guess for number of partitions */
	ptp=NULL; /* no partition table found so far */
	for (pi=0;(pi<pimax)&&(part_num<PART_NUM_MAX);){
		/* Note: possibly scan for S900 harddisk or S900/S1000/S3000 floppy format below => must allow bstart >= dp->bsize here */
		/* Note: members of current part[part_num] may be overwritten in akai_scan_harddisk9() or akai_scan_floppy() below */

		/* disk pointer */
		part[part_num].diskp=dp;

		part[part_num].valid=1; /* OK so far */

		/* set partition type = disk type (first guess) */
		/* Note: DISK_TYPE_HD (==PART_TYPE_HD) here */
		part[part_num].type=dp->type;

		/* index on disk */
		part[part_num].index=pi;

		/* block size */
		/* Note: AKAI_HD_BLOCKSIZE here */
		part[part_num].blksize=dp->blksize;

		/* start block */
		part[part_num].bstart=bstart;

		/* partition size (Note: also needed to read sampler partition header below) */
		/* first guess: max. available size */
		/* Note: uses harddisk blocksize (AKAI_HD_BLOCKSIZE) here */
		if (part[part_num].bstart<dp->bsize){
			part[part_num].bsize=dp->bsize-part[part_num].bstart;
		}else{
			part[part_num].bsize=0;
		}

		/* partition letter */
		part[part_num].letter='A'+pi;

		/* try to read S1000/S3000 harddisk sampler partition header at block 0 relative to start */
		if ((part[part_num].bsize<AKAI_PARTHEAD_BLKS)
			||(akai_io_blks(&part[part_num],(u_char *)&part[part_num].head.hd,
							0,
							AKAI_PARTHEAD_BLKS,
							1,IO_BLKS_READ)<0)){ /* 1: alloc cache if possible */
			/* not a valid S1000/S3000 harddisk sampler partition */
			if (pi==0){ /* first partition? */
				/* scan for S900 harddisk or S900/S1000/S3000 floppy format */
				goto scan_hd9_floppy;
			}
			/* discard this partition, keep old part_num */
			PRINTF_OUT("disk%u: cannot read header of partition %c, discarded\n",
				dp->index,part[part_num].letter);
			break; /* assume end of disk, done */
		}

		/* sampler partition size (Note: also needed for checksum in magic check below) */
		/* expected value, will be checked below */
		part[part_num].bsize=(part[part_num].head.hd.size[1]<<8)+part[part_num].head.hd.size[0];

		/* reserved blocks for system */
		/* first guess */
		part[part_num].bsyssize=AKAI_PARTHEAD_BLKS;

		/* number of volumes */
		/* first guess */
		part[part_num].volnummax=AKAI_ROOTDIR_ENTRIES;

		/* check magic: indicates S1000/S3000 harddisk sampler partition */
		if (akai_check_partheadmagic(&part[part_num])<0){
			/* not a valid S1000/S3000 harddisk sampler partition */
			if (pi==0){ /* first partition? */
				/* scan for S900 harddisk or S900/S1000/S3000 floppy format */
				goto scan_hd9_floppy;
			}else{
				PRINTF_OUT("disk%u: invalid format in partition %c, ignored\n",dp->index,'A'+pi);
				/* XXX invalid, but keep it */
				part[part_num].valid=0; /* invalid partition so far */
				/* Note: if non-empty, part_num++ via for-loop */
			}
		}
		/* Note: now, S1000/S3000 harddisk sampler partition */

		/* end of sampler partitions? */
		if (part[part_num].bsize==0){ /* empty partition? */
			/* Note: if partition table present, this is a premature end, since pimax should be correct */
			if (ptp!=NULL){ /* partition table present? */
				PRINTF_OUT("disk%u: premature end of sampler partitions\n",dp->index);
				/* XXX ignore */
			}
			/* end of sampler partitions, done */
			/* no further sampler partition, keep old part_num */
			break; /* done */
		}

		if (pi==0){ /* first partition? */
			/* Note: now, first partition header was read and checked */

			/* check magic in parttab: indicates S1000/S3000 harddisk partition table */
			ptp=&(part[part_num].head.hd.parttab); /* try */
			if (akai_check_parttabmagic(ptp)==0){
				/* get number of sampler partitions: first entry in partition table */
				pimax=ptp->partnum;
				if (pimax==0){
					PRINTF_OUT("disk%u: empty partition table, ignored\n",dp->index);
					pimax=AKAI_PART_NUM; /* XXX try all */
				}
				if (pimax>AKAI_PART_NUM){
					PRINTF_OUT("disk%u: invalid partition number (%u), truncated (%u)\n",
						dp->index,pimax,AKAI_PART_NUM);
					pimax=AKAI_PART_NUM; /* XXX truncate */
				}
			}else{
				/* if correct, no partition table present, e.g. for old harddisk format */
				ptp=NULL;
				/* XXX nevertheless, try without partition table to find valid partitions */
				/* XXX no need to mark invalid */
				PRINTF_OUT("disk%u: no partition table present, ignored\n",dp->index);
			}
		}

		if (ptp!=NULL){ /* partition table present? */
			/* partition size according to partition table */
			ptbsize=(ptp->part[pi][1]<<8)+ptp->part[pi][0];
			/* compare */
			if (part[part_num].bsize!=ptbsize){
				PRINTF_OUT("disk%u: inconsistent size of partition %c (0x%04x instead of 0x%04x), ignored\n",
					dp->index,part[part_num].letter,part[part_num].bsize,ptbsize);
				/* XXX keep valid */
			}
		}else{
			ptbsize=part[part_num].bsize; /* XXX */
		}

		/* check size */
		if (part[part_num].bsize>AKAI_PART_MAXSIZE){
			PRINTF_OUT("disk%u: invalid size (0x%04x) of partition %c",
				dp->index,part[part_num].bsize,part[part_num].letter);
			part[part_num].bsize=AKAI_PART_MAXSIZE;
			PRINTF_OUT(", truncated (0x%04x)\n",part[part_num].bsize);
			/* XXX keep valid */
		}
		if ((part[part_num].bstart+part[part_num].bsize)>dp->bsize){
			PRINTF_OUT("disk%u: expected size (0x%04x) of partition %c is too large",
				dp->index,part[part_num].bsize,part[part_num].letter);
			/* truncate */
			if (part[part_num].bstart<dp->bsize){
				part[part_num].bsize=dp->bsize-part[part_num].bstart;
			}else{
				part[part_num].bsize=0;
			}
			PRINTF_OUT(", truncated (0x%04x)\n",part[part_num].bsize);
			/* XXX keep valid */
			/* Note: this will be the last sampler partition */
		}

		/* start of FAT */
		part[part_num].fat=&part[part_num].head.hd.fatblk[0];

		/* count free and bad blocks */
		akai_countfree_part(&part[part_num]);

		/* bstart of next sampler partition */
		/* XXX trust part[part_num].bsize instead of ptbsize */
		bstart+=part[part_num].bsize;

		pi++;
		part_num++; /* found one */

		if (bstart>=dp->bsize){
			break; /* done */
		}
	}
	/* Note: now, bstart is total size of S1000/S3000 harddisk sampler partitions in blocks */

	if (ptp!=NULL){ /* partition table present? */
		/* check total number of non-empty sampler partitions */
		if (pi!=pimax){
			PRINTF_OUT("disk%u: inconsistent sampler partition number (%u instead of %u), ignored\n",
				dp->index,pi,pimax);
			/* XXX ignore */
		}

		/* check total size of sampler partitions */
		/* total size according to partition table */
		ptbsize=(ptp->part[pimax][1]<<8)+ptp->part[pimax][0];
		if (ptbsize!=bstart){
			PRINTF_OUT("disk%u: inconsistent total size of sampler partitions (0x%04x instead of 0x%04x), ignored\n",
				dp->index,bstart,ptbsize);
			/* XXX ignore */
		}

		/* scan for S1100/S3000 harddisk DD partitions */
		akai_scan_ddpart(dp,ptp,bstart,pi); /* Note: ignore possible error */
	}

	/* Note: valid S1000/S3000 harddisk should have at least one non-empty sampler partition */
	if (pi==0){
		PRINTF_OUT("disk%u: invalid format\n",dp->index);
		return -1;
	}
	return 0;

scan_hd9_floppy:
	/* scan for S900 harddisk or S900/S1000/S3000 floppy format */
	if ((!floppyenable)||(dp->bsize*dp->blksize>AKAI_FLH_SIZE*AKAI_FL_BLOCKSIZE)){
		/* scan for S900 harddisk format */
		/* Note: akai_scan_harddisk9() may overwrite members of current disk and/or part[part_num] if necessary */
		retval=akai_scan_harddisk9(dp);
	}else{
		/* scan for floppy format */
		/* Note: akai_scan_floppy() may overwrite members of current disk and/or part[part_num] if necessary */
		/* XXX first blocks with wrong blocksize cached, no problem, will be re-used */
		retval=akai_scan_floppy(dp);
	}
	if (retval<0){
		PRINTF_OUT("disk%u: invalid format\n",dp->index);
		/* discard this partition, keep old part_num */
		/* Note: keep current disk type */
	}
	/* assume end of disk, done */
	return retval;
}



int
akai_wipe_part(struct part_s *pp,int wipeflag,struct part_s *plistp,u_int pimax,int cdromflag)
{
	int i;
	char namebuf[AKAI_NAME_LEN+1]; /* +1 for '\0' */
	u_int pi,siz;
	u_int pnum,totb;
	u_int dnum,totdb;

	/* Note: allow invalid partitions to be wiped!!! */
	if ((pp==NULL)||(pp->blksize==0)||(pp->diskp==NULL)){
		return -1;
	}

	if (pp->type==PART_TYPE_DD){
		/* S1100/S3000 harddisk DD partition */

		if (wipeflag){
			static u_char buf[AKAI_DDPART_CBLKS*AKAI_HD_BLOCKSIZE];
			struct akai_ddparthead_s *hp;

			/* wipe cluster 0: contains partition header */
			bzero(buf,AKAI_DDPART_CBLKS*AKAI_HD_BLOCKSIZE);

			hp=(struct akai_ddparthead_s *)buf;

			/* mark cluster 0 as reserved for system: contains partition header */
			hp->fatcl[0][1]=0xff&(AKAI_DDFAT_CODE_SYS>>8);
			hp->fatcl[0][0]=0xff&(AKAI_DDFAT_CODE_SYS);

			/* update partition header in memory */
			bcopy(hp,&pp->head.dd,sizeof(struct akai_ddparthead_s));

			/* write cluster 0 */
			if (akai_io_blks(pp,buf,
							 0,
							 AKAI_DDPART_CBLKS, /* 1 cluster */
							 0,IO_BLKS_WRITE)<0){ /* 0: don't allocate new cache */
				return -1;
			}
		}

		/* count free and bad blocks */
		akai_countfree_part(pp);

		pp->volnummax=0;
		pp->valid=1; /* fixed */

		/* Note: no need to exit or restart now */
		return 0;
	}

	if ((pp->type==PART_TYPE_HD9)&&(pp->fat!=NULL)){
		/* S900 harddisk */

		/* number of volumes */
		pp->volnummax=AKAI_HD9ROOTDIR_ENTRIES;

		if (wipeflag){
			/* wipe whole header */
			bzero(&pp->head.hd9,sizeof(struct akai_hd9head_s));

			/* allocate blocks for harddisk header */
			for (i=0;i<AKAI_HD9HEAD_BLKS;i++){
				pp->fat[i][1]=0xff&(AKAI_FAT_CODE_SYS900HD>>8);
				pp->fat[i][0]=0xff&AKAI_FAT_CODE_SYS900HD;
			}
		}

		/* set harddisk size */
		if (pp->bsize>AKAI_HD9_MAXSIZE){
			pp->bsize=AKAI_HD9_MAXSIZE;
		}
		pp->head.hd9.size[1]=0xff&(pp->bsize>>8);
		pp->head.hd9.size[0]=0xff&pp->bsize;
		pp->head.hd9.flag1=AKAI_HD9FLAG1_SIZEVALID;

		/* reserved blocks for system */
		pp->bsyssize=AKAI_HD9HEAD_BLKS;

		/* count free and bad blocks */
		akai_countfree_part(pp);

		if (wipeflag){
			/* wipe root directory */
			bzero(&pp->head.hd9.vol[0],pp->volnummax*sizeof(struct akai_hd9rootdir_entry_s));
			for (i=0;i<(int)pp->volnummax;i++){
				/* default volume name */
				sprintf(namebuf,"VOLUME %03i",i+1);
				ascii2akai_name(namebuf,pp->head.hd9.vol[i].name,1); /* 1: S900 */
				pp->head.hd9.vol[i].start[1]=0xff&(AKAI_VOL_START_INACT>>8);
				pp->head.hd9.vol[i].start[0]=0xff&AKAI_VOL_START_INACT;
			}
			/* XXX must erase all vol_s which link to this partition */
		}

		/* copy first FAT entries */
		bcopy((u_char *)&pp->head.hd9.fatblk,(u_char *)&pp->head.hd9.fatblk0,AKAI_HD9FAT0_ENTRIES*2);
		/* write new harddisk header to harddisk */
		if (akai_io_blks(pp,(u_char *)&pp->head.hd9,
						 0,
						 AKAI_HD9HEAD_BLKS,
						 1,IO_BLKS_WRITE)<0){ /* 1: allocate cache if possible */
			return -1;
		}

		pp->valid=1; /* fixed */

		/* Note: no need to exit or restart now */
		return 0;
	}

	if ((pp->type!=PART_TYPE_HD)||(pp->fat==NULL)){
		return -1;
	}
	/* now, S1000/S3000 harddisk sampler partition */

	/* number of volumes */
	pp->volnummax=AKAI_ROOTDIR_ENTRIES;

	if (wipeflag){
		/* wipe whole header */
		bzero(&pp->head.hd,sizeof(struct akai_parthead_s));
		/* Note: tagsmagic is cleared as well */

		/* allocate blocks for partition header */
		for (i=0;i<AKAI_PARTHEAD_BLKS;i++){
			pp->fat[i][1]=0xff&(AKAI_FAT_CODE_SYS>>8);
			pp->fat[i][0]=0xff&AKAI_FAT_CODE_SYS;
		}

		if (cdromflag){
			/* allocate blocks for CD-ROM info */
			/* XXX minimum size */
			for (i=AKAI_CDINFO_BLK;i<AKAI_CDINFO_BLK+AKAI_CDINFO_MINSIZB;i++){
				pp->fat[i][1]=0xff&(AKAI_FAT_CODE_SYS>>8);
				pp->fat[i][0]=0xff&AKAI_FAT_CODE_SYS;
			}
		}
	}

	/* set partition size */
	if (pp->bsize>AKAI_PART_MAXSIZE){
		pp->bsize=AKAI_PART_MAXSIZE;
	}
	pp->head.hd.size[1]=0xff&(pp->bsize>>8);
	pp->head.hd.size[0]=0xff&pp->bsize;

	/* reserved blocks for system */
	pp->bsyssize=AKAI_PARTHEAD_BLKS;

	/* count free and bad blocks */
	akai_countfree_part(pp);

	/* fix magic: indicates harddisk */
	akai_fix_partheadmagic(pp);

	if (wipeflag){
		/* wipe root directory */
		bzero(&pp->head.hd.vol[0],pp->volnummax*sizeof(struct akai_rootdir_entry_s));
		for (i=0;i<(int)pp->volnummax;i++){
			/* default volume name */
			sprintf(namebuf,"VOLUME %03i",i+1);
			ascii2akai_name(namebuf,pp->head.hd.vol[i].name,0); /* 0: not S900 */
			pp->head.hd.vol[i].type=AKAI_VOL_TYPE_INACT;
		}
		/* XXX must erase all vol_s which link to this partition */
	}

	/* first partition on disk and want to fix partition table? */
	if ((pp->index==0)&&(plistp!=NULL)){
		/* initialize partition table */
		bzero(&(pp->head.hd.parttab),sizeof(struct akai_parttab_s));

		/* fix parttab magic: indicates partition table */
		akai_fix_parttabmagic(&(pp->head.hd.parttab));

		/* count number of partitions on same disk */
		pnum=0;
		totb=0;
		dnum=0;
		totdb=0;
		for (pi=0;pi<pimax;pi++){
			if ((plistp[pi].diskp!=NULL)&&(plistp[pi].diskp->fd==pp->diskp->fd)){ /* same disk? */
				if (plistp[pi].type==PART_TYPE_DD){
					/* S1100/S3000 harddisk DD partition */
					if (dnum>=AKAI_DDPART_NUM){ /* already enough and another one? */
						PRINTF_OUT("too many DD partitions on disk, truncated\n");
						continue; /* next */
					}
					siz=plistp[pi].bsize/AKAI_DDPART_CBLKS; /* XXX round down */
					/* set partition size in partition table */
					pp->head.hd.parttab.ddpart[dnum][1]=0xff&(siz>>8);
					pp->head.hd.parttab.ddpart[dnum][0]=0xff&siz;
					/* accumulate total size */
					totdb+=siz;
					dnum++; /* found one */
				}else{
					if (pnum>=AKAI_PART_NUM){ /* already enough and another one? */
						PRINTF_OUT("too many partitions on disk, truncated\n");
						continue; /* next */
					}
					siz=plistp[pi].bsize;
					/* set partition size in partition table */
					pp->head.hd.parttab.part[pnum][1]=0xff&(siz>>8);
					pp->head.hd.parttab.part[pnum][0]=0xff&siz;
					/* accumulate total size */
					totb+=siz;
					pnum++; /* found one */
				}
			}
		}

		/* set partition number */
		pp->head.hd.parttab.partnum=0xff&pnum;
		/* append total size at end of table */
		/* Note: pnum has been checked above */
		pp->head.hd.parttab.part[pnum][1]=0xff&(totb>>8);
		pp->head.hd.parttab.part[pnum][0]=0xff&totb;

		/* set DD partition number */
		pp->head.hd.parttab.ddpartnum=0xff&dnum;
		/* append total size at end of DD table */
		/* Note: dnum has been checked above */
		pp->head.hd.parttab.ddpart[dnum][1]=0xff&(totdb>>8);
		pp->head.hd.parttab.ddpart[dnum][0]=0xff&totdb;
	}

	/* write new partition header to partition */
	if (akai_io_blks(pp,(u_char *)&pp->head.hd,
					 0,
					 AKAI_PARTHEAD_BLKS,
					 1,IO_BLKS_WRITE)<0){ /* 1: allocate cache if possible */
		return -1;
	}

	if (cdromflag){
		static u_char buf[AKAI_CDINFO_MINSIZB*AKAI_HD_BLOCKSIZE]; /* XXX minimum size */
		struct akai_cdinfohead_s *p;

		/* wipe CD-ROM info */
		/* XXX minimum size */
		bzero(buf,AKAI_CDINFO_MINSIZB*AKAI_HD_BLOCKSIZE);
		p=(struct akai_cdinfohead_s *)buf;
		/* default label */
		/* Note: CD-ROM label uses S1000/S3000 char conversion */
		ascii2akai_name(AKAI_CDINFO_DEFLABEL,p->cdlabel,0); /* 0: not S900 */
		if (akai_io_blks(pp,buf,
						 AKAI_CDINFO_BLK,
						 AKAI_CDINFO_MINSIZB,
						 1,IO_BLKS_WRITE)<0){ /* 1: allocate cache if possible */
			return -1;
		}
	}

	pp->valid=1; /* fixed */

	/* Note: no need to exit or restart now */
	return 0;
}



/* Note: blksize might change, must flush prior to formatting!!! */
/* Note: overwrites part[] */
/* Note: must flush cache and exit (or restart) if return value >=0 !!! */
int
akai_wipe_harddisk(struct disk_s *dp,u_int bsize,u_int totb,int s3000flag,int cdromflag)
{
	u_int pi,pimax;
	u_int bc,bremain,bchunk,bmin;

	if (dp==NULL){
		return -1;
	}

	/* S1000/S3000 harddisk */

	if (totb*AKAI_HD_BLOCKSIZE>dp->bsize*dp->blksize){
		totb=(dp->bsize*dp->blksize)/AKAI_HD_BLOCKSIZE; /* truncate */
	}
	if (totb>AKAI_HD_MAXSIZE){
		totb=AKAI_HD_MAXSIZE; /* truncate */
	}
	if (bsize>totb){
		bsize=totb; /* truncate */
	}
	if (bsize>AKAI_PART_MAXSIZE){
		bsize=AKAI_PART_MAXSIZE; /* truncate */
	}
	if (bsize<AKAI_PARTHEAD_BLKS){
		PRINTF_ERR("invalid partition size\n");
		return -1;
	}

	/* set disk blocksize */
	dp->blksize=AKAI_HD_BLOCKSIZE;
	/* disk size in blocks */
	dp->bsize=dp->totsize/dp->blksize;

	/* minimum harddisk sampler partition size */
	if (cdromflag){
		/* with min. CD-ROM info behind header */
		bmin=AKAI_PARTHEAD_BLKS+AKAI_CDINFO_MINSIZB;
	}else{
		bmin=AKAI_PARTHEAD_BLKS;
	}

	/* Note: overwrite part[] */
	bremain=totb;
	bc=0; /* block counter */
	for (pi=0;(pi<AKAI_PART_NUM)&&(pi<PART_NUM_MAX);){
		if (bremain>bsize){
			bchunk=bsize;
		}else{
			bchunk=bremain;
		}
		if (bchunk<bmin){
			/* not enough for harddisk sampler partition */
			break; /* done */
		}

		/* initialize partition */
		part[pi].diskp=dp;
		part[pi].valid=1;
		part[pi].type=PART_TYPE_HD;
		part[pi].index=pi;
		part[pi].blksize=dp->blksize;
		part[pi].bstart=bc;
		part[pi].bsize=bchunk;
		part[pi].bsyssize=AKAI_PARTHEAD_BLKS;
		part[pi].fat=&part[pi].head.hd.fatblk[0];
		part[pi].volnummax=AKAI_ROOTDIR_ENTRIES;
		part[pi].letter='A'+pi;

		pi++; /* created one */

		bremain-=bchunk;
		bc+=bchunk;
		if (bremain==0){
			break; /* done */
		}
	}

	if (pi==0){
		PRINTF_OUT("no partitions created\n");
		return 0;
	}

	/* space for DD */
	/* XXX check totb alignment to AKAI_DDPART_BLKS granularity */
	bremain=dp->bsize-totb;
	/* fix size */
	bremain/=AKAI_DDPART_CBLKS; /* round down */
	if (bremain>0){
		bremain--; /* XXX */
	}
	bremain*=AKAI_DDPART_CBLKS;
	if ((pi<PART_NUM_MAX)&&(bremain>0)){ /* space for DD left at end of disk? */
		PRINTF_OUT("0x%04x blocks for DD at end of disk\n",bremain);
		/* initialize one DD partition at end of disk */
		part[pi].diskp=dp;
		part[pi].valid=1;
		part[pi].type=PART_TYPE_DD;
		part[pi].index=pi;
		part[pi].blksize=dp->blksize;
		part[pi].bstart=totb;
		part[pi].bsize=bremain;
		part[pi].csize=bremain/AKAI_DDPART_CBLKS;
		part[pi].fat=NULL;
		part[pi].volnummax=0;
		part[pi].letter=0;

		pi++; /* created one */
	}

	/* now, all partitions known for partition table */
	pimax=pi;
	for (pi=0;pi<pimax;pi++){
		/* wipe partition */
		if (akai_wipe_part(&part[pi],1,&part[0],pimax,cdromflag)<0){ /* 1: wipe */
			if (part[pi].type==PART_TYPE_DD){
				if (part[pi].letter==0){
					PRINTF_ERR("cannot wipe partition DD\n");
				}else{
					PRINTF_ERR("cannot wipe partition DD%u\n",(u_int)part[pi].letter);
				}
			}else{
				PRINTF_ERR("cannot wipe partition %c\n",part[pi].letter);
			}
			return 1; /* fatal */
		}
		if ((part[pi].type==PART_TYPE_HD)&&s3000flag){
			if (akai_rename_tag(&part[pi],NULL,0,1)<0){ /* 1: wipe */
				PRINTF_ERR("cannot initialize tags in partition %c\n",part[pi].letter);
				/* XXX ignore error */
			}
		}
#if 0 /* Note: must exit (or restart) anyway, no need to count free and bad blocks here */
		akai_countfree_part(&part[pi]);
#endif
	}

	PRINTF_OUT("created %u partition(s)\n",pimax);
	return 0;
}



/* Note: blksize might change, must flush prior to formatting!!! */
/* Note: overwrites part[] */
/* Note: must flush cache and exit (or restart) if return value >=0 !!! */
int
akai_wipe_harddisk9(struct disk_s *dp,u_int totb)
{

	if (dp==NULL){
		return -1;
	}

	/* S900 harddisk */

	/* Note: overwrite part[] */
	if (PART_NUM_MAX<1){
		PRINTF_ERR("not enough partition space allocated\n");
		return -1;
	}

	if (totb*AKAI_HD_BLOCKSIZE>dp->bsize*dp->blksize){
		totb=(dp->bsize*dp->blksize)/AKAI_HD_BLOCKSIZE; /* truncate */
	}
	if (totb>AKAI_HD9_MAXSIZE){
		totb=AKAI_HD9_MAXSIZE; /* truncate */
	}
	if (totb<AKAI_HD9HEAD_BLKS){
		PRINTF_ERR("invalid size\n");
		return -1;
	}

	/* set disk blocksize */
	dp->blksize=AKAI_HD_BLOCKSIZE;
	/* disk size in blocks */
	dp->bsize=dp->totsize/dp->blksize;

	/* Note: overwrite part[] */
	/* initialize partition */
	part[0].diskp=dp;
	part[0].valid=1;
	part[0].type=PART_TYPE_HD9;
	part[0].index=0;
	part[0].blksize=dp->blksize;
	part[0].bstart=0;
	part[0].bsize=totb;
	part[0].bsyssize=AKAI_HD9HEAD_BLKS;
	part[0].fat=&part[0].head.hd9.fatblk[0];
	part[0].volnummax=AKAI_HD9ROOTDIR_ENTRIES;
	part[0].letter='A'; /* fake partition letter */

	/* wipe partition */
	if (akai_wipe_part(&part[0],1,NULL,0,0)<0){ /* 1: wipe */
		return 1; /* fatal */
	}

#if 0 /* Note: must exit (or restart) anyway, no need to count free and bad blocks here */
	akai_countfree_part(&part[0]);
#endif

	return 0;
}



/* Note: blksize might change, must flush prior to formatting!!! */
/* Note: overwrites part[] */
/* Note: must flush cache and exit (or restart) if return value >=0 !!! */
int
akai_wipe_floppy(struct disk_s *dp,int lodensflag,int s3000flag,int s900flag)
{
	struct akai_voldir3000fl_s tmpdir3000;
	struct akai_flvol_label_s *lp;
	u_int hdsiz;
	u_int voldir3000bstart;
	u_int i,imax;
	u_int osver;
	u_int fatcodesys;

	if (dp==NULL){
		return -1;
	}

	/* floppy */

	/* Note: overwrite part[] */
	if (PART_NUM_MAX<1){
		PRINTF_ERR("not enough partition space allocated\n");
		return -1;
	}

	if (((!lodensflag)&&(dp->bsize*dp->blksize<AKAI_FLH_SIZE*AKAI_FL_BLOCKSIZE))
		||((lodensflag)&&(dp->bsize*dp->blksize<AKAI_FLL_SIZE*AKAI_FL_BLOCKSIZE))){
		PRINTF_ERR("invalid disk size\n");
		return -1;
	}

	/* set disk blocksize */
	dp->blksize=AKAI_FL_BLOCKSIZE;
	/* disk size in blocks */
	dp->bsize=dp->totsize/dp->blksize;

	/* floppy header */
	if (lodensflag){
		lp=&part[0].head.fll.label;
		hdsiz=AKAI_FLLHEAD_BLKS;
		voldir3000bstart=AKAI_VOLDIR3000FLL_BSTART; /* for S3000 */
	}else{
		lp=&part[0].head.flh.label;
		hdsiz=AKAI_FLHHEAD_BLKS;
		voldir3000bstart=AKAI_VOLDIR3000FLH_BSTART; /* for S3000 */
	}
	/* Note: low- and high-density floppy headers are identical */
	/*       up to first AKAI_FAT_ENTRIES_FLL FAT-entries */
	/* => set files and system blocks in FAT in high-density header */

	/* initialize partition */
	part[0].diskp=dp;
	part[0].valid=1;
	if (lodensflag){
		part[0].type=PART_TYPE_FLL;
	}else{
		part[0].type=PART_TYPE_FLH;
	}
	part[0].index=0;
	part[0].blksize=dp->blksize;
	part[0].bstart=0;
	if (lodensflag){
		part[0].bsize=AKAI_FLL_SIZE;
	}else{
		part[0].bsize=AKAI_FLH_SIZE;
	}
	/* Note: see comment above: FAT-start same for low- and high density */
	part[0].fat=&part[0].head.flh.fatblk[0];

	/* number of volumes */
	part[0].volnummax=1;

	/* fake partition letter */
	part[0].letter='A';

	/* wipe floppy header */
	/* Note: high-density floppy header enough, also for low-density floppy case */
	bzero(&part[0].head.flh,sizeof(struct akai_flhhead_s));

	/* OS version */
	if (s3000flag){
		/* S3000 */
		osver=AKAI_OSVER_S3000MAX; /* XXX */
	}else if (s900flag){
		/* S900 */
		osver=AKAI_OSVER_S900VOL;
	}else{
		/* S1000 */
		osver=AKAI_OSVER_S1000MAX; /* XXX */
	}
	lp->osver[1]=0xff&(osver>>8);
	lp->osver[0]=0xff&osver;

	/* wipe S900/S1000 volume directory */
	for (i=0;i<AKAI_VOLDIR_ENTRIES_S1000FL;i++){
		akai_fvol1000_initfile(&part[0].head.flh.file[i],osver,
							   s3000flag?AKAI_FILE_TAGFREE:AKAI_FILE_TAGS1000);
	}
	if (s3000flag){
		/* mark S3000 floppy */
		part[0].head.flh.file[0].type=AKAI_VOLDIR3000FL_FTYPE;
	}

	if (!s900flag){
		/* S1000/S3000 */

		/* default volume name */
		ascii2akai_name("NOT NAMED   ",lp->name,0); /* 0: not S900 */

		/* default volume parameters */
		akai_init_volparam(&lp->param,s3000flag);
	}

	/* allocate blocks in FAT */
	if (s900flag){
		/* S900 */
		fatcodesys=AKAI_FAT_CODE_SYS900FL;
	}else{
		/* S1000/S3000 */
		fatcodesys=AKAI_FAT_CODE_SYS;
	}
	imax=hdsiz; /* floppy header */
	if (s3000flag){
		/* S3000 volume directory is directly behind floppy header */
		imax+=AKAI_VOLDIR3000FL_BLKS;
	}
	for (i=0;i<imax;i++){
		part[0].fat[i][1]=0xff&(fatcodesys>>8);
		part[0].fat[i][0]=0xff&fatcodesys;
	}
	/* reserved blocks for system */
	part[0].bsyssize=imax;

	/* write floppy header */
	if (akai_io_blks(&part[0],(u_char *)&part[0].head.flh,
					 0,
					 hdsiz,
					 1,IO_BLKS_WRITE)<0){ /* 1: allocate cache if possible */
		return 1; /* fatal */
	}

	if (s3000flag){
		/* wipe S3000 volume directory */
		bzero(&tmpdir3000,sizeof(struct akai_voldir3000fl_s));
		for (i=0;i<AKAI_VOLDIR_ENTRIES_S3000FL;i++){
			tmpdir3000.file[i].type=AKAI_FTYPE_FREE;
		}

		/* write S3000 volume directory */
		if (akai_io_blks(&part[0],(u_char *)&tmpdir3000,
						 voldir3000bstart,
						 AKAI_VOLDIR3000FL_BLKS,
						 1,IO_BLKS_WRITE)<0){ /* 1: allocate cache if possible */
			return 1; /* fatal */
		}
	}

#if 0 /* Note: must exit (or restart) anyway, no need to count free and bad blocks here */
	akai_countfree_part(&part[0]);
#endif

	return 0;
}



/* XXX no check for recursion overflow */
int
change_curdir(char *name,u_int vi,char *lastname,int checklast)
{
	/* Note: variables below must be auto, for recursion!!! */
	char namebuf[DIRNAMEBUF_LEN+1]; /* +1 for '\0' */
	u_int l;
	u_int i;
	int j;
	char *np0,*np1;
	struct disk_s *tmpcurdiskp;
	struct part_s *tmpcurpartp;
	struct vol_s *tmpcurvolp;
	struct vol_s tmpcurvol_buf; /* change_curdir does not modify directories => OK to save and restore buffer */
	int ret;

	/* save state, needed in case of error */
	tmpcurdiskp=curdiskp;
	tmpcurpartp=curpartp;
	tmpcurvolp=curvolp;
	if (curvolp!=NULL){ /* need to save? */
		tmpcurvol_buf=*curvolp; /* !!! */
		/* Note: save original, to be restored later on, copy not to be used!!! */
	}

	ret=0; /* no error so far */
	np0=namebuf; /* first token */
	np1=NULL; /* no recursion so far */

	if (name==NULL){
		/* must be in partition */
		if ((curpartp==NULL)||(curvolp!=NULL)){
			ret=-1;
			goto change_curdir_done;
		}
		/* user-supplied volume index */
		if (vi>=curpartp->volnummax){
			ret=-1;
			goto change_curdir_done;
		}
		if (akai_get_vol(curpartp,&curvol_buf,vi)<0){ /* not found? */
			ret=-1;
			goto change_curdir_done;
		}
		curvolp=&curvol_buf;
		goto change_curdir_done;
	}

	/* copy name for possible manupulation */
	strncpy(namebuf,name,DIRNAMEBUF_LEN);
	namebuf[DIRNAMEBUF_LEN]='\0'; /* just in case */

	/* check if absolute path */
	if (np0[0]=='/'){
		/* goto system level */
		curdiskp=NULL;
		curpartp=NULL;
		curvolp=NULL;
		np0++; /* '\0' in worst case */
	}
	l=(u_int)strlen(np0);

	/* search first delimiter */
	for (i=0;i<l;i++){
		if (np0[i]=='/'){
			break;
		}
	}
	if (i<l){ /* found one? */
		/* separate strings */
		np0[i]='\0';
		np1=&np0[i+1]; /* '\0' in worst case, since i<l */
		/* Note: np1 must not be absolute path again!!! */
		if (np1[0]=='/'){ /* first char again delimiter? */
			ret=-1;
			goto change_curdir_done;
		}
		/* take care of np1 below */
	}
	/* now, np0 contains only one hirarchy level, but not the last one if np1!=NULL */

	if ((np1==NULL)&&(lastname!=NULL)){ /* is last hirarchy level and want to extract it? */
		/* Note: in particular if l==0, we end up here (for lastname!=NULL) */
		/* XXX no overflow check in strcpy */
		strcpy(lastname,np0);
		goto change_curdir_done;
	}
	/* Note: checklast irrelevant if lastname!=NULL */

	/* now, change current directory */

	if (l==0){
		/* Note: we do not search for disk, partition, or volume with empty name */
		/* nothing to do */
		goto change_curdir_done; /* done */
	}

	if (strcmp(np0,".")==0){
		/* nothing to do */
		goto change_curdir_done;
	}

	if (strcmp(np0,"..")==0){
		/* one level up */
		if (curdiskp==NULL){
			/* nothing to do */
			goto change_curdir_done;
		}
		if (curpartp==NULL){
			curdiskp=NULL;
			goto change_curdir_done;
		}
		if (curvolp==NULL){
			curpartp=NULL;
			goto change_curdir_done;
		}
		curvolp=NULL;
		goto change_curdir_done;
	}

	if (curdiskp==NULL){
#if 1
		if (strncasecmp(np0,"floppy",6)==0){
			np0+=6;
			/* find floppy */
			if (strlen(np0)==0){ /* empty? */
				/* "/floppy" -> "/disk0/A/<volume1>" */
				j=0; /* disk0 */
			}else{
				/* "/floppyN" -> "/diskN/A/<volume1>" */
				j=atoi(np0);
			}
			/* find disk */
			if ((j<0)||(j>=(int)disk_num)){
				PRINTF_ERR("invalid disk\n");
				ret=-1;
				goto change_curdir_done;
			}
			curdiskp=&disk[j];
			/* find partition */
			curpartp=akai_find_part(curdiskp,"A");
			if (curpartp==NULL){ /* not found? */
				ret=-1;
				goto change_curdir_done;
			}
			if ((np1!=NULL)||checklast){ /* not last or need to check last? */
				if (!curpartp->valid){
					PRINTF_ERR("invalid partition\n");
					ret=-1;
					goto change_curdir_done;
				}
				if ((curpartp->type!=PART_TYPE_FLL)&&(curpartp->type!=PART_TYPE_FLH)){
					PRINTF_OUT("disk%i is not a floppy\n",j);
					ret=-1;
					goto change_curdir_done;
				}
			}
			/* get volume1 (volume index 0) */
			if (akai_get_vol(curpartp,&curvol_buf,0)<0){ /* not found? */
				PRINTF_ERR("invalid volume\n");
				ret=-1;
				goto change_curdir_done;
			}
			curvolp=&curvol_buf; /* only this one!!! */
			goto change_curdir_done;
		}
#endif
		/* find disk */
		if (strncasecmp(np0,"disk",4)==0){
			np0+=4;
		}
		if (strlen(np0)==0){ /* empty? */
			ret=-1;
			goto change_curdir_done;
		}
		j=atoi(np0);
		if ((j<0)||(j>=(int)disk_num)){
			PRINTF_ERR("invalid disk\n");
			ret=-1;
			goto change_curdir_done;
		}
		curdiskp=&disk[j];
		goto change_curdir_done;
	}

	if (curpartp==NULL){
		/* find partition */
		curpartp=akai_find_part(curdiskp,np0);
		if (curpartp==NULL){ /* not found? */
			ret=-1;
			goto change_curdir_done;
		}
		if ((np1!=NULL)||checklast){ /* not last or need to check last? */
			if (!curpartp->valid){
				PRINTF_ERR("invalid partition\n");
				ret=-1;
				goto change_curdir_done;
			}
		}
		goto change_curdir_done;
	}

	if (curvolp==NULL){
		if (curpartp->type==PART_TYPE_DD){
			PRINTF_OUT("no volumes in DD partition\n");
			ret=-1;
			goto change_curdir_done;
		}
		/* find volume */
		if (akai_find_vol(curpartp,&curvol_buf,np0)<0){ /* not found? */
			ret=-1;
			goto change_curdir_done;
		}
		/* Note: akai_find_volume has checked for AKAI_VOL_TYPE_INACT */
		curvolp=&curvol_buf; /* only this one!!! */
		goto change_curdir_done;
	}

	/* no further directories */
	ret=-1;
	goto change_curdir_done;

change_curdir_done:
	if ((ret==0)&&(np1!=NULL)){ /* recursion requested? */
		ret=change_curdir(np1,vi,lastname,checklast); /* recursion */
	}
	if (ret<0){
		/* error, must restore state */
		curdiskp=tmpcurdiskp;
		curpartp=tmpcurpartp;
		curvolp=tmpcurvolp;
		if (curvolp!=NULL){ /* was saved? */
			*curvolp=tmpcurvol_buf; /* !!! */
			/* Note: restore orginal from copy!!! */
		}
	}
	return ret;
}

int
change_curdir_home(void)
{

	/* goto system level */
	curdiskp=NULL;
	curpartp=NULL;
	curvolp=NULL;

	if (disk_num==1){
		/* goto first disk */
		curdiskp=&disk[0];

		if ((part_num==1)&&part[0].valid){
			if (((part[0].type==PART_TYPE_FLL)||(part[0].type==PART_TYPE_FLH))){
				/* goto floppy volume */
				return change_curdir("/floppy",0,NULL,1); /* NULL,1: check last */
			}else if (part[0].type==PART_TYPE_HD9){
				/* goto fake partition A */
				return change_curdir("A",0,NULL,1); /* NULL,1: check last */
			}
		}
	}

	return 0;
}



void
save_curdir(int modflag)
{

	savecurdiskp=curdiskp;
	savecurpartp=curpartp;
	savecurvolp=curvolp;
	if (!modflag){
		/* will not modify volumes => OK to save and restore buffer */
		savecurvol_buf=curvol_buf;
	}else{
		/* save name */
		if (curvolp!=NULL){
			strcpy(savecurvolname,curvolp->name);
			/* save index for case of empty volume name */
			savecurvolindex=curvolp->index;
		}
	}
	savecurvolmodflag=modflag;
}

void
restore_curdir(void)
{
	char *np;

	 /* Note: OS keeps track of changes, no need to reload */
	curdiskp=savecurdiskp;

	/* Note: part[] keeps track of changes, no need to reload */
	curpartp=savecurpartp;
	if ((curpartp!=NULL)&&(curpartp->valid==0)){
		/* could happen */
		/* goto disk */
		curpartp=NULL;
		savecurvolp=NULL;
		savecurvolmodflag=0;
		PRINTF_OUT("partition has changed, now in upper disk\n");
		/* XXX just stay in partition */
	}

	/* Note: only one volume buffer, saved contents might be out-of-date (e.g. rename, delete, create)!!! */
	curvolp=savecurvolp;
	if (!savecurvolmodflag){
		/* has not modified volumes => OK to save and restore buffer */
		curvol_buf=savecurvol_buf;
	}else{
		/* volume buffer could be out-of-date, must reload */
		if (curvolp!=NULL){
			curvolp=NULL; /* goto partition */
			if (savecurvolname[0]=='\0'){ /* empty volume name? */
				np=NULL; /* => change_curdir() below will use savecurvolindex */
			}else{
				np=savecurvolname; /* => change_curdir() below will use savecurvolname */
			}
			if (change_curdir(np,savecurvolindex,NULL,1)<0){ /* 1: check */
				/* could happen */
				PRINTF_OUT("volume has changed, now in upper partition\n");
				/* XXX just stay in partition */
			}
		}
	}
}



void
curdir_info(int verbose,char *pathbuf)
{
	static char tmppathbuf[DIRNAMEBUF_LEN+1]; /* +1 for '\0' */
	int pathprintflag;
	u_int pathlen;
	struct vol_s tmpvol;
	u_int vi;
	u_int ai;

	if (pathbuf==NULL){
		pathbuf=tmppathbuf;
		pathprintflag=1;
	}else{
		pathprintflag=verbose;
	}
	sprintf(pathbuf,"/");
	if (curdiskp!=NULL){
		pathlen=1;
		sprintf(pathbuf+pathlen,"disk%u",curdiskp->index);
		if (curpartp!=NULL){
			pathlen=(u_int)strlen(pathbuf);
			if (curpartp->type==PART_TYPE_DD){
				if (curpartp->letter==0){
					sprintf(pathbuf+pathlen,"/DD");
				}else{
					sprintf(pathbuf+pathlen,"/DD%u",(u_int)curpartp->letter);
				}
			}else{
				sprintf(pathbuf+pathlen,"/%c",curpartp->letter);
			}
			if (curvolp!=NULL){
				pathlen=(u_int)strlen(pathbuf);
				sprintf(pathbuf+pathlen,"/%s",curvolp->name);
			}
		}
	}
	if (pathprintflag){
		PRINTF_OUT("%s",pathbuf);
	}

	if (verbose){
		PRINTF_OUT("\n");
		if (curvolp!=NULL){
			ai=0; /* volume alias number unknown */
			if ((curpartp!=NULL)&&(curpartp->type==PART_TYPE_HD9)){
				/* S900 harddisk */
				/* determine volume alias number */
				ai=1; /* start number */
				/* add number of active volumes in partition before current volume */
				for (vi=0;(vi<curvolp->index)&&(vi<curpartp->volnummax);vi++){
					/* try to get volume */
					if (akai_get_vol(curpartp,&tmpvol,vi)<0){
						continue; /* next volume */
					}
					ai++; /* one more active volume */
				}
			}
			akai_vol_info(curvolp,ai,1); /* 1: verbose */
		}else if (curpartp!=NULL){
			akai_part_info(curpartp,1); /* 1: verbose */
		}else if (curdiskp!=NULL){
			akai_disk_info(curdiskp,1); /* 1: verbose */
		}else{
			PRINTF_OUT("disks: %u\n",disk_num);
		}
	}
}

void
list_curdir(int recflag)
{

	if (curdiskp==NULL){
		akai_list_alldisks(recflag,curfiltertag);
		return;
	}

	if (curpartp==NULL){
		akai_list_disk(curdiskp,recflag,curfiltertag);
		return;
	}

	if (curvolp==NULL){
		akai_list_part(curpartp,recflag,curfiltertag);
		return;
	}

	akai_list_vol(curvolp,curfiltertag);
}



void
list_curfiltertags(void)
{
	u_int i;
	u_int noneflag;

	PRINTF_OUT("filter tags: ");
	noneflag=1; /* none so far */
	for (i=0;i<AKAI_FILE_TAGNUM;i++){
		if ((curfiltertag[i]!=AKAI_FILE_TAGFREE)&&(curfiltertag[i]!=AKAI_FILE_TAGS1000)){ /* not free? */
			PRINTF_OUT("%02u ",curfiltertag[i]);
			noneflag=0;
		}
	}
	if (noneflag){
		PRINTF_OUT("none");
	}
	PRINTF_OUT("\n");
}



int
copy_file(struct file_s *srcfp,struct vol_s *dstvp,struct file_s *dstfp,u_int dstindex,char *dstname,int delflag)
{
	struct file_s tmpfile;
	u_char *tmpbuf;
	int existsflag;

	if ((srcfp==NULL)||(dstvp==NULL)){
		return -1;
	}

	if (dstname==NULL){ /* no user-supplied name? */
		dstname=srcfp->name;
	}

	/* allocate buffer */
	if ((tmpbuf=(u_char *)malloc(srcfp->size))==NULL){
		PERROR("malloc");
		return -1;
	}

	/* check if destination file already exists */
	existsflag=0;
	if (dstindex==AKAI_CREATE_FILE_NOINDEX){ /* no user-supplied index? */
		/* check if file name already used in destination volume */
		if (akai_find_file(dstvp,&tmpfile,dstname)==0){
			existsflag=1;
		}
	}else{
		/* check if file with given index already exists in destination volume */
		if (akai_get_file(dstvp,&tmpfile,dstindex)==0){
			existsflag=1;
		}
	}
	if (existsflag){
		/* same file? Note: same partition and same block should be unique */
		if ((srcfp->volp->partp==tmpfile.volp->partp)&&(srcfp->bstart==tmpfile.bstart)){
			PRINTF_ERR("cannot copy to same file\n");
			free(tmpbuf);
			return -1;
		}
		/* exists */
		if (delflag){
			/* delete file */
			if (akai_delete_file(&tmpfile)<0){
				PRINTF_ERR("cannot overwrite existing file\n");
				free(tmpbuf);
				return -1;
			}
		}else{
			PRINTF_OUT("file name already used\n");
			free(tmpbuf);
			return -1;
		}
	}

	/* create file */
	/* Note: akai_create_file() will correct osver if necessary */
	if (akai_create_file(dstvp,&tmpfile,srcfp->size,dstindex,dstname,
						 srcfp->osver, /* default: from source file */
						 (srcfp->volp->type==AKAI_VOL_TYPE_S900)?NULL:srcfp->tag)<0){ /* no tags from S900 */
		PRINTF_ERR("cannot create file\n");
		free(tmpbuf);
		return -1;
	}

	if (dstfp!=NULL){
		/* copy for information */
		*dstfp=tmpfile;
	}

	/* read file */
	if (akai_read_file(0,tmpbuf,srcfp,0,srcfp->size)<0){
		PRINTF_OUT("read error\n");
		free(tmpbuf);
		return -1;
	}

	/* write file */
	if (akai_write_file(0,tmpbuf,&tmpfile,0,tmpfile.size)<0){
		PRINTF_OUT("write error\n");
		free(tmpbuf);
		return -1;
	}

	free(tmpbuf);
	return 0;
}



int
copy_vol_allfiles(struct vol_s *srcvp,struct part_s *dstpp,char *dstname,int delflag,int verbose)
{
	struct vol_s tmpvol;
	struct file_s tmpfile;
	u_int fi;

	if ((srcvp==NULL)||(dstpp==NULL)){
		return -1;
	}
	if (dstpp->type==PART_TYPE_DD){
		return -1;
	}

	if (dstname==NULL){ /* no user-supplied name? */
		dstname=srcvp->name;
	}

	/* check if destination volume already exists */
	if (akai_find_vol(dstpp,&tmpvol,dstname)<0){
		/* does not exist (or error in finding it, don't care) */
		/* create volume */
		if (akai_create_vol(dstpp,&tmpvol,
							srcvp->type,
							AKAI_CREATE_VOL_NOINDEX,
							dstname,
							srcvp->lnum,
							srcvp->param)<0){
			PRINTF_ERR("cannot create destination volume\n");
			return -1;
		}
	}else{
		/* volume already exists */
		/* copy load number and volume parameters */
		if (akai_rename_vol(&tmpvol,NULL,
							srcvp->lnum,
							srcvp->osver,
							srcvp->param)<0){
			PRINTF_ERR("cannot update volume\n");
			return -1;
		}
	}

	/* same volume? Note: same partition and same block should be unique */
	if ((srcvp->partp==tmpvol.partp)&&(srcvp->dirblk[0]==tmpvol.dirblk[0])){
		PRINTF_ERR("cannot copy into same volume\n");
		return -1;
	}

	/* files in volume directory */
	for (fi=0;fi<srcvp->fimax;fi++){
		/* get source file */
		if (akai_get_file(srcvp,&tmpfile,fi)<0){
			continue; /* next file */
		}

		if (verbose>0){
			if (verbose>1){
				PRINTF_OUT("%s/",srcvp->name);
			}
			PRINTF_OUT("%s\n",tmpfile.name);
		}

		if (copy_file(&tmpfile,&tmpvol,NULL,AKAI_CREATE_FILE_NOINDEX,NULL,delflag)<0){
			return -1;
		}
	}

	return 0;
}


int
copy_part_allvols(struct part_s *srcpp,struct part_s *dstpp,int delflag,int verbose)
{
	u_int vi;
	struct vol_s tmpvol;

	if ((srcpp==NULL)||(dstpp==NULL)){
		return -1;
	}
	if ((srcpp->type==PART_TYPE_DD)||(dstpp->type==PART_TYPE_DD)){
		return -1;
	}

	/* same partition? Note: partition pointer is unique */
	if (srcpp==dstpp){
		PRINTF_ERR("cannot copy into same partition\n");
		return -1;
	}

	/* volumes in root directory */
	for (vi=0;(vi<srcpp->volnummax)&&(vi<dstpp->volnummax);vi++){
		/* get volume */
		if (akai_get_vol(srcpp,&tmpvol,vi)<0){
			continue; /* next volume */
		}

		if (verbose>1){
			PRINTF_OUT("%s/\n",tmpvol.name);
		}

		/* copy all files in volume */
		if (copy_vol_allfiles(&tmpvol,dstpp,NULL,delflag,verbose)<0){
			return -1;
		}
	}

	return 0;
}



int
check_curnosamplervol(void)
{

	if (curdiskp==NULL){ /* no disk? */
		return 1;
	}
	if (curpartp==NULL){ /* no partition? */
		return 1;
	}
#if 1
	/* XXX actually, curvolp==NULL should suffice */
	if (curpartp->type==PART_TYPE_DD){ /* DD partition? */
		return 1;
	}
#endif
	if (curvolp==NULL){ /* no sampler volume? */
		return 1;
	}
	/* now, inside a sampler volume */
	return 0;
}

int
check_curnosamplerpart(void)
{

	if (curdiskp==NULL){ /* no disk? */
		return 1;
	}
	if (curpartp==NULL){ /* no partition? */
		return 1;
	}
	if (curpartp->type==PART_TYPE_DD){ /* DD partition? */
		return 1;
	}
	if (curvolp!=NULL){ /* sampler volume? */
		return 1;
	}
	/* now, on sampler partition level */
	return 0;
}

int
check_curnoddpart(void)
{

	if (curdiskp==NULL){ /* no disk? */
		return 1;
	}
	if (curpartp==NULL){ /* no partition? */
		return 1;
	}
	if (curpartp->type!=PART_TYPE_DD){ /* no DD partition? */
		return 1;
	}
	/* now, on DD partition level */
	return 0;
}

int
check_curnopart(void)
{

	if (curdiskp==NULL){ /* no disk? */
		return 1;
	}
	if (curpartp==NULL){ /* no partition? */
		return 1;
	}
	if (curvolp!=NULL){ /* sampler volume? */
		return 1;
	}
	/* now, on partition level (sampler or DD) */
	return 0;
}



/* EOF */
