]>
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> |
b182f8fb JE |
29 | #ifdef ENABLE_XZ |
30 | #include <lzma.h> | |
31 | #endif | |
3d8226ed GSB |
32 | #ifdef ENABLE_ZLIB |
33 | #include <zlib.h> | |
34 | #endif | |
35 | ||
c2e4286b LDM |
36 | #include <shared/util.h> |
37 | ||
38 | #include "libkmod.h" | |
39 | #include "libkmod-internal.h" | |
40 | ||
db5d14cf GSB |
41 | struct kmod_file; |
42 | struct file_ops { | |
43 | int (*load)(struct kmod_file *file); | |
44 | void (*unload)(struct kmod_file *file); | |
45 | }; | |
46 | ||
3d8226ed | 47 | struct kmod_file { |
b182f8fb JE |
48 | #ifdef ENABLE_XZ |
49 | bool xz_used; | |
50 | #endif | |
3d8226ed GSB |
51 | #ifdef ENABLE_ZLIB |
52 | gzFile gzf; | |
3d8226ed | 53 | #endif |
bb417099 | 54 | int fd; |
144d1826 | 55 | bool direct; |
3d8226ed GSB |
56 | off_t size; |
57 | void *memory; | |
db5d14cf | 58 | const struct file_ops *ops; |
c68e92f7 | 59 | const struct kmod_ctx *ctx; |
1eff942e | 60 | struct kmod_elf *elf; |
3d8226ed GSB |
61 | }; |
62 | ||
b182f8fb | 63 | #ifdef ENABLE_XZ |
3f635058 | 64 | static void xz_uncompress_belch(struct kmod_file *file, lzma_ret ret) |
b182f8fb JE |
65 | { |
66 | switch (ret) { | |
67 | case LZMA_MEM_ERROR: | |
3f635058 | 68 | ERR(file->ctx, "xz: %s\n", strerror(ENOMEM)); |
b182f8fb JE |
69 | break; |
70 | case LZMA_FORMAT_ERROR: | |
3f635058 | 71 | ERR(file->ctx, "xz: File format not recognized\n"); |
b182f8fb JE |
72 | break; |
73 | case LZMA_OPTIONS_ERROR: | |
3f635058 | 74 | ERR(file->ctx, "xz: Unsupported compression options\n"); |
b182f8fb JE |
75 | break; |
76 | case LZMA_DATA_ERROR: | |
3f635058 | 77 | ERR(file->ctx, "xz: File is corrupt\n"); |
b182f8fb JE |
78 | break; |
79 | case LZMA_BUF_ERROR: | |
3f635058 | 80 | ERR(file->ctx, "xz: Unexpected end of input\n"); |
b182f8fb JE |
81 | break; |
82 | default: | |
3f635058 | 83 | ERR(file->ctx, "xz: Internal error (bug)\n"); |
b182f8fb JE |
84 | break; |
85 | } | |
86 | } | |
87 | ||
88 | static int xz_uncompress(lzma_stream *strm, struct kmod_file *file) | |
89 | { | |
90 | uint8_t in_buf[BUFSIZ], out_buf[BUFSIZ]; | |
91 | lzma_action action = LZMA_RUN; | |
92 | lzma_ret ret; | |
93 | void *p = NULL; | |
94 | size_t total = 0; | |
95 | ||
96 | strm->avail_in = 0; | |
97 | strm->next_out = out_buf; | |
98 | strm->avail_out = sizeof(out_buf); | |
99 | ||
100 | while (true) { | |
101 | if (strm->avail_in == 0) { | |
102 | ssize_t rdret = read(file->fd, in_buf, sizeof(in_buf)); | |
103 | if (rdret < 0) { | |
104 | ret = -errno; | |
105 | goto out; | |
106 | } | |
107 | strm->next_in = in_buf; | |
108 | strm->avail_in = rdret; | |
109 | if (rdret == 0) | |
110 | action = LZMA_FINISH; | |
111 | } | |
112 | ret = lzma_code(strm, action); | |
113 | if (strm->avail_out == 0 || ret != LZMA_OK) { | |
114 | size_t write_size = BUFSIZ - strm->avail_out; | |
115 | char *tmp = realloc(p, total + write_size); | |
116 | if (tmp == NULL) { | |
117 | ret = -errno; | |
118 | goto out; | |
119 | } | |
120 | memcpy(tmp + total, out_buf, write_size); | |
121 | total += write_size; | |
122 | p = tmp; | |
123 | strm->next_out = out_buf; | |
124 | strm->avail_out = BUFSIZ; | |
125 | } | |
126 | if (ret == LZMA_STREAM_END) | |
127 | break; | |
128 | if (ret != LZMA_OK) { | |
3f635058 | 129 | xz_uncompress_belch(file, ret); |
b182f8fb JE |
130 | ret = -EINVAL; |
131 | goto out; | |
132 | } | |
133 | } | |
db5d14cf | 134 | file->xz_used = true; |
b182f8fb JE |
135 | file->memory = p; |
136 | file->size = total; | |
137 | return 0; | |
138 | out: | |
139 | free(p); | |
b182f8fb JE |
140 | return ret; |
141 | } | |
142 | ||
db5d14cf | 143 | static int load_xz(struct kmod_file *file) |
b182f8fb JE |
144 | { |
145 | lzma_stream strm = LZMA_STREAM_INIT; | |
146 | lzma_ret lzret; | |
147 | int ret; | |
148 | ||
149 | lzret = lzma_stream_decoder(&strm, UINT64_MAX, LZMA_CONCATENATED); | |
150 | if (lzret == LZMA_MEM_ERROR) { | |
3f635058 | 151 | ERR(file->ctx, "xz: %s\n", strerror(ENOMEM)); |
b182f8fb JE |
152 | return -ENOMEM; |
153 | } else if (lzret != LZMA_OK) { | |
3f635058 | 154 | ERR(file->ctx, "xz: Internal error (bug)\n"); |
b182f8fb JE |
155 | return -EINVAL; |
156 | } | |
157 | ret = xz_uncompress(&strm, file); | |
158 | lzma_end(&strm); | |
159 | return ret; | |
160 | } | |
b182f8fb | 161 | |
db5d14cf | 162 | static void unload_xz(struct kmod_file *file) |
bb417099 | 163 | { |
db5d14cf GSB |
164 | if (!file->xz_used) |
165 | return; | |
166 | free(file->memory); | |
bb417099 GSB |
167 | } |
168 | ||
db5d14cf GSB |
169 | static const char magic_xz[] = {0xfd, '7', 'z', 'X', 'Z', 0}; |
170 | #endif | |
171 | ||
172 | #ifdef ENABLE_ZLIB | |
173 | #define READ_STEP (4 * 1024 * 1024) | |
174 | static int load_zlib(struct kmod_file *file) | |
3d8226ed GSB |
175 | { |
176 | int err = 0; | |
177 | off_t did = 0, total = 0; | |
d3c16c79 | 178 | _cleanup_free_ unsigned char *p = NULL; |
3d8226ed GSB |
179 | |
180 | errno = 0; | |
bb417099 | 181 | file->gzf = gzdopen(file->fd, "rb"); |
d3c16c79 | 182 | if (file->gzf == NULL) |
3d8226ed | 183 | return -errno; |
db5d14cf | 184 | file->fd = -1; /* now owned by gzf due gzdopen() */ |
3d8226ed GSB |
185 | |
186 | for (;;) { | |
187 | int r; | |
188 | ||
189 | if (did == total) { | |
190 | void *tmp = realloc(p, total + READ_STEP); | |
191 | if (tmp == NULL) { | |
192 | err = -errno; | |
193 | goto error; | |
194 | } | |
195 | total += READ_STEP; | |
196 | p = tmp; | |
197 | } | |
198 | ||
199 | r = gzread(file->gzf, p + did, total - did); | |
200 | if (r == 0) | |
201 | break; | |
202 | else if (r < 0) { | |
c7d5a60d DR |
203 | int gzerr; |
204 | const char *gz_errmsg = gzerror(file->gzf, &gzerr); | |
205 | ||
206 | ERR(file->ctx, "gzip: %s\n", gz_errmsg); | |
207 | ||
208 | /* gzip might not set errno here */ | |
209 | err = gzerr == Z_ERRNO ? -errno : -EINVAL; | |
3d8226ed GSB |
210 | goto error; |
211 | } | |
212 | did += r; | |
213 | } | |
214 | ||
215 | file->memory = p; | |
216 | file->size = did; | |
d3c16c79 | 217 | p = NULL; |
3d8226ed | 218 | return 0; |
d3c16c79 | 219 | |
3d8226ed | 220 | error: |
3d8226ed GSB |
221 | gzclose(file->gzf); |
222 | return err; | |
223 | } | |
db5d14cf GSB |
224 | |
225 | static void unload_zlib(struct kmod_file *file) | |
226 | { | |
227 | if (file->gzf == NULL) | |
228 | return; | |
229 | free(file->memory); | |
230 | gzclose(file->gzf); /* closes file->fd */ | |
231 | } | |
232 | ||
233 | static const char magic_zlib[] = {0x1f, 0x8b}; | |
234 | #endif | |
235 | ||
236 | static const struct comp_type { | |
237 | size_t magic_size; | |
238 | const char *magic_bytes; | |
239 | const struct file_ops ops; | |
240 | } comp_types[] = { | |
241 | #ifdef ENABLE_XZ | |
242 | {sizeof(magic_xz), magic_xz, {load_xz, unload_xz}}, | |
243 | #endif | |
244 | #ifdef ENABLE_ZLIB | |
245 | {sizeof(magic_zlib), magic_zlib, {load_zlib, unload_zlib}}, | |
bb417099 | 246 | #endif |
db5d14cf GSB |
247 | {0, NULL, {NULL, NULL}} |
248 | }; | |
bb417099 | 249 | |
db5d14cf | 250 | static int load_reg(struct kmod_file *file) |
3d8226ed GSB |
251 | { |
252 | struct stat st; | |
3d8226ed | 253 | |
db5d14cf GSB |
254 | if (fstat(file->fd, &st) < 0) |
255 | return -errno; | |
3d8226ed GSB |
256 | |
257 | file->size = st.st_size; | |
c3e8d269 KC |
258 | file->memory = mmap(NULL, file->size, PROT_READ, MAP_PRIVATE, |
259 | file->fd, 0); | |
db5d14cf GSB |
260 | if (file->memory == MAP_FAILED) |
261 | return -errno; | |
144d1826 | 262 | file->direct = true; |
3d8226ed | 263 | return 0; |
3d8226ed | 264 | } |
3d8226ed | 265 | |
db5d14cf GSB |
266 | static void unload_reg(struct kmod_file *file) |
267 | { | |
268 | munmap(file->memory, file->size); | |
269 | } | |
270 | ||
7749bedb | 271 | static const struct file_ops reg_ops = { |
db5d14cf GSB |
272 | load_reg, unload_reg |
273 | }; | |
274 | ||
1eff942e LDM |
275 | struct kmod_elf *kmod_file_get_elf(struct kmod_file *file) |
276 | { | |
277 | if (file->elf) | |
278 | return file->elf; | |
279 | ||
280 | file->elf = kmod_elf_new(file->memory, file->size); | |
281 | return file->elf; | |
282 | } | |
283 | ||
c68e92f7 LDM |
284 | struct kmod_file *kmod_file_open(const struct kmod_ctx *ctx, |
285 | const char *filename) | |
3d8226ed GSB |
286 | { |
287 | struct kmod_file *file = calloc(1, sizeof(struct kmod_file)); | |
db5d14cf GSB |
288 | const struct comp_type *itr; |
289 | size_t magic_size_max = 0; | |
3d8226ed GSB |
290 | int err; |
291 | ||
292 | if (file == NULL) | |
293 | return NULL; | |
294 | ||
bb417099 GSB |
295 | file->fd = open(filename, O_RDONLY|O_CLOEXEC); |
296 | if (file->fd < 0) { | |
297 | err = -errno; | |
298 | goto error; | |
299 | } | |
300 | ||
db5d14cf GSB |
301 | for (itr = comp_types; itr->ops.load != NULL; itr++) { |
302 | if (magic_size_max < itr->magic_size) | |
303 | magic_size_max = itr->magic_size; | |
304 | } | |
305 | ||
144d1826 | 306 | file->direct = false; |
db5d14cf GSB |
307 | if (magic_size_max > 0) { |
308 | char *buf = alloca(magic_size_max + 1); | |
309 | ssize_t sz; | |
3d8226ed | 310 | |
db5d14cf GSB |
311 | if (buf == NULL) { |
312 | err = -errno; | |
313 | goto error; | |
314 | } | |
315 | sz = read_str_safe(file->fd, buf, magic_size_max + 1); | |
316 | lseek(file->fd, 0, SEEK_SET); | |
317 | if (sz != (ssize_t)magic_size_max) { | |
318 | if (sz < 0) | |
319 | err = sz; | |
320 | else | |
321 | err = -EINVAL; | |
322 | goto error; | |
323 | } | |
324 | ||
325 | for (itr = comp_types; itr->ops.load != NULL; itr++) { | |
326 | if (memcmp(buf, itr->magic_bytes, itr->magic_size) == 0) | |
327 | break; | |
328 | } | |
329 | if (itr->ops.load != NULL) | |
330 | file->ops = &itr->ops; | |
331 | } | |
332 | ||
333 | if (file->ops == NULL) | |
334 | file->ops = ®_ops; | |
335 | ||
336 | err = file->ops->load(file); | |
c68e92f7 | 337 | file->ctx = ctx; |
bb417099 | 338 | error: |
3d8226ed | 339 | if (err < 0) { |
db5d14cf GSB |
340 | if (file->fd >= 0) |
341 | close(file->fd); | |
3d8226ed GSB |
342 | free(file); |
343 | errno = -err; | |
344 | return NULL; | |
345 | } | |
346 | ||
347 | return file; | |
348 | } | |
349 | ||
350 | void *kmod_file_get_contents(const struct kmod_file *file) | |
351 | { | |
352 | return file->memory; | |
353 | } | |
354 | ||
355 | off_t kmod_file_get_size(const struct kmod_file *file) | |
356 | { | |
357 | return file->size; | |
358 | } | |
359 | ||
144d1826 KC |
360 | bool kmod_file_get_direct(const struct kmod_file *file) |
361 | { | |
362 | return file->direct; | |
363 | } | |
364 | ||
365 | int kmod_file_get_fd(const struct kmod_file *file) | |
366 | { | |
367 | return file->fd; | |
368 | } | |
369 | ||
3d8226ed GSB |
370 | void kmod_file_unref(struct kmod_file *file) |
371 | { | |
1eff942e LDM |
372 | if (file->elf) |
373 | kmod_elf_unref(file->elf); | |
374 | ||
db5d14cf GSB |
375 | file->ops->unload(file); |
376 | if (file->fd >= 0) | |
b182f8fb | 377 | close(file->fd); |
3d8226ed GSB |
378 | free(file); |
379 | } |