Imported Upstream version 4.14.1
[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 #include <sys/types.h>
17 #include <sys/stat.h>
18 #include <sys/wait.h>
19 #include <errno.h>
20 #include <popt.h>
21 #include <ctype.h>
22 #include <pthread.h>
23
24 #include <rpm/rpmfileutil.h>
25 #include <rpm/rpmurl.h>
26 #include <rpm/rpmmacro.h>
27 #include <rpm/rpmlog.h>
28 #include <rpm/argv.h>
29
30 #include "rpmio/rpmio_internal.h"
31
32 #include "debug.h"
33
34 static const char *rpm_config_dir = NULL;
35 static pthread_once_t configDirSet = PTHREAD_ONCE_INIT;
36
37 static int is_prelinked(int fdno)
38 {
39     int prelinked = 0;
40 #if HAVE_GELF_H && HAVE_LIBELF
41     Elf *elf = NULL;
42     Elf_Scn *scn = NULL;
43     Elf_Data *data = NULL;
44     GElf_Ehdr ehdr;
45     GElf_Shdr shdr;
46     GElf_Dyn dyn;
47
48     (void) elf_version(EV_CURRENT);
49
50     if ((elf = elf_begin (fdno, ELF_C_READ, NULL)) == NULL ||
51                elf_kind(elf) != ELF_K_ELF || gelf_getehdr(elf, &ehdr) == NULL ||
52                !(ehdr.e_type == ET_DYN || ehdr.e_type == ET_EXEC))
53         goto exit;
54
55     while (!prelinked && (scn = elf_nextscn(elf, scn)) != NULL) {
56         (void) gelf_getshdr(scn, &shdr);
57         if (shdr.sh_type != SHT_DYNAMIC)
58             continue;
59         while (!prelinked && (data = elf_getdata (scn, data)) != NULL) {
60             int maxndx = data->d_size / shdr.sh_entsize;
61
62             for (int ndx = 0; ndx < maxndx; ++ndx) {
63                 (void) gelf_getdyn (data, ndx, &dyn);
64                 if (!(dyn.d_tag == DT_GNU_PRELINKED || dyn.d_tag == DT_GNU_LIBLIST))
65                     continue;
66                 prelinked = 1;
67                 break;
68             }
69         }
70     }
71
72 exit:
73     if (elf) (void) elf_end(elf);
74 #endif
75     return prelinked;
76 }
77
78 static int open_dso(const char * path, pid_t * pidp, rpm_loff_t *fsizep)
79 {
80     static const char * cmd = NULL;
81     static int initted = 0;
82     int fdno;
83
84     if (!initted) {
85         cmd = rpmExpand("%{?__prelink_undo_cmd}", NULL);
86         initted++;
87     }
88
89     if (pidp) *pidp = 0;
90
91     if (fsizep) {
92         struct stat sb, * st = &sb;
93         if (stat(path, st) < 0)
94             return -1;
95         *fsizep = st->st_size;
96     }
97
98     fdno = open(path, O_RDONLY);
99     if (fdno < 0)
100         return fdno;
101
102     if (!(cmd && *cmd))
103         return fdno;
104
105     if (pidp != NULL && is_prelinked(fdno)) {
106         int pipes[2];
107         pid_t pid;
108
109         close(fdno);
110         pipes[0] = pipes[1] = -1;
111         if (pipe(pipes) < 0)
112             return -1;
113
114         pid = fork();
115         if (pid < 0) {
116             close(pipes[0]);
117             close(pipes[1]);
118             return -1;
119         }
120
121         if (pid == 0) {
122             ARGV_t av, lib;
123             int dfd;
124             argvSplit(&av, cmd, " ");
125
126             close(pipes[0]);
127             dfd = dup2(pipes[1], STDOUT_FILENO);
128             close(pipes[1]);
129             if (dfd >= 0 && (lib = argvSearch(av, "library", NULL)) != NULL) {
130                 *lib = (char *) path;
131                 unsetenv("MALLOC_CHECK_");
132                 execve(av[0], av+1, environ);
133             }
134             _exit(127); /* not normally reached */
135         } else {
136             *pidp = pid;
137             fdno = pipes[0];
138             close(pipes[1]);
139         }
140     }
141
142     return fdno;
143 }
144
145 int rpmDoDigest(int algo, const char * fn,int asAscii,
146                 unsigned char * digest, rpm_loff_t * fsizep)
147 {
148     const char * path;
149     urltype ut = urlPath(fn, &path);
150     unsigned char * dig = NULL;
151     size_t diglen, buflen = 32 * BUFSIZ;
152     unsigned char *buf = xmalloc(buflen);
153     FD_t fd;
154     rpm_loff_t fsize = 0;
155     pid_t pid = 0;
156     int rc = 0;
157     int fdno;
158
159     fdno = open_dso(path, &pid, &fsize);
160     if (fdno < 0) {
161         rc = 1;
162         goto exit;
163     }
164
165     switch (ut) {
166     case URL_IS_PATH:
167     case URL_IS_UNKNOWN:
168     case URL_IS_HTTPS:
169     case URL_IS_HTTP:
170     case URL_IS_FTP:
171     case URL_IS_HKP:
172     case URL_IS_DASH:
173     default:
174         /* Either use the pipe to prelink -y or open the URL. */
175         fd = (pid != 0) ? fdDup(fdno) : Fopen(fn, "r.ufdio");
176         (void) close(fdno);
177         if (fd == NULL || Ferror(fd)) {
178             rc = 1;
179             if (fd != NULL)
180                 (void) Fclose(fd);
181             break;
182         }
183         
184         fdInitDigest(fd, algo, 0);
185         fsize = 0;
186         while ((rc = Fread(buf, sizeof(*buf), buflen, fd)) > 0)
187             fsize += rc;
188         fdFiniDigest(fd, algo, (void **)&dig, &diglen, asAscii);
189         if (dig == NULL || Ferror(fd))
190             rc = 1;
191
192         (void) Fclose(fd);
193         break;
194     }
195
196     /* Reap the prelink -y helper. */
197     if (pid) {
198         int status;
199         (void) waitpid(pid, &status, 0);
200         if (!WIFEXITED(status) || WEXITSTATUS(status))
201             rc = 1;
202     }
203
204 exit:
205     if (fsizep)
206         *fsizep = fsize;
207     if (!rc)
208         memcpy(digest, dig, diglen);
209     dig = _free(dig);
210     free(buf);
211
212     return rc;
213 }
214
215 FD_t rpmMkTemp(char *templ)
216 {
217     mode_t mode;
218     int sfd;
219     FD_t tfd = NULL;
220
221     mode = umask(0077);
222     sfd = mkstemp(templ);
223     umask(mode);
224
225     if (sfd < 0) {
226         goto exit;
227     }
228
229     tfd = fdDup(sfd);
230     close(sfd);
231
232 exit:
233     return tfd;
234 }
235
236 FD_t rpmMkTempFile(const char * prefix, char **fn)
237 {
238     const char *tpmacro = "%{_tmppath}"; /* always set from rpmrc */
239     char *tempfn;
240     static int _initialized = 0;
241     FD_t tfd = NULL;
242
243     if (!prefix) prefix = "";
244
245     /* Create the temp directory if it doesn't already exist. */
246     if (!_initialized) {
247         _initialized = 1;
248         tempfn = rpmGenPath(prefix, tpmacro, NULL);
249         if (rpmioMkpath(tempfn, 0755, (uid_t) -1, (gid_t) -1))
250             goto exit;
251         free(tempfn);
252     }
253
254     tempfn = rpmGetPath(prefix, tpmacro, "/rpm-tmp.XXXXXX", NULL);
255     tfd = rpmMkTemp(tempfn);
256
257     if (tfd == NULL || Ferror(tfd)) {
258         rpmlog(RPMLOG_ERR, _("error creating temporary file %s: %m\n"), tempfn);
259         goto exit;
260     }
261
262 exit:
263     if (tfd != NULL && fn)
264         *fn = tempfn;
265     else
266         free(tempfn);
267
268     return tfd;
269 }
270
271 int rpmioMkpath(const char * path, mode_t mode, uid_t uid, gid_t gid)
272 {
273     char *d, *de;
274     int rc;
275
276     if (path == NULL || *path == '\0')
277         return -1;
278     d = rstrcat(NULL, path);
279     if (d[strlen(d)-1] != '/') {
280         rstrcat(&d,"/");
281     }
282     de = d;
283     for (;(de=strchr(de+1,'/'));) {
284         struct stat st;
285         *de = '\0';
286         rc = stat(d, &st);
287         if (rc) {
288             if (errno != ENOENT)
289                 goto exit;
290             rc = mkdir(d, mode);
291             if (rc)
292                 goto exit;
293             rpmlog(RPMLOG_DEBUG, "created directory(s) %s mode 0%o\n", path, mode);
294             if (!(uid == (uid_t) -1 && gid == (gid_t) -1)) {
295                 rc = chown(d, uid, gid);
296                 if (rc)
297                     goto exit;
298             }
299         } else if (!S_ISDIR(st.st_mode)) {
300             rc = ENOTDIR;
301             goto exit;
302         }
303         *de = '/';
304     }
305     rc = 0;
306 exit:
307     free(d);
308     return rc;
309 }
310
311 int rpmFileIsCompressed(const char * file, rpmCompressedMagic * compressed)
312 {
313     FD_t fd;
314     ssize_t nb;
315     int rc = -1;
316     unsigned char magic[13];
317
318     *compressed = COMPRESSED_NOT;
319
320     fd = Fopen(file, "r.ufdio");
321     if (fd == NULL || Ferror(fd)) {
322         /* XXX Fstrerror */
323         rpmlog(RPMLOG_ERR, _("File %s: %s\n"), file, Fstrerror(fd));
324         if (fd) (void) Fclose(fd);
325         return 1;
326     }
327     nb = Fread(magic, sizeof(magic[0]), sizeof(magic), fd);
328     if (nb < 0) {
329         rpmlog(RPMLOG_ERR, _("File %s: %s\n"), file, Fstrerror(fd));
330         rc = 1;
331     } else if (nb < sizeof(magic)) {
332         rpmlog(RPMLOG_ERR, _("File %s is smaller than %u bytes\n"),
333                 file, (unsigned)sizeof(magic));
334         rc = 0;
335     }
336     (void) Fclose(fd);
337     if (rc >= 0)
338         return rc;
339
340     rc = 0;
341
342     if ((magic[0] == 'B') && (magic[1] == 'Z') &&
343         (magic[2] == 'h')) {
344         *compressed = COMPRESSED_BZIP2;
345     } else if ((magic[0] == 'P') && (magic[1] == 'K') &&
346          (((magic[2] == 3) && (magic[3] == 4)) ||
347           ((magic[2] == '0') && (magic[3] == '0')))) {  /* pkzip */
348         *compressed = COMPRESSED_ZIP;
349     } else if ((magic[0] == 0xfd) && (magic[1] == 0x37) &&
350                (magic[2] == 0x7a) && (magic[3] == 0x58) &&
351                (magic[4] == 0x5a) && (magic[5] == 0x00)) {
352         /* new style xz (lzma) with magic */
353         *compressed = COMPRESSED_XZ;
354     } else if ((magic[0] == 0x28) && (magic[1] == 0x85) &&
355                (magic[2] == 0x2f)                     ) {
356         *compressed = COMPRESSED_ZSTD;
357     } else if ((magic[0] == 'L') && (magic[1] == 'Z') &&
358                (magic[2] == 'I') && (magic[3] == 'P')) {
359         *compressed = COMPRESSED_LZIP;
360     } else if ((magic[0] == 'L') && (magic[1] == 'R') &&
361                (magic[2] == 'Z') && (magic[3] == 'I')) {
362         *compressed = COMPRESSED_LRZIP;
363     } else if (((magic[0] == 0037) && (magic[1] == 0213)) || /* gzip */
364         ((magic[0] == 0037) && (magic[1] == 0236)) ||   /* old gzip */
365         ((magic[0] == 0037) && (magic[1] == 0036)) ||   /* pack */
366         ((magic[0] == 0037) && (magic[1] == 0240)) ||   /* SCO lzh */
367         ((magic[0] == 0037) && (magic[1] == 0235))      /* compress */
368         ) {
369         *compressed = COMPRESSED_OTHER;
370     } else if ((magic[0] == '7') && (magic[1] == 'z') &&
371                (magic[2] == 0xbc) && (magic[3] == 0xaf) &&
372                (magic[4] == 0x27) && (magic[5] == 0x1c)) {
373         *compressed = COMPRESSED_7ZIP;
374     } else if (rpmFileHasSuffix(file, ".lzma")) {
375         *compressed = COMPRESSED_LZMA;
376     } else if (rpmFileHasSuffix(file, ".gem")) {
377         *compressed = COMPRESSED_GEM;
378     }
379
380     return rc;
381 }
382
383 /* @todo "../sbin/./../bin/" not correct. */
384 char *rpmCleanPath(char * path)
385 {
386     const char *s;
387     char *se, *t, *te;
388     int begin = 1;
389
390     if (path == NULL)
391         return NULL;
392
393 /*fprintf(stderr, "*** RCP %s ->\n", path); */
394     s = t = te = path;
395     while (*s != '\0') {
396 /*fprintf(stderr, "*** got \"%.*s\"\trest \"%s\"\n", (t-path), path, s); */
397         switch (*s) {
398         case ':':                       /* handle url's */
399             if (s[1] == '/' && s[2] == '/') {
400                 *t++ = *s++;
401                 *t++ = *s++;
402                 break;
403             }
404             begin=1;
405             break;
406         case '/':
407             /* Move parent dir forward */
408             for (se = te + 1; se < t && *se != '/'; se++)
409                 {};
410             if (se < t && *se == '/') {
411                 te = se;
412 /*fprintf(stderr, "*** next pdir \"%.*s\"\n", (te-path), path); */
413             }
414             while (s[1] == '/')
415                 s++;
416             while (t > path && t[-1] == '/')
417                 t--;
418             break;
419         case '.':
420             /* Leading .. is special */
421             /* Check that it is ../, so that we don't interpret */
422             /* ..?(i.e. "...") or ..* (i.e. "..bogus") as "..". */
423             /* in the case of "...", this ends up being processed*/
424             /* as "../.", and the last '.' is stripped.  This   */
425             /* would not be correct processing.                 */
426             if (begin && s[1] == '.' && (s[2] == '/' || s[2] == '\0')) {
427 /*fprintf(stderr, "    leading \"..\"\n"); */
428                 *t++ = *s++;
429                 break;
430             }
431             /* Single . is special */
432             if (begin && s[1] == '\0') {
433                 break;
434             }
435             /* Handle the ./ cases */
436             if (t > path && t[-1] == '/') {
437                 /* Trim embedded ./ */
438                 if (s[1] == '/') {
439                     s+=2;
440                     continue;
441                 }
442                 /* Trim trailing /. */
443                 if (s[1] == '\0') {
444                     s++;
445                     continue;
446                 }
447             }
448             /* Trim embedded /../ and trailing /.. */
449             if (!begin && t > path && t[-1] == '/' && s[1] == '.' && (s[2] == '/' || s[2] == '\0')) {
450                 t = te;
451                 /* Move parent dir forward */
452                 if (te > path)
453                     for (--te; te > path && *te != '/'; te--)
454                         {};
455 /*fprintf(stderr, "*** prev pdir \"%.*s\"\n", (te-path), path); */
456                 s++;
457                 s++;
458                 continue;
459             }
460             break;
461         default:
462             begin = 0;
463             break;
464         }
465         *t++ = *s++;
466     }
467
468     /* Trim trailing / (but leave single / alone) */
469     if (t > &path[1] && t[-1] == '/')
470         t--;
471     *t = '\0';
472
473 /*fprintf(stderr, "\t%s\n", path); */
474     return path;
475 }
476
477 /* Merge 3 args into path, any or all of which may be a url. */
478
479 char * rpmGenPath(const char * urlroot, const char * urlmdir,
480                 const char *urlfile)
481 {
482     char * xroot = rpmGetPath(urlroot, NULL);
483     const char * root = xroot;
484     char * xmdir = rpmGetPath(urlmdir, NULL);
485     const char * mdir = xmdir;
486     char * xfile = rpmGetPath(urlfile, NULL);
487     const char * file = xfile;
488     char * result;
489     char * url = NULL;
490     int nurl = 0;
491     int ut;
492
493     ut = urlPath(xroot, &root);
494     if (url == NULL && ut > URL_IS_DASH) {
495         url = xroot;
496         nurl = root - xroot;
497     }
498     if (root == NULL || *root == '\0') root = "/";
499
500     ut = urlPath(xmdir, &mdir);
501     if (url == NULL && ut > URL_IS_DASH) {
502         url = xmdir;
503         nurl = mdir - xmdir;
504     }
505     if (mdir == NULL || *mdir == '\0') mdir = "/";
506
507     ut = urlPath(xfile, &file);
508     if (url == NULL && ut > URL_IS_DASH) {
509         url = xfile;
510         nurl = file - xfile;
511     }
512
513     if (url && nurl > 0) {
514         char *t = rstrcat(NULL, url);
515         t[nurl] = '\0';
516         url = t;
517     } else
518         url = xstrdup("");
519
520     result = rpmGetPath(url, root, "/", mdir, "/", file, NULL);
521
522     free(xroot);
523     free(xmdir);
524     free(xfile);
525     free(url);
526     return result;
527 }
528
529 /* Return concatenated and expanded canonical path. */
530
531 char * rpmGetPath(const char *path, ...)
532 {
533     va_list ap;
534     char *dest = NULL, *res;
535     const char *s;
536
537     if (path == NULL)
538         return xstrdup("");
539
540     va_start(ap, path);
541     for (s = path; s; s = va_arg(ap, const char *)) {
542         rstrcat(&dest, s);
543     }
544     va_end(ap);
545
546     res = rpmExpand(dest, NULL);
547     free(dest);
548
549     return rpmCleanPath(res);
550 }
551
552 char * rpmEscapeSpaces(const char * s)
553 {
554     const char * se;
555     char * t;
556     char * te;
557     size_t nb = 0;
558
559     for (se = s; *se; se++) {
560         if (isspace(*se))
561             nb++;
562         nb++;
563     }
564     nb++;
565
566     t = te = xmalloc(nb);
567     for (se = s; *se; se++) {
568         if (isspace(*se))
569             *te++ = '\\';
570         *te++ = *se;
571     }
572     *te = '\0';
573     return t;
574 }
575
576 int rpmFileHasSuffix(const char *path, const char *suffix)
577 {
578     size_t plen = strlen(path);
579     size_t slen = strlen(suffix);
580     return (plen >= slen && rstreq(path+plen-slen, suffix));
581 }
582
583 char * rpmGetCwd(void)
584 {
585     int currDirLen = 0;
586     char * currDir = NULL;
587
588     do {
589         currDirLen += 128;
590         currDir = xrealloc(currDir, currDirLen);
591         memset(currDir, 0, currDirLen);
592     } while (getcwd(currDir, currDirLen) == NULL && errno == ERANGE);
593
594     return currDir;
595 }
596
597 int rpmMkdirs(const char *root, const char *pathstr)
598 {
599     ARGV_t dirs = NULL;
600     int rc = 0;
601     argvSplit(&dirs, pathstr, ":");
602     
603     for (char **d = dirs; *d; d++) {
604         char *path = rpmGetPath(root ? root : "", *d, NULL);
605         if ((rc = rpmioMkpath(path, 0755, -1, -1)) != 0) {
606             const char *msg = _("failed to create directory");
607             /* try to be more informative if the failing part was a macro */
608             if (**d == '%') {
609                 rpmlog(RPMLOG_ERR, "%s %s: %s: %m\n", msg, *d, path);
610             } else {
611                 rpmlog(RPMLOG_ERR, "%s %s: %m\n", msg, path);
612             }
613         }
614         free(path);
615         if (rc) break;
616     }
617     argvFree(dirs);
618     return rc;
619 }
620
621 static void setConfigDir(void)
622 {
623     char *rpmenv = getenv("RPM_CONFIGDIR");
624     rpm_config_dir = rpmenv ? xstrdup(rpmenv) : RPMCONFIGDIR;
625 }
626
627 const char *rpmConfigDir(void)
628 {
629     pthread_once(&configDirSet, setConfigDir);
630     return rpm_config_dir;
631 }