app/test: support resources archived by tar
[dpdk.git] / app / test / resource.c
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 <archive.h>
37 #include <archive_entry.h>
38 #include <errno.h>
39 #include <sys/queue.h>
40
41 #include <rte_debug.h>
42
43 #include "resource.h"
44
45 struct resource_list resource_list = TAILQ_HEAD_INITIALIZER(resource_list);
46
47 size_t resource_size(const struct resource *r)
48 {
49         return r->end - r->begin;
50 }
51
52 const struct resource *resource_find(const char *name)
53 {
54         struct resource *r;
55
56         TAILQ_FOREACH(r, &resource_list, next) {
57                 RTE_VERIFY(r->name);
58
59                 if (!strcmp(r->name, name))
60                         return r;
61         }
62
63         return NULL;
64 }
65
66 int resource_fwrite(const struct resource *r, FILE *f)
67 {
68         const size_t goal = resource_size(r);
69         size_t total = 0;
70
71         while (total < goal) {
72                 size_t wlen = fwrite(r->begin + total, 1, goal - total, f);
73                 if (wlen == 0) {
74                         perror(__func__);
75                         return -1;
76                 }
77
78                 total += wlen;
79         }
80
81         return 0;
82 }
83
84 int resource_fwrite_file(const struct resource *r, const char *fname)
85 {
86         FILE *f;
87         int ret;
88
89         f = fopen(fname, "w");
90         if (f == NULL) {
91                 perror(__func__);
92                 return -1;
93         }
94
95         ret = resource_fwrite(r, f);
96         fclose(f);
97         return ret;
98 }
99
100 static int do_copy(struct archive *r, struct archive *w)
101 {
102         const void *buf;
103         size_t len;
104 #if ARCHIVE_VERSION_NUMBER >= 3000000
105         int64_t off;
106 #else
107         off_t off;
108 #endif
109         int ret;
110
111         while (1) {
112                 ret = archive_read_data_block(r, &buf, &len, &off);
113                 if (ret == ARCHIVE_RETRY)
114                         continue;
115
116                 if (ret == ARCHIVE_EOF)
117                         return 0;
118
119                 if (ret != ARCHIVE_OK)
120                         return ret;
121
122                 do {
123                         ret = archive_write_data_block(w, buf, len, off);
124                         if (ret != ARCHIVE_OK && ret != ARCHIVE_RETRY)
125                                 return ret;
126                 } while (ret != ARCHIVE_OK);
127         }
128 }
129
130 int resource_untar(const struct resource *res)
131 {
132         struct archive *r;
133         struct archive *w;
134         struct archive_entry *e;
135         void *p;
136         int flags = 0;
137         int ret;
138
139         p = malloc(resource_size(res));
140         if (p == NULL)
141                 rte_panic("Failed to malloc %zu B\n", resource_size(res));
142
143         memcpy(p, res->begin, resource_size(res));
144
145         r = archive_read_new();
146         if (r == NULL) {
147                 free(p);
148                 return -1;
149         }
150
151         archive_read_support_format_all(r);
152         archive_read_support_filter_all(r);
153
154         w = archive_write_disk_new();
155         if (w == NULL) {
156                 archive_read_free(r);
157                 free(p);
158                 return -1;
159         }
160
161         flags |= ARCHIVE_EXTRACT_PERM;
162         flags |= ARCHIVE_EXTRACT_FFLAGS;
163         archive_write_disk_set_options(w, flags);
164         archive_write_disk_set_standard_lookup(w);
165
166         ret = archive_read_open_memory(r, p, resource_size(res));
167         if (ret != ARCHIVE_OK)
168                 goto fail;
169
170         while (1) {
171                 ret = archive_read_next_header(r, &e);
172                 if (ret == ARCHIVE_EOF)
173                         break;
174                 if (ret != ARCHIVE_OK)
175                         goto fail;
176
177                 ret = archive_write_header(w, e);
178                 if (ret == ARCHIVE_EOF)
179                         break;
180                 if (ret != ARCHIVE_OK)
181                         goto fail;
182
183                 if (archive_entry_size(e) == 0)
184                         continue;
185
186                 ret = do_copy(r, w);
187                 if (ret != ARCHIVE_OK)
188                         goto fail;
189
190                 ret = archive_write_finish_entry(w);
191                 if (ret != ARCHIVE_OK)
192                         goto fail;
193         }
194
195         archive_write_free(w);
196         archive_read_free(r);
197         free(p);
198         return 0;
199
200 fail:
201         archive_write_free(w);
202         archive_read_free(r);
203         free(p);
204         rte_panic("Failed: %s\n", archive_error_string(r));
205         return -1;
206 }
207
208 int resource_rm_by_tar(const struct resource *res)
209 {
210         struct archive *r;
211         struct archive_entry *e;
212         void *p;
213         int try_again = 1;
214         int attempts = 0;
215         int ret;
216
217         p = malloc(resource_size(res));
218         if (p == NULL)
219                 rte_panic("Failed to malloc %zu B\n", resource_size(res));
220
221         memcpy(p, res->begin, resource_size(res));
222
223         /*
224          * If somebody creates a file somewhere inside the extracted TAR
225          * hierarchy during a test the resource_rm_by_tar might loop
226          * infinitely. We prevent this by adding the attempts counter there.
227          * In normal case, max N iteration is done where N is the depth of
228          * the file-hierarchy.
229          */
230         while (try_again && attempts < 10000) {
231                 r = archive_read_new();
232                 if (r == NULL) {
233                         free(p);
234                         return -1;
235                 }
236
237                 archive_read_support_format_all(r);
238                 archive_read_support_filter_all(r);
239
240                 ret = archive_read_open_memory(r, p, resource_size(res));
241                 if (ret != ARCHIVE_OK) {
242                         fprintf(stderr, "Failed: %s\n",
243                                         archive_error_string(r));
244                         goto fail;
245                 }
246
247                 try_again = 0;
248
249                 while (1) {
250                         ret = archive_read_next_header(r, &e);
251                         if (ret == ARCHIVE_EOF)
252                                 break;
253                         if (ret != ARCHIVE_OK)
254                                 goto fail;
255
256                         ret = remove(archive_entry_pathname(e));
257                         if (ret < 0) {
258                                 switch (errno) {
259                                 case ENOTEMPTY:
260                                 case EEXIST:
261                                         try_again = 1;
262                                         break;
263
264                                 /* should not usually happen: */
265                                 case ENOENT:
266                                 case ENOTDIR:
267                                 case EROFS:
268                                         attempts += 1;
269                                         continue;
270                                 default:
271                                         perror("Failed to remove file");
272                                         goto fail;
273                                 }
274                         }
275                 }
276
277                 archive_read_free(r);
278                 attempts += 1;
279         }
280
281         if (attempts >= 10000) {
282                 fprintf(stderr, "Failed to remove archive\n");
283                 free(p);
284                 return -1;
285         }
286
287         free(p);
288         return 0;
289
290 fail:
291         archive_read_free(r);
292         free(p);
293
294         rte_panic("Failed: %s\n", archive_error_string(r));
295         return -1;
296 }
297
298 void resource_register(struct resource *r)
299 {
300         TAILQ_INSERT_TAIL(&resource_list, r, next);
301 }