libalpm
Arch Linux Package Manager Library
util.c
Go to the documentation of this file.
00001 /*
00002  *  util.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) 2005 by Christian Hamar <krics@linuxforum.hu>
00008  *  Copyright (c) 2006 by David Kimpe <dnaku@frugalware.org>
00009  *  Copyright (c) 2005, 2006 by Miklos Vajna <vmiklos@frugalware.org>
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 <unistd.h>
00027 #include <ctype.h>
00028 #include <dirent.h>
00029 #include <time.h>
00030 #include <syslog.h>
00031 #include <errno.h>
00032 #include <limits.h>
00033 #include <sys/wait.h>
00034 #include <locale.h> /* setlocale */
00035 #include <fnmatch.h>
00036 
00037 /* libarchive */
00038 #include <archive.h>
00039 #include <archive_entry.h>
00040 
00041 #ifdef HAVE_LIBSSL
00042 #include <openssl/md5.h>
00043 #include <openssl/sha.h>
00044 #else
00045 #include "md5.h"
00046 #include "sha2.h"
00047 #endif
00048 
00049 /* libalpm */
00050 #include "util.h"
00051 #include "log.h"
00052 #include "alpm.h"
00053 #include "alpm_list.h"
00054 #include "handle.h"
00055 #include "trans.h"
00056 
00057 #ifndef HAVE_STRSEP
00058 /** Extracts tokens from a string.
00059  * Replaces strset which is not portable (missing on Solaris).
00060  * Copyright (c) 2001 by François Gouget <fgouget_at_codeweavers.com>
00061  * Modifies str to point to the first character after the token if one is
00062  * found, or NULL if one is not.
00063  * @param str string containing delimited tokens to parse
00064  * @param delim character delimiting tokens in str
00065  * @return pointer to the first token in str if str is not NULL, NULL if
00066  * str is NULL
00067  */
00068 char* strsep(char** str, const char* delims)
00069 {
00070     char* token;
00071 
00072     if(*str==NULL) {
00073         /* No more tokens */
00074         return NULL;
00075     }
00076 
00077     token=*str;
00078     while(**str!='\0') {
00079         if(strchr(delims,**str)!=NULL) {
00080             **str='\0';
00081             (*str)++;
00082             return token;
00083         }
00084         (*str)++;
00085     }
00086     /* There is no other token */
00087     *str=NULL;
00088     return token;
00089 }
00090 #endif
00091 
00092 int _alpm_makepath(const char *path)
00093 {
00094     return _alpm_makepath_mode(path, 0755);
00095 }
00096 
00097 /** Creates a directory, including parents if needed, similar to 'mkdir -p'.
00098  * @param path directory path to create
00099  * @param mode permission mode for created directories
00100  * @return 0 on success, 1 on error
00101  */
00102 int _alpm_makepath_mode(const char *path, mode_t mode)
00103 {
00104     /* A bit of pointer hell here. Descriptions:
00105      * orig - a copy of path so we can safely butcher it with strsep
00106      * str - the current position in the path string (after the delimiter)
00107      * ptr - the original position of str after calling strsep
00108      * incr - incrementally generated path for use in stat/mkdir call
00109      */
00110     char *orig, *str, *ptr, *incr;
00111     mode_t oldmask = umask(0000);
00112     int ret = 0;
00113 
00114     orig = strdup(path);
00115     incr = calloc(strlen(orig) + 1, sizeof(char));
00116     str = orig;
00117     while((ptr = strsep(&str, "/"))) {
00118         if(strlen(ptr)) {
00119             /* we have another path component- append the newest component to
00120              * existing string and create one more level of dir structure */
00121             strcat(incr, "/");
00122             strcat(incr, ptr);
00123             if(access(incr, F_OK)) {
00124                 if(mkdir(incr, mode)) {
00125                     ret = 1;
00126                     break;
00127                 }
00128             }
00129         }
00130     }
00131     free(orig);
00132     free(incr);
00133     umask(oldmask);
00134     return ret;
00135 }
00136 
00137 /** Copies a file.
00138  * @param src file path to copy from
00139  * @param dest file path to copy to
00140  * @return 0 on success, 1 on error 
00141  */
00142 int _alpm_copyfile(const char *src, const char *dest)
00143 {
00144     char *buf;
00145     int in, out, ret = 1;
00146     ssize_t nread;
00147     struct stat st;
00148 
00149     MALLOC(buf, (size_t)ALPM_BUFFER_SIZE, return 1);
00150 
00151     OPEN(in, src, O_RDONLY);
00152     do {
00153         out = open(dest, O_WRONLY | O_CREAT, 0000);
00154     } while(out == -1 && errno == EINTR);
00155     if(in < 0 || out < 0) {
00156         goto cleanup;
00157     }
00158 
00159     if(fstat(in, &st) || fchmod(out, st.st_mode)) {
00160         goto cleanup;
00161     }
00162 
00163     /* do the actual file copy */
00164     while((nread = read(in, buf, ALPM_BUFFER_SIZE)) > 0 || errno == EINTR) {
00165         ssize_t nwrite = 0;
00166         if(nread < 0) {
00167             continue;
00168         }
00169         do {
00170             nwrite = write(out, buf + nwrite, nread);
00171             if(nwrite >= 0) {
00172                 nread -= nwrite;
00173             } else if(errno != EINTR) {
00174                 goto cleanup;
00175             }
00176         } while(nread > 0);
00177     }
00178     ret = 0;
00179 
00180 cleanup:
00181     free(buf);
00182     if(in >= 0) {
00183         CLOSE(in);
00184     }
00185     if(out >= 0) {
00186         CLOSE(out);
00187     }
00188     return ret;
00189 }
00190 
00191 /** Trim trailing newlines from a string (if any exist).
00192  * @param str a single line of text
00193  * @return the length of the trimmed string
00194  */
00195 size_t _alpm_strip_newline(char *str)
00196 {
00197     size_t len;
00198     if(*str == '\0') {
00199         return 0;
00200     }
00201     len = strlen(str);
00202     while(len > 0 && str[len - 1] == '\n') {
00203         len--;
00204     }
00205     str[len] = '\0';
00206 
00207     return len;
00208 }
00209 
00210 /* Compression functions */
00211 
00212 /** Open an archive for reading and perform the necessary boilerplate.
00213  * This takes care of creating the libarchive 'archive' struct, setting up
00214  * compression and format options, opening a file descriptor, setting up the
00215  * buffer size, and performing a stat on the path once opened.
00216  * On error, no file descriptor is opened, and the archive pointer returned
00217  * will be set to NULL.
00218  * @param handle the context handle
00219  * @param path the path of the archive to open
00220  * @param buf space for a stat buffer for the given path
00221  * @param archive pointer to place the created archive object
00222  * @param error error code to set on failure to open archive
00223  * @return -1 on failure, >=0 file descriptor on success
00224  */
00225 int _alpm_open_archive(alpm_handle_t *handle, const char *path,
00226         struct stat *buf, struct archive **archive, alpm_errno_t error)
00227 {
00228     int fd;
00229     size_t bufsize = ALPM_BUFFER_SIZE;
00230 
00231     if((*archive = archive_read_new()) == NULL) {
00232         RET_ERR(handle, ALPM_ERR_LIBARCHIVE, -1);
00233     }
00234 
00235     archive_read_support_compression_all(*archive);
00236     archive_read_support_format_all(*archive);
00237 
00238     _alpm_log(handle, ALPM_LOG_DEBUG, "opening archive %s\n", path);
00239     OPEN(fd, path, O_RDONLY);
00240     if(fd < 0) {
00241         _alpm_log(handle, ALPM_LOG_ERROR,
00242                 _("could not open file %s: %s\n"), path, strerror(errno));
00243         goto error;
00244     }
00245 
00246     if(fstat(fd, buf) != 0) {
00247         _alpm_log(handle, ALPM_LOG_ERROR,
00248                 _("could not stat file %s: %s\n"), path, strerror(errno));
00249         goto error;
00250     }
00251 #ifdef HAVE_STRUCT_STAT_ST_BLKSIZE
00252     if(buf->st_blksize > ALPM_BUFFER_SIZE) {
00253         bufsize = buf->st_blksize;
00254     }
00255 #endif
00256 
00257     if(archive_read_open_fd(*archive, fd, bufsize) != ARCHIVE_OK) {
00258         _alpm_log(handle, ALPM_LOG_ERROR, _("could not open file %s: %s\n"),
00259                 path, archive_error_string(*archive));
00260         goto error;
00261     }
00262 
00263     return fd;
00264 
00265 error:
00266     archive_read_finish(*archive);
00267     *archive = NULL;
00268     if(fd >= 0) {
00269         CLOSE(fd);
00270     }
00271     RET_ERR(handle, error, -1);
00272 }
00273 
00274 /** Unpack a specific file in an archive.
00275  * @param handle the context handle
00276  * @param archive the archive to unpack
00277  * @param prefix where to extract the files
00278  * @param filename a file within the archive to unpack
00279  * @return 0 on success, 1 on failure
00280  */
00281 int _alpm_unpack_single(alpm_handle_t *handle, const char *archive,
00282         const char *prefix, const char *filename)
00283 {
00284     alpm_list_t *list = NULL;
00285     int ret = 0;
00286     if(filename == NULL) {
00287         return 1;
00288     }
00289     list = alpm_list_add(list, (void *)filename);
00290     ret = _alpm_unpack(handle, archive, prefix, list, 1);
00291     alpm_list_free(list);
00292     return ret;
00293 }
00294 
00295 /** Unpack a list of files in an archive.
00296  * @param handle the context handle
00297  * @param path the archive to unpack
00298  * @param prefix where to extract the files
00299  * @param list a list of files within the archive to unpack or NULL for all
00300  * @param breakfirst break after the first entry found
00301  * @return 0 on success, 1 on failure
00302  */
00303 int _alpm_unpack(alpm_handle_t *handle, const char *path, const char *prefix,
00304         alpm_list_t *list, int breakfirst)
00305 {
00306     int ret = 0;
00307     mode_t oldmask;
00308     struct archive *archive;
00309     struct archive_entry *entry;
00310     struct stat buf;
00311     int fd, cwdfd;
00312 
00313     fd = _alpm_open_archive(handle, path, &buf, &archive, ALPM_ERR_PKG_OPEN);
00314     if(fd < 0) {
00315         return 1;
00316     }
00317 
00318     oldmask = umask(0022);
00319 
00320     /* save the cwd so we can restore it later */
00321     OPEN(cwdfd, ".", O_RDONLY);
00322     if(cwdfd < 0) {
00323         _alpm_log(handle, ALPM_LOG_ERROR, _("could not get current working directory\n"));
00324     }
00325 
00326     /* just in case our cwd was removed in the upgrade operation */
00327     if(chdir(prefix) != 0) {
00328         _alpm_log(handle, ALPM_LOG_ERROR, _("could not change directory to %s (%s)\n"),
00329                 prefix, strerror(errno));
00330         ret = 1;
00331         goto cleanup;
00332     }
00333 
00334     while(archive_read_next_header(archive, &entry) == ARCHIVE_OK) {
00335         const char *entryname;
00336         mode_t mode;
00337 
00338         entryname = archive_entry_pathname(entry);
00339 
00340         /* If specific files were requested, skip entries that don't match. */
00341         if(list) {
00342             char *entry_prefix = strdup(entryname);
00343             char *p = strstr(entry_prefix,"/");
00344             if(p) {
00345                 *(p+1) = '\0';
00346             }
00347             char *found = alpm_list_find_str(list, entry_prefix);
00348             free(entry_prefix);
00349             if(!found) {
00350                 if(archive_read_data_skip(archive) != ARCHIVE_OK) {
00351                     ret = 1;
00352                     goto cleanup;
00353                 }
00354                 continue;
00355             } else {
00356                 _alpm_log(handle, ALPM_LOG_DEBUG, "extracting: %s\n", entryname);
00357             }
00358         }
00359 
00360         mode = archive_entry_mode(entry);
00361         if(S_ISREG(mode)) {
00362             archive_entry_set_perm(entry, 0644);
00363         } else if(S_ISDIR(mode)) {
00364             archive_entry_set_perm(entry, 0755);
00365         }
00366 
00367         /* Extract the archive entry. */
00368         int readret = archive_read_extract(archive, entry, 0);
00369         if(readret == ARCHIVE_WARN) {
00370             /* operation succeeded but a non-critical error was encountered */
00371             _alpm_log(handle, ALPM_LOG_WARNING, _("warning given when extracting %s (%s)\n"),
00372                     entryname, archive_error_string(archive));
00373         } else if(readret != ARCHIVE_OK) {
00374             _alpm_log(handle, ALPM_LOG_ERROR, _("could not extract %s (%s)\n"),
00375                     entryname, archive_error_string(archive));
00376             ret = 1;
00377             goto cleanup;
00378         }
00379 
00380         if(breakfirst) {
00381             break;
00382         }
00383     }
00384 
00385 cleanup:
00386     umask(oldmask);
00387     archive_read_finish(archive);
00388     CLOSE(fd);
00389     if(cwdfd >= 0) {
00390         if(fchdir(cwdfd) != 0) {
00391             _alpm_log(handle, ALPM_LOG_ERROR,
00392                     _("could not restore working directory (%s)\n"), strerror(errno));
00393         }
00394         CLOSE(cwdfd);
00395     }
00396 
00397     return ret;
00398 }
00399 
00400 /** Recursively removes a path similar to 'rm -rf'.
00401  * @param path path to remove
00402  * @return 0 on success, number of paths that could not be removed on error
00403  */
00404 int _alpm_rmrf(const char *path)
00405 {
00406     int errflag = 0;
00407     struct dirent *dp;
00408     DIR *dirp;
00409     char name[PATH_MAX];
00410     struct stat st;
00411 
00412     if(_alpm_lstat(path, &st) == 0) {
00413         if(!S_ISDIR(st.st_mode)) {
00414             if(!unlink(path)) {
00415                 return 0;
00416             } else {
00417                 if(errno == ENOENT) {
00418                     return 0;
00419                 } else {
00420                     return 1;
00421                 }
00422             }
00423         } else {
00424             dirp = opendir(path);
00425             if(!dirp) {
00426                 return 1;
00427             }
00428             for(dp = readdir(dirp); dp != NULL; dp = readdir(dirp)) {
00429                 if(dp->d_ino) {
00430                     sprintf(name, "%s/%s", path, dp->d_name);
00431                     if(strcmp(dp->d_name, "..") != 0 && strcmp(dp->d_name, ".") != 0) {
00432                         errflag += _alpm_rmrf(name);
00433                     }
00434                 }
00435             }
00436             closedir(dirp);
00437             if(rmdir(path)) {
00438                 errflag++;
00439             }
00440         }
00441         return errflag;
00442     }
00443     return 0;
00444 }
00445 
00446 /** Determine if there are files in a directory.
00447  * @param handle the context handle
00448  * @param path the full absolute directory path
00449  * @param full_count whether to return an exact count of files
00450  * @return a file count if full_count is != 0, else >0 if directory has
00451  * contents, 0 if no contents, and -1 on error
00452  */
00453 ssize_t _alpm_files_in_directory(alpm_handle_t *handle, const char *path,
00454         int full_count)
00455 {
00456     ssize_t files = 0;
00457     struct dirent *ent;
00458     DIR *dir = opendir(path);
00459 
00460     if(!dir) {
00461         if(errno == ENOTDIR) {
00462             _alpm_log(handle, ALPM_LOG_DEBUG, "%s was not a directory\n", path);
00463         } else {
00464             _alpm_log(handle, ALPM_LOG_DEBUG, "could not read directory %s\n",
00465                     path);
00466         }
00467         return -1;
00468     }
00469     while((ent = readdir(dir)) != NULL) {
00470         const char *name = ent->d_name;
00471 
00472         if(strcmp(name, ".") == 0 || strcmp(name, "..") == 0) {
00473             continue;
00474         }
00475 
00476         files++;
00477 
00478         if(!full_count) {
00479             break;
00480         }
00481     }
00482 
00483     closedir(dir);
00484     return files;
00485 }
00486 
00487 /** Write formatted message to log.
00488  * @param handle the context handle
00489  * @param format formatted string to write out
00490  * @param args formatting arguments
00491  * @return 0 or number of characters written on success, vfprintf return value
00492  * on error
00493  */
00494 int _alpm_logaction(alpm_handle_t *handle, const char *fmt, va_list args)
00495 {
00496     int ret = 0;
00497 
00498     if(handle->usesyslog) {
00499         /* we can't use a va_list more than once, so we need to copy it
00500          * so we can use the original when calling vfprintf below. */
00501         va_list args_syslog;
00502         va_copy(args_syslog, args);
00503         vsyslog(LOG_WARNING, fmt, args_syslog);
00504         va_end(args_syslog);
00505     }
00506 
00507     if(handle->logstream) {
00508         time_t t;
00509         struct tm *tm;
00510 
00511         t = time(NULL);
00512         tm = localtime(&t);
00513 
00514         /* Use ISO-8601 date format */
00515         fprintf(handle->logstream, "[%04d-%02d-%02d %02d:%02d] ",
00516                         tm->tm_year+1900, tm->tm_mon+1, tm->tm_mday,
00517                         tm->tm_hour, tm->tm_min);
00518         ret = vfprintf(handle->logstream, fmt, args);
00519         fflush(handle->logstream);
00520     }
00521 
00522     return ret;
00523 }
00524 
00525 /** Execute a command with arguments in a chroot.
00526  * @param handle the context handle
00527  * @param cmd command to execute
00528  * @param argv arguments to pass to cmd
00529  * @return 0 on success, 1 on error
00530  */
00531 int _alpm_run_chroot(alpm_handle_t *handle, const char *cmd, char *const argv[])
00532 {
00533     pid_t pid;
00534     int pipefd[2], cwdfd;
00535     int retval = 0;
00536 
00537     /* save the cwd so we can restore it later */
00538     OPEN(cwdfd, ".", O_RDONLY);
00539     if(cwdfd < 0) {
00540         _alpm_log(handle, ALPM_LOG_ERROR, _("could not get current working directory\n"));
00541     }
00542 
00543     /* just in case our cwd was removed in the upgrade operation */
00544     if(chdir(handle->root) != 0) {
00545         _alpm_log(handle, ALPM_LOG_ERROR, _("could not change directory to %s (%s)\n"),
00546                 handle->root, strerror(errno));
00547         goto cleanup;
00548     }
00549 
00550     _alpm_log(handle, ALPM_LOG_DEBUG, "executing \"%s\" under chroot \"%s\"\n",
00551             cmd, handle->root);
00552 
00553     /* Flush open fds before fork() to avoid cloning buffers */
00554     fflush(NULL);
00555 
00556     if(pipe(pipefd) == -1) {
00557         _alpm_log(handle, ALPM_LOG_ERROR, _("could not create pipe (%s)\n"), strerror(errno));
00558         retval = 1;
00559         goto cleanup;
00560     }
00561 
00562     /* fork- parent and child each have seperate code blocks below */
00563     pid = fork();
00564     if(pid == -1) {
00565         _alpm_log(handle, ALPM_LOG_ERROR, _("could not fork a new process (%s)\n"), strerror(errno));
00566         retval = 1;
00567         goto cleanup;
00568     }
00569 
00570     if(pid == 0) {
00571         /* this code runs for the child only (the actual chroot/exec) */
00572         CLOSE(1);
00573         CLOSE(2);
00574         while(dup2(pipefd[1], 1) == -1 && errno == EINTR);
00575         while(dup2(pipefd[1], 2) == -1 && errno == EINTR);
00576         CLOSE(pipefd[0]);
00577         CLOSE(pipefd[1]);
00578 
00579         /* use fprintf instead of _alpm_log to send output through the parent */
00580         if(chroot(handle->root) != 0) {
00581             fprintf(stderr, _("could not change the root directory (%s)\n"), strerror(errno));
00582             exit(1);
00583         }
00584         if(chdir("/") != 0) {
00585             fprintf(stderr, _("could not change directory to %s (%s)\n"),
00586                     "/", strerror(errno));
00587             exit(1);
00588         }
00589         umask(0022);
00590         execv(cmd, argv);
00591         /* execv only returns if there was an error */
00592         fprintf(stderr, _("call to execv failed (%s)\n"), strerror(errno));
00593         exit(1);
00594     } else {
00595         /* this code runs for the parent only (wait on the child) */
00596         int status;
00597         FILE *pipe_file;
00598 
00599         CLOSE(pipefd[1]);
00600         pipe_file = fdopen(pipefd[0], "r");
00601         if(pipe_file == NULL) {
00602             CLOSE(pipefd[0]);
00603             retval = 1;
00604         } else {
00605             while(!feof(pipe_file)) {
00606                 char line[PATH_MAX];
00607                 if(fgets(line, PATH_MAX, pipe_file) == NULL)
00608                     break;
00609                 alpm_logaction(handle, "%s", line);
00610                 EVENT(handle, ALPM_EVENT_SCRIPTLET_INFO, line, NULL);
00611             }
00612             fclose(pipe_file);
00613         }
00614 
00615         while(waitpid(pid, &status, 0) == -1) {
00616             if(errno != EINTR) {
00617                 _alpm_log(handle, ALPM_LOG_ERROR, _("call to waitpid failed (%s)\n"), strerror(errno));
00618                 retval = 1;
00619                 goto cleanup;
00620             }
00621         }
00622 
00623         /* report error from above after the child has exited */
00624         if(retval != 0) {
00625             _alpm_log(handle, ALPM_LOG_ERROR, _("could not open pipe (%s)\n"), strerror(errno));
00626             goto cleanup;
00627         }
00628         /* check the return status, make sure it is 0 (success) */
00629         if(WIFEXITED(status)) {
00630             _alpm_log(handle, ALPM_LOG_DEBUG, "call to waitpid succeeded\n");
00631             if(WEXITSTATUS(status) != 0) {
00632                 _alpm_log(handle, ALPM_LOG_ERROR, _("command failed to execute correctly\n"));
00633                 retval = 1;
00634             }
00635         }
00636     }
00637 
00638 cleanup:
00639     if(cwdfd >= 0) {
00640         if(fchdir(cwdfd) != 0) {
00641             _alpm_log(handle, ALPM_LOG_ERROR,
00642                     _("could not restore working directory (%s)\n"), strerror(errno));
00643         }
00644         CLOSE(cwdfd);
00645     }
00646 
00647     return retval;
00648 }
00649 
00650 /** Run ldconfig in a chroot.
00651  * @param handle the context handle
00652  * @return 0 on success, 1 on error
00653  */
00654 int _alpm_ldconfig(alpm_handle_t *handle)
00655 {
00656     char line[PATH_MAX];
00657 
00658     _alpm_log(handle, ALPM_LOG_DEBUG, "running ldconfig\n");
00659 
00660     snprintf(line, PATH_MAX, "%setc/ld.so.conf", handle->root);
00661     if(access(line, F_OK) == 0) {
00662         snprintf(line, PATH_MAX, "%ssbin/ldconfig", handle->root);
00663         if(access(line, X_OK) == 0) {
00664             char *argv[] = { "ldconfig", NULL };
00665             return _alpm_run_chroot(handle, "/sbin/ldconfig", argv);
00666         }
00667     }
00668 
00669     return 0;
00670 }
00671 
00672 /** Helper function for comparing strings using the alpm "compare func"
00673  * signature.
00674  * @param s1 first string to be compared
00675  * @param s2 second string to be compared
00676  * @return 0 if strings are equal, positive int if first unequal character
00677  * has a greater value in s1, negative if it has a greater value in s2
00678  */
00679 int _alpm_str_cmp(const void *s1, const void *s2)
00680 {
00681     return strcmp(s1, s2);
00682 }
00683 
00684 /** Find a filename in a registered alpm cachedir.
00685  * @param handle the context handle
00686  * @param filename name of file to find
00687  * @return malloced path of file, NULL if not found
00688  */
00689 char *_alpm_filecache_find(alpm_handle_t *handle, const char *filename)
00690 {
00691     char path[PATH_MAX];
00692     char *retpath;
00693     alpm_list_t *i;
00694     struct stat buf;
00695 
00696     /* Loop through the cache dirs until we find a matching file */
00697     for(i = handle->cachedirs; i; i = i->next) {
00698         snprintf(path, PATH_MAX, "%s%s", (char *)i->data,
00699                 filename);
00700         if(stat(path, &buf) == 0 && S_ISREG(buf.st_mode)) {
00701             retpath = strdup(path);
00702             _alpm_log(handle, ALPM_LOG_DEBUG, "found cached pkg: %s\n", retpath);
00703             return retpath;
00704         }
00705     }
00706     /* package wasn't found in any cachedir */
00707     return NULL;
00708 }
00709 
00710 /** Check the alpm cachedirs for existance and find a writable one.
00711  * If no valid cache directory can be found, use /tmp.
00712  * @param handle the context handle
00713  * @return pointer to a writable cache directory.
00714  */
00715 const char *_alpm_filecache_setup(alpm_handle_t *handle)
00716 {
00717     struct stat buf;
00718     alpm_list_t *i;
00719     char *cachedir, *tmpdir;
00720 
00721     /* Loop through the cache dirs until we find a usable directory */
00722     for(i = handle->cachedirs; i; i = i->next) {
00723         cachedir = i->data;
00724         if(stat(cachedir, &buf) != 0) {
00725             /* cache directory does not exist.... try creating it */
00726             _alpm_log(handle, ALPM_LOG_WARNING, _("no %s cache exists, creating...\n"),
00727                     cachedir);
00728             if(_alpm_makepath(cachedir) == 0) {
00729                 _alpm_log(handle, ALPM_LOG_DEBUG, "using cachedir: %s\n", cachedir);
00730                 return cachedir;
00731             }
00732         } else if(!S_ISDIR(buf.st_mode)) {
00733             _alpm_log(handle, ALPM_LOG_DEBUG,
00734                     "skipping cachedir, not a directory: %s\n", cachedir);
00735         } else if(access(cachedir, W_OK) != 0) {
00736             _alpm_log(handle, ALPM_LOG_DEBUG,
00737                     "skipping cachedir, not writable: %s\n", cachedir);
00738         } else if(!(buf.st_mode & (S_IWUSR | S_IWGRP | S_IWOTH))) {
00739             _alpm_log(handle, ALPM_LOG_DEBUG,
00740                     "skipping cachedir, no write bits set: %s\n", cachedir);
00741         } else {
00742             _alpm_log(handle, ALPM_LOG_DEBUG, "using cachedir: %s\n", cachedir);
00743             return cachedir;
00744         }
00745     }
00746 
00747     /* we didn't find a valid cache directory. use TMPDIR or /tmp. */
00748     if((tmpdir = getenv("TMPDIR")) && stat(tmpdir, &buf) && S_ISDIR(buf.st_mode)) {
00749         /* TMPDIR was good, we can use it */
00750     } else {
00751         tmpdir = "/tmp";
00752     }
00753     alpm_option_add_cachedir(handle, tmpdir);
00754     cachedir = handle->cachedirs->prev->data;
00755     _alpm_log(handle, ALPM_LOG_DEBUG, "using cachedir: %s\n", cachedir);
00756     _alpm_log(handle, ALPM_LOG_WARNING,
00757             _("couldn't find or create package cache, using %s instead\n"), cachedir);
00758     return cachedir;
00759 }
00760 
00761 /** lstat wrapper that treats /path/dirsymlink/ the same as /path/dirsymlink.
00762  * Linux lstat follows POSIX semantics and still performs a dereference on
00763  * the first, and for uses of lstat in libalpm this is not what we want.
00764  * @param path path to file to lstat
00765  * @param buf structure to fill with stat information
00766  * @return the return code from lstat
00767  */
00768 int _alpm_lstat(const char *path, struct stat *buf)
00769 {
00770     int ret;
00771     size_t len = strlen(path);
00772 
00773     /* strip the trailing slash if one exists */
00774     if(len != 0 && path[len - 1] == '/') {
00775         char *newpath = strdup(path);
00776         newpath[len - 1] = '\0';
00777         ret = lstat(newpath, buf);
00778         free(newpath);
00779     } else {
00780         ret = lstat(path, buf);
00781     }
00782 
00783     return ret;
00784 }
00785 
00786 #ifdef HAVE_LIBSSL
00787 /** Compute the MD5 message digest of a file.
00788  * @param path file path of file to compute  MD5 digest of
00789  * @param output string to hold computed MD5 digest
00790  * @return 0 on success, 1 on file open error, 2 on file read error
00791  */
00792 static int md5_file(const char *path, unsigned char output[16])
00793 {
00794     MD5_CTX ctx;
00795     unsigned char *buf;
00796     ssize_t n;
00797     int fd;
00798 
00799     MALLOC(buf, (size_t)ALPM_BUFFER_SIZE, return 1);
00800 
00801     OPEN(fd, path, O_RDONLY);
00802     if(fd < 0) {
00803         free(buf);
00804         return 1;
00805     }
00806 
00807     MD5_Init(&ctx);
00808 
00809     while((n = read(fd, buf, ALPM_BUFFER_SIZE)) > 0 || errno == EINTR) {
00810         if(n < 0) {
00811             continue;
00812         }
00813         MD5_Update(&ctx, buf, n);
00814     }
00815 
00816     CLOSE(fd);
00817     free(buf);
00818 
00819     if(n < 0) {
00820         return 2;
00821     }
00822 
00823     MD5_Final(output, &ctx);
00824     return 0;
00825 }
00826 
00827 /* third param is so we match the PolarSSL definition */
00828 /** Compute the SHA-224 or SHA-256 message digest of a file.
00829  * @param path file path of file to compute SHA2 digest of
00830  * @param output string to hold computed SHA2 digest
00831  * @param is224 use SHA-224 instead of SHA-256
00832  * @return 0 on success, 1 on file open error, 2 on file read error
00833  */
00834 static int sha2_file(const char *path, unsigned char output[32], int is224)
00835 {
00836     SHA256_CTX ctx;
00837     unsigned char *buf;
00838     ssize_t n;
00839     int fd;
00840 
00841     MALLOC(buf, (size_t)ALPM_BUFFER_SIZE, return 1);
00842 
00843     OPEN(fd, path, O_RDONLY);
00844     if(fd < 0) {
00845         free(buf);
00846         return 1;
00847     }
00848 
00849     if(is224) {
00850         SHA224_Init(&ctx);
00851     } else {
00852         SHA256_Init(&ctx);
00853     }
00854 
00855     while((n = read(fd, buf, ALPM_BUFFER_SIZE)) > 0 || errno == EINTR) {
00856         if(n < 0) {
00857             continue;
00858         }
00859         if(is224) {
00860             SHA224_Update(&ctx, buf, n);
00861         } else {
00862             SHA256_Update(&ctx, buf, n);
00863         }
00864     }
00865 
00866     CLOSE(fd);
00867     free(buf);
00868 
00869     if(n < 0) {
00870         return 2;
00871     }
00872 
00873     if(is224) {
00874         SHA224_Final(output, &ctx);
00875     } else {
00876         SHA256_Final(output, &ctx);
00877     }
00878     return 0;
00879 }
00880 #endif
00881 
00882 /** Create a string representing bytes in hexadecimal.
00883  * @param bytes the bytes to represent in hexadecimal
00884  * @param size number of bytes to consider
00885  * @return a NULL terminated string with the hexadecimal representation of
00886  * bytes or NULL on error. This string must be freed.
00887  */
00888 static char *hex_representation(unsigned char *bytes, size_t size)
00889 {
00890     static const char *hex_digits = "0123456789abcdef";
00891     char *str;
00892     size_t i;
00893 
00894     MALLOC(str, 2 * size + 1, return NULL);
00895 
00896     for (i = 0; i < size; i++) {
00897         str[2 * i] = hex_digits[bytes[i] >> 4];
00898         str[2 * i + 1] = hex_digits[bytes[i] & 0x0f];
00899     }
00900 
00901     str[2 * size] = '\0';
00902 
00903     return str;
00904 }
00905 
00906 /** Get the md5 sum of file.
00907  * @param filename name of the file
00908  * @return the checksum on success, NULL on error
00909  * @addtogroup alpm_misc
00910  */
00911 char SYMEXPORT *alpm_compute_md5sum(const char *filename)
00912 {
00913     unsigned char output[16];
00914 
00915     ASSERT(filename != NULL, return NULL);
00916 
00917     /* defined above for OpenSSL, otherwise defined in md5.h */
00918     if(md5_file(filename, output) > 0) {
00919         return NULL;
00920     }
00921 
00922     return hex_representation(output, 16);
00923 }
00924 
00925 /** Get the sha256 sum of file.
00926  * @param filename name of the file
00927  * @return the checksum on success, NULL on error
00928  * @addtogroup alpm_misc
00929  */
00930 char SYMEXPORT *alpm_compute_sha256sum(const char *filename)
00931 {
00932     unsigned char output[32];
00933 
00934     ASSERT(filename != NULL, return NULL);
00935 
00936     /* defined above for OpenSSL, otherwise defined in sha2.h */
00937     if(sha2_file(filename, output, 0) > 0) {
00938         return NULL;
00939     }
00940 
00941     return hex_representation(output, 32);
00942 }
00943 
00944 /** Calculates a file's MD5 or SHA2 digest  and compares it to an expected value. 
00945  * @param filepath path of the file to check
00946  * @param expected hash value to compare against
00947  * @param type digest type to use
00948  * @return 0 if file matches the expected hash, 1 if they do not match, -1 on
00949  * error
00950  */
00951 int _alpm_test_checksum(const char *filepath, const char *expected,
00952         enum _alpm_csum type)
00953 {
00954     char *computed;
00955     int ret;
00956 
00957     if(type == ALPM_CSUM_MD5) {
00958         computed = alpm_compute_md5sum(filepath);
00959     } else if(type == ALPM_CSUM_SHA256) {
00960         computed = alpm_compute_sha256sum(filepath);
00961     } else {
00962         return -1;
00963     }
00964 
00965     if(expected == NULL || computed == NULL) {
00966         ret = -1;
00967     } else if(strcmp(expected, computed) != 0) {
00968         ret = 1;
00969     } else {
00970         ret = 0;
00971     }
00972 
00973     FREE(computed);
00974     return ret;
00975 }
00976 
00977 /* Note: does NOT handle sparse files on purpose for speed. */
00978 /** TODO.
00979  * Does not handle sparse files on purpose for speed.
00980  * @param a
00981  * @param b
00982  * @return 
00983  */
00984 int _alpm_archive_fgets(struct archive *a, struct archive_read_buffer *b)
00985 {
00986     /* ensure we start populating our line buffer at the beginning */
00987     b->line_offset = b->line;
00988 
00989     while(1) {
00990         size_t block_remaining;
00991         char *eol;
00992 
00993         /* have we processed this entire block? */
00994         if(b->block + b->block_size == b->block_offset) {
00995             int64_t offset;
00996             if(b->ret == ARCHIVE_EOF) {
00997                 /* reached end of archive on the last read, now we are out of data */
00998                 goto cleanup;
00999             }
01000 
01001             /* zero-copy - this is the entire next block of data. */
01002             b->ret = archive_read_data_block(a, (void *)&b->block,
01003                     &b->block_size, &offset);
01004             b->block_offset = b->block;
01005             block_remaining = b->block_size;
01006 
01007             /* error, cleanup */
01008             if(b->ret < ARCHIVE_OK) {
01009                 goto cleanup;
01010             }
01011         } else {
01012             block_remaining = b->block + b->block_size - b->block_offset;
01013         }
01014 
01015         /* look through the block looking for EOL characters */
01016         eol = memchr(b->block_offset, '\n', block_remaining);
01017         if(!eol) {
01018             eol = memchr(b->block_offset, '\0', block_remaining);
01019         }
01020 
01021         /* allocate our buffer, or ensure our existing one is big enough */
01022         if(!b->line) {
01023             /* set the initial buffer to the read block_size */
01024             CALLOC(b->line, b->block_size + 1, sizeof(char), b->ret = -ENOMEM; goto cleanup);
01025             b->line_size = b->block_size + 1;
01026             b->line_offset = b->line;
01027         } else {
01028             /* note: we know eol > b->block_offset and b->line_offset > b->line,
01029              * so we know the result is unsigned and can fit in size_t */
01030             size_t new = eol ? (size_t)(eol - b->block_offset) : block_remaining;
01031             size_t needed = (size_t)((b->line_offset - b->line) + new + 1);
01032             if(needed > b->max_line_size) {
01033                 b->ret = -ERANGE;
01034                 goto cleanup;
01035             }
01036             if(needed > b->line_size) {
01037                 /* need to realloc + copy data to fit total length */
01038                 char *new;
01039                 CALLOC(new, needed, sizeof(char), b->ret = -ENOMEM; goto cleanup);
01040                 memcpy(new, b->line, b->line_size);
01041                 b->line_size = needed;
01042                 b->line_offset = new + (b->line_offset - b->line);
01043                 free(b->line);
01044                 b->line = new;
01045             }
01046         }
01047 
01048         if(eol) {
01049             size_t len = (size_t)(eol - b->block_offset);
01050             memcpy(b->line_offset, b->block_offset, len);
01051             b->line_offset[len] = '\0';
01052             b->block_offset = eol + 1;
01053             /* this is the main return point; from here you can read b->line */
01054             return ARCHIVE_OK;
01055         } else {
01056             /* we've looked through the whole block but no newline, copy it */
01057             size_t len = (size_t)(b->block + b->block_size - b->block_offset);
01058             memcpy(b->line_offset, b->block_offset, len);
01059             b->line_offset += len;
01060             b->block_offset = b->block + b->block_size;
01061             /* there was no new data, return what is left; saved ARCHIVE_EOF will be
01062              * returned on next call */
01063             if(len == 0) {
01064                 b->line_offset[0] = '\0';
01065                 return ARCHIVE_OK;
01066             }
01067         }
01068     }
01069 
01070 cleanup:
01071     {
01072         int ret = b->ret;
01073         FREE(b->line);
01074         memset(b, 0, sizeof(struct archive_read_buffer));
01075         return ret;
01076     }
01077 }
01078 
01079 /** Parse a full package specifier.
01080  * @param target package specifier to parse, such as: "pacman-4.0.1-2",
01081  * "pacman-4.01-2/", or "pacman-4.0.1-2/desc"
01082  * @param name to hold package name
01083  * @param version to hold package version
01084  * @param name_hash to hold package name hash
01085  * @return 0 on success, -1 on error
01086  */
01087 int _alpm_splitname(const char *target, char **name, char **version,
01088         unsigned long *name_hash)
01089 {
01090     /* the format of a db entry is as follows:
01091      *    package-version-rel/
01092      *    package-version-rel/desc (we ignore the filename portion)
01093      * package name can contain hyphens, so parse from the back- go back
01094      * two hyphens and we have split the version from the name.
01095      */
01096     const char *pkgver, *end;
01097 
01098     if(target == NULL) {
01099         return -1;
01100     }
01101 
01102     /* remove anything trailing a '/' */
01103     end = strchr(target, '/');
01104     if(!end) {
01105         end = target + strlen(target);
01106     }
01107 
01108     /* do the magic parsing- find the beginning of the version string
01109      * by doing two iterations of same loop to lop off two hyphens */
01110     for(pkgver = end - 1; *pkgver && *pkgver != '-'; pkgver--);
01111     for(pkgver = pkgver - 1; *pkgver && *pkgver != '-'; pkgver--);
01112     if(*pkgver != '-' || pkgver == target) {
01113         return -1;
01114     }
01115 
01116     /* copy into fields and return */
01117     if(version) {
01118         if(*version) {
01119             FREE(*version);
01120         }
01121         /* version actually points to the dash, so need to increment 1 and account
01122          * for potential end character */
01123         STRNDUP(*version, pkgver + 1, end - pkgver - 1, return -1);
01124     }
01125 
01126     if(name) {
01127         if(*name) {
01128             FREE(*name);
01129         }
01130         STRNDUP(*name, target, pkgver - target, return -1);
01131         if(name_hash) {
01132             *name_hash = _alpm_hash_sdbm(*name);
01133         }
01134     }
01135 
01136     return 0;
01137 }
01138 
01139 /** Hash the given string to an unsigned long value.
01140  * This is the standard sdbm hashing algorithm.
01141  * @param str string to hash
01142  * @return the hash value of the given string
01143  */
01144 unsigned long _alpm_hash_sdbm(const char *str)
01145 {
01146     unsigned long hash = 0;
01147     int c;
01148 
01149     if(!str) {
01150         return hash;
01151     }
01152     while((c = *str++)) {
01153         hash = c + hash * 65599;
01154     }
01155 
01156     return hash;
01157 }
01158 
01159 /** Convert a string to a file offset.
01160  * This parses bare positive integers only.
01161  * @param line string to convert
01162  * @return off_t on success, -1 on error
01163  */
01164 off_t _alpm_strtoofft(const char *line)
01165 {
01166     char *end;
01167     unsigned long long result;
01168     errno = 0;
01169 
01170     /* we are trying to parse bare numbers only, no leading anything */
01171     if(line[0] < '0' || line[0] > '9') {
01172         return (off_t)-1;
01173     }
01174     result = strtoull(line, &end, 10);
01175     if(result == 0 && end == line) {
01176         /* line was not a number */
01177         return (off_t)-1;
01178     } else if(result == ULLONG_MAX && errno == ERANGE) {
01179         /* line does not fit in unsigned long long */
01180         return (off_t)-1;
01181     } else if(*end) {
01182         /* line began with a number but has junk left over at the end */
01183         return (off_t)-1;
01184     }
01185 
01186     return (off_t)result;
01187 }
01188 
01189 /** Parses a date into an alpm_time_t struct.
01190  * @param line date to parse
01191  * @return time struct on success, 0 on error
01192  */
01193 alpm_time_t _alpm_parsedate(const char *line)
01194 {
01195     char *end;
01196     long long result;
01197     errno = 0;
01198 
01199     if(isalpha((unsigned char)line[0])) {
01200         /* initialize to null in case of failure */
01201         struct tm tmp_tm;
01202         memset(&tmp_tm, 0, sizeof(struct tm));
01203         setlocale(LC_TIME, "C");
01204         strptime(line, "%a %b %e %H:%M:%S %Y", &tmp_tm);
01205         setlocale(LC_TIME, "");
01206         return (alpm_time_t)mktime(&tmp_tm);
01207     }
01208 
01209     result = strtoll(line, &end, 10);
01210     if(result == 0 && end == line) {
01211         /* line was not a number */
01212         errno = EINVAL;
01213         return 0;
01214     } else if(errno == ERANGE) {
01215         /* line does not fit in long long */
01216         return 0;
01217     } else if(*end) {
01218         /* line began with a number but has junk left over at the end */
01219         errno = EINVAL;
01220         return 0;
01221     }
01222 
01223     return (alpm_time_t)result;
01224 }
01225 
01226 /** Wrapper around access() which takes a dir and file argument
01227  * separately and generates an appropriate error message.
01228  * If dir is NULL file will be treated as the whole path.
01229  * @param handle an alpm handle
01230  * @param dir directory path ending with and slash
01231  * @param file filename
01232  * @param amode access mode as described in access()
01233  * @return int value returned by access()
01234  */
01235 int _alpm_access(alpm_handle_t *handle, const char *dir, const char *file, int amode)
01236 {
01237     size_t len = 0;
01238     int ret = 0;
01239 
01240     if(dir) {
01241         char *check_path;
01242 
01243         len = strlen(dir) + strlen(file) + 1;
01244         CALLOC(check_path, len, sizeof(char), RET_ERR(handle, ALPM_ERR_MEMORY, -1));
01245         snprintf(check_path, len, "%s%s", dir, file);
01246 
01247         ret = access(check_path, amode);
01248         free(check_path);
01249     } else {
01250         dir = "";
01251         ret = access(file, amode);
01252     }
01253 
01254     if(ret != 0) {
01255         if(amode & R_OK) {
01256             _alpm_log(handle, ALPM_LOG_DEBUG, "\"%s%s\" is not readable: %s\n",
01257                     dir, file, strerror(errno));
01258         }
01259         if(amode & W_OK) {
01260             _alpm_log(handle, ALPM_LOG_DEBUG, "\"%s%s\" is not writable: %s\n",
01261                     dir, file, strerror(errno));
01262         }
01263         if(amode & X_OK) {
01264             _alpm_log(handle, ALPM_LOG_DEBUG, "\"%s%s\" is not executable: %s\n",
01265                     dir, file, strerror(errno));
01266         }
01267         if(amode == F_OK) {
01268             _alpm_log(handle, ALPM_LOG_DEBUG, "\"%s%s\" does not exist: %s\n",
01269                     dir, file, strerror(errno));
01270         }
01271     }
01272     return ret;
01273 }
01274 
01275 /** Checks whether a string matches a shell wildcard pattern.
01276  * Wrapper around fnmatch.
01277  * @param pattern pattern to match aganist
01278  * @param string string to check against pattern
01279  * @return 0 if string matches pattern, non-zero if they don't match and on
01280  * error
01281  */
01282 int _alpm_fnmatch(const void *pattern, const void *string)
01283 {
01284     return fnmatch(pattern, string, 0);
01285 }
01286 
01287 #ifndef HAVE_STRNDUP
01288 /* A quick and dirty implementation derived from glibc */
01289 /** Determines the length of a fixed-size string.
01290  * @param s string to be measured
01291  * @param max maximum number of characters to search for the string end
01292  * @return length of s or max, whichever is smaller
01293  */
01294 static size_t strnlen(const char *s, size_t max)
01295 {
01296     register const char *p;
01297     for(p = s; *p && max--; ++p);
01298     return (p - s);
01299 }
01300 
01301 /** Copies a string.
01302  * Returned string needs to be freed
01303  * @param s string to be copied
01304  * @param n maximum number of characters to copy
01305  * @return pointer to the new string on success, NULL on error
01306  */
01307 char *strndup(const char *s, size_t n)
01308 {
01309   size_t len = strnlen(s, n);
01310   char *new = (char *) malloc(len + 1);
01311 
01312   if(new == NULL)
01313     return NULL;
01314 
01315   new[len] = '\0';
01316   return (char *)memcpy(new, s, len);
01317 }
01318 #endif
01319 
01320 /* vim: set ts=2 sw=2 noet: */