1*592efe25SPierre Pronchery /*
2*592efe25SPierre Pronchery * SPDX-License-Identifier: BSD-2-Clause
3*592efe25SPierre Pronchery *
4*592efe25SPierre Pronchery * Copyright (c) 2025 The FreeBSD Foundation
5*592efe25SPierre Pronchery *
6*592efe25SPierre Pronchery * Portions of this software were developed by
7*592efe25SPierre Pronchery * Tuukka Pasanen <tuukka.pasanen@ilmi.fi> under sponsorship from
8*592efe25SPierre Pronchery * the FreeBSD Foundation
9*592efe25SPierre Pronchery */
10*592efe25SPierre Pronchery
11*592efe25SPierre Pronchery #include <libpkgconf/stdinc.h>
12*592efe25SPierre Pronchery #include <libpkgconf/libpkgconf.h>
13*592efe25SPierre Pronchery #include "util.h"
14*592efe25SPierre Pronchery #include "core.h"
15*592efe25SPierre Pronchery #include "software.h"
16*592efe25SPierre Pronchery #include "serialize.h"
17*592efe25SPierre Pronchery #include "simplelicensing.h"
18*592efe25SPierre Pronchery #include "generate.h"
19*592efe25SPierre Pronchery
20*592efe25SPierre Pronchery // NOTE: this function is passed to pkgconf_pkg_traverse
21*592efe25SPierre Pronchery static void
generate_spdx_package(pkgconf_client_t * client,pkgconf_pkg_t * pkg,void * ptr)22*592efe25SPierre Pronchery generate_spdx_package(pkgconf_client_t *client, pkgconf_pkg_t *pkg, void *ptr)
23*592efe25SPierre Pronchery {
24*592efe25SPierre Pronchery spdxtool_core_spdx_document_t *document = (spdxtool_core_spdx_document_t *)ptr;
25*592efe25SPierre Pronchery pkgconf_node_t *node = NULL;
26*592efe25SPierre Pronchery spdxtool_software_sbom_t *sbom = NULL;
27*592efe25SPierre Pronchery char *package_spdx = NULL;
28*592efe25SPierre Pronchery char *spdx_id_string = NULL;
29*592efe25SPierre Pronchery char sep = spdxtool_util_get_uri_separator(client);
30*592efe25SPierre Pronchery
31*592efe25SPierre Pronchery if (pkg->flags & PKGCONF_PKG_PROPF_VIRTUAL)
32*592efe25SPierre Pronchery return;
33*592efe25SPierre Pronchery
34*592efe25SPierre Pronchery spdx_id_string = spdxtool_util_get_spdx_id_string(client, "software_Sbom", pkg->id);
35*592efe25SPierre Pronchery if (!spdx_id_string)
36*592efe25SPierre Pronchery goto err;
37*592efe25SPierre Pronchery
38*592efe25SPierre Pronchery sbom = spdxtool_software_sbom_new(client, spdx_id_string, document->creation_info, "build");
39*592efe25SPierre Pronchery free(spdx_id_string);
40*592efe25SPierre Pronchery spdx_id_string = NULL;
41*592efe25SPierre Pronchery if (!sbom)
42*592efe25SPierre Pronchery goto err;
43*592efe25SPierre Pronchery
44*592efe25SPierre Pronchery sbom->spdx_document = document;
45*592efe25SPierre Pronchery sbom->rootElement = pkg;
46*592efe25SPierre Pronchery
47*592efe25SPierre Pronchery package_spdx = spdxtool_util_get_spdx_id_string(client, "Package", pkg->id);
48*592efe25SPierre Pronchery if (!package_spdx)
49*592efe25SPierre Pronchery goto err;
50*592efe25SPierre Pronchery
51*592efe25SPierre Pronchery pkgconf_tuple_add(client, &pkg->vars, "spdxId", package_spdx, false, 0);
52*592efe25SPierre Pronchery free(package_spdx);
53*592efe25SPierre Pronchery package_spdx = NULL;
54*592efe25SPierre Pronchery
55*592efe25SPierre Pronchery pkgconf_tuple_add(client, &pkg->vars, "creationInfo", document->creation_info, false, 0);
56*592efe25SPierre Pronchery pkgconf_tuple_add(client, &pkg->vars, "agent", document->agent, false, 0);
57*592efe25SPierre Pronchery
58*592efe25SPierre Pronchery if (pkg->maintainer != NULL)
59*592efe25SPierre Pronchery {
60*592efe25SPierre Pronchery const char *supplier = spdxtool_core_spdx_document_add_maintainer(client, document, pkg->maintainer);
61*592efe25SPierre Pronchery if (!supplier)
62*592efe25SPierre Pronchery goto err;
63*592efe25SPierre Pronchery
64*592efe25SPierre Pronchery pkgconf_tuple_add(client, &pkg->vars, "suppliedBy", supplier, false, 0);
65*592efe25SPierre Pronchery }
66*592efe25SPierre Pronchery
67*592efe25SPierre Pronchery if (pkg->license.head != NULL)
68*592efe25SPierre Pronchery {
69*592efe25SPierre Pronchery pkgconf_buffer_t spdx_id_buf = PKGCONF_BUFFER_INITIALIZER;
70*592efe25SPierre Pronchery
71*592efe25SPierre Pronchery pkgconf_buffer_append_fmt(&spdx_id_buf, "%s%chasDeclaredLicense", pkg->id, sep);
72*592efe25SPierre Pronchery char *spdx_id_name = pkgconf_buffer_freeze(&spdx_id_buf);
73*592efe25SPierre Pronchery if (!spdx_id_name)
74*592efe25SPierre Pronchery goto err;
75*592efe25SPierre Pronchery
76*592efe25SPierre Pronchery package_spdx = spdxtool_util_get_spdx_id_string(client, "Relationship", spdx_id_name);
77*592efe25SPierre Pronchery free(spdx_id_name);
78*592efe25SPierre Pronchery if (!package_spdx)
79*592efe25SPierre Pronchery goto err;
80*592efe25SPierre Pronchery
81*592efe25SPierre Pronchery pkgconf_tuple_add(client, &pkg->vars, "hasDeclaredLicense", package_spdx, false, 0);
82*592efe25SPierre Pronchery free(package_spdx);
83*592efe25SPierre Pronchery package_spdx = NULL;
84*592efe25SPierre Pronchery
85*592efe25SPierre Pronchery pkgconf_buffer_t concluded_buf = PKGCONF_BUFFER_INITIALIZER;
86*592efe25SPierre Pronchery pkgconf_buffer_append_fmt(&concluded_buf, "%s%chasConcludedLicense", pkg->id, sep);
87*592efe25SPierre Pronchery spdx_id_name = pkgconf_buffer_freeze(&concluded_buf);
88*592efe25SPierre Pronchery if (!spdx_id_name)
89*592efe25SPierre Pronchery goto err;
90*592efe25SPierre Pronchery
91*592efe25SPierre Pronchery package_spdx = spdxtool_util_get_spdx_id_string(client, "Relationship", spdx_id_name);
92*592efe25SPierre Pronchery free(spdx_id_name);
93*592efe25SPierre Pronchery if (!package_spdx)
94*592efe25SPierre Pronchery goto err;
95*592efe25SPierre Pronchery
96*592efe25SPierre Pronchery pkgconf_tuple_add(client, &pkg->vars, "hasConcludedLicense", package_spdx, false, 0);
97*592efe25SPierre Pronchery free(package_spdx);
98*592efe25SPierre Pronchery package_spdx = NULL;
99*592efe25SPierre Pronchery
100*592efe25SPierre Pronchery PKGCONF_FOREACH_LIST_ENTRY(pkg->license.head, node)
101*592efe25SPierre Pronchery {
102*592efe25SPierre Pronchery const pkgconf_license_t *license = node->data;
103*592efe25SPierre Pronchery if (license->type == PKGCONF_LICENSE_EXPRESSION)
104*592efe25SPierre Pronchery {
105*592efe25SPierre Pronchery if (!spdxtool_core_spdx_document_add_license(client, document, license->data))
106*592efe25SPierre Pronchery goto err;
107*592efe25SPierre Pronchery }
108*592efe25SPierre Pronchery }
109*592efe25SPierre Pronchery }
110*592efe25SPierre Pronchery
111*592efe25SPierre Pronchery node = calloc(1, sizeof(pkgconf_node_t));
112*592efe25SPierre Pronchery if (!node)
113*592efe25SPierre Pronchery goto err;
114*592efe25SPierre Pronchery
115*592efe25SPierre Pronchery pkgconf_node_insert_tail(node, sbom, &document->rootElement);
116*592efe25SPierre Pronchery return;
117*592efe25SPierre Pronchery
118*592efe25SPierre Pronchery err:
119*592efe25SPierre Pronchery pkgconf_error(client, "generate_spdx_package: failed for %s", pkg->id);
120*592efe25SPierre Pronchery free(package_spdx);
121*592efe25SPierre Pronchery free(spdx_id_string);
122*592efe25SPierre Pronchery spdxtool_software_sbom_free(sbom);
123*592efe25SPierre Pronchery }
124*592efe25SPierre Pronchery
125*592efe25SPierre Pronchery bool
spdxtool_generate(pkgconf_client_t * client,pkgconf_pkg_t * world,FILE * out,int maxdepth,const char * creation_time,const char * creation_id,const char * agent_name)126*592efe25SPierre Pronchery spdxtool_generate(pkgconf_client_t *client, pkgconf_pkg_t *world, FILE *out, int maxdepth,
127*592efe25SPierre Pronchery const char *creation_time, const char *creation_id, const char *agent_name)
128*592efe25SPierre Pronchery {
129*592efe25SPierre Pronchery const char *agent_name_string = agent_name ? agent_name : "Default";
130*592efe25SPierre Pronchery const char *creation_id_string = creation_id ? creation_id : "_:creationinfo_1";
131*592efe25SPierre Pronchery
132*592efe25SPierre Pronchery spdxtool_core_agent_t *agent = spdxtool_core_agent_new(client, creation_id_string, agent_name_string);
133*592efe25SPierre Pronchery if (!agent)
134*592efe25SPierre Pronchery {
135*592efe25SPierre Pronchery pkgconf_error(client, "Could not create agent struct");
136*592efe25SPierre Pronchery return false;
137*592efe25SPierre Pronchery }
138*592efe25SPierre Pronchery
139*592efe25SPierre Pronchery spdxtool_core_creation_info_t *creation = spdxtool_core_creation_info_new(client, agent->spdx_id, creation_id_string, creation_time);
140*592efe25SPierre Pronchery if (!creation)
141*592efe25SPierre Pronchery {
142*592efe25SPierre Pronchery pkgconf_error(client, "Could not create creation info struct");
143*592efe25SPierre Pronchery spdxtool_core_agent_free(agent);
144*592efe25SPierre Pronchery return false;
145*592efe25SPierre Pronchery }
146*592efe25SPierre Pronchery
147*592efe25SPierre Pronchery char *spdx_id_int = spdxtool_util_get_spdx_id_int(client, "spdxDocument");
148*592efe25SPierre Pronchery spdxtool_core_spdx_document_t *document = spdxtool_core_spdx_document_new(client, spdx_id_int, creation_id_string, agent->spdx_id);
149*592efe25SPierre Pronchery free(spdx_id_int);
150*592efe25SPierre Pronchery if (!document)
151*592efe25SPierre Pronchery {
152*592efe25SPierre Pronchery pkgconf_error(client, "Could not create document");
153*592efe25SPierre Pronchery spdxtool_core_creation_info_free(creation);
154*592efe25SPierre Pronchery spdxtool_core_agent_free(agent);
155*592efe25SPierre Pronchery return false;
156*592efe25SPierre Pronchery }
157*592efe25SPierre Pronchery
158*592efe25SPierre Pronchery int eflag = pkgconf_pkg_traverse(client, world, generate_spdx_package, document, maxdepth, 0);
159*592efe25SPierre Pronchery if (eflag != PKGCONF_PKG_ERRF_OK)
160*592efe25SPierre Pronchery {
161*592efe25SPierre Pronchery spdxtool_core_spdx_document_free(document);
162*592efe25SPierre Pronchery spdxtool_core_creation_info_free(creation);
163*592efe25SPierre Pronchery spdxtool_core_agent_free(agent);
164*592efe25SPierre Pronchery return false;
165*592efe25SPierre Pronchery }
166*592efe25SPierre Pronchery
167*592efe25SPierre Pronchery spdxtool_serialize_value_t *root = spdxtool_serialize_sbom(client, agent, creation, document);
168*592efe25SPierre Pronchery if (!root)
169*592efe25SPierre Pronchery {
170*592efe25SPierre Pronchery spdxtool_core_spdx_document_free(document);
171*592efe25SPierre Pronchery spdxtool_core_creation_info_free(creation);
172*592efe25SPierre Pronchery spdxtool_core_agent_free(agent);
173*592efe25SPierre Pronchery return false;
174*592efe25SPierre Pronchery }
175*592efe25SPierre Pronchery
176*592efe25SPierre Pronchery pkgconf_buffer_t buffer = PKGCONF_BUFFER_INITIALIZER;
177*592efe25SPierre Pronchery spdxtool_serialize_value_to_buf(&buffer, root, 0);
178*592efe25SPierre Pronchery spdxtool_serialize_value_free(root);
179*592efe25SPierre Pronchery
180*592efe25SPierre Pronchery bool ret = pkgconf_output_file_fmt(out, "%s\n", pkgconf_buffer_str(&buffer));
181*592efe25SPierre Pronchery pkgconf_buffer_finalize(&buffer);
182*592efe25SPierre Pronchery
183*592efe25SPierre Pronchery spdxtool_core_spdx_document_free(document);
184*592efe25SPierre Pronchery spdxtool_core_creation_info_free(creation);
185*592efe25SPierre Pronchery spdxtool_core_agent_free(agent);
186*592efe25SPierre Pronchery
187*592efe25SPierre Pronchery if (!ret)
188*592efe25SPierre Pronchery pkgconf_error(client, "spdxtool: Could not output to file: %s", strerror(errno));
189*592efe25SPierre Pronchery return ret;
190*592efe25SPierre Pronchery }
191