/*               amiga-fdisk    -   Amiga RDB fdisk for Linux
 *                amigastuff.c part  -  amiga specific stuff
 *                   written in 1996 by Stefan Reinauer
 *
 *    Copyright (C) 1996-1998 by Stefan Reinauer
 *
 *    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, 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., 675 Mass Ave, Cambridge, MA 02139, USA
 */

#include <fcntl.h>
#include <unistd.h>
#include <linux/hdreg.h>
#include <linux/fs.h>
#include <sys/ioctl.h>
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include <string.h>

#include <asm/system.h>
#include <asm/byteorder.h>

#include <amiga/types.h>
#include <amiga/filehandler.h>
#include <amiga/hardblocks.h>
#include <fdisk.h>

#define SECTOR_SIZE 512
#define MAXPARTS     16

#define BBB(pos)  ((struct BadBlockBlock *)(pos))
#define PART(pos) ((struct PartitionBlock *)(pos))
#define FSHB(pos) ((struct FileSysHeaderBlock *)(pos))
#define LSEG(pos) ((struct LoadSegBlock *)(pos))
#define RDSK(pos) ((struct RigidDiskBlock *)(pos)) 

struct AmigaBlock {
	ULONG   amiga_ID;		/* 4 character identifier */
	ULONG   amiga_SummedLongs;	/* size of this checksummed structure */
	LONG    amiga_ChkSum;		/* block checksum (longword sum to zero) */
};

struct RigidDiskBlock *rdb;
struct PartitionBlock *pa[MAXPARTS];
int parts, firstblock, lastblock, maxblock, minblock;
char *initsectors, *listsector;
char type[32];	/* Needed by DosType() */

int rigiddisk_new(int first);

char *get_block(block)
{
	/* This is a true quickhack. Whenn we are in list only
	 * mode, we may not have swap and so we may not be able
	 * to hold the whole RDB data, so we do some stupid
	 * switching. This is slow and breaks the interactive mode.
	 * So, in interactive mode you still need to have all RDB data
	 * in memory at once
	 */

	int l;

	if (list_only)
	{
		if ((l=open(disk_device,O_RDONLY))<0) {
			if (get_dev)
				fprintf (stderr,"Cannot open device %s\n",disk_device);
				return NULL;
		}

		if (lseek(l,(block*SECTOR_SIZE),SEEK_SET)<0) {
			close(l);
			fprintf (stderr,"Seek error occured while reading block %d.\n",block);
			return NULL;
		}

		if (read(l,listsector,SECTOR_SIZE)!=SECTOR_SIZE) {
			close(l);
			fprintf (stderr,"Error occured while reading block %d.\n",block);
			return NULL;
		}

		close(l);

		return listsector;

	}

	return initsectors+(block*SECTOR_SIZE);
}

LONG checksum(struct AmigaBlock *ptr)
{
	/* This function calculates the Checksum of one AmigaBlock. Trivial. */

	int i, end;
	LONG sum;
	ULONG *pt=(ULONG *)ptr;

	sum=htonl(pt[0]);
	end=htonl(pt[1]);

	if (end>SECTOR_SIZE)
		end=SECTOR_SIZE;

	for (i=1; i<end; i++) {
		sum+=htonl(pt[i]);
	}

	return (LONG)sum;
}

int valid_blocktype(ULONG btype)
{
	/* This is trivial, too. But as it appears quite often, 
	 * I've put it into a function
	 */

	return (btype==IDNAME_RIGIDDISK		||
		btype==IDNAME_BADBLOCK		||
		btype==IDNAME_FILESYSHEADER	||
		btype==IDNAME_LOADSEG		||
		btype==IDNAME_PARTITION);
}

int valid_blocknum(int block)
{
	return ( (block>=minblock && block<=maxblock) || (block == -1));
}

int sector_correct(struct AmigaBlock *ablock)
{
	if (valid_blocktype(htonl(ablock->amiga_ID)))
	{

  		ablock->amiga_ChkSum = ntohl( htonl(ablock->amiga_ChkSum) -
					checksum((struct AmigaBlock  *)ablock));
		return 0;
	}

	return -1;
}

