Revision 1 (by moose, 2006/07/06 01:31:16) Initial import
#ifndef __TFS_H__
#define __TFS_H__

// NOTE: think about compressing the bitmaps on disk, even RLE would provide savings.
// RLE is simple, maybe try and add some form of extension handling for other compression formats:
//  ie: zlib, lzo, etc
// will need a flag and/or a FOURCC specifying the compression codec.
// will need to do sparse file support, ie: when a part of a file is seek'ed beyond the current end of a file, skip allocating blocks inbetween the end and the new pos.


// WOW: REDESIGN:

/*
Block/Page layer handles block allocation and paging
Btree layer handles all objects though (guid) globally uniqe IDs

FS header contains guid for root node





*/




// Extent allocation instead of the plain block setup:
/*
// ie:
struct extent_header {
	uint16_t h_magic;
	uint16_t h_depth;
};



struct extent {
	union {
		struct {
			uint16_t l_block; // logical block index (I assume this is the index for the blocks in the file...)
			uint16_t l_leaf;  // block index of next leaf (extent_leaf, or extent)
			uint16_t l_reserved; // maybe for extended sizes? (l_start_high ?)
			uint16_t l_start; // physical block index
		};
		struct {
			uint16_t e_block; // logical block index (I assume this is the index for the blocks in the file...)
			uint16_t e_len;   // number of blocks
			uint16_t e_reserved; // maybe for extended sizes? (l_start_high ?)
			uint16_t e_start; // physical block index
		};
	};
};

struct ext_path {
	uint32_t p_depth;

};

struct inode {
	struct inode_header i_head;
	struct extent *i_extents;
};

*/

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

enum {
	TFS_DEFAULT_BLOCK_SIZE        = 1024,
	TFS_DEFAULT_BLOCKS_PER_GROUP  = 32768,
	TFS_DEFAULT_INODES_PER_GROUP  = 32768,
	TFS_DEFAULT_MAX_OPEN_INODES   = 256,
	TFS_DEFAULT_INODE_SIZE			= 256,

	TFS_DEFAULT_BLOCK_BITMAP_COUNT = TFS_DEFAULT_BLOCKS_PER_GROUP >> 5, ( >> 5 == / 32 )
	TFS_DEFAULT_INODE_BITMAP_COUNT = TFS_DEFAULT_INODES_PER_GROUP >> 5,
};

uint32_t tfs_block_size(tfs_t *tfs);
uint32_t tfs_inodes_per_group(tfs_t *tfs);
uint32_t tfs_blocks_per_group(tfs_t *tfs);

//#if _FILE_OFFSET_BITS == 64

#define MAX_OPEN_INODES TFS_DEFAULT_MAX_OPEN_INODES

#define BLOCK_SIZE TFS_DEFAULT_BLOCK_SIZE

// (BLOCK_SIZE << 5) == (BLOCK_SIZE * 32)
#define TOTAL_BITS (BLOCK_SIZE << 5)

// Maybe leave room for that data_offset thing
#define TOTAL_BLOCKS 32768
#define TOTAL_INODES 32768

// (TOTAL_BLOCKS >> 5) == (TOTAL_BLOCKS / 32)
#define BLOCK_BITMAP_COUNT TFS_DEFAULT_BLOCK_BITMAP_COUNT

#define INODE_BITMAP_COUNT TFS_DEFAULT_INODE_BITMAP_COUNT

#define GET_BIT(arr, v) ( ((arr)[v >> 10] >> (v & 31)) & 0x00000001 )
#define SET_BIT_ON(arr, v) ( (arr)[v >> 10] |= (1 << (v & 31)) )
#define SET_BIT_OFF(arr, v) ( (arr)[v >> 10] &= ~(1 << (v & 31)) )

#include <endian.h>
// only big and little
#define tfs_swap_endian(v) (((v)<<24) | (((v)&0xFF00)<<8) | (((v)&0xFF0000)>>8) | ((v)>>24))
// (((v) << 24) | ((v) << 16) | ((v) << 8) | ((uint8_t)(v)))

#if __BYTE_ORDER == __LITTLE_ENDIAN
# define tfs_le32_to_cpu(v) ((uint32_t)(v))
# define tfs_cpu_to_le32(v) ((uint32_t)(v))
#elif __BYTE_ORDER == __BIG_ENDIAN
# define tfs_le32_to_cpu(v) ((uint32_t)tfs_swap_endian(v))
# define tfs_cpu_to_le32(v) ((uint32_t)tfs_swap_endian(v))
#endif

