/* * be_sync.c : backend for sync databases * * Copyright (c) 2006-2018 Pacman Development Team <pacman-dev@archlinux.org> * Copyright (c) 2002-2006 by Judd Vinet <jvinet@zeroflux.org> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ #include <errno.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <limits.h> #include <unistd.h> /* libarchive */ #include <archive.h> #include <archive_entry.h> /* libalpm */ #include "util.h" #include "log.h" #include "libarchive-compat.h" #include "alpm.h" #include "alpm_list.h" #include "package.h" #include "handle.h" #include "deps.h" #include "dload.h" #include "filelist.h" static char *get_sync_dir(alpm_handle_t *handle) { size_t len = strlen(handle->dbpath) + 6; char *syncpath; struct stat buf; MALLOC(syncpath, len, RET_ERR(handle, ALPM_ERR_MEMORY, NULL)); sprintf(syncpath, "%s%s", handle->dbpath, "sync/"); if(stat(syncpath, &buf) != 0) { _alpm_log(handle, ALPM_LOG_DEBUG, "database dir '%s' does not exist, creating it\n", syncpath); if(_alpm_makepath(syncpath) != 0) { free(syncpath); RET_ERR(handle, ALPM_ERR_SYSTEM, NULL); } } else if(!S_ISDIR(buf.st_mode)) { _alpm_log(handle, ALPM_LOG_WARNING, _("removing invalid file: %s\n"), syncpath); if(unlink(syncpath) != 0 || _alpm_makepath(syncpath) != 0) { free(syncpath); RET_ERR(handle, ALPM_ERR_SYSTEM, NULL); } } return syncpath; } static int sync_db_validate(alpm_db_t *db) { int siglevel; const char *dbpath; if(db->status & DB_STATUS_VALID || db->status & DB_STATUS_MISSING) { return 0; } if(db->status & DB_STATUS_INVALID) { db->handle->pm_errno = ALPM_ERR_DB_INVALID_SIG; return -1; } dbpath = _alpm_db_path(db); if(!dbpath) { /* pm_errno set in _alpm_db_path() */ return -1; } /* we can skip any validation if the database doesn't exist */ if(_alpm_access(db->handle, NULL, dbpath, R_OK) != 0 && errno == ENOENT) { alpm_event_database_missing_t event = { .type = ALPM_EVENT_DATABASE_MISSING, .dbname = db->treename }; db->status &= ~DB_STATUS_EXISTS; db->status |= DB_STATUS_MISSING; EVENT(db->handle, &event); goto valid; } db->status |= DB_STATUS_EXISTS; db->status &= ~DB_STATUS_MISSING; /* this takes into account the default verification level if UNKNOWN * was assigned to this db */ siglevel = alpm_db_get_siglevel(db); if(siglevel & ALPM_SIG_DATABASE) { int retry, ret; do { retry = 0; alpm_siglist_t *siglist; ret = _alpm_check_pgp_helper(db->handle, dbpath, NULL, siglevel & ALPM_SIG_DATABASE_OPTIONAL, siglevel & ALPM_SIG_DATABASE_MARGINAL_OK, siglevel & ALPM_SIG_DATABASE_UNKNOWN_OK, &siglist); if(ret) { retry = _alpm_process_siglist(db->handle, db->treename, siglist, siglevel & ALPM_SIG_DATABASE_OPTIONAL, siglevel & ALPM_SIG_DATABASE_MARGINAL_OK, siglevel & ALPM_SIG_DATABASE_UNKNOWN_OK); } alpm_siglist_cleanup(siglist); free(siglist); } while(retry); if(ret) { db->status &= ~DB_STATUS_VALID; db->status |= DB_STATUS_INVALID; db->handle->pm_errno = ALPM_ERR_DB_INVALID_SIG; return 1; } } valid: db->status |= DB_STATUS_VALID; db->status &= ~DB_STATUS_INVALID; return 0; } /** Update a package database * * An update of the package database \a db will be attempted. Unless * \a force is true, the update will only be performed if the remote * database was modified since the last update. * * This operation requires a database lock, and will return an applicable error * if the lock could not be obtained. * * Example: * @code * alpm_list_t *syncs = alpm_get_syncdbs(); * for(i = syncs; i; i = alpm_list_next(i)) { * alpm_db_t *db = alpm_list_getdata(i); * result = alpm_db_update(0, db); * * if(result < 0) { * printf("Unable to update database: %s\n", alpm_strerrorlast()); * } else if(result == 1) { * printf("Database already up to date\n"); * } else { * printf("Database updated\n"); * } * } * @endcode * * @ingroup alpm_databases * @note After a successful update, the \link alpm_db_get_pkgcache() * package cache \endlink will be invalidated * @param force if true, then forces the update, otherwise update only in case * the database isn't up to date * @param db pointer to the package database to update * @return 0 on success, -1 on error (pm_errno is set accordingly), 1 if up to * to date */ int SYMEXPORT alpm_db_update(int force, alpm_db_t *db) { char *syncpath; const char *dbext; alpm_list_t *i; int updated = 0; int ret = -1; mode_t oldmask; alpm_handle_t *handle; int siglevel; /* Sanity checks */ ASSERT(db != NULL, return -1); handle = db->handle; handle->pm_errno = ALPM_ERR_OK; ASSERT(db != handle->db_local, RET_ERR(handle, ALPM_ERR_WRONG_ARGS, -1)); ASSERT(db->servers != NULL, RET_ERR(handle, ALPM_ERR_SERVER_NONE, -1)); if(!(db->usage & ALPM_DB_USAGE_SYNC)) { return 0; } syncpath = get_sync_dir(handle); if(!syncpath) { return -1; } /* force update of invalid databases to fix potential mismatched database/signature */ if(db->status & DB_STATUS_INVALID) { force = 1; } /* make sure we have a sane umask */ oldmask = umask(0022); siglevel = alpm_db_get_siglevel(db); /* attempt to grab a lock */ if(_alpm_handle_lock(handle)) { free(syncpath); umask(oldmask); RET_ERR(handle, ALPM_ERR_HANDLE_LOCK, -1); } dbext = db->handle->dbext; for(i = db->servers; i; i = i->next) { const char *server = i->data, *final_db_url = NULL; struct dload_payload payload; size_t len; int sig_ret = 0; memset(&payload, 0, sizeof(struct dload_payload)); /* set hard upper limit of 25MiB */ payload.max_size = 25 * 1024 * 1024; /* print server + filename into a buffer */ len = strlen(server) + strlen(db->treename) + strlen(dbext) + 2; MALLOC(payload.fileurl, len, { free(syncpath); umask(oldmask); RET_ERR(handle, ALPM_ERR_MEMORY, -1); } ); snprintf(payload.fileurl, len, "%s/%s%s", server, db->treename, dbext); payload.handle = handle; payload.force = force; payload.unlink_on_fail = 1; ret = _alpm_download(&payload, syncpath, NULL, &final_db_url); _alpm_dload_payload_reset(&payload); updated = (updated || ret == 0); if(ret != -1 && updated && (siglevel & ALPM_SIG_DATABASE)) { /* an existing sig file is no good at this point */ char *sigpath = _alpm_sigpath(handle, _alpm_db_path(db)); if(!sigpath) { ret = -1; break; } unlink(sigpath); free(sigpath); /* check if the final URL from internal downloader looks reasonable */ if(final_db_url != NULL) { if(strlen(final_db_url) < 3 || strcmp(final_db_url + strlen(final_db_url) - strlen(dbext), dbext) != 0) { final_db_url = NULL; } } /* if we downloaded a DB, we want the .sig from the same server */ if(final_db_url != NULL) { /* print final_db_url into a buffer (leave space for .sig) */ len = strlen(final_db_url) + 5; } else { /* print server + filename into a buffer (leave space for separator and .sig) */ len = strlen(server) + strlen(db->treename) + strlen(dbext) + 6; } MALLOC(payload.fileurl, len, { free(syncpath); umask(oldmask); RET_ERR(handle, ALPM_ERR_MEMORY, -1); } ); if(final_db_url != NULL) { snprintf(payload.fileurl, len, "%s.sig", final_db_url); } else { snprintf(payload.fileurl, len, "%s/%s%s.sig", server, db->treename, dbext); } payload.handle = handle; payload.force = 1; payload.errors_ok = (siglevel & ALPM_SIG_DATABASE_OPTIONAL); /* set hard upper limit of 16KiB */ payload.max_size = 16 * 1024; sig_ret = _alpm_download(&payload, syncpath, NULL, NULL); /* errors_ok suppresses error messages, but not the return code */ sig_ret = payload.errors_ok ? 0 : sig_ret; _alpm_dload_payload_reset(&payload); } if(ret != -1 && sig_ret != -1) { break; } } if(updated) { /* Cache needs to be rebuilt */ _alpm_db_free_pkgcache(db); /* clear all status flags regarding validity/existence */ db->status &= ~DB_STATUS_VALID; db->status &= ~DB_STATUS_INVALID; db->status &= ~DB_STATUS_EXISTS; db->status &= ~DB_STATUS_MISSING; /* if the download failed skip validation to preserve the download error */ if(ret != -1 && sync_db_validate(db) != 0) { /* pm_errno should be set */ ret = -1; } } if(ret == -1) { /* pm_errno was set by the download code */ _alpm_log(handle, ALPM_LOG_DEBUG, "failed to sync db: %s\n", alpm_strerror(handle->pm_errno)); } else { handle->pm_errno = ALPM_ERR_OK; } _alpm_handle_unlock(handle); free(syncpath); umask(oldmask); return ret; } /* Forward decl so I don't reorganize the whole file right now */ static int sync_db_read(alpm_db_t *db, struct archive *archive, struct archive_entry *entry, alpm_pkg_t **likely_pkg); static int _sync_get_validation(alpm_pkg_t *pkg) { if(pkg->validation) { return pkg->validation; } if(pkg->md5sum) { pkg->validation |= ALPM_PKG_VALIDATION_MD5SUM; } if(pkg->sha256sum) { pkg->validation |= ALPM_PKG_VALIDATION_SHA256SUM; } if(pkg->base64_sig) { pkg->validation |= ALPM_PKG_VALIDATION_SIGNATURE; } if(!pkg->validation) { pkg->validation |= ALPM_PKG_VALIDATION_NONE; } return pkg->validation; } static alpm_pkg_t *load_pkg_for_entry(alpm_db_t *db, const char *entryname, const char **entry_filename, alpm_pkg_t *likely_pkg) { char *pkgname = NULL, *pkgver = NULL; unsigned long pkgname_hash; alpm_pkg_t *pkg; /* get package and db file names */ if(entry_filename) { char *fname = strrchr(entryname, '/'); if(fname) { *entry_filename = fname + 1; } else { *entry_filename = NULL; } } if(_alpm_splitname(entryname, &pkgname, &pkgver, &pkgname_hash) != 0) { _alpm_log(db->handle, ALPM_LOG_ERROR, _("invalid name for database entry '%s'\n"), entryname); return NULL; } if(likely_pkg && pkgname_hash == likely_pkg->name_hash && strcmp(likely_pkg->name, pkgname) == 0) { pkg = likely_pkg; } else { pkg = _alpm_pkghash_find(db->pkgcache, pkgname); } if(pkg == NULL) { pkg = _alpm_pkg_new(); if(pkg == NULL) { RET_ERR(db->handle, ALPM_ERR_MEMORY, NULL); } pkg->name = pkgname; pkg->version = pkgver; pkg->name_hash = pkgname_hash; pkg->origin = ALPM_PKG_FROM_SYNCDB; pkg->origin_data.db = db; pkg->ops = &default_pkg_ops; pkg->ops->get_validation = _sync_get_validation; pkg->handle = db->handle; /* add to the collection */ _alpm_log(db->handle, ALPM_LOG_FUNCTION, "adding '%s' to package cache for db '%s'\n", pkg->name, db->treename); if(_alpm_pkghash_add(&db->pkgcache, pkg) == NULL) { _alpm_pkg_free(pkg); RET_ERR(db->handle, ALPM_ERR_MEMORY, NULL); } } else { free(pkgname); free(pkgver); } return pkg; } /* This function doesn't work as well as one might think, as size of database * entries varies considerably. Adding signatures nearly doubles the size of a * single entry. These current values are heavily influenced by Arch Linux; * databases with a single signature per package. */ static size_t estimate_package_count(struct stat *st, struct archive *archive) { int per_package; switch(_alpm_archive_filter_code(archive)) { case ARCHIVE_COMPRESSION_NONE: per_package = 3015; break; case ARCHIVE_COMPRESSION_GZIP: case ARCHIVE_COMPRESSION_COMPRESS: per_package = 464; break; case ARCHIVE_COMPRESSION_BZIP2: per_package = 394; break; case ARCHIVE_COMPRESSION_LZMA: case ARCHIVE_COMPRESSION_XZ: per_package = 400; break; #ifdef ARCHIVE_COMPRESSION_UU case ARCHIVE_COMPRESSION_UU: per_package = 3015 * 4 / 3; break; #endif default: /* assume it is at least somewhat compressed */ per_package = 500; } return (size_t)((st->st_size / per_package) + 1); } static int sync_db_populate(alpm_db_t *db) { const char *dbpath; size_t est_count, count; int fd; int ret = 0; int archive_ret; struct stat buf; struct archive *archive; struct archive_entry *entry; alpm_pkg_t *pkg = NULL; if(db->status & DB_STATUS_INVALID) { RET_ERR(db->handle, ALPM_ERR_DB_INVALID, -1); } if(db->status & DB_STATUS_MISSING) { RET_ERR(db->handle, ALPM_ERR_DB_NOT_FOUND, -1); } dbpath = _alpm_db_path(db); if(!dbpath) { /* pm_errno set in _alpm_db_path() */ return -1; } fd = _alpm_open_archive(db->handle, dbpath, &buf, &archive, ALPM_ERR_DB_OPEN); if(fd < 0) { db->status &= ~DB_STATUS_VALID; db->status |= DB_STATUS_INVALID; return -1; } est_count = estimate_package_count(&buf, archive); /* currently only .files dbs contain file lists - make flexible when required*/ if(strcmp(db->handle->dbext, ".files") == 0) { /* files databases are about four times larger on average */ est_count /= 4; } db->pkgcache = _alpm_pkghash_create(est_count); if(db->pkgcache == NULL) { db->handle->pm_errno = ALPM_ERR_MEMORY; ret = -1; goto cleanup; } while((archive_ret = archive_read_next_header(archive, &entry)) == ARCHIVE_OK) { mode_t mode = archive_entry_mode(entry); if(!S_ISDIR(mode)) { /* we have desc or depends - parse it */ if(sync_db_read(db, archive, entry, &pkg) != 0) { _alpm_log(db->handle, ALPM_LOG_ERROR, _("could not parse package description file '%s' from db '%s'\n"), archive_entry_pathname(entry), db->treename); ret = -1; } } } if(archive_ret != ARCHIVE_EOF) { _alpm_log(db->handle, ALPM_LOG_ERROR, _("could not read db '%s' (%s)\n"), db->treename, archive_error_string(archive)); _alpm_db_free_pkgcache(db); db->handle->pm_errno = ALPM_ERR_LIBARCHIVE; ret = -1; goto cleanup; } count = alpm_list_count(db->pkgcache->list); if(count > 0) { db->pkgcache->list = alpm_list_msort(db->pkgcache->list, count, _alpm_pkg_cmp); } _alpm_log(db->handle, ALPM_LOG_DEBUG, "added %zu packages to package cache for db '%s'\n", count, db->treename); cleanup: _alpm_archive_read_free(archive); if(fd >= 0) { close(fd); } return ret; } /* This function validates %FILENAME%. filename must be between 3 and * PATH_MAX characters and cannot be contain a path */ static int _alpm_validate_filename(alpm_db_t *db, const char *pkgname, const char *filename) { size_t len = strlen(filename); if(filename[0] == '.') { errno = EINVAL; _alpm_log(db->handle, ALPM_LOG_ERROR, _("%s database is inconsistent: filename " "of package %s is illegal\n"), db->treename, pkgname); return -1; } else if(memchr(filename, '/', len) != NULL) { errno = EINVAL; _alpm_log(db->handle, ALPM_LOG_ERROR, _("%s database is inconsistent: filename " "of package %s is illegal\n"), db->treename, pkgname); return -1; } else if(len > PATH_MAX) { errno = EINVAL; _alpm_log(db->handle, ALPM_LOG_ERROR, _("%s database is inconsistent: filename " "of package %s is too long\n"), db->treename, pkgname); return -1; } return 0; } #define READ_NEXT() do { \ if(_alpm_archive_fgets(archive, &buf) != ARCHIVE_OK) goto error; \ line = buf.line; \ _alpm_strip_newline(line, buf.real_line_size); \ } while(0) #define READ_AND_STORE(f) do { \ READ_NEXT(); \ STRDUP(f, line, goto error); \ } while(0) #define READ_AND_STORE_ALL(f) do { \ char *linedup; \ if(_alpm_archive_fgets(archive, &buf) != ARCHIVE_OK) goto error; \ if(_alpm_strip_newline(buf.line, buf.real_line_size) == 0) break; \ STRDUP(linedup, buf.line, goto error); \ f = alpm_list_add(f, linedup); \ } while(1) /* note the while(1) and not (0) */ #define READ_AND_SPLITDEP(f) do { \ if(_alpm_archive_fgets(archive, &buf) != ARCHIVE_OK) goto error; \ if(_alpm_strip_newline(buf.line, buf.real_line_size) == 0) break; \ f = alpm_list_add(f, alpm_dep_from_string(line)); \ } while(1) /* note the while(1) and not (0) */ static int sync_db_read(alpm_db_t *db, struct archive *archive, struct archive_entry *entry, alpm_pkg_t **likely_pkg) { const char *entryname, *filename; alpm_pkg_t *pkg; struct archive_read_buffer buf; entryname = archive_entry_pathname(entry); if(entryname == NULL) { _alpm_log(db->handle, ALPM_LOG_DEBUG, "invalid archive entry provided to _alpm_sync_db_read, skipping\n"); return -1; } _alpm_log(db->handle, ALPM_LOG_FUNCTION, "loading package data from archive entry %s\n", entryname); memset(&buf, 0, sizeof(buf)); /* 512K for a line length seems reasonable */ buf.max_line_size = 512 * 1024; pkg = load_pkg_for_entry(db, entryname, &filename, *likely_pkg); if(pkg == NULL) { _alpm_log(db->handle, ALPM_LOG_DEBUG, "entry %s could not be loaded into %s sync database\n", entryname, db->treename); return -1; } if(filename == NULL) { /* A file exists outside of a subdirectory. This isn't a read error, so return * success and try to continue on. */ _alpm_log(db->handle, ALPM_LOG_WARNING, _("unknown database file: %s\n"), entryname); return 0; } if(strcmp(filename, "desc") == 0 || strcmp(filename, "depends") == 0 || strcmp(filename, "files") == 0) { int ret; while((ret = _alpm_archive_fgets(archive, &buf)) == ARCHIVE_OK) { char *line = buf.line; if(_alpm_strip_newline(line, buf.real_line_size) == 0) { /* length of stripped line was zero */ continue; } if(strcmp(line, "%NAME%") == 0) { READ_NEXT(); if(strcmp(line, pkg->name) != 0) { _alpm_log(db->handle, ALPM_LOG_ERROR, _("%s database is inconsistent: name " "mismatch on package %s\n"), db->treename, pkg->name); } } else if(strcmp(line, "%VERSION%") == 0) { READ_NEXT(); if(strcmp(line, pkg->version) != 0) { _alpm_log(db->handle, ALPM_LOG_ERROR, _("%s database is inconsistent: version " "mismatch on package %s\n"), db->treename, pkg->name); } } else if(strcmp(line, "%FILENAME%") == 0) { READ_AND_STORE(pkg->filename); if(_alpm_validate_filename(db, pkg->name, pkg->filename) < 0) { return -1; } } else if(strcmp(line, "%BASE%") == 0) { READ_AND_STORE(pkg->base); } else if(strcmp(line, "%DESC%") == 0) { READ_AND_STORE(pkg->desc); } else if(strcmp(line, "%GROUPS%") == 0) { READ_AND_STORE_ALL(pkg->groups); } else if(strcmp(line, "%URL%") == 0) { READ_AND_STORE(pkg->url); } else if(strcmp(line, "%LICENSE%") == 0) { READ_AND_STORE_ALL(pkg->licenses); } else if(strcmp(line, "%ARCH%") == 0) { READ_AND_STORE(pkg->arch); } else if(strcmp(line, "%BUILDDATE%") == 0) { READ_NEXT(); pkg->builddate = _alpm_parsedate(line); } else if(strcmp(line, "%PACKAGER%") == 0) { READ_AND_STORE(pkg->packager); } else if(strcmp(line, "%CSIZE%") == 0) { READ_NEXT(); pkg->size = _alpm_strtoofft(line); } else if(strcmp(line, "%ISIZE%") == 0) { READ_NEXT(); pkg->isize = _alpm_strtoofft(line); } else if(strcmp(line, "%MD5SUM%") == 0) { READ_AND_STORE(pkg->md5sum); } else if(strcmp(line, "%SHA256SUM%") == 0) { READ_AND_STORE(pkg->sha256sum); } else if(strcmp(line, "%PGPSIG%") == 0) { READ_AND_STORE(pkg->base64_sig); } else if(strcmp(line, "%REPLACES%") == 0) { READ_AND_SPLITDEP(pkg->replaces); } else if(strcmp(line, "%DEPENDS%") == 0) { READ_AND_SPLITDEP(pkg->depends); } else if(strcmp(line, "%OPTDEPENDS%") == 0) { READ_AND_SPLITDEP(pkg->optdepends); } else if(strcmp(line, "%MAKEDEPENDS%") == 0) { READ_AND_SPLITDEP(pkg->makedepends); } else if(strcmp(line, "%CHECKDEPENDS%") == 0) { READ_AND_SPLITDEP(pkg->checkdepends); } else if(strcmp(line, "%CONFLICTS%") == 0) { READ_AND_SPLITDEP(pkg->conflicts); } else if(strcmp(line, "%PROVIDES%") == 0) { READ_AND_SPLITDEP(pkg->provides); } else if(strcmp(line, "%FILES%") == 0) { /* TODO: this could lazy load if there is future demand */ size_t files_count = 0, files_size = 0; alpm_file_t *files = NULL; while(1) { if(_alpm_archive_fgets(archive, &buf) != ARCHIVE_OK) { goto error; } line = buf.line; if(_alpm_strip_newline(line, buf.real_line_size) == 0) { break; } if(!_alpm_greedy_grow((void **)&files, &files_size, (files_count ? (files_count + 1) * sizeof(alpm_file_t) : 8 * sizeof(alpm_file_t)))) { goto error; } STRDUP(files[files_count].name, line, goto error); files_count++; } /* attempt to hand back any memory we don't need */ if(files_count > 0) { files = realloc(files, sizeof(alpm_file_t) * files_count); } else { FREE(files); } pkg->files.count = files_count; pkg->files.files = files; _alpm_filelist_sort(&pkg->files); } } if(ret != ARCHIVE_EOF) { goto error; } *likely_pkg = pkg; } else { /* unknown database file */ _alpm_log(db->handle, ALPM_LOG_DEBUG, "unknown database file: %s\n", filename); } return 0; error: _alpm_log(db->handle, ALPM_LOG_DEBUG, "error parsing database file: %s\n", filename); return -1; } struct db_operations sync_db_ops = { .validate = sync_db_validate, .populate = sync_db_populate, .unregister = _alpm_db_unregister, }; alpm_db_t *_alpm_db_register_sync(alpm_handle_t *handle, const char *treename, int level) { alpm_db_t *db; _alpm_log(handle, ALPM_LOG_DEBUG, "registering sync database '%s'\n", treename); #ifndef HAVE_LIBGPGME if(level != 0 && level != ALPM_SIG_USE_DEFAULT) { RET_ERR(handle, ALPM_ERR_MISSING_CAPABILITY_SIGNATURES, NULL); } #endif db = _alpm_db_new(treename, 0); if(db == NULL) { RET_ERR(handle, ALPM_ERR_DB_CREATE, NULL); } db->ops = &sync_db_ops; db->handle = handle; db->siglevel = level; sync_db_validate(db); handle->dbs_sync = alpm_list_add(handle->dbs_sync, db); return db; }