/**********************************************************************/
/* This file contains the datastructures used by PFS                  */
/* Most structures that are not directly written to disk are apt      */
/* to change.                                                         */
/**********************************************************************/

// max lengte van filename, diskname en comment
#define FNSIZE 108
#define DNSIZE 32
#define CMSIZE 80

#define ID_PFS_DISK		(0x50465300L)	//'PFS\0'

typedef unsigned char *DSTR;	// lengte, dan karakters

// IsDir(x) en IsFile(x) Argument: union objectinfo
#define IsDir(oi) ((oi).file.direntry && ((oi).file.direntry->type)>0)
#define IsFile(oi) ((oi).file.direntry && ((oi).file.direntry->type)<=0)
#define IsVolume(oi) ((oi).volume.root==0)
#define IsSameOI(oi1, oi2) ((oi1.file.direntry == oi2.file.direntry) && \
	(oi1.file.dirblock == oi2.file.dirblock))

// IsRoot(fi) checked of *oi bij de rootdir hoort
// IsRootA(fi) checked of oi bij de rootdir hoort
#define IsRoot(oi) (((oi)==NULL) || ((oi)->volume.root == NULL))
#define IsRootA(oi) ((oi).volume.root == NULL)

// *lock -> *fileentry
#define LOCKTOFILEENTRY(l) ((struct fileentry *)(((UBYTE*)l)-4))

/**********************************************************************/
/*                        File administration                         */
/**********************************************************************/

/* FileInfo
**
** This are internal representations of files. A volumeinfo is recognized
** by a zero root field.
**
** The fileinfo->direntry en fileinfo->dirblock directly point into a
** cached directory block. That block is guaranteed to be present.
** If a pointer to a objectinfo is NULL, the root of the current
** volume is meant.
*/
struct fileinfo
{
	struct direntry *direntry;
	struct cdirblock *dirblock;
};

struct volumeinfo
{
	ULONG	root;                   // always 0
	struct volumedata *volume;
};

union objectinfo
{
	struct fileinfo file;           // 0 =>it's a volumeinfo; <>0 => it's a fileinfo
	struct volumeinfo volume;
};

/**********************************************************************/
/*                    Fileentries/locks & volumes                     */
/**********************************************************************
**
** Three structures with the same basis, but different length. The are
** all coupled with 'next'.
** lock->fl_Key is the directoryblocknr
*/

/* entrytype's
** PS: ETF_VOLUME and ETF_LOCK are both LOCKS! TEST ON BOTH
*/
#define ET_VOLUME			0x0004
#define ET_FILEENTRY		0x0008
#define ET_LOCK				0x000c
#define ETF_VOLUME			1
#define ETF_FILEENTRY		2
#define ETF_LOCK			3
#define ET_SHAREDREAD		0
#define ET_SHAREDWRITE		1
#define ET_EXCLREAD			2
#define ET_EXCLWRITE		3

#define IsVolumeEntry(e)	((e)->type.flags.type == ETF_VOLUME)
#define IsFileEntry(e)		((e)->type.flags.type == ETF_FILEENTRY)
#define IsLockEntry(e)		((e)->type.flags.type == ETF_LOCK)

#define IsVolumeLock(le)	((le)->type.flags.type == ETF_VOLUME)

#define SHAREDLOCK(t) ((t).flags.access <= 1)

// ANODENR for fe's and le's; FIANODENR for fileinfo's; NOT FOR VOLUMES!!
#define ANODENR(fe) ((fe)->info.file.direntry->anode)
#define FIANODENR(fi) ((fi)->direntry->anode)

union listtype
{
	struct
	{
		unsigned pad:11;
		unsigned dir:1;		// 0 = file; 1 = dir or volume
		unsigned type:2;	// 0 = unknown; 3 = lock; 1 = volume; 2 = fileentry
		unsigned access:2;	// 0 = read shared; 2 = read excl; 1,3 = write shared, excl
	} flags;

	UWORD value;
};

typedef union listtype listtype;

/* Listentry
**
**- All locks on a volume are coupled from volume->firstfe by 'next'.
**  The end of the chain is 0.
**- [volume] points back to the volume
**- [info] is {NULL, don't care} if root.
*/

/* the general structure */
struct listentry
{
	struct listentry	*next;	// the link
	struct FileLock		lock;	// contains accesstype, dirblocknr
	listtype			type;
	union objectinfo	info;	// directory reference
	struct volumedata	*volume;	// the volume the lock is on
};

/* the specific structures */
struct fileentry
{
	struct fileentry	*next;
	struct FileLock		lock;
	listtype			type;
	union objectinfo	info;
	struct volumedata	*volume;

