1 /** \ingroup rpmcli
2 * \file lib/query.c
3 * Display tag values from package metadata.
4 */
5
6 #include "system.h"
7
8 #include <errno.h>
9 #include <inttypes.h>
10 #include <ctype.h>
11
12 #include <rpm/rpmcli.h>
13 #include <rpm/header.h>
14 #include <rpm/rpmdb.h>
15 #include <rpm/rpmfi.h>
16 #include <rpm/rpmts.h>
17 #include <rpm/rpmsq.h>
18 #include <rpm/rpmlog.h>
19 #include <rpm/rpmfileutil.h> /* rpmCleanPath */
20
21 #include "lib/rpmgi.h"
22 #include "lib/manifest.h"
23
24 #include "debug.h"
25
26
27 /**
28 */
printFileInfo(const char * name,rpm_loff_t size,unsigned short mode,unsigned int mtime,unsigned short rdev,unsigned int nlink,const char * owner,const char * group,const char * linkto,time_t now)29 static void printFileInfo(const char * name,
30 rpm_loff_t size, unsigned short mode,
31 unsigned int mtime,
32 unsigned short rdev, unsigned int nlink,
33 const char * owner, const char * group,
34 const char * linkto, time_t now)
35 {
36 char sizefield[21];
37 char ownerfield[8+1], groupfield[8+1];
38 char timefield[100];
39 time_t when = mtime; /* important if sizeof(int32_t) ! sizeof(time_t) */
40 struct tm * tm, _tm;
41 char * perms = rpmPermsString(mode);
42 char *link = NULL;
43
44 rstrlcpy(ownerfield, owner, sizeof(ownerfield));
45 rstrlcpy(groupfield, group, sizeof(groupfield));
46
47 /* this is normally right */
48 snprintf(sizefield, sizeof(sizefield), "%20" PRIu64, size);
49
50 /* this knows too much about dev_t */
51
52 if (S_ISLNK(mode)) {
53 rasprintf(&link, "%s -> %s", name, linkto);
54 } else if (S_ISCHR(mode)) {
55 perms[0] = 'c';
56 snprintf(sizefield, sizeof(sizefield), "%3u, %3u", ((unsigned)(rdev >> 8) & 0xff),
57 ((unsigned)rdev & 0xff));
58 } else if (S_ISBLK(mode)) {
59 perms[0] = 'b';
60 snprintf(sizefield, sizeof(sizefield), "%3u, %3u", ((unsigned)(rdev >> 8) & 0xff),
61 ((unsigned)rdev & 0xff));
62 }
63
64 /* Convert file mtime to display format */
65 tm = localtime_r(&when, &_tm);
66 timefield[0] = '\0';
67 if (tm != NULL)
68 { const char *fmt;
69 if (now > when + 6L * 30L * 24L * 60L * 60L || /* Old. */
70 now < when - 60L * 60L) /* In the future. */
71 {
72 /* The file is fairly old or in the future.
73 * POSIX says the cutoff is 6 months old;
74 * approximate this by 6*30 days.
75 * Allow a 1 hour slop factor for what is considered "the future",
76 * to allow for NFS server/client clock disagreement.
77 * Show the year instead of the time of day.
78 */
79 fmt = "%b %e %Y";
80 } else {
81 fmt = "%b %e %H:%M";
82 }
83 (void)strftime(timefield, sizeof(timefield) - 1, fmt, tm);
84 }
85
86 rpmlog(RPMLOG_NOTICE, "%s %4d %-8s %-8s %10s %s %s\n", perms,
87 (int)nlink, ownerfield, groupfield, sizefield, timefield,
88 link ? link : name);
89 free(perms);
90 free(link);
91 }
92
showQueryPackage(QVA_t qva,rpmts ts,Header h)93 int showQueryPackage(QVA_t qva, rpmts ts, Header h)
94 {
95 rpmfi fi = NULL;
96 rpmfiFlags fiflags = (RPMFI_NOHEADER | RPMFI_FLAGS_QUERY);
97 int rc = 0; /* XXX FIXME: need real return code */
98 time_t now = 0;
99
100 if (qva->qva_queryFormat != NULL) {
101 const char *errstr;
102 char *str = headerFormat(h, qva->qva_queryFormat, &errstr);
103
104 if ( str != NULL ) {
105 rpmlog(RPMLOG_NOTICE, "%s", str);
106 free(str);
107 } else {
108 rpmlog(RPMLOG_ERR, _("incorrect format: %s\n"), errstr);
109 }
110 }
111
112 /* Inclusion flags traditionally imply list mode */
113 if (qva->qva_incattr)
114 qva->qva_flags |= QUERY_FOR_LIST;
115
116 if (!(qva->qva_flags & QUERY_FOR_LIST))
117 goto exit;
118
119 if (!(qva->qva_flags & QUERY_FOR_DUMPFILES))
120 fiflags |= RPMFI_NOFILEDIGESTS;
121
122 fi = rpmfiNew(ts, h, RPMTAG_BASENAMES, fiflags);
123 if (rpmfiFC(fi) <= 0) {
124 rpmlog(RPMLOG_NOTICE, _("(contains no files)\n"));
125 goto exit;
126 }
127
128 fi = rpmfiInit(fi, 0);
129 while (rpmfiNext(fi) >= 0) {
130 rpmfileAttrs fflags = rpmfiFFlags(fi);
131 rpm_mode_t fmode = rpmfiFMode(fi);
132 rpm_rdev_t frdev = rpmfiFRdev(fi);
133 rpm_time_t fmtime = rpmfiFMtime(fi);
134 rpmfileState fstate = rpmfiFState(fi);
135 rpm_loff_t fsize = rpmfiFSize(fi);
136 const char *fn = rpmfiFN(fi);
137 const char *fuser = rpmfiFUser(fi);
138 const char *fgroup = rpmfiFGroup(fi);
139 const char *flink = rpmfiFLink(fi);
140 char *buf = NULL;
141
142 /* If filtering by inclusion, skip non-matching (eg --configfiles) */
143 if (qva->qva_incattr && !(fflags & qva->qva_incattr))
144 continue;
145
146 /* Skip on attributes (eg from --noghost) */
147 if (fflags & qva->qva_excattr)
148 continue;
149
150 if (qva->qva_flags & QUERY_FOR_STATE) {
151 switch (fstate) {
152 case RPMFILE_STATE_NORMAL:
153 rstrcat(&buf, _("normal "));
154 break;
155 case RPMFILE_STATE_REPLACED:
156 rstrcat(&buf, _("replaced "));
157 break;
158 case RPMFILE_STATE_NOTINSTALLED:
159 rstrcat(&buf, _("not installed "));
160 break;
161 case RPMFILE_STATE_NETSHARED:
162 rstrcat(&buf, _("net shared "));
163 break;
164 case RPMFILE_STATE_WRONGCOLOR:
165 rstrcat(&buf, _("wrong color "));
166 break;
167 case RPMFILE_STATE_MISSING:
168 rstrcat(&buf, _("(no state) "));
169 break;
170 default:
171 rasprintf(&buf, _("(unknown %3d) "), fstate);
172 break;
173 }
174 }
175
176 if (qva->qva_flags & QUERY_FOR_DUMPFILES) {
177 char *add, *fdigest;
178 fdigest = rpmfiFDigestHex(fi, NULL);
179 rasprintf(&add, "%s %" PRIu64 " %d %s 0%o ",
180 fn, fsize, fmtime, fdigest ? fdigest : "", fmode);
181 rstrcat(&buf, add);
182 free(add);
183 free(fdigest);
184
185 if (fuser && fgroup) {
186 rasprintf(&add, "%s %s", fuser, fgroup);
187 rstrcat(&buf, add);
188 free(add);
189 } else {
190 rpmlog(RPMLOG_ERR,
191 _("package has not file owner/group lists\n"));
192 }
193
194 rasprintf(&add, " %s %s %u %s",
195 fflags & RPMFILE_CONFIG ? "1" : "0",
196 fflags & RPMFILE_DOC ? "1" : "0",
197 frdev,
198 (flink && *flink ? flink : "X"));
199 rpmlog(RPMLOG_NOTICE, "%s%s\n", buf, add);
200 free(add);
201 } else
202 if (!rpmIsVerbose()) {
203 rpmlog(RPMLOG_NOTICE, "%s%s\n", buf ? buf : "", fn);
204 }
205 else {
206 uint32_t fnlink = rpmfiFNlink(fi);
207
208 /* XXX Adjust directory link count and size for display output. */
209 if (S_ISDIR(fmode)) {
210 fnlink++;
211 fsize = 0;
212 }
213
214 if (fuser && fgroup) {
215 /* On first call, grab snapshot of now */
216 if (now == 0)
217 now = time(NULL);
218 if (buf) {
219 rpmlog(RPMLOG_NOTICE, "%s", buf);
220 }
221 printFileInfo(fn, fsize, fmode, fmtime, frdev, fnlink,
222 fuser, fgroup, flink, now);
223 } else {
224 rpmlog(RPMLOG_ERR,
225 _("package has neither file owner or id lists\n"));
226 }
227 }
228 free(buf);
229 }
230
231 rc = 0;
232
233 exit:
234 rpmfiFree(fi);
235 return rc;
236 }
237
rpmDisplayQueryTags(FILE * fp)238 void rpmDisplayQueryTags(FILE * fp)
239 {
240 static const char * const tagTypeNames[] = {
241 "", "char", "int8", "int16", "int32", "int64",
242 "string", "blob", "argv", "i18nstring"
243 };
244 const char *tname, *sname;
245 rpmtd names = rpmtdNew();
246 (void) rpmTagGetNames(names, 1);
247
248 while ((tname = rpmtdNextString(names))) {
249 sname = tname + strlen("RPMTAG_");
250 if (rpmIsVerbose()) {
251 rpmTagVal tag = rpmTagGetValue(sname);
252 rpmTagType type = rpmTagGetTagType(tag);
253 fprintf(fp, "%-20s %6d", sname, tag);
254 if (type > RPM_NULL_TYPE && type <= RPM_MAX_TYPE)
255 fprintf(fp, " %s", tagTypeNames[type]);
256 } else {
257 fprintf(fp, "%s", sname);
258 }
259 fprintf(fp, "\n");
260 }
261 rpmtdFree(names);
262 }
263
rpmgiShowMatches(QVA_t qva,rpmts ts,rpmgi gi)264 static int rpmgiShowMatches(QVA_t qva, rpmts ts, rpmgi gi)
265 {
266 int ec = 0;
267 Header h;
268
269 while ((h = rpmgiNext(gi)) != NULL) {
270 int rc;
271
272 rpmsqPoll();
273 if ((rc = qva->qva_showPackage(qva, ts, h)) != 0)
274 ec = rc;
275 headerFree(h);
276 }
277 return ec + rpmgiNumErrors(gi);
278 }
279
rpmcliShowMatches(QVA_t qva,rpmts ts,rpmdbMatchIterator mi)280 static int rpmcliShowMatches(QVA_t qva, rpmts ts, rpmdbMatchIterator mi)
281 {
282 Header h;
283 int ec = 0;
284
285 if (mi == NULL)
286 return 1;
287
288 while ((h = rpmdbNextIterator(mi)) != NULL) {
289 int rc;
290 rpmsqPoll();
291 if ((rc = qva->qva_showPackage(qva, ts, h)) != 0)
292 ec = rc;
293 }
294 return ec;
295 }
296
matchesIterator(rpmts ts,rpmDbiTag dbi,const void * arg,size_t arglen)297 static rpmdbMatchIterator matchesIterator(rpmts ts, rpmDbiTag dbi,
298 const void *arg, size_t arglen)
299 {
300 unsigned int matches = 0;
301 rpmdbMatchIterator mi = rpmtsInitIterator(ts, dbi, arg, arglen);
302 while (rpmdbNextIterator(mi) != NULL)
303 matches++;
304 mi = rpmdbFreeIterator(mi);
305 if (matches)
306 mi = rpmtsInitIterator(ts, dbi, arg, arglen);
307 return mi;
308 }
309
initQueryIterator(QVA_t qva,rpmts ts,const char * arg)310 static rpmdbMatchIterator initQueryIterator(QVA_t qva, rpmts ts, const char * arg)
311 {
312 const char * s;
313 int i;
314 rpmdbMatchIterator mi = NULL;
315
316 (void) rpmsqPoll();
317
318 if (qva->qva_showPackage == NULL)
319 goto exit;
320
321 switch (qva->qva_source) {
322 case RPMQV_GROUP:
323 mi = rpmtsInitIterator(ts, RPMDBI_GROUP, arg, 0);
324 if (mi == NULL) {
325 rpmlog(RPMLOG_NOTICE,
326 _("group %s does not contain any packages\n"), arg);
327 }
328 break;
329
330 case RPMQV_TRIGGEREDBY:
331 mi = rpmtsInitIterator(ts, RPMDBI_TRIGGERNAME, arg, 0);
332 if (mi == NULL) {
333 rpmlog(RPMLOG_NOTICE, _("no package triggers %s\n"), arg);
334 }
335 break;
336
337 case RPMQV_PKGID:
338 { unsigned char MD5[16];
339 unsigned char * t;
340
341 for (i = 0, s = arg; *s && isxdigit(*s); s++, i++)
342 {};
343 if (i != 32) {
344 rpmlog(RPMLOG_ERR, _("malformed %s: %s\n"), "pkgid", arg);
345 goto exit;
346 }
347
348 MD5[0] = '\0';
349 for (i = 0, t = MD5, s = arg; i < 16; i++, t++, s += 2)
350 *t = (rnibble(s[0]) << 4) | rnibble(s[1]);
351
352 mi = rpmtsInitIterator(ts, RPMDBI_SIGMD5, MD5, sizeof(MD5));
353 if (mi == NULL) {
354 rpmlog(RPMLOG_NOTICE, _("no package matches %s: %s\n"),
355 "pkgid", arg);
356 }
357 } break;
358
359 case RPMQV_HDRID:
360 for (i = 0, s = arg; *s && isxdigit(*s); s++, i++)
361 {};
362 if (i != 40) {
363 rpmlog(RPMLOG_ERR, _("malformed %s: %s\n"), "hdrid", arg);
364 goto exit;
365 }
366
367 mi = rpmtsInitIterator(ts, RPMDBI_SHA1HEADER, arg, 0);
368 if (mi == NULL) {
369 rpmlog(RPMLOG_NOTICE, _("no package matches %s: %s\n"),
370 "hdrid", arg);
371 }
372 break;
373
374 case RPMQV_TID:
375 { char * end = NULL;
376 rpm_tid_t iid = strtoul(arg, &end, 0);
377
378 if ((*end) || (end == arg) || (iid == UINT_MAX)) {
379 rpmlog(RPMLOG_ERR, _("malformed %s: %s\n"), "tid", arg);
380 goto exit;
381 }
382 mi = rpmtsInitIterator(ts, RPMDBI_INSTALLTID, &iid, sizeof(iid));
383 if (mi == NULL) {
384 rpmlog(RPMLOG_NOTICE, _("no package matches %s: %s\n"),
385 "tid", arg);
386 }
387 } break;
388
389 case RPMQV_WHATCONFLICTS:
390 mi = rpmtsInitIterator(ts, RPMDBI_CONFLICTNAME, arg, 0);
391 if (mi == NULL) {
392 rpmlog(RPMLOG_NOTICE, _("no package conflicts %s\n"), arg);
393 }
394 break;
395
396 case RPMQV_WHATOBSOLETES:
397 mi = rpmtsInitIterator(ts, RPMDBI_OBSOLETENAME, arg, 0);
398 if (mi == NULL) {
399 rpmlog(RPMLOG_NOTICE, _("no package obsoletes %s\n"), arg);
400 }
401 break;
402
403 case RPMQV_WHATREQUIRES:
404 mi = rpmtsInitIterator(ts, RPMDBI_REQUIRENAME, arg, 0);
405 if (mi == NULL) {
406 rpmlog(RPMLOG_NOTICE, _("no package requires %s\n"), arg);
407 }
408 break;
409
410 case RPMQV_WHATRECOMMENDS:
411 mi = rpmtsInitIterator(ts, RPMDBI_RECOMMENDNAME, arg, 0);
412 if (mi == NULL) {
413 rpmlog(RPMLOG_NOTICE, _("no package recommends %s\n"), arg);
414 }
415 break;
416
417 case RPMQV_WHATSUGGESTS:
418 mi = rpmtsInitIterator(ts, RPMDBI_SUGGESTNAME, arg, 0);
419 if (mi == NULL) {
420 rpmlog(RPMLOG_NOTICE, _("no package suggests %s\n"), arg);
421 }
422 break;
423
424 case RPMQV_WHATSUPPLEMENTS:
425 mi = rpmtsInitIterator(ts, RPMDBI_SUPPLEMENTNAME, arg, 0);
426 if (mi == NULL) {
427 rpmlog(RPMLOG_NOTICE, _("no package supplements %s\n"), arg);
428 }
429 break;
430
431 case RPMQV_WHATENHANCES:
432 mi = rpmtsInitIterator(ts, RPMDBI_ENHANCENAME, arg, 0);
433 if (mi == NULL) {
434 rpmlog(RPMLOG_NOTICE, _("no package enhances %s\n"), arg);
435 }
436 break;
437
438 case RPMQV_WHATPROVIDES:
439 if (arg[0] != '/' && arg[0] != '.') {
440 mi = rpmtsInitIterator(ts, RPMDBI_PROVIDENAME, arg, 0);
441 if (mi == NULL) {
442 rpmlog(RPMLOG_NOTICE, _("no package provides %s\n"), arg);
443 }
444 break;
445 }
446 /* fallthrough on absolute and relative paths */
447 case RPMQV_PATH:
448 { char * fn;
449
450 for (s = arg; *s != '\0'; s++)
451 if (!(*s == '.' || *s == '/'))
452 break;
453
454 if (*s == '\0') {
455 char fnbuf[PATH_MAX];
456 fn = realpath(arg, fnbuf);
457 fn = xstrdup( (fn != NULL ? fn : arg) );
458 } else if (*arg != '/') {
459 char *curDir = rpmGetCwd();
460 fn = (char *) rpmGetPath(curDir, "/", arg, NULL);
461 free(curDir);
462 } else
463 fn = xstrdup(arg);
464 (void) rpmCleanPath(fn);
465
466 /* XXX Add a switch to enable former BASENAMES behavior? */
467 mi = rpmtsInitIterator(ts, RPMDBI_INSTFILENAMES, fn, 0);
468 if (mi == NULL)
469 mi = rpmtsInitIterator(ts, RPMDBI_PROVIDENAME, fn, 0);
470
471 if (mi == NULL) {
472 struct stat sb;
473 if (lstat(fn, &sb) != 0)
474 rpmlog(RPMLOG_ERR, _("file %s: %s\n"), fn, strerror(errno));
475 else
476 rpmlog(RPMLOG_NOTICE,
477 _("file %s is not owned by any package\n"), fn);
478 }
479
480 free(fn);
481 } break;
482
483 case RPMQV_DBOFFSET:
484 { char * end = NULL;
485 unsigned int recOffset = strtoul(arg, &end, 0);
486
487 if ((*end) || (end == arg) || (recOffset == UINT_MAX)) {
488 rpmlog(RPMLOG_ERR, _("invalid package number: %s\n"), arg);
489 goto exit;
490 }
491 rpmlog(RPMLOG_DEBUG, "package record number: %u\n", recOffset);
492 /* RPMDBI_PACKAGES */
493 mi = matchesIterator(ts, RPMDBI_PACKAGES, &recOffset, sizeof(recOffset));
494 if (mi == NULL) {
495 rpmlog(RPMLOG_ERR, _("record %u could not be read\n"), recOffset);
496 }
497 } break;
498
499 case RPMQV_PACKAGE:
500 {
501 mi = matchesIterator(ts, RPMDBI_LABEL, arg, 0);
502
503 if (mi == NULL && !rpmFileHasSuffix(arg, ".rpm"))
504 rpmlog(RPMLOG_NOTICE, _("package %s is not installed\n"), arg);
505 break;
506 }
507 default:
508 break;
509 }
510
511 exit:
512 return mi;
513 }
514
515 /*
516 * Initialize db iterator with optional filters. By default patterns
517 * applied to package name, others can be specified with <tagname>=<pattern>
518 */
initFilterIterator(rpmts ts,ARGV_const_t argv)519 static rpmdbMatchIterator initFilterIterator(rpmts ts, ARGV_const_t argv)
520 {
521 rpmdbMatchIterator mi = rpmtsInitIterator(ts, RPMDBI_PACKAGES, NULL, 0);
522
523 for (ARGV_const_t arg = argv; arg && *arg != NULL; arg++) {
524 rpmTagVal tag = RPMTAG_NAME;
525 char a[strlen(*arg)+1], *ae;
526 const char *pat = a;
527
528 strcpy(a, *arg);
529
530 /* Parse for "tag=pattern" args. */
531 if ((ae = strchr(a, '=')) != NULL) {
532 *ae++ = '\0';
533 tag = rpmTagGetValue(a);
534 if (tag == RPMTAG_NOT_FOUND) {
535 rpmlog(RPMLOG_ERR, _("unknown tag: \"%s\"\n"), a);
536 mi = rpmdbFreeIterator(mi);
537 break;
538 }
539 pat = ae;
540 }
541
542 rpmdbSetIteratorRE(mi, tag, RPMMIRE_DEFAULT, pat);
543 }
544
545 return mi;
546 }
547
rpmcliArgIter(rpmts ts,QVA_t qva,ARGV_const_t argv)548 int rpmcliArgIter(rpmts ts, QVA_t qva, ARGV_const_t argv)
549 {
550 int ec = 0;
551
552 switch (qva->qva_source) {
553 case RPMQV_ALL: {
554 rpmdbMatchIterator mi = initFilterIterator(ts, argv);
555 ec = rpmcliShowMatches(qva, ts, mi);
556 rpmdbFreeIterator(mi);
557 break;
558 }
559 case RPMQV_RPM: {
560 rpmgi gi = rpmgiNew(ts, giFlags, argv);
561 ec = rpmgiShowMatches(qva, ts, gi);
562 rpmgiFree(gi);
563 break;
564 }
565 case RPMQV_SPECRPMS:
566 case RPMQV_SPECBUILTRPMS:
567 case RPMQV_SPECSRPM: {
568 char *target = rpmExpand("%{_target}", NULL);
569 for (ARGV_const_t arg = argv; arg && *arg; arg++) {
570 ec += ((qva->qva_specQuery != NULL)
571 ? qva->qva_specQuery(ts, qva, *arg) : 1);
572 rpmFreeMacros(NULL);
573 rpmReadConfigFiles(rpmcliRcfile, target);
574 }
575 free(target);
576 break;
577 }
578 default:
579 for (ARGV_const_t arg = argv; arg && *arg; arg++) {
580 int ecLocal;
581 rpmdbMatchIterator mi = initQueryIterator(qva, ts, *arg);
582 ecLocal = rpmcliShowMatches(qva, ts, mi);
583 if (mi == NULL && qva->qva_source == RPMQV_PACKAGE) {
584 if (rpmFileHasSuffix(*arg, ".rpm")) {
585 char * const argFirst[2] = { arg[0], NULL };
586 rpmgi gi = rpmgiNew(ts, giFlags, argFirst);
587 ecLocal = rpmgiShowMatches(qva, ts, gi);
588 rpmgiFree(gi);
589 }
590 }
591 ec += ecLocal;
592 rpmdbFreeIterator(mi);
593 }
594 break;
595 }
596
597 return ec;
598 }
599
rpmcliQuery(rpmts ts,QVA_t qva,char * const * argv)600 int rpmcliQuery(rpmts ts, QVA_t qva, char * const * argv)
601 {
602 rpmVSFlags vsflags, ovsflags;
603 int ec = 0;
604
605 if (qva->qva_showPackage == NULL)
606 qva->qva_showPackage = showQueryPackage;
607
608 /* If --queryformat unspecified, then set default now. */
609 if (!(qva->qva_flags & _QUERY_FOR_BITS) && !(qva->qva_incattr) &&
610 qva->qva_queryFormat == NULL) {
611 char * fmt = rpmExpand("%{?_query_all_fmt}\n", NULL);
612 if (fmt == NULL || strlen(fmt) <= 1) {
613 free(fmt);
614 fmt = xstrdup("%{nvra}\n");
615 }
616 qva->qva_queryFormat = fmt;
617 }
618
619 vsflags = rpmExpandNumeric("%{?_vsflags_query}");
620 vsflags |= rpmcliVSFlags;
621
622 ovsflags = rpmtsSetVSFlags(ts, vsflags);
623 ec = rpmcliArgIter(ts, qva, argv);
624 rpmtsSetVSFlags(ts, ovsflags);
625
626 if (qva->qva_showPackage == showQueryPackage)
627 qva->qva_showPackage = NULL;
628
629 return ec;
630 }
631