diff --git a/fsformat.h b/fsformat.h new file mode 100644 index 0000000..ac05e18 --- /dev/null +++ b/fsformat.h @@ -0,0 +1,107 @@ +// This is NEW CODE to replace old fs.h header, defining on-disk filesystem +// layout used by mkfs and the kernel. +#ifndef _FSFORMAT_H +#define _FSFORMAT_H + +typedef unsigned int fsformat_uint32_t; +typedef long long fsformat_int64_t; +typedef unsigned short fsformat_uint16_t; +typedef short fsformat_int16_t; + +typedef struct fsformat_superblock fsformat_superblock_t; +typedef struct fsformat_inode fsformat_inode_t; +typedef struct fsformat_dirent_v0 fsformat_dirent_v0_t; +typedef struct fsformat_dirent_v1 fsformat_dirent_v1_t; + +// The "old" magic number is used to mark "version 0" or xv6-compatible +// filysystems. +#define FSFORMAT_MAGIC_OLD 0x10203040 +// The "new" magic number is used to mark filesystems which are above +// or equal to "version 1". These share the same superblock structure +// except with extended fields and options. +#define FSFORMAT_MAGIC_NEW 0x102030F0 + +struct fsformat_superblock { + // The first eight fields are compatible with xv6 filesystem (provided + // that the block size etc. match your fork of xv6!) + + fsformat_uint32_t magic; + fsformat_uint32_t totalblocks; + fsformat_uint32_t datablocks; + fsformat_uint32_t inodelimit; + fsformat_uint32_t logblocks; + fsformat_uint32_t firstlogblock; + fsformat_uint32_t firstinodeblock; + fsformat_uint32_t firstbitmapblock; + + // The rest of the fields will only be usable if the "magic" field is + // equal to FSFORMAT_MAGIC_NEW. These are partly used to detect or + // confirm filesystem settings such as block size. + + // Simple version number & FLAGS. The lower 8 bits is used for + // the simple version (e.g. 1 for version 1), the upper 24 bits may + // be used for additional flags, minor versions etc. in later versions + // but are set to zero for version 1. + fsformat_uint32_t extd_versionflags; + // Block size in bytes. + fsformat_uint32_t extd_blocksize; + // Superblock structure size in bytes (i.e. to detect which extended + // fields are usable. + fsformat_uint32_t extd_superblocksize; + // Inode structure size. + fsformat_uint32_t extd_inodestructsize; + // Filename length. + fsformat_uint32_t extd_filenamesize; + // Inode reference size. Either set to 2 or 4, or special numbers for + // other special formats in the future. + fsformat_uint32_t extd_inoderefsize; + + // Number of direct blocks per inode for storing data in the first + // few blocks of a file. + fsformat_uint32_t extd_directblocks; + // Number of indirect blocks per inode for storing additional data of + // large files + fsformat_uint32_t extd_indirectblocks; +}; + +#define FSFORMAT_DIRECTBLOCKS 12 + +struct fsformat_inode { + fsformat_int16_t type; + fsformat_int16_t device_major; + fsformat_int16_t device_minor; + fsformat_int16_t linkcount; + fsformat_uint32_t totalbytes; + fsformat_uint32_t addrs[FSFORMAT_DIRECTBLOCKS + 1]; +}; + +#define FSFORMAT_NAMESIZE_OLD 14 + +struct fsformat_dirent_v0 { + fsformat_uint16_t inodenumber; + char filename[FSFORMAT_NAMESIZE_OLD]; +}; + +#define FSFORMAT_NAMESIZE_NEW 120 + +struct fsformat_dirent_v1 { + fsformat_uint32_t datainode; + fsformat_uint32_t metainode; + char filename[FSFORMAT_NAMESIZE_NEW]; +}; + + +// The "big" dirent structure is used for conveying dirent information +// to userland, and so allows exceptionally large name sizes and dir +// info. This isn't really implemented yet but can be for consistency. +#define FSFORMAT_NAMESIZE_BIG 2032 + +struct fsformat_dirent_big { + fsformat_int64_t datainode; + fsformat_int64_t metainode; + char filename[FSFORMAT_NAMESIZE_BIG]; +}; + +// From ifndef at top of file: +#endif + diff --git a/mkfs.c b/mkfs.c new file mode 100644 index 0000000..22c7163 --- /dev/null +++ b/mkfs.c @@ -0,0 +1,701 @@ +// This is NEW CODE by Zak to replace the old xv6 mkfs program. +#include +#include +#include +#include +#include +#include + +// These are the similar to in the old mkfs program but will be slowly +// replaced. The stat structure needs to be #defined away (or replaced +// with some other name) because "stat" will likely be defined already +// on whatever operating system mkfs is running on. +#define stat kernel_stat +#include "slkern/types.h" +#include "slkern/fs.h" +#include "slkern/stat.h" +#include "slkern/param.h" + +#include "fsformat.h" + +typedef struct mkfs_job mkfs_job_t; +struct mkfs_job { + int device; + int rootinodenumber; + + int fsversion; // Filesystem version, 0 for compatibility 1 is standard + int blocksize; // The block size, currently 4096 is the standard + int totalblocks; // Total number of blocks in the filesystem we're making + int inodecount; // Number of inodes (i.e. maximum number of files/dirs) + + int specialblocks; // Should be 2, boot block and superblock at beginning + int logblocks; // Size of the log area in blocks + int inodeblocks; // Size of the inode area in blocks + int bitmapblocks; // Size of the used/free bitmap in blocks + + int metablocks; // Total number of special/log/inode/bitmap blocks + int datablocks; // Total number of data blocks (not including metadata) + + int block_nextfree; // Next free block to allocate + int inode_nextfree; // Next free inode to allocate + + int inodesize; + int inodesperblock; + + int inoderefsize; // Set to 2 for small fs or v1, set 4 for normal + int filenamesize; // Set to 14 bytes for v0 or flexible for v1+ + int directblocks; // Number of simple "direct" blocks of file data + int indirectblocks; // Number of blocks-of-blocks for more file data + + // The superblock is configured using MKFS_DISKORDER* macros for byte order. + fsformat_superblock_t* superblock; +}; + +// The mkfs_io_* functions will need to be modified if mkfs is used inside the +// kernel or in some other circumstances than running on Linux. Currently the +// same options are used internally as for the old filesystem code. + +int mkfs_io_opendevice(mkfs_job_t* job, char* filename) { + return open(filename, O_CREAT | O_RDWR | O_TRUNC, 0666); +} + +int mkfs_io_closedevice(mkfs_job_t* job) { + close(job->device); + return 0; +} + +// This is only used internally by the other IO functions and need not exist +// on other device IO implementations. +int mkfs_io_seekblock(mkfs_job_t* job, int blocknumber) { + int offset = job->blocksize * blocknumber; + if (lseek(job->device, offset, 0) == offset) { + return 0; // Good, seek'ed to write offset + } + return -1; // Bad, seek failed +} + +int mkfs_io_readblock(mkfs_job_t* job, int blocknumber, void* buffer) { + if (mkfs_io_seekblock(job, blocknumber) != 0) { + return -1; + } else if (read(job->device, buffer, job->blocksize) != job->blocksize) { + return -1; + } + return 0; +} + +int mkfs_io_writeblock(mkfs_job_t* job, int blocknumber, void* buffer) { + if (mkfs_io_seekblock(job, blocknumber) != 0) { + return -1; + } else if (write(job->device, buffer, job->blocksize) != job->blocksize) { + return -1; + } + return 0; +} + +// These should be changed if mkfs is ever ported to a big-endian system, but +// otherwise can simply fall through: +unsigned short mkfs_diskorder16(unsigned short x) { + return x; +} + +unsigned int mkfs_diskorder32(unsigned int x) { + return x; +} + +unsigned long long mkfs_diskorder64(unsigned long long x) { + return x; +} + +#define MKFS_DISKORDER16(x) mkfs_diskorder16(x) +#define MKFS_DISKORDER32(x) mkfs_diskorder32(x) +#define MKFS_DISKORDER64(x) mkfs_diskorder64(x) + +#define MKFS_INODEBLOCK(job, inodenumber) \ + (((inodenumber) / job->inodesperblock) + MKFS_DISKORDER32(job->superblock->firstinodeblock)) +#define MKFS_INODEINDEX(job, inodenumber) \ + ((inodenumber) % job->inodesperblock) + +mkfs_job_t* mkfs_job_alloc(int fsversion, char* filename, int blocksize, int totalblocks, int logblocks, int inodecount, int directblocks, int indirectblocks, int inoderefsize, int filenamesize) { + if (fsversion == 0) { + // Check that inode/filename sizes match v0 or else fail. + if (inoderefsize != 2 || filenamesize != 14) { + return NULL; + } + } else { + // Filesystems >= v1 support 2-byte (16-bit) or 4-byte (32-bit) refs + if (inoderefsize != 2 && inoderefsize != 4) { + return NULL; + } + } + + mkfs_job_t* result = malloc(sizeof(mkfs_job_t)); + if (result) { + result->fsversion = fsversion; + result->blocksize = blocksize; + result->totalblocks = totalblocks; + + result->device = mkfs_io_opendevice(result, filename); + if (result->device < 0) { + free(result); + return NULL; + } + + result->inoderefsize = inoderefsize; + result->filenamesize = filenamesize; + result->directblocks = directblocks; + result->indirectblocks = indirectblocks; + + result->inodesize = 12 + ((directblocks+indirectblocks) * 4); + result->inodesperblock = blocksize / result->inodesize; + + result->specialblocks = 2; + result->logblocks = logblocks; + result->inodeblocks = (inodecount / result->inodesperblock) + 1; // TODO: Stabilise inode structure and it's size + result->bitmapblocks = (totalblocks / (blocksize*8)) + 1; + + result->metablocks = result->specialblocks + result->logblocks + result->inodeblocks + result->bitmapblocks; + result->datablocks = result->totalblocks - result->metablocks; + + result->inodecount = inodecount; + + result->block_nextfree = result->metablocks; + result->inode_nextfree = 1; + + result->superblock = malloc(sizeof(fsformat_superblock_t)); + result->superblock->magic = MKFS_DISKORDER32(fsversion == 0 ? FSFORMAT_MAGIC_OLD : FSFORMAT_MAGIC_NEW); + result->superblock->totalblocks = MKFS_DISKORDER32(result->totalblocks); + result->superblock->datablocks = MKFS_DISKORDER32(result->datablocks); + result->superblock->inodelimit = MKFS_DISKORDER32(result->inodecount); + result->superblock->logblocks = MKFS_DISKORDER32(result->logblocks); + result->superblock->firstlogblock = MKFS_DISKORDER32(result->specialblocks); + result->superblock->firstinodeblock = MKFS_DISKORDER32(result->specialblocks + result->logblocks); + result->superblock->firstbitmapblock = MKFS_DISKORDER32(result->specialblocks + result->logblocks + result->inodeblocks); + + // Set the extended flags only for version 1. + if (fsversion >= 1) { + result->superblock->extd_versionflags = MKFS_DISKORDER32(fsversion); + result->superblock->extd_inodestructsize = MKFS_DISKORDER32((int) sizeof(fsformat_inode_t)); + result->superblock->extd_blocksize = MKFS_DISKORDER32(blocksize); + result->superblock->extd_superblocksize = MKFS_DISKORDER32((int) sizeof(fsformat_superblock_t)); + result->superblock->extd_inoderefsize = MKFS_DISKORDER32(inoderefsize); + result->superblock->extd_filenamesize = MKFS_DISKORDER32(filenamesize); + result->superblock->extd_directblocks = MKFS_DISKORDER32(directblocks); + result->superblock->extd_indirectblocks = MKFS_DISKORDER32(indirectblocks); + } + } + return result; +} + +unsigned char* mkfs_allocblockbuffer(mkfs_job_t* job) { + return calloc(1, job->blocksize); +} + +void mkfs_freeblockbuffer(mkfs_job_t* job, unsigned char* buffer) { + free(buffer); +} + +// Marks all blocks up to #blocksused as in-use +void mkfs_markused(mkfs_job_t* job, int blocksused) { + unsigned char* buffer = mkfs_allocblockbuffer(job); + + printf("mkfs_markused(job, %d)\n", blocksused); + + if (blocksused >= (job->blocksize * 8)) { + fprintf(stderr, "mkfs_markused: This function only works for creating small disks that won't use more than 1 page of the free/used bitmap\n"); + exit(-1); + } + + // Should already be cleared by mkfs_allocblockbuffer: memset(buf, 0, job->blocksize) + + for (int blocknum = 0; blocknum < blocksused; blocknum++) { + int idx = blocknum / 8; + int mask = 1 << (blocknum % 8); + buffer[idx] |= mask; + } + + // NOTE: The old code did not seem to consistently use disk endian functions. + printf("mkfs_markused: Writing bitmap block %d\n", MKFS_DISKORDER32(job->superblock->firstbitmapblock)); + + mkfs_io_writeblock(job, MKFS_DISKORDER32(job->superblock->firstbitmapblock), buffer); + + mkfs_freeblockbuffer(job, buffer); +} + +void mkfs_readinode(mkfs_job_t* job, unsigned int inodenumber, fsformat_inode_t* inode) { + unsigned int blocknumber = MKFS_INODEBLOCK(job, inodenumber); + unsigned int inodeindex = MKFS_INODEINDEX(job, inodenumber); + void* buffer = mkfs_allocblockbuffer(job); + fsformat_inode_t* inodebuffer = (fsformat_inode_t*) buffer; + fsformat_inode_t* diskcopy = inodebuffer + inodeindex; + + mkfs_io_readblock(job, blocknumber, buffer); + memcpy(inode, diskcopy, sizeof(fsformat_inode_t)); + + mkfs_freeblockbuffer(job, buffer); +} + +void mkfs_writeinode(mkfs_job_t* job, unsigned int inodenumber, fsformat_inode_t* inode) { + unsigned int blocknumber = MKFS_INODEBLOCK(job, inodenumber); + unsigned int inodeindex = MKFS_INODEINDEX(job, inodenumber); + void* buffer = mkfs_allocblockbuffer(job); + fsformat_inode_t* inodebuffer = (fsformat_inode_t*) buffer; + fsformat_inode_t* diskcopy = inodebuffer + inodeindex; + + mkfs_io_readblock(job, blocknumber, buffer); + memcpy(diskcopy, inode, sizeof(fsformat_inode_t)); + mkfs_io_writeblock(job, blocknumber, buffer); + + mkfs_freeblockbuffer(job, buffer); +} + +unsigned int mkfs_allocinode(mkfs_job_t* job, unsigned short inodetype) { + fsformat_inode_t inode; + unsigned int inodenumber = job->inode_nextfree; + job->inode_nextfree++; // The mkfs program just allocates inodes sequentially. + + memset(&inode, 0, sizeof(fsformat_inode_t)); + + inode.type = MKFS_DISKORDER16(inodetype); + + // I guess the number of links would generally be 1 for anything created by + // mkfs. + inode.linkcount = MKFS_DISKORDER16(1); + + // Size is initially set to zero regardless of type. + // Note that byte order won't matter for zero values but is used by convention + inode.totalbytes = MKFS_DISKORDER32(0); + + mkfs_writeinode(job, inodenumber, &inode); + + return inodenumber; +} + +#define MKFS_MIN(x,y) \ + (((x) >= (y)) ? (y) : (x)) + +void mkfs_appenddata(mkfs_job_t* job, unsigned int inodenumber, void* data, int size) { + unsigned char* buffer = mkfs_allocblockbuffer(job); + unsigned char* bytedata = (unsigned char*) data; + fsformat_inode_t inode; + + // Load the inode and the offset, these will be updated & saved at the end. + mkfs_readinode(job, inodenumber, &inode); + unsigned int offset = MKFS_DISKORDER32(inode.totalbytes); + + int countdown = size; + while (countdown > 0) { + unsigned int blocktableindex = offset / job->blocksize; + unsigned int block; + // TODO: Check blockpart < limit (MAXFILE? check how this is defined) + if (blocktableindex < NDIRECT) { + // The block is indexed directly + block = MKFS_DISKORDER32(inode.addrs[blocktableindex]); + if (block == 0) { + // The data block doesn't exist yet, so allocate it. + block = job->block_nextfree; + job->block_nextfree++; + inode.addrs[blocktableindex] = MKFS_DISKORDER32(block); + } + } else { + // The block is NOT indexed directly, so get/create an indirect table. + // This is stored at the end of the directly-indexed block numbers. + unsigned int indirectblock; + + if (job->fsversion == 0) { + // Version 0 stores large files as a block-of-block-indexes + indirectblock = MKFS_DISKORDER32(inode.addrs[job->directblocks]); + if (indirectblock == 0) { + // The "indirect" page of pointers is not allocated yet, so allocate it. + indirectblock = job->block_nextfree; + job->block_nextfree++; + inode.addrs[job->directblocks] = MKFS_DISKORDER32(indirectblock); + } + } else { + // Version >= 1 uses an additional layer of indirection, so + // offsets into large files are indexed by a table-o-tables. + // This works like the regular system but with an additional + // layer, with the "tableotables" being stored where the old + // table would be (and the old being stored in the new table...) + unsigned int tableotables = MKFS_DISKORDER32(inode.addrs[job->directblocks]); + if (tableotables == 0) { + // The "indirect" page of pointers is not allocated yet, so allocate it. + tableotables = job->block_nextfree; + job->block_nextfree++; + inode.addrs[job->directblocks] = MKFS_DISKORDER32(tableotables); + } + + void* ttbuffer = mkfs_allocblockbuffer(job); + unsigned int* ttids = (unsigned int*) ttbuffer; + + mkfs_io_readblock(job, tableotables, ttbuffer); + indirectblock = MKFS_DISKORDER32(ttids[(blocktableindex - job->directblocks) / (job->blocksize / 4)]); + + if (indirectblock == 0) { + // The "indirect" page of pointers is not allocated yet, so allocate it. + indirectblock = job->block_nextfree; + job->block_nextfree++; + ttids[(blocktableindex - job->directblocks) / (job->blocksize / 4)] = MKFS_DISKORDER32(indirectblock); + mkfs_io_writeblock(job, tableotables, ttbuffer); + } + + mkfs_freeblockbuffer(job, ttbuffer); + printf("Used tableotables %d got indirectblock %d\n", tableotables, indirectblock); + } + + void* indirectbuffer = mkfs_allocblockbuffer(job); + unsigned int* indirectids = (unsigned int*) indirectbuffer; + + mkfs_io_readblock(job, indirectblock, indirectbuffer); + block = MKFS_DISKORDER32(indirectids[(blocktableindex - job->directblocks) % (job->blocksize / 4)]); + if (block == 0) { + // The data block doesn't exist yet, so allocate it. + block = job->block_nextfree; + job->block_nextfree++; + // And be sure to store the block index in the indirect table + indirectids[(blocktableindex - job->directblocks) % (job->blocksize / 4)] = MKFS_DISKORDER32(block); + mkfs_io_writeblock(job, indirectblock, indirectbuffer); + } + // The lookup is now complete so the buffer of indirect pointers is freed: + mkfs_freeblockbuffer(job, indirectbuffer); + printf("Final block %d\n", block); + } + + // Find the size of a chunk remaining + unsigned int chunksize = MKFS_MIN(((blocktableindex + 1) * job->blocksize) - offset, countdown); + mkfs_io_readblock(job, block, buffer); + bcopy(bytedata, buffer + offset - (blocktableindex * job->blocksize), chunksize); + mkfs_io_writeblock(job, block, buffer); + countdown -= chunksize; + offset += chunksize; + bytedata += chunksize; + } + + // Save the inode and offset updated from those loaded at the start. + inode.totalbytes = MKFS_DISKORDER32(offset); + mkfs_writeinode(job, inodenumber, &inode); + + mkfs_freeblockbuffer(job, buffer); +} + +// Creates a new directory entry structure for a given filename+inode combo and +// appends it to the given directory (it's appended as though normal data). +void mkfs_appenddirent(mkfs_job_t* job, unsigned int dirinodenumber, char* name, unsigned int newinodenumber) { + if (job->fsversion == 0) { + // Reserve a dirent structure and initialise the entire thing to zero. + fsformat_dirent_v0_t entry; + memset(&entry, 0, sizeof(fsformat_dirent_v0_t)); + + // Configure the fields in the dirent structure. + entry.inodenumber = MKFS_DISKORDER16(newinodenumber); // TODO: This and some other fields should probably be extended to 32-bit or other/configurable sizes in future versions + strncpy(entry.filename, name, FSFORMAT_NAMESIZE_OLD); + + // And finally append it to the directory as though it's normal file data. + mkfs_appenddata(job, dirinodenumber, &entry, sizeof(fsformat_dirent_v0_t)); + } else { + // Reserve a dirent structure and initialise the entire thing to zero. + fsformat_dirent_v1_t entry; + memset(&entry, 0, sizeof(fsformat_dirent_v1_t)); + + // Configure the fields in the dirent structure. + entry.datainode = MKFS_DISKORDER32(newinodenumber); // TODO: This and some other fields should probably be extended to 32-bit or other/configurable sizes in future versions + entry.metainode = MKFS_DISKORDER32(0xFFFFFFFF); + strncpy(entry.filename, name, FSFORMAT_NAMESIZE_NEW); + + // And finally append it to the directory as though it's normal file data. + mkfs_appenddata(job, dirinodenumber, &entry, sizeof(fsformat_dirent_v1_t)); + } +} + +// Creates a new file inode and appends it as a directory entry to the given +// directory. Note that the size of a new file begins at zero and data is added +// afterwards with calls to mkfs_appenddata. This function returns the new inode +// number after creating it and adding it to the directory. +// TODO: Add another function to test adding subdirectories, which should +// otherwise work similarly (see mkfs_newfs for code to create the root +// directory, but obviously ".." would be set to parent...) +unsigned int mkfs_newfile(mkfs_job_t* job, unsigned int dirinodenumber, char* name) { + unsigned int result = mkfs_allocinode(job, T_FILE); + mkfs_appenddirent(job, dirinodenumber, name, result); + return result; +} + +// Initialises a new filesystem, returning the inode number of the root +// directory. This should perform the work of "mkfs" in a simple mkfs job, then +// afterwards files/dirs can be appended. +unsigned int mkfs_newfs(mkfs_job_t* job) { + // NOTE: This code assumes that mkfs_allocbuffer returns zeroed memory like + // the current implementation does. + unsigned char* buffer = mkfs_allocblockbuffer(job); + // Write zeroed blocks throughout while the buffer is filled with zeroes. + for (int blocknumber = 0; blocknumber < job->totalblocks; blocknumber++) { + mkfs_io_writeblock(job, blocknumber, buffer); + } + // The buffer should still be zeroed. + memcpy(buffer, job->superblock, sizeof(fsformat_superblock_t)); + mkfs_io_writeblock(job, 1, buffer); // Write the superblock at block #1 + + int result = mkfs_allocinode(job, T_DIR); // This becomes the root directory. + // TODO: Check that result is ROOTINO and if there's any need to make that configurable + mkfs_appenddirent(job, result, ".", result); + mkfs_appenddirent(job, result, "..", result); + job->rootinodenumber = result; // Store this for consistency so mkfs_finish is guaranteed to use the same value. + + return result; // Return the new root inode number. +} + +// Finishes the workflow of the "mkfs" program: Finalises the root directory, +// fills the free/used bitmap and closes the output. +void mkfs_finish(mkfs_job_t* job) { + // Adjust the root directory size to the nearest block boundary (I'm not sure + // why this needs to be done but I may update comments when I find out...). + // First load the root directory and get it's size value: + fsformat_inode_t inode; + mkfs_readinode(job, job->rootinodenumber, &inode); + unsigned int rootdirsize = MKFS_DISKORDER32(inode.totalbytes); + + // Increment size to block boundary. NOTE: This is a conceptually clear but + // mechanically expensive way to align to boundaries, it could be replaced + // if dealing with extra-large blocks. + while ((rootdirsize % job->blocksize) != 0) { + rootdirsize++; + } + + // Then set the size value and save the root directory again. + inode.totalbytes = MKFS_DISKORDER32(rootdirsize); + mkfs_writeinode(job, job->rootinodenumber, &inode); + + // Fill the free/used bitmap up to the number of blocks we've used. NOTE: This + // assumes we've used blocks contiguously from the start of the disk and not + // placed a few at the end or any other silly business! + mkfs_markused(job, job->block_nextfree); + + // We're just about done here, time to knock off for smoko break. + mkfs_io_closedevice(job); +} + +int usage(int argc, char** argv, int argerr, const char* error) { + FILE* o = error ? stderr : stdout; + char* progname = argv[0]; + fprintf(o, "This program '%s' creates a new filesystem image.\n", progname); + fprintf(o, "It is designed to support new iterations on simple filesystem design.\n"); + fprintf(o, "It supports a 'version 0' filesystem somewhat-compatible with xv6 (depending on block size etc.),\n"); + fprintf(o, "and a 'version 1' filesystem for in-development versions. A true 'version 2' doesn't exist yet but\n"); + fprintf(o, "will be based on stable iterations of version 1.\n"); + fprintf(o, "USAGE:\n"); + fprintf(o, " %s -v0|-v1 [--blocksize ] --blocks --output [--allowempty] [--defaulttype ] [[--type ] [--arrayname ] [--filename ] resource1 [[--type ] [--arrayname ] [--filename ] resource2 [...]]]\n\n", progname); + fprintf(o, "EXAMPLES:\n"); + fprintf(o, " %s -v0 --output testfs.img --blocksize 1024 --blocks 20000 --allow-empty", progname); + return error == NULL ? 0 : -1; +} + +#define MAX_RESOURCES 100 +FILE* files[MAX_RESOURCES]; +char* types[MAX_RESOURCES]; +char* filenames[MAX_RESOURCES]; +unsigned long sizes[MAX_RESOURCES]; + +#define BUFFERSIZE 1000 +unsigned char buffer[BUFFERSIZE]; + +int main(int argc, char** argv) { + char* defaulttype = "RESOURCE"; + char* nexttype = NULL; + char* nextfilename = NULL; + int nresources = 0; + int allowempty = 0; + int skip_underscores = 0; + + int version = -1; + int blocksize = -1; + int blocks = -1; + int logblocks = -1; + int inodes = -1; + + char* outputname = NULL; + + fprintf(stderr, "SecureLang mkfs for slkern & xv6 filesystems\n"); + + int argi = 1; + while (argi < argc) { + if (!strcmp(argv[argi], "--usage")) { + usage(argc, argv, argi, NULL); + return 0; + } else if (!strcmp(argv[argi], "--type")) { + if (argi + 1 >= argc) { + return usage(argc, argv, argi, "Missing argument after --type"); + } + argi++; + nexttype = argv[argi]; + } else if (!strcmp(argv[argi], "--filename")) { + if (argi + 1 >= argc) { + return usage(argc, argv, argi, "Missing argument after --filename"); + } + argi++; + nextfilename = argv[argi]; + } else if (!strcmp(argv[argi], "--output") || (argv[argi][0] == '-' && argv[argi][1] == 'o' && argv[argi][2] == 0)) { + if (outputname != NULL) { + usage(argc, argv, argi, "Can't specify output more than once"); + return -1; + } + if (argi + 1 >= argc) { + usage(argc, argv, argi, "Missing argument after --output"); + return -1; + } + argi++; + outputname = argv[argi]; + } else if (argv[argi][0] == '-' && argv[argi][1] == 'o') { + if (outputname != NULL) { + usage(argc, argv, argi, "Can't specify output more than once"); + return -1; + } + outputname = argv[argi]+2; + } else if (!strcmp(argv[argi], "--allowempty")) { + allowempty = 1; + } else if (!strcmp(argv[argi], "-v0")) { + if (version != -1) { + return usage(argc, argv, argi, "Version number has already been set"); + } + version = 0; + } else if (!strcmp(argv[argi], "-v1")) { + if (version != -1) { + return usage(argc, argv, argi, "Version number has already been set"); + } + version = 1; + } else if (!strcmp(argv[argi], "-v2")) { + return usage(argc, argv, argi, "There is no -v2 format (yet...), this is an early version"); + } else if (!strcmp(argv[argi], "--skip_underscores")) { + skip_underscores = 1; + } else if (!strcmp(argv[argi], "--blocksize")) { + if (argi + 1 >= argc) { + return usage(argc, argv, argi, "Missing argument after --blocksize"); + } else if (blocksize != -1) { + return usage(argc, argv, argi, "Block size has already been set"); + } else { + argi++; + blocksize = atoi(argv[argi]); + } + } else if (!strcmp(argv[argi], "--blocks")) { + if (argi + 1 >= argc) { + return usage(argc, argv, argi, "Missing argument after --blocks"); + } else if (blocks != -1) { + return usage(argc, argv, argi, "Filesystem size has already been set"); + } else { + argi++; + blocks = atoi(argv[argi]); + } + } else if (!strcmp(argv[argi], "--logblocks")) { + if (argi + 1 >= argc) { + return usage(argc, argv, argi, "Missing argument after --logblocks"); + } else if (logblocks != -1) { + return usage(argc, argv, argi, "Log size has already been set"); + } else { + argi++; + blocks = atoi(argv[argi]); + } + } else if (!strcmp(argv[argi], "--inodes")) { + if (argi + 1 >= argc) { + return usage(argc, argv, argi, "Missing argument after --inodes"); + } else if (inodes != -1) { + } else { + argi++; + blocks = atoi(argv[argi]); + } + } else { + if (nresources + 1 >= MAX_RESOURCES) { + return usage(argc, argv, argi, "Too many resources"); + } + files[nresources] = fopen(argv[argi], "rb"); + if (nexttype) { + types[nresources] = nexttype; + nexttype = NULL; + } else { + types[nresources] = defaulttype; + } + if (nextfilename) { + filenames[nresources] = nextfilename; + nextfilename = NULL; + } else { + char* lastsegment = argv[argi]; + char* s = lastsegment; + while (*s) { + if (*s == '/' || *s == '\\' || *s == ':' || (skip_underscores && *s == '_')) { + lastsegment = s+1; + } + s++; + } + filenames[nresources] = lastsegment; + } + nresources++; + } + argi++; + } + + if (version < 0) { + return usage(argc, argv, argi, "Expected filesystem version to be defined, e.g. -v0"); + } else if (blocks < 0) { + return usage(argc, argv, argi, "Expected filesystem size (or number of blocks) to be defined, e.g. --blocks 20000"); + } else if (outputname == NULL) { + return usage(argc, argv, argi, "Expected output name to be defined, e.g. --output testfs.img"); + } else if (nresources < 1 && !allowempty) { + return usage(argc, argv, argi, "Expected some resources to add or --allowempty to create an empty filesystem"); + } + + // Apply the defaults from the old xv6 filesystem & mkfs code or new defaults + // if optional settings aren't applied. + if (blocksize == -1) { + if (version == 0) { + fprintf(stderr, "Using default block size of 1024 for xv6 compatibility, but the default may be changed (e.g. 4096) as long as it matches the kernel's BSIZE"); + blocksize = 1024; + } else { + blocksize = 4096; + } + } + if (logblocks == -1) { + logblocks = 30; // 10 possible pages in a filesystem write * 3 + } + if (inodes == -1) { + inodes = 200; // 200 files or directories, including special files etc. + } + + printf("Filesystem parameters: block size: %d, number of blocks: %d, log blocks: %d, inodes: %d\n", blocksize, blocks, logblocks, inodes); + + // Allocate and initialise the job structure with the appropriate sizings. + mkfs_job_t* job = mkfs_job_alloc(version, outputname, blocksize, blocks, logblocks, inodes, 12, 1, 2, 14); + + // Then create the new filesystem resulting in an inode for the root directory + int rootdirectory = mkfs_newfs(job); + + // Then add the files ("resources") to the filesystem + for (int resi = 0; resi < nresources; resi++) { + if (!strcmp(types[resi], defaulttype)) { + fprintf(stderr, "NOTE: Type name '%s' is ignored by current versions of mkfs but may be used as a hint in the future.\n", types[resi]); + } + FILE* f = files[resi]; + unsigned long sz = 0; + size_t nread; + + // Create the file in the new filesyste, returning it's inode. + int fileinode = mkfs_newfile(job, rootdirectory, filenames[resi]); + + // Read from the existing file and append to the file in the new filesystem, + // this happens in arbitrary blocks based on whatever size is reserved for + // the buffer (which shouldn't impact the output just the number of cycles). + while ((nread = fread(buffer, 1, BUFFERSIZE, f)) > 0) { + /*int nwritten = */ mkfs_appenddata(job, fileinode, buffer, nread); + //if (nwritten != nread) { + // fprintf(stderr, "Error while appending to file '%s' (possibly out of space or beyond per-file size limit etc.)\n", filenames[resi]); + // exit(-1); + //} + sz += (unsigned long) nread; + } + + fprintf(stderr, "Added %ld bytes in '%s'\n", sz, filenames[resi]); + sizes[resi] = sz; // Note, this is unnecessary except if you want to print totals etc. + fclose(f); + } + + // Finalise & save any filesystem information. + mkfs_finish(job); + + exit(0); +}