int check_lseg(int blk)
{
	char *curr;

	while (blk!=-1) {
		if (blk>lastblock) lastblock=blk;

		curr=get_block(blk);

		if (htonl(LSEG(curr)->lsb_ID)!=IDNAME_LOADSEG) {
			fprintf (stderr, "LoadSeg messed up at block #%d.\n",blk);

			if (!override())
				return -1;	

			LSEG(curr)->lsb_ID=ntohl(IDNAME_LOADSEG);
		}

		if (checksum((struct AmigaBlock *)curr)!=0) {
			fprintf (stderr, "LoadSeg block #%d had wrong checksum. Fixed.\n",blk);
			sector_correct((struct AmigaBlock *)curr);
		}

		blk=htonl(LSEG(curr)->lsb_Next);

		if (!valid_blocknum(blk)) {
			fprintf (stderr, "LoadSeg block not valid. Skipping other LoadSeg blocks.\n");
			LSEG(curr)->lsb_Next=ntohl(-1); // Correct this.
			break;
		}
	}

	return 0;
}

int check_bbb(int blk)
{
	char *curr;

	while (blk!=-1) {
		if (blk>lastblock) lastblock=blk;

		curr=get_block(blk);

		if (htonl(BBB(curr)->bbb_ID)!=IDNAME_BADBLOCK) {
			fprintf (stderr, "BadBlockList messed up at block #%d.\n",blk);
			return -1;
		}

		if (checksum((struct AmigaBlock *)curr)!=0) {
			fprintf (stderr, "BadBlockList block #%d had wrong checksum. Fixed.\n",blk);
			sector_correct((struct AmigaBlock *)curr);
		}

		blk=htonl(BBB(curr)->bbb_Next);

		if (!valid_blocknum(blk)) {
			fprintf (stderr, "BadBlockList block not valid. Skipping other BadBlockList blocks.\n");
			BBB(curr)->bbb_Next=ntohl(-1); // Correct this.
			break;
		}

	}

	return 0;
}

int connectivity(void)
{
	char *curr;
	int  block;

/* Check whether all checksums are ok and try to fix them
 * Extract partition data.
 */

	lastblock=firstblock;

 /* Check BadBlockBlocks, LoadSegBlocks & FileSystemHeaderBlocks */

	if (check_bbb(htonl(rdb->rdb_BadBlockList))==-1) return -1;

	if (check_lseg(htonl(rdb->rdb_DriveInit))==-1) return -1;
	block=htonl(rdb->rdb_FileSysHeaderList);

	while (block!=-1) {
		if (block>lastblock) lastblock=block;

		curr=get_block(block);

		if (htonl(FSHB(curr)->fhb_ID)!=IDNAME_FILESYSHEADER) {
			fprintf (stderr, "FilesystemHeader messed up at block #%d.\n",block);
			return -1;
		}

		if (checksum((struct AmigaBlock *)curr)!=0) {
			fprintf (stderr, "FilesystemHeader block #%d had wrong checksum. Fixed.\n",block);
			sector_correct((struct AmigaBlock *)curr);
		}

		if (check_lseg(htonl(FSHB(curr)->fhb_SegListBlocks))==-1) return -1;

		if (list_only)		/* Because we overwrote the buffer with check_lseg */
			curr=get_block(block);

		block=htonl(FSHB(curr)->fhb_Next);

	}



 /* Check Partition Blocks */

	parts=0;
	block=htonl(rdb->rdb_PartitionList);

	while (block!=-1) {
		if (block>lastblock) lastblock=block;
	
		curr=get_block(block);
		if (htonl(PART(curr)->pb_ID)!=IDNAME_PARTITION) {
			fprintf (stderr, "PartitionEntry messed up at block #%d.\n",block);
			return -1;
		}
		if (checksum((struct AmigaBlock *)curr)!=0) {
			fprintf (stderr, "PartitionEntry block #%d had wrong checksum. Fixed.\n",block);
			sector_correct((struct AmigaBlock *)curr);
		}

		if (!list_only)
			pa[parts]=PART(curr);

		parts++;

		if (parts>MAXPARTS) {
			fprintf (stderr, "Too many partitions(max=%d). Giving up.\n",MAXPARTS);
			return -1;
		}

		block=htonl(PART(curr)->pb_Next);
	}

	/* Everything seems to be alright */
	return 0;
}

