xref: /freebsd/contrib/pkgconf/cli/spdxtool/main.c (revision 592efe252472a3385acf36b1f49ecf710a7f3d9c)
1 /*
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  *​ Copyright (c) 2025 The FreeBSD Foundation
5  *​
6  *​ Portions of this software were developed by
7  * Tuukka Pasanen <tuukka.pasanen@ilmi.fi> under sponsorship from
8  * the FreeBSD Foundation
9  */
10 
11 #include "libpkgconf/config.h"
12 #include <libpkgconf/stdinc.h>
13 #include <libpkgconf/libpkgconf.h>
14 #include "getopt_long.h"
15 #include "util.h"
16 #include "core.h"
17 #include "software.h"
18 #include "serialize.h"
19 #include "simplelicensing.h"
20 #include "generate.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 = "3.0.1";
27 static const char *bom_license = "CC0-1.0";
28 static const char *xsd_any_url_default_base = "https://github.com/pkgconf/pkgconf";
29 static const char *xsd_any_uri_default_base = "github.com:pkgconf:pkgconf";
30 static int maximum_traverse_depth = 2000;
31 
32 static pkgconf_client_t pkg_client;
33 static uint64_t want_flags;
34 // static int maximum_traverse_depth = 2000;
35 static FILE *error_msgout = NULL;
36 static FILE *sbom_out = NULL;
37 
38 static const char *
environ_lookup_handler(const pkgconf_client_t * client,const char * key)39 environ_lookup_handler(const pkgconf_client_t *client, const char *key)
40 {
41 	(void) client;
42 
43 	return getenv(key);
44 }
45 
46 static bool
error_handler(const char * msg,const pkgconf_client_t * client,void * data)47 error_handler(const char *msg, const pkgconf_client_t *client, void *data)
48 {
49 	(void) client;
50 	(void) data;
51 	if (!pkgconf_output_file_fmt(error_msgout, "%s", msg))
52 	{
53 		pkgconf_error(client, "spdxtool: Could not output error message: %s", strerror(errno));
54 		return false;
55 	}
56 	return true;
57 }
58 
59 static int
version(void)60 version(void)
61 {
62 	printf("spdxtool %s\n", PACKAGE_VERSION);
63 	return EXIT_SUCCESS;
64 }
65 
66 static int
about(void)67 about(void)
68 {
69 	printf("spdxtool (%s %s)\n\n", PACKAGE_NAME, PACKAGE_VERSION);
70 	printf("SPDX-License-Identifier: BSD-2-Clause\n\n");
71 	printf("Copyright (c) 2025 The FreeBSD Foundation\n\n");
72 	printf("Portions of this software were developed by\n");
73 	printf("Tuukka Pasanen <tuukka.pasanen@ilmi.fi> under sponsorship from\n");
74 	printf("the FreeBSD Foundation\n\n");
75 	printf("Report bugs at <%s>.\n", PACKAGE_BUGREPORT);
76 	return EXIT_SUCCESS;
77 }
78 
79 static int
usage(void)80 usage(void)
81 {
82 	printf("usage: spdxtool [modules]\n");
83 
84 	printf("\nOptions:\n");
85 
86 	printf("  --agent-name                      Set agent name [default: 'Default']\n");
87 	printf("  --creation-time                   Use string as creation time (Should be in ISO8601 format) [default: current time]\n");
88 	printf("  --creation-id                     Use string as creation id [default: '_:creationinfo_1']\n");
89 	printf("  --help                            this message\n");
90 	printf("  --about                           print bomtool version and license to stdout\n");
91 	printf("  --version                         print bomtool version to stdout\n");
92 	printf("  --output FILE                     output SBOM data to file\n");
93 	printf("  --spdx-base-id URL                Uset string as base of SPDX ids [default: %s]\n", xsd_any_uri_default_base);
94 	printf("  --use-uri                         Use URIs not URLs as SPDX id");
95 	printf("  --define-variable=varname=value   define variable global 'varname' as 'value'\n");
96 
97 	return EXIT_SUCCESS;
98 }
99 
100 int
main(int argc,char * argv[])101 main(int argc, char *argv[])
102 {
103 	int ret = EXIT_SUCCESS;
104 	pkgconf_list_t pkgq = PKGCONF_LIST_INITIALIZER;
105 	unsigned int want_client_flags = PKGCONF_PKG_PKGF_SEARCH_PRIVATE;
106 	pkgconf_cross_personality_t *personality = pkgconf_cross_personality_default();
107 	char *creation_time = NULL;
108 	char *creation_id = NULL;
109 	char *agent_name = NULL;
110 	char world_id[] = "virtual:world";
111 	char world_realname[] = "virtual world package";
112 	const char *spdx_id_base = xsd_any_url_default_base;
113 	bool colon_sep = false;
114 	pkgconf_pkg_t world =
115 	{
116 		.id = world_id,
117 		.realname = world_realname,
118 		.flags = PKGCONF_PKG_PROPF_STATIC | PKGCONF_PKG_PROPF_VIRTUAL,
119 	};
120 
121 	error_msgout = stderr;
122 	sbom_out = stdout;
123 
124 	struct pkg_option options[] =
125 	{
126 		{ "agent-name", required_argument, NULL, 100, },
127 		{ "creation-time", required_argument, NULL, 101, },
128 		{ "creation-id", required_argument, NULL, 102, },
129 		{ "version", no_argument, &want_flags, PKG_VERSION, },
130 		{ "about", no_argument, &want_flags, PKG_ABOUT, },
131 		{ "help", no_argument, &want_flags, PKG_HELP, },
132 		{ "output", required_argument, NULL, 103, },
133 		{ "spdx-base-id", required_argument, NULL, 104, },
134 		{ "use-uri", no_argument, NULL, 105, },
135 		{ "define-variable", required_argument, NULL, 106, },
136 		{ NULL, 0, NULL, 0 }
137 	};
138 
139 	while ((ret = pkg_getopt_long_only(argc, argv, "", options, NULL)) != -1)
140 	{
141 		switch (ret)
142 		{
143 		case 100:
144 			agent_name = pkg_optarg;
145 			break;
146 		case 101:
147 			creation_time = pkg_optarg;
148 			break;
149 		case 102:
150 			creation_id = pkg_optarg;
151 			break;
152 		case 103:
153 			sbom_out = fopen(pkg_optarg, "w");
154 			if (sbom_out == NULL)
155 			{
156 				pkgconf_output_file_fmt(stderr, "unable to open %s: %s\n", pkg_optarg, strerror(errno));
157 				return EXIT_FAILURE;
158 			}
159 			break;
160 		case 104:
161 			spdx_id_base = pkg_optarg;
162 			break;
163 		case 105:
164 			// If SPDX id base have not been altered use default
165 			if (!strcmp(spdx_id_base, xsd_any_url_default_base))
166 				spdx_id_base = xsd_any_uri_default_base;
167 			colon_sep = true;
168 			break;
169 		case 106:
170 			pkgconf_tuple_define_global(&pkg_client, pkg_optarg);
171 			break;
172 		case '?':
173 		case ':':
174 			return EXIT_FAILURE;
175 		default:
176 			break;
177 		}
178 	}
179 
180 	pkgconf_client_init(&pkg_client, error_handler, NULL, personality, NULL, environ_lookup_handler);
181 
182 	/* we have determined what features we want most likely.  in some cases, we override later. */
183 	pkgconf_client_set_flags(&pkg_client, want_client_flags);
184 
185 	/* at this point, want_client_flags should be set, so build the dir list */
186 	pkgconf_client_dir_list_build(&pkg_client, personality);
187 
188 
189 	if ((want_flags & PKG_ABOUT) == PKG_ABOUT)
190 		return about();
191 
192 	if ((want_flags & PKG_VERSION) == PKG_VERSION)
193 		return version();
194 
195 	if ((want_flags & PKG_HELP) == PKG_HELP)
196 		return usage();
197 
198 	/* Join the remaining arguments into a single query string, as the main
199 	 * pkgconf CLI does, and let the dependency parser handle module names,
200 	 * comparison operators and versions.
201 	 */
202 	pkgconf_buffer_t queryparams = PKGCONF_BUFFER_INITIALIZER;
203 
204 	while (pkg_optind < argc && argv[pkg_optind] != NULL)
205 	{
206 		if (pkgconf_buffer_len(&queryparams) > 0)
207 			pkgconf_buffer_push_byte(&queryparams, ' ');
208 
209 		pkgconf_buffer_append(&queryparams, argv[pkg_optind]);
210 		pkg_optind++;
211 	}
212 
213 	if (pkgconf_buffer_len(&queryparams) > 0)
214 		pkgconf_queue_push(&pkgq, pkgconf_buffer_str(&queryparams));
215 
216 	pkgconf_buffer_finalize(&queryparams);
217 
218 	if (!pkgconf_queue_solve(&pkg_client, &pkgq, &world, maximum_traverse_depth))
219 	{
220 		ret = EXIT_FAILURE;
221 		goto out;
222 	}
223 
224 	spdxtool_util_set_uri_root(&pkg_client, spdx_id_base);
225 	spdxtool_util_set_uri_separator_colon(&pkg_client, colon_sep);
226 	spdxtool_util_set_spdx_license(&pkg_client, bom_license);
227 	spdxtool_util_set_spdx_version(&pkg_client, spdx_version);
228 
229 	if (!spdxtool_generate(&pkg_client, &world, sbom_out, maximum_traverse_depth, creation_time, creation_id, agent_name))
230 	{
231 		ret = EXIT_FAILURE;
232 		goto out;
233 	}
234 
235 	ret = EXIT_SUCCESS;
236 
237 out:
238 	pkgconf_solution_free(&pkg_client, &world);
239 	pkgconf_queue_free(&pkgq);
240 	pkgconf_cross_personality_deinit(personality);
241 	pkgconf_client_deinit(&pkg_client);
242 
243 	return ret;
244 }
245