text: // This is a personal academic project. Dear PVS-Studio, please check it.
  // PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
  /* BareMetal File System Utility */
  /* Written by Ian Seyler of Return Infinity */
  
  /* Global includes */
  #include <stdio.h>
  #include <stdlib.h>
  #include <string.h>
  #include <strings.h>
  #include <ctype.h>
  
  /* Global defines */
  struct BMFSEntry
  {
  	char FileName[32];
  	unsigned long long StartingBlock;
  	unsigned long long ReservedBlocks;
  	unsigned long long FileSize;
  	unsigned long long Unused;
  };
  
  /* Global constants */
  // Min disk size is 6MiB (three blocks of 2MiB each.)
  const unsigned long long minimumDiskSize = (6 * 1024 * 1024);
  
  /* Global variables */
  FILE *file, *disk;
  unsigned int filesize, disksize;
  char tempfilename[32], tempstring[32];
  char *filename, *diskname, *command;
  char fs_tag[] = "BMFS";
  char s_list[] = "list";
  char s_format[] = "format";
  char s_initialize[] = "initialize";
  char s_create[] = "create";
  char s_read[] = "read";
  char s_write[] = "write";
  char s_delete[] = "delete";
  struct BMFSEntry entry;
  void *pentry = &entry;
  char *BlockMap;
  char *FileBlocks;
  char Directory[4096];
  char DiskInfo[512];
  
  /* Built-in functions */
  int findfile(char *filename, struct BMFSEntry *fileentry, int *entrynumber);
  void list();
  void format();
  int initialize(char *diskname, char *size, char *mbr, char *boot, char *kernel);
  void create(char *filename, unsigned long long maxsize);
  void read(char *filename);
  void write(char *filename);
  void delete(char *filename);
  
  /* Program code */
  int main(int argc, char *argv[])
  {
  	/* Parse arguments */
  	if (argc < 3)
  	{
  		printf("BareMetal File System Utility v1.0 (2013 04 10)\n");
  		printf("Written by Ian Seyler @ Return Infinity (ian.seyler@returninfinity.com)\n\n");
  		printf("Usage: %s disk function file\n", argv[0]);
  		printf("Disk: the name of the disk file\n");
  		printf("Function: list, read, write, create, delete, format, initialize\n");
  		printf("File: (if applicable)\n");
  		exit(0);
  	}
  
  	diskname = argv[1];
  	command = argv[2];
  	filename = argv[3];
  
  	if (strcasecmp(s_initialize, command) == 0)
  	{
  		if (argc >= 4)
  		{
  			char *size = argv[3];  // Required
  			char *mbr = (argc > 4 ? argv[4] : NULL);    // Opt.
  			char *boot = (argc > 5 ? argv[5] : NULL);   // Opt.
  			char *kernel = (argc > 6 ? argv[6] : NULL); // Opt.
  			int ret = initialize(diskname, size, mbr, boot, kernel);
  			exit(ret);
  		}
  		else
  		{
  			printf("Usage: %s disk %s ", argv[0], command);
  			printf("size [mbr_file] ");
  			printf("[bootloader_file] [kernel_file]\n");
  			exit(1);
  		}
  	}
  
  	if ((disk = fopen(diskname, "r+b")) == NULL)	// Open for read/write in binary mode
  	{
  		printf("Error: Unable to open disk '%s'\n", diskname);
  		exit(0);
  	}
  	else	// Opened ok, is it a valid BMFS disk?
  	{
  		fseek(disk, 0, SEEK_END);
  		disksize = ftell(disk) / 1048576;			// Disk size in MiB
  		fseek(disk, 1024, SEEK_SET);				// Seek 1KiB in for disk information
  		fread(DiskInfo, 512, 1, disk);				// Read 512 bytes to the DiskInfo buffer
  		fseek(disk, 4096, SEEK_SET);				// Seek 4KiB in for directory
  		fread(Directory, 4096, 1, disk);			// Read 4096 bytes to the Directory buffer
  		rewind(disk);
  		
  		if (strcasecmp(DiskInfo, fs_tag) != 0)		// Is it a BMFS formatted disk?
  		{
  			if (strcasecmp(s_format, command) == 0)
  			{
  				format();
  			}
  			else
  			{
  				printf("Error: Not a valid BMFS drive (Disk is not BMFS formatted).\n");
  			}
  			fclose(disk);
  			return 0;
  		}
  	}
  
  	if (strcasecmp(s_list, command) == 0)
  	{
  		list();
  	}
  	else if (strcasecmp(s_format, command) == 0)
  	{
  		if (argc > 3)
  		{
  			if (strcasecmp(argv[3], "/FORCE") == 0)
  			{
  				format();
  			}
  			else
  			{
  				printf("Format aborted!\n");
  			}
  		}
  		else	
  		{
  			printf("Format aborted!\n");
  		}
  	}
  	else if (strcasecmp(s_create, command) == 0)
  	{
  		if (filename == NULL)
  		{
  			printf("Error: File name not specified.\n");
  		}
  		else
  		{
  			if (argc > 4)
  			{
  				int filesize = atoi(argv[4]);
  				if (filesize >= 1)
  				{
  					create(filename, filesize);
  				}
  				else
  				{
  			  		printf("Error: Invalid file size.\n");
  				}
  			}
  			else
  			{
  				printf("Maximum file size in MiB: ");
  				fgets(tempstring, 32, stdin);			// Get up to 32 chars from the keyboard
  				filesize = atoi(tempstring);
  				if (filesize >= 1)
  					create(filename, filesize);
  				else
  					printf("Error: Invalid file size.\n");
  			}
  		}
  	}
  	else if (strcasecmp(s_read, command) == 0)
  	{
  		read(filename);
  	}
  	else if (strcasecmp(s_write, command) == 0)
  	{
  		write(filename);
  	}
  	else if (strcasecmp(s_delete, command) == 0)
  	{
  		delete(filename);
  	}
  	else
  	{
  		printf("Unknown command\n");
  	}
  	if (disk != NULL)
  	{
  		fclose( disk );
  		disk = NULL;
  	}
  	return 0;
  }
  
  
  int findfile(char *filename, struct BMFSEntry *fileentry, int *entrynumber)
  {
  	int tint;
  
  	for (tint = 0; tint < 64; tint++)
  	{
  		memcpy(pentry, Directory+(tint*64), 64);
  		if (entry.FileName[0] == 0x00)				// End of directory
  		{
  			tint = 64;
  		}
  		else if (entry.FileName[0] == 0x01)			// Emtpy entry
  		{
  			// Ignore
  		}
  		else										// Valid entry
  		{
  			if (strcmp(filename, entry.FileName) == 0)
  			{
  				memcpy(fileentry, pentry, 64);
  				*entrynumber = tint;
  				return 1;
  			}
  		}	
  	}
  	return 0;
  }
  
  
  void list()
  {
  	int tint;
  
  	printf("%s\nDisk Size: %d MiB\n", diskname, disksize);
  	printf("Name                            |            Size (B)|      Reserved (MiB)\n");
  	printf("==========================================================================\n");
  	for (tint = 0; tint < 64; tint++)			// Max 64 entries
  	{
  		memcpy(pentry, Directory+(tint*64), 64);
  		if (entry.FileName[0] == 0x00)				// End of directory, bail out
  		{
  			tint = 64;
  		}
  		else if (entry.FileName[0] == 0x01)			// Emtpy entry
  		{
  			// Ignore
  		}
  		else										// Valid entry
  		{
  			printf("%-32s %20lld %20lld\n", entry.FileName, entry.FileSize, (entry.ReservedBlocks*2));
  		}
  	}
  }
  
  
  void format()
  {
  	memset(DiskInfo, 0, 512);
  	memset(Directory, 0, 4096);
  	memcpy(DiskInfo, fs_tag, 4);				// Add the 'BMFS' tag
  	fseek(disk, 1024, SEEK_SET);				// Seek 1KiB in for disk information
  	fwrite(DiskInfo, 512, 1, disk);			// Write 512 bytes for the DiskInfo
  	fseek(disk, 4096, SEEK_SET);				// Seek 4KiB in for directory
  	fwrite(Directory, 4096, 1, disk);		// Write 4096 bytes for the Directory
  	printf("Format complete.\n");
  }
  
  
  int initialize(char *diskname, char *size, char *mbr, char *boot, char *kernel)
  {
  	unsigned long long diskSize = 0;
  	unsigned long long writeSize = 0;
  	const char *bootFileType = NULL;
  	size_t bufferSize = 50 * 1024;
  	char * buffer = NULL;
  	FILE *mbrFile = NULL;
  	FILE *bootFile = NULL;
  	FILE *kernelFile = NULL;
  	int diskSizeFactor = 0;
  	size_t chunkSize = 0;
  	int ret = 0;
  	size_t i;
  
  	// Determine how the second file will be described in output messages.
  	// If a kernel file is specified too, then assume the second file is the
  	// boot loader.  If no kernel file is specified, assume the boot loader
  	// and kernel are combined into one system file.
  	if (boot != NULL)
  	{
  		bootFileType = "boot loader";
  		if (kernel == NULL)
  		{
  			bootFileType = "system";
  		}
  	}
  
  	// Validate the disk size string and convert it to an integer value.
  	for (i = 0; size[i] != '\0' && ret == 0; ++i)
  	{
  		char ch = size[i];
  		if (isdigit(ch))
  		{
  			unsigned int n = ch - '0';
  			if (diskSize * 10 > diskSize ) // Make sure we don't overflow
  			{
  				diskSize *= 10;
  				diskSize += n;
  			}
  			else if (diskSize == 0) // First loop iteration
  			{
  				diskSize += n;
  			}
  			else
  			{
  				printf("Error: Disk size is too large\n");
  				ret = 1;
  			}
  		}
  		else if (i == 0) // No digits specified
  		{
  			printf("Error: A numeric disk size must be specified\n");
  			ret = 1;
  		}
  		else
  		{
  			switch (toupper(ch))
  			{
  					case 'K':
  						diskSizeFactor = 1;
  						break;
  					case 'M':
  						diskSizeFactor = 2;
  						break;
  					case 'G':
  						diskSizeFactor = 3;
  						break;
  					case 'T':
  						diskSizeFactor = 4;
  						break;
  					case 'P':
  						diskSizeFactor = 5;
  						break;
  					default:
  						printf("Error: Invalid disk size string: '%s'\n", size);
  						ret = 1;
  						break;
  			}
  
  			// If this character is a valid unit indicator, but is not at the
  			// end of the string, then the string is invalid.
  			if (ret == 0 && size[i+1] != '\0')
  			{
  				printf("Error: Invalid disk size string: '%s'\n", size);
  				ret = 1;
  			}
  		}
  	}
  
  	// Adjust the disk size if a unit indicator was given.  Note that an
  	// input of something like "0" or "0K" will get past the checks above.
  	if (ret == 0 && diskSize > 0 && diskSizeFactor > 0)
  	{
  		while (diskSizeFactor--)
  		{
  			if (diskSize * 1024 > diskSize ) // Make sure we don't overflow
  			{
  				diskSize *= 1024;
  			}
  			else
  			{
  				printf("Error: Disk size is too large\n");
  				ret = 1;
  			}
  		}
  	}
  
  	// Make sure the disk size is large enough.
  	if (ret == 0)
  	{
  		if (diskSize < minimumDiskSize)
  		{
  			printf( "Error: Disk size must be at least %llu bytes (%lluMiB)\n", minimumDiskSize, minimumDiskSize / (1024*1024));
  			ret = 1;
  		}
  	}
  
  	// Open the Master boot Record file for reading.
  	if (ret == 0 && mbr != NULL)
  	{
  		mbrFile = fopen(mbr, "rb");
  		if (mbrFile == NULL )
  		{
  			printf("Error: Unable to open MBR file '%s'\n", mbr);
  			ret = 1;
  		}
  	}
  
  	// Open the boot loader file for reading.
  	if (ret == 0 && boot != NULL)
  	{
  		bootFile = fopen(boot, "rb");
  		if (bootFile == NULL )
  		{
  			printf("Error: Unable to open %s file '%s'\n", bootFileType, boot);
  			ret = 1;
  		}
  	}
  
  	// Open the kernel file for reading.
  	if (ret == 0 && kernel != NULL)
  	{
  		kernelFile = fopen(kernel, "rb");
  		if (kernelFile == NULL )
  		{
  			printf("Error: Unable to open kernel file '%s'\n", kernel);
  			ret = 1;
  		}
  	}
  
  	// Allocate buffer to use for filling the disk image with zeros.
  	if (ret == 0)
  	{
  		buffer = (char *) malloc(bufferSize);
  		if (buffer == NULL)
  		{
  			printf("Error: Failed to allocate buffer\n");
  			ret = 1;
  		}
  	}
  
  	// Open the disk image file for writing.  This will truncate the disk file
  	// if it already exists, so we should do this only after we're ready to
  	// actually write to the file.
  	if (ret == 0)
  	{
  		disk = fopen(diskname, "wb");
  		if (disk == NULL)
  		{
  			printf("Error: Unable to open disk '%s'\n", diskname);
  			ret = 1;
  		}
  	}
  
  	// Fill the disk image with zeros.
  	if (ret == 0)
  	{
  		double percent;
  		memset(buffer, 0, bufferSize);
  		writeSize = 0;
  		while (writeSize < diskSize)
  		{
  			percent = writeSize;
  			percent /= diskSize;
  			percent *= 100;
  			printf("Formatting disk: %llu of %llu bytes (%.0f%%)...\r", writeSize, diskSize, percent);
  			chunkSize = bufferSize;
  			if (chunkSize > diskSize - writeSize)
  			{
  				chunkSize = diskSize - writeSize;
  			}
  			if (fwrite(buffer, chunkSize, 1, disk) != 1)
  			{
  				printf("Error: Failed to write disk '%s'\n", diskname);
  				ret = 1;
  				break;
  			}
  			writeSize += chunkSize;
  		}
  		if (ret == 0)
  		{
  			printf("Formatting disk: %llu of %llu bytes (100%%)%9s\n", writeSize, diskSize, "");
  		}
  	}
  
  	// Format the disk.
  	if (ret == 0)
  	{
  		rewind(disk);
  		format();
  	}
  
  	// Write the master boot record if it was specified by the caller.
  	if (ret == 0 && mbrFile !=NULL)
  	{
  		printf("Writing master boot record.\n");
  		fseek(disk, 0, SEEK_SET);
  		if (fread(buffer, 512, 1, mbrFile) == 1)
  		{
  			if (fwrite(buffer, 512, 1, disk) != 1)
  			{
  				printf("Error: Failed to write disk '%s'\n", diskname);
  				ret = 1;
  			}
  		}
  		else
  		{
  			printf("Error: Failed to read file '%s'\n", mbr);
  			ret = 1;
  		}
  	}
  
  	// Write the boot loader if it was specified by the caller.
  	if (ret == 0 && bootFile !=NULL)
  	{
  		printf("Writing %s file.\n", bootFileType);
  		fseek(disk, 8192, SEEK_SET);
  		for (;;)
  		{
  			chunkSize = fread( buffer, 1, bufferSize, bootFile);
  			if (chunkSize > 0)
  			{
  				if (fwrite(buffer, chunkSize, 1, disk) != 1)
  				{
  					printf("Error: Failed to write disk '%s'\n", diskname);
  					ret = 1;
  				}
  			}
  			else
  			{
  				if (ferror(disk))
  				{
  					printf("Error: Failed to read file '%s'\n", boot);
  					ret = 1;
  				}
  				break;
  			}
  		}
  	}
  
  	// Write the kernel if it was specified by the caller. The kernel must
  	// immediately follow the boot loader on disk (i.e. no seek needed.)
  	if (ret == 0 && kernelFile !=NULL)
  	{
  		printf("Writing kernel.\n");
  		for (;;)
  		{
  			chunkSize = fread( buffer, 1, bufferSize, kernelFile);
  			if (chunkSize > 0)
  			{
  				if (fwrite(buffer, chunkSize, 1, disk) != 1)
  				{
  					printf("Error: Failed to write disk '%s'\n", diskname);
  					ret = 1;
  				}
  			}
  			else
  			{
  				if (ferror(disk))
  				{
  					printf("Error: Failed to read file '%s'\n", kernel);
  					ret = 1;
  				}
  				break;
  			}
  		}
  	}
  
  	// Close any files that were opened.
  	if (mbrFile != NULL)
  	{
  		fclose(mbrFile);
  	}
  	if (bootFile != NULL)
  	{
  		fclose(bootFile);
  	}
  	if (kernelFile != NULL)
  	{
  		fclose(kernelFile);
  	}
  	if (disk != NULL)
  	{
  		fclose(disk);
  		disk = NULL;
  	}
  
  	// Free the buffer if it was allocated.
  	if (buffer != NULL)
  	{
  		free(buffer);
  	}
  
  	if (ret == 0)
  	{
  		printf("Disk initialization complete.\n");
  	}
  
  	return ret;
  }
  
  
  // helper function for qsort, sorts by StartingBlock field
  static int StartingBlockCmp(const void *pa, const void *pb)
  {
  	struct BMFSEntry *ea = (struct BMFSEntry *)pa;
  	struct BMFSEntry *eb = (struct BMFSEntry *)pb;
  	// empty records go to the end
  	if (ea->FileName[0] == 0x01)
  		return 1;
  	if (eb->FileName[0] == 0x01)
  		return -1;
  	// compare non-empty records by their starting blocks number
  	return (ea->StartingBlock - eb->StartingBlock);
  }
  
  void create(char *filename, unsigned long long maxsize)
  {
  	struct BMFSEntry tempentry;
  	int slot;
  	
  	if (maxsize % 2 != 0)
  		maxsize++;
  
  	if (findfile(filename, &tempentry, &slot) == 0)
  	{
  		unsigned long long blocks_requested = maxsize / 2; // how many blocks to allocate
  		unsigned long long num_blocks = disksize / 2; // number of blocks in the disk
  		char dir_copy[4096]; // copy of directory
  		int num_used_entries = 0; // how many entries of Directory are either used or deleted
  		int first_free_entry = -1; // where to put new entry
  		int tint;
  		struct BMFSEntry *pEntry;
  		unsigned long long new_file_start = 0;
  		unsigned long long prev_file_end = 1;
  
  		printf("Creating new file...\n");
  
  		// Make a copy of Directory to play with
  		memcpy(dir_copy, Directory, 4096);
  
  		// Calculate number of files
  		for (tint = 0; tint < 64; tint++) 
  		{
  			pEntry = (struct BMFSEntry *)(dir_copy + tint * 64); // points to the current directory entry
  			if (pEntry->FileName[0] == 0x00) // end of directory
  			{
  				num_used_entries = tint;
  				if (first_free_entry == -1)
  					first_free_entry = tint; // there were no unused entires before, will use this one
  				break;
  			}
  			else if (pEntry->FileName[0] == 0x01) // unused entry
  			{
  				if (first_free_entry == -1)
  					first_free_entry = tint; // will use it for our new file
  			}
  		}
  
  		if (first_free_entry == -1)
  		{
  			printf("Cannot create file: no free directory entries.\n");
  			return;
  		}
  
  		// Find an area with enough free blocks
  		// Sort our copy of the directory by starting block number
  		qsort(dir_copy, num_used_entries, 64, StartingBlockCmp);
  
  		for (tint = 0; tint < num_used_entries + 1; tint++)
  		{
  			// on each iteration of this loop we'll see if a new file can fit
  			// between the end of the previous file (initially == 1) 
  			// and the beginning of the current file (or the last data block if there are no more files).
  
  			unsigned long long this_file_start;
  			pEntry = (struct BMFSEntry *)(dir_copy + tint * 64); // points to the current directory entry
  
  			if (tint == num_used_entries || pEntry->FileName[0] == 0x01) 
  				this_file_start = num_blocks - 1; // index of the last block
  			else
  				this_file_start = pEntry->StartingBlock;
  
  			if (this_file_start - prev_file_end >= blocks_requested) 
  			{ // fits here
  				new_file_start = prev_file_end;
  				break;
  			}
  
  			if (tint < num_used_entries)
  				prev_file_end = pEntry->StartingBlock + pEntry->ReservedBlocks;
  		}
  
  		if (new_file_start == 0) 
  		{
  			printf("Cannot create file of size %lld MiB.\n", maxsize);
  			return;
  		}
  
  		// Add file record to Directory
  		pEntry = (struct BMFSEntry *)(Directory + first_free_entry * 64);
  		pEntry->StartingBlock = new_file_start;
  		pEntry->ReservedBlocks = blocks_requested;
  		pEntry->FileSize = 0;
  		strcpy(pEntry->FileName, filename);
  
  		if (first_free_entry == num_used_entries && num_used_entries + 1 < 64)
  		{
  			// here we used the record that was marked with 0x00, 
  			// so make sure to mark the next record with 0x00 if it exists
  			pEntry = (struct BMFSEntry *)(Directory + (num_used_entries + 1) * 64);
  			pEntry->FileName[0] = 0x00;
  		}
  
  		// Flush Directory to disk
  		fseek(disk, 4096, SEEK_SET);				// Seek 4KiB in for directory
  		fwrite(Directory, 4096, 1, disk);			// Write 4096 bytes for the Directory
  
  //		printf("Complete: file %s starts at block %lld, directory entry #%d.\n", filename, new_file_start, first_free_entry);
  		printf("Complete\n");
  	}
  	else
  	{
  		printf("Error: File already exists.\n");
  	}
  }
  
  
  void read(char *filename)
  {
  	struct BMFSEntry tempentry;
  	FILE *tfile;
  	int tint, slot;
  
  	if (0 == findfile(filename, &tempentry, &slot))
  	{
  		printf("Error: File not found in BMFS.\n");
  	}
  	else
  	{
  		printf("Reading '%s' from BMFS to local file... ", filename);
  		if ((tfile = fopen(tempentry.FileName, "wb")) == NULL)
  		{
  			printf("Error: Could not open local file '%s'\n", tempentry.FileName);
  		}
  		else
  		{
  			fseek(disk, tempentry.StartingBlock*2097152, SEEK_SET); // Skip to the starting block in the disk
  			for (tint=0; tint<tempentry.FileSize; tint++)
  			{
  				putc(getc(disk), tfile);			// This is really terrible.
  				// TODO: Rework with fread and fwrite (ideally with a 2MiB buffer)
  			}
  			fclose(tfile);
  			printf("Complete\n");
  		}
  	}
  }
  
  
  void write(char *filename)
  {
  	struct BMFSEntry tempentry;
  	FILE *tfile;
  	int tint, slot;
  	unsigned long long tempfilesize;
  
  	if (0 == findfile(filename, &tempentry, &slot))
  	{
  		printf("Error: File not found in BMFS. A file entry must first be created.\n");
  	}
  	else
  	{
  		printf("Writing local file '%s' to BMFS... ", filename);
  		if ((tfile = fopen(filename, "rb")) == NULL)
  		{
  			printf("Error: Could not open local file '%s'\n", tempentry.FileName);
  		}
  		else
  		{
  			// Is there enough room in BMFS?
  			fseek(tfile, 0, SEEK_END);
  			tempfilesize = ftell(tfile);
  			rewind(tfile);
  			if ((tempentry.ReservedBlocks*2097152) < tempfilesize)
  			{
  				printf("Not enough reserved space in BMFS.\n");
  			}
  			else
  			{
  				fseek(disk, tempentry.StartingBlock*2097152, SEEK_SET); // Skip to the starting block in the disk
  				for (tint=0; tint<tempfilesize; tint++)
  				{
  					putc(getc(tfile), disk);			// This is really terrible.
  					// TODO: Rework with fread and fwrite (ideally with a 2MiB buffer)
  				}
  				// Update directory
  				memcpy(Directory+(slot*64)+48, &tempfilesize, 8);
  				fseek(disk, 4096, SEEK_SET);				// Seek 4KiB in for directory
  				fwrite(Directory, 4096, 1, disk);			// Write new directory to disk
  				printf("Complete\n");
  			}
  			fclose(tfile);
  		}
  	}
  }
  
  
  void delete(char *filename)
  {
  	struct BMFSEntry tempentry;
  	char delmarker = 0x01;
  	int slot;
  
  	if (0 == findfile(filename, &tempentry, &slot))
  	{
  		printf("Error: File not found in BMFS.\n");
  	}
  	else
  	{
  		printf("Deleting file '%s' from BMFS... ", filename);
  		// Update directory
  		memcpy(Directory+(slot*64), &delmarker, 1);
  		fseek(disk, 4096, SEEK_SET);				// Seek 4KiB in for directory
  		fwrite(Directory, 4096, 1, disk);			// Write new directory to disk				
  		printf("Complete\n");
  	}
  }
  
  
  /* EOF */
  