It's very simple to create a FAT-like disk structure.
At a fixed location in the file, most likely first, you have a structure with some information about the disk. Then follows the "FAT", a table of simple structures detailing the files on the disk. This is basically a fixed-size table of structure similar to this:
struct FATEntry
{
char name[20]; /* Name of file */
uint32_t pos; /* Position of file on disk (sector, block, something else) */
uint32_t size; /* Size in bytes of file */
uint32_t mtime; /* Time of last modification */
};
After this table you have a fixed-sized area used for a bitmap of free blocks on the disk. If the filesystem can grow or shrink dynamically then the bitmap might not be needed. This is then followed by the actual file data.
For a system like this, all files must be continuously laid out on the disk. This will lead to fragmenting as you add and remove and resize files.
Another way is to use the linked-list approach, used for example on the old Amiga filesystems. Using this scheme all blocks are simply linked lists.
Like before you need a structure for the actual disk data, and possibly a bitmap showing free/allocated disk blocks. The only field needed in the disk-data structure is a "pointer" to the first file.
By pointer I mean an integer pointing out the location on disk of a block.
The files themselves can be similar to the above FAT-like system:
struct FileNode
{
char name[12]; /* Name of file */
uint32_t next; /* Next file, zero for last file */
uint32_t prev; /* Previous file, zero for first file */
uint32_t data; /* Link to first data block */
uint32_t mtime; /* Last modification time */
uint32_t size; /* Size in bytes of the file */
};
The data blocks are themselves linked lists:
struct DataNode
{
uint32_t next; /* Next data block for file, zero for last block */
char data[BLOCK_SIZE - 4]; /* Actual data, -4 for the block link */
};
The good thing about a linked-list filesystem is that it will never be fragmented. The drawbacks are that you might have to jump all over the disk to fetch data blocks, and that the data blocks can't be used in full as they need at least one link to the next data block.
A third way, common in Unix-like systems, is to have the file data contain a set of links to the data-blocks. Then the data blocks doesn't have to be stored contiguously on the disk. It will include some of the same drawbacks as the linked-list method, in that blocks could be stored all over the disk, and that the maximum size of the file is limited. One pro is that the data-blocks can be fully utilized.
Such a structure could look like
struct FileNode
{
char name[16]; /* Name of file */
uint32_t size; /* Size in bytes of file */
uint32_t mtime; /* Last modification time of file */
uint32_t data[26]; /* Array of data-blocks */
};
The above structure limits the maximum file-size to 26 data blocks.