int get_rdb(void)
{

 /* Initial work.
  * - Get RDB
  * - Check size of all init blocks
  * - get init sectors
  */

	int f,i;

	if ((rdb=RDSK(malloc(SECTOR_SIZE)))==NULL) {
		fprintf (stderr,"Not enough memory for one sector (poor guy).\n");
		return -1;
	}

	if ((f=open(disk_device,O_RDONLY))<0) {
		if (!list_only || get_dev)
			fprintf (stderr,"Cannot open device %s\n",disk_device);
		free((char *)rdb);
		return -1;
	}

	i=0;
	firstblock=-1;
	while ((i<RDB_LOCATION_LIMIT)&&(firstblock==-1)) {

		if (lseek(f,(i*SECTOR_SIZE),SEEK_SET)<0) {
			close(f);
			free((char *)rdb);
			fprintf (stderr,"Seek error occured while reading RDB.\n");
			return -1;
		}

		if (read(f,(char *)rdb,SECTOR_SIZE)!=SECTOR_SIZE) {
			close(f);
			free((char *)rdb);
			fprintf (stderr,"Error occured while reading RDB.\n");
			return -1;
		}

		if (htonl(rdb->rdb_ID)==IDNAME_RIGIDDISK) {
			firstblock=i;
			if (checksum((struct AmigaBlock *)rdb)) {
				sector_correct((struct AmigaBlock *)rdb);
				fprintf(stderr,"RDB on block %d had bad checksum. Fixed.\n",i);
			}
		}

		i++;
	}

	if (firstblock==-1) {
		fprintf(stderr,"No valid RDB found... ");
		close(f); free((char *)rdb);
		rigiddisk_new(0);
		return 0;
	}

	if ((i=htonl(rdb->rdb_RDBBlocksHi))==0) {
		fprintf (stderr,"RDB unrepairable inconsistent.\n");
		return -1;
	}

	if (!list_only) {
		free ((char *)rdb);

		if ((initsectors=malloc(i*SECTOR_SIZE))==NULL) {
			fprintf (stderr,"Not enough memory for partition table.\n");
			return -1;
		}

		if (lseek(f,0,SEEK_SET)<0) {
			close(f); free(initsectors);
			fprintf (stderr,"Seek error occured while reading partition table.\n");
			return -1;
		}

		if (read(f,initsectors,i*SECTOR_SIZE)!=i*SECTOR_SIZE) {
			close(f);
			free(initsectors);
			fprintf (stderr,"Error while reading partition table.\n");
			return -1;
		}

		rdb=RDSK(initsectors+firstblock*SECTOR_SIZE);

	} else {
		if ((listsector=malloc(SECTOR_SIZE))==NULL) {
			fprintf (stderr,"Not enough memory for one sector. Poor guy.\n");
			return -1;
		}	
	}

	close (f);

	maxblock=htonl(rdb->rdb_RDBBlocksHi);
	minblock=htonl(rdb->rdb_RDBBlocksLo);

	if (connectivity()==-1) {
		free(initsectors); fprintf (stderr,"Could not solve RDB problems. Sorry.\n");
		return -1;
	}

	/* Everything alright ? */
	return 0;
}

int countblk(void)
{
	int i,j=0;
	struct AmigaBlock *curr;

// Implement: Checking whether found block is in chained list

	for (i=0;i<=maxblock;i++) {
		curr=(struct AmigaBlock *)get_block(i);
		if (valid_blocktype(htonl(curr->amiga_ID))) j++;
	}

#ifdef DEBUG
	printf ("%d/%d blocks counted.\n",j,maxblock);
#endif
	return j;
}
 

int findblk(void)
{
	int i;
	struct AmigaBlock *curr;

// Implement: Checking whether found block is in chained list

	for (i=firstblock;i<=maxblock;i++) {
		curr=(struct AmigaBlock *)get_block(i);
		if (!valid_blocktype(htonl(curr->amiga_ID))) {
			printf ("block %d is free.\n",i);
			return i;
		}
	}
	return -1;
}

