]> git.ozlabs.org Git - yaboot.git/blobdiff - second/fs_ext2.c
Commit yaboot 1.3.0
[yaboot.git] / second / fs_ext2.c
diff --git a/second/fs_ext2.c b/second/fs_ext2.c
new file mode 100644 (file)
index 0000000..e2a5070
--- /dev/null
@@ -0,0 +1,604 @@
+/* ext2 filesystem
+   
+   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., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+   Note: This version is way too slow due to the way we use bmap. This
+         should be replaced by an iterator.
+*/
+
+#include "ctype.h"
+#include "types.h"
+#include "stddef.h"
+#include "file.h"
+#include "prom.h"
+#include "string.h"
+#include "partition.h"
+#include "fs.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 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;
+       static char buffer[1024];
+       int ofopened = 0;
+       
+        DEBUG_ENTER;
+        DEBUG_OPEN;
+
+       if (opened) {
+               prom_printf("ext2_open() : fs busy\n");
+               return FILE_ERR_NOTFOUND;
+       }
+       if (file->device_kind != FILE_DEVICE_BLOCK) {
+               prom_printf("Can't open ext2 filesystem on non-block device\n");
+               return FILE_ERR_NOTFOUND;
+       }
+
+       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;
+       doff = 0;
+       if (part)
+           doff = (unsigned long long)(part->part_start) * part->blocksize;
+       cur_file = file;
+
+
+       DEBUG_F("partition offset: %d\n", doff);
+
+       /* Open the OF device for the entire disk */
+       strncpy(buffer, dev_name, 1020);
+       strcat(buffer, ":0");
+
+       DEBUG_F("<%s>\n", buffer);
+
+       file->of_device = prom_open(buffer);
+
+       DEBUG_F("file->of_device = %08lx\n", file->of_device);
+
+       if (file->of_device == PROM_INVALID_HANDLE) {
+
+               DEBUG_F("Can't open device %s\n", file->of_device);
+
+               return FILE_ERR_NOTFOUND;
+       }
+       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 %s\n",
+                         file );
+            }
+            else
+            {
+                DEBUG_F( "ext2fs_open error #%d while loading file %s\n",
+                         result, file_name);
+            }
+
+           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);
+
+           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);
+           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);
+
+           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);
+
+           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(FILE_ERR_NOTFOUND);
+           return FILE_ERR_NOTFOUND;
+       }
+
+       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_ERR_NOTFOUND;
+
+
+       DEBUG_F("ext_read() from pos 0x%x, size: 0x%x\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 %d in read\n", retval);
+               
+       return read_total;
+
+#else /* FAST_VERSION */
+       int status;
+       unsigned int read = 0;
+       
+       if (!opened)
+           return FILE_ERR_NOTFOUND;
+
+
+       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_ERR_NOTFOUND;
+
+       file->pos = newpos;
+       return FILE_ERR_OK;
+}
+
+static int
+ext2_close(    struct boot_file_t*     file)
+{
+       if (!opened)
+               return FILE_ERR_NOTFOUND;
+
+       if (block_buffer)
+               free(block_buffer);
+       block_buffer = NULL;
+
+       if (fs)
+               ext2fs_close(fs);
+       fs = NULL;
+       
+       prom_close(file->of_device);
+
+       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)
+{
+    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;
+    size = (count < 0) ? -count : count * bs;
+    prom_lseek(cur_file->of_device, tempb);
+    if (prom_read(cur_file->of_device, data, size) != size) {
+       prom_printf ("\nRead error on block %d", 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;
+}
+