	UWORD	currentanode;		// anodenr belonging to offset in file
	UWORD	anodeoffset;		// blocknr within current anode
	UWORD	blockoffset;		// byteoffset within current block
	ULONG	offset;				// offset from start of file
};

struct lockentry
{
	struct lockentry	*next;
	struct FileLock		lock;
	listtype			type;
	union objectinfo	info;
	struct volumedata	*volume;

	UWORD				nextanode;	// used for examinenext
	struct fileinfo		nextentry;	// and examineall
};


/* Converts DOS BCPL Lock pointer (returned by Lock()) to a lockentry
** pointer
*/
#define LockEntryFromLock(x) ((x) ? \
 (struct lockentry*)((UBYTE*)BADDR(x)-offsetof(struct lockentry, lock)) : 0)

#define ListEntryFromLock(x) ((x) ? \
 (struct listentry*)((UBYTE*)BADDR(x)-offsetof(struct lockentry, lock)) : 0)


/**********************************************************************/
/*                        Disk administration                         */
/**********************************************************************/

struct volumedata
{
	struct volumedata	*next;		// volumechain
	struct DeviceList	*devlist;
	struct rootblock	*rootblk;

	struct listentry	*firstfe;		// the fileentrylist
	struct canodeblock	*firstanblk;	// the cached anodeblocklist
	struct cdirblock	*firstdirblk;	// the cached dirblocklist

	BOOL	rootblockchangeflag;
	WORD	numsofterrors;
	WORD	diskstate;			// normally ID_VALIDATED
	LONG	numblocks;			// total number of blocks
	LONG	numblocksused;
	LONG	numblocksfree;
	WORD	bytesperblock;
};

/**********************************************************************/
/*                            Blockformat                             */
/**********************************************************************/

#define DBLKID	0x4442	//'DB'
#define ABLKID	0x4142	//'AB'


/* Directory blocks
**
** dirblock structure:
**
** This is what a dirblock looks like on disk. Reserved fields are for
** future extension.
**
** CDirBlock structure:
**
** This is how a dirblock is cached.
**
** DirEntry structure:
**
** The 'type', 'creationtime' and 'protection' are, except for their
** size, in DOS format.
** The filename and the comment are dynamic: size followed by that
** number of characters (like BSTRs). The comment directly follows the
** filename. The 'next' field contains the size of the direntry. This
** should always be even. The end of the directoryblock is marked by
** next = 0.
**
** The size of a direntry can be calculated by:
** size = (sizeof(struct direntry) + strlen(name) + strlen(comment))&0xfe
*/
#define DB_HEADSPACE (6 + 10)
#define DB_ENTRYSPACE (TD_SECTOR - 16)

struct dirblock			// voor disk only (WORD blokno), timestamp later..
{
	UWORD id;					// 'DB'
	ULONG reserved_1[2];
	UWORD reserved_2;
	UWORD anodenr;				// anodenr belonging to this directory (points to FIRST block of dir)
	UWORD parent;				// parent; ANODE_ROOTDIR = root
	UBYTE entries[DB_ENTRYSPACE];	// entries, fini by NULL
};

struct cdirblock
{
	struct cdirblock *next;	// voor cachelist
	struct volumedata *volume;
	UWORD  blocknr;		// for already cached check
	UWORD  usecount;
	UWORD  changeflag;
	UWORD  done;
	struct dirblock blk;
};

struct direntry
{
	UBYTE next;			// sizeof direntry
	BYTE  type;			// dir, file, link etc
	UWORD anode;		// anode nummer
	ULONG size;			// sizeof file
	UWORD creationday;	// days since Jan. 1, 1978 (ala ADOS; WORD ipv LONG)
	UWORD creationminute; // minutes past modnight
	UWORD creationtick;	// ticks past minute, not really needed
	UBYTE protection;	// protection bits (ala ADOS)
	UBYTE nlength;		// lenght of filename
	UBYTE startofname;	// filename, followed by commentlen & comment
	UBYTE pad;			// make size even
};

// comment: de is struct direntry *
#define COMMENT(de) ((UBYTE*)(&((de)->startofname) + (de)->nlength))
#define OBJECTNAME(de) ((UBYTE*)(&(de->startofname)))

// get a pointer to the next direntry; de = struct direntry *
#define NEXTENTRY(de) ((struct direntry*)((UBYTE*)(de) + (de)->next))

// get a pointer to the firste direntry; blok is struct dirblock *
#define FIRSTENTRY(blok) ((struct direntry*)((blok)->blk.entries))

#define ENTRYLEN(de, comment) ((sizeof(struct direntry) + (de)->nlength + strlen(comment))&0xfffe)


