page_actor.c 3.35 KB
/*
 * Copyright (c) 2013
 * Phillip Lougher <phillip@squashfs.org.uk>
 *
 * This work is licensed under the terms of the GNU GPL, version 2. See
 * the COPYING file in the top-level directory.
 */

#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/pagemap.h>
#include <linux/buffer_head.h>
#include "page_actor.h"

struct squashfs_page_actor *squashfs_page_actor_init(struct page **page,
	int pages, int length, void (*release_pages)(struct page **, int, int))
{
	struct squashfs_page_actor *actor = kmalloc(sizeof(*actor), GFP_KERNEL);

	if (actor == NULL)
		return NULL;

	actor->length = length ? : pages * PAGE_SIZE;
	actor->page = page;
	actor->pages = pages;
	actor->next_page = 0;
	actor->pageaddr = NULL;
	actor->release_pages = release_pages;
	return actor;
}

void squashfs_page_actor_free(struct squashfs_page_actor *actor, int error)
{
	if (!actor)
		return;

	if (actor->release_pages)
		actor->release_pages(actor->page, actor->pages, error);
	kfree(actor);
}

void squashfs_actor_to_buf(struct squashfs_page_actor *actor, void *buf,
	int length)
{
	void *pageaddr;
	int pos = 0, avail, i;

	for (i = 0; i < actor->pages && pos < length; ++i) {
		avail = min_t(int, length - pos, PAGE_SIZE);
		if (actor->page[i]) {
			pageaddr = kmap_atomic(actor->page[i]);
			memcpy(buf + pos, pageaddr, avail);
			kunmap_atomic(pageaddr);
		}
		pos += avail;
	}
}

void squashfs_buf_to_actor(void *buf, struct squashfs_page_actor *actor,
	int length)
{
	void *pageaddr;
	int pos = 0, avail, i;

	for (i = 0; i < actor->pages && pos < length; ++i) {
		avail = min_t(int, length - pos, PAGE_SIZE);
		if (actor->page[i]) {
			pageaddr = kmap_atomic(actor->page[i]);
			memcpy(pageaddr, buf + pos, avail);
			kunmap_atomic(pageaddr);
		}
		pos += avail;
	}
}

void squashfs_bh_to_actor(struct buffer_head **bh, int nr_buffers,
	struct squashfs_page_actor *actor, int offset, int length, int blksz)
{
	void *kaddr = NULL;
	int bytes = 0, pgoff = 0, b = 0, p = 0, avail, i;

	while (bytes < length) {
		if (actor->page[p]) {
			kaddr = kmap_atomic(actor->page[p]);
			while (pgoff < PAGE_SIZE && bytes < length) {
				avail = min_t(int, blksz - offset,
						PAGE_SIZE - pgoff);
				memcpy(kaddr + pgoff, bh[b]->b_data + offset,
				       avail);
				pgoff += avail;
				bytes += avail;
				offset = (offset + avail) % blksz;
				if (!offset) {
					put_bh(bh[b]);
					++b;
				}
			}
			kunmap_atomic(kaddr);
			pgoff = 0;
		} else {
			for (i = 0; i < PAGE_SIZE / blksz; ++i) {
				if (bh[b])
					put_bh(bh[b]);
				++b;
			}
			bytes += PAGE_SIZE;
		}
		++p;
	}
}

void squashfs_bh_to_buf(struct buffer_head **bh, int nr_buffers, void *buf,
	int offset, int length, int blksz)
{
	int i, avail, bytes = 0;

	for (i = 0; i < nr_buffers && bytes < length; ++i) {
		avail = min_t(int, length - bytes, blksz - offset);
		if (bh[i]) {
			memcpy(buf + bytes, bh[i]->b_data + offset, avail);
			put_bh(bh[i]);
		}
		bytes += avail;
		offset = 0;
	}
}

void free_page_array(struct page **page, int nr_pages)
{
	int i;

	for (i = 0; i < nr_pages; ++i)
		__free_page(page[i]);
	kfree(page);
}

struct page **alloc_page_array(int nr_pages, int gfp_mask)
{
	int i;
	struct page **page;

	page = kcalloc(nr_pages, sizeof(struct page *), gfp_mask);
	if (!page)
		return NULL;
	for (i = 0; i < nr_pages; ++i) {
		page[i] = alloc_page(gfp_mask);
		if (!page[i]) {
			free_page_array(page, i);
			return NULL;
		}
	}
	return page;
}