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