Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/pkg
Path: blob/main/libpkg/pkg_abi.c
2645 views
1
/*-
2
* Copyright (c) 2011-2025 Baptiste Daroussin <[email protected]>
3
* Copyright (c) 2012-2013 Matthew Seaman <[email protected]>
4
* Copyright (c) 2024 The FreeBSD Foundation
5
*
6
* This software was developed in part by Isaac Freund <[email protected]>
7
* under sponsorship from the FreeBSD Foundation.
8
*
9
* SPDX-License-Identifier: BSD-2-Clause
10
*/
11
#ifdef HAVE_CONFIG_H
12
#include "pkg_config.h"
13
#endif
14
15
#include <ctype.h>
16
#include <paths.h>
17
#include <string.h>
18
#include <unistd.h>
19
20
#include "pkg.h"
21
#include "private/pkg_abi.h"
22
#include "private/binfmt.h"
23
#include "private/event.h"
24
#include "private/pkg.h"
25
#include "private/utils.h"
26
#include "xmalloc.h"
27
28
#define _PATH_UNAME "/usr/bin/uname"
29
30
/* All possibilities on FreeBSD as of 5/26/2014 */
31
struct arch_trans {
32
const char *elftype;
33
const char *archid;
34
};
35
36
static const struct arch_trans machine_arch_translation[] = { { "x86:32", "i386" },
37
{ "x86:64", "amd64" }, { "powerpc:32:eb", "powerpc" },
38
{ "powerpc:64:eb", "powerpc64" }, { "powerpc:64:el", "powerpc64le" },
39
{ "sparc64:64", "sparc64" }, { "ia64:64", "ia64" },
40
/* All the ARM stuff */
41
{ "armv6:32:el:eabi:hardfp", "armv6" },
42
{ "armv7:32:el:eabi:hardfp", "armv7" }, { "aarch64:64", "aarch64" },
43
/* And now MIPS */
44
{ "mips:32:el:o32", "mipsel" }, { "mips:32:el:n32", "mipsn32el" },
45
{ "mips:32:eb:o32", "mips" }, { "mips:32:eb:n32", "mipsn32" },
46
{ "mips:64:el:n64", "mips64el" }, { "mips:64:eb:n64", "mips64" },
47
/* And RISC-V */
48
{ "riscv:32:hf", "riscv32" }, { "riscv:32:sf", "riscv32sf" },
49
{ "riscv:64:hf", "riscv64" }, { "riscv:64:sf", "riscv64sf" },
50
51
{ NULL, NULL } };
52
53
static const struct {
54
enum pkg_os os;
55
const char *string;
56
} os_string_table[] = {
57
{ PKG_OS_UNKNOWN, "Unknown" },
58
{ PKG_OS_FREEBSD, "FreeBSD" },
59
{ PKG_OS_NETBSD, "NetBSD" },
60
{ PKG_OS_DRAGONFLY, "dragonfly" },
61
{ PKG_OS_LINUX, "Linux" },
62
{ PKG_OS_DARWIN, "Darwin" },
63
{ PKG_OS_ANY, "*" },
64
{ -1, NULL },
65
};
66
67
/* This table does not include PKG_ARCH_AMD64 as the string translation of
68
that arch is os-dependent. */
69
static const struct {
70
enum pkg_arch arch;
71
const char *string;
72
} arch_string_table[] = {
73
{ PKG_ARCH_UNKNOWN, "unknown"},
74
{ PKG_ARCH_I386, "i386"},
75
{ PKG_ARCH_ARMV6, "armv6"},
76
{ PKG_ARCH_ARMV7, "armv7"},
77
{ PKG_ARCH_AARCH64, "aarch64"},
78
{ PKG_ARCH_POWERPC, "powerpc"},
79
{ PKG_ARCH_POWERPC64, "powerpc64"},
80
{ PKG_ARCH_POWERPC64LE, "powerpc64le"},
81
{ PKG_ARCH_RISCV32, "riscv32"},
82
{ PKG_ARCH_RISCV64, "riscv64"},
83
{ PKG_ARCH_ANY, "*"},
84
{ -1, NULL },
85
};
86
87
const char *
88
pkg_os_to_string(enum pkg_os os)
89
{
90
for (size_t i = 0; os_string_table[i].string != NULL; i++) {
91
if (os == os_string_table[i].os) {
92
return os_string_table[i].string;
93
}
94
}
95
assert(0);
96
}
97
98
enum pkg_os
99
pkg_os_from_string(const char *string)
100
{
101
for (size_t i = 0; os_string_table[i].string != NULL; i++) {
102
if (STREQ(string, os_string_table[i].string)) {
103
return os_string_table[i].os;
104
}
105
}
106
return (PKG_OS_UNKNOWN);
107
}
108
109
/* Returns true if the OS uses "amd64" rather than "x86_64" */
110
static bool
111
pkg_os_uses_amd64_name(enum pkg_os os)
112
{
113
switch (os) {
114
case PKG_OS_FREEBSD:
115
return (true);
116
case PKG_OS_DARWIN:
117
case PKG_OS_NETBSD:
118
case PKG_OS_LINUX:
119
return (false);
120
case PKG_OS_DRAGONFLY:
121
case PKG_OS_UNKNOWN:
122
default:
123
assert(0);
124
}
125
}
126
127
const char *
128
pkg_arch_to_string(enum pkg_os os, enum pkg_arch arch)
129
{
130
if (arch == PKG_ARCH_AMD64) {
131
if (os == PKG_OS_DRAGONFLY) {
132
return ("x86:64");
133
} else if (pkg_os_uses_amd64_name(os)) {
134
return ("amd64");
135
} else {
136
return ("x86_64");
137
}
138
}
139
140
for (size_t i = 0; arch_string_table[i].string != NULL; i++) {
141
if (arch == arch_string_table[i].arch) {
142
return arch_string_table[i].string;
143
}
144
}
145
146
assert(0);
147
}
148
149
enum pkg_arch
150
pkg_arch_from_string(enum pkg_os os, const char *string)
151
{
152
if (os == PKG_OS_DRAGONFLY) {
153
if (STREQ(string, "x86:64")) {
154
return (PKG_ARCH_AMD64);
155
}
156
} else if (pkg_os_uses_amd64_name(os)) {
157
if (STREQ(string, "amd64")) {
158
return (PKG_ARCH_AMD64);
159
}
160
} else {
161
if (STREQ(string, "x86_64")) {
162
return (PKG_ARCH_AMD64);
163
}
164
}
165
166
for (size_t i = 0; arch_string_table[i].string != NULL; i++) {
167
if (STREQ(string, arch_string_table[i].string)) {
168
return arch_string_table[i].arch;
169
}
170
}
171
172
return (PKG_ARCH_UNKNOWN);
173
}
174
175
bool
176
pkg_abi_string_only_major_version(enum pkg_os os)
177
{
178
switch (os) {
179
case PKG_OS_FREEBSD:
180
case PKG_OS_NETBSD:
181
case PKG_OS_DARWIN:
182
return (true);
183
case PKG_OS_DRAGONFLY:
184
case PKG_OS_LINUX:
185
return (false);
186
case PKG_OS_UNKNOWN:
187
default:
188
assert (0);
189
}
190
}
191
192
char *
193
pkg_abi_to_string(const struct pkg_abi *abi)
194
{
195
char *ret;
196
if (pkg_abi_string_only_major_version(abi->os)) {
197
xasprintf(&ret, "%s:%d:%s", pkg_os_to_string(abi->os),
198
abi->major, pkg_arch_to_string(abi->os, abi->arch));
199
} else {
200
xasprintf(&ret, "%s:%d.%d:%s", pkg_os_to_string(abi->os),
201
abi->major, abi->minor,
202
pkg_arch_to_string(abi->os, abi->arch));
203
}
204
return (ret);
205
}
206
207
bool
208
pkg_abi_from_string(struct pkg_abi *abi, const char *string)
209
{
210
*abi = (struct pkg_abi){0};
211
212
bool ret = false;
213
214
char *copy = xstrdup(string);
215
216
char *iter = copy;
217
char *os = strsep(&iter, ":");
218
assert(os != NULL);
219
abi->os = pkg_os_from_string(os);
220
if (abi->os == PKG_OS_UNKNOWN) {
221
pkg_emit_error("Unknown OS '%s' in ABI string", os);
222
goto out;
223
}
224
225
char *version = strsep(&iter, ":");
226
if (version == NULL) {
227
if (abi->os == PKG_OS_ANY) {
228
abi->major = 0;
229
abi->minor = 0;
230
abi->arch = PKG_ARCH_ANY;
231
ret = true;
232
goto out;
233
}
234
pkg_emit_error("Invalid ABI string '%s', "
235
"missing version and architecture", string);
236
goto out;
237
}
238
const char *errstr = NULL;
239
if (pkg_abi_string_only_major_version(abi->os)) {
240
abi->major = strtonum(version, 1, INT_MAX, &errstr);
241
} else {
242
/* XXX add tests for this */
243
char *major = strsep(&version, ".");
244
char *minor = strsep(&version, ".");
245
246
assert(major != NULL);
247
if (minor == NULL) {
248
pkg_emit_error("Invalid ABI string %s, "
249
"missing minor OS version", string);
250
goto out;
251
}
252
253
abi->major = strtonum(major, 1, INT_MAX, &errstr);
254
if (errstr != NULL) {
255
abi->minor = strtonum(minor, 1, INT_MAX, &errstr);
256
}
257
}
258
if (errstr != NULL) {
259
if (STREQ(version, "*")) {
260
abi->major = 0;
261
abi->minor = 0;
262
abi->arch = PKG_ARCH_ANY;
263
ret = true;
264
goto out;
265
}
266
pkg_emit_error("Invalid version in ABI string '%s'", string);
267
goto out;
268
}
269
270
/* DragonFlyBSD continues to use the legacy/altabi format.
271
For example: dragonfly:5.10:x86:64
272
This means we can't use strsep again since that would split the arch
273
string for dragonfly. */
274
char *arch = iter;
275
if (arch == NULL) {
276
pkg_emit_error("Invalid ABI string '%s', "
277
"missing architecture", string);
278
goto out;
279
}
280
281
abi->arch = pkg_arch_from_string(abi->os, arch);
282
if (abi->arch == PKG_ARCH_UNKNOWN) {
283
pkg_emit_error("Unknown architecture '%s' in ABI string", arch);
284
goto out;
285
}
286
287
if (abi->os == PKG_OS_DRAGONFLY && abi->arch != PKG_ARCH_AMD64) {
288
pkg_emit_error("Invalid ABI string '%s', "
289
"only x86:64 is supported on dragonfly.", string);
290
goto out;
291
}
292
293
ret = true;
294
out:
295
free(copy);
296
return (ret);
297
}
298
299
void
300
pkg_abi_set_freebsd_osversion(struct pkg_abi *abi, int osversion)
301
{
302
assert(abi->os == PKG_OS_FREEBSD);
303
304
abi->major = osversion / 100000;
305
abi->minor = (osversion / 1000) % 100;
306
abi->patch = osversion % 1000;
307
}
308
309
int
310
pkg_abi_get_freebsd_osversion(struct pkg_abi *abi)
311
{
312
assert(abi->os == PKG_OS_FREEBSD);
313
314
return (abi->major * 100000) + (abi->minor * 1000) + abi->patch;
315
}
316
317
int
318
pkg_abi_from_file(struct pkg_abi *abi)
319
{
320
char rooted_abi_file[PATH_MAX];
321
const char *abi_files[] = {
322
getenv("ABI_FILE"),
323
_PATH_UNAME,
324
_PATH_BSHELL,
325
};
326
char work_abi_file[PATH_MAX];
327
char work_arch_hint[PATH_MAX];
328
329
int i, fd;
330
331
/*
332
* Perhaps not yet needed, but it may be in the future that there's no
333
* need to check root under some conditions where there is a rootdir.
334
* This also helps alleviate some excessive wrapping later.
335
*/
336
bool checkroot = ctx.pkg_rootdir != NULL;
337
for (fd = -1, i = 0; i < NELEM(abi_files); i++) {
338
if (abi_files[i] == NULL)
339
continue;
340
341
const char *sep = strrchr(abi_files[i], '#');
342
if (sep) {
343
strlcpy(work_abi_file, abi_files[i],
344
MIN(sep - abi_files[i] + 1, sizeof(work_abi_file)));
345
strlcpy(work_arch_hint, sep + 1,
346
sizeof(work_arch_hint));
347
} else {
348
strlcpy(work_abi_file, abi_files[i],
349
sizeof(work_abi_file));
350
work_arch_hint[0] = '\0';
351
}
352
353
/*
354
* Try prepending rootdir and using that if it exists. If
355
* ABI_FILE is specified, assume that the consumer didn't want
356
* it mangled by rootdir.
357
*/
358
if (i > 0 && checkroot &&
359
snprintf(rooted_abi_file, PATH_MAX, "%s/%s",
360
ctx.pkg_rootdir, work_abi_file) < PATH_MAX) {
361
if ((fd = open(rooted_abi_file, O_RDONLY)) >= 0) {
362
strlcpy(work_abi_file, rooted_abi_file,
363
sizeof(work_abi_file));
364
break;
365
}
366
}
367
if ((fd = open(work_abi_file, O_RDONLY)) >= 0) {
368
break;
369
}
370
/* if the ABI_FILE was provided we only care about it */
371
if (i == 0)
372
break;
373
}
374
if (fd == -1) {
375
pkg_emit_error(
376
"Unable to determine the ABI, none of the ABI_FILEs can be read.");
377
return EPKG_FATAL;
378
}
379
380
381
int ret = pkg_elf_abi_from_fd(fd, abi);
382
if (EPKG_OK != ret) {
383
if (-1 == lseek(fd, 0, SEEK_SET)) {
384
pkg_emit_errno("Error seeking file", work_abi_file);
385
ret = EPKG_FATAL;
386
goto close_out;
387
}
388
389
enum pkg_arch arch_hint = PKG_ARCH_UNKNOWN;
390
if (work_arch_hint[0]) {
391
arch_hint = pkg_arch_from_string(PKG_OS_DARWIN, work_arch_hint);
392
if (arch_hint == PKG_ARCH_UNKNOWN) {
393
pkg_emit_error("Invalid ABI_FILE architecture hint %s",
394
work_arch_hint);
395
ret = EPKG_FATAL;
396
goto close_out;
397
}
398
}
399
400
ret = pkg_macho_abi_from_fd(fd, abi, arch_hint);
401
if (EPKG_OK != ret) {
402
pkg_emit_error(
403
"Unable to determine ABI, %s cannot be parsed.",
404
work_abi_file);
405
ret = EPKG_FATAL;
406
goto close_out;
407
}
408
}
409
410
close_out:
411
if (close(fd)) {
412
pkg_emit_errno("Error closing file", work_abi_file);
413
ret = EPKG_FATAL;
414
}
415
return ret;
416
}
417
418
int
419
pkg_arch_to_legacy(const char *arch, char *dest, size_t sz)
420
{
421
int i = 0;
422
const struct arch_trans *arch_trans;
423
424
memset(dest, '\0', sz);
425
/* Lower case the OS */
426
while (arch[i] != ':' && arch[i] != '\0') {
427
dest[i] = tolower(arch[i]);
428
i++;
429
}
430
if (arch[i] == '\0')
431
return (0);
432
433
dest[i++] = ':';
434
435
/* Copy the version */
436
while (arch[i] != ':' && arch[i] != '\0') {
437
dest[i] = arch[i];
438
i++;
439
}
440
if (arch[i] == '\0')
441
return (0);
442
443
dest[i++] = ':';
444
445
for (arch_trans = machine_arch_translation; arch_trans->elftype != NULL;
446
arch_trans++) {
447
if (STREQ(arch + i, arch_trans->archid)) {
448
strlcpy(dest + i, arch_trans->elftype,
449
sz - (arch + i - dest));
450
return (0);
451
}
452
}
453
strlcpy(dest + i, arch + i, sz - (arch + i - dest));
454
455
return (0);
456
}
457
458
void
459
pkg_cleanup_shlibs_required(struct pkg *pkg, charv_t *internal_provided)
460
{
461
struct pkg_file *file = NULL;
462
const char *lib;
463
464
vec_foreach(pkg->shlibs_required, i) {
465
char *s = pkg->shlibs_required.d[i];
466
if (charv_search(&pkg->shlibs_provided, s) != NULL ||
467
charv_search(internal_provided, s) != NULL) {
468
pkg_debug(2,
469
"remove %s from required shlibs as the "
470
"package %s provides this library itself",
471
s, pkg->name);
472
vec_remove_and_free(&pkg->shlibs_required, i, free);
473
i--;
474
continue;
475
}
476
if (charv_search(&pkg->shlibs_required_ignore, s) != NULL) {
477
pkg_debug(2,
478
"remove %s from required shlibs for package %s as it "
479
"is listed in shlibs_required_ignore.",
480
s, pkg->name);
481
vec_remove_and_free(&pkg->shlibs_required, i, free);
482
i--;
483
continue;
484
}
485
if (match_ucl_lists(s,
486
pkg_config_get("SHLIB_REQUIRE_IGNORE_GLOB"),
487
pkg_config_get("SHLIB_REQUIRE_IGNORE_REGEX"))) {
488
pkg_debug(2,
489
"remove %s from required shlibs for package %s as it "
490
"is matched by SHLIB_REQUIRE_IGNORE_GLOB/REGEX.",
491
s, pkg->name);
492
vec_remove(&pkg->shlibs_required, i);
493
if (charv_insert_sorted(&pkg->shlibs_required_ignore, s) != NULL) {
494
free(s);
495
}
496
i--;
497
continue;
498
}
499
file = NULL;
500
while (pkg_files(pkg, &file) == EPKG_OK) {
501
if ((lib = strstr(file->path, s)) != NULL &&
502
strlen(lib) == strlen(s) && lib[-1] == '/') {
503
pkg_debug(2,
504
"remove %s from required shlibs as "
505
"the package %s provides this file itself",
506
s, pkg->name);
507
508
vec_remove_and_free(&pkg->shlibs_required, i,
509
free);
510
i--;
511
break;
512
}
513
}
514
}
515
}
516
517
int
518
pkg_analyse_files(struct pkgdb *db __unused, struct pkg *pkg, const char *stage)
519
{
520
struct pkg_file *file = NULL;
521
int ret = EPKG_OK;
522
char fpath[MAXPATHLEN + 1];
523
bool failures = false;
524
525
int (*pkg_analyse_init)(const char *stage);
526
int (*pkg_analyse)(const bool developer_mode, struct pkg *pkg,
527
const char *fpath, char **provided, enum pkg_shlib_flags *flags);
528
int (*pkg_analyse_close)();
529
530
if (0 == strncmp(pkg->abi, "Darwin", 6)) {
531
pkg_analyse_init=pkg_analyse_init_macho;
532
pkg_analyse=pkg_analyse_macho;
533
pkg_analyse_close=pkg_analyse_close_macho;
534
} else {
535
pkg_analyse_init=pkg_analyse_init_elf;
536
pkg_analyse=pkg_analyse_elf;
537
pkg_analyse_close=pkg_analyse_close_elf;
538
}
539
540
if (vec_len(&pkg->shlibs_required) != 0) {
541
vec_free_and_free(&pkg->shlibs_required, free);
542
}
543
544
if (vec_len(&pkg->shlibs_provided) != 0) {
545
vec_free_and_free(&pkg->shlibs_provided, free);
546
}
547
548
ret = pkg_analyse_init(stage);
549
if (ret != EPKG_OK) {
550
goto cleanup;
551
}
552
553
/* Assume no architecture dependence, for contradiction */
554
if (ctx.developer_mode)
555
pkg->flags &= ~(PKG_CONTAINS_ELF_OBJECTS |
556
PKG_CONTAINS_STATIC_LIBS | PKG_CONTAINS_LA);
557
558
/* shlibs that are provided by files in the package but not matched by
559
SHLIB_PROVIDE_PATHS_* are still used to filter the shlibs
560
required by the package */
561
charv_t internal_provided = vec_init();
562
/* list of shlibs that are in the path to be evaluated for provided but are symlinks */
563
charv_t maybe_provided = vec_init();
564
565
while (pkg_files(pkg, &file) == EPKG_OK) {
566
struct stat st;
567
if (stage != NULL)
568
snprintf(fpath, sizeof(fpath), "%s/%s", stage,
569
file->path);
570
else
571
strlcpy(fpath, file->path, sizeof(fpath));
572
573
char *provided = NULL;
574
enum pkg_shlib_flags provided_flags = PKG_SHLIB_FLAGS_NONE;
575
576
ret = pkg_analyse(ctx.developer_mode, pkg, fpath, &provided, &provided_flags);
577
if (EPKG_WARN == ret) {
578
failures = true;
579
}
580
581
if (provided != NULL) {
582
const ucl_object_t *paths = NULL;
583
584
switch (provided_flags) {
585
case PKG_SHLIB_FLAGS_NONE:
586
paths = pkg_config_get("SHLIB_PROVIDE_PATHS_NATIVE");
587
break;
588
case PKG_SHLIB_FLAGS_COMPAT_32:
589
paths = pkg_config_get("SHLIB_PROVIDE_PATHS_COMPAT_32");
590
break;
591
case PKG_SHLIB_FLAGS_COMPAT_LINUX:
592
paths = pkg_config_get("SHLIB_PROVIDE_PATHS_COMPAT_LINUX");
593
break;
594
case (PKG_SHLIB_FLAGS_COMPAT_32 | PKG_SHLIB_FLAGS_COMPAT_LINUX):
595
paths = pkg_config_get("SHLIB_PROVIDE_PATHS_COMPAT_LINUX_32");
596
break;
597
default:
598
assert(0);
599
}
600
assert(paths != NULL);
601
602
if (lstat(fpath, &st) != 0) {
603
pkg_emit_errno("lstat() failed for", fpath);
604
free(provided);
605
continue;
606
}
607
/* If the corresponding PATHS option isn't set (i.e. an empty ucl array)
608
don't do any filtering for backwards compatibility. */
609
if (ucl_array_size(paths) == 0 || pkg_match_paths_list(paths, file->path)) {
610
if (S_ISREG(st.st_mode)) {
611
pkg_addshlib_provided(pkg, provided, provided_flags);
612
} else {
613
vec_push(&maybe_provided, pkg_shlib_name_with_flags(provided, provided_flags));
614
}
615
} else {
616
vec_push(&internal_provided, pkg_shlib_name_with_flags(provided, provided_flags));
617
}
618
free(provided);
619
}
620
}
621
622
vec_foreach(maybe_provided, i) {
623
vec_foreach(internal_provided, j) {
624
if (STREQ(maybe_provided.d[i], internal_provided.d[j])) {
625
pkg_addshlib_provided(pkg, maybe_provided.d[i], PKG_SHLIB_FLAGS_NONE);
626
vec_remove_and_free(&internal_provided, j, free);
627
j--;
628
}
629
}
630
vec_remove_and_free(&maybe_provided, i, free);
631
i--;
632
}
633
vec_free(&maybe_provided);
634
/*
635
* Do not depend on libraries that a package provides itself
636
*/
637
pkg_cleanup_shlibs_required(pkg, &internal_provided);
638
while (internal_provided.len > 0) {
639
char *s = vec_pop(&internal_provided);
640
if (charv_insert_sorted(&pkg->shlibs_provided_ignore, s) != NULL) {
641
free(s);
642
}
643
}
644
645
vec_foreach(pkg->shlibs_provided, i) {
646
char *s = pkg->shlibs_provided.d[i];
647
if (charv_search(&pkg->shlibs_provided_ignore, s) != NULL) {
648
pkg_debug(2,
649
"remove %s from provided shlibs for package %s as it "
650
"is listed in shlibs_provided_ignore.",
651
s, pkg->name);
652
vec_remove_and_free(&pkg->shlibs_provided, i, free);
653
i--;
654
continue;
655
}
656
if (match_ucl_lists(s,
657
pkg_config_get("SHLIB_PROVIDE_IGNORE_GLOB"),
658
pkg_config_get("SHLIB_PROVIDE_IGNORE_REGEX"))) {
659
pkg_debug(2,
660
"remove %s from provided shlibs for package %s as it "
661
"is matched by SHLIB_PROVIDE_IGNORE_GLOB/REGEX.",
662
s, pkg->name);
663
vec_remove(&pkg->shlibs_provided, i);
664
if (charv_insert_sorted(&pkg->shlibs_provided_ignore, s) != NULL) {
665
free(s);
666
}
667
i--;
668
continue;
669
}
670
}
671
672
/*
673
* if the package is not supposed to provide share libraries then
674
* drop the provided one
675
*/
676
if (pkg_kv_get(&pkg->annotations, "no_provide_shlib") != NULL) {
677
vec_free_and_free(&pkg->shlibs_provided, free);
678
}
679
680
if (failures)
681
goto cleanup;
682
683
ret = EPKG_OK;
684
685
cleanup:
686
ret = pkg_analyse_close();
687
688
return (ret);
689
}
690
691