1 /*        $NetBSD: pkg_io.c,v 1.5 2024/06/11 09:26:57 wiz Exp $       */
2 /*-
3  * Copyright (c) 2008, 2009 Joerg Sonnenberger <joerg@NetBSD.org>.
4  * All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  *
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in
14  *    the documentation and/or other materials provided with the
15  *    distribution.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
20  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
21  * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
22  * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
23  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
25  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
26  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
27  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28  * SUCH DAMAGE.
29  */
30 
31 #if HAVE_CONFIG_H
32 #include "config.h"
33 #endif
34 #include <nbcompat.h>
35 #if HAVE_SYS_CDEFS_H
36 #include <sys/cdefs.h>
37 #endif
38 
39 __RCSID("$NetBSD: pkg_io.c,v 1.5 2024/06/11 09:26:57 wiz Exp $");
40 
41 #include <archive.h>
42 #include <archive_entry.h>
43 #if HAVE_ERR_H
44 #include <err.h>
45 #endif
46 #if HAVE_ERRNO_H
47 #include <errno.h>
48 #endif
49 #include <stdlib.h>
50 
51 #include "lib.h"
52 
53 #ifdef BOOTSTRAP
54 #undef IS_URL
55 #define IS_URL(x) 0
56 #else
57 #include <fetch.h>
58 #endif
59 
60 struct pkg_path {
61           TAILQ_ENTRY(pkg_path) pl_link;
62           char *pl_path;
63 };
64 
65 static char *orig_cwd, *last_toplevel;
66 static TAILQ_HEAD(, pkg_path) pkg_path = TAILQ_HEAD_INITIALIZER(pkg_path);
67 
68 #ifndef BOOTSTRAP
69 struct fetch_archive {
70           struct url *url;
71           fetchIO *fetch;
72           char buffer[32768];
73           off_t size;
74           int restart;
75 };
76 
77 static int
fetch_archive_open(struct archive * a,void * client_data)78 fetch_archive_open(struct archive *a, void *client_data)
79 {
80           struct fetch_archive *f = client_data;
81           struct url_stat us;
82 
83           f->fetch = fetchXGet(f->url, &us, fetch_flags);
84           if (f->fetch == NULL)
85                     return ENOENT;
86           f->size = us.size;
87           f->restart = 1;
88           f->url->offset = 0;
89           return 0;
90 }
91 
92 static ssize_t
fetch_archive_read(struct archive * a,void * client_data,const void ** buffer)93 fetch_archive_read(struct archive *a, void *client_data,
94     const void **buffer)
95 {
96           struct fetch_archive *f = client_data;
97           struct url_stat us;
98           ssize_t rv;
99 
100           *buffer = f->buffer;
101           rv = fetchIO_read(f->fetch, f->buffer, sizeof(f->buffer));
102           if (rv > 0) {
103                     f->url->offset += rv;
104                     return rv;
105           }
106           if (f->restart == 0)
107                     return rv;
108           if (rv == 0) {
109                     if (f->size == -1)
110                               return 0;
111                     if (f->url->offset == f->size)
112                               return 0;
113           }
114           f->restart = 0;
115           if (1) {
116                     char *url = fetchStringifyURL(f->url);
117                     fprintf(stderr, "Trying to reconnect %s\n", url);
118                     free(url);
119           }
120           fetchIO_close(f->fetch);
121           f->fetch = fetchXGet(f->url, &us, fetch_flags);
122           if (f->fetch == NULL)
123                     return -1;
124           if (us.size != f->size)
125                     return -1;
126           rv = fetchIO_read(f->fetch, f->buffer, sizeof(f->buffer));
127           if (rv > 0)
128                     f->url->offset += rv;
129           return rv;
130 }
131 
132 static int
fetch_archive_close(struct archive * a,void * client_data)133 fetch_archive_close(struct archive *a, void *client_data)
134 {
135           struct fetch_archive *f = client_data;
136 
137           if (f->fetch != NULL)
138                     fetchIO_close(f->fetch);
139           fetchFreeURL(f->url);
140           free(f);
141           return 0;
142 }
143 
144 static struct archive *
open_archive_by_url(struct url * url,char ** archive_name)145 open_archive_by_url(struct url *url, char **archive_name)
146 {
147           struct fetch_archive *f;
148           struct archive *a;
149 
150           f = xmalloc(sizeof(*f));
151           f->url = fetchCopyURL(url);
152 
153           *archive_name = fetchStringifyURL(url);
154 
155           a = prepare_archive();
156           if (archive_read_open(a, f, fetch_archive_open, fetch_archive_read,
157               fetch_archive_close)) {
158                     free(*archive_name);
159                     *archive_name = NULL;
160                     archive_read_free(a);
161                     return NULL;
162           }
163 
164           return a;
165 }
166 #endif /* !BOOTSTRAP */
167 
168 struct archive *
prepare_archive(void)169 prepare_archive(void)
170 {
171           struct archive *a = archive_read_new();
172           if (a == NULL)
173                     errx(EXIT_FAILURE, "memory allocation failed");
174           archive_read_support_filter_gzip(a);
175           archive_read_support_filter_bzip2(a);
176           archive_read_support_filter_xz(a);
177           archive_read_support_format_ar(a);
178           archive_read_support_format_tar(a);
179           archive_read_set_options(a, "hdrcharset=BINARY");
180           return a;
181 }
182 
183 struct archive *
open_archive(const char * url,char ** archive_name)184 open_archive(const char *url, char **archive_name)
185 {
186           struct url *u;
187           struct archive *a;
188 
189           *archive_name = NULL;
190 
191           if (!IS_URL(url)) {
192                     a = prepare_archive();
193                     if (archive_read_open_filename(a, url, 1024)) {
194                               archive_read_close(a);
195                               return NULL;
196                     }
197                     *archive_name = xstrdup(url);
198                     return a;
199           }
200 
201 #ifdef BOOTSTRAP
202           return NULL;
203 #else
204           if ((u = fetchParseURL(url)) == NULL)
205                     return NULL;
206 
207           a = open_archive_by_url(u, archive_name);
208 
209           fetchFreeURL(u);
210           return a;
211 #endif
212 }
213 
214 #ifndef BOOTSTRAP
215 static int
strip_suffix(char * filename)216 strip_suffix(char *filename)
217 {
218           size_t len;
219 
220           len = strlen(filename);
221           if (len <= 4)
222                     return 0;
223           if (strcmp(filename + len - 4, ".tgz") == 0 ||
224               strcmp(filename + len - 4, ".tbz") == 0) {
225                     filename[len - 4] = '\0';
226                     return 1;
227           } else
228                     return 0;
229 }
230 
231 static int
find_best_package_int(struct url * url,const char * pattern,struct url ** best_url)232 find_best_package_int(struct url *url, const char *pattern,
233     struct url **best_url)
234 {
235           char *cur_match, *url_pattern, *best_match = NULL;
236           struct url_list ue;
237           size_t i;
238 
239           if (*best_url) {
240                     if ((best_match = fetchUnquoteFilename(*best_url)) == NULL)
241                               return -1;
242           } else
243                     best_match = NULL;
244 
245           if (best_match && strip_suffix(best_match) == 0) {
246                     free(best_match);
247                     return -1;
248           }
249 
250           for (i = 0; pattern[i] != '\0'; ++i) {
251                     if (!isalnum((unsigned char)(pattern[i])) &&
252                         (pattern[i]) != '-')
253                               break;
254           }
255           url_pattern = xasprintf("%*.*s*", (int)i, (int)i, pattern);
256 
257           fetchInitURLList(&ue);
258           if (fetchList(&ue, url, url_pattern, fetch_flags)) {
259                     char *base_url;
260                     base_url = fetchStringifyURL(url);
261                     warnx("Can't process %s/%s: %s", base_url, url_pattern,
262                         fetchLastErrString);
263                     free(base_url);
264                     free(url_pattern);
265                     fetchFreeURLList(&ue);
266                     return -1;
267           }
268           free(url_pattern);
269 
270           for (i = 0; i < ue.length; ++i) {
271                     cur_match = fetchUnquoteFilename(ue.urls + i);
272 
273                     if (cur_match == NULL) {
274                               free(best_match);
275                               fetchFreeURLList(&ue);
276                               return -1;
277                     }
278                     if (strip_suffix(cur_match) == 0) {
279                               free(cur_match);
280                               continue;
281                     }
282                     if (pkg_order(pattern, cur_match, best_match) == 1) {
283                               if (*best_url)
284                                         fetchFreeURL(*best_url);
285                               *best_url = fetchCopyURL(ue.urls + i);
286                               free(best_match);
287                               best_match = cur_match;
288                               cur_match = NULL;
289                               if (*best_url == NULL) {
290                                         free(best_match);
291                                         return -1;
292                               }
293                     }
294                     free(cur_match);
295           }
296           free(best_match);
297           fetchFreeURLList(&ue);
298           return 0;
299 }
300 
301 void
process_pkg_path(void)302 process_pkg_path(void)
303 {
304           char cwd[PATH_MAX];
305           int relative_path;
306           struct pkg_path *pl;
307           const char *start, *next;
308           size_t len;
309 
310           if (getcwd(cwd, sizeof(cwd)) == NULL)
311                     errx(EXIT_FAILURE, "getcwd failed");
312 
313           orig_cwd = xstrdup(cwd);
314 
315           if (config_pkg_path == NULL)
316                     return;
317 
318           for (start = config_pkg_path; *start; start = next) {
319                     len = strcspn(start, ";");
320                     if (*(next = start + len) != '\0')
321                               ++next;
322 
323                     relative_path = !IS_FULLPATH(start) && !IS_URL(start);
324                     pl = xmalloc(sizeof(*pl));
325                     pl->pl_path = xasprintf("%s%s%*.*s",
326                         relative_path ? cwd : "", len && relative_path ? "/" : "",
327                         (int)len, (int)len, start);
328                     TAILQ_INSERT_TAIL(&pkg_path, pl, pl_link);
329           }
330 }
331 
332 struct url *
find_best_package(const char * toplevel,const char * pattern,int do_path)333 find_best_package(const char *toplevel, const char *pattern, int do_path)
334 {
335           struct url *url, *best_match = NULL;
336           struct pkg_path *pl;
337 
338           if (toplevel) {
339                     url = fetchParseURL(last_toplevel);
340                     if (url != NULL) {
341                               find_best_package_int(url, pattern, &best_match);
342                               /* XXX Check return value and complain */
343                               fetchFreeURL(url);
344                     }
345           }
346           if (!do_path)
347                     return best_match;
348 
349           TAILQ_FOREACH(pl, &pkg_path, pl_link) {
350                     url = fetchParseURL(pl->pl_path);
351                     if (url != NULL) {
352                               find_best_package_int(url, pattern, &best_match);
353                               /* XXX Check return value and complain */
354                               fetchFreeURL(url);
355                     }
356           }
357 
358           return best_match;
359 }
360 #endif /* !BOOTSTRAP */
361 
362 struct archive *
find_archive(const char * fname,int top_level,char ** archive_name)363 find_archive(const char *fname, int top_level, char **archive_name)
364 {
365           struct archive *a;
366           struct url *best_match;
367           char *full_fname, *last_slash;
368           int search_path;
369 
370           search_path = 0;
371           if (IS_FULLPATH(fname) || IS_URL(fname)) {
372                     full_fname = xstrdup(fname);
373           } else {
374                     if (strchr(fname, '/') == NULL)
375                               search_path = 1;
376                     full_fname = xasprintf("%s/%s", orig_cwd, fname);
377           }
378 
379           last_slash = strrchr(full_fname, '/');
380           if (top_level) {
381                     free(last_toplevel);
382                     *last_slash = '\0';
383                     last_toplevel = xstrdup(full_fname);
384                     *last_slash = '/';
385           }
386 
387           a = open_archive(full_fname, archive_name);
388           if (a != NULL) {
389                     free(full_fname);
390                     return a;
391           }
392 #ifndef BOOTSTRAP
393           fname = last_slash + 1;
394           *last_slash = '\0';
395 
396           best_match = find_best_package(full_fname, fname, 0);
397 
398           if (search_path && best_match == NULL)
399                     best_match = find_best_package(last_toplevel, fname, 1);
400 
401           free(full_fname);
402 
403           if (best_match == NULL)
404                     return NULL;
405           a = open_archive_by_url(best_match, archive_name);
406           fetchFreeURL(best_match);
407 #endif /* !BOOTSTRAP */
408           return a;
409 }
410