1 /** \ingroup payload
2  * \file lib/fsm.c
3  * File state machine to handle a payload from a package.
4  */
5 
6 #include "system.h"
7 
8 #include <utime.h>
9 #include <errno.h>
10 #if WITH_CAP
11 #include <sys/capability.h>
12 #endif
13 
14 #include <rpm/rpmte.h>
15 #include <rpm/rpmts.h>
16 #include <rpm/rpmlog.h>
17 #include <rpm/rpmmacro.h>
18 
19 #include "rpmio/rpmio_internal.h"	/* fdInit/FiniDigest */
20 #include "lib/fsm.h"
21 #include "lib/rpmte_internal.h"	/* XXX rpmfs */
22 #include "lib/rpmplugins.h"	/* rpm plugins hooks */
23 #include "lib/rpmug.h"
24 
25 #include "debug.h"
26 
27 #define	_FSM_DEBUG	0
28 int _fsm_debug = _FSM_DEBUG;
29 
30 /* XXX Failure to remove is not (yet) cause for failure. */
31 static int strict_erasures = 0;
32 
33 #define	SUFFIX_RPMORIG	".rpmorig"
34 #define	SUFFIX_RPMSAVE	".rpmsave"
35 #define	SUFFIX_RPMNEW	".rpmnew"
36 
37 /* Default directory and file permissions if not mapped */
38 #define _dirPerms 0755
39 #define _filePerms 0644
40 
41 /*
42  * XXX Forward declarations for previously exported functions to avoid moving
43  * things around needlessly
44  */
45 static const char * fileActionString(rpmFileAction a);
46 
47 /** \ingroup payload
48  * Build path to file from file info, optionally ornamented with suffix.
49  * @param fi		file info iterator
50  * @param suffix	suffix to use (NULL disables)
51  * @retval		path to file (malloced)
52  */
fsmFsPath(rpmfi fi,const char * suffix)53 static char * fsmFsPath(rpmfi fi, const char * suffix)
54 {
55     return rstrscat(NULL, rpmfiDN(fi), rpmfiBN(fi), suffix ? suffix : "", NULL);
56 }
57 
58 /** \ingroup payload
59  * Directory name iterator.
60  */
61 typedef struct dnli_s {
62     rpmfiles fi;
63     char * active;
64     int reverse;
65     int isave;
66     int i;
67 } * DNLI_t;
68 
69 /** \ingroup payload
70  * Destroy directory name iterator.
71  * @param dnli		directory name iterator
72  * @retval		NULL always
73  */
dnlFreeIterator(DNLI_t dnli)74 static DNLI_t dnlFreeIterator(DNLI_t dnli)
75 {
76     if (dnli) {
77 	if (dnli->active) free(dnli->active);
78 	free(dnli);
79     }
80     return NULL;
81 }
82 
83 /** \ingroup payload
84  * Create directory name iterator.
85  * @param fi		file info set
86  * @param fs		file state set
87  * @param reverse	traverse directory names in reverse order?
88  * @return		directory name iterator
89  */
dnlInitIterator(rpmfiles fi,rpmfs fs,int reverse)90 static DNLI_t dnlInitIterator(rpmfiles fi, rpmfs fs, int reverse)
91 {
92     DNLI_t dnli;
93     int i, j;
94     int dc;
95 
96     if (fi == NULL)
97 	return NULL;
98     dc = rpmfilesDC(fi);
99     dnli = xcalloc(1, sizeof(*dnli));
100     dnli->fi = fi;
101     dnli->reverse = reverse;
102     dnli->i = (reverse ? dc : 0);
103 
104     if (dc) {
105 	dnli->active = xcalloc(dc, sizeof(*dnli->active));
106 	int fc = rpmfilesFC(fi);
107 
108 	/* Identify parent directories not skipped. */
109 	for (i = 0; i < fc; i++)
110             if (!XFA_SKIPPING(rpmfsGetAction(fs, i)))
111 		dnli->active[rpmfilesDI(fi, i)] = 1;
112 
113 	/* Exclude parent directories that are explicitly included. */
114 	for (i = 0; i < fc; i++) {
115 	    int dil;
116 	    size_t dnlen, bnlen;
117 
118 	    if (!S_ISDIR(rpmfilesFMode(fi, i)))
119 		continue;
120 
121 	    dil = rpmfilesDI(fi, i);
122 	    dnlen = strlen(rpmfilesDN(fi, dil));
123 	    bnlen = strlen(rpmfilesBN(fi, i));
124 
125 	    for (j = 0; j < dc; j++) {
126 		const char * dnl;
127 		size_t jlen;
128 
129 		if (!dnli->active[j] || j == dil)
130 		    continue;
131 		dnl = rpmfilesDN(fi, j);
132 		jlen = strlen(dnl);
133 		if (jlen != (dnlen+bnlen+1))
134 		    continue;
135 		if (!rstreqn(dnl, rpmfilesDN(fi, dil), dnlen))
136 		    continue;
137 		if (!rstreqn(dnl+dnlen, rpmfilesBN(fi, i), bnlen))
138 		    continue;
139 		if (dnl[dnlen+bnlen] != '/' || dnl[dnlen+bnlen+1] != '\0')
140 		    continue;
141 		/* This directory is included in the package. */
142 		dnli->active[j] = 0;
143 		break;
144 	    }
145 	}
146 
147 	/* Print only once per package. */
148 	if (!reverse) {
149 	    j = 0;
150 	    for (i = 0; i < dc; i++) {
151 		if (!dnli->active[i]) continue;
152 		if (j == 0) {
153 		    j = 1;
154 		    rpmlog(RPMLOG_DEBUG,
155 	"========== Directories not explicitly included in package:\n");
156 		}
157 		rpmlog(RPMLOG_DEBUG, "%10d %s\n", i, rpmfilesDN(fi, i));
158 	    }
159 	    if (j)
160 		rpmlog(RPMLOG_DEBUG, "==========\n");
161 	}
162     }
163     return dnli;
164 }
165 
166 /** \ingroup payload
167  * Return next directory name (from file info).
168  * @param dnli		directory name iterator
169  * @return		next directory name
170  */
171 static
dnlNextIterator(DNLI_t dnli)172 const char * dnlNextIterator(DNLI_t dnli)
173 {
174     const char * dn = NULL;
175 
176     if (dnli) {
177 	rpmfiles fi = dnli->fi;
178 	int dc = rpmfilesDC(fi);
179 	int i = -1;
180 
181 	if (dnli->active)
182 	do {
183 	    i = (!dnli->reverse ? dnli->i++ : --dnli->i);
184 	} while (i >= 0 && i < dc && !dnli->active[i]);
185 
186 	if (i >= 0 && i < dc)
187 	    dn = rpmfilesDN(fi, i);
188 	else
189 	    i = -1;
190 	dnli->isave = i;
191     }
192     return dn;
193 }
194 
fsmSetFCaps(const char * path,const char * captxt)195 static int fsmSetFCaps(const char *path, const char *captxt)
196 {
197     int rc = 0;
198 #if WITH_CAP
199     if (captxt && *captxt != '\0') {
200 	cap_t fcaps = cap_from_text(captxt);
201 	if (fcaps == NULL || cap_set_file(path, fcaps) != 0) {
202 	    rc = RPMERR_SETCAP_FAILED;
203 	}
204 	if (_fsm_debug) {
205 	    rpmlog(RPMLOG_DEBUG, " %8s (%s, %s) %s\n", __func__,
206 		   path, captxt, (rc < 0 ? strerror(errno) : ""));
207 	}
208 	cap_free(fcaps);
209     }
210 #endif
211     return rc;
212 }
213 
wfd_close(FD_t * wfdp)214 static void wfd_close(FD_t *wfdp)
215 {
216     if (wfdp && *wfdp) {
217 	int myerrno = errno;
218 	static int oneshot = 0;
219 	static int flush_io = 0;
220 	if (!oneshot) {
221 	    flush_io = (rpmExpandNumeric("%{?_flush_io}") > 0);
222 	    oneshot = 1;
223 	}
224 	if (flush_io) {
225 	    int fdno = Fileno(*wfdp);
226 	    fsync(fdno);
227 	}
228 	Fclose(*wfdp);
229 	*wfdp = NULL;
230 	errno = myerrno;
231     }
232 }
233 
wfd_open(FD_t * wfdp,const char * dest)234 static int wfd_open(FD_t *wfdp, const char *dest)
235 {
236     int rc = 0;
237     /* Create the file with 0200 permissions (write by owner). */
238     {
239 	mode_t old_umask = umask(0577);
240 	*wfdp = Fopen(dest, "wx.ufdio");
241 	umask(old_umask);
242     }
243     if (Ferror(*wfdp)) {
244 	rc = RPMERR_OPEN_FAILED;
245 	goto exit;
246     }
247 
248     return 0;
249 
250 exit:
251     wfd_close(wfdp);
252     return rc;
253 }
254 
255 /** \ingroup payload
256  * Create file from payload stream.
257  * @return		0 on success
258  */
expandRegular(rpmfi fi,const char * dest,rpmpsm psm,int nodigest)259 static int expandRegular(rpmfi fi, const char *dest, rpmpsm psm, int nodigest)
260 {
261     FD_t wfd = NULL;
262     int rc;
263 
264     rc = wfd_open(&wfd, dest);
265     if (rc != 0)
266         goto exit;
267 
268     rc = rpmfiArchiveReadToFilePsm(fi, wfd, nodigest, psm);
269     wfd_close(&wfd);
270 exit:
271     return rc;
272 }
273 
fsmMkfile(rpmfi fi,const char * dest,rpmfiles files,rpmpsm psm,int nodigest,int * setmeta,int * firsthardlink,FD_t * firstlinkfile)274 static int fsmMkfile(rpmfi fi, const char *dest, rpmfiles files,
275 		     rpmpsm psm, int nodigest, int *setmeta,
276 		     int * firsthardlink, FD_t *firstlinkfile)
277 {
278     int rc = 0;
279     int numHardlinks = rpmfiFNlink(fi);
280 
281     if (numHardlinks > 1) {
282 	/* Create first hardlinked file empty */
283 	if (*firsthardlink < 0) {
284 	    *firsthardlink = rpmfiFX(fi);
285 	    rc = wfd_open(firstlinkfile, dest);
286 	} else {
287 	    /* Create hard links for others */
288 	    char *fn = rpmfilesFN(files, *firsthardlink);
289 	    rc = link(fn, dest);
290 	    if (rc < 0) {
291 		rc = RPMERR_LINK_FAILED;
292 	    }
293 	    free(fn);
294 	}
295     }
296     /* Write normal files or fill the last hardlinked (already
297        existing) file with content */
298     if (numHardlinks<=1) {
299 	if (!rc)
300 	    rc = expandRegular(fi, dest, psm, nodigest);
301     } else if (rpmfiArchiveHasContent(fi)) {
302 	if (!rc)
303 	    rc = rpmfiArchiveReadToFilePsm(fi, *firstlinkfile, nodigest, psm);
304 	wfd_close(firstlinkfile);
305 	*firsthardlink = -1;
306     } else {
307 	*setmeta = 0;
308     }
309 
310     return rc;
311 }
312 
fsmReadLink(const char * path,char * buf,size_t bufsize,size_t * linklen)313 static int fsmReadLink(const char *path,
314 		       char *buf, size_t bufsize, size_t *linklen)
315 {
316     ssize_t llen = readlink(path, buf, bufsize - 1);
317     int rc = RPMERR_READLINK_FAILED;
318 
319     if (_fsm_debug) {
320         rpmlog(RPMLOG_DEBUG, " %8s (%s, buf, %d) %s\n",
321 	       __func__,
322                path, (int)(bufsize -1), (llen < 0 ? strerror(errno) : ""));
323     }
324 
325     if (llen >= 0) {
326 	buf[llen] = '\0';
327 	rc = 0;
328 	*linklen = llen;
329     }
330     return rc;
331 }
332 
fsmStat(const char * path,int dolstat,struct stat * sb)333 static int fsmStat(const char *path, int dolstat, struct stat *sb)
334 {
335     int rc;
336     if (dolstat){
337 	rc = lstat(path, sb);
338     } else {
339         rc = stat(path, sb);
340     }
341     if (_fsm_debug && rc && errno != ENOENT)
342         rpmlog(RPMLOG_DEBUG, " %8s (%s, ost) %s\n",
343                __func__,
344                path, (rc < 0 ? strerror(errno) : ""));
345     if (rc < 0) {
346         rc = (errno == ENOENT ? RPMERR_ENOENT : RPMERR_LSTAT_FAILED);
347 	/* Ensure consistent struct content on failure */
348         memset(sb, 0, sizeof(*sb));
349     }
350     return rc;
351 }
352 
fsmRmdir(const char * path)353 static int fsmRmdir(const char *path)
354 {
355     int rc = rmdir(path);
356     if (_fsm_debug)
357 	rpmlog(RPMLOG_DEBUG, " %8s (%s) %s\n", __func__,
358 	       path, (rc < 0 ? strerror(errno) : ""));
359     if (rc < 0)
360 	switch (errno) {
361 	case ENOENT:        rc = RPMERR_ENOENT;    break;
362 	case ENOTEMPTY:     rc = RPMERR_ENOTEMPTY; break;
363 	default:            rc = RPMERR_RMDIR_FAILED; break;
364 	}
365     return rc;
366 }
367 
fsmMkdir(const char * path,mode_t mode)368 static int fsmMkdir(const char *path, mode_t mode)
369 {
370     int rc = mkdir(path, (mode & 07777));
371     if (_fsm_debug)
372 	rpmlog(RPMLOG_DEBUG, " %8s (%s, 0%04o) %s\n", __func__,
373 	       path, (unsigned)(mode & 07777),
374 	       (rc < 0 ? strerror(errno) : ""));
375     if (rc < 0)	rc = RPMERR_MKDIR_FAILED;
376     return rc;
377 }
378 
fsmMkfifo(const char * path,mode_t mode)379 static int fsmMkfifo(const char *path, mode_t mode)
380 {
381     int rc = mkfifo(path, (mode & 07777));
382 
383     if (_fsm_debug) {
384 	rpmlog(RPMLOG_DEBUG, " %8s (%s, 0%04o) %s\n",
385 	       __func__, path, (unsigned)(mode & 07777),
386 	       (rc < 0 ? strerror(errno) : ""));
387     }
388 
389     if (rc < 0)
390 	rc = RPMERR_MKFIFO_FAILED;
391 
392     return rc;
393 }
394 
fsmMknod(const char * path,mode_t mode,dev_t dev)395 static int fsmMknod(const char *path, mode_t mode, dev_t dev)
396 {
397     /* FIX: check S_IFIFO or dev != 0 */
398     int rc = mknod(path, (mode & ~07777), dev);
399 
400     if (_fsm_debug) {
401 	rpmlog(RPMLOG_DEBUG, " %8s (%s, 0%o, 0x%x) %s\n",
402 	       __func__, path, (unsigned)(mode & ~07777),
403 	       (unsigned)dev, (rc < 0 ? strerror(errno) : ""));
404     }
405 
406     if (rc < 0)
407 	rc = RPMERR_MKNOD_FAILED;
408 
409     return rc;
410 }
411 
412 /**
413  * Create (if necessary) directories not explicitly included in package.
414  * @param files		file data
415  * @param fs		file states
416  * @param plugins	rpm plugins handle
417  * @return		0 on success
418  */
fsmMkdirs(rpmfiles files,rpmfs fs,rpmPlugins plugins)419 static int fsmMkdirs(rpmfiles files, rpmfs fs, rpmPlugins plugins)
420 {
421     DNLI_t dnli = dnlInitIterator(files, fs, 0);
422     struct stat sb;
423     const char *dpath;
424     int rc = 0;
425     int i;
426     size_t ldnlen = 0;
427     const char * ldn = NULL;
428 
429     while ((dpath = dnlNextIterator(dnli)) != NULL) {
430 	size_t dnlen = strlen(dpath);
431 	char * te, dn[dnlen+1];
432 
433 	if (dnlen <= 1)
434 	    continue;
435 
436 	if (dnlen == ldnlen && rstreq(dpath, ldn))
437 	    continue;
438 
439 	/* Copy as we need to modify the string */
440 	(void) stpcpy(dn, dpath);
441 
442 	/* Assume '/' directory exists, "mkdir -p" for others if non-existent */
443 	for (i = 1, te = dn + 1; *te != '\0'; te++, i++) {
444 	    if (*te != '/')
445 		continue;
446 
447 	    /* Already validated? */
448 	    if (i < ldnlen &&
449 		(ldn[i] == '/' || ldn[i] == '\0') && rstreqn(dn, ldn, i))
450 		continue;
451 
452 	    /* Validate next component of path. */
453 	    *te = '\0';
454 	    rc = fsmStat(dn, 1, &sb); /* lstat */
455 	    *te = '/';
456 
457 	    /* Directory already exists? */
458 	    if (rc == 0 && S_ISDIR(sb.st_mode)) {
459 		continue;
460 	    } else if (rc == RPMERR_ENOENT) {
461 		*te = '\0';
462 		mode_t mode = S_IFDIR | (_dirPerms & 07777);
463 		rpmFsmOp op = (FA_CREATE|FAF_UNOWNED);
464 
465 		/* Run fsm file pre hook for all plugins */
466 		rc = rpmpluginsCallFsmFilePre(plugins, NULL, dn, mode, op);
467 
468 		if (!rc)
469 		    rc = fsmMkdir(dn, mode);
470 
471 		if (!rc) {
472 		    rc = rpmpluginsCallFsmFilePrepare(plugins, NULL, dn, dn,
473 						      mode, op);
474 		}
475 
476 		/* Run fsm file post hook for all plugins */
477 		rpmpluginsCallFsmFilePost(plugins, NULL, dn, mode, op, rc);
478 
479 		if (!rc) {
480 		    rpmlog(RPMLOG_DEBUG,
481 			    "%s directory created with perms %04o\n",
482 			    dn, (unsigned)(mode & 07777));
483 		}
484 		*te = '/';
485 	    }
486 	    if (rc)
487 		break;
488 	}
489 	if (rc) break;
490 
491 	/* Save last validated path. */
492 	ldn = dpath;
493 	ldnlen = dnlen;
494     }
495     dnlFreeIterator(dnli);
496 
497     return rc;
498 }
499 
removeSBITS(const char * path)500 static void removeSBITS(const char *path)
501 {
502     struct stat stb;
503     if (lstat(path, &stb) == 0 && S_ISREG(stb.st_mode)) {
504 	if ((stb.st_mode & 06000) != 0) {
505 	    (void) chmod(path, stb.st_mode & 0777);
506 	}
507 #if WITH_CAP
508 	if (stb.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH)) {
509 	    (void) cap_set_file(path, NULL);
510 	}
511 #endif
512     }
513 }
514 
fsmDebug(const char * fpath,rpmFileAction action,const struct stat * st)515 static void fsmDebug(const char *fpath, rpmFileAction action,
516 		     const struct stat *st)
517 {
518     rpmlog(RPMLOG_DEBUG, "%-10s %06o%3d (%4d,%4d)%6d %s\n",
519 	   fileActionString(action), (int)st->st_mode,
520 	   (int)st->st_nlink, (int)st->st_uid,
521 	   (int)st->st_gid, (int)st->st_size,
522 	    (fpath ? fpath : ""));
523 }
524 
fsmSymlink(const char * opath,const char * path)525 static int fsmSymlink(const char *opath, const char *path)
526 {
527     int rc = symlink(opath, path);
528 
529     if (_fsm_debug) {
530 	rpmlog(RPMLOG_DEBUG, " %8s (%s, %s) %s\n", __func__,
531 	       opath, path, (rc < 0 ? strerror(errno) : ""));
532     }
533 
534     if (rc < 0)
535 	rc = RPMERR_SYMLINK_FAILED;
536     return rc;
537 }
538 
fsmUnlink(const char * path)539 static int fsmUnlink(const char *path)
540 {
541     int rc = 0;
542     removeSBITS(path);
543     rc = unlink(path);
544     if (_fsm_debug)
545 	rpmlog(RPMLOG_DEBUG, " %8s (%s) %s\n", __func__,
546 	       path, (rc < 0 ? strerror(errno) : ""));
547     if (rc < 0)
548 	rc = (errno == ENOENT ? RPMERR_ENOENT : RPMERR_UNLINK_FAILED);
549     return rc;
550 }
551 
fsmRename(const char * opath,const char * path)552 static int fsmRename(const char *opath, const char *path)
553 {
554     removeSBITS(path);
555     int rc = rename(opath, path);
556 #if defined(ETXTBSY) && defined(__HPUX__)
557     /* XXX HP-UX (and other os'es) don't permit rename to busy files. */
558     if (rc && errno == ETXTBSY) {
559 	char *rmpath = NULL;
560 	rstrscat(&rmpath, path, "-RPMDELETE", NULL);
561 	rc = rename(path, rmpath);
562 	if (!rc) rc = rename(opath, path);
563 	free(rmpath);
564     }
565 #endif
566     if (_fsm_debug)
567 	rpmlog(RPMLOG_DEBUG, " %8s (%s, %s) %s\n", __func__,
568 	       opath, path, (rc < 0 ? strerror(errno) : ""));
569     if (rc < 0)
570 	rc = (errno == EISDIR ? RPMERR_EXIST_AS_DIR : RPMERR_RENAME_FAILED);
571     return rc;
572 }
573 
fsmRemove(const char * path,mode_t mode)574 static int fsmRemove(const char *path, mode_t mode)
575 {
576     return S_ISDIR(mode) ? fsmRmdir(path) : fsmUnlink(path);
577 }
578 
fsmChown(const char * path,mode_t mode,uid_t uid,gid_t gid)579 static int fsmChown(const char *path, mode_t mode, uid_t uid, gid_t gid)
580 {
581     int rc = S_ISLNK(mode) ? lchown(path, uid, gid) : chown(path, uid, gid);
582     if (rc < 0) {
583 	struct stat st;
584 	if (lstat(path, &st) == 0 && st.st_uid == uid && st.st_gid == gid)
585 	    rc = 0;
586     }
587     if (_fsm_debug)
588 	rpmlog(RPMLOG_DEBUG, " %8s (%s, %d, %d) %s\n", __func__,
589 	       path, (int)uid, (int)gid,
590 	       (rc < 0 ? strerror(errno) : ""));
591     if (rc < 0)	rc = RPMERR_CHOWN_FAILED;
592     return rc;
593 }
594 
fsmChmod(const char * path,mode_t mode)595 static int fsmChmod(const char *path, mode_t mode)
596 {
597     int rc = chmod(path, (mode & 07777));
598     if (rc < 0) {
599 	struct stat st;
600 	if (lstat(path, &st) == 0 && (st.st_mode & 07777) == (mode & 07777))
601 	    rc = 0;
602     }
603     if (_fsm_debug)
604 	rpmlog(RPMLOG_DEBUG, " %8s (%s, 0%04o) %s\n", __func__,
605 	       path, (unsigned)(mode & 07777),
606 	       (rc < 0 ? strerror(errno) : ""));
607     if (rc < 0)	rc = RPMERR_CHMOD_FAILED;
608     return rc;
609 }
610 
fsmUtime(const char * path,mode_t mode,time_t mtime)611 static int fsmUtime(const char *path, mode_t mode, time_t mtime)
612 {
613     int rc = 0;
614     struct timeval stamps[2] = {
615 	{ .tv_sec = mtime, .tv_usec = 0 },
616 	{ .tv_sec = mtime, .tv_usec = 0 },
617     };
618 
619 #if HAVE_LUTIMES
620     rc = lutimes(path, stamps);
621 #else
622     if (!S_ISLNK(mode))
623 	rc = utimes(path, stamps);
624 #endif
625 
626     if (_fsm_debug)
627 	rpmlog(RPMLOG_DEBUG, " %8s (%s, 0x%x) %s\n", __func__,
628 	       path, (unsigned)mtime, (rc < 0 ? strerror(errno) : ""));
629     if (rc < 0)	rc = RPMERR_UTIME_FAILED;
630     /* ...but utime error is not critical for directories */
631     if (rc && S_ISDIR(mode))
632 	rc = 0;
633     return rc;
634 }
635 
fsmVerify(const char * path,rpmfi fi)636 static int fsmVerify(const char *path, rpmfi fi)
637 {
638     int rc;
639     int saveerrno = errno;
640     struct stat dsb;
641     mode_t mode = rpmfiFMode(fi);
642 
643     rc = fsmStat(path, 1, &dsb);
644     if (rc)
645 	return rc;
646 
647     if (S_ISREG(mode)) {
648 	/* HP-UX (and other os'es) don't permit unlink on busy files. */
649 	char *rmpath = rstrscat(NULL, path, "-RPMDELETE", NULL);
650 	rc = fsmRename(path, rmpath);
651 	/* XXX shouldn't we take unlink return code here? */
652 	if (!rc)
653 	    (void) fsmUnlink(rmpath);
654 	else
655 	    rc = RPMERR_UNLINK_FAILED;
656 	free(rmpath);
657         return (rc ? rc : RPMERR_ENOENT);	/* XXX HACK */
658     } else if (S_ISDIR(mode)) {
659         if (S_ISDIR(dsb.st_mode)) return 0;
660         if (S_ISLNK(dsb.st_mode)) {
661 	    uid_t luid = dsb.st_uid;
662             rc = fsmStat(path, 0, &dsb);
663             if (rc == RPMERR_ENOENT) rc = 0;
664             if (rc) return rc;
665             errno = saveerrno;
666 	    /* Only permit directory symlinks by target owner and root */
667             if (S_ISDIR(dsb.st_mode) && (luid == 0 || luid == dsb.st_uid))
668 		    return 0;
669         }
670     } else if (S_ISLNK(mode)) {
671         if (S_ISLNK(dsb.st_mode)) {
672             char buf[8 * BUFSIZ];
673             size_t len;
674             rc = fsmReadLink(path, buf, 8 * BUFSIZ, &len);
675             errno = saveerrno;
676             if (rc) return rc;
677             if (rstreq(rpmfiFLink(fi), buf)) return 0;
678         }
679     } else if (S_ISFIFO(mode)) {
680         if (S_ISFIFO(dsb.st_mode)) return 0;
681     } else if (S_ISCHR(mode) || S_ISBLK(mode)) {
682         if ((S_ISCHR(dsb.st_mode) || S_ISBLK(dsb.st_mode)) &&
683             (dsb.st_rdev == rpmfiFRdev(fi))) return 0;
684     } else if (S_ISSOCK(mode)) {
685         if (S_ISSOCK(dsb.st_mode)) return 0;
686     }
687     /* XXX shouldn't do this with commit/undo. */
688     rc = fsmUnlink(path);
689     if (rc == 0)	rc = RPMERR_ENOENT;
690     return (rc ? rc : RPMERR_ENOENT);	/* XXX HACK */
691 }
692 
693 #define	IS_DEV_LOG(_x)	\
694 	((_x) != NULL && strlen(_x) >= (sizeof("/dev/log")-1) && \
695 	rstreqn((_x), "/dev/log", sizeof("/dev/log")-1) && \
696 	((_x)[sizeof("/dev/log")-1] == '\0' || \
697 	 (_x)[sizeof("/dev/log")-1] == ';'))
698 
699 
700 
701 /* Rename pre-existing modified or unmanaged file. */
fsmBackup(rpmfi fi,rpmFileAction action)702 static int fsmBackup(rpmfi fi, rpmFileAction action)
703 {
704     int rc = 0;
705     const char *suffix = NULL;
706 
707     if (!(rpmfiFFlags(fi) & RPMFILE_GHOST)) {
708 	switch (action) {
709 	case FA_SAVE:
710 	    suffix = SUFFIX_RPMSAVE;
711 	    break;
712 	case FA_BACKUP:
713 	    suffix = SUFFIX_RPMORIG;
714 	    break;
715 	default:
716 	    break;
717 	}
718     }
719 
720     if (suffix) {
721 	char * opath = fsmFsPath(fi, NULL);
722 	char * path = fsmFsPath(fi, suffix);
723 	rc = fsmRename(opath, path);
724 	if (!rc) {
725 	    rpmlog(RPMLOG_WARNING, _("%s saved as %s\n"), opath, path);
726 	}
727 	free(path);
728 	free(opath);
729     }
730     return rc;
731 }
732 
fsmSetmeta(const char * path,rpmfi fi,rpmPlugins plugins,rpmFileAction action,const struct stat * st,int nofcaps)733 static int fsmSetmeta(const char *path, rpmfi fi, rpmPlugins plugins,
734 		      rpmFileAction action, const struct stat * st,
735 		      int nofcaps)
736 {
737     int rc = 0;
738     const char *dest = rpmfiFN(fi);
739 
740     if (!rc && !getuid()) {
741 	rc = fsmChown(path, st->st_mode, st->st_uid, st->st_gid);
742     }
743     if (!rc && !S_ISLNK(st->st_mode)) {
744 	rc = fsmChmod(path, st->st_mode);
745     }
746     /* Set file capabilities (if enabled) */
747     if (!rc && !nofcaps && S_ISREG(st->st_mode) && !getuid()) {
748 	rc = fsmSetFCaps(path, rpmfiFCaps(fi));
749     }
750     if (!rc) {
751 	rc = fsmUtime(path, st->st_mode, rpmfiFMtime(fi));
752     }
753     if (!rc) {
754 	rc = rpmpluginsCallFsmFilePrepare(plugins, fi,
755 					  path, dest, st->st_mode, action);
756     }
757 
758     return rc;
759 }
760 
fsmCommit(char ** path,rpmfi fi,rpmFileAction action,const char * suffix)761 static int fsmCommit(char **path, rpmfi fi, rpmFileAction action, const char *suffix)
762 {
763     int rc = 0;
764 
765     /* XXX Special case /dev/log, which shouldn't be packaged anyways */
766     if (!(S_ISSOCK(rpmfiFMode(fi)) && IS_DEV_LOG(*path))) {
767 	const char *nsuffix = (action == FA_ALTNAME) ? SUFFIX_RPMNEW : NULL;
768 	char *dest = *path;
769 	/* Construct final destination path (nsuffix is usually NULL) */
770 	if (suffix)
771 	    dest = fsmFsPath(fi, nsuffix);
772 
773 	/* Rename temporary to final file name if needed. */
774 	if (dest != *path) {
775 	    rc = fsmRename(*path, dest);
776 	    if (!rc && nsuffix) {
777 		char * opath = fsmFsPath(fi, NULL);
778 		rpmlog(RPMLOG_WARNING, _("%s created as %s\n"),
779 		       opath, dest);
780 		free(opath);
781 	    }
782 	    free(*path);
783 	    *path = dest;
784 	}
785     }
786 
787     return rc;
788 }
789 
790 /**
791  * Return formatted string representation of file disposition.
792  * @param a		file disposition
793  * @return		formatted string
794  */
fileActionString(rpmFileAction a)795 static const char * fileActionString(rpmFileAction a)
796 {
797     switch (a) {
798     case FA_UNKNOWN:	return "unknown";
799     case FA_CREATE:	return "create";
800     case FA_BACKUP:	return "backup";
801     case FA_SAVE:	return "save";
802     case FA_SKIP:	return "skip";
803     case FA_ALTNAME:	return "altname";
804     case FA_ERASE:	return "erase";
805     case FA_SKIPNSTATE: return "skipnstate";
806     case FA_SKIPNETSHARED: return "skipnetshared";
807     case FA_SKIPCOLOR:	return "skipcolor";
808     case FA_TOUCH:     return "touch";
809     default:		return "???";
810     }
811 }
812 
813 /* Remember any non-regular file state for recording in the rpmdb */
setFileState(rpmfs fs,int i)814 static void setFileState(rpmfs fs, int i)
815 {
816     switch (rpmfsGetAction(fs, i)) {
817     case FA_SKIPNSTATE:
818 	rpmfsSetState(fs, i, RPMFILE_STATE_NOTINSTALLED);
819 	break;
820     case FA_SKIPNETSHARED:
821 	rpmfsSetState(fs, i, RPMFILE_STATE_NETSHARED);
822 	break;
823     case FA_SKIPCOLOR:
824 	rpmfsSetState(fs, i, RPMFILE_STATE_WRONGCOLOR);
825 	break;
826     case FA_TOUCH:
827 	rpmfsSetState(fs, i, RPMFILE_STATE_NORMAL);
828 	break;
829     default:
830 	break;
831     }
832 }
833 
rpmPackageFilesInstall(rpmts ts,rpmte te,rpmfiles files,rpmpsm psm,char ** failedFile)834 int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files,
835               rpmpsm psm, char ** failedFile)
836 {
837     FD_t payload = rpmtePayload(te);
838     rpmfi fi = rpmfiNewArchiveReader(payload, files, RPMFI_ITER_READ_ARCHIVE);
839     rpmfs fs = rpmteGetFileStates(te);
840     rpmPlugins plugins = rpmtsPlugins(ts);
841     struct stat sb;
842     int saveerrno = errno;
843     int rc = 0;
844     int nodigest = (rpmtsFlags(ts) & RPMTRANS_FLAG_NOFILEDIGEST) ? 1 : 0;
845     int nofcaps = (rpmtsFlags(ts) & RPMTRANS_FLAG_NOCAPS) ? 1 : 0;
846     int firsthardlink = -1;
847     FD_t firstlinkfile = NULL;
848     int skip;
849     rpmFileAction action;
850     char *tid = NULL;
851     const char *suffix;
852     char *fpath = NULL;
853 
854     if (fi == NULL) {
855 	rc = RPMERR_BAD_MAGIC;
856 	goto exit;
857     }
858 
859     /* transaction id used for temporary path suffix while installing */
860     rasprintf(&tid, ";%08x", (unsigned)rpmtsGetTid(ts));
861 
862     /* Detect and create directories not explicitly in package. */
863     rc = fsmMkdirs(files, fs, plugins);
864 
865     while (!rc) {
866 	/* Read next payload header. */
867 	rc = rpmfiNext(fi);
868 
869 	if (rc < 0) {
870 	    if (rc == RPMERR_ITER_END)
871 		rc = 0;
872 	    break;
873 	}
874 
875 	action = rpmfsGetAction(fs, rpmfiFX(fi));
876 	skip = XFA_SKIPPING(action);
877 	if (action != FA_TOUCH) {
878 	    suffix = S_ISDIR(rpmfiFMode(fi)) ? NULL : tid;
879 	} else {
880 	    suffix = NULL;
881 	}
882 	fpath = fsmFsPath(fi, suffix);
883 
884 	/* Remap file perms, owner, and group. */
885 	rc = rpmfiStat(fi, 1, &sb);
886 
887 	fsmDebug(fpath, action, &sb);
888 
889         /* Exit on error. */
890         if (rc)
891             break;
892 
893 	/* Run fsm file pre hook for all plugins */
894 	rc = rpmpluginsCallFsmFilePre(plugins, fi, fpath,
895 				      sb.st_mode, action);
896 	if (rc) {
897 	    skip = 1;
898 	} else {
899 	    setFileState(fs, rpmfiFX(fi));
900 	}
901 
902         if (!skip) {
903 	    int setmeta = 1;
904 
905 	    /* Directories replacing something need early backup */
906 	    if (!suffix) {
907 		rc = fsmBackup(fi, action);
908 	    }
909 	    /* Assume file does't exist when tmp suffix is in use */
910 	    if (!suffix) {
911 		rc = fsmVerify(fpath, fi);
912 	    } else {
913 		rc = RPMERR_ENOENT;
914 	    }
915 
916 	    /* See if the file was removed while our attention was elsewhere */
917 	    if (rc == RPMERR_ENOENT && action == FA_TOUCH) {
918 		rpmlog(RPMLOG_DEBUG, "file %s vanished unexpectedly\n", fpath);
919 		action = FA_CREATE;
920 		fsmDebug(fpath, action, &sb);
921 	    }
922 
923 	    /* When touching we don't need any of this... */
924 	    if (action == FA_TOUCH)
925 		goto touch;
926 
927             if (S_ISREG(sb.st_mode)) {
928 		if (rc == RPMERR_ENOENT) {
929 		    rc = fsmMkfile(fi, fpath, files, psm, nodigest,
930 				   &setmeta, &firsthardlink, &firstlinkfile);
931 		}
932             } else if (S_ISDIR(sb.st_mode)) {
933                 if (rc == RPMERR_ENOENT) {
934                     mode_t mode = sb.st_mode;
935                     mode &= ~07777;
936                     mode |=  00700;
937                     rc = fsmMkdir(fpath, mode);
938                 }
939             } else if (S_ISLNK(sb.st_mode)) {
940 		if (rc == RPMERR_ENOENT) {
941 		    rc = fsmSymlink(rpmfiFLink(fi), fpath);
942 		}
943             } else if (S_ISFIFO(sb.st_mode)) {
944                 /* This mimics cpio S_ISSOCK() behavior but probably isn't right */
945                 if (rc == RPMERR_ENOENT) {
946                     rc = fsmMkfifo(fpath, 0000);
947                 }
948             } else if (S_ISCHR(sb.st_mode) ||
949                        S_ISBLK(sb.st_mode) ||
950                        S_ISSOCK(sb.st_mode))
951             {
952                 if (rc == RPMERR_ENOENT) {
953                     rc = fsmMknod(fpath, sb.st_mode, sb.st_rdev);
954                 }
955             } else {
956                 /* XXX Special case /dev/log, which shouldn't be packaged anyways */
957                 if (!IS_DEV_LOG(fpath))
958                     rc = RPMERR_UNKNOWN_FILETYPE;
959             }
960 
961 touch:
962 	    /* Set permissions, timestamps etc for non-hardlink entries */
963 	    if (!rc && setmeta) {
964 		rc = fsmSetmeta(fpath, fi, plugins, action, &sb, nofcaps);
965 	    }
966         } else if (firsthardlink >= 0 && rpmfiArchiveHasContent(fi)) {
967 	    /* On FA_TOUCH no hardlinks are created thus this is skipped. */
968 	    /* we skip the hard linked file containing the content */
969 	    /* write the content to the first used instead */
970 	    char *fn = rpmfilesFN(files, firsthardlink);
971 	    rc = rpmfiArchiveReadToFilePsm(fi, firstlinkfile, nodigest, psm);
972 	    wfd_close(&firstlinkfile);
973 	    firsthardlink = -1;
974 	    free(fn);
975 	}
976 
977         if (rc) {
978             if (!skip) {
979                 /* XXX only erase if temp fn w suffix is in use */
980                 if (suffix) {
981 		    (void) fsmRemove(fpath, sb.st_mode);
982                 }
983                 errno = saveerrno;
984             }
985         } else {
986 	    /* Notify on success. */
987 	    rpmpsmNotify(psm, RPMCALLBACK_INST_PROGRESS, rpmfiArchiveTell(fi));
988 
989 	    if (!skip) {
990 		/* Backup file if needed. Directories are handled earlier */
991 		if (suffix)
992 		    rc = fsmBackup(fi, action);
993 
994 		if (!rc)
995 		    rc = fsmCommit(&fpath, fi, action, suffix);
996 	    }
997 	}
998 
999 	if (rc)
1000 	    *failedFile = xstrdup(fpath);
1001 
1002 	/* Run fsm file post hook for all plugins */
1003 	rpmpluginsCallFsmFilePost(plugins, fi, fpath,
1004 				  sb.st_mode, action, rc);
1005 	fpath = _free(fpath);
1006     }
1007 
1008     rpmswAdd(rpmtsOp(ts, RPMTS_OP_UNCOMPRESS), fdOp(payload, FDSTAT_READ));
1009     rpmswAdd(rpmtsOp(ts, RPMTS_OP_DIGEST), fdOp(payload, FDSTAT_DIGEST));
1010 
1011 exit:
1012 
1013     /* No need to bother with close errors on read */
1014     rpmfiArchiveClose(fi);
1015     rpmfiFree(fi);
1016     Fclose(payload);
1017     free(tid);
1018     free(fpath);
1019 
1020     return rc;
1021 }
1022 
1023 
rpmPackageFilesRemove(rpmts ts,rpmte te,rpmfiles files,rpmpsm psm,char ** failedFile)1024 int rpmPackageFilesRemove(rpmts ts, rpmte te, rpmfiles files,
1025               rpmpsm psm, char ** failedFile)
1026 {
1027     rpmfi fi = rpmfilesIter(files, RPMFI_ITER_BACK);
1028     rpmfs fs = rpmteGetFileStates(te);
1029     rpmPlugins plugins = rpmtsPlugins(ts);
1030     struct stat sb;
1031     int rc = 0;
1032     char *fpath = NULL;
1033 
1034     while (!rc && rpmfiNext(fi) >= 0) {
1035 	rpmFileAction action = rpmfsGetAction(fs, rpmfiFX(fi));
1036 	fpath = fsmFsPath(fi, NULL);
1037 	rc = fsmStat(fpath, 1, &sb);
1038 
1039 	fsmDebug(fpath, action, &sb);
1040 
1041 	/* Run fsm file pre hook for all plugins */
1042 	rc = rpmpluginsCallFsmFilePre(plugins, fi, fpath,
1043 				      sb.st_mode, action);
1044 
1045 	if (!XFA_SKIPPING(action))
1046 	    rc = fsmBackup(fi, action);
1047 
1048         /* Remove erased files. */
1049         if (action == FA_ERASE) {
1050 	    int missingok = (rpmfiFFlags(fi) & (RPMFILE_MISSINGOK | RPMFILE_GHOST));
1051 
1052 	    rc = fsmRemove(fpath, sb.st_mode);
1053 
1054 	    /*
1055 	     * Missing %ghost or %missingok entries are not errors.
1056 	     * XXX: Are non-existent files ever an actual error here? Afterall
1057 	     * that's exactly what we're trying to accomplish here,
1058 	     * and complaining about job already done seems like kinderkarten
1059 	     * level "But it was MY turn!" whining...
1060 	     */
1061 	    if (rc == RPMERR_ENOENT && missingok) {
1062 		rc = 0;
1063 	    }
1064 
1065 	    /*
1066 	     * Dont whine on non-empty directories for now. We might be able
1067 	     * to track at least some of the expected failures though,
1068 	     * such as when we knowingly left config file backups etc behind.
1069 	     */
1070 	    if (rc == RPMERR_ENOTEMPTY) {
1071 		rc = 0;
1072 	    }
1073 
1074 	    if (rc) {
1075 		int lvl = strict_erasures ? RPMLOG_ERR : RPMLOG_WARNING;
1076 		rpmlog(lvl, _("%s %s: remove failed: %s\n"),
1077 			S_ISDIR(sb.st_mode) ? _("directory") : _("file"),
1078 			fpath, strerror(errno));
1079             }
1080         }
1081 
1082 	/* Run fsm file post hook for all plugins */
1083 	rpmpluginsCallFsmFilePost(plugins, fi, fpath,
1084 				  sb.st_mode, action, rc);
1085 
1086         /* XXX Failure to remove is not (yet) cause for failure. */
1087         if (!strict_erasures) rc = 0;
1088 
1089 	if (rc)
1090 	    *failedFile = xstrdup(fpath);
1091 
1092 	if (rc == 0) {
1093 	    /* Notify on success. */
1094 	    /* On erase we're iterating backwards, fixup for progress */
1095 	    rpm_loff_t amount = rpmfiFC(fi) - rpmfiFX(fi);
1096 	    rpmpsmNotify(psm, RPMCALLBACK_UNINST_PROGRESS, amount);
1097 	}
1098 	fpath = _free(fpath);
1099     }
1100 
1101     free(fpath);
1102     rpmfiFree(fi);
1103 
1104     return rc;
1105 }
1106 
1107 
1108