1 // SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0
2 /*
3 * erofs-utils/lib/blobchunk.c
4 *
5 * Copyright (C) 2021, Alibaba Cloud
6 */
7 #define _GNU_SOURCE
8 #include "erofs/hashmap.h"
9 #include "erofs/blobchunk.h"
10 #include "erofs/block_list.h"
11 #include "erofs/cache.h"
12 #include "erofs/io.h"
13 #include <unistd.h>
14
15 void erofs_sha256(const unsigned char *in, unsigned long in_size,
16 unsigned char out[32]);
17
18 struct erofs_blobchunk {
19 struct hashmap_entry ent;
20 char sha256[32];
21 unsigned int chunksize;
22 erofs_blk_t blkaddr;
23 };
24
25 static struct hashmap blob_hashmap;
26 static FILE *blobfile;
27 static erofs_blk_t remapped_base;
28 static bool multidev;
29 static struct erofs_buffer_head *bh_devt;
30
erofs_blob_getchunk(int fd,unsigned int chunksize)31 static struct erofs_blobchunk *erofs_blob_getchunk(int fd,
32 unsigned int chunksize)
33 {
34 static u8 zeroed[EROFS_BLKSIZ];
35 u8 *chunkdata, sha256[32];
36 int ret;
37 unsigned int hash;
38 erofs_off_t blkpos;
39 struct erofs_blobchunk *chunk;
40
41 chunkdata = malloc(chunksize);
42 if (!chunkdata)
43 return ERR_PTR(-ENOMEM);
44
45 ret = read(fd, chunkdata, chunksize);
46 if (ret < chunksize) {
47 chunk = ERR_PTR(-EIO);
48 goto out;
49 }
50 erofs_sha256(chunkdata, chunksize, sha256);
51 hash = memhash(sha256, sizeof(sha256));
52 chunk = hashmap_get_from_hash(&blob_hashmap, hash, sha256);
53 if (chunk) {
54 DBG_BUGON(chunksize != chunk->chunksize);
55 goto out;
56 }
57 chunk = malloc(sizeof(struct erofs_blobchunk));
58 if (!chunk) {
59 chunk = ERR_PTR(-ENOMEM);
60 goto out;
61 }
62
63 chunk->chunksize = chunksize;
64 blkpos = ftell(blobfile);
65 DBG_BUGON(erofs_blkoff(blkpos));
66 chunk->blkaddr = erofs_blknr(blkpos);
67 memcpy(chunk->sha256, sha256, sizeof(sha256));
68 hashmap_entry_init(&chunk->ent, hash);
69 hashmap_add(&blob_hashmap, chunk);
70
71 erofs_dbg("Writing chunk (%u bytes) to %u", chunksize, chunk->blkaddr);
72 ret = fwrite(chunkdata, chunksize, 1, blobfile);
73 if (ret == 1 && erofs_blkoff(chunksize))
74 ret = fwrite(zeroed, EROFS_BLKSIZ - erofs_blkoff(chunksize),
75 1, blobfile);
76 if (ret < 1) {
77 struct hashmap_entry key;
78
79 hashmap_entry_init(&key, hash);
80 hashmap_remove(&blob_hashmap, &key, sha256);
81 free(chunk);
82 chunk = ERR_PTR(-ENOSPC);
83 goto out;
84 }
85 out:
86 free(chunkdata);
87 return chunk;
88 }
89
erofs_blob_hashmap_cmp(const void * a,const void * b,const void * key)90 static int erofs_blob_hashmap_cmp(const void *a, const void *b,
91 const void *key)
92 {
93 const struct erofs_blobchunk *ec1 =
94 container_of((struct hashmap_entry *)a,
95 struct erofs_blobchunk, ent);
96 const struct erofs_blobchunk *ec2 =
97 container_of((struct hashmap_entry *)b,
98 struct erofs_blobchunk, ent);
99
100 return memcmp(ec1->sha256, key ? key : ec2->sha256,
101 sizeof(ec1->sha256));
102 }
103
erofs_blob_write_chunk_indexes(struct erofs_inode * inode,erofs_off_t off)104 int erofs_blob_write_chunk_indexes(struct erofs_inode *inode,
105 erofs_off_t off)
106 {
107 struct erofs_inode_chunk_index idx = {0};
108 erofs_blk_t extent_start = EROFS_NULL_ADDR;
109 erofs_blk_t extent_end, extents_blks;
110 unsigned int dst, src, unit;
111 bool first_extent = true;
112 erofs_blk_t base_blkaddr = 0;
113
114 if (multidev) {
115 idx.device_id = 1;
116 DBG_BUGON(!(inode->u.chunkformat & EROFS_CHUNK_FORMAT_INDEXES));
117 } else {
118 base_blkaddr = remapped_base;
119 }
120
121 if (inode->u.chunkformat & EROFS_CHUNK_FORMAT_INDEXES)
122 unit = sizeof(struct erofs_inode_chunk_index);
123 else
124 unit = EROFS_BLOCK_MAP_ENTRY_SIZE;
125
126 for (dst = src = 0; dst < inode->extent_isize;
127 src += sizeof(void *), dst += unit) {
128 struct erofs_blobchunk *chunk;
129
130 chunk = *(void **)(inode->chunkindexes + src);
131
132 idx.blkaddr = base_blkaddr + chunk->blkaddr;
133 if (extent_start != EROFS_NULL_ADDR &&
134 idx.blkaddr == extent_end + 1) {
135 extent_end = idx.blkaddr;
136 } else {
137 if (extent_start != EROFS_NULL_ADDR) {
138 erofs_droid_blocklist_write_extent(inode,
139 extent_start,
140 (extent_end - extent_start) + 1,
141 first_extent, false);
142 first_extent = false;
143 }
144 extent_start = idx.blkaddr;
145 extent_end = idx.blkaddr;
146 }
147 if (unit == EROFS_BLOCK_MAP_ENTRY_SIZE)
148 memcpy(inode->chunkindexes + dst, &idx.blkaddr, unit);
149 else
150 memcpy(inode->chunkindexes + dst, &idx, sizeof(idx));
151 }
152 off = roundup(off, unit);
153
154 if (extent_start == EROFS_NULL_ADDR)
155 extents_blks = 0;
156 else
157 extents_blks = (extent_end - extent_start) + 1;
158 erofs_droid_blocklist_write_extent(inode, extent_start, extents_blks,
159 first_extent, true);
160
161 return dev_write(inode->chunkindexes, off, inode->extent_isize);
162 }
163
erofs_blob_write_chunked_file(struct erofs_inode * inode)164 int erofs_blob_write_chunked_file(struct erofs_inode *inode)
165 {
166 unsigned int chunksize = 1 << cfg.c_chunkbits;
167 unsigned int count = DIV_ROUND_UP(inode->i_size, chunksize);
168 struct erofs_inode_chunk_index *idx;
169 erofs_off_t pos, len;
170 unsigned int unit;
171 int fd, ret;
172
173 inode->u.chunkformat |= inode->u.chunkbits - LOG_BLOCK_SIZE;
174 if (multidev)
175 inode->u.chunkformat |= EROFS_CHUNK_FORMAT_INDEXES;
176
177 if (inode->u.chunkformat & EROFS_CHUNK_FORMAT_INDEXES)
178 unit = sizeof(struct erofs_inode_chunk_index);
179 else
180 unit = EROFS_BLOCK_MAP_ENTRY_SIZE;
181
182 inode->extent_isize = count * unit;
183 idx = malloc(count * max(sizeof(*idx), sizeof(void *)));
184 if (!idx)
185 return -ENOMEM;
186 inode->chunkindexes = idx;
187
188 fd = open(inode->i_srcpath, O_RDONLY | O_BINARY);
189 if (fd < 0) {
190 ret = -errno;
191 goto err;
192 }
193
194 for (pos = 0; pos < inode->i_size; pos += len) {
195 struct erofs_blobchunk *chunk;
196
197 len = min_t(u64, inode->i_size - pos, chunksize);
198 chunk = erofs_blob_getchunk(fd, len);
199 if (IS_ERR(chunk)) {
200 ret = PTR_ERR(chunk);
201 close(fd);
202 goto err;
203 }
204 *(void **)idx++ = chunk;
205 }
206 inode->datalayout = EROFS_INODE_CHUNK_BASED;
207 close(fd);
208 return 0;
209 err:
210 free(inode->chunkindexes);
211 inode->chunkindexes = NULL;
212 return ret;
213 }
214
erofs_blob_remap(void)215 int erofs_blob_remap(void)
216 {
217 struct erofs_buffer_head *bh;
218 ssize_t length;
219 erofs_off_t pos_in, pos_out;
220 ssize_t ret;
221
222 fflush(blobfile);
223 length = ftell(blobfile);
224 if (length < 0)
225 return -errno;
226 if (multidev) {
227 struct erofs_deviceslot dis = {
228 .blocks = erofs_blknr(length),
229 };
230
231 pos_out = erofs_btell(bh_devt, false);
232 ret = dev_write(&dis, pos_out, sizeof(dis));
233 if (ret)
234 return ret;
235
236 bh_devt->op = &erofs_drop_directly_bhops;
237 erofs_bdrop(bh_devt, false);
238 return 0;
239 }
240 bh = erofs_balloc(DATA, length, 0, 0);
241 if (IS_ERR(bh))
242 return PTR_ERR(bh);
243
244 erofs_mapbh(bh->block);
245 pos_out = erofs_btell(bh, false);
246 pos_in = 0;
247 remapped_base = erofs_blknr(pos_out);
248 ret = erofs_copy_file_range(fileno(blobfile), &pos_in,
249 erofs_devfd, &pos_out, length);
250 bh->op = &erofs_drop_directly_bhops;
251 erofs_bdrop(bh, false);
252 return ret < length ? -EIO : 0;
253 }
254
erofs_blob_exit(void)255 void erofs_blob_exit(void)
256 {
257 if (blobfile)
258 fclose(blobfile);
259
260 hashmap_free(&blob_hashmap, 1);
261 }
262
erofs_blob_init(const char * blobfile_path)263 int erofs_blob_init(const char *blobfile_path)
264 {
265 if (!blobfile_path) {
266 #ifdef HAVE_TMPFILE64
267 blobfile = tmpfile64();
268 #else
269 blobfile = tmpfile();
270 #endif
271 multidev = false;
272 } else {
273 blobfile = fopen(blobfile_path, "wb");
274 multidev = true;
275 }
276 if (!blobfile)
277 return -EACCES;
278
279 hashmap_init(&blob_hashmap, erofs_blob_hashmap_cmp, 0);
280 return 0;
281 }
282
erofs_generate_devtable(void)283 int erofs_generate_devtable(void)
284 {
285 struct erofs_deviceslot dis;
286
287 if (!multidev)
288 return 0;
289
290 bh_devt = erofs_balloc(DEVT, sizeof(dis), 0, 0);
291 if (IS_ERR(bh_devt))
292 return PTR_ERR(bh_devt);
293
294 dis = (struct erofs_deviceslot) {};
295 erofs_mapbh(bh_devt->block);
296 bh_devt->op = &erofs_skip_write_bhops;
297 sbi.devt_slotoff = erofs_btell(bh_devt, false) / EROFS_DEVT_SLOT_SIZE;
298 sbi.extra_devices = 1;
299 erofs_sb_set_device_table();
300 return 0;
301 }
302