int room_for_partition(void)
{
	/* This function will never be called in list_only mode :-) */

	int i;
	if (parts==MAXPARTS) {
		fprintf (stderr,"Too many partitions(max=%d).\n",MAXPARTS);
		return -1;
	}

	if ((i=findblk())<minblock) {
		fprintf (stderr,"No room in RDB area for new partition. Sorry. (minblock: %d) \n", minblock);
		return -1;
	}
	return i; 
}

int partition_new(int nr)
{
	int i,j;
	struct	DosEnvec *de;

	if ((nr<1)||(nr>parts+1)) {
		fprintf (stderr,"Invalid partition number.   existing: %d, tried: %d\n",parts,nr);
		return -1;
	}

	if ((i=room_for_partition())==-1) {
		fprintf (stderr,"Could not create new partition.\n");
		return -1;
	}

	printf ("Creating new partition entry at block %d.\n",i);

	nr--;
	for (j=parts;j>nr;j--)
			pa[j]=pa[j-1];

	parts++;

	if (i>lastblock) lastblock=i; 

	rdb->rdb_HighRDSKBlock=ntohl(lastblock);

	pa[nr]=PART(get_block(i));	/* And we hope, that list_only will 
				 * never call us :) 
				 */
	memset(pa[nr],-1,SECTOR_SIZE);

	pa[nr]->pb_ID          =  ntohl(IDNAME_PARTITION);
	pa[nr]->pb_SummedLongs =  ntohl(64);
	pa[nr]->pb_HostID      =  rdb->rdb_HostID;
	pa[nr]->pb_Flags       =  ntohl(0);
	pa[nr]->pb_DevFlags    =  ntohl(0);

	memcpy(pa[nr]->pb_DriveName,"\003dhx",4);

	if (nr==0) {
		pa[nr]->pb_Next=rdb->rdb_PartitionList;
		rdb->rdb_PartitionList=ntohl(i);
	} else {
		pa[nr]->pb_Next=pa[nr-1]->pb_Next;
		pa[nr-1]->pb_Next=ntohl(i);
	} 

	de=(struct DosEnvec *)pa[nr]->pb_Environment;
	de->de_TableSize	= ntohl(19);	/* 20 ? */
	de->de_SizeBlock	= ntohl(128);
	de->de_SecOrg		= ntohl(0);
	de->de_Surfaces		= ntohl(1);

	/* On my old Seagate this was 1. If it does not work, try
         * de->de_Surfaces=rdb->rdb_Heads   */

	de->de_SectorPerBlock	= ntohl(1);
	de->de_BlocksPerTrack	= rdb->rdb_CylBlocks;
	/* BlocksPerTrack should be rdb->rdb_sectors?! But CylBlocks
	 * seems to be better if a hd has more than 1 _LOGICAL_ surface
	 * (Does this ever happen? Correct me if I am wrong.. 
	 */
	de->de_Reserved		= ntohl(2);
	de->de_PreAlloc		= ntohl(0);
	de->de_Interleave	= ntohl(0);
	de->de_NumBuffers	= ntohl(30);
	de->de_BufMemType	= ntohl(0);
	de->de_MaxTransfer	= ntohl(0x7fffffff);
	de->de_Mask		= ntohl(0xffffffff);
	de->de_BootPri		= ntohl(0);
	de->de_DosType		= ntohl(0x444f5301);
	de->de_Baud		= ntohl(0);
	de->de_Control		= ntohl(0);
	de->de_BootBlocks	= ntohl(0);

	return 0;
}

int partition_delete (int nr)
{
	if ((parts<nr)||(nr<1)) {
		fprintf (stderr,"Could not delete partition %d.\n",nr);
		return -1;
	}

	nr--;

	if (nr==0) {
		if (htonl(rdb->rdb_PartitionList)==(ULONG)lastblock)
			lastblock--; /* Correct lastblock counter */

		rdb->rdb_PartitionList=pa[0]->pb_Next;

	} else {
		if (htonl(pa[nr-1]->pb_Next)==(ULONG)lastblock)
			lastblock--;
		pa[nr-1]->pb_Next=pa[nr]->pb_Next;
	}

	while (nr<parts) {
		pa[nr]=pa[nr+1];
		nr++;
	}
	pa[parts]	= NULL;

	parts--;
	return 0;
}

