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