diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/convertdb.c | 163 | ||||
-rw-r--r-- | src/db.c | 508 | ||||
-rw-r--r-- | src/db.h | 46 | ||||
-rw-r--r-- | src/list.c | 122 | ||||
-rw-r--r-- | src/list.h | 20 | ||||
-rw-r--r-- | src/md5.c | 338 | ||||
-rw-r--r-- | src/md5.h | 48 | ||||
-rw-r--r-- | src/md5driver.c | 83 | ||||
-rw-r--r-- | src/package.c | 228 | ||||
-rw-r--r-- | src/package.h | 67 | ||||
-rw-r--r-- | src/pacman.c | 1660 | ||||
-rw-r--r-- | src/pacman.h | 62 | ||||
-rw-r--r-- | src/pacsync.c | 183 | ||||
-rw-r--r-- | src/pacsync.h | 29 | ||||
-rw-r--r-- | src/rpmvercmp.c | 120 | ||||
-rw-r--r-- | src/util.c | 571 | ||||
-rw-r--r-- | src/util.h | 52 |
17 files changed, 4300 insertions, 0 deletions
diff --git a/src/convertdb.c b/src/convertdb.c new file mode 100644 index 00000000..b3387162 --- /dev/null +++ b/src/convertdb.c @@ -0,0 +1,163 @@ +/* + * pacman + * + * Copyright (c) 2002 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <ctype.h> +#include <limits.h> +#include <string.h> +#include <libgen.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include "list.h" + +char* trim(char *str); + +int main(int argc, char* argv[]) +{ + FILE* db = NULL; + FILE* fp = NULL; + char* ptr = NULL; + char* dbdir = "/var/lib/pacman/pacman.db"; + char name[256]; + char ver[256]; + char line[PATH_MAX+1]; + char topdir[PATH_MAX+1]; + char path[PATH_MAX+1]; + mode_t oldumask; + struct stat buf; + + if(argc < 2) { + printf("converts a pacman 1.x database to a pacman 2.0 format\n"); + printf("usage: %s <target_dir>\n\n", basename(argv[0])); + printf("convertdb will convert all package data from /var/lib/pacman/pacman.db\n"); + printf("to a 2.0 format and place it in target_dir.\n\n"); + return(0); + } + + db = fopen(dbdir, "r"); + if(db == NULL) { + perror(dbdir); + return(1); + } + + oldumask = umask(0000); + while(fgets(name, 255, db)) { + PMList *backup = NULL; + PMList *lp; + + if(!fgets(ver, 255, db)) { + perror(dbdir); + return(1); + } + trim(name); + trim(ver); + fprintf(stderr, "converting %s\n", name); + /* package directory */ + snprintf(topdir, PATH_MAX, "%s/%s-%s", argv[1], name, ver); + mkdir(topdir, 0755); + + /* DESC */ + snprintf(path, PATH_MAX, "%s/desc", topdir); + if(!(fp = fopen(path, "w"))) { + perror(path); + return(1); + } + fputs("%NAME%\n", fp); + fprintf(fp, "%s\n\n", name); + fputs("%VERSION%\n", fp); + fprintf(fp, "%s\n\n", ver); + fputs("%DESC%\n\n", fp); + fclose(fp); + + /* DEPENDS */ + snprintf(path, PATH_MAX, "%s/depends", topdir); + if(!(fp = fopen(path, "w"))) { + perror(path); + return(1); + } + fputs("%DEPENDS%\n\n", fp); + fputs("%REQUIREDBY%\n\n", fp); + fputs("%CONFLICTS%\n\n", fp); + fclose(fp); + + /* FILES */ + snprintf(path, PATH_MAX, "%s/files", topdir); + if(!(fp = fopen(path, "w"))) { + perror(path); + return(1); + } + fputs("%FILES%\n", fp); + while(fgets(line, 255, db) && strcmp(trim(line), "")) { + trim(line); + ptr = line; + + /* check for backup designation and frontslashes that shouldn't be there */ + if(line[0] == '*') ptr++; + if(*ptr == '/') ptr++; + if(line[0] == '*') backup = list_add(backup, strdup(ptr)); + + fprintf(fp, "%s\n", ptr); + } + fprintf(fp, "\n"); + fputs("%BACKUP%\n", fp); + for(lp = backup; lp; lp = lp->next) { + /* print the filename and a bad md5 hash. we just use 32 f's cuz we can't + * md5 the original file since we don't have it + */ + fprintf(fp, "%s\tffffffffffffffffffffffffffffffff\n", (char*)lp->data); + } + fputs("\n", fp); + fclose(fp); + snprintf(path, PATH_MAX, "/var/lib/pacman/scripts/%s", name); + if(!stat(path, &buf)) { + snprintf(line, PATH_MAX, "/bin/cp %s %s/install", path, topdir); + system(line); + } + list_free(backup); + } + umask(oldumask); + return(0); +} + +/* Trim whitespace and newlines from a string + */ +char* trim(char *str) +{ + char *pch = str; + while(isspace(*pch)) { + pch++; + } + if(pch != str) { + memmove(str, pch, (strlen(pch) + 1)); + } + + pch = (char*)(str + (strlen(str) - 1)); + while(isspace(*pch)) { + pch--; + } + *++pch = '\0'; + + return str; +} + +/* vim: set ts=2 sw=2 noet: */ diff --git a/src/db.c b/src/db.c new file mode 100644 index 00000000..f747be5b --- /dev/null +++ b/src/db.c @@ -0,0 +1,508 @@ +/* + * pacman + * + * Copyright (c) 2002 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +#include "config.h" +#include <stdio.h> +#include <stdlib.h> +#include <errno.h> +#include <string.h> +#include <limits.h> +#include <sys/stat.h> +#include <sys/types.h> +#include "list.h" +#include "package.h" +#include "db.h" +#include "util.h" +#include "pacman.h" + +extern PMList *pm_packages; +extern char *pmo_root; +extern unsigned short pmo_upgrade; + +extern int errno; + +/* Verify database integrity and build a list of + * installed packages + * + * returns: 0 on success + * 1 if db is not initialized + * 2 if db is corrupt + */ +pacdb_t* db_open(char *dbpath, char *treename) +{ + pacdb_t *db = NULL; + char path[PATH_MAX]; + + MALLOC(db, sizeof(pacdb_t)); + snprintf(path, PATH_MAX-1, "%s/%s", dbpath, treename); + db->dir = opendir(path); + if(db->dir == NULL) { + return(NULL); + } + strncpy(db->treename, treename, 128); + + return(db); +} + +void db_close(pacdb_t* db) +{ + closedir(db->dir); + return; +} + +/* frees pkgcache if necessary and returns a new package + * cache from db + */ +PMList* db_loadpkgs(pacdb_t *db, PMList *pkgcache) +{ + pkginfo_t *info; + PMList *lp; + pkginfo_t **arr = NULL; + unsigned int arrct = 0; + int i; + PMList *cache = NULL; + + /* if pm_packages already contains data, free it first */ + for(lp = pkgcache; lp; lp = lp->next) { + if(lp->data) { + freepkg(lp->data); + lp->data = NULL; + } + } + list_free(pkgcache); + + rewinddir(db->dir); + while((info = db_scan(db, NULL, INFRQ_DESC)) != NULL) { + /* add to the collective */ + /* we load all package names into a linear array first, so qsort can handle it */ + if(arr == NULL) { + arr = (pkginfo_t**)malloc(sizeof(pkginfo_t*)); + arrct++; + } else { + arr = (pkginfo_t**)realloc(arr, (++arrct)*sizeof(pkginfo_t*)); + } + if(arr == NULL) { + fprintf(stderr, "error: out of memory\n"); + exit(1); + } + arr[arrct-1] = info; + } + + /* sort the package list */ + qsort(arr, (size_t)arrct, sizeof(pkginfo_t*), pkgcmp); + + /* now load them into the proper PMList */ + for(i = 0; i < arrct; i++) { + cache = list_add(cache, arr[i]); + } + + return(cache); +} + +pkginfo_t* db_scan(pacdb_t *db, char *target, unsigned int inforeq) +{ + struct dirent *ent = NULL; + char name[256]; + char *ptr = NULL; + int found = 0; + + if(target != NULL) { + /* search for a specific package (by name only) */ + rewinddir(db->dir); + /* read the . and .. first */ + ent = readdir(db->dir); + ent = readdir(db->dir); + + ent = readdir(db->dir); + while(!found && ent != NULL) { + strncpy(name, ent->d_name, 255); + /* truncate the string at the second-to-last hyphen, */ + /* which will give us the package name */ + if((ptr = rindex(name, '-'))) { + *ptr = '\0'; + } + if((ptr = rindex(name, '-'))) { + *ptr = '\0'; + } + if(!strcmp(name, target)) { + found = 1; + continue; + } + ent = readdir(db->dir); + } + if(!found) { + return(NULL); + } + } else { + /* normal iteration */ + ent = readdir(db->dir); + if(ent && !strcmp(ent->d_name, ".")) { + ent = readdir(db->dir); + } + if(ent && !strcmp(ent->d_name, "..")) { + ent = readdir(db->dir); + } + if(ent == NULL) { + return(NULL); + } + } + return(db_read(db, ent, inforeq)); +} + +pkginfo_t* db_read(pacdb_t *db, struct dirent *ent, unsigned int inforeq) +{ + FILE *fp = NULL; + struct stat buf; + pkginfo_t *info = NULL; + char path[PATH_MAX]; + char topdir[PATH_MAX]; + char line[512]; + + if(ent == NULL) { + return(NULL); + } + snprintf(topdir, PATH_MAX, "%s%s/%s", pmo_root, PKGDIR, db->treename); + info = newpkg(); + + /* we always load DESC */ + inforeq |= INFRQ_DESC; + + snprintf(path, PATH_MAX, "%s/%s", topdir, ent->d_name); + if(stat(path, &buf)) { + /* directory doesn't exist or can't be opened */ + return(NULL); + } + + /* DESC */ + if(inforeq & INFRQ_DESC) { + snprintf(path, PATH_MAX, "%s/%s/desc", topdir, ent->d_name); + fp = fopen(path, "r"); + if(fp == NULL) { + fprintf(stderr, "error: %s: %s\n", path, strerror(errno)); + return(NULL); + } + while(!feof(fp)) { + if(fgets(line, 256, fp) == NULL) { + break; + } + trim(line); + if(!strcmp(line, "%NAME%")) { + if(fgets(info->name, 256, fp) == NULL) { + return(NULL); + } + trim(info->name); + } else if(!strcmp(line, "%VERSION%")) { + if(fgets(info->version, 64, fp) == NULL) { + return(NULL); + } + trim(info->version); + } else if(!strcmp(line, "%DESC%")) { + if(fgets(info->desc, 512, fp) == NULL) { + return(NULL); + } + trim(info->desc); + } else if(!strcmp(line, "%BUILDDATE%")) { + if(fgets(info->builddate, 32, fp) == NULL) { + return(NULL); + } + trim(info->builddate); + } else if(!strcmp(line, "%INSTALLDATE%")) { + if(fgets(info->installdate, 32, fp) == NULL) { + return(NULL); + } + trim(info->installdate); + } else if(!strcmp(line, "%PACKAGER%")) { + if(fgets(info->packager, 64, fp) == NULL) { + return(NULL); + } + trim(info->packager); + } else if(!strcmp(line, "%SIZE%")) { + char tmp[32]; + if(fgets(tmp, sizeof(tmp), fp) == NULL) { + return(NULL); + } + trim(tmp); + info->size = atol(tmp); + } + } + fclose(fp); + } + + /* FILES */ + if(inforeq & INFRQ_FILES) { + snprintf(path, PATH_MAX, "%s/%s/files", topdir, ent->d_name); + fp = fopen(path, "r"); + if(fp == NULL) { + fprintf(stderr, "error: %s: %s\n", path, strerror(errno)); + return(NULL); + } + while(fgets(line, 256, fp)) { + trim(line); + if(!strcmp(line, "%FILES%")) { + while(fgets(line, 512, fp) && strlen(trim(line))) { + char *s = strdup(line); + info->files = list_add(info->files, s); + } + } + if(!strcmp(line, "%BACKUP%")) { + while(fgets(line, 512, fp) && strlen(trim(line))) { + char *s = strdup(line); + info->backup = list_add(info->backup, s); + } + } + } + fclose(fp); + } + + /* DEPENDS */ + if(inforeq & INFRQ_DEPENDS) { + snprintf(path, PATH_MAX, "%s/%s/depends", topdir, ent->d_name); + fp = fopen(path, "r"); + if(fp == NULL) { + fprintf(stderr, "db_read: error: %s: %s\n", path, strerror(errno)); + return(NULL); + } + while(!feof(fp)) { + fgets(line, 255, fp); + trim(line); + if(!strcmp(line, "%DEPENDS%")) { + while(fgets(line, 512, fp) && strlen(trim(line))) { + char *s = strdup(line); + info->depends = list_add(info->depends, s); + } + } + if(!strcmp(line, "%REQUIREDBY%")) { + while(fgets(line, 512, fp) && strlen(trim(line))) { + char *s = strdup(line); + info->requiredby = list_add(info->requiredby, s); + } + } + if(!strcmp(line, "%CONFLICTS%")) { + while(fgets(line, 512, fp) && strlen(trim(line))) { + char *s = strdup(line); + info->conflicts = list_add(info->conflicts, s); + } + } + } + fclose(fp); + } + + /* INSTALL */ + snprintf(path, PATH_MAX, "%s/%s/install", topdir, ent->d_name); + if(!stat(path, &buf)) { + info->scriptlet = 1; + } + + return(info); +} + +int db_write(pacdb_t *db, pkginfo_t *info) +{ + FILE *fp = NULL; + char topdir[PATH_MAX]; + char path[PATH_MAX]; + mode_t oldmask; + PMList *lp = NULL; + + if(info == NULL) { + return(1); + } + snprintf(topdir, PATH_MAX, "%s%s/%s/%s-%s", pmo_root, PKGDIR, db->treename, + info->name, info->version); + + oldmask = umask(0000); + mkdir(topdir, 0755); + umask(oldmask); + + /* DESC */ + snprintf(path, PATH_MAX, "%s/desc", topdir); + fp = fopen(path, "w"); + if(fp == NULL) { + perror("db_write"); + return(1); + } + fputs("%NAME%\n", fp); + fprintf(fp, "%s\n\n", info->name); + fputs("%VERSION%\n", fp); + fprintf(fp, "%s\n\n", info->version); + fputs("%DESC%\n", fp); + fprintf(fp, "%s\n\n", info->desc); + fputs("%BUILDDATE%\n", fp); + fprintf(fp, "%s\n\n", info->builddate); + fputs("%INSTALLDATE%\n", fp); + fprintf(fp, "%s\n\n", info->installdate); + fputs("%PACKAGER%\n", fp); + fprintf(fp, "%s\n\n", info->packager); + fputs("%SIZE%\n", fp); + fprintf(fp, "%ld\n\n", info->size); + fclose(fp); + + /* FILES */ + snprintf(path, PATH_MAX, "%s/files", topdir); + fp = fopen(path, "w"); + if(fp == NULL) { + perror("db_write"); + return(1); + } + fputs("%FILES%\n", fp); + for(lp = info->files; lp; lp = lp->next) { + fprintf(fp, "%s\n", (char*)lp->data); + } + fprintf(fp, "\n"); + fputs("%BACKUP%\n", fp); + for(lp = info->backup; lp; lp = lp->next) { + fprintf(fp, "%s\n", (char*)lp->data); + } + fprintf(fp, "\n"); + fclose(fp); + + /* DEPENDS */ + snprintf(path, PATH_MAX, "%s/depends", topdir); + fp = fopen(path, "w"); + if(fp == NULL) { + perror("db_write"); + return(1); + } + fputs("%DEPENDS%\n", fp); + for(lp = info->depends; lp; lp = lp->next) { + fprintf(fp, "%s\n", (char*)lp->data); + } + fprintf(fp, "\n"); + fputs("%REQUIREDBY%\n", fp); + for(lp = info->requiredby; lp; lp = lp->next) { + fprintf(fp, "%s\n", (char*)lp->data); + } + fprintf(fp, "\n"); + fputs("%CONFLICTS%\n", fp); + for(lp = info->conflicts; lp; lp = lp->next) { + fprintf(fp, "%s\n", (char*)lp->data); + } + fprintf(fp, "\n"); + fclose(fp); + + /* INSTALL */ + /* nothing needed here (script is automatically extracted) */ + + return(0); +} + + +PMList* db_find_conflicts(pacdb_t *db, PMList *targets) +{ + PMList *i, *j, *k; + char *filestr = NULL; + char path[PATH_MAX+1]; + char *str = NULL; + struct stat buf; + PMList *conflicts = NULL; + + /* CHECK 1: check every db package against every target package */ + /* XXX: I've disabled the database-against-targets check for now, as the + * many many strcmp() calls slow it down heavily and most of the + * checking is redundant to the targets-against-filesystem check. + * This will be re-enabled if I can improve performance significantly. + * + pkginfo_t *info = NULL; + char *dbstr = NULL; + vprint("Checking database against targets...\n"); + rewinddir(db->dir); + while((info = db_scan(db, NULL, INFRQ_DESC | INFRQ_FILES)) != NULL) { + for(i = info->files; i; i = i->next) { + if(i->data == NULL) continue; + dbstr = (char*)i->data; + for(j = targets; j; j = j->next) { + pkginfo_t *targ = (pkginfo_t*)j->data; + if(strcmp(info->name, targ->name)) { + for(k = targ->files; k; k = k->next) { + filestr = (char*)k->data; + if(!strcmp(dbstr, filestr)) { + if(rindex(k->data, '/') == filestr+strlen(filestr)-1) { + continue; + } + MALLOC(str, 512); + snprintf(str, 512, "%s: exists in \"%s\" (target) and \"%s\" (installed)", dbstr, + targ->name, info->name); + conflicts = list_add(conflicts, str); + } + } + } + } + } + }*/ + + /* CHECK 2: check every target against every target */ + vprint("Checking targets against targets...\n"); + for(i = targets; i; i = i->next) { + pkginfo_t *p1 = (pkginfo_t*)i->data; + for(j = i; j; j = j->next) { + pkginfo_t *p2 = (pkginfo_t*)j->data; + if(strcmp(p1->name, p2->name)) { + for(k = p1->files; k; k = k->next) { + filestr = k->data; + if(!strcmp(filestr, "._install")) { + continue; + } + if(rindex(filestr, '/') == filestr+strlen(filestr)-1) { + /* this filename has a trailing '/', so it's a directory -- skip it. */ + continue; + } + if(is_in(filestr, p2->files)) { + MALLOC(str, 512); + snprintf(str, 512, "%s: exists in \"%s\" (target) and \"%s\" (target)", + filestr, p1->name, p2->name); + conflicts = list_add(conflicts, str); + } + } + } + } + } + + /* CHECK 3: check every target against the filesystem */ + vprint("Checking targets against filesystem...\n"); + for(i = targets; i; i = i->next) { + pkginfo_t *p = (pkginfo_t*)i->data; + pkginfo_t *dbpkg = NULL; + for(j = p->files; j; j = j->next) { + filestr = (char*)j->data; + snprintf(path, PATH_MAX, "%s%s", pmo_root, filestr); + if(!stat(path, &buf) && !S_ISDIR(buf.st_mode)) { + int ok = 0; + if(dbpkg == NULL) { + dbpkg = db_scan(db, p->name, INFRQ_DESC | INFRQ_FILES); + } + if(dbpkg && is_in(j->data, dbpkg->files)) { + ok = 1; + } + if(!ok) { + MALLOC(str, 512); + snprintf(str, 512, "%s: exists in filesystem", path); + conflicts = list_add(conflicts, str); + } + } + } + freepkg(dbpkg); + dbpkg = NULL; + } + + return(conflicts); +} + +/* vim: set ts=2 sw=2 noet: */ diff --git a/src/db.h b/src/db.h new file mode 100644 index 00000000..7428e270 --- /dev/null +++ b/src/db.h @@ -0,0 +1,46 @@ +/* + * pacman + * + * Copyright (c) 2002 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ +#ifndef _PAC_DB_H +#define _PAC_DB_H + +#include <dirent.h> + +/* info requests for db_read */ +#define INFRQ_DESC 0x01 +#define INFRQ_DEPENDS 0x02 +#define INFRQ_FILES 0x04 +#define INFRQ_ALL 0xFF + +typedef struct __pacdb_t { + char treename[128]; + DIR* dir; +} pacdb_t; + +pacdb_t* db_open(char *dbpath, char *treename); +void db_close(pacdb_t *db); +PMList* db_loadpkgs(pacdb_t *db, PMList *pkgcache); +pkginfo_t* db_scan(pacdb_t *db, char *target, unsigned int inforeq); +pkginfo_t* db_read(pacdb_t *db, struct dirent *ent, unsigned int inforeq); +int db_write(pacdb_t *db, pkginfo_t *info); +PMList* db_find_conflicts(pacdb_t *db, PMList* targets); + +#endif +/* vim: set ts=2 sw=2 noet: */ diff --git a/src/list.c b/src/list.c new file mode 100644 index 00000000..2a33b6e3 --- /dev/null +++ b/src/list.c @@ -0,0 +1,122 @@ +/* + * pacman + * + * Copyright (c) 2002 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +#include "config.h" +#include <stdio.h> +#include <stdlib.h> +#include <limits.h> +#include <errno.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include "list.h" + +PMList* list_new() +{ + PMList *list = NULL; + + list = (PMList*)malloc(sizeof(PMList)); + if(list == NULL) { + return(NULL); + } + list->data = NULL; + list->prev = NULL; + list->next = NULL; + return(list); +} + +void list_free(PMList *list) +{ + if(list == NULL) { + return; + } + if(list->data != NULL) { + free(list->data); + list->data = NULL; + } + if(list->next != NULL) { + list_free(list->next); + } + free(list); + return; +} + +PMList* list_add(PMList *list, void *data) +{ + PMList *ptr, *lp; + + ptr = list; + if(ptr == NULL) { + ptr = list_new(); + } + + lp = list_last(ptr); + if(lp == ptr && lp->data == NULL) { + /* nada */ + } else { + lp->next = list_new(); + if(lp->next == NULL) { + return(NULL); + } + lp->next->prev = lp; + lp = lp->next; + } + lp->data = data; + return(ptr); +} + +int list_count(PMList *list) +{ + int i; + PMList *lp; + + for(lp = list, i = 0; lp; lp = lp->next, i++); + return(i); +} + +/* List one is extended and returned + * List two is freed (but not its data) + */ +PMList* list_merge(PMList *one, PMList *two) +{ + PMList *lp; + + for(lp = two; lp; lp = lp->next) { + if(lp->data) { + list_add(one, lp->data); + lp->data = NULL; + } + } + list_free(two); + + return(one); +} + +PMList* list_last(PMList *list) +{ + PMList *ptr; + + for(ptr = list; ptr && ptr->next; ptr = ptr->next); + return(ptr); +} + +/* vim: set ts=2 sw=2 noet: */ diff --git a/src/list.h b/src/list.h new file mode 100644 index 00000000..9749d934 --- /dev/null +++ b/src/list.h @@ -0,0 +1,20 @@ +#ifndef _PAC_LIST_H +#define _PAC_LIST_H + +/* your average linked list */ +typedef struct __pmlist_t { + void* data; + struct __pmlist_t* prev; + struct __pmlist_t* next; +} PMList; + +PMList* list_new(); +void list_free(PMList* list); +PMList* list_add(PMList* list, void* data); +int list_count(PMList* list); +PMList* list_merge(PMList *one, PMList *two); +PMList* list_last(PMList* list); + +#endif + +/* vim: set ts=2 sw=2 noet: */ diff --git a/src/md5.c b/src/md5.c new file mode 100644 index 00000000..fcb1611e --- /dev/null +++ b/src/md5.c @@ -0,0 +1,338 @@ +/* MD5C.C - RSA Data Security, Inc., MD5 message-digest algorithm + */ + +/* Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All +rights reserved. + +License to copy and use this software is granted provided that it +is identified as the "RSA Data Security, Inc. MD5 Message-Digest +Algorithm" in all material mentioning or referencing this software +or this function. + +License is also granted to make and use derivative works provided +that such works are identified as "derived from the RSA Data +Security, Inc. MD5 Message-Digest Algorithm" in all material +mentioning or referencing the derived work. + +RSA Data Security, Inc. makes no representations concerning either +the merchantability of this software or the suitability of this +software for any particular purpose. It is provided "as is" +without express or implied warranty of any kind. + +These notices must be retained in any copies of any part of this +documentation and/or software. + */ + +#include "md5.h" + +/* Constants for MD5Transform routine. + */ + +#define S11 7 +#define S12 12 +#define S13 17 +#define S14 22 +#define S21 5 +#define S22 9 +#define S23 14 +#define S24 20 +#define S31 4 +#define S32 11 +#define S33 16 +#define S34 23 +#define S41 6 +#define S42 10 +#define S43 15 +#define S44 21 + +static void MD5Transform(UINT4 [4], unsigned char [64]); +static void Encode(unsigned char *, UINT4 *, unsigned int); +static void Decode(UINT4 *, unsigned char *, unsigned int); +static void MD5_memcpy(POINTER, POINTER, unsigned int); +static void MD5_memset(POINTER, int, unsigned int); + +static unsigned char PADDING[64] = { + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +/* F, G, H and I are basic MD5 functions. + */ +#define F(x, y, z) (((x) & (y)) | ((~x) & (z))) +#define G(x, y, z) (((x) & (z)) | ((y) & (~z))) +#define H(x, y, z) ((x) ^ (y) ^ (z)) +#define I(x, y, z) ((y) ^ ((x) | (~z))) + +/* ROTATE_LEFT rotates x left n bits. + */ +#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32-(n)))) + +/* FF, GG, HH, and II transformations for rounds 1, 2, 3, and 4. +Rotation is separate from addition to prevent recomputation. + */ +#define FF(a, b, c, d, x, s, ac) { \ + (a) += F ((b), (c), (d)) + (x) + (UINT4)(ac); \ + (a) = ROTATE_LEFT ((a), (s)); \ + (a) += (b); \ + } +#define GG(a, b, c, d, x, s, ac) { \ + (a) += G ((b), (c), (d)) + (x) + (UINT4)(ac); \ + (a) = ROTATE_LEFT ((a), (s)); \ + (a) += (b); \ + } +#define HH(a, b, c, d, x, s, ac) { \ + (a) += H ((b), (c), (d)) + (x) + (UINT4)(ac); \ + (a) = ROTATE_LEFT ((a), (s)); \ + (a) += (b); \ + } +#define II(a, b, c, d, x, s, ac) { \ + (a) += I ((b), (c), (d)) + (x) + (UINT4)(ac); \ + (a) = ROTATE_LEFT ((a), (s)); \ + (a) += (b); \ + } + +/* MD5 initialization. Begins an MD5 operation, writing a new context. + */ +void MD5Init (context) +MD5_CTX *context; /* context */ +{ + context->count[0] = context->count[1] = 0; + /* Load magic initialization constants. +*/ + context->state[0] = 0x67452301; + context->state[1] = 0xefcdab89; + context->state[2] = 0x98badcfe; + context->state[3] = 0x10325476; +} + +/* MD5 block update operation. Continues an MD5 message-digest + operation, processing another message block, and updating the + context. + */ +void MD5Update (context, input, inputLen) +MD5_CTX *context; /* context */ +unsigned char *input; /* input block */ +unsigned int inputLen; /* length of input block */ +{ + unsigned int i, index, partLen; + + /* Compute number of bytes mod 64 */ + index = (unsigned int)((context->count[0] >> 3) & 0x3F); + + /* Update number of bits */ + if ((context->count[0] += ((UINT4)inputLen << 3)) + + < ((UINT4)inputLen << 3)) + context->count[1]++; + context->count[1] += ((UINT4)inputLen >> 29); + + partLen = 64 - index; + + /* Transform as many times as possible. +*/ + if (inputLen >= partLen) { + MD5_memcpy + ((POINTER)&context->buffer[index], (POINTER)input, partLen); + MD5Transform (context->state, context->buffer); + + for (i = partLen; i + 63 < inputLen; i += 64) + MD5Transform (context->state, &input[i]); + + index = 0; + } + else + i = 0; + + /* Buffer remaining input */ + MD5_memcpy + ((POINTER)&context->buffer[index], (POINTER)&input[i], + inputLen-i); +} + +/* MD5 finalization. Ends an MD5 message-digest operation, writing the + the message digest and zeroizing the context. + */ +void MD5Final (digest, context) +unsigned char digest[16]; /* message digest */ +MD5_CTX *context; /* context */ +{ + unsigned char bits[8]; + unsigned int index, padLen; + + /* Save number of bits */ + Encode (bits, context->count, 8); + + /* Pad out to 56 mod 64. +*/ + index = (unsigned int)((context->count[0] >> 3) & 0x3f); + padLen = (index < 56) ? (56 - index) : (120 - index); + MD5Update (context, PADDING, padLen); + + /* Append length (before padding) */ + MD5Update (context, bits, 8); + + /* Store state in digest */ + Encode (digest, context->state, 16); + + /* Zeroize sensitive information. +*/ + MD5_memset ((POINTER)context, 0, sizeof (*context)); +} + +/* MD5 basic transformation. Transforms state based on block. + */ +static void MD5Transform (state, block) +UINT4 state[4]; +unsigned char block[64]; +{ + UINT4 a = state[0], b = state[1], c = state[2], d = state[3], x[16]; + + Decode (x, block, 64); + + /* Round 1 */ + FF (a, b, c, d, x[ 0], S11, 0xd76aa478); /* 1 */ + FF (d, a, b, c, x[ 1], S12, 0xe8c7b756); /* 2 */ + FF (c, d, a, b, x[ 2], S13, 0x242070db); /* 3 */ + FF (b, c, d, a, x[ 3], S14, 0xc1bdceee); /* 4 */ + FF (a, b, c, d, x[ 4], S11, 0xf57c0faf); /* 5 */ + FF (d, a, b, c, x[ 5], S12, 0x4787c62a); /* 6 */ + FF (c, d, a, b, x[ 6], S13, 0xa8304613); /* 7 */ + FF (b, c, d, a, x[ 7], S14, 0xfd469501); /* 8 */ + FF (a, b, c, d, x[ 8], S11, 0x698098d8); /* 9 */ + FF (d, a, b, c, x[ 9], S12, 0x8b44f7af); /* 10 */ + FF (c, d, a, b, x[10], S13, 0xffff5bb1); /* 11 */ + FF (b, c, d, a, x[11], S14, 0x895cd7be); /* 12 */ + FF (a, b, c, d, x[12], S11, 0x6b901122); /* 13 */ + FF (d, a, b, c, x[13], S12, 0xfd987193); /* 14 */ + FF (c, d, a, b, x[14], S13, 0xa679438e); /* 15 */ + FF (b, c, d, a, x[15], S14, 0x49b40821); /* 16 */ + + /* Round 2 */ + GG (a, b, c, d, x[ 1], S21, 0xf61e2562); /* 17 */ + GG (d, a, b, c, x[ 6], S22, 0xc040b340); /* 18 */ + GG (c, d, a, b, x[11], S23, 0x265e5a51); /* 19 */ + GG (b, c, d, a, x[ 0], S24, 0xe9b6c7aa); /* 20 */ + GG (a, b, c, d, x[ 5], S21, 0xd62f105d); /* 21 */ + GG (d, a, b, c, x[10], S22, 0x2441453); /* 22 */ + GG (c, d, a, b, x[15], S23, 0xd8a1e681); /* 23 */ + GG (b, c, d, a, x[ 4], S24, 0xe7d3fbc8); /* 24 */ + GG (a, b, c, d, x[ 9], S21, 0x21e1cde6); /* 25 */ + GG (d, a, b, c, x[14], S22, 0xc33707d6); /* 26 */ + GG (c, d, a, b, x[ 3], S23, 0xf4d50d87); /* 27 */ + + GG (b, c, d, a, x[ 8], S24, 0x455a14ed); /* 28 */ + GG (a, b, c, d, x[13], S21, 0xa9e3e905); /* 29 */ + GG (d, a, b, c, x[ 2], S22, 0xfcefa3f8); /* 30 */ + GG (c, d, a, b, x[ 7], S23, 0x676f02d9); /* 31 */ + GG (b, c, d, a, x[12], S24, 0x8d2a4c8a); /* 32 */ + + /* Round 3 */ + HH (a, b, c, d, x[ 5], S31, 0xfffa3942); /* 33 */ + HH (d, a, b, c, x[ 8], S32, 0x8771f681); /* 34 */ + HH (c, d, a, b, x[11], S33, 0x6d9d6122); /* 35 */ + HH (b, c, d, a, x[14], S34, 0xfde5380c); /* 36 */ + HH (a, b, c, d, x[ 1], S31, 0xa4beea44); /* 37 */ + HH (d, a, b, c, x[ 4], S32, 0x4bdecfa9); /* 38 */ + HH (c, d, a, b, x[ 7], S33, 0xf6bb4b60); /* 39 */ + HH (b, c, d, a, x[10], S34, 0xbebfbc70); /* 40 */ + HH (a, b, c, d, x[13], S31, 0x289b7ec6); /* 41 */ + HH (d, a, b, c, x[ 0], S32, 0xeaa127fa); /* 42 */ + HH (c, d, a, b, x[ 3], S33, 0xd4ef3085); /* 43 */ + HH (b, c, d, a, x[ 6], S34, 0x4881d05); /* 44 */ + HH (a, b, c, d, x[ 9], S31, 0xd9d4d039); /* 45 */ + HH (d, a, b, c, x[12], S32, 0xe6db99e5); /* 46 */ + HH (c, d, a, b, x[15], S33, 0x1fa27cf8); /* 47 */ + HH (b, c, d, a, x[ 2], S34, 0xc4ac5665); /* 48 */ + + /* Round 4 */ + II (a, b, c, d, x[ 0], S41, 0xf4292244); /* 49 */ + II (d, a, b, c, x[ 7], S42, 0x432aff97); /* 50 */ + II (c, d, a, b, x[14], S43, 0xab9423a7); /* 51 */ + II (b, c, d, a, x[ 5], S44, 0xfc93a039); /* 52 */ + II (a, b, c, d, x[12], S41, 0x655b59c3); /* 53 */ + II (d, a, b, c, x[ 3], S42, 0x8f0ccc92); /* 54 */ + II (c, d, a, b, x[10], S43, 0xffeff47d); /* 55 */ + II (b, c, d, a, x[ 1], S44, 0x85845dd1); /* 56 */ + II (a, b, c, d, x[ 8], S41, 0x6fa87e4f); /* 57 */ + II (d, a, b, c, x[15], S42, 0xfe2ce6e0); /* 58 */ + II (c, d, a, b, x[ 6], S43, 0xa3014314); /* 59 */ + II (b, c, d, a, x[13], S44, 0x4e0811a1); /* 60 */ + II (a, b, c, d, x[ 4], S41, 0xf7537e82); /* 61 */ + II (d, a, b, c, x[11], S42, 0xbd3af235); /* 62 */ + II (c, d, a, b, x[ 2], S43, 0x2ad7d2bb); /* 63 */ + II (b, c, d, a, x[ 9], S44, 0xeb86d391); /* 64 */ + + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + + /* Zeroize sensitive information. + +*/ + MD5_memset ((POINTER)x, 0, sizeof (x)); +} + +/* Encodes input (UINT4) into output (unsigned char). Assumes len is + a multiple of 4. + */ +static void Encode (output, input, len) +unsigned char *output; +UINT4 *input; +unsigned int len; +{ + unsigned int i, j; + + for (i = 0, j = 0; j < len; i++, j += 4) { + output[j] = (unsigned char)(input[i] & 0xff); + output[j+1] = (unsigned char)((input[i] >> 8) & 0xff); + output[j+2] = (unsigned char)((input[i] >> 16) & 0xff); + output[j+3] = (unsigned char)((input[i] >> 24) & 0xff); + } +} + +/* Decodes input (unsigned char) into output (UINT4). Assumes len is + a multiple of 4. + */ +static void Decode (output, input, len) +UINT4 *output; +unsigned char *input; +unsigned int len; +{ + unsigned int i, j; + + for (i = 0, j = 0; j < len; i++, j += 4) + output[i] = ((UINT4)input[j]) | (((UINT4)input[j+1]) << 8) | + (((UINT4)input[j+2]) << 16) | (((UINT4)input[j+3]) << 24); +} + +/* Note: Replace "for loop" with standard memcpy if possible. + */ + +static void MD5_memcpy (output, input, len) +POINTER output; +POINTER input; +unsigned int len; +{ + unsigned int i; + + for (i = 0; i < len; i++) + + output[i] = input[i]; +} + +/* Note: Replace "for loop" with standard memset if possible. + */ +static void MD5_memset (output, value, len) +POINTER output; +int value; +unsigned int len; +{ + unsigned int i; + + for (i = 0; i < len; i++) + ((char *)output)[i] = (char)value; +} + +/* vim: set ts=2 sw=2 noet: */ diff --git a/src/md5.h b/src/md5.h new file mode 100644 index 00000000..b5ce6fd9 --- /dev/null +++ b/src/md5.h @@ -0,0 +1,48 @@ +/* MD5.H - header file for MD5C.C + */ + +/* Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All +rights reserved. + +License to copy and use this software is granted provided that it +is identified as the "RSA Data Security, Inc. MD5 Message-Digest +Algorithm" in all material mentioning or referencing this software +or this function. + +License is also granted to make and use derivative works provided +that such works are identified as "derived from the RSA Data +Security, Inc. MD5 Message-Digest Algorithm" in all material +mentioning or referencing the derived work. + +RSA Data Security, Inc. makes no representations concerning either +the merchantability of this software or the suitability of this +software for any particular purpose. It is provided "as is" +without express or implied warranty of any kind. + +These notices must be retained in any copies of any part of this +documentation and/or software. + */ + + +/* POINTER defines a generic pointer type */ +typedef unsigned char *POINTER; + +/* UINT2 defines a two byte word */ +typedef unsigned short int UINT2; + +/* UINT4 defines a four byte word */ +typedef unsigned long int UINT4; + + +/* MD5 context. */ +typedef struct { + UINT4 state[4]; /* state (ABCD) */ + UINT4 count[2]; /* number of bits, modulo 2^64 (lsb first) */ + unsigned char buffer[64]; /* input buffer */ +} MD5_CTX; + +void MD5Init(MD5_CTX *); +void MD5Update(MD5_CTX *, unsigned char *, unsigned int); +void MD5Final(unsigned char [16], MD5_CTX *); + +/* vim: set ts=2 sw=2 noet: */ diff --git a/src/md5driver.c b/src/md5driver.c new file mode 100644 index 00000000..52c7189a --- /dev/null +++ b/src/md5driver.c @@ -0,0 +1,83 @@ +/* MD5DRIVER.C - taken and modified from MDDRIVER.C (license below) */ +/* for use in pacman. */ +/*********************************************************************/ + +/* Copyright (C) 1990-2, RSA Data Security, Inc. Created 1990. All +rights reserved. + +RSA Data Security, Inc. makes no representations concerning either +the merchantability of this software or the suitability of this +software for any particular purpose. It is provided "as is" +without express or implied warranty of any kind. + +These notices must be retained in any copies of any part of this +documentation and/or software. + */ + +/* The following makes MD default to MD5 if it has not already been + defined with C compiler flags. + */ +#define MD MD5 + +#include <stdio.h> +#include <time.h> +#include <string.h> +#include "md5.h" + +/* Length of test block, number of test blocks. + */ +#define TEST_BLOCK_LEN 1000 +#define TEST_BLOCK_COUNT 1000 + +char* MDFile(char *); +void MDPrint(unsigned char [16]); + +#define MD_CTX MD5_CTX +#define MDInit MD5Init +#define MDUpdate MD5Update +#define MDFinal MD5Final + +char* MDFile(char *filename) +{ + FILE *file; + MD_CTX context; + int len; + unsigned char buffer[1024], digest[16]; + + if((file = fopen(filename, "rb")) == NULL) { + printf ("%s can't be opened\n", filename); + } else { + char *ret; + int i; + + MDInit(&context); + while((len = fread(buffer, 1, 1024, file))) { + MDUpdate(&context, buffer, len); + } + MDFinal(digest, &context); + fclose(file); + /*printf("MD5 (%s) = ", filename); + MDPrint(digest); + printf("\n");*/ + + ret = (char*)malloc(33); + ret[0] = '\0'; + for(i = 0; i < 16; i++) { + sprintf(ret, "%s%02x", ret, digest[i]); + } + + return(ret); + } + return(NULL); +} + +/* Prints a message digest in hexadecimal. + */ +void MDPrint(unsigned char digest[16]) +{ + unsigned int i; + for (i = 0; i < 16; i++) + printf ("%02x", digest[i]); +} + +/* vim: set ts=2 sw=2 noet: */ diff --git a/src/package.c b/src/package.c new file mode 100644 index 00000000..69461f67 --- /dev/null +++ b/src/package.c @@ -0,0 +1,228 @@ +/* + * pacman + * + * Copyright (c) 2002 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +#include "config.h" +#include <stdio.h> +#include <stdlib.h> +#include <limits.h> +#include <fcntl.h> +#include <unistd.h> +#include <string.h> +#include "list.h" +#include "package.h" +#include "db.h" +#include "util.h" +#include "pacman.h" + +extern tartype_t gztype; + +pkginfo_t* load_pkg(char *pkgfile, unsigned short output) +{ + char *expath; + char *descfile; + int i; + TAR *tar; + pkginfo_t *info = NULL; + PMList *backup = NULL; + PMList *lp; + + info = newpkg(); + descfile = strdup("/tmp/pacman_XXXXXX"); + + if(tar_open(&tar, pkgfile, &gztype, O_RDONLY, 0, TAR_GNU) == -1) { + perror("could not open package"); + return(NULL); + } + vprint("load_pkg: loading filelist from package...\n"); + for(i = 0; !th_read(tar); i++) { + if(!strcmp(th_get_pathname(tar), ".PKGINFO")) { + /* extract this file into /tmp. it has info for us */ + vprint("load_pkg: found package description file.\n"); + mkstemp(descfile); + tar_extract_file(tar, descfile); + /* parse the info file */ + parse_descfile(descfile, info, &backup, output); + if(!strlen(info->name)) { + fprintf(stderr, "error: missing package name in description file.\n"); + return(NULL); + } + if(!strlen(info->version)) { + fprintf(stderr, "error: missing package version in description file.\n"); + return(NULL); + } + for(lp = backup; lp; lp = lp->next) { + if(lp->data) { + info->backup = list_add(info->backup, lp->data); + } + } + continue; + } + if(!strcmp(th_get_pathname(tar), "._install")) { + info->scriptlet = 1; + } else { + expath = strdup(th_get_pathname(tar)); + /* add the path to the list */ + info->files = list_add(info->files, expath); + } + + if(TH_ISREG(tar) && tar_skip_regfile(tar)) { + perror("bad package file"); + return(NULL); + } + expath = NULL; + } + tar_close(tar); + FREE(descfile); + + if(!strlen(info->name) || !strlen(info->version)) { + fprintf(stderr, "Error: Missing .PKGINFO file in %s\n", pkgfile); + return(NULL); + } + + return(info); +} + +/* Parses the package description file for the current package + * + * Returns: 0 on success, 1 on error + * + */ +int parse_descfile(char *descfile, pkginfo_t *info, PMList **backup, int output) +{ + FILE* fp = NULL; + char line[PATH_MAX+1]; + char* ptr = NULL; + char* key = NULL; + int linenum = 0; + PMList* bak = NULL; + + if((fp = fopen(descfile, "r")) == NULL) { + perror(descfile); + return(1); + } + + while(!feof(fp)) { + fgets(line, PATH_MAX, fp); + if(output) { + printf("%s", line); + } + linenum++; + trim(line); + if(line[0] == '#') { + continue; + } + if(strlen(line) == 0) { + continue; + } + ptr = line; + key = strsep(&ptr, "="); + if(key == NULL || ptr == NULL) { + fprintf(stderr, "Syntax error in description file line %d\n", linenum); + } else { + trim(key); + key = strtoupper(key); + trim(ptr); + if(!strcmp(key, "PKGNAME")) { + strncpy(info->name, ptr, sizeof(info->name)); + } else if(!strcmp(key, "PKGVER")) { + strncpy(info->version, ptr, sizeof(info->version)); + } else if(!strcmp(key, "PKGDESC")) { + strncpy(info->desc, ptr, sizeof(info->desc)); + } else if(!strcmp(key, "BUILDDATE")) { + strncpy(info->builddate, ptr, sizeof(info->builddate)); + } else if(!strcmp(key, "INSTALLDATE")) { + strncpy(info->installdate, ptr, sizeof(info->installdate)); + } else if(!strcmp(key, "PACKAGER")) { + strncpy(info->packager, ptr, sizeof(info->packager)); + } else if(!strcmp(key, "SIZE")) { + char tmp[32]; + strncpy(tmp, ptr, sizeof(tmp)); + info->size = atol(tmp); + } else if(!strcmp(key, "DEPEND")) { + char *s = strdup(ptr); + info->depends = list_add(info->depends, s); + } else if(!strcmp(key, "CONFLICT")) { + char *s = strdup(ptr); + info->conflicts = list_add(info->conflicts, s); + } else if(!strcmp(key, "BACKUP")) { + char *s = strdup(ptr); + bak = list_add(bak, s); + } else { + fprintf(stderr, "Syntax error in description file line %d\n", linenum); + } + } + line[0] = '\0'; + } + fclose(fp); + unlink(descfile); + + *backup = bak; + return(0); +} + +pkginfo_t* newpkg() +{ + pkginfo_t* pkg = NULL; + MALLOC(pkg, sizeof(pkginfo_t)); + + pkg->name[0] = '\0'; + pkg->version[0] = '\0'; + pkg->desc[0] = '\0'; + pkg->builddate[0] = '\0'; + pkg->installdate[0] = '\0'; + pkg->packager[0] = '\0'; + pkg->size = 0; + pkg->scriptlet = 0; + pkg->requiredby = NULL; + pkg->conflicts = NULL; + pkg->files = NULL; + pkg->backup = NULL; + pkg->depends = NULL; + + return(pkg); +} + +void freepkg(pkginfo_t *pkg) +{ + if(pkg == NULL) { + return; + } + + list_free(pkg->files); + list_free(pkg->backup); + list_free(pkg->depends); + list_free(pkg->conflicts); + list_free(pkg->requiredby); + FREE(pkg); + return; +} + +/* Helper function for sorting packages + */ +int pkgcmp(const void *p1, const void *p2) +{ + pkginfo_t **pkg1 = (pkginfo_t**)p1; + pkginfo_t **pkg2 = (pkginfo_t**)p2; + + return(strcmp(pkg1[0]->name, pkg2[0]->name)); +} + +/* vim: set ts=2 sw=2 noet: */ diff --git a/src/package.h b/src/package.h new file mode 100644 index 00000000..f9dc9477 --- /dev/null +++ b/src/package.h @@ -0,0 +1,67 @@ +/* + * pacman + * + * Copyright (c) 2002 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ +#ifndef _PAC_PACKAGE_H +#define _PAC_PACKAGE_H + +/* mods for depend_t.mod */ +#define DEP_ANY 0 +#define DEP_EQ 1 +#define DEP_GE 2 +#define DEP_LE 3 + +/* Package Structures */ +typedef char** fileset_t; +typedef struct __pkginfo_t { + char name[256]; + char version[64]; + char desc[512]; + char builddate[32]; + char installdate[32]; + char packager[64]; + unsigned long size; + unsigned short scriptlet; + PMList *files; + PMList *backup; + PMList *depends; + PMList *requiredby; + PMList *conflicts; +} pkginfo_t; + +typedef struct __depend_t { + unsigned short mod; + char name[256]; + char version[64]; +} depend_t; + +typedef struct __depmissing_t { + enum {DEPEND, REQUIRED, CONFLICT} type; + char target[256]; + depend_t depend; +} depmissing_t; + +pkginfo_t* load_pkg(char *pkgfile, unsigned short output); +int parse_descfile(char *descfile, pkginfo_t *info, PMList **backup, int output); +pkginfo_t* newpkg(); +void freepkg(pkginfo_t *pkg); +int pkgcmp(const void *p1, const void *p2); + +#endif +/* vim: set ts=2 sw=2 noet: */ diff --git a/src/pacman.c b/src/pacman.c new file mode 100644 index 00000000..7c70cacd --- /dev/null +++ b/src/pacman.c @@ -0,0 +1,1660 @@ +/* + * pacman + * + * Copyright (c) 2002 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +#include "config.h" +#include <stdio.h> +#include <stdarg.h> +#include <stdlib.h> +#include <signal.h> +#include <limits.h> +#include <errno.h> +#include <fcntl.h> +#include <string.h> +#include <dirent.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <time.h> +/* pacman */ +#include "list.h" +#include "md5.h" +#include "package.h" +#include "db.h" +#include "util.h" +#include "pacsync.h" +#include "pacman.h" + +extern tartype_t gztype; + +/* other prototypes */ +int rpmvercmp(const char *a, const char *b); +char* MDFile(char *); + +/* + * GLOBALS + * + */ + +/* pacman options */ +char* pmo_root = NULL; +unsigned short pmo_op = PM_MAIN; +unsigned short pmo_verbose = 0; +unsigned short pmo_version = 0; +unsigned short pmo_help = 0; +unsigned short pmo_force = 0; +unsigned short pmo_nodeps = 0; +unsigned short pmo_upgrade = 0; +unsigned short pmo_nosave = 0; +unsigned short pmo_vertest = 0; +unsigned short pmo_q_isfile = 0; +unsigned short pmo_q_info = 0; +unsigned short pmo_q_list = 0; +unsigned short pmo_q_owns = 0; +unsigned short pmo_s_upgrade = 0; +unsigned short pmo_s_sync = 0; +unsigned short pmo_s_search = 0; +unsigned short pmo_s_clean = 0; + +/* configuration options */ +char pmc_syncserver[512] = ""; +char pmc_syncpath[512] = ""; +char pmc_syncname[512] = ""; + +char *lckfile = "/tmp/pacman.lck"; + +/* list of installed packages */ +PMList *pm_packages = NULL; + +/* list of targets specified on command line */ +PMList *pm_targets = NULL; + +int main(int argc, char *argv[]) +{ + int ret = 0; + char *ptr = NULL; + char *dbpath = NULL; + pacdb_t *db_local = NULL; + PMList *lp; + + /* default root */ + MALLOC(pmo_root, PATH_MAX); + strcpy(pmo_root, "/"); + + if(argc < 2) { + usage(PM_MAIN, (char*)basename(argv[0])); + return(0); + } + + ret = parseargs(PM_ADD, argc, argv); + if(ret) { + return(ret); + } + + /* check for permission */ + if(pmo_op != PM_MAIN && pmo_op != PM_QUERY && pmo_op != PM_DEPTEST) { + if(pmo_op == PM_SYNC && pmo_s_search) { + /* special case: PM_SYNC can be used w/ pmo_s_search by any user */ + } else { + uid_t uid = geteuid(); + if(uid != 0) { + fprintf(stderr, "error: you cannot perform this operation unless you are root.\n"); + return(1); + } + } + } + + vprint("Installation Root: %s\n", pmo_root); + if(pm_targets) { + vprint("Targets:\n"); + for(lp = pm_targets; lp; lp = lp->next) { + vprint(" %s\n", lp->data); + } + } + + /* lock */ + if(lckmk(lckfile, 1, 1) == -1) { + fprintf(stderr, "error: unable to lock pacman database.\n"); + fprintf(stderr, " if you're sure pacman is not already running, you\n"); + fprintf(stderr, " can remove %s\n", lckfile); + return(127); + } + + /* set signal handlers */ + signal(SIGINT, cleanup); + signal(SIGTERM, cleanup); + + /* check for db existence */ + /* add a trailing '/' if there isn't one */ + if(pmo_root[strlen(pmo_root)-1] != '/') { + MALLOC(ptr, strlen(pmo_root)+1); + strcpy(ptr, pmo_root); + strcat(ptr, "/"); + FREE(pmo_root); + pmo_root = ptr; + } + /* db location */ + MALLOC(dbpath, PATH_MAX); + snprintf(dbpath, PATH_MAX-1, "%s%s", pmo_root, PKGDIR); + vprint("Top-level DB Path: %s\n", dbpath); + + db_local = db_open(dbpath, "local"); + if(db_local == NULL) { + /* couldn't open the db directory - try creating it */ + char path[PATH_MAX]; + + snprintf(path, PATH_MAX, "%s/local", dbpath); + vprint("initializing database...\n", path); + ret = makepath(path); + + if(ret) { + fprintf(stderr, "error: could not create database.\n"); + cleanup(1); + } + if((db_local = db_open(dbpath, "local")) == NULL) { + fprintf(stderr, "error: could not open database.\n"); + cleanup(1); + } + } + /* load pm_packages cache */ + pm_packages = db_loadpkgs(db_local, pm_packages); + + /* start the requested operation */ + switch(pmo_op) { + case PM_ADD: ret = pacman_add(db_local, pm_targets); break; + case PM_REMOVE: ret = pacman_remove(db_local, pm_targets); break; + case PM_UPGRADE: ret = pacman_upgrade(db_local, pm_targets); break; + case PM_QUERY: ret = pacman_query(db_local, pm_targets); break; + case PM_SYNC: ret = pacman_sync(db_local, pm_targets); break; + case PM_DEPTEST: ret = pacman_deptest(db_local, pm_targets); break; + case PM_MAIN: ret = 0; break; + default: fprintf(stderr, "error: no operation specified (use -h for help)\n\n"); + ret = 1; + } + db_close(db_local); + FREE(pmo_root); + FREE(dbpath); + cleanup(ret); + /* not reached */ + return(0); +} + +int pacman_deptest(pacdb_t *db, PMList *targets) +{ + PMList *list = NULL; + PMList *lp, *deps; + pkginfo_t *dummy; + + if(pmo_vertest) { + if(targets && targets->data && targets->next && targets->next->data) { + int ret = rpmvercmp(targets->data, targets->next->data); + printf("%d\n", ret); + return(ret); + } + return(0); + } + + dummy = newpkg(); + sprintf(dummy->name, "_dummy_"); + sprintf(dummy->version, "1.0-1"); + for(lp = targets; lp; lp = lp->next) { + if(lp->data == NULL) continue; + dummy->depends = list_add(dummy->depends, lp->data); + } + list = list_add(list, dummy); + deps = checkdeps(db, PM_ADD, list); + freepkg(dummy); + list->data = NULL; + list_free(list); + + if(deps) { + for(lp = deps; lp; lp = lp->next) { + depmissing_t *miss = (depmissing_t*)lp->data; + if(miss->type == CONFLICT) { + printf("conflict: %s\n", miss->depend.name); + } else if(miss->type == DEPEND || miss->type == REQUIRED) { + printf("requires: %s", miss->depend.name); + switch(miss->depend.mod) { + case DEP_EQ: printf("=%s", miss->depend.version); break; + case DEP_GE: printf(">=%s", miss->depend.version); break; + case DEP_LE: printf("<=%s", miss->depend.version); break; + } + printf("\n"); + } + FREE(miss); + lp->data = NULL; + } + FREE(deps); + return(127); + } + return(0); +} + +int pacman_sync(pacdb_t *db, PMList *targets) +{ + char url[1024]; + char *dbpath = NULL; + int allgood = 1, confirm = 0; + pacdb_t *db_sync = NULL; + PMList *pkgcache = NULL; + PMList *i, *j; + PMList *final = NULL; + PMList *trail = NULL; + PMList *deps = NULL; + int cols; + + /* parse the system-wide config file */ + snprintf(url, 511, "/%s", PACCONF); + if(parseconfig(url)) { + return(1); + } + if(!strlen(pmc_syncserver)) { + fprintf(stderr, "error: no Sync_Server specified in %s\n", url); + return(1); + } + if(!strlen(pmc_syncname)) { + fprintf(stderr, "error: no Sync_Tree_Name specified in %s\n", url); + return(1); + } + if(!strlen(pmc_syncpath)) { + fprintf(stderr, "error: no Sync_Tree_Path specified in %s\n", url); + return(1); + } + if(pmc_syncpath[0] != '/') { + sprintf(pmc_syncpath, "/%s", pmc_syncpath); + } + + if(pmo_s_clean) { + mode_t oldmask; + + printf("removing packages from cache... "); + if(rmrf("/var/cache/pacman/pkg")) { + fprintf(stderr, "error: could not remove cache directory: %s\n", strerror(errno)); + return(1); + } + + oldmask = umask(0000); + if(mkdir("/var/cache/pacman", 0755) && mkdir("/var/cache/pacman/pkg", 0755)) { + fprintf(stderr, "error: could not create new cache directory: %s\n", strerror(errno)); + return(1); + } + umask(oldmask); + + printf("done.\n"); + return(0); + } + + if(pmo_s_sync && !pmo_s_search) { + /* grab a fresh package list */ + printf(":: Synchronizing remote package tree... \n"); + sync_synctree(); + printf("\n"); + } + + /* open the sync db */ + MALLOC(dbpath, PATH_MAX); + snprintf(dbpath, PATH_MAX-1, "%s%s", pmo_root, PKGDIR); + db_sync = db_open(dbpath, pmc_syncname); + if(db_sync == NULL) { + fprintf(stderr, "error: could not open the sync database.\n"); + fprintf(stderr, " have you used --refresh yet?\n"); + return(1); + } + /* cache packages */ + pkgcache = db_loadpkgs(db_sync, pkgcache); + + if(db_sync == NULL) { + fprintf(stderr, "error: could not open sync database (%s/%s)\n", dbpath, pmc_syncname); + return(1); + } + + final = list_new(); + trail = list_new(); + + if(pmo_s_search) { + /* search sync db */ + for(i = targets; i; i = i->next) { + char *targ = strdup(i->data); + strtoupper(targ); + for(j = pkgcache; j; j = j->next) { + pkginfo_t *pkg = (pkginfo_t*)j->data; + char *haystack; + /* check name */ + haystack = strdup(pkg->name); + strtoupper(haystack); + if(strstr(haystack, targ)) { + printf("%s %s\n", pkg->name, pkg->version); + } else { + /* check description */ + FREE(haystack); + haystack = strdup(pkg->desc); + strtoupper(haystack); + if(strstr(haystack, targ)) { + printf("%s %s\n", pkg->name, pkg->version); + } + } + FREE(haystack); + } + FREE(targ); + } + } else if(pmo_s_upgrade) { + int newer = 0; + for(i = pm_packages; i && allgood; i = i->next) { + int cmp, found = 0; + pkginfo_t *local = (pkginfo_t*)i->data; + pkginfo_t *sync = NULL; + + for(j = pkgcache; !found && j; j = j->next) { + sync = (pkginfo_t*)j->data; + if(local == NULL || local->name == NULL) { + vprint("local is NULL!\n"); + } + if(sync == NULL || sync->name == NULL) { + vprint("sync is NULL!\n"); + } + if(!strcmp(local->name, sync->name)) { + found = 1; + } + } + if(!found) { + /*fprintf(stderr, "%s: not found in sync db. skipping.", local->name);*/ + continue; + } + /* compare versions and see if we need to upgrade */ + cmp = rpmvercmp(local->version, sync->version); + if(cmp > 0) { + /* local version is newer */ + fprintf(stderr, ":: %s-%s: local version is newer\n", + local->name, local->version); + newer = 1; + continue; + } else if(cmp == 0) { + /* versions are identical */ + continue; + } else { + /* re-fetch the package record with dependency info */ + sync = db_scan(db_sync, sync->name, INFRQ_DESC | INFRQ_DEPENDS); + /* add to the targets list */ + if(!is_pkgin(sync, final)) { + allgood = !resolvedeps(db, db_sync, sync, final, trail); + /* check again, as resolvedeps could have added our target for us */ + if(!is_pkgin(sync, final)) { + final = list_add(final, sync); + } + } + } + } + if(newer) { + fprintf(stderr, ":: Above packages will be skipped. To manually upgrade use 'pacman -S <pkg>'\n"); + } + } else { + /* process targets */ + for(i = targets; i && allgood; i = i->next) { + if(i->data) { + int cmp; + pkginfo_t *local; + pkginfo_t *sync; + + local = db_scan(db, (char*)i->data, INFRQ_DESC); + sync = db_scan(db_sync, (char*)i->data, INFRQ_DESC | INFRQ_DEPENDS); + if(sync == NULL) { + fprintf(stderr, ":: %s: not found in sync db\n", (char*)i->data); + continue; + } + if(local) { + /* this is an upgrade, compare versions and determine if it is necessary */ + cmp = rpmvercmp(local->version, sync->version); + if(cmp > 0) { + /* local version is newer - get confirmation first */ + if(!yesno(":: %s-%s: local version is newer. Upgrade anyway? [Y/n] ", local->name, local->version)) { + continue; + } + } else if(cmp == 0) { + /* versions are identical */ + if(!yesno(":: %s-%s: is up to date. Upgrade anyway? [Y/n] ", local->name, local->version)) { + continue; + } + } + } + /* add to targets list */ + if(!is_pkgin(sync, final)) { + allgood = !resolvedeps(db, db_sync, sync, final, trail); + /* check again, as resolvedeps could have added our target for us */ + if(!is_pkgin(sync, final)) { + final = list_add(final, sync); + } + } + } + } + } + + if(allgood) { + /* check for inter-conflicts and whatnot */ + deps = checkdeps(db, PM_UPGRADE, final); + if(deps) { + fprintf(stderr, "error: unresolvable conflicts/dependencies:\n"); + for(i = deps; i; i = i->next) { + depmissing_t *miss = (depmissing_t*)i->data; + if(miss->type == CONFLICT) { + fprintf(stderr, " %s: conflicts with %s\n", miss->target, miss->depend.name); + } else if(miss->type == DEPEND || miss->type == REQUIRED) { + fprintf(stderr, " %s: requires %s", miss->target, miss->depend.name); + switch(miss->depend.mod) { + case DEP_EQ: fprintf(stderr, "=%s", miss->depend.version); break; + case DEP_GE: fprintf(stderr, ">=%s", miss->depend.version); break; + case DEP_LE: fprintf(stderr, "<=%s", miss->depend.version); break; + } + if(miss->type == DEPEND) { + fprintf(stderr, " but it is not in the sync db\n"); + } else { + fprintf(stderr, "\n"); + } + } + FREE(miss); + i->data = NULL; + } + list_free(deps); + /* abort mission */ + allgood = 0; + } + + /* list targets */ + if(final && final->data) { + fprintf(stderr, "\nTargets: "); + } + cols = 9; + for(i = final; allgood && i; i = i->next) { + pkginfo_t *p = (pkginfo_t*)i->data; + if(p) { + char t[PATH_MAX]; + int s; + snprintf(t, PATH_MAX, "%s-%s ", p->name, p->version); + s = strlen(t); + if(s+cols > 78) { + cols = 9; + fprintf(stderr, "\n%9s", " "); + } + fprintf(stderr, "%s", t); + cols += s; + } + } + printf("\n"); + + /* get confirmation */ + confirm = 0; + if(allgood && final && final->data) { + confirm = yesno("\nDo you want to install/upgrade these packages? [Y/n] "); + } + } + + if(allgood && confirm) { + PMList *files = NULL; + char ldir[PATH_MAX]; + int varcache = 1; + + /* download targets */ + for(i = final; i; i = i->next) { + pkginfo_t *p = (pkginfo_t*)i->data; + if(p) { + struct stat buf; + char path[PATH_MAX]; + + snprintf(path, PATH_MAX, "%svar/cache/pacman/pkg/%s-%s.pkg.tar.gz", + pmo_root, p->name, p->version); + if(stat(path, &buf)) { + /* file is not in the cache dir, so add it to the list */ + snprintf(path, PATH_MAX, "%s-%s.pkg.tar.gz", p->name, p->version); + files = list_add(files, strdup(path)); + } + } + } + snprintf(ldir, PATH_MAX, "%svar/cache/pacman/pkg", pmo_root); + + if(files) { + struct stat buf; + + printf("\n:: Downloading packages...\n"); fflush(stdout); + if(stat(ldir, &buf)) { + mode_t oldmask; + char parent[PATH_MAX]; + + /* no cache directory.... try creating it */ + snprintf(parent, PATH_MAX, "%svar/cache/pacman", pmo_root); + fprintf(stderr, "warning: no %s cache exists. creating...\n", ldir); + oldmask = umask(0000); + if(mkdir(parent, 0755) || mkdir(ldir, 0755)) { + /* couldn't mkdir the cache directory, so fall back to /tmp and unlink + * the package afterwards. + */ + fprintf(stderr, "warning: couldn't create package cache, using /tmp instead\n"); + snprintf(ldir, PATH_MAX, "/tmp"); + varcache = 0; + } + umask(oldmask); + } + if(downloadfiles(pmc_syncserver, pmc_syncpath, ldir, files)) { + fprintf(stderr, "error: ftp transfer failed.\n"); + allgood = 0; + } + list_free(files); + files = NULL; + } + + /* install targets */ + for(i = final; allgood && i; i = i->next) { + char *str; + pkginfo_t *p = (pkginfo_t*)i->data; + if(p) { + MALLOC(str, PATH_MAX); + snprintf(str, PATH_MAX, "%s/%s-%s.pkg.tar.gz", ldir, p->name, p->version); + files = list_add(files, str); + } + } + if(allgood) { + pacman_upgrade(db, files); + } + + if(!varcache) { + /* delete packages */ + for(i = files; i; i = i->next) { + unlink(i->data); + } + } + } + + /* cleanup */ + for(i = pkgcache; i; i = i->next) { + if(i->data) freepkg((pkginfo_t*)i->data); + i->data = NULL; + } + for(i = final; i; i = i->next) { + if(i->data) freepkg((pkginfo_t*)i->data); + i->data = NULL; + } + for(i = trail; i; i = i->next) { + /* this list used the same pointers as final, so they're already freed */ + i->data = NULL; + } + list_free(pkgcache); + list_free(final); + list_free(trail); + db_close(db_sync); + return(!allgood); +} + +int pacman_add(pacdb_t *db, PMList *targets) +{ + int i, ret = 0, errors = 0; + TAR *tar = NULL; + char expath[PATH_MAX]; + char pm_install[PATH_MAX]; + pkginfo_t *info = NULL; + struct stat buf; + PMList *targ, *file, *lp, *j; + PMList *alltargs = NULL; + PMList *filenames = NULL; + unsigned short real_pmo_upgrade; + + if(targets == NULL) { + return(0); + } + + vprint("Loading all target data...\n"); + for(targ = targets; targ; targ = targ->next) { + /* Populate the package struct */ + vprint(" %s\n", (char*)targ->data); + info = load_pkg((char*)targ->data, 0); + if(info == NULL) { + return(1); + } + alltargs = list_add(alltargs, info); + filenames = list_add(filenames, strdup(targ->data)); + } + + if(!pmo_nodeps) { + vprint("Checking dependencies...\n"); + lp = checkdeps(db, (pmo_upgrade ? PM_UPGRADE : PM_ADD), alltargs); + if(lp) { + fprintf(stderr, "error: unsatisfied dependencies:\n"); + for(j = lp; j; j = j->next) { + depmissing_t* miss = (depmissing_t*)j->data; + printf(" %s: ", miss->target); + if(miss->type == DEPEND || miss->type == REQUIRED) { + printf("requires %s", miss->depend.name); + switch(miss->depend.mod) { + case DEP_EQ: printf("=%s", miss->depend.version); break; + case DEP_GE: printf(">=%s", miss->depend.version); break; + case DEP_LE: printf("<=%s", miss->depend.version); break; + } + printf("\n"); + } else if(miss->type == CONFLICT) { + printf("conflicts with %s\n", miss->depend.name); + } + } + list_free(lp); + return(1); + } + list_free(lp); + } + + if(!pmo_force) { + printf("checking for conflicts... "); + fflush(stdout); + lp = db_find_conflicts(db, alltargs); + if(lp) { + printf("\nerror: the following file conflicts were found:\n"); + for(j = lp; j; j = j->next) { + printf(" %s\n", (char*)j->data); + } + printf("\n"); + list_free(lp); + return(1); + } + printf("done.\n"); + list_free(lp); + } + + /* this can get modified in the next for loop, so we reset it on each iteration */ + real_pmo_upgrade = pmo_upgrade; + + for(targ = alltargs, file = filenames; targ && file; targ = targ->next, file = file->next) { + pkginfo_t* oldpkg = NULL; + info = (pkginfo_t*)targ->data; + errors = 0; + + pmo_upgrade = real_pmo_upgrade; + /* check for an already installed package */ + if(!pmo_upgrade && is_pkgin(info, pm_packages)) { + fprintf(stderr, "error: %s is already installed. (try --upgrade)\n", info->name); + continue; + } + + /* see if this is an upgrade. if so, remove the old package first */ + if(pmo_upgrade) { + if(is_pkgin(info, pm_packages)) { + PMList* tmp = list_new(); + int retcode; + + printf("upgrading %s... ", info->name); + /* we'll need the full record for backup checks later */ + oldpkg = db_scan(db, info->name, INFRQ_ALL); + + list_add(tmp, strdup(info->name)); + vprint("removing old package first...\n"); + retcode = pacman_remove(db, tmp); + list_free(tmp); + /* reload package cache */ + pm_packages = db_loadpkgs(db, pm_packages); + if(retcode == 1) { + fprintf(stderr, "\nupgrade aborted.\n"); + return(1); + } + } else { + /* no previous package version is installed, so this is actually just an + * install + */ + pmo_upgrade = 0; + } + } + if(!pmo_upgrade) { + printf("installing %s... ", info->name); + } + fflush(stdout); + + /* open the .tar.gz package */ + if(tar_open(&tar, (char*)file->data, &gztype, O_RDONLY, 0, TAR_GNU) == -1) { + perror("could not open package"); + return(1); + } + vprint("extracting files...\n"); + for(i = 0; !th_read(tar); i++) { + int nb = 0; + char *md5_orig = NULL; + char pathname[PATH_MAX]; + strncpy(pathname, th_get_pathname(tar), PATH_MAX); + + if(!strcmp(pathname, ".PKGINFO")) { + tar_skip_regfile(tar); + continue; + } + + if(!strcmp(pathname, "._install")) { + /* the install script goes inside the db */ + snprintf(expath, PATH_MAX, "%s%s/%s/%s-%s/install", pmo_root, + PKGDIR, db->treename, info->name, info->version); + } else { + /* build the new pathname relative to pmo_root */ + snprintf(expath, PATH_MAX, "%s%s", pmo_root, pathname); + } + + if(!stat(expath, &buf) && !S_ISDIR(buf.st_mode)) { + /* file already exists */ + if(!pmo_upgrade) { + nb = is_in(pathname, info->backup); + } else { + /* op == PM_UPGRADE */ + md5_orig = needbackup(pathname, oldpkg->backup); + if(md5_orig) { + nb = 1; + } + } + } + + if(nb) { + char *temp; + char *md5_local, *md5_pkg; + + md5_local = MDFile(expath); + /* extract the package's version to a temporary file and md5 it */ + temp = strdup("/tmp/pacman_XXXXXX"); + mkstemp(temp); + if(tar_extract_file(tar, temp)) { + fprintf(stderr, "could not extract %s: %s\n", pathname, strerror(errno)); + errors++; + continue; + } + md5_pkg = MDFile(temp); + /* append the new md5 hash to it's respective entry in info->backup + * (it will be the new orginal) + */ + for(lp = info->backup; lp; lp = lp->next) { + char *fn; + + if(!lp->data) continue; + if(!strcmp((char*)lp->data, pathname)) { + /* 32 for the hash, 1 for the terminating NULL, and 1 for the tab delimiter */ + MALLOC(fn, strlen(lp->data)+34); + sprintf(fn, "%s\t%s", (char*)lp->data, md5_pkg); + free(lp->data); + lp->data = fn; + } + } + + vprint("checking md5 hashes for %s\n", expath); + vprint(" current: %s\n", md5_local); + vprint(" new: %s\n", md5_pkg); + if(md5_orig) { + vprint(" original: %s\n", md5_orig); + } + + if(!pmo_upgrade) { + /* PM_ADD */ + + /* if a file already exists with a different md5 hash, + * then we rename it to a .pacorig extension and continue */ + if(strcmp(md5_local, md5_pkg)) { + char newpath[PATH_MAX]; + snprintf(newpath, PATH_MAX, "%s.pacorig", expath); + if(rename(expath, newpath)) { + fprintf(stderr, "error: could not rename %s: %s\n", expath, strerror(errno)); + } + if(copyfile(temp, expath)) { + fprintf(stderr, "error: could not copy %s to %s: %s\n", temp, expath, strerror(errno)); + errors++; + } else { + fprintf(stderr, "warning: %s saved as %s\n", expath, newpath); + } + } + } else if(md5_orig) { + /* PM_UPGRADE */ + int installnew = 0; + + /* the fun part */ + if(!strcmp(md5_orig, md5_local)) { + if(!strcmp(md5_local, md5_pkg)) { + vprint(" action: installing new file\n"); + installnew = 1; + } else { + vprint(" action: installing new file\n"); + installnew = 1; + } + } else if(!strcmp(md5_orig, md5_pkg)) { + vprint(" action: leaving existing file in place\n"); + } else if(!strcmp(md5_local, md5_pkg)) { + vprint(" action: installing new file\n"); + installnew = 1; + } else { + char newpath[PATH_MAX]; + vprint(" action: saving current file and installing new one\n"); + installnew = 1; + snprintf(newpath, PATH_MAX, "%s.pacsave", expath); + if(rename(expath, newpath)) { + fprintf(stderr, "error: could not rename %s: %s\n", expath, strerror(errno)); + } else { + fprintf(stderr, "warning: %s saved as %s\n", expath, newpath); + } + } + + if(installnew) { + /*vprint(" %s\n", expath);*/ + if(copyfile(temp, expath)) { + fprintf(stderr, "error: could not copy %s to %s: %s\n", temp, expath, strerror(errno)); + errors++; + } + } + } + + FREE(md5_local); + FREE(md5_pkg); + FREE(md5_orig); + unlink(temp); + FREE(temp); + } else { + /*vprint(" %s\n", expath);*/ + if(tar_extract_file(tar, expath)) { + fprintf(stderr, "could not extract %s: %s\n", pathname, strerror(errno)); + errors++; + } + /* calculate an md5 hash if this is in info->backup */ + for(lp = info->backup; lp; lp = lp->next) { + char *fn, *md5; + char path[PATH_MAX]; + + if(!lp->data) continue; + if(!strcmp((char*)lp->data, pathname)) { + snprintf(path, PATH_MAX, "%s%s", pmo_root, (char*)lp->data); + md5 = MDFile(path); + /* 32 for the hash, 1 for the terminating NULL, and 1 for the tab delimiter */ + MALLOC(fn, strlen(lp->data)+34); + sprintf(fn, "%s\t%s", (char*)lp->data, md5); + free(lp->data); + lp->data = fn; + } + } + } + } + tar_close(tar); + if(errors) { + ret = 1; + fprintf(stderr, "error installing %s - skipping db update for this package\n", info->name); + } else { + time_t t = time(NULL); + + /* if this is an upgrade then propagate the old package's requiredby list over to + * the new package */ + if(pmo_upgrade && oldpkg) { + list_free(info->requiredby); + info->requiredby = NULL; + for(lp = oldpkg->requiredby; lp; lp = lp->next) { + char *s = strdup(lp->data); + info->requiredby = list_add(info->requiredby, s); + } + } + + vprint("Updating database..."); + /* make an install date (in UTC) */ + strncpy(info->installdate, asctime(gmtime(&t)), sizeof(info->installdate)); + if(db_write(db, info)) { + fprintf(stderr, "error updating database for %s!\n", info->name); + return(1); + } + vprint("done.\n"); + + /* update dependency packages' REQUIREDBY fields */ + for(lp = info->depends; lp; lp = lp->next) { + pkginfo_t *depinfo = NULL; + depend_t depend; + + if(splitdep(lp->data, &depend)) { + continue; + } + depinfo = db_scan(db, depend.name, INFRQ_ALL); + if(depinfo == NULL) { + continue; + } + depinfo->requiredby = list_add(depinfo->requiredby, strdup(info->name)); + db_write(db, depinfo); + freepkg(depinfo); + } + printf("done.\n"); fflush(stdout); + + /* run the post-install script if it exists */ + snprintf(pm_install, PATH_MAX, "%s%s/%s/%s-%s/install", pmo_root, PKGDIR, db->treename, info->name, info->version); + if(!stat(pm_install, &buf)) { + char cmdline[PATH_MAX+1]; + snprintf(pm_install, PATH_MAX, "%s/%s/%s-%s/install", PKGDIR, db->treename, info->name, info->version); + vprint("Executing post-install script...\n"); + snprintf(cmdline, PATH_MAX, "chroot %s /bin/sh %s post_%s %s %s", pmo_root, pm_install, + (pmo_upgrade ? "upgrade" : "install"), info->version, (pmo_upgrade ? oldpkg->version : "")); + system(cmdline); + } + } + + freepkg(oldpkg); + } + + /* clean up */ + for(lp = alltargs; lp; lp = lp->next) { + freepkg((pkginfo_t*)lp->data); + lp->data = NULL; + } + list_free(alltargs); + list_free(filenames); + + /* run ldconfig if it exists */ + snprintf(expath, PATH_MAX, "%setc/ld.so.conf", pmo_root); + if(!stat(expath, &buf)) { + snprintf(expath, PATH_MAX, "%ssbin/ldconfig", pmo_root); + if(!stat(expath, &buf)) { + char cmd[PATH_MAX]; + snprintf(cmd, PATH_MAX, "%s -r %s", expath, pmo_root); + vprint("running \"%s\"\n", cmd); + system(cmd); + } + } + + return(ret); +} + +int pacman_remove(pacdb_t *db, PMList *targets) +{ + char line[PATH_MAX+1]; + char pm_install[PATH_MAX+1]; + pkginfo_t *info = NULL; + pkginfo_t *depinfo = NULL; + struct stat buf; + char *newpath = NULL; + depend_t depend; + PMList *alltargs = NULL; + PMList *targ, *lp, *j; + + if(targets == NULL) { + return(0); + } + + /* load package info from all targets */ + for(lp = targets; lp; lp = lp->next) { + info = db_scan(db, (char*)lp->data, INFRQ_ALL); + if(info == NULL) { + fprintf(stderr, "error: could not find %s in database\n", (char*)lp->data); + return(1); + } + alltargs = list_add(alltargs, info); + } + if(!pmo_nodeps && !pmo_upgrade) { + vprint("Checking dependencies...\n"); + lp = checkdeps(db, PM_REMOVE, alltargs); + if(lp) { + fprintf(stderr, "error: this will break the following dependencies:\n"); + for(j = lp; j; j = j->next) { + depmissing_t* miss = (depmissing_t*)j->data; + printf(" %s: is required by %s\n", miss->target, miss->depend.name); + } + list_free(lp); + return(1); + } + list_free(lp); + } + + for(targ = alltargs; targ; targ = targ->next) { + info = (pkginfo_t*)targ->data; + + if(!pmo_upgrade) { + printf("removing %s... ", info->name); + fflush(stdout); + /* run the pre-remove script if it exists */ + snprintf(pm_install, PATH_MAX, "%s%s/%s/%s-%s/install", pmo_root, PKGDIR, db->treename, info->name, info->version); + if(!stat(pm_install, &buf)) { + vprint("Executing pre-remove script...\n"); + snprintf(pm_install, PATH_MAX, "%s%s/%s/%s-%s/install", pmo_root, PKGDIR, db->treename, info->name, info->version); + snprintf(line, PATH_MAX, "chroot %s /bin/sh %s pre_remove %s", pmo_root, pm_install, info->version); + + system(line); + } + } + + /* iterate through the list backwards, unlinking files */ + for(lp = list_last(info->files); lp; lp = lp->prev) { + int nb = 0; + if(needbackup((char*)lp->data, info->backup)) { + nb = 1; + } + snprintf(line, PATH_MAX, "%s%s", pmo_root, (char*)lp->data); + if(lstat(line, &buf)) { + vprint("file %s does not exist\n", line); + continue; + } + if(S_ISDIR(buf.st_mode)) { + /*vprint(" removing directory %s\n", line);*/ + if(rmdir(line)) { + /* this is okay, other packages are probably using it. */ + } + } else { + /* if the file is flagged, back it up to .pacsave */ + if(nb) { + if(pmo_upgrade) { + /* we're upgrading so just leave the file as is. pacman_add() will handle it */ + } else { + if(!pmo_nosave) { + newpath = (char*)realloc(newpath, strlen(line)+strlen(".pacsave")+1); + sprintf(newpath, "%s.pacsave", line); + rename(line, newpath); + fprintf(stderr, "warning: %s saved as %s\n", line, newpath); + } else { + /*vprint(" unlinking %s\n", line);*/ + if(unlink(line)) { + perror("cannot remove file"); + } + } + } + } else { + /*vprint(" unlinking %s\n", line);*/ + if(unlink(line)) { + perror("cannot remove file"); + } + } + } + } + + /* remove the package from the database */ + snprintf(line, PATH_MAX, "%s%s/%s/%s-%s", pmo_root, PKGDIR, db->treename, + info->name, info->version); + + /* DESC */ + snprintf(pm_install, PATH_MAX, "%s/desc", line); + unlink(pm_install); + /* FILES */ + snprintf(pm_install, PATH_MAX, "%s/files", line); + unlink(pm_install); + /* DEPENDS */ + snprintf(pm_install, PATH_MAX, "%s/depends", line); + unlink(pm_install); + /* INSTALL */ + snprintf(pm_install, PATH_MAX, "%s/install", line); + unlink(pm_install); + /* directory */ + rmdir(line); + + /* update dependency packages' REQUIREDBY fields */ + for(lp = info->depends; lp; lp = lp->next) { + PMList *last, *j; + + if(splitdep((char*)lp->data, &depend)) { + continue; + } + depinfo = db_scan(db, depend.name, INFRQ_ALL); + if(depinfo == NULL) { + continue; + } + /* splice out this entry from requiredby */ + last = list_last(depinfo->requiredby); + for(j = depinfo->requiredby; j; j = j->next) { + if(!strcmp((char*)j->data, info->name)) { + if(j == depinfo->requiredby) { + depinfo->requiredby = j->next; + } + if(j->prev) j->prev->next = j->next; + if(j->next) j->next->prev = j->prev; + /* free the spliced node */ + j->prev = j->next = NULL; + list_free(j); + break; + } + } + db_write(db, depinfo); + freepkg(depinfo); + } + if(!pmo_upgrade) { + printf("done.\n"); + } + } + + return(0); +} + +int pacman_query(pacdb_t *db, PMList *targets) +{ + char *package = NULL; + char path[PATH_MAX+1]; + pkginfo_t *info = NULL; + PMList *targ, *lp, *q; + int done = 0; + + for(targ = targets; !done; targ = (targ ? targ->next : NULL)) { + if(targets == NULL) { + done = 1; + } else { + if(targ->next == NULL) { + done = 1; + } + package = (char*)targ->data; + } + /* output info for a .tar.gz package */ + if(pmo_q_isfile) { + if(package == NULL) { + fprintf(stderr, "error: no package file was specified (-p)\n"); + return(1); + } + info = load_pkg(package, pmo_q_info); + if(info == NULL) { + fprintf(stderr, "error: %s is not a package\n", package); + return(1); + } + if(pmo_q_list) { + for(lp = info->files; lp; lp = lp->next) { + if(strcmp(lp->data, ".PKGINFO")) { + printf("%s\n", (char*)lp->data); + } + } + } else { + if(!pmo_q_info) { + printf("%s %s\n", info->name, info->version); + } + } + continue; + } + + /* determine the owner of a file */ + if(pmo_q_owns) { + char rpath[PATH_MAX]; + if(package == NULL) { + fprintf(stderr, "error: no file was specified for --owns\n"); + return(1); + } + if(realpath(package, rpath)) { + int gotcha = 0; + rewinddir(db->dir); + while((info = db_scan(db, NULL, INFRQ_DESC | INFRQ_FILES)) != NULL && !gotcha) { + for(lp = info->files; lp; lp = lp->next) { + sprintf(path, "%s%s", pmo_root, (char*)lp->data); + if(!strcmp(path, rpath)) { + printf("%s %s\n", info->name, info->version); + gotcha = 1; + } + } + } + if(!gotcha) { + fprintf(stderr, "No package owns %s\n", package); + } + return(2); + } else { + fprintf(stderr, "error: %s is not a file.\n", package); + return(1); + } + } + + /* find packages in the db */ + if(package == NULL) { + /* no target */ + for(lp = pm_packages; lp; lp = lp->next) { + pkginfo_t *tmpp = (pkginfo_t*)lp->data; + if(pmo_q_list) { + info = db_scan(db, tmpp->name, INFRQ_DESC | INFRQ_FILES); + if(info == NULL) { + /* something weird happened */ + return(1); + } + for(q = info->files; q; q = q->next) { + printf("%s%s\n", pmo_root, (char*)q->data); + } + } else { + printf("%s %s\n", tmpp->name, tmpp->version); + } + } + } else { + /* find a target */ + if(pmo_q_list) { + info = db_scan(db, package, INFRQ_DESC | INFRQ_FILES); + if(info == NULL) { + fprintf(stderr, "Package \"%s\" was not found.\n", package); + return(2); + } + for(lp = info->files; lp; lp = lp->next) { + printf("%s%s\n", pmo_root, (char*)lp->data); + } + } else if(pmo_q_info) { + int cols; + + info = db_scan(db, package, INFRQ_DESC | INFRQ_DEPENDS); + if(info == NULL) { + fprintf(stderr, "Package \"%s\" was not found.\n", package); + return(2); + } + + printf("Name : %s\n", info->name); + printf("Version : %s\n", info->version); + printf("Packager : %s\n", info->packager); + printf("Size : %ld\n", info->size); + printf("Build Date : %s %s\n", info->builddate, strlen(info->builddate) ? "UTC" : ""); + printf("Install Date : %s %s\n", info->installdate, strlen(info->installdate) ? "UTC" : ""); + printf("Install Script: %s\n", (info->scriptlet ? "yes" : "no")); + printf("Depends On : "); + if(info->depends) { + for(lp = info->depends, cols = 16; lp; lp = lp->next) { + int s = strlen((char*)lp->data)+1; + if(s+cols > 79) { + cols = 16; + printf("\n%16s%s ", " ", (char*)lp->data); + } else { + printf("%s ", (char*)lp->data); + } + cols += s; + } + printf("\n"); + } else { + printf("None\n"); + } + printf("Required By : "); + if(info->requiredby) { + for(lp = info->requiredby, cols = 16; lp; lp = lp->next) { + int s = strlen((char*)lp->data)+1; + if(s+cols > 79) { + cols = 16; + printf("\n%16s%s ", " ", (char*)lp->data); + } else { + printf("%s ", (char*)lp->data); + } + cols += s; + } + printf("\n"); + } else { + printf("None\n"); + } + printf("Conflicts With: "); + if(info->conflicts) { + for(lp = info->conflicts, cols = 16; lp; lp = lp->next) { + int s = strlen((char*)lp->data)+1; + if(s+cols > 79) { + cols = 16; + printf("\n%16s%s ", " ", (char*)lp->data); + } else { + printf("%s ", (char*)lp->data); + } + cols += s; + } + printf("\n"); + } else { + printf("None\n"); + } + printf("Description : %s\n", info->desc); + printf("\n"); + } else { + info = db_scan(db, package, INFRQ_DESC); + if(info == NULL) { + fprintf(stderr, "Package \"%s\" was not found.\n", package); + return(2); + } + printf("%s %s\n", info->name, info->version); + } + } + } + + if(info) { + freepkg(info); + } + + return(0); +} + +int pacman_upgrade(pacdb_t *db, PMList *targets) +{ + /* this is basically just a remove,add process. pacman_add() will */ + /* handle it */ + pmo_upgrade = 1; + return(pacman_add(db, targets)); +} + +/* populates *list with packages that need to be installed to satisfy all + * dependencies (recursive) for *package + * + * make sure *list and *trail are already initialized + */ +int resolvedeps(pacdb_t *local, pacdb_t *sync, pkginfo_t *package, PMList *list, PMList *trail) +{ + pkginfo_t *info; + PMList *i; + PMList *targ = NULL; + PMList *deps = NULL; + + targ = list_new(); + targ = list_add(targ, package); + deps = checkdeps(local, PM_ADD, targ); + targ->data = NULL; + list_free(targ); + for(i = deps; i; i = i->next) { + depmissing_t *miss = (depmissing_t*)i->data; + info = db_scan(sync, miss->depend.name, INFRQ_DESC | INFRQ_DEPENDS); + if(info == NULL) { + fprintf(stderr, "error: cannot resolve dependencies for \"%s\":\n", miss->target); + fprintf(stderr, " \"%s\" is not in the package set\n", miss->depend.name); + return(1); + } + if(is_pkgin(info, list) == 1) { + /* this dep is already in the target list */ + continue; + } + if(miss->type == CONFLICT) { + fprintf(stderr, "error: %s conflicts with %s\n", miss->target, miss->depend.name); + return(1); + } else if(miss->type == DEPEND) { + /*printf("resolving %s\n", info->name); fflush(stdout);*/ + if(!is_pkgin(info, trail)) { + list_add(trail, info); + if(resolvedeps(local, sync, info, list, trail)) { + return(1); + } + list_add(list, info); + } else { + /* cycle detected -- skip it */ + /*printf("cycle detected\n"); fflush(stdout);*/ + } + } + } + return(0); +} + +/* + * returns a PMList* of missing_t pointers. + * + * conflicts are always name only, but dependencies can include versions + * with depmod operators. + * + */ +PMList* checkdeps(pacdb_t *db, unsigned short op, PMList *targets) +{ + pkginfo_t *info = NULL; + depend_t depend; + PMList *i, *j, *k; + int cmp; + int found = 0; + PMList *baddeps = NULL; + depmissing_t *miss = NULL; + + if(op == PM_UPGRADE) { + /* PM_UPGRADE handles the backwards dependencies, ie, the packages + * listed in the requiredby field. + */ + for(i = targets; i; i = i->next) { + pkginfo_t *tp, *oldpkg; + if(i->data == NULL) { + continue; + } + tp = (pkginfo_t*)i->data; + + if((oldpkg = db_scan(db, tp->name, INFRQ_DESC | INFRQ_DEPENDS)) == NULL) { + continue; + } + for(j = oldpkg->requiredby; j; j = j->next) { + char *ver; + pkginfo_t *p; + found = 0; + if((p = db_scan(db, j->data, INFRQ_DESC | INFRQ_DEPENDS)) == NULL) { + continue; + } + for(k = p->depends; k; k = k->next) { + if(splitdep(k->data, &depend)) { + continue; + } + if(!strcmp(depend.name, oldpkg->name)) { + found = 1; + } + } + if(found == 0) { + continue; + } + found = 0; + if(depend.mod == DEP_ANY) { + found = 1; + } else { + /* note that we use the version from the NEW package in the check */ + ver = strdup(tp->version); + if(!index(depend.version,'-')) { + char *ptr; + for(ptr = ver; *ptr != '-'; ptr++); + *ptr = '\0'; + } + cmp = rpmvercmp(ver, depend.version); + switch(depend.mod) { + case DEP_EQ: found = (cmp == 0); break; + case DEP_GE: found = (cmp >= 0); break; + case DEP_LE: found = (cmp <= 0); break; + } + FREE(ver); + } + if(!found) { + MALLOC(miss, sizeof(depmissing_t)); + miss->type = REQUIRED; + miss->depend.mod = depend.mod; + strncpy(miss->target, p->name, 256); + strncpy(miss->depend.name, depend.name, 256); + strncpy(miss->depend.version, depend.version, 64); + baddeps = list_add(baddeps, miss); + } + } + freepkg(oldpkg); + } + } + if(op == PM_ADD || op == PM_UPGRADE) { + for(i = targets; i; i = i->next) { + pkginfo_t *tp; + if(i->data == NULL) { + continue; + } + tp = (pkginfo_t*)i->data; + + /* CONFLICTS */ + for(j = tp->conflicts; j; j = j->next) { + /* check targets against database */ + for(k = pm_packages; k; k = k->next) { + pkginfo_t *dp = (pkginfo_t*)k->data; + if(!strcmp(j->data, dp->name)) { + MALLOC(miss, sizeof(depmissing_t)); + miss->type = CONFLICT; + miss->depend.mod = DEP_ANY; + miss->depend.version[0] = '\0'; + strncpy(miss->target, tp->name, 256); + strncpy(miss->depend.name, dp->name, 256); + baddeps = list_add(baddeps, miss); + } + } + /* check targets against targets */ + for(k = targets; k; k = k->next) { + pkginfo_t *a = (pkginfo_t*)k->data; + if(!strcmp(a->name, (char*)j->data)) { + MALLOC(miss, sizeof(depmissing_t)); + miss->type = CONFLICT; + miss->depend.mod = DEP_ANY; + miss->depend.version[0] = '\0'; + strncpy(miss->target, tp->name, 256); + strncpy(miss->depend.name, a->name, 256); + baddeps = list_add(baddeps, miss); + } + } + } + /* check database against targets */ + rewinddir(db->dir); + while((info = db_scan(db, NULL, INFRQ_DESC | INFRQ_DEPENDS)) != NULL) { + for(j = info->conflicts; j; j = j->next) { + if(!strcmp((char*)j->data, tp->name)) { + MALLOC(miss, sizeof(depmissing_t)); + miss->type = CONFLICT; + miss->depend.mod = DEP_ANY; + miss->depend.version[0] = '\0'; + strncpy(miss->target, tp->name, 256); + strncpy(miss->depend.name, (char*)j->data, 256); + baddeps = list_add(baddeps, miss); + } + } + } + + /* DEPENDENCIES */ + /* cycle thru this targets dependency list */ + for(j = tp->depends; j; j = j->next) { + /* split into name/version pairs */ + if(splitdep((char*)j->data, &depend)) { + fprintf(stderr, "warning: invalid dependency in %s\n", (char*)tp->name); + continue; + } + found = 0; + /* check database */ + for(k = pm_packages; k && !found; k = k->next) { + pkginfo_t *p = (pkginfo_t*)k->data; + if(!strcmp(p->name, depend.name)) { + if(depend.mod == DEP_ANY) { + /* accept any version */ + found = 1; + } else { + char *ver = strdup(p->version); + /* check for a release in depend.version. if it's + * missing remove it from p->version as well. + */ + if(!index(depend.version,'-')) { + char *ptr; + for(ptr = ver; *ptr != '-'; ptr++); + *ptr = '\0'; + } + cmp = rpmvercmp(ver, depend.version); + switch(depend.mod) { + case DEP_EQ: found = (cmp == 0); break; + case DEP_GE: found = (cmp >= 0); break; + case DEP_LE: found = (cmp <= 0); break; + } + FREE(ver); + } + } + } + /* check other targets */ + for(k = targets; k && !found; k = k->next) { + pkginfo_t *p = (pkginfo_t*)k->data; + if(!strcmp(p->name, depend.name)) { + if(depend.mod == DEP_ANY) { + /* accept any version */ + found = 1; + } else { + char *ver = strdup(p->version); + /* check for a release in depend.version. if it's + * missing remove it from p->version as well. + */ + if(!index(depend.version,'-')) { + char *ptr; + for(ptr = ver; *ptr != '-'; ptr++); + *ptr = '\0'; + } + cmp = rpmvercmp(ver, depend.version); + switch(depend.mod) { + case DEP_EQ: found = (cmp == 0); break; + case DEP_GE: found = (cmp >= 0); break; + case DEP_LE: found = (cmp <= 0); break; + } + FREE(ver); + } + } + /* TODO: switch positions in targets if one package should precede + * the other (wrt deps) + */ + } + if(!found) { + MALLOC(miss, sizeof(depmissing_t)); + miss->type = DEPEND; + miss->depend.mod = depend.mod; + strncpy(miss->target, tp->name, 256); + strncpy(miss->depend.name, depend.name, 256); + strncpy(miss->depend.version, depend.version, 64); + baddeps = list_add(baddeps, miss); + } + } + } + } else if(op == PM_REMOVE) { + /* check requiredby fields */ + for(i = targets; i; i = i->next) { + pkginfo_t* tp; + if(i->data == NULL) { + continue; + } + tp = (pkginfo_t*)i->data; + for(j = tp->requiredby; j; j = j->next) { + if(!is_in((char*)j->data, targets)) { + MALLOC(miss, sizeof(depmissing_t)); + miss->type = REQUIRED; + miss->depend.mod = DEP_ANY; + miss->depend.version[0] = '\0'; + strncpy(miss->target, tp->name, 256); + strncpy(miss->depend.name, (char*)j->data, 256); + baddeps = list_add(baddeps, miss); + } + } + } + } + + return(baddeps); +} + +int splitdep(char *depstr, depend_t *depend) +{ + char *str = NULL; + char *ptr = NULL; + + str = strdup(depstr); + + if((ptr = strstr(str, ">="))) { + depend->mod = DEP_GE; + } else if((ptr = strstr(str, "<="))) { + depend->mod = DEP_LE; + } else if((ptr = strstr(str, "="))) { + depend->mod = DEP_EQ; + } else { + /* no version specified - accept any */ + depend->mod = DEP_ANY; + strcpy(depend->name, str); + strcpy(depend->version, ""); + } + + if(ptr == NULL) { + return(0); + } + *ptr = '\0'; + strcpy(depend->name, str); + ptr++; + if(depend->mod != DEP_EQ) { + ptr++; + } + strcpy(depend->version, ptr); + FREE(str); + return(0); +} + +int lckmk(char *file, int retries, unsigned int sleep_secs) +{ + int fd, count = 0; + + while((fd = open(file, O_WRONLY | O_CREAT | O_EXCL, 0000)) == -1 && errno == EACCES) { + if(++count < retries) { + sleep(sleep_secs); + } else { + return(-1); + } + } + return(fd > 0 ? 0 : -1); +} + +int lckrm(char *file) +{ + return(unlink(file)); +} + +void cleanup(int signum) +{ + if(lckrm(lckfile)) { + fprintf(stderr, "warning: could not remove lock file %s\n", lckfile); + } + exit(signum); +} + +/* vim: set ts=2 sw=2 noet: */ diff --git a/src/pacman.h b/src/pacman.h new file mode 100644 index 00000000..330969ce --- /dev/null +++ b/src/pacman.h @@ -0,0 +1,62 @@ +/* + * pacman + * + * Copyright (c) 2002 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ +#ifndef _PAC_PACMAN_H +#define _PAC_PACMAN_H + +#ifndef PACVER +#define PACVER "2.0" +#endif + +#ifndef PKGDIR +#define PKGDIR "var/lib/pacman" +#endif + +#ifndef PACCONF +#define PACCONF "etc/pacman.conf" +#endif + +/* Operations */ +#define PM_MAIN 1 +#define PM_ADD 2 +#define PM_REMOVE 3 +#define PM_QUERY 4 +#define PM_UPGRADE 5 +#define PM_SYNC 6 +#define PM_DEPTEST 7 + +int pacman_add(pacdb_t *db, PMList *targets); +int pacman_remove(pacdb_t *db, PMList *targets); +int pacman_upgrade(pacdb_t *db, PMList *targets); +int pacman_query(pacdb_t *db, PMList *targets); +int pacman_sync(pacdb_t *db, PMList *targets); +int pacman_deptest(pacdb_t *db, PMList *targets); + +PMList* checkdeps(pacdb_t *db, unsigned short op, PMList *targets); +int resolvedeps(pacdb_t *local, pacdb_t *sync, pkginfo_t *package, PMList *list, PMList *trail); +int splitdep(char *depstr, depend_t *depend); + +int lckmk(char *file, int retries, unsigned int sleep_secs); +int lckrm(char *lckfile); +void cleanup(int signum); + +#endif /* PACMAN_H */ + +/* vim: set ts=2 sw=2 noet: */ diff --git a/src/pacsync.c b/src/pacsync.c new file mode 100644 index 00000000..224528f8 --- /dev/null +++ b/src/pacsync.c @@ -0,0 +1,183 @@ +/* + * pacman + * + * Copyright (c) 2002 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +#include "config.h" +#include <stdio.h> +#include <stdlib.h> +#include <limits.h> +#include <errno.h> +#include <string.h> +#include <sys/types.h> +#include <unistd.h> +#include <ftplib.h> +/* pacman */ +#include "list.h" +#include "package.h" +#include "db.h" +#include "util.h" +#include "pacsync.h" +#include "pacman.h" + +static int log_progress(netbuf *ctl, int xfered, void *arg); +static char sync_fnm[25]; + +/* pacman options */ +extern char *pmo_root; +/* configuration options */ +extern char pmc_syncserver[512]; +extern char pmc_syncpath[512]; +extern char pmc_syncname[512]; + +int sync_synctree() +{ + char ldir[PATH_MAX] = ""; + char path[PATH_MAX]; + mode_t oldmask; + char *str; + PMList *files = NULL; + + snprintf(ldir, PATH_MAX, "%s%s/%s", pmo_root, PKGDIR, pmc_syncname); + + /* remove the old dir */ + vprint("Removing %s (if it exists)\n", ldir); + rmrf(ldir); + + /* make the new dir */ + oldmask = umask(0000); + mkdir(ldir, 0755); + umask(oldmask); + + /* build out list of one */ + snprintf(path, PATH_MAX, "%s.db.tar.gz", pmc_syncname); + str = strdup(path); + files = list_add(files, str); + if(downloadfiles(pmc_syncserver, pmc_syncpath, ldir, files)) { + list_free(files); + return(1); + } + + /* uncompress the sync database */ + snprintf(path, PATH_MAX, "%s/%s", ldir, (char*)files->data); + list_free(files); + vprint("Unpacking %s...\n", path); + if(unpack(path, ldir)) { + return(1); + } + + /* remove the .tar.gz */ + unlink(path); + + return(0); +} + +int downloadfiles(char *server, char *remotepath, char *localpath, PMList *files) +{ + int fsz; + netbuf *control = NULL; + PMList *lp; + int ret = 0; + + if(files == NULL) { + return(0); + } + + FtpInit(); + if(!FtpConnect(server, &control)) { + fprintf(stderr, "error: cannot connect to %s\n", server); + return(1); + } + if(!FtpLogin("anonymous", "arch@guest", control)) { + fprintf(stderr, "error: anonymous login failed\n"); + FtpQuit(control); + return(1); + } + + + if(!FtpChdir(remotepath, control)) { + fprintf(stderr, "error: could not cwd to %s: %s\n", remotepath, + FtpLastResponse(control)); + return(1); + } + + /* get each file in the list */ + for(lp = files; lp; lp = lp->next) { + char output[PATH_MAX]; + int j; + + snprintf(output, PATH_MAX, "%s/%s", localpath, (char*)lp->data); + + /* passive mode */ + /* TODO: make passive ftp an option */ + if(!FtpOptions(FTPLIB_CONNMODE, FTPLIB_PASSIVE, control)) { + fprintf(stderr, "warning: failed to set passive mode\n"); + } + + if(!FtpSize((char*)lp->data, &fsz, FTPLIB_IMAGE, control)) { + fprintf(stderr, "warning: failed to get filesize for %s\n", (char*)lp->data); + } + + /* set up our progress bar's callback */ + FtpOptions(FTPLIB_CALLBACK, (long)log_progress, control); + FtpOptions(FTPLIB_IDLETIME, (long)1000, control); + FtpOptions(FTPLIB_CALLBACKARG, (long)&fsz, control); + FtpOptions(FTPLIB_CALLBACKBYTES, (10*1024), control); + + strncpy(sync_fnm, lp->data, 24); + for(j = strlen(sync_fnm); j < 24; j++) { + sync_fnm[j] = ' '; + } + sync_fnm[24] = '\0'; + if(!FtpGet(output, lp->data, FTPLIB_IMAGE, control)) { + fprintf(stderr, "\nerror: could not download %s: %s\n", (char*)lp->data, + FtpLastResponse(control)); + /* unlink the file */ + unlink(output); + ret = 1; + } else { + log_progress(control, fsz, &fsz); + } + printf("\n"); + fflush(stdout); + } + + FtpQuit(control); + return(ret); +} + +static int log_progress(netbuf *ctl, int xfered, void *arg) +{ + int fsz = *(int*)arg; + int pct = (unsigned int)(xfered * 100) / fsz; + int i; + + printf("%s [", sync_fnm); + for(i = 0; i < (int)(pct/3); i++) { + printf("#"); + } + for(i = (int)(pct/3); i < (int)(100/3); i++) { + printf(" "); + } + printf("] %3d%% | %6dK\r", pct, (xfered/1024)); + fflush(stdout); + return(1); +} + +/* vim: set ts=2 sw=2 noet: */ diff --git a/src/pacsync.h b/src/pacsync.h new file mode 100644 index 00000000..d7c666f3 --- /dev/null +++ b/src/pacsync.h @@ -0,0 +1,29 @@ +/* + * pacman + * + * Copyright (c) 2002 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ +#ifndef _PAC_PACSYNC_H +#define _PAC_PACSYNC_H + +int sync_synctree(); +int downloadfiles(char *server, char *remotepath, char *localpath, PMList *files); + +#endif + +/* vim: set ts=2 sw=2 noet: */ diff --git a/src/rpmvercmp.c b/src/rpmvercmp.c new file mode 100644 index 00000000..de4aefe2 --- /dev/null +++ b/src/rpmvercmp.c @@ -0,0 +1,120 @@ +/* + * pacman + * + * Copyright (c) 2002 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +#include <stdio.h> +#include <ctype.h> +#include <string.h> + +/* this function was taken from rpm 4.0.4 and rewritten */ + +int rpmvercmp(const char *a, const char *b) +{ + char *str1, *str2; + char *one, *two; + char *rel1 = NULL, *rel2 = NULL; + char oldch1, oldch2; + int is1num, is2num; + int rc; + + if(!strcmp(a,b)) { + return(0); + } + + str1 = strdup(a); + str2 = strdup(b); + + /* lose the release number */ + for(one = str1; *one && *one != '-'; one++); + if(one) { + *one = '\0'; + rel1 = ++one; + } + for(two = str2; *two && *two != '-'; two++); + if(two) { + *two = '\0'; + rel2 = ++two; + } + + one = str1; + two = str2; + + while(*one || *two) { + while(*one && !isalnum(*one)) one++; + while(*two && !isalnum(*two)) two++; + + str1 = one; + str2 = two; + + /* find the next segment for each string */ + if(isdigit(*str1)) { + is1num = 1; + while(*str1 && isdigit(*str1)) str1++; + } else { + is1num = 0; + while(*str1 && isalpha(*str1)) str1++; + } + if(isdigit(*str2)) { + is2num = 1; + while(*str2 && isdigit(*str2)) str2++; + } else { + is2num = 0; + while(*str2 && isalpha(*str2)) str2++; + } + + oldch1 = *str1; + *str1 = '\0'; + oldch2 = *str2; + *str2 = '\0'; + + /* see if we ran out of segments on one string */ + if(one == str1 && two != str2) { + return(is2num ? -1 : 1); + } + if(one != str1 && two == str2) { + return(is1num ? 1 : -1); + } + + /* see if we have a type mismatch (ie, one is alpha and one is digits) */ + if(is1num && !is2num) return(1); + if(!is1num && is2num) return(-1); + + if(is1num) while(*one == '0') one++; + if(is2num) while(*two == '0') two++; + + rc = strverscmp(one, two); + if(rc) return(rc); + + *str1 = oldch1; + *str2 = oldch2; + one = str1; + two = str2; + } + + if((!*one) && (!*two)) { + /* compare release numbers */ + if(rel1 && rel2) return(rpmvercmp(rel1, rel2)); + return(0); + } + + return(*one ? 1 : -1); +} + +/* vim: set ts=2 sw=2 noet: */ diff --git a/src/util.c b/src/util.c new file mode 100644 index 00000000..1fe2e037 --- /dev/null +++ b/src/util.c @@ -0,0 +1,571 @@ +/* + * pacman + * + * Copyright (c) 2002 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +#include "config.h" +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <string.h> +#include <limits.h> +#include <errno.h> +#include <fcntl.h> +#include <libgen.h> +#include <getopt.h> +#include <ctype.h> +#include "list.h" +#include "package.h" +#include "db.h" +#include "util.h" +#include "pacman.h" + +extern char* pmo_root; +extern unsigned short pmo_op; +extern unsigned short pmo_version; +extern unsigned short pmo_verbose; +extern unsigned short pmo_help; +extern unsigned short pmo_force; +extern unsigned short pmo_nodeps; +extern unsigned short pmo_upgrade; +extern unsigned short pmo_nosave; +extern unsigned short pmo_vertest; +extern unsigned short pmo_q_isfile; +extern unsigned short pmo_q_info; +extern unsigned short pmo_q_list; +extern unsigned short pmo_q_owns; +extern unsigned short pmo_s_sync; +extern unsigned short pmo_s_search; +extern unsigned short pmo_s_clean; +extern unsigned short pmo_s_upgrade; + +extern char pmc_syncserver[512]; +extern char pmc_syncname[512]; +extern char pmc_syncpath[512]; + +extern PMList *pm_targets; + +/* borrowed and modifed from Per Liden's pkgutils (http://crux.nu) */ +static int gzopen_frontend(char *pathname, int oflags, int mode) +{ + char* gzoflags; + int fd; + gzFile gzf; + + switch (oflags & O_ACCMODE) { + case O_WRONLY: + gzoflags = "w"; + break; + case O_RDONLY: + gzoflags = "r"; + break; + case O_RDWR: + default: + errno = EINVAL; + return -1; + } + + if((fd = open(pathname, oflags, mode)) == -1) { + return -1; + } + if((oflags & O_CREAT) && fchmod(fd, mode)) { + return -1; + } + if(!(gzf = gzdopen(fd, gzoflags))) { + errno = ENOMEM; + return -1; + } + + return (int)gzf; +} + +tartype_t gztype = { + (openfunc_t) gzopen_frontend, + (closefunc_t)gzclose, + (readfunc_t) gzread, + (writefunc_t)gzwrite +}; + +int unpack(char *archive, char *prefix) +{ + TAR *tar = NULL; + char expath[PATH_MAX]; + + /* open the .tar.gz package */ + if(tar_open(&tar, archive, &gztype, O_RDONLY, 0, TAR_GNU) == -1) { + perror(archive); + return(1); + } + while(!th_read(tar)) { + snprintf(expath, PATH_MAX, "%s/%s", prefix, th_get_pathname(tar)); + if(tar_extract_file(tar, expath)) { + fprintf(stderr, "could not extract %s: %s\n", th_get_pathname(tar), strerror(errno)); + } + } + tar_close(tar); + + return(0); +} + +/* Parse command-line arguments for each operation + * op: the operation code requested + * argc: argc + * argv: argv + * + * Returns: 0 on success, 1 on error + */ +int parseargs(int op, int argc, char **argv) +{ + int opt; + int option_index = 0; + static struct option opts[] = + { + {"add", no_argument, 0, 'A'}, + {"remove", no_argument, 0, 'R'}, + {"upgrade", no_argument, 0, 'U'}, + {"query", no_argument, 0, 'Q'}, + {"sync", no_argument, 0, 'S'}, + {"deptest", no_argument, 0, 'T'}, + {"vertest", no_argument, 0, 'Y'}, + {"root", required_argument, 0, 'r'}, + {"verbose", no_argument, 0, 'v'}, + {"version", no_argument, 0, 'V'}, + {"help", no_argument, 0, 'h'}, + {"search", no_argument, 0, 's'}, + {"clean", no_argument, 0, 'c'}, + {"force", no_argument, 0, 'f'}, + {"nodeps", no_argument, 0, 'd'}, + {"nosave", no_argument, 0, 'n'}, + {"owns", no_argument, 0, 'o'}, + {"list", no_argument, 0, 'l'}, + {"file", no_argument, 0, 'p'}, + {"info", no_argument, 0, 'i'}, + {"sysupgrade", no_argument, 0, 'u'}, + {"refresh", no_argument, 0, 'y'}, + {0, 0, 0, 0} + }; + + while((opt = getopt_long(argc, argv, "ARUQSTYr:vhscVfnoldpiuy", opts, &option_index))) { + if(opt < 0) { + break; + } + switch(opt) { + case 0: break; + case 'A': pmo_op = (pmo_op != PM_MAIN ? 0 : PM_ADD); break; + case 'R': pmo_op = (pmo_op != PM_MAIN ? 0 : PM_REMOVE); break; + case 'U': pmo_op = (pmo_op != PM_MAIN ? 0 : PM_UPGRADE); break; + case 'Q': pmo_op = (pmo_op != PM_MAIN ? 0 : PM_QUERY); break; + case 'S': pmo_op = (pmo_op != PM_MAIN ? 0 : PM_SYNC); break; + case 'T': pmo_op = (pmo_op != PM_MAIN ? 0 : PM_DEPTEST); break; + case 'Y': pmo_op = (pmo_op != PM_MAIN ? 0 : PM_DEPTEST); pmo_vertest = 1; break; + case 'h': pmo_help = 1; break; + case 'V': pmo_version = 1; break; + case 'v': pmo_verbose = 1; break; + case 'f': pmo_force = 1; break; + case 'd': pmo_nodeps = 1; break; + case 'n': pmo_nosave = 1; break; + case 'l': pmo_q_list = 1; break; + case 'p': pmo_q_isfile = 1; break; + case 'i': pmo_q_info = 1; break; + case 'o': pmo_q_owns = 1; break; + case 'u': pmo_s_upgrade = 1; break; + case 'y': pmo_s_sync = 1; break; + case 's': pmo_s_search = 1; break; + case 'c': pmo_s_clean = 1; break; + case 'r': if(realpath(optarg, pmo_root) == NULL) { + perror("bad root path"); + return(1); + } break; + case '?': return(1); + default: return(1); + } + } + + if(pmo_op == 0) { + fprintf(stderr, "error: only one operation may be used at a time\n\n"); + return(1); + } + + if(pmo_help) { + usage(pmo_op, (char*)basename(argv[0])); + return(2); + } + if(pmo_version) { + version(); + return(2); + } + + while(optind < argc) { + /* add the target to our target array */ + char *s = strdup(argv[optind]); + pm_targets = list_add(pm_targets, s); + optind++; + } + + return(0); +} + +int parseconfig(char *configfile) +{ + FILE *fp = NULL; + char line[PATH_MAX+1]; + char *ptr = NULL; + char *key = NULL; + int linenum = 0; + + if((fp = fopen(configfile, "r")) == NULL) { + perror(configfile); + return(1); + } + + while(fgets(line, PATH_MAX, fp)) { + linenum++; + trim(line); + if(strlen(line) == 0) { + continue; + } + if(line[0] == '#') { + continue; + } + ptr = line; + key = strsep(&ptr, "="); + if(key == NULL || ptr == NULL) { + fprintf(stderr, "syntax error in config file (line %d)\n", linenum); + } else { + trim(key); + key = strtoupper(key); + trim(ptr); + if(!strcmp(key, "SYNC_SERVER")) { + strncpy(pmc_syncserver, ptr, sizeof(pmc_syncserver)-1); + } else if(!strcmp(key, "SYNC_TREE_PATH")) { + strncpy(pmc_syncpath, ptr, sizeof(pmc_syncpath)-1); + } else if(!strcmp(key, "SYNC_TREE_NAME")) { + strncpy(pmc_syncname, ptr, sizeof(pmc_syncname)-1); + } else { + fprintf(stderr, "Syntax error in description file line %d\n", linenum); + } + } + line[0] = '\0'; + } + fclose(fp); + + return(0); +} + +int copyfile(char *src, char *dest) +{ + FILE *in, *out; + size_t len; + char buf[1025]; + + in = fopen(src, "r"); + if(in == NULL) { + return(1); + } + out = fopen(dest, "w"); + if(out == NULL) { + return(1); + } + + while((len = fread(buf, 1, 1024, in))) { + fwrite(buf, 1, len, out); + } + + fclose(in); + fclose(out); + return(0); +} + +int makepath(char *path) +{ + char *orig, *str, *ptr; + char full[PATH_MAX] = ""; + mode_t oldmask; + + oldmask = umask(0000); + + orig = strdup(path); + str = orig; + while((ptr = strsep(&str, "/"))) { + if(strlen(ptr)) { + struct stat buf; + + strcat(full, "/"); + strcat(full, ptr); + if(stat(full, &buf)) { + if(mkdir(full, 0755)) { + free(orig); + return(1); + } + } + } + } + free(orig); + umask(oldmask); + return(0); +} + +int rmrf(char *path) +{ + int errflag = 0; + struct dirent *dp; + DIR *dirp; + char name[PATH_MAX]; + extern int errno; + + if(!unlink(path)) { + return(0); + } else { + if (errno == ENOENT) { + return(0); + } else if (errno == EPERM) { + /* fallthrough */ + } else if (errno == EISDIR) { + /* fallthrough */ + } else if (errno == ENOTDIR) { + return(1); + } else { + /* not a directory */ + return(1); + } + + if((dirp = opendir(path)) == (DIR *)-1) { + return(1); + } + for(dp = readdir(dirp); dp != NULL; dp = readdir(dirp)) { + if (dp->d_ino) { + sprintf(name, "%s/%s", path, dp->d_name); + if(strcmp(dp->d_name, "..") && strcmp(dp->d_name, ".")) { + errflag += rmrf(name); + } + } + } + closedir(dirp); + if(rmdir(path)) { + errflag++; + } + return(errflag); + } + return(0); +} + +/* Display usage/syntax for the specified operation. + * op: the operation code requested + * myname: basename(argv[0]) + */ +void usage(int op, char *myname) +{ + if(op == PM_MAIN) { + printf("usage: %s {-h --help}\n", myname); + printf(" %s {-V --version}\n", myname); + printf(" %s {-A --add} [options] <file>\n", myname); + printf(" %s {-R --remove} [options] <package>\n", myname); + printf(" %s {-U --upgrade} [options] <file>\n", myname); + printf(" %s {-Q --query} [options] [package]\n", myname); + printf(" %s {-S --sync} [options] [package]\n", myname); + printf("\nuse '%s --help' with other options for more syntax\n\n", myname); + } else { + if(op == PM_ADD) { + printf("usage: %s {-A --add} [options] <file>\n", myname); + printf("options:\n"); + printf(" -f, --force force install, overwrite conflicting files\n"); + printf(" -d, --nodeps skip dependency checks\n"); + } else if(op == PM_REMOVE) { + printf("usage: %s {-R --remove} [options] <package>\n", myname); + printf("options:\n"); + printf(" -d, --nodeps skip dependency checks\n"); + printf(" -n, --nosave remove configuration files as well\n"); + } else if(op == PM_UPGRADE) { + printf("usage: %s {-U --upgrade} [options] <file>\n", myname); + printf("options:\n"); + printf(" -f, --force force install, overwrite conflicting files\n"); + printf(" -d, --nodeps skip dependency checks\n"); + } else if(op == PM_QUERY) { + printf("usage: %s {-Q --query} [options] [package]\n", myname); + printf("options:\n"); + printf(" -o, --owns <file> query the package that owns <file>\n"); + printf(" -l, --list list the contents of the queried package\n"); + printf(" -i, --info view package information\n"); + printf(" -p, --file pacman will query the package file [package] instead of\n"); + printf(" looking in the database\n"); + } else if(op == PM_SYNC) { + printf("usage: %s {-S --sync} [options] [package]\n", myname); + printf("options:\n"); + printf(" -s, --search search sync database for matching strings\n"); + printf(" -f, --force force install, overwrite conflicting files\n"); + printf(" -d, --nodeps skip dependency checks\n"); + printf(" -y, --refresh download a fresh package sync database from the server\n"); + printf(" -u, --sysupgrade upgrade all packages that are out of date\n"); + printf(" -c, --clean remove packages from cache directory to free up diskspace\n"); + } + printf(" -v, --verbose be verbose\n"); + printf(" -r, --root <path> set an alternate installation root\n"); + } +} + +/* Version + */ +void version(void) +{ + printf("\n"); + printf(" .--. Pacman v%s\n", PACVER); + printf("/ _.-' .-. .-. .-. Copyright (C) 2002 Judd Vinet <jvinet@zeroflux.org>\n"); + printf("\\ '-. '-' '-' '-' \n"); + printf(" '--' This program may be freely redistributed under\n"); + printf(" the terms of the GNU GPL\n\n"); +} + +/* Check verbosity option and, if set, print the + * string to stdout + */ +int vprint(char *fmt, ...) +{ + va_list args; + if(pmo_verbose) { + va_start(args, fmt); + vprintf(fmt, args); + va_end(args); + fflush(stdout); + } + return(0); +} + +int yesno(char *fmt, ...) +{ + char response[32]; + va_list args; + + va_start(args, fmt); + vprintf(fmt, args); + va_end(args); + fflush(stdout); + if(fgets(response, 32, stdin)) { + trim(response); + if(!strcasecmp(response, "Y") || !strcasecmp(response, "YES") || !strlen(response)) { + return(1); + } + } + return(0); +} + +/* Test for existence of a string in a PMList + */ +int is_in(char *needle, PMList *haystack) +{ + PMList *lp; + + for(lp = haystack; lp; lp = lp->next) { + if(lp->data && !strcmp(lp->data, needle)) { + return(1); + } + } + return(0); +} + + +/* Look for a filename in a pkginfo_t.backup list. If we find it, + * then we return the md5 hash (parsed from the same line) + */ +char* needbackup(char* file, PMList *backup) +{ + PMList *lp; + + /* run through the backup list and parse out the md5 hash for our file */ + for(lp = backup; lp; lp = lp->next) { + char* str = strdup(lp->data); + char* ptr; + + /* tab delimiter */ + ptr = index(str, '\t'); + if(ptr == NULL) { + FREE(str); + continue; + } + *ptr = '\0'; + ptr++; + /* now str points to the filename and ptr points to the md5 hash */ + if(!strcmp(file, str)) { + char *md5 = strdup(ptr); + FREE(str); + return(md5); + } + FREE(str); + } + return(NULL); +} + +/* Test for existence of a package in a PMList* + * of pkginfo_t* + * + * returns: 0 for no match + * 1 for identical match + * -1 for name-only match (version mismatch) + */ +int is_pkgin(pkginfo_t *needle, PMList *haystack) +{ + PMList *lp; + pkginfo_t *info; + + for(lp = haystack; lp; lp = lp->next) { + info = (pkginfo_t*)lp->data; + if(info && !strcmp(info->name, needle->name)) { + if(!strcmp(info->version, needle->version)) { + return(1); + } + return(-1); + } + } + return(0); +} + +/* Convert a string to uppercase + */ +char* strtoupper(char *str) +{ + char *ptr = str; + + while(*ptr) { + (*ptr) = toupper(*ptr); + ptr++; + } + return str; +} + +/* Trim whitespace and newlines from a string + */ +char* trim(char *str) +{ + char *pch = str; + while(isspace(*pch)) { + pch++; + } + if(pch != str) { + memmove(str, pch, (strlen(pch) + 1)); + } + + pch = (char*)(str + (strlen(str) - 1)); + while(isspace(*pch)) { + pch--; + } + *++pch = '\0'; + + return str; +} + +/* vim: set ts=2 sw=2 noet: */ diff --git a/src/util.h b/src/util.h new file mode 100644 index 00000000..132d7c06 --- /dev/null +++ b/src/util.h @@ -0,0 +1,52 @@ +/* + * pacman + * + * Copyright (c) 2002 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ +#ifndef _PAC_UTIL_H +#define _PAC_UTIL_H + +#include <libtar.h> +#include <zlib.h> + +#define MALLOC(p, b) { if((b) > 0) { \ + p = malloc(b); if (!(p)) { \ + fprintf(stderr, "malloc failure: could not allocate %d byets\n", b); \ + exit(1); }} else p = NULL; } + +#define FREE(p) { if (p) { free(p); (p)= NULL; }} + +int unpack(char *archive, char *prefix); +int parseargs(int op, int argc, char **argv); +int parseconfig(char *configfile); +int copyfile(char *src, char *dest); +int makepath(char *path); +int rmrf(char *path); +int vprint(char *fmt, ...); +int yesno(char* fmt, ...); +void usage(int op, char *myname); +void version(void); +char* needbackup(char *file, PMList *backup); +int is_in(char *needle, PMList *haystack); +int is_pkgin(pkginfo_t *needle, PMList *haystack); +char* trim(char *str); +char* strtoupper(char *str); + + +#endif +/* vim: set ts=2 sw=2 noet: */ |