typedef struct blockgroup_s {
	union {
		uint32_t info;
		struct {
			uint32_t block_bitmap_full : 1;
			uint32_t inode_bitmap_full : 1;
			uint32_t reserved : 30;
		};
	};

	uint32_t *block_bitmap; //[BLOCK_BITMAP_COUNT];
	uint32_t *inode_bitmap; //[INODE_BITMAP_COUNT];

	uint32_t inode_table_offset;
	uint32_t data_offset;

} blockgroup_t;

#define BLOCKGROUP_BLOCK_OFFSET ((BLOCK_BITMAP_COUNT * sizeof(uint32_t)) + (INODE_BITMAP_COUNT * sizeof(uint32_t)) + (INODE_SIZE * TOTAL_INODES))
#define BLOCKGROUP_POS(bg) (bg * ( (BLOCK_BITMAP_COUNT * sizeof(uint32_t)) + (INODE_BITMAP_COUNT * sizeof(uint32_t)) + (INODE_SIZE * TOTAL_INODES) + (BLOCK_SIZE * TOTAL_BLOCKS) ))
#define BLOCK_OFFSET(bk) (bk * BLOCK_SIZE)

#define INODE_TYPE_NONE 0
#define INODE_TYPE_ROOT 1
#define INODE_TYPE_DIR 2
#define INODE_TYPE_FILE 4

// rename COMPR to FILTR?
#define INODE_FLAG_COMPR 1
#define INODE_FLAG_READ 2
#define INODE_FLAG_WRITE 4

// really just info.. (on disk size, in bytes)
#define INODE_SIZE sizeof(inode_t)

typedef union tfs_addr_u {
	uint32_t addr;
	struct {
		uint16_t offset;
		uint16_t blockgroup;
	};
} tfs_addr_t;

// disk structure
typedef struct inode_s {
	uint8_t i_type;
	uint8_t i_flags;
	uint16_t i_filtr; // bitmap of filters
	uint16_t i_blocks;
	uint32_t i_size;
	uint32_t i_ref; // for hard links

	uint32_t in; // 'physical' inode number.
	uint32_t pos;

	// symlink data, dir listing, extent's, etc
	uint8_t i_nd[ ];
} __attribute__((packed)) inode_t;

typedef struct tfs_dirent_s {
	uint32_t d_ino;
	char d_name[0];
} tfs_dirent_t;

typedef struct tfs_dir_s {
	inode_t *inode;
	tfs_dirent_t *dirent;
} tfs_dir_t;

typedef struct tfs_s {
	uint32_t t_block_size;
	uint32_t t_inodes_per_group;
	uint32_t t_blocks_per_group;

	uint32_t t_block_count;
	uint32_t t_inode_count;
	uint32_t t_free_blocks_count;
	uint32_t t_free_inodes_count;

	uint32_t t_root_directory_inode;

	// might want to add a recently accessed cache for inodes, but who knows, it might not help performance at all.

	uint32_t t_blockgroup_count;
	blockgroup_t **t_blockgroup;

	// open inode table
	uint32_t t_open_inodes_count;
	uint32_t t_open_inodes_free;
	// think about mapping open inodes to thier names, so when searching for an inode, see if its already open, or a parent of it is open.
	inode_t **t_open_inodes;

	// system file descriptor
	int fd;
	int t_error;

	// cur dir
	inode_t *curwd;
} tfs_t;

/* Loads filesystem structure (header+blockgroups) from file */
int32_t _tfs_load_structure(tfs_t *tfs);

/* Loads blockgroup structure, normally called by _tfs_load_structure */
int32_t _tfs_load_blockgroup(tfs_t *tfs);

/* Allocates a new blockgroup structure in the tfs */
int32_t _tfs_allocate_blockgroup(tfs_t *tfs);


int32_t _tfs_allocate_block(tfs_t *tfs, int32_t bg, int32_t blk, int32_t count, int32_t *blocks);

// FIXME: allocate_block really needs to know what inode its allocating for..
//   and where that inode is, and where the block is located in the inode, this will help with allocating blocks in sparse inodes...
int32_t _tfs_allocate_block(tfs_t *tfs);
int32_t _tfs_free_block(tfs_t *tfs, int32_t block);
int32_t _tfs_allocate_inode(tfs_t *tfs);
void _tfs_free_inode(tfs_t *tfs, int32_t inode);

