801db88416961d596ff6b6e70a4821a00908fe3a
[platform/upstream/rpm.git] / lib / uninstall.c
1 #include "config.h"
2
3 #if HAVE_ALLOCA_H
4 # include <alloca.h>
5 #endif 
6
7 #include <errno.h>
8 #include <fcntl.h>
9 #include <stdlib.h>
10 #include <string.h>
11 #include <sys/stat.h>
12 #include <sys/types.h>
13 #include <sys/wait.h>
14 #include <unistd.h>
15
16 #include "install.h"
17 #include "intl.h"
18 #include "messages.h"
19 #include "md5.h"
20 #include "misc.h"
21 #include "rpmdb.h"
22 #include "rpmlib.h"
23
24 static char * SCRIPT_PATH = "PATH=/sbin:/bin:/usr/sbin:/usr/bin:"
25                                          "/usr/X11R6/bin\nexport PATH\n";
26
27 enum fileActions { REMOVE, BACKUP, KEEP };
28
29 static int sharedFileCmp(const void * one, const void * two);
30 static int handleSharedFiles(rpmdb db, int offset, char ** fileList, 
31                              char ** fileMd5List, int fileCount, 
32                              enum fileActions * fileActions);
33 static int removeFile(char * file, char state, unsigned int flags, char * md5, 
34                       short mode, enum fileActions action, char * rmmess, 
35                       int brokenMd5, int test);
36
37 static int sharedFileCmp(const void * one, const void * two) {
38     if (((struct sharedFile *) one)->secRecOffset <
39         ((struct sharedFile *) two)->secRecOffset)
40         return -1;
41     else if (((struct sharedFile *) one)->secRecOffset ==
42         ((struct sharedFile *) two)->secRecOffset)
43         return 0;
44     else 
45         return 1;
46 }
47
48 int findSharedFiles(rpmdb db, int offset, char ** fileList, int fileCount,
49                     struct sharedFile ** listPtr, int * listCountPtr) {
50     int i, j;
51     struct sharedFile * list = NULL;
52     int itemsUsed = 0;
53     int itemsAllocated = 0;
54     dbiIndexSet matches;
55
56     itemsAllocated = 5;
57     list = malloc(sizeof(struct sharedFile) * itemsAllocated);
58
59     for (i = 0; i < fileCount; i++) {
60         if (!rpmdbFindByFile(db, fileList[i], &matches)) {
61             for (j = 0; j < matches.count; j++) {
62                 if (matches.recs[j].recOffset != offset) {
63                     if (itemsUsed == itemsAllocated) {
64                         itemsAllocated += 10;
65                         list = realloc(list, sizeof(struct sharedFile) * 
66                                             itemsAllocated);
67                     }
68                     list[itemsUsed].mainFileNumber = i;
69                     list[itemsUsed].secRecOffset = matches.recs[j].recOffset;
70                     list[itemsUsed].secFileNumber = matches.recs[j].fileNumber;
71                     itemsUsed++;
72                 }
73             }
74
75             dbiFreeIndexRecord(matches);
76         }
77     }
78
79     if (itemsUsed) {
80         qsort(list, itemsUsed, sizeof(struct sharedFile), sharedFileCmp);
81         *listPtr = list;
82         *listCountPtr = itemsUsed;
83     } else {
84         free(list);
85         *listPtr = NULL;
86         *listCountPtr = 0;
87     }
88
89     return 0;
90 }
91
92 static int handleSharedFiles(rpmdb db, int offset, char ** fileList, 
93                              char ** fileMd5List, int fileCount, 
94                              enum fileActions * fileActions) {
95     Header sech = NULL;
96     int secOffset = 0;
97     struct sharedFile * sharedList;
98     int sharedCount;
99     char * name, * version, * release;
100     int secFileCount;
101     char ** secFileMd5List, ** secFileList;
102     char * secFileStatesList;
103     int type;
104     int i;
105     int rc = 0;
106
107     if (findSharedFiles(db, offset, fileList, fileCount, &sharedList, 
108                         &sharedCount)) {
109         return 1;
110     }
111
112     if (!sharedCount) {
113         return 0;
114     }
115
116     for (i = 0; i < sharedCount; i++) {
117         if (secOffset != sharedList[i].secRecOffset) {
118             if (secOffset) {
119                 headerFree(sech);
120                 free(secFileMd5List);
121                 free(secFileList);
122             }
123
124             secOffset = sharedList[i].secRecOffset;
125             sech = rpmdbGetRecord(db, secOffset);
126             if (!sech) {
127                 rpmError(RPMERR_DBCORRUPT, 
128                          _("cannot read header at %d for uninstall"), offset);
129                 rc = 1;
130                 break;
131             }
132
133             headerGetEntry(sech, RPMTAG_NAME, &type, (void **) &name, 
134                      &secFileCount);
135             headerGetEntry(sech, RPMTAG_VERSION, &type, (void **) &version, 
136                      &secFileCount);
137             headerGetEntry(sech, RPMTAG_RELEASE, &type, (void **) &release, 
138                      &secFileCount);
139
140             rpmMessage(RPMMESS_DEBUG, 
141                         _("package %s-%s-%s contain shared files\n"), 
142                         name, version, release);
143
144             if (!headerGetEntry(sech, RPMTAG_FILENAMES, &type, 
145                           (void **) &secFileList, &secFileCount)) {
146                 rpmError(RPMERR_DBCORRUPT, "package %s contains no files",
147                       name);
148                 headerFree(sech);
149                 rc = 1;
150                 break;
151             }
152
153             headerGetEntry(sech, RPMTAG_FILESTATES, &type, 
154                      (void **) &secFileStatesList, &secFileCount);
155             headerGetEntry(sech, RPMTAG_FILEMD5S, &type, 
156                      (void **) &secFileMd5List, &secFileCount);
157         }
158
159         rpmMessage(RPMMESS_DEBUG, "file %s is shared\n",
160                 fileList[sharedList[i].mainFileNumber]);
161         
162         switch (secFileStatesList[sharedList[i].secFileNumber]) {
163           case RPMFILE_STATE_REPLACED:
164             rpmMessage(RPMMESS_DEBUG, "     file has already been replaced\n");
165             break;
166
167           case RPMFILE_STATE_NOTINSTALLED:
168             rpmMessage(RPMMESS_DEBUG, "     file was never installed\n");
169             break;
170     
171           case RPMFILE_STATE_NETSHARED:
172             rpmMessage(RPMMESS_DEBUG, "     file is netshared (so don't touch it)\n");
173             fileActions[sharedList[i].mainFileNumber] = KEEP;
174             break;
175     
176           case RPMFILE_STATE_NORMAL:
177             if (!strcmp(fileMd5List[sharedList[i].mainFileNumber],
178                         secFileMd5List[sharedList[i].secFileNumber])) {
179                 rpmMessage(RPMMESS_DEBUG, "    file is truely shared - saving\n");
180             }
181             fileActions[sharedList[i].mainFileNumber] = KEEP;
182             break;
183         }
184     }
185
186     if (secOffset) {
187         headerFree(sech);
188         free(secFileMd5List);
189         free(secFileList);
190     }
191     free(sharedList);
192
193     return rc;
194 }
195
196 int rpmRemovePackage(char * prefix, rpmdb db, unsigned int offset, int flags) {
197     Header h;
198     int i;
199     int fileCount;
200     char * rmmess, * name, * version, * release;
201     char * fnbuffer = NULL;
202     dbiIndexSet matches;
203     int fnbuffersize = 0;
204     int prefixLength = strlen(prefix);
205     char ** fileList, ** fileMd5List;
206     int type, count;
207     uint_32 * fileFlagsList;
208     int_16 * fileModesList;
209     char * fileStatesList;
210     enum { REMOVE, BACKUP, KEEP } * fileActions;
211     int scriptArg;
212
213     h = rpmdbGetRecord(db, offset);
214     if (!h) {
215         rpmError(RPMERR_DBCORRUPT, "cannot read header at %d for uninstall",
216               offset);
217         return 1;
218     }
219
220     headerGetEntry(h, RPMTAG_NAME, &type, (void **) &name,  &count);
221     headerGetEntry(h, RPMTAG_VERSION, &type, (void **) &version,  &count);
222     headerGetEntry(h, RPMTAG_RELEASE, &type, (void **) &release,  &count);
223     /* when we run scripts, we pass an argument which is the number of 
224        versions of this package that will be installed when we are finished */
225     if (rpmdbFindPackage(db, name, &matches)) {
226         rpmError(RPMERR_DBCORRUPT, "cannot read packages named %s for uninstall",
227               name);
228         return 1;
229     }
230  
231     scriptArg = matches.count - 1;
232     dbiFreeIndexRecord(matches);
233
234     if (flags & RPMUNINSTALL_TEST) {
235         rmmess = "would remove";
236     } else {
237         rmmess = "removing";
238     }
239
240     rpmMessage(RPMMESS_DEBUG, "running preuninstall script (if any)\n");
241
242     if (runScript(prefix, h, RPMTAG_PREUN, scriptArg, 
243                  flags & RPMUNINSTALL_NOSCRIPTS)) {
244         headerFree(h);
245         return 1;
246     }
247     
248     rpmMessage(RPMMESS_DEBUG, "%s files test = %d\n", rmmess, flags & RPMUNINSTALL_TEST);
249     if (headerGetEntry(h, RPMTAG_FILENAMES, &type, (void **) &fileList, 
250          &fileCount)) {
251         if (prefix[0]) {
252             fnbuffersize = 1024;
253             fnbuffer = alloca(fnbuffersize);
254         }
255
256         headerGetEntry(h, RPMTAG_FILESTATES, &type, (void **) &fileStatesList, 
257                  &fileCount);
258         headerGetEntry(h, RPMTAG_FILEMD5S, &type, (void **) &fileMd5List, 
259                  &fileCount);
260         headerGetEntry(h, RPMTAG_FILEFLAGS, &type, (void **) &fileFlagsList, 
261                  &fileCount);
262         headerGetEntry(h, RPMTAG_FILEMODES, &type, (void **) &fileModesList, 
263                  &fileCount);
264
265         fileActions = alloca(sizeof(*fileActions) * fileCount);
266         for (i = 0; i < fileCount; i++) 
267             if (fileStatesList[i] == RPMFILE_STATE_NOTINSTALLED ||
268                 fileStatesList[i] == RPMFILE_STATE_NETSHARED) 
269                 fileActions[i] = KEEP;
270             else
271                 fileActions[i] = REMOVE;
272
273         handleSharedFiles(db, offset, fileList, fileMd5List, fileCount, fileActions);
274
275         /* go through the filelist backwards to help insure that rmdir()
276            will work */
277         for (i = fileCount - 1; i >= 0; i--) {
278             if (strcmp(prefix, "/")) {
279                 if ((strlen(fileList[i]) + prefixLength + 1) > fnbuffersize) {
280                     fnbuffersize = (strlen(fileList[i]) + prefixLength) * 2;
281                     fnbuffer = alloca(fnbuffersize);
282                 }
283                 strcpy(fnbuffer, prefix);
284                 strcat(fnbuffer, "/");
285                 strcat(fnbuffer, fileList[i]);
286             } else {
287                 fnbuffer = fileList[i];
288             }
289
290             removeFile(fnbuffer, fileStatesList[i], fileFlagsList[i],
291                        fileMd5List[i], fileModesList[i], fileActions[i], 
292                        rmmess, !headerIsEntry(h, RPMTAG_RPMVERSION),
293                        flags & RPMUNINSTALL_TEST);
294         }
295
296         free(fileList);
297         free(fileMd5List);
298     }
299
300     rpmMessage(RPMMESS_DEBUG, "running postuninstall script (if any)\n");
301     runScript(prefix, h, RPMTAG_POSTUN, scriptArg, flags & RPMUNINSTALL_NOSCRIPTS);
302
303     headerFree(h);
304
305     rpmMessage(RPMMESS_DEBUG, "%s database entry\n", rmmess);
306     if (!(flags & RPMUNINSTALL_TEST))
307         rpmdbRemove(db, offset, 0);
308
309     return 0;
310 }
311
312 int runScript(char * prefix, Header h, int tag, int arg, int norunScripts) {
313     int count, type;
314     char * script;
315     char * fn;
316     int fd;
317     int isdebug = rpmIsDebug();
318     int child;
319     int status;
320     char upgradeArg[20];
321     char * installPrefix = NULL;
322     char * installPrefixEnv = NULL;
323
324     sprintf(upgradeArg, "%d", arg);
325
326     if (norunScripts) return 0;
327
328     if (headerGetEntry(h, tag, &type, (void **) &script, &count)) {
329         if (headerGetEntry(h, RPMTAG_INSTALLPREFIX, &type, (void **) &installPrefix,
330                      &count)) {
331             installPrefixEnv = alloca(strlen(installPrefix) + 30);
332             strcpy(installPrefixEnv, "RPM_INSTALL_PREFIX=");
333             strcat(installPrefixEnv, installPrefix);
334         }
335
336         fn = tmpnam(NULL);
337         rpmMessage(RPMMESS_DEBUG, "script found - running from file %s\n", fn);
338         fd = open(fn, O_CREAT | O_RDWR);
339         if (!isdebug) unlink(fn);
340         if (fd < 0) {
341             rpmError(RPMERR_SCRIPT, 
342                         _("error creating file for (un)install script"));
343             return 1;
344         }
345         write(fd, SCRIPT_PATH, strlen(SCRIPT_PATH));
346         write(fd, script, strlen(script));
347         
348         /* run the script via /bin/sh - just feed the commands to the
349            shell as stdin */
350         if (!(child = fork())) {
351             if (installPrefixEnv) {
352                 doputenv(installPrefixEnv);
353             }
354
355             lseek(fd, 0, SEEK_SET);
356             close(0);
357             dup2(fd, 0);
358             close(fd);
359
360             if (strcmp(prefix, "/")) {
361                 rpmMessage(RPMMESS_DEBUG, "performing chroot(%s)\n", prefix);
362                 chroot(prefix);
363                 chdir("/");
364             }
365
366             if (isdebug)
367                 execl("/bin/sh", "/bin/sh", "-xs", upgradeArg, NULL);
368             else
369                 execl("/bin/sh", "/bin/sh", "-s", upgradeArg, NULL);
370             _exit(-1);
371         }
372         close(fd);
373         waitpid(child, &status, 0);
374
375         if (!WIFEXITED(status) || WEXITSTATUS(status)) {
376             rpmError(RPMERR_SCRIPT, _("execution of script failed"));
377             return 1;
378         }
379     }
380
381     return 0;
382 }
383
384 static int removeFile(char * file, char state, unsigned int flags, char * md5, 
385                       short mode, enum fileActions action, char * rmmess, 
386                       int brokenMd5, int test) {
387     char currentMd5[40];
388     int rc = 0;
389     char * newfile;
390         
391     switch (state) {
392       case RPMFILE_STATE_REPLACED:
393         rpmMessage(RPMMESS_DEBUG, "%s has already been replaced\n", file);
394         break;
395
396       case RPMFILE_STATE_NORMAL:
397         if ((action == REMOVE) && (flags & RPMFILE_CONFIG)) {
398             /* if it's a config file, we may not want to remove it */
399             rpmMessage(RPMMESS_DEBUG, "finding md5sum of %s\n", file);
400             if (brokenMd5)
401                 rc = mdfileBroken(file, currentMd5);
402             else
403                 rc = mdfile(file, currentMd5);
404
405             if (mdfile(file, currentMd5)) 
406                 rpmMessage(RPMMESS_DEBUG, 
407                                 "    failed - assuming file removed\n");
408             else {
409                 if (strcmp(currentMd5, md5)) {
410                     rpmMessage(RPMMESS_DEBUG, "    file changed - will save\n");
411                     action = BACKUP;
412                 } else {
413                     rpmMessage(RPMMESS_DEBUG, 
414                                 "    file unchanged - will remove\n");
415                 }
416             }
417         }
418
419         switch (action) {
420
421           case KEEP:
422             rpmMessage(RPMMESS_DEBUG, "keeping %s\n", file);
423             break;
424
425           case BACKUP:
426             rpmMessage(RPMMESS_DEBUG, "saving %s as %s.rpmsave\n", file, file);
427             if (!test) {
428                 newfile = alloca(strlen(file) + 20);
429                 strcpy(newfile, file);
430                 strcat(newfile, ".rpmsave");
431                 if (rename(file, newfile)) {
432                     rpmError(RPMERR_RENAME, _("rename of %s to %s failed: %s"),
433                                 file, newfile, strerror(errno));
434                     rc = 1;
435                 }
436             }
437             break;
438
439           case REMOVE:
440             rpmMessage(RPMMESS_DEBUG, "%s - %s\n", file, rmmess);
441             if (S_ISDIR(mode)) {
442                 if (!test) {
443                     if (rmdir(file)) {
444                         if (errno == ENOTEMPTY)
445                             rpmError(RPMERR_RMDIR, 
446                                 _("cannot remove %s - directory not empty"), 
447                                 file);
448                         else
449                             rpmError(RPMERR_RMDIR, _("rmdir of %s failed: %s"),
450                                         file, strerror(errno));
451                         rc = 1;
452                     }
453                 }
454             } else {
455                 if (!test) {
456                     if (unlink(file)) {
457                         rpmError(RPMERR_UNLINK, _("removal of %s failed: %s"),
458                                     file, strerror(errno));
459                         rc = 1;
460                     }
461                 }
462             }
463             break;
464         }
465    }
466  
467    return 0;
468 }