libalpm
Arch Linux Package Manager Library
be_package.c
Go to the documentation of this file.
00001 /*
00002  *  be_package.c : backend for packages
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  *
00007  *  This program is free software; you can redistribute it and/or modify
00008  *  it under the terms of the GNU General Public License as published by
00009  *  the Free Software Foundation; either version 2 of the License, or
00010  *  (at your option) any later version.
00011  *
00012  *  This program is distributed in the hope that it will be useful,
00013  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00014  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00015  *  GNU General Public License for more details.
00016  *
00017  *  You should have received a copy of the GNU General Public License
00018  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
00019  */
00020 
00021 #include <stdlib.h>
00022 #include <string.h>
00023 #include <errno.h>
00024 #include <sys/types.h>
00025 #include <sys/stat.h>
00026 #include <fcntl.h>
00027 
00028 /* libarchive */
00029 #include <archive.h>
00030 #include <archive_entry.h>
00031 
00032 /* libalpm */
00033 #include "alpm_list.h"
00034 #include "alpm.h"
00035 #include "util.h"
00036 #include "log.h"
00037 #include "handle.h"
00038 #include "package.h"
00039 #include "deps.h" /* _alpm_splitdep */
00040 
00041 struct package_changelog {
00042     struct archive *archive;
00043     int fd;
00044 };
00045 
00046 /**
00047  * Open a package changelog for reading. Similar to fopen in functionality,
00048  * except that the returned 'file stream' is from an archive.
00049  * @param pkg the package (file) to read the changelog
00050  * @return a 'file stream' to the package changelog
00051  */
00052 static void *_package_changelog_open(alpm_pkg_t *pkg)
00053 {
00054     ASSERT(pkg != NULL, return NULL);
00055 
00056     struct package_changelog *changelog;
00057     struct archive *archive;
00058     struct archive_entry *entry;
00059     const char *pkgfile = pkg->origin_data.file;
00060     struct stat buf;
00061     int fd;
00062 
00063     fd = _alpm_open_archive(pkg->handle, pkgfile, &buf,
00064             &archive, ALPM_ERR_PKG_OPEN);
00065     if(fd < 0) {
00066         return NULL;
00067     }
00068 
00069     while(archive_read_next_header(archive, &entry) == ARCHIVE_OK) {
00070         const char *entry_name = archive_entry_pathname(entry);
00071 
00072         if(strcmp(entry_name, ".CHANGELOG") == 0) {
00073             changelog = malloc(sizeof(struct package_changelog));
00074             if(!changelog) {
00075                 pkg->handle->pm_errno = ALPM_ERR_MEMORY;
00076                 archive_read_finish(archive);
00077                 CLOSE(fd);
00078                 return NULL;
00079             }
00080             changelog->archive = archive;
00081             changelog->fd = fd;
00082             return changelog;
00083         }
00084     }
00085     /* we didn't find a changelog */
00086     archive_read_finish(archive);
00087     CLOSE(fd);
00088     errno = ENOENT;
00089 
00090     return NULL;
00091 }
00092 
00093 /**
00094  * Read data from an open changelog 'file stream'. Similar to fread in
00095  * functionality, this function takes a buffer and amount of data to read.
00096  * @param ptr a buffer to fill with raw changelog data
00097  * @param size the size of the buffer
00098  * @param pkg the package that the changelog is being read from
00099  * @param fp a 'file stream' to the package changelog
00100  * @return the number of characters read, or 0 if there is no more data
00101  */
00102 static size_t _package_changelog_read(void *ptr, size_t size,
00103         const alpm_pkg_t UNUSED *pkg, void *fp)
00104 {
00105     struct package_changelog *changelog = fp;
00106     ssize_t sret = archive_read_data(changelog->archive, ptr, size);
00107     /* Report error (negative values) */
00108     if(sret < 0) {
00109         RET_ERR(pkg->handle, ALPM_ERR_LIBARCHIVE, 0);
00110     } else {
00111         return (size_t)sret;
00112     }
00113 }
00114 
00115 /**
00116  * Close a package changelog for reading. Similar to fclose in functionality,
00117  * except that the 'file stream' is from an archive.
00118  * @param pkg the package (file) that the changelog was read from
00119  * @param fp a 'file stream' to the package changelog
00120  * @return whether closing the package changelog stream was successful
00121  */
00122 static int _package_changelog_close(const alpm_pkg_t UNUSED *pkg, void *fp)
00123 {
00124     int ret;
00125     struct package_changelog *changelog = fp;
00126     ret = archive_read_finish(changelog->archive);
00127     CLOSE(changelog->fd);
00128     free(changelog);
00129     return ret;
00130 }
00131 
00132 /** Package file operations struct accessor. We implement this as a method
00133  * rather than a static struct as in be_files because we want to reuse the
00134  * majority of the default_pkg_ops struct and add only a few operations of
00135  * our own on top.
00136  */
00137 static struct pkg_operations *get_file_pkg_ops(void)
00138 {
00139     static struct pkg_operations file_pkg_ops;
00140     static int file_pkg_ops_initialized = 0;
00141     if(!file_pkg_ops_initialized) {
00142         file_pkg_ops = default_pkg_ops;
00143         file_pkg_ops.changelog_open  = _package_changelog_open;
00144         file_pkg_ops.changelog_read  = _package_changelog_read;
00145         file_pkg_ops.changelog_close = _package_changelog_close;
00146         file_pkg_ops_initialized = 1;
00147     }
00148     return &file_pkg_ops;
00149 }
00150 
00151 /**
00152  * Parses the package description file for a package into a alpm_pkg_t struct.
00153  * @param archive the archive to read from, pointed at the .PKGINFO entry
00154  * @param newpkg an empty alpm_pkg_t struct to fill with package info
00155  *
00156  * @return 0 on success, -1 on error
00157  */
00158 static int parse_descfile(alpm_handle_t *handle, struct archive *a, alpm_pkg_t *newpkg)
00159 {
00160     char *ptr = NULL;
00161     char *key = NULL;
00162     int ret, linenum = 0;
00163     struct archive_read_buffer buf;
00164 
00165     memset(&buf, 0, sizeof(buf));
00166     /* 512K for a line length seems reasonable */
00167     buf.max_line_size = 512 * 1024;
00168 
00169     /* loop until we reach EOF or other error */
00170     while((ret = _alpm_archive_fgets(a, &buf)) == ARCHIVE_OK) {
00171         size_t len = _alpm_strip_newline(buf.line);
00172 
00173         linenum++;
00174         key = buf.line;
00175         if(len == 0 || key[0] == '#') {
00176             continue;
00177         }
00178         /* line is always in this format: "key = value"
00179          * we can be sure the " = " exists, so look for that */
00180         ptr = memchr(key, ' ', len);
00181         if(!ptr || (size_t)(ptr - key + 2) > len || memcmp(ptr, " = ", 3) != 0) {
00182             _alpm_log(handle, ALPM_LOG_DEBUG,
00183                     "%s: syntax error in description file line %d\n",
00184                     newpkg->name ? newpkg->name : "error", linenum);
00185         } else {
00186             /* NULL the end of the key portion, move ptr to start of value */
00187             *ptr = '\0';
00188             ptr += 3;
00189             if(strcmp(key, "pkgname") == 0) {
00190                 STRDUP(newpkg->name, ptr, return -1);
00191                 newpkg->name_hash = _alpm_hash_sdbm(newpkg->name);
00192             } else if(strcmp(key, "pkgbase") == 0) {
00193                 /* not used atm */
00194             } else if(strcmp(key, "pkgver") == 0) {
00195                 STRDUP(newpkg->version, ptr, return -1);
00196             } else if(strcmp(key, "pkgdesc") == 0) {
00197                 STRDUP(newpkg->desc, ptr, return -1);
00198             } else if(strcmp(key, "group") == 0) {
00199                 newpkg->groups = alpm_list_add(newpkg->groups, strdup(ptr));
00200             } else if(strcmp(key, "url") == 0) {
00201                 STRDUP(newpkg->url, ptr, return -1);
00202             } else if(strcmp(key, "license") == 0) {
00203                 newpkg->licenses = alpm_list_add(newpkg->licenses, strdup(ptr));
00204             } else if(strcmp(key, "builddate") == 0) {
00205                 newpkg->builddate = _alpm_parsedate(ptr);
00206             } else if(strcmp(key, "packager") == 0) {
00207                 STRDUP(newpkg->packager, ptr, return -1);
00208             } else if(strcmp(key, "arch") == 0) {
00209                 STRDUP(newpkg->arch, ptr, return -1);
00210             } else if(strcmp(key, "size") == 0) {
00211                 /* size in the raw package is uncompressed (installed) size */
00212                 newpkg->isize = _alpm_strtoofft(ptr);
00213             } else if(strcmp(key, "depend") == 0) {
00214                 alpm_depend_t *dep = _alpm_splitdep(ptr);
00215                 newpkg->depends = alpm_list_add(newpkg->depends, dep);
00216             } else if(strcmp(key, "optdepend") == 0) {
00217                 newpkg->optdepends = alpm_list_add(newpkg->optdepends, strdup(ptr));
00218             } else if(strcmp(key, "conflict") == 0) {
00219                 alpm_depend_t *conflict = _alpm_splitdep(ptr);
00220                 newpkg->conflicts = alpm_list_add(newpkg->conflicts, conflict);
00221             } else if(strcmp(key, "replaces") == 0) {
00222                 alpm_depend_t *replace = _alpm_splitdep(ptr);
00223                 newpkg->replaces = alpm_list_add(newpkg->replaces, replace);
00224             } else if(strcmp(key, "provides") == 0) {
00225                 alpm_depend_t *provide = _alpm_splitdep(ptr);
00226                 newpkg->provides = alpm_list_add(newpkg->provides, provide);
00227             } else if(strcmp(key, "backup") == 0) {
00228                 alpm_backup_t *backup;
00229                 CALLOC(backup, 1, sizeof(alpm_backup_t), return -1);
00230                 STRDUP(backup->name, ptr, return -1);
00231                 newpkg->backup = alpm_list_add(newpkg->backup, backup);
00232             } else if(strcmp(key, "force") == 0) {
00233                 /* deprecated, skip it */
00234             } else if(strcmp(key, "makepkgopt") == 0) {
00235                 /* not used atm */
00236             } else {
00237                 _alpm_log(handle, ALPM_LOG_DEBUG, "%s: unknown key '%s' in description file line %d\n",
00238                                     newpkg->name ? newpkg->name : "error", key, linenum);
00239             }
00240         }
00241     }
00242     if(ret != ARCHIVE_EOF) {
00243         _alpm_log(handle, ALPM_LOG_DEBUG, "error parsing package descfile\n");
00244         return -1;
00245     }
00246 
00247     return 0;
00248 }
00249 
00250 static void files_merge(alpm_file_t a[], alpm_file_t b[], alpm_file_t c[],
00251         size_t m, size_t n)
00252 {
00253     size_t i = 0, j = 0, k = 0;
00254     while(i < m && j < n) {
00255         if(strcmp(a[i].name, b[j].name) < 0) {
00256             c[k++] = a[i++];
00257         } else {
00258             c[k++] = b[j++];
00259         }
00260     }
00261     while(i < m) {
00262         c[k++] = a[i++];
00263     }
00264     while(j < n) {
00265         c[k++] = b[j++];
00266     }
00267 }
00268 
00269 static alpm_file_t *files_msort(alpm_file_t *files, size_t n)
00270 {
00271     alpm_file_t *work;
00272     size_t blocksize = 1;
00273 
00274     CALLOC(work, n, sizeof(alpm_file_t), return NULL);
00275 
00276     for(blocksize = 1; blocksize < n; blocksize *= 2) {
00277         size_t i, max_extent = 0;
00278         for(i = 0; i < n - blocksize; i += 2 * blocksize) {
00279             /* this limits our actual merge to the length of the array, since we will
00280              * not likely be a perfect power of two. */
00281             size_t right_blocksize = blocksize;
00282             if(i + blocksize * 2 > n) {
00283                 right_blocksize = n - i - blocksize;
00284             }
00285             files_merge(files + i, files + i + blocksize, work + i,
00286                     blocksize, right_blocksize);
00287             max_extent = i + blocksize + right_blocksize;
00288         }
00289         /* ensure we only copy what we actually touched on this merge pass,
00290          * no more, no less */
00291         memcpy(files, work, max_extent * sizeof(alpm_file_t));
00292     }
00293     free(work);
00294     return files;
00295 }
00296 
00297 /**
00298  * Validate a package.
00299  * @param handle the context handle
00300  * @param pkgfile path to the package file
00301  * @param syncpkg package object to load verification data from (md5sum,
00302  * sha256sum, and/or base64 signature)
00303  * @param level the required level of signature verification
00304  * @param sigdata signature data from the package to pass back
00305  * @return 0 if package is fully valid, -1 and pm_errno otherwise
00306  */
00307 int _alpm_pkg_validate_internal(alpm_handle_t *handle,
00308         const char *pkgfile, alpm_pkg_t *syncpkg, alpm_siglevel_t level,
00309         alpm_siglist_t **sigdata)
00310 {
00311     int has_sig;
00312     handle->pm_errno = 0;
00313 
00314     if(pkgfile == NULL || strlen(pkgfile) == 0) {
00315         RET_ERR(handle, ALPM_ERR_WRONG_ARGS, -1);
00316     }
00317 
00318     /* attempt to access the package file, ensure it exists */
00319     if(access(pkgfile, R_OK) != 0) {
00320         RET_ERR(handle, ALPM_ERR_PKG_NOT_FOUND, -1);
00321     }
00322 
00323     /* can we get away with skipping checksums? */
00324     has_sig = 0;
00325     if(level & ALPM_SIG_PACKAGE) {
00326         if(syncpkg && syncpkg->base64_sig) {
00327             has_sig = 1;
00328         } else {
00329             char *sigpath = _alpm_sigpath(handle, pkgfile);
00330             if(sigpath && !_alpm_access(handle, NULL, sigpath, R_OK)) {
00331                 has_sig = 1;
00332             }
00333             free(sigpath);
00334         }
00335     }
00336 
00337     if(syncpkg && !has_sig) {
00338         if(syncpkg->md5sum && !syncpkg->sha256sum) {
00339             _alpm_log(handle, ALPM_LOG_DEBUG, "md5sum: %s\n", syncpkg->md5sum);
00340             _alpm_log(handle, ALPM_LOG_DEBUG, "checking md5sum for %s\n", pkgfile);
00341             if(_alpm_test_checksum(pkgfile, syncpkg->md5sum, ALPM_CSUM_MD5) != 0) {
00342                 RET_ERR(handle, ALPM_ERR_PKG_INVALID_CHECKSUM, -1);
00343             }
00344         }
00345 
00346         if(syncpkg->sha256sum) {
00347             _alpm_log(handle, ALPM_LOG_DEBUG, "sha256sum: %s\n", syncpkg->sha256sum);
00348             _alpm_log(handle, ALPM_LOG_DEBUG, "checking sha256sum for %s\n", pkgfile);
00349             if(_alpm_test_checksum(pkgfile, syncpkg->sha256sum, ALPM_CSUM_SHA256) != 0) {
00350                 RET_ERR(handle, ALPM_ERR_PKG_INVALID_CHECKSUM, -1);
00351             }
00352         }
00353     }
00354 
00355     /* even if we don't have a sig, run the check code if level tells us to */
00356     if(has_sig || level & ALPM_SIG_PACKAGE) {
00357         const char *sig = syncpkg ? syncpkg->base64_sig : NULL;
00358         _alpm_log(handle, ALPM_LOG_DEBUG, "sig data: %s\n", sig ? sig : "<from .sig>");
00359         if(_alpm_check_pgp_helper(handle, pkgfile, sig,
00360                     level & ALPM_SIG_PACKAGE_OPTIONAL, level & ALPM_SIG_PACKAGE_MARGINAL_OK,
00361                     level & ALPM_SIG_PACKAGE_UNKNOWN_OK, sigdata)) {
00362             handle->pm_errno = ALPM_ERR_PKG_INVALID_SIG;
00363             return -1;
00364         }
00365     }
00366 
00367     return 0;
00368 }
00369 
00370 /**
00371  * Load a package and create the corresponding alpm_pkg_t struct.
00372  * @param handle the context handle
00373  * @param pkgfile path to the package file
00374  * @param full whether to stop the load after metadata is read or continue
00375  * through the full archive
00376  */
00377 alpm_pkg_t *_alpm_pkg_load_internal(alpm_handle_t *handle,
00378         const char *pkgfile, int full)
00379 {
00380     int ret, fd, config = 0;
00381     struct archive *archive;
00382     struct archive_entry *entry;
00383     alpm_pkg_t *newpkg;
00384     struct stat st;
00385     size_t files_count = 0, files_size = 0;
00386     alpm_file_t *files = NULL;
00387 
00388     if(pkgfile == NULL || strlen(pkgfile) == 0) {
00389         RET_ERR(handle, ALPM_ERR_WRONG_ARGS, NULL);
00390     }
00391 
00392     fd = _alpm_open_archive(handle, pkgfile, &st, &archive, ALPM_ERR_PKG_OPEN);
00393     if(fd < 0) {
00394         if(errno == ENOENT) {
00395             handle->pm_errno = ALPM_ERR_PKG_NOT_FOUND;
00396         }
00397         return NULL;
00398     }
00399 
00400     newpkg = _alpm_pkg_new();
00401     if(newpkg == NULL) {
00402         handle->pm_errno = ALPM_ERR_MEMORY;
00403         goto error;
00404     }
00405     STRDUP(newpkg->filename, pkgfile,
00406             handle->pm_errno = ALPM_ERR_MEMORY; goto error);
00407     newpkg->size = st.st_size;
00408 
00409     _alpm_log(handle, ALPM_LOG_DEBUG, "starting package load for %s\n", pkgfile);
00410 
00411     /* If full is false, only read through the archive until we find our needed
00412      * metadata. If it is true, read through the entire archive, which serves
00413      * as a verfication of integrity and allows us to create the filelist. */
00414     while((ret = archive_read_next_header(archive, &entry)) == ARCHIVE_OK) {
00415         const char *entry_name = archive_entry_pathname(entry);
00416 
00417         if(strcmp(entry_name, ".PKGINFO") == 0) {
00418             /* parse the info file */
00419             if(parse_descfile(handle, archive, newpkg) != 0) {
00420                 _alpm_log(handle, ALPM_LOG_ERROR, _("could not parse package description file in %s\n"),
00421                         pkgfile);
00422                 goto pkg_invalid;
00423             }
00424             if(newpkg->name == NULL || strlen(newpkg->name) == 0) {
00425                 _alpm_log(handle, ALPM_LOG_ERROR, _("missing package name in %s\n"), pkgfile);
00426                 goto pkg_invalid;
00427             }
00428             if(newpkg->version == NULL || strlen(newpkg->version) == 0) {
00429                 _alpm_log(handle, ALPM_LOG_ERROR, _("missing package version in %s\n"), pkgfile);
00430                 goto pkg_invalid;
00431             }
00432             config = 1;
00433             continue;
00434         } else if(strcmp(entry_name,  ".INSTALL") == 0) {
00435             newpkg->scriptlet = 1;
00436         } else if(*entry_name == '.') {
00437             /* for now, ignore all files starting with '.' that haven't
00438              * already been handled (for future possibilities) */
00439         } else if(full) {
00440             /* Keep track of all files for filelist generation */
00441             if(files_count >= files_size) {
00442                 size_t old_size = files_size;
00443                 if(files_size == 0) {
00444                     files_size = 4;
00445                 } else {
00446                     files_size *= 2;
00447                 }
00448                 files = realloc(files, sizeof(alpm_file_t) * files_size);
00449                 if(!files) {
00450                     ALLOC_FAIL(sizeof(alpm_file_t) * files_size);
00451                     goto error;
00452                 }
00453                 /* ensure all new memory is zeroed out, in both the initial
00454                  * allocation and later reallocs */
00455                 memset(files + old_size, 0,
00456                         sizeof(alpm_file_t) * (files_size - old_size));
00457             }
00458             STRDUP(files[files_count].name, entry_name, goto error);
00459             files[files_count].size = archive_entry_size(entry);
00460             files[files_count].mode = archive_entry_mode(entry);
00461             files_count++;
00462         }
00463 
00464         if(archive_read_data_skip(archive)) {
00465             _alpm_log(handle, ALPM_LOG_ERROR, _("error while reading package %s: %s\n"),
00466                     pkgfile, archive_error_string(archive));
00467             handle->pm_errno = ALPM_ERR_LIBARCHIVE;
00468             goto error;
00469         }
00470 
00471         /* if we are not doing a full read, see if we have all we need */
00472         if(!full && config) {
00473             break;
00474         }
00475     }
00476 
00477     if(ret != ARCHIVE_EOF && ret != ARCHIVE_OK) { /* An error occured */
00478         _alpm_log(handle, ALPM_LOG_ERROR, _("error while reading package %s: %s\n"),
00479                 pkgfile, archive_error_string(archive));
00480         handle->pm_errno = ALPM_ERR_LIBARCHIVE;
00481         goto error;
00482     }
00483 
00484     if(!config) {
00485         _alpm_log(handle, ALPM_LOG_ERROR, _("missing package metadata in %s\n"), pkgfile);
00486         goto pkg_invalid;
00487     }
00488 
00489     archive_read_finish(archive);
00490     CLOSE(fd);
00491 
00492     /* internal fields for package struct */
00493     newpkg->origin = PKG_FROM_FILE;
00494     newpkg->origin_data.file = strdup(pkgfile);
00495     newpkg->ops = get_file_pkg_ops();
00496     newpkg->handle = handle;
00497     newpkg->infolevel = INFRQ_BASE | INFRQ_DESC | INFRQ_SCRIPTLET;
00498 
00499     if(full) {
00500         if(files) {
00501             /* attempt to hand back any memory we don't need */
00502             files = realloc(files, sizeof(alpm_file_t) * files_count);
00503             /* "checking for conflicts" requires a sorted list, ensure that here */
00504             _alpm_log(handle, ALPM_LOG_DEBUG,
00505                     "sorting package filelist for %s\n", pkgfile);
00506             newpkg->files.files = files_msort(files, files_count);
00507         }
00508         newpkg->files.count = files_count;
00509         newpkg->infolevel |= INFRQ_FILES;
00510     }
00511 
00512     return newpkg;
00513 
00514 pkg_invalid:
00515     handle->pm_errno = ALPM_ERR_PKG_INVALID;
00516 error:
00517     _alpm_pkg_free(newpkg);
00518     archive_read_finish(archive);
00519     if(fd >= 0) {
00520         CLOSE(fd);
00521     }
00522 
00523     return NULL;
00524 }
00525 
00526 int SYMEXPORT alpm_pkg_load(alpm_handle_t *handle, const char *filename, int full,
00527         alpm_siglevel_t level, alpm_pkg_t **pkg)
00528 {
00529     CHECK_HANDLE(handle, return -1);
00530     ASSERT(pkg != NULL, RET_ERR(handle, ALPM_ERR_WRONG_ARGS, -1));
00531 
00532     if(_alpm_pkg_validate_internal(handle, filename, NULL, level, NULL) == -1) {
00533         /* pm_errno is set by pkg_validate */
00534         return -1;
00535     }
00536     *pkg = _alpm_pkg_load_internal(handle, filename, full);
00537     if(*pkg == NULL) {
00538         /* pm_errno is set by pkg_load */
00539         return -1;
00540     }
00541 
00542     return 0;
00543 }
00544 
00545 /* vim: set ts=2 sw=2 noet: */