aboutsummaryrefslogtreecommitdiff
path: root/cli/bomtool/main.c
diff options
context:
space:
mode:
Diffstat (limited to 'cli/bomtool/main.c')
-rw-r--r--cli/bomtool/main.c222
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);