xref: /freebsd/contrib/pkgconf/cli/bomtool/main.c (revision a3cefe7f2b4df0f70ff92d4570ce18e517af43ec)
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