A few final changes for the 3.0.6 release
[pacman.git] / lib / libalpm / conflict.c
blob3ab4ee71380d4a722054fb37e999c82f3c9515fb
1 /*
2 * conflict.c
3 *
4 * Copyright (c) 2002-2006 by Judd Vinet <jvinet@zeroflux.org>
5 * Copyright (c) 2005 by Aurelien Foret <orelien@chez.com>
6 * Copyright (c) 2006 by David Kimpe <dnaku@frugalware.org>
7 * Copyright (c) 2006 by Miklos Vajna <vmiklos@frugalware.org>
8 * Copyright (c) 2006 by Christian Hamar <krics@linuxforum.hu>
9 *
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, write to the Free Software
22 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
23 * USA.
26 #include "config.h"
28 #include <stdlib.h>
29 #include <stdio.h>
30 #include <unistd.h>
31 #include <string.h>
32 #include <limits.h>
33 #if defined(__APPLE__) || defined(__OpenBSD__)
34 #include <sys/syslimits.h>
35 #endif
36 #include <sys/stat.h>
37 #include <libintl.h>
39 /* libalpm */
40 #include "conflict.h"
41 #include "alpm_list.h"
42 #include "handle.h"
43 #include "trans.h"
44 #include "util.h"
45 #include "error.h"
46 #include "log.h"
47 #include "cache.h"
48 #include "deps.h"
51 /** See if potential conflict 'name' matches package 'pkg'.
52 * @param target the name of the parent package we're checking
53 * @param depname the name of the dependency we're checking
54 * @param pkg the package to check
55 * @param conflict the name of the possible conflict
56 * @return A depmissing struct indicating the conflict
57 * @note The first two paramters are here to simplify the addition
58 * of new 'depmiss' objects.
60 * TODO WTF is a 'depmissing' doing indicating a conflict??
62 static pmdepmissing_t *does_conflict(const char *target, const char *depname,
63 pmpkg_t *pkg, const char *conflict)
65 alpm_list_t *i;
67 /* check the actual package name, easy */
68 if(strcmp(alpm_pkg_get_name(pkg), conflict) == 0) {
69 _alpm_log(PM_LOG_DEBUG, _(" found conflict '%s' : package '%s'"), conflict, target);
70 return(_alpm_depmiss_new(target, PM_DEP_TYPE_CONFLICT,
71 PM_DEP_MOD_ANY, depname, NULL));
72 } else {
73 /* check what this package provides, harder */
74 for(i = alpm_pkg_get_provides(pkg); i; i = i->next) {
75 const char *provision = i->data;
77 if(strcmp(provision, conflict) == 0) {
78 _alpm_log(PM_LOG_DEBUG, _(" found conflict '%s' : package '%s' provides '%s'"),
79 conflict, target, provision);
80 return(_alpm_depmiss_new(target, PM_DEP_TYPE_CONFLICT,
81 PM_DEP_MOD_ANY, depname, NULL));
85 return(NULL); /* not a conflict */
88 static alpm_list_t *chk_pkg_vs_db(alpm_list_t *baddeps, pmpkg_t *pkg, pmdb_t *db)
90 pmdepmissing_t *miss = NULL;
91 const char *pkgname;
92 alpm_list_t *i, *j;
94 pkgname = alpm_pkg_get_name(pkg);
96 for(i = alpm_pkg_get_conflicts(pkg); i; i = i->next) {
97 const char *conflict = i->data;
99 if(strcmp(pkgname, conflict) == 0) {
100 /* a package cannot conflict with itself -- that's just not nice */
101 _alpm_log(PM_LOG_DEBUG, _("package '%s' conflicts with itself - packaging error"),
102 pkgname);
103 continue;
106 /* CHECK 1: check targets against database */
107 _alpm_log(PM_LOG_DEBUG, _("checkconflicts: target '%s' vs db"), pkgname);
109 for(j = _alpm_db_get_pkgcache(db); j; j = j->next) {
110 pmpkg_t *dbpkg = j->data;
112 if(strcmp(alpm_pkg_get_name(dbpkg), pkgname) == 0) {
113 /* skip the package we're currently processing */
114 continue;
117 miss = does_conflict(pkgname, alpm_pkg_get_name(dbpkg), dbpkg, conflict);
118 if(miss && !_alpm_depmiss_isin(miss, baddeps)) {
119 baddeps = alpm_list_add(baddeps, miss);
120 } else {
121 FREE(miss);
125 return(baddeps);
128 static alpm_list_t *chk_pkg_vs_targets(alpm_list_t *baddeps,
129 pmpkg_t *pkg, pmdb_t *db,
130 alpm_list_t *targets)
132 pmdepmissing_t *miss = NULL;
133 const char *pkgname;
134 alpm_list_t *i, *j;
136 pkgname = alpm_pkg_get_name(pkg);
138 for(i = alpm_pkg_get_conflicts(pkg); i; i = i->next) {
139 const char *conflict = i->data;
141 if(strcmp(pkgname, conflict) == 0) {
142 /* a package cannot conflict with itself -- that's just not nice */
143 _alpm_log(PM_LOG_DEBUG, _("package '%s' conflicts with itself - packaging error"),
144 pkgname);
145 continue;
148 /* CHECK 2: check targets against targets */
149 _alpm_log(PM_LOG_DEBUG, _("checkconflicts: target '%s' vs all targets"), pkgname);
151 for(j = targets; j; j = j->next) {
152 const char *targetname;
153 pmpkg_t *target = j->data;
154 targetname = alpm_pkg_get_name(target);
156 if(strcmp(targetname, pkgname) == 0) {
157 /* skip the package we're currently processing */
158 continue;
161 miss = does_conflict(pkgname, targetname, target, conflict);
162 if(miss && !_alpm_depmiss_isin(miss, baddeps)) {
163 baddeps = alpm_list_add(baddeps, miss);
164 } else {
165 FREE(miss);
169 return(baddeps);
172 static alpm_list_t *chk_db_vs_targets(alpm_list_t *baddeps, pmpkg_t *pkg,
173 pmdb_t *db, alpm_list_t *targets)
175 pmdepmissing_t *miss = NULL;
176 const char *pkgname;
177 alpm_list_t *i, *j;
179 pkgname = alpm_pkg_get_name(pkg);
181 _alpm_log(PM_LOG_DEBUG, _("checkconflicts: db vs target '%s'"), pkgname);
183 for(i = _alpm_db_get_pkgcache(db); i; i = i->next) {
184 alpm_list_t *conflicts = NULL;
185 const char *dbpkgname;
187 pmpkg_t *dbpkg = i->data;
188 dbpkgname = alpm_pkg_get_name(dbpkg);
190 if(strcmp(dbpkgname, pkgname) == 0) {
191 /* skip the package we're currently processing */
192 continue;
195 /* is this db package in the targets? if so use the
196 * new package's conflict list to pick up new changes */
197 int use_newconflicts = 0;
198 for(j = targets; j; j = j->next) {
199 pmpkg_t *targ = j->data;
200 if(strcmp(alpm_pkg_get_name(targ), dbpkgname) == 0) {
201 _alpm_log(PM_LOG_DEBUG, _("target '%s' is also in target list, using NEW conflicts"),
202 dbpkgname);
203 conflicts = alpm_pkg_get_conflicts(targ);
204 use_newconflicts = 1;
205 break;
208 /* if we didn't find newer conflicts, use the original list */
209 if(!use_newconflicts) {
210 conflicts = alpm_pkg_get_conflicts(dbpkg);
213 for(j = conflicts; j; j = j->next) {
214 const char *conflict = j->data;
217 miss = does_conflict(pkgname, dbpkgname, pkg, conflict);
218 if(miss && !_alpm_depmiss_isin(miss, baddeps)) {
219 baddeps = alpm_list_add(baddeps, miss);
220 } else {
221 FREE(miss);
225 return(baddeps);
228 /* Returns a alpm_list_t* of pmdepmissing_t pointers.
230 * conflicts are always name only
232 alpm_list_t *_alpm_checkconflicts(pmdb_t *db, alpm_list_t *packages)
234 alpm_list_t *i, *baddeps = NULL;
236 ALPM_LOG_FUNC;
238 if(db == NULL) {
239 return(NULL);
242 for(i = packages; i; i = i->next) {
243 pmpkg_t *pkg = i->data;
244 if(pkg == NULL) {
245 continue;
248 /* run three different conflict checks on each package */
249 baddeps = chk_pkg_vs_db(baddeps, pkg, db);
250 baddeps = chk_pkg_vs_targets(baddeps, pkg, db, packages);
251 baddeps = chk_db_vs_targets(baddeps, pkg, db, packages);
254 /* debug loop */
255 for(i = baddeps; i; i = i->next) {
256 pmdepmissing_t *miss = i->data;
257 _alpm_log(PM_LOG_DEBUG, _("\tCONFLICTS:: %s conflicts with %s"), miss->target, miss->depend.name);
260 return(baddeps);
264 /* Returns a alpm_list_t* of file conflicts.
265 * Hooray for set-intersects!
266 * Pre-condition: both lists are sorted!
268 static alpm_list_t *chk_fileconflicts(alpm_list_t *filesA, alpm_list_t *filesB)
270 alpm_list_t *ret = NULL;
271 alpm_list_t *pA = filesA, *pB = filesB;
273 while(pA && pB) {
274 const char *strA = pA->data;
275 const char *strB = pB->data;
276 /* skip directories, we don't care about dir conflicts */
277 if(strA[strlen(strA)-1] == '/') {
278 pA = pA->next;
279 } else if(strB[strlen(strB)-1] == '/') {
280 pB = pB->next;
281 } else {
282 int cmp = strcmp(strA, strB);
283 if(cmp < 0) {
284 /* item only in filesA, ignore it */
285 pA = pA->next;
286 } else if(cmp > 0) {
287 /* item only in filesB, ignore it */
288 pB = pB->next;
289 } else {
290 /* item in both, record it */
291 ret = alpm_list_add(ret, strdup(strA));
292 pA = pA->next;
293 pB = pB->next;
298 return(ret);
301 /* Returns a alpm_list_t* of files that are in filesA but *NOT* in filesB
302 * This is an 'A minus B' set operation
303 * Pre-condition: both lists are sorted!
305 static alpm_list_t *chk_filedifference(alpm_list_t *filesA, alpm_list_t *filesB)
307 alpm_list_t *ret = NULL;
308 alpm_list_t *pA = filesA, *pB = filesB;
310 while(pA && pB) {
311 const char *strA = pA->data;
312 const char *strB = pB->data;
313 /* skip directories, we don't care about dir conflicts */
314 if(strA[strlen(strA)-1] == '/') {
315 pA = pA->next;
316 } else if(strB[strlen(strB)-1] == '/') {
317 pB = pB->next;
318 } else {
319 int cmp = strcmp(strA, strB);
320 if(cmp < 0) {
321 /* item only in filesA, record it */
322 ret = alpm_list_add(ret, strdup(strA));
323 pA = pA->next;
324 } else if(cmp > 0) {
325 /* item only in fileB, but this means nothing */
326 pB = pB->next;
327 } else {
328 /* item in both, ignore it */
329 pA = pA->next;
330 pB = pB->next;
335 return(ret);
338 /* Adds pmconflict_t to a conflicts list. Pass the conflicts list, type (either
339 * PM_CONFLICT_TYPE_TARGET or PM_CONFLICT_TYPE_FILE), a file string, and either
340 * two package names or one package name and NULL. This is a wrapper for former
341 * functionality that was done inline.
343 static alpm_list_t *add_fileconflict(alpm_list_t *conflicts,
344 pmconflicttype_t type, const char *filestr,
345 const char* name1, const char* name2)
347 pmconflict_t *conflict = malloc(sizeof(pmconflict_t));
348 if(conflict == NULL) {
349 _alpm_log(PM_LOG_ERROR, _("malloc failure: could not allocate %d bytes"),
350 sizeof(pmconflict_t));
351 return(conflicts);
353 conflict->type = type;
354 STRNCPY(conflict->target, name1, PKG_NAME_LEN);
355 STRNCPY(conflict->file, filestr, CONFLICT_FILE_LEN);
356 if(name2) {
357 STRNCPY(conflict->ctarget, name2, PKG_NAME_LEN);
358 } else {
359 conflict->ctarget[0] = '\0';
362 conflicts = alpm_list_add(conflicts, conflict);
363 _alpm_log(PM_LOG_DEBUG, "found file conflict %s, packages %s and %s",
364 filestr, name1, name2 ? name2 : "(filesystem)");
366 return(conflicts);
369 /* Find file conflicts that may occur during the transaction with two checks:
370 * 1: check every target against every target
371 * 2: check every target against the filesystem */
372 alpm_list_t *_alpm_db_find_conflicts(pmdb_t *db, pmtrans_t *trans, char *root)
374 alpm_list_t *i, *conflicts = NULL;
375 alpm_list_t *targets = trans->packages;
376 int numtargs = alpm_list_count(targets);
378 ALPM_LOG_FUNC;
380 if(db == NULL || targets == NULL || root == NULL) {
381 return(NULL);
384 for(i = targets; i; i = i->next) {
385 alpm_list_t *j, *k, *tmpfiles = NULL;
386 pmpkg_t *p1, *p2, *dbpkg;
387 char path[PATH_MAX+1];
389 p1 = i->data;
390 if(!p1) {
391 continue;
394 double percent = (double)(alpm_list_count(targets) - alpm_list_count(i) + 1)
395 / alpm_list_count(targets);
396 PROGRESS(trans, PM_TRANS_PROGRESS_CONFLICTS_START, "", (percent * 100),
397 numtargs, (numtargs - alpm_list_count(i) +1));
398 /* CHECK 1: check every target against every target */
399 for(j = i->next; j; j = j->next) {
400 p2 = j->data;
401 if(!p2) {
402 continue;
404 _alpm_log(PM_LOG_DEBUG, "searching for file conflicts: %s and %s",
405 alpm_pkg_get_name(p1), alpm_pkg_get_name(p2));
406 tmpfiles = chk_fileconflicts(alpm_pkg_get_files(p1), alpm_pkg_get_files(p2));
408 if(tmpfiles) {
409 for(k = tmpfiles; k; k = k->next) {
410 snprintf(path, PATH_MAX, "%s%s", root, (char *)k->data);
411 conflicts = add_fileconflict(conflicts, PM_CONFLICT_TYPE_TARGET, path,
412 alpm_pkg_get_name(p1), alpm_pkg_get_name(p2));
414 alpm_list_free_inner(tmpfiles, &free);
415 alpm_list_free(tmpfiles);
419 /* declarations for second check */
420 struct stat buf;
421 char *filestr = NULL;
423 /* CHECK 2: check every target against the filesystem */
424 _alpm_log(PM_LOG_DEBUG, "searching for filesystem conflicts: %s", p1->name);
425 dbpkg = _alpm_db_get_pkgfromcache(db, p1->name);
427 /* Do two different checks here. f the package is currently installed,
428 * then only check files that are new in the new package. If the package
429 * is not currently installed, then simply stat the whole filelist */
430 if(dbpkg) {
431 /* older ver of package currently installed */
432 tmpfiles = chk_filedifference(alpm_pkg_get_files(p1), alpm_pkg_get_files(dbpkg));
433 } else {
434 /* no version of package currently installed */
435 tmpfiles = alpm_list_strdup(alpm_pkg_get_files(p1));
438 /* loop over each file to be installed */
439 for(j = tmpfiles; j; j = j->next) {
440 filestr = j->data;
442 snprintf(path, PATH_MAX, "%s%s", root, filestr);
444 /* stat the file - if it exists and is not a dir, do some checks */
445 if(lstat(path, &buf) == 0 && !S_ISDIR(buf.st_mode)) {
446 _alpm_log(PM_LOG_DEBUG, "checking possible conflict: %s", path);
448 /* Make sure the possible conflict is not a symlink that points to a
449 * path in the old package. This is kind of dirty with inode usage */
450 if(dbpkg) {
451 struct stat buf2;
452 char str[PATH_MAX+1];
453 unsigned ok = 0;
454 for(k = dbpkg->files; k; k = k->next) {
455 snprintf(str, PATH_MAX, "%s%s", root, (char*)k->data);
456 lstat(str, &buf2);
457 if(buf.st_ino == buf2.st_ino) {
458 ok = 1;
459 _alpm_log(PM_LOG_DEBUG, "conflict was a symlink: %s", path);
460 break;
463 if(ok == 1) {
464 continue;
468 /* Look at all the targets to see if file has changed hands */
469 int resolved_conflict = 0; /* have we acted on this conflict? */
470 for(k = targets; k; k = k->next) {
471 p2 = k->data;
472 if(!p2 || strcmp(p1->name, p2->name) == 0) {
473 continue;
476 pmpkg_t *localp2 = _alpm_db_get_pkgfromcache(db, p2->name);
478 /* Check if it used to exist in a package, but doesn't anymore */
479 alpm_list_t *pkgfiles, *localfiles; /* added for readability */
480 pkgfiles = alpm_pkg_get_files(p2);
481 localfiles = alpm_pkg_get_files(localp2);
483 if(localp2 && !alpm_list_find_str(pkgfiles, filestr)
484 && alpm_list_find_str(localfiles, filestr)) {
485 /* check if the file is now in the backup array */
486 if(alpm_list_find_str(alpm_pkg_get_backup(p1), filestr)) {
487 /* keep file intact if it is in backup array */
488 trans->skip_add = alpm_list_add(trans->skip_add, strdup(path));
489 trans->skip_remove = alpm_list_add(trans->skip_remove, strdup(path));
490 _alpm_log(PM_LOG_DEBUG, "file in backup array, adding to add and remove skiplist: %s", filestr);
491 resolved_conflict = 1;
492 break;
493 } else {
494 /* skip removal of file, but not add. this will prevent a second
495 * package from removing the file when it was already installed
496 * by its new owner */
497 trans->skip_remove = alpm_list_add(trans->skip_remove, strdup(path));
498 _alpm_log(PM_LOG_DEBUG, "file changed packages, adding to remove skiplist: %s", filestr);
499 resolved_conflict = 1;
500 break;
504 if(!resolved_conflict) {
505 _alpm_log(PM_LOG_DEBUG, "file found in conflict: %s", path);
506 conflicts = add_fileconflict(conflicts, PM_CONFLICT_TYPE_FILE,
507 path, p1->name, NULL);
509 } else {
510 _alpm_log(PM_LOG_DEBUG, "%s is a directory, not a conflict", path);
513 alpm_list_free_inner(tmpfiles, &free);
514 alpm_list_free(tmpfiles);
517 return(conflicts);
520 const char SYMEXPORT *alpm_conflict_get_target(pmconflict_t *conflict)
522 ALPM_LOG_FUNC;
524 /* Sanity checks */
525 ASSERT(handle != NULL, return(NULL));
526 ASSERT(conflict != NULL, return(NULL));
528 return conflict->target;
531 pmconflicttype_t SYMEXPORT alpm_conflict_get_type(pmconflict_t *conflict)
533 ALPM_LOG_FUNC;
535 /* Sanity checks */
536 ASSERT(handle != NULL, return(-1));
537 ASSERT(conflict != NULL, return(-1));
539 return conflict->type;
542 const char SYMEXPORT *alpm_conflict_get_file(pmconflict_t *conflict)
544 ALPM_LOG_FUNC;
546 /* Sanity checks */
547 ASSERT(handle != NULL, return(NULL));
548 ASSERT(conflict != NULL, return(NULL));
550 return conflict->file;
553 const char SYMEXPORT *alpm_conflict_get_ctarget(pmconflict_t *conflict)
555 ALPM_LOG_FUNC;
557 /* Sanity checks */
558 ASSERT(handle != NULL, return(NULL));
559 ASSERT(conflict != NULL, return(NULL));
561 return conflict->ctarget;
563 /* vim: set ts=2 sw=2 noet: */