#include "config.h" #include "miscfn.h" #if HAVE_ALLOCA_H # include #endif #include #include #include #include #include #include #include #include #include #include #include "install.h" #include "intl.h" #include "messages.h" #include "md5.h" #include "misc.h" #include "rpmdb.h" #include "rpmlib.h" static char * SCRIPT_PATH = "PATH=/sbin:/bin:/usr/sbin:/usr/bin:" "/usr/X11R6/bin\nexport PATH\n"; enum fileActions { REMOVE, BACKUP, KEEP }; static int sharedFileCmp(const void * one, const void * two); static int handleSharedFiles(rpmdb db, int offset, char ** fileList, char ** fileMd5List, int fileCount, enum fileActions * fileActions); static int removeFile(char * file, char state, unsigned int flags, char * md5, short mode, enum fileActions action, char * rmmess, int brokenMd5, int test); static int sharedFileCmp(const void * one, const void * two) { if (((struct sharedFile *) one)->secRecOffset < ((struct sharedFile *) two)->secRecOffset) return -1; else if (((struct sharedFile *) one)->secRecOffset == ((struct sharedFile *) two)->secRecOffset) return 0; else return 1; } int findSharedFiles(rpmdb db, int offset, char ** fileList, int fileCount, struct sharedFile ** listPtr, int * listCountPtr) { int i, j; struct sharedFile * list = NULL; int itemsUsed = 0; int itemsAllocated = 0; dbiIndexSet matches; itemsAllocated = 5; list = malloc(sizeof(struct sharedFile) * itemsAllocated); for (i = 0; i < fileCount; i++) { if (!rpmdbFindByFile(db, fileList[i], &matches)) { for (j = 0; j < matches.count; j++) { if (matches.recs[j].recOffset != offset) { if (itemsUsed == itemsAllocated) { itemsAllocated += 10; list = realloc(list, sizeof(struct sharedFile) * itemsAllocated); } list[itemsUsed].mainFileNumber = i; list[itemsUsed].secRecOffset = matches.recs[j].recOffset; list[itemsUsed].secFileNumber = matches.recs[j].fileNumber; itemsUsed++; } } dbiFreeIndexRecord(matches); } } if (itemsUsed) { qsort(list, itemsUsed, sizeof(struct sharedFile), sharedFileCmp); *listPtr = list; *listCountPtr = itemsUsed; } else { free(list); *listPtr = NULL; *listCountPtr = 0; } return 0; } static int handleSharedFiles(rpmdb db, int offset, char ** fileList, char ** fileMd5List, int fileCount, enum fileActions * fileActions) { Header sech = NULL; int secOffset = 0; struct sharedFile * sharedList; int sharedCount; char * name, * version, * release; int secFileCount; char ** secFileMd5List, ** secFileList; char * secFileStatesList; int type; int i; int rc = 0; if (findSharedFiles(db, offset, fileList, fileCount, &sharedList, &sharedCount)) { return 1; } if (!sharedCount) { return 0; } for (i = 0; i < sharedCount; i++) { if (secOffset != sharedList[i].secRecOffset) { if (secOffset) { headerFree(sech); free(secFileMd5List); free(secFileList); } secOffset = sharedList[i].secRecOffset; sech = rpmdbGetRecord(db, secOffset); if (!sech) { rpmError(RPMERR_DBCORRUPT, _("cannot read header at %d for uninstall"), offset); rc = 1; break; } headerGetEntry(sech, RPMTAG_NAME, &type, (void **) &name, &secFileCount); headerGetEntry(sech, RPMTAG_VERSION, &type, (void **) &version, &secFileCount); headerGetEntry(sech, RPMTAG_RELEASE, &type, (void **) &release, &secFileCount); rpmMessage(RPMMESS_DEBUG, _("package %s-%s-%s contain shared files\n"), name, version, release); if (!headerGetEntry(sech, RPMTAG_FILENAMES, &type, (void **) &secFileList, &secFileCount)) { rpmError(RPMERR_DBCORRUPT, "package %s contains no files", name); headerFree(sech); rc = 1; break; } headerGetEntry(sech, RPMTAG_FILESTATES, &type, (void **) &secFileStatesList, &secFileCount); headerGetEntry(sech, RPMTAG_FILEMD5S, &type, (void **) &secFileMd5List, &secFileCount); } rpmMessage(RPMMESS_DEBUG, "file %s is shared\n", fileList[sharedList[i].mainFileNumber]); switch (secFileStatesList[sharedList[i].secFileNumber]) { case RPMFILE_STATE_REPLACED: rpmMessage(RPMMESS_DEBUG, " file has already been replaced\n"); break; case RPMFILE_STATE_NOTINSTALLED: rpmMessage(RPMMESS_DEBUG, " file was never installed\n"); break; case RPMFILE_STATE_NETSHARED: rpmMessage(RPMMESS_DEBUG, " file is netshared (so don't touch it)\n"); fileActions[sharedList[i].mainFileNumber] = KEEP; break; case RPMFILE_STATE_NORMAL: if (!strcmp(fileMd5List[sharedList[i].mainFileNumber], secFileMd5List[sharedList[i].secFileNumber])) { rpmMessage(RPMMESS_DEBUG, " file is truely shared - saving\n"); } fileActions[sharedList[i].mainFileNumber] = KEEP; break; } } if (secOffset) { headerFree(sech); free(secFileMd5List); free(secFileList); } free(sharedList); return rc; } int rpmRemovePackage(char * prefix, rpmdb db, unsigned int offset, int flags) { Header h; int i; int fileCount; char * rmmess, * name, * version, * release; char * fnbuffer = NULL; dbiIndexSet matches; int fnbuffersize = 0; int prefixLength = strlen(prefix); char ** fileList, ** fileMd5List; int type, count; uint_32 * fileFlagsList; int_16 * fileModesList; char * fileStatesList; enum { REMOVE, BACKUP, KEEP } * fileActions; int scriptArg; if (flags & RPMUNINSTALL_JUSTDB) flags |= RPMUNINSTALL_NOSCRIPTS; h = rpmdbGetRecord(db, offset); if (!h) { rpmError(RPMERR_DBCORRUPT, "cannot read header at %d for uninstall", offset); return 1; } headerGetEntry(h, RPMTAG_NAME, &type, (void **) &name, &count); headerGetEntry(h, RPMTAG_VERSION, &type, (void **) &version, &count); headerGetEntry(h, RPMTAG_RELEASE, &type, (void **) &release, &count); /* when we run scripts, we pass an argument which is the number of versions of this package that will be installed when we are finished */ if (rpmdbFindPackage(db, name, &matches)) { rpmError(RPMERR_DBCORRUPT, "cannot read packages named %s for uninstall", name); return 1; } scriptArg = matches.count - 1; dbiFreeIndexRecord(matches); if (flags & RPMUNINSTALL_TEST) { rmmess = "would remove"; } else { rmmess = "removing"; } rpmMessage(RPMMESS_DEBUG, "running preuninstall script (if any)\n"); if (runScript(prefix, h, RPMTAG_PREUN, RPMTAG_PREUNPROG, scriptArg, flags & RPMUNINSTALL_NOSCRIPTS)) { headerFree(h); return 1; } rpmMessage(RPMMESS_DEBUG, "%s files test = %d\n", rmmess, flags & RPMUNINSTALL_TEST); if (!(flags & RPMUNINSTALL_JUSTDB) && headerGetEntry(h, RPMTAG_FILENAMES, &type, (void **) &fileList, &fileCount)) { if (prefix[0]) { fnbuffersize = 1024; fnbuffer = alloca(fnbuffersize); } headerGetEntry(h, RPMTAG_FILESTATES, &type, (void **) &fileStatesList, &fileCount); headerGetEntry(h, RPMTAG_FILEMD5S, &type, (void **) &fileMd5List, &fileCount); headerGetEntry(h, RPMTAG_FILEFLAGS, &type, (void **) &fileFlagsList, &fileCount); headerGetEntry(h, RPMTAG_FILEMODES, &type, (void **) &fileModesList, &fileCount); fileActions = alloca(sizeof(*fileActions) * fileCount); for (i = 0; i < fileCount; i++) if (fileStatesList[i] == RPMFILE_STATE_NOTINSTALLED || fileStatesList[i] == RPMFILE_STATE_NETSHARED) fileActions[i] = KEEP; else fileActions[i] = REMOVE; handleSharedFiles(db, offset, fileList, fileMd5List, fileCount, fileActions); /* go through the filelist backwards to help insure that rmdir() will work */ for (i = fileCount - 1; i >= 0; i--) { if (strcmp(prefix, "/")) { if ((strlen(fileList[i]) + prefixLength + 1) > fnbuffersize) { fnbuffersize = (strlen(fileList[i]) + prefixLength) * 2; fnbuffer = alloca(fnbuffersize); } strcpy(fnbuffer, prefix); strcat(fnbuffer, "/"); strcat(fnbuffer, fileList[i]); } else { fnbuffer = fileList[i]; } removeFile(fnbuffer, fileStatesList[i], fileFlagsList[i], fileMd5List[i], fileModesList[i], fileActions[i], rmmess, !headerIsEntry(h, RPMTAG_RPMVERSION), flags & RPMUNINSTALL_TEST); } free(fileList); free(fileMd5List); } rpmMessage(RPMMESS_DEBUG, "running postuninstall script (if any)\n"); runScript(prefix, h, RPMTAG_POSTUN, RPMTAG_POSTUNPROG, scriptArg, flags & RPMUNINSTALL_NOSCRIPTS); headerFree(h); rpmMessage(RPMMESS_DEBUG, "%s database entry\n", rmmess); if (!(flags & RPMUNINSTALL_TEST)) rpmdbRemove(db, offset, 0); return 0; } int runScript(char * prefix, Header h, int scriptTag, int progTag, int arg, int norunScripts) { char * script; char * fn; int fd = -1; int isdebug = rpmIsDebug(); int child; int status; char upgradeArg[20]; char * installPrefix = NULL; char * installPrefixEnv = NULL; char * argv[10]; char ** argvPtr = argv; char * program; int pipes[2]; sprintf(upgradeArg, "%d", arg); if (norunScripts) return 0; /* headerGetEntry() sets the data pointer to NULL if the entry doesn not exist */ headerGetEntry(h, progTag, NULL, (void **) &program, NULL); headerGetEntry(h, scriptTag, NULL, (void **) &script, NULL); if (!program && !script) return 0; else if (!program) program = "/bin/sh"; if (headerGetEntry(h, RPMTAG_INSTALLPREFIX, NULL, (void **) &installPrefix, NULL)) { installPrefixEnv = alloca(strlen(installPrefix) + 30); strcpy(installPrefixEnv, "RPM_INSTALL_PREFIX="); strcat(installPrefixEnv, installPrefix); strcat(installPrefixEnv, "\n"); } rpmMessage(RPMMESS_DEBUG, "running inst helper %s\n", program); if (script) { if (makeTempFile(prefix, &fn, &fd)) return 1; write(fd, "#!", 2); write(fd, program, strlen(program)); write(fd, "\n", 1); if (isdebug && !strcmp(program, "/bin/sh")) write(fd, "set -x\n", 7); if (installPrefixEnv) write(fd, installPrefixEnv, strlen(installPrefixEnv)); write(fd, SCRIPT_PATH, strlen(SCRIPT_PATH)); write(fd, script, strlen(script)); close(fd); program = fn + strlen(prefix); } *argvPtr++ = program; if (script) { *argvPtr++ = upgradeArg; } *argvPtr++ = NULL; if (!(child = fork())) { /* make stdin inaccessible */ pipe(pipes); close(pipes[1]); dup2(pipes[0], 0); if (strcmp(prefix, "/")) { rpmMessage(RPMMESS_DEBUG, "performing chroot(%s)\n", prefix); chroot(prefix); chdir("/"); } execv(argv[0], argv); _exit(-1); } waitpid(child, &status, 0); if (script) { if (!isdebug) unlink(fn); free(fn); } if (!WIFEXITED(status) || WEXITSTATUS(status)) { rpmError(RPMERR_SCRIPT, _("execution of script failed")); return 1; } return 0; } static int removeFile(char * file, char state, unsigned int flags, char * md5, short mode, enum fileActions action, char * rmmess, int brokenMd5, int test) { char currentMd5[40]; int rc = 0; char * newfile; switch (state) { case RPMFILE_STATE_REPLACED: rpmMessage(RPMMESS_DEBUG, "%s has already been replaced\n", file); break; case RPMFILE_STATE_NORMAL: if ((action == REMOVE) && (flags & RPMFILE_CONFIG)) { /* if it's a config file, we may not want to remove it */ rpmMessage(RPMMESS_DEBUG, "finding md5sum of %s\n", file); if (brokenMd5) rc = mdfileBroken(file, currentMd5); else rc = mdfile(file, currentMd5); if (mdfile(file, currentMd5)) rpmMessage(RPMMESS_DEBUG, " failed - assuming file removed\n"); else { if (strcmp(currentMd5, md5)) { rpmMessage(RPMMESS_DEBUG, " file changed - will save\n"); action = BACKUP; } else { rpmMessage(RPMMESS_DEBUG, " file unchanged - will remove\n"); } } } switch (action) { case KEEP: rpmMessage(RPMMESS_DEBUG, "keeping %s\n", file); break; case BACKUP: rpmMessage(RPMMESS_DEBUG, "saving %s as %s.rpmsave\n", file, file); if (!test) { newfile = alloca(strlen(file) + 20); strcpy(newfile, file); strcat(newfile, ".rpmsave"); if (rename(file, newfile)) { rpmError(RPMERR_RENAME, _("rename of %s to %s failed: %s"), file, newfile, strerror(errno)); rc = 1; } } break; case REMOVE: rpmMessage(RPMMESS_DEBUG, "%s - %s\n", file, rmmess); if (S_ISDIR(mode)) { if (!test) { if (rmdir(file)) { if (errno == ENOTEMPTY) rpmError(RPMERR_RMDIR, _("cannot remove %s - directory not empty"), file); else rpmError(RPMERR_RMDIR, _("rmdir of %s failed: %s"), file, strerror(errno)); rc = 1; } } } else { if (!test) { if (unlink(file)) { rpmError(RPMERR_UNLINK, _("removal of %s failed: %s"), file, strerror(errno)); rc = 1; } } } break; } } return 0; }