// SPDX-License-Identifier: GPL-2.0-or-later 
 | 
/* 
 | 
 * Squashfs - a compressed read only filesystem for Linux 
 | 
 * 
 | 
 * Copyright (c) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 
 | 
 * Phillip Lougher <phillip@squashfs.org.uk> 
 | 
 * 
 | 
 * xz_wrapper.c 
 | 
 */ 
 | 
  
 | 
  
 | 
#include <linux/mutex.h> 
 | 
#include <linux/bio.h> 
 | 
#include <linux/slab.h> 
 | 
#include <linux/xz.h> 
 | 
#include <linux/bitops.h> 
 | 
  
 | 
#include "squashfs_fs.h" 
 | 
#include "squashfs_fs_sb.h" 
 | 
#include "squashfs.h" 
 | 
#include "decompressor.h" 
 | 
#include "page_actor.h" 
 | 
  
 | 
struct squashfs_xz { 
 | 
    struct xz_dec *state; 
 | 
    struct xz_buf buf; 
 | 
}; 
 | 
  
 | 
struct disk_comp_opts { 
 | 
    __le32 dictionary_size; 
 | 
    __le32 flags; 
 | 
}; 
 | 
  
 | 
struct comp_opts { 
 | 
    int dict_size; 
 | 
}; 
 | 
  
 | 
static void *squashfs_xz_comp_opts(struct squashfs_sb_info *msblk, 
 | 
    void *buff, int len) 
 | 
{ 
 | 
    struct disk_comp_opts *comp_opts = buff; 
 | 
    struct comp_opts *opts; 
 | 
    int err = 0, n; 
 | 
  
 | 
    opts = kmalloc(sizeof(*opts), GFP_KERNEL); 
 | 
    if (opts == NULL) { 
 | 
        err = -ENOMEM; 
 | 
        goto out2; 
 | 
    } 
 | 
  
 | 
    if (comp_opts) { 
 | 
        /* check compressor options are the expected length */ 
 | 
        if (len < sizeof(*comp_opts)) { 
 | 
            err = -EIO; 
 | 
            goto out; 
 | 
        } 
 | 
  
 | 
        opts->dict_size = le32_to_cpu(comp_opts->dictionary_size); 
 | 
  
 | 
        /* the dictionary size should be 2^n or 2^n+2^(n+1) */ 
 | 
        n = ffs(opts->dict_size) - 1; 
 | 
        if (opts->dict_size != (1 << n) && opts->dict_size != (1 << n) + 
 | 
                        (1 << (n + 1))) { 
 | 
            err = -EIO; 
 | 
            goto out; 
 | 
        } 
 | 
    } else 
 | 
        /* use defaults */ 
 | 
        opts->dict_size = max_t(int, msblk->block_size, 
 | 
                            SQUASHFS_METADATA_SIZE); 
 | 
  
 | 
    return opts; 
 | 
  
 | 
out: 
 | 
    kfree(opts); 
 | 
out2: 
 | 
    return ERR_PTR(err); 
 | 
} 
 | 
  
 | 
  
 | 
static void *squashfs_xz_init(struct squashfs_sb_info *msblk, void *buff) 
 | 
{ 
 | 
    struct comp_opts *comp_opts = buff; 
 | 
    struct squashfs_xz *stream; 
 | 
    int err; 
 | 
  
 | 
    stream = kmalloc(sizeof(*stream), GFP_KERNEL); 
 | 
    if (stream == NULL) { 
 | 
        err = -ENOMEM; 
 | 
        goto failed; 
 | 
    } 
 | 
  
 | 
    stream->state = xz_dec_init(XZ_PREALLOC, comp_opts->dict_size); 
 | 
    if (stream->state == NULL) { 
 | 
        kfree(stream); 
 | 
        err = -ENOMEM; 
 | 
        goto failed; 
 | 
    } 
 | 
  
 | 
    return stream; 
 | 
  
 | 
failed: 
 | 
    ERROR("Failed to initialise xz decompressor\n"); 
 | 
    return ERR_PTR(err); 
 | 
} 
 | 
  
 | 
  
 | 
static void squashfs_xz_free(void *strm) 
 | 
{ 
 | 
    struct squashfs_xz *stream = strm; 
 | 
  
 | 
    if (stream) { 
 | 
        xz_dec_end(stream->state); 
 | 
        kfree(stream); 
 | 
    } 
 | 
} 
 | 
  
 | 
  
 | 
static int squashfs_xz_uncompress(struct squashfs_sb_info *msblk, void *strm, 
 | 
    struct bio *bio, int offset, int length, 
 | 
    struct squashfs_page_actor *output) 
 | 
{ 
 | 
    struct bvec_iter_all iter_all = {}; 
 | 
    struct bio_vec *bvec = bvec_init_iter_all(&iter_all); 
 | 
    int total = 0, error = 0; 
 | 
    struct squashfs_xz *stream = strm; 
 | 
  
 | 
    xz_dec_reset(stream->state); 
 | 
    stream->buf.in_pos = 0; 
 | 
    stream->buf.in_size = 0; 
 | 
    stream->buf.out_pos = 0; 
 | 
    stream->buf.out_size = PAGE_SIZE; 
 | 
    stream->buf.out = squashfs_first_page(output); 
 | 
  
 | 
    for (;;) { 
 | 
        enum xz_ret xz_err; 
 | 
  
 | 
        if (stream->buf.in_pos == stream->buf.in_size) { 
 | 
            const void *data; 
 | 
            int avail; 
 | 
  
 | 
            if (!bio_next_segment(bio, &iter_all)) { 
 | 
                /* XZ_STREAM_END must be reached. */ 
 | 
                error = -EIO; 
 | 
                break; 
 | 
            } 
 | 
  
 | 
            avail = min(length, ((int)bvec->bv_len) - offset); 
 | 
            data = page_address(bvec->bv_page) + bvec->bv_offset; 
 | 
            length -= avail; 
 | 
            stream->buf.in = data + offset; 
 | 
            stream->buf.in_size = avail; 
 | 
            stream->buf.in_pos = 0; 
 | 
            offset = 0; 
 | 
        } 
 | 
  
 | 
        if (stream->buf.out_pos == stream->buf.out_size) { 
 | 
            stream->buf.out = squashfs_next_page(output); 
 | 
            if (stream->buf.out != NULL) { 
 | 
                stream->buf.out_pos = 0; 
 | 
                total += PAGE_SIZE; 
 | 
            } 
 | 
        } 
 | 
  
 | 
        xz_err = xz_dec_run(stream->state, &stream->buf); 
 | 
        if (xz_err == XZ_STREAM_END) 
 | 
            break; 
 | 
        if (xz_err != XZ_OK) { 
 | 
            error = -EIO; 
 | 
            break; 
 | 
        } 
 | 
    } 
 | 
  
 | 
    squashfs_finish_page(output); 
 | 
  
 | 
    return error ? error : total + stream->buf.out_pos; 
 | 
} 
 | 
  
 | 
const struct squashfs_decompressor squashfs_xz_comp_ops = { 
 | 
    .init = squashfs_xz_init, 
 | 
    .comp_opts = squashfs_xz_comp_opts, 
 | 
    .free = squashfs_xz_free, 
 | 
    .decompress = squashfs_xz_uncompress, 
 | 
    .id = XZ_COMPRESSION, 
 | 
    .name = "xz", 
 | 
    .supported = 1 
 | 
}; 
 |