iBoot/lib/fs/hfs/hfs_fs.c

386 lines
7.7 KiB
C

/* Copyright 2007-2011 Apple Inc. All rights Reserved */
/* Copyright 2006 Apple Computer Inc, All Rights Reserved */
/*
* Interface shim between the iBoot fs/bdev layers and the HFS code.
*/
#include <debug.h>
#include <stdlib.h>
#include <string.h>
#include <lib/libc.h>
#include <lib/fs.h>
#include <lib/blockdev.h>
#ifndef HFS_UNITTEST
#include <sys/task.h>
#endif
#include "hfs.h"
#include "hfs_format.h"
#if 0
# define HFSdebug(fmt, args...) printf("%s: " fmt "\n", __FUNCTION__ , ##args)
#else
# define HFSdebug(fmt, args...) do { } while (0)
#endif
/*******************************************************************************
* fs interface
*/
static int hfs_mount(void **ctxt, const char *devname);
static void hfs_unmount(void *ctxt);
static int hfs_fsstat(void *ctxt, struct statfs *stat);
static void *hfs_open(void *ctxt, const char *path, uint8_t flags);
static int hfs_read(void *filectxt, void *buf, size_t len);
static int hfs_fstat(void *filectxt, struct stat *st);
static off_t hfs_seek(void *filectxt, off_t offset, int whence);
static void hfs_close(void *filectxt);
static void *hfs_opendir(void *ctxt, const char *path);
static int hfs_readdir(void *dirctxt, struct dirent *ent);
static void hfs_rewinddir(void *dirctxt);
static void hfs_closedir(void *dirctxt);
struct fs_ops hfs_fs_ops = {
hfs_mount,
hfs_unmount,
hfs_fsstat,
hfs_open,
hfs_read,
hfs_fstat,
hfs_seek,
hfs_close,
hfs_opendir,
hfs_readdir,
hfs_rewinddir,
hfs_closedir
};
struct hfs_context {
struct blockdev *dev;
struct statfs s;
uint32_t blocksize;
};
struct hfs_openfile {
struct hfs_context *fs;
char *path;
off_t pos;
struct stat s;
};
struct hfs_opendir {
struct hfs_context *fs;
char *path;
off_t pos;
};
/*
* Locking. Since the HFS code is not re-entrant, we must ensure
* that only one task calls into it at a time.
*/
#ifndef HFS_UNITTEST
static struct task *hfs_owner_task;
#define LOCK() \
do { \
while (hfs_owner_task != NULL) \
task_yield(); \
hfs_owner_task = current_task; \
} while(0);
#define UNLOCK() \
do { \
hfs_owner_task = NULL; \
} while(0);
#else
#define LOCK() do {} while(0)
#define UNLOCK() do {} while(0)
#endif
static int
hfs_mount(void **ctxt, const char *devname)
{
struct hfs_context *cp;
struct HFSPlusVolumeHeader *vh;
int result;
HFSdebug("mounting '%s'", devname);
result = -1;
LOCK();
cp = malloc(sizeof(struct hfs_context));
// find the device
if ((cp->dev = lookup_blockdev(devname)) == NULL)
goto out;
// make sure it's something we recognise
if (HFSInitPartition(cp))
goto out;
// HFSDetect();
// save FS stat info while we're at it
vh = HFSGetVolumeHeader();
cp->s.f_bsize = vh->blockSize;
cp->s.f_blocks = vh->totalBlocks;
cp->s.f_bfree = vh->freeBlocks;
result = 0;
out:
if (result) {
free(cp);
HFSdebug("mount failed");
} else {
HFSdebug("mounted fs %p", cp);
*ctxt = cp;
}
UNLOCK();
return(result);
}
static void
hfs_unmount(void *ctxt)
{
free(ctxt);
}
static int
hfs_fsstat(void *ctxt, struct statfs *stat)
{
struct hfs_context *cp = (struct hfs_context *)ctxt;
*stat = cp->s;
return(0);
}
static void *
hfs_open(void *ctxt, const char *path, uint8_t open_flags)
{
struct hfs_context *cp = (struct hfs_context *)ctxt;
struct hfs_openfile *of;
uint32_t flags;
uint32_t path_length;
time_t time;
off_t size;
HFSdebug("%s: openfile", path);
if (path[0] == '/')
path++;
of = NULL;
LOCK();
// check for file existence
if (HFSGetFileInfo(cp, path, &flags, &time, &size))
goto out;
if ((flags & kFileTypeMask) != kFileTypeFlat)
goto out;
// alloc file and populate path
path_length = strlen(path) + 1;
of = malloc(sizeof(struct hfs_openfile) + path_length);
of->path = (char *)(of + 1);
strlcpy(of->path, path, path_length);
of->pos = 0;
of->fs = cp;
// Since we have stat information courtesy of checking the file's existence,
// save it away for the inevitable stat.
of->s.st_ino = 0; // XXX
of->s.st_size = size;
of->s.st_blocks = size / cp->s.f_bsize + ((size % cp->s.f_bsize) ? 1 : 0);
of->s.st_type = FILE_TYPE_FILE;
of->s.st_flags = 0; // XXX
of->s.st_ctime = of->s.st_mtime = of->s.st_atime = time;
out:
UNLOCK();
if (of == NULL)
HFSdebug("openfile failed");
return(of);
}
static int
hfs_read(void *filectxt, void *buf, size_t len)
{
struct hfs_openfile *of = (struct hfs_openfile *)filectxt;
int result;
if (of->s.st_type != FILE_TYPE_FILE)
return(-1);
HFSdebug("%s: read %zu bytes to %p", of->path, len, buf);
result = HFSReadFile(of->fs, of->path, buf, of->pos, len);
if (result)
HFSdebug("read returns %d", result);
if (result > 0)
of->pos += result;
return(result);
}
static int
hfs_fstat(void *filectxt, struct stat *st)
{
struct hfs_openfile *of = (struct hfs_openfile *)filectxt;
*st = of->s;
return(0);
}
static off_t
hfs_seek(void *filectxt, off_t offset, int whence)
{
struct hfs_openfile *of = (struct hfs_openfile *)filectxt;
off_t newpos;
switch(whence) {
case FILE_SEEK_SET:
newpos = offset;
break;
case FILE_SEEK_CUR:
newpos = of->pos + offset;
break;
case FILE_SEEK_END:
newpos = of->s.st_size + offset;
break;
default:
return(-1);
}
if ((newpos < 0) || (newpos > of->s.st_size))
return(-1);
HFSdebug("%s: seek to 0x%llx", of->path, newpos);
return((of->pos = newpos));
}
static void
hfs_close(void *filectxt)
{
free(filectxt);
}
static void *
hfs_opendir(void *ctxt, const char *path)
{
struct hfs_context *cp = (struct hfs_context *)ctxt;
struct hfs_opendir *od;
uint32_t flags;
uint32_t path_length;
HFSdebug("%s: opendir", path);
od = NULL;
if (path[0] == '/')
path++;
LOCK();
// check for directory existence (/ always exists)
if (path[0] != '\0') {
if (HFSGetFileInfo(cp, path, &flags, 0, 0)) {
HFSdebug("%s: can't get directory info", path);
goto out;
}
}
// alloc file and populate path
path_length = strlen(path) + 1;
od = malloc(sizeof(struct hfs_opendir) + path_length);
od->path = (char *)(od + 1);
strlcpy(od->path, path, path_length);
od->pos = 0;
od->fs = cp;
out:
UNLOCK();
if (od == NULL) {
HFSdebug("opendir failed");
} else {
HFSdebug("opendir OK");
}
return(od);
}
static int
hfs_readdir(void *dirctxt, struct dirent *ent)
{
struct hfs_opendir *od = (struct hfs_opendir *)dirctxt;
uint32_t flags;
time_t time;
off_t size;
int result;
if (od->pos == -1)
return(-1); // end of directory
HFSdebug("%s: readdir at 0x%llx", od->path, od->pos);
LOCK();
if (HFSGetDirEntry(od->fs, od->path, &od->pos, ent->d_name, sizeof(ent->d_name), &flags, &time, &size) >= 0) {
ent->d_fileno = od->pos; // kinda dubious
RELEASE_ASSERT(size <= INT32_MAX);
ent->d_reclen = size; // ?! seems to be what it's for
switch(flags & kFileTypeMask) {
case kFileTypeFlat:
ent->d_type = FILE_TYPE_FILE;
break;
case kFileTypeDirectory:
ent->d_type = FILE_TYPE_DIR;
break;
default:
ent->d_type = FILE_TYPE_UNKNOWN;
break;
}
ent->d_name[sizeof(ent->d_name) - 1] = '\0';
ent->d_namlen = strlen(ent->d_name);
ent->d_ctime = ent->d_atime = ent->d_atime = time;
result = 0;
} else {
result = -1;
}
UNLOCK();
return(result);
}
static void
hfs_rewinddir(void *dirctxt)
{
struct hfs_opendir *od = (struct hfs_opendir *)dirctxt;
od->pos = 0;
}
static void
hfs_closedir(void *dirctxt)
{
free(dirctxt);
}
/*******************************************************************************
* bdev interface
*
* Note that the blockdev layer deblocks for us, so this interface is trivial.
*/
int
HFSBlockRead(CICell *ih, void *buf, off_t offset, uint32_t length)
{
struct hfs_context *cp = (struct hfs_context *)ih;
HFSdebug("HFS physical read from 0x%llx/0x%x to %p", (unsigned long long)offset, length, buf);
return(blockdev_read(cp->dev, buf, offset, length));
}