/*- * Copyright 2010. Ivan Voras <ivoras@freebsd.org> * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. */ /* * FreeBSD install - a package for the installation and maintainance * of non-core utilities. * * Library of helper functions. */ #include <sys/cdefs.h> __FBSDID("$FreeBSD$"); #include <sys/param.h> #include <sys/utsname.h> #include <sys/stat.h> #include <stdlib.h> #include <stdio.h> #include <unistd.h> #include <assert.h> #include <fcntl.h> #include <paths.h> #include <errno.h> #include <err.h> #include <fts.h> #include <archive.h> #include <archive_entry.h> #include <pkg.h> #include "pkg_patch.h" /* * Tests if the given string ends with the other string. The return value is * analogous to strcmp(). */ int strendswith(const char *base, const char *end) { int base_len, end_len; base_len = strlen(base); end_len = strlen(end); if (base_len < end_len) return -1; return strncmp(base + (base_len - end_len), end, end_len); } /* * Removes a directory hierarchy. */ int rm_rf(char *dir) { char cmd[PATH_MAX]; /* XXX: reimplement in C? */ sprintf(cmd, "%s -rf %s", _PATH_RM, dir); return system(cmd); } /* * Simple file copy. */ int cp(char *from, char *to) { int fd1, fd2, rval = 0; size_t bs = 1 * 1024 * 1024; char *buf; fd1 = open(from, O_RDONLY); if (fd1 < 0) return (-1); fd2 = open(to, O_WRONLY | O_CREAT | O_TRUNC, 0600); if (fd2 < 0) { close(fd1); return (-1); } buf = malloc(bs); if (buf == NULL) { rval = -1; goto end; } while (bs > 0) { bs = read(fd1, buf, bs); if (bs > 0) { ssize_t written = 0, wr; while (bs - written > 0) { wr = write(fd2, buf + written, bs - written); if (wr < 0) { rval = -1; goto end; } written += wr; } } } end: if (buf != NULL) free(buf); close(fd2); close(fd1); return (rval); } /* * Starts a package extract (tar) job as a separate process. */ int pkgxjob_start(struct pkgxjob *job, char *dir, char *filename) { char cmd[PATH_MAX]; /* libarchive not threadsafe for extract; call external tar */ job->filename = filename; sprintf(cmd, "%s -x -C %s -f %s", _PATH_TAR, dir, filename); if (Verbose > 1) printf("pkgxjob: %s\n", cmd); job->fp = popen(cmd, "r+"); if (job->fp == NULL) return (-1); return (0); } /* * Finish (cleanup) a tar job. */ int pkgxjob_finish(struct pkgxjob *job) { return (pclose(job->fp)); } /* * Gather files in a file hierarchy into the given filelist_head. */ int filelist_gather(char *dir, struct filelist_head *head) { FTS *fts; FTSENT *fe; char *path_argv[] = { dir, NULL }; size_t dir_len; fts = fts_open(path_argv, FTS_NOCHDIR | FTS_PHYSICAL | FTS_XDEV, NULL); if (fts == NULL) return (-1); dir_len = strlen(dir); while ((fe = fts_read(fts)) != NULL) { struct filelist *fl; if (fe->fts_info == FTS_D || fe->fts_info == FTS_F || fe->fts_info == FTS_SL || fe->fts_info == FTS_SLNONE) { if (fe->fts_pathlen == dir_len) continue; fl = malloc(sizeof(*fl)); if (fl == NULL) return (-1); strncpy(fl->filename, fe->fts_path + dir_len + 1, PATH_MAX); memcpy(&fl->st, fe->fts_statp, sizeof(struct stat)); SLIST_INSERT_HEAD(head, fl, linkage); } } fts_close(fts); return (0); } /* * Returns a list of differences between filelists. */ int filelist_diff(struct filelist_head *flist1, struct filelist_head *flist2, struct filelist_head *fldiff) { struct filelist *fl1, *fl2; int found; SLIST_FOREACH(fl1, flist1, linkage) { found = 0; SLIST_FOREACH(fl2, flist2, linkage) { if (strncmp(fl1->filename, fl2->filename, PATH_MAX) == 0) { found = 1; break; } } if (!found) { fl2 = malloc(sizeof(*fl2)); memcpy(fl2, fl1, sizeof(*fl2)); SLIST_INSERT_HEAD(fldiff, fl2, linkage); } } return (0); } /* * Return a list consisting of the intersection of two given filelists. */ int filelist_intersect(struct filelist_head *flist1, struct filelist_head *flist2, struct filelist_head *flintersect) { struct filelist *fl1, *fl2; int found; SLIST_FOREACH(fl1, flist1, linkage) { found = 0; SLIST_FOREACH(fl2, flist2, linkage) { if (strncmp(fl1->filename, fl2->filename, PATH_MAX) == 0) { found = 1; break; } } if (found) { fl2 = malloc(sizeof(*fl2)); memcpy(fl2, fl1, sizeof(*fl2)); SLIST_INSERT_HEAD(flintersect, fl2, linkage); } } return (0); } /* * Free the filelist's entries. */ void filelist_free(struct filelist_head *flist) { struct filelist *fl1, *fl2; SLIST_FOREACH_SAFE(fl1, flist, linkage, fl2) { SLIST_REMOVE(flist, fl1, filelist, linkage); free(fl1); } } /* * Free the pkgjoinlist's entries. */ void pkgjoinlist_free(struct pkgjoinlist_head *plist) { struct pkgjoinlist *pl1, *pl2; SLIST_FOREACH_SAFE(pl1, plist, linkage, pl2) { SLIST_REMOVE(plist, pl1, pkgjoinlist, linkage); free(pl1); } } /* * Returns a file list consisting of the intersection of packages from the first * list */ int filelist_intersect_pkg(struct filelist_head *flist1, struct filelist_head *flist2, struct pkgjoinlist_head *pkgisect) { char basename1[PKGNAME_MAX], version1[PKGNAME_MAX], suffix1[20]; char basename2[PKGNAME_MAX], version2[PKGNAME_MAX], suffix2[20]; struct filelist *fl1, *fl2; struct pkgjoinlist *pi; int found; SLIST_FOREACH(fl1, flist1, linkage) { found = 0; parse_package_name(fl1->filename, basename1, version1, suffix1); SLIST_FOREACH(fl2, flist2, linkage) { parse_package_name(fl2->filename, basename2, version2, suffix2); if (strncmp(basename1, basename2, PKGNAME_MAX) == 0) { found = 1; break; } } if (found) { pi = calloc(1, sizeof(*pi)); strncpy(pi->name1, fl1->filename, PKGNAME_MAX); strncpy(pi->name2, fl2->filename, PKGNAME_MAX); SLIST_INSERT_HEAD(pkgisect, pi, linkage); } } return (0); } /* * Returns the number of elements in the given filelist. */ unsigned int filelist_count(struct filelist_head *flist) { unsigned int count = 0; struct filelist *fl; SLIST_FOREACH(fl, flist, linkage) count++; return (count); } /* * For a given binary package archive filename, extract its base package name * (e.g. "apache-ant"), its version (e.g. "1.7.1") and its suffix (e.g. ".tbz"). * Any of the component pointers / arguments can be NULL. The package filname * might contain path information (slashes), which will be discarded. */ void parse_package_name(char *pkgfile, char *basename, char *version, char *suff) { char *tmp, *p; /* Strip directory path, if any */ p = strrchr(pkgfile, '/'); if (p != NULL) tmp = strdup(p + 1); else tmp = strdup(pkgfile); p = strrchr(tmp, '.'); if (suff != NULL) strcpy(suff, p); *p = '\0'; p = strrchr(tmp, '-'); if (version != NULL) strcpy(version, p + 1); *p = '\0'; if (basename != NULL) strcpy(basename, tmp); free(tmp); } /* * Copy generic file attributes. * TODO: See if there is any need to take care of ACLs (tar apparently doesn't). */ int copy_file_attrs(char *from, struct stat *st_from, char *to) { struct stat *st, st2; struct timeval tv[2]; if (st_from != NULL) st = st_from; else { assert(from != NULL); if (lstat(from, &st2) < 0) { warn("copy_file_attrs: lstat(%s) failed", from); return (-errno); } st = &st2; } if (chown(to, st->st_uid, st->st_gid) < 0) { warn("copy_file_attrs: chown() failed"); return (-errno); } tv[0].tv_usec = tv[1].tv_usec = 0; tv[0].tv_sec = tv[1].tv_sec = st->st_mtime; if (lutimes(to, tv) < 0) { warn("copy_file_attrs: lutimes(%s,%d) failed", to, st->st_mtime); return (-errno); } if (lchmod(to, st->st_mode) < 0) { warn("copy_file_attrs: lchmod(%o) failed", st->st_mode); return (-errno); } return (0); } /* * File copy, preserving generic file attributes. Knows how to handle * (re-create) symlinks. */ int copy_file_absolute(char *from, char *to) { struct stat st; if (lstat(from, &st) != 0) return (errno); if (S_ISDIR(st.st_mode)) { if (mkdir(to, 0700) != 0) { if (errno != EEXIST) return (-errno); } if (copy_file_attrs(from, &st, to) != 0) return (-errno); return (0); } else if (S_ISLNK(st.st_mode)) { char tmp[PATH_MAX]; memset(tmp, 0, sizeof(tmp)); if (readlink(from, tmp, PATH_MAX) < 0) return (-errno); if (symlink(tmp, to) < 0) return (-errno); return (0); } if (cp(from, to) != 0) return (-errno); if (copy_file_attrs(from, &st, to) != 0) return (-errno); return (0); } /* * Replicates / re-creates a directory tree in the destination to contain * all directories from the source, including their properties: ownership, * mode, mtime. */ int replicate_dirtree(char *from, char __unused *to) { FTS *fts; FTSENT *fe; char *path_argv[] = { from, NULL }; size_t from_len; int rval; rval = 0; from_len = strlen(from); fts = fts_open(path_argv, FTS_NOCHDIR | FTS_PHYSICAL | FTS_XDEV, NULL); if (fts == NULL) return (-1); while ((fe = fts_read(fts)) != NULL) { char new_dir[PATH_MAX]; if (fe->fts_info == FTS_D) { snprintf(new_dir, PATH_MAX, "%s%s", to, fe->fts_path + from_len); if (access(new_dir, F_OK) == 0) continue; if (mkdir(new_dir, 0700) < 0) { rval = -errno; goto end; } if (copy_file_attrs(fe->fts_path, fe->fts_statp, new_dir) != 0) { rval = -errno; goto end; } } } end: if (fts_close(fts) < 0) return (-1); return (rval); } /* * Counts the elements in the given pplist. */ unsigned int pplist_count(struct pplist_head *ppl) { unsigned int count = 0; struct pplist *pl; STAILQ_FOREACH(pl, ppl, linkage) count++; return (count); } /* * Reads the given file into struct pkg_patch. */ void read_pkgpatch_file(char *filename, struct pkg_patch *pp) { FILE *fp; char line[PATH_MAX], *p, *p2, *p3, *cmd; int llen; struct pplist *pl; fp = fopen(filename, "r"); if (fp == NULL) err(1, "Cannot open file: %s", filename); memset(pp, 0, sizeof(*pp)); STAILQ_INIT(&pp->pp_add); STAILQ_INIT(&pp->pp_remove); STAILQ_INIT(&pp->pp_rmdir); STAILQ_INIT(&pp->pp_patch); while (fgets(line, PATH_MAX, fp) != NULL) { llen = strlen(line); if (line[llen-1] == '\n') { line[llen-1] = '\0'; /* strip newline */ llen--; } p = strchr(line, '#'); /* skip comments */ if (p != NULL) *p = '\0'; if (line[0] == '\0') /* skip empty lines */ continue; cmd = line; p = strchr(line, ' '); if (p == NULL) errx(1, "Invalid command format in %s", PKGPATCH_FNAME); *p++ = '\0'; if (strcmp(cmd, "@version") == 0) { p2 = strchr(p, '.'); if (p2 == NULL) errx(1, "Invalid version format in %s", PKGPATCH_FNAME); *p2++ = '\0'; pp->version_major = atoi(p); pp->version_minor = atoi(p2); } else if (strcmp(cmd, "@source") == 0) { strlcpy(pp->source, p, PATH_MAX); } else if (strcmp(cmd, "@target") == 0) { strlcpy(pp->target, p, PATH_MAX); } else if (strcmp(cmd, "@add") == 0) { pl = calloc(1, sizeof(*pl)); strlcpy(pl->filename, p, PATH_MAX); STAILQ_INSERT_TAIL(&pp->pp_add, pl, linkage); } else if (strcmp(cmd, "@remove") == 0) { pl = calloc(1, sizeof(*pl)); strlcpy(pl->filename, p, PATH_MAX); STAILQ_INSERT_TAIL(&pp->pp_remove, pl, linkage); } else if (strcmp(cmd, "@rmdir") == 0) { pl = calloc(1, sizeof(*pl)); strlcpy(pl->filename, p, PATH_MAX); STAILQ_INSERT_TAIL(&pp->pp_rmdir, pl, linkage); } else if (strcmp(cmd, "@patch") == 0) { pl = calloc(1, sizeof(*pl)); p2 = strchr(p, '['); if (p2 != NULL) { /* * Parse options block of the form * \[name=value[,name=value...]\] */ char m[100], *pm, *p4, *p5; pm = m; p3 = strchr(p2, ']'); assert(p3-p2 < (int)sizeof(m)); strlcpy(m, p2 + 1, p3 - p2); p3++; while (*p3 == ' ') p3++; strlcpy(pl->filename, p3, PATH_MAX); while ((p4 = strsep(&pm, ",")) != NULL) { p5 = strchr(p4, '='); if (p5 != NULL) *p5++ = '\0'; if (strcmp(p4, "method") == 0) { if (p5 == NULL) errx(1, "patch option " "error"); if (strcmp(p5, "bsdiff") == 0) pl->method = PPMETHOD_BSDIFF; else if (strcmp(p5, "cp") == 0) pl->method = PPMETHOD_CP; else if (strcmp(p5, "ln") == 0) pl->method = PPMETHOD_LN; } } } else { /* Default options */ strlcpy(pl->filename, p, PATH_MAX); pl->method = PPMETHOD_CP; } STAILQ_INSERT_TAIL(&pp->pp_patch, pl, linkage); } else errx(1, "Unknown command: %s", cmd); } fclose(fp); } /* * Converts "zulu time" time_t value to iso8601 datetime. Not thread-safe. * Accepts -1 for time to mean "current" time. */ char * time_to_iso8601(time_t t) { static char stm[25]; struct tm *tptr; if (t == -1) { struct timeval tp; gettimeofday(&tp, NULL); t = tp.tv_sec; } tptr = gmtime(&t); strftime(stm, 25, "%FT%TZ", tptr); return stm; } /* Converts given iso8601 datetime string to time_t. */ time_t iso8601_to_time(char *t) { struct tm tms; if (strptime(t, "%FT%T%Z", &tms) != NULL) return (0); return timegm(&tms); } /* Convert given time_t to asctime */ char * time_ctime(time_t t) { if (t == -1) t = time(NULL); return (ctime(&t)); } /* Twirl the baton, writing backspace */ void baton_twirl() { static const char bpos[4] = { '-', '\\', '|', '/' }; static unsigned int counter = 0; fprintf(stdout, "%c\b", bpos[counter++ % 4]); fflush(stdout); } /* Read the +CONTENTS file from the given package file */ Package * pkg_read_plist(char *filename) { struct archive *arc; struct archive_entry *entry; Package *pkg = NULL; int er; arc = archive_read_new(); archive_read_support_compression_all(arc); archive_read_support_format_tar(arc); er = archive_read_open_filename(arc, filename, 65536); if (er != ARCHIVE_OK) return NULL; while (archive_read_next_header(arc, &entry) == ARCHIVE_OK) { FILE *fplist; size_t bs = 16 * 1024; char *buf; if (strncmp(archive_entry_pathname(entry), CONTENTS_FNAME, PATH_MAX) != 0) continue; fplist = tmpfile(); buf = malloc(bs); while (bs > 0) { bs = archive_read_data(arc, buf, bs); if (bs > 0) if (fwrite(buf, 1, bs, fplist) != bs) err(1, "Cannot extract plist"); } free(buf); fseek(fplist, 0, 0); pkg = calloc(1, sizeof(*pkg)); read_plist(pkg, fplist); fclose(fplist); break; } archive_read_finish(arc); return (pkg); } /* * Check for conflicts from metadata in the new package to recorded packages. * Returns (i+1) if the given package pnew conflicts on the i'th element * in pkglist. Returns 0 if no conflict detected. */ int check_conflicts(Package *pnew, char **pkglist) { int er, i; PackingList pl; if (pkglist == NULL) { pkglist = matchinstalled(MATCH_ALL, NULL, &er); if (pkglist == NULL || er != 0) { warnx("Cannot fetch a list of installed packages " "(matchinstalled(MATCH_ALL...))"); return (-1); } } pl = pnew->head; while (pl != NULL) { if (pl->type == PLIST_CONFLICTS) { for (i = 0; pkglist[i] != NULL; i++) { if (strncmp(pl->name, pkglist[i], PKGNAME_MAX) == 0) return (i + 1); } } pl = pl->next; } /* XXX: When libpkg grows a storefree() API, use it to free pkglist * if needed. */ return (0); } /* * Compare two package names based on their individual components. Returns * the amount of similarity between the names. */ enum CMP_NAME compare_package_names(char *pkg1, char *pkg2) { char base1[PKGNAME_MAX], ver1[PKGNAME_MAX]; char base2[PKGNAME_MAX], ver2[PKGNAME_MAX]; if (pkg1 == NULL || pkg2 == NULL) return (CMP_NO_MATCH); parse_package_name(pkg1, base1, ver1, NULL); parse_package_name(pkg2, base2, ver2, NULL); if (strncmp(base1, base2, PKGNAME_MAX) != 0) return (CMP_NO_MATCH); if (strncmp(ver1, ver2, PKGNAME_MAX) != 0) return (CMP_BASE_MATCH); return (CMP_FULL_MATCH); } /* * Check package dependencies on the given pkglist or the currently installed * packages. */ enum CMP_NAME check_dependencies(Package *pnew, char **pkglist) { int er, i; PackingList pl; enum CMP_NAME best; best = CMP_FULL_MATCH; if (pkglist == NULL) { pkglist = matchinstalled(MATCH_ALL, NULL, &er); if (pkglist == NULL || er != 0) { warnx("Cannot fetch a list of installed packages " "(matchinstalled(MATCH_ALL...))"); return (-1); } } pl = pnew->head; while (pl != NULL) { if (pl->type == PLIST_PKGDEP) { enum CMP_NAME match; int found = 0; for (i = 0; pkglist[i] != NULL; i++) { match = compare_package_names(pl->name, pkglist[i]); if (match == CMP_BASE_MATCH) { if (best == CMP_FULL_MATCH) best = CMP_BASE_MATCH; warnx("Expecting %s but found %s", pl->name, pkglist[i]); found = 1; break; } else if (match == CMP_FULL_MATCH) { found = 1; break; } } if (!found) { warnx("Dependancy not found: %s", pl->name); best = CMP_NO_MATCH; break; } } pl = pl->next; } /* XXX: When libpkg grows a storefree() API, use it to free pkglist * if needed. */ return (best); } /* Return a char* pointer to the filename portion of the given full filename */ char * find_filename(char *fullname) { char *p; p = strrchr(fullname, '/'); if (p == NULL) return (fullname); return (p + 1); }