Make base64 encoding/decoding part of rpmio public API
[platform/upstream/rpm.git] / rpmio / rpmfileutil.c
1 #include "system.h"
2
3 #if HAVE_GELF_H
4
5 #include <gelf.h>
6
7 #if !defined(DT_GNU_PRELINKED)
8 #define DT_GNU_PRELINKED        0x6ffffdf5
9 #endif
10 #if !defined(DT_GNU_LIBLIST)
11 #define DT_GNU_LIBLIST          0x6ffffef9
12 #endif
13
14 #endif
15
16 #if defined(HAVE_MMAP)
17 #include <sys/mman.h>
18 #endif
19
20 #include <sys/types.h>
21 #include <sys/stat.h>
22 #include <sys/wait.h>
23 #include <errno.h>
24 #include <popt.h>
25 #include <ctype.h>
26
27 #include <rpm/rpmfileutil.h>
28 #include <rpm/rpmurl.h>
29 #include <rpm/rpmmacro.h>
30 #include <rpm/rpmlog.h>
31 #include <rpm/argv.h>
32
33 #include "rpmio/rpmio_internal.h"
34
35 #include "debug.h"
36
37 static const char *rpm_config_dir = NULL;
38
39 static int open_dso(const char * path, pid_t * pidp, rpm_loff_t *fsizep)
40 {
41     static const char * cmd = NULL;
42     static int initted = 0;
43     int fdno;
44
45     if (!initted) {
46         cmd = rpmExpand("%{?__prelink_undo_cmd}", NULL);
47         initted++;
48     }
49
50     if (pidp) *pidp = 0;
51
52     if (fsizep) {
53         struct stat sb, * st = &sb;
54         if (stat(path, st) < 0)
55             return -1;
56         *fsizep = st->st_size;
57     }
58
59     fdno = open(path, O_RDONLY);
60     if (fdno < 0)
61         return fdno;
62
63     if (!(cmd && *cmd))
64         return fdno;
65
66 #if HAVE_GELF_H && HAVE_LIBELF
67  {  Elf *elf = NULL;
68     Elf_Scn *scn = NULL;
69     Elf_Data *data = NULL;
70     GElf_Ehdr ehdr;
71     GElf_Shdr shdr;
72     GElf_Dyn dyn;
73     int bingo;
74
75     (void) elf_version(EV_CURRENT);
76
77     if ((elf = elf_begin (fdno, ELF_C_READ, NULL)) == NULL
78      || elf_kind(elf) != ELF_K_ELF
79      || gelf_getehdr(elf, &ehdr) == NULL
80      || !(ehdr.e_type == ET_DYN || ehdr.e_type == ET_EXEC))
81         goto exit;
82
83     bingo = 0;
84     while (!bingo && (scn = elf_nextscn(elf, scn)) != NULL) {
85         (void) gelf_getshdr(scn, &shdr);
86         if (shdr.sh_type != SHT_DYNAMIC)
87             continue;
88         while (!bingo && (data = elf_getdata (scn, data)) != NULL) {
89             int maxndx = data->d_size / shdr.sh_entsize;
90             int ndx;
91
92             for (ndx = 0; ndx < maxndx; ++ndx) {
93                 (void) gelf_getdyn (data, ndx, &dyn);
94                 if (!(dyn.d_tag == DT_GNU_PRELINKED || dyn.d_tag == DT_GNU_LIBLIST))
95                     continue;
96                 bingo = 1;
97                 break;
98             }
99         }
100     }
101
102     if (pidp != NULL && bingo) {
103         int pipes[2];
104         pid_t pid;
105         int xx;
106
107         xx = close(fdno);
108         pipes[0] = pipes[1] = -1;
109         xx = pipe(pipes);
110         if (!(pid = fork())) {
111             ARGV_t av, lib;
112             argvSplit(&av, cmd, " ");
113
114             xx = close(pipes[0]);
115             xx = dup2(pipes[1], STDOUT_FILENO);
116             xx = close(pipes[1]);
117             if ((lib = argvSearch(av, "library", NULL)) != NULL) {
118                 *lib = (char *) path;
119                 unsetenv("MALLOC_CHECK_");
120                 xx = execve(av[0], av+1, environ);
121             }
122             _exit(127);
123         }
124         *pidp = pid;
125         fdno = pipes[0];
126         xx = close(pipes[1]);
127     }
128
129 exit:
130     if (elf) (void) elf_end(elf);
131  }
132 #endif
133
134     return fdno;
135 }
136
137 int rpmDoDigest(int algo, const char * fn,int asAscii,
138                 unsigned char * digest, rpm_loff_t * fsizep)
139 {
140     const char * path;
141     urltype ut = urlPath(fn, &path);
142     unsigned char * dig = NULL;
143     size_t diglen;
144     unsigned char buf[32*BUFSIZ];
145     FD_t fd;
146     rpm_loff_t fsize = 0;
147     pid_t pid = 0;
148     int rc = 0;
149     int fdno;
150
151     fdno = open_dso(path, &pid, &fsize);
152     if (fdno < 0) {
153         rc = 1;
154         goto exit;
155     }
156
157     /* file to large (32 MB), do not mmap file */
158     if (fsize > (size_t) 32*1024*1024)
159       if (ut == URL_IS_PATH || ut == URL_IS_UNKNOWN)
160         ut = URL_IS_DASH; /* force fd io */
161
162     switch(ut) {
163     case URL_IS_PATH:
164     case URL_IS_UNKNOWN:
165 #ifdef HAVE_MMAP
166       if (pid == 0) {
167         int xx;
168         DIGEST_CTX ctx;
169         void * mapped;
170
171         if (fsize) {
172             mapped = mmap(NULL, fsize, PROT_READ, MAP_SHARED, fdno, 0);
173             if (mapped == MAP_FAILED) {
174                 xx = close(fdno);
175                 rc = 1;
176                 break;
177             }
178
179 #ifdef  MADV_SEQUENTIAL
180             xx = madvise(mapped, fsize, MADV_SEQUENTIAL);
181 #endif
182         }
183
184         ctx = rpmDigestInit(algo, RPMDIGEST_NONE);
185         if (fsize)
186             xx = rpmDigestUpdate(ctx, mapped, fsize);
187         xx = rpmDigestFinal(ctx, (void **)&dig, &diglen, asAscii);
188         if (fsize)
189             xx = munmap(mapped, fsize);
190         xx = close(fdno);
191         break;
192       }
193 #endif
194     case URL_IS_HTTPS:
195     case URL_IS_HTTP:
196     case URL_IS_FTP:
197     case URL_IS_HKP:
198     case URL_IS_DASH:
199     default:
200         /* Either use the pipe to prelink -y or open the URL. */
201         fd = (pid != 0) ? fdDup(fdno) : Fopen(fn, "r.ufdio");
202         (void) close(fdno);
203         if (fd == NULL || Ferror(fd)) {
204             rc = 1;
205             if (fd != NULL)
206                 (void) Fclose(fd);
207             break;
208         }
209         
210         fdInitDigest(fd, algo, 0);
211         fsize = 0;
212         while ((rc = Fread(buf, sizeof(buf[0]), sizeof(buf), fd)) > 0)
213             fsize += rc;
214         fdFiniDigest(fd, algo, (void **)&dig, &diglen, asAscii);
215         if (Ferror(fd))
216             rc = 1;
217
218         (void) Fclose(fd);
219         break;
220     }
221
222     /* Reap the prelink -y helper. */
223     if (pid) {
224         int status;
225         (void) waitpid(pid, &status, 0);
226         if (!WIFEXITED(status) || WEXITSTATUS(status))
227             rc = 1;
228     }
229
230 exit:
231     if (fsizep)
232         *fsizep = fsize;
233     if (!rc)
234         memcpy(digest, dig, diglen);
235     dig = _free(dig);
236
237     return rc;
238 }
239
240 FD_t rpmMkTemp(char *templ)
241 {
242     mode_t mode;
243     int sfd;
244     FD_t tfd = NULL;
245
246     mode = umask(0077);
247     sfd = mkstemp(templ);
248     umask(mode);
249
250     if (sfd < 0) {
251         goto exit;
252     }
253
254     tfd = fdDup(sfd);
255     close(sfd);
256
257 exit:
258     return tfd;
259 }
260
261 FD_t rpmMkTempFile(const char * prefix, char **fn)
262 {
263     const char *tpmacro = "%{_tmppath}"; /* always set from rpmrc */
264     char *tempfn;
265     static int _initialized = 0;
266     FD_t tfd = NULL;
267
268     if (!prefix) prefix = "";
269
270     /* Create the temp directory if it doesn't already exist. */
271     if (!_initialized) {
272         _initialized = 1;
273         tempfn = rpmGenPath(prefix, tpmacro, NULL);
274         if (rpmioMkpath(tempfn, 0755, (uid_t) -1, (gid_t) -1))
275             goto exit;
276         free(tempfn);
277     }
278
279     tempfn = rpmGetPath(prefix, tpmacro, "/rpm-tmp.XXXXXX", NULL);
280     tfd = rpmMkTemp(tempfn);
281
282     if (tfd == NULL || Ferror(tfd)) {
283         rpmlog(RPMLOG_ERR, _("error creating temporary file %s: %m\n"), tempfn);
284         goto exit;
285     }
286
287 exit:
288     if (tfd != NULL && fn)
289         *fn = tempfn;
290     else
291         free(tempfn);
292
293     return tfd;
294 }
295
296 int rpmioMkpath(const char * path, mode_t mode, uid_t uid, gid_t gid)
297 {
298     char *d, *de;
299     int rc;
300
301     if (path == NULL || *path == '\0')
302         return -1;
303     d = rstrcat(NULL, path);
304     if (d[strlen(d)-1] != '/') {
305         rstrcat(&d,"/");
306     }
307     de = d;
308     for (;(de=strchr(de+1,'/'));) {
309         struct stat st;
310         *de = '\0';
311         rc = stat(d, &st);
312         if (rc) {
313             if (errno != ENOENT)
314                 goto exit;
315             rc = mkdir(d, mode);
316             if (rc)
317                 goto exit;
318             rpmlog(RPMLOG_DEBUG, "created directory(s) %s mode 0%o\n", path, mode);
319             if (!(uid == (uid_t) -1 && gid == (gid_t) -1)) {
320                 rc = chown(d, uid, gid);
321                 if (rc)
322                     goto exit;
323             }
324         } else if (!S_ISDIR(st.st_mode)) {
325             rc = ENOTDIR;
326             goto exit;
327         }
328         *de = '/';
329     }
330     rc = 0;
331 exit:
332     free(d);
333     return rc;
334 }
335
336 int rpmFileIsCompressed(const char * file, rpmCompressedMagic * compressed)
337 {
338     FD_t fd;
339     ssize_t nb;
340     int rc = -1;
341     unsigned char magic[13];
342
343     *compressed = COMPRESSED_NOT;
344
345     fd = Fopen(file, "r.ufdio");
346     if (fd == NULL || Ferror(fd)) {
347         /* XXX Fstrerror */
348         rpmlog(RPMLOG_ERR, _("File %s: %s\n"), file, Fstrerror(fd));
349         if (fd) (void) Fclose(fd);
350         return 1;
351     }
352     nb = Fread(magic, sizeof(magic[0]), sizeof(magic), fd);
353     if (nb < 0) {
354         rpmlog(RPMLOG_ERR, _("File %s: %s\n"), file, Fstrerror(fd));
355         rc = 1;
356     } else if (nb < sizeof(magic)) {
357         rpmlog(RPMLOG_ERR, _("File %s is smaller than %u bytes\n"),
358                 file, (unsigned)sizeof(magic));
359         rc = 0;
360     }
361     (void) Fclose(fd);
362     if (rc >= 0)
363         return rc;
364
365     rc = 0;
366
367     if ((magic[0] == 'B') && (magic[1] == 'Z')) {
368         *compressed = COMPRESSED_BZIP2;
369     } else if ((magic[0] == 'P') && (magic[1] == 'K') &&
370          (((magic[2] == 3) && (magic[3] == 4)) ||
371           ((magic[2] == '0') && (magic[3] == '0')))) {  /* pkzip */
372         *compressed = COMPRESSED_ZIP;
373     } else if ((magic[0] == 0xfd) && (magic[1] == 0x37) &&
374                (magic[2] == 0x7a) && (magic[3] == 0x58) &&
375                (magic[4] == 0x5a) && (magic[5] == 0x00)) {
376         /* new style xz (lzma) with magic */
377         *compressed = COMPRESSED_XZ;
378     } else if ((magic[0] == 'L') && (magic[1] == 'Z') &&
379                (magic[2] == 'I') && (magic[3] == 'P')) {
380         *compressed = COMPRESSED_LZIP;
381     } else if ((magic[0] == 'L') && (magic[1] == 'R') &&
382                (magic[2] == 'Z') && (magic[3] == 'I')) {
383         *compressed = COMPRESSED_LRZIP;
384     } else if (((magic[0] == 0037) && (magic[1] == 0213)) || /* gzip */
385         ((magic[0] == 0037) && (magic[1] == 0236)) ||   /* old gzip */
386         ((magic[0] == 0037) && (magic[1] == 0036)) ||   /* pack */
387         ((magic[0] == 0037) && (magic[1] == 0240)) ||   /* SCO lzh */
388         ((magic[0] == 0037) && (magic[1] == 0235))      /* compress */
389         ) {
390         *compressed = COMPRESSED_OTHER;
391     } else if (rpmFileHasSuffix(file, ".lzma")) {
392         *compressed = COMPRESSED_LZMA;
393     }
394
395     return rc;
396 }
397
398 /* @todo "../sbin/./../bin/" not correct. */
399 char *rpmCleanPath(char * path)
400 {
401     const char *s;
402     char *se, *t, *te;
403     int begin = 1;
404
405     if (path == NULL)
406         return NULL;
407
408 /*fprintf(stderr, "*** RCP %s ->\n", path); */
409     s = t = te = path;
410     while (*s != '\0') {
411 /*fprintf(stderr, "*** got \"%.*s\"\trest \"%s\"\n", (t-path), path, s); */
412         switch(*s) {
413         case ':':                       /* handle url's */
414             if (s[1] == '/' && s[2] == '/') {
415                 *t++ = *s++;
416                 *t++ = *s++;
417                 break;
418             }
419             begin=1;
420             break;
421         case '/':
422             /* Move parent dir forward */
423             for (se = te + 1; se < t && *se != '/'; se++)
424                 {};
425             if (se < t && *se == '/') {
426                 te = se;
427 /*fprintf(stderr, "*** next pdir \"%.*s\"\n", (te-path), path); */
428             }
429             while (s[1] == '/')
430                 s++;
431             while (t > path && t[-1] == '/')
432                 t--;
433             break;
434         case '.':
435             /* Leading .. is special */
436             /* Check that it is ../, so that we don't interpret */
437             /* ..?(i.e. "...") or ..* (i.e. "..bogus") as "..". */
438             /* in the case of "...", this ends up being processed*/
439             /* as "../.", and the last '.' is stripped.  This   */
440             /* would not be correct processing.                 */
441             if (begin && s[1] == '.' && (s[2] == '/' || s[2] == '\0')) {
442 /*fprintf(stderr, "    leading \"..\"\n"); */
443                 *t++ = *s++;
444                 break;
445             }
446             /* Single . is special */
447             if (begin && s[1] == '\0') {
448                 break;
449             }
450             /* Handle the ./ cases */
451             if (t > path && t[-1] == '/') {
452                 /* Trim embedded ./ */
453                 if (s[1] == '/') {
454                     s+=2;
455                     continue;
456                 }
457                 /* Trim trailing /. */
458                 if (s[1] == '\0') {
459                     s++;
460                     continue;
461                 }
462             }
463             /* Trim embedded /../ and trailing /.. */
464             if (!begin && t > path && t[-1] == '/' && s[1] == '.' && (s[2] == '/' || s[2] == '\0')) {
465                 t = te;
466                 /* Move parent dir forward */
467                 if (te > path)
468                     for (--te; te > path && *te != '/'; te--)
469                         {};
470 /*fprintf(stderr, "*** prev pdir \"%.*s\"\n", (te-path), path); */
471                 s++;
472                 s++;
473                 continue;
474             }
475             break;
476         default:
477             begin = 0;
478             break;
479         }
480         *t++ = *s++;
481     }
482
483     /* Trim trailing / (but leave single / alone) */
484     if (t > &path[1] && t[-1] == '/')
485         t--;
486     *t = '\0';
487
488 /*fprintf(stderr, "\t%s\n", path); */
489     return path;
490 }
491
492 /* Merge 3 args into path, any or all of which may be a url. */
493
494 char * rpmGenPath(const char * urlroot, const char * urlmdir,
495                 const char *urlfile)
496 {
497     char * xroot = rpmGetPath(urlroot, NULL);
498     const char * root = xroot;
499     char * xmdir = rpmGetPath(urlmdir, NULL);
500     const char * mdir = xmdir;
501     char * xfile = rpmGetPath(urlfile, NULL);
502     const char * file = xfile;
503     char * result;
504     char * url = NULL;
505     int nurl = 0;
506     int ut;
507
508     ut = urlPath(xroot, &root);
509     if (url == NULL && ut > URL_IS_DASH) {
510         url = xroot;
511         nurl = root - xroot;
512     }
513     if (root == NULL || *root == '\0') root = "/";
514
515     ut = urlPath(xmdir, &mdir);
516     if (url == NULL && ut > URL_IS_DASH) {
517         url = xmdir;
518         nurl = mdir - xmdir;
519     }
520     if (mdir == NULL || *mdir == '\0') mdir = "/";
521
522     ut = urlPath(xfile, &file);
523     if (url == NULL && ut > URL_IS_DASH) {
524         url = xfile;
525         nurl = file - xfile;
526     }
527
528     if (url && nurl > 0) {
529         char *t = rstrcat(NULL, url);
530         t[nurl] = '\0';
531         url = t;
532     } else
533         url = xstrdup("");
534
535     result = rpmGetPath(url, root, "/", mdir, "/", file, NULL);
536
537     free(xroot);
538     free(xmdir);
539     free(xfile);
540     free(url);
541     return result;
542 }
543
544 /* Return concatenated and expanded canonical path. */
545
546 char * rpmGetPath(const char *path, ...)
547 {
548     va_list ap;
549     char *dest = NULL, *res;
550     const char *s;
551
552     if (path == NULL)
553         return xstrdup("");
554
555     va_start(ap, path);
556     for (s = path; s; s = va_arg(ap, const char *)) {
557         rstrcat(&dest, s);
558     }
559     va_end(ap);
560
561     res = rpmExpand(dest, NULL);
562     free(dest);
563
564     return rpmCleanPath(res);
565 }
566
567 int rpmGlob(const char * patterns, int * argcPtr, ARGV_t * argvPtr)
568 {
569     int ac = 0;
570     const char ** av = NULL;
571     int argc = 0;
572     ARGV_t argv = NULL;
573     char * globRoot = NULL;
574     const char *home = getenv("HOME");
575     int gflags = 0;
576 #ifdef ENABLE_NLS
577     char * old_collate = NULL;
578     char * old_ctype = NULL;
579     const char * t;
580 #endif
581     size_t maxb, nb;
582     int i, j;
583     int rc;
584
585     if (home != NULL && strlen(home) > 0) 
586         gflags |= GLOB_TILDE;
587
588     /* Can't use argvSplit() here, it doesn't handle whitespace etc escapes */
589     rc = poptParseArgvString(patterns, &ac, &av);
590     if (rc)
591         return rc;
592
593 #ifdef ENABLE_NLS
594     t = setlocale(LC_COLLATE, NULL);
595     if (t)
596         old_collate = xstrdup(t);
597     t = setlocale(LC_CTYPE, NULL);
598     if (t)
599         old_ctype = xstrdup(t);
600     (void) setlocale(LC_COLLATE, "C");
601     (void) setlocale(LC_CTYPE, "C");
602 #endif
603         
604     if (av != NULL)
605     for (j = 0; j < ac; j++) {
606         char * globURL;
607         const char * path;
608         int ut = urlPath(av[j], &path);
609         int local = (ut == URL_IS_PATH) || (ut == URL_IS_UNKNOWN);
610         size_t plen = strlen(path);
611         int flags = gflags;
612         int dir_only = (plen > 0 && path[plen-1] == '/');
613         glob_t gl;
614
615         if (!local || (!glob_pattern_p(av[j], 0) && strchr(path, '~') == NULL)) {
616             argvAdd(&argv, av[j]);
617             continue;
618         }
619
620 #ifdef GLOB_ONLYDIR
621         if (dir_only)
622             flags |= GLOB_ONLYDIR;
623 #endif
624         
625         gl.gl_pathc = 0;
626         gl.gl_pathv = NULL;
627         
628         rc = glob(av[j], flags, NULL, &gl);
629         if (rc)
630             goto exit;
631
632         /* XXX Prepend the URL leader for globs that have stripped it off */
633         maxb = 0;
634         for (i = 0; i < gl.gl_pathc; i++) {
635             if ((nb = strlen(&(gl.gl_pathv[i][0]))) > maxb)
636                 maxb = nb;
637         }
638         
639         nb = ((ut == URL_IS_PATH) ? (path - av[j]) : 0);
640         maxb += nb;
641         maxb += 1;
642         globURL = globRoot = xmalloc(maxb);
643
644         switch (ut) {
645         case URL_IS_PATH:
646         case URL_IS_DASH:
647             strncpy(globRoot, av[j], nb);
648             break;
649         case URL_IS_HTTPS:
650         case URL_IS_HTTP:
651         case URL_IS_FTP:
652         case URL_IS_HKP:
653         case URL_IS_UNKNOWN:
654         default:
655             break;
656         }
657         globRoot += nb;
658         *globRoot = '\0';
659
660         for (i = 0; i < gl.gl_pathc; i++) {
661             const char * globFile = &(gl.gl_pathv[i][0]);
662
663             if (dir_only) {
664                 struct stat sb;
665                 if (lstat(gl.gl_pathv[i], &sb) || !S_ISDIR(sb.st_mode))
666                     continue;
667             }
668                 
669             if (globRoot > globURL && globRoot[-1] == '/')
670                 while (*globFile == '/') globFile++;
671             strcpy(globRoot, globFile);
672             argvAdd(&argv, globURL);
673         }
674         globfree(&gl);
675         free(globURL);
676     }
677
678     argc = argvCount(argv);
679     if (argc > 0) {
680         if (argvPtr)
681             *argvPtr = argv;
682         if (argcPtr)
683             *argcPtr = argc;
684         rc = 0;
685     } else
686         rc = 1;
687
688
689 exit:
690 #ifdef ENABLE_NLS       
691     if (old_collate) {
692         (void) setlocale(LC_COLLATE, old_collate);
693         free(old_collate);
694     }
695     if (old_ctype) {
696         (void) setlocale(LC_CTYPE, old_ctype);
697         free(old_ctype);
698     }
699 #endif
700     av = _free(av);
701     if (rc || argvPtr == NULL) {
702         argvFree(argv);
703     }
704     return rc;
705 }
706
707 char * rpmEscapeSpaces(const char * s)
708 {
709     const char * se;
710     char * t;
711     char * te;
712     size_t nb = 0;
713
714     for (se = s; *se; se++) {
715         if (isspace(*se))
716             nb++;
717         nb++;
718     }
719     nb++;
720
721     t = te = xmalloc(nb);
722     for (se = s; *se; se++) {
723         if (isspace(*se))
724             *te++ = '\\';
725         *te++ = *se;
726     }
727     *te = '\0';
728     return t;
729 }
730
731 int rpmFileHasSuffix(const char *path, const char *suffix)
732 {
733     size_t plen = strlen(path);
734     size_t slen = strlen(suffix);
735     return (plen >= slen && rstreq(path+plen-slen, suffix));
736 }
737
738 char * rpmGetCwd(void)
739 {
740     int currDirLen = 0;
741     char * currDir = NULL;
742
743     do {
744         currDirLen += 128;
745         currDir = xrealloc(currDir, currDirLen);
746         memset(currDir, 0, currDirLen);
747     } while (getcwd(currDir, currDirLen) == NULL && errno == ERANGE);
748
749     return currDir;
750 }
751
752 int rpmMkdirs(const char *root, const char *pathstr)
753 {
754     ARGV_t dirs = NULL;
755     int rc = 0;
756     argvSplit(&dirs, pathstr, ":");
757     
758     for (char **d = dirs; *d; d++) {
759         char *path = rpmGetPath(root ? root : "", *d, NULL);
760         if ((rc = rpmioMkpath(path, 0755, -1, -1)) != 0) {
761             const char *msg = _("failed to create directory");
762             /* try to be more informative if the failing part was a macro */
763             if (**d == '%') {
764                 rpmlog(RPMLOG_ERR, "%s %s: %s: %m\n", msg, *d, path);
765             } else {
766                 rpmlog(RPMLOG_ERR, "%s %s: %m\n", msg, path);
767             }
768         }
769         free(path);
770         if (rc) break;
771     }
772     argvFree(dirs);
773     return rc;
774 }
775
776 const char *rpmConfigDir(void)
777 {
778     if (rpm_config_dir == NULL) {
779         char *rpmenv = getenv("RPM_CONFIGDIR");
780         rpm_config_dir = rpmenv ? xstrdup(rpmenv) : RPMCONFIGDIR;
781     }
782     return rpm_config_dir;
783 }