/* Anode blocks and Anodes
**
** All space on disk is allocated by anodes. Anodes are runlength
** lists: a blocknr and a number indicating how many blocks are
** allocated by this anode.
**
** The anodes are organized in an array spread over anodeblocks. The
** anodenr is the index in this array. The anodeblock themselves are
** allocated in the rootblock.
**
** The first 6 anodes have a special function, they allocate
** freespace, the rootdirectory and perform system functions
**
** Every anodeblock contains ANODESINBLOCK anodes.
**
** struct canodeblock:
**
** The anodeblock in the cache..
**
** struct anode:
**
** The anode itself. The 'clustersize' is the number of blocks being
** allocated. The allocated blocks are 'blocknr' up to (but not 
** including)  'blocknr' + 'clustersize'. The 'next' field is a link
** to another anode where the allocation continues. At the end of the
** anode chain next = 0.
**
** If blocknr = 0 the anode is free, and can be reused. If the anode
** contains { 0, -1, next} the anode is reserved by a file or
** directory but is not yet in use. PS: empty anodes, with clustersize
** 0, are valid.
** Vrijgeven van een anode:  clustersize = 0; blocknr =  0; next = 0;
*/

#define ANODESINBLOCK	82

/* anode 0 is reserved; EOF; must be 0, -1, 0 (use for endoflist detection)
** ANODE_FREESPACE MUST have ANODE_RESERVED linked. All reserved blocks
** must be at the end of the chain. There may not be 'mixed' anodes
** anywhere
**
** Anodes < 6 may be {0,0,0} but should NEVER be allocated for other
** purposes.
** ANODE_FREESPACE, ANODE_BADBLOCKS and ANODE_EOF should always be
** reserved {0, -1, 0}.
** ANODE_USERFIRST is the first anode usable for files etc.
*/
#define ANODE_EOF			0
#define ANODE_FREESPACE		1
#define ANODE_RESERVED		2
#define ANODE_TOBEFREED		3
#define ANODE_BADBLOCKS		4
#define ANODE_ROOTDIR		5
#define ANODE_USERFIRST		6

struct anode
{
	UWORD clustersize;
	UWORD blocknr;
	UWORD next;
};


struct anodeblock
{
	UWORD id;
	ULONG reserved_1[2];
	UWORD seqnr;		// sequence number of anodeblock (starts with 0)
	UWORD next;			// chain, 0 = end
	UWORD pad;
	struct anode nodes[ANODESINBLOCK];
	ULONG reserved_2;
};

struct canodeblock
{
	struct canodeblock *next;	// for cachelist
	struct volumedata *volume;
	UWORD  blocknr;
	UWORD  usecount;
	UWORD  changeflag;
	UWORD  done;
	struct anodeblock blk;
};


/* Data blocks
*/
#define DATAINBLOCK	512

struct datablock
{
	UBYTE data[DATAINBLOCK];	// only data
};

/* Rootblock and bootblocks
*/
#define BOOTBLOCK1 0
#define BOOTBLOCK2 1
#define ROOTBLOCK 2

struct rootblock
{
	LONG  disktype;			// 'FDOS' same place as AmigaDos puts id
	LONG  reserved_1[2];
	UWORD creationday;		// days since Jan. 1, 1978 (ala ADOS; WORD ipv LONG)
	UWORD creationminute; 	// minutes past midnight
	UWORD creationtick;		// ticks past minute, not really needed
	UWORD protection;		// protection bits (ala ADOS)
	UBYTE diskname[32];		// DSTR of diskname;
	UWORD firstreserved;	// first reserved block (must be 0)
	UWORD lastreserved;		// end of reserved area
	LONG  reserved_2[10];
	WORD anodeblocks[42];	// 0->notallocated
	UBYTE reserved_3[DATAINBLOCK - 96 - 2*42];
};

struct bootblock
{
	LONG disktype;
	UBYTE reserved[508];
};


/* Reserved areas and other notes
**
** Anode and directory blocks are allocated in a special reserved area
** at the start of the partition. The reserved area bounderies are defined
** in the rootblock and vary with disksize. The reserved area can
** overflow in the normal area and vice versa.
**
** Directories are allocated by anodes, but they don't support
** clustersizes other than 1.
**
** Allocation strategy
**
** PFS always always writes directory and anodeblocks in a different
** place than they came from. The same for files: overwritten files are
** actually written somewhere else. A kind of 'freed first reused last'
** (FFRL) algorithm is used, so old versions stay on the disk for a long
** time. Sometimes PFS will abandon FFRL if this can prevent fragmentation.
** Recovery tools could make use of this: old versions can be restored
** if the current one is corrupt, and deleted files can be recovered.
**
** Update strategy
**
** Dirty anode and dirblocks are written to disk before the rootblock.
** Because the rootblock allocates the anodeblock which allocate the
** directories, and every anode is written on a new position, all
** changes take effect at the moment the rootblock is written. Before
** that seemingly nothing has changed.
*/


