1 /*
2 * bomtool/main.c
3 * main() routine, printer functions
4 *
5 * Copyright (c) 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019
6 * pkgconf authors (see AUTHORS).
7 *
8 * Permission to use, copy, modify, and/or distribute this software for any
9 * purpose with or without fee is hereby granted, provided that the above
10 * copyright notice and this permission notice appear in all copies.
11 *
12 * This software is provided 'as is' and without any warranty, express or
13 * implied. In no event shall the authors be liable for any damages arising
14 * from the use of this software.
15 */
16
17 #include "libpkgconf/config.h"
18 #include <libpkgconf/stdinc.h>
19 #include <libpkgconf/libpkgconf.h>
20 #include "getopt_long.h"
21
22 #define PKG_VERSION (((uint64_t) 1) << 1)
23 #define PKG_ABOUT (((uint64_t) 1) << 2)
24 #define PKG_HELP (((uint64_t) 1) << 3)
25
26 static const char *spdx_version = "SPDX-2.2";
27 static const char *bom_license = "CC0-1.0";
28 static const char *document_ref = "SPDXRef-DOCUMENT";
29
30 static pkgconf_client_t pkg_client;
31 static uint64_t want_flags;
32 static size_t maximum_package_count = 0;
33 static int maximum_traverse_depth = 2000;
34 FILE *error_msgout = NULL;
35
36 static bool
error_handler(const char * msg,const pkgconf_client_t * client,void * data)37 error_handler(const char *msg, const pkgconf_client_t *client, void *data)
38 {
39 (void) client;
40 (void) data;
41 fprintf(error_msgout, "%s", msg);
42 return true;
43 }
44
45 static const char *
sbom_spdx_identity(pkgconf_pkg_t * pkg)46 sbom_spdx_identity(pkgconf_pkg_t *pkg)
47 {
48 static char buf[PKGCONF_ITEM_SIZE];
49
50 snprintf(buf, sizeof buf, "%sC64%s", pkg->id, pkg->version);
51
52 return buf;
53 }
54
55 static const char *
sbom_name(pkgconf_pkg_t * world)56 sbom_name(pkgconf_pkg_t *world)
57 {
58 static char buf[PKGCONF_BUFSIZE];
59 pkgconf_node_t *node;
60
61 pkgconf_strlcpy(buf, "SBOM-SPDX", sizeof buf);
62
63 PKGCONF_FOREACH_LIST_ENTRY(world->required.head, node)
64 {
65 pkgconf_dependency_t *dep = node->data;
66 pkgconf_pkg_t *match = dep->match;
67
68 if ((dep->flags & PKGCONF_PKG_DEPF_QUERY) != PKGCONF_PKG_DEPF_QUERY)
69 continue;
70
71 if (!dep->match)
72 continue;
73
74 pkgconf_strlcat(buf, "-", sizeof buf);
75 pkgconf_strlcat(buf, sbom_spdx_identity(match), sizeof buf);
76 }
77
78 return buf;
79 }
80
81 static void
write_sbom_header(pkgconf_client_t * client,pkgconf_pkg_t * world)82 write_sbom_header(pkgconf_client_t *client, pkgconf_pkg_t *world)
83 {
84 (void) client;
85 (void) world;
86
87 printf("SPDXVersion: %s\n", spdx_version);
88 printf("DataLicense: %s\n", bom_license);
89 printf("SPDXID: %s\n", document_ref);
90 printf("DocumentName: %s\n", sbom_name(world));
91 printf("DocumentNamespace: https://spdx.org/spdxdocs/bomtool-%s\n", PACKAGE_VERSION);
92 printf("Creator: Tool: bomtool %s\n", PACKAGE_VERSION);
93
94 printf("\n\n");
95 }
96
97 static const char *
sbom_identity(pkgconf_pkg_t * pkg)98 sbom_identity(pkgconf_pkg_t *pkg)
99 {
100 static char buf[PKGCONF_ITEM_SIZE];
101
102 snprintf(buf, sizeof buf, "%s@%s", pkg->id, pkg->version);
103
104 return buf;
105 }
106
107 static void
write_sbom_package(pkgconf_client_t * client,pkgconf_pkg_t * pkg,void * unused)108 write_sbom_package(pkgconf_client_t *client, pkgconf_pkg_t *pkg, void *unused)
109 {
110 (void) client;
111 (void) unused;
112
113 if (pkg->flags & PKGCONF_PKG_PROPF_VIRTUAL)
114 return;
115
116 printf("##### Package: %s\n\n", sbom_identity(pkg));
117
118 printf("PackageName: %s\n", sbom_identity(pkg));
119 printf("SPDXID: SPDXRef-Package-%s\n", sbom_spdx_identity(pkg));
120 printf("PackageVersion: %s\n", pkg->version);
121 printf("PackageDownloadLocation: NOASSERTION\n");
122 printf("PackageVerificationCode: NOASSERTION\n");
123
124 /* XXX: What about projects? */
125 if (pkg->maintainer != NULL)
126 printf("PackageSupplier: Person: %s\n", pkg->maintainer);
127
128 if (pkg->url != NULL)
129 printf("PackageHomePage: %s\n", pkg->url);
130
131 printf("PackageLicenseDeclared: %s\n", pkg->license != NULL ? pkg->license : "NOASSERTION");
132
133 if (pkg->copyright != NULL)
134 printf("PackageCopyrightText: <text>%s</text>\n", pkg->copyright);
135
136 if (pkg->description != NULL)
137 printf("PackageSummary: <text>%s</text>\n", pkg->description);
138
139 printf("\n\n");
140 }
141
142 static void
write_sbom_relationships(pkgconf_client_t * client,pkgconf_pkg_t * pkg,void * unused)143 write_sbom_relationships(pkgconf_client_t *client, pkgconf_pkg_t *pkg, void *unused)
144 {
145 (void) client;
146 (void) unused;
147
148 char baseref[PKGCONF_ITEM_SIZE];
149 pkgconf_node_t *node;
150
151 if (pkg->flags & PKGCONF_PKG_PROPF_VIRTUAL)
152 return;
153
154 snprintf(baseref, sizeof baseref, "SPDXRef-Package-%sC64%s", pkg->id, pkg->version);
155
156 PKGCONF_FOREACH_LIST_ENTRY(pkg->required.head, node)
157 {
158 pkgconf_dependency_t *dep = node->data;
159 pkgconf_pkg_t *match = dep->match;
160
161 if (!dep->match)
162 continue;
163
164 printf("Relationship: %s DEPENDS_ON SPDXRef-Package-%s\n", baseref, sbom_spdx_identity(match));
165 printf("Relationship: SPDXRef-Package-%s DEPENDENCY_OF %s\n", sbom_spdx_identity(match), baseref);
166 }
167
168 PKGCONF_FOREACH_LIST_ENTRY(pkg->requires_private.head, node)
169 {
170 pkgconf_dependency_t *dep = node->data;
171 pkgconf_pkg_t *match = dep->match;
172
173 if (!dep->match)
174 continue;
175
176 printf("Relationship: %s DEPENDS_ON SPDXRef-Package-%s\n", baseref, sbom_spdx_identity(match));
177 printf("Relationship: SPDXRef-Package-%s DEV_DEPENDENCY_OF %s\n", sbom_spdx_identity(match), baseref);
178 }
179
180 if (pkg->required.head != NULL || pkg->requires_private.head != NULL)
181 printf("\n\n");
182 }
183
184 static bool
generate_sbom_from_world(pkgconf_client_t * client,pkgconf_pkg_t * world)185 generate_sbom_from_world(pkgconf_client_t *client, pkgconf_pkg_t *world)
186 {
187 int eflag;
188 pkgconf_node_t *node;
189
190 write_sbom_header(client, world);
191
192 eflag = pkgconf_pkg_traverse(client, world, write_sbom_package, NULL, maximum_traverse_depth, 0);
193 if (eflag != PKGCONF_PKG_ERRF_OK)
194 return false;
195
196 eflag = pkgconf_pkg_traverse(client, world, write_sbom_relationships, NULL, maximum_traverse_depth, 0);
197 if (eflag != PKGCONF_PKG_ERRF_OK)
198 return false;
199
200 PKGCONF_FOREACH_LIST_ENTRY(world->required.head, node)
201 {
202 pkgconf_dependency_t *dep = node->data;
203 pkgconf_pkg_t *match = dep->match;
204
205 if (!dep->match)
206 continue;
207
208 printf("Relationship: %s DESCRIBES SPDXRef-Package-%s\n", document_ref, sbom_spdx_identity(match));
209 }
210
211 return true;
212 }
213
214 static int
version(void)215 version(void)
216 {
217 printf("bomtool %s\n", PACKAGE_VERSION);
218 return EXIT_SUCCESS;
219 }
220
221 static int
about(void)222 about(void)
223 {
224 printf("bomtool (%s %s)\n", PACKAGE_NAME, PACKAGE_VERSION);
225 printf("Copyright (c) 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021\n");
226 printf(" pkgconf authors (see AUTHORS in documentation directory).\n\n");
227 printf("Permission to use, copy, modify, and/or distribute this software for any\n");
228 printf("purpose with or without fee is hereby granted, provided that the above\n");
229 printf("copyright notice and this permission notice appear in all copies.\n\n");
230 printf("This software is provided 'as is' and without any warranty, express or\n");
231 printf("implied. In no event shall the authors be liable for any damages arising\n");
232 printf("from the use of this software.\n\n");
233 printf("Report bugs at <%s>.\n", PACKAGE_BUGREPORT);
234 return EXIT_SUCCESS;
235 }
236
237 static int
usage(void)238 usage(void)
239 {
240 printf("usage: bomtool [--flags] [modules]\n");
241
242 printf("\nbasic options:\n\n");
243
244 printf(" --help this message\n");
245 printf(" --about print bomtool version and license to stdout\n");
246 printf(" --version print bomtool version to stdout\n");
247
248 return EXIT_SUCCESS;
249 }
250
251 int
main(int argc,char * argv[])252 main(int argc, char *argv[])
253 {
254 int ret = EXIT_SUCCESS;
255 pkgconf_list_t pkgq = PKGCONF_LIST_INITIALIZER;
256 unsigned int want_client_flags = PKGCONF_PKG_PKGF_SEARCH_PRIVATE;
257 pkgconf_cross_personality_t *personality = pkgconf_cross_personality_default();
258 pkgconf_pkg_t world = {
259 .id = "virtual:world",
260 .realname = "virtual world package",
261 .flags = PKGCONF_PKG_PROPF_STATIC | PKGCONF_PKG_PROPF_VIRTUAL,
262 };
263
264 error_msgout = stderr;
265
266 struct pkg_option options[] = {
267 { "version", no_argument, &want_flags, PKG_VERSION, },
268 { "about", no_argument, &want_flags, PKG_ABOUT, },
269 { "help", no_argument, &want_flags, PKG_HELP, },
270 { NULL, 0, NULL, 0 }
271 };
272
273 while ((ret = pkg_getopt_long_only(argc, argv, "", options, NULL)) != -1)
274 {
275 switch (ret)
276 {
277 case '?':
278 case ':':
279 return EXIT_FAILURE;
280 default:
281 break;
282 }
283 }
284
285 pkgconf_client_init(&pkg_client, error_handler, NULL, personality);
286
287 /* we have determined what features we want most likely. in some cases, we override later. */
288 pkgconf_client_set_flags(&pkg_client, want_client_flags);
289
290 /* at this point, want_client_flags should be set, so build the dir list */
291 pkgconf_client_dir_list_build(&pkg_client, personality);
292
293 if ((want_flags & PKG_ABOUT) == PKG_ABOUT)
294 return about();
295
296 if ((want_flags & PKG_VERSION) == PKG_VERSION)
297 return version();
298
299 if ((want_flags & PKG_HELP) == PKG_HELP)
300 return usage();
301
302 while (1)
303 {
304 const char *package = argv[pkg_optind];
305
306 if (package == NULL)
307 break;
308
309 /* check if there is a limit to the number of packages allowed to be included, if so and we have hit
310 * the limit, stop adding packages to the queue.
311 */
312 if (maximum_package_count > 0 && pkgq.length > maximum_package_count)
313 break;
314
315 while (isspace((unsigned char)package[0]))
316 package++;
317
318 /* skip empty packages */
319 if (package[0] == '\0') {
320 pkg_optind++;
321 continue;
322 }
323
324 if (argv[pkg_optind + 1] == NULL || !PKGCONF_IS_OPERATOR_CHAR(*(argv[pkg_optind + 1])))
325 {
326 pkgconf_queue_push(&pkgq, package);
327 pkg_optind++;
328 }
329 else
330 {
331 char packagebuf[PKGCONF_BUFSIZE];
332
333 snprintf(packagebuf, sizeof packagebuf, "%s %s %s", package, argv[pkg_optind + 1], argv[pkg_optind + 2]);
334 pkg_optind += 3;
335
336 pkgconf_queue_push(&pkgq, packagebuf);
337 }
338 }
339
340 if (pkgq.head == NULL)
341 {
342 fprintf(stderr, "Please specify at least one package name on the command line.\n");
343 ret = EXIT_FAILURE;
344 goto out;
345 }
346
347 ret = EXIT_SUCCESS;
348
349 if (!pkgconf_queue_solve(&pkg_client, &pkgq, &world, maximum_traverse_depth))
350 {
351 ret = EXIT_FAILURE;
352 goto out;
353 }
354
355 if (!generate_sbom_from_world(&pkg_client, &world))
356 {
357 ret = EXIT_FAILURE;
358 goto out;
359 }
360
361 out:
362 pkgconf_solution_free(&pkg_client, &world);
363 pkgconf_queue_free(&pkgq);
364 pkgconf_cross_personality_deinit(personality);
365 pkgconf_client_deinit(&pkg_client);
366
367 return ret;
368 }
369