diff options
Diffstat (limited to 'arm9/source/devices/file.c')
-rw-r--r-- | arm9/source/devices/file.c | 422 |
1 files changed, 233 insertions, 189 deletions
diff --git a/arm9/source/devices/file.c b/arm9/source/devices/file.c index dcc987d..546ca0c 100644 --- a/arm9/source/devices/file.c +++ b/arm9/source/devices/file.c @@ -1,279 +1,323 @@ -#include "nds.h" -#include "fat.h" #include "file.h" -#include <sys/stat.h> + +/* +TODO: I believe that multiple open files are supported, but I'm not so sure +about multiple open directories. I'm not sure what the limit is for maximum +open files or directories, or whether it's possible to accidentally exceed +this limit with multiple Bedrock instances. + +TODO: I need to thoroughly test every function of the device on hardware. +There's a lot that I'm not sure about. +*/ + + +// True if the filesystem is available. static bool FS_ENABLE = false; -static u8 buffer[255]; + +// Scratch buffer for manipulating paths when ascending and descending. +static u8 scratch[256]; -void init_filesystem() { +// Attempt to initialise the filesystem. +void init_nds_filesystem(void) { FS_ENABLE = fatInitDefault(); } -bool filesystem_enabled() { +// Return whether the filesystem is available. +bool nds_filesystem_enabled() { return FS_ENABLE; } -bool is_valid_path(u8 *path) { - // Prevent non-absolute paths from being opened. - if (path[0] != '/') { - return false; - } - // Remove trailing slash if any, without removing leading slash. - for (int i=2; i<255; i++) { - if (path[i] == 0) { - if (path[i-1] == '/') { - path[i-1] = 0; - } - break; - } - } - // Prevent paths with relative components from being opened. - for (int i=0; i<255; i++) { - if (path[i] == '/') { - if (path[i+1]=='.') { - // Check for '.' components. - if (path[i+2]=='/' || path[i+2]==0) { - return false; - } - // Check for '..' components. - if (path[i+2]=='.' && (path[i+3]=='/' || path[i+3]==0)) { - return false; - } - } - } else if (path[i] == 0) { - break; - } +// Rewind the seek position of the current directory. +void fs_rewind_dir(FileDevice *fs) { + if (fs->dir) { + rewinddir(fs->dir); + fs->dir_i = 0; } - return true; } +// Reset the current entry to its original state. +void fs_reset_entry(FileDevice *fs) { + if (fs->file) fseek(fs->file, 0, SEEK_SET); + fs_rewind_dir(fs); + fs->child = NULL; + pathbuf_clear(&fs->child_path); + fs->address = 0; + fs->length = 0; + fs->length_cached = false; +} + +// Close the current entry, if any. Don't clear the open and action buffers, +// they'll be needed for opening a file or performing an action after this +// method returns. They'll need to be cleared manually. void fs_close(FileDevice *fs) { - fclose(fs->file); - fs->file = NULL; - closedir(fs->dir); - fs->dir = NULL; - fs->open = false; - fs->success = false; - fs->is_dir = false; - fs->child_is_dir = false; - pb_clear(&fs->path); - pb_clear(&fs->child_path); - fs->pointer = 0; + if (fs->file) { + fclose(fs->file); + fs->file = NULL; + } + if (fs->dir) { + closedir(fs->dir); + fs->dir = NULL; + fs->dir_i = 0; + fs->is_root = false; + fs->child = NULL; + } + // Clear variables and buffers. + pathbuf_clear(&fs->path); + pathbuf_clear(&fs->child_path); + fs->address = 0; fs->length = 0; - fs->child = NULL; - fs->dir_i = 0; - fs->at_root = false; + fs->length_cached = false; } +// Clear the open and action buffers. void fs_clear(FileDevice *fs) { - pb_clear(&fs->entry); - pb_clear(&fs->action); + pathbuf_clear(&fs->open); + pathbuf_clear(&fs->action); } -void fs_open_entry(FileDevice* fs, u8 *path) { - fs->success = false; - if (!is_valid_path(path)) return; +// Reset a file device. +void fs_reset(FileDevice *fs) { + fs_close(fs); + fs_clear(fs); + fs->address_write = 0; + fs->length_write = 0; + fs->error = false; +} + +// Calculate the length of the current entry. +void fs_calculate_length(FileDevice *fs) { + if (fs->length_cached) return; + if (fs->dir) { + // Find directory length by reading directory entries until null is + // returned. If the current directory is not the root directory, + // decrease the length by two to ignore the . and .. dirs. + fs_rewind_dir(fs); + for (fs->length=0; readdir(fs->dir); fs->length++); + if (!fs->is_root) fs->length -= 2; + fs_rewind_dir(fs); + } else if (fs->file) { + // Find file length by seeking to the end of the file. + fseek(fs->file, 0, SEEK_END); + fs->length = ftell(fs->file); + // Restore previous seek position. + fseek(fs->file, fs->address, SEEK_SET); + } + fs->length_cached = true; +} + +// Returns true if an entry is open. +bool fs_get_open(FileDevice *fs) { + return fs->file || fs->dir; +} + +// Returns true if the error flag is set, then clears the error flag. +bool fs_get_error(FileDevice *fs) { + if (fs->error) { + fs->error = false; + return true; + } else { + return false; + } +} + +// Returns true if a directory is open. +bool fs_get_type(FileDevice *fs) { + return fs->dir; +} + +// Returns true if a child is selected and that child is a directory. +bool fs_get_child_type(FileDevice *fs) { + return fs->child && fs->child->d_type == DT_DIR; +} +// Attempt to open the given path as a filesystem entry, keeping the original +// entry open on failure. Path must be valid. +void fs_open_entry(FileDevice* fs, u8 *path) { + // Attempt to open the path as a directory. DIR *tmpdir = opendir((char*) path); if (tmpdir) { fs_close(fs); fs->dir = tmpdir; - fs->open = true; - fs->success = true; - fs->is_dir = true; fs->dir_i = 0; - // Find length of directory - while ((fs->child = readdir(fs->dir))) { - fs->dir_i++; - }; - // If this is not the root directory, reduce len by 2 for . and .. dirs. - if (path[1] == 0) { fs->at_root = true; } else { fs->dir_i -= 2; } - fs->length = fs->dir_i; - fs_select_child(fs, 0); - pb_populate(&fs->path, path); + pathbuf_populate(&fs->path, path); + if (pathbuf_is_root(&fs->path)) fs->is_root = true; return; } - - // MEMORY LEAK HERE ONLY - // for (int i=0; i<500; i++) { - // fs->tmpfile = fopen((char*) path, "r+"); - // fclose(fs->tmpfile); - // } - // END OF MEMORY LEAK CODE - - // FILE CODE MUST COME AFTER DIR CODE TO AVOID MEMORY LEAK + // Attempt to open the path as a file. Moving this to before the + // directory check will cause a memory leak. Specifically, the memory + // leak is caused when opening and closing a file many thousands of times. FILE *tmpfile = fopen((char*) path, "r+"); if (tmpfile) { fs_close(fs); fs->file = tmpfile; - fs->open = true; - fs->success = true; - fs->is_dir = false; - // TODO: Only read length when we need to. Keep a bool around. - // fseek(fs->file, 0, SEEK_END); - // fs->length = ftell(fs->file); - // rewind(fs->file); - pb_populate(&fs->path, path); + pathbuf_populate(&fs->path, path); return; } + // Could not open entry. + fs->error = true; } -void fs_select_child(FileDevice *fs, u32 pointer) { - // Increment past the . and .. directories. - if (!fs->at_root) { - pointer += 2; - } - fs->pointer = pointer; - rewinddir(fs->dir); - - for (fs->dir_i=0; fs->dir_i<pointer+1; fs->dir_i++) { +// Select the nth child of the current directory. Directory must be open. +void fs_select_child(FileDevice *fs, u32 n) { + fs->address = n; + // Ignore the . and .. entries. + if (!fs->is_root) n += 2; + // Iterate to the nth child. + for (fs_rewind_dir(fs); fs->dir_i<=n; fs->dir_i++) { fs->child = readdir(fs->dir); }; if (fs->child) { - pb_populate(&fs->child_path, (u8*) &fs->child->d_name); - fs->child_is_dir = fs->child->d_type == DT_DIR; + pathbuf_populate(&fs->child_path, (u8*) &fs->child->d_name); } else { - pb_clear(&fs->child_path); - fs->child_is_dir = false; + pathbuf_clear(&fs->child_path); + fs->error = true; } } -void fs_push_entry(FileDevice *fs, u8 byte) { - if (pb_push_byte(&fs->entry, byte)) { +// Push a byte to the open port. +void fs_push_open(FileDevice *fs, u8 byte) { + if (pathbuf_push(&fs->open, byte)) { fs_close(fs); - if (fs->entry.mem[0] != 0) { - fs->success = false; - if (pb_is_terminated(&fs->entry)) { - fs_open_entry(fs, fs->entry.mem); + if (!pathbuf_is_empty(&fs->open)) { + if (pathbuf_is_valid(&fs->open)) { + fs_open_entry(fs, (u8*) &fs->open.mem); + } else { + fs->error = true; } } fs_clear(fs); } } +// Push a byte to the action port. void fs_push_action(FileDevice *fs, u8 byte) { - if (pb_push_byte(&fs->action, byte)) { - fs->success = false; - bool action = fs->action.mem[0] != 0; - if (fs->open) { - if (action) { - if (!pb_is_terminated(&fs->action)) return; - if (!is_valid_path(fs->action.mem)) return; - fs->success = rename((char*) fs->path.mem, (char*) fs->action.mem); + if (pathbuf_push(&fs->action, byte)) { + bool is_entry = !pathbuf_is_empty(&fs->path); + bool is_action = !pathbuf_is_empty(&fs->action); + if (is_entry) { + if (is_action) { + if (!pathbuf_is_valid(&fs->action)) return; + // Move open entry to action path. + if (rename((char*) &fs->path.mem, (char*) &fs->action.mem)) { + fs->error = true; + }; } else { - // TODO: DELETE + // TODO: Delete the open entry. The remove() function will + // delete a file or an empty directory, so we will need to + // create a function that can recursively delete all entries + // within a directory. + } + } else { + if (is_action) { + if (!pathbuf_is_valid(&fs->action)) return; + // Create all parent directories. + for (u8 *p=&fs->action.mem[1]; *p; p++) { + // Temporarily null out each path separator to get the + // path of each parent directory. + if (*p == '/') { + *p = 0; mkdir((char*) &fs->action.mem, 0777); *p = '/'; + } + } + // Create a new file at action path. + FILE *tmpfile = fopen((char*) &fs->action.mem, "w"); + if (!tmpfile) fs->error = true; + fclose(tmpfile); } - } else if (action) { - if (!pb_is_terminated(&fs->action)) return; - if (!is_valid_path(fs->action.mem)) return; - create_parents(fs->action.mem); - FILE *tmpfile = fopen((char*) fs->action.mem, "w"); - fs->success = tmpfile != NULL; - fclose(tmpfile); } fs_close(fs); fs_clear(fs); } } -// Recursively create parent directories. -void create_parents(u8 *path) { - // Iterate over characters left-to-right. - for (u8 *p=path+1; *p; p++) { - if (*p == '/') { - *p = 0; - mkdir((char*) path, 0777); - *p = '/'; - } - } - return; -} - - +// Read a byte from the file head. u8 fs_read_byte(FileDevice *fs) { + // TODO: Check that this returns 0 if fs->file is NULL. Look into + // setting fs->error on error. + fs->address++; return fgetc(fs->file); } +// Write a byte to the file head. void fs_write_byte(FileDevice *fs, u8 byte) { + // TODO: Look into setting fs->error on error. + fs->address++; fputc(byte, fs->file); } +// Attempt to ascend to the parent directory. void fs_ascend(FileDevice *fs) { - fs->success = false; - if (fs->open) { + if (!pathbuf_is_empty(&fs->path)) { // Don't ascend if we're already at the root. - if (fs->path.mem[1] == 0) { + if (fs->is_root) { + fs_reset_entry(fs); + fs->error = true; return; } - // Path is guaranteed to have no trailing slash. - memcpy(&buffer, &fs->path.mem[0], 255); - u8 slash_i = 0; - for (int i=0; i<255; i++) { - if (buffer[i] == '/') { - slash_i = i; - } else if (buffer[i] == 0) { - // Trim to leave the slash - buffer[slash_i+1] = 0; - break; - } + // Find and null the final slash in the path to get the path of the + // parent directory. Path is guaranteed not to have a trailing slash. + // If the final slash is at index 0, we need to null past the slash + // instead, getting the root directory. + memcpy(&scratch, &fs->path.mem, 256); + u8 slash_i = 1; + for (int i=1; i<256; i++) { + if (scratch[i] == '/') slash_i = i; + if (scratch[i] == 0 ) break; } - fs_open_entry(fs, &buffer[0]); + scratch[slash_i] = 0; + fs_open_entry(fs, (u8*) &scratch); + fs_reset_entry(fs); } else { + // Open the default directory if no entry is open. fs_open_entry(fs, (u8*) "/"); + fs_reset_entry(fs); } } +// Attempt to descend to the selected child entry. void fs_descend(FileDevice *fs) { - fs->success = false; - if (!(fs->open && fs->is_dir && fs->child)) { - return; - } - memcpy(&buffer, &fs->path.mem[0], 255); - // Find location of terminating null in buffer. - int start = 0; - for (int i=0; i<255; i++) { - start = i; - if (buffer[i] == 0) break; - } - // Find length of child name. - int len = 0; - for (int i=0; i<255; i++) { - len = i; - if (fs->child->d_name[i] == 0) break; - } - // Check that buffer has capacity for child name. - if (start+len > 255) { - fs->success = false; - return; - } - // Add a slash if not at root. - if (start != 1) { - buffer[start] = '/'; - start++; - } - // Copy child name into buffer. - for (int i=0; i<len; i++) { - buffer[start+i] = fs->child->d_name[i]; + if (fs->child) { + memcpy(&scratch, &fs->path.mem, 256); + // Find length of current path. + int path_len = 0; + while (scratch[path_len]) path_len++; + // Find length of child name. + int name_len = 0; + while (fs->child->d_name[name_len]) name_len++; + // Test that buffer has capacity for child name. + if (path_len+1+name_len > 255) { + fs_reset_entry(fs); + fs->error = true; + return; + } + // Append a slash to the path if not at root. + if (path_len != 1) scratch[path_len++] = '/'; + // Copy child name into buffer. + for (int i=0; i<name_len; i++) { + scratch[path_len+i] = fs->child->d_name[i]; + } + fs_open_entry(fs, (u8*) &scratch); + fs_reset_entry(fs); + } else { + fs->error = true; } - fs_open_entry(fs, &buffer[0]); } +// Seek within the current entry. void fs_seek(FileDevice *fs) { - if (fs->open) { - if (fs->is_dir) { - fs_select_child(fs, fs->pointer_write); - } else { - fseek(fs->file, fs->pointer_write, SEEK_SET); - fs->pointer = ftell(fs->file); - } + if (fs->dir) { + fs_select_child(fs, fs->address_write); + } else if (fs->file) { + fseek(fs->file, fs->address_write, SEEK_SET); + fs->address = ftell(fs->file); } } +// Resize the current entry. void fs_resize(FileDevice *fs) { - if (fs->open && !fs->is_dir) { + if (fs->file) { // truncate(fs->file); - // TODO + // TODO: Figure out how to resize a file. } } |