aboutsummaryrefslogtreecommitdiff
path: root/arm9/source/devices/file.c
diff options
context:
space:
mode:
Diffstat (limited to 'arm9/source/devices/file.c')
-rw-r--r--arm9/source/devices/file.c422
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.
}
}