1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Squashfs - a compressed read only filesystem for Linux |
4 | * |
5 | * Copyright (c) 2016-present, Facebook, Inc. |
6 | * All rights reserved. |
7 | * |
8 | * zstd_wrapper.c |
9 | */ |
10 | |
11 | #include <linux/mutex.h> |
12 | #include <linux/bio.h> |
13 | #include <linux/slab.h> |
14 | #include <linux/zstd.h> |
15 | #include <linux/vmalloc.h> |
16 | |
17 | #include "squashfs_fs.h" |
18 | #include "squashfs_fs_sb.h" |
19 | #include "squashfs.h" |
20 | #include "decompressor.h" |
21 | #include "page_actor.h" |
22 | |
23 | struct workspace { |
24 | void *mem; |
25 | size_t mem_size; |
26 | size_t window_size; |
27 | }; |
28 | |
29 | static void *zstd_init(struct squashfs_sb_info *msblk, void *buff) |
30 | { |
31 | struct workspace *wksp = kmalloc(size: sizeof(*wksp), GFP_KERNEL); |
32 | |
33 | if (wksp == NULL) |
34 | goto failed; |
35 | wksp->window_size = max_t(size_t, |
36 | msblk->block_size, SQUASHFS_METADATA_SIZE); |
37 | wksp->mem_size = zstd_dstream_workspace_bound(max_window_size: wksp->window_size); |
38 | wksp->mem = vmalloc(size: wksp->mem_size); |
39 | if (wksp->mem == NULL) |
40 | goto failed; |
41 | |
42 | return wksp; |
43 | |
44 | failed: |
45 | ERROR("Failed to allocate zstd workspace\n" ); |
46 | kfree(objp: wksp); |
47 | return ERR_PTR(error: -ENOMEM); |
48 | } |
49 | |
50 | |
51 | static void zstd_free(void *strm) |
52 | { |
53 | struct workspace *wksp = strm; |
54 | |
55 | if (wksp) |
56 | vfree(addr: wksp->mem); |
57 | kfree(objp: wksp); |
58 | } |
59 | |
60 | |
61 | static int zstd_uncompress(struct squashfs_sb_info *msblk, void *strm, |
62 | struct bio *bio, int offset, int length, |
63 | struct squashfs_page_actor *output) |
64 | { |
65 | struct workspace *wksp = strm; |
66 | zstd_dstream *stream; |
67 | size_t total_out = 0; |
68 | int error = 0; |
69 | zstd_in_buffer in_buf = { NULL, 0, 0 }; |
70 | zstd_out_buffer out_buf = { NULL, 0, 0 }; |
71 | struct bvec_iter_all iter_all = {}; |
72 | struct bio_vec *bvec = bvec_init_iter_all(iter_all: &iter_all); |
73 | |
74 | stream = zstd_init_dstream(max_window_size: wksp->window_size, workspace: wksp->mem, workspace_size: wksp->mem_size); |
75 | |
76 | if (!stream) { |
77 | ERROR("Failed to initialize zstd decompressor\n" ); |
78 | return -EIO; |
79 | } |
80 | |
81 | out_buf.size = PAGE_SIZE; |
82 | out_buf.dst = squashfs_first_page(actor: output); |
83 | if (IS_ERR(ptr: out_buf.dst)) { |
84 | error = PTR_ERR(ptr: out_buf.dst); |
85 | goto finish; |
86 | } |
87 | |
88 | for (;;) { |
89 | size_t zstd_err; |
90 | |
91 | if (in_buf.pos == in_buf.size) { |
92 | const void *data; |
93 | int avail; |
94 | |
95 | if (!bio_next_segment(bio, iter: &iter_all)) { |
96 | error = -EIO; |
97 | break; |
98 | } |
99 | |
100 | avail = min(length, ((int)bvec->bv_len) - offset); |
101 | data = bvec_virt(bvec); |
102 | length -= avail; |
103 | in_buf.src = data + offset; |
104 | in_buf.size = avail; |
105 | in_buf.pos = 0; |
106 | offset = 0; |
107 | } |
108 | |
109 | if (out_buf.pos == out_buf.size) { |
110 | out_buf.dst = squashfs_next_page(actor: output); |
111 | if (IS_ERR(ptr: out_buf.dst)) { |
112 | error = PTR_ERR(ptr: out_buf.dst); |
113 | break; |
114 | } else if (out_buf.dst == NULL) { |
115 | /* Shouldn't run out of pages |
116 | * before stream is done. |
117 | */ |
118 | error = -EIO; |
119 | break; |
120 | } |
121 | out_buf.pos = 0; |
122 | out_buf.size = PAGE_SIZE; |
123 | } |
124 | |
125 | total_out -= out_buf.pos; |
126 | zstd_err = zstd_decompress_stream(dstream: stream, output: &out_buf, input: &in_buf); |
127 | total_out += out_buf.pos; /* add the additional data produced */ |
128 | if (zstd_err == 0) |
129 | break; |
130 | |
131 | if (zstd_is_error(code: zstd_err)) { |
132 | ERROR("zstd decompression error: %d\n" , |
133 | (int)zstd_get_error_code(zstd_err)); |
134 | error = -EIO; |
135 | break; |
136 | } |
137 | } |
138 | |
139 | finish: |
140 | |
141 | squashfs_finish_page(actor: output); |
142 | |
143 | return error ? error : total_out; |
144 | } |
145 | |
146 | const struct squashfs_decompressor squashfs_zstd_comp_ops = { |
147 | .init = zstd_init, |
148 | .free = zstd_free, |
149 | .decompress = zstd_uncompress, |
150 | .id = ZSTD_COMPRESSION, |
151 | .name = "zstd" , |
152 | .alloc_buffer = 1, |
153 | .supported = 1 |
154 | }; |
155 | |