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