|  | /* | 
|  | *  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" | 
|  |  | 
|  | #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); | 
|  | 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: | 
|  | */ |