int partition_togglebootable(int nr)
{
	if ((nr<1)||(nr>parts)) return -1;
	nr--;
	pa[nr]->pb_Flags=ntohl(htonl(pa[nr]->pb_Flags)^PBFF_BOOTABLE);
	return 0;
}

int partition_togglenomount(int nr)
{
	if ((nr<1)||(nr>parts)) return -1;
	nr--;
	pa[nr]->pb_Flags=ntohl(htonl(pa[nr]->pb_Flags)^PBFF_NOMOUNT);
	return 0;
}

int partition_locyl(int nr,int cyl)
{
	if ((nr<1)||(nr>parts)) return -1;
	nr--;
	((struct DosEnvec *)(pa[nr]->pb_Environment))->de_LowCyl=ntohl(cyl);
	return 0;
}

int partition_hicyl(int nr,int cyl)
{
	if ((nr<1)||(nr>parts)) return -1;
	nr--;
	((struct DosEnvec *)pa[nr]->pb_Environment)->de_HighCyl=ntohl(cyl);
	return 0;
}

int partition_bootpri(int nr,int pri)
{
	if ((nr<1)||(nr>parts)) return -1;
	nr--;
	((struct DosEnvec *)pa[nr]->pb_Environment)->de_BootPri=ntohl(pri);
	return 0;
}

int partition_bootblk(int nr,int blks)
{
	if ((nr<1)||(nr>parts)) return -1;
	nr--;
	((struct DosEnvec *)pa[nr]->pb_Environment)->de_BootBlocks=ntohl(blks);
	return 0;
}

int partition_dostype(int nr, ULONG dostype)
{
	if ((nr<1)||(nr>parts)) return -1;
	nr--;
	((struct DosEnvec *)pa[nr]->pb_Environment)->de_DosType=ntohl(dostype);
	return 0;
}

int rigiddisk_correct(void)
{
	/* Correct checksums of all Blocks */
	int i,ts;
	struct DosEnvec *de;

	rdb->rdb_HighRDSKBlock=ntohl(lastblock);

 /* Historical overhead from the very old days. The DosEnvec is a
    dynamically growing table, and some fields are only present in newer
    versions of AmigaDos. So if we decide to create acient looking RDB's,
    we are ready for it. (If a partion is mounted under AmigaDos, unknown
    fields should be ignored, and missing fields given a senseful default.)
    NB, AmigaLilo depends on the presence of fields 19 and 20, which are
    only supported by V37 or newer ROMs.
 */

	for (i=firstblock;i<maxblock+1;i++)
		sector_correct((struct AmigaBlock *)(get_block(i)));

	connectivity();

	for (i=0;i<parts;i++) {
		de=(struct DosEnvec *)pa[i]->pb_Environment;
		ts=htonl(de->de_TableSize);
		if (ts<12) de->de_BufMemType=ntohl(0);
		if (ts<13) de->de_MaxTransfer=ntohl(0x7fffffff);
		if (ts<14) de->de_Mask=ntohl(0xffffffff);
		if (ts<15) de->de_BootPri=ntohl(0);
		if (ts<16) de->de_DosType=ntohl(0x444f5300);
		if (ts<17) de->de_Baud=ntohl(0);
		if (ts<18) de->de_Control=ntohl(0);
		if (ts<19) {
			de->de_BootBlocks=ntohl(0);
			de->de_TableSize=ntohl(19);
		}
		sector_correct((struct AmigaBlock *)(pa[i]));
	}
	return 0;
}

