1/*-
2 *   BSD LICENSE
3 *
4 *   Copyright(c) 2016 RehiveTech. All rights reserved.
5 *   All rights reserved.
6 *
7 *   Redistribution and use in source and binary forms, with or without
8 *   modification, are permitted provided that the following conditions
9 *   are met:
10 *
11 *     * Redistributions of source code must retain the above copyright
12 *       notice, this list of conditions and the following disclaimer.
13 *     * Redistributions in binary form must reproduce the above copyright
14 *       notice, this list of conditions and the following disclaimer in
15 *       the documentation and/or other materials provided with the
16 *       distribution.
17 *     * Neither the name of RehiveTech nor the names of its
18 *       contributors may be used to endorse or promote products derived
19 *       from this software without specific prior written permission.
20 *
21 *   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22 *   "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23 *   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
24 *   A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
25 *   OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
26 *   SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
27 *   LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
28 *   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
29 *   THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30 *   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
31 *   OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 */
33
34#include <stdio.h>
35#include <string.h>
36#include <errno.h>
37#include <sys/queue.h>
38
39#include <rte_debug.h>
40
41#include "resource.h"
42
43struct resource_list resource_list = TAILQ_HEAD_INITIALIZER(resource_list);
44
45size_t resource_size(const struct resource *r)
46{
47	return r->end - r->begin;
48}
49
50const struct resource *resource_find(const char *name)
51{
52	struct resource *r;
53
54	TAILQ_FOREACH(r, &resource_list, next) {
55		RTE_VERIFY(r->name);
56
57		if (!strcmp(r->name, name))
58			return r;
59	}
60
61	return NULL;
62}
63
64int resource_fwrite(const struct resource *r, FILE *f)
65{
66	const size_t goal = resource_size(r);
67	size_t total = 0;
68
69	while (total < goal) {
70		size_t wlen = fwrite(r->begin + total, 1, goal - total, f);
71		if (wlen == 0) {
72			perror(__func__);
73			return -1;
74		}
75
76		total += wlen;
77	}
78
79	return 0;
80}
81
82int resource_fwrite_file(const struct resource *r, const char *fname)
83{
84	FILE *f;
85	int ret;
86
87	f = fopen(fname, "w");
88	if (f == NULL) {
89		perror(__func__);
90		return -1;
91	}
92
93	ret = resource_fwrite(r, f);
94	fclose(f);
95	return ret;
96}
97
98#ifdef RTE_APP_TEST_RESOURCE_TAR
99#include <archive.h>
100#include <archive_entry.h>
101
102static int do_copy(struct archive *r, struct archive *w)
103{
104	const void *buf;
105	size_t len;
106#if ARCHIVE_VERSION_NUMBER >= 3000000
107	int64_t off;
108#else
109	off_t off;
110#endif
111	int ret;
112
113	while (1) {
114		ret = archive_read_data_block(r, &buf, &len, &off);
115		if (ret == ARCHIVE_RETRY)
116			continue;
117
118		if (ret == ARCHIVE_EOF)
119			return 0;
120
121		if (ret != ARCHIVE_OK)
122			return ret;
123
124		do {
125			ret = archive_write_data_block(w, buf, len, off);
126			if (ret != ARCHIVE_OK && ret != ARCHIVE_RETRY)
127				return ret;
128		} while (ret != ARCHIVE_OK);
129	}
130}
131
132int resource_untar(const struct resource *res)
133{
134	struct archive *r;
135	struct archive *w;
136	struct archive_entry *e;
137	void *p;
138	int flags = 0;
139	int ret;
140
141	p = malloc(resource_size(res));
142	if (p == NULL)
143		rte_panic("Failed to malloc %zu B\n", resource_size(res));
144
145	memcpy(p, res->begin, resource_size(res));
146
147	r = archive_read_new();
148	if (r == NULL) {
149		free(p);
150		return -1;
151	}
152
153	archive_read_support_format_all(r);
154	archive_read_support_filter_all(r);
155
156	w = archive_write_disk_new();
157	if (w == NULL) {
158		archive_read_free(r);
159		free(p);
160		return -1;
161	}
162
163	flags |= ARCHIVE_EXTRACT_PERM;
164	flags |= ARCHIVE_EXTRACT_FFLAGS;
165	archive_write_disk_set_options(w, flags);
166	archive_write_disk_set_standard_lookup(w);
167
168	ret = archive_read_open_memory(r, p, resource_size(res));
169	if (ret != ARCHIVE_OK)
170		goto fail;
171
172	while (1) {
173		ret = archive_read_next_header(r, &e);
174		if (ret == ARCHIVE_EOF)
175			break;
176		if (ret != ARCHIVE_OK)
177			goto fail;
178
179		ret = archive_write_header(w, e);
180		if (ret == ARCHIVE_EOF)
181			break;
182		if (ret != ARCHIVE_OK)
183			goto fail;
184
185		if (archive_entry_size(e) == 0)
186			continue;
187
188		ret = do_copy(r, w);
189		if (ret != ARCHIVE_OK)
190			goto fail;
191
192		ret = archive_write_finish_entry(w);
193		if (ret != ARCHIVE_OK)
194			goto fail;
195	}
196
197	archive_write_free(w);
198	archive_read_free(r);
199	free(p);
200	return 0;
201
202fail:
203	archive_write_free(w);
204	archive_read_free(r);
205	free(p);
206	rte_panic("Failed: %s\n", archive_error_string(r));
207	return -1;
208}
209
210int resource_rm_by_tar(const struct resource *res)
211{
212	struct archive *r;
213	struct archive_entry *e;
214	void *p;
215	int try_again = 1;
216	int attempts = 0;
217	int ret;
218
219	p = malloc(resource_size(res));
220	if (p == NULL)
221		rte_panic("Failed to malloc %zu B\n", resource_size(res));
222
223	memcpy(p, res->begin, resource_size(res));
224
225	/*
226	 * If somebody creates a file somewhere inside the extracted TAR
227	 * hierarchy during a test the resource_rm_by_tar might loop
228	 * infinitely. We prevent this by adding the attempts counter there.
229	 * In normal case, max N iteration is done where N is the depth of
230	 * the file-hierarchy.
231	 */
232	while (try_again && attempts < 10000) {
233		r = archive_read_new();
234		if (r == NULL) {
235			free(p);
236			return -1;
237		}
238
239		archive_read_support_format_all(r);
240		archive_read_support_filter_all(r);
241
242		ret = archive_read_open_memory(r, p, resource_size(res));
243		if (ret != ARCHIVE_OK) {
244			fprintf(stderr, "Failed: %s\n",
245					archive_error_string(r));
246			goto fail;
247		}
248
249		try_again = 0;
250
251		while (1) {
252			ret = archive_read_next_header(r, &e);
253			if (ret == ARCHIVE_EOF)
254				break;
255			if (ret != ARCHIVE_OK)
256				goto fail;
257
258			ret = remove(archive_entry_pathname(e));
259			if (ret < 0) {
260				switch (errno) {
261				case ENOTEMPTY:
262				case EEXIST:
263					try_again = 1;
264					break;
265
266				/* should not usually happen: */
267				case ENOENT:
268				case ENOTDIR:
269				case EROFS:
270					attempts += 1;
271					continue;
272				default:
273					perror("Failed to remove file");
274					goto fail;
275				}
276			}
277		}
278
279		archive_read_free(r);
280		attempts += 1;
281	}
282
283	if (attempts >= 10000) {
284		fprintf(stderr, "Failed to remove archive\n");
285		free(p);
286		return -1;
287	}
288
289	free(p);
290	return 0;
291
292fail:
293	archive_read_free(r);
294	free(p);
295
296	rte_panic("Failed: %s\n", archive_error_string(r));
297	return -1;
298}
299
300#endif /* RTE_APP_TEST_RESOURCE_TAR */
301
302void resource_register(struct resource *r)
303{
304	TAILQ_INSERT_TAIL(&resource_list, r, next);
305}
306