static int extract_single_file(alpm_handle_t *handle, struct archive *archive, struct archive_entry *entry, alpm_pkg_t *newpkg, alpm_pkg_t *oldpkg) { const char *entryname; mode_t entrymode; char filename[PATH_MAX]; /* the actual file we're extracting */ int needbackup = 0, notouch = 0; const char *hash_orig = NULL; char *entryname_orig = NULL; int errors = 0; entryname = archive_entry_pathname(entry); entrymode = archive_entry_mode(entry); if(strcmp(entryname, ".INSTALL") == 0) { /* the install script goes inside the db */ snprintf(filename, PATH_MAX, "%s%s-%s/install", _alpm_db_path(handle->db_local), newpkg->name, newpkg->version); archive_entry_set_perm(entry, 0644); } else if(strcmp(entryname, ".CHANGELOG") == 0) { /* the changelog goes inside the db */ snprintf(filename, PATH_MAX, "%s%s-%s/changelog", _alpm_db_path(handle->db_local), newpkg->name, newpkg->version); archive_entry_set_perm(entry, 0644); } else if(strcmp(entryname, ".MTREE") == 0) { /* the mtree file goes inside the db */ snprintf(filename, PATH_MAX, "%s%s-%s/mtree", _alpm_db_path(handle->db_local), newpkg->name, newpkg->version); archive_entry_set_perm(entry, 0644); } else if(*entryname == '.') { /* for now, ignore all files starting with '.' that haven't * already been handled (for future possibilities) */ _alpm_log(handle, ALPM_LOG_DEBUG, "skipping extraction of '%s'\n", entryname); archive_read_data_skip(archive); return 0; } else { /* build the new entryname relative to handle->root */ snprintf(filename, PATH_MAX, "%s%s", handle->root, entryname); } /* if a file is in NoExtract then we never extract it */ if(_alpm_fnmatch_patterns(handle->noextract, entryname) == 0) { _alpm_log(handle, ALPM_LOG_DEBUG, "%s is in NoExtract," " skipping extraction of %s\n", entryname, filename); alpm_logaction(handle, ALPM_CALLER_PREFIX, "note: %s is in NoExtract, skipping extraction\n", entryname); archive_read_data_skip(archive); return 0; } /* Check for file existence. This is one of the more crucial parts * to get 'right'. Here are the possibilities, with the filesystem * on the left and the package on the top: * (F=file, N=node, S=symlink, D=dir) * | F/N | D * non-existent | 1 | 2 * F/N | 3 | 4 * D | 5 | 6 * * 1,2- extract, no magic necessary. lstat (_alpm_lstat) will fail here. * 3,4- conflict checks should have caught this. either overwrite * or backup the file. * 5- file replacing directory- don't allow it. * 6- skip extraction, dir already exists. */ struct stat lsbuf; if(_alpm_lstat(filename, &lsbuf) != 0) { /* cases 1,2: file doesn't exist, skip all backup checks */ } else { if(S_ISDIR(lsbuf.st_mode)) { if(S_ISDIR(entrymode)) { uid_t entryuid = archive_entry_uid(entry); gid_t entrygid = archive_entry_gid(entry); /* case 6: existing dir, ignore it */ if(lsbuf.st_mode != entrymode) { /* if filesystem perms are different than pkg perms, warn user */ mode_t mask = 07777; _alpm_log(handle, ALPM_LOG_WARNING, _("directory permissions differ on %s\n" "filesystem: %o package: %o\n"), filename, lsbuf.st_mode & mask, entrymode & mask); alpm_logaction(handle, ALPM_CALLER_PREFIX, "warning: directory permissions differ on %s\n" "filesystem: %o package: %o\n", filename, lsbuf.st_mode & mask, entrymode & mask); } if((entryuid != lsbuf.st_uid) || (entrygid != lsbuf.st_gid)) { _alpm_log(handle, ALPM_LOG_WARNING, _("directory ownership differs on %s\n" "filesystem: %u:%u package: %u:%u\n"), filename, lsbuf.st_uid, lsbuf.st_gid, entryuid, entrygid); alpm_logaction(handle, ALPM_CALLER_PREFIX, "warning: directory ownership differs on %s\n" "filesystem: %u:%u package: %u:%u\n", filename, lsbuf.st_uid, lsbuf.st_gid, entryuid, entrygid); } _alpm_log(handle, ALPM_LOG_DEBUG, "extract: skipping dir extraction of %s\n", filename); archive_read_data_skip(archive); return 0; } else { /* case 5: trying to overwrite dir with file, don't allow it */ _alpm_log(handle, ALPM_LOG_ERROR, _("extract: not overwriting dir with file %s\n"), filename); archive_read_data_skip(archive); return 1; } } else if(S_ISDIR(entrymode)) { /* case 4: trying to overwrite file with dir */ _alpm_log(handle, ALPM_LOG_DEBUG, "extract: overwriting file with dir %s\n", filename); } else { /* case 3: */ /* if file is in NoUpgrade, don't touch it */ if(_alpm_fnmatch_patterns(handle->noupgrade, entryname) == 0) { notouch = 1; } else { alpm_backup_t *backup; /* go to the backup array and see if our conflict is there */ /* check newpkg first, so that adding backup files is retroactive */ backup = _alpm_needbackup(entryname, newpkg); if(backup) { needbackup = 1; } /* check oldpkg for a backup entry, store the hash if available */ if(oldpkg) { backup = _alpm_needbackup(entryname, oldpkg); if(backup) { hash_orig = backup->hash; needbackup = 1; } } } } } /* we need access to the original entryname later after calls to * archive_entry_set_pathname(), so we need to dupe it and free() later */ STRDUP(entryname_orig, entryname, RET_ERR(handle, ALPM_ERR_MEMORY, -1)); if(needbackup) { char *checkfile; char *hash_local = NULL, *hash_pkg = NULL; size_t len; len = strlen(filename) + 10; MALLOC(checkfile, len, errors++; handle->pm_errno = ALPM_ERR_MEMORY; goto needbackup_cleanup); snprintf(checkfile, len, "%s.paccheck", filename); if(perform_extraction(handle, archive, entry, checkfile, entryname_orig)) { errors++; goto needbackup_cleanup; } hash_local = alpm_compute_md5sum(filename); hash_pkg = alpm_compute_md5sum(checkfile); /* update the md5 hash in newpkg's backup (it will be the new original) */ alpm_list_t *i; for(i = alpm_pkg_get_backup(newpkg); i; i = i->next) { alpm_backup_t *backup = i->data; char *newhash; if(!backup->name || strcmp(backup->name, entryname_orig) != 0) { continue; } STRDUP(newhash, hash_pkg, RET_ERR(handle, ALPM_ERR_MEMORY, -1)); FREE(backup->hash); backup->hash = newhash; } _alpm_log(handle, ALPM_LOG_DEBUG, "checking hashes for %s\n", entryname_orig); _alpm_log(handle, ALPM_LOG_DEBUG, "current: %s\n", hash_local); _alpm_log(handle, ALPM_LOG_DEBUG, "new: %s\n", hash_pkg); _alpm_log(handle, ALPM_LOG_DEBUG, "original: %s\n", hash_orig); if(hash_local && hash_pkg && strcmp(hash_local, hash_pkg) == 0) { /* local and new files are the same, updating anyway to get * correct timestamps */ _alpm_log(handle, ALPM_LOG_DEBUG, "action: installing new file: %s\n", entryname_orig); if(try_rename(handle, checkfile, filename)) { errors++; } } else if(hash_orig && hash_pkg && strcmp(hash_orig, hash_pkg) == 0) { /* original and new files are the same, leave the local version alone, * including any user changes */ _alpm_log(handle, ALPM_LOG_DEBUG, "action: leaving existing file in place\n"); unlink(checkfile); } else if(hash_orig && hash_local && strcmp(hash_orig, hash_local) == 0) { /* installed file has NOT been changed by user, * update to the new version */ _alpm_log(handle, ALPM_LOG_DEBUG, "action: installing new file: %s\n", entryname_orig); if(try_rename(handle, checkfile, filename)) { errors++; } } else { /* none of the three files matched another, unpack the new file alongside * the local file */ if(oldpkg) { char *newpath; size_t newlen = strlen(filename) + strlen(".pacnew") + 1; _alpm_log(handle, ALPM_LOG_DEBUG, "action: keeping current file and installing" " new one with .pacnew ending\n"); MALLOC(newpath, newlen, errors++; handle->pm_errno = ALPM_ERR_MEMORY; goto needbackup_cleanup); snprintf(newpath, newlen, "%s.pacnew", filename); if(try_rename(handle, checkfile, newpath)) { errors++; } else { _alpm_log(handle, ALPM_LOG_WARNING, _("%s installed as %s\n"), filename, newpath); alpm_logaction(handle, ALPM_CALLER_PREFIX, "warning: %s installed as %s\n", filename, newpath); } free(newpath); } else { char *newpath; size_t newlen = strlen(filename) + strlen(".pacorig") + 1; _alpm_log(handle, ALPM_LOG_DEBUG, "action: saving existing file with a .pacorig ending" " and installing a new one\n"); MALLOC(newpath, newlen, errors++; handle->pm_errno = ALPM_ERR_MEMORY; goto needbackup_cleanup); snprintf(newpath, newlen, "%s.pacorig", filename); /* move the existing file to the "pacorig" */ if(try_rename(handle, filename, newpath)) { errors++; /* failed rename filename -> filename.pacorig */ errors++; /* failed rename checkfile -> filename */ } else { /* rename the file we extracted to the real name */ if(try_rename(handle, checkfile, filename)) { errors++; } else { _alpm_log(handle, ALPM_LOG_WARNING, _("%s saved as %s\n"), filename, newpath); alpm_logaction(handle, ALPM_CALLER_PREFIX, "warning: %s saved as %s\n", filename, newpath); } } free(newpath); } } needbackup_cleanup: free(checkfile); free(hash_local); free(hash_pkg); } else {
/** * @brief Unlink a package file, backing it up if necessary. * * @param handle the context handle * @param oldpkg the package being removed * @param newpkg the package replacing \a oldpkg * @param fileobj file to remove * @param skip_remove list of files that shouldn't be removed * @param nosave whether files should be backed up * * @return 0 on success, -1 if there was an error unlinking the file, 1 if the * file was skipped or did not exist */ static int unlink_file(alpm_handle_t *handle, alpm_pkg_t *oldpkg, alpm_pkg_t *newpkg, const alpm_file_t *fileobj, alpm_list_t *skip_remove, int nosave) { struct stat buf; char file[PATH_MAX]; snprintf(file, PATH_MAX, "%s%s", handle->root, fileobj->name); /* check the remove skip list before removing the file. * see the big comment block in db_find_fileconflicts() for an * explanation. */ if(alpm_list_find(skip_remove, fileobj->name, _alpm_fnmatch)) { _alpm_log(handle, ALPM_LOG_DEBUG, "%s is in skip_remove, skipping removal\n", file); return 1; } if(_alpm_lstat(file, &buf)) { _alpm_log(handle, ALPM_LOG_DEBUG, "file %s does not exist\n", file); return 1; } if(S_ISDIR(buf.st_mode)) { ssize_t files = _alpm_files_in_directory(handle, file, 0); /* if we have files, no need to remove the directory */ if(files > 0) { _alpm_log(handle, ALPM_LOG_DEBUG, "keeping directory %s (contains files)\n", file); } else if(files < 0) { _alpm_log(handle, ALPM_LOG_DEBUG, "keeping directory %s (could not count files)\n", file); } else if(newpkg && alpm_filelist_contains(alpm_pkg_get_files(newpkg), fileobj->name)) { _alpm_log(handle, ALPM_LOG_DEBUG, "keeping directory %s (in new package)\n", file); } else if(dir_is_mountpoint(handle, file, &buf)) { _alpm_log(handle, ALPM_LOG_DEBUG, "keeping directory %s (mountpoint)\n", file); } else { /* one last check- does any other package own this file? */ alpm_list_t *local, *local_pkgs; int found = 0; local_pkgs = _alpm_db_get_pkgcache(handle->db_local); for(local = local_pkgs; local && !found; local = local->next) { alpm_pkg_t *local_pkg = local->data; alpm_filelist_t *filelist; /* we duplicated the package when we put it in the removal list, so we * so we can't use direct pointer comparison here. */ if(oldpkg->name_hash == local_pkg->name_hash && strcmp(oldpkg->name, local_pkg->name) == 0) { continue; } filelist = alpm_pkg_get_files(local_pkg); if(alpm_filelist_contains(filelist, fileobj->name)) { _alpm_log(handle, ALPM_LOG_DEBUG, "keeping directory %s (owned by %s)\n", file, local_pkg->name); found = 1; } } if(!found) { if(rmdir(file)) { _alpm_log(handle, ALPM_LOG_DEBUG, "directory removal of %s failed: %s\n", file, strerror(errno)); return -1; } else { _alpm_log(handle, ALPM_LOG_DEBUG, "removed directory %s (no remaining owners)\n", file); } } } } else { /* if the file needs backup and has been modified, back it up to .pacsave */ alpm_backup_t *backup = _alpm_needbackup(fileobj->name, oldpkg); if(backup) { if(nosave) { _alpm_log(handle, ALPM_LOG_DEBUG, "transaction is set to NOSAVE, not backing up '%s'\n", file); } else { char *filehash = alpm_compute_md5sum(file); int cmp = filehash ? strcmp(filehash, backup->hash) : 0; FREE(filehash); if(cmp != 0) { char *newpath; size_t len = strlen(file) + 8 + 1; MALLOC(newpath, len, RET_ERR(handle, ALPM_ERR_MEMORY, -1)); shift_pacsave(handle, file); snprintf(newpath, len, "%s.pacsave", file); if(rename(file, newpath)) { _alpm_log(handle, ALPM_LOG_ERROR, _("could not rename %s to %s (%s)\n"), file, newpath, strerror(errno)); alpm_logaction(handle, ALPM_CALLER_PREFIX, "error: could not rename %s to %s (%s)\n", file, newpath, strerror(errno)); free(newpath); return -1; } _alpm_log(handle, ALPM_LOG_WARNING, _("%s saved as %s\n"), file, newpath); alpm_logaction(handle, ALPM_CALLER_PREFIX, "warning: %s saved as %s\n", file, newpath); free(newpath); return 0; } } } _alpm_log(handle, ALPM_LOG_DEBUG, "unlinking %s\n", file); if(unlink(file) == -1) { _alpm_log(handle, ALPM_LOG_ERROR, _("cannot remove %s (%s)\n"), file, strerror(errno)); alpm_logaction(handle, ALPM_CALLER_PREFIX, "error: cannot remove %s (%s)\n", file, strerror(errno)); return -1; } } return 0; }
/* Find file conflicts that may occur during the transaction with two checks: * 1: check every target against every target * 2: check every target against the filesystem */ alpm_list_t *_alpm_db_find_fileconflicts(pmdb_t *db, pmtrans_t *trans, char *root) { alpm_list_t *i, *conflicts = NULL; alpm_list_t *targets = trans->packages; int numtargs = alpm_list_count(targets); int current; ALPM_LOG_FUNC; if(db == NULL || targets == NULL || root == NULL) { return(NULL); } /* TODO this whole function needs a huge change, which hopefully will * be possible with real transactions. Right now we only do half as much * here as we do when we actually extract files in add.c with our 12 * different cases. */ for(current = 1, i = targets; i; i = i->next, current++) { alpm_list_t *j, *k, *tmpfiles = NULL; pmpkg_t *p1, *p2, *dbpkg; char path[PATH_MAX+1]; p1 = i->data; if(!p1) { continue; } double percent = (double)current / numtargs; PROGRESS(trans, PM_TRANS_PROGRESS_CONFLICTS_START, "", (percent * 100), numtargs, current); /* CHECK 1: check every target against every target */ _alpm_log(PM_LOG_DEBUG, "searching for file conflicts: %s\n", alpm_pkg_get_name(p1)); for(j = i->next; j; j = j->next) { p2 = j->data; if(!p2) { continue; } tmpfiles = chk_fileconflicts(alpm_pkg_get_files(p1), alpm_pkg_get_files(p2)); if(tmpfiles) { for(k = tmpfiles; k; k = k->next) { snprintf(path, PATH_MAX, "%s%s", root, (char *)k->data); conflicts = add_fileconflict(conflicts, PM_FILECONFLICT_TARGET, path, alpm_pkg_get_name(p1), alpm_pkg_get_name(p2)); } FREELIST(tmpfiles); } } /* declarations for second check */ struct stat lsbuf, sbuf; char *filestr = NULL; /* CHECK 2: check every target against the filesystem */ _alpm_log(PM_LOG_DEBUG, "searching for filesystem conflicts: %s\n", p1->name); dbpkg = _alpm_db_get_pkgfromcache(db, p1->name); /* Do two different checks here. f the package is currently installed, * then only check files that are new in the new package. If the package * is not currently installed, then simply stat the whole filelist */ if(dbpkg) { /* older ver of package currently installed */ tmpfiles = chk_filedifference(alpm_pkg_get_files(p1), alpm_pkg_get_files(dbpkg)); } else { /* no version of package currently installed */ tmpfiles = alpm_list_strdup(alpm_pkg_get_files(p1)); } /* loop over each file to be installed */ for(j = tmpfiles; j; j = j->next) { int skip_conflict = 0; filestr = j->data; snprintf(path, PATH_MAX, "%s%s", root, filestr); /* stat the file - if it exists, do some checks */ if(_alpm_lstat(path, &lsbuf) != 0) { continue; } stat(path, &sbuf); if(path[strlen(path)-1] == '/') { if(S_ISDIR(lsbuf.st_mode)) { _alpm_log(PM_LOG_DEBUG, "%s is a directory, not a conflict\n", path); skip_conflict = 1; } else if(S_ISLNK(lsbuf.st_mode) && S_ISDIR(sbuf.st_mode)) { _alpm_log(PM_LOG_DEBUG, "%s is a symlink to a dir, hopefully not a conflict\n", path); skip_conflict = 1; } } if(!skip_conflict) { _alpm_log(PM_LOG_DEBUG, "checking possible conflict: %s\n", path); /* Look at all the targets to see if file has changed hands */ int resolved_conflict = 0; /* have we acted on this conflict? */ for(k = targets; k; k = k->next) { p2 = k->data; if(!p2 || strcmp(p1->name, p2->name) == 0) { continue; } pmpkg_t *localp2 = _alpm_db_get_pkgfromcache(db, p2->name); /* Check if it used to exist in a package, but doesn't anymore */ alpm_list_t *pkgfiles, *localfiles; /* added for readability */ pkgfiles = alpm_pkg_get_files(p2); localfiles = alpm_pkg_get_files(localp2); if(localp2 && !alpm_list_find_str(pkgfiles, filestr) && alpm_list_find_str(localfiles, filestr)) { /* skip removal of file, but not add. this will prevent a second * package from removing the file when it was already installed * by its new owner (whether the file is in backup array or not */ trans->skip_remove = alpm_list_add(trans->skip_remove, strdup(path)); _alpm_log(PM_LOG_DEBUG, "file changed packages, adding to remove skiplist: %s\n", filestr); resolved_conflict = 1; break; } } if(!resolved_conflict) { _alpm_log(PM_LOG_DEBUG, "file found in conflict: %s\n", path); conflicts = add_fileconflict(conflicts, PM_FILECONFLICT_FILESYSTEM, path, p1->name, NULL); } } } FREELIST(tmpfiles); } return(conflicts); }