libalpm
Arch Linux Package Manager Library
|
00001 /* 00002 * conflict.c 00003 * 00004 * Copyright (c) 2006-2011 Pacman Development Team <pacman-dev@archlinux.org> 00005 * Copyright (c) 2002-2006 by Judd Vinet <jvinet@zeroflux.org> 00006 * Copyright (c) 2005 by Aurelien Foret <orelien@chez.com> 00007 * Copyright (c) 2006 by David Kimpe <dnaku@frugalware.org> 00008 * Copyright (c) 2006 by Miklos Vajna <vmiklos@frugalware.org> 00009 * Copyright (c) 2006 by Christian Hamar <krics@linuxforum.hu> 00010 * 00011 * This program is free software; you can redistribute it and/or modify 00012 * it under the terms of the GNU General Public License as published by 00013 * the Free Software Foundation; either version 2 of the License, or 00014 * (at your option) any later version. 00015 * 00016 * This program is distributed in the hope that it will be useful, 00017 * but WITHOUT ANY WARRANTY; without even the implied warranty of 00018 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00019 * GNU General Public License for more details. 00020 * 00021 * You should have received a copy of the GNU General Public License 00022 * along with this program. If not, see <http://www.gnu.org/licenses/>. 00023 */ 00024 00025 #include <stdlib.h> 00026 #include <stdio.h> 00027 #include <string.h> 00028 #include <limits.h> 00029 #include <sys/stat.h> 00030 #include <dirent.h> 00031 00032 /* libalpm */ 00033 #include "conflict.h" 00034 #include "alpm_list.h" 00035 #include "alpm.h" 00036 #include "handle.h" 00037 #include "trans.h" 00038 #include "util.h" 00039 #include "log.h" 00040 #include "deps.h" 00041 00042 static alpm_conflict_t *conflict_new(alpm_pkg_t *pkg1, alpm_pkg_t *pkg2, 00043 alpm_depend_t *reason) 00044 { 00045 alpm_conflict_t *conflict; 00046 00047 MALLOC(conflict, sizeof(alpm_conflict_t), return NULL); 00048 00049 conflict->package1_hash = pkg1->name_hash; 00050 conflict->package2_hash = pkg2->name_hash; 00051 STRDUP(conflict->package1, pkg1->name, return NULL); 00052 STRDUP(conflict->package2, pkg2->name, return NULL); 00053 conflict->reason = reason; 00054 00055 return conflict; 00056 } 00057 00058 void _alpm_conflict_free(alpm_conflict_t *conflict) 00059 { 00060 FREE(conflict->package2); 00061 FREE(conflict->package1); 00062 FREE(conflict); 00063 } 00064 00065 alpm_conflict_t *_alpm_conflict_dup(const alpm_conflict_t *conflict) 00066 { 00067 alpm_conflict_t *newconflict; 00068 CALLOC(newconflict, 1, sizeof(alpm_conflict_t), return NULL); 00069 00070 newconflict->package1_hash = conflict->package1_hash; 00071 newconflict->package2_hash = conflict->package2_hash; 00072 STRDUP(newconflict->package1, conflict->package1, return NULL); 00073 STRDUP(newconflict->package2, conflict->package2, return NULL); 00074 newconflict->reason = conflict->reason; 00075 00076 return newconflict; 00077 } 00078 00079 static int conflict_isin(alpm_conflict_t *needle, alpm_list_t *haystack) 00080 { 00081 alpm_list_t *i; 00082 for(i = haystack; i; i = i->next) { 00083 alpm_conflict_t *conflict = i->data; 00084 if(needle->package1_hash == conflict->package1_hash 00085 && needle->package2_hash == conflict->package2_hash 00086 && strcmp(needle->package1, conflict->package1) == 0 00087 && strcmp(needle->package2, conflict->package2) == 0) { 00088 return 1; 00089 } 00090 } 00091 00092 return 0; 00093 } 00094 00095 /** Adds the pkg1/pkg2 conflict to the baddeps list. 00096 * @param handle the context handle 00097 * @param baddeps list to add conflict to 00098 * @param pkg1 first package 00099 * @param pkg2 package causing conflict 00100 * @param reason reason for this conflict 00101 */ 00102 static int add_conflict(alpm_handle_t *handle, alpm_list_t **baddeps, 00103 alpm_pkg_t *pkg1, alpm_pkg_t *pkg2, alpm_depend_t *reason) 00104 { 00105 alpm_conflict_t *conflict = conflict_new(pkg1, pkg2, reason); 00106 if(!conflict) { 00107 return -1; 00108 } 00109 if(!conflict_isin(conflict, *baddeps)) { 00110 char *conflict_str = alpm_dep_compute_string(reason); 00111 *baddeps = alpm_list_add(*baddeps, conflict); 00112 _alpm_log(handle, ALPM_LOG_DEBUG, "package %s conflicts with %s (by %s)\n", 00113 pkg1->name, pkg2->name, conflict_str); 00114 free(conflict_str); 00115 } else { 00116 _alpm_conflict_free(conflict); 00117 } 00118 return 0; 00119 } 00120 00121 /** Check if packages from list1 conflict with packages from list2. 00122 * This looks at the conflicts fields of all packages from list1, and sees 00123 * if they match packages from list2. 00124 * If a conflict (pkg1, pkg2) is found, it is added to the baddeps list 00125 * in this order if order >= 0, or reverse order (pkg2,pkg1) otherwise. 00126 * 00127 * @param handle the context handle 00128 * @param list1 first list of packages 00129 * @param list2 second list of packages 00130 * @param baddeps list to store conflicts 00131 * @param order if >= 0 the conflict order is preserved, if < 0 it's reversed 00132 */ 00133 static void check_conflict(alpm_handle_t *handle, 00134 alpm_list_t *list1, alpm_list_t *list2, 00135 alpm_list_t **baddeps, int order) { 00136 alpm_list_t *i; 00137 00138 if(!baddeps) { 00139 return; 00140 } 00141 for(i = list1; i; i = i->next) { 00142 alpm_pkg_t *pkg1 = i->data; 00143 alpm_list_t *j; 00144 00145 for(j = alpm_pkg_get_conflicts(pkg1); j; j = j->next) { 00146 alpm_depend_t *conflict = j->data; 00147 alpm_list_t *k; 00148 00149 for(k = list2; k; k = k->next) { 00150 alpm_pkg_t *pkg2 = k->data; 00151 00152 if(pkg1->name_hash == pkg2->name_hash 00153 && strcmp(pkg1->name, pkg2->name) == 0) { 00154 /* skip the package we're currently processing */ 00155 continue; 00156 } 00157 00158 if(_alpm_depcmp(pkg2, conflict)) { 00159 if(order >= 0) { 00160 add_conflict(handle, baddeps, pkg1, pkg2, conflict); 00161 } else { 00162 add_conflict(handle, baddeps, pkg2, pkg1, conflict); 00163 } 00164 } 00165 } 00166 } 00167 } 00168 } 00169 00170 /* Check for inter-conflicts */ 00171 alpm_list_t *_alpm_innerconflicts(alpm_handle_t *handle, alpm_list_t *packages) 00172 { 00173 alpm_list_t *baddeps = NULL; 00174 00175 _alpm_log(handle, ALPM_LOG_DEBUG, "check targets vs targets\n"); 00176 check_conflict(handle, packages, packages, &baddeps, 0); 00177 00178 return baddeps; 00179 } 00180 00181 /* Check for target vs (db - target) conflicts */ 00182 alpm_list_t *_alpm_outerconflicts(alpm_db_t *db, alpm_list_t *packages) 00183 { 00184 alpm_list_t *baddeps = NULL; 00185 00186 if(db == NULL) { 00187 return NULL; 00188 } 00189 00190 alpm_list_t *dblist = alpm_list_diff(_alpm_db_get_pkgcache(db), 00191 packages, _alpm_pkg_cmp); 00192 00193 /* two checks to be done here for conflicts */ 00194 _alpm_log(db->handle, ALPM_LOG_DEBUG, "check targets vs db\n"); 00195 check_conflict(db->handle, packages, dblist, &baddeps, 1); 00196 _alpm_log(db->handle, ALPM_LOG_DEBUG, "check db vs targets\n"); 00197 check_conflict(db->handle, dblist, packages, &baddeps, -1); 00198 00199 alpm_list_free(dblist); 00200 return baddeps; 00201 } 00202 00203 /** Check the package conflicts in a database 00204 * 00205 * @param handle the context handle 00206 * @param pkglist the list of packages to check 00207 * @return an alpm_list_t of alpm_conflict_t 00208 */ 00209 alpm_list_t SYMEXPORT *alpm_checkconflicts(alpm_handle_t *handle, 00210 alpm_list_t *pkglist) 00211 { 00212 CHECK_HANDLE(handle, return NULL); 00213 return _alpm_innerconflicts(handle, pkglist); 00214 } 00215 00216 static const int DIFFERENCE = 0; 00217 static const int INTERSECT = 1; 00218 /* Returns a set operation on the provided two lists of files. 00219 * Pre-condition: both lists are sorted! 00220 * When done, free the list but NOT the contained data. 00221 * 00222 * Operations: 00223 * DIFFERENCE - a difference operation is performed. filesA - filesB. 00224 * INTERSECT - an intersection operation is performed. filesA & filesB. 00225 */ 00226 static alpm_list_t *filelist_operation(alpm_filelist_t *filesA, 00227 alpm_filelist_t *filesB, int operation) 00228 { 00229 alpm_list_t *ret = NULL; 00230 size_t ctrA = 0, ctrB = 0; 00231 00232 while(ctrA < filesA->count && ctrB < filesB->count) { 00233 alpm_file_t *fileA = filesA->files + ctrA; 00234 alpm_file_t *fileB = filesB->files + ctrB; 00235 const char *strA = fileA->name; 00236 const char *strB = fileB->name; 00237 /* skip directories, we don't care about them */ 00238 if(strA[strlen(strA)-1] == '/') { 00239 ctrA++; 00240 } else if(strB[strlen(strB)-1] == '/') { 00241 ctrB++; 00242 } else { 00243 int cmp = strcmp(strA, strB); 00244 if(cmp < 0) { 00245 if(operation == DIFFERENCE) { 00246 /* item only in filesA, qualifies as a difference */ 00247 ret = alpm_list_add(ret, fileA); 00248 } 00249 ctrA++; 00250 } else if(cmp > 0) { 00251 ctrB++; 00252 } else { 00253 if(operation == INTERSECT) { 00254 /* item in both, qualifies as an intersect */ 00255 ret = alpm_list_add(ret, fileA); 00256 } 00257 ctrA++; 00258 ctrB++; 00259 } 00260 } 00261 } 00262 00263 /* if doing a difference, ensure we have completely emptied pA */ 00264 while(operation == DIFFERENCE && ctrA < filesA->count) { 00265 alpm_file_t *fileA = filesA->files + ctrA; 00266 const char *strA = fileA->name; 00267 /* skip directories */ 00268 if(strA[strlen(strA)-1] != '/') { 00269 ret = alpm_list_add(ret, fileA); 00270 } 00271 ctrA++; 00272 } 00273 00274 return ret; 00275 } 00276 00277 /* Adds alpm_fileconflict_t to a conflicts list. Pass the conflicts list, the 00278 * conflicting file path, and either two packages or one package and NULL. 00279 */ 00280 static alpm_list_t *add_fileconflict(alpm_handle_t *handle, 00281 alpm_list_t *conflicts, const char *filestr, 00282 alpm_pkg_t *pkg1, alpm_pkg_t *pkg2) 00283 { 00284 alpm_fileconflict_t *conflict; 00285 MALLOC(conflict, sizeof(alpm_fileconflict_t), goto error); 00286 00287 STRDUP(conflict->target, pkg1->name, goto error); 00288 STRDUP(conflict->file, filestr, goto error); 00289 if(pkg2) { 00290 conflict->type = ALPM_FILECONFLICT_TARGET; 00291 STRDUP(conflict->ctarget, pkg2->name, goto error); 00292 } else { 00293 conflict->type = ALPM_FILECONFLICT_FILESYSTEM; 00294 STRDUP(conflict->ctarget, "", goto error); 00295 } 00296 00297 conflicts = alpm_list_add(conflicts, conflict); 00298 _alpm_log(handle, ALPM_LOG_DEBUG, "found file conflict %s, packages %s and %s\n", 00299 filestr, pkg1->name, pkg2 ? pkg2->name : "(filesystem)"); 00300 00301 return conflicts; 00302 00303 error: 00304 RET_ERR(handle, ALPM_ERR_MEMORY, conflicts); 00305 } 00306 00307 void _alpm_fileconflict_free(alpm_fileconflict_t *conflict) 00308 { 00309 FREE(conflict->ctarget); 00310 FREE(conflict->file); 00311 FREE(conflict->target); 00312 FREE(conflict); 00313 } 00314 00315 const alpm_file_t *_alpm_filelist_contains(alpm_filelist_t *filelist, 00316 const char *name) 00317 { 00318 size_t i; 00319 const alpm_file_t *file; 00320 00321 if(!filelist) { 00322 return NULL; 00323 } 00324 00325 for(file = filelist->files, i = 0; i < filelist->count; file++, i++) { 00326 if(strcmp(file->name, name) == 0) { 00327 return file; 00328 } 00329 } 00330 return NULL; 00331 } 00332 00333 static int dir_belongsto_pkg(const char *root, const char *dirpath, 00334 alpm_pkg_t *pkg) 00335 { 00336 struct stat sbuf; 00337 char path[PATH_MAX]; 00338 char abspath[PATH_MAX]; 00339 struct dirent *ent = NULL; 00340 DIR *dir; 00341 00342 snprintf(abspath, PATH_MAX, "%s%s", root, dirpath); 00343 dir = opendir(abspath); 00344 if(dir == NULL) { 00345 return 1; 00346 } 00347 00348 while((ent = readdir(dir)) != NULL) { 00349 const char *name = ent->d_name; 00350 00351 if(strcmp(name, ".") == 0 || strcmp(name, "..") == 0) { 00352 continue; 00353 } 00354 snprintf(path, PATH_MAX, "%s/%s", dirpath, name); 00355 snprintf(abspath, PATH_MAX, "%s%s", root, path); 00356 if(stat(abspath, &sbuf) != 0) { 00357 continue; 00358 } 00359 if(S_ISDIR(sbuf.st_mode)) { 00360 if(dir_belongsto_pkg(root, path, pkg)) { 00361 continue; 00362 } else { 00363 closedir(dir); 00364 return 0; 00365 } 00366 } else { 00367 if(_alpm_filelist_contains(alpm_pkg_get_files(pkg), path)) { 00368 continue; 00369 } else { 00370 closedir(dir); 00371 return 0; 00372 } 00373 } 00374 } 00375 closedir(dir); 00376 return 1; 00377 } 00378 00379 /* Find file conflicts that may occur during the transaction with two checks: 00380 * 1: check every target against every target 00381 * 2: check every target against the filesystem */ 00382 alpm_list_t *_alpm_db_find_fileconflicts(alpm_handle_t *handle, 00383 alpm_list_t *upgrade, alpm_list_t *remove) 00384 { 00385 alpm_list_t *i, *conflicts = NULL; 00386 size_t numtargs = alpm_list_count(upgrade); 00387 size_t current; 00388 size_t rootlen; 00389 00390 if(!upgrade) { 00391 return NULL; 00392 } 00393 00394 rootlen = strlen(handle->root); 00395 00396 /* TODO this whole function needs a huge change, which hopefully will 00397 * be possible with real transactions. Right now we only do half as much 00398 * here as we do when we actually extract files in add.c with our 12 00399 * different cases. */ 00400 for(current = 0, i = upgrade; i; i = i->next, current++) { 00401 alpm_pkg_t *p1 = i->data; 00402 alpm_list_t *j; 00403 alpm_filelist_t tmpfiles; 00404 alpm_pkg_t *dbpkg; 00405 size_t filenum; 00406 00407 int percent = (current * 100) / numtargs; 00408 PROGRESS(handle, ALPM_PROGRESS_CONFLICTS_START, "", percent, 00409 numtargs, current); 00410 /* CHECK 1: check every target against every target */ 00411 _alpm_log(handle, ALPM_LOG_DEBUG, "searching for file conflicts: %s\n", 00412 p1->name); 00413 for(j = i->next; j; j = j->next) { 00414 alpm_list_t *common_files; 00415 alpm_pkg_t *p2 = j->data; 00416 common_files = filelist_operation(alpm_pkg_get_files(p1), 00417 alpm_pkg_get_files(p2), INTERSECT); 00418 00419 if(common_files) { 00420 alpm_list_t *k; 00421 char path[PATH_MAX]; 00422 for(k = common_files; k; k = k->next) { 00423 alpm_file_t *file = k->data; 00424 snprintf(path, PATH_MAX, "%s%s", handle->root, file->name); 00425 conflicts = add_fileconflict(handle, conflicts, path, p1, p2); 00426 if(handle->pm_errno == ALPM_ERR_MEMORY) { 00427 FREELIST(conflicts); 00428 FREELIST(common_files); 00429 return NULL; 00430 } 00431 } 00432 alpm_list_free(common_files); 00433 } 00434 } 00435 00436 /* CHECK 2: check every target against the filesystem */ 00437 _alpm_log(handle, ALPM_LOG_DEBUG, "searching for filesystem conflicts: %s\n", 00438 p1->name); 00439 dbpkg = _alpm_db_get_pkgfromcache(handle->db_local, p1->name); 00440 00441 /* Do two different checks here. If the package is currently installed, 00442 * then only check files that are new in the new package. If the package 00443 * is not currently installed, then simply stat the whole filelist. Note 00444 * that the former list needs to be freed while the latter list should NOT 00445 * be freed. */ 00446 if(dbpkg) { 00447 alpm_list_t *difference; 00448 /* older ver of package currently installed */ 00449 difference = filelist_operation(alpm_pkg_get_files(p1), 00450 alpm_pkg_get_files(dbpkg), DIFFERENCE); 00451 tmpfiles.count = alpm_list_count(difference); 00452 tmpfiles.files = alpm_list_to_array(difference, tmpfiles.count, 00453 sizeof(alpm_file_t)); 00454 alpm_list_free(difference); 00455 } else { 00456 /* no version of package currently installed */ 00457 tmpfiles = *alpm_pkg_get_files(p1); 00458 } 00459 00460 for(filenum = 0; filenum < tmpfiles.count; filenum++) { 00461 alpm_file_t *file = tmpfiles.files + filenum; 00462 const char *filestr = file->name; 00463 const char *relative_path; 00464 alpm_list_t *k; 00465 /* have we acted on this conflict? */ 00466 int resolved_conflict = 0; 00467 struct stat lsbuf; 00468 char path[PATH_MAX]; 00469 size_t pathlen; 00470 00471 pathlen = snprintf(path, PATH_MAX, "%s%s", handle->root, filestr); 00472 00473 /* stat the file - if it exists, do some checks */ 00474 if(_alpm_lstat(path, &lsbuf) != 0) { 00475 continue; 00476 } 00477 00478 _alpm_log(handle, ALPM_LOG_DEBUG, "checking possible conflict: %s\n", path); 00479 00480 if(S_ISDIR(file->mode)) { 00481 struct stat sbuf; 00482 if(S_ISDIR(lsbuf.st_mode)) { 00483 _alpm_log(handle, ALPM_LOG_DEBUG, "file is a directory, not a conflict\n"); 00484 continue; 00485 } 00486 stat(path, &sbuf); 00487 if(S_ISLNK(lsbuf.st_mode) && S_ISDIR(sbuf.st_mode)) { 00488 _alpm_log(handle, ALPM_LOG_DEBUG, 00489 "file is a symlink to a dir, hopefully not a conflict\n"); 00490 continue; 00491 } 00492 /* if we made it to here, we want all subsequent path comparisons to 00493 * not include the trailing slash. This allows things like file -> 00494 * directory replacements. */ 00495 path[pathlen - 1] = '\0'; 00496 } 00497 00498 relative_path = path + rootlen; 00499 00500 /* Check remove list (will we remove the conflicting local file?) */ 00501 for(k = remove; k && !resolved_conflict; k = k->next) { 00502 alpm_pkg_t *rempkg = k->data; 00503 if(rempkg && _alpm_filelist_contains(alpm_pkg_get_files(rempkg), 00504 relative_path)) { 00505 _alpm_log(handle, ALPM_LOG_DEBUG, 00506 "local file will be removed, not a conflict\n"); 00507 resolved_conflict = 1; 00508 } 00509 } 00510 00511 /* Look at all the targets to see if file has changed hands */ 00512 for(k = upgrade; k && !resolved_conflict; k = k->next) { 00513 alpm_pkg_t *p2 = k->data; 00514 if(!p2 || strcmp(p1->name, p2->name) == 0) { 00515 continue; 00516 } 00517 alpm_pkg_t *localp2 = _alpm_db_get_pkgfromcache(handle->db_local, p2->name); 00518 00519 /* localp2->files will be removed (target conflicts are handled by CHECK 1) */ 00520 if(localp2 && _alpm_filelist_contains(alpm_pkg_get_files(localp2), filestr)) { 00521 /* skip removal of file, but not add. this will prevent a second 00522 * package from removing the file when it was already installed 00523 * by its new owner (whether the file is in backup array or not */ 00524 handle->trans->skip_remove = 00525 alpm_list_add(handle->trans->skip_remove, strdup(filestr)); 00526 _alpm_log(handle, ALPM_LOG_DEBUG, 00527 "file changed packages, adding to remove skiplist\n"); 00528 resolved_conflict = 1; 00529 } 00530 } 00531 00532 /* check if all files of the dir belong to the installed pkg */ 00533 if(!resolved_conflict && S_ISDIR(lsbuf.st_mode) && dbpkg) { 00534 char *dir = malloc(strlen(filestr) + 2); 00535 sprintf(dir, "%s/", filestr); 00536 if(_alpm_filelist_contains(alpm_pkg_get_files(dbpkg), dir)) { 00537 _alpm_log(handle, ALPM_LOG_DEBUG, 00538 "check if all files in %s belongs to %s\n", 00539 dir, dbpkg->name); 00540 resolved_conflict = dir_belongsto_pkg(handle->root, filestr, dbpkg); 00541 } 00542 free(dir); 00543 } 00544 00545 /* check if a component of the filepath was a link. canonicalize the path 00546 * and look for it in the old package. note that the actual file under 00547 * consideration cannot itself be a link, as it might be unowned- path 00548 * components can be safely checked as all directories are "unowned". */ 00549 if(!resolved_conflict && dbpkg && !S_ISLNK(lsbuf.st_mode)) { 00550 char *rpath = calloc(PATH_MAX, sizeof(char)); 00551 if(realpath(path, rpath)) { 00552 const char *relative_rpath = rpath + rootlen; 00553 if(_alpm_filelist_contains(alpm_pkg_get_files(dbpkg), relative_rpath)) { 00554 _alpm_log(handle, ALPM_LOG_DEBUG, 00555 "package contained the resolved realpath\n"); 00556 resolved_conflict = 1; 00557 } 00558 } 00559 free(rpath); 00560 } 00561 00562 /* is the file unowned and in the backup list of the new package? */ 00563 if(!resolved_conflict && _alpm_needbackup(filestr, p1)) { 00564 alpm_list_t *local_pkgs = _alpm_db_get_pkgcache(handle->db_local); 00565 int found = 0; 00566 for(k = local_pkgs; k && !found; k = k->next) { 00567 if(_alpm_filelist_contains(alpm_pkg_get_files(k->data), filestr)) { 00568 found = 1; 00569 } 00570 } 00571 if(!found) { 00572 _alpm_log(handle, ALPM_LOG_DEBUG, 00573 "file was unowned but in new backup list\n"); 00574 resolved_conflict = 1; 00575 } 00576 } 00577 00578 if(!resolved_conflict) { 00579 conflicts = add_fileconflict(handle, conflicts, path, p1, NULL); 00580 if(handle->pm_errno == ALPM_ERR_MEMORY) { 00581 FREELIST(conflicts); 00582 if(dbpkg) { 00583 /* only freed if it was generated from filelist_operation() */ 00584 free(tmpfiles.files); 00585 } 00586 return NULL; 00587 } 00588 } 00589 } 00590 if(dbpkg) { 00591 /* only freed if it was generated from filelist_operation() */ 00592 free(tmpfiles.files); 00593 } 00594 } 00595 PROGRESS(handle, ALPM_PROGRESS_CONFLICTS_START, "", 100, 00596 numtargs, current); 00597 00598 return conflicts; 00599 } 00600 00601 /* vim: set ts=2 sw=2 noet: */