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 * 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 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 60 version(void) 61 { 62 printf("spdxtool %s\n", PACKAGE_VERSION); 63 return EXIT_SUCCESS; 64 } 65 66 static int 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 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 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