/* allocates a slot for an open inode */
int32_t _tfs_allocate_open_inode(tfs_t *tfs);
void _tfs_free_open_inode(tfs_t *tfs, uint32_t slot);
void _tfs_flush_open_inode(tfs_t *tfs, uint32_t slot);

/* scans for an 'open' slot */
int32_t _tfs_find_free_open_inode_slot(tfs_t *tfs);
/* returns a (inode_t*) corresponding to the passed in 'open' inode number (fd) */
inode_t *_tfs_get_open_inode(tfs_t *tfs, uint32_t fd);


/* takes a physical inode number and returns a "file desriptor (open slot number) */
int32_t _tfs_open_inode(tfs_t *tfs, uint32_t inode);

uint32_t _tfs_inode_read(tfs_t *tfs, inode_t *i, uint32_t size, void *ptr);
uint32_t _tfs_scan_dir(tfs_t *tfs, inode_t *dir, const char *name);
uint32_t _tfs_find_inode(tfs_t *tfs, int dir, int create, char *path);

int32_t _tfs_seek_to_blockgroup(tfs_t *tfs, uint32_t bg);
int32_t _tfs_seek_to_inode(tfs_t *tfs, uint32_t inode);
int32_t _tfs_seek_to_block(tfs_t *tfs, uint32_t block);

int32_t _tfs_flush_blockgroup(tfs_t *tfs, uint32_t bgn);

tfs_t *tfs_mount(char *path);

tfs_t *tfs_create(char *path);
void tfs_destroy(tfs_t *tfs);

int32_t tfs_sync(tfs_t *tfs);

uint32_t tfs_open(tfs_t *tfs, char *path, char *mode);
void tfs_close(tfs_t *tfs, uint32_t fd);

size_t tfs_read(tfs_t *tfs, uint32_t fd, void *buf, size_t count);
size_t tfs_write(tfs_t *tfs, uint32_t fd, void *buf, size_t count);

off_t tfs_tell(tfs_t *tfs, uint32_t fd);
uint32_t tfs_seek(tfs_t *tfs, uint32_t fd, off_t offset, uint32_t whence);

int32_t tfs_error(tfs_t *tfs, uint32_t fd); // last (aka: -1) fd inode number is reserverd for getting errors from the tfs system
char *tfs_strerror(int32_t err);

int32_t tfs_chdir(tfs_t *tfs, const char *d);

enum {
	TFSE_INT_NOMEM = 1,
	TFSE_INT_FORMAT, // incorrect format (mount may throw this)
	TFSE_INT_NOROOM,
	TFSE_INT_INVALIDARG,
	TFSE_INT_BLOCKGONE, // tfs expected block to exist (ie, its been referenced somewhere, but its not allocated?)
	TFSE_INT_INODEGONE,
	TFSE_INT_MAX = 1000,
	TFSE_EOF = TFSE_INT_MAX + 1,
	TFSE_NOT_FOUND,
	TFSE_READ_ONLY, // may be thrown on attempt to open a ro stream for writing
	TFSE_NOTDIR,
	TFSE_ISDIR,
};

#include "memory.h"
#define TFS_INIT() tfs_init(&malloc, &calloc, &realloc, &free)
int tfs_init(tfs_malloc_func_ptr m, tfs_calloc_func_ptr c, tfs_realloc_func_ptr r, tfs_free_func_ptr f);

/*
Needs to be growable...

use the *_free_* vars to check if blocks have been freed, and use them if so...
To start with, the file is not allocated fully, just enough for the first block group, and other headers.
not even the entire first chunk of data blocks need be physically allocated

tfs filesystem structure/meta-data should be written as changes happen. should help keep tfs files structurly sound...

in file representation:

___________________
| block group | ...
-------------------

Block Group:

___________________________________________________________
| block bitmap | inode bitmap | inode table | data blocks |
-----------------------------------------------------------

With current settings, block groups store 32MB of data, have a max of 32k inodes, and data blocks.

Possible Ideas:
---------------

When opened for writing, the inode will get a few data blocks pre allocated
Use the data blocks for the inode table.

*/

#endif /* __TFS_H__ */