]>
Commit | Line | Data |
---|---|---|
3d8226ed GSB |
1 | /* |
2 | * libkmod - interface to kernel module operations | |
3 | * | |
e6b0e49b | 4 | * Copyright (C) 2011-2013 ProFUSION embedded systems |
3d8226ed GSB |
5 | * |
6 | * This library is free software; you can redistribute it and/or | |
7 | * modify it under the terms of the GNU Lesser General Public | |
8 | * License as published by the Free Software Foundation; either | |
9 | * version 2.1 of the License, or (at your option) any later version. | |
10 | * | |
11 | * This library is distributed in the hope that it will be useful, | |
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
14 | * Lesser General Public License for more details. | |
15 | * | |
16 | * You should have received a copy of the GNU Lesser General Public | |
dea2dfee | 17 | * License along with this library; if not, see <http://www.gnu.org/licenses/>. |
3d8226ed GSB |
18 | */ |
19 | ||
c2e4286b | 20 | #include <errno.h> |
b182f8fb | 21 | #include <stdbool.h> |
3d8226ed GSB |
22 | #include <stdio.h> |
23 | #include <stdlib.h> | |
3d8226ed | 24 | #include <string.h> |
3d8226ed | 25 | #include <sys/mman.h> |
c2e4286b LDM |
26 | #include <sys/stat.h> |
27 | #include <sys/types.h> | |
3d8226ed | 28 | #include <unistd.h> |
3821e197 TM |
29 | #ifdef ENABLE_ZSTD |
30 | #include <zstd.h> | |
31 | #endif | |
b182f8fb JE |
32 | #ifdef ENABLE_XZ |
33 | #include <lzma.h> | |
34 | #endif | |
3d8226ed GSB |
35 | #ifdef ENABLE_ZLIB |
36 | #include <zlib.h> | |
37 | #endif | |
38 | ||
c2e4286b LDM |
39 | #include <shared/util.h> |
40 | ||
41 | #include "libkmod.h" | |
42 | #include "libkmod-internal.h" | |
43 | ||
3d8226ed | 44 | struct kmod_file { |
bb417099 | 45 | int fd; |
e5398276 | 46 | enum kmod_file_compression_type compression; |
3d8226ed GSB |
47 | off_t size; |
48 | void *memory; | |
ad158923 | 49 | int (*load)(struct kmod_file *file); |
c68e92f7 | 50 | const struct kmod_ctx *ctx; |
1eff942e | 51 | struct kmod_elf *elf; |
3d8226ed GSB |
52 | }; |
53 | ||
3821e197 TM |
54 | #ifdef ENABLE_ZSTD |
55 | static int zstd_read_block(struct kmod_file *file, size_t block_size, | |
56 | ZSTD_inBuffer *input, size_t *input_capacity) | |
57 | { | |
58 | ssize_t rdret; | |
59 | int ret; | |
60 | ||
61 | if (*input_capacity < block_size) { | |
62 | free((void *)input->src); | |
63 | input->src = malloc(block_size); | |
64 | if (input->src == NULL) { | |
65 | ret = -errno; | |
66 | ERR(file->ctx, "zstd: %m\n"); | |
67 | return ret; | |
68 | } | |
69 | *input_capacity = block_size; | |
70 | } | |
71 | ||
72 | rdret = read(file->fd, (void *)input->src, block_size); | |
73 | if (rdret < 0) { | |
74 | ret = -errno; | |
75 | ERR(file->ctx, "zstd: %m\n"); | |
76 | return ret; | |
77 | } | |
78 | ||
79 | input->pos = 0; | |
80 | input->size = rdret; | |
81 | return 0; | |
82 | } | |
83 | ||
84 | static int zstd_ensure_outbuffer_space(ZSTD_outBuffer *buffer, size_t min_free) | |
85 | { | |
86 | uint8_t *old_buffer = buffer->dst; | |
87 | int ret = 0; | |
88 | ||
89 | if (buffer->size - buffer->pos >= min_free) | |
90 | return 0; | |
91 | ||
92 | buffer->size += min_free; | |
93 | buffer->dst = realloc(buffer->dst, buffer->size); | |
94 | if (buffer->dst == NULL) { | |
95 | ret = -errno; | |
96 | free(old_buffer); | |
97 | } | |
98 | ||
99 | return ret; | |
100 | } | |
101 | ||
102 | static int zstd_decompress_block(struct kmod_file *file, ZSTD_DStream *dstr, | |
103 | ZSTD_inBuffer *input, ZSTD_outBuffer *output, | |
104 | size_t *next_block_size) | |
105 | { | |
106 | size_t out_buf_min_size = ZSTD_DStreamOutSize(); | |
107 | int ret = 0; | |
108 | ||
109 | do { | |
110 | ssize_t dsret; | |
111 | ||
112 | ret = zstd_ensure_outbuffer_space(output, out_buf_min_size); | |
113 | if (ret) { | |
114 | ERR(file->ctx, "zstd: %s\n", strerror(-ret)); | |
115 | break; | |
116 | } | |
117 | ||
118 | dsret = ZSTD_decompressStream(dstr, output, input); | |
119 | if (ZSTD_isError(dsret)) { | |
120 | ret = -EINVAL; | |
121 | ERR(file->ctx, "zstd: %s\n", ZSTD_getErrorName(dsret)); | |
122 | break; | |
123 | } | |
124 | if (dsret > 0) | |
125 | *next_block_size = (size_t)dsret; | |
126 | } while (input->pos < input->size | |
127 | || output->pos > output->size | |
128 | || output->size - output->pos < out_buf_min_size); | |
129 | ||
130 | return ret; | |
131 | } | |
132 | ||
133 | static int load_zstd(struct kmod_file *file) | |
134 | { | |
135 | ZSTD_DStream *dstr; | |
136 | size_t next_block_size; | |
137 | size_t zst_inb_capacity = 0; | |
138 | ZSTD_inBuffer zst_inb = { 0 }; | |
139 | ZSTD_outBuffer zst_outb = { 0 }; | |
140 | int ret; | |
141 | ||
142 | dstr = ZSTD_createDStream(); | |
143 | if (dstr == NULL) { | |
144 | ret = -EINVAL; | |
145 | ERR(file->ctx, "zstd: Failed to create decompression stream\n"); | |
146 | goto out; | |
147 | } | |
148 | ||
149 | next_block_size = ZSTD_initDStream(dstr); | |
150 | ||
151 | while (true) { | |
152 | ret = zstd_read_block(file, next_block_size, &zst_inb, | |
153 | &zst_inb_capacity); | |
154 | if (ret != 0) | |
155 | goto out; | |
156 | if (zst_inb.size == 0) /* EOF */ | |
157 | break; | |
158 | ||
159 | ret = zstd_decompress_block(file, dstr, &zst_inb, &zst_outb, | |
160 | &next_block_size); | |
161 | if (ret != 0) | |
162 | goto out; | |
163 | } | |
164 | ||
165 | ZSTD_freeDStream(dstr); | |
166 | free((void *)zst_inb.src); | |
3821e197 TM |
167 | file->memory = zst_outb.dst; |
168 | file->size = zst_outb.pos; | |
169 | return 0; | |
170 | out: | |
171 | if (dstr != NULL) | |
172 | ZSTD_freeDStream(dstr); | |
173 | free((void *)zst_inb.src); | |
174 | free((void *)zst_outb.dst); | |
175 | return ret; | |
176 | } | |
0c127388 EV |
177 | #else |
178 | static int load_zstd(struct kmod_file *file) | |
179 | { | |
180 | return -ENOSYS; | |
181 | } | |
182 | #endif | |
3821e197 | 183 | |
3821e197 | 184 | static const char magic_zstd[] = {0x28, 0xB5, 0x2F, 0xFD}; |
3821e197 | 185 | |
b182f8fb | 186 | #ifdef ENABLE_XZ |
3f635058 | 187 | static void xz_uncompress_belch(struct kmod_file *file, lzma_ret ret) |
b182f8fb JE |
188 | { |
189 | switch (ret) { | |
190 | case LZMA_MEM_ERROR: | |
3f635058 | 191 | ERR(file->ctx, "xz: %s\n", strerror(ENOMEM)); |
b182f8fb JE |
192 | break; |
193 | case LZMA_FORMAT_ERROR: | |
3f635058 | 194 | ERR(file->ctx, "xz: File format not recognized\n"); |
b182f8fb JE |
195 | break; |
196 | case LZMA_OPTIONS_ERROR: | |
3f635058 | 197 | ERR(file->ctx, "xz: Unsupported compression options\n"); |
b182f8fb JE |
198 | break; |
199 | case LZMA_DATA_ERROR: | |
3f635058 | 200 | ERR(file->ctx, "xz: File is corrupt\n"); |
b182f8fb JE |
201 | break; |
202 | case LZMA_BUF_ERROR: | |
3f635058 | 203 | ERR(file->ctx, "xz: Unexpected end of input\n"); |
b182f8fb JE |
204 | break; |
205 | default: | |
3f635058 | 206 | ERR(file->ctx, "xz: Internal error (bug)\n"); |
b182f8fb JE |
207 | break; |
208 | } | |
209 | } | |
210 | ||
211 | static int xz_uncompress(lzma_stream *strm, struct kmod_file *file) | |
212 | { | |
213 | uint8_t in_buf[BUFSIZ], out_buf[BUFSIZ]; | |
214 | lzma_action action = LZMA_RUN; | |
215 | lzma_ret ret; | |
216 | void *p = NULL; | |
217 | size_t total = 0; | |
218 | ||
219 | strm->avail_in = 0; | |
220 | strm->next_out = out_buf; | |
221 | strm->avail_out = sizeof(out_buf); | |
222 | ||
223 | while (true) { | |
224 | if (strm->avail_in == 0) { | |
225 | ssize_t rdret = read(file->fd, in_buf, sizeof(in_buf)); | |
226 | if (rdret < 0) { | |
227 | ret = -errno; | |
228 | goto out; | |
229 | } | |
230 | strm->next_in = in_buf; | |
231 | strm->avail_in = rdret; | |
232 | if (rdret == 0) | |
233 | action = LZMA_FINISH; | |
234 | } | |
235 | ret = lzma_code(strm, action); | |
236 | if (strm->avail_out == 0 || ret != LZMA_OK) { | |
237 | size_t write_size = BUFSIZ - strm->avail_out; | |
238 | char *tmp = realloc(p, total + write_size); | |
239 | if (tmp == NULL) { | |
240 | ret = -errno; | |
241 | goto out; | |
242 | } | |
243 | memcpy(tmp + total, out_buf, write_size); | |
244 | total += write_size; | |
245 | p = tmp; | |
246 | strm->next_out = out_buf; | |
247 | strm->avail_out = BUFSIZ; | |
248 | } | |
249 | if (ret == LZMA_STREAM_END) | |
250 | break; | |
251 | if (ret != LZMA_OK) { | |
3f635058 | 252 | xz_uncompress_belch(file, ret); |
b182f8fb JE |
253 | ret = -EINVAL; |
254 | goto out; | |
255 | } | |
256 | } | |
257 | file->memory = p; | |
258 | file->size = total; | |
259 | return 0; | |
260 | out: | |
261 | free(p); | |
b182f8fb JE |
262 | return ret; |
263 | } | |
264 | ||
db5d14cf | 265 | static int load_xz(struct kmod_file *file) |
b182f8fb JE |
266 | { |
267 | lzma_stream strm = LZMA_STREAM_INIT; | |
268 | lzma_ret lzret; | |
269 | int ret; | |
270 | ||
271 | lzret = lzma_stream_decoder(&strm, UINT64_MAX, LZMA_CONCATENATED); | |
272 | if (lzret == LZMA_MEM_ERROR) { | |
3f635058 | 273 | ERR(file->ctx, "xz: %s\n", strerror(ENOMEM)); |
b182f8fb JE |
274 | return -ENOMEM; |
275 | } else if (lzret != LZMA_OK) { | |
3f635058 | 276 | ERR(file->ctx, "xz: Internal error (bug)\n"); |
b182f8fb JE |
277 | return -EINVAL; |
278 | } | |
279 | ret = xz_uncompress(&strm, file); | |
280 | lzma_end(&strm); | |
281 | return ret; | |
282 | } | |
0c127388 EV |
283 | #else |
284 | static int load_xz(struct kmod_file *file) | |
285 | { | |
286 | return -ENOSYS; | |
287 | } | |
288 | #endif | |
b182f8fb | 289 | |
db5d14cf | 290 | static const char magic_xz[] = {0xfd, '7', 'z', 'X', 'Z', 0}; |
db5d14cf GSB |
291 | |
292 | #ifdef ENABLE_ZLIB | |
293 | #define READ_STEP (4 * 1024 * 1024) | |
294 | static int load_zlib(struct kmod_file *file) | |
3d8226ed GSB |
295 | { |
296 | int err = 0; | |
297 | off_t did = 0, total = 0; | |
d3c16c79 | 298 | _cleanup_free_ unsigned char *p = NULL; |
09256b9a | 299 | gzFile gzf; |
d6cd6c74 | 300 | int gzfd; |
3d8226ed GSB |
301 | |
302 | errno = 0; | |
d6cd6c74 EV |
303 | gzfd = fcntl(file->fd, F_DUPFD_CLOEXEC, 3); |
304 | if (gzfd < 0) | |
305 | return -errno; | |
306 | ||
09256b9a EV |
307 | gzf = gzdopen(gzfd, "rb"); /* takes ownership of the fd */ |
308 | if (gzf == NULL) { | |
d6cd6c74 | 309 | close(gzfd); |
3d8226ed | 310 | return -errno; |
d6cd6c74 | 311 | } |
3d8226ed GSB |
312 | |
313 | for (;;) { | |
314 | int r; | |
315 | ||
316 | if (did == total) { | |
317 | void *tmp = realloc(p, total + READ_STEP); | |
318 | if (tmp == NULL) { | |
319 | err = -errno; | |
320 | goto error; | |
321 | } | |
322 | total += READ_STEP; | |
323 | p = tmp; | |
324 | } | |
325 | ||
09256b9a | 326 | r = gzread(gzf, p + did, total - did); |
3d8226ed GSB |
327 | if (r == 0) |
328 | break; | |
329 | else if (r < 0) { | |
c7d5a60d | 330 | int gzerr; |
09256b9a | 331 | const char *gz_errmsg = gzerror(gzf, &gzerr); |
c7d5a60d DR |
332 | |
333 | ERR(file->ctx, "gzip: %s\n", gz_errmsg); | |
334 | ||
335 | /* gzip might not set errno here */ | |
336 | err = gzerr == Z_ERRNO ? -errno : -EINVAL; | |
3d8226ed GSB |
337 | goto error; |
338 | } | |
339 | did += r; | |
340 | } | |
341 | ||
342 | file->memory = p; | |
343 | file->size = did; | |
d3c16c79 | 344 | p = NULL; |
09256b9a | 345 | gzclose(gzf); |
3d8226ed | 346 | return 0; |
d3c16c79 | 347 | |
3d8226ed | 348 | error: |
09256b9a | 349 | gzclose(gzf); /* closes the gzfd */ |
3d8226ed GSB |
350 | return err; |
351 | } | |
0c127388 EV |
352 | #else |
353 | static int load_zlib(struct kmod_file *file) | |
354 | { | |
355 | return -ENOSYS; | |
356 | } | |
357 | #endif | |
db5d14cf | 358 | |
db5d14cf | 359 | static const char magic_zlib[] = {0x1f, 0x8b}; |
db5d14cf | 360 | |
db5d14cf | 361 | static int load_reg(struct kmod_file *file) |
3d8226ed GSB |
362 | { |
363 | struct stat st; | |
3d8226ed | 364 | |
db5d14cf GSB |
365 | if (fstat(file->fd, &st) < 0) |
366 | return -errno; | |
3d8226ed GSB |
367 | |
368 | file->size = st.st_size; | |
c3e8d269 KC |
369 | file->memory = mmap(NULL, file->size, PROT_READ, MAP_PRIVATE, |
370 | file->fd, 0); | |
90b271fb EV |
371 | if (file->memory == MAP_FAILED) { |
372 | file->memory = NULL; | |
db5d14cf | 373 | return -errno; |
90b271fb | 374 | } |
e5398276 | 375 | |
3d8226ed | 376 | return 0; |
3d8226ed | 377 | } |
3d8226ed | 378 | |
045fd571 EV |
379 | static const struct comp_type { |
380 | size_t magic_size; | |
381 | enum kmod_file_compression_type compression; | |
382 | const char *magic_bytes; | |
383 | int (*load)(struct kmod_file *file); | |
384 | } comp_types[] = { | |
385 | {sizeof(magic_zstd), KMOD_FILE_COMPRESSION_ZSTD, magic_zstd, load_zstd}, | |
386 | {sizeof(magic_xz), KMOD_FILE_COMPRESSION_XZ, magic_xz, load_xz}, | |
387 | {sizeof(magic_zlib), KMOD_FILE_COMPRESSION_ZLIB, magic_zlib, load_zlib}, | |
5a8b16b7 | 388 | {0, KMOD_FILE_COMPRESSION_NONE, NULL, load_reg} |
045fd571 EV |
389 | }; |
390 | ||
1eff942e LDM |
391 | struct kmod_elf *kmod_file_get_elf(struct kmod_file *file) |
392 | { | |
81e5c797 EV |
393 | int err; |
394 | ||
1eff942e LDM |
395 | if (file->elf) |
396 | return file->elf; | |
397 | ||
81e5c797 EV |
398 | err = kmod_file_load_contents(file); |
399 | if (err) { | |
400 | errno = err; | |
401 | return NULL; | |
402 | } | |
403 | ||
1eff942e LDM |
404 | file->elf = kmod_elf_new(file->memory, file->size); |
405 | return file->elf; | |
406 | } | |
407 | ||
c68e92f7 LDM |
408 | struct kmod_file *kmod_file_open(const struct kmod_ctx *ctx, |
409 | const char *filename) | |
3d8226ed | 410 | { |
61bf8e74 | 411 | struct kmod_file *file; |
61bf8e74 EV |
412 | char buf[7]; |
413 | ssize_t sz; | |
414 | ||
415 | assert_cc(sizeof(magic_zstd) < sizeof(buf)); | |
416 | assert_cc(sizeof(magic_xz) < sizeof(buf)); | |
417 | assert_cc(sizeof(magic_zlib) < sizeof(buf)); | |
3d8226ed | 418 | |
61bf8e74 | 419 | file = calloc(1, sizeof(struct kmod_file)); |
3d8226ed GSB |
420 | if (file == NULL) |
421 | return NULL; | |
422 | ||
bb417099 GSB |
423 | file->fd = open(filename, O_RDONLY|O_CLOEXEC); |
424 | if (file->fd < 0) { | |
61bf8e74 EV |
425 | free(file); |
426 | return NULL; | |
bb417099 GSB |
427 | } |
428 | ||
61bf8e74 EV |
429 | sz = read_str_safe(file->fd, buf, sizeof(buf)); |
430 | lseek(file->fd, 0, SEEK_SET); | |
431 | if (sz != (sizeof(buf) - 1)) { | |
432 | if (sz < 0) | |
433 | errno = -sz; | |
434 | else | |
435 | errno = EINVAL; | |
db5d14cf | 436 | |
61bf8e74 EV |
437 | close(file->fd); |
438 | free(file); | |
439 | return NULL; | |
440 | } | |
441 | ||
5a8b16b7 EV |
442 | for (unsigned int i = 0; i < ARRAY_SIZE(comp_types); i++) { |
443 | const struct comp_type *itr = &comp_types[i]; | |
444 | ||
445 | file->load = itr->load; | |
446 | file->compression = itr->compression; | |
447 | if (itr->magic_size && | |
448 | memcmp(buf, itr->magic_bytes, itr->magic_size) == 0) { | |
61bf8e74 | 449 | break; |
db5d14cf | 450 | } |
db5d14cf GSB |
451 | } |
452 | ||
c68e92f7 | 453 | file->ctx = ctx; |
7a86f129 | 454 | |
3d8226ed GSB |
455 | return file; |
456 | } | |
457 | ||
7a86f129 LDM |
458 | /* |
459 | * Callers should just check file->memory got updated | |
460 | */ | |
81e5c797 | 461 | int kmod_file_load_contents(struct kmod_file *file) |
7a86f129 LDM |
462 | { |
463 | if (file->memory) | |
81e5c797 | 464 | return 0; |
7a86f129 LDM |
465 | |
466 | /* The load functions already log possible errors. */ | |
81e5c797 | 467 | return file->load(file); |
7a86f129 LDM |
468 | } |
469 | ||
3d8226ed GSB |
470 | void *kmod_file_get_contents(const struct kmod_file *file) |
471 | { | |
472 | return file->memory; | |
473 | } | |
474 | ||
475 | off_t kmod_file_get_size(const struct kmod_file *file) | |
476 | { | |
477 | return file->size; | |
478 | } | |
479 | ||
09c9f8c5 | 480 | enum kmod_file_compression_type kmod_file_get_compression(const struct kmod_file *file) |
144d1826 | 481 | { |
09c9f8c5 | 482 | return file->compression; |
144d1826 KC |
483 | } |
484 | ||
485 | int kmod_file_get_fd(const struct kmod_file *file) | |
486 | { | |
487 | return file->fd; | |
488 | } | |
489 | ||
3d8226ed GSB |
490 | void kmod_file_unref(struct kmod_file *file) |
491 | { | |
1eff942e LDM |
492 | if (file->elf) |
493 | kmod_elf_unref(file->elf); | |
494 | ||
ad158923 EV |
495 | if (file->compression == KMOD_FILE_COMPRESSION_NONE) { |
496 | if (file->memory) | |
497 | munmap(file->memory, file->size); | |
498 | } else { | |
499 | free(file->memory); | |
500 | } | |
7a86f129 | 501 | |
d6cd6c74 | 502 | close(file->fd); |
3d8226ed GSB |
503 | free(file); |
504 | } |