├── .editorconfig ├── .github └── workflows │ └── main.yml ├── .gitignore ├── LICENSE ├── README.md ├── build.sh ├── clean.sh ├── doc ├── BareMetal File System Lite.md └── BareMetal File System.md └── src ├── bmfs.c └── bmfslite.c /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: https://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # Unix-style newlines with a newline ending every file 7 | [*] 8 | end_of_line = lf 9 | insert_final_newline = false 10 | 11 | # Set tab indentation 12 | [*.asm] 13 | charset = utf-8 14 | indent_style = tab 15 | indent_size = 8 -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ '*' ] 6 | pull_request: 7 | branches: [ '*' ] 8 | jobs: 9 | build-macos: 10 | runs-on: macos-latest 11 | strategy: 12 | fail-fast: false 13 | 14 | steps: 15 | - uses: actions/checkout@v4 16 | 17 | - name: install packages 18 | run: brew install nasm x86_64-elf-gcc 19 | 20 | - name: build 21 | run: sh ./build.sh 22 | 23 | build-ubuntu: 24 | runs-on: ubuntu-latest 25 | strategy: 26 | fail-fast: false 27 | 28 | steps: 29 | - uses: actions/checkout@v4 30 | 31 | - name: install packages 32 | run: sudo apt install nasm 33 | 34 | - name: build 35 | run: sh ./build.sh 36 | 37 | build-windows: 38 | runs-on: windows-latest 39 | strategy: 40 | fail-fast: false 41 | 42 | steps: 43 | - uses: actions/checkout@v4 44 | 45 | - name: install packages 46 | run: choco install nasm mingw 47 | 48 | - name: build 49 | run: sh ./build.sh 50 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Object files 2 | *.o 3 | 4 | # Executables 5 | *.exe 6 | *.out 7 | *.app 8 | 9 | # UNIX/Linux/Mac Executable 10 | bmfs 11 | bmfslite 12 | 13 | # Mac crap 14 | .DS_Store 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | =============================================================================== 2 | BareMetal OS -- License 3 | =============================================================================== 4 | 5 | Copyright (C) 2008-2024 Return Infinity -- http://www.returninfinity.com 6 | 7 | All rights reserved. 8 | 9 | Redistribution and use in source and binary forms, with or without 10 | modification, are permitted provided that the following conditions are met: 11 | 12 | * Redistributions of source code must retain the above copyright 13 | notice, this list of conditions and the following disclaimer. 14 | 15 | * Redistributions in binary form must reproduce the above copyright 16 | notice, this list of conditions and the following disclaimer in the 17 | documentation and/or other materials provided with the distribution. 18 | 19 | * Neither the name BareMetal nor the names of any BareMetal contributors 20 | may be used to endorse or promote products derived from this software 21 | without specific prior written permission. 22 | 23 | THIS SOFTWARE IS PROVIDED BY RETURN INFINITY AND CONTRIBUTORS "AS IS" 24 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 25 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 26 | ARE DISCLAIMED. IN NO EVENT SHALL RETURN INFINITY BE LIABLE FOR ANY 27 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 28 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 29 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 30 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 31 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE 32 | USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 33 | 34 | 35 | =============================================================================== 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![CI](https://github.com/ReturnInfinity/BMFS/actions/workflows/main.yml/badge.svg)](https://github.com/ReturnInfinity/BMFS/actions/workflows/main.yml) 2 | 3 | # BMFS 4 | 5 | Utility for accessing a disk or disk image formatted with BareMetal File System (BMFS). 6 | 7 | 8 | ## Prerequisites 9 | 10 | The scripts in this repo depend on a Debian-based Linux system like [Ubuntu](https://www.ubuntu.com/download/desktop) or [Elementary](https://elementary.io). macOS is also supported. 11 | 12 | - [GCC](https://gcc.gnu.org) - C compiler for building C/C++ applications. 13 | 14 | In Linux this can be completed with the following command: 15 | 16 | sudo apt install gcc 17 | 18 | 19 | ## Building BMFS 20 | 21 | ./build.sh 22 | 23 | *You can copy the bmfs binary to a location in the system path for ease of use* 24 | 25 | 26 | ## Creating a new, formatted disk image 27 | 28 | bmfs disk.image initialize 128M 29 | 30 | 31 | ## Creating a new disk image that boots BareMetal OS 32 | 33 | bmfs disk.image initialize 128M path/to/bmfs_mbr.sys path/to/pure64.sys path/to/kernel64.sys 34 | 35 | or if the Pure64 boot loader and BareMetal-OS kernel are combined into one file: 36 | 37 | bmfs disk.image initialize 128M path/to/bmfs_mbr.sys path/to/software.sys 38 | 39 | 40 | ## Formatting a disk image 41 | 42 | bmfs disk.image format 43 | 44 | In Linux/Unix/Mac OS X you can also format a physical drive by passing the correct path. 45 | 46 | sudo bmfs /dev/sdc format 47 | 48 | 49 | ## Display BMFS disk contents 50 | 51 | bmfs disk.image list 52 | 53 | Sample output: 54 | 55 | C:\baremetal>utils\bmfs BMFS-256-flat.vmdk list 56 | Disk Size: 256 MiB 57 | Name | Size (B)| Reserved (MiB) 58 | ========================================================================== 59 | test.app 31 2 60 | AnotherFile.app 1 2 61 | helloc.app 800 2 62 | 63 | 64 | ## Create a new file and reserve space for it 65 | 66 | bmfs disk.image create FileName.Ext 67 | 68 | You will be prompted for the size to reserve. 69 | 70 | Alternately, you can specify the reserved size after the file name. The reserved size is given in Megabytes and will automatically round up to an even number. 71 | 72 | bmfs disk.image create FileName.Ext 4 73 | 74 | 75 | ## Read from BMFS to a local file 76 | 77 | bmfs disk.image read FileName.Ext 78 | 79 | 80 | ## Write a local file to BMFS 81 | 82 | bmfs disk.image write FileName.Ext 83 | 84 | 85 | ## Delete a file on BMFS 86 | 87 | bmfs disk.image delete FileName.Ext 88 | 89 | 90 | // EOF 91 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | mkdir -p bin 4 | gcc -o bin/bmfs src/bmfs.c -Wall -W -pedantic -std=c99 5 | gcc -o bin/bmfslite src/bmfslite.c -Wall -W -pedantic -std=c99 6 | -------------------------------------------------------------------------------- /clean.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | rm -rf bin/ 4 | -------------------------------------------------------------------------------- /doc/BareMetal File System Lite.md: -------------------------------------------------------------------------------- 1 | # BareMetal File System Lite (BMFS-Lite) - Version 1 2 | 3 | BMFS-Lite is a new in-memory file system ([RAM Drive](https://en.wikipedia.org/wiki/RAM_drive)) used by BareMetal-OS. Based on BMFS, the design is extremely simplified compared to conventional file systems. The system is also geared more toward a small number of files (utilities, demos). 4 | 5 | 6 | ## Characteristics: 7 | 8 | - Very simple layout 9 | - All files are contiguous 10 | - RAM-Disk is divided into 1 KiB blocks 11 | - Flat organization; no subdirectories/subfolders 12 | 13 | 14 | ## RAM-Disk structure 15 | 16 | #### Blocks 17 | 18 | With BMFS-Lite, each RAM block is 1KiB. 19 | 20 | #### Free Blocks 21 | 22 | The location of free blocks can be calculated from the directory. As all files are contiguous we can extract the location of free blocks by comparing against the blocks that are currently in use. The calculation for locating free blocks only needs to be completed in the file create function. 23 | 24 | #### Disk layout 25 | 26 | The first and last disk blocks are reserved for file system usage. All other disk blocks can be used for data. 27 | 28 | Block 0 .. 3: 29 | 4x 1KiB - Directory (Max 64 files, 64-bytes for each record) 30 | 31 | Block 4 .. n: 32 | Data 33 | 34 | #### Directory 35 | 36 | BMFS-Lite supports a single directory with a maximum of 64 individual files. Each file record is 64 bytes. The directory structure is 4096 bytes and starts at the first block. 37 | 38 | #### Directory Record structure: 39 | 40 | Filename (32 bytes) - Null-terminated ASCII string 41 | Starting Block number (64-bit unsigned int) 42 | Blocks reserved (64-bit unsigned int) 43 | File size (64-bit unsigned int) 44 | Unused (8 bytes) 45 | 46 | A file name that starts with 0x00 marks the end of the directory. A file name that starts with 0x01 marks an unused record that should be ignored. 47 | 48 | ## Functions 49 | 50 | The following system calls should be available: 51 | 52 | - Create (Create and reserve space for a new file) 53 | - Delete (Delete an existing file from the file system) 54 | - Read (Read a file into system memory) 55 | - Write (Write a section of system memory to a file) 56 | - Directory/List (Prepare and display a list of file) 57 | - Query (Query the existence/details of a file) 58 | 59 | 60 | #### Create 61 | 62 | The create function accepts two parameters: 63 | 64 | Name = A null-terminated string no more that 31 characters 65 | Reserved = The number of blocks to reserve for the file 66 | 67 | 68 | #### Delete 69 | 70 | The delete function accepts one parameter: 71 | 72 | Name = The name of the file to delete 73 | 74 | 75 | #### Read 76 | 77 | The read function accepts two parameters: 78 | 79 | Name = The name of the file to read 80 | Destination = The memory address to store the file 81 | 82 | 83 | #### Write 84 | 85 | The write function accepts three parameters: 86 | 87 | Name = The name of the file to write 88 | Source = The memory address of the data 89 | Size = The amount of bytes to write 90 | 91 | 92 | #### Directory/List 93 | 94 | The dir/ls function accepts no parameters 95 | 96 | 97 | #### Query 98 | 99 | The query function accepts one parameter: 100 | 101 | Name = The name of the file to query 102 | 103 | The query function will return the following: 104 | 105 | Size = The current size of the file in bytes 106 | Reserved = The amount of blocks reserved for the file (0 if it doesn't exist) 107 | -------------------------------------------------------------------------------- /doc/BareMetal File System.md: -------------------------------------------------------------------------------- 1 | # BareMetal File System (BMFS) - Version 1 2 | 3 | BMFS is a new file system used by the BareMetal kernel and its related systems. The design is extremely simplified compared to conventional file systems. The system is also geared more toward a small number of very large files (databases, large data files). As all files are contiguous we can also implement memory mapped disk IO. BMFS was inspired by the [RT11 File System](http://en.wikipedia.org/wiki/RT11#File_system). 4 | 5 | 6 | ## Characteristics: 7 | 8 | - Very simple layout 9 | - All files are contiguous 10 | - Disk is divided into 2 MiB blocks 11 | - Flat organization; no subdirectories/subfolders 12 | 13 | 14 | ## Disk structure 15 | 16 | #### Blocks 17 | 18 | For simplicity, BMFS acts as an abstraction layer where a number of contiguous [sectors](http://en.wikipedia.org/wiki/Disk_sector) are accessed instead of individual sectors. With BMFS, each disk block is 2MiB. The disk driver will handle the optimal way to access the disk (based on if the disk uses 512 byte sectors or supports the new [Advanced Format](http://en.wikipedia.org/wiki/Advanced_Format) 4096 byte sectors). 2MiB blocks were chosen to match the 2MiB memory page allocation that is used within BareMetal. 19 | 20 | #### Free Blocks 21 | 22 | The location of free blocks can be calculated from the directory. As all files are contiguous we can extract the location of free blocks by comparing against the blocks that are currently in use. The calculation for locating free blocks only needs to be completed in the file create function. 23 | 24 | #### Disk layout 25 | 26 | The first and last disk blocks are reserved for file system usage. All other disk blocks can be used for data. 27 | 28 | Block 0: 29 | 4KiB - Legacy MBR (Master Boot Sector) sector (512B) 30 | - Free space (512B) 31 | - BMFS marker (512B) 32 | - Free space (2560B) 33 | 4KiB - Directory (Max 64 files, 64-bytes for each record) 34 | The remaining space in Block 0 is free to use. 35 | 36 | Block 1 .. n-1: 37 | Data 38 | 39 | Block n (last block on disk): 40 | Copy of Block 0 41 | 42 | #### Directory 43 | 44 | BMFS supports a single directory with a maximum of 64 individual files. Each file record is 64 bytes. The directory structure is 4096 bytes and starts at sector 8. 45 | 46 | #### Directory Record structure: 47 | 48 | Filename (32 bytes) - Null-terminated ASCII string 49 | Starting Block number (64-bit unsigned int) 50 | Blocks reserved (64-bit unsigned int) 51 | File size (64-bit unsigned int) 52 | Unused (8 bytes) 53 | 54 | A file name that starts with 0x00 marks the end of the directory. A file name that starts with 0x01 marks an unused record that should be ignored. 55 | 56 | Maximum file size supported is 70,368,744,177,664 bytes (64 TiB) with a maximum of 33,554,432 allocated blocks. 57 | 58 | 59 | ## Functions 60 | 61 | The following system calls should be available: 62 | 63 | - Create (Create and reserve space for a new file) 64 | - Delete (Delete an existing file from the file system) 65 | - Read (Read a file into system memory) 66 | - Write (Write a section of system memory to a file) 67 | - Directory/List (Prepare and display a list of file) 68 | - Query (Query the existence/details of a file) 69 | 70 | 71 | #### Create 72 | 73 | The create function accepts two parameters: 74 | 75 | Name = A null-terminated string no more that 31 characters 76 | Reserved = The number of blocks to reserve for the file 77 | 78 | 79 | #### Delete 80 | 81 | The delete function accepts one parameter: 82 | 83 | Name = The name of the file to delete 84 | 85 | 86 | #### Read 87 | 88 | The read function accepts two parameters: 89 | 90 | Name = The name of the file to read 91 | Destination = The memory address to store the file 92 | 93 | 94 | #### Write 95 | 96 | The write function accepts three parameters: 97 | 98 | Name = The name of the file to write 99 | Source = The memory address of the data 100 | Size = The amount of bytes to write 101 | 102 | 103 | #### Directory/List 104 | 105 | The dir/ls function accepts no parameters 106 | 107 | 108 | #### Query 109 | 110 | The query function accepts one parameter: 111 | 112 | Name = The name of the file to query 113 | 114 | The query function will return the following: 115 | 116 | Size = The current size of the file in bytes 117 | Reserved = The amount of blocks reserved for the file (0 if it doesn't exist) 118 | -------------------------------------------------------------------------------- /src/bmfs.c: -------------------------------------------------------------------------------- 1 | /* BareMetal File System Utility */ 2 | /* Written by Ian Seyler of Return Infinity */ 3 | /* v1.3 (2023 10 30) */ 4 | 5 | /* Global includes */ 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | /* Typedefs */ 15 | typedef uint8_t u8; 16 | typedef uint16_t u16; 17 | typedef uint32_t u32; 18 | typedef uint64_t u64; 19 | 20 | /* Global defines */ 21 | struct BMFSEntry 22 | { 23 | char FileName[32]; 24 | u64 StartingBlock; 25 | u64 ReservedBlocks; 26 | u64 FileSize; 27 | u64 Unused; 28 | }; 29 | 30 | /* Global constants */ 31 | // Min disk size is 6MiB (three blocks of 2MiB each.) 32 | const unsigned int minimumDiskSize = (6 * 1024 * 1024); 33 | // Block size is 2MiB 34 | const unsigned int blockSize = 2 * 1024 * 1024; 35 | 36 | /* Global variables */ 37 | FILE *file, *disk; 38 | unsigned int filesize, disksize, retval; 39 | char tempfilename[32], tempstring[32]; 40 | char *filename, *diskname, *command; 41 | char fs_tag[] = "BMFS"; 42 | char s_list[] = "list"; 43 | char s_format[] = "format"; 44 | char s_initialize[] = "initialize"; 45 | char s_create[] = "create"; 46 | char s_read[] = "read"; 47 | char s_write[] = "write"; 48 | char s_delete[] = "delete"; 49 | struct BMFSEntry entry; 50 | void *pentry = &entry; 51 | char *BlockMap; 52 | char *FileBlocks; 53 | char Directory[4096]; 54 | char DiskInfo[512]; 55 | 56 | /* Built-in functions */ 57 | int bmfs_find(char *filename, struct BMFSEntry *fileentry, int *entrynumber); 58 | void bmfs_list(void); 59 | void bmfs_format(void); 60 | int bmfs_initialize(char *diskname, char *size, char *mbr, char *boot, char *kernel); 61 | void bmfs_create(char *filename, unsigned long long maxsize); 62 | void bmfs_read(char *filename); 63 | void bmfs_write(char *filename); 64 | void bmfs_delete(char *filename); 65 | 66 | /* Program code */ 67 | int main(int argc, char *argv[]) 68 | { 69 | /* Parse arguments */ 70 | if (argc == 1) // No arguments provided 71 | { 72 | printf("BareMetal File System Utility v1.3 (2023 10 30)\n"); 73 | printf("Written by Ian Seyler @ Return Infinity (ian.seyler@returninfinity.com)\n\n"); 74 | printf("Usage: bmfs disk function file\n\n"); 75 | printf("Disk: the name of the disk file\n"); 76 | printf("Function: list, read, write, create, delete, format, initialize\n"); 77 | printf("File: (if applicable)\n"); 78 | exit(EXIT_SUCCESS); 79 | } 80 | else if (argc == 2) 81 | { 82 | printf("Missing argument - function\n"); 83 | exit(EXIT_FAILURE); 84 | } 85 | 86 | if (argc >= 3) 87 | { 88 | diskname = (argc > 1 ? argv[1] : NULL); 89 | command = (argc > 2 ? argv[2] : NULL); 90 | filename = (argc > 3 ? argv[3] : NULL); 91 | } 92 | 93 | if (argc > 2 && strcasecmp(s_initialize, command) == 0) 94 | { 95 | if (argc >= 4) 96 | { 97 | char *size = (argc > 3 ? argv[3] : NULL); // Required 98 | char *mbr = (argc > 4 ? argv[4] : NULL); // Opt. 99 | char *boot = (argc > 5 ? argv[5] : NULL); // Opt. 100 | char *kernel = (argc > 6 ? argv[6] : NULL); // Opt. 101 | int ret = bmfs_initialize(diskname, size, mbr, boot, kernel); 102 | exit(ret); 103 | } 104 | else 105 | { 106 | printf("Usage: bmfs disk %s ", command); 107 | printf("size [mbr_file] "); 108 | printf("[bootloader_file] [kernel_file]\n"); 109 | exit(EXIT_FAILURE); 110 | } 111 | } 112 | 113 | if ((disk = fopen(diskname, "r+b")) == NULL) // Open for read/write in binary mode 114 | { 115 | printf("bmfs error: Unable to open disk '%s'\n", diskname); 116 | exit(EXIT_FAILURE); 117 | } 118 | else // Opened ok, is it a valid BMFS disk? 119 | { 120 | fseek(disk, 0, SEEK_END); 121 | disksize = ftell(disk) / 1048576; // Disk size in MiB 122 | fseek(disk, 1024, SEEK_SET); // Seek 1KiB in for disk information 123 | retval = fread(DiskInfo, 512, 1, disk); // Read 512 bytes to the DiskInfo buffer 124 | fseek(disk, 4096, SEEK_SET); // Seek 4KiB in for directory 125 | retval = fread(Directory, 4096, 1, disk); // Read 4096 bytes to the Directory buffer 126 | rewind(disk); 127 | 128 | if (strcasecmp(DiskInfo, fs_tag) != 0) // Is it a BMFS formatted disk? 129 | { 130 | if (strcasecmp(s_format, command) == 0) 131 | { 132 | bmfs_format(); 133 | } 134 | else 135 | { 136 | printf("bmfs error: Not a valid BMFS drive (Disk is not BMFS formatted).\n"); 137 | } 138 | fclose(disk); 139 | return 0; 140 | } 141 | } 142 | 143 | if (strcasecmp(s_list, command) == 0) 144 | { 145 | bmfs_list(); 146 | } 147 | else if (strcasecmp(s_format, command) == 0) 148 | { 149 | if (argc > 3) 150 | { 151 | if (strcasecmp(argv[3], "/FORCE") == 0) 152 | { 153 | bmfs_format(); 154 | } 155 | else 156 | { 157 | printf("Format aborted!\n"); 158 | } 159 | } 160 | else 161 | { 162 | printf("Format aborted!\n"); 163 | } 164 | } 165 | else if (strcasecmp(s_create, command) == 0) 166 | { 167 | if (filename == NULL) 168 | { 169 | printf("bmfs error: File name not specified.\n"); 170 | } 171 | else 172 | { 173 | if (argc > 4) 174 | { 175 | int filesize = atoi(argv[4]); 176 | if (filesize >= 1) 177 | { 178 | bmfs_create(filename, filesize); 179 | } 180 | else 181 | { 182 | printf("bmfs error: Invalid file size.\n"); 183 | } 184 | } 185 | else 186 | { 187 | printf("Maximum file size in MiB: "); 188 | if (fgets(tempstring, 32, stdin) != NULL) // Get up to 32 chars from the keyboard 189 | filesize = atoi(tempstring); 190 | if (filesize >= 1) 191 | bmfs_create(filename, filesize); 192 | else 193 | printf("bmfs error: Invalid file size.\n"); 194 | } 195 | } 196 | } 197 | else if (strcasecmp(s_read, command) == 0) 198 | { 199 | bmfs_read(filename); 200 | } 201 | else if (strcasecmp(s_write, command) == 0) 202 | { 203 | bmfs_write(filename); 204 | } 205 | else if (strcasecmp(s_delete, command) == 0) 206 | { 207 | bmfs_delete(filename); 208 | } 209 | else 210 | { 211 | printf("bmfs error: Unknown command\n"); 212 | } 213 | 214 | if (disk != NULL) 215 | { 216 | fclose( disk ); 217 | disk = NULL; 218 | } 219 | 220 | return 0; 221 | } 222 | 223 | 224 | int bmfs_find(char *filename, struct BMFSEntry *fileentry, int *entrynumber) 225 | { 226 | int tint; 227 | 228 | for (tint = 0; tint < 64; tint++) 229 | { 230 | memcpy(pentry, Directory+(tint*64), 64); 231 | if (entry.FileName[0] == 0x00) // End of directory 232 | { 233 | tint = 64; 234 | } 235 | else if (entry.FileName[0] == 0x01) // Empty entry 236 | { 237 | // Ignore 238 | } 239 | else // Valid entry 240 | { 241 | if (strcmp(filename, entry.FileName) == 0) 242 | { 243 | memcpy(fileentry, pentry, 64); 244 | *entrynumber = tint; 245 | return 1; 246 | } 247 | } 248 | } 249 | return 0; 250 | } 251 | 252 | 253 | void bmfs_list(void) 254 | { 255 | int tint; 256 | 257 | printf("Disk Size: %d MiB\n", disksize); 258 | printf("Name | Size (B)| Reserved (MiB)\n"); 259 | printf("==========================================================================\n"); 260 | for (tint = 0; tint < 64; tint++) // Max 64 entries 261 | { 262 | memcpy(pentry, Directory+(tint*64), 64); 263 | if (entry.FileName[0] == 0x00) // End of directory, bail out 264 | { 265 | tint = 64; 266 | } 267 | else if (entry.FileName[0] == 0x01) // Empty entry 268 | { 269 | // Ignore 270 | } 271 | else // Valid entry 272 | { 273 | printf("%-32s %20lld %20lld\n", entry.FileName, (long long int)entry.FileSize, (long long int)(entry.ReservedBlocks*2)); 274 | } 275 | } 276 | } 277 | 278 | 279 | void bmfs_format(void) 280 | { 281 | memset(DiskInfo, 0, 512); 282 | memset(Directory, 0, 4096); 283 | memcpy(DiskInfo, fs_tag, 4); // Add the 'BMFS' tag 284 | fseek(disk, 1024, SEEK_SET); // Seek 1KiB in for disk information 285 | fwrite(DiskInfo, 512, 1, disk); // Write 512 bytes for the DiskInfo 286 | fseek(disk, 4096, SEEK_SET); // Seek 4KiB in for directory 287 | fwrite(Directory, 4096, 1, disk); // Write 4096 bytes for the Directory 288 | } 289 | 290 | 291 | int bmfs_initialize(char *diskname, char *size, char *mbr, char *boot, char *kernel) 292 | { 293 | unsigned long long diskSize = 0; 294 | unsigned long long writeSize = 0; 295 | const char *bootFileType = NULL; 296 | size_t bufferSize = 50 * 1024; 297 | char * buffer = NULL; 298 | FILE *mbrFile = NULL; 299 | FILE *bootFile = NULL; 300 | FILE *kernelFile = NULL; 301 | int diskSizeFactor = 0; 302 | size_t chunkSize = 0; 303 | int ret = 0; 304 | size_t i; 305 | 306 | // Determine how the second file will be described in output messages. 307 | // If a kernel file is specified too, then assume the second file is the 308 | // boot loader. If no kernel file is specified, assume the boot loader 309 | // and kernel are combined into one system file. 310 | if (boot != NULL) 311 | { 312 | bootFileType = "boot loader"; 313 | if (kernel == NULL) 314 | { 315 | bootFileType = "system"; 316 | } 317 | } 318 | 319 | // Validate the disk size string and convert it to an integer value. 320 | for (i = 0; size[i] != '\0' && ret == 0; ++i) 321 | { 322 | char ch = size[i]; 323 | if (isdigit(ch)) 324 | { 325 | unsigned int n = ch - '0'; 326 | if (diskSize * 10 > diskSize ) // Make sure we don't overflow 327 | { 328 | diskSize *= 10; 329 | diskSize += n; 330 | } 331 | else if (diskSize == 0) // First loop iteration 332 | { 333 | diskSize += n; 334 | } 335 | else 336 | { 337 | printf("bmfs error: Disk size is too large\n"); 338 | ret = 1; 339 | } 340 | } 341 | else if (i == 0) // No digits specified 342 | { 343 | printf("bmfs error: A numeric disk size must be specified\n"); 344 | ret = 1; 345 | } 346 | else 347 | { 348 | switch (toupper(ch)) 349 | { 350 | case 'K': 351 | diskSizeFactor = 1; 352 | break; 353 | case 'M': 354 | diskSizeFactor = 2; 355 | break; 356 | case 'G': 357 | diskSizeFactor = 3; 358 | break; 359 | case 'T': 360 | diskSizeFactor = 4; 361 | break; 362 | case 'P': 363 | diskSizeFactor = 5; 364 | break; 365 | default: 366 | printf("bmfs error: Invalid disk size string: '%s'\n", size); 367 | ret = 1; 368 | break; 369 | } 370 | 371 | // If this character is a valid unit indicator, but is not at the 372 | // end of the string, then the string is invalid. 373 | if (ret == 0 && size[i+1] != '\0') 374 | { 375 | printf("bmfs error: Invalid disk size string: '%s'\n", size); 376 | ret = 1; 377 | } 378 | } 379 | } 380 | 381 | // Adjust the disk size if a unit indicator was given. Note that an 382 | // input of something like "0" or "0K" will get past the checks above. 383 | if (ret == 0 && diskSize > 0 && diskSizeFactor > 0) 384 | { 385 | while (diskSizeFactor--) 386 | { 387 | if (diskSize * 1024 > diskSize ) // Make sure we don't overflow 388 | { 389 | diskSize *= 1024; 390 | } 391 | else 392 | { 393 | printf("bmfs error: Disk size is too large\n"); 394 | ret = 1; 395 | } 396 | } 397 | } 398 | 399 | // Make sure the disk size is large enough. 400 | if (ret == 0) 401 | { 402 | if (diskSize < minimumDiskSize) 403 | { 404 | printf("bmfs error: Disk size must be at least %d bytes (%dMiB)\n", minimumDiskSize, minimumDiskSize / (1024*1024)); 405 | ret = 1; 406 | } 407 | } 408 | 409 | // Open the Master boot Record file for reading. 410 | if (ret == 0 && mbr != NULL) 411 | { 412 | mbrFile = fopen(mbr, "rb"); 413 | if (mbrFile == NULL ) 414 | { 415 | printf("bmfs error: Unable to open MBR file '%s'\n", mbr); 416 | ret = 1; 417 | } 418 | } 419 | 420 | // Open the boot loader file for reading. 421 | if (ret == 0 && boot != NULL) 422 | { 423 | bootFile = fopen(boot, "rb"); 424 | if (bootFile == NULL ) 425 | { 426 | printf("bmfs error: Unable to open %s file '%s'\n", bootFileType, boot); 427 | ret = 1; 428 | } 429 | } 430 | 431 | // Open the kernel file for reading. 432 | if (ret == 0 && kernel != NULL) 433 | { 434 | kernelFile = fopen(kernel, "rb"); 435 | if (kernelFile == NULL ) 436 | { 437 | printf("bmfs error: Unable to open kernel file '%s'\n", kernel); 438 | ret = 1; 439 | } 440 | } 441 | 442 | // Allocate buffer to use for filling the disk image with zeros. 443 | if (ret == 0) 444 | { 445 | buffer = (char *) malloc(bufferSize); 446 | if (buffer == NULL) 447 | { 448 | printf("bmfs error: Failed to allocate buffer\n"); 449 | ret = 1; 450 | } 451 | } 452 | 453 | // Open the disk image file for writing. This will truncate the disk file 454 | // if it already exists, so we should do this only after we're ready to 455 | // actually write to the file. 456 | if (ret == 0) 457 | { 458 | disk = fopen(diskname, "wb"); 459 | if (disk == NULL) 460 | { 461 | printf("bmfs error: Unable to open disk '%s'\n", diskname); 462 | ret = 1; 463 | } 464 | } 465 | 466 | // Fill the disk image with zeros. 467 | if (ret == 0) 468 | { 469 | double percent; 470 | memset(buffer, 0, bufferSize); 471 | writeSize = 0; 472 | while (writeSize < diskSize) 473 | { 474 | percent = writeSize; 475 | percent /= diskSize; 476 | percent *= 100; 477 | printf("Formatting disk: %llu of %llu bytes (%.0f%%)...\r", writeSize, diskSize, percent); 478 | chunkSize = bufferSize; 479 | if (chunkSize > diskSize - writeSize) 480 | { 481 | chunkSize = diskSize - writeSize; 482 | } 483 | if (fwrite(buffer, chunkSize, 1, disk) != 1) 484 | { 485 | printf("bmfs error: Failed to write disk '%s'\n", diskname); 486 | ret = 1; 487 | break; 488 | } 489 | writeSize += chunkSize; 490 | } 491 | if (ret == 0) 492 | { 493 | printf("Formatting disk: %llu of %llu bytes (100%%)%9s\n", writeSize, diskSize, ""); 494 | } 495 | } 496 | 497 | // Format the disk. 498 | if (ret == 0) 499 | { 500 | rewind(disk); 501 | bmfs_format(); 502 | } 503 | 504 | // Write the master boot record if it was specified by the caller. 505 | if (ret == 0 && mbrFile != NULL) 506 | { 507 | fseek(disk, 0, SEEK_SET); 508 | if (fread(buffer, 512, 1, mbrFile) == 1) 509 | { 510 | if (fwrite(buffer, 512, 1, disk) != 1) 511 | { 512 | printf("bmfs error: Failed to write disk '%s'\n", diskname); 513 | ret = 1; 514 | } 515 | } 516 | else 517 | { 518 | printf("bmfs error: Failed to read file '%s'\n", mbr); 519 | ret = 1; 520 | } 521 | } 522 | 523 | // Write the boot loader if it was specified by the caller. 524 | if (ret == 0 && bootFile != NULL) 525 | { 526 | fseek(disk, 8192, SEEK_SET); 527 | for (;;) 528 | { 529 | chunkSize = fread( buffer, 1, bufferSize, bootFile); 530 | if (chunkSize > 0) 531 | { 532 | if (fwrite(buffer, chunkSize, 1, disk) != 1) 533 | { 534 | printf("bmfs error: Failed to write disk '%s'\n", diskname); 535 | ret = 1; 536 | } 537 | } 538 | else 539 | { 540 | if (ferror(disk)) 541 | { 542 | printf("bmfs error: Failed to read file '%s'\n", boot); 543 | ret = 1; 544 | } 545 | break; 546 | } 547 | } 548 | } 549 | 550 | // Write the kernel if it was specified by the caller. The kernel must 551 | // immediately follow the boot loader on disk (i.e. no seek needed.) 552 | if (ret == 0 && kernelFile != NULL) 553 | { 554 | for (;;) 555 | { 556 | chunkSize = fread( buffer, 1, bufferSize, kernelFile); 557 | if (chunkSize > 0) 558 | { 559 | if (fwrite(buffer, chunkSize, 1, disk) != 1) 560 | { 561 | printf("bmfs error: Failed to write disk '%s'\n", diskname); 562 | ret = 1; 563 | } 564 | } 565 | else 566 | { 567 | if (ferror(disk)) 568 | { 569 | printf("bmfs error: Failed to read file '%s'\n", kernel); 570 | ret = 1; 571 | } 572 | break; 573 | } 574 | } 575 | } 576 | 577 | // Close any files that were opened. 578 | if (mbrFile != NULL) 579 | { 580 | fclose(mbrFile); 581 | } 582 | if (bootFile != NULL) 583 | { 584 | fclose(bootFile); 585 | } 586 | if (kernelFile != NULL) 587 | { 588 | fclose(kernelFile); 589 | } 590 | if (disk != NULL) 591 | { 592 | fclose(disk); 593 | disk = NULL; 594 | } 595 | 596 | // Free the buffer if it was allocated. 597 | if (buffer != NULL) 598 | { 599 | free(buffer); 600 | } 601 | 602 | if (ret == 0) 603 | { 604 | printf("Disk initialization complete.\n"); 605 | } 606 | 607 | return ret; 608 | } 609 | 610 | 611 | // helper function for qsort, sorts by StartingBlock field 612 | static int StartingBlockCmp(const void *pa, const void *pb) 613 | { 614 | struct BMFSEntry *ea = (struct BMFSEntry *)pa; 615 | struct BMFSEntry *eb = (struct BMFSEntry *)pb; 616 | // empty records go to the end 617 | if (ea->FileName[0] == 0x01) 618 | return 1; 619 | if (eb->FileName[0] == 0x01) 620 | return -1; 621 | // compare non-empty records by their starting blocks number 622 | return (ea->StartingBlock - eb->StartingBlock); 623 | } 624 | 625 | void bmfs_create(char *filename, unsigned long long maxsize) 626 | { 627 | struct BMFSEntry tempentry; 628 | int slot; 629 | 630 | if (maxsize % 2 != 0) 631 | maxsize++; 632 | 633 | if (bmfs_find(filename, &tempentry, &slot) == 0) 634 | { 635 | unsigned long long blocks_requested = maxsize / 2; // how many blocks to allocate 636 | unsigned long long num_blocks = disksize / 2; // number of blocks in the disk 637 | char dir_copy[4096]; // copy of directory 638 | int num_used_entries = 0; // how many entries of Directory are either used or deleted 639 | int first_free_entry = -1; // where to put new entry 640 | int tint; 641 | struct BMFSEntry *pEntry; 642 | unsigned long long new_file_start = 0; 643 | unsigned long long prev_file_end = 1; 644 | 645 | // Make a copy of Directory to play with 646 | memcpy(dir_copy, Directory, 4096); 647 | 648 | // Calculate number of files 649 | for (tint = 0; tint < 64; tint++) 650 | { 651 | pEntry = (struct BMFSEntry *)(dir_copy + tint * 64); // points to the current directory entry 652 | if (pEntry->FileName[0] == 0x00) // end of directory 653 | { 654 | num_used_entries = tint; 655 | if (first_free_entry == -1) 656 | first_free_entry = tint; // there were no unused entires before, will use this one 657 | break; 658 | } 659 | else if (pEntry->FileName[0] == 0x01) // unused entry 660 | { 661 | if (first_free_entry == -1) 662 | first_free_entry = tint; // will use it for our new file 663 | } 664 | } 665 | 666 | if (first_free_entry == -1) 667 | { 668 | printf("bmfs error: Cannot create file. No free directory entries.\n"); 669 | return; 670 | } 671 | 672 | // Find an area with enough free blocks 673 | // Sort our copy of the directory by starting block number 674 | qsort(dir_copy, num_used_entries, 64, StartingBlockCmp); 675 | 676 | for (tint = 0; tint < num_used_entries + 1; tint++) 677 | { 678 | // on each iteration of this loop we'll see if a new file can fit 679 | // between the end of the previous file (initially == 1) 680 | // and the beginning of the current file (or the last data block if there are no more files). 681 | 682 | unsigned long long this_file_start; 683 | pEntry = (struct BMFSEntry *)(dir_copy + tint * 64); // points to the current directory entry 684 | 685 | if (tint == num_used_entries || pEntry->FileName[0] == 0x01) 686 | this_file_start = num_blocks - 1; // index of the last block 687 | else 688 | this_file_start = pEntry->StartingBlock; 689 | 690 | if (this_file_start - prev_file_end >= blocks_requested) 691 | { // fits here 692 | new_file_start = prev_file_end; 693 | break; 694 | } 695 | 696 | if (tint < num_used_entries) 697 | prev_file_end = pEntry->StartingBlock + pEntry->ReservedBlocks; 698 | } 699 | 700 | if (new_file_start == 0) 701 | { 702 | printf("bmfs error: Cannot create file of size %lld MiB.\n", maxsize); 703 | return; 704 | } 705 | 706 | // Add file record to Directory 707 | pEntry = (struct BMFSEntry *)(Directory + first_free_entry * 64); 708 | pEntry->StartingBlock = new_file_start; 709 | pEntry->ReservedBlocks = blocks_requested; 710 | pEntry->FileSize = 0; 711 | strcpy(pEntry->FileName, filename); 712 | 713 | if (first_free_entry == num_used_entries && num_used_entries + 1 < 64) 714 | { 715 | // here we used the record that was marked with 0x00, 716 | // so make sure to mark the next record with 0x00 if it exists 717 | pEntry = (struct BMFSEntry *)(Directory + (num_used_entries + 1) * 64); 718 | pEntry->FileName[0] = 0x00; 719 | } 720 | 721 | // Flush Directory to disk 722 | fseek(disk, 4096, SEEK_SET); // Seek 4KiB in for directory 723 | fwrite(Directory, 4096, 1, disk); // Write 4096 bytes for the Directory 724 | 725 | // printf("Complete: file %s starts at block %lld, directory entry #%d.\n", filename, new_file_start, first_free_entry); 726 | } 727 | else 728 | { 729 | printf("bmfs error: File already exists.\n"); 730 | } 731 | } 732 | 733 | // Read a file from a BMFS volume 734 | void bmfs_read(char *filename) 735 | { 736 | struct BMFSEntry tempentry; 737 | FILE *tfile; 738 | int slot, retval; 739 | unsigned long long bytestoread; 740 | char *buffer; 741 | 742 | if (0 == bmfs_find(filename, &tempentry, &slot)) 743 | { 744 | printf("bmfs error: File not found in BMFS.\n"); 745 | } 746 | else 747 | { 748 | if ((tfile = fopen(tempentry.FileName, "wb")) == NULL) 749 | { 750 | printf("bmfs error: Could not open local file '%s'\n", tempentry.FileName); 751 | } 752 | else 753 | { 754 | bytestoread = tempentry.FileSize; 755 | fseek(disk, tempentry.StartingBlock*blockSize, SEEK_SET); // Skip to the starting block in the disk 756 | buffer = malloc(blockSize); 757 | if (buffer == NULL) 758 | { 759 | printf("bmfs error: Unable to allocate enough memory for buffer.\n"); 760 | } 761 | else 762 | { 763 | while (bytestoread != 0) 764 | { 765 | if (bytestoread >= blockSize) 766 | { 767 | retval = fread(buffer, blockSize, 1, disk); 768 | if (retval == 1) 769 | { 770 | fwrite(buffer, blockSize, 1, tfile); 771 | bytestoread -= blockSize; 772 | } 773 | else 774 | { 775 | printf("bmfs error: Unexpected read length detected.\n"); 776 | bytestoread = 0; 777 | } 778 | } 779 | else 780 | { 781 | retval = fread(buffer, bytestoread, 1, disk); 782 | if (retval == 1) 783 | { 784 | fwrite(buffer, bytestoread, 1, tfile); 785 | bytestoread = 0; 786 | } 787 | else 788 | { 789 | printf("bmfs error: Unexpected read length detected.\n"); 790 | bytestoread = 0; 791 | } 792 | } 793 | } 794 | } 795 | fclose(tfile); 796 | } 797 | } 798 | } 799 | 800 | 801 | // Write a file to a BMFS volume 802 | void bmfs_write(char *filename) 803 | { 804 | struct BMFSEntry tempentry; 805 | FILE *tfile; 806 | int slot, retval; 807 | unsigned long long tempfilesize; 808 | char *buffer; 809 | 810 | if ((tfile = fopen(filename, "rb")) == NULL) 811 | { 812 | printf("bmfs error: Could not open local file '%s'\n", filename); 813 | } 814 | else 815 | { 816 | // Is there enough room in BMFS? 817 | fseek(tfile, 0, SEEK_END); 818 | tempfilesize = ftell(tfile); 819 | rewind(tfile); 820 | if (0 == bmfs_find(filename, &tempentry, &slot)) 821 | { 822 | if (tempfilesize < blockSize) 823 | { 824 | bmfs_create(filename, (tempfilesize+blockSize)/blockSize); 825 | } 826 | else 827 | { 828 | bmfs_create(filename, ceil((tempfilesize+1048576)/1048576)); 829 | } 830 | bmfs_find(filename, &tempentry, &slot); 831 | } 832 | if ((tempentry.ReservedBlocks*blockSize) < tempfilesize) 833 | { 834 | printf("bmfs error: Not enough reserved space in BMFS.\n"); 835 | } 836 | else 837 | { 838 | fseek(disk, tempentry.StartingBlock*blockSize, SEEK_SET); // Skip to the starting block in the disk 839 | buffer = malloc(blockSize); 840 | if (buffer == NULL) 841 | { 842 | printf("bmfs error: Unable to allocate enough memory for buffer.\n"); 843 | } 844 | else 845 | { 846 | while (tempfilesize != 0) 847 | { 848 | if (tempfilesize >= blockSize) 849 | { 850 | retval = fread(buffer, blockSize, 1, tfile); 851 | if (retval == 1) 852 | { 853 | fwrite(buffer, blockSize, 1, disk); 854 | tempfilesize -= blockSize; 855 | } 856 | else 857 | { 858 | printf("bmfs error: Unexpected read length detected.\n"); 859 | tempfilesize = 0; 860 | } 861 | } 862 | else 863 | { 864 | retval = fread(buffer, tempfilesize, 1, tfile); 865 | if (retval == 1) 866 | { 867 | memset(buffer+(tempfilesize), 0, (blockSize-tempfilesize)); // 0 the rest of the buffer 868 | fwrite(buffer, blockSize, 1, disk); 869 | tempfilesize = 0; 870 | } 871 | else 872 | { 873 | printf("bmfs error: Unexpected read length detected.\n"); 874 | tempfilesize = 0; 875 | } 876 | } 877 | } 878 | } 879 | // Update directory 880 | tempfilesize = ftell(tfile); 881 | memcpy(Directory+(slot*64)+48, &tempfilesize, 8); 882 | fseek(disk, 4096, SEEK_SET); // Seek 4KiB in for directory 883 | fwrite(Directory, 4096, 1, disk); // Write new directory to disk 884 | } 885 | fclose(tfile); 886 | } 887 | } 888 | 889 | 890 | void bmfs_delete(char *filename) 891 | { 892 | struct BMFSEntry tempentry; 893 | char delmarker = 0x01; 894 | int slot; 895 | 896 | if (0 == bmfs_find(filename, &tempentry, &slot)) 897 | { 898 | printf("bmfs error: File not found in BMFS.\n"); 899 | } 900 | else 901 | { 902 | // Update directory 903 | memcpy(Directory+(slot*64), &delmarker, 1); 904 | fseek(disk, 4096, SEEK_SET); // Seek 4KiB in for directory 905 | fwrite(Directory, 4096, 1, disk); // Write new directory to disk 906 | } 907 | } 908 | 909 | 910 | /* EOF */ 911 | -------------------------------------------------------------------------------- /src/bmfslite.c: -------------------------------------------------------------------------------- 1 | /* BareMetal File System Lite Utility */ 2 | /* Written by Ian Seyler of Return Infinity */ 3 | /* v1.0 (2024 11 21) */ 4 | 5 | /* Global includes */ 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | /* Typedefs */ 14 | typedef uint8_t u8; 15 | typedef uint16_t u16; 16 | typedef uint32_t u32; 17 | typedef uint64_t u64; 18 | 19 | /* Global defines */ 20 | struct BMFSEntry 21 | { 22 | char FileName[32]; 23 | u64 StartingBlock; // Files start at block 4 24 | u64 ReservedBlocks; 25 | u64 FileSize; 26 | u64 Unused; 27 | }; 28 | 29 | /* Global constants */ 30 | // Min drive size is 64KiB 31 | const unsigned int minimumDiskSize = (64 * 1024); 32 | // Max drive size is 2MiB 33 | const unsigned int maximumDiskSize = (2 * 1024 * 1024); 34 | // Block size in BMFS-Lite is 1KiB 35 | const unsigned int blockSize = 1024; 36 | 37 | /* Global variables */ 38 | FILE *file, *disk; 39 | unsigned int filesize, disksize, retval; 40 | char tempfilename[32], tempstring[32]; 41 | char *filename, *diskname, *command; 42 | char s_list[] = "list"; 43 | char s_format[] = "format"; 44 | char s_initialize[] = "initialize"; 45 | char s_create[] = "create"; 46 | char s_read[] = "read"; 47 | char s_write[] = "write"; 48 | struct BMFSEntry entry; 49 | void *pentry = &entry; 50 | char *BlockMap; 51 | char *FileBlocks; 52 | char Directory[4096]; 53 | 54 | /* Built-in functions */ 55 | int bmfs_find(char *filename, struct BMFSEntry *fileentry, int *entrynumber); 56 | void bmfs_list(void); 57 | void bmfs_format(void); 58 | int bmfs_initialize(char *diskname, char *size); 59 | void bmfs_create(char *filename, unsigned long long maxsize); 60 | void bmfs_read(char *filename); 61 | void bmfs_write(char *filename); 62 | 63 | /* Program code */ 64 | int main(int argc, char *argv[]) 65 | { 66 | /* Parse arguments */ 67 | if (argc == 1) // No arguments provided 68 | { 69 | printf("BareMetal File System Lite Utility v1.0 (2024 11 19)\n"); 70 | printf("Written by Ian Seyler @ Return Infinity (ian.seyler@returninfinity.com)\n\n"); 71 | printf("Usage: bmfs disk function file\n\n"); 72 | printf("Disk: the name of the disk file\n"); 73 | printf("Function: list, read, write, create, format, initialize\n"); 74 | printf("File: (if applicable)\n"); 75 | exit(EXIT_SUCCESS); 76 | } 77 | 78 | if (argc >= 2) 79 | { 80 | diskname = (argc > 1 ? argv[1] : NULL); 81 | command = (argc > 2 ? argv[2] : NULL); 82 | filename = (argc > 3 ? argv[3] : NULL); 83 | } 84 | 85 | if (argc > 2 && strcasecmp(s_initialize, command) == 0) 86 | { 87 | if (argc >= 4) 88 | { 89 | // Create a disk image of the requested size 90 | char *size = (argc > 3 ? argv[3] : NULL); // Required 91 | int ret = bmfs_initialize(diskname, size); 92 | exit(ret); 93 | } 94 | else 95 | { 96 | // Create a 1MiB disk image if no size was requested 97 | char *size = "1048576"; 98 | int ret = bmfs_initialize(diskname, size); 99 | exit(ret); 100 | } 101 | } 102 | 103 | if ((disk = fopen(diskname, "r+b")) == NULL) // Open for read/write in binary mode 104 | { 105 | printf("bmfs error: Unable to open disk '%s'\n", diskname); 106 | exit(EXIT_FAILURE); 107 | } 108 | else // Opened ok, is it a valid BMFS disk? 109 | { 110 | fseek(disk, 0, SEEK_END); 111 | disksize = ftell(disk); // Disk size in Bytes 112 | fseek(disk, 0, SEEK_SET); // Seek to start for directory 113 | retval = fread(Directory, 4096, 1, disk); // Read 4096 bytes to the Directory buffer 114 | rewind(disk); 115 | } 116 | 117 | if (strcasecmp(s_list, command) == 0) 118 | { 119 | bmfs_list(); 120 | } 121 | else if (strcasecmp(s_format, command) == 0) 122 | { 123 | if (argc > 3) 124 | { 125 | if (strcasecmp(argv[3], "/FORCE") == 0) 126 | { 127 | bmfs_format(); 128 | } 129 | else 130 | { 131 | printf("Format aborted!\n"); 132 | } 133 | } 134 | else 135 | { 136 | printf("Format aborted!\n"); 137 | } 138 | } 139 | else if (strcasecmp(s_create, command) == 0) 140 | { 141 | if (filename == NULL) 142 | { 143 | printf("bmfs-lite error: File name not specified.\n"); 144 | } 145 | else 146 | { 147 | if (argc > 4) 148 | { 149 | int filesize = atoi(argv[4]); 150 | if (filesize >= 1) 151 | { 152 | bmfs_create(filename, filesize); 153 | } 154 | else 155 | { 156 | printf("bmfs-lite error: Invalid file size.\n"); 157 | } 158 | } 159 | else 160 | { 161 | printf("Maximum file size in bytes: "); 162 | if (fgets(tempstring, 32, stdin) != NULL) // Get up to 32 chars from the keyboard 163 | filesize = atoi(tempstring); 164 | if (filesize >= 1) 165 | bmfs_create(filename, filesize); 166 | else 167 | printf("bmfs-lite error: Invalid file size.\n"); 168 | } 169 | } 170 | } 171 | else if (strcasecmp(s_read, command) == 0) 172 | { 173 | bmfs_read(filename); 174 | } 175 | else if (strcasecmp(s_write, command) == 0) 176 | { 177 | bmfs_write(filename); 178 | } 179 | else 180 | { 181 | printf("bmfs error: Unknown command\n"); 182 | } 183 | 184 | if (disk != NULL) 185 | { 186 | fclose( disk ); 187 | disk = NULL; 188 | } 189 | 190 | return 0; 191 | } 192 | 193 | 194 | int bmfs_find(char *filename, struct BMFSEntry *fileentry, int *entrynumber) 195 | { 196 | int tint; 197 | 198 | for (tint = 0; tint < 64; tint++) 199 | { 200 | memcpy(pentry, Directory+(tint*64), 64); 201 | if (entry.FileName[0] == 0x00) // End of directory 202 | { 203 | tint = 64; 204 | } 205 | else if (entry.FileName[0] == 0x01) // Empty entry 206 | { 207 | // Ignore 208 | } 209 | else // Valid entry 210 | { 211 | if (strcmp(filename, entry.FileName) == 0) 212 | { 213 | memcpy(fileentry, pentry, 64); 214 | *entrynumber = tint; 215 | return 1; 216 | } 217 | } 218 | } 219 | return 0; 220 | } 221 | 222 | 223 | void bmfs_list(void) 224 | { 225 | int tint; 226 | 227 | printf("BMFS-Lite Drive Size: %d bytes\n", disksize); 228 | printf("Name | Size | Reserved | Block\n"); 229 | printf("============================================================\n"); 230 | for (tint = 0; tint < 64; tint++) // Max 64 entries 231 | { 232 | memcpy(pentry, Directory+(tint*64), 64); 233 | if (entry.FileName[0] == 0x00) // End of directory, bail out 234 | { 235 | tint = 64; 236 | } 237 | else if (entry.FileName[0] == 0x01) // Empty entry 238 | { 239 | // Ignore 240 | } 241 | else // Valid entry 242 | { 243 | printf("%-32s %8lld %10lld %7lld\n", entry.FileName, (long long int)entry.FileSize, (long long int)(entry.ReservedBlocks*blockSize), (long long int)entry.StartingBlock); 244 | } 245 | } 246 | } 247 | 248 | 249 | void bmfs_format(void) 250 | { 251 | // Todo - overwrite entire file with zeros 252 | } 253 | 254 | 255 | int bmfs_initialize(char *diskname, char *size) 256 | { 257 | unsigned long long diskSize = 0; 258 | unsigned long long writeSize = 0; 259 | size_t bufferSize = 50 * 1024; 260 | char * buffer = NULL; 261 | int diskSizeFactor = 0; 262 | size_t chunkSize = 0; 263 | int ret = 0; 264 | size_t i; 265 | 266 | // Validate the disk size string and convert it to an integer value. 267 | for (i = 0; size[i] != '\0' && ret == 0; ++i) 268 | { 269 | char ch = size[i]; 270 | if (isdigit(ch)) 271 | { 272 | unsigned int n = ch - '0'; 273 | if (diskSize * 10 > diskSize ) // Make sure we don't overflow 274 | { 275 | diskSize *= 10; 276 | diskSize += n; 277 | } 278 | else if (diskSize == 0) // First loop iteration 279 | { 280 | diskSize += n; 281 | } 282 | else 283 | { 284 | printf("bmfs error: Disk size is too large\n"); 285 | ret = 1; 286 | } 287 | } 288 | else if (i == 0) // No digits specified 289 | { 290 | printf("bmfs error: A numeric disk size must be specified\n"); 291 | ret = 1; 292 | } 293 | else 294 | { 295 | switch (toupper(ch)) 296 | { 297 | case 'K': 298 | diskSizeFactor = 1; 299 | break; 300 | case 'M': 301 | diskSizeFactor = 2; 302 | break; 303 | default: 304 | printf("bmfs error: Invalid disk size string: '%s'\n", size); 305 | ret = 1; 306 | break; 307 | } 308 | 309 | // If this character is a valid unit indicator, but is not at the 310 | // end of the string, then the string is invalid. 311 | if (ret == 0 && size[i+1] != '\0') 312 | { 313 | printf("bmfs error: Invalid disk size string: '%s'\n", size); 314 | ret = 1; 315 | } 316 | } 317 | } 318 | 319 | // Adjust the disk size if a unit indicator was given. Note that an 320 | // input of something like "0" or "0K" will get past the checks above. 321 | if (ret == 0 && diskSize > 0 && diskSizeFactor > 0) 322 | { 323 | while (diskSizeFactor--) 324 | { 325 | if (diskSize * 1024 > diskSize ) // Make sure we don't overflow 326 | { 327 | diskSize *= 1024; 328 | } 329 | else 330 | { 331 | printf("bmfs error: Disk size is too large\n"); 332 | ret = 1; 333 | } 334 | } 335 | } 336 | 337 | // Make sure the disk size is large enough. 338 | if (ret == 0) 339 | { 340 | if (diskSize < minimumDiskSize) 341 | { 342 | printf("bmfs error: Disk size must be at least %d bytes\n", minimumDiskSize); 343 | ret = 1; 344 | } 345 | if (diskSize > maximumDiskSize) 346 | { 347 | printf("bmfs error: Disk size must not be greater than %d bytes\n", maximumDiskSize); 348 | ret = 1; 349 | } 350 | } 351 | 352 | // Allocate buffer to use for filling the disk image with zeros. 353 | if (ret == 0) 354 | { 355 | buffer = (char *) malloc(bufferSize); 356 | if (buffer == NULL) 357 | { 358 | printf("bmfs error: Failed to allocate buffer\n"); 359 | ret = 1; 360 | } 361 | } 362 | 363 | // Open the disk image file for writing. This will truncate the disk file 364 | // if it already exists, so we should do this only after we're ready to 365 | // actually write to the file. 366 | if (ret == 0) 367 | { 368 | disk = fopen(diskname, "wb"); 369 | if (disk == NULL) 370 | { 371 | printf("bmfs error: Unable to open disk '%s'\n", diskname); 372 | ret = 1; 373 | } 374 | } 375 | 376 | // Fill the disk image with zeros. 377 | if (ret == 0) 378 | { 379 | // double percent; 380 | memset(buffer, 0, bufferSize); 381 | writeSize = 0; 382 | while (writeSize < diskSize) 383 | { 384 | // percent = writeSize; 385 | // percent /= diskSize; 386 | // percent *= 100; 387 | // printf("Formatting disk: %llu of %llu bytes (%.0f%%)...\r", writeSize, diskSize, percent); 388 | chunkSize = bufferSize; 389 | if (chunkSize > diskSize - writeSize) 390 | { 391 | chunkSize = diskSize - writeSize; 392 | } 393 | if (fwrite(buffer, chunkSize, 1, disk) != 1) 394 | { 395 | printf("bmfs error: Failed to write disk '%s'\n", diskname); 396 | ret = 1; 397 | break; 398 | } 399 | writeSize += chunkSize; 400 | } 401 | if (ret == 0) 402 | { 403 | // printf("Formatting disk: %llu of %llu bytes (100%%)%9s\n", writeSize, diskSize, ""); 404 | } 405 | } 406 | 407 | // Format the disk. 408 | if (ret == 0) 409 | { 410 | rewind(disk); 411 | bmfs_format(); 412 | } 413 | 414 | if (disk != NULL) 415 | { 416 | fclose(disk); 417 | disk = NULL; 418 | } 419 | 420 | // Free the buffer if it was allocated. 421 | if (buffer != NULL) 422 | { 423 | free(buffer); 424 | } 425 | 426 | if (ret == 0) 427 | { 428 | // printf("Disk initialization complete.\n"); 429 | } 430 | 431 | return ret; 432 | } 433 | 434 | // helper function for qsort, sorts by StartingBlock field 435 | static int StartingBlockCmp(const void *pa, const void *pb) 436 | { 437 | struct BMFSEntry *ea = (struct BMFSEntry *)pa; 438 | struct BMFSEntry *eb = (struct BMFSEntry *)pb; 439 | // empty records go to the end 440 | if (ea->FileName[0] == 0x01) 441 | return 1; 442 | if (eb->FileName[0] == 0x01) 443 | return -1; 444 | // compare non-empty records by their starting blocks number 445 | return (ea->StartingBlock - eb->StartingBlock); 446 | } 447 | 448 | void bmfs_create(char *filename, unsigned long long maxsize) 449 | { 450 | struct BMFSEntry tempentry; 451 | int slot; 452 | 453 | if (bmfs_find(filename, &tempentry, &slot) == 0) 454 | { 455 | unsigned long long blocks_requested = maxsize / 2; // how many blocks to allocate 456 | char dir_copy[4096]; // copy of directory 457 | int num_used_entries = 0; // how many entries of Directory are either used or deleted 458 | int first_free_entry = -1; // where to put new entry 459 | int tint; 460 | struct BMFSEntry *pEntry; 461 | unsigned long long new_file_start = 0; 462 | 463 | if(strlen(filename) > 31) 464 | { 465 | printf("bmfs error: Filename too long.\n"); 466 | return; 467 | } 468 | 469 | // Make a copy of Directory to play with 470 | memcpy(dir_copy, Directory, 4096); 471 | 472 | // Calculate number of files 473 | for (tint = 0; tint < 64; tint++) 474 | { 475 | pEntry = (struct BMFSEntry *)(dir_copy + tint * 64); // points to the current directory entry 476 | if (pEntry->FileName[0] == 0x00) // end of directory 477 | { 478 | num_used_entries = tint; 479 | if (first_free_entry == -1) 480 | first_free_entry = tint; // there were no unused entires before, will use this one 481 | break; 482 | } 483 | else if (pEntry->FileName[0] == 0x01) // unused entry 484 | { 485 | if (first_free_entry == -1) 486 | first_free_entry = tint; // will use it for our new file 487 | } 488 | } 489 | 490 | if (first_free_entry == -1) 491 | { 492 | printf("bmfs error: Cannot create file. No free directory entries.\n"); 493 | return; 494 | } 495 | 496 | // Find an area with enough free blocks 497 | // Sort our copy of the directory by starting block number 498 | qsort(dir_copy, num_used_entries, 64, StartingBlockCmp); 499 | 500 | for (tint = 0; tint < num_used_entries + 1; tint++) 501 | { 502 | // on each iteration of this loop we'll see if a new file can fit 503 | // between the end of the previous file (initially == 1) 504 | // and the beginning of the current file (or the last data block if there are no more files). 505 | 506 | pEntry = (struct BMFSEntry *)(dir_copy + tint * 64); // points to the current directory entry 507 | 508 | new_file_start += pEntry->ReservedBlocks; 509 | } 510 | new_file_start += 4; // Skip the directory 511 | 512 | // Add file record to Directory 513 | pEntry = (struct BMFSEntry *)(Directory + first_free_entry * 64); 514 | pEntry->StartingBlock = new_file_start; 515 | pEntry->ReservedBlocks = blocks_requested; 516 | pEntry->FileSize = 0; 517 | strcpy(pEntry->FileName, filename); 518 | 519 | if (first_free_entry == num_used_entries && num_used_entries + 1 < 64) 520 | { 521 | // here we used the record that was marked with 0x00, 522 | // so make sure to mark the next record with 0x00 if it exists 523 | pEntry = (struct BMFSEntry *)(Directory + (num_used_entries + 1) * 64); 524 | pEntry->FileName[0] = 0x00; 525 | } 526 | 527 | // Flush Directory to disk 528 | fseek(disk, 0, SEEK_SET); // Seek to start for directory 529 | fwrite(Directory, 4096, 1, disk); // Write 4096 bytes for the Directory 530 | 531 | // printf("Complete: file %s starts at block %lld, directory entry #%d.\n", pEntry->FileName, pEntry->StartingBlock, first_free_entry); 532 | } 533 | else 534 | { 535 | printf("bmfs error: File already exists.\n"); 536 | } 537 | } 538 | 539 | // Read a file from a BMFS volume 540 | void bmfs_read(char *filename) 541 | { 542 | struct BMFSEntry tempentry; 543 | FILE *tfile; 544 | int slot, retval; 545 | unsigned long long bytestoread; 546 | char *buffer; 547 | 548 | if (0 == bmfs_find(filename, &tempentry, &slot)) 549 | { 550 | printf("bmfs error: File not found in BMFS.\n"); 551 | } 552 | else 553 | { 554 | if ((tfile = fopen(tempentry.FileName, "wb")) == NULL) 555 | { 556 | printf("bmfs error: Could not open local file '%s'\n", tempentry.FileName); 557 | } 558 | else 559 | { 560 | bytestoread = tempentry.FileSize; 561 | fseek(disk, tempentry.StartingBlock*blockSize, SEEK_SET); // Skip to the starting block in the disk 562 | buffer = malloc(blockSize); 563 | if (buffer == NULL) 564 | { 565 | printf("bmfs error: Unable to allocate enough memory for buffer.\n"); 566 | } 567 | else 568 | { 569 | while (bytestoread != 0) 570 | { 571 | if (bytestoread >= blockSize) 572 | { 573 | retval = fread(buffer, blockSize, 1, disk); 574 | if (retval == 1) 575 | { 576 | fwrite(buffer, blockSize, 1, tfile); 577 | bytestoread -= blockSize; 578 | } 579 | else 580 | { 581 | printf("bmfs error: Unexpected read length detected.\n"); 582 | bytestoread = 0; 583 | } 584 | } 585 | else 586 | { 587 | retval = fread(buffer, bytestoread, 1, disk); 588 | if (retval == 1) 589 | { 590 | fwrite(buffer, bytestoread, 1, tfile); 591 | bytestoread = 0; 592 | } 593 | else 594 | { 595 | printf("bmfs error: Unexpected read length detected.\n"); 596 | bytestoread = 0; 597 | } 598 | } 599 | } 600 | } 601 | fclose(tfile); 602 | } 603 | } 604 | } 605 | 606 | 607 | // Write a file to a BMFS volume 608 | void bmfs_write(char *filename) 609 | { 610 | struct BMFSEntry tempentry; 611 | FILE *tfile; 612 | int slot, retval; 613 | unsigned long long tempfilesize; 614 | char *buffer; 615 | 616 | if ((tfile = fopen(filename, "rb")) == NULL) 617 | { 618 | printf("bmfs error: Could not open local file '%s'\n", filename); 619 | } 620 | else 621 | { 622 | // Is there enough room in BMFS? 623 | fseek(tfile, 0, SEEK_END); 624 | tempfilesize = ftell(tfile); 625 | rewind(tfile); 626 | if (0 == bmfs_find(filename, &tempentry, &slot)) 627 | { 628 | bmfs_create(filename, (tempfilesize+blockSize)/blockSize*2); 629 | bmfs_find(filename, &tempentry, &slot); 630 | } 631 | if ((tempentry.ReservedBlocks*blockSize) < tempfilesize) 632 | { 633 | printf("bmfs error: Not enough reserved space in BMFS.\n"); 634 | } 635 | else 636 | { 637 | fseek(disk, tempentry.StartingBlock*blockSize, SEEK_SET); // Skip to the starting block in the disk 638 | buffer = malloc(blockSize); 639 | if (buffer == NULL) 640 | { 641 | printf("bmfs error: Unable to allocate enough memory for buffer.\n"); 642 | } 643 | else 644 | { 645 | while (tempfilesize != 0) 646 | { 647 | if (tempfilesize >= blockSize) 648 | { 649 | retval = fread(buffer, blockSize, 1, tfile); 650 | if (retval == 1) 651 | { 652 | fwrite(buffer, blockSize, 1, disk); 653 | tempfilesize -= blockSize; 654 | } 655 | else 656 | { 657 | printf("bmfs error: Unexpected read length detected.\n"); 658 | tempfilesize = 0; 659 | } 660 | } 661 | else 662 | { 663 | retval = fread(buffer, tempfilesize, 1, tfile); 664 | if (retval == 1) 665 | { 666 | memset(buffer+(tempfilesize), 0, (blockSize-tempfilesize)); // 0 the rest of the buffer 667 | fwrite(buffer, blockSize, 1, disk); 668 | tempfilesize = 0; 669 | } 670 | else 671 | { 672 | printf("bmfs error: Unexpected read length detected.\n"); 673 | tempfilesize = 0; 674 | } 675 | } 676 | } 677 | } 678 | // Update directory 679 | tempfilesize = ftell(tfile); 680 | memcpy(Directory+(slot*64)+48, &tempfilesize, 8); 681 | fseek(disk, 0, SEEK_SET); // Seek to directory 682 | fwrite(Directory, 4096, 1, disk); // Write new directory to disk 683 | } 684 | fclose(tfile); 685 | } 686 | } 687 | 688 | 689 | /* EOF */ 690 | --------------------------------------------------------------------------------