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