int rigiddisk_new(int first)
{
	struct hd_geometry geo={-1,};
	long long hdsize;
	int i,f;

	if ((f=open(disk_device,O_RDONLY))<0) {
		fprintf (stderr,"Cannot open device %s\n",disk_device);
		return -1;
	}

	printf ("Creating new Rigid Disk Block\n");

	if (ioctl(f,HDIO_GETGEO,&geo)!=0) {
		printf("Can't get geometry data. Trying lseek/trivial mapping.\n");

		hdsize=lseek(f,0,SEEK_END);
		if (hdsize != -1) {
			if (hdsize==0) {
				printf("LSEEK: Could not lseek (Floppy?). Exit.\n");
				exit(0);
			}
		/* XXXXXXX Fixme */
			if ((hdsize%SECTOR_SIZE)!=0) {
				printf("LSEEK: File length error (corrupt disk image?). Exit.\n");
				exit(0);
			}

			geo.heads=1;
			geo.sectors=1;
			geo.cylinders=hdsize/SECTOR_SIZE;
			geo.start=0;

			while ((((geo.cylinders/2)*2)==geo.cylinders) && (geo.cylinders>1024)) {
				geo.sectors*=2;
				geo.cylinders/=2;
				if (((geo.cylinders/2)*2)==geo.cylinders && (geo.cylinders>1024) ) {
					geo.heads*=2;
					geo.cylinders/=2;
				}
			}


		} else {
			fprintf (stderr, "LSEEK: Error occured. Exit.\n");
			exit(0);
		}
	}

	printf("geometry: %d heads, %d secs, %d cyl, %ld start\n",
		 geo.heads,geo.sectors,geo.cylinders,geo.start);

	i=geo.sectors*geo.heads*2;

	if ((initsectors=malloc(i*SECTOR_SIZE))==NULL) {
		fprintf (stderr,"Not enough memory for partition table.\n");
		return -1;
	}

	rdb=RDSK(get_block(first));

	memset(rdb,-1,SECTOR_SIZE);

	rdb->rdb_ID		= ntohl(IDNAME_RIGIDDISK);
	rdb->rdb_SummedLongs	= ntohl(64);
	rdb->rdb_BlockBytes	= ntohl(SECTOR_SIZE);
	rdb->rdb_Flags		= ntohl(0);
	rdb->rdb_Cylinders	= ntohl(geo.cylinders);
	rdb->rdb_Sectors	= ntohl(geo.sectors);
	rdb->rdb_Heads		= ntohl(geo.heads);
	rdb->rdb_Interleave	= ntohl(0);
	rdb->rdb_Park		= ntohl(geo.cylinders);
	rdb->rdb_WritePreComp	= ntohl(geo.cylinders);
	rdb->rdb_ReducedWrite	= ntohl(geo.cylinders);
	rdb->rdb_StepRate	= ntohl(0);

	rdb->rdb_RDBBlocksLo	= ntohl(first);
	rdb->rdb_RDBBlocksHi	= ntohl(geo.sectors*geo.heads*2-1);
	rdb->rdb_LoCylinder	= ntohl(2);
	rdb->rdb_HiCylinder	= ntohl(geo.cylinders-1);
	rdb->rdb_CylBlocks	= ntohl(geo.sectors*geo.heads);
	rdb->rdb_AutoParkSeconds= ntohl(0);
	rdb->rdb_HighRDSKBlock	= ntohl(first);

	firstblock=first;
	lastblock=first;
	minblock=first;
	maxblock=geo.sectors*geo.heads*2-1;

	rigiddisk_correct();

	return 0;
}

