diff options
Diffstat (limited to 'cli')
| -rw-r--r-- | cli/bomtool/main.c | 222 | ||||
| -rw-r--r-- | cli/core.c | 1464 | ||||
| -rw-r--r-- | cli/core.h | 103 | ||||
| -rw-r--r-- | cli/getopt_long.c | 3 | ||||
| -rw-r--r-- | cli/getopt_long.h | 2 | ||||
| -rw-r--r-- | cli/main.c | 1639 | ||||
| -rw-r--r-- | cli/renderer-msvc.c | 138 | ||||
| -rw-r--r-- | cli/renderer-msvc.h | 4 | ||||
| -rw-r--r-- | cli/spdxtool/core.c | 729 | ||||
| -rw-r--r-- | cli/spdxtool/core.h | 77 | ||||
| -rw-r--r-- | cli/spdxtool/main.c | 425 | ||||
| -rw-r--r-- | cli/spdxtool/serialize.c | 467 | ||||
| -rw-r--r-- | cli/spdxtool/serialize.h | 539 | ||||
| -rw-r--r-- | cli/spdxtool/simplelicensing.c | 113 | ||||
| -rw-r--r-- | cli/spdxtool/simplelicensing.h | 36 | ||||
| -rw-r--r-- | cli/spdxtool/software.c | 447 | ||||
| -rw-r--r-- | cli/spdxtool/software.h | 37 | ||||
| -rw-r--r-- | cli/spdxtool/util.c | 331 | ||||
| -rw-r--r-- | cli/spdxtool/util.h | 130 |
19 files changed, 5285 insertions, 1621 deletions
diff --git a/cli/bomtool/main.c b/cli/bomtool/main.c index 1e8391aa5ae9..136d7c1ce2ca 100644 --- a/cli/bomtool/main.c +++ b/cli/bomtool/main.c @@ -2,6 +2,8 @@ * bomtool/main.c * main() routine, printer functions * + * SPDX-License-Identifier: pkgconf + * * Copyright (c) 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 * pkgconf authors (see AUTHORS). * @@ -14,6 +16,9 @@ * from the use of this software. */ +#include <ctype.h> +#include <time.h> + #include "libpkgconf/config.h" #include <libpkgconf/stdinc.h> #include <libpkgconf/libpkgconf.h> @@ -22,6 +27,8 @@ #define PKG_VERSION (((uint64_t) 1) << 1) #define PKG_ABOUT (((uint64_t) 1) << 2) #define PKG_HELP (((uint64_t) 1) << 3) +#define PKG_OUTPUT (((uint64_t) 1) << 4) +#define PKG_DEFINE_VARIABLE (((uint64_t) 1) << 5) static const char *spdx_version = "SPDX-2.2"; static const char *bom_license = "CC0-1.0"; @@ -31,14 +38,39 @@ static pkgconf_client_t pkg_client; static uint64_t want_flags; static size_t maximum_package_count = 0; static int maximum_traverse_depth = 2000; -FILE *error_msgout = NULL; +static FILE *error_msgout = NULL; +static FILE *sbom_out = NULL; + +#define OUTPUT_OR_RET(client, f, fmt, ...) \ + do { \ + if (!pkgconf_output_file_fmt((f), (fmt), ##__VA_ARGS__)) { \ + pkgconf_error((client), "bomtool: Could not output to file: %s", strerror(errno)); \ + return; \ + } \ + } while (0) + +#define OUTPUT_OR_RET_FALSE(client, f, fmt, ...) \ + do { \ + if (!pkgconf_output_file_fmt((f), (fmt), ##__VA_ARGS__)) { \ + pkgconf_error((client), "bomtool: Could not output to file: %s", strerror(errno)); \ + return false; \ + } \ + } while (0) + +static const char * +environ_lookup_handler(const pkgconf_client_t *client, const char *key) +{ + (void) client; + + return getenv(key); +} static bool error_handler(const char *msg, const pkgconf_client_t *client, void *data) { (void) client; (void) data; - fprintf(error_msgout, "%s", msg); + OUTPUT_OR_RET_FALSE(client, error_msgout, "%s", msg); return true; } @@ -46,19 +78,32 @@ static const char * sbom_spdx_identity(pkgconf_pkg_t *pkg) { static char buf[PKGCONF_ITEM_SIZE]; + size_t i, o; - snprintf(buf, sizeof buf, "%sC64%s", pkg->id, pkg->version); - + /* Sanitize the package ID: only letters, numbers, dot (.) and dash (-) + * are allowed. + */ + for (i = 0, o = 0; i < strlen(pkg->id) && o < sizeof(buf); i++, o++) { + char c = pkg->id[i]; + if (c == '-' || c == '.' || isalnum(c)) + buf[o] = c; + else { + snprintf(buf + o, sizeof(buf) - o, "C%02x", c); + o += 2; + } + } + snprintf(buf + o, sizeof(buf) - o, "C40%s", pkg->version); + /* ^^^ 0x40 is the at sign (@) */ return buf; } -static const char * +static char * sbom_name(pkgconf_pkg_t *world) { - static char buf[PKGCONF_BUFSIZE]; + pkgconf_buffer_t name = PKGCONF_BUFFER_INITIALIZER; pkgconf_node_t *node; - pkgconf_strlcpy(buf, "SBOM-SPDX", sizeof buf); + pkgconf_buffer_append(&name, "SBOM-SPDX"); PKGCONF_FOREACH_LIST_ENTRY(world->required.head, node) { @@ -71,27 +116,49 @@ sbom_name(pkgconf_pkg_t *world) if (!dep->match) continue; - pkgconf_strlcat(buf, "-", sizeof buf); - pkgconf_strlcat(buf, sbom_spdx_identity(match), sizeof buf); + pkgconf_buffer_append_fmt(&name, "-%s", sbom_spdx_identity(match)); } - return buf; + return pkgconf_buffer_freeze(&name); } -static void +static bool write_sbom_header(pkgconf_client_t *client, pkgconf_pkg_t *world) { - (void) client; - (void) world; + time_t t; + struct tm *tm; + char buf[21]; + + OUTPUT_OR_RET_FALSE(client, sbom_out, "SPDXVersion: %s\n", spdx_version); + OUTPUT_OR_RET_FALSE(client, sbom_out, "DataLicense: %s\n", bom_license); + OUTPUT_OR_RET_FALSE(client, sbom_out, "SPDXID: %s\n", document_ref); + + char *docname = sbom_name(world); + if (!docname) + { + pkgconf_error(client, "write_sbom_header: out of memory"); + return false; + } + + if (!pkgconf_output_file_fmt(sbom_out, "DocumentName: %s\n", docname)) + { + free(docname); + return false; + } + + free(docname); - printf("SPDXVersion: %s\n", spdx_version); - printf("DataLicense: %s\n", bom_license); - printf("SPDXID: %s\n", document_ref); - printf("DocumentName: %s\n", sbom_name(world)); - printf("DocumentNamespace: https://spdx.org/spdxdocs/bomtool-%s\n", PACKAGE_VERSION); - printf("Creator: Tool: bomtool %s\n", PACKAGE_VERSION); + OUTPUT_OR_RET_FALSE(client, sbom_out, "DocumentNamespace: https://spdx.org/spdxdocs/bomtool-%s\n", PACKAGE_VERSION); + OUTPUT_OR_RET_FALSE(client, sbom_out, "Creator: Tool: bomtool %s\n", PACKAGE_VERSION); - printf("\n\n"); + t = time(NULL); + tm = gmtime(&t); + strftime(buf, sizeof(buf), "%Y-%m-%dT%H:%M:%SZ", tm); + OUTPUT_OR_RET_FALSE(client, sbom_out, "Created: %s\n", buf); + + OUTPUT_OR_RET_FALSE(client, sbom_out, "\n\n"); + + return true; } static const char * @@ -104,39 +171,87 @@ sbom_identity(pkgconf_pkg_t *pkg) return buf; } +static bool +write_copyright_lines(pkgconf_client_t *client, const pkgconf_list_t *copyright_lines) +{ + const pkgconf_node_t *node; + + if (copyright_lines->head == NULL) { + OUTPUT_OR_RET_FALSE(client, sbom_out, "PackageCopyrightText: NOASSERTION\n"); + return true; + } + + OUTPUT_OR_RET_FALSE(client, sbom_out, "PackageCopyrightText: <text>"); + + PKGCONF_FOREACH_LIST_ENTRY(copyright_lines->head, node) + { + const pkgconf_bufferset_t *set = node->data; + OUTPUT_OR_RET_FALSE(client, sbom_out, "%s%s", pkgconf_buffer_str_or_empty(&set->buffer), node->prev != NULL ? "\n" : ""); + } + + OUTPUT_OR_RET_FALSE(client, sbom_out, "</text>\n"); + + return true; +} + static void write_sbom_package(pkgconf_client_t *client, pkgconf_pkg_t *pkg, void *unused) { + pkgconf_buffer_t license_buf = PKGCONF_BUFFER_INITIALIZER; (void) client; (void) unused; if (pkg->flags & PKGCONF_PKG_PROPF_VIRTUAL) return; - printf("##### Package: %s\n\n", sbom_identity(pkg)); + OUTPUT_OR_RET(client, sbom_out, "##### Package: %s\n\n", sbom_identity(pkg)); + OUTPUT_OR_RET(client, sbom_out, "PackageName: %s\n", sbom_identity(pkg)); + OUTPUT_OR_RET(client, sbom_out, "SPDXID: SPDXRef-Package-%s\n", sbom_spdx_identity(pkg)); + OUTPUT_OR_RET(client, sbom_out, "PackageVersion: %s\n", pkg->version); + OUTPUT_OR_RET(client, sbom_out, "PackageDownloadLocation: NOASSERTION\n"); - printf("PackageName: %s\n", sbom_identity(pkg)); - printf("SPDXID: SPDXRef-Package-%s\n", sbom_spdx_identity(pkg)); - printf("PackageVersion: %s\n", pkg->version); - printf("PackageDownloadLocation: NOASSERTION\n"); - printf("PackageVerificationCode: NOASSERTION\n"); + /* NOASSERTION is not a valide value for PackageVerificationCode. It + * expect 40 lowercase hexadecimal digits. + */ +#if 0 + OUTPUT_OR_RET(client, sbom_out, "PackageVerificationCode: NOASSERTION\n"); +#endif /* XXX: What about projects? */ if (pkg->maintainer != NULL) - printf("PackageSupplier: Person: %s\n", pkg->maintainer); + OUTPUT_OR_RET(client, sbom_out, "PackageSupplier: Person: %s\n", pkg->maintainer); if (pkg->url != NULL) - printf("PackageHomePage: %s\n", pkg->url); + OUTPUT_OR_RET(client, sbom_out, "PackageHomePage: %s\n", pkg->url); - printf("PackageLicenseDeclared: %s\n", pkg->license != NULL ? pkg->license : "NOASSERTION"); + if (pkg->license.head != NULL) + { + pkgconf_license_render(client, &pkg->license, &license_buf); + bool ret = pkgconf_output_file_fmt(sbom_out, "PackageLicenseDeclared: %s\n", pkgconf_buffer_str_or_empty(&license_buf)); + int errno_save = errno; + pkgconf_buffer_finalize(&license_buf); + if (!ret) + { + pkgconf_error(client, "bomtool: could not output to file: %s", strerror(errno_save)); + return; + } + } + else + OUTPUT_OR_RET(client, sbom_out, "PackageLicenseDeclared: NOASSERTION\n"); + OUTPUT_OR_RET(client, sbom_out, "PackageLicenseConcluded: NOASSERTION\n"); - if (pkg->copyright != NULL) - printf("PackageCopyrightText: <text>%s</text>\n", pkg->copyright); + if (!write_copyright_lines(client, &pkg->copyright)) + return; if (pkg->description != NULL) - printf("PackageSummary: <text>%s</text>\n", pkg->description); + OUTPUT_OR_RET(client, sbom_out, "PackageSummary: <text>%s</text>\n", pkg->description); - printf("\n\n"); + if (pkg->source != NULL) + OUTPUT_OR_RET(client, sbom_out, "PackageDownloadLocation: %s\n", pkg->source); + else + OUTPUT_OR_RET(client, sbom_out, "PackageDownloadLocation: NOASSERTION\n"); + + OUTPUT_OR_RET(client, sbom_out, "\n\n"); } static void @@ -151,7 +266,7 @@ write_sbom_relationships(pkgconf_client_t *client, pkgconf_pkg_t *pkg, void *unu if (pkg->flags & PKGCONF_PKG_PROPF_VIRTUAL) return; - snprintf(baseref, sizeof baseref, "SPDXRef-Package-%sC64%s", pkg->id, pkg->version); + snprintf(baseref, sizeof baseref, "SPDXRef-Package-%s", sbom_spdx_identity(pkg)); PKGCONF_FOREACH_LIST_ENTRY(pkg->required.head, node) { @@ -161,8 +276,8 @@ write_sbom_relationships(pkgconf_client_t *client, pkgconf_pkg_t *pkg, void *unu if (!dep->match) continue; - printf("Relationship: %s DEPENDS_ON SPDXRef-Package-%s\n", baseref, sbom_spdx_identity(match)); - printf("Relationship: SPDXRef-Package-%s DEPENDENCY_OF %s\n", sbom_spdx_identity(match), baseref); + OUTPUT_OR_RET(client, sbom_out, "Relationship: %s DEPENDS_ON SPDXRef-Package-%s\n", baseref, sbom_spdx_identity(match)); + OUTPUT_OR_RET(client, sbom_out, "Relationship: SPDXRef-Package-%s DEPENDENCY_OF %s\n", sbom_spdx_identity(match), baseref); } PKGCONF_FOREACH_LIST_ENTRY(pkg->requires_private.head, node) @@ -173,12 +288,12 @@ write_sbom_relationships(pkgconf_client_t *client, pkgconf_pkg_t *pkg, void *unu if (!dep->match) continue; - printf("Relationship: %s DEPENDS_ON SPDXRef-Package-%s\n", baseref, sbom_spdx_identity(match)); - printf("Relationship: SPDXRef-Package-%s DEV_DEPENDENCY_OF %s\n", sbom_spdx_identity(match), baseref); + OUTPUT_OR_RET(client, sbom_out, "Relationship: %s DEPENDS_ON SPDXRef-Package-%s\n", baseref, sbom_spdx_identity(match)); + OUTPUT_OR_RET(client, sbom_out, "Relationship: SPDXRef-Package-%s DEV_DEPENDENCY_OF %s\n", sbom_spdx_identity(match), baseref); } if (pkg->required.head != NULL || pkg->requires_private.head != NULL) - printf("\n\n"); + OUTPUT_OR_RET(client, sbom_out, "\n\n"); } static bool @@ -187,7 +302,8 @@ generate_sbom_from_world(pkgconf_client_t *client, pkgconf_pkg_t *world) int eflag; pkgconf_node_t *node; - write_sbom_header(client, world); + if (!write_sbom_header(client, world)) + return false; eflag = pkgconf_pkg_traverse(client, world, write_sbom_package, NULL, maximum_traverse_depth, 0); if (eflag != PKGCONF_PKG_ERRF_OK) @@ -205,7 +321,7 @@ generate_sbom_from_world(pkgconf_client_t *client, pkgconf_pkg_t *world) if (!dep->match) continue; - printf("Relationship: %s DESCRIBES SPDXRef-Package-%s\n", document_ref, sbom_spdx_identity(match)); + OUTPUT_OR_RET_FALSE(client, sbom_out, "Relationship: %s DESCRIBES SPDXRef-Package-%s\n", document_ref, sbom_spdx_identity(match)); } return true; @@ -244,6 +360,8 @@ usage(void) printf(" --help this message\n"); printf(" --about print bomtool version and license to stdout\n"); printf(" --version print bomtool version to stdout\n"); + printf(" --output FILE output SBOM text to FILE\n"); + printf(" --define-variable=varname=value define variable 'varname' as 'value'\n"); return EXIT_SUCCESS; } @@ -262,11 +380,14 @@ main(int argc, char *argv[]) }; error_msgout = stderr; + sbom_out = stdout; struct pkg_option options[] = { { "version", no_argument, &want_flags, PKG_VERSION, }, { "about", no_argument, &want_flags, PKG_ABOUT, }, { "help", no_argument, &want_flags, PKG_HELP, }, + { "output", required_argument, NULL, PKG_OUTPUT, }, + { "define-variable", required_argument, NULL, PKG_DEFINE_VARIABLE, }, { NULL, 0, NULL, 0 } }; @@ -274,6 +395,18 @@ main(int argc, char *argv[]) { switch (ret) { + case PKG_OUTPUT: + sbom_out = fopen(pkg_optarg, "w"); + if (sbom_out == NULL) + { + pkgconf_output_file_fmt(stderr, "unable to open %s: %s\n", pkg_optarg, strerror(errno)); + return EXIT_FAILURE; + } + + break; + case PKG_DEFINE_VARIABLE: + pkgconf_tuple_define_global(&pkg_client, pkg_optarg); + break; case '?': case ':': return EXIT_FAILURE; @@ -282,7 +415,7 @@ main(int argc, char *argv[]) } } - pkgconf_client_init(&pkg_client, error_handler, NULL, personality); + pkgconf_client_init(&pkg_client, error_handler, NULL, personality, NULL, environ_lookup_handler); /* we have determined what features we want most likely. in some cases, we override later. */ pkgconf_client_set_flags(&pkg_client, want_client_flags); @@ -339,7 +472,7 @@ main(int argc, char *argv[]) if (pkgq.head == NULL) { - fprintf(stderr, "Please specify at least one package name on the command line.\n"); + pkgconf_output_file_fmt(stderr, "Please specify at least one package name on the command line.\n"); ret = EXIT_FAILURE; goto out; } @@ -359,6 +492,9 @@ main(int argc, char *argv[]) } out: + if (sbom_out != stdout) + fclose(sbom_out); + pkgconf_solution_free(&pkg_client, &world); pkgconf_queue_free(&pkgq); pkgconf_cross_personality_deinit(personality); diff --git a/cli/core.c b/cli/core.c new file mode 100644 index 000000000000..abdbeb8a5167 --- /dev/null +++ b/cli/core.c @@ -0,0 +1,1464 @@ +/* + * core.c + * core, printer functions + * + * SPDX-License-Identifier: pkgconf + * + * Copyright (c) 2011-2025 pkgconf authors (see AUTHORS). + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include "libpkgconf/config.h" +#include <libpkgconf/stdinc.h> +#include <libpkgconf/libpkgconf.h> +#include "core.h" +#include "getopt_long.h" +#ifndef PKGCONF_LITE +#include "renderer-msvc.h" +#endif + +static bool +print_list_entry(const pkgconf_pkg_t *entry, void *data) +{ + const pkgconf_node_t *n; + pkgconf_client_t *client = data; + + if (entry->flags & PKGCONF_PKG_PROPF_UNINSTALLED) + return false; + + pkgconf_output_fmt(client->output, PKGCONF_OUTPUT_STDOUT, + "%-30s %s - %s\n", entry->id, entry->realname, entry->description); + + PKGCONF_FOREACH_LIST_ENTRY(entry->provides.head, n) + { + const pkgconf_dependency_t *dep = n->data; + + if (!strcmp(dep->package, entry->id)) + continue; + + pkgconf_output_fmt(client->output, PKGCONF_OUTPUT_STDOUT, + "%-30s %s - %s (provided by %s)\n", dep->package, entry->realname, entry->description, entry->id); + } + + return false; +} + +static bool +print_package_entry(const pkgconf_pkg_t *entry, void *data) +{ + const pkgconf_node_t *n; + pkgconf_client_t *client = data; + + if (entry->flags & PKGCONF_PKG_PROPF_UNINSTALLED) + return false; + + pkgconf_output_puts(client->output, PKGCONF_OUTPUT_STDOUT, entry->id); + + PKGCONF_FOREACH_LIST_ENTRY(entry->provides.head, n) + { + const pkgconf_dependency_t *dep = n->data; + + if (!strcmp(dep->package, entry->id)) + continue; + + pkgconf_output_puts(client->output, PKGCONF_OUTPUT_STDOUT, dep->package); + } + + return false; +} + +static bool +filter_cflags(const pkgconf_client_t *client, const pkgconf_fragment_t *frag, void *data) +{ + int got_flags = 0; + pkgconf_cli_state_t *state = client->client_data; + (void) data; + + if (!(state->want_flags & PKG_KEEP_SYSTEM_CFLAGS) && pkgconf_fragment_has_system_dir(client, frag)) + return false; + + if (state->want_fragment_filter != NULL && (strchr(state->want_fragment_filter, frag->type) == NULL || !frag->type)) + return false; + + if (frag->type == 'I') + got_flags = PKG_CFLAGS_ONLY_I; + else + got_flags = PKG_CFLAGS_ONLY_OTHER; + + return (state->want_flags & got_flags) != 0; +} + +static bool +filter_libs(const pkgconf_client_t *client, const pkgconf_fragment_t *frag, void *data) +{ + int got_flags = 0; + pkgconf_cli_state_t *state = client->client_data; + (void) data; + + if (!(state->want_flags & PKG_KEEP_SYSTEM_LIBS) && pkgconf_fragment_has_system_dir(client, frag)) + return false; + + if (state->want_fragment_filter != NULL && (strchr(state->want_fragment_filter, frag->type) == NULL || !frag->type)) + return false; + + switch (frag->type) + { + case 'L': got_flags = PKG_LIBS_ONLY_LDPATH; break; + case 'l': got_flags = PKG_LIBS_ONLY_LIBNAME; break; + default: got_flags = PKG_LIBS_ONLY_OTHER; break; + } + + return (state->want_flags & got_flags) != 0; +} + +static void +print_variables(pkgconf_output_t *output, pkgconf_pkg_t *pkg) +{ + pkgconf_node_t *node; + + PKGCONF_FOREACH_LIST_ENTRY(pkg->vars.head, node) + { + pkgconf_tuple_t *tuple = node->data; + + pkgconf_output_puts(output, PKGCONF_OUTPUT_STDOUT, tuple->key); + } +} + +static void +print_dependency_list(pkgconf_output_t *output, pkgconf_list_t *list) +{ + pkgconf_node_t *node; + + PKGCONF_FOREACH_LIST_ENTRY(list->head, node) + { + pkgconf_dependency_t *dep = node->data; + + pkgconf_output_fmt(output, PKGCONF_OUTPUT_STDOUT, "%s", dep->package); + + if (dep->version != NULL) + pkgconf_output_fmt(output, PKGCONF_OUTPUT_STDOUT, " %s %s", + pkgconf_pkg_get_comparator(dep), dep->version); + + pkgconf_output_fmt(output, PKGCONF_OUTPUT_STDOUT, "\n"); + } +} + +static bool +apply_provides(pkgconf_client_t *client, pkgconf_pkg_t *world, void *unused, int maxdepth) +{ + pkgconf_node_t *iter; + (void) unused; + (void) maxdepth; + + PKGCONF_FOREACH_LIST_ENTRY(world->required.head, iter) + { + pkgconf_dependency_t *dep = iter->data; + pkgconf_pkg_t *pkg = dep->match; + + print_dependency_list(client->output, &pkg->provides); + } + + return true; +} + +#ifndef PKGCONF_LITE +static void +print_digraph_node(pkgconf_client_t *client, pkgconf_pkg_t *pkg, void *data) +{ + pkgconf_node_t *node; + pkgconf_pkg_t **last_seen = data; + + if (pkg->flags & PKGCONF_PKG_PROPF_VIRTUAL) + return; + + pkgconf_output_fmt(client->output, PKGCONF_OUTPUT_STDOUT, "\"%s\" [fontname=Sans fontsize=8", pkg->id); + + if (pkg->flags & PKGCONF_PKG_PROPF_VISITED_PRIVATE) + pkgconf_output_fmt(client->output, PKGCONF_OUTPUT_STDOUT, " fontcolor=gray color=gray"); + + pkgconf_output_fmt(client->output, PKGCONF_OUTPUT_STDOUT, " label=\"%s@%s\"]\n", pkg->id, pkg->version); + + if (last_seen != NULL) + { + if (*last_seen != NULL) + pkgconf_output_fmt(client->output, PKGCONF_OUTPUT_STDOUT, + "\"%s\" -> \"%s\" [fontname=Sans fontsize=8 color=red]\n", (*last_seen)->id, pkg->id); + + *last_seen = pkg; + } + + PKGCONF_FOREACH_LIST_ENTRY(pkg->required.head, node) + { + pkgconf_dependency_t *dep = node->data; + const char *dep_id = (dep->match != NULL) ? dep->match->id : dep->package; + + pkgconf_output_fmt(client->output, PKGCONF_OUTPUT_STDOUT, "\"%s\" -> \"%s\" [fontname=Sans fontsize=8", + pkg->id, dep_id); + + if (dep->flags & PKGCONF_PKG_DEPF_PRIVATE) + pkgconf_output_fmt(client->output, PKGCONF_OUTPUT_STDOUT, " color=gray"); + + pkgconf_output_puts(client->output, PKGCONF_OUTPUT_STDOUT, "]"); + } + + PKGCONF_FOREACH_LIST_ENTRY(pkg->requires_private.head, node) + { + pkgconf_dependency_t *dep = node->data; + const char *dep_id = (dep->match != NULL) ? dep->match->id : dep->package; + + pkgconf_output_fmt(client->output, PKGCONF_OUTPUT_STDOUT, + "\"%s\" -> \"%s\" [fontname=Sans fontsize=8 color=gray]\n", pkg->id, dep_id); + } + + if (!(client->flags & PKGCONF_PKG_PKGF_MERGE_PRIVATE_FRAGMENTS)) + { + PKGCONF_FOREACH_LIST_ENTRY(pkg->requires_shared.head, node) + { + pkgconf_dependency_t *dep = node->data; + const char *dep_id = (dep->match != NULL) ? dep->match->id : dep->package; + pkgconf_output_fmt(client->output, PKGCONF_OUTPUT_STDOUT, + "\"%s\" -> \"%s\" [fontname=Sans fontsize=8 color=orange]\n", pkg->id, dep_id); + } + } +} + +static bool +apply_digraph(pkgconf_client_t *client, pkgconf_pkg_t *world, void *data, int maxdepth) +{ + int eflag; + pkgconf_cli_state_t *state = client->client_data; + pkgconf_list_t *list = data; + pkgconf_pkg_t *last_seen = NULL; + pkgconf_node_t *iter; + + pkgconf_output_puts(client->output, PKGCONF_OUTPUT_STDOUT, + "digraph deptree {\n" + "edge [color=blue len=7.5 fontname=Sans fontsize=8]\n" + "node [fontname=Sans fontsize=8]\n"); + + if (state->want_flags & PKG_PRINT_DIGRAPH_QUERY_NODES) + { + pkgconf_output_puts(client->output, PKGCONF_OUTPUT_STDOUT, + "\"user:request\" [fontname=Sans fontsize=8]\n"); + + PKGCONF_FOREACH_LIST_ENTRY(list->head, iter) + { + pkgconf_queue_t *pkgq = iter->data; + pkgconf_pkg_t *pkg = pkgconf_pkg_find(client, pkgq->package); + + pkgconf_output_fmt(client->output, PKGCONF_OUTPUT_STDOUT, + "\"user:request\" -> \"%s\" [fontname=Sans fontsize=8]\n", + pkg == NULL ? pkgq->package : pkg->id); + + if (pkg != NULL) + pkgconf_pkg_unref(client, pkg); + } + } + + eflag = pkgconf_pkg_traverse(client, world, print_digraph_node, &last_seen, maxdepth, 0); + + if (eflag != PKGCONF_PKG_ERRF_OK) + return false; + + pkgconf_output_puts(client->output, PKGCONF_OUTPUT_STDOUT, "}"); + return true; +} + +static void +print_solution_node(pkgconf_client_t *client, pkgconf_pkg_t *pkg, void *unused) +{ + (void) unused; + + pkgconf_output_fmt(client->output, PKGCONF_OUTPUT_STDOUT, "%s (%"PRIu64")%s\n", + pkg->id, pkg->identifier, + (pkg->flags & PKGCONF_PKG_PROPF_VISITED_PRIVATE) == PKGCONF_PKG_PROPF_VISITED_PRIVATE ? " [private]" : ""); +} + +static bool +apply_print_solution(pkgconf_client_t *client, pkgconf_pkg_t *world, void *unused, int maxdepth) +{ + int eflag; + + eflag = pkgconf_pkg_traverse(client, world, print_solution_node, unused, maxdepth, 0); + + return eflag == PKGCONF_PKG_ERRF_OK; +} +#endif + +static bool +apply_modversion(pkgconf_client_t *client, pkgconf_pkg_t *world, void *data, int maxdepth) +{ + pkgconf_cli_state_t *state = client->client_data; + pkgconf_node_t *queue_iter; + pkgconf_list_t *pkgq = data; + (void) maxdepth; + + PKGCONF_FOREACH_LIST_ENTRY(pkgq->head, queue_iter) + { + pkgconf_node_t *world_iter; + pkgconf_queue_t *queue_node = queue_iter->data; + + PKGCONF_FOREACH_LIST_ENTRY(world->required.head, world_iter) + { + pkgconf_dependency_t *dep = world_iter->data; + pkgconf_pkg_t *pkg = dep->match; + + const size_t name_len = strlen(pkg->why); + if (name_len > strlen(queue_node->package) || + strncmp(pkg->why, queue_node->package, name_len) || + (queue_node->package[name_len] != 0 && + !isspace((unsigned char)queue_node->package[name_len]) && + !PKGCONF_IS_OPERATOR_CHAR(queue_node->package[name_len]))) + { + continue; + } + + if (pkg->version != NULL) { + if (state->verbosity) + pkgconf_output_fmt(client->output, PKGCONF_OUTPUT_STDOUT, "%s: ", pkg->id); + + pkgconf_output_puts(client->output, PKGCONF_OUTPUT_STDOUT, pkg->version); + } + + break; + } + } + + return true; +} + +static bool +apply_variables(pkgconf_client_t *client, pkgconf_pkg_t *world, void *unused, int maxdepth) +{ + pkgconf_node_t *iter; + (void) unused; + (void) maxdepth; + + PKGCONF_FOREACH_LIST_ENTRY(world->required.head, iter) + { + pkgconf_dependency_t *dep = iter->data; + pkgconf_pkg_t *pkg = dep->match; + + print_variables(client->output, pkg); + } + + return true; +} + +static bool +apply_path(pkgconf_client_t *client, pkgconf_pkg_t *world, void *unused, int maxdepth) +{ + pkgconf_node_t *iter; + (void) unused; + (void) maxdepth; + + PKGCONF_FOREACH_LIST_ENTRY(world->required.head, iter) + { + pkgconf_dependency_t *dep = iter->data; + pkgconf_pkg_t *pkg = dep->match; + + /* a module entry with no filename is either virtual, static (builtin) or synthesized. */ + if (pkg->filename != NULL) + pkgconf_output_puts(client->output, PKGCONF_OUTPUT_STDOUT, pkg->filename); + } + + return true; +} + +static char * +variable_eval(pkgconf_client_t *client, const pkgconf_list_t *vars, const char *varname) +{ + pkgconf_buffer_t varbuf = PKGCONF_BUFFER_INITIALIZER; + const pkgconf_buffer_t *sysroot_dir = PKGCONF_BUFFER_FROM_STR(client->sysroot_dir); + bool saw_sysroot = false; + pkgconf_variable_t *v; + + pkgconf_bytecode_eval_ctx_t ctx = { + .client = client, + .vars = vars, + }; + + v = pkgconf_bytecode_eval_lookup_var(&ctx, varname, strlen(varname)); + (void) pkgconf_variable_eval(client, vars, v, &varbuf, &saw_sysroot); + + if (!saw_sysroot && pkgconf_path_is_plausible(&varbuf)) + { + /* if sysroot is set, and value does not already begin with sysroot */ + if (!pkgconf_buffer_has_prefix(&varbuf, sysroot_dir)) + pkgconf_buffer_prepend(&varbuf, pkgconf_buffer_str_or_empty(sysroot_dir)); + } + + return pkgconf_buffer_freeze(&varbuf); +} + +static bool +apply_variable(pkgconf_client_t *client, pkgconf_pkg_t *world, const void *variable, int maxdepth) +{ + pkgconf_node_t *iter; + (void) maxdepth; + + PKGCONF_FOREACH_LIST_ENTRY(world->required.head, iter) + { + pkgconf_dependency_t *dep = iter->data; + pkgconf_pkg_t *pkg = dep->match; + char *result; + + if (pkg == NULL) + continue; + + result = variable_eval(client, &pkg->vars, variable); + + if (result != NULL) + { + pkgconf_output_fmt(client->output, PKGCONF_OUTPUT_STDOUT, + "%s%s", iter->prev != NULL ? " " : "", result); + free(result); + } + } + + pkgconf_output_puts(client->output, PKGCONF_OUTPUT_STDOUT, ""); + + return true; +} + +static bool +apply_env_var(const char *prefix, pkgconf_client_t *client, pkgconf_pkg_t *world, int maxdepth, + unsigned int (*collect_fn)(pkgconf_client_t *client, pkgconf_pkg_t *world, pkgconf_list_t *list, int maxdepth), + bool (*filter_fn)(const pkgconf_client_t *client, const pkgconf_fragment_t *frag, void *data), + void (*postprocess_fn)(pkgconf_client_t *client, pkgconf_pkg_t *world, pkgconf_list_t *fragment_list)) +{ + pkgconf_cli_state_t *state = client->client_data; + pkgconf_list_t unfiltered_list = PKGCONF_LIST_INITIALIZER; + pkgconf_list_t filtered_list = PKGCONF_LIST_INITIALIZER; + pkgconf_buffer_t render_buf = PKGCONF_BUFFER_INITIALIZER; + unsigned int eflag; + + eflag = collect_fn(client, world, &unfiltered_list, maxdepth); + if (eflag != PKGCONF_PKG_ERRF_OK) + return false; + + pkgconf_fragment_filter(client, &filtered_list, &unfiltered_list, filter_fn, NULL); + + if (postprocess_fn != NULL) + postprocess_fn(client, world, &filtered_list); + + if (filtered_list.head == NULL) + goto out; + + pkgconf_fragment_render_buf(&filtered_list, &render_buf, true, state->want_render_ops, (state->want_flags & PKG_NEWLINES) ? '\n' : ' '); + pkgconf_output_fmt(client->output, PKGCONF_OUTPUT_STDOUT, "%s='%s'\n", + prefix, pkgconf_buffer_str_or_empty(&render_buf)); + pkgconf_buffer_finalize(&render_buf); + +out: + pkgconf_fragment_free(&unfiltered_list); + pkgconf_fragment_free(&filtered_list); + + return true; +} + +static void +maybe_add_module_definitions(pkgconf_client_t *client, pkgconf_pkg_t *world, pkgconf_list_t *fragment_list) +{ + pkgconf_node_t *world_iter; + pkgconf_cli_state_t *state = client->client_data; + + if ((state->want_flags & PKG_EXISTS_CFLAGS) != PKG_EXISTS_CFLAGS) + return; + + PKGCONF_FOREACH_LIST_ENTRY(world->required.head, world_iter) + { + pkgconf_dependency_t *dep = world_iter->data; + char havebuf[PKGCONF_ITEM_SIZE]; + char *p; + + if ((dep->flags & PKGCONF_PKG_DEPF_QUERY) != PKGCONF_PKG_DEPF_QUERY) + continue; + + if (dep->match == NULL) + continue; + + snprintf(havebuf, sizeof havebuf, "HAVE_%s", dep->match->id); + + for (p = havebuf; *p; p++) + { + switch (*p) + { + case ' ': + case '-': + *p = '_'; + break; + + default: + *p = (char) toupper((unsigned char) *p); + } + } + + pkgconf_fragment_insert(client, fragment_list, 'D', havebuf, false); + } +} + +static void +apply_env_variables(pkgconf_client_t *client, pkgconf_pkg_t *world, const char *env_prefix) +{ + pkgconf_node_t *world_iter; + pkgconf_cli_state_t *state = client->client_data; + + PKGCONF_FOREACH_LIST_ENTRY(world->required.head, world_iter) + { + pkgconf_dependency_t *dep = world_iter->data; + pkgconf_pkg_t *pkg = dep->match; + pkgconf_node_t *tuple_iter; + + if ((dep->flags & PKGCONF_PKG_DEPF_QUERY) != PKGCONF_PKG_DEPF_QUERY) + continue; + + if (dep->match == NULL) + continue; + + PKGCONF_FOREACH_LIST_ENTRY(pkg->vars.head, tuple_iter) + { + pkgconf_tuple_t *tuple = tuple_iter->data; + char havebuf[PKGCONF_ITEM_SIZE]; + char *p; + + if (state->want_variable != NULL && strcmp(state->want_variable, tuple->key)) + continue; + + snprintf(havebuf, sizeof havebuf, "%s_%s", env_prefix, tuple->key); + + for (p = havebuf; *p; p++) + { + switch (*p) + { + case ' ': + case '-': + *p = '_'; + break; + + default: + *p = (char) toupper((unsigned char) *p); + } + } + + char *val = pkgconf_variable_eval_str(client, &pkg->vars, tuple, NULL); + pkgconf_output_fmt(client->output, PKGCONF_OUTPUT_STDOUT, + "%s='%s'\n", havebuf, val); + free(val); + } + } +} + +static bool +apply_env(pkgconf_client_t *client, pkgconf_pkg_t *world, const void *env_prefix_p, int maxdepth) +{ + pkgconf_cli_state_t *state = client->client_data; + const char *want_env_prefix = env_prefix_p, *it; + char workbuf[PKGCONF_ITEM_SIZE]; + + for (it = want_env_prefix; *it != '\0'; it++) + if (!isalpha((unsigned char)*it) && + !isdigit((unsigned char)*it)) + { + return false; + } + + snprintf(workbuf, sizeof workbuf, "%s_CFLAGS", want_env_prefix); + if (!apply_env_var(workbuf, client, world, maxdepth, pkgconf_pkg_cflags, filter_cflags, maybe_add_module_definitions)) + return false; + + snprintf(workbuf, sizeof workbuf, "%s_LIBS", want_env_prefix); + if (!apply_env_var(workbuf, client, world, maxdepth, pkgconf_pkg_libs, filter_libs, NULL)) + return false; + + if ((state->want_flags & PKG_VARIABLES) == PKG_VARIABLES || state->want_variable != NULL) + apply_env_variables(client, world, want_env_prefix); + + return true; +} + +static bool +apply_cflags(pkgconf_client_t *client, pkgconf_pkg_t *world, pkgconf_list_t *target_list, int maxdepth) +{ + pkgconf_list_t unfiltered_list = PKGCONF_LIST_INITIALIZER; + pkgconf_list_t filtered_list = PKGCONF_LIST_INITIALIZER; + int eflag; + + eflag = pkgconf_pkg_cflags(client, world, &unfiltered_list, maxdepth); + if (eflag != PKGCONF_PKG_ERRF_OK) + return false; + + pkgconf_fragment_filter(client, &filtered_list, &unfiltered_list, filter_cflags, NULL); + maybe_add_module_definitions(client, world, &filtered_list); + + if (filtered_list.head == NULL) + goto out; + + pkgconf_fragment_copy_list(client, target_list, &filtered_list); + +out: + pkgconf_fragment_free(&unfiltered_list); + pkgconf_fragment_free(&filtered_list); + + return true; +} + +static bool +apply_libs(pkgconf_client_t *client, pkgconf_pkg_t *world, pkgconf_list_t *target_list, int maxdepth) +{ + pkgconf_list_t unfiltered_list = PKGCONF_LIST_INITIALIZER; + pkgconf_list_t filtered_list = PKGCONF_LIST_INITIALIZER; + int eflag; + + eflag = pkgconf_pkg_libs(client, world, &unfiltered_list, maxdepth); + if (eflag != PKGCONF_PKG_ERRF_OK) + return false; + + pkgconf_fragment_filter(client, &filtered_list, &unfiltered_list, filter_libs, NULL); + + if (filtered_list.head == NULL) + goto out; + + pkgconf_fragment_copy_list(client, target_list, &filtered_list); + +out: + pkgconf_fragment_free(&unfiltered_list); + pkgconf_fragment_free(&filtered_list); + + return true; +} + +static bool +apply_requires(pkgconf_client_t *client, pkgconf_pkg_t *world, void *unused, int maxdepth) +{ + pkgconf_node_t *iter; + (void) unused; + (void) maxdepth; + + PKGCONF_FOREACH_LIST_ENTRY(world->required.head, iter) + { + pkgconf_dependency_t *dep = iter->data; + pkgconf_pkg_t *pkg = dep->match; + + print_dependency_list(client->output, &pkg->required); + } + + return true; +} + +static bool +apply_requires_private(pkgconf_client_t *client, pkgconf_pkg_t *world, void *unused, int maxdepth) +{ + pkgconf_node_t *iter; + (void) unused; + (void) maxdepth; + + PKGCONF_FOREACH_LIST_ENTRY(world->required.head, iter) + { + pkgconf_dependency_t *dep = iter->data; + pkgconf_pkg_t *pkg = dep->match; + + print_dependency_list(client->output, &pkg->requires_private); + } + return true; +} + +static void +check_uninstalled(pkgconf_client_t *client, pkgconf_pkg_t *pkg, void *data) +{ + int *retval = data; + (void) client; + + if (pkg->flags & PKGCONF_PKG_PROPF_UNINSTALLED) + *retval = EXIT_SUCCESS; +} + +static bool +apply_uninstalled(pkgconf_client_t *client, pkgconf_pkg_t *world, void *data, int maxdepth) +{ + int eflag; + + eflag = pkgconf_pkg_traverse(client, world, check_uninstalled, data, maxdepth, 0); + + if (eflag != PKGCONF_PKG_ERRF_OK) + return false; + + return true; +} + +#ifndef PKGCONF_LITE +static void +print_graph_node(pkgconf_client_t *client, pkgconf_pkg_t *pkg, void *data) +{ + pkgconf_node_t *n; + + (void) data; + + pkgconf_output_fmt(client->output, PKGCONF_OUTPUT_STDOUT, "node '%s' {\n", pkg->id); + + if (pkg->version != NULL) + pkgconf_output_fmt(client->output, PKGCONF_OUTPUT_STDOUT, " version = '%s';\n", pkg->version); + + PKGCONF_FOREACH_LIST_ENTRY(pkg->required.head, n) + { + pkgconf_dependency_t *dep = n->data; + + pkgconf_output_fmt(client->output, PKGCONF_OUTPUT_STDOUT, " dependency '%s'", dep->package); + + if (dep->compare != PKGCONF_CMP_ANY) + { + pkgconf_output_fmt(client->output, PKGCONF_OUTPUT_STDOUT, + " {\n" + " comparator = '%s';\n" + " version = '%s';\n" + " };\n", + pkgconf_pkg_get_comparator(dep), dep->version); + } + else + pkgconf_output_puts(client->output, PKGCONF_OUTPUT_STDOUT, ";"); + } + + pkgconf_output_puts(client->output, PKGCONF_OUTPUT_STDOUT, "};"); +} + +static bool +apply_simulate(pkgconf_client_t *client, pkgconf_pkg_t *world, void *data, int maxdepth) +{ + int eflag; + + eflag = pkgconf_pkg_traverse(client, world, print_graph_node, data, maxdepth, 0); + + if (eflag != PKGCONF_PKG_ERRF_OK) + return false; + + return true; +} +#endif + +static void +print_fragment_tree_branch(pkgconf_output_t *output, pkgconf_list_t *fragment_list, int indent) +{ + pkgconf_node_t *iter; + + PKGCONF_FOREACH_LIST_ENTRY(fragment_list->head, iter) + { + pkgconf_fragment_t *frag = iter->data; + + if (frag->type) + pkgconf_output_fmt(output, PKGCONF_OUTPUT_STDOUT, + "%*s'-%c%s' [type %c, " SIZE_FMT_SPECIFIER " children]\n", indent, "", frag->type, frag->data, frag->type, frag->children.length); + else + pkgconf_output_fmt(output, PKGCONF_OUTPUT_STDOUT, + "%*s'%s' [untyped, " SIZE_FMT_SPECIFIER " children]\n", indent, "", frag->data, frag->children.length); + + print_fragment_tree_branch(output, &frag->children, indent + 2); + } + + if (fragment_list->head != NULL) + pkgconf_output_puts(output, PKGCONF_OUTPUT_STDOUT, ""); +} + +static bool +apply_fragment_tree(pkgconf_client_t *client, pkgconf_pkg_t *world, void *data, int maxdepth) +{ + pkgconf_list_t unfiltered_list = PKGCONF_LIST_INITIALIZER; + int eflag; + + (void) data; + + eflag = pkgconf_pkg_cflags(client, world, &unfiltered_list, maxdepth); + if (eflag != PKGCONF_PKG_ERRF_OK) + return false; + + eflag = pkgconf_pkg_libs(client, world, &unfiltered_list, maxdepth); + if (eflag != PKGCONF_PKG_ERRF_OK) + return false; + + print_fragment_tree_branch(client->output, &unfiltered_list, 0); + pkgconf_fragment_free(&unfiltered_list); + + return true; +} + +static void +print_license(pkgconf_client_t *client, pkgconf_pkg_t *pkg, void *data) +{ + pkgconf_buffer_t render_buf = PKGCONF_BUFFER_INITIALIZER; + (void) data; + + if (pkg->flags & PKGCONF_PKG_PROPF_VIRTUAL) + return; + + if (pkg->license.head == NULL) + { + pkgconf_output_fmt(client->output, PKGCONF_OUTPUT_STDOUT, + "%s: NOASSERTION\n", pkg->id); + } + else + { + /* NOASSERTION is the default when the license is unknown, per SPDX spec § 3.15 */ + pkgconf_license_render(client, &pkg->license, &render_buf); + pkgconf_output_fmt(client->output, PKGCONF_OUTPUT_STDOUT, "%s: ", pkg->id); + pkgconf_output_putbuf(client->output, PKGCONF_OUTPUT_STDOUT, &render_buf, true); + pkgconf_buffer_finalize(&render_buf); + } +} + +static bool +apply_license(pkgconf_client_t *client, pkgconf_pkg_t *world, void *data, int maxdepth) +{ + int eflag; + + eflag = pkgconf_pkg_traverse(client, world, print_license, data, maxdepth, 0); + + if (eflag != PKGCONF_PKG_ERRF_OK) + return false; + + return true; +} + +static bool +apply_link_abi(pkgconf_client_t *client, pkgconf_pkg_t *world, void *data, int maxdepth) +{ + pkgconf_list_t abis = PKGCONF_LIST_INITIALIZER; + pkgconf_buffer_t render_buf = PKGCONF_BUFFER_INITIALIZER; + pkgconf_node_t *node; + int *retval = data; + + if (pkgconf_pkg_link_abi(client, world, &abis, maxdepth) != PKGCONF_PKG_ERRF_OK) + { + pkgconf_bufferset_free(&abis); + *retval = EXIT_FAILURE; + return false; + } + + PKGCONF_FOREACH_LIST_ENTRY(abis.head, node) + { + pkgconf_bufferset_t *tag = node->data; + + if (pkgconf_buffer_len(&render_buf)) + pkgconf_buffer_push_byte(&render_buf, ' '); + + pkgconf_buffer_append(&render_buf, pkgconf_buffer_str(&tag->buffer)); + } + + pkgconf_output_putbuf(client->output, PKGCONF_OUTPUT_STDOUT, &render_buf, true); + + pkgconf_buffer_finalize(&render_buf); + pkgconf_bufferset_free(&abis); + + return true; +} + +static void +print_license_file(pkgconf_client_t *client, pkgconf_pkg_t *pkg, void *data) +{ + (void) data; + + if (pkg->flags & PKGCONF_PKG_PROPF_VIRTUAL) + return; + + /* If license file location is not available then just print empty */ + pkgconf_output_fmt(client->output, PKGCONF_OUTPUT_STDOUT, "%s: %s\n", + pkg->id, pkg->license_file != NULL ? pkg->license_file : ""); +} + +static bool +apply_license_file(pkgconf_client_t *client, pkgconf_pkg_t *world, void *data, int maxdepth) +{ + int eflag; + + eflag = pkgconf_pkg_traverse(client, world, print_license_file, data, maxdepth, 0); + + if (eflag != PKGCONF_PKG_ERRF_OK) + return false; + + return true; +} + +static void +print_source(pkgconf_client_t *client, pkgconf_pkg_t *pkg, void *data) +{ + (void) data; + + if (pkg->flags & PKGCONF_PKG_PROPF_VIRTUAL) + return; + + /* If source is empty then empty string is printed otherwise URL */ + pkgconf_output_fmt(client->output, PKGCONF_OUTPUT_STDOUT, "%s: %s\n", + pkg->id, pkg->source != NULL ? pkg->source : ""); +} + +static bool +apply_source(pkgconf_client_t *client, pkgconf_pkg_t *world, void *data, int maxdepth) +{ + int eflag; + + eflag = pkgconf_pkg_traverse(client, world, print_source, data, maxdepth, 0); + + if (eflag != PKGCONF_PKG_ERRF_OK) + return false; + + return true; +} + +void +path_list_to_buffer(const pkgconf_list_t *list, pkgconf_buffer_t *buffer, char delim) +{ + pkgconf_node_t *n; + + PKGCONF_FOREACH_LIST_ENTRY(list->head, n) + { + pkgconf_path_t *pn = n->data; + + if (n != list->head) + pkgconf_buffer_push_byte(buffer, delim); + + pkgconf_buffer_append(buffer, pn->path); + } +} + +static void +unveil_search_paths(pkgconf_client_t *client, const pkgconf_cross_personality_t *personality) +{ + pkgconf_node_t *n; + + client->unveil_handler(client, "/dev/null", "rwc"); + + PKGCONF_FOREACH_LIST_ENTRY(client->dir_list.head, n) + { + pkgconf_path_t *pn = n->data; + + client->unveil_handler(client, pn->path, "r"); + } + + PKGCONF_FOREACH_LIST_ENTRY(personality->dir_list.head, n) + { + pkgconf_path_t *pn = n->data; + + client->unveil_handler(client, pn->path, "r"); + } +} + +/* SAFETY: pkgconf_client_t takes ownership of these package objects */ +static void +register_builtins(pkgconf_client_t *client, const pkgconf_cross_personality_t *personality) +{ + pkgconf_buffer_t pc_path_buf = PKGCONF_BUFFER_INITIALIZER; + path_list_to_buffer(&personality->dir_list, &pc_path_buf, ':'); + + pkgconf_buffer_t pc_system_libdirs_buf = PKGCONF_BUFFER_INITIALIZER; + path_list_to_buffer(&personality->filter_libdirs, &pc_system_libdirs_buf, ':'); + + pkgconf_buffer_t pc_system_includedirs_buf = PKGCONF_BUFFER_INITIALIZER; + path_list_to_buffer(&personality->filter_includedirs, &pc_system_includedirs_buf, ':'); + + pkgconf_pkg_t *pkg_config_virtual = calloc(1, sizeof(pkgconf_pkg_t)); + if (pkg_config_virtual == NULL) + { + goto error; + } + + pkg_config_virtual->owner = client; + pkg_config_virtual->id = strdup("pkg-config"); + pkg_config_virtual->realname = strdup("pkg-config"); + pkg_config_virtual->description = strdup("virtual package defining pkgconf API version supported"); + pkg_config_virtual->url = strdup(PACKAGE_BUGREPORT); + pkg_config_virtual->version = strdup(PACKAGE_VERSION); + + pkgconf_tuple_add(client, &pkg_config_virtual->vars, "pc_system_libdirs", pkgconf_buffer_str_or_empty(&pc_system_libdirs_buf), false, 0); + pkgconf_tuple_add(client, &pkg_config_virtual->vars, "pc_system_includedirs", pkgconf_buffer_str_or_empty(&pc_system_includedirs_buf), false, 0); + pkgconf_tuple_add(client, &pkg_config_virtual->vars, "pc_path", pkgconf_buffer_str_or_empty(&pc_path_buf), false, 0); + + if (!pkgconf_client_preload_one(client, pkg_config_virtual)) + { + goto error; + } + + pkgconf_pkg_t *pkgconf_virtual = calloc(1, sizeof(pkgconf_pkg_t)); + if (pkgconf_virtual == NULL) + { + goto error; + } + + pkgconf_virtual->owner = client; + pkgconf_virtual->id = strdup("pkgconf"); + pkgconf_virtual->realname = strdup("pkgconf"); + pkgconf_virtual->description = strdup("virtual package defining pkgconf API version supported"); + pkgconf_virtual->url = strdup(PACKAGE_BUGREPORT); + pkgconf_virtual->version = strdup(PACKAGE_VERSION); + + pkgconf_tuple_add(client, &pkgconf_virtual->vars, "pc_system_libdirs", pkgconf_buffer_str_or_empty(&pc_system_libdirs_buf), false, 0); + pkgconf_tuple_add(client, &pkgconf_virtual->vars, "pc_system_includedirs", pkgconf_buffer_str_or_empty(&pc_system_includedirs_buf), false, 0); + pkgconf_tuple_add(client, &pkgconf_virtual->vars, "pc_path", pkgconf_buffer_str_or_empty(&pc_path_buf), false, 0); + + if (!pkgconf_client_preload_one(client, pkgconf_virtual)) + { + goto error; + } + +error: + pkgconf_buffer_finalize(&pc_path_buf); + pkgconf_buffer_finalize(&pc_system_libdirs_buf); + pkgconf_buffer_finalize(&pc_system_includedirs_buf); +} + +#ifndef PKGCONF_LITE +static void +dump_personality(pkgconf_output_t *output, const pkgconf_cross_personality_t *p) +{ + pkgconf_buffer_t pc_path_buf = PKGCONF_BUFFER_INITIALIZER; + path_list_to_buffer(&p->dir_list, &pc_path_buf, ':'); + + pkgconf_buffer_t pc_system_libdirs_buf = PKGCONF_BUFFER_INITIALIZER; + path_list_to_buffer(&p->filter_libdirs, &pc_system_libdirs_buf, ':'); + + pkgconf_buffer_t pc_system_includedirs_buf = PKGCONF_BUFFER_INITIALIZER; + path_list_to_buffer(&p->filter_includedirs, &pc_system_includedirs_buf, ':'); + + pkgconf_output_fmt(output, PKGCONF_OUTPUT_STDOUT, "Triplet: %s\n", p->name); + + if (p->sysroot_dir) + pkgconf_output_fmt(output, PKGCONF_OUTPUT_STDOUT, "SysrootDir: %s\n", p->sysroot_dir); + + pkgconf_output_fmt(output, PKGCONF_OUTPUT_STDOUT, "DefaultSearchPaths: %s\n", pc_path_buf.base); + pkgconf_output_fmt(output, PKGCONF_OUTPUT_STDOUT, "SystemIncludePaths: %s\n", pc_system_includedirs_buf.base); + pkgconf_output_fmt(output, PKGCONF_OUTPUT_STDOUT, "SystemLibraryPaths: %s\n", pc_system_libdirs_buf.base); + + if (p->want_default_pure) + pkgconf_output_fmt(output, PKGCONF_OUTPUT_STDOUT, "WantDefaultPure: true\n"); + + if (p->want_default_static) + pkgconf_output_fmt(output, PKGCONF_OUTPUT_STDOUT, "WantDefaultStatic: true\n"); + + pkgconf_buffer_finalize(&pc_path_buf); + pkgconf_buffer_finalize(&pc_system_libdirs_buf); + pkgconf_buffer_finalize(&pc_system_includedirs_buf); +} +#endif + +int +pkgconf_cli_run(pkgconf_cli_state_t *state, int argc, char *argv[], int last_argc) +{ + (void) argc; + + int ret = EXIT_SUCCESS; + unsigned int want_client_flags = PKGCONF_PKG_PKGF_NONE; + const char *builddir; + const char *sysroot_dir; + pkgconf_list_t pkgq = PKGCONF_LIST_INITIALIZER; + pkgconf_list_t deplist = PKGCONF_LIST_INITIALIZER; + pkgconf_node_t *node; + pkgconf_buffer_t queryparams = PKGCONF_BUFFER_INITIALIZER; + pkgconf_pkg_t world = { + .id = "virtual:world", + .realname = "virtual world package", + .flags = PKGCONF_PKG_PROPF_STATIC | PKGCONF_PKG_PROPF_VIRTUAL, + }; + +#ifndef PKGCONF_LITE + if ((state->want_flags & PKG_DUMP_PERSONALITY) == PKG_DUMP_PERSONALITY) + { + dump_personality(state->pkg_client.output, state->pkg_client.personality); + + ret = EXIT_SUCCESS; + goto out; + } +#endif + +#ifndef PKGCONF_LITE + if ((state->want_flags & PKG_MSVC_SYNTAX) == PKG_MSVC_SYNTAX) + state->want_render_ops = msvc_renderer_get(); +#endif + + if (pkgconf_client_getenv(&state->pkg_client, "PKG_CONFIG_FDO_SYSROOT_RULES")) + want_client_flags |= PKGCONF_PKG_PKGF_FDO_SYSROOT_RULES; + + if (pkgconf_client_getenv(&state->pkg_client, "PKG_CONFIG_PKGCONF1_SYSROOT_RULES")) + want_client_flags |= PKGCONF_PKG_PKGF_PKGCONF1_SYSROOT_RULES; + + if ((state->want_flags & PKG_SHORT_ERRORS) == PKG_SHORT_ERRORS) + want_client_flags |= PKGCONF_PKG_PKGF_SIMPLIFY_ERRORS; + + if ((state->want_flags & PKG_DONT_RELOCATE_PATHS) == PKG_DONT_RELOCATE_PATHS) + want_client_flags |= PKGCONF_PKG_PKGF_DONT_RELOCATE_PATHS; + + state->error_msgout = stderr; + if ((state->want_flags & PKG_ERRORS_ON_STDOUT) == PKG_ERRORS_ON_STDOUT) + state->error_msgout = stdout; + if ((state->want_flags & PKG_SILENCE_ERRORS) == PKG_SILENCE_ERRORS) { + state->error_msgout = fopen(PATH_DEV_NULL, "w"); + state->opened_error_msgout = true; + } + + if ((state->want_flags & PKG_IGNORE_CONFLICTS) == PKG_IGNORE_CONFLICTS || pkgconf_client_getenv(&state->pkg_client, "PKG_CONFIG_IGNORE_CONFLICTS") != NULL) + want_client_flags |= PKGCONF_PKG_PKGF_SKIP_CONFLICTS; + + if ((state->want_flags & PKG_STATIC) == PKG_STATIC || state->pkg_client.personality->want_default_static) + want_client_flags |= (PKGCONF_PKG_PKGF_SEARCH_PRIVATE | PKGCONF_PKG_PKGF_MERGE_PRIVATE_FRAGMENTS); + + if ((state->want_flags & PKG_SHARED) == PKG_SHARED) + want_client_flags &= ~(PKGCONF_PKG_PKGF_SEARCH_PRIVATE | PKGCONF_PKG_PKGF_MERGE_PRIVATE_FRAGMENTS); + + /* if --static and --pure are both specified, then disable merge-back. + * this allows for a --static which searches private modules, but has the same fragment behaviour as if + * --static were disabled. see <https://github.com/pkgconf/pkgconf/issues/83> for rationale. + */ + if ((state->want_flags & PKG_PURE) == PKG_PURE || pkgconf_client_getenv(&state->pkg_client, "PKG_CONFIG_PURE_DEPGRAPH") != NULL || state->pkg_client.personality->want_default_pure) + want_client_flags &= ~PKGCONF_PKG_PKGF_MERGE_PRIVATE_FRAGMENTS; + + if ((state->want_flags & PKG_ENV_ONLY) == PKG_ENV_ONLY) + want_client_flags |= PKGCONF_PKG_PKGF_ENV_ONLY; + + if ((state->want_flags & PKG_NO_CACHE) == PKG_NO_CACHE) + want_client_flags |= PKGCONF_PKG_PKGF_NO_CACHE; + +/* On Windows we want to always redefine the prefix by default + * but allow that behavior to be manually disabled */ +#if !defined(_WIN32) && !defined(_WIN64) + if ((state->want_flags & PKG_DEFINE_PREFIX) == PKG_DEFINE_PREFIX || pkgconf_client_getenv(&state->pkg_client, "PKG_CONFIG_RELOCATE_PATHS") != NULL) +#endif + want_client_flags |= PKGCONF_PKG_PKGF_REDEFINE_PREFIX; + + if ((state->want_flags & PKG_NO_UNINSTALLED) == PKG_NO_UNINSTALLED || pkgconf_client_getenv(&state->pkg_client, "PKG_CONFIG_DISABLE_UNINSTALLED") != NULL) + want_client_flags |= PKGCONF_PKG_PKGF_NO_UNINSTALLED; + + if ((state->want_flags & PKG_NO_PROVIDES) == PKG_NO_PROVIDES) + want_client_flags |= PKGCONF_PKG_PKGF_SKIP_PROVIDES; + + if ((state->want_flags & PKG_DONT_DEFINE_PREFIX) == PKG_DONT_DEFINE_PREFIX || pkgconf_client_getenv(&state->pkg_client, "PKG_CONFIG_DONT_DEFINE_PREFIX") != NULL) + want_client_flags &= ~PKGCONF_PKG_PKGF_REDEFINE_PREFIX; + + if ((state->want_flags & PKG_INTERNAL_CFLAGS) == PKG_INTERNAL_CFLAGS) + want_client_flags |= PKGCONF_PKG_PKGF_DONT_FILTER_INTERNAL_CFLAGS; + + /* --static --libs, --exists require the full dependency graph to be solved */ + if ((state->want_flags & (PKG_STATIC|PKG_LIBS)) == (PKG_STATIC|PKG_LIBS) || (state->want_flags & PKG_EXISTS) == PKG_EXISTS) + want_client_flags |= PKGCONF_PKG_PKGF_REQUIRE_INTERNAL; + + /* if these selectors are used, it means that we are querying metadata. + * so signal to libpkgconf that we only want to walk the flattened dependency set. + */ + if ((state->want_flags & PKG_MODVERSION) == PKG_MODVERSION || + (state->want_flags & PKG_REQUIRES) == PKG_REQUIRES || + (state->want_flags & PKG_REQUIRES_PRIVATE) == PKG_REQUIRES_PRIVATE || + (state->want_flags & PKG_PROVIDES) == PKG_PROVIDES || + (state->want_flags & PKG_VARIABLES) == PKG_VARIABLES || + (state->want_flags & PKG_PATH) == PKG_PATH || + state->want_variable != NULL) + state->maximum_traverse_depth = 1; + + /* if we are asking for a variable, path or list of variables, this only makes sense + * for a single package. + */ + if ((state->want_flags & PKG_VARIABLES) == PKG_VARIABLES || + (state->want_flags & PKG_PATH) == PKG_PATH || + state->want_variable != NULL) + { + state->maximum_package_count = 1; + } + + if ((state->want_flags & PKG_REQUIRES_PRIVATE) == PKG_REQUIRES_PRIVATE || + (state->want_flags & PKG_CFLAGS)) + { + want_client_flags |= PKGCONF_PKG_PKGF_SEARCH_PRIVATE; + } + + if ((builddir = pkgconf_client_getenv(&state->pkg_client, "PKG_CONFIG_TOP_BUILD_DIR")) != NULL) + pkgconf_client_set_buildroot_dir(&state->pkg_client, builddir); + + if ((sysroot_dir = pkgconf_client_getenv(&state->pkg_client, "PKG_CONFIG_SYSROOT_DIR")) != NULL) + { + const char *destdir; + + pkgconf_client_set_sysroot_dir(&state->pkg_client, sysroot_dir); + + if ((destdir = pkgconf_client_getenv(&state->pkg_client, "DESTDIR")) != NULL) + { + if (!strcmp(destdir, sysroot_dir)) + want_client_flags |= PKGCONF_PKG_PKGF_FDO_SYSROOT_RULES; + } + } + + /* we have determined what features we want most likely. in some cases, we override later. */ + pkgconf_client_set_flags(&state->pkg_client, want_client_flags); + + /* at this point, want_client_flags should be set, so build the dir list */ + pkgconf_client_dir_list_build(&state->pkg_client, state->pkg_client.personality); + + /* unveil the entire search path now that we have loaded the personality data and built the dir list. */ + unveil_search_paths(&state->pkg_client, state->pkg_client.personality); + + /* register built-in packages */ + register_builtins(&state->pkg_client, state->pkg_client.personality); + + /* preload any files in PKG_CONFIG_PRELOADED_FILES */ + pkgconf_client_preload_from_environ(&state->pkg_client, "PKG_CONFIG_PRELOADED_FILES"); + + if (state->required_pkgconfig_version != NULL) + { + if (pkgconf_compare_version(PACKAGE_VERSION, state->required_pkgconfig_version) >= 0) + ret = EXIT_SUCCESS; + else + ret = EXIT_FAILURE; + + goto out; + } + + if ((state->want_flags & PKG_LIST) == PKG_LIST) + { + pkgconf_scan_all(&state->pkg_client, &state->pkg_client, print_list_entry); + ret = EXIT_SUCCESS; + goto out; + } + + if ((state->want_flags & PKG_LIST_PACKAGE_NAMES) == PKG_LIST_PACKAGE_NAMES) + { + pkgconf_scan_all(&state->pkg_client, &state->pkg_client, print_package_entry); + ret = EXIT_SUCCESS; + goto out; + } + + while (last_argc < argc && argv[last_argc]) + { + if (pkgconf_buffer_len(&queryparams) > 0) + pkgconf_buffer_push_byte(&queryparams, ' '); + + pkgconf_buffer_append(&queryparams, argv[last_argc]); + last_argc++; + } + + pkgconf_dependency_parse_str(&state->pkg_client, &deplist, pkgconf_buffer_str_or_empty(&queryparams), 0); + pkgconf_buffer_finalize(&queryparams); + + if (state->required_module_version != NULL || state->required_exact_module_version != NULL || state->required_max_module_version != NULL) + { + const char *target_version = NULL; + pkgconf_pkg_comparator_t compare = PKGCONF_CMP_ANY; + + if (state->required_module_version != NULL) + { + target_version = state->required_module_version; + compare = PKGCONF_CMP_GREATER_THAN_EQUAL; + } + else if (state->required_exact_module_version != NULL) + { + target_version = state->required_exact_module_version; + compare = PKGCONF_CMP_EQUAL; + } + else if (state->required_max_module_version != NULL) + { + target_version = state->required_max_module_version; + compare = PKGCONF_CMP_LESS_THAN_EQUAL; + } + + PKGCONF_FOREACH_LIST_ENTRY(deplist.head, node) + { + pkgconf_dependency_t *dep = node->data; + + /* already constrained at query level */ + if (dep->compare != PKGCONF_CMP_ANY) + continue; + + dep->compare = compare; + dep->version = strdup(target_version); + } + } + + PKGCONF_FOREACH_LIST_ENTRY(deplist.head, node) + { + pkgconf_dependency_t *dep = node->data; + pkgconf_queue_push_dependency(&pkgq, dep); + } + + pkgconf_dependency_free(&deplist); + + if (pkgq.head == NULL) + { + pkgconf_output_puts(state->pkg_client.output, PKGCONF_OUTPUT_STDERR, + "Please specify at least one package name on the command line."); + ret = EXIT_FAILURE; + goto out; + } + + ret = EXIT_SUCCESS; + + if (!pkgconf_queue_solve(&state->pkg_client, &pkgq, &world, state->maximum_traverse_depth)) + { + ret = EXIT_FAILURE; + goto out; + } + + /* we shouldn't need to unveil any more filesystem accesses from this point, so lock it down */ + state->pkg_client.unveil_handler(&state->pkg_client, NULL, NULL); + +#ifndef PKGCONF_LITE + if ((state->want_flags & PKG_SIMULATE) == PKG_SIMULATE) + { + state->want_flags &= ~(PKG_CFLAGS|PKG_LIBS); + + pkgconf_client_set_flags(&state->pkg_client, want_client_flags | PKGCONF_PKG_PKGF_SKIP_ERRORS); + apply_simulate(&state->pkg_client, &world, NULL, -1); + } +#endif + + if ((state->want_flags & PKG_VALIDATE) == PKG_VALIDATE) + goto out; + + if ((state->want_flags & PKG_DUMP_LICENSE) == PKG_DUMP_LICENSE) + { + apply_license(&state->pkg_client, &world, &ret, 2); + goto out; + } + + if ((state->want_flags & PKG_LINK_ABI) == PKG_LINK_ABI) + { + /* private dependencies only contribute their ABI when statically linked */ + if (!(state->want_flags & PKG_STATIC)) + pkgconf_client_set_flags(&state->pkg_client, state->pkg_client.flags & ~PKGCONF_PKG_PKGF_SEARCH_PRIVATE); + + apply_link_abi(&state->pkg_client, &world, &ret, 2); + goto out; + } + + if ((state->want_flags & PKG_DUMP_LICENSE_FILE) == PKG_DUMP_LICENSE_FILE) + { + apply_license_file(&state->pkg_client, &world, &ret, 2); + goto out; + } + + if ((state->want_flags & PKG_DUMP_SOURCE) == PKG_DUMP_SOURCE) + { + apply_source(&state->pkg_client, &world, &ret, 2); + goto out; + } + + + if ((state->want_flags & PKG_UNINSTALLED) == PKG_UNINSTALLED) + { + ret = EXIT_FAILURE; + apply_uninstalled(&state->pkg_client, &world, &ret, 2); + goto out; + } + + if (state->want_env_prefix != NULL) + { + apply_env(&state->pkg_client, &world, state->want_env_prefix, 2); + goto out; + } + + if ((state->want_flags & PKG_PROVIDES) == PKG_PROVIDES) + { + state->want_flags &= ~(PKG_CFLAGS|PKG_LIBS); + apply_provides(&state->pkg_client, &world, NULL, 2); + } + +#ifndef PKGCONF_LITE + if ((state->want_flags & PKG_DIGRAPH) == PKG_DIGRAPH) + { + state->want_flags &= ~(PKG_CFLAGS|PKG_LIBS); + apply_digraph(&state->pkg_client, &world, &pkgq, 2); + } + + if ((state->want_flags & PKG_SOLUTION) == PKG_SOLUTION) + { + state->want_flags &= ~(PKG_CFLAGS|PKG_LIBS); + apply_print_solution(&state->pkg_client, &world, NULL, 2); + } +#endif + + if ((state->want_flags & PKG_MODVERSION) == PKG_MODVERSION) + { + state->want_flags &= ~(PKG_CFLAGS|PKG_LIBS); + apply_modversion(&state->pkg_client, &world, &pkgq, 2); + } + + if ((state->want_flags & PKG_PATH) == PKG_PATH) + { + state->want_flags &= ~(PKG_CFLAGS|PKG_LIBS); + + pkgconf_client_set_flags(&state->pkg_client, want_client_flags | PKGCONF_PKG_PKGF_SKIP_ROOT_VIRTUAL); + apply_path(&state->pkg_client, &world, NULL, 2); + } + + if ((state->want_flags & PKG_VARIABLES) == PKG_VARIABLES) + { + state->want_flags &= ~(PKG_CFLAGS|PKG_LIBS); + apply_variables(&state->pkg_client, &world, NULL, 2); + } + + if (state->want_variable != NULL) + { + state->want_flags &= ~(PKG_CFLAGS|PKG_LIBS); + + pkgconf_client_set_flags(&state->pkg_client, want_client_flags | PKGCONF_PKG_PKGF_SKIP_ROOT_VIRTUAL); + apply_variable(&state->pkg_client, &world, state->want_variable, 2); + } + + if ((state->want_flags & PKG_REQUIRES) == PKG_REQUIRES) + { + state->want_flags &= ~(PKG_CFLAGS|PKG_LIBS); + apply_requires(&state->pkg_client, &world, NULL, 2); + } + + if ((state->want_flags & PKG_REQUIRES_PRIVATE) == PKG_REQUIRES_PRIVATE) + { + state->want_flags &= ~(PKG_CFLAGS|PKG_LIBS); + + apply_requires_private(&state->pkg_client, &world, NULL, 2); + } + + if ((state->want_flags & PKG_FRAGMENT_TREE)) + { + state->want_flags &= ~(PKG_CFLAGS|PKG_LIBS); + + apply_fragment_tree(&state->pkg_client, &world, NULL, 2); + } + + if ((state->want_flags & (PKG_CFLAGS|PKG_LIBS))) + { + pkgconf_list_t target_list = PKGCONF_LIST_INITIALIZER; + pkgconf_buffer_t render_buf = PKGCONF_BUFFER_INITIALIZER; + + if ((state->want_flags & PKG_CFLAGS)) + apply_cflags(&state->pkg_client, &world, &target_list, 2); + + if ((state->want_flags & PKG_LIBS) && !(state->want_flags & PKG_STATIC)) + pkgconf_client_set_flags(&state->pkg_client, state->pkg_client.flags & ~PKGCONF_PKG_PKGF_SEARCH_PRIVATE); + + if ((state->want_flags & PKG_LIBS)) + apply_libs(&state->pkg_client, &world, &target_list, 2); + + pkgconf_fragment_render_buf(&target_list, &render_buf, true, state->want_render_ops, (state->want_flags & PKG_NEWLINES) ? '\n' : ' '); + pkgconf_output_putbuf(state->pkg_client.output, PKGCONF_OUTPUT_STDOUT, &render_buf, true); + pkgconf_buffer_finalize(&render_buf); + + pkgconf_fragment_free(&target_list); + } + +out: + pkgconf_solution_free(&state->pkg_client, &world); + pkgconf_queue_free(&pkgq); + pkgconf_cli_state_reset(state); + + return ret; +} + +void +pkgconf_cli_state_reset(pkgconf_cli_state_t *state) +{ + pkgconf_cross_personality_deinit((void *) state->pkg_client.personality); + pkgconf_client_deinit(&state->pkg_client); + + if (state->logfile_out != NULL) + fclose(state->logfile_out); + if (state->opened_error_msgout) + fclose(state->error_msgout); +} diff --git a/cli/core.h b/cli/core.h new file mode 100644 index 000000000000..c6c5886e070e --- /dev/null +++ b/cli/core.h @@ -0,0 +1,103 @@ +/* + * core.h + * core, printer functions + * + * SPDX-License-Identifier: pkgconf + * + * Copyright (c) 2011-2025 pkgconf authors (see AUTHORS). + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#ifndef __CLI_CORE_H +#define __CLI_CORE_H + +#define PKG_CFLAGS_ONLY_I (((uint64_t) 1) << 2) +#define PKG_CFLAGS_ONLY_OTHER (((uint64_t) 1) << 3) +#define PKG_CFLAGS (PKG_CFLAGS_ONLY_I|PKG_CFLAGS_ONLY_OTHER) +#define PKG_LIBS_ONLY_LDPATH (((uint64_t) 1) << 5) +#define PKG_LIBS_ONLY_LIBNAME (((uint64_t) 1) << 6) +#define PKG_LIBS_ONLY_OTHER (((uint64_t) 1) << 7) +#define PKG_LIBS (PKG_LIBS_ONLY_LDPATH|PKG_LIBS_ONLY_LIBNAME|PKG_LIBS_ONLY_OTHER) +#define PKG_MODVERSION (((uint64_t) 1) << 8) +#define PKG_REQUIRES (((uint64_t) 1) << 9) +#define PKG_REQUIRES_PRIVATE (((uint64_t) 1) << 10) +#define PKG_VARIABLES (((uint64_t) 1) << 11) +#define PKG_DIGRAPH (((uint64_t) 1) << 12) +#define PKG_KEEP_SYSTEM_CFLAGS (((uint64_t) 1) << 13) +#define PKG_KEEP_SYSTEM_LIBS (((uint64_t) 1) << 14) +#define PKG_VERSION (((uint64_t) 1) << 15) +#define PKG_ABOUT (((uint64_t) 1) << 16) +#define PKG_ENV_ONLY (((uint64_t) 1) << 17) +#define PKG_ERRORS_ON_STDOUT (((uint64_t) 1) << 18) +#define PKG_SILENCE_ERRORS (((uint64_t) 1) << 19) +#define PKG_IGNORE_CONFLICTS (((uint64_t) 1) << 20) +#define PKG_STATIC (((uint64_t) 1) << 21) +#define PKG_NO_UNINSTALLED (((uint64_t) 1) << 22) +#define PKG_UNINSTALLED (((uint64_t) 1) << 23) +#define PKG_LIST (((uint64_t) 1) << 24) +#define PKG_HELP (((uint64_t) 1) << 25) +#define PKG_PRINT_ERRORS (((uint64_t) 1) << 26) +#define PKG_SIMULATE (((uint64_t) 1) << 27) +#define PKG_NO_CACHE (((uint64_t) 1) << 28) +#define PKG_PROVIDES (((uint64_t) 1) << 29) +#define PKG_VALIDATE (((uint64_t) 1) << 30) +#define PKG_LIST_PACKAGE_NAMES (((uint64_t) 1) << 31) +#define PKG_NO_PROVIDES (((uint64_t) 1) << 32) +#define PKG_PURE (((uint64_t) 1) << 33) +#define PKG_PATH (((uint64_t) 1) << 34) +#define PKG_DEFINE_PREFIX (((uint64_t) 1) << 35) +#define PKG_DONT_DEFINE_PREFIX (((uint64_t) 1) << 36) +#define PKG_DONT_RELOCATE_PATHS (((uint64_t) 1) << 37) +#define PKG_DEBUG (((uint64_t) 1) << 38) +#define PKG_SHORT_ERRORS (((uint64_t) 1) << 39) +#define PKG_EXISTS (((uint64_t) 1) << 40) +#define PKG_MSVC_SYNTAX (((uint64_t) 1) << 41) +#define PKG_INTERNAL_CFLAGS (((uint64_t) 1) << 42) +#define PKG_DUMP_PERSONALITY (((uint64_t) 1) << 43) +#define PKG_SHARED (((uint64_t) 1) << 44) +#define PKG_DUMP_LICENSE (((uint64_t) 1) << 45) +#define PKG_SOLUTION (((uint64_t) 1) << 46) +#define PKG_EXISTS_CFLAGS (((uint64_t) 1) << 47) +#define PKG_FRAGMENT_TREE (((uint64_t) 1) << 48) +#define PKG_DUMP_SOURCE (((uint64_t) 1) << 49) +#define PKG_DUMP_LICENSE_FILE (((uint64_t) 1) << 50) +#define PKG_NEWLINES (((uint64_t) 1) << 51) +#define PKG_PRINT_DIGRAPH_QUERY_NODES (((uint64_t) 1) << 52) +#define PKG_LINK_ABI (((uint64_t) 1) << 53) + +typedef struct { + pkgconf_client_t pkg_client; + pkgconf_fragment_render_ops_t *want_render_ops; + + uint64_t want_flags; + int verbosity; + int maximum_traverse_depth; + size_t maximum_package_count; + + const char *want_variable; + const char *want_fragment_filter; + const char *want_env_prefix; + + char *required_pkgconfig_version; + const char *required_exact_module_version; + const char *required_max_module_version; + const char *required_module_version; + + FILE *error_msgout; + FILE *logfile_out; + + bool opened_error_msgout; +} pkgconf_cli_state_t; + +extern void path_list_to_buffer(const pkgconf_list_t *list, pkgconf_buffer_t *buffer, char delim); +extern int pkgconf_cli_run(pkgconf_cli_state_t *state, int argc, char *argv[], int last_argc); +extern void pkgconf_cli_state_reset(pkgconf_cli_state_t *state); + +#endif diff --git a/cli/getopt_long.c b/cli/getopt_long.c index c47215fae4cf..9bda0e1e32ef 100644 --- a/cli/getopt_long.c +++ b/cli/getopt_long.c @@ -1,7 +1,8 @@ /* $OpenBSD: getopt_long.c,v 1.21 2006/09/22 17:22:05 millert Exp $ */ /* $NetBSD: getopt_long.c,v 1.15 2002/01/31 22:43:40 tv Exp $ */ -/* +/* SPDX-License-Identifier: LicenseRef-scancode-sudo AND BSD-4-Clause + * * Copyright (c) 2002 Todd C. Miller <Todd.Miller@courtesan.com> * * Permission to use, copy, modify, and distribute this software for any diff --git a/cli/getopt_long.h b/cli/getopt_long.h index b1242915ab5b..b60a3f87b2c1 100644 --- a/cli/getopt_long.h +++ b/cli/getopt_long.h @@ -2,6 +2,8 @@ /* $FreeBSD$ */ /*- + * SPDX-License-Identifier: BSD-2-Clause + * * Copyright (c) 2000 The NetBSD Foundation, Inc. * All rights reserved. * diff --git a/cli/main.c b/cli/main.c index 53eee4c87443..7959737b9913 100644 --- a/cli/main.c +++ b/cli/main.c @@ -1,9 +1,10 @@ /* * main.c - * main() routine, printer functions + * main() routine * - * Copyright (c) 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 - * pkgconf authors (see AUTHORS). + * SPDX-License-Identifier: pkgconf + * + * Copyright (c) 2011-2025 pkgconf authors (see AUTHORS). * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -18,820 +19,97 @@ #include <libpkgconf/stdinc.h> #include <libpkgconf/libpkgconf.h> #include "getopt_long.h" -#ifndef PKGCONF_LITE -#include "renderer-msvc.h" -#endif -#ifdef _WIN32 -#include <io.h> /* for _setmode() */ -#include <fcntl.h> -#endif - -#define PKG_CFLAGS_ONLY_I (((uint64_t) 1) << 2) -#define PKG_CFLAGS_ONLY_OTHER (((uint64_t) 1) << 3) -#define PKG_CFLAGS (PKG_CFLAGS_ONLY_I|PKG_CFLAGS_ONLY_OTHER) -#define PKG_LIBS_ONLY_LDPATH (((uint64_t) 1) << 5) -#define PKG_LIBS_ONLY_LIBNAME (((uint64_t) 1) << 6) -#define PKG_LIBS_ONLY_OTHER (((uint64_t) 1) << 7) -#define PKG_LIBS (PKG_LIBS_ONLY_LDPATH|PKG_LIBS_ONLY_LIBNAME|PKG_LIBS_ONLY_OTHER) -#define PKG_MODVERSION (((uint64_t) 1) << 8) -#define PKG_REQUIRES (((uint64_t) 1) << 9) -#define PKG_REQUIRES_PRIVATE (((uint64_t) 1) << 10) -#define PKG_VARIABLES (((uint64_t) 1) << 11) -#define PKG_DIGRAPH (((uint64_t) 1) << 12) -#define PKG_KEEP_SYSTEM_CFLAGS (((uint64_t) 1) << 13) -#define PKG_KEEP_SYSTEM_LIBS (((uint64_t) 1) << 14) -#define PKG_VERSION (((uint64_t) 1) << 15) -#define PKG_ABOUT (((uint64_t) 1) << 16) -#define PKG_ENV_ONLY (((uint64_t) 1) << 17) -#define PKG_ERRORS_ON_STDOUT (((uint64_t) 1) << 18) -#define PKG_SILENCE_ERRORS (((uint64_t) 1) << 19) -#define PKG_IGNORE_CONFLICTS (((uint64_t) 1) << 20) -#define PKG_STATIC (((uint64_t) 1) << 21) -#define PKG_NO_UNINSTALLED (((uint64_t) 1) << 22) -#define PKG_UNINSTALLED (((uint64_t) 1) << 23) -#define PKG_LIST (((uint64_t) 1) << 24) -#define PKG_HELP (((uint64_t) 1) << 25) -#define PKG_PRINT_ERRORS (((uint64_t) 1) << 26) -#define PKG_SIMULATE (((uint64_t) 1) << 27) -#define PKG_NO_CACHE (((uint64_t) 1) << 28) -#define PKG_PROVIDES (((uint64_t) 1) << 29) -#define PKG_VALIDATE (((uint64_t) 1) << 30) -#define PKG_LIST_PACKAGE_NAMES (((uint64_t) 1) << 31) -#define PKG_NO_PROVIDES (((uint64_t) 1) << 32) -#define PKG_PURE (((uint64_t) 1) << 33) -#define PKG_PATH (((uint64_t) 1) << 34) -#define PKG_DEFINE_PREFIX (((uint64_t) 1) << 35) -#define PKG_DONT_DEFINE_PREFIX (((uint64_t) 1) << 36) -#define PKG_DONT_RELOCATE_PATHS (((uint64_t) 1) << 37) -#define PKG_DEBUG (((uint64_t) 1) << 38) -#define PKG_SHORT_ERRORS (((uint64_t) 1) << 39) -#define PKG_EXISTS (((uint64_t) 1) << 40) -#define PKG_MSVC_SYNTAX (((uint64_t) 1) << 41) -#define PKG_INTERNAL_CFLAGS (((uint64_t) 1) << 42) -#define PKG_DUMP_PERSONALITY (((uint64_t) 1) << 43) -#define PKG_SHARED (((uint64_t) 1) << 44) -#define PKG_DUMP_LICENSE (((uint64_t) 1) << 45) -#define PKG_SOLUTION (((uint64_t) 1) << 46) -#define PKG_EXISTS_CFLAGS (((uint64_t) 1) << 47) -#define PKG_FRAGMENT_TREE (((uint64_t) 1) << 48) - -static pkgconf_client_t pkg_client; -static const pkgconf_fragment_render_ops_t *want_render_ops = NULL; - -static uint64_t want_flags; -static int verbosity = 0; -static int maximum_traverse_depth = 2000; -static size_t maximum_package_count = 0; - -static char *want_variable = NULL; -static char *want_fragment_filter = NULL; - -FILE *error_msgout = NULL; -FILE *logfile_out = NULL; - -static bool -error_handler(const char *msg, const pkgconf_client_t *client, void *data) -{ - (void) client; - (void) data; - fprintf(error_msgout, "%s", msg); - return true; -} - -static bool -print_list_entry(const pkgconf_pkg_t *entry, void *data) -{ - (void) data; - - if (entry->flags & PKGCONF_PKG_PROPF_UNINSTALLED) - return false; - - printf("%-30s %s - %s\n", entry->id, entry->realname, entry->description); +#include "core.h" - return false; -} - -static bool -print_package_entry(const pkgconf_pkg_t *entry, void *data) +static const char * +environ_lookup_handler(const pkgconf_client_t *client, const char *key) { - (void) data; - - if (entry->flags & PKGCONF_PKG_PROPF_UNINSTALLED) - return false; - - printf("%s\n", entry->id); - - return false; -} - -static bool -filter_cflags(const pkgconf_client_t *client, const pkgconf_fragment_t *frag, void *data) -{ - int got_flags = 0; (void) client; - (void) data; - - if (!(want_flags & PKG_KEEP_SYSTEM_CFLAGS) && pkgconf_fragment_has_system_dir(client, frag)) - return false; - if (want_fragment_filter != NULL && (strchr(want_fragment_filter, frag->type) == NULL || !frag->type)) - return false; - - if (frag->type == 'I') - got_flags = PKG_CFLAGS_ONLY_I; - else - got_flags = PKG_CFLAGS_ONLY_OTHER; - - return (want_flags & got_flags) != 0; + return getenv(key); } static bool -filter_libs(const pkgconf_client_t *client, const pkgconf_fragment_t *frag, void *data) +error_handler(const char *msg, const pkgconf_client_t *client, void *data) { - int got_flags = 0; - (void) client; (void) data; + pkgconf_cli_state_t *state = client->client_data; - if (!(want_flags & PKG_KEEP_SYSTEM_LIBS) && pkgconf_fragment_has_system_dir(client, frag)) - return false; - - if (want_fragment_filter != NULL && (strchr(want_fragment_filter, frag->type) == NULL || !frag->type)) - return false; - - switch (frag->type) - { - case 'L': got_flags = PKG_LIBS_ONLY_LDPATH; break; - case 'l': got_flags = PKG_LIBS_ONLY_LIBNAME; break; - default: got_flags = PKG_LIBS_ONLY_OTHER; break; - } - - return (want_flags & got_flags) != 0; -} - -static void -print_variables(pkgconf_pkg_t *pkg) -{ - pkgconf_node_t *node; - - PKGCONF_FOREACH_LIST_ENTRY(pkg->vars.head, node) - { - pkgconf_tuple_t *tuple = node->data; - - printf("%s\n", tuple->key); - } -} - -static void -print_requires(pkgconf_pkg_t *pkg) -{ - pkgconf_node_t *node; - - PKGCONF_FOREACH_LIST_ENTRY(pkg->required.head, node) - { - pkgconf_dependency_t *dep = node->data; - - printf("%s", dep->package); - - if (dep->version != NULL) - printf(" %s %s", pkgconf_pkg_get_comparator(dep), dep->version); - - printf("\n"); - } -} - -static void -print_requires_private(pkgconf_pkg_t *pkg) -{ - pkgconf_node_t *node; - - PKGCONF_FOREACH_LIST_ENTRY(pkg->requires_private.head, node) - { - pkgconf_dependency_t *dep = node->data; - - printf("%s", dep->package); - - if (dep->version != NULL) - printf(" %s %s", pkgconf_pkg_get_comparator(dep), dep->version); - - printf("\n"); - } -} - -static void -print_provides(pkgconf_pkg_t *pkg) -{ - pkgconf_node_t *node; - - PKGCONF_FOREACH_LIST_ENTRY(pkg->provides.head, node) - { - pkgconf_dependency_t *dep = node->data; - - printf("%s", dep->package); - - if (dep->version != NULL) - printf(" %s %s", pkgconf_pkg_get_comparator(dep), dep->version); - - printf("\n"); - } -} - -static bool -apply_provides(pkgconf_client_t *client, pkgconf_pkg_t *world, void *unused, int maxdepth) -{ - pkgconf_node_t *iter; - (void) client; - (void) unused; - (void) maxdepth; - - PKGCONF_FOREACH_LIST_ENTRY(world->required.head, iter) - { - pkgconf_dependency_t *dep = iter->data; - pkgconf_pkg_t *pkg = dep->match; - - print_provides(pkg); - } - - return true; -} - -#ifndef PKGCONF_LITE -static void -print_digraph_node(pkgconf_client_t *client, pkgconf_pkg_t *pkg, void *data) -{ - pkgconf_node_t *node; - (void) client; - pkgconf_pkg_t **last_seen = data; - - if(pkg->flags & PKGCONF_PKG_PROPF_VIRTUAL) - return; - - if (pkg->flags & PKGCONF_PKG_PROPF_VISITED_PRIVATE) - printf("\"%s\" [fontname=Sans fontsize=8 fontcolor=gray color=gray]\n", pkg->id); - else - printf("\"%s\" [fontname=Sans fontsize=8]\n", pkg->id); - - if (last_seen != NULL) - { - if (*last_seen != NULL) - printf("\"%s\" -> \"%s\" [fontname=Sans fontsize=8 color=red]\n", (*last_seen)->id, pkg->id); - - *last_seen = pkg; - } - - PKGCONF_FOREACH_LIST_ENTRY(pkg->required.head, node) - { - pkgconf_dependency_t *dep = node->data; - const char *dep_id = (dep->match != NULL) ? dep->match->id : dep->package; - - if ((dep->flags & PKGCONF_PKG_DEPF_PRIVATE) == 0) - printf("\"%s\" -> \"%s\" [fontname=Sans fontsize=8]\n", pkg->id, dep_id); - else - printf("\"%s\" -> \"%s\" [fontname=Sans fontsize=8 color=gray]\n", pkg->id, dep_id); - } - - PKGCONF_FOREACH_LIST_ENTRY(pkg->requires_private.head, node) - { - pkgconf_dependency_t *dep = node->data; - const char *dep_id = (dep->match != NULL) ? dep->match->id : dep->package; - - printf("\"%s\" -> \"%s\" [fontname=Sans fontsize=8 color=gray]\n", pkg->id, dep_id); - } -} - -static bool -apply_digraph(pkgconf_client_t *client, pkgconf_pkg_t *world, void *data, int maxdepth) -{ - int eflag; - pkgconf_list_t *list = data; - pkgconf_pkg_t *last_seen = NULL; - pkgconf_node_t *iter; - - printf("digraph deptree {\n"); - printf("edge [color=blue len=7.5 fontname=Sans fontsize=8]\n"); - printf("node [fontname=Sans fontsize=8]\n"); - printf("\"user:request\" [fontname=Sans fontsize=8]\n"); - - PKGCONF_FOREACH_LIST_ENTRY(list->head, iter) - { - pkgconf_queue_t *pkgq = iter->data; - pkgconf_pkg_t *pkg = pkgconf_pkg_find(client, pkgq->package); - printf("\"user:request\" -> \"%s\" [fontname=Sans fontsize=8]\n", pkg == NULL ? pkgq->package : pkg->id); - if (pkg != NULL) - pkgconf_pkg_unref(client, pkg); - } - - eflag = pkgconf_pkg_traverse(client, world, print_digraph_node, &last_seen, maxdepth, 0); - - if (eflag != PKGCONF_PKG_ERRF_OK) - return false; - - printf("}\n"); - return true; -} - -static void -print_solution_node(pkgconf_client_t *client, pkgconf_pkg_t *pkg, void *unused) -{ - (void) client; - (void) unused; - - printf("%s (%"PRIu64")%s\n", pkg->id, pkg->identifier, (pkg->flags & PKGCONF_PKG_PROPF_VISITED_PRIVATE) == PKGCONF_PKG_PROPF_VISITED_PRIVATE ? " [private]" : ""); -} - -static bool -apply_print_solution(pkgconf_client_t *client, pkgconf_pkg_t *world, void *unused, int maxdepth) -{ - int eflag; - - eflag = pkgconf_pkg_traverse(client, world, print_solution_node, unused, maxdepth, 0); - - return eflag == PKGCONF_PKG_ERRF_OK; -} -#endif - -static bool -apply_modversion(pkgconf_client_t *client, pkgconf_pkg_t *world, void *data, int maxdepth) -{ - pkgconf_node_t *queue_iter; - pkgconf_list_t *pkgq = data; - (void) client; - (void) maxdepth; - - PKGCONF_FOREACH_LIST_ENTRY(pkgq->head, queue_iter) - { - pkgconf_node_t *world_iter; - pkgconf_queue_t *queue_node = queue_iter->data; - - PKGCONF_FOREACH_LIST_ENTRY(world->required.head, world_iter) - { - pkgconf_dependency_t *dep = world_iter->data; - pkgconf_pkg_t *pkg = dep->match; - - const size_t name_len = strlen(pkg->why); - if (name_len > strlen(queue_node->package) || - strncmp(pkg->why, queue_node->package, name_len) || - (queue_node->package[name_len] != 0 && - !isspace((unsigned char)queue_node->package[name_len]) && - !PKGCONF_IS_OPERATOR_CHAR(queue_node->package[name_len]))) - continue; - - if (pkg->version != NULL) { - if (verbosity) - printf("%s: ", pkg->id); - - printf("%s\n", pkg->version); - } - - break; - } - } - - return true; -} - -static bool -apply_variables(pkgconf_client_t *client, pkgconf_pkg_t *world, void *unused, int maxdepth) -{ - pkgconf_node_t *iter; - (void) client; - (void) unused; - (void) maxdepth; - - PKGCONF_FOREACH_LIST_ENTRY(world->required.head, iter) - { - pkgconf_dependency_t *dep = iter->data; - pkgconf_pkg_t *pkg = dep->match; - - print_variables(pkg); - } - - return true; -} - -static bool -apply_path(pkgconf_client_t *client, pkgconf_pkg_t *world, void *unused, int maxdepth) -{ - pkgconf_node_t *iter; - (void) client; - (void) unused; - (void) maxdepth; - - PKGCONF_FOREACH_LIST_ENTRY(world->required.head, iter) - { - pkgconf_dependency_t *dep = iter->data; - pkgconf_pkg_t *pkg = dep->match; - - /* a module entry with no filename is either virtual, static (builtin) or synthesized. */ - if (pkg->filename != NULL) - printf("%s\n", pkg->filename); - } - - return true; -} - -static bool -apply_variable(pkgconf_client_t *client, pkgconf_pkg_t *world, void *variable, int maxdepth) -{ - pkgconf_node_t *iter; - (void) maxdepth; - - PKGCONF_FOREACH_LIST_ENTRY(world->required.head, iter) - { - pkgconf_dependency_t *dep = iter->data; - pkgconf_pkg_t *pkg = dep->match; - const char *var; - - var = pkgconf_tuple_find(client, &pkg->vars, variable); - - if (var != NULL) - printf("%s%s", iter->prev != NULL ? " " : "", var); - } - - printf("\n"); - - return true; -} - -static bool -apply_env_var(const char *prefix, pkgconf_client_t *client, pkgconf_pkg_t *world, int maxdepth, - unsigned int (*collect_fn)(pkgconf_client_t *client, pkgconf_pkg_t *world, pkgconf_list_t *list, int maxdepth), - bool (*filter_fn)(const pkgconf_client_t *client, const pkgconf_fragment_t *frag, void *data), - void (*postprocess_fn)(pkgconf_client_t *client, pkgconf_pkg_t *world, pkgconf_list_t *fragment_list)) -{ - pkgconf_list_t unfiltered_list = PKGCONF_LIST_INITIALIZER; - pkgconf_list_t filtered_list = PKGCONF_LIST_INITIALIZER; - unsigned int eflag; - char *render_buf; - - eflag = collect_fn(client, world, &unfiltered_list, maxdepth); - if (eflag != PKGCONF_PKG_ERRF_OK) - return false; - - pkgconf_fragment_filter(client, &filtered_list, &unfiltered_list, filter_fn, NULL); - - if (postprocess_fn != NULL) - postprocess_fn(client, world, &filtered_list); - - if (filtered_list.head == NULL) - goto out; - - render_buf = pkgconf_fragment_render(&filtered_list, true, want_render_ops); - printf("%s='%s'\n", prefix, render_buf); - free(render_buf); - -out: - pkgconf_fragment_free(&unfiltered_list); - pkgconf_fragment_free(&filtered_list); + if (state->error_msgout == NULL) + return true; + pkgconf_output_file_fmt(state->error_msgout, "%s", msg); return true; } static void -maybe_add_module_definitions(pkgconf_client_t *client, pkgconf_pkg_t *world, pkgconf_list_t *fragment_list) -{ - pkgconf_node_t *world_iter; - - if ((want_flags & PKG_EXISTS_CFLAGS) != PKG_EXISTS_CFLAGS) - return; - - PKGCONF_FOREACH_LIST_ENTRY(world->required.head, world_iter) - { - pkgconf_dependency_t *dep = world_iter->data; - char havebuf[PKGCONF_ITEM_SIZE]; - char *p; - - if ((dep->flags & PKGCONF_PKG_DEPF_QUERY) != PKGCONF_PKG_DEPF_QUERY) - continue; - - if (dep->match == NULL) - continue; - - snprintf(havebuf, sizeof havebuf, "HAVE_%s", dep->match->id); - - for (p = havebuf; *p; p++) - { - switch (*p) - { - case ' ': - case '-': - *p = '_'; - break; - - default: - *p = toupper((unsigned char) *p); - } - } - - pkgconf_fragment_insert(client, fragment_list, 'D', havebuf, false); - } -} - -static void -apply_env_variables(pkgconf_client_t *client, pkgconf_pkg_t *world, const char *env_prefix) -{ - (void) client; - pkgconf_node_t *world_iter; - - PKGCONF_FOREACH_LIST_ENTRY(world->required.head, world_iter) - { - pkgconf_dependency_t *dep = world_iter->data; - pkgconf_pkg_t *pkg = dep->match; - pkgconf_node_t *tuple_iter; - - if ((dep->flags & PKGCONF_PKG_DEPF_QUERY) != PKGCONF_PKG_DEPF_QUERY) - continue; - - if (dep->match == NULL) - continue; - - PKGCONF_FOREACH_LIST_ENTRY(pkg->vars.head, tuple_iter) - { - pkgconf_tuple_t *tuple = tuple_iter->data; - char havebuf[PKGCONF_ITEM_SIZE]; - char *p; - - if (want_variable != NULL && strcmp(want_variable, tuple->key)) - continue; - - snprintf(havebuf, sizeof havebuf, "%s_%s", env_prefix, tuple->key); - - for (p = havebuf; *p; p++) - { - switch (*p) - { - case ' ': - case '-': - *p = '_'; - break; - - default: - *p = toupper((unsigned char) *p); - } - } - - printf("%s='%s'\n", havebuf, tuple->value); - } - } -} - -static bool -apply_env(pkgconf_client_t *client, pkgconf_pkg_t *world, void *env_prefix_p, int maxdepth) -{ - const char *want_env_prefix = env_prefix_p, *it; - char workbuf[PKGCONF_ITEM_SIZE]; - - for (it = want_env_prefix; *it != '\0'; it++) - if (!isalpha((unsigned char)*it) && - !isdigit((unsigned char)*it)) - return false; - - snprintf(workbuf, sizeof workbuf, "%s_CFLAGS", want_env_prefix); - if (!apply_env_var(workbuf, client, world, maxdepth, pkgconf_pkg_cflags, filter_cflags, maybe_add_module_definitions)) - return false; - - snprintf(workbuf, sizeof workbuf, "%s_LIBS", want_env_prefix); - if (!apply_env_var(workbuf, client, world, maxdepth, pkgconf_pkg_libs, filter_libs, NULL)) - return false; - - if ((want_flags & PKG_VARIABLES) == PKG_VARIABLES || want_variable != NULL) - apply_env_variables(client, world, want_env_prefix); - - return true; -} - -static bool -apply_cflags(pkgconf_client_t *client, pkgconf_pkg_t *world, void *unused, int maxdepth) -{ - pkgconf_list_t unfiltered_list = PKGCONF_LIST_INITIALIZER; - pkgconf_list_t filtered_list = PKGCONF_LIST_INITIALIZER; - int eflag; - char *render_buf; - (void) unused; - - eflag = pkgconf_pkg_cflags(client, world, &unfiltered_list, maxdepth); - if (eflag != PKGCONF_PKG_ERRF_OK) - return false; - - pkgconf_fragment_filter(client, &filtered_list, &unfiltered_list, filter_cflags, NULL); - maybe_add_module_definitions(client, world, &filtered_list); - - if (filtered_list.head == NULL) - goto out; - - render_buf = pkgconf_fragment_render(&filtered_list, true, want_render_ops); - printf("%s", render_buf); - free(render_buf); - -out: - pkgconf_fragment_free(&unfiltered_list); - pkgconf_fragment_free(&filtered_list); - - return true; -} - -static bool -apply_libs(pkgconf_client_t *client, pkgconf_pkg_t *world, void *unused, int maxdepth) -{ - pkgconf_list_t unfiltered_list = PKGCONF_LIST_INITIALIZER; - pkgconf_list_t filtered_list = PKGCONF_LIST_INITIALIZER; - int eflag; - char *render_buf; - (void) unused; - - eflag = pkgconf_pkg_libs(client, world, &unfiltered_list, maxdepth); - if (eflag != PKGCONF_PKG_ERRF_OK) - return false; - - pkgconf_fragment_filter(client, &filtered_list, &unfiltered_list, filter_libs, NULL); - - if (filtered_list.head == NULL) - goto out; - - render_buf = pkgconf_fragment_render(&filtered_list, true, want_render_ops); - printf("%s", render_buf); - free(render_buf); - -out: - pkgconf_fragment_free(&unfiltered_list); - pkgconf_fragment_free(&filtered_list); - - return true; -} - -static bool -apply_requires(pkgconf_client_t *client, pkgconf_pkg_t *world, void *unused, int maxdepth) -{ - pkgconf_node_t *iter; - (void) client; - (void) unused; - (void) maxdepth; - - PKGCONF_FOREACH_LIST_ENTRY(world->required.head, iter) - { - pkgconf_dependency_t *dep = iter->data; - pkgconf_pkg_t *pkg = dep->match; - - print_requires(pkg); - } - - return true; -} - -static bool -apply_requires_private(pkgconf_client_t *client, pkgconf_pkg_t *world, void *unused, int maxdepth) +unveil_handler(const pkgconf_client_t *client, const char *path, const char *permissions) { - pkgconf_node_t *iter; (void) client; - (void) unused; - (void) maxdepth; - PKGCONF_FOREACH_LIST_ENTRY(world->required.head, iter) + if (pkgconf_unveil(path, permissions) == -1 && errno != ENOENT) { - pkgconf_dependency_t *dep = iter->data; - pkgconf_pkg_t *pkg = dep->match; - - print_requires_private(pkg); + pkgconf_output_file_fmt(stderr, "pkgconf: unveil failed: %s\n", strerror(errno)); + exit(EXIT_FAILURE); } - return true; } static void -check_uninstalled(pkgconf_client_t *client, pkgconf_pkg_t *pkg, void *data) -{ - int *retval = data; - (void) client; - - if (pkg->flags & PKGCONF_PKG_PROPF_UNINSTALLED) - *retval = EXIT_SUCCESS; -} - -static bool -apply_uninstalled(pkgconf_client_t *client, pkgconf_pkg_t *world, void *data, int maxdepth) +relocate_path(const char *path) { - int eflag; + pkgconf_buffer_t pathbuf = PKGCONF_BUFFER_INITIALIZER; - eflag = pkgconf_pkg_traverse(client, world, check_uninstalled, data, maxdepth, 0); + pkgconf_buffer_append(&pathbuf, path); + pkgconf_path_relocate(&pathbuf); - if (eflag != PKGCONF_PKG_ERRF_OK) - return false; + printf("%s\n", pkgconf_buffer_str(&pathbuf)); - return true; + pkgconf_buffer_finalize(&pathbuf); } #ifndef PKGCONF_LITE -static void -print_graph_node(pkgconf_client_t *client, pkgconf_pkg_t *pkg, void *data) -{ - pkgconf_node_t *n; - - (void) client; - (void) data; - - printf("node '%s' {\n", pkg->id); - - if (pkg->version != NULL) - printf(" version = '%s';\n", pkg->version); - - PKGCONF_FOREACH_LIST_ENTRY(pkg->required.head, n) - { - pkgconf_dependency_t *dep = n->data; - printf(" dependency '%s'", dep->package); - if (dep->compare != PKGCONF_CMP_ANY) - { - printf(" {\n"); - printf(" comparator = '%s';\n", pkgconf_pkg_get_comparator(dep)); - printf(" version = '%s';\n", dep->version); - printf(" };\n"); - } - else - printf(";\n"); - } - - printf("};\n"); -} - -static bool -apply_simulate(pkgconf_client_t *client, pkgconf_pkg_t *world, void *data, int maxdepth) +static pkgconf_cross_personality_t * +deduce_personality(char *argv[]) { - int eflag; - - eflag = pkgconf_pkg_traverse(client, world, print_graph_node, data, maxdepth, 0); + const char *argv0 = argv[0]; + char *i, *prefix; + pkgconf_cross_personality_t *out; - if (eflag != PKGCONF_PKG_ERRF_OK) - return false; + i = strrchr(argv0, '/'); + if (i != NULL) + argv0 = i + 1; - return true; -} +#if defined(_WIN32) || defined(_WIN64) + i = strrchr(argv0, '\\'); + if (i != NULL) + argv0 = i + 1; #endif -static void -print_fragment_tree_branch(pkgconf_list_t *fragment_list, int indent) -{ - pkgconf_node_t *iter; - - PKGCONF_FOREACH_LIST_ENTRY(fragment_list->head, iter) - { - pkgconf_fragment_t *frag = iter->data; - - if (frag->type) - printf("%*s'-%c%s' [type %c]\n", indent, "", frag->type, frag->data, frag->type); - else - printf("%*s'%s' [untyped]\n", indent, "", frag->data); + i = strstr(argv0, "-pkg"); + if (i == NULL) + return pkgconf_cross_personality_default(); - print_fragment_tree_branch(&frag->children, indent + 2); - } + prefix = pkgconf_strndup(argv0, i - argv0); + out = pkgconf_cross_personality_find(prefix); + free(prefix); + if (out == NULL) + return pkgconf_cross_personality_default(); - if (fragment_list->head != NULL) - printf("\n"); + return out; } +#endif -static bool -apply_fragment_tree(pkgconf_client_t *client, pkgconf_pkg_t *world, void *data, int maxdepth) -{ - pkgconf_list_t unfiltered_list = PKGCONF_LIST_INITIALIZER; - int eflag; - - (void) data; - - eflag = pkgconf_pkg_cflags(client, world, &unfiltered_list, maxdepth); - if (eflag != PKGCONF_PKG_ERRF_OK) - return false; - - eflag = pkgconf_pkg_libs(client, world, &unfiltered_list, maxdepth); - if (eflag != PKGCONF_PKG_ERRF_OK) - return false; - - print_fragment_tree_branch(&unfiltered_list, 0); - pkgconf_fragment_free(&unfiltered_list); - - return true; -} +#ifdef _WIN32 +static UINT original_console_cp; +static UINT original_console_out_cp; static void -print_license(pkgconf_client_t *client, pkgconf_pkg_t *pkg, void *data) +restore_console_code_page(void) { - (void) client; - (void) data; - - if (pkg->flags & PKGCONF_PKG_PROPF_VIRTUAL) - return; - - /* NOASSERTION is the default when the license is unknown, per SPDX spec § 3.15 */ - printf("%s: %s\n", pkg->id, pkg->license != NULL ? pkg->license : "NOASSERTION"); -} - -static bool -apply_license(pkgconf_client_t *client, pkgconf_pkg_t *world, void *data, int maxdepth) -{ - int eflag; - - eflag = pkgconf_pkg_traverse(client, world, print_license, data, maxdepth, 0); - - if (eflag != PKGCONF_PKG_ERRF_OK) - return false; - - return true; + SetConsoleCP(original_console_cp); + SetConsoleOutputCP(original_console_out_cp); } +#endif static void version(void) @@ -931,6 +209,7 @@ usage(void) printf(" --print-variables print all known variables in module to stdout\n"); #ifndef PKGCONF_LITE printf(" --digraph print entire dependency graph in graphviz 'dot' format\n"); + printf(" --print-digraph-query-nodes also print query nodes in 'dot' format\n"); printf(" --solution print dependency graph solution in a simple format\n"); #endif printf(" --keep-system-cflags keep -I%s entries in cflags output\n", SYSTEM_INCLUDEDIR); @@ -939,6 +218,8 @@ usage(void) printf(" --modversion print the specified module's version to stdout\n"); printf(" --internal-cflags do not filter 'internal' cflags from output\n"); printf(" --license print the specified module's license to stdout if known\n"); + printf(" --link-abi print the link ABIs (e.g. c, c++) the module must be linked against\n"); + printf(" --source print the specified module's source code location to stdout if known\n"); printf(" --exists-cflags add -DHAVE_FOO fragments to cflags for each found module\n"); printf("\nfiltering output:\n\n"); @@ -948,162 +229,44 @@ usage(void) printf(" --fragment-filter=types filter output fragments to the specified types\n"); printf(" --env=prefix print output as shell-compatible environmental variables\n"); printf(" --fragment-tree visualize printed CFLAGS/LIBS fragments as a tree\n"); + printf(" --newlines use newlines for whitespace between fragments\n"); printf("\nreport bugs to <%s>.\n", PACKAGE_BUGREPORT); } -static void -relocate_path(const char *path) -{ - char buf[PKGCONF_BUFSIZE]; - - pkgconf_strlcpy(buf, path, sizeof buf); - pkgconf_path_relocate(buf, sizeof buf); - - printf("%s\n", buf); -} - -#ifndef PKGCONF_LITE -static void -dump_personality(const pkgconf_cross_personality_t *p) +int +main(int argc, char *argv[]) { - pkgconf_node_t *n; - - printf("Triplet: %s\n", p->name); - - if (p->sysroot_dir) - printf("SysrootDir: %s\n", p->sysroot_dir); - - printf("DefaultSearchPaths: "); - PKGCONF_FOREACH_LIST_ENTRY(p->dir_list.head, n) - { - pkgconf_path_t *pn = n->data; - printf("%s ", pn->path); - } - - printf("\n"); - printf("SystemIncludePaths: "); - PKGCONF_FOREACH_LIST_ENTRY(p->filter_includedirs.head, n) - { - pkgconf_path_t *pn = n->data; - printf("%s ", pn->path); - } - - printf("\n"); - printf("SystemLibraryPaths: "); - PKGCONF_FOREACH_LIST_ENTRY(p->filter_libdirs.head, n) +#ifdef _WIN32 + // When activeCodePage is set to UTF-8 in the application manifest (requires Windows 1903+), + // GetACP() returns CP_UTF8 but the console code pages are not automatically updated to match. + // Detect this condition via GetACP() == CP_UTF8 and align the console code pages accordingly. + // Restoring on exit is safe: if ACP is already CP_UTF8, resetting to the saved values is a no-op. + if (GetACP() == CP_UTF8) { - pkgconf_path_t *pn = n->data; - printf("%s ", pn->path); + original_console_cp = GetConsoleCP(); + original_console_out_cp = GetConsoleOutputCP(); + SetConsoleCP(CP_UTF8); + SetConsoleOutputCP(CP_UTF8); + atexit(restore_console_code_page); } - - printf("\n"); -} - -static pkgconf_cross_personality_t * -deduce_personality(char *argv[]) -{ - const char *argv0 = argv[0]; - char *i, *prefix; - pkgconf_cross_personality_t *out; - - i = strrchr(argv0, '/'); - if (i != NULL) - argv0 = i + 1; - -#if defined(_WIN32) || defined(_WIN64) - i = strrchr(argv0, '\\'); - if (i != NULL) - argv0 = i + 1; -#endif - - i = strstr(argv0, "-pkg"); - if (i == NULL) - return pkgconf_cross_personality_default(); - - prefix = pkgconf_strndup(argv0, i - argv0); - out = pkgconf_cross_personality_find(prefix); - free(prefix); - if (out == NULL) - return pkgconf_cross_personality_default(); - - return out; -} #endif -static void -unveil_handler(const pkgconf_client_t *client, const char *path, const char *permissions) -{ - (void) client; - - if (pkgconf_unveil(path, permissions) == -1) - { - fprintf(stderr, "pkgconf: unveil failed: %s\n", strerror(errno)); - exit(EXIT_FAILURE); - } -} - -static bool -unveil_search_paths(const pkgconf_client_t *client, const pkgconf_cross_personality_t *personality) -{ - pkgconf_node_t *n; - - if (pkgconf_unveil("/dev/null", "rwc") == -1) - return false; - - PKGCONF_FOREACH_LIST_ENTRY(client->dir_list.head, n) - { - pkgconf_path_t *pn = n->data; - - if (pkgconf_unveil(pn->path, "r") == -1) - return false; - } - - PKGCONF_FOREACH_LIST_ENTRY(personality->dir_list.head, n) - { - pkgconf_path_t *pn = n->data; - - if (pkgconf_unveil(pn->path, "r") == -1) - return false; - } - - pkgconf_client_set_unveil_handler(&pkg_client, unveil_handler); - - return true; -} - -int -main(int argc, char *argv[]) -{ int ret; - pkgconf_list_t pkgq = PKGCONF_LIST_INITIALIZER; + pkgconf_cli_state_t state = { + .want_flags = 0, + }; pkgconf_list_t dir_list = PKGCONF_LIST_INITIALIZER; - char *builddir; - char *sysroot_dir; char *env_traverse_depth; - char *required_pkgconfig_version = NULL; - char *required_exact_module_version = NULL; - char *required_max_module_version = NULL; - char *required_module_version = NULL; char *logfile_arg = NULL; - char *want_env_prefix = NULL; - unsigned int want_client_flags = PKGCONF_PKG_PKGF_NONE; pkgconf_cross_personality_t *personality = NULL; - bool opened_error_msgout = false; - pkgconf_pkg_t world = { - .id = "virtual:world", - .realname = "virtual world package", - .flags = PKGCONF_PKG_PROPF_STATIC | PKGCONF_PKG_PROPF_VIRTUAL, - }; if (pkgconf_pledge("stdio rpath wpath cpath unveil", NULL) == -1) { - fprintf(stderr, "pkgconf: pledge failed: %s\n", strerror(errno)); + pkgconf_output_file_fmt(stderr, "pkgconf: pledge failed: %s\n", strerror(errno)); return EXIT_FAILURE; } - want_flags = 0; - #ifdef _WIN32 /* When running regression tests in cygwin, and building native * executable, tests fail unless native executable outputs unix @@ -1115,85 +278,92 @@ main(int argc, char *argv[]) #endif struct pkg_option options[] = { - { "version", no_argument, &want_flags, PKG_VERSION|PKG_PRINT_ERRORS, }, - { "about", no_argument, &want_flags, PKG_ABOUT|PKG_PRINT_ERRORS, }, + { "version", no_argument, &state.want_flags, PKG_VERSION|PKG_PRINT_ERRORS, }, + { "about", no_argument, &state.want_flags, PKG_ABOUT|PKG_PRINT_ERRORS, }, { "atleast-version", required_argument, NULL, 2, }, { "atleast-pkgconfig-version", required_argument, NULL, 3, }, - { "libs", no_argument, &want_flags, PKG_LIBS|PKG_PRINT_ERRORS, }, - { "cflags", no_argument, &want_flags, PKG_CFLAGS|PKG_PRINT_ERRORS, }, - { "modversion", no_argument, &want_flags, PKG_MODVERSION|PKG_PRINT_ERRORS, }, + { "libs", no_argument, &state.want_flags, PKG_LIBS|PKG_PRINT_ERRORS, }, + { "cflags", no_argument, &state.want_flags, PKG_CFLAGS|PKG_PRINT_ERRORS, }, + { "modversion", no_argument, &state.want_flags, PKG_MODVERSION|PKG_PRINT_ERRORS, }, { "variable", required_argument, NULL, 7, }, - { "exists", no_argument, &want_flags, PKG_EXISTS, }, - { "print-errors", no_argument, &want_flags, PKG_PRINT_ERRORS, }, - { "short-errors", no_argument, &want_flags, PKG_SHORT_ERRORS, }, + { "exists", no_argument, &state.want_flags, PKG_EXISTS, }, + { "print-errors", no_argument, &state.want_flags, PKG_PRINT_ERRORS, }, + { "short-errors", no_argument, &state.want_flags, PKG_SHORT_ERRORS, }, { "maximum-traverse-depth", required_argument, NULL, 11, }, - { "static", no_argument, &want_flags, PKG_STATIC, }, - { "shared", no_argument, &want_flags, PKG_SHARED, }, - { "pure", no_argument, &want_flags, PKG_PURE, }, - { "print-requires", no_argument, &want_flags, PKG_REQUIRES, }, - { "print-variables", no_argument, &want_flags, PKG_VARIABLES|PKG_PRINT_ERRORS, }, + { "static", no_argument, &state.want_flags, PKG_STATIC, }, + { "shared", no_argument, &state.want_flags, PKG_SHARED, }, + { "pure", no_argument, &state.want_flags, PKG_PURE, }, + { "print-requires", no_argument, &state.want_flags, PKG_REQUIRES, }, + { "print-variables", no_argument, &state.want_flags, PKG_VARIABLES|PKG_PRINT_ERRORS, }, #ifndef PKGCONF_LITE - { "digraph", no_argument, &want_flags, PKG_DIGRAPH, }, - { "solution", no_argument, &want_flags, PKG_SOLUTION, }, + { "digraph", no_argument, &state.want_flags, PKG_DIGRAPH, }, + { "solution", no_argument, &state.want_flags, PKG_SOLUTION, }, #endif - { "help", no_argument, &want_flags, PKG_HELP, }, - { "env-only", no_argument, &want_flags, PKG_ENV_ONLY, }, - { "print-requires-private", no_argument, &want_flags, PKG_REQUIRES_PRIVATE, }, - { "cflags-only-I", no_argument, &want_flags, PKG_CFLAGS_ONLY_I|PKG_PRINT_ERRORS, }, - { "cflags-only-other", no_argument, &want_flags, PKG_CFLAGS_ONLY_OTHER|PKG_PRINT_ERRORS, }, - { "libs-only-L", no_argument, &want_flags, PKG_LIBS_ONLY_LDPATH|PKG_PRINT_ERRORS, }, - { "libs-only-l", no_argument, &want_flags, PKG_LIBS_ONLY_LIBNAME|PKG_PRINT_ERRORS, }, - { "libs-only-other", no_argument, &want_flags, PKG_LIBS_ONLY_OTHER|PKG_PRINT_ERRORS, }, - { "uninstalled", no_argument, &want_flags, PKG_UNINSTALLED, }, - { "no-uninstalled", no_argument, &want_flags, PKG_NO_UNINSTALLED, }, - { "keep-system-cflags", no_argument, &want_flags, PKG_KEEP_SYSTEM_CFLAGS, }, - { "keep-system-libs", no_argument, &want_flags, PKG_KEEP_SYSTEM_LIBS, }, + { "help", no_argument, &state.want_flags, PKG_HELP, }, + { "env-only", no_argument, &state.want_flags, PKG_ENV_ONLY, }, + { "print-requires-private", no_argument, &state.want_flags, PKG_REQUIRES_PRIVATE, }, + { "cflags-only-I", no_argument, &state.want_flags, PKG_CFLAGS_ONLY_I|PKG_PRINT_ERRORS, }, + { "cflags-only-other", no_argument, &state.want_flags, PKG_CFLAGS_ONLY_OTHER|PKG_PRINT_ERRORS, }, + { "libs-only-L", no_argument, &state.want_flags, PKG_LIBS_ONLY_LDPATH|PKG_PRINT_ERRORS, }, + { "libs-only-l", no_argument, &state.want_flags, PKG_LIBS_ONLY_LIBNAME|PKG_PRINT_ERRORS, }, + { "libs-only-other", no_argument, &state.want_flags, PKG_LIBS_ONLY_OTHER|PKG_PRINT_ERRORS, }, + { "uninstalled", no_argument, &state.want_flags, PKG_UNINSTALLED, }, + { "no-uninstalled", no_argument, &state.want_flags, PKG_NO_UNINSTALLED, }, + { "keep-system-cflags", no_argument, &state.want_flags, PKG_KEEP_SYSTEM_CFLAGS, }, + { "keep-system-libs", no_argument, &state.want_flags, PKG_KEEP_SYSTEM_LIBS, }, { "define-variable", required_argument, NULL, 27, }, { "exact-version", required_argument, NULL, 28, }, { "max-version", required_argument, NULL, 29, }, - { "ignore-conflicts", no_argument, &want_flags, PKG_IGNORE_CONFLICTS, }, - { "errors-to-stdout", no_argument, &want_flags, PKG_ERRORS_ON_STDOUT, }, - { "silence-errors", no_argument, &want_flags, PKG_SILENCE_ERRORS, }, - { "list-all", no_argument, &want_flags, PKG_LIST|PKG_PRINT_ERRORS, }, - { "list-package-names", no_argument, &want_flags, PKG_LIST_PACKAGE_NAMES|PKG_PRINT_ERRORS, }, + { "ignore-conflicts", no_argument, &state.want_flags, PKG_IGNORE_CONFLICTS, }, + { "errors-to-stdout", no_argument, &state.want_flags, PKG_ERRORS_ON_STDOUT, }, + { "silence-errors", no_argument, &state.want_flags, PKG_SILENCE_ERRORS, }, + { "list-all", no_argument, &state.want_flags, PKG_LIST|PKG_PRINT_ERRORS, }, + { "list-package-names", no_argument, &state.want_flags, PKG_LIST_PACKAGE_NAMES|PKG_PRINT_ERRORS, }, #ifndef PKGCONF_LITE - { "simulate", no_argument, &want_flags, PKG_SIMULATE, }, + { "simulate", no_argument, &state.want_flags, PKG_SIMULATE, }, #endif - { "no-cache", no_argument, &want_flags, PKG_NO_CACHE, }, - { "print-provides", no_argument, &want_flags, PKG_PROVIDES, }, - { "no-provides", no_argument, &want_flags, PKG_NO_PROVIDES, }, - { "debug", no_argument, &want_flags, PKG_DEBUG|PKG_PRINT_ERRORS, }, - { "validate", no_argument, &want_flags, PKG_VALIDATE|PKG_PRINT_ERRORS|PKG_ERRORS_ON_STDOUT }, + { "no-cache", no_argument, &state.want_flags, PKG_NO_CACHE, }, + { "print-provides", no_argument, &state.want_flags, PKG_PROVIDES, }, + { "no-provides", no_argument, &state.want_flags, PKG_NO_PROVIDES, }, + { "debug", no_argument, &state.want_flags, PKG_DEBUG|PKG_PRINT_ERRORS, }, + { "validate", no_argument, &state.want_flags, PKG_VALIDATE|PKG_PRINT_ERRORS|PKG_ERRORS_ON_STDOUT }, { "log-file", required_argument, NULL, 40 }, - { "path", no_argument, &want_flags, PKG_PATH }, + { "path", no_argument, &state.want_flags, PKG_PATH }, { "with-path", required_argument, NULL, 42 }, { "prefix-variable", required_argument, NULL, 43 }, - { "define-prefix", no_argument, &want_flags, PKG_DEFINE_PREFIX }, + { "define-prefix", no_argument, &state.want_flags, PKG_DEFINE_PREFIX }, { "relocate", required_argument, NULL, 45 }, - { "dont-define-prefix", no_argument, &want_flags, PKG_DONT_DEFINE_PREFIX }, - { "dont-relocate-paths", no_argument, &want_flags, PKG_DONT_RELOCATE_PATHS }, + { "dont-define-prefix", no_argument, &state.want_flags, PKG_DONT_DEFINE_PREFIX }, + { "dont-relocate-paths", no_argument, &state.want_flags, PKG_DONT_RELOCATE_PATHS }, { "env", required_argument, NULL, 48 }, #ifndef PKGCONF_LITE - { "msvc-syntax", no_argument, &want_flags, PKG_MSVC_SYNTAX }, + { "msvc-syntax", no_argument, &state.want_flags, PKG_MSVC_SYNTAX }, #endif { "fragment-filter", required_argument, NULL, 50 }, - { "internal-cflags", no_argument, &want_flags, PKG_INTERNAL_CFLAGS }, + { "internal-cflags", no_argument, &state.want_flags, PKG_INTERNAL_CFLAGS }, #ifndef PKGCONF_LITE - { "dump-personality", no_argument, &want_flags, PKG_DUMP_PERSONALITY }, + { "dump-personality", no_argument, &state.want_flags, PKG_DUMP_PERSONALITY }, { "personality", required_argument, NULL, 53 }, #endif - { "license", no_argument, &want_flags, PKG_DUMP_LICENSE }, + { "license", no_argument, &state.want_flags, PKG_DUMP_LICENSE }, + { "license-file", no_argument, &state.want_flags, PKG_DUMP_LICENSE_FILE }, + { "link-abi", no_argument, &state.want_flags, PKG_LINK_ABI }, { "verbose", no_argument, NULL, 55 }, - { "exists-cflags", no_argument, &want_flags, PKG_EXISTS_CFLAGS }, - { "fragment-tree", no_argument, &want_flags, PKG_FRAGMENT_TREE }, + { "exists-cflags", no_argument, &state.want_flags, PKG_EXISTS_CFLAGS }, + { "fragment-tree", no_argument, &state.want_flags, PKG_FRAGMENT_TREE }, + { "source", no_argument, &state.want_flags, PKG_DUMP_SOURCE }, + { "newlines", no_argument, &state.want_flags, PKG_NEWLINES }, +#ifndef PKGCONF_LITE + { "print-digraph-query-nodes", no_argument, &state.want_flags, PKG_PRINT_DIGRAPH_QUERY_NODES }, +#endif { NULL, 0, NULL, 0 } }; #ifndef PKGCONF_LITE if (getenv("PKG_CONFIG_EARLY_TRACE")) { - error_msgout = stderr; - pkgconf_client_set_trace_handler(&pkg_client, error_handler, NULL); + state.error_msgout = stderr; + pkgconf_client_set_trace_handler(&state.pkg_client, error_handler, NULL); } #endif @@ -1202,25 +372,25 @@ main(int argc, char *argv[]) switch (ret) { case 2: - required_module_version = pkg_optarg; + state.required_module_version = pkg_optarg; break; case 3: - required_pkgconfig_version = pkg_optarg; + state.required_pkgconfig_version = pkg_optarg; break; case 7: - want_variable = pkg_optarg; + state.want_variable = pkg_optarg; break; case 11: - maximum_traverse_depth = atoi(pkg_optarg); + state.maximum_traverse_depth = atoi(pkg_optarg); break; case 27: - pkgconf_tuple_define_global(&pkg_client, pkg_optarg); + pkgconf_tuple_define_global(&state.pkg_client, pkg_optarg); break; case 28: - required_exact_module_version = pkg_optarg; + state.required_exact_module_version = pkg_optarg; break; case 29: - required_max_module_version = pkg_optarg; + state.required_max_module_version = pkg_optarg; break; case 40: logfile_arg = pkg_optarg; @@ -1229,16 +399,16 @@ main(int argc, char *argv[]) pkgconf_path_prepend(pkg_optarg, &dir_list, true); break; case 43: - pkgconf_client_set_prefix_varname(&pkg_client, pkg_optarg); + pkgconf_client_set_prefix_varname(&state.pkg_client, pkg_optarg); break; case 45: relocate_path(pkg_optarg); return EXIT_SUCCESS; case 48: - want_env_prefix = pkg_optarg; + state.want_env_prefix = pkg_optarg; break; case 50: - want_fragment_filter = pkg_optarg; + state.want_fragment_filter = pkg_optarg; break; #ifndef PKGCONF_LITE case 53: @@ -1246,7 +416,7 @@ main(int argc, char *argv[]) break; #endif case 55: - verbosity++; + state.verbosity++; break; case '?': case ':': @@ -1265,55 +435,41 @@ main(int argc, char *argv[]) #endif } -#ifndef PKGCONF_LITE - if ((want_flags & PKG_DUMP_PERSONALITY) == PKG_DUMP_PERSONALITY) - { - dump_personality(personality); - return EXIT_SUCCESS; - } -#endif - /* now, bring up the client. settings are preserved since the client is prealloced */ - pkgconf_client_init(&pkg_client, error_handler, NULL, personality); - - /* unveil the entire search path now that we have loaded the personality data. */ - if (!unveil_search_paths(&pkg_client, personality)) - { - fprintf(stderr, "pkgconf: unveil failed: %s\n", strerror(errno)); - return EXIT_FAILURE; - } + pkgconf_client_init(&state.pkg_client, error_handler, &state, personality, &state, environ_lookup_handler); + pkgconf_client_set_unveil_handler(&state.pkg_client, unveil_handler); #ifndef PKGCONF_LITE - if ((want_flags & PKG_MSVC_SYNTAX) == PKG_MSVC_SYNTAX || getenv("PKG_CONFIG_MSVC_SYNTAX") != NULL) - want_render_ops = msvc_renderer_get(); + if (getenv("PKG_CONFIG_MSVC_SYNTAX") != NULL) + state.want_flags |= PKG_MSVC_SYNTAX; #endif if ((env_traverse_depth = getenv("PKG_CONFIG_MAXIMUM_TRAVERSE_DEPTH")) != NULL) - maximum_traverse_depth = atoi(env_traverse_depth); + state.maximum_traverse_depth = atoi(env_traverse_depth); - if ((want_flags & PKG_PRINT_ERRORS) != PKG_PRINT_ERRORS) - want_flags |= (PKG_SILENCE_ERRORS); + if ((state.want_flags & PKG_PRINT_ERRORS) != PKG_PRINT_ERRORS) + state.want_flags |= (PKG_SILENCE_ERRORS); - if ((want_flags & PKG_SILENCE_ERRORS) == PKG_SILENCE_ERRORS && !getenv("PKG_CONFIG_DEBUG_SPEW")) - want_flags |= (PKG_SILENCE_ERRORS); + if ((state.want_flags & PKG_SILENCE_ERRORS) == PKG_SILENCE_ERRORS && !getenv("PKG_CONFIG_DEBUG_SPEW")) + state.want_flags |= (PKG_SILENCE_ERRORS); else - want_flags &= ~(PKG_SILENCE_ERRORS); + state.want_flags &= ~(PKG_SILENCE_ERRORS); if (getenv("PKG_CONFIG_DONT_RELOCATE_PATHS")) - want_flags |= (PKG_DONT_RELOCATE_PATHS); + state.want_flags |= (PKG_DONT_RELOCATE_PATHS); - if ((want_flags & PKG_VALIDATE) == PKG_VALIDATE || (want_flags & PKG_DEBUG) == PKG_DEBUG) - pkgconf_client_set_warn_handler(&pkg_client, error_handler, NULL); + if ((state.want_flags & PKG_VALIDATE) == PKG_VALIDATE || (state.want_flags & PKG_DEBUG) == PKG_DEBUG) + pkgconf_client_set_warn_handler(&state.pkg_client, error_handler, NULL); #ifndef PKGCONF_LITE - if ((want_flags & PKG_DEBUG) == PKG_DEBUG) - pkgconf_client_set_trace_handler(&pkg_client, error_handler, NULL); + if ((state.want_flags & PKG_DEBUG) == PKG_DEBUG) + pkgconf_client_set_trace_handler(&state.pkg_client, error_handler, NULL); #endif - pkgconf_path_prepend_list(&pkg_client.dir_list, &dir_list); + pkgconf_path_prepend_list(&state.pkg_client.dir_list, &dir_list); pkgconf_path_free(&dir_list); - if ((want_flags & PKG_ABOUT) == PKG_ABOUT) + if ((state.want_flags & PKG_ABOUT) == PKG_ABOUT) { about(); @@ -1321,7 +477,7 @@ main(int argc, char *argv[]) goto out; } - if ((want_flags & PKG_VERSION) == PKG_VERSION) + if ((state.want_flags & PKG_VERSION) == PKG_VERSION) { version(); @@ -1329,7 +485,7 @@ main(int argc, char *argv[]) goto out; } - if ((want_flags & PKG_HELP) == PKG_HELP) + if ((state.want_flags & PKG_HELP) == PKG_HELP) { usage(); @@ -1337,148 +493,6 @@ main(int argc, char *argv[]) goto out; } - if (getenv("PKG_CONFIG_FDO_SYSROOT_RULES")) - want_client_flags |= PKGCONF_PKG_PKGF_FDO_SYSROOT_RULES; - - if (getenv("PKG_CONFIG_PKGCONF1_SYSROOT_RULES")) - want_client_flags |= PKGCONF_PKG_PKGF_PKGCONF1_SYSROOT_RULES; - - if ((want_flags & PKG_SHORT_ERRORS) == PKG_SHORT_ERRORS) - want_client_flags |= PKGCONF_PKG_PKGF_SIMPLIFY_ERRORS; - - if ((want_flags & PKG_DONT_RELOCATE_PATHS) == PKG_DONT_RELOCATE_PATHS) - want_client_flags |= PKGCONF_PKG_PKGF_DONT_RELOCATE_PATHS; - - error_msgout = stderr; - if ((want_flags & PKG_ERRORS_ON_STDOUT) == PKG_ERRORS_ON_STDOUT) - error_msgout = stdout; - if ((want_flags & PKG_SILENCE_ERRORS) == PKG_SILENCE_ERRORS) { - error_msgout = fopen(PATH_DEV_NULL, "w"); - opened_error_msgout = true; - } - - if ((want_flags & PKG_IGNORE_CONFLICTS) == PKG_IGNORE_CONFLICTS || getenv("PKG_CONFIG_IGNORE_CONFLICTS") != NULL) - want_client_flags |= PKGCONF_PKG_PKGF_SKIP_CONFLICTS; - - if ((want_flags & PKG_STATIC) == PKG_STATIC || personality->want_default_static) - want_client_flags |= (PKGCONF_PKG_PKGF_SEARCH_PRIVATE | PKGCONF_PKG_PKGF_MERGE_PRIVATE_FRAGMENTS); - - if ((want_flags & PKG_SHARED) == PKG_SHARED) - want_client_flags &= ~(PKGCONF_PKG_PKGF_SEARCH_PRIVATE | PKGCONF_PKG_PKGF_MERGE_PRIVATE_FRAGMENTS); - - /* if --static and --pure are both specified, then disable merge-back. - * this allows for a --static which searches private modules, but has the same fragment behaviour as if - * --static were disabled. see <https://github.com/pkgconf/pkgconf/issues/83> for rationale. - */ - if ((want_flags & PKG_PURE) == PKG_PURE || getenv("PKG_CONFIG_PURE_DEPGRAPH") != NULL || personality->want_default_pure) - want_client_flags &= ~PKGCONF_PKG_PKGF_MERGE_PRIVATE_FRAGMENTS; - - if ((want_flags & PKG_ENV_ONLY) == PKG_ENV_ONLY) - want_client_flags |= PKGCONF_PKG_PKGF_ENV_ONLY; - - if ((want_flags & PKG_NO_CACHE) == PKG_NO_CACHE) - want_client_flags |= PKGCONF_PKG_PKGF_NO_CACHE; - -/* On Windows we want to always redefine the prefix by default - * but allow that behavior to be manually disabled */ -#if !defined(_WIN32) && !defined(_WIN64) - if ((want_flags & PKG_DEFINE_PREFIX) == PKG_DEFINE_PREFIX || getenv("PKG_CONFIG_RELOCATE_PATHS") != NULL) -#endif - want_client_flags |= PKGCONF_PKG_PKGF_REDEFINE_PREFIX; - - if ((want_flags & PKG_NO_UNINSTALLED) == PKG_NO_UNINSTALLED || getenv("PKG_CONFIG_DISABLE_UNINSTALLED") != NULL) - want_client_flags |= PKGCONF_PKG_PKGF_NO_UNINSTALLED; - - if ((want_flags & PKG_NO_PROVIDES) == PKG_NO_PROVIDES) - want_client_flags |= PKGCONF_PKG_PKGF_SKIP_PROVIDES; - - if ((want_flags & PKG_DONT_DEFINE_PREFIX) == PKG_DONT_DEFINE_PREFIX || getenv("PKG_CONFIG_DONT_DEFINE_PREFIX") != NULL) - want_client_flags &= ~PKGCONF_PKG_PKGF_REDEFINE_PREFIX; - - if ((want_flags & PKG_INTERNAL_CFLAGS) == PKG_INTERNAL_CFLAGS) - want_client_flags |= PKGCONF_PKG_PKGF_DONT_FILTER_INTERNAL_CFLAGS; - - /* if these selectors are used, it means that we are querying metadata. - * so signal to libpkgconf that we only want to walk the flattened dependency set. - */ - if ((want_flags & PKG_MODVERSION) == PKG_MODVERSION || - (want_flags & PKG_REQUIRES) == PKG_REQUIRES || - (want_flags & PKG_REQUIRES_PRIVATE) == PKG_REQUIRES_PRIVATE || - (want_flags & PKG_PROVIDES) == PKG_PROVIDES || - (want_flags & PKG_VARIABLES) == PKG_VARIABLES || - (want_flags & PKG_PATH) == PKG_PATH || - want_variable != NULL) - maximum_traverse_depth = 1; - - /* if we are asking for a variable, path or list of variables, this only makes sense - * for a single package. - */ - if ((want_flags & PKG_VARIABLES) == PKG_VARIABLES || - (want_flags & PKG_PATH) == PKG_PATH || - want_variable != NULL) - maximum_package_count = 1; - - if (getenv("PKG_CONFIG_ALLOW_SYSTEM_CFLAGS") != NULL) - want_flags |= PKG_KEEP_SYSTEM_CFLAGS; - - if (getenv("PKG_CONFIG_ALLOW_SYSTEM_LIBS") != NULL) - want_flags |= PKG_KEEP_SYSTEM_LIBS; - - if ((builddir = getenv("PKG_CONFIG_TOP_BUILD_DIR")) != NULL) - pkgconf_client_set_buildroot_dir(&pkg_client, builddir); - - if ((want_flags & PKG_REQUIRES_PRIVATE) == PKG_REQUIRES_PRIVATE || - (want_flags & PKG_CFLAGS)) - { - want_client_flags |= PKGCONF_PKG_PKGF_SEARCH_PRIVATE; - } - - if ((sysroot_dir = getenv("PKG_CONFIG_SYSROOT_DIR")) != NULL) - { - const char *destdir; - - pkgconf_client_set_sysroot_dir(&pkg_client, sysroot_dir); - - if ((destdir = getenv("DESTDIR")) != NULL) - { - if (!strcmp(destdir, sysroot_dir)) - want_client_flags |= PKGCONF_PKG_PKGF_FDO_SYSROOT_RULES; - } - } - - /* we have determined what features we want most likely. in some cases, we override later. */ - pkgconf_client_set_flags(&pkg_client, want_client_flags); - - /* at this point, want_client_flags should be set, so build the dir list */ - pkgconf_client_dir_list_build(&pkg_client, personality); - - /* preload any files in PKG_CONFIG_PRELOADED_FILES */ - pkgconf_client_preload_from_environ(&pkg_client, "PKG_CONFIG_PRELOADED_FILES"); - - if (required_pkgconfig_version != NULL) - { - if (pkgconf_compare_version(PACKAGE_VERSION, required_pkgconfig_version) >= 0) - ret = EXIT_SUCCESS; - else - ret = EXIT_FAILURE; - - goto out; - } - - if ((want_flags & PKG_LIST) == PKG_LIST) - { - pkgconf_scan_all(&pkg_client, NULL, print_list_entry); - ret = EXIT_SUCCESS; - goto out; - } - - if ((want_flags & PKG_LIST_PACKAGE_NAMES) == PKG_LIST_PACKAGE_NAMES) - { - pkgconf_scan_all(&pkg_client, NULL, print_package_entry); - ret = EXIT_SUCCESS; - goto out; - } - if (logfile_arg == NULL) logfile_arg = getenv("PKG_CONFIG_LOG"); @@ -1486,338 +500,23 @@ main(int argc, char *argv[]) { if (pkgconf_unveil(logfile_arg, "rwc") == -1) { - fprintf(stderr, "pkgconf: unveil failed: %s\n", strerror(errno)); + pkgconf_output_file_fmt(stderr, "pkgconf: unveil failed: %s\n", strerror(errno)); return EXIT_FAILURE; } - logfile_out = fopen(logfile_arg, "w"); - pkgconf_audit_set_log(&pkg_client, logfile_out); - } - - if (required_module_version != NULL) - { - pkgconf_pkg_t *pkg = NULL; - pkgconf_node_t *node; - pkgconf_list_t deplist = PKGCONF_LIST_INITIALIZER; - - while (argv[pkg_optind]) - { - pkgconf_dependency_parse_str(&pkg_client, &deplist, argv[pkg_optind], 0); - pkg_optind++; - } - - PKGCONF_FOREACH_LIST_ENTRY(deplist.head, node) - { - pkgconf_dependency_t *pkgiter = node->data; - - pkg = pkgconf_pkg_find(&pkg_client, pkgiter->package); - if (pkg == NULL) - { - if (want_flags & PKG_PRINT_ERRORS) - pkgconf_error(&pkg_client, "Package '%s' was not found\n", pkgiter->package); - - ret = EXIT_FAILURE; - goto cleanup; - } - - if (pkgconf_compare_version(pkg->version, required_module_version) >= 0) - { - ret = EXIT_SUCCESS; - goto cleanup; - } - } - - ret = EXIT_FAILURE; -cleanup: - if (pkg != NULL) - pkgconf_pkg_unref(&pkg_client, pkg); - pkgconf_dependency_free(&deplist); - goto out; - } - else if (required_exact_module_version != NULL) - { - pkgconf_pkg_t *pkg = NULL; - pkgconf_node_t *node; - pkgconf_list_t deplist = PKGCONF_LIST_INITIALIZER; - - while (argv[pkg_optind]) - { - pkgconf_dependency_parse_str(&pkg_client, &deplist, argv[pkg_optind], 0); - pkg_optind++; - } - - PKGCONF_FOREACH_LIST_ENTRY(deplist.head, node) - { - pkgconf_dependency_t *pkgiter = node->data; - - pkg = pkgconf_pkg_find(&pkg_client, pkgiter->package); - if (pkg == NULL) - { - if (want_flags & PKG_PRINT_ERRORS) - pkgconf_error(&pkg_client, "Package '%s' was not found\n", pkgiter->package); - - ret = EXIT_FAILURE; - goto cleanup2; - } - - if (pkgconf_compare_version(pkg->version, required_exact_module_version) == 0) - { - ret = EXIT_SUCCESS; - goto cleanup2; - } - } - - ret = EXIT_FAILURE; -cleanup2: - if (pkg != NULL) - pkgconf_pkg_unref(&pkg_client, pkg); - pkgconf_dependency_free(&deplist); - goto out; - } - else if (required_max_module_version != NULL) - { - pkgconf_pkg_t *pkg = NULL; - pkgconf_node_t *node; - pkgconf_list_t deplist = PKGCONF_LIST_INITIALIZER; - - while (argv[pkg_optind]) - { - pkgconf_dependency_parse_str(&pkg_client, &deplist, argv[pkg_optind], 0); - pkg_optind++; - } - - PKGCONF_FOREACH_LIST_ENTRY(deplist.head, node) - { - pkgconf_dependency_t *pkgiter = node->data; - - pkg = pkgconf_pkg_find(&pkg_client, pkgiter->package); - if (pkg == NULL) - { - if (want_flags & PKG_PRINT_ERRORS) - pkgconf_error(&pkg_client, "Package '%s' was not found\n", pkgiter->package); - - ret = EXIT_FAILURE; - goto cleanup3; - } - - if (pkgconf_compare_version(pkg->version, required_max_module_version) <= 0) - { - ret = EXIT_SUCCESS; - goto cleanup3; - } - } - - ret = EXIT_FAILURE; -cleanup3: - if (pkg != NULL) - pkgconf_pkg_unref(&pkg_client, pkg); - pkgconf_dependency_free(&deplist); - goto out; - } - - while (1) - { - char *package = argv[pkg_optind]; - char *end; - - if (package == NULL) - break; - - /* check if there is a limit to the number of packages allowed to be included, if so and we have hit - * the limit, stop adding packages to the queue. - */ - if (maximum_package_count > 0 && pkgq.length >= maximum_package_count) - break; - - while (isspace((unsigned char)package[0])) - package++; - - /* skip empty packages */ - if (package[0] == '\0') { - pkg_optind++; - continue; - } - - end = package + strlen(package) - 1; - while(end > package && isspace((unsigned char)end[0])) end--; - end[1] = '\0'; - - if (argv[pkg_optind + 1] == NULL || !PKGCONF_IS_OPERATOR_CHAR(*(argv[pkg_optind + 1]))) - { - pkgconf_queue_push(&pkgq, package); - pkg_optind++; - } - else if (argv[pkg_optind + 2] == NULL) - { - char packagebuf[PKGCONF_BUFSIZE]; - - snprintf(packagebuf, sizeof packagebuf, "%s %s", package, argv[pkg_optind + 1]); - pkg_optind += 2; - - pkgconf_queue_push(&pkgq, packagebuf); - } - else - { - char packagebuf[PKGCONF_BUFSIZE]; - - snprintf(packagebuf, sizeof packagebuf, "%s %s %s", package, argv[pkg_optind + 1], argv[pkg_optind + 2]); - pkg_optind += 3; - - pkgconf_queue_push(&pkgq, packagebuf); - } - } - - if (pkgq.head == NULL) - { - fprintf(stderr, "Please specify at least one package name on the command line.\n"); - ret = EXIT_FAILURE; - goto out; - } - - ret = EXIT_SUCCESS; - - if (!pkgconf_queue_solve(&pkg_client, &pkgq, &world, maximum_traverse_depth)) - { - ret = EXIT_FAILURE; - goto out; - } - - /* we shouldn't need to unveil any more filesystem accesses from this point, so lock it down */ - if (pkgconf_unveil(NULL, NULL) == -1) - { - fprintf(stderr, "pkgconf: unveil lockdown failed: %s\n", strerror(errno)); - return EXIT_FAILURE; - } - -#ifndef PKGCONF_LITE - if ((want_flags & PKG_SIMULATE) == PKG_SIMULATE) - { - want_flags &= ~(PKG_CFLAGS|PKG_LIBS); - - pkgconf_client_set_flags(&pkg_client, want_client_flags | PKGCONF_PKG_PKGF_SKIP_ERRORS); - apply_simulate(&pkg_client, &world, NULL, -1); + state.logfile_out = fopen(logfile_arg, "a"); + pkgconf_audit_set_log(&state.pkg_client, state.logfile_out); } -#endif - - if ((want_flags & PKG_VALIDATE) == PKG_VALIDATE) - goto out; - if ((want_flags & PKG_DUMP_LICENSE) == PKG_DUMP_LICENSE) - { - apply_license(&pkg_client, &world, &ret, 2); - goto out; - } - - if ((want_flags & PKG_UNINSTALLED) == PKG_UNINSTALLED) - { - ret = EXIT_FAILURE; - apply_uninstalled(&pkg_client, &world, &ret, 2); - goto out; - } - - if (want_env_prefix != NULL) - { - apply_env(&pkg_client, &world, want_env_prefix, 2); - goto out; - } - - if ((want_flags & PKG_PROVIDES) == PKG_PROVIDES) - { - want_flags &= ~(PKG_CFLAGS|PKG_LIBS); - apply_provides(&pkg_client, &world, NULL, 2); - } - -#ifndef PKGCONF_LITE - if ((want_flags & PKG_DIGRAPH) == PKG_DIGRAPH) - { - want_flags &= ~(PKG_CFLAGS|PKG_LIBS); - apply_digraph(&pkg_client, &world, &pkgq, 2); - } - - if ((want_flags & PKG_SOLUTION) == PKG_SOLUTION) - { - want_flags &= ~(PKG_CFLAGS|PKG_LIBS); - apply_print_solution(&pkg_client, &world, NULL, 2); - } -#endif - - if ((want_flags & PKG_MODVERSION) == PKG_MODVERSION) - { - want_flags &= ~(PKG_CFLAGS|PKG_LIBS); - apply_modversion(&pkg_client, &world, &pkgq, 2); - } - - if ((want_flags & PKG_PATH) == PKG_PATH) - { - want_flags &= ~(PKG_CFLAGS|PKG_LIBS); - - pkgconf_client_set_flags(&pkg_client, want_client_flags | PKGCONF_PKG_PKGF_SKIP_ROOT_VIRTUAL); - apply_path(&pkg_client, &world, NULL, 2); - } - - if ((want_flags & PKG_VARIABLES) == PKG_VARIABLES) - { - want_flags &= ~(PKG_CFLAGS|PKG_LIBS); - apply_variables(&pkg_client, &world, NULL, 2); - } - - if (want_variable) - { - want_flags &= ~(PKG_CFLAGS|PKG_LIBS); - - pkgconf_client_set_flags(&pkg_client, want_client_flags | PKGCONF_PKG_PKGF_SKIP_ROOT_VIRTUAL); - apply_variable(&pkg_client, &world, want_variable, 2); - } - - if ((want_flags & PKG_REQUIRES) == PKG_REQUIRES) - { - want_flags &= ~(PKG_CFLAGS|PKG_LIBS); - apply_requires(&pkg_client, &world, NULL, 2); - } - - if ((want_flags & PKG_REQUIRES_PRIVATE) == PKG_REQUIRES_PRIVATE) - { - want_flags &= ~(PKG_CFLAGS|PKG_LIBS); - - apply_requires_private(&pkg_client, &world, NULL, 2); - } - - if ((want_flags & PKG_FRAGMENT_TREE)) - { - want_flags &= ~(PKG_CFLAGS|PKG_LIBS); - - apply_fragment_tree(&pkg_client, &world, NULL, 2); - } - - if ((want_flags & PKG_CFLAGS)) - { - apply_cflags(&pkg_client, &world, NULL, 2); - } - - if ((want_flags & PKG_LIBS)) - { - if (want_flags & PKG_CFLAGS) - printf(" "); - - if (!(want_flags & PKG_STATIC)) - pkgconf_client_set_flags(&pkg_client, pkg_client.flags & ~PKGCONF_PKG_PKGF_SEARCH_PRIVATE); + if (getenv("PKG_CONFIG_ALLOW_SYSTEM_CFLAGS") != NULL) + state.want_flags |= PKG_KEEP_SYSTEM_CFLAGS; - apply_libs(&pkg_client, &world, NULL, 2); - } + if (getenv("PKG_CONFIG_ALLOW_SYSTEM_LIBS") != NULL) + state.want_flags |= PKG_KEEP_SYSTEM_LIBS; - if (want_flags & (PKG_CFLAGS|PKG_LIBS)) - printf("\n"); + return pkgconf_cli_run(&state, argc, argv, pkg_optind); out: - pkgconf_solution_free(&pkg_client, &world); - pkgconf_queue_free(&pkgq); - pkgconf_cross_personality_deinit(personality); - pkgconf_client_deinit(&pkg_client); - - if (logfile_out != NULL) - fclose(logfile_out); - if (opened_error_msgout) - fclose(error_msgout); - + pkgconf_cli_state_reset(&state); return ret; } diff --git a/cli/renderer-msvc.c b/cli/renderer-msvc.c index 8e31699bbca4..07c9ccf7f5b0 100644 --- a/cli/renderer-msvc.c +++ b/cli/renderer-msvc.c @@ -2,6 +2,8 @@ * renderer-msvc.c * MSVC library syntax renderer * + * SPDX-License-Identifier: pkgconf + * * Copyright (c) 2017 pkgconf authors (see AUTHORS). * * Permission to use, copy, modify, and/or distribute this software for any @@ -20,17 +22,14 @@ #include "renderer-msvc.h" static inline bool -fragment_should_quote(const pkgconf_fragment_t *frag) +should_quote(const pkgconf_buffer_t *buf) { const char *src; - if (frag->data == NULL) - return false; - - for (src = frag->data; *src; src++) + for (src = buf->base; *src && src < buf->end; src++) { if (((*src < ' ') || - (*src >= (' ' + (frag->children.head != NULL ? 1 : 0)) && *src < '$') || + (*src >= (' ') && *src < '$') || (*src > '$' && *src < '(') || (*src > ')' && *src < '+') || (*src > ':' && *src < '=') || @@ -45,127 +44,54 @@ fragment_should_quote(const pkgconf_fragment_t *frag) return false; } -static inline size_t -fragment_len(const pkgconf_fragment_t *frag) -{ - size_t len = 1; - - if (frag->type) - len += 2; - - if (frag->data != NULL) - { - len += strlen(frag->data); - - if (fragment_should_quote(frag)) - len += 2; - } - - return len; -} - static inline bool allowed_fragment(const pkgconf_fragment_t *frag) { return !(!frag->type || frag->data == NULL || strchr("DILl", frag->type) == NULL); } -static size_t -msvc_renderer_render_len(const pkgconf_list_t *list, bool escape) -{ - (void) escape; - - size_t out = 1; /* trailing nul */ - pkgconf_node_t *node; - - PKGCONF_FOREACH_LIST_ENTRY(list->head, node) - { - const pkgconf_fragment_t *frag = node->data; - - if (!allowed_fragment(frag)) - continue; - - switch (frag->type) - { - case 'L': - out += 9; /* "/libpath:" */ - break; - case 'l': - out += 4; /* ".lib" */ - break; - default: - break; - } - - out += fragment_len(frag); - } - - return out; -} - static void -msvc_renderer_render_buf(const pkgconf_list_t *list, char *buf, size_t buflen, bool escape) +msvc_renderer_render(const pkgconf_fragment_render_ctx_t *ctx, const pkgconf_fragment_t *frag, pkgconf_buffer_t *buf) { - pkgconf_node_t *node; - char *bptr = buf; - - memset(buf, 0, buflen); - - PKGCONF_FOREACH_LIST_ENTRY(list->head, node) - { - const pkgconf_fragment_t *frag = node->data; - size_t buf_remaining = buflen - (bptr - buf); - size_t cnt; + pkgconf_buffer_t tmpbuf = PKGCONF_BUFFER_INITIALIZER; + bool escape = ctx->escape; - if (!allowed_fragment(frag)) - continue; + if (!allowed_fragment(frag)) + return; - if (fragment_len(frag) > buf_remaining) - break; - - switch(frag->type) { - case 'D': - case 'I': - *bptr++ = '/'; - *bptr++ = frag->type; - break; - case 'L': - cnt = pkgconf_strlcpy(bptr, "/libpath:", buf_remaining); - bptr += cnt; - buf_remaining -= cnt; - break; - } + switch(frag->type) { + case 'D': + case 'I': + pkgconf_buffer_append_fmt(buf, "/%c", frag->type); + break; + case 'L': + pkgconf_buffer_append(buf, "/libpath:"); + break; + } - escape = fragment_should_quote(frag); + pkgconf_buffer_append(&tmpbuf, frag->data); - if (escape) - *bptr++ = '"'; + if (frag->type == 'l') + pkgconf_buffer_append(&tmpbuf, ".lib"); - cnt = pkgconf_strlcpy(bptr, frag->data, buf_remaining); - bptr += cnt; - buf_remaining -= cnt; + escape = should_quote(&tmpbuf); - if (frag->type == 'l') - { - cnt = pkgconf_strlcpy(bptr, ".lib", buf_remaining); - bptr += cnt; - } + if (escape) + pkgconf_buffer_push_byte(buf, '"'); - if (escape) - *bptr++ = '"'; + pkgconf_buffer_append(buf, pkgconf_buffer_str_or_empty(&tmpbuf)); - *bptr++ = ' '; - } + if (escape) + pkgconf_buffer_push_byte(buf, '"'); - *bptr = '\0'; + pkgconf_buffer_finalize(&tmpbuf); } -static const pkgconf_fragment_render_ops_t msvc_renderer_ops = { - .render_len = msvc_renderer_render_len, - .render_buf = msvc_renderer_render_buf +static pkgconf_fragment_render_ops_t msvc_renderer_ops = { + .render = msvc_renderer_render }; -const pkgconf_fragment_render_ops_t * +pkgconf_fragment_render_ops_t * msvc_renderer_get(void) { return &msvc_renderer_ops; diff --git a/cli/renderer-msvc.h b/cli/renderer-msvc.h index 2a095fdc36b4..6e9549902f54 100644 --- a/cli/renderer-msvc.h +++ b/cli/renderer-msvc.h @@ -2,6 +2,8 @@ * renderer-msvc.h * MSVC library syntax renderer header * + * SPDX-License-Identifier: pkgconf + * * Copyright (c) 2017 pkgconf authors (see AUTHORS). * * Permission to use, copy, modify, and/or distribute this software for any @@ -18,6 +20,6 @@ #include <libpkgconf/libpkgconf.h> -const pkgconf_fragment_render_ops_t *msvc_renderer_get(void); +pkgconf_fragment_render_ops_t *msvc_renderer_get(void); #endif diff --git a/cli/spdxtool/core.c b/cli/spdxtool/core.c new file mode 100644 index 000000000000..5cf7f10e81e1 --- /dev/null +++ b/cli/spdxtool/core.c @@ -0,0 +1,729 @@ +/* + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2025 The FreeBSD Foundation + * + * Portions of this software were developed by + * Tuukka Pasanen <tuukka.pasanen@ilmi.fi> under sponsorship from + * the FreeBSD Foundation + */ + +#include <stdlib.h> +#include <string.h> +#include "util.h" +#include "serialize.h" +#include "core.h" +#include "software.h" +#include "simplelicensing.h" + +/* + * !doc + * + * .. c:function:: spdxtool_core_agent_t *spdxtool_core_agent_new(pkgconf_client_t *client, const char *creation_info_id, const char *name) + * + * Create new /Core/Agent struct + * + * :param pkgconf_client_t *client: The pkgconf client being accessed. + * :param char *creation_info_id: CreationInfo spdxId + * :param char *name: Name of agent + * :return: NULL if some problem occurs and Agent struct if not + */ +spdxtool_core_agent_t * +spdxtool_core_agent_new(pkgconf_client_t *client, const char *creation_info_id, const char *name) +{ + if (!client || !creation_info_id || !name) + return NULL; + + spdxtool_core_agent_t *agent = calloc(1, sizeof(spdxtool_core_agent_t)); + if (!agent) + goto err; + + agent->type = "Agent"; + + char *spdx_id_name = strdup(name); + if (!spdx_id_name) + goto err; + + spdxtool_util_string_correction(spdx_id_name); + + agent->spdx_id = spdxtool_util_get_spdx_id_string(client, agent->type, spdx_id_name); + free(spdx_id_name); + + agent->creation_info = strdup(creation_info_id); + agent->name = strdup(name); + + if (!agent->spdx_id || !agent->creation_info || !agent->name) + goto err; + + return agent; + +err: + pkgconf_error(client, "spdxtool_core_agent_new: out of memory"); + spdxtool_core_agent_free(agent); + return NULL; +} + +/* + * !doc + * + * .. c:function:: void spdxtool_core_agent_free(spdxtool_core_agent_t *agent) + * + * Free /Core/Agent struct + * + * :param spdxtool_core_agent_t *agent: Agent struct to be freed. + * :return: nothing + */ +void +spdxtool_core_agent_free(spdxtool_core_agent_t *agent) +{ + if (!agent) + return; + + free(agent->creation_info); + free(agent->spdx_id); + free(agent->name); + + free(agent); +} + +/* + * !doc + * + * .. c:function:: spdxtool_serialize_value_t *spdxtool_core_agent_to_object(pkgconf_client_t *client, const spdxtool_core_agent_t *agent) + * + * Serialize /Core/Agent struct to a JSON value tree. + * + * :param pkgconf_client_t *client: The pkgconf client being accessed. + * :param const spdxtool_core_agent_t *agent: Agent struct to be serialized. + * :return: spdxtool_serialize_value_t * representing the Agent object. + */ +spdxtool_serialize_value_t * +spdxtool_core_agent_to_object(pkgconf_client_t *client, const spdxtool_core_agent_t *agent) +{ + spdxtool_serialize_value_t *ret = NULL; + spdxtool_serialize_object_list_t *object_list = spdxtool_serialize_object_list_new(); + if (!object_list) + goto err; + + if (!(spdxtool_serialize_object_add_string(object_list, "type", agent->type) && + spdxtool_serialize_object_add_string(object_list, "creationInfo", agent->creation_info) && + spdxtool_serialize_object_add_string(object_list, "spdxId", agent->spdx_id) && + spdxtool_serialize_object_add_string(object_list, "name", agent->name))) + { + goto err; + } + + ret = spdxtool_serialize_value_object(object_list); + object_list = NULL; + +err: + if (!ret) + pkgconf_error(client, "spdxtool_core_agent_to_object: out of memory"); + + spdxtool_serialize_object_list_free(object_list); + return ret; +} + +/* + * !doc + * + * .. c:function:: spdxtool_core_creation_info_t *spdxtool_core_creation_info_new(pkgconf_client_t *client, const char *agent_id, const char *id, const char *time) + * + * Create new /Core/CreationInfo struct + * + * :param pkgconf_client_t *client: The pkgconf client being accessed. + * :param const char *agent_id: Agent spdxId + * :param const char *id: Id for creation info + * :param const char *time: If NULL current time is used if not then + * this time string is used. Time string should be + * in ISO8601 format: YYYY-MM-DDTHH:MM:SSZ + * :return: NULL if some problem occurs and CreationInfo struct if not + */ +spdxtool_core_creation_info_t * +spdxtool_core_creation_info_new(pkgconf_client_t *client, const char *agent_id, const char *id, const char *time) +{ + if (!client || !agent_id || !id) + return NULL; + + spdxtool_core_creation_info_t *creation = calloc(1, sizeof(spdxtool_core_creation_info_t)); + if (!creation) + goto err; + + creation->type = "CreationInfo"; + creation->created_using = "pkgconf spdxtool"; + creation->id = strdup(id); + creation->created = time ? strdup(time) : spdxtool_util_get_current_iso8601_time(); + creation->created_by = strdup(agent_id); + creation->spec_version = strdup(spdxtool_util_get_spdx_version(client)); + + if (!creation->id || !creation->created || !creation->created_by || !creation->spec_version) + goto err; + + return creation; + +err: + pkgconf_error(client, "spdxtool_core_creation_info_new: out of memory"); + spdxtool_core_creation_info_free(creation); + return NULL; +} + +/* + * !doc + * + * .. c:function:: void spdxtool_core_creation_info_free(spdxtool_core_creation_info_t *creation) + * + * Free /Core/CreationInfo struct + * + * :param spdxtool_core_creation_info_t *creation: CreationInfo struct to be freed. + * :return: nothing + */ +void +spdxtool_core_creation_info_free(spdxtool_core_creation_info_t *creation) +{ + if (!creation) + return; + + free(creation->id); + free(creation->created); + free(creation->created_by); + free(creation->spec_version); + + free(creation); +} + +/* + * !doc + * + * .. c:function:: spdxtool_serialize_value_t *spdxtool_core_creation_info_to_object(pkgconf_client_t *client, const spdxtool_core_creation_info_t *creation) + * + * Serialize /Core/CreationInfo struct to a JSON value tree. + * + * :param pkgconf_client_t *client: The pkgconf client being accessed. + * :param const spdxtool_core_creation_info_t *creation: CreationInfo struct to be serialized. + * :return: spdxtool_serialize_value_t * representing the CreationInfo object. + */ +spdxtool_serialize_value_t * +spdxtool_core_creation_info_to_object(pkgconf_client_t *client, const spdxtool_core_creation_info_t *creation) +{ + spdxtool_serialize_value_t *ret = NULL; + spdxtool_serialize_object_list_t *object_list = spdxtool_serialize_object_list_new(); + if (!object_list) + goto err; + + spdxtool_serialize_array_t *created_by = spdxtool_serialize_array_new(); + if (!created_by) + goto err; + + if (!spdxtool_serialize_array_add_string(created_by, creation->created_by)) + { + spdxtool_serialize_array_free(created_by); + goto err; + } + + if (!(spdxtool_serialize_object_add_string(object_list, "type", creation->type) && + spdxtool_serialize_object_add_string(object_list, "@id", creation->id) && + spdxtool_serialize_object_add_string(object_list, "created", creation->created) && + spdxtool_serialize_object_add_array(object_list, "createdBy", created_by) && + spdxtool_serialize_object_add_string(object_list, "specVersion", creation->spec_version))) + { + goto err; + } + + ret = spdxtool_serialize_value_object(object_list); + object_list = NULL; + +err: + if (!ret) + pkgconf_error(client, "spdxtool_core_creation_info_to_object: out of memory"); + + spdxtool_serialize_object_list_free(object_list); + return ret; +} + +/* + * !doc + * + * .. c:function:: spdxtool_core_creation_info_t *spdxtool_core_creation_info_new(pkgconf_client_t *client, const char *spdx_id, const char *creation_id, const char *agent) + * + * Create new /Core/SpdxDocument struct + * In SPDX Lite SBOM there can be only one SpdxDocument + * + * :param pkgconf_client_t *client: The pkgconf client being accessed. + * :param const char *spdx_id: Id of this SpdxDocument + * :param const char *creation_id: Id for creation info + * :param const char *agent_id: Agent for this document + * :return: NULL if some problem occurs and SpdxDocument struct if not + */ +spdxtool_core_spdx_document_t * +spdxtool_core_spdx_document_new(pkgconf_client_t *client, const char *spdx_id, const char *creation_id, const char *agent_id) +{ + if (!client || !spdx_id || !creation_id || !agent_id) + return NULL; + + spdxtool_core_spdx_document_t *spdx = calloc(1, sizeof(spdxtool_core_spdx_document_t)); + if (!spdx) + goto err; + + spdx->type = "SpdxDocument"; + spdx->spdx_id = strdup(spdx_id); + spdx->agent = strdup(agent_id); + spdx->creation_info = strdup(creation_id); + + if (!spdx->spdx_id || !spdx->agent || !spdx->creation_info) + goto err; + + return spdx; + +err: + pkgconf_error(client, "spdxtool_core_spdx_document_new: out of memory"); + spdxtool_core_spdx_document_free(spdx); + return NULL; +} + +/* + * !doc + * + * .. c:function:: void spdxtool_core_spdx_document_free(spdxtool_core_spdx_document_t *spdx) + * + * Free /Core/SpdxDocument struct + * + * :param spdxtool_core_spdx_document_t *spdx: SpdxDocument struct to be freed. + * :return: nothing + */ +void +spdxtool_core_spdx_document_free(spdxtool_core_spdx_document_t *spdx) +{ + pkgconf_node_t *iter = NULL, *iter_next = NULL; + + if (!spdx) + return; + + free(spdx->spdx_id); + free(spdx->creation_info); + free(spdx->agent); + + PKGCONF_FOREACH_LIST_ENTRY_SAFE(spdx->rootElement.head, iter_next, iter) + { + spdxtool_software_sbom_t *sbom = iter->data; + spdxtool_software_sbom_free(sbom); + free(iter); + } + + PKGCONF_FOREACH_LIST_ENTRY_SAFE(spdx->element.head, iter_next, iter) + { + free(iter->data); + free(iter); + } + + PKGCONF_FOREACH_LIST_ENTRY_SAFE(spdx->licenses.head, iter_next, iter) + { + spdxtool_simplelicensing_license_expression_t *expression = iter->data; + spdxtool_simplelicensing_licenseExpression_free(expression); + free(iter); + } + + PKGCONF_FOREACH_LIST_ENTRY_SAFE(spdx->relationships.head, iter_next, iter) + { + spdxtool_core_relationship_t *relationship = iter->data; + spdxtool_core_relationship_free(relationship); + free(iter); + } + + PKGCONF_FOREACH_LIST_ENTRY_SAFE(spdx->packages.head, iter_next, iter) + { + free(iter); + } + + free(spdx); +} + +/* + * !doc + * + * .. c:function:: spdxtool_serialize_value_t *spdxtool_core_spdx_document_to_object(pkgconf_client_t *client, spdxtool_core_spdx_document_t *spdx) + * + * Serialize /Core/SpdxDocument struct to a JSON value tree. This function + * should be called after all SBOMs and packages have been serialized so that + * the document's element and rootElement lists are fully populated. + * + * :param pkgconf_client_t *client: The pkgconf client being accessed. + * :param spdxtool_core_spdx_document_t *spdx: SpdxDocument struct to be serialized. + * :return: spdxtool_serialize_value_t * representing the SpdxDocument object. + */ +spdxtool_serialize_value_t * +spdxtool_core_spdx_document_to_object(pkgconf_client_t *client, spdxtool_core_spdx_document_t *spdx) +{ + spdxtool_serialize_value_t *ret = NULL; + spdxtool_serialize_object_list_t *object_list = NULL; + spdxtool_serialize_array_t *root_element_array = NULL; + spdxtool_serialize_array_t *element_array = NULL; + + object_list = spdxtool_serialize_object_list_new(); + if (!object_list) + goto err; + + root_element_array = spdxtool_serialize_array_new(); + if (!root_element_array) + goto err; + + pkgconf_node_t *iter = NULL; + PKGCONF_FOREACH_LIST_ENTRY(spdx->rootElement.head, iter) + { + spdxtool_software_sbom_t *sbom = iter->data; + if (!spdxtool_serialize_array_add_string(root_element_array, sbom->spdx_id)) + goto err; + } + + element_array = spdxtool_serialize_array_new(); + if (!element_array) + goto err; + + if (!spdxtool_serialize_array_add_string(element_array, spdx->agent)) + goto err; + + PKGCONF_FOREACH_LIST_ENTRY(spdx->element.head, iter) + { + char *element_id = iter->data; + if (!spdxtool_serialize_array_add_string(element_array, element_id)) + goto err; + } + + PKGCONF_FOREACH_LIST_ENTRY(spdx->rootElement.head, iter) + { + spdxtool_software_sbom_t *sbom = iter->data; + char *pkg_spdx_id = spdxtool_util_tuple_lookup(client, &sbom->rootElement->vars, "spdxId"); + if (!pkg_spdx_id) + goto err; + + bool ok = spdxtool_serialize_array_add_string(element_array, sbom->spdx_id) && + spdxtool_serialize_array_add_string(element_array, pkg_spdx_id); + free(pkg_spdx_id); + + if (!ok) + goto err; + } + + if (!(spdxtool_serialize_object_add_string(object_list, "type", spdx->type) && + spdxtool_serialize_object_add_string(object_list, "creationInfo", spdx->creation_info) && + spdxtool_serialize_object_add_string(object_list, "spdxId", spdx->spdx_id) && + spdxtool_serialize_object_add_array(object_list, "rootElement", root_element_array) && + spdxtool_serialize_object_add_array(object_list, "element", element_array))) + { + goto err; + } + + root_element_array = NULL; + element_array = NULL; + + ret = spdxtool_serialize_value_object(object_list); + object_list = NULL; + +err: + if (!ret) + pkgconf_error(client, "spdxtool_core_spdx_document_to_object: out of memory"); + + spdxtool_serialize_object_list_free(object_list); + spdxtool_serialize_array_free(root_element_array); + spdxtool_serialize_array_free(element_array); + return ret; +} + +/* + * !doc + * + * .. c:function:: bool spdxtool_core_spdx_document_is_license(pkgconf_client_t *client, spdxtool_core_spdx_document_t *spdx, const char *license) + * + * Find out if specific license is already there. + * + * :param pkgconf_client_t *client: The pkgconf client being accessed. + * :param spdxtool_core_spdx_document_t *spdx: SpdxDocument struct being used. + * :param const char *license: SPDX name of license + * :return: true is license is there and false if not + */ +bool +spdxtool_core_spdx_document_is_license(pkgconf_client_t *client, const spdxtool_core_spdx_document_t *spdx, const char *license) +{ + pkgconf_node_t *iter = NULL; + spdxtool_simplelicensing_license_expression_t *expression = NULL; + + (void) client; + + if (!license || !spdx) + { + return false; + } + + PKGCONF_FOREACH_LIST_ENTRY(spdx->licenses.head, iter) + { + expression = iter->data; + if (!strcmp(expression->license_expression, license)) + { + return true; + } + } + + return false; +} + +/* + * !doc + * + * .. c:function:: bool spdxtool_core_spdx_document_add_license(pkgconf_client_t *client, spdxtool_core_spdx_document_t *spdx, const char *license) + * + * Add license to SpdxDocument and make sure that specific license is not already there. + * + * :param pkgconf_client_t *client: The pkgconf client being accessed. + * :param spdxtool_core_spdx_document_t *spdx: SpdxDocument struct being used. + * :param const char *license: SPDX name of license + * :return: true on success, false on failure + */ +bool +spdxtool_core_spdx_document_add_license(pkgconf_client_t *client, spdxtool_core_spdx_document_t *spdx, const char *license) +{ + if (!license || !spdx) + return false; + + if (spdxtool_core_spdx_document_is_license(client, spdx, license)) + return true; + + pkgconf_node_t *node = calloc(1, sizeof(pkgconf_node_t)); + if (!node) + { + pkgconf_error(client, "spdxtool_core_spdx_document_add_license: out of memory"); + return false; + } + + spdxtool_simplelicensing_license_expression_t *expression = spdxtool_simplelicensing_licenseExpression_new(client, license); + if (!expression) + { + free(node); + return false; + } + + pkgconf_node_insert_tail(node, expression, &spdx->licenses); + if (!spdxtool_core_spdx_document_add_element(client, spdx, expression->spdx_id)) + return false; + + return true; +} + +/* + * !doc + * + * .. c:function:: bool spdxtool_core_spdx_document_add_element(pkgconf_client_t *client, spdxtool_core_spdx_document_t *spdx, const char *element) + * + * Add element spdxId to SpdxDocument + * + * :param pkgconf_client_t *client: The pkgconf client being accessed. + * :param spdxtool_core_spdx_document_t *spdx: SpdxDocument struct being used. + * :param char *element: spdxId of element + * :return: true on success, false on failure + */ +bool +spdxtool_core_spdx_document_add_element(pkgconf_client_t *client, spdxtool_core_spdx_document_t *spdx, const char *element) +{ + if (!element || !spdx) + return false; + + pkgconf_node_t *node = calloc(1, sizeof(pkgconf_node_t)); + if (!node) + { + pkgconf_error(client, "spdxtool_core_spdx_document_add_element: out of memory"); + return false; + } + + char *nelement = strdup(element); + if (!nelement) + { + pkgconf_error(client, "spdxtool_core_spdx_document_add_element: out of memory"); + free(node); + return false; + } + + pkgconf_node_insert_tail(node, nelement, &spdx->element); + return true; +} + +/* + * !doc + * + * .. c:function:: bool spdxtool_core_spdx_document_add_relationship(pkgconf_client_t *client, spdxtool_core_spdx_document_t *spdx, spdxtool_core_relationship_t *relationship) + * + * Add relationship rel to SpdxDocument + * + * :param pkgconf_client_t *client: The pkgconf client being accessed. + * :param spdxtool_core_spdx_document_t *spdx: SpdxDocument struct being used. + * :param spdxtool_core_relationship_t *relationship: relationship to add. + * :return: true on success, false on failure + */ +bool +spdxtool_core_spdx_document_add_relationship(pkgconf_client_t *client, spdxtool_core_spdx_document_t *spdx, spdxtool_core_relationship_t *relationship) +{ + if (!client || !spdx || !relationship) + return false; + + pkgconf_node_t *node = calloc(1, sizeof(pkgconf_node_t)); + if (!node) + { + pkgconf_error(client, "spdxtool_core_spdx_document_add_relationship: out of memory"); + return false; + } + + pkgconf_node_insert_tail(node, relationship, &spdx->relationships); + return true; +} + +/* + * !doc + * + * .. c:function:: bool spdxtool_core_spdx_document_add_package(pkgconf_client_t *client, spdxtool_core_spdx_document_t *spdx, pkgconf_pkg_t *pkg) + * + * Register a package with the SpdxDocument for later serialization. The document + * does not take ownership of the package pointer; the package must outlive the + * document and will not be freed by spdxtool_core_spdx_document_free. + * + * :param pkgconf_client_t *client: The pkgconf client being accessed. + * :param spdxtool_core_spdx_document_t *spdx: SpdxDocument struct to register the package with. + * :param pkgconf_pkg_t *pkg: Package to register. Ownership is NOT transferred. + * :return: true on success, false on failure + */ +bool +spdxtool_core_spdx_document_add_package(pkgconf_client_t *client, spdxtool_core_spdx_document_t *spdx, pkgconf_pkg_t *pkg) +{ + if (!client || !spdx || !pkg) + return false; + + pkgconf_node_t *node = calloc(1, sizeof(pkgconf_node_t)); + if (!node) + { + pkgconf_error(client, "spdxtool_core_spdx_document_add_package: out of memory"); + return false; + } + + pkgconf_node_insert_tail(node, pkg, &spdx->packages); + return true; +} + +/* + * !doc + * + * .. c:function:: spdxtool_core_relationship_t *spdxtool_core_relationship_new(pkgconf_client_t *client, const char *creation_info_id, const char *spdx_id, const char *from, const char *to, const char *relationship_type) + * + * Create new /Core/Relationship struct + * + * :param pkgconf_client_t *client: The pkgconf client being accessed. + * :param const char *creation_id: Id for creation info + * :param const char *spdx_id: Id of this SpdxDocument + * :param const char *from: from spdxId + * :param const char *to: to spdxId + * :param const char *relationship_type: These can be found on SPDX documentation + * :return: NULL if some problem occurs and SpdxDocument struct if not + */ +spdxtool_core_relationship_t * +spdxtool_core_relationship_new(pkgconf_client_t *client, const char *creation_info_id, const char *spdx_id, const char *from, pkgconf_list_t *to, const char *relationship_type) +{ + if (!client || !creation_info_id || !spdx_id || !from || !to || !relationship_type) + return NULL; + + spdxtool_core_relationship_t *relationship = calloc(1, sizeof(spdxtool_core_relationship_t)); + if (!relationship) + goto err; + + relationship->type = "Relationship"; + relationship->creation_info = strdup(creation_info_id); + relationship->spdx_id = strdup(spdx_id); + relationship->from = strdup(from); + relationship->to = to; + relationship->relationship_type = strdup(relationship_type); + + if (!relationship->creation_info || !relationship->spdx_id || !relationship->from || !relationship->relationship_type) + goto err; + + return relationship; + +err: + pkgconf_error(client, "spdxtool_core_relationship_new: out of memory"); + spdxtool_core_relationship_free(relationship); + return NULL; +} + +/* + * !doc + * + * .. c:function:: void spdxtool_core_relationship_free(spdxtool_core_relationship_t *relationship) + * + * Free /Core/Relationship struct + * + * :param spdxtool_core_relationship_t *relationship: Relationship struct to be freed. + * :return: nothing + */ +void +spdxtool_core_relationship_free(spdxtool_core_relationship_t *relationship) +{ + if (!relationship) + return; + + free(relationship->spdx_id); + free(relationship->creation_info); + free(relationship->from); + pkgconf_license_free(relationship->to); + free(relationship->to); + free(relationship->relationship_type); + + free(relationship); +} + +/* + * !doc + * + * .. c:function:: spdxtool_serialize_value_t *spdxtool_core_relationship_to_object(pkgconf_client_t *client, const spdxtool_core_relationship_t *relationship) + * + * Serialize /Core/Relationship struct to a JSON value tree. + * + * :param pkgconf_client_t *client: The pkgconf client being accessed. + * :param const spdxtool_core_relationship_t *relationship: Relationship struct to be serialized. + * :return: spdxtool_serialize_value_t * representing the Relationship object. + */ +spdxtool_serialize_value_t * +spdxtool_core_relationship_to_object(pkgconf_client_t *client, const spdxtool_core_relationship_t *relationship) +{ + spdxtool_serialize_value_t *ret = NULL; + spdxtool_serialize_object_list_t *object_list = spdxtool_serialize_object_list_new(); + if (!object_list) + goto err; + + spdxtool_serialize_array_t *to = spdxtool_serialize_array_new(); + if (!to) + goto err; + + pkgconf_node_t *node = NULL; + PKGCONF_FOREACH_LIST_ENTRY(relationship->to->head, node) + { + const pkgconf_license_t *license = node->data; + if (!spdxtool_serialize_array_add_string(to, license->data)) + { + spdxtool_serialize_array_free(to); + goto err; + } + } + + if (!(spdxtool_serialize_object_add_string(object_list, "type", relationship->type) && + spdxtool_serialize_object_add_string(object_list, "creationInfo", relationship->creation_info) && + spdxtool_serialize_object_add_string(object_list, "spdxId", relationship->spdx_id) && + spdxtool_serialize_object_add_string(object_list, "from", relationship->from) && + spdxtool_serialize_object_add_array(object_list, "to", to) && + spdxtool_serialize_object_add_string(object_list, "relationshipType", relationship->relationship_type))) + { + goto err; + } + + ret = spdxtool_serialize_value_object(object_list); + object_list = NULL; + +err: + if (!ret) + pkgconf_error(client, "spdxtool_core_relationship_to_object: out of memory"); + + spdxtool_serialize_object_list_free(object_list); + return ret; +} diff --git a/cli/spdxtool/core.h b/cli/spdxtool/core.h new file mode 100644 index 000000000000..0cbb1f4b3d51 --- /dev/null +++ b/cli/spdxtool/core.h @@ -0,0 +1,77 @@ +/* + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2025 The FreeBSD Foundation + * + * Portions of this software were developed by + * Tuukka Pasanen <tuukka.pasanen@ilmi.fi> under sponsorship from + * the FreeBSD Foundation + */ + +#ifndef CLI__SPDXTOOL__CORE_H +#define CLI__SPDXTOOL__CORE_H + +#include <stdlib.h> +#include "util.h" +#include "serialize.h" + +#ifdef __cplusplus +extern "C" { +#endif + +spdxtool_core_agent_t * +spdxtool_core_agent_new(pkgconf_client_t *client, const char *creation_id, const char *name); + +void +spdxtool_core_agent_free(spdxtool_core_agent_t *agent); + +spdxtool_serialize_value_t * +spdxtool_core_agent_to_object(pkgconf_client_t *client, const spdxtool_core_agent_t *agent); + +spdxtool_core_creation_info_t * +spdxtool_core_creation_info_new(pkgconf_client_t *client, const char *agent_id, const char *id, const char *time); + +void +spdxtool_core_creation_info_free(spdxtool_core_creation_info_t *creation); + +spdxtool_serialize_value_t * +spdxtool_core_creation_info_to_object(pkgconf_client_t *client, const spdxtool_core_creation_info_t *creation); + +spdxtool_core_spdx_document_t * +spdxtool_core_spdx_document_new(pkgconf_client_t *client, const char *spdx_id, const char *creation_id, const char *agent_id); + +bool +spdxtool_core_spdx_document_is_license(pkgconf_client_t *client, const spdxtool_core_spdx_document_t *spdx, const char *license); + +bool +spdxtool_core_spdx_document_add_license(pkgconf_client_t *client, spdxtool_core_spdx_document_t *spdx, const char *license); + +bool +spdxtool_core_spdx_document_add_relationship(pkgconf_client_t *client, spdxtool_core_spdx_document_t *spdx, spdxtool_core_relationship_t *relationship); + +bool +spdxtool_core_spdx_document_add_package(pkgconf_client_t *client, spdxtool_core_spdx_document_t *spdx, pkgconf_pkg_t *pkg); + +bool +spdxtool_core_spdx_document_add_element(pkgconf_client_t *client, spdxtool_core_spdx_document_t *spdx, const char *element); + +void +spdxtool_core_spdx_document_free(spdxtool_core_spdx_document_t *spdx); + +spdxtool_serialize_value_t * +spdxtool_core_spdx_document_to_object(pkgconf_client_t *client, spdxtool_core_spdx_document_t *spdx); + +spdxtool_core_relationship_t * +spdxtool_core_relationship_new(pkgconf_client_t *client, const char *creation_info_id, const char *spdx_id, const char *from, pkgconf_list_t *to, const char *relationship_type); + +void +spdxtool_core_relationship_free(spdxtool_core_relationship_t *relationship); + +spdxtool_serialize_value_t * +spdxtool_core_relationship_to_object(pkgconf_client_t *client, const spdxtool_core_relationship_t *relationship); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/cli/spdxtool/main.c b/cli/spdxtool/main.c new file mode 100644 index 000000000000..70cdce2dd891 --- /dev/null +++ b/cli/spdxtool/main.c @@ -0,0 +1,425 @@ +/* + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2025 The FreeBSD Foundation + * + * Portions of this software were developed by + * Tuukka Pasanen <tuukka.pasanen@ilmi.fi> under sponsorship from + * the FreeBSD Foundation + */ + +#include "libpkgconf/config.h" +#include <libpkgconf/stdinc.h> +#include <libpkgconf/libpkgconf.h> +#include "getopt_long.h" +#include "util.h" +#include "core.h" +#include "software.h" +#include "serialize.h" +#include "simplelicensing.h" + +#define PKG_VERSION (((uint64_t) 1) << 1) +#define PKG_ABOUT (((uint64_t) 1) << 2) +#define PKG_HELP (((uint64_t) 1) << 3) + +static const char *spdx_version = "3.0.1"; +static const char *bom_license = "CC0-1.0"; +static const char *xsd_any_url_default_base = "https://github.com/pkgconf/pkgconf"; +static const char *xsd_any_uri_default_base = "github.com:pkgconf:pkgconf"; +static int maximum_traverse_depth = 2000; + +static pkgconf_client_t pkg_client; +static uint64_t want_flags; +static size_t maximum_package_count = 0; +// static int maximum_traverse_depth = 2000; +static FILE *error_msgout = NULL; +static FILE *sbom_out = NULL; + +static const char * +environ_lookup_handler(const pkgconf_client_t *client, const char *key) +{ + (void) client; + + return getenv(key); +} + +static bool +error_handler(const char *msg, const pkgconf_client_t *client, void *data) +{ + (void) client; + (void) data; + if (!pkgconf_output_file_fmt(error_msgout, "%s", msg)) + { + pkgconf_error(client, "spdxtool: Could not output error message: %s", strerror(errno)); + return false; + } + return true; +} + +// NOTE: this function is passed to pkgconf_pkg_traverse +static void +generate_spdx_package(pkgconf_client_t *client, pkgconf_pkg_t *pkg, void *ptr) +{ + spdxtool_core_spdx_document_t *document = (spdxtool_core_spdx_document_t *)ptr; + pkgconf_node_t *node = NULL; + spdxtool_software_sbom_t *sbom = NULL; + char *package_spdx = NULL; + char *spdx_id_string = NULL; + char sep = spdxtool_util_get_uri_separator(client); + + if (pkg->flags & PKGCONF_PKG_PROPF_VIRTUAL) + return; + + spdx_id_string = spdxtool_util_get_spdx_id_string(client, "software_Sbom", pkg->id); + if (!spdx_id_string) + goto err; + + sbom = spdxtool_software_sbom_new(client, spdx_id_string, document->creation_info, "build"); + free(spdx_id_string); + spdx_id_string = NULL; + if (!sbom) + goto err; + + sbom->spdx_document = document; + sbom->rootElement = pkg; + + package_spdx = spdxtool_util_get_spdx_id_string(client, "Package", pkg->id); + if (!package_spdx) + goto err; + + pkgconf_tuple_add(client, &pkg->vars, "spdxId", package_spdx, false, 0); + free(package_spdx); + package_spdx = NULL; + + pkgconf_tuple_add(client, &pkg->vars, "creationInfo", document->creation_info, false, 0); + pkgconf_tuple_add(client, &pkg->vars, "agent", document->agent, false, 0); + + if (pkg->license.head != NULL) + { + pkgconf_buffer_t spdx_id_buf = PKGCONF_BUFFER_INITIALIZER; + + pkgconf_buffer_append_fmt(&spdx_id_buf, "%s%chasDeclaredLicense", pkg->id, sep); + char *spdx_id_name = pkgconf_buffer_freeze(&spdx_id_buf); + if (!spdx_id_name) + goto err; + + package_spdx = spdxtool_util_get_spdx_id_string(client, "Relationship", spdx_id_name); + free(spdx_id_name); + if (!package_spdx) + goto err; + + pkgconf_tuple_add(client, &pkg->vars, "hasDeclaredLicense", package_spdx, false, 0); + free(package_spdx); + package_spdx = NULL; + + pkgconf_buffer_t concluded_buf = PKGCONF_BUFFER_INITIALIZER; + pkgconf_buffer_append_fmt(&concluded_buf, "%s%chasConcludedLicense", pkg->id, sep); + spdx_id_name = pkgconf_buffer_freeze(&concluded_buf); + if (!spdx_id_name) + goto err; + + package_spdx = spdxtool_util_get_spdx_id_string(client, "Relationship", spdx_id_name); + free(spdx_id_name); + if (!package_spdx) + goto err; + + pkgconf_tuple_add(client, &pkg->vars, "hasConcludedLicense", package_spdx, false, 0); + free(package_spdx); + package_spdx = NULL; + + PKGCONF_FOREACH_LIST_ENTRY(pkg->license.head, node) + { + const pkgconf_license_t *license = node->data; + if (license->type == PKGCONF_LICENSE_EXPRESSION) + { + if (!spdxtool_core_spdx_document_add_license(client, document, license->data)) + goto err; + } + } + } + + node = calloc(1, sizeof(pkgconf_node_t)); + if (!node) + goto err; + + pkgconf_node_insert_tail(node, sbom, &document->rootElement); + return; + +err: + pkgconf_error(client, "generate_spdx_package: failed for %s", pkg->id); + free(package_spdx); + free(spdx_id_string); + spdxtool_software_sbom_free(sbom); +} + +static bool +generate_spdx(pkgconf_client_t *client, pkgconf_pkg_t *world, const char *creation_time, const char *creation_id, const char *agent_name) +{ + const char *agent_name_string = agent_name ? agent_name : "Default"; + const char *creation_id_string = creation_id ? creation_id : "_:creationinfo_1"; + + spdxtool_core_agent_t *agent = spdxtool_core_agent_new(client, creation_id_string, agent_name_string); + if (!agent) + { + pkgconf_error(client, "Could not create agent struct"); + return false; + } + + spdxtool_core_creation_info_t *creation = spdxtool_core_creation_info_new(client, agent->spdx_id, creation_id_string, creation_time); + if (!creation) + { + pkgconf_error(client, "Could not create creation info struct"); + spdxtool_core_agent_free(agent); + return false; + } + + char *spdx_id_int = spdxtool_util_get_spdx_id_int(client, "spdxDocument"); + spdxtool_core_spdx_document_t *document = spdxtool_core_spdx_document_new(client, spdx_id_int, creation_id_string, agent->spdx_id); + free(spdx_id_int); + if (!document) + { + pkgconf_error(client, "Could not create document"); + spdxtool_core_creation_info_free(creation); + spdxtool_core_agent_free(agent); + return false; + } + + int eflag = pkgconf_pkg_traverse(client, world, generate_spdx_package, document, maximum_traverse_depth, 0); + if (eflag != PKGCONF_PKG_ERRF_OK) + { + spdxtool_core_spdx_document_free(document); + spdxtool_core_creation_info_free(creation); + spdxtool_core_agent_free(agent); + return false; + } + + spdxtool_serialize_value_t *root = spdxtool_serialize_sbom(client, agent, creation, document); + if (!root) + { + spdxtool_core_spdx_document_free(document); + spdxtool_core_creation_info_free(creation); + spdxtool_core_agent_free(agent); + return false; + } + + pkgconf_buffer_t buffer = PKGCONF_BUFFER_INITIALIZER; + spdxtool_serialize_value_to_buf(&buffer, root, 0); + spdxtool_serialize_value_free(root); + + bool ret = pkgconf_output_file_fmt(sbom_out, "%s\n", pkgconf_buffer_str(&buffer)); + pkgconf_buffer_finalize(&buffer); + + spdxtool_core_spdx_document_free(document); + spdxtool_core_creation_info_free(creation); + spdxtool_core_agent_free(agent); + + if (!ret) + pkgconf_error(client, "spdxtool: Could not output to file: %s", strerror(errno)); + return ret; +} + +static int +version(void) +{ + printf("spdxtool %s\n", PACKAGE_VERSION); + return EXIT_SUCCESS; +} + +static int +about(void) +{ + printf("spdxtool (%s %s)\n\n", PACKAGE_NAME, PACKAGE_VERSION); + printf("SPDX-License-Identifier: BSD-2-Clause\n\n"); + printf("Copyright (c) 2025 The FreeBSD Foundation\n\n"); + printf("Portions of this software were developed by\n"); + printf("Tuukka Pasanen <tuukka.pasanen@ilmi.fi> under sponsorship from\n"); + printf("the FreeBSD Foundation\n\n"); + printf("Report bugs at <%s>.\n", PACKAGE_BUGREPORT); + return EXIT_SUCCESS; +} + +static int +usage(void) +{ + printf("usage: spdxtool [modules]\n"); + + printf("\nOptions:\n"); + + printf(" --agent-name Set agent name [default: 'Default']\n"); + printf(" --creation-time Use string as creation time (Should be in ISO8601 format) [default: current time]\n"); + printf(" --creation-id Use string as creation id [default: '_:creationinfo_1']\n"); + printf(" --help this message\n"); + printf(" --about print bomtool version and license to stdout\n"); + printf(" --version print bomtool version to stdout\n"); + printf(" --output FILE output SBOM data to file\n"); + printf(" --spdx-base-id URL Uset string as base of SPDX ids [default: %s]\n", xsd_any_uri_default_base); + printf(" --use-uri Use URIs not URLs as SPDX id"); + printf(" --define-variable=varname=value define variable global 'varname' as 'value'\n"); + + return EXIT_SUCCESS; +} + +int +main(int argc, char *argv[]) +{ + int ret = EXIT_SUCCESS; + pkgconf_list_t pkgq = PKGCONF_LIST_INITIALIZER; + unsigned int want_client_flags = PKGCONF_PKG_PKGF_SEARCH_PRIVATE; + pkgconf_cross_personality_t *personality = pkgconf_cross_personality_default(); + char *creation_time = NULL; + char *creation_id = NULL; + char *agent_name = NULL; + char world_id[] = "virtual:world"; + char world_realname[] = "virtual world package"; + const char *spdx_id_base = xsd_any_url_default_base; + bool colon_sep = false; + pkgconf_pkg_t world = + { + .id = world_id, + .realname = world_realname, + .flags = PKGCONF_PKG_PROPF_STATIC | PKGCONF_PKG_PROPF_VIRTUAL, + }; + + error_msgout = stderr; + sbom_out = stdout; + + struct pkg_option options[] = + { + { "agent-name", required_argument, NULL, 100, }, + { "creation-time", required_argument, NULL, 101, }, + { "creation-id", required_argument, NULL, 102, }, + { "version", no_argument, &want_flags, PKG_VERSION, }, + { "about", no_argument, &want_flags, PKG_ABOUT, }, + { "help", no_argument, &want_flags, PKG_HELP, }, + { "output", required_argument, NULL, 103, }, + { "spdx-base-id", required_argument, NULL, 104, }, + { "use-uri", no_argument, NULL, 105, }, + { "define-variable", required_argument, NULL, 106, }, + { NULL, 0, NULL, 0 } + }; + + while ((ret = pkg_getopt_long_only(argc, argv, "", options, NULL)) != -1) + { + switch (ret) + { + case 100: + agent_name = pkg_optarg; + break; + case 101: + creation_time = pkg_optarg; + break; + case 102: + creation_id = pkg_optarg; + break; + case 103: + sbom_out = fopen(pkg_optarg, "w"); + if (sbom_out == NULL) + { + pkgconf_output_file_fmt(stderr, "unable to open %s: %s\n", pkg_optarg, strerror(errno)); + return EXIT_FAILURE; + } + break; + case 104: + spdx_id_base = pkg_optarg; + break; + case 105: + // If SPDX id base have not been altered use default + if (!strcmp(spdx_id_base, xsd_any_url_default_base)) + spdx_id_base = xsd_any_uri_default_base; + colon_sep = true; + break; + case 106: + pkgconf_tuple_define_global(&pkg_client, pkg_optarg); + break; + case '?': + case ':': + return EXIT_FAILURE; + default: + break; + } + } + + pkgconf_client_init(&pkg_client, error_handler, NULL, personality, NULL, environ_lookup_handler); + + /* we have determined what features we want most likely. in some cases, we override later. */ + pkgconf_client_set_flags(&pkg_client, want_client_flags); + + /* at this point, want_client_flags should be set, so build the dir list */ + pkgconf_client_dir_list_build(&pkg_client, personality); + + + if ((want_flags & PKG_ABOUT) == PKG_ABOUT) + return about(); + + if ((want_flags & PKG_VERSION) == PKG_VERSION) + return version(); + + if ((want_flags & PKG_HELP) == PKG_HELP) + return usage(); + + while (1) + { + const char *package = argv[pkg_optind]; + + if (package == NULL) + break; + + /* check if there is a limit to the number of packages allowed to be included, if so and we have hit + * the limit, stop adding packages to the queue. + */ + if (maximum_package_count > 0 && pkgq.length > maximum_package_count) + break; + + while (isspace((unsigned char)package[0])) + package++; + + /* skip empty packages */ + if (package[0] == '\0') + { + pkg_optind++; + continue; + } + + if (argv[pkg_optind + 1] == NULL || !PKGCONF_IS_OPERATOR_CHAR(*(argv[pkg_optind + 1]))) + { + pkgconf_queue_push(&pkgq, package); + pkg_optind++; + } + else + { + char packagebuf[PKGCONF_BUFSIZE]; + + snprintf(packagebuf, sizeof packagebuf, "%s %s %s", package, argv[pkg_optind + 1], argv[pkg_optind + 2]); + pkg_optind += 3; + + pkgconf_queue_push(&pkgq, packagebuf); + } + } + + if (!pkgconf_queue_solve(&pkg_client, &pkgq, &world, maximum_traverse_depth)) + { + ret = EXIT_FAILURE; + goto out; + } + + spdxtool_util_set_uri_root(&pkg_client, spdx_id_base); + spdxtool_util_set_uri_separator_colon(&pkg_client, colon_sep); + spdxtool_util_set_spdx_license(&pkg_client, bom_license); + spdxtool_util_set_spdx_version(&pkg_client, spdx_version); + + if (!generate_spdx(&pkg_client, &world, creation_time, creation_id, agent_name)) + { + ret = EXIT_FAILURE; + goto out; + } + + ret = EXIT_SUCCESS; + +out: + pkgconf_solution_free(&pkg_client, &world); + pkgconf_queue_free(&pkgq); + pkgconf_cross_personality_deinit(personality); + pkgconf_client_deinit(&pkg_client); + + return ret; +} diff --git a/cli/spdxtool/serialize.c b/cli/spdxtool/serialize.c new file mode 100644 index 000000000000..69ef3a70a3b1 --- /dev/null +++ b/cli/spdxtool/serialize.c @@ -0,0 +1,467 @@ +/* + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2025 The FreeBSD Foundation + * + * Portions of this software were developed by + * Tuukka Pasanen <tuukka.pasanen@ilmi.fi> under sponsorship from + * the FreeBSD Foundation + * + * Copyright (C) 2026 Elizabeth Ashford. + */ + +#include <stdlib.h> +#include <string.h> +#include "util.h" +#include "core.h" +#include "software.h" +#include "simplelicensing.h" +#include "serialize.h" + +static void +serialize_escape_string(pkgconf_buffer_t *buffer, const char *s) +{ + for (const char *p = s; *p; p++) + { + switch (*p) + { + case '\"': + pkgconf_buffer_append(buffer, "\\\""); + break; + case '\\': + pkgconf_buffer_append(buffer, "\\\\"); + break; + case '\b': + pkgconf_buffer_append(buffer, "\\b"); + break; + case '\f': + pkgconf_buffer_append(buffer, "\\f"); + break; + case '\n': + pkgconf_buffer_append(buffer, "\\n"); + break; + case '\r': + pkgconf_buffer_append(buffer, "\\r"); + break; + case '\t': + pkgconf_buffer_append(buffer, "\\t"); + break; + default: + if (*p < 0x20) + pkgconf_buffer_append_fmt(buffer, "\\u%04x", (unsigned int)*p); + else + pkgconf_buffer_push_byte(buffer, *p); + } + } +} + +static inline void +serialize_add_indent(pkgconf_buffer_t *buffer, unsigned int level) +{ + for (; level; level--) + pkgconf_buffer_append(buffer, " "); +} + +/* + * !doc + * + * .. c:function:: void spdxtool_serialize_value_to_buf(pkgconf_buffer_t *buffer, spdxtool_serialize_value_t *value, unsigned int indent) + * + * Serialize the given JSON to the buffer + * + * :param pkgconf_buffer_t *buffer: Buffer to add to. + * :param spdxtool_serialize_value *value: Value to serialize. + * :param unsigned int indent: Indent level + * :return: true on success, false on failure + */ +bool +spdxtool_serialize_value_to_buf(pkgconf_buffer_t *buffer, spdxtool_serialize_value_t *value, unsigned int indent) +{ + if (!buffer || !value) + return false; + + switch(value->type) { + case SPDXTOOL_SERIALIZE_TYPE_STRING: + pkgconf_buffer_push_byte(buffer, '"'); + serialize_escape_string(buffer, value->value.s ? value->value.s : ""); + pkgconf_buffer_push_byte(buffer, '"'); + break; + case SPDXTOOL_SERIALIZE_TYPE_INT: + pkgconf_buffer_append_fmt(buffer, "%d", value->value.i); + break; + case SPDXTOOL_SERIALIZE_TYPE_BOOL: + pkgconf_buffer_append(buffer, value->value.b ? "true" : "false"); + break; + case SPDXTOOL_SERIALIZE_TYPE_NULL: + pkgconf_buffer_append(buffer, "null"); + break; + case SPDXTOOL_SERIALIZE_TYPE_OBJECT: + { + pkgconf_node_t *iter; + pkgconf_buffer_push_byte(buffer, '{'); + pkgconf_buffer_push_byte(buffer, '\n'); + + PKGCONF_FOREACH_LIST_ENTRY(value->value.o->entries.head, iter) + { + spdxtool_serialize_object_t *entry = iter->data; + serialize_add_indent(buffer, indent + 1); + pkgconf_buffer_append_fmt(buffer, "\"%s\": ", entry->key); + spdxtool_serialize_value_to_buf(buffer, entry->value, indent + 1); + if (iter->next) + pkgconf_buffer_push_byte(buffer, ','); + pkgconf_buffer_push_byte(buffer, '\n'); + } + + serialize_add_indent(buffer, indent); + pkgconf_buffer_push_byte(buffer, '}'); + break; + } + case SPDXTOOL_SERIALIZE_TYPE_ARRAY: + { + pkgconf_node_t *iter; + pkgconf_buffer_push_byte(buffer, '['); + pkgconf_buffer_push_byte(buffer, '\n'); + + PKGCONF_FOREACH_LIST_ENTRY(value->value.a->items.head, iter) + { + spdxtool_serialize_value_t *entry = iter->data; + serialize_add_indent(buffer, indent + 1); + spdxtool_serialize_value_to_buf(buffer, entry, indent + 1); + if (iter->next) + pkgconf_buffer_push_byte(buffer, ','); + pkgconf_buffer_push_byte(buffer, '\n'); + } + serialize_add_indent(buffer, indent); + pkgconf_buffer_push_byte(buffer, ']'); + break; + } + } + + return true; +} + +/* + * !doc + * + * .. c:function:: spdxtool_serialize_value_t *spdxtool_serialize_object_add_take(spdxtool_serialize_object_list_t *object_list, const char *key, spdxtool_serialize_value_t *value) + * + * Add a key-value pair to a JSON object list. The key is copied internally. + * The object list takes ownership of the value. + * + * :param spdxtool_serialize_object_list_t *object_list: Object list to add to. + * :param const char *key: Key string, copied internally. + * :param spdxtool_serialize_value_t *value: Value to associate with the key. Ownership transfers to the object list. + * :return: The value added, not owned by the caller. + */ +spdxtool_serialize_value_t * +spdxtool_serialize_object_add_take(spdxtool_serialize_object_list_t *object_list, const char *key, spdxtool_serialize_value_t *value) +{ + if (!object_list || !value) + { + spdxtool_serialize_value_free(value); + return NULL; + } + + pkgconf_node_t *node = calloc(1, sizeof(pkgconf_node_t)); + spdxtool_serialize_object_t *object = calloc(1, sizeof(spdxtool_serialize_object_t)); + char *keycopy = key ? strdup(key) : strdup(""); + if (!node || !object || !keycopy) + { + free(node); + free(keycopy); + spdxtool_serialize_object_free(object); + spdxtool_serialize_value_free(value); + return NULL; + } + + object->key = keycopy; + object->value = value; + pkgconf_node_insert_tail(node, object, &object_list->entries); + return value; +} + +/* + * !doc + * + * .. c:function:: spdxtool_serialize_value_t *spdxtool_serialize_array_add_take(spdxtool_serialize_array_t *array, spdxtool_serialize_value_t value) + * + * Add a value to a JSON array. The array takes ownership of the value. + * + * :param spdxtool_serialize_array_t *array: Array to add to. + * :param spdxtool_serialize_value_t value: Value to append. Ownership transfers to the array. + * :return: The value added, not owned by the caller. + */ +spdxtool_serialize_value_t * +spdxtool_serialize_array_add_take(spdxtool_serialize_array_t *array, spdxtool_serialize_value_t *value) +{ + if (!array) + { + // Taking value, so free + spdxtool_serialize_value_free(value); + return NULL; + } + + pkgconf_node_t *node = calloc(1, sizeof(pkgconf_node_t)); + if (!node) + { + spdxtool_serialize_value_free(value); + return NULL; + } + + pkgconf_node_insert_tail(node, value, &array->items); + return value; +} + +/* + * !doc + * + * .. c:function:: spdxtool_serialize_object_list_t *spdxtool_serialize_object_list_new(void) + * + * Allocate and initialize a new empty JSON object list. + * + * :return: Pointer to a new spdxtool_serialize_object_list_t, or NULL on allocation failure. + */ +spdxtool_serialize_object_list_t * +spdxtool_serialize_object_list_new(void) +{ + return calloc(1, sizeof(spdxtool_serialize_object_list_t)); +} + +/* + * !doc + * + * .. c:function:: spdxtool_serialize_array_t *spdxtool_serialize_array_new(void) + * + * Allocate and initialize a new empty JSON array. + * + * :return: Pointer to a new spdxtool_serialize_array_t, or NULL on allocation failure. + */ +spdxtool_serialize_array_t * +spdxtool_serialize_array_new(void) +{ + return calloc(1, sizeof(spdxtool_serialize_array_t)); +} + +/* + * !doc + * + * .. c:function:: void spdxtool_serialize_value_free(spdxtool_serialize_value_t *value) + * + * Free all resources owned by a JSON value. For strings, frees the string. + * For objects and arrays, recursively frees all children. The value pointer + * itself is not freed as it is assumed to be stack-allocated. + * + * :param spdxtool_serialize_value_t *value: Value to free. May be NULL. + * :return: nothing + */ +void +spdxtool_serialize_value_free(spdxtool_serialize_value_t *value) +{ + if (!value) + return; + + switch (value->type) + { + case SPDXTOOL_SERIALIZE_TYPE_STRING: + free(value->value.s); + break; + case SPDXTOOL_SERIALIZE_TYPE_ARRAY: + spdxtool_serialize_array_free(value->value.a); + break; + case SPDXTOOL_SERIALIZE_TYPE_OBJECT: + spdxtool_serialize_object_list_free(value->value.o); + break; + default: + // Nothing to do + break; + } + + free(value); +} + +/* + * !doc + * + * .. c:function:: void spdxtool_serialize_object_free(spdxtool_serialize_object_t *object) + * + * Free a JSON object entry, including its key string and owned value. + * The object pointer itself is not freed by this function. + * + * :param spdxtool_serialize_object_t *object: Object entry to free. May be NULL. + * :return: nothing + */ +void +spdxtool_serialize_object_free(spdxtool_serialize_object_t *object) +{ + if (!object) + return; + + free(object->key); + spdxtool_serialize_value_free(object->value); +} + +/* + * !doc + * + * .. c:function:: void spdxtool_serialize_object_list_free(spdxtool_serialize_object_list_t *object_list) + * + * Free a JSON object list and all of its entries, including their keys and values. + * + * :param spdxtool_serialize_object_list_t *object_list: Object list to free. May be NULL. + * :return: nothing + */ +void +spdxtool_serialize_object_list_free(spdxtool_serialize_object_list_t *object_list) +{ + if (!object_list) + return; + + pkgconf_node_t *iter_next = NULL, *iter = NULL; + PKGCONF_FOREACH_LIST_ENTRY_SAFE(object_list->entries.head, iter_next, iter) + { + spdxtool_serialize_object_t *object = iter->data; + spdxtool_serialize_object_free(object); + free(object); + free(iter); + } + + free(object_list); +} + +/* + * !doc + * + * .. c:function:: void spdxtool_serialize_array_free(spdxtool_serialize_array_t *array) + * + * Free a JSON array and all of its elements. + * + * :param spdxtool_serialize_array_t *array: Array to free. May be NULL. + * :return: nothing + */ +void +spdxtool_serialize_array_free(spdxtool_serialize_array_t *array) +{ + if (!array) + return; + + pkgconf_node_t *iter_next = NULL, *iter = NULL; + PKGCONF_FOREACH_LIST_ENTRY_SAFE(array->items.head, iter_next, iter) + { + spdxtool_serialize_value_t *value = iter->data; + spdxtool_serialize_value_free(value); + free(iter); + } + + free(array); +} + +/* + * !doc + * + * .. c:function:: spdxtool_serialize_value_t *spdxtool_serialize_sbom(pkgconf_client_t *client, spdxtool_core_agent_t *agent, spdxtool_core_creation_info_t *creation, spdxtool_core_spdx_document_t *spdx) + * + * Serialize a complete SPDX SBOM document to a JSON-LD value tree. Iterates + * all SBOMs, packages, relationships, and license expressions registered on + * the document. The SpdxDocument object is emitted last to ensure all element + * IDs have been populated by prior iteration. This function must be called + * after pkgconf_pkg_traverse has completed so that all packages and their + * dependencies are registered on spdx. + * + * :param pkgconf_client_t *client: The pkgconf client being accessed. + * :param spdxtool_core_agent_t *agent: Agent struct to include in the document. + * :param spdxtool_core_creation_info_t *creation: CreationInfo struct to include in the document. + * :param spdxtool_core_spdx_document_t *spdx: SpdxDocument struct containing all registered SBOMs, packages, relationships, and licenses. + * :return: spdxtool_serialize_value_t * representing the complete JSON-LD document, or a null string value on allocation failure. + */ +spdxtool_serialize_value_t * +spdxtool_serialize_sbom(pkgconf_client_t *client, spdxtool_core_agent_t *agent, spdxtool_core_creation_info_t *creation, spdxtool_core_spdx_document_t *spdx) +{ + const char *errstr = "out of memory"; + spdxtool_serialize_value_t *ret = NULL; + spdxtool_serialize_array_t *graph = NULL; + spdxtool_serialize_object_list_t *root = spdxtool_serialize_object_list_new(); + if (!root) + goto err; + + if (!spdxtool_serialize_object_add_string(root, "@context", "https://spdx.org/rdf/3.0.1/spdx-context.jsonld")) + goto err; + + graph = spdxtool_serialize_array_new(); + if (!graph) + goto err; + + if (!spdxtool_serialize_array_add_take(graph, spdxtool_core_agent_to_object(client, agent))) + goto err; + + if (!spdxtool_serialize_array_add_take(graph, spdxtool_core_creation_info_to_object(client, creation))) + goto err; + + pkgconf_node_t *iter = NULL; + PKGCONF_FOREACH_LIST_ENTRY(spdx->licenses.head, iter) + { + spdxtool_simplelicensing_license_expression_t *expression = iter->data; + if (!expression) + { + errstr = "licenses list corrupted"; + goto err; + } + if (!spdxtool_serialize_array_add_take(graph, spdxtool_simplelicensing_licenseExpression_to_object(client, spdx->creation_info, expression))) + goto err; + } + + PKGCONF_FOREACH_LIST_ENTRY(spdx->rootElement.head, iter) + { + spdxtool_software_sbom_t *current_sbom = iter->data; + if (!current_sbom) + { + errstr = "sbom list corrupted"; + goto err; + } + if (!spdxtool_serialize_array_add_take(graph, spdxtool_software_sbom_to_object(client, current_sbom))) + goto err; + } + + PKGCONF_FOREACH_LIST_ENTRY(spdx->packages.head, iter) + { + pkgconf_pkg_t *pkg = iter->data; + if (!pkg) + { + errstr = "pkg list corrupted"; + goto err; + } + if (!spdxtool_serialize_array_add_take(graph, spdxtool_software_package_to_object(client, pkg, spdx))) + goto err; + } + + PKGCONF_FOREACH_LIST_ENTRY(spdx->relationships.head, iter) + { + spdxtool_core_relationship_t *relationship = iter->data; + if (!relationship) + { + errstr = "relationship list corrupted"; + goto err; + } + if (!spdxtool_serialize_array_add_take(graph, spdxtool_core_relationship_to_object(client, relationship))) + goto err; + } + + // SpdxDocument last — spdx->element must be fully populated first + if (!spdxtool_serialize_array_add_take(graph, spdxtool_core_spdx_document_to_object(client, spdx))) + goto err; + + bool ok = spdxtool_serialize_object_add_array(root, "@graph", graph); + graph = NULL; + if (!ok) + goto err; + + ret = spdxtool_serialize_value_object(root); + root = NULL; + +err: + if (!ret) + pkgconf_error(client, "spdxtool_serialize_sbom: %s", errstr); + + spdxtool_serialize_object_list_free(root); + spdxtool_serialize_array_free(graph); + return ret; +} diff --git a/cli/spdxtool/serialize.h b/cli/spdxtool/serialize.h new file mode 100644 index 000000000000..e04b7f38b741 --- /dev/null +++ b/cli/spdxtool/serialize.h @@ -0,0 +1,539 @@ +/* + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2025 The FreeBSD Foundation + * + * Portions of this software were developed by + * Tuukka Pasanen <tuukka.pasanen@ilmi.fi> under sponsorship from + * the FreeBSD Foundation + * + * Copyright (C) 2026 Elizabeth Ashford. + */ + +#include <stdlib.h> +#include <string.h> +#include "util.h" + +#ifndef CLI__SPDXTOOL__SERIALIZE_H +#define CLI__SPDXTOOL__SERIALIZE_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum spdxtool_serialize_type_ +{ + SPDXTOOL_SERIALIZE_TYPE_STRING, // JSON string type + SPDXTOOL_SERIALIZE_TYPE_INT, // JSON number type (int) + SPDXTOOL_SERIALIZE_TYPE_BOOL, // JSON bool type + SPDXTOOL_SERIALIZE_TYPE_NULL, // JSON null type + SPDXTOOL_SERIALIZE_TYPE_OBJECT, // JSON object type + SPDXTOOL_SERIALIZE_TYPE_ARRAY // JSON array type +} spdxtool_serialize_type_t; + +typedef struct spdxtool_serialize_value_ { + spdxtool_serialize_type_t type; + union { + char *s; + int i; + bool b; + struct spdxtool_serialize_object_list_ *o; + struct spdxtool_serialize_array_ *a; + } value; +} spdxtool_serialize_value_t; + +typedef struct spdxtool_serialize_object_ { + char *key; + spdxtool_serialize_value_t *value; +} spdxtool_serialize_object_t; + +typedef struct spdxtool_serialize_object_list_ { + pkgconf_list_t entries; +} spdxtool_serialize_object_list_t; + +typedef struct spdxtool_serialize_array_ { + pkgconf_list_t items; +} spdxtool_serialize_array_t; + +bool +spdxtool_serialize_value_to_buf(pkgconf_buffer_t *buffer, spdxtool_serialize_value_t *value, unsigned int indent); + +spdxtool_serialize_value_t * +spdxtool_serialize_value_dup(const spdxtool_serialize_value_t *value); + +spdxtool_serialize_value_t * +spdxtool_serialize_object_add_take(spdxtool_serialize_object_list_t *object_list, const char *key, spdxtool_serialize_value_t* value); + +spdxtool_serialize_object_list_t * +spdxtool_serialize_object_list_new(void); + +spdxtool_serialize_array_t * +spdxtool_serialize_array_new(void); + +spdxtool_serialize_value_t * +spdxtool_serialize_array_add_take(spdxtool_serialize_array_t *array, spdxtool_serialize_value_t* value); + +void +spdxtool_serialize_value_free(spdxtool_serialize_value_t *value); + +void +spdxtool_serialize_object_list_free(spdxtool_serialize_object_list_t *object_list); + +void +spdxtool_serialize_object_free(spdxtool_serialize_object_t *object); + +void +spdxtool_serialize_array_free(spdxtool_serialize_array_t *array); + +/* + * !doc + * + * .. c:function:: spdxtool_serialize_value_t * spdxtool_serialize_value_string(const char *s) + * + * Construct a JSON string value. The string is copied internally. + * If this return value is not stolen, it must be freed with spdxtool_serialize_value_free(). + * + * :param const char *s: String to copy. May be NULL, in which case the value holds NULL. + * :return: spdxtool_serialize_value_t * of type SPDXTOOL_SERIALIZE_TYPE_STRING. + */ +static inline spdxtool_serialize_value_t * +spdxtool_serialize_value_string(const char *s) +{ + if (!s) + return NULL; + + char *sv = strdup(s); + if (!sv) + return NULL; + + spdxtool_serialize_value_t *value = calloc(1, sizeof(spdxtool_serialize_value_t)); + if (!value) + { + free(sv); + return NULL; + } + + value->type = SPDXTOOL_SERIALIZE_TYPE_STRING; + value->value.s = sv; + return value; +} + +/* + * !doc + * + * .. c:function:: spdxtool_serialize_value_t *spdxtool_serialize_value_int(int d) + * + * Construct a JSON integer value. + * If this return value is not stolen, it must be freed with spdxtool_serialize_value_free(). + * + * :param int d: int value. + * :return: spdxtool_serialize_value_t * of type SPDXTOOL_SERIALIZE_TYPE_INT. + */ +static inline spdxtool_serialize_value_t * +spdxtool_serialize_value_int(int i) +{ + spdxtool_serialize_value_t *value = calloc(1, sizeof(spdxtool_serialize_value_t)); + if (!value) + return NULL; + + value->type = SPDXTOOL_SERIALIZE_TYPE_INT; + value->value.i = i; + return value; +} + +/* + * !doc + * + * .. c:function:: spdxtool_serialize_value_t *spdxtool_serialize_value_bool(bool b) + * + * Construct a JSON boolean value. + * If this return value is not stolen, it must be freed with spdxtool_serialize_value_free(). + * + * :param bool b: Boolean value. + * :return: spdxtool_serialize_value_t * of type SPDXTOOL_SERIALIZE_TYPE_BOOL. + */ +static inline spdxtool_serialize_value_t * +spdxtool_serialize_value_bool(bool b) +{ + spdxtool_serialize_value_t *value = calloc(1, sizeof(spdxtool_serialize_value_t)); + if (!value) + return NULL; + + value->type = SPDXTOOL_SERIALIZE_TYPE_BOOL; + value->value.b = b; + return value; +} + +/* + * !doc + * + * .. c:function:: spdxtool_serialize_value_t *spdxtool_serialize_value_null(void) + * + * Construct a JSON null value. + * If this return value is not stolen, it must be freed with spdxtool_serialize_value_free(). + * + * :return: spdxtool_serialize_value_t * of type SPDXTOOL_SERIALIZE_TYPE_NULL. + */ +static inline spdxtool_serialize_value_t * +spdxtool_serialize_value_null(void) +{ + spdxtool_serialize_value_t *value = calloc(1, sizeof(spdxtool_serialize_value_t)); + if (!value) + return NULL; + + value->type = SPDXTOOL_SERIALIZE_TYPE_NULL; + return value; +} + + +/* + * !doc + * + * .. c:function:: spdxtool_serialize_value_t *spdxtool_serialize_value_object(spdxtool_serialize_object_list_t *object_list) + * + * Construct a JSON object value wrapping an existing object list. + * The returned value takes ownership of the object list. + * If this return value is not stolen, it must be freed with spdxtool_serialize_value_free(). + * + * :param spdxtool_serialize_object_list_t *object_list: Object list to wrap. Ownership transfers to the returned value. + * :return: spdxtool_serialize_value_t * of type SPDXTOOL_SERIALIZE_TYPE_OBJECT. + */ +static inline spdxtool_serialize_value_t * +spdxtool_serialize_value_object(spdxtool_serialize_object_list_t *object_list) +{ + spdxtool_serialize_value_t *value = calloc(1, sizeof(spdxtool_serialize_value_t)); + if (!value) + return NULL; + + value->type = SPDXTOOL_SERIALIZE_TYPE_OBJECT; + value->value.o = object_list; + return value; +} + +/* + * !doc + * + * .. c:function:: spdxtool_serialize_value_t *spdxtool_serialize_value_array(spdxtool_serialize_array_t *array) + * + * Construct a JSON array value wrapping an existing array. + * The returned value takes ownership of the array. + * If this return value is not stolen, it must be freed with spdxtool_serialize_value_free(). + * + * :param spdxtool_serialize_array_t *array: Array to wrap. Ownership transfers to the returned value. + * :return: spdxtool_serialize_value_t * of type SPDXTOOL_SERIALIZE_TYPE_ARRAY. + */ +static inline spdxtool_serialize_value_t * +spdxtool_serialize_value_array(spdxtool_serialize_array_t *array) +{ + spdxtool_serialize_value_t *value = calloc(1, sizeof(spdxtool_serialize_value_t)); + if (!value) + return NULL; + + value->type = SPDXTOOL_SERIALIZE_TYPE_ARRAY; + value->value.a = array; + return value; +} + +/* + * !doc + * + * .. c:function:: void spdxtool_serialize_object_add_string(spdxtool_serialize_object_list_t *object_list, const char *key, const char *value) + * + * Add a string key-value pair to a JSON object. The string is copied internally. + * Unconditionally adds the key even if value is NULL. + * + * :param spdxtool_serialize_object_list_t *object_list: Object list to add to. + * :param const char *key: Key string. + * :param const char *value: String value to copy. + * :return: spdxtool_serialize_value_t * of type SPDXTOOL_SERIALIZE_TYPE_STRING, located in the object. + * This object is not owned by the caller. + */ +static inline spdxtool_serialize_value_t * +spdxtool_serialize_object_add_string(spdxtool_serialize_object_list_t *object_list, const char *key, const char *value) +{ + return spdxtool_serialize_object_add_take(object_list, key, spdxtool_serialize_value_string(value)); +} + +/* + * !doc + * + * .. c:function:: spdxtool_serialize_value_t *spdxtool_serialize_object_add_string_opt(spdxtool_serialize_object_list_t *object_list, const char *key, const char *value) + * + * Add a string key-value pair to a JSON object only if value is non-NULL. + * Use this for optional fields that should be omitted entirely when absent. + * + * :param spdxtool_serialize_object_list_t *object_list: Object list to add to. + * :param const char *key: Key string. + * :param const char *value: String value to copy, or NULL to skip. + * :return: If value is set: spdxtool_serialize_value_t * of type SPDXTOOL_SERIALIZE_TYPE_STRING, located in the object. + * This object is not owned by the caller. + * If value is not set: NULL. + */ +static inline spdxtool_serialize_value_t * +spdxtool_serialize_object_add_string_opt(spdxtool_serialize_object_list_t *object_list, const char *key, const char *value) +{ + if (value) + return spdxtool_serialize_object_add_take(object_list, key, spdxtool_serialize_value_string(value)); + + return NULL; +} + +/* + * !doc + * + * .. c:function:: spdxtool_serialize_value_t *spdxtool_serialize_object_add_int(spdxtool_serialize_object_list_t *object_list, const char *key, int value) + * + * Add a int key-value pair to a JSON object. + * + * :param spdxtool_serialize_object_list_t *object_list: Object list to add to. + * :param const char *key: Key string. + * :param int value: Integer value. + * :return: spdxtool_serialize_value_t * of type SPDXTOOL_SERIALIZE_TYPE_INT, located in the object. + * This object is not owned by the caller. + */ +static inline spdxtool_serialize_value_t * +spdxtool_serialize_object_add_int(spdxtool_serialize_object_list_t *object_list, const char *key, int value) +{ + return spdxtool_serialize_object_add_take(object_list, key, spdxtool_serialize_value_int(value)); +} + +/* + * !doc + * + * .. c:function:: spdxtool_serialize_value_t *spdxtool_serialize_object_add_bool(spdxtool_serialize_object_list_t *object_list, const char *key, bool value) + * + * Add a boolean key-value pair to a JSON object. + * + * :param spdxtool_serialize_object_list_t *object_list: Object list to add to. + * :param const char *key: Key string. + * :param bool value: Boolean value. + * :return: spdxtool_serialize_value_t * of type SPDXTOOL_SERIALIZE_TYPE_BOOL, located in the object. + * This object is not owned by the caller. + */ +static inline spdxtool_serialize_value_t * +spdxtool_serialize_object_add_bool(spdxtool_serialize_object_list_t *object_list, const char *key, bool value) +{ + return spdxtool_serialize_object_add_take(object_list, key, spdxtool_serialize_value_bool(value)); +} + +/* + * !doc + * + * .. c:function:: spdxtool_serialize_value_t *spdxtool_serialize_object_add_null(spdxtool_serialize_object_list_t *object_list, const char *key) + * + * Add a null key-value pair to a JSON object. + * + * :param spdxtool_serialize_object_list_t *object_list: Object list to add to. + * :param const char *key: Key string. + * :return: spdxtool_serialize_value_t * of type SPDXTOOL_SERIALIZE_TYPE_NULL, located in the object. + * This object is not owned by the caller. + */ +static inline spdxtool_serialize_value_t * +spdxtool_serialize_object_add_null(spdxtool_serialize_object_list_t *object_list, const char *key) +{ + return spdxtool_serialize_object_add_take(object_list, key, spdxtool_serialize_value_null()); +} + +/* + * !doc + * + * .. c:function:: spdxtool_serialize_value_t *spdxtool_serialize_object_add_object(spdxtool_serialize_object_list_t *object_list, const char *key, spdxtool_serialize_object_list_t *value) + * + * Add an object key-value pair to a JSON object. + * This takes ownership of the object in value unconditionally, freeing on failure. + * + * :param spdxtool_serialize_object_list_t *object_list: Object list to add to. + * :param const char *key: Key string. + * :param spdxtool_serialize_object_list_t *value: Object value to add. + * :return: spdxtool_serialize_value_t * of type SPDXTOOL_SERIALIZE_TYPE_OBJECT, located in the object. + * This object is not owned by the caller. + */ +static inline spdxtool_serialize_value_t * +spdxtool_serialize_object_add_object(spdxtool_serialize_object_list_t *object_list, const char *key, spdxtool_serialize_object_list_t *value) +{ + return spdxtool_serialize_object_add_take(object_list, key, spdxtool_serialize_value_object(value)); +} + +/* + * !doc + * + * .. c:function:: spdxtool_serialize_value_t *spdxtool_serialize_object_add_array(spdxtool_serialize_object_list_t *object_list, const char *key, spdxtool_serialize_array_t *value) + * + * Add an array key-value pair to a JSON object. + * This takes ownership of the array in value unconditionally, freeing on failure. + * + * :param spdxtool_serialize_object_list_t *object_list: Object list to add to. + * :param const char *key: Key string. + * :param spdxtool_serialize_array_t *value: Array value to add. + * :return: spdxtool_serialize_value_t * of type SPDXTOOL_SERIALIZE_TYPE_ARRAY, located in the object. + * This object is not owned by the caller. + */ +static inline spdxtool_serialize_value_t * +spdxtool_serialize_object_add_array(spdxtool_serialize_object_list_t *object_list, const char *key, spdxtool_serialize_array_t *value) +{ + return spdxtool_serialize_object_add_take(object_list, key, spdxtool_serialize_value_array(value)); +} + +/* + * !doc + * + * .. c:function:: spdxtool_serialize_value_t *spdxtool_serialize_array_add_string(spdxtool_serialize_array_t *array, const char *value) + * + * Append a string value to a JSON array. The string is copied internally. + * Unconditionally appends even if value is NULL. + * + * :param spdxtool_serialize_array_t *array: Array to append to. + * :param const char *value: String value to copy. + * :return: spdxtool_serialize_value_t * of type SPDXTOOL_SERIALIZE_TYPE_STRING, located in the array. + * This object is not owned by the caller. + */ +static inline spdxtool_serialize_value_t * +spdxtool_serialize_array_add_string(spdxtool_serialize_array_t *array, const char *value) +{ + return spdxtool_serialize_array_add_take(array, spdxtool_serialize_value_string(value)); +} + +/* + * !doc + * + * .. c:function:: spdxtool_serialize_value_t *spdxtool_serialize_array_add_string_opt(spdxtool_serialize_array_t *a, const char *value) + * + * Append a string value to a JSON array only if value is non-NULL. + * Use this for optional array entries that should be omitted when absent. + * + * :param spdxtool_serialize_array_t *a: Array to append to. + * :param const char *value: String value to copy, or NULL to skip. + * :return: If value is set: spdxtool_serialize_value_t * of type SPDXTOOL_SERIALIZE_TYPE_STRING, located in the array. + * This object is not owned by the caller. + * If value is not set: NULL. + */ +static inline spdxtool_serialize_value_t * +spdxtool_serialize_array_add_string_opt(spdxtool_serialize_array_t *array, const char *value) +{ + if (value) + return spdxtool_serialize_array_add_take(array, spdxtool_serialize_value_string(value)); + + return NULL; +} + +/* + * !doc + * + * .. c:function:: spdxtool_serialize_value_t *spdxtool_serialize_array_add_int(spdxtool_serialize_array_t *array, int value) + * + * Append a int value to a JSON array. + * + * :param spdxtool_serialize_array_t *array: Array to append to. + * :param int value: integer value. + * :return: spdxtool_serialize_value_t * of type SPDXTOOL_SERIALIZE_TYPE_INT, located in the array. + * This object is not owned by the caller. + */ +static inline spdxtool_serialize_value_t * +spdxtool_serialize_array_add_int(spdxtool_serialize_array_t *array, int value) +{ + return spdxtool_serialize_array_add_take(array, spdxtool_serialize_value_int(value)); +} + +/* + * !doc + * + * .. c:function:: spdxtool_serialize_value_t *spdxtool_serialize_array_add_bool(spdxtool_serialize_array_t *array, bool value) + * + * Append a boolean value to a JSON array. + * + * :param spdxtool_serialize_array_t *array: Array to append to. + * :param bool value: Boolean value. + * :return: spdxtool_serialize_value_t * of type SPDXTOOL_SERIALIZE_TYPE_BOOL, located in the array. + * This object is not owned by the caller. + */ +static inline spdxtool_serialize_value_t * +spdxtool_serialize_array_add_bool(spdxtool_serialize_array_t *array, bool value) +{ + return spdxtool_serialize_array_add_take(array, spdxtool_serialize_value_bool(value)); +} + +/* + * !doc + * + * .. c:function:: spdxtool_serialize_value_t *spdxtool_serialize_array_add_null(spdxtool_serialize_array_t *array) + * + * Append a null value to a JSON array. + * + * :param spdxtool_serialize_array_t *array: Array to append to. + * :return: spdxtool_serialize_value_t * of type SPDXTOOL_SERIALIZE_TYPE_NULL, located in the array. + * This object is not owned by the caller. + */ +static inline spdxtool_serialize_value_t * +spdxtool_serialize_array_add_null(spdxtool_serialize_array_t *array) +{ + return spdxtool_serialize_array_add_take(array, spdxtool_serialize_value_null()); +} + +/* + * !doc + * + * .. c:function:: spdxtool_serialize_value_t *spdxtool_serialize_array_add_object(spdxtool_serialize_array_t *array, spdxtool_serialize_object_list_t *value) + * + * Append an object value to a JSON array. + * This takes ownership of the object in value unconditionally, freeing on failure. + * + * :param spdxtool_serialize_array_t *array: Array to append to. + * :param spdxtool_serialize_object_list_t *value: Object value. + * :return: spdxtool_serialize_value_t * of type SPDXTOOL_SERIALIZE_TYPE_OBJECT, located in the array. + * This object is not owned by the caller. + */ +static inline spdxtool_serialize_value_t * +spdxtool_serialize_array_add_object(spdxtool_serialize_array_t *array, spdxtool_serialize_object_list_t *value) +{ + if (!value) + return NULL; + + spdxtool_serialize_value_t *ret = spdxtool_serialize_value_object(value); + if (!ret) + { + // Since we take possession of the pointer unconditionally, clean up. + spdxtool_serialize_object_list_free(value); + return NULL; + } + + return spdxtool_serialize_array_add_take(array, ret); +} + +/* + * !doc + * + * .. c:function:: spdxtool_serialize_value_t *spdxtool_serialize_array_add_array(spdxtool_serialize_array_t *array, spdxtool_serialize_array_t *value) + * + * Append an array value to a JSON array. + * This takes ownership of the array in value unconditionally, freeing on failure. + * + * :param spdxtool_serialize_array_t *array: Array to append to. + * :param spdxtool_serialize_array_t *value: Array value. + * :return: spdxtool_serialize_value_t * of type SPDXTOOL_SERIALIZE_TYPE_ARRAY, located in the array. + * This object is not owned by the caller. + */ +static inline spdxtool_serialize_value_t * +spdxtool_serialize_array_add_array(spdxtool_serialize_array_t *array, spdxtool_serialize_array_t *value) +{ + if (!value) + return NULL; + + spdxtool_serialize_value_t *ret = spdxtool_serialize_value_array(value); + if (!ret) + { + // Since we take possession of the pointer unconditionally, clean up. + spdxtool_serialize_array_free(value); + return NULL; + } + + return spdxtool_serialize_array_add_take(array, ret); +} + +spdxtool_serialize_value_t * +spdxtool_serialize_sbom(pkgconf_client_t *client, spdxtool_core_agent_t *agent, spdxtool_core_creation_info_t *creation, spdxtool_core_spdx_document_t *spdx); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/cli/spdxtool/simplelicensing.c b/cli/spdxtool/simplelicensing.c new file mode 100644 index 000000000000..a9823b9b3b6c --- /dev/null +++ b/cli/spdxtool/simplelicensing.c @@ -0,0 +1,113 @@ +/* + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2025 The FreeBSD Foundation + * + * Portions of this software were developed by + * Tuukka Pasanen <tuukka.pasanen@ilmi.fi> under sponsorship from + * the FreeBSD Foundation + */ + +#include <stdlib.h> +#include <string.h> +#include "util.h" +#include "serialize.h" +#include "software.h" +#include "core.h" +#include "simplelicensing.h" + +/* + * !doc + * + * .. c:function:: spdxtool_simplelicensing_license_expression_t *spdxtool_simplelicensing_licenseExpression_new(pkgconf_client_t *client, char *license) + * + * Create new /SimpleLicensing/SimpleLicensingText struct + * + * :param pkgconf_client_t *client: The pkgconf client being accessed. + * :param char *license: SPDX name of license + * :return: NULL if some problem occurs and SimpleLicensingText struct if not + */ +spdxtool_simplelicensing_license_expression_t * +spdxtool_simplelicensing_licenseExpression_new(pkgconf_client_t *client, const char *license) +{ + if (!client || !license) + return NULL; + + spdxtool_simplelicensing_license_expression_t *expression = calloc(1, sizeof(spdxtool_simplelicensing_license_expression_t)); + if (!expression) + goto err; + + expression->type = "simplelicensing_LicenseExpression"; + expression->license_expression = strdup(license); + expression->spdx_id = spdxtool_util_get_spdx_id_string(client, expression->type, license); + + if (!expression->license_expression || !expression->spdx_id) + goto err; + + return expression; + +err: + pkgconf_error(client, "spdxtool_simplelicensing_licenseExpression_new: out of memory"); + spdxtool_simplelicensing_licenseExpression_free(expression); + return NULL; +} + +/* + * !doc + * + * .. c:function:: void spdxtool_simplelicensing_licenseExpression_free(spdxtool_simplelicensing_license_expression_t *expression) + * + * Free /SimpleLicensing/SimpleLicensingText struct + * + * :param spdxtool_simplelicensing_license_expression_t *expression: SimpleLicensingText struct to be freed. + * :return: nothing + */ +void +spdxtool_simplelicensing_licenseExpression_free(spdxtool_simplelicensing_license_expression_t *expression) +{ + if(!expression) + return; + + free(expression->spdx_id); + free(expression->license_expression); + + free(expression); +} + +/* + * !doc + * + * .. c:function:: spdxtool_serialize_value_t *spdxtool_simplelicensing_licenseExpression_to_object(const char *creation_info, const spdxtool_simplelicensing_license_expression_t *expression) + * + * Serialize /SimpleLicensing/LicenseExpression struct to a JSON value tree. + * + * :param const char *creation_info: The creationInfo ID string to embed in the object. + * :param const spdxtool_simplelicensing_license_expression_t *expression: LicenseExpression struct to be serialized. + * :return: spdxtool_serialize_value_t * representing the LicenseExpression object. + */ +spdxtool_serialize_value_t * +spdxtool_simplelicensing_licenseExpression_to_object(pkgconf_client_t *client, const char *creation_info, const spdxtool_simplelicensing_license_expression_t *expression) +{ + spdxtool_serialize_value_t *ret = NULL; + spdxtool_serialize_object_list_t *object_list = spdxtool_serialize_object_list_new(); + if (!object_list) + goto err; + + if (!(spdxtool_serialize_object_add_string(object_list, "type", "simplelicensing_LicenseExpression") && + spdxtool_serialize_object_add_string(object_list, "creationInfo", creation_info) && + spdxtool_serialize_object_add_string(object_list, "spdxId", expression->spdx_id) && + spdxtool_serialize_object_add_string(object_list, "simplelicensing_licenseExpression", expression->license_expression))) + { + goto err; + } + + ret = spdxtool_serialize_value_object(object_list); + object_list = NULL; + +err: + if (!ret) + pkgconf_error(client, "spdxtool_simplelicensing_licenseExpression_to_object: out of memory"); + + spdxtool_serialize_object_list_free(object_list); + return ret; +} diff --git a/cli/spdxtool/simplelicensing.h b/cli/spdxtool/simplelicensing.h new file mode 100644 index 000000000000..328136c620c7 --- /dev/null +++ b/cli/spdxtool/simplelicensing.h @@ -0,0 +1,36 @@ +/* + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2025 The FreeBSD Foundation + * + * Portions of this software were developed by + * Tuukka Pasanen <tuukka.pasanen@ilmi.fi> under sponsorship from + * the FreeBSD Foundation + */ + +#include <stdlib.h> +#include <string.h> +#include "util.h" + +#ifndef CLI__SPDXTOOL__SIMPLELICENSING_H +#define CLI__SPDXTOOL__SIMPLELICENSING_H + + +#ifdef __cplusplus +extern "C" { +#endif + +spdxtool_simplelicensing_license_expression_t * +spdxtool_simplelicensing_licenseExpression_new(pkgconf_client_t *client, const char *license); + +void +spdxtool_simplelicensing_licenseExpression_free(spdxtool_simplelicensing_license_expression_t *expression); + +spdxtool_serialize_value_t * +spdxtool_simplelicensing_licenseExpression_to_object(pkgconf_client_t *client, const char *creation_info, const spdxtool_simplelicensing_license_expression_t *expression); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/cli/spdxtool/software.c b/cli/spdxtool/software.c new file mode 100644 index 000000000000..4e9712ba6942 --- /dev/null +++ b/cli/spdxtool/software.c @@ -0,0 +1,447 @@ +/* + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2025 The FreeBSD Foundation + * + * Portions of this software were developed by + * Tuukka Pasanen <tuukka.pasanen@ilmi.fi> under sponsorship from + * the FreeBSD Foundation + */ + +#include <stdlib.h> +#include <string.h> +#include <libpkgconf/libpkgconf.h> +#include "util.h" +#include "serialize.h" +#include "software.h" +#include "core.h" + +/* + * !doc + * + * .. c:function:: spdxtool_software_sbom_t *spdxtool_software_sbom_new(pkgconf_client_t *client, const char *spdx_id, const char *creation_id, const char *sbom_type) + * + * Create new /Software/Sbom struct + * + * :param pkgconf_client_t *client: The pkgconf client being accessed. + * :param const char *spdx_id: spdxId for this SBOM element + * :param const char *creation_id: id for CreationInfo + * :param const char *sbom_type: Sbom types can be found SPDX documention + * :return: NULL if some problem occurs and Sbom struct if not + */ +spdxtool_software_sbom_t * +spdxtool_software_sbom_new(pkgconf_client_t *client, const char *spdx_id, const char *creation_id, const char *sbom_type) +{ + if (!client || !spdx_id || !creation_id || !sbom_type) + return NULL; + + spdxtool_software_sbom_t *sbom = calloc(1, sizeof(spdxtool_software_sbom_t)); + if (!sbom) + goto err; + + sbom->type = "software_Sbom"; + sbom->spdx_id = strdup(spdx_id); + sbom->creation_info = strdup(creation_id); + sbom->sbom_type = strdup(sbom_type); + + if (!sbom->spdx_id || !sbom->creation_info || !sbom->sbom_type) + goto err; + + return sbom; + +err: + pkgconf_error(client, "spdxtool_software_sbom_new: out of memory"); + spdxtool_software_sbom_free(sbom); + return NULL; +} + +/* + * !doc + * + * .. c:function:: void spdxtool_software_sbom_free(spdxtool_software_sbom_t *sbom) + * + * Free /Software/Sbom struct + * + * :param spdxtool_software_sbom_t *sbom: Sbom struct to be freed. + * :return: nothing + */ +void +spdxtool_software_sbom_free(spdxtool_software_sbom_t *sbom) +{ + if(!sbom) + return; + + free(sbom->spdx_id); + free(sbom->creation_info); + free(sbom->sbom_type); + + free(sbom); +} + +/* + * !doc + * + * .. c:function:: spdxtool_serialize_value_t *spdxtool_software_sbom_to_object(pkgconf_client_t *client, spdxtool_software_sbom_t *sbom) + * + * Serialize /Software/Sbom struct to a JSON value tree. As a side effect, + * the package associated with the SBOM's rootElement is registered on the + * document via spdxtool_core_spdx_document_add_package, and relationship + * element IDs are registered via spdxtool_core_spdx_document_add_element. + * + * :param pkgconf_client_t *client: The pkgconf client being accessed. + * :param spdxtool_software_sbom_t *sbom: Sbom struct to be serialized. + * :return: spdxtool_serialize_value_t * representing the Sbom object. + */ +spdxtool_serialize_value_t * +spdxtool_software_sbom_to_object(pkgconf_client_t *client, spdxtool_software_sbom_t *sbom) +{ + spdxtool_serialize_value_t *ret = NULL; + spdxtool_serialize_object_list_t *object_list = NULL; + spdxtool_serialize_array_t *sbom_type_array = NULL; + spdxtool_serialize_array_t *root_element_array = NULL; + spdxtool_serialize_array_t *element_array = NULL; + char *spdx_id = NULL; + + char sep = spdxtool_util_get_uri_separator(client); + + spdx_id = spdxtool_util_tuple_lookup(client, &sbom->rootElement->vars, "spdxId"); + if (!spdx_id) + goto err; + + object_list = spdxtool_serialize_object_list_new(); + if (!object_list) + goto err; + + sbom_type_array = spdxtool_serialize_array_new(); + if (!sbom_type_array) + goto err; + + if (!spdxtool_serialize_array_add_string(sbom_type_array, sbom->sbom_type)) + goto err; + + root_element_array = spdxtool_serialize_array_new(); + if (!root_element_array) + goto err; + + if (!spdxtool_serialize_array_add_string(root_element_array, spdx_id)) + goto err; + + element_array = spdxtool_serialize_array_new(); + if (!element_array) + goto err; + + pkgconf_node_t *node = NULL; + PKGCONF_FOREACH_LIST_ENTRY(sbom->rootElement->required.head, node) + { + pkgconf_dependency_t *dep = node->data; + pkgconf_pkg_t *match = dep->match; + pkgconf_buffer_t relationship_buf = PKGCONF_BUFFER_INITIALIZER; + + pkgconf_buffer_append_fmt(&relationship_buf, "%s%cdependsOn%c%s", sbom->rootElement->id, sep, sep, match->id); + char *relationship_str = pkgconf_buffer_freeze(&relationship_buf); + if (!relationship_str) + goto err; + + char *spdx_id_relation = spdxtool_util_get_spdx_id_string(client, "Relationship", relationship_str); + free(relationship_str); + if (!spdx_id_relation) + goto err; + + if (!spdxtool_serialize_array_add_string(element_array, spdx_id_relation)) + { + free(spdx_id_relation); + goto err; + } + + if (!spdxtool_core_spdx_document_add_element(client, sbom->spdx_document, spdx_id_relation)) + { + free(spdx_id_relation); + goto err; + } + + free(spdx_id_relation); + } + + char *value = spdxtool_util_tuple_lookup(client, &sbom->rootElement->vars, "hasDeclaredLicense"); + if (value) + { + if (!spdxtool_serialize_array_add_string(element_array, value)) + { + free(value); + goto err; + } + + if (!spdxtool_core_spdx_document_add_element(client, sbom->spdx_document, value)) + { + free(value); + goto err; + } + + free(value); + } + + value = spdxtool_util_tuple_lookup(client, &sbom->rootElement->vars, "hasConcludedLicense"); + if (value) + { + if (!spdxtool_serialize_array_add_string(element_array, value)) + { + free(value); + goto err; + } + + if (!spdxtool_core_spdx_document_add_element(client, sbom->spdx_document, value)) + { + free(value); + goto err; + } + + free(value); + } + + if (!(spdxtool_serialize_object_add_string(object_list, "type", sbom->type) && + spdxtool_serialize_object_add_string(object_list, "creationInfo", sbom->creation_info) && + spdxtool_serialize_object_add_string(object_list, "spdxId", sbom->spdx_id))) + { + goto err; + } + + if (!spdxtool_serialize_object_add_array(object_list, "software_sbomType", sbom_type_array)) + goto err; + sbom_type_array = NULL; + + if (!spdxtool_serialize_object_add_array(object_list, "rootElement", root_element_array)) + goto err; + root_element_array = NULL; + + if (!spdxtool_serialize_object_add_array(object_list, "element", element_array)) + goto err; + element_array = NULL; + + if (!spdxtool_core_spdx_document_add_package(client, sbom->spdx_document, sbom->rootElement)) + goto err; + + ret = spdxtool_serialize_value_object(object_list); + object_list = NULL; + +err: + if (!ret) + pkgconf_error(client, "spdxtool_software_sbom_to_object: out of memory"); + + free(spdx_id); + spdxtool_serialize_object_list_free(object_list); + spdxtool_serialize_array_free(sbom_type_array); + spdxtool_serialize_array_free(root_element_array); + spdxtool_serialize_array_free(element_array); + return ret; +} + +static bool +serialize_copyright_lines_to_object(spdxtool_serialize_object_list_t *object_list, const pkgconf_list_t *copyright_lines) +{ + pkgconf_buffer_t copyright_buf = PKGCONF_BUFFER_INITIALIZER; + const pkgconf_node_t *node; + + if (copyright_lines->head == NULL) + return spdxtool_serialize_object_add_string(object_list, "software_copyrightText", "NOASSERTION") != NULL; + + PKGCONF_FOREACH_LIST_ENTRY(copyright_lines->head, node) + { + const pkgconf_bufferset_t *set = node->data; + pkgconf_buffer_join(©right_buf, '\n', pkgconf_buffer_str_or_empty(&set->buffer), NULL); + } + + bool ok = spdxtool_serialize_object_add_string(object_list, "software_copyrightText", pkgconf_buffer_str_or_empty(©right_buf)) != NULL; + pkgconf_buffer_finalize(©right_buf); + return ok; +} + +/* + * !doc + * + * .. c:function:: spdxtool_serialize_value_t *spdxtool_software_package_to_object(pkgconf_client_t *client, pkgconf_pkg_t *pkg, spdxtool_core_spdx_document_t *spdx) + * + * Serialize /Software/Package struct to a JSON value tree. As a side effect, + * any license and dependency relationships generated during serialization are + * added to the document via spdxtool_core_spdx_document_add_relationship. + * + * :param pkgconf_client_t *client: The pkgconf client being accessed. + * :param pkgconf_pkg_t *pkg: Package struct to be serialized. + * :param spdxtool_core_spdx_document_t *spdx: SpdxDocument to which generated relationships are added. + * :return: spdxtool_serialize_value_t * representing the Package object. + */ +spdxtool_serialize_value_t * +spdxtool_software_package_to_object(pkgconf_client_t *client, pkgconf_pkg_t *pkg, spdxtool_core_spdx_document_t *spdx) +{ + spdxtool_serialize_value_t *ret = NULL; + spdxtool_serialize_object_list_t *object_list = NULL; + spdxtool_serialize_array_t *originated_by = NULL; + char *creation_info = NULL; + char *spdx_id = NULL; + char *agent = NULL; + char *spdx_id_license = NULL; + pkgconf_list_t relations = PKGCONF_LIST_INITIALIZER; + pkgconf_list_t *cpy_relations = NULL; + pkgconf_node_t *node = NULL; + char sep = spdxtool_util_get_uri_separator(client); + + creation_info = spdxtool_util_tuple_lookup(client, &pkg->vars, "creationInfo"); + spdx_id = spdxtool_util_tuple_lookup(client, &pkg->vars, "spdxId"); + agent = spdxtool_util_tuple_lookup(client, &pkg->vars, "agent"); + + if (!creation_info || !spdx_id || !agent) + goto err; + + object_list = spdxtool_serialize_object_list_new(); + if (!object_list) + goto err; + + originated_by = spdxtool_serialize_array_new(); + if (!originated_by) + goto err; + + if (!spdxtool_serialize_array_add_string(originated_by, agent)) + goto err; + + if (!(spdxtool_serialize_object_add_string(object_list, "type", "software_Package") && + spdxtool_serialize_object_add_string(object_list, "creationInfo", creation_info) && + spdxtool_serialize_object_add_string(object_list, "spdxId", spdx_id) && + spdxtool_serialize_object_add_string(object_list, "name", pkg->realname))) + { + goto err; + } + + if (!spdxtool_serialize_object_add_array(object_list, "originatedBy", originated_by)) + goto err; + originated_by = NULL; + + if (!serialize_copyright_lines_to_object(object_list, &pkg->copyright)) + goto err; + + if (!spdxtool_serialize_object_add_string(object_list, "software_homePage", + pkg->url ? pkg->url : "")) + { + goto err; + } + + if (!spdxtool_serialize_object_add_string(object_list, "software_downloadLocation", + pkg->source ? pkg->source : "")) + { + goto err; + } + + if (!spdxtool_serialize_object_add_string(object_list, "software_packageVersion", pkg->version)) + goto err; + + PKGCONF_FOREACH_LIST_ENTRY(pkg->license.head, node) + { + const pkgconf_license_t *license = node->data; + if (license->type == PKGCONF_LICENSE_EXPRESSION) + { + spdx_id_license = spdxtool_util_get_spdx_id_string(client, "simplelicensing_LicenseExpression", license->data); + if (!spdx_id_license) + goto err; + + pkgconf_license_insert(client, &relations, PKGCONF_LICENSE_UNKNOWN, spdx_id_license); + free(spdx_id_license); + spdx_id_license = NULL; + } + } + + char *tuple_license = spdxtool_util_tuple_lookup(client, &pkg->vars, "hasDeclaredLicense"); + if (tuple_license) + { + cpy_relations = calloc(1, sizeof(pkgconf_list_t)); + if (!cpy_relations) + { + free(tuple_license); + goto err; + } + + pkgconf_license_copy_list(client, cpy_relations, &relations); + spdxtool_core_relationship_t *relationship = spdxtool_core_relationship_new(client, creation_info, tuple_license, spdx_id, cpy_relations, "hasDeclaredLicense"); + free(tuple_license); + if (!relationship) + goto err; + if (!spdxtool_core_spdx_document_add_relationship(client, spdx, relationship)) + goto err; + cpy_relations = NULL; + } + + tuple_license = spdxtool_util_tuple_lookup(client, &pkg->vars, "hasConcludedLicense"); + if (tuple_license) + { + cpy_relations = calloc(1, sizeof(pkgconf_list_t)); + if (!cpy_relations) + { + free(tuple_license); + goto err; + } + + pkgconf_license_copy_list(client, cpy_relations, &relations); + spdxtool_core_relationship_t *relationship = spdxtool_core_relationship_new(client, creation_info, tuple_license, spdx_id, cpy_relations, "hasConcludedLicense"); + free(tuple_license); + if (!relationship) + goto err; + if (!spdxtool_core_spdx_document_add_relationship(client, spdx, relationship)) + goto err; + cpy_relations = NULL; + } + pkgconf_license_free(&relations); + + PKGCONF_FOREACH_LIST_ENTRY(pkg->required.head, node) + { + pkgconf_dependency_t *dep = node->data; + pkgconf_pkg_t *match = dep->match; + pkgconf_buffer_t relationship_buf = PKGCONF_BUFFER_INITIALIZER; + + pkgconf_buffer_append_fmt(&relationship_buf, "%s%cdependsOn%c%s", pkg->id, sep, sep, match->id); + char *relationship_str = pkgconf_buffer_freeze(&relationship_buf); + if (!relationship_str) + goto err; + + char *spdx_id_relation = spdxtool_util_get_spdx_id_string(client, "Relationship", relationship_str); + free(relationship_str); + if (!spdx_id_relation) + goto err; + + char *spdx_id_package = spdxtool_util_get_spdx_id_string(client, "Package", match->id); + if (!spdx_id_package) + { + free(spdx_id_relation); + goto err; + } + + cpy_relations = calloc(1, sizeof(pkgconf_list_t)); + if (!cpy_relations) + { + free(spdx_id_relation); + free(spdx_id_package); + goto err; + } + + pkgconf_license_insert(client, cpy_relations, PKGCONF_LICENSE_UNKNOWN, spdx_id_package); + spdxtool_core_relationship_t *relationship = spdxtool_core_relationship_new(client, creation_info, spdx_id_relation, spdx_id, cpy_relations, "dependsOn"); + free(spdx_id_relation); + free(spdx_id_package); + if (!relationship) + goto err; + if (!spdxtool_core_spdx_document_add_relationship(client, spdx, relationship)) + goto err; + cpy_relations = NULL; + } + + ret = spdxtool_serialize_value_object(object_list); + object_list = NULL; + +err: + if (!ret) + pkgconf_error(client, "spdxtool_software_package_to_object: out of memory"); + + free(creation_info); + free(spdx_id); + free(agent); + free(spdx_id_license); + spdxtool_serialize_object_list_free(object_list); + spdxtool_serialize_array_free(originated_by); + return ret; +} diff --git a/cli/spdxtool/software.h b/cli/spdxtool/software.h new file mode 100644 index 000000000000..d18710cb0d1b --- /dev/null +++ b/cli/spdxtool/software.h @@ -0,0 +1,37 @@ +/* + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2025 The FreeBSD Foundation + * + * Portions of this software were developed by + * Tuukka Pasanen <tuukka.pasanen@ilmi.fi> under sponsorship from + * the FreeBSD Foundation + */ + +#ifndef CLI__SPDXTOOL_SOFTWARE_H +#define CLI__SPDXTOOL_SOFTWARE_H + +#include <stdlib.h> +#include "util.h" + +#ifdef __cplusplus +extern "C" { +#endif + +spdxtool_software_sbom_t * +spdxtool_software_sbom_new(pkgconf_client_t *client, const char *spdx_id, const char *creation_id, const char *sbom_type); + +void +spdxtool_software_sbom_free(spdxtool_software_sbom_t *sbom); + +spdxtool_serialize_value_t * +spdxtool_software_package_to_object(pkgconf_client_t *client, pkgconf_pkg_t *pkg, spdxtool_core_spdx_document_t *doc); + +spdxtool_serialize_value_t * +spdxtool_software_sbom_to_object(pkgconf_client_t *client, spdxtool_software_sbom_t *sbom); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/cli/spdxtool/util.c b/cli/spdxtool/util.c new file mode 100644 index 000000000000..e47bf0a1e61f --- /dev/null +++ b/cli/spdxtool/util.c @@ -0,0 +1,331 @@ +/* + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2025 The FreeBSD Foundation + * + * Portions of this software were developed by + * Tuukka Pasanen <tuukka.pasanen@ilmi.fi> under sponsorship from + * the FreeBSD Foundation + */ + +#include <libpkgconf/libpkgconf.h> +#include <libpkgconf/stdinc.h> +#include <string.h> +#include <stdlib.h> +#include <time.h> +#include <ctype.h> +#include "util.h" + +/* + * !doc + * + * .. c:function:: void spdxtool_util_set_key(pkgconf_client_t *client, const char *key, const char *key_value, const char *key_default) + * + * Set key wit default value. Default value is used if key is NULL + * + * :param pkgconf_client_t* client: The pkgconf client being accessed. + * :param const char *key: Key to be preserved + * :param const char *key_value: Value for key + * :param const char *key_default: Default value if key_value is NULL + * :return: nothing + */ +void +spdxtool_util_set_key(pkgconf_client_t *client, const char *key, const char *key_value, const char *key_default) +{ + PKGCONF_TRACE(client, "set uri_root to: %s", key_value != NULL ? key_value : key_default); + pkgconf_tuple_add_global(client, key, key_value != NULL ? key_value : key_default); +} + +/* + * !doc + * + * .. c:function:: void spdxtool_util_set_uri_root(pkgconf_client_t *client, const char *uri_root) + * + * Set URI/URL root for spdxId. Type for this is 'xsd:anyURI' which means + * it can they may be absolute or relative. + * + * :param pkgconf_client_t* client: The pkgconf client being accessed. + * :param const char *uri_root: URI root. + * :return: nothing + */ +void +spdxtool_util_set_uri_root(pkgconf_client_t *client, const char *uri_root) +{ + spdxtool_util_set_key(client, "spdx_uri_root", uri_root, "https://github.com/pkgconf/pkgconf"); +} + +/* + * !doc + * + * .. c:function:: const char *spdxtool_util_get_uri_root(pkgconf_client_t *client) + * + * Get current URI + * + * :param pkgconf_client_t* client: The pkgconf client being accessed. + * :return: URI or NULL + */ +const char * +spdxtool_util_get_uri_root(pkgconf_client_t *client) +{ + return pkgconf_tuple_find_global(client, "spdx_uri_root"); +} + +/* + * !doc + * + * .. c:function:: void spdxtool_util_set_uri_separator_colon(pkgconf_client_t *client, bool is_colon) + * + * when using URI rather than URL change separator from slash '/' to colon ':'. + * + * :param pkgconf_client_t* client: The pkgconf client being accessed. + * :param bool is_colon If true use colon ':' is separator and otherwise slash '/' + * :return: nothing + */ +void +spdxtool_util_set_uri_separator_colon(pkgconf_client_t *client, bool is_colon) +{ + if (is_colon == true) + client->flags |= SPDXTOOL_SEPARATOR_COLON; + else + client->flags = client->flags & ~SPDXTOOL_SEPARATOR_COLON; +} + +/* + * !doc + * + * .. c:function:: char spdxtool_util_get_uri_separator(pkgconf_client_t *client) + * + * Get separator char + * + * :param pkgconf_client_t* client: The pkgconf client being accessed. + * :return: Colon ':' or slash '/' + */ +char +spdxtool_util_get_uri_separator(pkgconf_client_t *client) +{ + if (client->flags & SPDXTOOL_SEPARATOR_COLON) + return ':'; + + return '/'; +} + +/* + * !doc + * + * .. c:function:: void spdxtool_util_set_spdx_version(pkgconf_client_t *client, const char *spdx_version) + * + * Set current SPDX SBOM Spec version. If not set it's 3.0.1 + * + * :param pkgconf_client_t* client: The pkgconf client being accessed. + * :param char* spdx_version: SPDX specification version. + * :return: nothing + */ +void +spdxtool_util_set_spdx_version(pkgconf_client_t *client, const char *spdx_version) +{ + spdxtool_util_set_key(client, "spdx_version", spdx_version, "3.0.1"); +} + +/* + * !doc + * + * .. c:function:: const char *spdxtool_util_get_spdx_version(pkgconf_client_t *client) + * + * Get SPDX SBOM specification version in use + * + * :param pkgconf_client_t* client: The pkgconf client being accessed. + * :return: Spec version + */ +const char * +spdxtool_util_get_spdx_version(pkgconf_client_t *client) +{ + return pkgconf_tuple_find_global(client, "spdx_version"); +} + +/* + * !doc + * + * .. c:function:: void spdxtool_util_set_spdx_license(pkgconf_client_t *client, const char *spdx_license) + * + * Under which license SBOM is released. License string should be in + * SPDX style. Something like: FreeBSD-DOC, CC0-1.0 or MIT + * + * :param pkgconf_client_t* client: The pkgconf client being accessed. + * :param const char *spdx_license: SPDX compatible license + * :return: nothing + */ +void +spdxtool_util_set_spdx_license(pkgconf_client_t *client, const char *spdx_license) +{ + spdxtool_util_set_key(client, "spdx_license", spdx_license, "CC0-1.0"); +} + +/* + * !doc + * + * .. c:function:: const char *spdxtool_util_get_spdx_license(pkgconf_client_t *client) + * + * Get license which SBOM is release in + * + * :param pkgconf_client_t* client: The pkgconf client being accessed. + * :return: SPDX license + */ +const char * +spdxtool_util_get_spdx_license(pkgconf_client_t *client) +{ + return pkgconf_tuple_find_global(client, "spdx_license"); +} + +static size_t last_id = 0; + +/* + * !doc + * + * .. c:function:: char *spdxtool_util_get_spdx_id_int(pkgconf_client_t *client, char *part) + * + * Get spdxId with current URI. + * URI is lookg like https://test/part/1 + * + * :param pkgconf_client_t* client: The pkgconf client being accessed. + * :param char *part: Addition subdir part before number + * :return: URI + */ +char * +spdxtool_util_get_spdx_id_int(pkgconf_client_t *client, const char *part) +{ + const char *global_xsd_any_uri = spdxtool_util_get_uri_root(client); + char sep = spdxtool_util_get_uri_separator(client); + pkgconf_buffer_t current_uri = PKGCONF_BUFFER_INITIALIZER; + + pkgconf_buffer_join(¤t_uri, sep, global_xsd_any_uri, part, NULL); + pkgconf_buffer_append_fmt(¤t_uri, "%c" SIZE_FMT_SPECIFIER, sep, ++last_id); + + return pkgconf_buffer_freeze(¤t_uri); +} + +/* + * !doc + * + * .. c:function:: char *spdxtool_util_get_spdx_id_string(pkgconf_client_t *client, const char *part, const char *string_id) + * + * Get string id URI + * looks something like: https://test/part/string_id + * + * :param pkgconf_client_t* client: The pkgconf client being accessed. + * :param const char* part: subdir part of URI. + * :param const char* string_id: String ID. + * :return: URI + */ +char * +spdxtool_util_get_spdx_id_string(pkgconf_client_t *client, const char *part, const char *string_id) +{ + const char *global_xsd_any_uri = spdxtool_util_get_uri_root(client); + char sep = spdxtool_util_get_uri_separator(client); + pkgconf_buffer_t current_uri = PKGCONF_BUFFER_INITIALIZER; + + pkgconf_buffer_join(¤t_uri, sep, global_xsd_any_uri, part, string_id, NULL); + + return pkgconf_buffer_freeze(¤t_uri); +} + +/* + * !doc + * + * .. c:function:: char *spdxtool_util_get_iso8601_time(time_t *wanted_time) + * + * Get ISO8601 time string + * + * :param time_t *wanted_time: Time to be converted + * :return: Time string in ISO8601 format + */ +char * +spdxtool_util_get_iso8601_time(time_t *wanted_time) +{ + char buf[PKGCONF_ITEM_SIZE]; + + if (!wanted_time) + return NULL; + + struct tm *tm_info = gmtime(wanted_time); + if (!tm_info) + return NULL; + + /* ISO8601 time with Z at the end */ + strftime(buf, sizeof buf, "%Y-%m-%dT%H:%M:%SZ", tm_info); + return strdup(buf); +} + +/* + * !doc + * + * .. c:function:: char *spdxtool_util_get_current_iso8601_time(void) + * + * Get ISO8601 current timestamp + * + * :return: Time string in ISO8601 format + */ +char * +spdxtool_util_get_current_iso8601_time(void) +{ + time_t now; + time(&now); + return spdxtool_util_get_iso8601_time(&now); +} + +/* + * !doc + * + * .. c:function:: char *spdxtool_util_string_correction(char *str) + * + * Lowercase string and change spaces to '_' + * + * :param char *str: String to be converted + * :return: Converted string + */ +char * +spdxtool_util_string_correction(char *str) +{ + char *ptr = str; + /* Lowecase string and make spaces '_' */ + for ( ; *ptr; ++ptr) + { + *ptr = (char) tolower(*ptr); + if(isspace((unsigned char) *ptr)) + { + *ptr = '_'; + } + } + + return str; +} + +/* + * !doc + * + * .. c:function:: char *spdxtool_util_tuple_lookup(pkgconf_client_t *client, pkgconf_list_t *vars, const char *key) + * + * Lowercase string and change spaces to '_' + * + * :param pkgconf_client_t *client: client to use for the lookup + * :param pkgconf_list_t *vars: locally-scoped vars to look through + * :param char *key: key to lookup + * :return: Converted string + */ +char * +spdxtool_util_tuple_lookup(pkgconf_client_t *client, pkgconf_list_t *vars, const char *key) +{ + pkgconf_buffer_t buf = PKGCONF_BUFFER_INITIALIZER; + pkgconf_bytecode_eval_ctx_t ctx = { + .client = client, + .vars = vars, + }; + + pkgconf_variable_t *v = pkgconf_bytecode_eval_lookup_var(&ctx, key, strlen(key)); + if (v == NULL) + return NULL; + + if (!pkgconf_variable_eval(client, vars, v, &buf, NULL)) + return NULL; + + return pkgconf_buffer_freeze(&buf); +} diff --git a/cli/spdxtool/util.h b/cli/spdxtool/util.h new file mode 100644 index 000000000000..be86d6e154d7 --- /dev/null +++ b/cli/spdxtool/util.h @@ -0,0 +1,130 @@ +/* + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2025 The FreeBSD Foundation + * + * Portions of this software were developed by + * Tuukka Pasanen <tuukka.pasanen@ilmi.fi> under sponsorship from + * the FreeBSD Foundation + */ + +#ifndef CLI__SPDXTOOL__UTIL_H +#define CLI__SPDXTOOL__UTIL_H + +#include <libpkgconf/libpkgconf.h> +#include <time.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#define SPDXTOOL_SEPARATOR_COLON 0x80 + +typedef struct spdxtool_core_agent_ +{ + char *spdx_id; + const char *type; + char *creation_info; + char *name; +} spdxtool_core_agent_t; + +typedef struct spdxtool_core_creation_info_ +{ + char *id; + const char *type; + char *created; + char *created_by; + const char *created_using; + char *spec_version; +} spdxtool_core_creation_info_t; + +typedef struct spdxtool_core_spdx_document +{ + const char *type; + char *spdx_id; + char *creation_info; + char *agent; + pkgconf_list_t licenses; + pkgconf_list_t element; + pkgconf_list_t rootElement; + pkgconf_list_t relationships; + pkgconf_list_t packages; +} spdxtool_core_spdx_document_t; + +typedef struct spdxtool_software_sbom_ +{ + char *spdx_id; + const char *type; + char *creation_info; + char *sbom_type; + spdxtool_core_spdx_document_t *spdx_document; + pkgconf_pkg_t *rootElement; +} spdxtool_software_sbom_t; + +typedef struct spdxtool_simplelicensing_license_expression_ +{ + const char *type; + char *spdx_id; + char *license_expression; +} spdxtool_simplelicensing_license_expression_t; + +typedef struct spdxtool_core_relationship_ +{ + const char *type; + char *spdx_id; + char *creation_info; + char *from; + pkgconf_list_t *to; + char *relationship_type; +} spdxtool_core_relationship_t; + +void +spdxtool_util_set_key(pkgconf_client_t *client, const char *key, const char *key_value, const char *key_default); + +void +spdxtool_util_set_uri_root(pkgconf_client_t *client, const char *uri_root); + +void +spdxtool_util_set_uri_separator_colon(pkgconf_client_t *client, bool sep); + +char +spdxtool_util_get_uri_separator(pkgconf_client_t *client); + +const char * +spdxtool_util_get_uri_root(pkgconf_client_t *client); + +void +spdxtool_util_set_spdx_version(pkgconf_client_t *client, const char *spdx_version); + +const char * +spdxtool_util_get_spdx_version(pkgconf_client_t *client); + +void +spdxtool_util_set_spdx_license(pkgconf_client_t *client, const char *spdx_license); + +const char * +spdxtool_util_get_spdx_license(pkgconf_client_t *client); + +char * +spdxtool_util_get_spdx_id_int(pkgconf_client_t *client, const char *part); + +char * +spdxtool_util_get_spdx_id_string(pkgconf_client_t *client, const char *part, const char *string_id); + +char * +spdxtool_util_get_iso8601_time(time_t *wanted_time); + +char * +spdxtool_util_get_current_iso8601_time(void); + +char * +spdxtool_util_string_correction(char *str); + +char * +spdxtool_util_tuple_lookup(pkgconf_client_t *client, pkgconf_list_t *vars, const char *key); + +#ifdef __cplusplus +} +#endif + +#endif |
