Files
lk/lib/fs/fat/file.cpp
2022-09-06 23:19:30 -07:00

384 lines
11 KiB
C++

/*
* Copyright (c) 2015 Steve White
* Copyright (c) 2022 Travis Geiselbrecht
*
* Use of this source code is governed by a MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT
*/
#include "file.h"
#include <lk/err.h>
#include <lib/bio.h>
#include <lib/fs.h>
#include <lk/trace.h>
#include <stdlib.h>
#include <string.h>
#include <lk/debug.h>
#include "fat_fs.h"
#include "fat_priv.h"
#include "dir.h"
#include "file_iterator.h"
#define LOCAL_TRACE FAT_GLOBAL_TRACE(1)
fat_file::fat_file(fat_fs *f) : fs_(f) {}
fat_file::~fat_file() = default;
void fat_file::inc_ref() {
ref_++;
LTRACEF_LEVEL(2, "file %p (%u:%u): ref now %i\n", this, dir_loc_.starting_dir_cluster, dir_loc_.dir_offset, ref_);
if (ref_ == 1) {
DEBUG_ASSERT(!list_in_list(&node_));
fs_->add_to_file_list(this);
}
DEBUG_ASSERT(list_in_list(&node_));
}
bool fat_file::dec_ref() {
ref_--;
LTRACEF_LEVEL(2, "file %p (%u:%u): ref now %i\n", this, dir_loc_.starting_dir_cluster, dir_loc_.dir_offset, ref_);
if (ref_ == 0) {
DEBUG_ASSERT(list_in_list(&node_));
list_delete(&node_);
return true;
}
return false;
}
status_t fat_file::open_file_priv(const dir_entry &entry, const dir_entry_location &loc) {
DEBUG_ASSERT(fs_->lock.is_held());
LTRACEF("found file at location %u:%u\n", loc.starting_dir_cluster, loc.dir_offset);
// move this out to the wrapper function so we can properly deal with dirs
//
// did we get a file?
if (entry.attributes != fat_attribute::directory) {
// XXX better attribute testing
start_cluster_ = entry.start_cluster;
length_ = entry.length;
attributes_ = entry.attributes;
dir_loc_ = loc;
inc_ref();
return NO_ERROR;
} else if (entry.attributes == fat_attribute::directory) {
// we can open directories, but just not do anything with it except stat
start_cluster_ = entry.start_cluster;
length_ = 0;
attributes_ = entry.attributes;
dir_loc_ = loc;
inc_ref();
return NO_ERROR;
} else {
return ERR_NOT_VALID;
}
}
// static
status_t fat_file::open_file(fscookie *cookie, const char *path, filecookie **fcookie) {
fat_fs *fs = (fat_fs *)cookie;
LTRACEF("fscookie %p path '%s' fcookie %p\n", cookie, path, fcookie);
AutoLock guard(fs->lock);
// look for the file in the fs
dir_entry entry;
dir_entry_location loc;
status_t err = fat_dir_walk(fs, path, &entry, &loc);
if (err != NO_ERROR) {
return err;
}
// we found it, see if there's an existing file object
fat_file *file = fs->lookup_file(loc);
if (!file) {
// didn't find an existing one, create a new object
file = new fat_file(fs);
}
DEBUG_ASSERT(file);
// perform file object private open
err = file->open_file_priv(entry, loc);
if (err < 0) {
delete file;
return err;
}
*fcookie = (filecookie *)file;
return err;
}
ssize_t fat_file::read_file_priv(void *_buf, const off_t offset, size_t len) {
uint8_t *buf = (uint8_t *)_buf;
LTRACEF("file %p buf %p offset %lld len %zu\n", this, _buf, offset, len);
if (is_dir()) {
return ERR_NOT_FILE;
}
// negative offsets are invalid
if (offset < 0) {
return ERR_INVALID_ARGS;
}
AutoLock guard(fs_->lock);
// trim the read to the file
if (offset >= length_) {
return 0;
}
// above test should ensure offset < 32bit because file->length is 32bit unsigned
DEBUG_ASSERT(offset <= UINT32_MAX);
if (offset + len > length_) {
len = length_ - offset;
}
LTRACEF("trimmed offset %lld len %zu\n", offset, len);
// create a file block iterator and push it forward to the starting point
uint32_t logical_cluster = offset / fs_->info().bytes_per_cluster;
uint32_t sector_within_cluster = (offset % fs_->info().bytes_per_cluster) / fs_->info().bytes_per_sector;
uint32_t offset_within_sector = offset % fs_->info().bytes_per_sector;
LTRACEF("starting off logical cluster %u, sector within %u, offset within %u\n",
logical_cluster, sector_within_cluster, offset_within_sector);
file_block_iterator fbi(fs_, start_cluster_);
// move it forward to our index point
// also loads the buffer
status_t err = fbi.next_sectors(logical_cluster * fs_->info().sectors_per_cluster + sector_within_cluster);
if (err < 0) {
LTRACEF("error moving up to starting point!\n");
return err;
}
ssize_t amount_read = 0;
size_t buf_offset = 0; // offset into the output buffer
while (buf_offset < len) {
size_t to_read = MIN(fs_->info().bytes_per_sector - offset_within_sector, len - buf_offset);
LTRACEF("top of loop: sector offset %u, buf offset %zu, remaining len %zu, to_read %zu\n",
offset_within_sector, buf_offset, len, to_read);
// grab a pointer directly to the block cache
const uint8_t *ptr;
ptr = fbi.get_bcache_ptr(offset_within_sector);
if (!ptr) {
LTRACEF("error getting pointer to file in cache\n");
return ERR_IO;
}
// make sure we dont do something silly
DEBUG_ASSERT(buf_offset + to_read <= len);
DEBUG_ASSERT(offset_within_sector + to_read <= fs_->info().bytes_per_sector);
// copy out to the buffer
memcpy(buf + buf_offset, ptr, to_read);
// next iteration of the loop
amount_read += to_read;
buf_offset += to_read;
// go to the next sector
offset_within_sector += to_read;
DEBUG_ASSERT(offset_within_sector <= fs_->info().bytes_per_sector);
if (offset_within_sector == fs_->info().bytes_per_sector) {
offset_within_sector = 0;
// push the iterator forward to next sector
err = fbi.next_sector();
if (err < 0) {
return err;
}
}
}
return amount_read;
}
// static
ssize_t fat_file::read_file(filecookie *fcookie, void *_buf, const off_t offset, size_t len) {
fat_file *file = (fat_file *)fcookie;
return file->read_file_priv(_buf, offset, len);
}
status_t fat_file::stat_file_priv(struct file_stat *stat) {
AutoLock guard(fs_->lock);
LTRACEF("file %p state %p\n", this, stat);
stat->size = length_;
stat->is_dir = is_dir();
return NO_ERROR;
}
// static
status_t fat_file::stat_file(filecookie *fcookie, struct file_stat *stat) {
fat_file *file = (fat_file *)fcookie;
return file->stat_file_priv(stat);
}
status_t fat_file::close_file_priv(bool *last_ref) {
AutoLock guard(fs_->lock);
// drop a ref to it, which may remove from the global list
// and return whether or not it was the last ref
*last_ref = dec_ref();
return NO_ERROR;
}
// static
status_t fat_file::close_file(filecookie *fcookie) {
fat_file *file = (fat_file *)fcookie;
LTRACEF("file %p\n", file);
bool last_ref;
status_t err = file->close_file_priv(&last_ref);
if (err < 0) {
return err;
}
// if this was the last ref, delete the file
if (last_ref) {
LTRACEF("last ref, deleting %p (%u:%u)\n", file, file->dir_loc().starting_dir_cluster, file->dir_loc().dir_offset);
delete file;
}
return NO_ERROR;
}
// static
status_t fat_file::create_file(fscookie *cookie, const char *path, filecookie **fcookie, uint64_t len) {
fat_fs *fs = (fat_fs *)cookie;
LTRACEF("fs %p path '%s' len %" PRIu64 "\n", fs, path, len);
// currently only support zero length files
if (len != 0) {
return ERR_NOT_IMPLEMENTED;
}
{
AutoLock guard(fs->lock);
// tell the dir code to find us a spot
dir_entry_location loc;
status_t err = fat_dir_allocate(fs, path, fat_attribute::file, 0, 0, &loc);
if (err < 0) {
return err;
}
// we have found and allocated a spot
fat_file *file = new fat_file(fs);
file->dir_loc_ = loc;
file->inc_ref();
*fcookie = (filecookie *)file;
}
return NO_ERROR;
}
status_t fat_file::truncate_file_priv(uint64_t _len) {
LTRACEF("file %p, len %" PRIu64" \n", this, _len);
if (_len == length_) {
return NO_ERROR;
}
// test some boundary conditions
if (_len >= 2UL*1024*1024*1024) {
// 2GB limit on the fs
return ERR_TOO_BIG;
}
AutoLock guard(fs_->lock);
// from now on out use this as our length variable
const uint32_t len32 = _len;
// TODO: test for max size of fs
if (len32 == length_) {
return NO_ERROR;
} else {
// are we expanding/shrinking within a cluster that's already allocated?
const uint32_t bpc = fs_->info().bytes_per_cluster;
const uint32_t current_cluster_count = (length_ + bpc - 1) / bpc;
const uint32_t new_cluster_count = (len32 + bpc - 1) / bpc;
LTRACEF("existing len %u, clusters %u: newlen %u, clusters %u\n", length_, current_cluster_count, len32, new_cluster_count);
if (new_cluster_count == current_cluster_count) {
// new length doesn't change the cluster count
// update the dir entry and move on
status_t err = fat_dir_update_entry(fs_, dir_loc_, start_cluster_, len32);
if (err != NO_ERROR) {
return err;
}
// remember our new length
length_ = len32;
} else if (len32 > length_) {
// expanding the file
LTRACEF("expanding the file: start_cluster_ %u\n", start_cluster_);
// TODO: compartmentalize this cluster extension/shrinking so DIR code can reuse it
// TODO: write zeros to any partial blocks we're extending
// walk to the end of the existing cluster chain
const uint32_t existing_chain_end = fat_find_last_cluster_in_chain(fs_, start_cluster_);
uint32_t first_cluster;
uint32_t last_cluster;
status_t err = fat_allocate_cluster_chain(fs_, existing_chain_end, new_cluster_count - current_cluster_count,
&first_cluster, &last_cluster, true);
LTRACEF("fat_allocate_cluster_chain returns %d, first_cluster %u, last_cluster %u\n", err, first_cluster, last_cluster);
if (err != NO_ERROR) {
return err;
}
// update the dir entry, linking the first cluster in our chain if it's the first one
err = fat_dir_update_entry(fs_, dir_loc_, start_cluster_ ? start_cluster_ : first_cluster, len32);
if (err != NO_ERROR) {
return err;
}
// remember our new length
length_ = len32;
// if we just created the first cluster, remember it here
if (start_cluster_ == 0) {
start_cluster_ = first_cluster;
}
} else {
// shrinking the file
PANIC_UNIMPLEMENTED;
return ERR_NOT_IMPLEMENTED;
}
}
bcache_flush(fs_->bcache());
return NO_ERROR;
}
// static
status_t fat_file::truncate_file(filecookie *fcookie, uint64_t len) {
fat_file *file = (fat_file *)fcookie;
return file->truncate_file_priv(len);
}