int rigiddisk_reorg(int startblk)
{
	/* Put all Blocks together and/or move RDB to another block */
	char *curr, *curr2, *newblks;
	int  i,j,block,blk2;
	ULONG *crk;

#ifdef DEBUG
	printf ("Reorganizing RDB.\n");
#endif

	i=countblk()+startblk-1; 
	/* Needed blocks = Starting Block + Allocated Blocks. Is this correct? */

	if (i>maxblock) {
		fprintf (stderr,"Not enough disk space to place RDB at Block %d. Have %d, need %d.\n",
			startblk,maxblock,i);
		return -1;
	}

	if ((newblks=malloc(maxblock*SECTOR_SIZE))==NULL) {
		fprintf (stderr,"Not enough memory for reorganisation.\n");
		return -1;
	}

	memset(newblks,-1,maxblock*SECTOR_SIZE); 

	j = (firstblock<startblk)?firstblock:startblk;
	j = (j<2)?j:2;

	if (j>0) printf ("Trying to save %d sector(s) \n",j);

	for (i=0; i<j; i++) {
		if (!valid_blocktype(htonl(((struct AmigaBlock *)(get_block(i)))->amiga_ID))) {
			printf ("Copying raw block #%d (MBR?)\n",i);
			memcpy  (newblks+i*SECTOR_SIZE,initsectors+i*SECTOR_SIZE,SECTOR_SIZE);
		}
	}

	i=startblk;

	memcpy (newblks+i*SECTOR_SIZE,rdb,SECTOR_SIZE);

	crk=&(RDSK(newblks+(startblk*SECTOR_SIZE))->rdb_BadBlockList);
	i++;

	curr=newblks+(i*SECTOR_SIZE);

	block=htonl(rdb->rdb_BadBlockList);

	while (block!=-1) {
		memcpy (curr,initsectors+block*SECTOR_SIZE,SECTOR_SIZE);
		(*crk)=ntohl(i);
		crk=&(BBB(curr)->bbb_Next);
		block=htonl(BBB(initsectors+block*SECTOR_SIZE)->bbb_Next);
		i++;
		curr=newblks+i*SECTOR_SIZE;
	}

	(*crk)=ntohl(-1);
	crk=&(RDSK(newblks+(startblk*SECTOR_SIZE))->rdb_DriveInit);
	block=htonl(rdb->rdb_DriveInit);

	while (block!=-1) {
		memcpy (curr,initsectors+(block*SECTOR_SIZE),SECTOR_SIZE);
		(*crk)=ntohl(i);
		crk=&(LSEG(curr)->lsb_Next);
		block=htonl(LSEG(initsectors+block*SECTOR_SIZE)->lsb_Next);
		i++;
		curr=newblks+i*SECTOR_SIZE;
	}

	(*crk)=ntohl(-1);
	crk=&(RDSK(newblks+startblk*SECTOR_SIZE)->rdb_FileSysHeaderList);
	block=htonl(rdb->rdb_FileSysHeaderList);

	while (block!=-1) {
		memcpy (curr,initsectors+block*SECTOR_SIZE,SECTOR_SIZE);
		(*crk)=ntohl(i);
		crk=&(FSHB(curr)->fhb_SegListBlocks);

		curr2=curr; 
		i++;

		curr=newblks+i*SECTOR_SIZE;
 
		blk2=htonl(FSHB(get_block(block))->fhb_SegListBlocks);

		while (blk2!=-1) {
			memcpy (curr,initsectors+blk2*SECTOR_SIZE,SECTOR_SIZE);

			(*crk)=ntohl(i);
			crk=&(LSEG(curr)->lsb_Next);

			blk2=htonl(LSEG(initsectors+blk2*SECTOR_SIZE)->lsb_Next);
			i++;

			curr=newblks+i*SECTOR_SIZE;
		}

		crk=&(FSHB(curr2)->fhb_Next);
		block=htonl(FSHB(initsectors+block*SECTOR_SIZE)->fhb_Next);
	}
 
	crk=&(RDSK(newblks+startblk*SECTOR_SIZE)->rdb_PartitionList);
	block=htonl(rdb->rdb_PartitionList);

	while (block!=-1) {
		memcpy (curr,initsectors+block*SECTOR_SIZE,SECTOR_SIZE);

		(*crk)=ntohl(i);
		crk=&(PART(curr)->pb_Next);

		block=htonl(PART(initsectors+block*SECTOR_SIZE)->pb_Next);
		i++;

		curr=newblks+i*SECTOR_SIZE;
	}

	lastblock=i;
	firstblock=startblk;
	minblock=startblk;

	free (initsectors);
	initsectors=newblks;
	rdb=RDSK(initsectors+firstblock*SECTOR_SIZE);

	rigiddisk_correct();

	return 0;
}

