519 lines
11 KiB
C
519 lines
11 KiB
C
|
/*
|
||
|
* Copyright (c) 2009 Travis Geiselbrecht
|
||
|
*
|
||
|
* Permission is hereby granted, free of charge, to any person obtaining
|
||
|
* a copy of this software and associated documentation files
|
||
|
* (the "Software"), to deal in the Software without restriction,
|
||
|
* including without limitation the rights to use, copy, modify, merge,
|
||
|
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||
|
* and to permit persons to whom the Software is furnished to do so,
|
||
|
* subject to the following conditions:
|
||
|
*
|
||
|
* The above copyright notice and this permission notice shall be
|
||
|
* included in all copies or substantial portions of the Software.
|
||
|
*
|
||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||
|
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||
|
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||
|
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||
|
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||
|
*/
|
||
|
#include <debug.h>
|
||
|
#include <list.h>
|
||
|
#include <err.h>
|
||
|
#include <string.h>
|
||
|
#include <stdlib.h>
|
||
|
#include <lib/fs.h>
|
||
|
#include <lib/bio.h>
|
||
|
|
||
|
#if WITH_LIB_FS_EXT2
|
||
|
#include <lib/fs/ext2.h>
|
||
|
#endif
|
||
|
#if WITH_LIB_FS_FAT32
|
||
|
#include <lib/fs/fat32.h>
|
||
|
#endif
|
||
|
|
||
|
#define LOCAL_TRACE 0
|
||
|
|
||
|
struct fs_type {
|
||
|
const char *name;
|
||
|
int (*mount)(bdev_t *, fscookie *);
|
||
|
int (*unmount)(fscookie);
|
||
|
int (*open)(fscookie, const char *, filecookie *);
|
||
|
int (*create)(fscookie, const char *, filecookie *);
|
||
|
int (*mkdir)(fscookie, const char *);
|
||
|
int (*stat)(filecookie, struct file_stat *);
|
||
|
int (*read)(filecookie, void *, off_t, size_t);
|
||
|
int (*write)(filecookie, const void *, off_t, size_t);
|
||
|
int (*close)(filecookie);
|
||
|
};
|
||
|
|
||
|
struct fs_mount {
|
||
|
struct list_node node;
|
||
|
char *path;
|
||
|
bdev_t *dev;
|
||
|
fscookie cookie;
|
||
|
int refs;
|
||
|
struct fs_type *type;
|
||
|
};
|
||
|
|
||
|
struct fs_file {
|
||
|
filecookie cookie;
|
||
|
struct fs_mount *mount;
|
||
|
};
|
||
|
|
||
|
static struct list_node mounts;
|
||
|
|
||
|
static struct fs_type types[] = {
|
||
|
#if WITH_LIB_FS_EXT2
|
||
|
{
|
||
|
.name = "ext2",
|
||
|
.mount = ext2_mount,
|
||
|
.unmount = ext2_unmount,
|
||
|
.open = ext2_open_file,
|
||
|
.stat = ext2_stat_file,
|
||
|
.read = ext2_read_file,
|
||
|
.close = ext2_close_file,
|
||
|
},
|
||
|
#endif
|
||
|
#if WITH_LIB_FS_FAT32
|
||
|
{
|
||
|
.name = "fat32",
|
||
|
.mount = fat32_mount,
|
||
|
.unmount = fat32_unmount,
|
||
|
.open = fat32_open_file,
|
||
|
.create = fat32_create_file,
|
||
|
.mkdir = fat32_make_dir,
|
||
|
.stat = fat32_stat_file,
|
||
|
.read = fat32_read_file,
|
||
|
.write = fat32_write_file,
|
||
|
.close = fat32_close_file,
|
||
|
},
|
||
|
#endif
|
||
|
};
|
||
|
|
||
|
static void test_normalize(const char *in);
|
||
|
static struct fs_mount *find_mount(const char *path, const char **trimmed_path);
|
||
|
|
||
|
void fs_init(void)
|
||
|
{
|
||
|
list_initialize(&mounts);
|
||
|
#if 0
|
||
|
test_normalize("/");
|
||
|
test_normalize("/test");
|
||
|
test_normalize("/test/");
|
||
|
test_normalize("test/");
|
||
|
test_normalize("test");
|
||
|
test_normalize("/test//");
|
||
|
test_normalize("/test/foo");
|
||
|
test_normalize("/test/foo/");
|
||
|
test_normalize("/test/foo/bar");
|
||
|
test_normalize("/test/foo/bar//");
|
||
|
test_normalize("/test//foo/bar//");
|
||
|
test_normalize("/test//./foo/bar//");
|
||
|
test_normalize("/test//./.foo/bar//");
|
||
|
test_normalize("/test//./..foo/bar//");
|
||
|
test_normalize("/test//./../foo/bar//");
|
||
|
test_normalize("/test/../foo");
|
||
|
test_normalize("/test/bar/../foo");
|
||
|
test_normalize("../foo");
|
||
|
test_normalize("../foo/");
|
||
|
test_normalize("/../foo");
|
||
|
test_normalize("/../foo/");
|
||
|
test_normalize("/../../foo");
|
||
|
test_normalize("/bleh/../../foo");
|
||
|
test_normalize("/bleh/bar/../../foo");
|
||
|
test_normalize("/bleh/bar/../../foo/..");
|
||
|
test_normalize("/bleh/bar/../../foo/../meh");
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
static struct fs_mount *find_mount(const char *path, const char **trimmed_path)
|
||
|
{
|
||
|
struct fs_mount *mount;
|
||
|
size_t pathlen = strlen(path);
|
||
|
|
||
|
list_for_every_entry(&mounts, mount, struct fs_mount, node) {
|
||
|
size_t mountpathlen = strlen(mount->path);
|
||
|
if (pathlen < mountpathlen)
|
||
|
continue;
|
||
|
|
||
|
LTRACEF("comparing %s with %s\n", path, mount->path);
|
||
|
|
||
|
if (memcmp(path, mount->path, mountpathlen) == 0) {
|
||
|
if (trimmed_path)
|
||
|
*trimmed_path = &path[mountpathlen];
|
||
|
|
||
|
return mount;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
static int mount(const char *path, const char *device, struct fs_type *type)
|
||
|
{
|
||
|
char temppath[512];
|
||
|
|
||
|
strlcpy(temppath, path, sizeof(temppath));
|
||
|
fs_normalize_path(temppath);
|
||
|
|
||
|
if(temppath[0] != '/')
|
||
|
return ERR_BAD_PATH;
|
||
|
|
||
|
if (find_mount(temppath, NULL))
|
||
|
return ERR_ALREADY_MOUNTED;
|
||
|
|
||
|
bdev_t *dev = bio_open(device);
|
||
|
if (!dev)
|
||
|
return ERR_NOT_FOUND;
|
||
|
|
||
|
fscookie cookie;
|
||
|
int err = type->mount(dev, &cookie);
|
||
|
if (err < 0) {
|
||
|
bio_close(dev);
|
||
|
return err;
|
||
|
}
|
||
|
|
||
|
/* create the mount structure and add it to the list */
|
||
|
struct fs_mount *mount = malloc(sizeof(struct fs_mount));
|
||
|
mount->path = strdup(temppath);
|
||
|
mount->dev = dev;
|
||
|
mount->cookie = cookie;
|
||
|
mount->refs = 1;
|
||
|
mount->type = type;
|
||
|
|
||
|
list_add_head(&mounts, &mount->node);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int fs_mount(const char *path, const char *device)
|
||
|
{
|
||
|
return mount(path, device, &types[0]);
|
||
|
}
|
||
|
|
||
|
int fs_mount_type(const char *path, const char *device, const char *name)
|
||
|
{
|
||
|
size_t i;
|
||
|
|
||
|
for (i = 0; i < countof(types); i++) {
|
||
|
if (!strcmp(name, types[i].name))
|
||
|
return mount(path, device, &types[i]);
|
||
|
}
|
||
|
|
||
|
return ERR_NOT_FOUND;
|
||
|
}
|
||
|
|
||
|
static void put_mount(struct fs_mount *mount)
|
||
|
{
|
||
|
if (!(--mount->refs)) {
|
||
|
list_delete(&mount->node);
|
||
|
mount->type->unmount(mount->cookie);
|
||
|
free(mount->path);
|
||
|
bio_close(mount->dev);
|
||
|
free(mount);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
int fs_unmount(const char *path)
|
||
|
{
|
||
|
char temppath[512];
|
||
|
|
||
|
strlcpy(temppath, path, sizeof(temppath));
|
||
|
fs_normalize_path(temppath);
|
||
|
|
||
|
struct fs_mount *mount = find_mount(temppath, NULL);
|
||
|
if (!mount)
|
||
|
return ERR_NOT_FOUND;
|
||
|
|
||
|
put_mount(mount);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
int fs_open_file(const char *path, filecookie *fcookie)
|
||
|
{
|
||
|
int err;
|
||
|
char temppath[512];
|
||
|
filecookie cookie;
|
||
|
|
||
|
strlcpy(temppath, path, sizeof(temppath));
|
||
|
fs_normalize_path(temppath);
|
||
|
|
||
|
LTRACEF("path %s temppath %s\n", path, temppath);
|
||
|
|
||
|
const char *newpath;
|
||
|
struct fs_mount *mount = find_mount(temppath, &newpath);
|
||
|
if (!mount)
|
||
|
return ERR_NOT_FOUND;
|
||
|
|
||
|
LTRACEF("path %s temppath %s newpath %s\n", path, temppath, newpath);
|
||
|
|
||
|
err = mount->type->open(mount->cookie, newpath, &cookie);
|
||
|
if (err < 0)
|
||
|
return err;
|
||
|
|
||
|
struct fs_file *f = malloc(sizeof(*f));
|
||
|
f->cookie = cookie;
|
||
|
f->mount = mount;
|
||
|
mount->refs++;
|
||
|
*fcookie = f;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int fs_create_file(const char *path, filecookie *fcookie)
|
||
|
{
|
||
|
int err;
|
||
|
char temppath[512];
|
||
|
filecookie cookie;
|
||
|
|
||
|
strlcpy(temppath, path, sizeof(temppath));
|
||
|
fs_normalize_path(temppath);
|
||
|
|
||
|
const char *newpath;
|
||
|
struct fs_mount *mount = find_mount(temppath, &newpath);
|
||
|
if (!mount)
|
||
|
return ERR_NOT_FOUND;
|
||
|
|
||
|
if (!mount->type->create)
|
||
|
return ERR_NOT_SUPPORTED;
|
||
|
|
||
|
err = mount->type->create(mount->cookie, newpath, &cookie);
|
||
|
if (err < 0)
|
||
|
return err;
|
||
|
|
||
|
struct fs_file *f = malloc(sizeof(*f));
|
||
|
f->cookie = cookie;
|
||
|
f->mount = mount;
|
||
|
mount->refs++;
|
||
|
*fcookie = f;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int fs_make_dir(const char *path)
|
||
|
{
|
||
|
char temppath[512];
|
||
|
|
||
|
strlcpy(temppath, path, sizeof(temppath));
|
||
|
fs_normalize_path(temppath);
|
||
|
|
||
|
const char *newpath;
|
||
|
struct fs_mount *mount = find_mount(temppath, &newpath);
|
||
|
if (!mount)
|
||
|
return ERR_NOT_FOUND;
|
||
|
|
||
|
if (!mount->type->mkdir)
|
||
|
return ERR_NOT_SUPPORTED;
|
||
|
|
||
|
return mount->type->mkdir(mount->cookie, newpath);
|
||
|
}
|
||
|
|
||
|
int fs_read_file(filecookie fcookie, void *buf, off_t offset, size_t len)
|
||
|
{
|
||
|
struct fs_file *f = fcookie;
|
||
|
|
||
|
return f->mount->type->read(f->cookie, buf, offset, len);
|
||
|
}
|
||
|
|
||
|
int fs_write_file(filecookie fcookie, const void *buf, off_t offset, size_t len)
|
||
|
{
|
||
|
struct fs_file *f = fcookie;
|
||
|
|
||
|
if (!f->mount->type->write)
|
||
|
return ERR_NOT_SUPPORTED;
|
||
|
|
||
|
return f->mount->type->write(f->cookie, buf, offset, len);
|
||
|
}
|
||
|
|
||
|
int fs_close_file(filecookie fcookie)
|
||
|
{
|
||
|
int err;
|
||
|
struct fs_file *f = fcookie;
|
||
|
|
||
|
err = f->mount->type->close(f->cookie);
|
||
|
if (err < 0)
|
||
|
return err;
|
||
|
|
||
|
put_mount(f->mount);
|
||
|
free(f);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int fs_stat_file(filecookie fcookie, struct file_stat *stat)
|
||
|
{
|
||
|
struct fs_file *f = fcookie;
|
||
|
|
||
|
return f->mount->type->stat(f->cookie, stat);
|
||
|
}
|
||
|
|
||
|
ssize_t fs_load_file(const char *path, void *ptr, size_t maxlen)
|
||
|
{
|
||
|
int err;
|
||
|
filecookie cookie;
|
||
|
|
||
|
/* open the file */
|
||
|
err = fs_open_file(path, &cookie);
|
||
|
if (err < 0)
|
||
|
return err;
|
||
|
|
||
|
/* stat it for size, see how much we need to read */
|
||
|
struct file_stat stat;
|
||
|
fs_stat_file(cookie, &stat);
|
||
|
|
||
|
err = fs_read_file(cookie, ptr, 0, MIN(maxlen, stat.size));
|
||
|
|
||
|
fs_close_file(cookie);
|
||
|
|
||
|
return err;
|
||
|
}
|
||
|
|
||
|
static void test_normalize(const char *in)
|
||
|
{
|
||
|
char path[1024];
|
||
|
|
||
|
strlcpy(path, in, sizeof(path));
|
||
|
fs_normalize_path(path);
|
||
|
printf("'%s' -> '%s'\n", in, path);
|
||
|
}
|
||
|
|
||
|
void fs_normalize_path(char *path)
|
||
|
{
|
||
|
int outpos;
|
||
|
int pos;
|
||
|
char c;
|
||
|
bool done;
|
||
|
enum {
|
||
|
INITIAL,
|
||
|
FIELD_START,
|
||
|
IN_FIELD,
|
||
|
SEP,
|
||
|
SEEN_SEP,
|
||
|
DOT,
|
||
|
SEEN_DOT,
|
||
|
DOTDOT,
|
||
|
SEEN_DOTDOT,
|
||
|
} state;
|
||
|
|
||
|
state = INITIAL;
|
||
|
pos = 0;
|
||
|
outpos = 0;
|
||
|
done = false;
|
||
|
|
||
|
/* remove duplicate path seperators, flatten empty fields (only composed of .), backtrack fields with .., remove trailing slashes */
|
||
|
while (!done) {
|
||
|
c = path[pos];
|
||
|
switch (state) {
|
||
|
case INITIAL:
|
||
|
if (c == '/') {
|
||
|
state = SEP;
|
||
|
} else if (c == '.') {
|
||
|
state = DOT;
|
||
|
} else {
|
||
|
state = FIELD_START;
|
||
|
}
|
||
|
break;
|
||
|
case FIELD_START:
|
||
|
if (c == '.') {
|
||
|
state = DOT;
|
||
|
} else if (c == 0) {
|
||
|
done = true;
|
||
|
} else {
|
||
|
state = IN_FIELD;
|
||
|
}
|
||
|
break;
|
||
|
case IN_FIELD:
|
||
|
if (c == '/') {
|
||
|
state = SEP;
|
||
|
} else if (c == 0) {
|
||
|
done = true;
|
||
|
} else {
|
||
|
path[outpos++] = c;
|
||
|
pos++;
|
||
|
}
|
||
|
break;
|
||
|
case SEP:
|
||
|
pos++;
|
||
|
path[outpos++] = '/';
|
||
|
state = SEEN_SEP;
|
||
|
break;
|
||
|
case SEEN_SEP:
|
||
|
if (c == '/') {
|
||
|
// eat it
|
||
|
pos++;
|
||
|
} else if (c == 0) {
|
||
|
done = true;
|
||
|
} else {
|
||
|
state = FIELD_START;
|
||
|
}
|
||
|
break;
|
||
|
case DOT:
|
||
|
pos++; // consume the dot
|
||
|
state = SEEN_DOT;
|
||
|
break;
|
||
|
case SEEN_DOT:
|
||
|
if (c == '.') {
|
||
|
// dotdot now
|
||
|
state = DOTDOT;
|
||
|
} else if (c == '/') {
|
||
|
// a field composed entirely of a .
|
||
|
// consume the / and move directly to the SEEN_SEP state
|
||
|
pos++;
|
||
|
state = SEEN_SEP;
|
||
|
} else if (c == 0) {
|
||
|
done = true;
|
||
|
} else {
|
||
|
// a field prefixed with a .
|
||
|
// emit a . and move directly into the IN_FIELD state
|
||
|
path[outpos++] = '.';
|
||
|
state = IN_FIELD;
|
||
|
}
|
||
|
break;
|
||
|
case DOTDOT:
|
||
|
pos++; // consume the dot
|
||
|
state = SEEN_DOTDOT;
|
||
|
break;
|
||
|
case SEEN_DOTDOT:
|
||
|
if (c == '/' || c == 0) {
|
||
|
// a field composed entirely of '..'
|
||
|
// search back and consume a field we've already emitted
|
||
|
if (outpos > 0) {
|
||
|
// we have already consumed at least one field
|
||
|
outpos--;
|
||
|
|
||
|
// walk backwards until we find the next field boundary
|
||
|
while (outpos > 0) {
|
||
|
if (path[outpos - 1] == '/') {
|
||
|
break;
|
||
|
}
|
||
|
outpos--;
|
||
|
}
|
||
|
}
|
||
|
pos++;
|
||
|
state = SEEN_SEP;
|
||
|
if (c == 0)
|
||
|
done = true;
|
||
|
} else {
|
||
|
// a field prefixed with ..
|
||
|
// emit the .. and move directly to the IN_FIELD state
|
||
|
path[outpos++] = '.';
|
||
|
path[outpos++] = '.';
|
||
|
state = IN_FIELD;
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* dont end with trailing slashes */
|
||
|
if (outpos > 0 && path[outpos - 1] == '/')
|
||
|
outpos--;
|
||
|
|
||
|
path[outpos++] = 0;
|
||
|
}
|
||
|
|