diff options
Diffstat (limited to 'cli/bomtool/main.c')
| -rw-r--r-- | cli/bomtool/main.c | 222 |
1 files changed, 179 insertions, 43 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); |
