/* * Squashfs - a compressed read only filesystem for Linux * * Copyright (c) 2002, 2003, 2004, 2005, 2006, 2007, 2008 * Phillip Lougher * * 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, * 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, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * block.c */ /* * This file implements the low-level routines to read and decompress * datablocks and metadata blocks. */ #include #include #include #include #include #include #include #include #include "squashfs_fs.h" #include "squashfs_fs_sb.h" #include "squashfs.h" #include "decompressor.h" #include "page_actor.h" static struct workqueue_struct *squashfs_read_wq; struct squashfs_read_request { struct super_block *sb; u64 index; int length; int compressed; int offset; u64 read_end; struct squashfs_page_actor *output; enum { SQUASHFS_COPY, SQUASHFS_DECOMPRESS, SQUASHFS_METADATA, } data_processing; bool synchronous; /* * If the read is synchronous, it is possible to retrieve information * about the request by setting these pointers. */ int *res; int *bytes_read; int *bytes_uncompressed; int nr_buffers; struct buffer_head **bh; struct work_struct offload; }; struct squashfs_bio_request { struct buffer_head **bh; int nr_buffers; }; static int squashfs_bio_submit(struct squashfs_read_request *req); int squashfs_init_read_wq(void) { squashfs_read_wq = create_workqueue("SquashFS read wq"); return !!squashfs_read_wq; } void squashfs_destroy_read_wq(void) { flush_workqueue(squashfs_read_wq); destroy_workqueue(squashfs_read_wq); } static void free_read_request(struct squashfs_read_request *req, int error) { if (!req->synchronous) squashfs_page_actor_free(req->output, error); if (req->res) *(req->res) = error; kfree(req->bh); kfree(req); } static void squashfs_process_blocks(struct squashfs_read_request *req) { int error = 0; int bytes, i, length; struct squashfs_sb_info *msblk = req->sb->s_fs_info; struct squashfs_page_actor *actor = req->output; struct buffer_head **bh = req->bh; int nr_buffers = req->nr_buffers; for (i = 0; i < nr_buffers; ++i) { if (!bh[i]) continue; wait_on_buffer(bh[i]); if (!buffer_uptodate(bh[i])) error = -EIO; } if (error) goto cleanup; if (req->data_processing == SQUASHFS_METADATA) { /* Extract the length of the metadata block */ if (req->offset != msblk->devblksize - 1) { length = le16_to_cpup((__le16 *) (bh[0]->b_data + req->offset)); } else { length = (unsigned char)bh[0]->b_data[req->offset]; length |= (unsigned char)bh[1]->b_data[0] << 8; } req->compressed = SQUASHFS_COMPRESSED(length); req->data_processing = req->compressed ? SQUASHFS_DECOMPRESS : SQUASHFS_COPY; length = SQUASHFS_COMPRESSED_SIZE(length); if (req->index + length + 2 > req->read_end) { for (i = 0; i < nr_buffers; ++i) put_bh(bh[i]); kfree(bh); req->length = length; req->index += 2; squashfs_bio_submit(req); return; } req->length = length; req->offset = (req->offset + 2) % PAGE_SIZE; if (req->offset < 2) { put_bh(bh[0]); ++bh; --nr_buffers; } } if (req->bytes_read) *(req->bytes_read) = req->length; if (req->data_processing == SQUASHFS_COPY) { squashfs_bh_to_actor(bh, nr_buffers, req->output, req->offset, req->length, msblk->devblksize); } else if (req->data_processing == SQUASHFS_DECOMPRESS) { req->length = squashfs_decompress(msblk, bh, nr_buffers, req->offset, req->length, actor); if (req->length < 0) { error = -EIO; goto cleanup; } } /* Last page may have trailing bytes not filled */ bytes = req->length % PAGE_SIZE; if (bytes && actor->page[actor->pages - 1]) zero_user_segment(actor->page[actor->pages - 1], bytes, PAGE_SIZE); cleanup: if (req->bytes_uncompressed) *(req->bytes_uncompressed) = req->length; if (error) { for (i = 0; i < nr_buffers; ++i) if (bh[i]) put_bh(bh[i]); } free_read_request(req, error); } static void read_wq_handler(struct work_struct *work) { squashfs_process_blocks(container_of(work, struct squashfs_read_request, offload)); } static void squashfs_bio_end_io(struct bio *bio) { int i; int error = bio->bi_error; struct squashfs_bio_request *bio_req = bio->bi_private; bio_put(bio); for (i = 0; i < bio_req->nr_buffers; ++i) { if (!bio_req->bh[i]) continue; if (!error) set_buffer_uptodate(bio_req->bh[i]); else clear_buffer_uptodate(bio_req->bh[i]); unlock_buffer(bio_req->bh[i]); } kfree(bio_req); } static int bh_is_optional(struct squashfs_read_request *req, int idx) { int start_idx, end_idx; struct squashfs_sb_info *msblk = req->sb->s_fs_info; start_idx = (idx * msblk->devblksize - req->offset) >> PAGE_SHIFT; end_idx = ((idx + 1) * msblk->devblksize - req->offset + 1) >> PAGE_SHIFT; if (start_idx >= req->output->pages) return 1; if (start_idx < 0) start_idx = end_idx; if (end_idx >= req->output->pages) end_idx = start_idx; return !req->output->page[start_idx] && !req->output->page[end_idx]; } static int actor_getblks(struct squashfs_read_request *req, u64 block) { int i; req->bh = kmalloc_array(req->nr_buffers, sizeof(*(req->bh)), GFP_NOIO); if (!req->bh) return -ENOMEM; for (i = 0; i < req->nr_buffers; ++i) { /* * When dealing with an uncompressed block, the actor may * contains NULL pages. There's no need to read the buffers * associated with these pages. */ if (!req->compressed && bh_is_optional(req, i)) { req->bh[i] = NULL; continue; } req->bh[i] = sb_getblk(req->sb, block + i); if (!req->bh[i]) { while (--i) { if (req->bh[i]) put_bh(req->bh[i]); } return -1; } } return 0; } static int squashfs_bio_submit(struct squashfs_read_request *req) { struct bio *bio = NULL; struct buffer_head *bh; struct squashfs_bio_request *bio_req = NULL; int b = 0, prev_block = 0; struct squashfs_sb_info *msblk = req->sb->s_fs_info; u64 read_start = round_down(req->index, msblk->devblksize); u64 read_end = round_up(req->index + req->length, msblk->devblksize); sector_t block = read_start >> msblk->devblksize_log2; sector_t block_end = read_end >> msblk->devblksize_log2; int offset = read_start - round_down(req->index, PAGE_SIZE); int nr_buffers = block_end - block; int blksz = msblk->devblksize; int bio_max_pages = nr_buffers > BIO_MAX_PAGES ? BIO_MAX_PAGES : nr_buffers; /* Setup the request */ req->read_end = read_end; req->offset = req->index - read_start; req->nr_buffers = nr_buffers; if (actor_getblks(req, block) < 0) goto getblk_failed; /* Create and submit the BIOs */ for (b = 0; b < nr_buffers; ++b, offset += blksz) { bh = req->bh[b]; if (!bh || !trylock_buffer(bh)) continue; if (buffer_uptodate(bh)) { unlock_buffer(bh); continue; } offset %= PAGE_SIZE; /* Append the buffer to the current BIO if it is contiguous */ if (bio && bio_req && prev_block + 1 == b) { if (bio_add_page(bio, bh->b_page, blksz, offset)) { bio_req->nr_buffers += 1; prev_block = b; continue; } } /* Otherwise, submit the current BIO and create a new one */ if (bio) submit_bio(READ, bio); bio_req = kcalloc(1, sizeof(struct squashfs_bio_request), GFP_NOIO); if (!bio_req) goto req_alloc_failed; bio_req->bh = &req->bh[b]; bio = bio_alloc(GFP_NOIO, bio_max_pages); if (!bio) goto bio_alloc_failed; bio->bi_bdev = req->sb->s_bdev; bio->bi_iter.bi_sector = (block + b) << (msblk->devblksize_log2 - 9); bio->bi_private = bio_req; bio->bi_end_io = squashfs_bio_end_io; bio_add_page(bio, bh->b_page, blksz, offset); bio_req->nr_buffers += 1; prev_block = b; } if (bio) submit_bio(READ, bio); if (req->synchronous) squashfs_process_blocks(req); else { INIT_WORK(&req->offload, read_wq_handler); schedule_work(&req->offload); } return 0; bio_alloc_failed: kfree(bio_req); req_alloc_failed: unlock_buffer(bh); while (--nr_buffers >= b) if (req->bh[nr_buffers]) put_bh(req->bh[nr_buffers]); while (--b >= 0) if (req->bh[b]) wait_on_buffer(req->bh[b]); getblk_failed: free_read_request(req, -ENOMEM); return -ENOMEM; } static int read_metadata_block(struct squashfs_read_request *req, u64 *next_index) { int ret, error, bytes_read = 0, bytes_uncompressed = 0; struct squashfs_sb_info *msblk = req->sb->s_fs_info; if (req->index + 2 > msblk->bytes_used) { free_read_request(req, -EINVAL); return -EINVAL; } req->length = 2; /* Do not read beyond the end of the device */ if (req->index + req->length > msblk->bytes_used) req->length = msblk->bytes_used - req->index; req->data_processing = SQUASHFS_METADATA; /* * Reading metadata is always synchronous because we don't know the * length in advance and the function is expected to update * 'next_index' and return the length. */ req->synchronous = true; req->res = &error; req->bytes_read = &bytes_read; req->bytes_uncompressed = &bytes_uncompressed; TRACE("Metadata block @ 0x%llx, %scompressed size %d, src size %d\n", req->index, req->compressed ? "" : "un", bytes_read, req->output->length); ret = squashfs_bio_submit(req); if (ret) return ret; if (error) return error; if (next_index) *next_index += 2 + bytes_read; return bytes_uncompressed; } static int read_data_block(struct squashfs_read_request *req, int length, u64 *next_index, bool synchronous) { int ret, error = 0, bytes_uncompressed = 0, bytes_read = 0; req->compressed = SQUASHFS_COMPRESSED_BLOCK(length); req->length = length = SQUASHFS_COMPRESSED_SIZE_BLOCK(length); req->data_processing = req->compressed ? SQUASHFS_DECOMPRESS : SQUASHFS_COPY; req->synchronous = synchronous; if (synchronous) { req->res = &error; req->bytes_read = &bytes_read; req->bytes_uncompressed = &bytes_uncompressed; } TRACE("Data block @ 0x%llx, %scompressed size %d, src size %d\n", req->index, req->compressed ? "" : "un", req->length, req->output->length); ret = squashfs_bio_submit(req); if (ret) return ret; if (synchronous) ret = error ? error : bytes_uncompressed; if (next_index) *next_index += length; return ret; } /* * Read and decompress a metadata block or datablock. Length is non-zero * if a datablock is being read (the size is stored elsewhere in the * filesystem), otherwise the length is obtained from the first two bytes of * the metadata block. A bit in the length field indicates if the block * is stored uncompressed in the filesystem (usually because compression * generated a larger block - this does occasionally happen with compression * algorithms). */ static int __squashfs_read_data(struct super_block *sb, u64 index, int length, u64 *next_index, struct squashfs_page_actor *output, bool sync) { struct squashfs_read_request *req; req = kcalloc(1, sizeof(struct squashfs_read_request), GFP_KERNEL); if (!req) { if (!sync) squashfs_page_actor_free(output, -ENOMEM); return -ENOMEM; } req->sb = sb; req->index = index; req->output = output; if (next_index) *next_index = index; if (length) length = read_data_block(req, length, next_index, sync); else length = read_metadata_block(req, next_index); if (length < 0) { ERROR("squashfs_read_data failed to read block 0x%llx\n", (unsigned long long)index); return -EIO; } return length; } int squashfs_read_data(struct super_block *sb, u64 index, int length, u64 *next_index, struct squashfs_page_actor *output) { return __squashfs_read_data(sb, index, length, next_index, output, true); } int squashfs_read_data_async(struct super_block *sb, u64 index, int length, u64 *next_index, struct squashfs_page_actor *output) { return __squashfs_read_data(sb, index, length, next_index, output, false); }