diff options
author | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
commit | 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch) | |
tree | 0bba044c4ce775e45a88a51686b5d9f90697ea9d /fs/isofs/compress.c |
Linux-2.6.12-rc2v2.6.12-rc2
Initial git repository build. I'm not bothering with the full history,
even though we have it. We can create a separate "historical" git
archive of that later if we want to, and in the meantime it's about
3.2GB when imported into git - space that would just make the early
git days unnecessarily complicated, when we don't have a lot of good
infrastructure for it.
Let it rip!
Diffstat (limited to 'fs/isofs/compress.c')
-rw-r--r-- | fs/isofs/compress.c | 359 |
1 files changed, 359 insertions, 0 deletions
diff --git a/fs/isofs/compress.c b/fs/isofs/compress.c new file mode 100644 index 00000000000..fb42c3f3bf0 --- /dev/null +++ b/fs/isofs/compress.c @@ -0,0 +1,359 @@ +/* -*- linux-c -*- ------------------------------------------------------- * + * + * Copyright 2001 H. Peter Anvin - All Rights Reserved + * + * 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, Inc., 675 Mass Ave, Cambridge MA 02139, + * USA; either version 2 of the License, or (at your option) any later + * version; incorporated herein by reference. + * + * ----------------------------------------------------------------------- */ + +/* + * linux/fs/isofs/compress.c + * + * Transparent decompression of files on an iso9660 filesystem + */ + +#include <linux/config.h> +#include <linux/module.h> + +#include <linux/stat.h> +#include <linux/time.h> +#include <linux/iso_fs.h> +#include <linux/kernel.h> +#include <linux/major.h> +#include <linux/mm.h> +#include <linux/string.h> +#include <linux/slab.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/nls.h> +#include <linux/ctype.h> +#include <linux/smp_lock.h> +#include <linux/blkdev.h> +#include <linux/vmalloc.h> +#include <linux/zlib.h> +#include <linux/buffer_head.h> + +#include <asm/system.h> +#include <asm/uaccess.h> +#include <asm/semaphore.h> + +#include "zisofs.h" + +/* This should probably be global. */ +static char zisofs_sink_page[PAGE_CACHE_SIZE]; + +/* + * This contains the zlib memory allocation and the mutex for the + * allocation; this avoids failures at block-decompression time. + */ +static void *zisofs_zlib_workspace; +static struct semaphore zisofs_zlib_semaphore; + +/* + * When decompressing, we typically obtain more than one page + * per reference. We inject the additional pages into the page + * cache as a form of readahead. + */ +static int zisofs_readpage(struct file *file, struct page *page) +{ + struct inode *inode = file->f_dentry->d_inode; + struct address_space *mapping = inode->i_mapping; + unsigned int maxpage, xpage, fpage, blockindex; + unsigned long offset; + unsigned long blockptr, blockendptr, cstart, cend, csize; + struct buffer_head *bh, *ptrbh[2]; + unsigned long bufsize = ISOFS_BUFFER_SIZE(inode); + unsigned int bufshift = ISOFS_BUFFER_BITS(inode); + unsigned long bufmask = bufsize - 1; + int err = -EIO; + int i; + unsigned int header_size = ISOFS_I(inode)->i_format_parm[0]; + unsigned int zisofs_block_shift = ISOFS_I(inode)->i_format_parm[1]; + /* unsigned long zisofs_block_size = 1UL << zisofs_block_shift; */ + unsigned int zisofs_block_page_shift = zisofs_block_shift-PAGE_CACHE_SHIFT; + unsigned long zisofs_block_pages = 1UL << zisofs_block_page_shift; + unsigned long zisofs_block_page_mask = zisofs_block_pages-1; + struct page *pages[zisofs_block_pages]; + unsigned long index = page->index; + int indexblocks; + + /* We have already been given one page, this is the one + we must do. */ + xpage = index & zisofs_block_page_mask; + pages[xpage] = page; + + /* The remaining pages need to be allocated and inserted */ + offset = index & ~zisofs_block_page_mask; + blockindex = offset >> zisofs_block_page_shift; + maxpage = (inode->i_size + PAGE_CACHE_SIZE - 1) >> PAGE_CACHE_SHIFT; + maxpage = min(zisofs_block_pages, maxpage-offset); + + for ( i = 0 ; i < maxpage ; i++, offset++ ) { + if ( i != xpage ) { + pages[i] = grab_cache_page_nowait(mapping, offset); + } + page = pages[i]; + if ( page ) { + ClearPageError(page); + kmap(page); + } + } + + /* This is the last page filled, plus one; used in case of abort. */ + fpage = 0; + + /* Find the pointer to this specific chunk */ + /* Note: we're not using isonum_731() here because the data is known aligned */ + /* Note: header_size is in 32-bit words (4 bytes) */ + blockptr = (header_size + blockindex) << 2; + blockendptr = blockptr + 4; + + indexblocks = ((blockptr^blockendptr) >> bufshift) ? 2 : 1; + ptrbh[0] = ptrbh[1] = NULL; + + if ( isofs_get_blocks(inode, blockptr >> bufshift, ptrbh, indexblocks) != indexblocks ) { + if ( ptrbh[0] ) brelse(ptrbh[0]); + printk(KERN_DEBUG "zisofs: Null buffer on reading block table, inode = %lu, block = %lu\n", + inode->i_ino, blockptr >> bufshift); + goto eio; + } + ll_rw_block(READ, indexblocks, ptrbh); + + bh = ptrbh[0]; + if ( !bh || (wait_on_buffer(bh), !buffer_uptodate(bh)) ) { + printk(KERN_DEBUG "zisofs: Failed to read block table, inode = %lu, block = %lu\n", + inode->i_ino, blockptr >> bufshift); + if ( ptrbh[1] ) + brelse(ptrbh[1]); + goto eio; + } + cstart = le32_to_cpu(*(__le32 *)(bh->b_data + (blockptr & bufmask))); + + if ( indexblocks == 2 ) { + /* We just crossed a block boundary. Switch to the next block */ + brelse(bh); + bh = ptrbh[1]; + if ( !bh || (wait_on_buffer(bh), !buffer_uptodate(bh)) ) { + printk(KERN_DEBUG "zisofs: Failed to read block table, inode = %lu, block = %lu\n", + inode->i_ino, blockendptr >> bufshift); + goto eio; + } + } + cend = le32_to_cpu(*(__le32 *)(bh->b_data + (blockendptr & bufmask))); + brelse(bh); + + csize = cend-cstart; + + /* Now page[] contains an array of pages, any of which can be NULL, + and the locks on which we hold. We should now read the data and + release the pages. If the pages are NULL the decompressed data + for that particular page should be discarded. */ + + if ( csize == 0 ) { + /* This data block is empty. */ + + for ( fpage = 0 ; fpage < maxpage ; fpage++ ) { + if ( (page = pages[fpage]) != NULL ) { + memset(page_address(page), 0, PAGE_CACHE_SIZE); + + flush_dcache_page(page); + SetPageUptodate(page); + kunmap(page); + unlock_page(page); + if ( fpage == xpage ) + err = 0; /* The critical page */ + else + page_cache_release(page); + } + } + } else { + /* This data block is compressed. */ + z_stream stream; + int bail = 0, left_out = -1; + int zerr; + int needblocks = (csize + (cstart & bufmask) + bufmask) >> bufshift; + int haveblocks; + struct buffer_head *bhs[needblocks+1]; + struct buffer_head **bhptr; + + /* Because zlib is not thread-safe, do all the I/O at the top. */ + + blockptr = cstart >> bufshift; + memset(bhs, 0, (needblocks+1)*sizeof(struct buffer_head *)); + haveblocks = isofs_get_blocks(inode, blockptr, bhs, needblocks); + ll_rw_block(READ, haveblocks, bhs); + + bhptr = &bhs[0]; + bh = *bhptr++; + + /* First block is special since it may be fractional. + We also wait for it before grabbing the zlib + semaphore; odds are that the subsequent blocks are + going to come in in short order so we don't hold + the zlib semaphore longer than necessary. */ + + if ( !bh || (wait_on_buffer(bh), !buffer_uptodate(bh)) ) { + printk(KERN_DEBUG "zisofs: Hit null buffer, fpage = %d, xpage = %d, csize = %ld\n", + fpage, xpage, csize); + goto b_eio; + } + stream.next_in = bh->b_data + (cstart & bufmask); + stream.avail_in = min(bufsize-(cstart & bufmask), csize); + csize -= stream.avail_in; + + stream.workspace = zisofs_zlib_workspace; + down(&zisofs_zlib_semaphore); + + zerr = zlib_inflateInit(&stream); + if ( zerr != Z_OK ) { + if ( err && zerr == Z_MEM_ERROR ) + err = -ENOMEM; + printk(KERN_DEBUG "zisofs: zisofs_inflateInit returned %d\n", + zerr); + goto z_eio; + } + + while ( !bail && fpage < maxpage ) { + page = pages[fpage]; + if ( page ) + stream.next_out = page_address(page); + else + stream.next_out = (void *)&zisofs_sink_page; + stream.avail_out = PAGE_CACHE_SIZE; + + while ( stream.avail_out ) { + int ao, ai; + if ( stream.avail_in == 0 && left_out ) { + if ( !csize ) { + printk(KERN_WARNING "zisofs: ZF read beyond end of input\n"); + bail = 1; + break; + } else { + bh = *bhptr++; + if ( !bh || + (wait_on_buffer(bh), !buffer_uptodate(bh)) ) { + /* Reached an EIO */ + printk(KERN_DEBUG "zisofs: Hit null buffer, fpage = %d, xpage = %d, csize = %ld\n", + fpage, xpage, csize); + + bail = 1; + break; + } + stream.next_in = bh->b_data; + stream.avail_in = min(csize,bufsize); + csize -= stream.avail_in; + } + } + ao = stream.avail_out; ai = stream.avail_in; + zerr = zlib_inflate(&stream, Z_SYNC_FLUSH); + left_out = stream.avail_out; + if ( zerr == Z_BUF_ERROR && stream.avail_in == 0 ) + continue; + if ( zerr != Z_OK ) { + /* EOF, error, or trying to read beyond end of input */ + if ( err && zerr == Z_MEM_ERROR ) + err = -ENOMEM; + if ( zerr != Z_STREAM_END ) + printk(KERN_DEBUG "zisofs: zisofs_inflate returned %d, inode = %lu, index = %lu, fpage = %d, xpage = %d, avail_in = %d, avail_out = %d, ai = %d, ao = %d\n", + zerr, inode->i_ino, index, + fpage, xpage, + stream.avail_in, stream.avail_out, + ai, ao); + bail = 1; + break; + } + } + + if ( stream.avail_out && zerr == Z_STREAM_END ) { + /* Fractional page written before EOF. This may + be the last page in the file. */ + memset(stream.next_out, 0, stream.avail_out); + stream.avail_out = 0; + } + + if ( !stream.avail_out ) { + /* This page completed */ + if ( page ) { + flush_dcache_page(page); + SetPageUptodate(page); + kunmap(page); + unlock_page(page); + if ( fpage == xpage ) + err = 0; /* The critical page */ + else + page_cache_release(page); + } + fpage++; + } + } + zlib_inflateEnd(&stream); + + z_eio: + up(&zisofs_zlib_semaphore); + + b_eio: + for ( i = 0 ; i < haveblocks ; i++ ) { + if ( bhs[i] ) + brelse(bhs[i]); + } + } + +eio: + + /* Release any residual pages, do not SetPageUptodate */ + while ( fpage < maxpage ) { + page = pages[fpage]; + if ( page ) { + flush_dcache_page(page); + if ( fpage == xpage ) + SetPageError(page); + kunmap(page); + unlock_page(page); + if ( fpage != xpage ) + page_cache_release(page); + } + fpage++; + } + + /* At this point, err contains 0 or -EIO depending on the "critical" page */ + return err; +} + +struct address_space_operations zisofs_aops = { + .readpage = zisofs_readpage, + /* No sync_page operation supported? */ + /* No bmap operation supported */ +}; + +static int initialized; + +int __init zisofs_init(void) +{ + if ( initialized ) { + printk("zisofs_init: called more than once\n"); + return 0; + } + + zisofs_zlib_workspace = vmalloc(zlib_inflate_workspacesize()); + if ( !zisofs_zlib_workspace ) + return -ENOMEM; + init_MUTEX(&zisofs_zlib_semaphore); + + initialized = 1; + return 0; +} + +void zisofs_cleanup(void) +{ + if ( !initialized ) { + printk("zisofs_cleanup: called without initialization\n"); + return; + } + + vfree(zisofs_zlib_workspace); + initialized = 0; +} |