2022-08-12 12:07:56 +00:00
|
|
|
/*
|
|
|
|
* bomtool/main.c
|
|
|
|
* main() routine, printer functions
|
|
|
|
*
|
|
|
|
* Copyright (c) 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019
|
|
|
|
* 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 "getopt_long.h"
|
|
|
|
|
|
|
|
#define PKG_VERSION (((uint64_t) 1) << 1)
|
|
|
|
#define PKG_ABOUT (((uint64_t) 1) << 2)
|
|
|
|
#define PKG_HELP (((uint64_t) 1) << 3)
|
|
|
|
|
2022-08-12 12:57:53 +00:00
|
|
|
static const char *spdx_version = "SPDX-2.2";
|
|
|
|
static const char *bom_license = "CC0-1.0";
|
|
|
|
static const char *document_ref = "SPDXRef-DOCUMENT";
|
|
|
|
|
2022-08-12 12:07:56 +00:00
|
|
|
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 bool
|
|
|
|
error_handler(const char *msg, const pkgconf_client_t *client, void *data)
|
|
|
|
{
|
|
|
|
(void) client;
|
|
|
|
(void) data;
|
|
|
|
fprintf(error_msgout, "%s", msg);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2022-08-12 12:57:53 +00:00
|
|
|
static const char *
|
|
|
|
sbom_spdx_identity(pkgconf_pkg_t *pkg)
|
|
|
|
{
|
|
|
|
static char buf[PKGCONF_ITEM_SIZE];
|
|
|
|
|
|
|
|
snprintf(buf, sizeof buf, "%sC64%s", pkg->id, pkg->version);
|
|
|
|
|
|
|
|
return buf;
|
|
|
|
}
|
|
|
|
|
|
|
|
static const char *
|
|
|
|
sbom_name(pkgconf_pkg_t *world)
|
|
|
|
{
|
|
|
|
static char buf[PKGCONF_BUFSIZE];
|
|
|
|
pkgconf_node_t *node;
|
|
|
|
|
|
|
|
pkgconf_strlcpy(buf, "SBOM-SPDX", sizeof buf);
|
|
|
|
|
|
|
|
PKGCONF_FOREACH_LIST_ENTRY(world->required.head, node)
|
|
|
|
{
|
|
|
|
pkgconf_dependency_t *dep = node->data;
|
|
|
|
pkgconf_pkg_t *match = dep->match;
|
|
|
|
|
|
|
|
if (!dep->match)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
pkgconf_strlcat(buf, "-", sizeof buf);
|
|
|
|
pkgconf_strlcat(buf, sbom_spdx_identity(match), sizeof buf);
|
|
|
|
}
|
|
|
|
|
|
|
|
return buf;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
write_sbom_header(pkgconf_client_t *client, pkgconf_pkg_t *world)
|
|
|
|
{
|
|
|
|
(void) client;
|
|
|
|
(void) world;
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
printf("\n\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
static const char *
|
|
|
|
sbom_identity(pkgconf_pkg_t *pkg)
|
|
|
|
{
|
|
|
|
static char buf[PKGCONF_ITEM_SIZE];
|
|
|
|
|
|
|
|
snprintf(buf, sizeof buf, "%s@%s", pkg->id, pkg->version);
|
|
|
|
|
|
|
|
return buf;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
write_sbom_package(pkgconf_client_t *client, pkgconf_pkg_t *pkg, void *unused)
|
|
|
|
{
|
|
|
|
(void) client;
|
|
|
|
(void) unused;
|
|
|
|
|
|
|
|
if (pkg->flags & PKGCONF_PKG_PROPF_VIRTUAL)
|
|
|
|
return;
|
|
|
|
|
|
|
|
printf("##### Package: %s\n\n", sbom_identity(pkg));
|
|
|
|
|
|
|
|
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");
|
|
|
|
|
|
|
|
/* XXX: What about projects? */
|
|
|
|
if (pkg->maintainer != NULL)
|
|
|
|
printf("PackageSupplier: Person: %s\n", pkg->maintainer);
|
|
|
|
|
|
|
|
if (pkg->url != NULL)
|
|
|
|
printf("PackageHomePage: %s\n", pkg->url);
|
|
|
|
|
|
|
|
printf("PackageLicenseDeclared: %s\n", pkg->license != NULL ? pkg->license : "NOASSERTION");
|
|
|
|
|
|
|
|
if (pkg->copyright != NULL)
|
|
|
|
printf("PackageCopyrightText: <text>%s</text>\n", pkg->copyright);
|
|
|
|
|
|
|
|
if (pkg->description != NULL)
|
|
|
|
printf("PackageSummary: <text>%s</text>\n", pkg->description);
|
|
|
|
|
|
|
|
printf("\n\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
write_sbom_relationships(pkgconf_client_t *client, pkgconf_pkg_t *pkg, void *unused)
|
|
|
|
{
|
|
|
|
(void) client;
|
|
|
|
(void) unused;
|
|
|
|
|
|
|
|
char baseref[PKGCONF_ITEM_SIZE];
|
|
|
|
pkgconf_node_t *node;
|
|
|
|
|
|
|
|
if (pkg->flags & PKGCONF_PKG_PROPF_VIRTUAL)
|
|
|
|
return;
|
|
|
|
|
|
|
|
snprintf(baseref, sizeof baseref, "SPDXRef-Package-%sC64%s", pkg->id, pkg->version);
|
|
|
|
|
|
|
|
PKGCONF_FOREACH_LIST_ENTRY(pkg->required.head, node)
|
|
|
|
{
|
|
|
|
pkgconf_dependency_t *dep = node->data;
|
|
|
|
pkgconf_pkg_t *match = dep->match;
|
|
|
|
|
|
|
|
if (!dep->match)
|
|
|
|
continue;
|
|
|
|
|
2022-08-13 06:44:19 +00:00
|
|
|
printf("Relationship: %s DEPENDS_ON SPDXRef-Package-%s\n", baseref, sbom_spdx_identity(match));
|
2022-08-12 12:57:53 +00:00
|
|
|
printf("Relationship: SPDXRef-Package-%s DEPENDENCY_OF %s\n", sbom_spdx_identity(match), baseref);
|
|
|
|
}
|
|
|
|
|
|
|
|
PKGCONF_FOREACH_LIST_ENTRY(pkg->requires_private.head, node)
|
|
|
|
{
|
|
|
|
pkgconf_dependency_t *dep = node->data;
|
|
|
|
pkgconf_pkg_t *match = dep->match;
|
|
|
|
|
|
|
|
if (!dep->match)
|
|
|
|
continue;
|
|
|
|
|
2022-08-13 06:44:19 +00:00
|
|
|
printf("Relationship: %s DEPENDS_ON SPDXRef-Package-%s\n", baseref, sbom_spdx_identity(match));
|
2022-08-12 12:57:53 +00:00
|
|
|
printf("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");
|
|
|
|
}
|
|
|
|
|
2022-08-12 12:07:56 +00:00
|
|
|
static bool
|
|
|
|
generate_sbom_from_world(pkgconf_client_t *client, pkgconf_pkg_t *world)
|
|
|
|
{
|
2022-08-12 12:57:53 +00:00
|
|
|
int eflag;
|
|
|
|
pkgconf_node_t *node;
|
|
|
|
|
|
|
|
write_sbom_header(client, world);
|
|
|
|
|
|
|
|
eflag = pkgconf_pkg_traverse(client, world, write_sbom_package, NULL, maximum_traverse_depth, 0);
|
|
|
|
if (eflag != PKGCONF_PKG_ERRF_OK)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
eflag = pkgconf_pkg_traverse(client, world, write_sbom_relationships, NULL, maximum_traverse_depth, 0);
|
|
|
|
if (eflag != PKGCONF_PKG_ERRF_OK)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
PKGCONF_FOREACH_LIST_ENTRY(world->required.head, node)
|
|
|
|
{
|
|
|
|
pkgconf_dependency_t *dep = node->data;
|
|
|
|
pkgconf_pkg_t *match = dep->match;
|
|
|
|
|
|
|
|
if (!dep->match)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
printf("Relationship: %s DESCRIBES SPDXRef-Package-%s\n", document_ref, sbom_spdx_identity(match));
|
|
|
|
}
|
|
|
|
|
2022-08-12 12:07:56 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
version(void)
|
|
|
|
{
|
|
|
|
printf("bomtool %s\n", PACKAGE_VERSION);
|
|
|
|
return EXIT_SUCCESS;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
about(void)
|
|
|
|
{
|
|
|
|
printf("bomtool (%s %s)\n", PACKAGE_NAME, PACKAGE_VERSION);
|
|
|
|
printf("Copyright (c) 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021\n");
|
|
|
|
printf(" pkgconf authors (see AUTHORS in documentation directory).\n\n");
|
|
|
|
printf("Permission to use, copy, modify, and/or distribute this software for any\n");
|
|
|
|
printf("purpose with or without fee is hereby granted, provided that the above\n");
|
|
|
|
printf("copyright notice and this permission notice appear in all copies.\n\n");
|
|
|
|
printf("This software is provided 'as is' and without any warranty, express or\n");
|
|
|
|
printf("implied. In no event shall the authors be liable for any damages arising\n");
|
|
|
|
printf("from the use of this software.\n\n");
|
|
|
|
printf("Report bugs at <%s>.\n", PACKAGE_BUGREPORT);
|
|
|
|
return EXIT_SUCCESS;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
usage(void)
|
|
|
|
{
|
|
|
|
printf("usage: bomtool [--flags] [modules]\n");
|
|
|
|
|
|
|
|
printf("\nbasic options:\n\n");
|
|
|
|
|
|
|
|
printf(" --help this message\n");
|
|
|
|
printf(" --about print bomtool version and license to stdout\n");
|
|
|
|
printf(" --version print bomtool version to stdout\n");
|
|
|
|
|
|
|
|
return EXIT_SUCCESS;
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
main(int argc, char *argv[])
|
|
|
|
{
|
|
|
|
int ret = EXIT_SUCCESS;
|
|
|
|
pkgconf_list_t pkgq = PKGCONF_LIST_INITIALIZER;
|
2022-08-13 06:44:40 +00:00
|
|
|
unsigned int want_client_flags = PKGCONF_PKG_PKGF_SEARCH_PRIVATE;
|
2022-08-12 12:07:56 +00:00
|
|
|
pkgconf_cross_personality_t *personality = pkgconf_cross_personality_default();
|
|
|
|
pkgconf_pkg_t world = {
|
|
|
|
.id = "virtual:world",
|
|
|
|
.realname = "virtual world package",
|
|
|
|
.flags = PKGCONF_PKG_PROPF_STATIC | PKGCONF_PKG_PROPF_VIRTUAL,
|
|
|
|
};
|
|
|
|
|
|
|
|
error_msgout = stderr;
|
|
|
|
|
|
|
|
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, },
|
|
|
|
{ NULL, 0, NULL, 0 }
|
|
|
|
};
|
|
|
|
|
|
|
|
while ((ret = pkg_getopt_long_only(argc, argv, "", options, NULL)) != -1)
|
|
|
|
{
|
|
|
|
switch (ret)
|
|
|
|
{
|
|
|
|
case '?':
|
|
|
|
case ':':
|
|
|
|
return EXIT_FAILURE;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pkgconf_client_init(&pkg_client, error_handler, NULL, personality);
|
|
|
|
|
|
|
|
/* 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;
|
|
|
|
|
Avoid undefined behaviour with the ctype(3) functions.
fix https://github.com/pkgconf/pkgconf/issues/291
As defined in the C standard:
In all cases the argument is an int, the value of which shall
be representable as an unsigned char or shall equal the value
of the macro EOF. If the argument has any other value, the
behavior is undefined.
This is because they're designed to work with the int values returned
by getc or fgetc; they need extra work to handle a char value.
If EOF is -1 (as it almost always is), with 8-bit bytes, the allowed
inputs to the ctype(3) functions are:
{-1, 0, 1, 2, 3, ..., 255}.
However, on platforms where char is signed, such as x86 with the
usual ABI, code like
char *ptr = ...;
... isspace(*ptr) ...
may pass in values in the range:
{-128, -127, -126, ..., -2, -1, 0, 1, ..., 127}.
This has two problems:
1. Inputs in the set {-128, -127, -126, ..., -2} are forbidden.
2. The non-EOF byte 0xff is conflated with the value EOF = -1, so
even though the input is not forbidden, it may give the wrong
answer.
Casting char to unsigned int first before passing the result to
ctype(3) doesn't help: inputs like -128 are unchanged by this cast,
because (on a two's-complement machine with 32-bit int and unsigned
int), converting the signed char with integer value -128 to unsigned
int gives integer value 2^32 - 128 = 0xffffff80, which is out of
range, and which is converted in int back to -128, which is also out
of range.
It is necessary to cast char inputs to unsigned char first; you can
then cast to unsigned int if you like but there's no need because the
functions will always convert the argument to int by definition. So
the above fragment needs to be:
char *ptr = ...;
... isspace((unsigned char)*ptr) ...
This patch changes unsigned int casts to unsigned char casts, and
adds unsigned char casts where they are missing.
2023-03-17 19:32:58 +00:00
|
|
|
while (isspace((unsigned char)package[0]))
|
2022-08-12 12:07:56 +00:00
|
|
|
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 (pkgq.head == NULL)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "Please specify at least one package name on the command line.\n");
|
|
|
|
ret = EXIT_FAILURE;
|
2022-08-16 18:50:35 +00:00
|
|
|
goto out;
|
2022-08-12 12:07:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
ret = EXIT_SUCCESS;
|
|
|
|
|
|
|
|
if (!pkgconf_queue_solve(&pkg_client, &pkgq, &world, maximum_traverse_depth))
|
|
|
|
{
|
|
|
|
ret = EXIT_FAILURE;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!generate_sbom_from_world(&pkg_client, &world))
|
|
|
|
{
|
|
|
|
ret = EXIT_FAILURE;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
out:
|
2022-08-16 20:41:10 +00:00
|
|
|
pkgconf_solution_free(&pkg_client, &world);
|
2022-08-12 12:07:56 +00:00
|
|
|
pkgconf_queue_free(&pkgq);
|
|
|
|
pkgconf_cross_personality_deinit(personality);
|
|
|
|
pkgconf_client_deinit(&pkg_client);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|