1/*
2 * libkmod - interface to kernel module operations
3 *
4 * Copyright (C) 2011-2013  ProFUSION embedded systems
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
17 * License along with this library; if not, see <http://www.gnu.org/licenses/>.
18 */
19
20#include <errno.h>
21#include <stdbool.h>
22#include <stdio.h>
23#include <stdlib.h>
24#include <string.h>
25#include <sys/mman.h>
26#include <sys/stat.h>
27#include <sys/types.h>
28#include <unistd.h>
29#ifdef ENABLE_XZ
30#include <lzma.h>
31#endif
32#ifdef ENABLE_ZLIB
33#include <zlib.h>
34#endif
35
36#include <shared/util.h>
37
38#include "libkmod.h"
39#include "libkmod-internal.h"
40
41struct kmod_file;
42struct file_ops {
43	int (*load)(struct kmod_file *file);
44	void (*unload)(struct kmod_file *file);
45};
46
47struct kmod_file {
48#ifdef ENABLE_XZ
49	bool xz_used;
50#endif
51#ifdef ENABLE_ZLIB
52	gzFile gzf;
53#endif
54	int fd;
55	bool direct;
56	off_t size;
57	void *memory;
58	const struct file_ops *ops;
59	const struct kmod_ctx *ctx;
60	struct kmod_elf *elf;
61};
62
63#ifdef ENABLE_XZ
64static void xz_uncompress_belch(struct kmod_file *file, lzma_ret ret)
65{
66	switch (ret) {
67	case LZMA_MEM_ERROR:
68		ERR(file->ctx, "xz: %s\n", strerror(ENOMEM));
69		break;
70	case LZMA_FORMAT_ERROR:
71		ERR(file->ctx, "xz: File format not recognized\n");
72		break;
73	case LZMA_OPTIONS_ERROR:
74		ERR(file->ctx, "xz: Unsupported compression options\n");
75		break;
76	case LZMA_DATA_ERROR:
77		ERR(file->ctx, "xz: File is corrupt\n");
78		break;
79	case LZMA_BUF_ERROR:
80		ERR(file->ctx, "xz: Unexpected end of input\n");
81		break;
82	default:
83		ERR(file->ctx, "xz: Internal error (bug)\n");
84		break;
85	}
86}
87
88static 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) {
129			xz_uncompress_belch(file, ret);
130			ret = -EINVAL;
131			goto out;
132		}
133	}
134	file->xz_used = true;
135	file->memory = p;
136	file->size = total;
137	return 0;
138 out:
139	free(p);
140	return ret;
141}
142
143static int load_xz(struct kmod_file *file)
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) {
151		ERR(file->ctx, "xz: %s\n", strerror(ENOMEM));
152		return -ENOMEM;
153	} else if (lzret != LZMA_OK) {
154		ERR(file->ctx, "xz: Internal error (bug)\n");
155		return -EINVAL;
156	}
157	ret = xz_uncompress(&strm, file);
158	lzma_end(&strm);
159	return ret;
160}
161
162static void unload_xz(struct kmod_file *file)
163{
164	if (!file->xz_used)
165		return;
166	free(file->memory);
167}
168
169static const char magic_xz[] = {0xfd, '7', 'z', 'X', 'Z', 0};
170#endif
171
172#ifdef ENABLE_ZLIB
173#define READ_STEP (4 * 1024 * 1024)
174static int load_zlib(struct kmod_file *file)
175{
176	int err = 0;
177	off_t did = 0, total = 0;
178	_cleanup_free_ unsigned char *p = NULL;
179
180	errno = 0;
181	file->gzf = gzdopen(file->fd, "rb");
182	if (file->gzf == NULL)
183		return -errno;
184	file->fd = -1; /* now owned by gzf due gzdopen() */
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) {
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;
210			goto error;
211		}
212		did += r;
213	}
214
215	file->memory = p;
216	file->size = did;
217	p = NULL;
218	return 0;
219
220error:
221	gzclose(file->gzf);
222	return err;
223}
224
225static 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
233static const char magic_zlib[] = {0x1f, 0x8b};
234#endif
235
236static 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}},
246#endif
247	{0, NULL, {NULL, NULL}}
248};
249
250static int load_reg(struct kmod_file *file)
251{
252	struct stat st;
253
254	if (fstat(file->fd, &st) < 0)
255		return -errno;
256
257	file->size = st.st_size;
258	file->memory = mmap(NULL, file->size, PROT_READ, MAP_PRIVATE,
259			    file->fd, 0);
260	if (file->memory == MAP_FAILED)
261		return -errno;
262	file->direct = true;
263	return 0;
264}
265
266static void unload_reg(struct kmod_file *file)
267{
268	munmap(file->memory, file->size);
269}
270
271static const struct file_ops reg_ops = {
272	load_reg, unload_reg
273};
274
275struct 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
284struct kmod_file *kmod_file_open(const struct kmod_ctx *ctx,
285						const char *filename)
286{
287	struct kmod_file *file = calloc(1, sizeof(struct kmod_file));
288	const struct comp_type *itr;
289	size_t magic_size_max = 0;
290	int err;
291
292	if (file == NULL)
293		return NULL;
294
295	file->fd = open(filename, O_RDONLY|O_CLOEXEC);
296	if (file->fd < 0) {
297		err = -errno;
298		goto error;
299	}
300
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
306	file->direct = false;
307	if (magic_size_max > 0) {
308		char *buf = alloca(magic_size_max + 1);
309		ssize_t sz;
310
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 = &reg_ops;
335
336	err = file->ops->load(file);
337	file->ctx = ctx;
338error:
339	if (err < 0) {
340		if (file->fd >= 0)
341			close(file->fd);
342		free(file);
343		errno = -err;
344		return NULL;
345	}
346
347	return file;
348}
349
350void *kmod_file_get_contents(const struct kmod_file *file)
351{
352	return file->memory;
353}
354
355off_t kmod_file_get_size(const struct kmod_file *file)
356{
357	return file->size;
358}
359
360bool kmod_file_get_direct(const struct kmod_file *file)
361{
362	return file->direct;
363}
364
365int kmod_file_get_fd(const struct kmod_file *file)
366{
367	return file->fd;
368}
369
370void kmod_file_unref(struct kmod_file *file)
371{
372	if (file->elf)
373		kmod_elf_unref(file->elf);
374
375	file->ops->unload(file);
376	if (file->fd >= 0)
377		close(file->fd);
378	free(file);
379}
380