1 #include "system.h"
2 
3 #include <rpm/rpmtypes.h>
4 #include <rpm/header.h>
5 #include <rpm/rpmfi.h>
6 #include <rpm/rpmfileutil.h>
7 #include <rpm/rpmmacro.h>
8 #include <rpm/rpmlog.h>
9 
10 #include "lib/rpmfs.h"
11 #include "lib/misc.h"
12 
13 #include "debug.h"
14 
15 /**
16  * Identify a file type.
17  * @param ft		file type
18  * @return		string to identify a file type
19  */
20 static
ftstring(rpmFileTypes ft)21 const char * ftstring (rpmFileTypes ft)
22 {
23     switch (ft) {
24     case XDIR:	return "directory";
25     case CDEV:	return "char dev";
26     case BDEV:	return "block dev";
27     case LINK:	return "link";
28     case SOCK:	return "sock";
29     case PIPE:	return "fifo/pipe";
30     case REG:	return "file";
31     default:	return "unknown file type";
32     }
33 }
34 
duparray(char ** src,int size)35 static char **duparray(char ** src, int size)
36 {
37     char **dest = xmalloc((size+1) * sizeof(*dest));
38     for (int i = 0; i < size; i++) {
39 	dest[i] = xstrdup(src[i]);
40     }
41     free(src);
42     return dest;
43 }
44 
addPrefixes(Header h,rpmRelocation * relocations,int numRelocations)45 static int addPrefixes(Header h, rpmRelocation *relocations, int numRelocations)
46 {
47     struct rpmtd_s validRelocs;
48     const char *validprefix;
49     const char ** actualRelocations;
50     int numActual = 0;
51 
52     headerGet(h, RPMTAG_PREFIXES, &validRelocs, HEADERGET_MINMEM);
53     /*
54      * If no relocations are specified (usually the case), then return the
55      * original header. If there are prefixes, however, then INSTPREFIXES
56      * should be added for RPM_INSTALL_PREFIX environ variables in scriptlets,
57      * but, since relocateFileList() can be called more than once for
58      * the same header, don't bother if already present.
59      */
60     if (relocations == NULL || numRelocations == 0) {
61 	if (rpmtdCount(&validRelocs) > 0) {
62 	    if (!headerIsEntry(h, RPMTAG_INSTPREFIXES)) {
63 		rpmtdSetTag(&validRelocs, RPMTAG_INSTPREFIXES);
64 		headerPut(h, &validRelocs, HEADERPUT_DEFAULT);
65 	    }
66 	    rpmtdFreeData(&validRelocs);
67 	}
68 	return 0;
69     }
70 
71     actualRelocations = xmalloc(rpmtdCount(&validRelocs) * sizeof(*actualRelocations));
72     rpmtdInit(&validRelocs);
73     while ((validprefix = rpmtdNextString(&validRelocs))) {
74 	int j;
75 	for (j = 0; j < numRelocations; j++) {
76 	    if (relocations[j].oldPath == NULL || /* XXX can't happen */
77 		!rstreq(validprefix, relocations[j].oldPath))
78 		continue;
79 	    /* On install, a relocate to NULL means skip the path. */
80 	    if (relocations[j].newPath) {
81 		actualRelocations[numActual] = relocations[j].newPath;
82 		numActual++;
83 	    }
84 	    break;
85 	}
86 	if (j == numRelocations) {
87 	    actualRelocations[numActual] = validprefix;
88 	    numActual++;
89 	}
90     }
91     rpmtdFreeData(&validRelocs);
92 
93     if (numActual) {
94 	headerPutStringArray(h, RPMTAG_INSTPREFIXES, actualRelocations, numActual);
95     }
96     free(actualRelocations);
97     /* When any relocations are present there'll be more work to do */
98     return 1;
99 }
100 
saveOrig(Header h)101 static void saveOrig(Header h)
102 {
103 	struct rpmtd_s td;
104 	headerGet(h, RPMTAG_BASENAMES, &td, HEADERGET_MINMEM);
105 	rpmtdSetTag(&td, RPMTAG_ORIGBASENAMES);
106 	headerPut(h, &td, HEADERPUT_DEFAULT);
107 	rpmtdFreeData(&td);
108 
109 	headerGet(h, RPMTAG_DIRNAMES, &td, HEADERGET_MINMEM);
110 	rpmtdSetTag(&td, RPMTAG_ORIGDIRNAMES);
111 	headerPut(h, &td, HEADERPUT_DEFAULT);
112 	rpmtdFreeData(&td);
113 
114 	headerGet(h, RPMTAG_DIRINDEXES, &td, HEADERGET_MINMEM);
115 	rpmtdSetTag(&td, RPMTAG_ORIGDIRINDEXES);
116 	headerPut(h, &td, HEADERPUT_DEFAULT);
117 	rpmtdFreeData(&td);
118 }
119 
rpmRelocateFileList(rpmRelocation * relocations,int numRelocations,rpmfs fs,Header h)120 void rpmRelocateFileList(rpmRelocation *relocations, int numRelocations,
121 			 rpmfs fs, Header h)
122 {
123     char ** baseNames;
124     char ** dirNames;
125     uint32_t * dirIndexes;
126     rpm_count_t fileCount, dirCount;
127     int nrelocated = 0;
128     int fileAlloced = 0;
129     char * fn = NULL;
130     int haveRelocatedBase = 0;
131     size_t maxlen = 0;
132     int i, j;
133     struct rpmtd_s bnames, dnames, dindexes, fmodes;
134 
135     if (!addPrefixes(h, relocations, numRelocations))
136 	return;
137 
138     if (rpmIsDebug()) {
139 	rpmlog(RPMLOG_DEBUG, "========== relocations\n");
140 	for (i = 0; i < numRelocations; i++) {
141 	    if (relocations[i].oldPath == NULL) continue; /* XXX can't happen */
142 	    if (relocations[i].newPath == NULL)
143 		rpmlog(RPMLOG_DEBUG, "%5d exclude  %s\n",
144 			i, relocations[i].oldPath);
145 	    else
146 		rpmlog(RPMLOG_DEBUG, "%5d relocate %s -> %s\n",
147 			i, relocations[i].oldPath, relocations[i].newPath);
148 	}
149     }
150 
151     for (i = 0; i < numRelocations; i++) {
152 	if (relocations[i].newPath == NULL) continue;
153 	size_t len = strlen(relocations[i].newPath);
154 	if (len > maxlen) maxlen = len;
155     }
156 
157     headerGet(h, RPMTAG_BASENAMES, &bnames, HEADERGET_MINMEM);
158     headerGet(h, RPMTAG_DIRINDEXES, &dindexes, HEADERGET_ALLOC);
159     headerGet(h, RPMTAG_DIRNAMES, &dnames, HEADERGET_MINMEM);
160     headerGet(h, RPMTAG_FILEMODES, &fmodes, HEADERGET_MINMEM);
161     /* TODO XXX ugh.. use rpmtd iterators & friends instead */
162     baseNames = bnames.data;
163     dirIndexes = dindexes.data;
164     fileCount = rpmtdCount(&bnames);
165     dirCount = rpmtdCount(&dnames);
166     /* XXX TODO: use rpmtdDup() instead */
167     dirNames = dnames.data = duparray(dnames.data, dirCount);
168     dnames.flags |= RPMTD_PTR_ALLOCED;
169 
170     /*
171      * For all relocations, we go through sorted file/relocation lists
172      * backwards so that /usr/local relocations take precedence over /usr
173      * ones.
174      */
175 
176     /* Relocate individual paths. */
177 
178     for (i = fileCount - 1; i >= 0; i--) {
179 	rpmFileTypes ft;
180 	int fnlen;
181 
182 	size_t len = maxlen +
183 		strlen(dirNames[dirIndexes[i]]) + strlen(baseNames[i]) + 1;
184 	if (len >= fileAlloced) {
185 	    fileAlloced = len * 2;
186 	    fn = xrealloc(fn, fileAlloced);
187 	}
188 
189 assert(fn != NULL);		/* XXX can't happen */
190 	*fn = '\0';
191 	fnlen = stpcpy( stpcpy(fn, dirNames[dirIndexes[i]]), baseNames[i]) - fn;
192 
193 	/*
194 	 * See if this file path needs relocating.
195 	 */
196 	/*
197 	 * XXX FIXME: Would a bsearch of the (already sorted)
198 	 * relocation list be a good idea?
199 	 */
200 	for (j = numRelocations - 1; j >= 0; j--) {
201 	    if (relocations[j].oldPath == NULL) /* XXX can't happen */
202 		continue;
203 	    len = !rstreq(relocations[j].oldPath, "/")
204 		? strlen(relocations[j].oldPath)
205 		: 0;
206 
207 	    if (fnlen < len)
208 		continue;
209 	    /*
210 	     * Only subdirectories or complete file paths may be relocated. We
211 	     * don't check for '\0' as our directory names all end in '/'.
212 	     */
213 	    if (!(fn[len] == '/' || fnlen == len))
214 		continue;
215 
216 	    if (!rstreqn(relocations[j].oldPath, fn, len))
217 		continue;
218 	    break;
219 	}
220 	if (j < 0) continue;
221 
222 	rpmtdSetIndex(&fmodes, i);
223 	ft = rpmfiWhatis(rpmtdGetNumber(&fmodes));
224 
225 	/* On install, a relocate to NULL means skip the path. */
226 	if (relocations[j].newPath == NULL) {
227 	    if (ft == XDIR) {
228 		/* Start with the parent, looking for directory to exclude. */
229 		for (j = dirIndexes[i]; j < dirCount; j++) {
230 		    len = strlen(dirNames[j]) - 1;
231 		    while (len > 0 && dirNames[j][len-1] == '/') len--;
232 		    if (fnlen != len)
233 			continue;
234 		    if (!rstreqn(fn, dirNames[j], fnlen))
235 			continue;
236 		    break;
237 		}
238 	    }
239 	    rpmfsSetAction(fs, i, FA_SKIPNSTATE);
240 	    rpmlog(RPMLOG_DEBUG, "excluding %s %s\n",
241 		   ftstring(ft), fn);
242 	    continue;
243 	}
244 
245 	/* Relocation on full paths only, please. */
246 	if (fnlen != len) continue;
247 
248 	rpmlog(RPMLOG_DEBUG, "relocating %s to %s\n",
249 	       fn, relocations[j].newPath);
250 	nrelocated++;
251 
252 	strcpy(fn, relocations[j].newPath);
253 	{   char * te = strrchr(fn, '/');
254 	    if (te) {
255 		if (te > fn) te++;	/* root is special */
256 		fnlen = te - fn;
257 	    } else
258 		te = fn + strlen(fn);
259 	    if (!rstreq(baseNames[i], te)) { /* basename changed too? */
260 		if (!haveRelocatedBase) {
261 		    /* XXX TODO: use rpmtdDup() instead */
262 		    bnames.data = baseNames = duparray(baseNames, fileCount);
263 		    bnames.flags |= RPMTD_PTR_ALLOCED;
264 		    haveRelocatedBase = 1;
265 		}
266 		free(baseNames[i]);
267 		baseNames[i] = xstrdup(te);
268 	    }
269 	    *te = '\0';			/* terminate new directory name */
270 	}
271 
272 	/* Does this directory already exist in the directory list? */
273 	for (j = 0; j < dirCount; j++) {
274 	    if (fnlen != strlen(dirNames[j]))
275 		continue;
276 	    if (!rstreqn(fn, dirNames[j], fnlen))
277 		continue;
278 	    break;
279 	}
280 
281 	if (j < dirCount) {
282 	    dirIndexes[i] = j;
283 	    continue;
284 	}
285 
286 	/* Creating new paths is a pita */
287 	dirNames = dnames.data = xrealloc(dnames.data,
288 			       sizeof(*dirNames) * (dirCount + 1));
289 
290 	dirNames[dirCount] = xstrdup(fn);
291 	dirIndexes[i] = dirCount;
292 	dirCount++;
293 	dnames.count++;
294     }
295 
296     /* Finish off by relocating directories. */
297     for (i = dirCount - 1; i >= 0; i--) {
298 	for (j = numRelocations - 1; j >= 0; j--) {
299 
300 	    if (relocations[j].oldPath == NULL) /* XXX can't happen */
301 		continue;
302 	    size_t len = !rstreq(relocations[j].oldPath, "/")
303 		? strlen(relocations[j].oldPath)
304 		: 0;
305 
306 	    if (len && !rstreqn(relocations[j].oldPath, dirNames[i], len))
307 		continue;
308 
309 	    /*
310 	     * Only subdirectories or complete file paths may be relocated. We
311 	     * don't check for '\0' as our directory names all end in '/'.
312 	     */
313 	    if (dirNames[i][len] != '/')
314 		continue;
315 
316 	    if (relocations[j].newPath) { /* Relocate the path */
317 		char *t = NULL;
318 		rstrscat(&t, relocations[j].newPath, (dirNames[i] + len), NULL);
319 		/* Unfortunately rpmCleanPath strips the trailing slash.. */
320 		(void) rpmCleanPath(t);
321 		rstrcat(&t, "/");
322 
323 		rpmlog(RPMLOG_DEBUG,
324 		       "relocating directory %s to %s\n", dirNames[i], t);
325 		free(dirNames[i]);
326 		dirNames[i] = t;
327 		nrelocated++;
328 	    }
329 	}
330     }
331 
332     /* Save original filenames in header and replace (relocated) filenames. */
333     if (nrelocated) {
334 	saveOrig(h);
335 	headerMod(h, &bnames);
336 	headerMod(h, &dnames);
337 	headerMod(h, &dindexes);
338     }
339 
340     rpmtdFreeData(&bnames);
341     rpmtdFreeData(&dnames);
342     rpmtdFreeData(&dindexes);
343     rpmtdFreeData(&fmodes);
344     free(fn);
345 }
346 
347 /**
348  * Macros to be defined from per-header tag values.
349  * @todo Should other macros be added from header when installing a package?
350  */
351 static struct tagMacro {
352     const char *macroname; 	/*!< Macro name to define. */
353     rpmTag tag;			/*!< Header tag to use for value. */
354 } const tagMacros[] = {
355     { "name",		RPMTAG_NAME },
356     { "version",	RPMTAG_VERSION },
357     { "release",	RPMTAG_RELEASE },
358     { "epoch",		RPMTAG_EPOCH },
359     { NULL, 0 }
360 };
361 
362 /**
363  * Define or undefine per-header macros.
364  * @param h		header
365  * @param define	define/undefine?
366  * @return		0 always
367  */
rpmInstallLoadMacros(Header h,int define)368 static void rpmInstallLoadMacros(Header h, int define)
369 {
370     const struct tagMacro * tagm;
371 
372     for (tagm = tagMacros; tagm->macroname != NULL; tagm++) {
373 	struct rpmtd_s td;
374 	char *body;
375 	if (!headerGet(h, tagm->tag, &td, HEADERGET_DEFAULT))
376 	    continue;
377 
378 	/*
379 	 * Undefine doesn't need the actual data for anything, but
380 	 * this way ensures we only undefine what was defined earlier.
381 	 */
382 	if (define) {
383 	    body = rpmtdFormat(&td, RPMTD_FORMAT_STRING, NULL);
384 	    rpmPushMacro(NULL, tagm->macroname, NULL, body, -1);
385 	    free(body);
386 	} else {
387 	    rpmPopMacro(NULL, tagm->macroname);
388 	}
389 	rpmtdFreeData(&td);
390     }
391 }
392 
headerFindSpec(Header h)393 int headerFindSpec(Header h)
394 {
395     struct rpmtd_s filenames;
396     int specix = -1;
397 
398     if (headerGet(h, RPMTAG_BASENAMES, &filenames, HEADERGET_MINMEM)) {
399 	struct rpmtd_s td;
400 	const char *str;
401 
402 	/* Try to find spec by file flags */
403 	if (headerGet(h, RPMTAG_FILEFLAGS, &td, HEADERGET_MINMEM)) {
404 	    rpmfileAttrs *flags;
405 	    while (specix < 0 && (flags = rpmtdNextUint32(&td))) {
406 		if (*flags & RPMFILE_SPECFILE)
407 		    specix = rpmtdGetIndex(&td);
408 	    }
409 	    rpmtdFreeData(&td);
410 	}
411 	/* Still no spec? Look by filename. */
412 	while (specix < 0 && (str = rpmtdNextString(&filenames))) {
413 	    if (rpmFileHasSuffix(str, ".spec"))
414 		specix = rpmtdGetIndex(&filenames);
415 	}
416 	rpmtdFreeData(&filenames);
417     }
418     return specix;
419 }
420 
421 /*
422  * Source rpms only contain basenames, on install the full paths are
423  * constructed with %{_specdir} and %{_sourcedir} macros. Because
424  * of that regular relocation wont work, we need to do it the hard
425  * way. Return spec file index on success, -1 on errors.
426  */
rpmRelocateSrpmFileList(Header h,const char * rootDir)427 int rpmRelocateSrpmFileList(Header h, const char *rootDir)
428 {
429     int specix = headerFindSpec(h);
430 
431     if (specix >= 0) {
432 	const char *bn;
433 	struct rpmtd_s filenames;
434 	/* save original file names */
435 	saveOrig(h);
436 
437 	headerDel(h, RPMTAG_BASENAMES);
438 	headerDel(h, RPMTAG_DIRNAMES);
439 	headerDel(h, RPMTAG_DIRINDEXES);
440 
441 	/* Macros need to be added before trying to create directories */
442 	rpmInstallLoadMacros(h, 1);
443 
444 	/* ALLOC is needed as we modify the header */
445 	headerGet(h, RPMTAG_ORIGBASENAMES, &filenames, HEADERGET_ALLOC);
446 	for (int i = 0; (bn = rpmtdNextString(&filenames)); i++) {
447 	    int spec = (i == specix);
448 	    char *fn = rpmGenPath(rootDir,
449 				  spec ? "%{_specdir}" : "%{_sourcedir}", bn);
450 	    headerPutString(h, RPMTAG_OLDFILENAMES, fn);
451 	    free(fn);
452 	}
453 	rpmtdFreeData(&filenames);
454 	headerConvert(h, HEADERCONV_COMPRESSFILELIST);
455 	rpmInstallLoadMacros(h, 0);
456     }
457 
458     return specix;
459 }
460 
461 /* stupid bubble sort, but it's probably faster here */
sortRelocs(rpmRelocation * relocations,int numRelocations)462 static void sortRelocs(rpmRelocation *relocations, int numRelocations)
463 {
464     for (int i = 0; i < numRelocations; i++) {
465 	int madeSwap = 0;
466 	for (int j = 1; j < numRelocations; j++) {
467 	    rpmRelocation tmpReloc;
468 	    if (relocations[j - 1].oldPath == NULL || /* XXX can't happen */
469 		relocations[j    ].oldPath == NULL || /* XXX can't happen */
470 		strcmp(relocations[j - 1].oldPath, relocations[j].oldPath) <= 0)
471 		continue;
472 	    tmpReloc = relocations[j - 1];
473 	    relocations[j - 1] = relocations[j];
474 	    relocations[j] = tmpReloc;
475 	    madeSwap = 1;
476 	}
477 	if (!madeSwap) break;
478     }
479 }
480 
stripTrailingChar(char * s,char c)481 static char * stripTrailingChar(char * s, char c)
482 {
483     char * t;
484     for (t = s + strlen(s) - 1; *t == c && t >= s; t--)
485 	*t = '\0';
486     return s;
487 }
488 
rpmRelocationBuild(Header h,rpmRelocation * rawrelocs,int * rnrelocs,rpmRelocation ** rrelocs,uint8_t ** rbadrelocs)489 void rpmRelocationBuild(Header h, rpmRelocation *rawrelocs,
490 		int *rnrelocs, rpmRelocation **rrelocs, uint8_t **rbadrelocs)
491 {
492     int i;
493     struct rpmtd_s validRelocs;
494     rpmRelocation * relocs = NULL;
495     uint8_t *badrelocs = NULL;
496     int nrelocs = 0;
497 
498     for (rpmRelocation *r = rawrelocs; r->oldPath || r->newPath; r++)
499 	nrelocs++;
500 
501     headerGet(h, RPMTAG_PREFIXES, &validRelocs, HEADERGET_MINMEM);
502     relocs = xmalloc(sizeof(*relocs) * (nrelocs+1));
503 
504     /* Build sorted relocation list from raw relocations. */
505     for (i = 0; i < nrelocs; i++) {
506 	char * t;
507 
508 	/*
509 	 * Default relocations (oldPath == NULL) are handled in the UI,
510 	 * not rpmlib.
511 	 */
512 	if (rawrelocs[i].oldPath == NULL) continue; /* XXX can't happen */
513 
514 	/* FIXME: Trailing /'s will confuse us greatly. Internal ones will
515 	   too, but those are more trouble to fix up. :-( */
516 	t = xstrdup(rawrelocs[i].oldPath);
517 	relocs[i].oldPath = (t[0] == '/' && t[1] == '\0')
518 	    ? t
519 	    : stripTrailingChar(t, '/');
520 
521 	/* An old path w/o a new path is valid, and indicates exclusion */
522 	if (rawrelocs[i].newPath) {
523 	    int valid = 0;
524 	    const char *validprefix;
525 
526 	    t = xstrdup(rawrelocs[i].newPath);
527 	    relocs[i].newPath = (t[0] == '/' && t[1] == '\0')
528 		? t
529 		: stripTrailingChar(t, '/');
530 
531 	   	/* FIX:  relocations[i].oldPath == NULL */
532 	    /* Verify that the relocation's old path is in the header. */
533 	    rpmtdInit(&validRelocs);
534 	    while ((validprefix = rpmtdNextString(&validRelocs))) {
535 		if (rstreq(validprefix, relocs[i].oldPath)) {
536 		    valid = 1;
537 		    break;
538 		}
539 	    }
540 
541 	    if (!valid) {
542 		if (badrelocs == NULL)
543 		    badrelocs = xcalloc(nrelocs, sizeof(*badrelocs));
544 		badrelocs[i] = 1;
545 	    }
546 	} else {
547 	    relocs[i].newPath = NULL;
548 	}
549     }
550     relocs[i].oldPath = NULL;
551     relocs[i].newPath = NULL;
552     sortRelocs(relocs, nrelocs);
553 
554     rpmtdFreeData(&validRelocs);
555 
556     *rrelocs = relocs;
557     *rnrelocs = nrelocs;
558     *rbadrelocs = badrelocs;
559 }
560 
561