blob: 67571f28fadda668e967cde6e263a2ce37dda0a8 [file] [log] [blame] [raw]
/*
* fs_ext2.c - an implementation for the Ext2/Ext3 filesystem
*
* Copyright (C) 2001, 2002 Ethan Benson
*
* Copyright (C) 1999 Benjamin Herrenschmidt
*
* Adapted from quik/silo
*
* Copyright (C) 1996 Maurizio Plaza
* 1996 Jakub Jelinek
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#include "ctype.h"
#include "types.h"
#include "stddef.h"
#include "file.h"
#include "prom.h"
#include "string.h"
#include "partition.h"
#include "fs.h"
#include "errors.h"
#include "debug.h"
#include "bootinfo.h"
#define FAST_VERSION
#define MAX_READ_RANGE 256
#undef VERBOSE_DEBUG
typedef int FILE;
#include "linux/ext2_fs.h"
#include "ext2fs/ext2fs.h"
static int ext2_open( struct boot_file_t* file,
const char* dev_name,
struct partition_t* part,
const char* file_name);
static int ext2_read( struct boot_file_t* file,
unsigned int size,
void* buffer);
static int ext2_seek( struct boot_file_t* file,
unsigned int newpos);
static int ext2_close( struct boot_file_t* file);
struct fs_t ext2_filesystem =
{
"ext2",
ext2_open,
ext2_read,
ext2_seek,
ext2_close
};
/* IO manager structure for the ext2 library */
static errcode_t linux_open (const char *name, int flags, io_channel * channel);
static errcode_t linux_close (io_channel channel);
static errcode_t linux_set_blksize (io_channel channel, int blksize);
static errcode_t linux_read_blk (io_channel channel, unsigned long block, int count, void *data);
static errcode_t linux_write_blk (io_channel channel, unsigned long block, int count, const void *data);
static errcode_t linux_flush (io_channel channel);
static struct struct_io_manager struct_linux_manager =
{
EXT2_ET_MAGIC_IO_MANAGER,
"linux I/O Manager",
linux_open,
linux_close,
linux_set_blksize,
linux_read_blk,
linux_write_blk,
linux_flush
};
static io_manager linux_io_manager = &struct_linux_manager;
/* Currently, we have a mess between what is in the file structure
* and what is stored globally here. I'll clean this up later
*/
static int opened = 0; /* We can't open twice ! */
static unsigned int bs; /* Blocksize */
static unsigned long long doff; /* Byte offset where partition starts */
static unsigned long long dend; /* Byte offset where partition ends */
static ino_t root,cwd;
static ext2_filsys fs = 0;
static struct boot_file_t* cur_file;
static char *block_buffer = NULL;
#ifdef FAST_VERSION
static unsigned long read_range_start;
static unsigned long read_range_count;
static unsigned long read_last_logical;
static unsigned long read_total;
static unsigned long read_max;
static struct boot_file_t* read_cur_file;
static errcode_t read_result;
static char* read_buffer;
static int read_dump_range(void);
static int read_iterator(ext2_filsys fs, blk_t *blocknr, int lg_block, void *private);
#else /* FAST_VERSION */
static struct ext2_inode cur_inode;
#endif /* FAST_VERSION */
void com_err (const char *a, long i, const char *fmt,...)
{
prom_printf ((char *) fmt);
}
static int
ext2_open( struct boot_file_t* file,
const char* dev_name,
struct partition_t* part,
const char* file_name)
{
int result = 0;
int error = FILE_ERR_NOTFOUND;
static char buffer[1024];
int ofopened = 0;
DEBUG_ENTER;
DEBUG_OPEN;
if (opened) {
DEBUG_LEAVE(FILE_ERR_FSBUSY);
return FILE_ERR_FSBUSY;
}
if (file->device_kind != FILE_DEVICE_BLOCK) {
DEBUG_LEAVE(FILE_ERR_BADDEV);
return FILE_ERR_BADDEV;
}
fs = NULL;
/* We don't care too much about the device block size since we run
* thru the deblocker. We may have to change that is we plan to be
* compatible with older versions of OF
*/
bs = 1024;
/*
* On the other hand, we do care about the actual size of the
* partition, reads or seeks past the end may cause undefined
* behavior on some devices. A netapp that tries to seek and
* read past the end of the lun takes ~30 secs to recover per
* attempt.
*/
doff = dend = 0;
if (part) {
doff = (unsigned long long)(part->part_start) * part->blocksize;
dend = doff + (unsigned long long)part->part_size * part->blocksize;
}
cur_file = file;
DEBUG_F("partition offset: %Lx, end: %Lx\n", doff, dend);
/* Open the OF device for the entire disk */
strncpy(buffer, dev_name, 1020);
if (_machine != _MACH_bplan)
strcat(buffer, ":0");
DEBUG_F("<%s>\n", buffer);
file->of_device = prom_open(buffer);
DEBUG_F("file->of_device = %p\n", file->of_device);
if (file->of_device == PROM_INVALID_HANDLE) {
DEBUG_F("Can't open device %p\n", file->of_device);
DEBUG_LEAVE(FILE_IOERR);
return FILE_IOERR;
}
ofopened = 1;
/* Open the ext2 filesystem */
result = ext2fs_open (buffer, EXT2_FLAG_RW, 0, 0, linux_io_manager, &fs);
if (result) {
if(result == EXT2_ET_BAD_MAGIC)
{
DEBUG_F( "ext2fs_open returned bad magic loading file %p\n",
file );
}
else
{
DEBUG_F( "ext2fs_open error #%d while loading file %s\n",
result, file_name);
}
error = FILE_ERR_BAD_FSYS;
goto bail;
}
/* Allocate the block buffer */
block_buffer = malloc(fs->blocksize * 2);
if (!block_buffer) {
DEBUG_F("ext2fs: can't alloc block buffer (%d bytes)\n", fs->blocksize * 2);
error = FILE_IOERR;
goto bail;
}
/* Lookup file by pathname */
root = cwd = EXT2_ROOT_INO;
result = ext2fs_namei_follow(fs, root, cwd, file_name, &file->inode);
if (result) {
DEBUG_F("ext2fs_namei error #%d while loading file %s\n", result, file_name);
if (result == EXT2_ET_SYMLINK_LOOP)
error = FILE_ERR_SYMLINK_LOOP;
else if (result == EXT2_ET_FILE_NOT_FOUND)
error = FILE_ERR_NOTFOUND;
else
error = FILE_IOERR;
goto bail;
}
#if 0
result = ext2fs_follow_link(fs, root, cwd, file->inode, &file->inode);
if (result) {
DEBUG_F("ext2fs_follow_link error #%d while loading file %s\n", result, file_name);
error = FILE_ERR_NOTFOUND;
goto bail;
}
#endif
#ifndef FAST_VERSION
result = ext2fs_read_inode(fs, file->inode, &cur_inode);
if (result) {
DEBUG_F("ext2fs_read_inode error #%d while loading file %s\n", result, file_name);
if (result == EXT2_ET_FILE_TOO_BIG)
error = FILE_ERR_LENGTH;
else if (result == EXT2_ET_LLSEEK_FAILED)
error = FILE_CANT_SEEK;
else if (result == EXT2_ET_FILE_NOT_FOUND)
error = FILE_ERR_NOTFOUND;
else
error = FILE_IOERR;
goto bail;
}
#endif /* FAST_VERSION */
file->pos = 0;
opened = 1;
bail:
if (!opened) {
if (fs)
ext2fs_close(fs);
fs = NULL;
if (ofopened)
prom_close(file->of_device);
if (block_buffer)
free(block_buffer);
block_buffer = NULL;
cur_file = NULL;
DEBUG_LEAVE_F(error);
return error;
}
DEBUG_LEAVE(FILE_ERR_OK);
return FILE_ERR_OK;
}
#ifdef FAST_VERSION
static int
read_dump_range(void)
{
int count = read_range_count;
int size;
#ifdef VERBOSE_DEBUG
DEBUG_F(" dumping range: start: 0x%x count: 0x%x\n",
read_range_count, read_range_start);
#endif
/* Check if we need to handle a special case for the last block */
if ((count * bs) > read_max)
count--;
if (count) {
size = count * bs;
read_result = io_channel_read_blk(fs->io, read_range_start, count, read_buffer);
if (read_result)
return BLOCK_ABORT;
read_buffer += size;
read_max -= size;
read_total += size;
read_cur_file->pos += size;
read_range_count -= count;
read_range_start += count;
read_last_logical += count;
}
/* Handle remaining block */
if (read_max && read_range_count) {
read_result = io_channel_read_blk(fs->io, read_range_start, 1, block_buffer);
if (read_result)
return BLOCK_ABORT;
memcpy(read_buffer, block_buffer, read_max);
read_cur_file->pos += read_max;
read_total += read_max;
read_max = 0;
}
read_range_count = read_range_start = 0;
return (read_max == 0) ? BLOCK_ABORT : 0;
}
static int
read_iterator(ext2_filsys fs, blk_t *blocknr, int lg_block, void *private)
{
#ifdef VERBOSE_DEBUG
DEBUG_F("read_it: p_bloc: 0x%x, l_bloc: 0x%x, f_pos: 0x%x, rng_pos: 0x%x ",
*blocknr, lg_block, read_cur_file->pos, read_last_logical);
#endif
if (lg_block < 0) {
#ifdef VERBOSE_DEBUG
DEBUG_F(" <skip lg>\n");
#endif
return 0;
}
/* If we have not reached the start block yet, we skip */
if (lg_block < read_cur_file->pos / bs) {
#ifdef VERBOSE_DEBUG
DEBUG_F(" <skip pos>\n");
#endif
return 0;
}
/* If block is contiguous to current range, just extend range,
* exit if we pass the remaining bytes count to read
*/
if (read_range_start && read_range_count < MAX_READ_RANGE
&& (*blocknr == read_range_start + read_range_count)
&& (lg_block == read_last_logical + read_range_count)) {
#ifdef VERBOSE_DEBUG
DEBUG_F(" block in range\n");
#endif
++read_range_count;
return ((read_range_count * bs) >= read_max) ? BLOCK_ABORT : 0;
}
/* Range doesn't match. Dump existing range */
if (read_range_start) {
#ifdef VERBOSE_DEBUG
DEBUG_F(" calling dump range \n");
#endif
if (read_dump_range())
return BLOCK_ABORT;
}
/* Here we handle holes in the file */
if (lg_block && lg_block != read_last_logical) {
unsigned long nzero;
#ifdef VERBOSE_DEBUG
DEBUG_F(" hole from lg_bloc 0x%x\n", read_last_logical);
#endif
if (read_cur_file->pos % bs) {
int offset = read_cur_file->pos % bs;
int size = bs - offset;
if (size > read_max)
size = read_max;
memset(read_buffer, 0, size);
read_max -= size;
read_total += size;
read_buffer += size;
read_cur_file->pos += size;
++read_last_logical;
if (read_max == 0)
return BLOCK_ABORT;
}
nzero = (lg_block - read_last_logical) * bs;
if (nzero) {
if (nzero > read_max)
nzero = read_max;
memset(read_buffer, 0, nzero);
read_max -= nzero;
read_total += nzero;
read_buffer += nzero;
read_cur_file->pos += nzero;
if (read_max == 0)
return BLOCK_ABORT;
}
read_last_logical = lg_block;
}
/* If we are not aligned, handle that case */
if (read_cur_file->pos % bs) {
int offset = read_cur_file->pos % bs;
int size = bs - offset;
#ifdef VERBOSE_DEBUG
DEBUG_F(" handle unaligned start\n");
#endif
read_result = io_channel_read_blk(fs->io, *blocknr, 1, block_buffer);
if (read_result)
return BLOCK_ABORT;
if (size > read_max)
size = read_max;
memcpy(read_buffer, block_buffer + offset, size);
read_cur_file->pos += size;
read_max -= size;
read_total += size;
read_buffer += size;
read_last_logical = lg_block + 1;
return (read_max == 0) ? BLOCK_ABORT : 0;
}
/* If there is still a physical block to add, then create a new range */
if (*blocknr) {
#ifdef VERBOSE_DEBUG
DEBUG_F(" new range\n");
#endif
read_range_start = *blocknr;
read_range_count = 1;
return (bs >= read_max) ? BLOCK_ABORT : 0;
}
#ifdef VERBOSE_DEBUG
DEBUG_F("\n");
#endif
return 0;
}
#endif /* FAST_VERSION */
static int
ext2_read( struct boot_file_t* file,
unsigned int size,
void* buffer)
{
errcode_t retval;
#ifdef FAST_VERSION
if (!opened)
return FILE_IOERR;
DEBUG_F("ext_read() from pos 0x%Lx, size: 0x%ux\n", file->pos, size);
read_cur_file = file;
read_range_start = 0;
read_range_count = 0;
read_last_logical = file->pos / bs;
read_total = 0;
read_max = size;
read_buffer = (unsigned char*)buffer;
read_result = 0;
retval = ext2fs_block_iterate(fs, file->inode, 0, 0, read_iterator, 0);
if (retval == BLOCK_ABORT)
retval = read_result;
if (!retval && read_range_start) {
#ifdef VERBOSE_DEBUG
DEBUG_F("on exit: range_start is 0x%x, calling dump...\n",
read_range_start);
#endif
read_dump_range();
retval = read_result;
}
if (retval)
prom_printf ("ext2: i/o error %ld in read\n", (long) retval);
return read_total;
#else /* FAST_VERSION */
int status;
unsigned int read = 0;
if (!opened)
return FILE_IOERR;
DEBUG_F("ext_read() from pos 0x%x, size: 0x%x\n", file->pos, size);
while(size) {
blk_t fblock = file->pos / bs;
blk_t pblock;
unsigned int blkorig, s, b;
pblock = 0;
status = ext2fs_bmap(fs, file->inode, &cur_inode,
block_buffer, 0, fblock, &pblock);
if (status) {
DEBUG_F("ext2fs_bmap(fblock:%d) return: %d\n", fblock, status);
return read;
}
blkorig = fblock * bs;
b = file->pos - blkorig;
s = ((bs - b) > size) ? size : (bs - b);
if (pblock) {
unsigned long long pos =
((unsigned long long)pblock) * (unsigned long long)bs;
pos += doff;
prom_lseek(file->of_device, pos);
status = prom_read(file->of_device, block_buffer, bs);
if (status != bs) {
prom_printf("ext2: io error in read, ex: %d, got: %d\n",
bs, status);
return read;
}
} else
memset(block_buffer, 0, bs);
memcpy(buffer, block_buffer + b, s);
read += s;
size -= s;
buffer += s;
file->pos += s;
}
return read;
#endif /* FAST_VERSION */
}
static int
ext2_seek( struct boot_file_t* file,
unsigned int newpos)
{
if (!opened)
return FILE_CANT_SEEK;
file->pos = newpos;
return FILE_ERR_OK;
}
static int
ext2_close( struct boot_file_t* file)
{
if (!opened)
return FILE_IOERR;
if (block_buffer)
free(block_buffer);
block_buffer = NULL;
if (fs)
ext2fs_close(fs);
fs = NULL;
prom_close(file->of_device);
DEBUG_F("ext2_close called\n");
opened = 0;
return 0;
}
static errcode_t linux_open (const char *name, int flags, io_channel * channel)
{
io_channel io;
if (!name)
return EXT2_ET_BAD_DEVICE_NAME;
io = (io_channel) malloc (sizeof (struct struct_io_channel));
if (!io)
return EXT2_ET_BAD_DEVICE_NAME;
memset (io, 0, sizeof (struct struct_io_channel));
io->magic = EXT2_ET_MAGIC_IO_CHANNEL;
io->manager = linux_io_manager;
io->name = (char *) malloc (strlen (name) + 1);
strcpy (io->name, name);
io->block_size = bs;
io->read_error = 0;
io->write_error = 0;
*channel = io;
return 0;
}
static errcode_t linux_close (io_channel channel)
{
free(channel);
return 0;
}
static errcode_t linux_set_blksize (io_channel channel, int blksize)
{
DEBUG_F("bs set to 0x%x\n", blksize);
channel->block_size = bs = blksize;
if (block_buffer) {
free(block_buffer);
block_buffer = malloc(bs * 2);
}
return 0;
}
static errcode_t linux_read_blk (io_channel channel, unsigned long block, int count, void *data)
{
int size;
unsigned long long tempb;
if (count == 0)
return 0;
tempb = (((unsigned long long) block) *
((unsigned long long)bs)) + (unsigned long long)doff;
/*
* Only test tempb exceeding dend if dend is set to allow things
* like boot: hd:0,\xxxx
*/
if (dend && tempb > dend) {
DEBUG_F("\nSeek error on block %lx, tempb=%Lx\n", block, tempb >> 9);
return EXT2_ET_LLSEEK_FAILED;
}
size = (count < 0) ? -count : count * bs;
prom_lseek(cur_file->of_device, tempb);
if (prom_read(cur_file->of_device, data, size) != size) {
DEBUG_F("\nRead error on block %ld\n", block);
return EXT2_ET_SHORT_READ;
}
return 0;
}
static errcode_t linux_write_blk (io_channel channel, unsigned long block, int count, const void *data)
{
return 0;
}
static errcode_t linux_flush (io_channel channel)
{
return 0;
}
/*
* Local variables:
* c-file-style: "k&r"
* c-basic-offset: 5
* End:
*/