2 * \file lib/transaction.c
7 #include <rpmmacro.h> /* XXX for rpmExpand */
9 #define _NEED_TEITERATOR 1
14 #include "legacy.h" /* XXX domd5 */
15 #include "misc.h" /* XXX stripTrailingChar, splitString, currentDirectory */
17 /* XXX FIXME: merge with existing (broken?) tests in system.h */
18 /* portability fiddles */
19 #if STATFS_IN_SYS_STATVFS
21 # include <sys/statvfs.h>
22 #if defined(__LCLINT__)
23 /*@-declundef -exportheader -protoparammatch @*/ /* LCL: missing annotation */
24 extern int statvfs (const char * file, /*@out@*/ struct statvfs * buf)
25 /*@globals fileSystem @*/
26 /*@modifies *buf, fileSystem @*/;
27 /*@=declundef =exportheader =protoparammatch @*/
31 # if STATFS_IN_SYS_VFS
34 # if STATFS_IN_SYS_MOUNT
35 # include <sys/mount.h>
37 # if STATFS_IN_SYS_STATFS
38 # include <sys/statfs.h>
46 /*@access FD_t @*/ /* XXX compared with NULL */
47 /*@access Header @*/ /* XXX compared with NULL */
48 /*@access rpmProblemSet @*/ /* XXX need rpmProblemSetOK() */
49 /*@access dbiIndexSet @*/
59 /*@access teIterator @*/
60 /*@access rpmTransactionSet @*/
64 struct diskspaceInfo {
65 dev_t dev; /*!< File system device number. */
66 signed long bneeded; /*!< No. of blocks needed. */
67 signed long ineeded; /*!< No. of inodes needed. */
68 int bsize; /*!< File system block size. */
69 signed long bavail; /*!< No. of blocks available. */
70 signed long iavail; /*!< No. of inodes available. */
74 * Adjust for root only reserved space. On linux e2fs, this is 5%.
76 #define adj_fs_blocks(_nb) (((_nb) * 21) / 20)
78 /* argon thought a shift optimization here was a waste of time... he's
80 #define BLOCK_ROUND(size, block) (((size) + (block) - 1) / (block))
82 void rpmtransSetScriptFd(rpmTransactionSet ts, FD_t fd)
84 ts->scriptFd = (fd ? fdLink(fd, "rpmtransSetScriptFd") : NULL);
87 int rpmtransGetKeys(const rpmTransactionSet ts, fnpyKey ** ep, int * nep)
91 if (nep) *nep = ts->orderCount;
93 teIterator pi; transactionElement p;
96 *ep = e = xmalloc(ts->orderCount * sizeof(*e));
97 pi = teInitIterator(ts);
98 while ((p = teNextIterator(pi)) != NULL) {
99 switch (teGetType(p)) {
101 /*@-dependenttrans@*/
103 /*@=dependenttrans@*/
104 /*@switchbreak@*/ break;
108 /*@switchbreak@*/ break;
112 pi = teFreeIterator(pi);
119 static int archOkay(/*@null@*/ const char * pkgArch)
122 if (pkgArch == NULL) return 0;
123 return (rpmMachineScore(RPM_MACHTABLE_INSTARCH, pkgArch) ? 1 : 0);
128 static int osOkay(/*@null@*/ const char * pkgOs)
131 if (pkgOs == NULL) return 0;
132 return (rpmMachineScore(RPM_MACHTABLE_INSTOS, pkgOs) ? 1 : 0);
137 static int sharedCmp(const void * one, const void * two)
140 sharedFileInfo a = (sharedFileInfo) one;
141 sharedFileInfo b = (sharedFileInfo) two;
143 if (a->otherPkg < b->otherPkg)
145 else if (a->otherPkg > b->otherPkg)
153 static fileAction decideFileFate(const rpmTransactionSet ts,
154 const TFI_t ofi, TFI_t nfi)
155 /*@globals fileSystem @*/
156 /*@modifies nfi, fileSystem @*/
158 const char * fn = tfiGetFN(nfi);
159 int newFlags = tfiGetFFlags(nfi);
161 fileTypes dbWhat, newWhat, diskWhat;
163 int save = (newFlags & RPMFILE_NOREPLACE) ? FA_ALTNAME : FA_SAVE;
165 if (lstat(fn, &sb)) {
167 * The file doesn't exist on the disk. Create it unless the new
168 * package has marked it as missingok, or allfiles is requested.
170 if (!(ts->transFlags & RPMTRANS_FLAG_ALLFILES)
171 && (newFlags & RPMFILE_MISSINGOK))
173 rpmMessage(RPMMESS_DEBUG, _("%s skipped due to missingok flag\n"),
181 diskWhat = whatis(sb.st_mode);
182 dbWhat = whatis(ofi->fmodes[ofi->i]);
183 newWhat = whatis(nfi->fmodes[nfi->i]);
186 * RPM >= 2.3.10 shouldn't create config directories -- we'll ignore
187 * them in older packages as well.
192 if (diskWhat != newWhat)
194 else if (newWhat != dbWhat && diskWhat != dbWhat)
196 else if (dbWhat != newWhat)
198 else if (dbWhat != LINK && dbWhat != REG)
202 * This order matters - we'd prefer to CREATE the file if at all
203 * possible in case something else (like the timestamp) has changed.
206 if (ofi->md5s != NULL && nfi->md5s != NULL) {
207 const unsigned char * omd5 = ofi->md5s + (16 * ofi->i);
208 const unsigned char * nmd5 = nfi->md5s + (16 * nfi->i);
209 if (domd5(fn, buffer, 0))
210 return FA_CREATE; /* assume file has been removed */
211 if (!memcmp(omd5, buffer, 16))
212 return FA_CREATE; /* unmodified config file, replace. */
213 if (!memcmp(omd5, nmd5, 16))
214 return FA_SKIP; /* identical file, don't bother. */
216 const char * omd5 = ofi->fmd5s[ofi->i];
217 const char * nmd5 = nfi->fmd5s[nfi->i];
218 if (domd5(fn, buffer, 1))
219 return FA_CREATE; /* assume file has been removed */
220 if (!strcmp(omd5, buffer))
221 return FA_CREATE; /* unmodified config file, replace. */
222 if (!strcmp(omd5, nmd5))
223 return FA_SKIP; /* identical file, don't bother. */
225 } else /* dbWhat == LINK */ {
226 memset(buffer, 0, sizeof(buffer));
227 if (readlink(fn, buffer, sizeof(buffer) - 1) == -1)
228 return FA_CREATE; /* assume file has been removed */
229 if (!strcmp(ofi->flinks[ofi->i], buffer))
230 return FA_CREATE; /* unmodified config file, replace. */
231 if (!strcmp(ofi->flinks[ofi->i], nfi->flinks[nfi->i]))
232 return FA_SKIP; /* identical file, don't bother. */
236 * The config file on the disk has been modified, but
237 * the ones in the two packages are different. It would
238 * be nice if RPM was smart enough to at least try and
239 * merge the difference ala CVS, but...
246 static int filecmp(TFI_t afi, TFI_t bfi)
249 fileTypes awhat = whatis(afi->fmodes[afi->i]);
250 fileTypes bwhat = whatis(bfi->fmodes[bfi->i]);
252 if (awhat != bwhat) return 1;
255 const char * alink = afi->flinks[afi->i];
256 const char * blink = bfi->flinks[bfi->i];
257 return strcmp(alink, blink);
258 } else if (awhat == REG) {
259 if (afi->md5s != NULL && bfi->md5s != NULL) {
260 const unsigned char * amd5 = afi->md5s + (16 * afi->i);
261 const unsigned char * bmd5 = bfi->md5s + (16 * bfi->i);
262 return memcmp(amd5, bmd5, 16);
264 const char * amd5 = afi->fmd5s[afi->i];
265 const char * bmd5 = bfi->fmd5s[bfi->i];
266 return strcmp(amd5, bmd5);
275 /* XXX only ts->{probs,rpmdb} modified */
276 static int handleInstInstalledFiles(const rpmTransactionSet ts,
277 transactionElement p, TFI_t fi,
278 sharedFileInfo shared,
279 int sharedCount, int reportConflicts)
280 /*@globals fileSystem @*/
281 /*@modifies ts, fi, fileSystem @*/
283 const char * altNEVR = NULL;
284 TFI_t otherFi = NULL;
288 { rpmdbMatchIterator mi;
292 mi = rpmtsInitIterator(ts, RPMDBI_PACKAGES,
293 &shared->otherPkg, sizeof(shared->otherPkg));
294 while ((h = rpmdbNextIterator(mi)) != NULL) {
295 altNEVR = hGetNEVR(h, NULL);
296 otherFi = fiNew(ts, NULL, h, RPMTAG_BASENAMES, scareMem);
299 mi = rpmdbFreeIterator(mi);
305 fi->replaced = xcalloc(sharedCount, sizeof(*fi->replaced));
307 for (i = 0; i < sharedCount; i++, shared++) {
308 int otherFileNum, fileNum;
310 otherFileNum = shared->otherFileNum;
311 (void) tfiSetFX(otherFi, otherFileNum);
313 fileNum = shared->pkgFileNum;
314 (void) tfiSetFX(fi, fileNum);
317 /* XXX another tedious segfault, assume file state normal. */
318 if (otherStates && otherStates[otherFileNum] != RPMFILE_STATE_NORMAL)
322 if (XFA_SKIPPING(fi->actions[fileNum]))
325 if (filecmp(otherFi, fi)) {
326 if (reportConflicts) {
327 rpmProblemSetAppend(ts->probs, RPMPROB_FILE_CONFLICT,
328 teGetNEVR(p), teGetKey(p),
329 tfiGetDN(fi), tfiGetBN(fi),
333 if (!(tfiGetFFlags(otherFi) | tfiGetFFlags(fi)) & RPMFILE_CONFIG) {
334 /*@-assignexpose@*/ /* FIX: p->replaced, not fi */
335 if (!shared->isRemoved)
336 fi->replaced[numReplaced++] = *shared;
341 if ((tfiGetFFlags(otherFi) | tfiGetFFlags(fi)) & RPMFILE_CONFIG) {
343 action = decideFileFate(ts, otherFi, fi);
344 fi->actions[fileNum] = action;
346 fi->replacedSizes[fileNum] = otherFi->fsizes[otherFi->i];
350 altNEVR = _free(altNEVR);
351 otherFi = fiFree(otherFi, 1);
353 fi->replaced = xrealloc(fi->replaced, /* XXX memory leak */
354 sizeof(*fi->replaced) * (numReplaced + 1));
355 fi->replaced[numReplaced].otherPkg = 0;
362 /* XXX only ts->rpmdb modified */
363 static int handleRmvdInstalledFiles(const rpmTransactionSet ts, TFI_t fi,
364 sharedFileInfo shared, int sharedCount)
365 /*@globals fileSystem @*/
366 /*@modifies ts, fi, fileSystem @*/
370 const char * otherStates;
373 rpmdbMatchIterator mi;
375 mi = rpmtsInitIterator(ts, RPMDBI_PACKAGES,
376 &shared->otherPkg, sizeof(shared->otherPkg));
377 h = rpmdbNextIterator(mi);
379 mi = rpmdbFreeIterator(mi);
383 xx = hge(h, RPMTAG_FILESTATES, NULL, (void **) &otherStates, NULL);
385 for (i = 0; i < sharedCount; i++, shared++) {
386 int otherFileNum, fileNum;
387 otherFileNum = shared->otherFileNum;
388 fileNum = shared->pkgFileNum;
390 if (otherStates[otherFileNum] != RPMFILE_STATE_NORMAL)
393 fi->actions[fileNum] = FA_SKIP;
396 mi = rpmdbFreeIterator(mi);
401 #define ISROOT(_d) (((_d)[0] == '/' && (_d)[1] == '\0') ? "" : (_d))
404 static int _fps_debug = 0;
406 static int fpsCompare (const void * one, const void * two)
408 const struct fingerPrint_s * a = (const struct fingerPrint_s *)one;
409 const struct fingerPrint_s * b = (const struct fingerPrint_s *)two;
410 int adnlen = strlen(a->entry->dirName);
411 int asnlen = (a->subDir ? strlen(a->subDir) : 0);
412 int abnlen = strlen(a->baseName);
413 int bdnlen = strlen(b->entry->dirName);
414 int bsnlen = (b->subDir ? strlen(b->subDir) : 0);
415 int bbnlen = strlen(b->baseName);
416 char * afn, * bfn, * t;
419 if (adnlen == 1 && asnlen != 0) adnlen = 0;
420 if (bdnlen == 1 && bsnlen != 0) bdnlen = 0;
422 afn = t = alloca(adnlen+asnlen+abnlen+2);
423 if (adnlen) t = stpcpy(t, a->entry->dirName);
425 if (a->subDir && asnlen) t = stpcpy(t, a->subDir);
426 if (abnlen) t = stpcpy(t, a->baseName);
427 if (afn[0] == '/' && afn[1] == '/') afn++;
429 bfn = t = alloca(bdnlen+bsnlen+bbnlen+2);
430 if (bdnlen) t = stpcpy(t, b->entry->dirName);
432 if (b->subDir && bsnlen) t = stpcpy(t, b->subDir);
433 if (bbnlen) t = stpcpy(t, b->baseName);
434 if (bfn[0] == '/' && bfn[1] == '/') bfn++;
436 rc = strcmp(afn, bfn);
439 fprintf(stderr, "\trc(%d) = strcmp(\"%s\", \"%s\")\n", rc, afn, bfn);
444 fprintf(stderr, "\t%s/%s%s\trc %d\n",
445 ISROOT(b->entry->dirName),
446 (b->subDir ? b->subDir : ""),
456 static int _linear_fps_search = 0;
458 static int findFps(const struct fingerPrint_s * fiFps,
459 const struct fingerPrint_s * otherFps,
467 fprintf(stderr, "==> %s/%s%s\n",
468 ISROOT(fiFps->entry->dirName),
469 (fiFps->subDir ? fiFps->subDir : ""),
473 if (_linear_fps_search) {
476 for (otherFileNum = 0; otherFileNum < otherFc; otherFileNum++, otherFps++) {
480 fprintf(stderr, "\t%4d %s/%s%s\n", otherFileNum,
481 ISROOT(otherFps->entry->dirName),
482 (otherFps->subDir ? otherFps->subDir : ""),
486 /* If the addresses are the same, so are the values. */
487 if (fiFps == otherFps)
490 /* Otherwise, compare fingerprints by value. */
491 /*@-nullpass@*/ /* LCL: looks good to me */
492 if (FP_EQUAL((*fiFps), (*otherFps)))
497 if (otherFileNum == otherFc) {
500 fprintf(stderr, "*** NULL %s/%s%s\n",
501 ISROOT(fiFps->entry->dirName),
502 (fiFps->subDir ? fiFps->subDir : ""),
511 const struct fingerPrint_s * bingoFps;
513 bingoFps = bsearch(fiFps, otherFps, otherFc, sizeof(*otherFps), fpsCompare);
514 if (bingoFps == NULL) {
516 fprintf(stderr, "*** NULL %s/%s%s\n",
517 ISROOT(fiFps->entry->dirName),
518 (fiFps->subDir ? fiFps->subDir : ""),
524 /* If the addresses are the same, so are the values. */
525 /*@-nullpass@*/ /* LCL: looks good to me */
526 if (!(fiFps == bingoFps || FP_EQUAL((*fiFps), (*bingoFps)))) {
528 fprintf(stderr, "*** BAD %s/%s%s\n",
529 ISROOT(bingoFps->entry->dirName),
530 (bingoFps->subDir ? bingoFps->subDir : ""),
536 otherFileNum = (bingoFps != NULL ? (bingoFps - otherFps) : 0);
544 * Update disk space needs on each partition for this package.
546 /* XXX only ts->{probs,di} modified */
547 static void handleOverlappedFiles(const rpmTransactionSet ts,
548 const transactionElement p, TFI_t fi)
549 /*@globals fileSystem @*/
550 /*@modifies ts, fi, fileSystem @*/
552 struct diskspaceInfo * ds = NULL;
553 uint_32 fixupSize = 0;
558 if (fi != NULL) /* XXX lclint */
559 while ((i = tfiNext(fi)) >= 0) {
560 struct fingerPrint_s * fiFps;
561 int otherPkgNum, otherFileNum;
566 if (XFA_SKIPPING(fi->actions[i]))
574 while (ds->bsize && ds->dev != fi->fps[i].entry->dev) ds++;
575 if (!ds->bsize) ds = NULL;
580 * Retrieve all records that apply to this file. Note that the
581 * file info records were built in the same order as the packages
582 * will be installed and removed so the records for an overlapped
583 * files will be sorted in exactly the same order.
585 (void) htGetEntry(ts->ht, fiFps,
586 (const void ***) &recs, &numRecs, NULL);
589 * If this package is being added, look only at other packages
590 * being added -- removed packages dance to a different tune.
591 * If both this and the other package are being added, overlapped
592 * files must be identical (or marked as a conflict). The
593 * disposition of already installed config files leads to
594 * a small amount of extra complexity.
596 * If this package is being removed, then there are two cases that
597 * need to be worried about:
598 * If the other package is being added, then skip any overlapped files
599 * so that this package removal doesn't nuke the overlapped files
600 * that were just installed.
601 * If both this and the other package are being removed, then each
602 * file removal from preceding packages needs to be skipped so that
603 * the file removal occurs only on the last occurence of an overlapped
604 * file in the transaction set.
608 /* Locate this overlapped file in the set of added/removed packages. */
609 for (j = 0; j < numRecs && recs[j] != fi; j++)
612 /* Find what the previous disposition of this file was. */
613 otherFileNum = -1; /* keep gcc quiet */
615 for (otherPkgNum = j - 1; otherPkgNum >= 0; otherPkgNum--) {
616 struct fingerPrint_s * otherFps;
619 otherFi = recs[otherPkgNum];
621 /* Added packages need only look at other added packages. */
622 if (teGetType(p) == TR_ADDED && teGetType(otherFi->te) != TR_ADDED)
623 /*@innercontinue@*/ continue;
625 otherFps = otherFi->fps;
626 otherFc = tfiGetFC(otherFi);
628 otherFileNum = findFps(fiFps, otherFps, otherFc);
629 (void) tfiSetFX(otherFi, otherFileNum);
631 /* XXX is this test still necessary? */
632 if (otherFi->actions[otherFileNum] != FA_UNKNOWN)
633 /*@innerbreak@*/ break;
636 switch (teGetType(p)) {
639 if (otherPkgNum < 0) {
640 /* XXX is this test still necessary? */
641 if (fi->actions[i] != FA_UNKNOWN)
642 /*@switchbreak@*/ break;
643 if ((tfiGetFFlags(fi) & RPMFILE_CONFIG) &&
645 /* Here is a non-overlapped pre-existing config file. */
646 fi->actions[i] = (tfiGetFFlags(fi) & RPMFILE_NOREPLACE)
647 ? FA_ALTNAME : FA_BACKUP;
649 fi->actions[i] = FA_CREATE;
651 /*@switchbreak@*/ break;
654 assert(otherFi != NULL);
655 /* Mark added overlapped non-identical files as a conflict. */
656 if ((ts->ignoreSet & RPMPROB_FILTER_REPLACENEWFILES)
657 && filecmp(otherFi, fi))
659 rpmProblemSetAppend(ts->probs, RPMPROB_NEW_FILE_CONFLICT,
660 teGetNEVR(p), teGetKey(p),
662 teGetNEVR(otherFi->te),
666 /* Try to get the disk accounting correct even if a conflict. */
667 fixupSize = otherFi->fsizes[otherFileNum];
669 if ((tfiGetFFlags(fi) & RPMFILE_CONFIG) && !lstat(fn, &sb)) {
670 /* Here is an overlapped pre-existing config file. */
671 fi->actions[i] = (tfiGetFFlags(fi) & RPMFILE_NOREPLACE)
672 ? FA_ALTNAME : FA_SKIP;
674 fi->actions[i] = FA_CREATE;
676 } /*@switchbreak@*/ break;
679 if (otherPkgNum >= 0) {
680 assert(otherFi != NULL);
681 /* Here is an overlapped added file we don't want to nuke. */
682 if (otherFi->actions[otherFileNum] != FA_ERASE) {
683 /* On updates, don't remove files. */
684 fi->actions[i] = FA_SKIP;
685 /*@switchbreak@*/ break;
687 /* Here is an overlapped removed file: skip in previous. */
688 otherFi->actions[otherFileNum] = FA_SKIP;
690 if (XFA_SKIPPING(fi->actions[i]))
691 /*@switchbreak@*/ break;
692 if (fi->fstates && fi->fstates[i] != RPMFILE_STATE_NORMAL)
693 /*@switchbreak@*/ break;
694 if (!(S_ISREG(fi->fmodes[i]) && (tfiGetFFlags(fi) & RPMFILE_CONFIG))) {
695 fi->actions[i] = FA_ERASE;
696 /*@switchbreak@*/ break;
699 /* Here is a pre-existing modified config file that needs saving. */
701 if (fi->md5s != NULL) {
702 const unsigned char * fmd5 = fi->md5s + (16 * i);
703 if (!domd5(fn, md5sum, 0) && memcmp(fmd5, md5sum, 16)) {
704 fi->actions[i] = FA_BACKUP;
705 /*@switchbreak@*/ break;
708 const char * fmd5 = fi->fmd5s[i];
709 if (!domd5(fn, md5sum, 1) && strcmp(fmd5, md5sum)) {
710 fi->actions[i] = FA_BACKUP;
711 /*@switchbreak@*/ break;
715 fi->actions[i] = FA_ERASE;
716 /*@switchbreak@*/ break;
720 uint_32 s = BLOCK_ROUND(fi->fsizes[i], ds->bsize);
722 switch (fi->actions[i]) {
728 /*@switchbreak@*/ break;
731 * FIXME: If two packages share a file (same md5sum), and
732 * that file is being replaced on disk, will ds->bneeded get
733 * decremented twice? Quite probably!
737 ds->bneeded -= BLOCK_ROUND(fi->replacedSizes[i], ds->bsize);
738 /*@switchbreak@*/ break;
743 /*@switchbreak@*/ break;
746 /*@switchbreak@*/ break;
749 ds->bneeded -= BLOCK_ROUND(fixupSize, ds->bsize);
755 * Ensure that current package is newer than installed package.
756 * @param ts transaction set
757 * @param p current transaction element
758 * @param h installed header
759 * @return 0 if not newer, 1 if okay
761 static int ensureOlder(rpmTransactionSet ts,
762 const transactionElement p, const Header h)
765 int_32 reqFlags = (RPMSENSE_LESS | RPMSENSE_EQUAL);
772 if (p == NULL || h == NULL)
775 nb = strlen(teGetNEVR(p)) + (teGetE(p) != NULL ? strlen(teGetE(p)) : 0) + 1;
779 if (teGetE(p) != NULL) t = stpcpy( stpcpy(t, teGetE(p)), ":");
780 if (teGetV(p) != NULL) t = stpcpy(t, teGetV(p));
782 if (teGetR(p) != NULL) t = stpcpy(t, teGetR(p));
784 req = dsSingle(RPMTAG_REQUIRENAME, teGetN(p), reqEVR, reqFlags);
785 rc = headerMatchesDepFlags(h, req);
789 const char * altNEVR = hGetNEVR(h, NULL);
790 rpmProblemSetAppend(ts->probs, RPMPROB_OLDPACKAGE,
791 teGetNEVR(p), teGetKey(p),
795 altNEVR = _free(altNEVR);
805 /*@-mustmod@*/ /* FIX: fi->actions is modified. */
806 static void skipFiles(const rpmTransactionSet ts, TFI_t fi)
807 /*@globals rpmGlobalMacroContext @*/
808 /*@modifies fi, rpmGlobalMacroContext @*/
810 int noDocs = (ts->transFlags & RPMTRANS_FLAG_NODOCS);
811 char ** netsharedPaths = NULL;
812 const char ** languages;
813 const char * dn, * bn;
814 int dnlen, bnlen, ix;
822 noDocs = rpmExpandNumeric("%{_excludedocs}");
824 { const char *tmpPath = rpmExpand("%{_netsharedpath}", NULL);
826 if (tmpPath && *tmpPath != '%')
827 netsharedPaths = splitString(tmpPath, strlen(tmpPath), ':');
829 tmpPath = _free(tmpPath);
832 s = rpmExpand("%{_install_langs}", NULL);
834 if (!(s && *s != '%'))
837 languages = (const char **) splitString(s, strlen(s), ':');
843 /* Compute directory refcount, skip directory if now empty. */
845 drc = alloca(dc * sizeof(*drc));
846 memset(drc, 0, dc * sizeof(*drc));
847 dff = alloca(dc * sizeof(*dff));
848 memset(dff, 0, dc * sizeof(*dff));
851 if (fi != NULL) /* XXX lclint */
852 while ((i = tfiNext(fi)) >= 0)
862 continue; /* XXX can't happen */
866 /* Don't bother with skipped files */
867 if (XFA_SKIPPING(fi->actions[i])) {
873 * Skip net shared paths.
874 * Net shared paths are not relative to the current root (though
875 * they do need to take package relocations into account).
877 for (nsp = netsharedPaths; nsp && *nsp; nsp++) {
882 if (strncmp(dn, *nsp, len))
883 /*@innercontinue@*/ continue;
884 /* Only directories or complete file paths can be net shared */
885 if (!(dn[len] == '/' || dn[len] == '\0'))
886 /*@innercontinue@*/ continue;
888 if (len < (dnlen + bnlen))
889 /*@innercontinue@*/ continue;
890 if (strncmp(dn, *nsp, dnlen))
891 /*@innercontinue@*/ continue;
892 if (strncmp(bn, (*nsp) + dnlen, bnlen))
893 /*@innercontinue@*/ continue;
895 /* Only directories or complete file paths can be net shared */
896 if (!((*nsp)[len] == '/' || (*nsp)[len] == '\0'))
897 /*@innercontinue@*/ continue;
900 /*@innerbreak@*/ break;
904 drc[ix]--; dff[ix] = 1;
905 fi->actions[i] = FA_SKIPNETSHARED;
910 * Skip i18n language specific files.
912 if (fi->flangs && languages && *fi->flangs[i]) {
913 const char **lang, *l, *le;
914 for (lang = languages; *lang != NULL; lang++) {
915 if (!strcmp(*lang, "all"))
916 /*@innerbreak@*/ break;
917 for (l = fi->flangs[i]; *l != '\0'; l = le) {
918 for (le = l; *le != '\0' && *le != '|'; le++)
920 if ((le-l) > 0 && !strncmp(*lang, l, (le-l)))
921 /*@innerbreak@*/ break;
922 if (*le == '|') le++; /* skip over | */
925 /*@innerbreak@*/ break;
928 drc[ix]--; dff[ix] = 1;
929 fi->actions[i] = FA_SKIPNSTATE;
935 * Skip documentation if requested.
937 if (noDocs && (tfiGetFFlags(fi) & RPMFILE_DOC)) {
938 drc[ix]--; dff[ix] = 1;
939 fi->actions[i] = FA_SKIPNSTATE;
944 /* Skip (now empty) directories that had skipped files. */
946 if (fi != NULL) /* XXX can't happen */
947 for (j = 0; j < dc; j++)
949 if ((fi = tdiInit(fi)) != NULL)
950 while (j = tdiNext(fi) >= 0)
954 if (drc[j]) continue; /* dir still has files. */
955 if (!dff[j]) continue; /* dir was not emptied here. */
957 /* Find parent directory and basename. */
958 dn = fi->dnl[j]; dnlen = strlen(dn) - 1;
959 bn = dn + dnlen; bnlen = 0;
960 while (bn > dn && bn[-1] != '/') {
966 /* If explicitly included in the package, skip the directory. */
968 if (fi != NULL) /* XXX lclint */
969 while ((i = tfiNext(fi)) >= 0) {
972 if (XFA_SKIPPING(fi->actions[i]))
973 /*@innercontinue@*/ continue;
974 if (whatis(fi->fmodes[i]) != XDIR)
975 /*@innercontinue@*/ continue;
976 dir = fi->dnl[fi->dil[i]];
977 if (strlen(dir) != dnlen)
978 /*@innercontinue@*/ continue;
979 if (strncmp(dir, dn, dnlen))
980 /*@innercontinue@*/ continue;
981 if (strlen(fi->bnl[i]) != bnlen)
982 /*@innercontinue@*/ continue;
983 if (strncmp(fi->bnl[i], bn, bnlen))
984 /*@innercontinue@*/ continue;
985 rpmMessage(RPMMESS_DEBUG, _("excluding directory %s\n"), dn);
986 fi->actions[i] = FA_SKIPNSTATE;
987 /*@innerbreak@*/ break;
991 if (netsharedPaths) freeSplitString(netsharedPaths);
992 #ifdef DYING /* XXX freeFi will deal with this later. */
993 fi->flangs = _free(fi->flangs);
995 if (languages) freeSplitString((char **)languages);
1000 * Return transaction element's file info.
1001 * @todo Take a TFI_t refcount here.
1002 * @param tei transaction element iterator
1003 * @return transaction element file info
1006 TFI_t teiGetFi(const teIterator tei)
1011 if (tei != NULL && tei->ocsave != -1) {
1012 /*@-type -abstract@*/ /* FIX: transactionElement not opaque */
1013 transactionElement te = tei->ts->order[tei->ocsave];
1015 if ((fi = te->fi) != NULL)
1018 /*@=type =abstract@*/
1020 /*@-compdef -refcounttrans -usereleased @*/
1022 /*@=compdef =refcounttrans =usereleased @*/
1025 #define NOTIFY(_ts, _al) if ((_ts)->notify) (void) (_ts)->notify _al
1027 int rpmRunTransactions( rpmTransactionSet ts,
1028 rpmCallbackFunction notify, rpmCallbackData notifyData,
1029 rpmProblemSet okProbs, rpmProblemSet * newProbs,
1030 rpmtransFlags transFlags, rpmprobFilterFlags ignoreSet)
1034 int totalFileCount = 0;
1036 struct diskspaceInfo * dip;
1037 sharedFileInfo shared, sharedList;
1041 fingerPrintCache fpc;
1042 PSM_t psm = memset(alloca(sizeof(*psm)), 0, sizeof(*psm));
1043 teIterator pi; transactionElement p;
1044 teIterator qi; transactionElement q;
1047 /* FIXME: what if the same package is included in ts twice? */
1049 ts->transFlags = transFlags;
1050 if (ts->transFlags & RPMTRANS_FLAG_NOSCRIPTS)
1051 ts->transFlags |= (_noTransScripts | _noTransTriggers);
1052 if (ts->transFlags & RPMTRANS_FLAG_NOTRIGGERS)
1053 ts->transFlags |= _noTransTriggers;
1055 /* XXX MULTILIB is broken, as packages can and do execute /sbin/ldconfig. */
1056 if (ts->transFlags & (RPMTRANS_FLAG_JUSTDB | RPMTRANS_FLAG_MULTILIB))
1057 ts->transFlags |= (_noTransScripts | _noTransTriggers);
1059 ts->notify = notify;
1060 ts->notifyData = notifyData;
1061 ts->probs = rpmProblemSetFree(ts->probs);
1062 ts->probs = rpmProblemSetCreate();
1063 *newProbs = rpmpsLink(ts->probs, "RunTransactions");
1064 ts->ignoreSet = ignoreSet;
1065 ts->currDir = _free(ts->currDir);
1066 ts->currDir = currentDirectory();
1068 if (ts->rpmdb) ts->rpmdb->db_chrootDone = 0;
1069 ts->id = (int_32) time(NULL);
1071 memset(psm, 0, sizeof(*psm));
1072 psm->ts = rpmtsLink(ts, "tsRun");
1074 /* Get available space on mounted file systems. */
1075 if (!(ts->ignoreSet & RPMPROB_FILTER_DISKSPACE) &&
1076 !rpmGetFilesystemList(&ts->filesystems, &ts->filesystemCount))
1080 rpmMessage(RPMMESS_DEBUG, _("getting list of mounted filesystems\n"));
1082 ts->di = _free(ts->di);
1083 dip = ts->di = xcalloc((ts->filesystemCount + 1), sizeof(*ts->di));
1085 for (i = 0; (i < ts->filesystemCount) && dip; i++) {
1086 #if STATFS_IN_SYS_STATVFS
1088 memset(&sfb, 0, sizeof(sfb));
1089 if (statvfs(ts->filesystems[i], &sfb))
1093 /* This platform has the 4-argument version of the statfs call. The last two
1094 * should be the size of struct statfs and 0, respectively. The 0 is the
1095 * filesystem type, and is always 0 when statfs is called on a mounted
1096 * filesystem, as we're doing.
1098 memset(&sfb, 0, sizeof(sfb));
1099 if (statfs(ts->filesystems[i], &sfb, sizeof(sfb), 0))
1101 memset(&sfb, 0, sizeof(sfb));
1102 if (statfs(ts->filesystems[i], &sfb))
1108 ts->di[i].bsize = sfb.f_bsize;
1109 ts->di[i].bneeded = 0;
1110 ts->di[i].ineeded = 0;
1111 #ifdef STATFS_HAS_F_BAVAIL
1112 ts->di[i].bavail = sfb.f_bavail;
1114 /* FIXME: the statfs struct doesn't have a member to tell how many blocks are
1115 * available for non-superusers. f_blocks - f_bfree is probably too big, but
1116 * it's about all we can do.
1118 ts->di[i].bavail = sfb.f_blocks - sfb.f_bfree;
1120 /* XXX Avoid FAT and other file systems that have not inodes. */
1121 ts->di[i].iavail = !(sfb.f_ffree == 0 && sfb.f_files == 0)
1124 xx = stat(ts->filesystems[i], &sb);
1125 ts->di[i].dev = sb.st_dev;
1129 if (dip) ts->di[i].bsize = 0;
1132 /* ===============================================
1133 * For packages being installed:
1134 * - verify package arch/os.
1135 * - verify package epoch:version-release is newer.
1137 * For packages being removed:
1140 /* The ordering doesn't matter here */
1141 pi = teInitIterator(ts);
1142 while ((p = teNext(pi, TR_ADDED)) != NULL) {
1143 rpmdbMatchIterator mi;
1146 if ((fi = teiGetFi(pi)) == NULL)
1147 continue; /* XXX can't happen */
1150 if (!(ts->ignoreSet & RPMPROB_FILTER_IGNOREARCH))
1151 if (!archOkay(teGetA(p)))
1152 rpmProblemSetAppend(ts->probs, RPMPROB_BADARCH,
1153 teGetNEVR(p), teGetKey(p),
1157 if (!(ts->ignoreSet & RPMPROB_FILTER_IGNOREOS))
1158 if (!osOkay(teGetO(p)))
1159 rpmProblemSetAppend(ts->probs, RPMPROB_BADOS,
1160 teGetNEVR(p), teGetKey(p),
1164 if (!(ts->ignoreSet & RPMPROB_FILTER_OLDPACKAGE)) {
1166 mi = rpmtsInitIterator(ts, RPMTAG_NAME, teGetN(p), 0);
1167 while ((h = rpmdbNextIterator(mi)) != NULL)
1168 xx = ensureOlder(ts, p, h);
1169 mi = rpmdbFreeIterator(mi);
1172 /* XXX multilib should not display "already installed" problems */
1173 if (!(ts->ignoreSet & RPMPROB_FILTER_REPLACEPKG) && !teGetMultiLib(p)) {
1174 mi = rpmtsInitIterator(ts, RPMTAG_NAME, teGetN(p), 0);
1175 xx = rpmdbSetIteratorRE(mi, RPMTAG_VERSION, RPMMIRE_DEFAULT,
1177 xx = rpmdbSetIteratorRE(mi, RPMTAG_RELEASE, RPMMIRE_DEFAULT,
1180 while (rpmdbNextIterator(mi) != NULL) {
1181 rpmProblemSetAppend(ts->probs, RPMPROB_PKG_INSTALLED,
1182 teGetNEVR(p), teGetKey(p),
1185 /*@innerbreak@*/ break;
1187 mi = rpmdbFreeIterator(mi);
1190 /* Count no. of files (if any). */
1191 totalFileCount += fc;
1194 pi = teFreeIterator(pi);
1196 /* The ordering doesn't matter here */
1197 pi = teInitIterator(ts);
1198 while ((p = teNext(pi, TR_REMOVED)) != NULL) {
1201 if ((fi = teiGetFi(pi)) == NULL)
1202 continue; /* XXX can't happen */
1205 totalFileCount += fc;
1207 pi = teFreeIterator(pi);
1209 /* ===============================================
1210 * Initialize transaction element file info for package:
1214 * FIXME?: we'd be better off assembling one very large file list and
1215 * calling fpLookupList only once. I'm not sure that the speedup is
1216 * worth the trouble though.
1218 pi = teInitIterator(ts);
1219 while ((p = teNextIterator(pi)) != NULL) {
1222 if ((fi = teiGetFi(pi)) == NULL)
1223 continue; /* XXX can't happen */
1226 #ifdef DYING /* XXX W2DO? this is now done in teiGetFi, okay ??? */
1227 fi->magic = TFIMAGIC;
1232 switch (teGetType(p)) {
1235 /* Skip netshared paths, not our i18n files, and excluded docs */
1238 /*@switchbreak@*/ break;
1240 fi->record = teGetDBOffset(p);
1241 /*@switchbreak@*/ break;
1245 fi->fps = (fc > 0 ? xmalloc(fc * sizeof(*fi->fps)) : NULL);
1247 pi = teFreeIterator(pi);
1249 if (!ts->chrootDone) {
1251 /*@-superuser -noeffect @*/
1252 xx = chroot(ts->rootDir);
1253 /*@=superuser =noeffect @*/
1255 if (ts->rpmdb) ts->rpmdb->db_chrootDone = 1;
1258 ts->ht = htCreate(totalFileCount * 2, 0, 0, fpHashFunction, fpEqual);
1259 fpc = fpCacheCreate(totalFileCount);
1261 /* ===============================================
1262 * Add fingerprint for each file not skipped.
1264 pi = teInitIterator(ts);
1265 while ((p = teNextIterator(pi)) != NULL) {
1268 if ((fi = teiGetFi(pi)) == NULL)
1269 continue; /* XXX can't happen */
1272 fpLookupList(fpc, fi->dnl, fi->bnl, fi->dil, fc, fi->fps);
1274 fi = tfiInit(fi, 0);
1275 if (fi != NULL) /* XXX lclint */
1276 while ((i = tfiNext(fi)) >= 0) {
1277 if (XFA_SKIPPING(fi->actions[i]))
1278 /*@innercontinue@*/ continue;
1279 /*@-dependenttrans@*/
1280 htAddEntry(ts->ht, fi->fps + i, (void *) fi);
1281 /*@=dependenttrans@*/
1285 pi = teFreeIterator(pi);
1287 /*@-noeffectuncon @*/ /* FIX: check rc */
1288 NOTIFY(ts, (NULL, RPMCALLBACK_TRANS_START, 6, ts->orderCount,
1289 NULL, ts->notifyData));
1290 /*@=noeffectuncon@*/
1292 /* ===============================================
1293 * Compute file disposition for each package in transaction set.
1295 pi = teInitIterator(ts);
1296 while ((p = teNextIterator(pi)) != NULL) {
1297 dbiIndexSet * matches;
1301 if ((fi = teiGetFi(pi)) == NULL)
1302 continue; /* XXX can't happen */
1305 /*@-noeffectuncon @*/ /* FIX: check rc */
1306 NOTIFY(ts, (NULL, RPMCALLBACK_TRANS_PROGRESS, teiGetOc(pi),
1307 ts->orderCount, NULL, ts->notifyData));
1308 /*@=noeffectuncon@*/
1310 if (fc == 0) continue;
1312 /* Extract file info for all files in this package from the database. */
1313 matches = xcalloc(fc, sizeof(*matches));
1314 if (rpmdbFindFpList(ts->rpmdb, fi->fps, matches, fc)) {
1315 psm->ts = rpmtsUnlink(ts, "tsRun (rpmFindFpList fail)");
1316 return 1; /* XXX WTFO? */
1320 fi = tfiInit(fi, 0);
1321 while ((i = tfiNext(fi)) >= 0)
1322 numShared += dbiIndexSetCount(matches[i]);
1324 /* Build sorted file info list for this package. */
1325 shared = sharedList = xcalloc((numShared + 1), sizeof(*sharedList));
1327 fi = tfiInit(fi, 0);
1328 while ((i = tfiNext(fi)) >= 0) {
1330 * Take care not to mark files as replaced in packages that will
1331 * have been removed before we will get here.
1333 for (j = 0; j < dbiIndexSetCount(matches[i]); j++) {
1335 ro = dbiIndexRecordOffset(matches[i], j);
1337 qi = teInitIterator(ts);
1338 while ((q = teNext(qi, TR_REMOVED)) != NULL) {
1340 /*@innerbreak@*/ break;
1341 if (teGetDBOffset(q) == ro)
1344 qi = teFreeIterator(qi);
1346 shared->pkgFileNum = i;
1347 shared->otherPkg = dbiIndexRecordOffset(matches[i], j);
1348 shared->otherFileNum = dbiIndexRecordFileNumber(matches[i], j);
1349 shared->isRemoved = (knownBad == ro);
1352 matches[i] = dbiFreeIndexSet(matches[i]);
1354 numShared = shared - sharedList;
1355 shared->otherPkg = -1;
1356 matches = _free(matches);
1358 /* Sort file info by other package index (otherPkg) */
1359 qsort(sharedList, numShared, sizeof(*shared), sharedCmp);
1361 /* For all files from this package that are in the database ... */
1363 for (i = 0; i < numShared; i = nexti) {
1366 shared = sharedList + i;
1368 /* Find the end of the files in the other package. */
1369 for (nexti = i + 1; nexti < numShared; nexti++) {
1370 if (sharedList[nexti].otherPkg != shared->otherPkg)
1371 /*@innerbreak@*/ break;
1374 /* Is this file from a package being removed? */
1376 if (ts->removedPackages != NULL)
1377 for (j = 0; j < ts->numRemovedPackages; j++) {
1378 if (ts->removedPackages[j] != shared->otherPkg)
1379 /*@innercontinue@*/ continue;
1381 /*@innerbreak@*/ break;
1384 /* Determine the fate of each file. */
1385 switch (teGetType(p)) {
1387 xx = handleInstInstalledFiles(ts, p, fi, shared, nexti - i,
1388 !(beingRemoved || (ts->ignoreSet & RPMPROB_FILTER_REPLACEOLDFILES)));
1389 /*@switchbreak@*/ break;
1392 xx = handleRmvdInstalledFiles(ts, fi, shared, nexti - i);
1393 /*@switchbreak@*/ break;
1400 /* Update disk space needs on each partition for this package. */
1401 handleOverlappedFiles(ts, p, fi);
1403 /* Check added package has sufficient space on each partition used. */
1404 switch (teGetType(p)) {
1406 if (!(ts->di && tfiGetFC(fi) > 0))
1407 /*@switchbreak@*/ break;
1408 for (i = 0; i < ts->filesystemCount; i++) {
1412 /* XXX Avoid FAT and other file systems that have not inodes. */
1413 if (dip->iavail <= 0)
1414 /*@innercontinue@*/ continue;
1416 if (adj_fs_blocks(dip->bneeded) > dip->bavail) {
1417 rpmProblemSetAppend(ts->probs, RPMPROB_DISKSPACE,
1418 teGetNEVR(p), teGetKey(p),
1419 ts->filesystems[i], NULL, NULL,
1420 (adj_fs_blocks(dip->bneeded) - dip->bavail) * dip->bsize);
1423 if (adj_fs_blocks(dip->ineeded) > dip->iavail) {
1424 rpmProblemSetAppend(ts->probs, RPMPROB_DISKNODES,
1425 teGetNEVR(p), teGetKey(p),
1426 ts->filesystems[i], NULL, NULL,
1427 (adj_fs_blocks(dip->ineeded) - dip->iavail));
1430 /*@switchbreak@*/ break;
1432 /*@switchbreak@*/ break;
1435 pi = teFreeIterator(pi);
1437 if (ts->chrootDone) {
1438 /*@-superuser -noeffect @*/
1440 /*@=superuser =noeffect @*/
1442 if (ts->rpmdb) ts->rpmdb->db_chrootDone = 0;
1443 xx = chdir(ts->currDir);
1446 /*@-noeffectuncon @*/ /* FIX: check rc */
1447 NOTIFY(ts, (NULL, RPMCALLBACK_TRANS_STOP, 6, ts->orderCount,
1448 NULL, ts->notifyData));
1449 /*@=noeffectuncon @*/
1451 /* ===============================================
1452 * Free unused memory as soon as possible.
1454 pi = teInitIterator(ts);
1455 while ((p = teNextIterator(pi)) != NULL) {
1456 if ((fi = teiGetFi(pi)) == NULL)
1457 continue; /* XXX can't happen */
1458 if (tfiGetFC(fi) == 0)
1460 fi->fps = _free(fi->fps);
1462 pi = teFreeIterator(pi);
1468 /* ===============================================
1469 * If unfiltered problems exist, free memory and return.
1471 if ((ts->transFlags & RPMTRANS_FLAG_BUILD_PROBS)
1472 || (ts->probs->numProblems &&
1473 (okProbs != NULL || rpmProblemSetTrim(ts->probs, okProbs)))
1476 if (psm->ts != NULL)
1477 psm->ts = rpmtsUnlink(psm->ts, "tsRun (problems)");
1478 return ts->orderCount;
1481 /* ===============================================
1482 * Save removed files before erasing.
1484 if (ts->transFlags & (RPMTRANS_FLAG_DIRSTASH | RPMTRANS_FLAG_REPACKAGE)) {
1485 pi = teInitIterator(ts);
1486 while ((p = teNextIterator(pi)) != NULL) {
1488 switch (teGetType(p)) {
1490 /*@switchbreak@*/ break;
1492 if (!(ts->transFlags & RPMTRANS_FLAG_REPACKAGE))
1493 /*@switchbreak@*/ break;
1495 psm->fi = rpmfiLink(fi, "tsRepackage");
1496 xx = psmStage(psm, PSM_PKGSAVE);
1497 (void) rpmfiUnlink(fi, "tsRepackage");
1500 /*@switchbreak@*/ break;
1503 pi = teFreeIterator(pi);
1506 /* ===============================================
1507 * Install and remove packages.
1509 lastKey = (alKey)-2; /* erased packages have -1 */
1510 pi = teInitIterator(ts);
1511 /*@-branchstate@*/ /* FIX: fi reload needs work */
1512 while ((p = teNextIterator(pi)) != NULL) {
1518 if ((fi = teiGetFi(pi)) == NULL)
1519 continue; /* XXX can't happen */
1522 psm->fi = rpmfiLink(fi, "tsInstall");
1523 switch (teGetType(p)) {
1526 pkgKey = teGetAddedKey(p);
1528 rpmMessage(RPMMESS_DEBUG, "========== +++ %s\n", teGetNEVR(p));
1530 /*@-type@*/ /* FIX: transactionElement not opaque */
1532 /*@-noeffectuncon@*/ /* FIX: notify annotations */
1533 p->fd = ts->notify(fi->h, RPMCALLBACK_INST_OPEN_FILE, 0, 0,
1534 teGetKey(p), ts->notifyData);
1535 /*@=noeffectuncon@*/
1536 if (teGetFd(p) != NULL) {
1539 rpmrc = rpmReadPackageFile(ts, teGetFd(p),
1540 "rpmRunTransactions", &h);
1542 if (!(rpmrc == RPMRC_OK || rpmrc == RPMRC_BADSIZE)) {
1543 /*@-noeffectuncon@*/ /* FIX: notify annotations */
1544 p->fd = ts->notify(fi->h, RPMCALLBACK_INST_CLOSE_FILE,
1546 teGetKey(p), ts->notifyData);
1547 /*@=noeffectuncon@*/
1551 if (teGetFd(p) != NULL) gotfd = 1;
1556 if (teGetFd(p) != NULL) {
1558 char * fstates = fi->fstates;
1559 fileAction * actions = fi->actions;
1563 (void) fiFree(fi, 0);
1565 fi->magic = TFIMAGIC;
1568 (void) fiNew(ts, fi, h, RPMTAG_BASENAMES, 1);
1569 fi->fstates = _free(fi->fstates);
1570 fi->fstates = fstates;
1571 fi->actions = _free(fi->actions);
1572 fi->actions = actions;
1576 if (teGetMultiLib(p))
1577 ts->transFlags |= RPMTRANS_FLAG_MULTILIB;
1579 if (psmStage(psm, PSM_PKGINSTALL)) {
1583 fi->h = headerFree(fi->h, "TR_ADDED fi->h free");
1589 h = headerFree(h, "TR_ADDED h free");
1592 /*@-noeffectuncon @*/ /* FIX: check rc */
1593 (void) ts->notify(fi->h, RPMCALLBACK_INST_CLOSE_FILE, 0, 0,
1594 teGetKey(p), ts->notifyData);
1595 /*@=noeffectuncon @*/
1600 (void) fiFree(fi, 0);
1601 /*@switchbreak@*/ break;
1603 rpmMessage(RPMMESS_DEBUG, "========== --- %s\n", teGetNEVR(p));
1604 /* If install failed, then we shouldn't erase. */
1605 if (teGetDependsOnKey(p) != lastKey) {
1606 if (psmStage(psm, PSM_PKGERASE))
1609 (void) fiFree(fi, 0);
1610 /*@switchbreak@*/ break;
1612 xx = rpmdbSync(ts->rpmdb);
1613 (void) rpmfiUnlink(psm->fi, "tsInstall");
1618 pi = teFreeIterator(pi);
1620 psm->ts = rpmtsUnlink(psm->ts, "tsRun");
1622 /*@-nullstate@*/ /* FIX: ts->flList may be NULL */