int rigiddisk_save(void)
{
	/* save RDB to disk */
	int f;

	rigiddisk_correct();

	if ((f=open(disk_device,O_WRONLY))<0) {
		fprintf (stderr,"Cannot open device %s\n",disk_device);
		return -1;
	}

	if (lseek(f,0,SEEK_SET)<0) {
		close(f); fprintf (stderr,"Seek error occured while writing partition table.\n");
		return -1;
	}

	if (write(f,initsectors,maxblock*SECTOR_SIZE)!=maxblock*SECTOR_SIZE) {
		close(f);
		fprintf (stderr,"Error occured while writing partition table.\n");
		return -1;
	}

	printf ("Rereading partition table.\n");
	sync();
	if(ioctl(f, BLKRRPART)) {
 		printf ("Could not reread partition table. You MUST reboot now to see the changes.\n");
		return -1;
	}

	return 0;
}

char *DosType(ULONG dostype)
{
	int i,j=0;

	union {
		ULONG dlong;
		char dchar[4];
	} dt;

	dt.dlong = htonl(dostype);

	if (!list_only) {
		j = sprintf(type, "0x%04x = ",dostype);

		for (i = 0; i < 4; i++)
			if (isprint(dt.dchar[i]))
				type[j++] = dt.dchar[i];
			else
				j += sprintf(type + j, "\\%o", dt.dchar[i]);

		j += sprintf(type + j, " ");		

		type[j]=0;
	}

	switch (ntohl(dt.dlong)) {
	case 0x444f5300:	j += sprintf(type + j, "Amiga OFS");break;
	case 0x444f5301:	j += sprintf(type + j, "Amiga FFS");break;
	case 0x444f5302:	j += sprintf(type + j, "Amiga OFS Int.");break;
	case 0x444f5303:	j += sprintf(type + j, "Amiga FFS Int.");break;
	case 0x444f5304:	j += sprintf(type + j, "Amiga OFS DirCache");break;
	case 0x444f5305:	j += sprintf(type + j, "Amiga FFS DirCache");break;
	case 0x4C4E5800:	j += sprintf(type + j, "Linux native");break;
	case 0x53575000:	j += sprintf(type + j, "Linux swap");break;
	default:		j += sprintf(type + j, "[unknown]");break;
	}

	return type;
}

int rigiddisk_show(void)
{
	printf ("Disk %s: %ld heads, %ld sectors, %ld cylinders, RDB: %d\n", disk_device,
		 htonl(rdb->rdb_Heads),htonl(rdb->rdb_Sectors),htonl(rdb->rdb_Cylinders),firstblock);

	printf ("Logical Cylinders from %ld to %ld, %ld  bytes/Cylinder \n\n", 
		 htonl(rdb->rdb_LoCylinder), htonl(rdb->rdb_HiCylinder), SECTOR_SIZE*htonl(rdb->rdb_Sectors)*htonl(rdb->rdb_Heads));

	return 0;
}

int partition_show(void)
{
	int i,block;
	struct DosEnvec *de;
	struct PartitionBlock *curr;

	block=htonl(rdb->rdb_PartitionList);

	printf ("   Device  Boot Mount   Begin      End     Size   Pri  BBlks    System\n");
	for (i=0; i<parts; i++) {

		curr=PART(get_block(block));
		block=htonl(curr->pb_Next);

		de=(struct DosEnvec *)curr->pb_Environment;
		printf ("%s%-2d   ",disk_device,(i+1));
		printf ("%s    ", htonl(curr->pb_Flags)&PBFF_BOOTABLE?"*":" ");
		printf ("%s    ", htonl(curr->pb_Flags)&PBFF_NOMOUNT?" ":"*");
		printf ("%6ld   %6ld   ", htonl(de->de_LowCyl),htonl(de->de_HighCyl));
		printf ("%6ld   ", (htonl(de->de_HighCyl)-htonl(de->de_LowCyl)+1)*
				htonl(de->de_BlocksPerTrack)*htonl(de->de_Surfaces)/2);
		printf ("%3ld    ",htonl(de->de_BootPri));
		printf ("%3ld  ",htonl(de->de_BootBlocks));
		printf ("%s\n",DosType(htonl(de->de_DosType)));
	}


	return 0;
}

int quitall(void)
{
	if (!list_only) {
		free (initsectors);
	} else
		free (listsector);

	return 0;
}
