1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
2 *
3 * Copyright (C) 2014 Richard Hughes <richard@hughsie.com>
4 *
5 * SPDX-License-Identifier: LGPL-2.1+
6 */
7
8 /**
9 * SECTION:asb-package-rpm
10 * @short_description: Object representing a .RPM package file.
11 * @stability: Unstable
12 *
13 * This object represents one .rpm package file.
14 */
15
16 #include "config.h"
17
18 #include <limits.h>
19 #include <archive.h>
20 #include <archive_entry.h>
21
22 #include <rpm/rpmlib.h>
23 #include <rpm/rpmts.h>
24
25 #include "asb-package-rpm.h"
26 #include "asb-plugin.h"
27
28 typedef struct
29 {
30 Header h;
31 } AsbPackageRpmPrivate;
32
G_DEFINE_TYPE_WITH_PRIVATE(AsbPackageRpm,asb_package_rpm,ASB_TYPE_PACKAGE)33 G_DEFINE_TYPE_WITH_PRIVATE (AsbPackageRpm, asb_package_rpm, ASB_TYPE_PACKAGE)
34
35 #define GET_PRIVATE(o) (asb_package_rpm_get_instance_private (o))
36
37 static void
38 asb_package_rpm_finalize (GObject *object)
39 {
40 AsbPackageRpm *pkg = ASB_PACKAGE_RPM (object);
41 AsbPackageRpmPrivate *priv = GET_PRIVATE (pkg);
42
43 if (priv->h != NULL)
44 headerFree (priv->h);
45
46 G_OBJECT_CLASS (asb_package_rpm_parent_class)->finalize (object);
47 }
48
49 static void
asb_package_rpm_init(AsbPackageRpm * pkg)50 asb_package_rpm_init (AsbPackageRpm *pkg)
51 {
52 }
53
54 static void
asb_package_rpm_set_license(AsbPackage * pkg,const gchar * license)55 asb_package_rpm_set_license (AsbPackage *pkg, const gchar *license)
56 {
57 guint i;
58 g_autofree gchar *new = NULL;
59 g_auto(GStrv) tokens = NULL;
60
61 /* this isn't supposed to happen */
62 if (license == NULL) {
63 asb_package_log (pkg, ASB_PACKAGE_LOG_LEVEL_WARNING,
64 "no license!");
65 return;
66 }
67
68 /* tokenize the license string and log non SPDX licenses */
69 new = as_utils_license_to_spdx (license);
70 tokens = as_utils_spdx_license_tokenize (new);
71 for (i = 0; tokens[i] != NULL; i++) {
72
73 /* ignore */
74 if (tokens[i][0] == '(' ||
75 tokens[i][0] == ')' ||
76 tokens[i][0] == '&' ||
77 tokens[i][0] == '|')
78 continue;
79
80 /* already SPDX */
81 if (tokens[i][0] == '@')
82 continue;
83
84 /* no matching SPDX entry */
85 asb_package_log (pkg,
86 ASB_PACKAGE_LOG_LEVEL_WARNING,
87 "Unable to currently map Fedora "
88 "license '%s' to SPDX", tokens[i]);
89 }
90 asb_package_set_license (pkg, new);
91 }
92
93 static void
asb_package_rpm_set_source(AsbPackage * pkg,const gchar * source)94 asb_package_rpm_set_source (AsbPackage *pkg, const gchar *source)
95 {
96 gchar *tmp;
97 g_autofree gchar *srcrpm = NULL;
98
99 /* this isn't supposed to happen */
100 if (source == NULL) {
101 asb_package_log (pkg, ASB_PACKAGE_LOG_LEVEL_WARNING,
102 "no source!");
103 return;
104 }
105 srcrpm = g_strdup (source);
106 tmp = g_strstr_len (srcrpm, -1, ".src.rpm");
107 if (tmp != NULL)
108 *tmp = '\0';
109 asb_package_set_source (pkg, srcrpm);
110
111 /* get the srpm name */
112 tmp = g_strrstr (srcrpm, "-");
113 if (tmp != NULL)
114 *tmp = '\0';
115 tmp = g_strrstr (srcrpm, "-");
116 if (tmp != NULL)
117 *tmp = '\0';
118 asb_package_set_source_pkgname (pkg, srcrpm);
119 }
120
121 static gboolean
asb_package_rpm_ensure_nevra(AsbPackage * pkg,GError ** error)122 asb_package_rpm_ensure_nevra (AsbPackage *pkg, GError **error)
123 {
124 AsbPackageRpm *pkg_rpm = ASB_PACKAGE_RPM (pkg);
125 AsbPackageRpmPrivate *priv = GET_PRIVATE (pkg_rpm);
126 rpmtd td;
127
128 td = rpmtdNew ();
129 headerGet (priv->h, RPMTAG_NAME, td, HEADERGET_MINMEM);
130 asb_package_set_name (pkg, rpmtdGetString (td));
131 headerGet (priv->h, RPMTAG_VERSION, td, HEADERGET_MINMEM);
132 asb_package_set_version (pkg, rpmtdGetString (td));
133 headerGet (priv->h, RPMTAG_RELEASE, td, HEADERGET_MINMEM);
134 asb_package_set_release (pkg, rpmtdGetString (td));
135 headerGet (priv->h, RPMTAG_ARCH, td, HEADERGET_MINMEM);
136 asb_package_set_arch (pkg, rpmtdGetString (td));
137 headerGet (priv->h, RPMTAG_EPOCH, td, HEADERGET_MINMEM);
138 asb_package_set_epoch (pkg, (guint) rpmtdGetNumber (td));
139 rpmtdFree (td);
140 return TRUE;
141 }
142
143 static gboolean
asb_package_rpm_ensure_source(AsbPackage * pkg,GError ** error)144 asb_package_rpm_ensure_source (AsbPackage *pkg, GError **error)
145 {
146 AsbPackageRpm *pkg_rpm = ASB_PACKAGE_RPM (pkg);
147 AsbPackageRpmPrivate *priv = GET_PRIVATE (pkg_rpm);
148 rpmtd td;
149
150 td = rpmtdNew ();
151 headerGet (priv->h, RPMTAG_SOURCERPM, td, HEADERGET_MINMEM);
152 asb_package_rpm_set_source (pkg, rpmtdGetString (td));
153 rpmtdFree (td);
154 return TRUE;
155 }
156
157 static gboolean
asb_package_rpm_ensure_url(AsbPackage * pkg,GError ** error)158 asb_package_rpm_ensure_url (AsbPackage *pkg, GError **error)
159 {
160 AsbPackageRpm *pkg_rpm = ASB_PACKAGE_RPM (pkg);
161 AsbPackageRpmPrivate *priv = GET_PRIVATE (pkg_rpm);
162 rpmtd td;
163
164 td = rpmtdNew ();
165 headerGet (priv->h, RPMTAG_URL, td, HEADERGET_MINMEM);
166 asb_package_set_url (pkg, rpmtdGetString (td));
167 rpmtdFree (td);
168 return TRUE;
169 }
170
171 static gboolean
asb_package_rpm_ensure_vcs(AsbPackage * pkg,GError ** error)172 asb_package_rpm_ensure_vcs (AsbPackage *pkg, GError **error)
173 {
174 AsbPackageRpm *pkg_rpm = ASB_PACKAGE_RPM (pkg);
175 AsbPackageRpmPrivate *priv = GET_PRIVATE (pkg_rpm);
176 rpmtd td;
177
178 td = rpmtdNew ();
179 headerGet (priv->h, RPMTAG_VCS, td, HEADERGET_MINMEM);
180 asb_package_set_vcs (pkg, rpmtdGetString (td));
181 rpmtdFree (td);
182 return TRUE;
183 }
184
185 static gboolean
asb_package_rpm_ensure_license(AsbPackage * pkg,GError ** error)186 asb_package_rpm_ensure_license (AsbPackage *pkg, GError **error)
187 {
188 AsbPackageRpm *pkg_rpm = ASB_PACKAGE_RPM (pkg);
189 AsbPackageRpmPrivate *priv = GET_PRIVATE (pkg_rpm);
190 rpmtd td;
191
192 td = rpmtdNew ();
193 headerGet (priv->h, RPMTAG_LICENSE, td, HEADERGET_MINMEM);
194 asb_package_rpm_set_license (pkg, rpmtdGetString (td));
195 rpmtdFree (td);
196 return TRUE;
197 }
198
199 static void
asb_package_rpm_add_release(AsbPackage * pkg,guint64 timestamp,const gchar * name,const gchar * text)200 asb_package_rpm_add_release (AsbPackage *pkg,
201 guint64 timestamp,
202 const gchar *name,
203 const gchar *text)
204 {
205 AsRelease *release;
206 const gchar *version;
207 gchar *tmp;
208 gchar *vr;
209 g_autofree gchar *name_dup = NULL;
210
211 /* get last string chunk */
212 name_dup = g_strchomp (g_strdup (name));
213 vr = g_strrstr (name_dup, " ");
214 if (vr == NULL)
215 return;
216
217 /* get last string chunk */
218 version = vr + 1;
219
220 /* ignore version-less dashed email address, e.g.
221 * 'Fedora Release Engineering <rel-eng@lists.fedoraproject.org>' */
222 if (g_strstr_len (version, -1, "@") != NULL ||
223 g_strstr_len (version, -1, "<") != NULL ||
224 g_strstr_len (version, -1, ">") != NULL)
225 return;
226
227 tmp = g_strrstr_len (version, -1, "-");
228 if (tmp != NULL)
229 *tmp = '\0';
230
231 /* remove any epoch */
232 tmp = g_strstr_len (version, -1, ":");
233 if (tmp != NULL)
234 version = tmp + 1;
235
236 /* remove any version prefix */
237 if (version != NULL && version[0] == '-')
238 version = version + 1;
239
240 /* is version already in the database */
241 release = asb_package_get_release (pkg, version);
242 if (release != NULL) {
243 /* use the earlier timestamp to ignore auto-rebuilds with just
244 * a bumped release */
245 if (timestamp < as_release_get_timestamp (release))
246 as_release_set_timestamp (release, timestamp);
247 } else {
248 release = as_release_new ();
249 as_release_set_version (release, version);
250 as_release_set_timestamp (release, timestamp);
251 asb_package_add_release (pkg, version, release);
252 g_object_unref (release);
253 }
254 }
255
256 static gboolean
asb_package_rpm_ensure_releases(AsbPackage * pkg,GError ** error)257 asb_package_rpm_ensure_releases (AsbPackage *pkg, GError **error)
258 {
259 AsbPackageRpm *pkg_rpm = ASB_PACKAGE_RPM (pkg);
260 AsbPackageRpmPrivate *priv = GET_PRIVATE (pkg_rpm);
261 guint i;
262 rpmtd td[3] = { NULL, NULL, NULL };
263
264 /* read out the file list */
265 for (i = 0; i < 3; i++)
266 td[i] = rpmtdNew ();
267 /* get the ChangeLog info */
268 headerGet (priv->h, RPMTAG_CHANGELOGTIME, td[0], HEADERGET_MINMEM);
269 headerGet (priv->h, RPMTAG_CHANGELOGNAME, td[1], HEADERGET_MINMEM);
270 headerGet (priv->h, RPMTAG_CHANGELOGTEXT, td[2], HEADERGET_MINMEM);
271 while (rpmtdNext (td[0]) != -1 &&
272 rpmtdNext (td[1]) != -1 &&
273 rpmtdNext (td[2]) != -1) {
274 asb_package_rpm_add_release (pkg,
275 rpmtdGetNumber (td[0]),
276 rpmtdGetString (td[1]),
277 rpmtdGetString (td[2]));
278 }
279 for (i = 0; i < 3; i++) {
280 rpmtdFreeData (td[i]);
281 rpmtdFree (td[i]);
282 }
283 return TRUE;
284 }
285
286 static gboolean
asb_package_rpm_ensure_deps(AsbPackage * pkg,GError ** error)287 asb_package_rpm_ensure_deps (AsbPackage *pkg, GError **error)
288 {
289 AsbPackageRpm *pkg_rpm = ASB_PACKAGE_RPM (pkg);
290 AsbPackageRpmPrivate *priv = GET_PRIVATE (pkg_rpm);
291 const gchar *dep;
292 gboolean ret = TRUE;
293 gchar *tmp;
294 gint rc;
295 rpmtd td = NULL;
296
297 /* read out the dep list */
298 td = rpmtdNew ();
299 rc = headerGet (priv->h, RPMTAG_REQUIRENAME, td, HEADERGET_MINMEM);
300 if (!rc) {
301 ret = FALSE;
302 g_set_error (error,
303 ASB_PLUGIN_ERROR,
304 ASB_PLUGIN_ERROR_FAILED,
305 "Failed to read list of requires %s",
306 asb_package_get_filename (pkg));
307 goto out;
308 }
309 while (rpmtdNext (td) != -1) {
310 g_autofree gchar *dep_no_qual = NULL;
311 dep = rpmtdGetString (td);
312 if (g_str_has_prefix (dep, "rpmlib"))
313 continue;
314 if (g_strcmp0 (dep, "/bin/sh") == 0)
315 continue;
316 dep_no_qual = g_strdup (dep);
317 tmp = g_strstr_len (dep_no_qual, -1, "(");
318 if (tmp != NULL)
319 *tmp = '\0';
320 asb_package_add_dep (pkg, dep_no_qual);
321 }
322 /* Add the corresponding -lang package as a dependency */
323 tmp = g_strconcat (asb_package_get_name (pkg), "-lang", NULL);
324 asb_package_add_dep (pkg, tmp);
325 g_free (tmp);
326 out:
327 rpmtdFreeData (td);
328 rpmtdFree (td);
329 return ret;
330 }
331
332 static gboolean
asb_package_rpm_ensure_filelists(AsbPackage * pkg,GError ** error)333 asb_package_rpm_ensure_filelists (AsbPackage *pkg, GError **error)
334 {
335 AsbPackageRpm *pkg_rpm = ASB_PACKAGE_RPM (pkg);
336 AsbPackageRpmPrivate *priv = GET_PRIVATE (pkg_rpm);
337 gboolean ret = TRUE;
338 gint rc;
339 guint i;
340 rpmtd td[3] = { NULL, NULL, NULL };
341 g_autofree const gchar **dirnames = NULL;
342 g_autofree gint32 *dirindex = NULL;
343 g_auto(GStrv) filelist = NULL;
344
345 /* is a virtual package with no files */
346 if (!headerIsEntry (priv->h, RPMTAG_DIRINDEXES))
347 return TRUE;
348
349 /* read out the file list */
350 for (i = 0; i < 3; i++)
351 td[i] = rpmtdNew ();
352 rc = headerGet (priv->h, RPMTAG_DIRNAMES, td[0], HEADERGET_MINMEM);
353 if (rc)
354 rc = headerGet (priv->h, RPMTAG_BASENAMES, td[1], HEADERGET_MINMEM);
355 if (rc)
356 rc = headerGet (priv->h, RPMTAG_DIRINDEXES, td[2], HEADERGET_MINMEM);
357 if (!rc) {
358 ret = FALSE;
359 g_set_error (error,
360 ASB_PLUGIN_ERROR,
361 ASB_PLUGIN_ERROR_FAILED,
362 "Failed to read package file list %s",
363 asb_package_get_filename (pkg));
364 goto out;
365 }
366 i = 0;
367 dirnames = g_new0 (const gchar *, rpmtdCount (td[0]) + 1);
368 while (rpmtdNext (td[0]) != -1)
369 dirnames[i++] = rpmtdGetString (td[0]);
370 i = 0;
371 dirindex = g_new0 (gint32, rpmtdCount (td[2]) + 1);
372 while (rpmtdNext (td[2]) != -1)
373 dirindex[i++] = (gint32) rpmtdGetNumber (td[2]);
374 i = 0;
375 filelist = g_new0 (gchar *, rpmtdCount (td[1]) + 1);
376 while (rpmtdNext (td[1]) != -1) {
377 filelist[i] = g_build_filename (dirnames[dirindex[i]],
378 rpmtdGetString (td[1]),
379 NULL);
380 i++;
381 }
382 asb_package_set_filelist (pkg, filelist);
383 out:
384 for (i = 0; i < 3; i++) {
385 rpmtdFreeData (td[i]);
386 rpmtdFree (td[i]);
387 }
388 return ret;
389 }
390
391 static const gchar *
asb_package_rpm_strerror(rpmRC rc)392 asb_package_rpm_strerror (rpmRC rc)
393 {
394 const gchar *str;
395 switch (rc) {
396 case RPMRC_OK:
397 str = "Generic success";
398 break;
399 case RPMRC_NOTFOUND:
400 str = "Generic not found";
401 break;
402 case RPMRC_FAIL:
403 str = "Generic failure";
404 break;
405 case RPMRC_NOTTRUSTED:
406 str = "Signature is OK, but key is not trusted";
407 break;
408 case RPMRC_NOKEY:
409 str = "Public key is unavailable";
410 break;
411 default:
412 str = "unknown";
413 break;
414 }
415 return str;
416 }
417
418 static gboolean
asb_package_rpm_close(AsbPackage * pkg,GError ** error)419 asb_package_rpm_close (AsbPackage *pkg, GError **error)
420 {
421 AsbPackageRpm *pkg_rpm = ASB_PACKAGE_RPM (pkg);
422 AsbPackageRpmPrivate *priv = GET_PRIVATE (pkg_rpm);
423 if (priv->h != NULL)
424 headerFree (priv->h);
425 priv->h = NULL;
426 return TRUE;
427 }
428
429 static gboolean
asb_package_rpm_open(AsbPackage * pkg,const gchar * filename,GError ** error)430 asb_package_rpm_open (AsbPackage *pkg, const gchar *filename, GError **error)
431 {
432 AsbPackageRpm *pkg_rpm = ASB_PACKAGE_RPM (pkg);
433 AsbPackageRpmPrivate *priv = GET_PRIVATE (pkg_rpm);
434 FD_t fd;
435 gboolean ret = TRUE;
436 rpmRC rc;
437 rpmts ts;
438
439 /* open the file */
440 ts = rpmtsCreate ();
441 rpmtsSetVSFlags (ts, _RPMVSF_NODIGESTS | _RPMVSF_NOSIGNATURES);
442 fd = Fopen (filename, "r");
443 if (fd == NULL) {
444 ret = FALSE;
445 g_set_error (error,
446 ASB_PLUGIN_ERROR,
447 ASB_PLUGIN_ERROR_FAILED,
448 "Failed to open package %s", filename);
449 goto out;
450 }
451
452 /* create package */
453 rc = rpmReadPackageFile (ts, fd, filename, &priv->h);
454 if (rc == RPMRC_FAIL) {
455 ret = FALSE;
456 g_set_error (error,
457 ASB_PLUGIN_ERROR,
458 ASB_PLUGIN_ERROR_FAILED,
459 "Failed to read package %s: %s",
460 filename, asb_package_rpm_strerror (rc));
461 goto out;
462 }
463
464 /* read package stuff */
465 ret = asb_package_rpm_ensure_nevra (pkg, error);
466 if (!ret)
467 goto out;
468 out:
469 rpmtsFree (ts);
470 Fclose (fd);
471 return ret;
472 }
473
474 static gboolean
asb_package_rpm_ensure(AsbPackage * pkg,AsbPackageEnsureFlags flags,GError ** error)475 asb_package_rpm_ensure (AsbPackage *pkg,
476 AsbPackageEnsureFlags flags,
477 GError **error)
478 {
479 if ((flags & ASB_PACKAGE_ENSURE_NEVRA) > 0) {
480 if (!asb_package_rpm_ensure_nevra (pkg, error))
481 return FALSE;
482 }
483 if ((flags & ASB_PACKAGE_ENSURE_DEPS) > 0) {
484 if (!asb_package_rpm_ensure_deps (pkg, error))
485 return FALSE;
486 }
487 if ((flags & ASB_PACKAGE_ENSURE_RELEASES) > 0) {
488 if (!asb_package_rpm_ensure_releases (pkg, error))
489 return FALSE;
490 }
491 if ((flags & ASB_PACKAGE_ENSURE_FILES) > 0) {
492 if (!asb_package_rpm_ensure_filelists (pkg, error))
493 return FALSE;
494 }
495 if ((flags & ASB_PACKAGE_ENSURE_LICENSE) > 0) {
496 if (!asb_package_rpm_ensure_license (pkg, error))
497 return FALSE;
498 }
499 if ((flags & ASB_PACKAGE_ENSURE_URL) > 0) {
500 if (!asb_package_rpm_ensure_url (pkg, error))
501 return FALSE;
502 }
503 if ((flags & ASB_PACKAGE_ENSURE_SOURCE) > 0) {
504 if (!asb_package_rpm_ensure_source (pkg, error))
505 return FALSE;
506 }
507 if ((flags & ASB_PACKAGE_ENSURE_VCS) > 0) {
508 if (!asb_package_rpm_ensure_vcs (pkg, error))
509 return FALSE;
510 }
511 return TRUE;
512 }
513
514 static gint
asb_package_rpm_compare(AsbPackage * pkg1,AsbPackage * pkg2)515 asb_package_rpm_compare (AsbPackage *pkg1, AsbPackage *pkg2)
516 {
517 return rpmvercmp (asb_package_get_evr (pkg1),
518 asb_package_get_evr (pkg2));
519 }
520
521 static void
asb_package_rpm_class_init(AsbPackageRpmClass * klass)522 asb_package_rpm_class_init (AsbPackageRpmClass *klass)
523 {
524 AsbPackageClass *package_class = ASB_PACKAGE_CLASS (klass);
525 GObjectClass *object_class = G_OBJECT_CLASS (klass);
526
527 object_class->finalize = asb_package_rpm_finalize;
528 package_class->open = asb_package_rpm_open;
529 package_class->close = asb_package_rpm_close;
530 package_class->ensure = asb_package_rpm_ensure;
531 package_class->compare = asb_package_rpm_compare;
532 }
533
534 static gpointer
asb_package_rpm_init_cb(gpointer user_data)535 asb_package_rpm_init_cb (gpointer user_data)
536 {
537 rpmReadConfigFiles (NULL, NULL);
538 return NULL;
539 }
540
541 /**
542 * asb_package_rpm_new:
543 *
544 * Creates a new RPM package.
545 *
546 * Returns: a package
547 *
548 * Since: 0.1.0
549 **/
550 AsbPackage *
asb_package_rpm_new(void)551 asb_package_rpm_new (void)
552 {
553 AsbPackage *pkg;
554 static GOnce rpm_init = G_ONCE_INIT;
555 g_once (&rpm_init, asb_package_rpm_init_cb, NULL);
556 pkg = g_object_new (ASB_TYPE_PACKAGE_RPM, NULL);
557 return ASB_PACKAGE (pkg);
558 }
559