diff options
Diffstat (limited to 'lib/libalpm/be_sync.c')
-rw-r--r-- | lib/libalpm/be_sync.c | 472 |
1 files changed, 472 insertions, 0 deletions
diff --git a/lib/libalpm/be_sync.c b/lib/libalpm/be_sync.c new file mode 100644 index 00000000..4ad045c2 --- /dev/null +++ b/lib/libalpm/be_sync.c @@ -0,0 +1,472 @@ +/* + * be_sync.c + * + * Copyright (c) 2006-2011 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 "config.h" + +#include <errno.h> +#include <limits.h> + +/* libarchive */ +#include <archive.h> +#include <archive_entry.h> + +/* libalpm */ +#include "util.h" +#include "log.h" +#include "alpm.h" +#include "alpm_list.h" +#include "package.h" +#include "handle.h" +#include "delta.h" +#include "deps.h" +#include "dload.h" + +/** 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. + * + * A transaction is necessary for this operation, in order to obtain a + * database lock. During this transaction the front-end will be informed + * of the download progress of the database via the download callback. + * + * Example: + * @code + * alpm_list_t *syncs = alpm_option_get_syncdbs(); + * if(alpm_trans_init(0, NULL, NULL, NULL) == 0) { + * for(i = syncs; i; i = alpm_list_next(i)) { + * pmdb_t *db = alpm_list_getdata(i); + * result = alpm_db_update(0, db); + * alpm_trans_release(); + * + * 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, pmdb_t *db) +{ + char *dbfile, *syncpath; + const char *dbpath; + struct stat buf; + size_t len; + int ret; + + ALPM_LOG_FUNC; + + /* Sanity checks */ + ASSERT(handle != NULL, RET_ERR(PM_ERR_HANDLE_NULL, -1)); + ASSERT(db != NULL && db != handle->db_local, RET_ERR(PM_ERR_WRONG_ARGS, -1)); + + if(!alpm_list_find_ptr(handle->dbs_sync, db)) { + RET_ERR(PM_ERR_DB_NOT_FOUND, -1); + } + + len = strlen(db->treename) + 4; + MALLOC(dbfile, len, RET_ERR(PM_ERR_MEMORY, -1)); + sprintf(dbfile, "%s.db", db->treename); + + dbpath = alpm_option_get_dbpath(); + len = strlen(dbpath) + 6; + MALLOC(syncpath, len, RET_ERR(PM_ERR_MEMORY, -1)); + sprintf(syncpath, "%s%s", dbpath, "sync/"); + + if(stat(syncpath, &buf) != 0) { + _alpm_log(PM_LOG_DEBUG, "database dir '%s' does not exist, creating it\n", + syncpath); + if(_alpm_makepath(syncpath) != 0) { + free(dbfile); + free(syncpath); + RET_ERR(PM_ERR_SYSTEM, -1); + } + } else if(!S_ISDIR(buf.st_mode)) { + _alpm_log(PM_LOG_WARNING, _("removing invalid file: %s\n"), syncpath); + if(unlink(syncpath) != 0 || _alpm_makepath(syncpath) != 0) { + free(dbfile); + free(syncpath); + RET_ERR(PM_ERR_SYSTEM, -1); + } + } + + ret = _alpm_download_single_file(dbfile, db->servers, syncpath, force); + free(dbfile); + free(syncpath); + + if(ret == 1) { + /* files match, do nothing */ + pm_errno = 0; + return(1); + } else if(ret == -1) { + /* pm_errno was set by the download code */ + _alpm_log(PM_LOG_DEBUG, "failed to sync db: %s\n", alpm_strerrorlast()); + return(-1); + } + + /* Cache needs to be rebuilt */ + _alpm_db_free_pkgcache(db); + + return(0); +} + +/* Forward decl so I don't reorganize the whole file right now */ +static int sync_db_read(pmdb_t *db, struct archive *archive, + struct archive_entry *entry, pmpkg_t *likely_pkg); + +/* + * This is the data table used to generate the estimating function below. + * "Weighted Avg" means averaging the bottom table values; thus each repo, big + * or small, will have equal influence. "Unweighted Avg" means averaging the + * sums of the top table columns, thus each package has equal influence. The + * final values are calculated by (surprise) averaging the averages, because + * why the hell not. + * + * Database Pkgs tar bz2 gz xz + * community 2096 5294080 256391 421227 301296 + * core 180 460800 25257 36850 29356 + * extra 2606 6635520 294647 470818 339392 + * multilib 126 327680 16120 23261 18732 + * testing 76 204800 10902 14348 12100 + * + * Bytes Per Package + * community 2096 2525.80 122.32 200.97 143.75 + * core 180 2560.00 140.32 204.72 163.09 + * extra 2606 2546.25 113.06 180.67 130.23 + * multilib 126 2600.63 127.94 184.61 148.67 + * testing 76 2694.74 143.45 188.79 159.21 + + * Weighted Avg 2585.48 129.42 191.95 148.99 + * Unweighted Avg 2543.39 118.74 190.16 137.93 + * Average of Avgs 2564.44 124.08 191.06 143.46 + */ +static int estimate_package_count(struct stat *st, struct archive *archive) +{ + unsigned int per_package; + + switch(archive_compression(archive)) { + case ARCHIVE_COMPRESSION_NONE: + per_package = 2564; + break; + case ARCHIVE_COMPRESSION_GZIP: + per_package = 191; + break; + case ARCHIVE_COMPRESSION_BZIP2: + per_package = 124; + break; + case ARCHIVE_COMPRESSION_COMPRESS: + per_package = 193; + break; + case ARCHIVE_COMPRESSION_LZMA: + case ARCHIVE_COMPRESSION_XZ: + per_package = 143; + break; + case ARCHIVE_COMPRESSION_UU: + per_package = 3543; + break; + default: + /* assume it is at least somewhat compressed */ + per_package = 200; + } + return((int)(st->st_size / per_package) + 1); +} + +static int sync_db_populate(pmdb_t *db) +{ + int est_count, count = 0; + struct stat buf; + struct archive *archive; + struct archive_entry *entry; + pmpkg_t *pkg = NULL; + + ALPM_LOG_FUNC; + + ASSERT(db != NULL, RET_ERR(PM_ERR_DB_NULL, -1)); + + if((archive = archive_read_new()) == NULL) + RET_ERR(PM_ERR_LIBARCHIVE, 1); + + archive_read_support_compression_all(archive); + archive_read_support_format_all(archive); + + if(archive_read_open_filename(archive, _alpm_db_path(db), + ARCHIVE_DEFAULT_BYTES_PER_BLOCK) != ARCHIVE_OK) { + _alpm_log(PM_LOG_ERROR, _("could not open %s: %s\n"), _alpm_db_path(db), + archive_error_string(archive)); + archive_read_finish(archive); + RET_ERR(PM_ERR_DB_OPEN, 1); + } + if(lstat(_alpm_db_path(db), &buf) != 0) { + RET_ERR(PM_ERR_DB_OPEN, 1); + } + est_count = estimate_package_count(&buf, archive); + + /* initialize hash at 66% full */ + db->pkgcache = _alpm_pkghash_create(est_count * 3 / 2); + + while(archive_read_next_header(archive, &entry) == ARCHIVE_OK) { + const struct stat *st; + + st = archive_entry_stat(entry); + + if(S_ISDIR(st->st_mode)) { + const char *name; + + pkg = _alpm_pkg_new(); + if(pkg == NULL) { + archive_read_finish(archive); + RET_ERR(PM_ERR_MEMORY, -1); + } + + name = archive_entry_pathname(entry); + + if(_alpm_splitname(name, pkg) != 0) { + _alpm_log(PM_LOG_ERROR, _("invalid name for database entry '%s'\n"), + name); + _alpm_pkg_free(pkg); + continue; + } + + /* duplicated database entries are not allowed */ + if(_alpm_pkghash_find(db->pkgcache, pkg->name)) { + _alpm_log(PM_LOG_ERROR, _("duplicated database entry '%s'\n"), pkg->name); + _alpm_pkg_free(pkg); + continue; + } + + pkg->origin = PKG_FROM_SYNCDB; + pkg->ops = &default_pkg_ops; + pkg->origin_data.db = db; + + /* add to the collection */ + _alpm_log(PM_LOG_FUNCTION, "adding '%s' to package cache for db '%s'\n", + pkg->name, db->treename); + db->pkgcache = _alpm_pkghash_add(db->pkgcache, pkg); + count++; + } else { + /* we have desc, depends or deltas - parse it */ + sync_db_read(db, archive, entry, pkg); + } + } + + if(count > 0) { + db->pkgcache->list = alpm_list_msort(db->pkgcache->list, (size_t)count, _alpm_pkg_cmp); + } + archive_read_finish(archive); + + return(count); +} + +#define READ_NEXT(s) do { \ + if(_alpm_archive_fgets(archive, &buf) != ARCHIVE_OK) goto error; \ + s = _alpm_strtrim(buf.line); \ +} while(0) + +#define READ_AND_STORE(f) do { \ + READ_NEXT(line); \ + STRDUP(f, line, goto error); \ +} while(0) + +#define READ_AND_STORE_ALL(f) do { \ + char *linedup; \ + READ_NEXT(line); \ + if(strlen(line) == 0) break; \ + STRDUP(linedup, line, goto error); \ + f = alpm_list_add(f, linedup); \ +} while(1) /* note the while(1) and not (0) */ + +static int sync_db_read(pmdb_t *db, struct archive *archive, + struct archive_entry *entry, pmpkg_t *likely_pkg) +{ + const char *entryname = NULL, *filename; + char *pkgname, *p, *q; + pmpkg_t *pkg; + struct archive_read_buffer buf; + + ALPM_LOG_FUNC; + + if(db == NULL) { + RET_ERR(PM_ERR_DB_NULL, -1); + } + + if(entry != NULL) { + entryname = archive_entry_pathname(entry); + } + if(entryname == NULL) { + _alpm_log(PM_LOG_DEBUG, "invalid archive entry provided to _alpm_sync_db_read, skipping\n"); + return(-1); + } + + _alpm_log(PM_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; + + /* get package and db file names */ + STRDUP(pkgname, entryname, RET_ERR(PM_ERR_MEMORY, -1)); + p = pkgname + strlen(pkgname); + for(q = --p; *q && *q != '/'; q--); + filename = q + 1; + for(p = --q; *p && *p != '-'; p--); + for(q = --p; *q && *q != '-'; q--); + *q = '\0'; + + /* package is already in db due to parsing of directory name */ + if(likely_pkg && strcmp(likely_pkg->name, pkgname) == 0) { + pkg = likely_pkg; + } else { + pkg = _alpm_pkghash_find(db->pkgcache, pkgname); + } + if(pkg == NULL) { + _alpm_log(PM_LOG_DEBUG, "package %s not found in %s sync database", + pkgname, db->treename); + return(-1); + } + + if(strcmp(filename, "desc") == 0 || strcmp(filename, "depends") == 0 + || strcmp(filename, "deltas") == 0) { + while(_alpm_archive_fgets(archive, &buf) == ARCHIVE_OK) { + char *line = _alpm_strtrim(buf.line); + + if(strcmp(line, "%NAME%") == 0) { + READ_NEXT(line); + if(strcmp(line, pkg->name) != 0) { + _alpm_log(PM_LOG_ERROR, _("%s database is inconsistent: name " + "mismatch on package %s\n"), db->treename, pkg->name); + } + } else if(strcmp(line, "%VERSION%") == 0) { + READ_NEXT(line); + if(strcmp(line, pkg->version) != 0) { + _alpm_log(PM_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); + } 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(line); + pkg->builddate = _alpm_parsedate(line); + } else if(strcmp(line, "%PACKAGER%") == 0) { + READ_AND_STORE(pkg->packager); + } else if(strcmp(line, "%CSIZE%") == 0) { + /* Note: the CSIZE and SIZE fields both share the "size" field in the + * pkginfo_t struct. This can be done b/c CSIZE is currently only used + * in sync databases, and SIZE is only used in local databases. + */ + READ_NEXT(line); + pkg->size = atol(line); + /* also store this value to isize if isize is unset */ + if(pkg->isize == 0) { + pkg->isize = pkg->size; + } + } else if(strcmp(line, "%ISIZE%") == 0) { + READ_NEXT(line); + pkg->isize = atol(line); + } else if(strcmp(line, "%MD5SUM%") == 0) { + READ_AND_STORE(pkg->md5sum); + } else if(strcmp(line, "%REPLACES%") == 0) { + READ_AND_STORE_ALL(pkg->replaces); + } else if(strcmp(line, "%DEPENDS%") == 0) { + /* Different than the rest because of the _alpm_splitdep call. */ + while(1) { + READ_NEXT(line); + if(strlen(line) == 0) break; + pkg->depends = alpm_list_add(pkg->depends, _alpm_splitdep(line)); + } + } else if(strcmp(line, "%OPTDEPENDS%") == 0) { + READ_AND_STORE_ALL(pkg->optdepends); + } else if(strcmp(line, "%CONFLICTS%") == 0) { + READ_AND_STORE_ALL(pkg->conflicts); + } else if(strcmp(line, "%PROVIDES%") == 0) { + READ_AND_STORE_ALL(pkg->provides); + } else if(strcmp(line, "%DELTAS%") == 0) { + READ_AND_STORE_ALL(pkg->deltas); + } + } + } else { + /* unknown database file */ + _alpm_log(PM_LOG_DEBUG, "unknown database file: %s", filename); + } + +error: + FREE(pkgname); + /* TODO: return 0 always? */ + return(0); +} + +struct db_operations sync_db_ops = { + .populate = sync_db_populate, + .unregister = _alpm_db_unregister, +}; + +pmdb_t *_alpm_db_register_sync(const char *treename) +{ + pmdb_t *db; + alpm_list_t *i; + + ALPM_LOG_FUNC; + + for(i = handle->dbs_sync; i; i = i->next) { + pmdb_t *sdb = i->data; + if(strcmp(treename, sdb->treename) == 0) { + _alpm_log(PM_LOG_DEBUG, "attempt to re-register the '%s' database, using existing\n", sdb->treename); + return sdb; + } + } + + _alpm_log(PM_LOG_DEBUG, "registering sync database '%s'\n", treename); + + db = _alpm_db_new(treename, 0); + db->ops = &sync_db_ops; + if(db == NULL) { + RET_ERR(PM_ERR_DB_CREATE, NULL); + } + + handle->dbs_sync = alpm_list_add(handle->dbs_sync, db); + return(db); +} + + +/* vim: set ts=2 sw=2 noet: */ |