From d6c74352427367a334d37469962bbcf1b9ae364c Mon Sep 17 00:00:00 2001 From: Timo Teras Date: Thu, 17 Apr 2008 14:09:13 +0000 Subject: Initial commit of some stuff written so far. Still in state of flux. Expect breakage and major changes. --- AUTHORS | 2 + Makefile | 52 ++++ NEWS | 0 README | 0 TODO | 42 +++ src/Makefile | 39 +++ src/add.c | 42 +++ src/apk.c | 81 ++++++ src/apk_applet.h | 27 ++ src/apk_archive.h | 42 +++ src/apk_blob.h | 37 +++ src/apk_database.h | 97 +++++++ src/apk_defines.h | 147 ++++++++++ src/apk_hash.h | 55 ++++ src/apk_package.h | 84 ++++++ src/apk_state.h | 50 ++++ src/apk_version.h | 26 ++ src/archive.c | 349 +++++++++++++++++++++++ src/blob.c | 125 +++++++++ src/database.c | 802 +++++++++++++++++++++++++++++++++++++++++++++++++++++ src/del.c | 53 ++++ src/hash.c | 97 +++++++ src/index.c | 70 +++++ src/md5.c | 488 ++++++++++++++++++++++++++++++++ src/md5.h | 60 ++++ src/package.c | 483 ++++++++++++++++++++++++++++++++ src/state.c | 267 ++++++++++++++++++ src/template.c | 15 + src/ver.c | 76 +++++ src/version.c | 165 +++++++++++ 30 files changed, 3873 insertions(+) create mode 100644 AUTHORS create mode 100644 Makefile create mode 100644 NEWS create mode 100644 README create mode 100644 TODO create mode 100644 src/Makefile create mode 100644 src/add.c create mode 100644 src/apk.c create mode 100644 src/apk_applet.h create mode 100644 src/apk_archive.h create mode 100644 src/apk_blob.h create mode 100644 src/apk_database.h create mode 100644 src/apk_defines.h create mode 100644 src/apk_hash.h create mode 100644 src/apk_package.h create mode 100644 src/apk_state.h create mode 100644 src/apk_version.h create mode 100644 src/archive.c create mode 100644 src/blob.c create mode 100644 src/database.c create mode 100644 src/del.c create mode 100644 src/hash.c create mode 100644 src/index.c create mode 100644 src/md5.c create mode 100644 src/md5.h create mode 100644 src/package.c create mode 100644 src/state.c create mode 100644 src/template.c create mode 100644 src/ver.c create mode 100644 src/version.c diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..6449493 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,2 @@ +Natanael Copa +Timo Teräs diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..563f066 --- /dev/null +++ b/Makefile @@ -0,0 +1,52 @@ +# Makefile - one file to rule them all, one file to bind them +# +# Copyright (C) 2007 Timo Teräs +# All rights reserved. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License version 3 as published +# by the Free Software Foundation. See http://www.gnu.org/ for details. + +VERSION := 2.0-pre0 + +SVN_REV := $(shell svn info 2> /dev/null | grep ^Revision | cut -d ' ' -f 2) +ifneq ($(SVN_REV),) +FULL_VERSION := $(VERSION)-r$(SVN_REV) +else +FULL_VERSION := $(VERSION) +endif + +CC=gcc +INSTALL=install +INSTALLDIR=$(INSTALL) -d + +CFLAGS=-O2 -g -D_GNU_SOURCE -Werror -Wall -Wstrict-prototypes -std=gnu99 \ + -DAPK_VERSION=\"$(FULL_VERSION)\" +LDFLAGS=-g -lpthread + +DESTDIR= +SBINDIR=/usr/sbin +CONFDIR=/etc/apk +MANDIR=/usr/share/man +DOCDIR=/usr/share/doc/apk + +SUBDIRS=src + +.PHONY: compile install clean all + +all: compile + +compile install clean:: + @for i in $(SUBDIRS); do $(MAKE) $(MFLAGS) -C $$i $(MAKECMDGOALS); done + +install:: + $(INSTALLDIR) $(DESTDIR)$(DOCDIR) + $(INSTALL) README $(DESTDIR)$(DOCDIR) + +dist: + svn-clean + (TOP=`pwd` && cd .. && ln -s $$TOP apk-tools-$(VERSION) && \ + tar --exclude '*/.svn*' -cjvf apk-tools-$(VERSION).tar.bz2 apk-tools-$(VERSION)/* && \ + rm apk-tools-$(VERSION)) + +.EXPORT_ALL_VARIABLES: diff --git a/NEWS b/NEWS new file mode 100644 index 0000000..e69de29 diff --git a/README b/README new file mode 100644 index 0000000..e69de29 diff --git a/TODO b/TODO new file mode 100644 index 0000000..5c67bdb --- /dev/null +++ b/TODO @@ -0,0 +1,42 @@ +- Command line parsing +- Get repositories/root from command line +- Repository index/package fetching from URLs +- Installation of local files + +- Implement lbu stuff + +- Error handling and rollback +- Dependency manipulation API: deletion, overwrite, check compatibility + +- File ownership chowning +- New user/group creation + +- Non-trivial solution finder + - Versioned dependencies + - Conflicts + - Provides + +- Order removal of packages to honour dependencies + - Create reverse dependencies for installed pkgs + +- Remember counts for hash table creation + +- Possibly create a token hash for package names, versions and licenses, etc. +- Calculate changeset installed-size change +- Compress databases +- Option to not read fs entry cache +- Essentials(?) + +- Oldies: + add, delete: read (pkgs+fs), modify DEPs, recalc+commit+write (pkgs+fs) + fetch: read (pkgs), download remote packages + fetch -u: read (pkgs), download indexes, write (pkgs) + glob: read (pkgs), operate on package db + info: read (pkgs+fs), mostly on package db, might need .apks + version: read (pkgs), compare all installed pkg versions + +- New: + deps: show master dependencies + index: new TARGET, scan packages, write INDEX (pkgs) + upgrade: read TARGET, mark upgrade flags, recalculate, commit (pkgs+fs) + diff --git a/src/Makefile b/src/Makefile new file mode 100644 index 0000000..0964012 --- /dev/null +++ b/src/Makefile @@ -0,0 +1,39 @@ +# Makefile - one file to rule them all, one file to bind them +# +# Copyright (C) 2007 Timo Teräs +# All rights reserved. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License version 3 as published +# by the Free Software Foundation. See http://www.gnu.org/ for details. + +TARGETS = apk + +apk_OBJS = \ + state.o \ + database.o \ + package.o \ + archive.o \ + version.o \ + blob.o \ + hash.o \ + md5.o \ + add.o \ + del.o \ + ver.o \ + index.o \ + apk.o + +ALL_OBJS = $(apk_OBJS) + +all: $(TARGETS) + +apk: $(apk_OBJS) + +clean:: + @rm -f $(TARGETS) $(ALL_OBJS) + +install:: + $(INSTALLDIR) $(DESTDIR)$(SBINDIR) + $(INSTALL) $(TARGETS) $(DESTDIR)$(SBINDIR) + diff --git a/src/add.c b/src/add.c new file mode 100644 index 0000000..28dcb9b --- /dev/null +++ b/src/add.c @@ -0,0 +1,42 @@ +/* add.c - Alpine Package Keeper (APK) + * + * Copyright (C) 2005-2008 Natanael Copa + * Copyright (C) 2008 Timo Teräs + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. See http://www.gnu.org/ for details. + */ + +#include +#include "apk_applet.h" +#include "apk_database.h" + +static int add_main(int argc, char **argv) +{ + struct apk_database db; + int i; + + apk_db_init(&db, "/home/fabled/tmproot/"); + apk_db_read_config(&db); + for (i = 0; i < argc; i++) { + struct apk_dependency dep = { + .name = apk_db_get_name(&db, argv[i]), + }; + apk_deps_add(&db.world, &dep); + } + apk_db_recalculate_and_commit(&db); + apk_db_free(&db); + + return 0; +} + +static struct apk_applet apk_add = { + .name = "add", + .usage = "apkname...", + .main = add_main, +}; + +APK_DEFINE_APPLET(apk_add); + diff --git a/src/apk.c b/src/apk.c new file mode 100644 index 0000000..f8f13dd --- /dev/null +++ b/src/apk.c @@ -0,0 +1,81 @@ +/* apk.c - Alpine Package Keeper (APK) + * + * Copyright (C) 2005-2008 Natanael Copa + * Copyright (C) 2008 Timo Teräs + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. See http://www.gnu.org/ for details. + */ + +#include +#include +#include + +#include "apk_defines.h" +#include "apk_applet.h" + +void apk_log(const char *prefix, const char *format, ...) +{ + va_list va; + + if (prefix != NULL) + fprintf(stderr, prefix); + va_start(va, format); + vfprintf(stderr, format, va); + va_end(va); + fprintf(stderr, "\n"); +} + +int usage(void) +{ + struct apk_applet **a, *applet; + + printf("apk-tools " APK_VERSION "\n" + "\n" + "Usage:\n"); + + for (a = &__start_apkapplets; a < &__stop_apkapplets; a++) { + applet = *a; + printf(" apk %s %s\n", + applet->name, applet->usage); + } + printf("\n"); + + return 1; +} + +int main(int argc, char **argv) +{ + struct apk_applet **a, *applet; + char *prog; + + prog = strrchr(argv[0], '/'); + if (prog == NULL) + prog = argv[0]; + else + prog++; + + if (strcmp(prog, "apk") == 0) { + if (argc < 2) + return usage(); + prog = argv[1]; + argv++; + argc--; + } else if (strncmp(prog, "apk_", 4) == 0) { + prog += 4; + } else + return usage(); + + for (a = &__start_apkapplets; a < &__stop_apkapplets; a++) { + applet = *a; + if (strcmp(prog, applet->name) == 0) { + argv++; + argc--; + return applet->main(argc, argv); + } + } + + return usage(); +} diff --git a/src/apk_applet.h b/src/apk_applet.h new file mode 100644 index 0000000..d243d65 --- /dev/null +++ b/src/apk_applet.h @@ -0,0 +1,27 @@ +/* apk_applet.h - Alpine Package Keeper (APK) + * + * Copyright (C) 2005-2008 Natanael Copa + * Copyright (C) 2008 Timo Teräs + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. See http://www.gnu.org/ for details. + */ + +#ifndef APK_APPLET_H +#define APK_APPLET_H + +struct apk_applet { + const char *name; + const char *usage; + int (*main)(int argc, char **argv); +}; + +extern struct apk_applet *__start_apkapplets, *__stop_apkapplets; + +#define APK_DEFINE_APPLET(x) \ + static struct apk_applet *__applet_##x \ + __attribute__((__section__("apkapplets") used)) = &x; + +#endif diff --git a/src/apk_archive.h b/src/apk_archive.h new file mode 100644 index 0000000..2e8d274 --- /dev/null +++ b/src/apk_archive.h @@ -0,0 +1,42 @@ +/* apk_archive.c - Alpine Package Keeper (APK) + * + * Copyright (C) 2005-2008 Natanael Copa + * Copyright (C) 2008 Timo Teräs + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. See http://www.gnu.org/ for details. + */ + +#ifndef APK_ARCHIVE +#define APK_ARCHIVE + +#include +#include +#include "apk_blob.h" + +struct apk_archive_entry { + char *name; + char *link_target; + char *uname; + char *gname; + off_t size; + uid_t uid; + gid_t gid; + mode_t mode; + time_t mtime; + dev_t device; + int read_fd; +}; + +typedef int (*apk_archive_entry_parser)(struct apk_archive_entry *entry, void *ctx); + +pid_t apk_open_gz(int *fd); +int apk_parse_tar(int fd, apk_archive_entry_parser parser, void *ctx); +int apk_parse_tar_gz(int fd, apk_archive_entry_parser parser, void *ctx); +apk_blob_t apk_archive_entry_read(struct apk_archive_entry *ae); +int apk_archive_entry_extract(struct apk_archive_entry *ae, const char *to); +pthread_t apk_checksum_and_tee(int *fd, void *ptr); + +#endif diff --git a/src/apk_blob.h b/src/apk_blob.h new file mode 100644 index 0000000..033d331 --- /dev/null +++ b/src/apk_blob.h @@ -0,0 +1,37 @@ +/* apk_blob.h - Alpine Package Keeper (APK) + * + * Copyright (C) 2005-2008 Natanael Copa + * Copyright (C) 2008 Timo Teräs + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. See http://www.gnu.org/ for details. + */ + +#ifndef APK_BLOB_H +#define APK_BLOB_H + +#include + +struct apk_blob { + unsigned int len; + char *ptr; +}; +typedef struct apk_blob apk_blob_t; + +#define APK_BLOB_NULL ((apk_blob_t){0, NULL}) +#define APK_BLOB_STR(str) ((apk_blob_t){strlen(str), (str)}) +#define APK_BLOB_BUF(buf) ((apk_blob_t){sizeof(buf), (char *)(buf)}) +#define APK_BLOB_PTR_LEN(beg,len) ((apk_blob_t){(len), (beg)}) +#define APK_BLOB_PTR_PTR(beg,end) APK_BLOB_PTR_LEN((beg),(end)-(beg)+1) + +char *apk_blob_cstr(apk_blob_t str); +int apk_blob_splitstr(apk_blob_t blob, char *split, apk_blob_t *l, apk_blob_t *r); +int apk_blob_rsplit(apk_blob_t blob, char split, apk_blob_t *l, apk_blob_t *r); +unsigned apk_blob_uint(apk_blob_t blob, int base); + +int apk_hexdump_parse(apk_blob_t to, apk_blob_t from); +int apk_hexdump_format(int tolen, char *to, apk_blob_t from); + +#endif diff --git a/src/apk_database.h b/src/apk_database.h new file mode 100644 index 0000000..96ff361 --- /dev/null +++ b/src/apk_database.h @@ -0,0 +1,97 @@ +/* apk_database.h - Alpine Package Keeper (APK) + * + * Copyright (C) 2005-2008 Natanael Copa + * Copyright (C) 2008 Timo Teräs + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. See http://www.gnu.org/ for details. + */ + +#ifndef APK_PKGDB_H +#define APK_PKGDB_H + +#include "apk_version.h" +#include "apk_hash.h" +#include "apk_archive.h" +#include "apk_package.h" + +#define APK_MAX_REPOS 32 + +struct apk_db_file { + struct hlist_node dir_files_list; + struct hlist_node pkg_files_list; + + struct apk_db_dir *dir; + struct apk_package *owner; + char filename[]; +}; + +struct apk_db_dir { + apk_hash_node hash_node; + + struct hlist_head files; + struct apk_db_dir *parent; + + unsigned refs; + mode_t mode; + char dirname[]; +}; + +struct apk_name { + apk_hash_node hash_node; + + char *name; + struct apk_package_array *pkgs; +}; + +struct apk_repository { + char *url; +}; + +struct apk_database { + char *root; + unsigned pkg_id, num_repos; + + struct apk_dependency_array *world; + struct apk_repository repos[APK_MAX_REPOS]; + + struct { + struct apk_hash names; + struct apk_hash packages; + } available; + + struct { + struct hlist_head packages; + struct apk_hash dirs; + struct { + unsigned files; + unsigned dirs; + unsigned packages; + } stats; + } installed; +}; + +struct apk_name *apk_db_get_name(struct apk_database *db, const char *name); +void apk_name_free(struct apk_name *pkgname); + +void apk_db_init(struct apk_database *db, const char *root); +void apk_db_free(struct apk_database *db); +int apk_db_read_config(struct apk_database *db); +int apk_db_write_config(struct apk_database *db); + +int apk_db_pkg_add_file(struct apk_database *db, const char *file); +struct apk_package *apk_db_get_pkg(struct apk_database *db, csum_t sum); + +int apk_db_index_read(struct apk_database *db, int fd, int repo); +void apk_db_index_write(struct apk_database *db, int fd); + +int apk_db_add_repository(struct apk_database *db, const char *repo); +int apk_db_recalculate_and_commit(struct apk_database *db); + +int apk_db_install_pkg(struct apk_database *db, + struct apk_package *oldpkg, + struct apk_package *newpkg); + +#endif diff --git a/src/apk_defines.h b/src/apk_defines.h new file mode 100644 index 0000000..ecd08e3 --- /dev/null +++ b/src/apk_defines.h @@ -0,0 +1,147 @@ +/* apk_defines.c - Alpine Package Keeper (APK) + * + * Copyright (C) 2005-2008 Natanael Copa + * Copyright (C) 2008 Timo Teräs + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. See http://www.gnu.org/ for details. + */ + +#ifndef APK_DEFINES_H +#define APK_DEFINES_H + +#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) +#define BIT(x) (1 << (x)) + +#ifndef TRUE +#define TRUE 1 +#endif + +#ifndef FALSE +#define FALSE 0 +#endif + +#ifndef NULL +#define NULL 0L +#endif + +#ifndef container_of +#define container_of(ptr, type, member) ({ \ + const typeof( ((type *)0)->member ) *__mptr = (ptr); \ + (type *)( (char *)__mptr - offsetof(type,member) );}) +#endif + +#if 1 +#include "md5.h" + +typedef md5sum_t csum_t; +typedef struct md5_ctx csum_ctx_t; + +#define csum_init(ctx) md5_init(ctx) +#define csum_process(ctx, buf, len) md5_process(ctx, buf, len) +#define csum_finish(ctx, buf) md5_finish(ctx, buf) +#endif + +#define apk_error(args...) apk_log("ERROR: ", args) +#define apk_warning(args...) apk_log("WARNING: ", args) +#define apk_message(args...) apk_log(NULL, args) + +void apk_log(const char *prefix, const char *format, ...); + +#define APK_ARRAY(array_type_name, elem_type_name) \ + struct array_type_name { \ + int num; \ + elem_type_name item[]; \ + }; \ + static inline struct array_type_name * \ + array_type_name##_resize(struct array_type_name *a, int size) \ + { \ + struct array_type_name *tmp; \ + tmp = (struct array_type_name *) \ + realloc(a, sizeof(struct array_type_name) + \ + size * sizeof(elem_type_name)); \ + tmp->num = size; \ + return tmp; \ + } \ + static inline elem_type_name * \ + array_type_name##_add(struct array_type_name **a) \ + { \ + int size = 1; \ + if (*a != NULL) size += (*a)->num; \ + *a = array_type_name##_resize(*a, size); \ + return &(*a)->item[size-1]; \ + } + +#define LIST_END (void *) 0xe01 +#define LIST_POISON1 (void *) 0xdeadbeef +#define LIST_POISON2 (void *) 0xabbaabba + +struct hlist_head { + struct hlist_node *first; +}; + +struct hlist_node { + struct hlist_node *next; +}; + +static inline int hlist_empty(const struct hlist_head *h) +{ + return !h->first; +} + +static inline int hlist_hashed(const struct hlist_node *n) +{ + return n->next != NULL; +} + +static inline void __hlist_del(struct hlist_node *n, struct hlist_node **pprev) +{ + *pprev = n->next; +} + +static inline void hlist_del(struct hlist_node *n, struct hlist_node **pprev) +{ + __hlist_del(n, pprev); + n->next = LIST_POISON1; +} + +static inline void hlist_add_head(struct hlist_node *n, struct hlist_head *h) +{ + struct hlist_node *first = h->first; + + n->next = first ? first : LIST_END; + h->first = n; +} + +static inline void hlist_add_after(struct hlist_node *n, struct hlist_node **prev) +{ + n->next = *prev ? *prev : LIST_END; + *prev = n; +} + +#define hlist_entry(ptr, type, member) container_of(ptr,type,member) + +#define hlist_for_each(pos, head) \ + for (pos = (head)->first; pos && pos != LIST_END; \ + pos = pos->next) + +#define hlist_for_each_safe(pos, n, head) \ + for (pos = (head)->first; pos && pos != LIST_END && \ + ({ n = pos->next; 1; }); \ + pos = n) + +#define hlist_for_each_entry(tpos, pos, head, member) \ + for (pos = (head)->first; \ + pos && pos != LIST_END && \ + ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \ + pos = pos->next) + +#define hlist_for_each_entry_safe(tpos, pos, n, head, member) \ + for (pos = (head)->first; \ + pos && pos != LIST_END && ({ n = pos->next; 1; }) && \ + ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \ + pos = n) + +#endif diff --git a/src/apk_hash.h b/src/apk_hash.h new file mode 100644 index 0000000..f2f4ced --- /dev/null +++ b/src/apk_hash.h @@ -0,0 +1,55 @@ +/* apk_hash.h - Alpine Package Keeper (APK) + * + * Copyright (C) 2005-2008 Natanael Copa + * Copyright (C) 2008 Timo Teräs + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. See http://www.gnu.org/ for details. + */ + +#ifndef APK_HASH_H +#define APK_HASH_H + +#include +#include "apk_defines.h" + +typedef void *apk_hash_item; +typedef const void *apk_hash_key; + +typedef unsigned long (*apk_hash_f)(apk_hash_key); +typedef int (*apk_hash_compare_f)(apk_hash_key, apk_hash_key); +typedef void (*apk_hash_delete_f)(apk_hash_item); +typedef int (*apk_hash_enumerator_f)(apk_hash_item, void *ctx); + +struct apk_hash_ops { + ptrdiff_t node_offset; + apk_hash_key (*get_key)(apk_hash_item item); + unsigned long (*hash_key)(apk_hash_key key); + int (*compare)(apk_hash_key key, apk_hash_key item); + void (*delete_item)(apk_hash_item item); +}; + +typedef struct hlist_node apk_hash_node; +APK_ARRAY(apk_hash_array, struct hlist_head); + +struct apk_hash { + const struct apk_hash_ops *ops; + struct apk_hash_array *buckets; + int num_items; +}; + +unsigned long apk_hash_string(const char *string); +unsigned long apk_hash_csum(const void *); + +void apk_hash_init(struct apk_hash *h, const struct apk_hash_ops *ops, + int num_buckets); +void apk_hash_free(struct apk_hash *h); + +int apk_hash_foreach(struct apk_hash *h, apk_hash_enumerator_f e, void *ctx); +apk_hash_item apk_hash_get(struct apk_hash *h, apk_hash_key key); +void apk_hash_insert(struct apk_hash *h, apk_hash_item item); +void apk_hash_delete(struct apk_hash *h, apk_hash_key key); + +#endif diff --git a/src/apk_package.h b/src/apk_package.h new file mode 100644 index 0000000..cdcce49 --- /dev/null +++ b/src/apk_package.h @@ -0,0 +1,84 @@ +/* apk_database.h - Alpine Package Keeper (APK) + * + * Copyright (C) 2005-2008 Natanael Copa + * Copyright (C) 2008 Timo Teräs + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. See http://www.gnu.org/ for details. + */ + +#ifndef APK_PKG_H +#define APK_PKG_H + +#include "apk_version.h" +#include "apk_hash.h" + +struct apk_database; +struct apk_name; + +#define APK_SCRIPT_PRE_INSTALL 0 +#define APK_SCRIPT_POST_INSTALL 1 +#define APK_SCRIPT_PRE_DEINSTALL 2 +#define APK_SCRIPT_POST_DEINSTALL 3 +#define APK_SCRIPT_PRE_UPGRADE 4 +#define APK_SCRIPT_POST_UPGRADE 5 + +struct apk_script { + struct hlist_node script_list; + unsigned int type; + unsigned int size; + char script[]; +}; + +struct apk_dependency { + unsigned conflict : 1; + unsigned prefer_upgrade : 1; + unsigned version_mask : 3; + + struct apk_name *name; + char *version; +}; +APK_ARRAY(apk_dependency_array, struct apk_dependency); + +struct apk_package { + apk_hash_node hash_node; + + csum_t csum; + unsigned id, repos; + struct apk_name *name; + char *version; + char *url, *description, *license; + struct apk_dependency_array *depends; + unsigned int installed_size, size; + + /* for installed packages only */ + struct hlist_node installed_pkgs_list; + struct hlist_head owned_files; + struct hlist_head scripts; +}; +APK_ARRAY(apk_package_array, struct apk_package *); + +int apk_deps_add(struct apk_dependency_array **depends, + struct apk_dependency *dep); +void apk_deps_parse(struct apk_database *db, + struct apk_dependency_array **depends, + apk_blob_t blob); +int apk_deps_format(char *buf, int size, + struct apk_dependency_array *depends); +int apk_script_type(const char *name); + +struct apk_package *apk_pkg_read(struct apk_database *db, const char *name); +void apk_pkg_free(struct apk_package *pkg); + +int apk_pkg_get_state(struct apk_package *pkg); +int apk_pkg_add_script(struct apk_package *pkg, int fd, + unsigned int type, unsigned int size); +int apk_pkg_run_script(struct apk_package *pkg, const char *root, + unsigned int type); + +struct apk_package *apk_pkg_parse_index_entry(struct apk_database *db, apk_blob_t entry); +apk_blob_t apk_pkg_format_index_entry(struct apk_package *pkg, int size, char *buf); + +#endif diff --git a/src/apk_state.h b/src/apk_state.h new file mode 100644 index 0000000..0e95475 --- /dev/null +++ b/src/apk_state.h @@ -0,0 +1,50 @@ +/* apk_state.h - Alpine Package Keeper (APK) + * + * Copyright (C) 2005-2008 Natanael Copa + * Copyright (C) 2008 Timo Teräs + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. See http://www.gnu.org/ for details. + */ + +#ifndef APK_STATE_H +#define APK_STATE_H + +#include "apk_database.h" + +#define APK_STATE_NOT_CONSIDERED 0 +#define APK_STATE_PREFER_UPGRADE 1 +#define APK_STATE_INSTALL 2 +#define APK_STATE_NO_INSTALL 3 + +struct apk_state { + int refs; + unsigned char bitarray[]; +}; + +struct apk_deferred_state { + unsigned int preference; + struct apk_package *deferred_install; + /* struct apk_pkg_name_queue *install_queue; */ + struct apk_state *state; +}; + +struct apk_state *apk_state_new(struct apk_database *db); +struct apk_state *apk_state_dup(struct apk_state *state); +void apk_state_unref(struct apk_state *state); + +int apk_state_commit(struct apk_state *state, struct apk_database *db); + +int apk_state_satisfy_deps(struct apk_state *state, + struct apk_dependency_array *deps); + +void apk_state_pkg_set(struct apk_state *state, + struct apk_package *pkg); +int apk_state_pkg_install(struct apk_state *state, + struct apk_package *pkg); +int apk_state_pkg_is_installed(struct apk_state *state, + struct apk_package *pkg); + +#endif diff --git a/src/apk_version.h b/src/apk_version.h new file mode 100644 index 0000000..e0b5a37 --- /dev/null +++ b/src/apk_version.h @@ -0,0 +1,26 @@ +/* apk_version.h - Alpine Package Keeper (APK) + * + * Copyright (C) 2005-2008 Natanael Copa + * Copyright (C) 2008 Timo Teräs + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. See http://www.gnu.org/ for details. + */ + +#ifndef APK_VERSION_H +#define APK_VERSION_H + +#include "apk_blob.h" + +#define APK_VERSION_LESS -1 +#define APK_VERSION_EQUAL 0 +#define APK_VERSION_GREATER 1 + +#define APK_VERSION_RESULT_MASK(r) (1 << ((r)+1)) + +int apk_version_validate(apk_blob_t ver); +int apk_version_compare(apk_blob_t a, apk_blob_t b); + +#endif diff --git a/src/archive.c b/src/archive.c new file mode 100644 index 0000000..e5092dc --- /dev/null +++ b/src/archive.c @@ -0,0 +1,349 @@ +/* archive.c - Alpine Package Keeper (APK) + * + * Copyright (C) 2005-2008 Natanael Copa + * Copyright (C) 2008 Timo Teräs + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. See http://www.gnu.org/ for details. + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "apk_defines.h" +#include "apk_archive.h" + +#ifndef GUNZIP_BINARY +#define GUNZIP_BINARY "/bin/gunzip" +#endif + +struct tar_header { + /* ustar header, Posix 1003.1 */ + char name[100]; /* 0-99 */ + char mode[8]; /* 100-107 */ + char uid[8]; /* 108-115 */ + char gid[8]; /* 116-123 */ + char size[12]; /* 124-135 */ + char mtime[12]; /* 136-147 */ + char chksum[8]; /* 148-155 */ + char typeflag; /* 156-156 */ + char linkname[100]; /* 157-256 */ + char magic[8]; /* 257-264 */ + char uname[32]; /* 265-296 */ + char gname[32]; /* 297-328 */ + char devmajor[8]; /* 329-336 */ + char devminor[8]; /* 337-344 */ + char prefix[155]; /* 345-499 */ + char padding[12]; /* 500-512 */ +}; + +static int get_dev_null(void) +{ + static int fd_null = 0; + + if (fd_null == 0) { + fd_null = open("/dev/null", O_WRONLY); + if (fd_null < 0) + err(EX_OSFILE, "/dev/null"); + } + return fd_null; + +} + +pid_t apk_open_gz(int *fd) +{ + int pipe_fd[2]; + pid_t child_pid; + + if (pipe(pipe_fd) < 0) + err(EX_OSERR, "pipe"); + + child_pid = fork(); + if (child_pid < 0) + err(EX_OSERR, "fork"); + + if (child_pid == 0) { + close(pipe_fd[0]); + dup2(pipe_fd[1], STDOUT_FILENO); + dup2(*fd, STDIN_FILENO); + dup2(get_dev_null(), STDERR_FILENO); + close(pipe_fd[1]); + execl(GUNZIP_BINARY, "gunzip", "-c", NULL); + err(EX_UNAVAILABLE, GUNZIP_BINARY); + } + + close(pipe_fd[1]); + *fd = pipe_fd[0]; + + return child_pid; +} + +#define GET_OCTAL(s) apk_blob_uint(APK_BLOB_PTR_LEN(s, sizeof(s)), 8) + +static int do_splice(int from_fd, int to_fd, int len) +{ + int i = 0, r; + + while (i != len) { + r = splice(from_fd, NULL, to_fd, NULL, len - i, SPLICE_F_MOVE); + if (r == -1) + return i; + i += r; + } + + return i; +} + +int apk_parse_tar(int fd, apk_archive_entry_parser parser, void *ctx) +{ + struct apk_archive_entry entry; + struct tar_header buf; + unsigned long offset = 0; + int end = 0, r; + + memset(&entry, 0, sizeof(entry)); + while (read(fd, &buf, 512) == 512) { + offset += 512; + if (buf.name[0] == '\0') { + if (end) + break; + end++; + continue; + } + + entry = (struct apk_archive_entry){ + .size = GET_OCTAL(buf.size), + .uid = GET_OCTAL(buf.uid), + .gid = GET_OCTAL(buf.gid), + .mode = GET_OCTAL(buf.mode) & 0777, + .mtime = GET_OCTAL(buf.mtime), + .name = entry.name, + .uname = buf.uname, + .gname = buf.gname, + .device = makedev(GET_OCTAL(buf.devmajor), + GET_OCTAL(buf.devminor)), + .read_fd = fd, + }; + + switch (buf.typeflag) { + case 'L': + if (entry.name != NULL) + free(entry.name); + entry.name = malloc(entry.size+1); + read(fd, entry.name, entry.size); + offset += entry.size; + entry.size = 0; + break; + case '0': + case '7': /* regular file */ + entry.mode |= S_IFREG; + break; + case '1': /* hard link */ + entry.mode |= S_IFREG; + entry.link_target = buf.linkname; + break; + case '2': /* symbolic link */ + entry.mode |= S_IFLNK; + entry.link_target = buf.linkname; + break; + case '3': /* char device */ + entry.mode |= S_IFCHR; + break; + case '4': /* block devicek */ + entry.mode |= S_IFBLK; + break; + case '5': /* directory */ + entry.mode |= S_IFDIR; + break; + default: + break; + } + + if (entry.mode & S_IFMT) { + if (entry.name == NULL) + entry.name = strdup(buf.name); + + /* callback parser function */ + offset += entry.size; + r = parser(&entry, ctx); + if (r != 0) + return r; + offset -= entry.size; + + free(entry.name); + entry.name = NULL; + } + + if (entry.size) + offset += do_splice(fd, get_dev_null(), entry.size); + + /* align to next 512 block */ + if (offset & 511) + offset += do_splice(fd, get_dev_null(), + 512 - (offset & 511)); + } + + return 0; +} + +int apk_parse_tar_gz(int fd, apk_archive_entry_parser parser, void *ctx) +{ + pid_t pid; + int r, status; + + pid = apk_open_gz(&fd); + if (pid < 0) + return pid; + + r = apk_parse_tar(fd, parser, ctx); + close(fd); + waitpid(pid, &status, 0); + + return r; +} + +apk_blob_t apk_archive_entry_read(struct apk_archive_entry *ae) +{ + char *str; + int pos = 0; + ssize_t r; + + str = malloc(ae->size + 1); + pos = 0; + while (ae->size) { + r = read(ae->read_fd, &str[pos], ae->size); + if (r < 0) { + free(str); + return APK_BLOB_NULL; + } + pos += r; + ae->size -= r; + } + str[pos] = 0; + + return APK_BLOB_PTR_LEN(str, pos+1); +} + +int apk_archive_entry_extract(struct apk_archive_entry *ae, const char *fn) +{ + int r = -1; + + if (fn == NULL) + fn = ae->name; + + /* BIG HONKING FIXME */ + unlink(fn); + + switch (ae->mode & S_IFMT) { + case S_IFDIR: + r = mkdir(fn, ae->mode & 0777); + if (r < 0 && errno == EEXIST) + r = 0; + break; + case S_IFREG: + if (ae->link_target == NULL) { + r = open(fn, O_WRONLY | O_CREAT, ae->mode & 0777); + if (r < 0) + break; + ae->size -= do_splice(ae->read_fd, r, ae->size); + close(r); + r = ae->size ? -1 : 0; + } else { + r = link(ae->link_target, fn); + } + break; + case S_IFLNK: + r = symlink(ae->link_target, fn); + break; + case S_IFSOCK: + case S_IFBLK: + case S_IFCHR: + case S_IFIFO: + r = mknod(fn, ae->mode, ae->device); + break; + } + if (r != 0) + apk_error("Failed to extract %s\n", ae->name); + return r; +} + +struct checksum_and_tee { + int in_fd, tee_fd; + void *ptr; +}; + +static void *__apk_checksum_and_tee(void *arg) +{ + struct checksum_and_tee *args = (struct checksum_and_tee *) arg; + char buf[2*1024]; + int r, w, wt; + __off64_t offset; + csum_ctx_t ctx; + int dosplice = 1; + + offset = lseek(args->in_fd, 0, SEEK_CUR); + csum_init(&ctx); + do { + r = read(args->in_fd, buf, sizeof(buf)); + if (r <= 0) + break; + + wt = 0; + do { + if (dosplice) { + w = splice(args->in_fd, &offset, args->tee_fd, NULL, + r - wt, SPLICE_F_MOVE); + if (w < 0) { + dosplice = 0; + continue; + } + } else { + w = write(args->tee_fd, &buf[wt], r - wt); + if (w < 0) + break; + } + wt += w; + } while (wt != r); + + csum_process(&ctx, buf, r); + } while (r == sizeof(buf)); + + csum_finish(&ctx, args->ptr); + close(args->tee_fd); + close(args->in_fd); + free(args); + + return NULL; +} + +pthread_t apk_checksum_and_tee(int *fd, void *ptr) +{ + struct checksum_and_tee *args; + int fds[2]; + pthread_t tid; + + if (pipe(fds) < 0) + return -1; + + fcntl(fds[0], F_SETFD, FD_CLOEXEC); + fcntl(fds[1], F_SETFD, FD_CLOEXEC); + + args = malloc(sizeof(*args)); + *args = (struct checksum_and_tee){ *fd, fds[1], ptr }; + if (pthread_create(&tid, NULL, __apk_checksum_and_tee, args) < 0) + return -1; + + *fd = fds[0]; + return tid; +} + diff --git a/src/blob.c b/src/blob.c new file mode 100644 index 0000000..eecf642 --- /dev/null +++ b/src/blob.c @@ -0,0 +1,125 @@ +/* blob.c - Alpine Package Keeper (APK) + * + * Copyright (C) 2005-2008 Natanael Copa + * Copyright (C) 2008 Timo Teräs + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. See http://www.gnu.org/ for details. + */ + +#include +#include + +#include "apk_blob.h" + +char *apk_blob_cstr(apk_blob_t blob) +{ + char *cstr; + + if (blob.ptr[blob.len-1] == 0) + return strdup(blob.ptr); + + cstr = malloc(blob.len + 1); + memcpy(cstr, blob.ptr, blob.len); + cstr[blob.len] = 0; + + return cstr; +} + +int apk_blob_rsplit(apk_blob_t blob, char split, apk_blob_t *l, apk_blob_t *r) +{ + char *sep; + + sep = memrchr(blob.ptr, split, blob.len); + if (sep == NULL) + return 0; + + if (l != NULL) + *l = APK_BLOB_PTR_PTR(blob.ptr, sep - 1); + if (r != NULL) + *r = APK_BLOB_PTR_PTR(sep + 1, blob.ptr + blob.len); + + return 1; +} + +int apk_blob_splitstr(apk_blob_t blob, char *split, apk_blob_t *l, apk_blob_t *r) +{ + int splitlen = strlen(split); + char *pos = blob.ptr, *end = blob.ptr + blob.len - splitlen + 1; + + if (end < pos) + return 0; + + while (1) { + pos = memchr(pos, split[0], end - pos); + if (pos == NULL) + return 0; + + if (memcmp(pos, split, splitlen) != 0) { + pos++; + continue; + } + + *l = APK_BLOB_PTR_PTR(blob.ptr, pos-1); + *r = APK_BLOB_PTR_PTR(pos+splitlen, blob.ptr+blob.len-1); + return 1; + } +} + +static int dx(int c) +{ + if (c >= '0' && c <= '9') + return c - '0'; + if (c >= 'a' && c <= 'f') + return c - 'a' + 0xa; + if (c >= 'A' && c <= 'F') + return c - 'A' + 0xa; + return -1; +} + +unsigned apk_blob_uint(apk_blob_t blob, int base) +{ + unsigned val; + int i, ch; + + val = 0; + for (i = 0; i < blob.len; i++) { + if (blob.ptr[i] == 0) + break; + ch = dx(blob.ptr[i]); + if (ch < 0 || ch >= base) + return 0; + val *= base; + val += ch; + } + return val; +} + +int apk_hexdump_parse(apk_blob_t to, apk_blob_t from) +{ + int i; + + if (to.len * 2 != from.len) + return -1; + + for (i = 0; i < from.len / 2; i++) + to.ptr[i] = (dx(from.ptr[i*2]) << 4) + dx(from.ptr[i*2+1]); + + return 0; +} + +int apk_hexdump_format(int tolen, char *to, apk_blob_t from) +{ + static const char *xd = "0123456789abcdef"; + int i; + + for (i = 0; i < from.len && i*2+2 < tolen; i++) { + to[i*2+0] = xd[(from.ptr[i] >> 4) & 0xf]; + to[i*2+1] = xd[from.ptr[i] & 0xf]; + } + to[i*2] = 0; + + return i*2; +} diff --git a/src/database.c b/src/database.c new file mode 100644 index 0000000..f98be64 --- /dev/null +++ b/src/database.c @@ -0,0 +1,802 @@ +/* database.c - Alpine Package Keeper (APK) + * + * Copyright (C) 2005-2008 Natanael Copa + * Copyright (C) 2008 Timo Teräs + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. See http://www.gnu.org/ for details. + */ + +#include +#include +#include +#include +#include +#include + +#include "apk_defines.h" +#include "apk_package.h" +#include "apk_database.h" +#include "apk_state.h" + +struct install_ctx { + struct apk_database *db; + struct apk_package *pkg; + + int script; + struct apk_db_dir *dircache; + struct hlist_node **file_dir_node; + struct hlist_node **file_pkg_node; +}; + +static apk_hash_key pkg_name_get_key(apk_hash_item item) +{ + return ((struct apk_name *) item)->name; +} + +static const struct apk_hash_ops pkg_name_hash_ops = { + .node_offset = offsetof(struct apk_name, hash_node), + .get_key = pkg_name_get_key, + .hash_key = (apk_hash_f) apk_hash_string, + .compare = (apk_hash_compare_f) strcmp, + .delete_item = (apk_hash_delete_f) apk_name_free, +}; + +static apk_hash_key pkg_info_get_key(apk_hash_item item) +{ + return ((struct apk_package *) item)->csum; +} + +static int cmpcsum(apk_hash_key a, apk_hash_key b) +{ + return memcmp(a, b, sizeof(csum_t)); +} + +static const struct apk_hash_ops pkg_info_hash_ops = { + .node_offset = offsetof(struct apk_package, hash_node), + .get_key = pkg_info_get_key, + .hash_key = (apk_hash_f) apk_hash_csum, + .compare = cmpcsum, + .delete_item = (apk_hash_delete_f) apk_pkg_free, +}; + +static apk_hash_key apk_db_dir_get_key(apk_hash_item item) +{ + return ((struct apk_db_dir *) item)->dirname; +} + +static const struct apk_hash_ops dir_hash_ops = { + .node_offset = offsetof(struct apk_db_dir, hash_node), + .get_key = apk_db_dir_get_key, + .hash_key = (apk_hash_f) apk_hash_string, + .compare = (apk_hash_compare_f) strcmp, + .delete_item = (apk_hash_delete_f) free, +}; + +struct apk_name *apk_db_get_name(struct apk_database *db, const char *name) +{ + struct apk_name *pn; + + pn = (struct apk_name *) apk_hash_get(&db->available.names, name); + if (pn != NULL) + return pn; + + pn = calloc(1, sizeof(struct apk_name)); + if (pn == NULL) + return NULL; + + pn->name = strdup(name); + apk_hash_insert(&db->available.names, pn); + + return pn; +} + +void apk_name_free(struct apk_name *name) +{ + free(name->name); + free(name); +} + +static struct apk_db_dir *apk_db_dir_ref(struct apk_database *db, + struct apk_db_dir *dir, + int create_dir) +{ + if (dir->refs == 0) { + if (dir->parent != NULL) + apk_db_dir_ref(db, dir->parent, create_dir); + db->installed.stats.dirs++; + if (create_dir) + mkdir(dir->dirname, dir->mode); + } + dir->refs++; + + return dir; +} + +static void apk_db_dir_unref(struct apk_database *db, struct apk_db_dir *dir) +{ + dir->refs--; + if (dir->refs > 0) + return; + + db->installed.stats.dirs--; + rmdir(dir->dirname); + + if (dir->parent != NULL) + apk_db_dir_unref(db, dir->parent); +} + +static struct apk_db_dir *apk_db_dir_get(struct apk_database *db, + apk_blob_t name) +{ + struct apk_db_dir *dir; + apk_blob_t bparent; + char *cstr; + + if (name.ptr[name.len-1] == '/') + name.len--; + + cstr = apk_blob_cstr(name); + dir = (struct apk_db_dir *) apk_hash_get(&db->installed.dirs, cstr); + free(cstr); + if (dir != NULL) + return dir; + + dir = calloc(1, sizeof(*dir) + name.len + 1); + memcpy(dir->dirname, name.ptr, name.len); + dir->dirname[name.len] = 0; + apk_hash_insert(&db->installed.dirs, dir); + + if (apk_blob_rsplit(name, '/', &bparent, NULL)) + dir->parent = apk_db_dir_get(db, bparent); + + return dir; +} + +static struct apk_db_file *apk_db_file_new(struct apk_db_dir *dir, + apk_blob_t name, + struct hlist_node **after) +{ + struct apk_db_file *file; + + file = calloc(1, sizeof(*file) + name.len + 1); + hlist_add_after(&file->dir_files_list, after); + file->dir = dir; + memcpy(file->filename, name.ptr, name.len); + file->filename[name.len] = 0; + + return file; +} + +static void apk_db_file_set_owner(struct apk_database *db, + struct apk_db_file *file, + struct apk_package *owner, + int create_dir, + struct hlist_node **after) +{ + if (file->owner != NULL) + return; + + db->installed.stats.files++; + file->dir = apk_db_dir_ref(db, file->dir, create_dir); + file->owner = owner; + hlist_add_after(&file->pkg_files_list, after); +} + +static struct apk_db_file *apk_db_file_get(struct apk_database *db, + apk_blob_t name, + struct install_ctx *ctx) +{ + struct apk_db_dir *dir; + struct apk_db_file *file; + struct hlist_node *cur; + apk_blob_t bdir, bfile; + + if (!apk_blob_rsplit(name, '/', &bdir, &bfile)) + return NULL; + + dir = NULL; + if (ctx != NULL && ctx->dircache != NULL) { + dir = ctx->dircache; + if (strncmp(dir->dirname, bdir.ptr, bdir.len) != 0 || + dir->dirname[bdir.len] != 0) + dir = NULL; + } + if (dir == NULL) { + dir = apk_db_dir_get(db, bdir); + if (ctx != NULL) { + ctx->dircache = dir; + ctx->file_dir_node = &dir->files.first; + } + } + + hlist_for_each_entry(file, cur, &dir->files, dir_files_list) { + if (strncmp(file->filename, bfile.ptr, bfile.len) == 0 && + file->filename[bfile.len] == 0) + return file; + } + + file = apk_db_file_new(dir, bfile, ctx->file_dir_node); + ctx->file_dir_node = &file->dir_files_list.next; + + return file; +} + +static int apk_db_read_fdb(struct apk_database *db, int fd) +{ + struct apk_package *pkg = NULL; + struct apk_db_dir *dir = NULL; + struct apk_db_file *file = NULL; + struct hlist_node **pkg_node = &db->installed.packages.first; + struct hlist_node **file_dir_node = NULL; + struct hlist_node **file_pkg_node = NULL; + + char buf[1024]; + apk_blob_t l, r; + csum_t csum; + int n; + + r = APK_BLOB_PTR_LEN(buf, 0); + while (1) { + n = read(fd, &r.ptr[r.len], sizeof(buf) - r.len); + if (n <= 0) + break; + r.len += n; + + while (apk_blob_splitstr(r, "\n", &l, &r)) { + n = l.ptr[0]; + l.ptr++; + l.len--; + switch (n) { + case 'P': + if (apk_hexdump_parse(APK_BLOB_BUF(csum), l)) { + apk_error("Not a valid checksum"); + return -1; + } + pkg = apk_db_get_pkg(db, csum); + if (pkg == NULL) { + apk_error("Package '%.*s' is installed, but not in any repository", + l.len, l.ptr); + return -1; + } + if (!hlist_hashed(&pkg->installed_pkgs_list)) { + db->installed.stats.packages++; + hlist_add_after(&pkg->installed_pkgs_list, pkg_node); + pkg_node = &pkg->installed_pkgs_list.next; + } + dir = NULL; + file_dir_node = NULL; + file_pkg_node = &pkg->owned_files.first; + break; + case 'D': + if (pkg == NULL) { + apk_error("FDB directory entry before package entry"); + return -1; + } + dir = apk_db_dir_get(db, l); + file_dir_node = &dir->files.first; + break; + case 'F': + if (dir == NULL) { + apk_error("FDB file entry before directory entry"); + return -1; + } + file = apk_db_file_new(dir, l, file_dir_node); + apk_db_file_set_owner(db, file, pkg, FALSE, file_pkg_node); + file_dir_node = &file->dir_files_list.next; + file_pkg_node = &file->pkg_files_list.next; + break; + default: + apk_error("FDB entry '%c' unsupported", n); + return -1; + } + } + + memcpy(&buf[0], r.ptr, r.len); + r = APK_BLOB_PTR_LEN(buf, r.len); + } + + return 0; +} + +static int apk_db_write_fdb(struct apk_database *db, int fd) +{ + struct apk_package *pkg; + struct apk_db_dir *dir; + struct apk_db_file *file; + struct hlist_node *c1, *c2; + char buf[1024]; + int n; + + hlist_for_each_entry(pkg, c1, &db->installed.packages, installed_pkgs_list) { + n = 0; + buf[n++] = 'P'; + n += apk_hexdump_format(sizeof(buf)-n, &buf[n], + APK_BLOB_BUF(pkg->csum)); + buf[n++] = '\n'; + + dir = NULL; + hlist_for_each_entry(file, c2, &pkg->owned_files, pkg_files_list) { + if (file->owner == NULL) + continue; + + if (dir != file->dir) { + n += snprintf(&buf[n], sizeof(buf)-n, + "D%s\n", + file->dir->dirname); + dir = file->dir; + } + + n += snprintf(&buf[n], sizeof(buf)-n, + "F%s\n", + file->filename); + + write(fd, buf, n); + n = 0; + } + } + + return 0; +} + +struct apk_script_header { + csum_t csum; + unsigned int type; + unsigned int size; +}; + +static int apk_db_write_scriptdb(struct apk_database *db, int fd) +{ + struct apk_package *pkg; + struct apk_script *script; + struct apk_script_header hdr; + struct hlist_node *c1, *c2; + + hlist_for_each_entry(pkg, c1, &db->installed.packages, + installed_pkgs_list) { + hlist_for_each_entry(script, c2, &pkg->scripts, script_list) { + memcpy(hdr.csum, pkg->csum, sizeof(csum_t)); + hdr.type = script->type; + hdr.size = script->size; + + write(fd, &hdr, sizeof(hdr)); + write(fd, script->script, script->size); + } + } + + return 0; +} + +static int apk_db_read_scriptdb(struct apk_database *db, int fd) +{ + struct apk_package *pkg; + struct apk_script_header hdr; + + while (read(fd, &hdr, sizeof(hdr)) == sizeof(hdr)) { + pkg = apk_db_get_pkg(db, hdr.csum); + apk_pkg_add_script(pkg, fd, hdr.type, hdr.size); + } + + return 0; +} + +static const char *get_db_path(struct apk_database *db, const char *f) +{ + static char fn[1024]; + + snprintf(fn, sizeof(fn), "%s%s", db->root, f); + + return fn; +} + +void apk_db_init(struct apk_database *db, const char *root) +{ + memset(db, 0, sizeof(*db)); + apk_hash_init(&db->available.names, &pkg_name_hash_ops, 1000); + apk_hash_init(&db->available.packages, &pkg_info_hash_ops, 4000); + apk_hash_init(&db->installed.dirs, &dir_hash_ops, 1000); + + if (root != NULL) { + db->root = strdup(root); + apk_db_add_repository(db, "/home/fabled/foo/"); + mkdir(get_db_path(db, "tmp"), 01777); + mkdir(get_db_path(db, "dev"), 0755); + mknod(get_db_path(db, "dev/null"), 0666, makedev(1, 3)); + } +} + +int apk_db_read_config(struct apk_database *db) +{ + struct stat st; + char *buf; + int fd; + + if (db->root == NULL) + return -1; + + /* Read: + * 1. installed repository + * 2. source repositories + * 3. master dependencies + * 4. package statuses + * 5. files db + * 6. script db + */ + fd = open(get_db_path(db, "var/lib/apk/world"), O_RDONLY); + if (fd >= 0) { + fstat(fd, &st); + buf = malloc(st.st_size); + read(fd, buf, st.st_size); + apk_deps_parse(db, &db->world, + APK_BLOB_PTR_LEN(buf, st.st_size)); + close(fd); + } else { + apk_deps_parse(db, &db->world, + APK_BLOB_STR("busybox, alpine-baselayout, " + "apk-tools, alpine-conf")); + } + + fd = open(get_db_path(db, "var/lib/apk/files"), O_RDONLY); + if (fd >= 0) { + apk_db_read_fdb(db, fd); + close(fd); + } + + fd = open(get_db_path(db, "var/lib/apk/scripts"), O_RDONLY); + if (fd >= 0) { + apk_db_read_scriptdb(db, fd); + close(fd); + } + + return 0; +} + +struct write_ctx { + struct apk_database *db; + int fd; +}; + +int apk_db_write_config(struct apk_database *db) +{ + char buf[1024]; + int n, fd; + + if (db->root == NULL) + return -1; + + mkdir(get_db_path(db, "var"), 0755); + mkdir(get_db_path(db, "var/lib"), 0755); + mkdir(get_db_path(db, "var/lib/apk"), 0755); + + fd = creat(get_db_path(db, "var/lib/apk/world"), 0600); + if (fd < 0) + return -1; + n = apk_deps_format(buf, sizeof(buf), db->world); + write(fd, buf, n); + close(fd); + + fd = creat(get_db_path(db, "var/lib/apk/files"), 0600); + if (fd < 0) + return -1; + apk_db_write_fdb(db, fd); + close(fd); + + fd = creat(get_db_path(db, "var/lib/apk/scripts"), 0600); + if (fd < 0) + return -1; + apk_db_write_scriptdb(db, fd); + close(fd); + + return 0; +} + +void apk_db_free(struct apk_database *db) +{ + apk_hash_free(&db->available.names); + apk_hash_free(&db->available.packages); + apk_hash_free(&db->installed.dirs); + if (db->root != NULL) + free(db->root); +} + +static void apk_db_pkg_add(struct apk_database *db, struct apk_package *pkg) +{ + struct apk_package *idb; + + idb = apk_hash_get(&db->available.packages, pkg->csum); + if (idb == NULL) { + pkg->id = db->pkg_id++; + apk_hash_insert(&db->available.packages, pkg); + *apk_package_array_add(&pkg->name->pkgs) = pkg; + } else { + idb->repos |= pkg->repos; + apk_pkg_free(pkg); + } +} + +struct apk_package *apk_db_get_pkg(struct apk_database *db, csum_t sum) +{ + return apk_hash_get(&db->available.packages, sum); +} + +int apk_db_pkg_add_file(struct apk_database *db, const char *file) +{ + struct apk_package *info; + + info = apk_pkg_read(db, file); + if (info == NULL) + return FALSE; + + apk_db_pkg_add(db, info); + return TRUE; +} + +int apk_db_index_read(struct apk_database *db, int fd, int repo) +{ + struct apk_package *pkg; + char buf[1024]; + int n; + apk_blob_t l, r; + + r = APK_BLOB_PTR_LEN(buf, 0); + while (1) { + n = read(fd, &r.ptr[r.len], sizeof(buf) - r.len); + if (n <= 0) + break; + r.len += n; + + while (apk_blob_splitstr(r, "\n\n", &l, &r)) { + pkg = apk_pkg_parse_index_entry(db, l); + if (pkg != NULL) { + pkg->repos |= BIT(repo); + apk_db_pkg_add(db, pkg); + } + } + + memcpy(&buf[0], r.ptr, r.len); + r = APK_BLOB_PTR_LEN(buf, r.len); + } + + return 0; +} + +static int write_index_entry(apk_hash_item item, void *ctx) +{ + int fd = (int) ctx; + char buf[1024]; + apk_blob_t blob; + + blob = apk_pkg_format_index_entry(item, sizeof(buf), buf); + if (blob.ptr) + write(fd, blob.ptr, blob.len); + + return 0; +} + +void apk_db_index_write(struct apk_database *db, int fd) +{ + apk_hash_foreach(&db->available.packages, write_index_entry, (void *) fd); +} + +int apk_db_add_repository(struct apk_database *db, const char *repo) +{ + char tmp[256]; + int fd, r; + + if (db->num_repos >= APK_MAX_REPOS) + return -1; + + r = db->num_repos++; + db->repos[r] = (struct apk_repository){ + .url = strdup(repo) + }; + + snprintf(tmp, sizeof(tmp), "%sAPK_INDEX", repo); + fd = open(tmp, O_RDONLY); + if (fd < 0) { + apk_error("Failed to open index file %s", tmp); + return -1; + } + apk_db_index_read(db, fd, r); + close(fd); + + return 0; +} + +int apk_db_recalculate_and_commit(struct apk_database *db) +{ + struct apk_state *state; + int r; + + state = apk_state_new(db); + r = apk_state_satisfy_deps(state, db->world); + if (r == 0) { + r = apk_state_commit(state, db); + if (r != 0) { + apk_error("Failed to commit changes"); + return r; + } + apk_db_write_config(db); + + apk_message("OK: %d packages, %d dirs, %d files", + db->installed.stats.packages, + db->installed.stats.dirs, + db->installed.stats.files); + } else { + apk_error("Failed to build installation graph"); + } + apk_state_unref(state); + + return r; +} + +static int apk_db_install_archive_entry(struct apk_archive_entry *ae, + struct install_ctx *ctx) +{ + struct apk_database *db = ctx->db; + struct apk_package *pkg = ctx->pkg; + apk_blob_t name = APK_BLOB_STR(ae->name); + struct apk_db_file *file; + const char *p; + int r = 0, type; + + if (strncmp(ae->name, "var/db/apk/", 11) == 0) { + p = &ae->name[11]; + if (strncmp(p, pkg->name->name, strlen(pkg->name->name)) != 0) + return 0; + p += strlen(pkg->name->name) + 1; + if (strncmp(p, pkg->version, strlen(pkg->version)) != 0) + return 0; + p += strlen(pkg->version) + 1; + + type = apk_script_type(p); + if (type < 0) + return 0; + + ae->size -= apk_pkg_add_script(pkg, ae->read_fd, + type, ae->size); + + if (type == ctx->script) { + r = apk_pkg_run_script(pkg, db->root, type); + if (r != 0) + apk_error("%s-%s: Failed to execute pre-install/upgrade script", + pkg->name->name, pkg->version); + } + + return r; + } + + if (ctx->file_pkg_node == NULL) + ctx->file_pkg_node = &pkg->owned_files.first; + + if (!S_ISDIR(ae->mode)) { + file = apk_db_file_get(db, name, ctx); + if (file == NULL) + return -1; + + if (file->owner != NULL && + file->owner->name != pkg->name) { + apk_error("%s: Trying to overwrite %s owned by %s.\n", + pkg->name->name, ae->name, + file->owner->name->name); + return -1; + } + + apk_db_file_set_owner(db, file, pkg, TRUE, ctx->file_pkg_node); + ctx->file_pkg_node = &file->pkg_files_list.next; + + if (strncmp(file->filename, ".keep_", 6) == 0) + return 0; + + r = apk_archive_entry_extract(ae, NULL); + } else { + if (name.ptr[name.len-1] == '/') + name.len--; + apk_db_dir_get(db, name)->mode = 0777 & ae->mode; + } + + return r; +} + +static void apk_db_purge_pkg(struct apk_database *db, + struct apk_package *pkg) +{ + struct apk_db_file *file; + struct hlist_node *c, *n; + char fn[1024]; + + hlist_for_each_entry_safe(file, c, n, &pkg->owned_files, pkg_files_list) { + file->owner = NULL; + snprintf(fn, sizeof(fn), "%s/%s", + file->dir->dirname, + file->filename); + unlink(fn); + + apk_db_dir_unref(db, file->dir); + hlist_del(c, &pkg->owned_files.first); + + db->installed.stats.files--; + } + db->installed.stats.packages--; +} + +int apk_db_install_pkg(struct apk_database *db, + struct apk_package *oldpkg, + struct apk_package *newpkg) +{ + struct install_ctx ctx; + csum_t csum; + char file[256]; + pthread_t tid = 0; + int fd, r; + + if (chdir(db->root) < 0) + return errno; + + /* Purge the old package if there */ + if (oldpkg != NULL) { + if (newpkg == NULL) { + r = apk_pkg_run_script(oldpkg, db->root, + APK_SCRIPT_PRE_DEINSTALL); + if (r != 0) + return r; + } + apk_db_purge_pkg(db, oldpkg); + if (newpkg == NULL) { + apk_pkg_run_script(oldpkg, db->root, + APK_SCRIPT_POST_DEINSTALL); + return 0; + } + } + + /* Install the new stuff */ + snprintf(file, sizeof(file), + "%s%s-%s.apk", + db->repos[0].url, newpkg->name->name, newpkg->version); + + fd = open(file, O_RDONLY); + if (fd < 0) + return errno; + + fcntl(fd, F_SETFD, FD_CLOEXEC); + + tid = apk_checksum_and_tee(&fd, csum); + if (tid < 0) + goto err_close; + + ctx = (struct install_ctx) { + .db = db, + .pkg = newpkg, + .script = (oldpkg == NULL) ? + APK_SCRIPT_PRE_INSTALL : APK_SCRIPT_PRE_UPGRADE, + }; + if (apk_parse_tar_gz(fd, (apk_archive_entry_parser) + apk_db_install_archive_entry, &ctx) != 0) + goto err_close; + + pthread_join(tid, NULL); + close(fd); + + db->installed.stats.packages++; + hlist_add_head(&newpkg->installed_pkgs_list, &db->installed.packages); + + if (memcmp(csum, newpkg->csum, sizeof(csum)) != 0) + apk_warning("%s-%s: checksum does not match", + newpkg->name->name, newpkg->version); + + r = apk_pkg_run_script(newpkg, db->root, + (oldpkg == NULL) ? + APK_SCRIPT_POST_INSTALL : APK_SCRIPT_POST_UPGRADE); + if (r != 0) + apk_error("%s-%s: Failed to execute post-install/upgrade script", + newpkg->name->name, newpkg->version); + return r; + +err_close: + close(fd); + if (tid != 0) + pthread_join(tid, NULL); + return -1; +} diff --git a/src/del.c b/src/del.c new file mode 100644 index 0000000..c4d7539 --- /dev/null +++ b/src/del.c @@ -0,0 +1,53 @@ +/* del.c - Alpine Package Keeper (APK) + * + * Copyright (C) 2005-2008 Natanael Copa + * Copyright (C) 2008 Timo Teräs + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. See http://www.gnu.org/ for details. + */ + +#include +#include "apk_applet.h" +#include "apk_database.h" + +static int del_main(int argc, char **argv) +{ + struct apk_database db; + int i, j; + + apk_db_init(&db, "/home/fabled/tmproot/"); + apk_db_read_config(&db); + + if (db.world == NULL) + goto out; + + for (i = 0; i < argc; i++) { + for (j = 0; j < db.world->num; j++) { + if (strcmp(db.world->item[j].name->name, + argv[i]) == 0) { + db.world->item[j] = + db.world->item[db.world->num-1]; + db.world = + apk_dependency_array_resize(db.world, db.world->num-1); + } + } + } + + apk_db_recalculate_and_commit(&db); +out: + apk_db_free(&db); + + return 0; +} + +static struct apk_applet apk_del = { + .name = "del", + .usage = "apkname...", + .main = del_main, +}; + +APK_DEFINE_APPLET(apk_del); + diff --git a/src/hash.c b/src/hash.c new file mode 100644 index 0000000..447601e --- /dev/null +++ b/src/hash.c @@ -0,0 +1,97 @@ +/* hash.c - Alpine Package Keeper (APK) + * + * Copyright (C) 2005-2008 Natanael Copa + * Copyright (C) 2008 Timo Teräs + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. See http://www.gnu.org/ for details. + */ + +#include "apk_defines.h" +#include "apk_hash.h" + +unsigned long apk_hash_string(const char *str) +{ + unsigned long hash = 5381; + int c; + + while ((c = *str++) != 0) + hash = hash * 33 + c; + + return hash; +} + +unsigned long apk_hash_csum(const void *ptr) +{ + return *(const unsigned long *) ptr; +} + +void apk_hash_init(struct apk_hash *h, const struct apk_hash_ops *ops, + int num_buckets) +{ + h->ops = ops; + h->buckets = apk_hash_array_resize(NULL, num_buckets); + h->num_items = 0; +} + +void apk_hash_free(struct apk_hash *h) +{ + apk_hash_foreach(h, (apk_hash_enumerator_f) h->ops->delete_item, NULL); + free(h->buckets); +} + +int apk_hash_foreach(struct apk_hash *h, apk_hash_enumerator_f e, void *ctx) +{ + apk_hash_node *pos, *n; + ptrdiff_t offset = h->ops->node_offset; + int i, r; + + for (i = 0; i < h->buckets->num; i++) { + hlist_for_each_safe(pos, n, &h->buckets->item[i]) { + r = e(((void *) pos) - offset, ctx); + if (r != 0) + return r; + } + } + + return 0; +} + +apk_hash_item apk_hash_get(struct apk_hash *h, apk_hash_key key) +{ + ptrdiff_t offset = h->ops->node_offset; + unsigned long hash; + apk_hash_node *pos; + apk_hash_item item; + apk_hash_key itemkey; + + hash = h->ops->hash_key(key) % h->buckets->num; + hlist_for_each(pos, &h->buckets->item[hash]) { + item = ((void *) pos) - offset; + itemkey = h->ops->get_key(item); + if (h->ops->compare(key, itemkey) == 0) + return item; + } + + return NULL; +} + +void apk_hash_insert(struct apk_hash *h, apk_hash_item item) +{ + apk_hash_key key; + unsigned long hash; + apk_hash_node *node; + + key = h->ops->get_key(item); + hash = h->ops->hash_key(key) % h->buckets->num; + node = (apk_hash_node *) (item + h->ops->node_offset); + hlist_add_head(node, &h->buckets->item[hash]); + h->num_items++; +} + +void apk_hash_delete(struct apk_hash *h, apk_hash_key key) +{ +} + diff --git a/src/index.c b/src/index.c new file mode 100644 index 0000000..4234002 --- /dev/null +++ b/src/index.c @@ -0,0 +1,70 @@ +/* index.c - Alpine Package Keeper (APK) + * + * Copyright (C) 2005-2008 Natanael Copa + * Copyright (C) 2008 Timo Teräs + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. See http://www.gnu.org/ for details. + */ + +#include +#include + +#include "apk_applet.h" +#include "apk_database.h" + +struct counts { + int total; + int unsatisfied; +}; + +static int warn_if_no_providers(apk_hash_item item, void *ctx) +{ + struct counts *counts = (struct counts *) ctx; + struct apk_name *name = (struct apk_name *) item; + + if (name->pkgs == NULL) { + if (++counts->unsatisfied < 10) + apk_warning("No provider for dependency '%s'", + name->name); + else if (counts->unsatisfied == 10) + apk_warning("Too many unsatisfiable dependencies, " + "not reporting the rest."); + } + counts->total++; + + return 0; +} + +static int index_main(int argc, char **argv) +{ + struct apk_database db; + struct counts counts = {0,0}; + int i; + + apk_db_init(&db, NULL); + for (i = 0; i < argc; i++) + apk_db_pkg_add_file(&db, argv[i]); + apk_db_index_write(&db, STDOUT_FILENO); + apk_hash_foreach(&db.available.names, warn_if_no_providers, &counts); + apk_db_free(&db); + + if (counts.unsatisfied != 0) + apk_warning("Total of %d unsatisfiable package " + "names. Your repository maybe broken.", + counts.unsatisfied); + apk_message("Index has %d packages", counts.total); + + return 0; +} + +static struct apk_applet apk_index = { + .name = "index", + .usage = "apkname...", + .main = index_main, +}; + +APK_DEFINE_APPLET(apk_index); + diff --git a/src/md5.c b/src/md5.c new file mode 100644 index 0000000..e165724 --- /dev/null +++ b/src/md5.c @@ -0,0 +1,488 @@ +/* md5.c - Compute MD5 checksum of files or strings according to the + * definition of MD5 in RFC 1321 from April 1992. + * Copyright (C) 1995-1999 Free Software Foundation, Inc. + * + * 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, 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. + */ + +/* Written by Ulrich Drepper */ +/* Hacked to work with BusyBox by Alfred M. Szmidt */ + +/* Sucked directly into ipkg since the md5sum functions aren't in libbb + Dropped a few functions since ipkg only needs md5_stream. + Got rid of evil, twisted defines of FALSE=1 and TRUE=0 + 6 March 2002 Carl Worth +*/ + +/* + * June 29, 2001 Manuel Novoa III + * + * Added MD5SUM_SIZE_VS_SPEED configuration option. + * + * Current valid values, with data from my system for comparison, are: + * (using uClibc and running on linux-2.4.4.tar.bz2) + * user times (sec) text size (386) + * 0 (fastest) 1.1 6144 + * 1 1.4 5392 + * 2 3.0 5088 + * 3 (smallest) 5.1 4912 + */ + +#define MD5SUM_SIZE_VS_SPEED 0 + +/**********************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#if defined HAVE_LIMITS_H +# include +#endif + +#include "md5.h" + +/* Handle endian-ness */ +#if __BYTE_ORDER == __LITTLE_ENDIAN +#define SWAP(n) (n) +#else +#define SWAP(n) ((n << 24) | ((n&65280)<<8) | ((n&16711680)>>8) | (n>>24)) +#endif + +#if MD5SUM_SIZE_VS_SPEED == 0 +/* This array contains the bytes used to pad the buffer to the next + 64-byte boundary. (RFC 1321, 3.1: Step 1) */ +static const unsigned char fillbuf[64] = { 0x80, 0 /* , 0, 0, ... */ }; +#endif + +/* These are the four functions used in the four steps of the MD5 algorithm + and defined in the RFC 1321. The first function is a little bit optimized + (as found in Colin Plumbs public domain implementation). */ +/* #define FF(b, c, d) ((b & c) | (~b & d)) */ +#define FF(b, c, d) (d ^ (b & (c ^ d))) +#define FG(b, c, d) FF (d, b, c) +#define FH(b, c, d) (b ^ c ^ d) +#define FI(b, c, d) (c ^ (b | ~d)) + +#define CYCLIC(w, s) (w = (w << s) | (w >> (32 - s))) + +/* Process LEN bytes of BUFFER, accumulating context into CTX. + It is assumed that LEN % 64 == 0. */ +static void md5_process_block(struct md5_ctx *ctx, + const void *buffer, size_t len) +{ + md5_uint32 correct_words[16]; + const md5_uint32 *words = buffer; + size_t nwords = len / sizeof(md5_uint32); + const md5_uint32 *endp = words + nwords; +#if MD5SUM_SIZE_VS_SPEED > 0 + static const md5_uint32 C_array[] = { + /* round 1 */ + 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, + 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501, + 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, + 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, + /* round 2 */ + 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, + 0xd62f105d, 0x2441453, 0xd8a1e681, 0xe7d3fbc8, + 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, + 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a, + /* round 3 */ + 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, + 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, + 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x4881d05, + 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, + /* round 4 */ + 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, + 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1, + 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, + 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391 + }; + + static const char P_array[] = { +#if MD5SUM_SIZE_VS_SPEED > 1 + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, /* 1 */ +#endif + 1, 6, 11, 0, 5, 10, 15, 4, 9, 14, 3, 8, 13, 2, 7, 12, /* 2 */ + 5, 8, 11, 14, 1, 4, 7, 10, 13, 0, 3, 6, 9, 12, 15, 2, /* 3 */ + 0, 7, 14, 5, 12, 3, 10, 1, 8, 15, 6, 13, 4, 11, 2, 9 /* 4 */ + }; + +#if MD5SUM_SIZE_VS_SPEED > 1 + static const char S_array[] = { + 7, 12, 17, 22, + 5, 9, 14, 20, + 4, 11, 16, 23, + 6, 10, 15, 21 + }; +#endif +#endif + + md5_uint32 A = ctx->A; + md5_uint32 B = ctx->B; + md5_uint32 C = ctx->C; + md5_uint32 D = ctx->D; + + /* First increment the byte count. RFC 1321 specifies the possible + length of the file up to 2^64 bits. Here we only compute the + number of bytes. Do a double word increment. */ + ctx->total[0] += len; + if (ctx->total[0] < len) + ++ctx->total[1]; + + /* Process all bytes in the buffer with 64 bytes in each round of + the loop. */ + while (words < endp) { + md5_uint32 *cwp = correct_words; + md5_uint32 A_save = A; + md5_uint32 B_save = B; + md5_uint32 C_save = C; + md5_uint32 D_save = D; + +#if MD5SUM_SIZE_VS_SPEED > 1 + const md5_uint32 *pc; + const char *pp; + const char *ps; + int i; + md5_uint32 temp; + + for ( i=0 ; i < 16 ; i++ ) { + cwp[i] = SWAP(words[i]); + } + words += 16; + +#if MD5SUM_SIZE_VS_SPEED > 2 + pc = C_array; pp = P_array; ps = S_array - 4; + + for ( i = 0 ; i < 64 ; i++ ) { + if ((i&0x0f) == 0) ps += 4; + temp = A; + switch (i>>4) { + case 0: + temp += FF(B,C,D); + break; + case 1: + temp += FG(B,C,D); + break; + case 2: + temp += FH(B,C,D); + break; + case 3: + temp += FI(B,C,D); + } + temp += cwp[(int)(*pp++)] + *pc++; + temp = CYCLIC(temp, ps[i&3]); + temp += B; + A = D; D = C; C = B; B = temp; + } +#else + pc = C_array; pp = P_array; ps = S_array; + + for ( i = 0 ; i < 16 ; i++ ) { + temp = A + FF(B,C,D) + cwp[(int)(*pp++)] + *pc++; + temp = CYCLIC (temp, ps[i&3]); + temp += B; + A = D; D = C; C = B; B = temp; + } + + ps += 4; + for ( i = 0 ; i < 16 ; i++ ) { + temp = A + FG(B,C,D) + cwp[(int)(*pp++)] + *pc++; + temp = CYCLIC (temp, ps[i&3]); + temp += B; + A = D; D = C; C = B; B = temp; + } + ps += 4; + for ( i = 0 ; i < 16 ; i++ ) { + temp = A + FH(B,C,D) + cwp[(int)(*pp++)] + *pc++; + temp = CYCLIC (temp, ps[i&3]); + temp += B; + A = D; D = C; C = B; B = temp; + } + ps += 4; + for ( i = 0 ; i < 16 ; i++ ) { + temp = A + FI(B,C,D) + cwp[(int)(*pp++)] + *pc++; + temp = CYCLIC (temp, ps[i&3]); + temp += B; + A = D; D = C; C = B; B = temp; + } + +#endif +#else + /* First round: using the given function, the context and a constant + the next context is computed. Because the algorithms processing + unit is a 32-bit word and it is determined to work on words in + little endian byte order we perhaps have to change the byte order + before the computation. To reduce the work for the next steps + we store the swapped words in the array CORRECT_WORDS. */ + +#define OP(a, b, c, d, s, T) \ + do \ + { \ + a += FF (b, c, d) + (*cwp++ = SWAP (*words)) + T; \ + ++words; \ + CYCLIC (a, s); \ + a += b; \ + } \ + while (0) + + /* Before we start, one word to the strange constants. + They are defined in RFC 1321 as + + T[i] = (int) (4294967296.0 * fabs (sin (i))), i=1..64 + */ + +#if MD5SUM_SIZE_VS_SPEED == 1 + const md5_uint32 *pc; + const char *pp; + int i; +#endif + + /* Round 1. */ +#if MD5SUM_SIZE_VS_SPEED == 1 + pc = C_array; + for ( i=0 ; i < 4 ; i++ ) { + OP(A, B, C, D, 7, *pc++); + OP(D, A, B, C, 12, *pc++); + OP(C, D, A, B, 17, *pc++); + OP(B, C, D, A, 22, *pc++); + } +#else + OP(A, B, C, D, 7, 0xd76aa478); + OP(D, A, B, C, 12, 0xe8c7b756); + OP(C, D, A, B, 17, 0x242070db); + OP(B, C, D, A, 22, 0xc1bdceee); + OP(A, B, C, D, 7, 0xf57c0faf); + OP(D, A, B, C, 12, 0x4787c62a); + OP(C, D, A, B, 17, 0xa8304613); + OP(B, C, D, A, 22, 0xfd469501); + OP(A, B, C, D, 7, 0x698098d8); + OP(D, A, B, C, 12, 0x8b44f7af); + OP(C, D, A, B, 17, 0xffff5bb1); + OP(B, C, D, A, 22, 0x895cd7be); + OP(A, B, C, D, 7, 0x6b901122); + OP(D, A, B, C, 12, 0xfd987193); + OP(C, D, A, B, 17, 0xa679438e); + OP(B, C, D, A, 22, 0x49b40821); +#endif + + /* For the second to fourth round we have the possibly swapped words + in CORRECT_WORDS. Redefine the macro to take an additional first + argument specifying the function to use. */ +#undef OP +#define OP(f, a, b, c, d, k, s, T) \ + do \ + { \ + a += f (b, c, d) + correct_words[k] + T; \ + CYCLIC (a, s); \ + a += b; \ + } \ + while (0) + + /* Round 2. */ +#if MD5SUM_SIZE_VS_SPEED == 1 + pp = P_array; + for ( i=0 ; i < 4 ; i++ ) { + OP(FG, A, B, C, D, (int)(*pp++), 5, *pc++); + OP(FG, D, A, B, C, (int)(*pp++), 9, *pc++); + OP(FG, C, D, A, B, (int)(*pp++), 14, *pc++); + OP(FG, B, C, D, A, (int)(*pp++), 20, *pc++); + } +#else + OP(FG, A, B, C, D, 1, 5, 0xf61e2562); + OP(FG, D, A, B, C, 6, 9, 0xc040b340); + OP(FG, C, D, A, B, 11, 14, 0x265e5a51); + OP(FG, B, C, D, A, 0, 20, 0xe9b6c7aa); + OP(FG, A, B, C, D, 5, 5, 0xd62f105d); + OP(FG, D, A, B, C, 10, 9, 0x02441453); + OP(FG, C, D, A, B, 15, 14, 0xd8a1e681); + OP(FG, B, C, D, A, 4, 20, 0xe7d3fbc8); + OP(FG, A, B, C, D, 9, 5, 0x21e1cde6); + OP(FG, D, A, B, C, 14, 9, 0xc33707d6); + OP(FG, C, D, A, B, 3, 14, 0xf4d50d87); + OP(FG, B, C, D, A, 8, 20, 0x455a14ed); + OP(FG, A, B, C, D, 13, 5, 0xa9e3e905); + OP(FG, D, A, B, C, 2, 9, 0xfcefa3f8); + OP(FG, C, D, A, B, 7, 14, 0x676f02d9); + OP(FG, B, C, D, A, 12, 20, 0x8d2a4c8a); +#endif + + /* Round 3. */ +#if MD5SUM_SIZE_VS_SPEED == 1 + for ( i=0 ; i < 4 ; i++ ) { + OP(FH, A, B, C, D, (int)(*pp++), 4, *pc++); + OP(FH, D, A, B, C, (int)(*pp++), 11, *pc++); + OP(FH, C, D, A, B, (int)(*pp++), 16, *pc++); + OP(FH, B, C, D, A, (int)(*pp++), 23, *pc++); + } +#else + OP(FH, A, B, C, D, 5, 4, 0xfffa3942); + OP(FH, D, A, B, C, 8, 11, 0x8771f681); + OP(FH, C, D, A, B, 11, 16, 0x6d9d6122); + OP(FH, B, C, D, A, 14, 23, 0xfde5380c); + OP(FH, A, B, C, D, 1, 4, 0xa4beea44); + OP(FH, D, A, B, C, 4, 11, 0x4bdecfa9); + OP(FH, C, D, A, B, 7, 16, 0xf6bb4b60); + OP(FH, B, C, D, A, 10, 23, 0xbebfbc70); + OP(FH, A, B, C, D, 13, 4, 0x289b7ec6); + OP(FH, D, A, B, C, 0, 11, 0xeaa127fa); + OP(FH, C, D, A, B, 3, 16, 0xd4ef3085); + OP(FH, B, C, D, A, 6, 23, 0x04881d05); + OP(FH, A, B, C, D, 9, 4, 0xd9d4d039); + OP(FH, D, A, B, C, 12, 11, 0xe6db99e5); + OP(FH, C, D, A, B, 15, 16, 0x1fa27cf8); + OP(FH, B, C, D, A, 2, 23, 0xc4ac5665); +#endif + + /* Round 4. */ +#if MD5SUM_SIZE_VS_SPEED == 1 + for ( i=0 ; i < 4 ; i++ ) { + OP(FI, A, B, C, D, (int)(*pp++), 6, *pc++); + OP(FI, D, A, B, C, (int)(*pp++), 10, *pc++); + OP(FI, C, D, A, B, (int)(*pp++), 15, *pc++); + OP(FI, B, C, D, A, (int)(*pp++), 21, *pc++); + } +#else + OP(FI, A, B, C, D, 0, 6, 0xf4292244); + OP(FI, D, A, B, C, 7, 10, 0x432aff97); + OP(FI, C, D, A, B, 14, 15, 0xab9423a7); + OP(FI, B, C, D, A, 5, 21, 0xfc93a039); + OP(FI, A, B, C, D, 12, 6, 0x655b59c3); + OP(FI, D, A, B, C, 3, 10, 0x8f0ccc92); + OP(FI, C, D, A, B, 10, 15, 0xffeff47d); + OP(FI, B, C, D, A, 1, 21, 0x85845dd1); + OP(FI, A, B, C, D, 8, 6, 0x6fa87e4f); + OP(FI, D, A, B, C, 15, 10, 0xfe2ce6e0); + OP(FI, C, D, A, B, 6, 15, 0xa3014314); + OP(FI, B, C, D, A, 13, 21, 0x4e0811a1); + OP(FI, A, B, C, D, 4, 6, 0xf7537e82); + OP(FI, D, A, B, C, 11, 10, 0xbd3af235); + OP(FI, C, D, A, B, 2, 15, 0x2ad7d2bb); + OP(FI, B, C, D, A, 9, 21, 0xeb86d391); +#endif +#endif + + /* Add the starting values of the context. */ + A += A_save; + B += B_save; + C += C_save; + D += D_save; + } + + /* Put checksum in context given as argument. */ + ctx->A = A; + ctx->B = B; + ctx->C = C; + ctx->D = D; +} + +/* Initialize structure containing state of computation. + (RFC 1321, 3.3: Step 3) */ +void md5_init(struct md5_ctx *ctx) +{ + ctx->A = 0x67452301; + ctx->B = 0xefcdab89; + ctx->C = 0x98badcfe; + ctx->D = 0x10325476; + + ctx->total[0] = ctx->total[1] = 0; + ctx->buflen = 0; +} + +void md5_process(struct md5_ctx *ctx, const void *buffer, size_t len) +{ + /* When we already have some bits in our internal buffer concatenate + both inputs first. */ + if (ctx->buflen != 0) { + size_t left_over = ctx->buflen; + size_t add = 128 - left_over > len ? len : 128 - left_over; + + memcpy(&ctx->buffer[left_over], buffer, add); + ctx->buflen += add; + + if (left_over + add > 64) { + md5_process_block(ctx, ctx->buffer, (left_over + add) & ~63); + /* The regions in the following copy operation cannot overlap. */ + memcpy(ctx->buffer, &ctx->buffer[(left_over + add) & ~63], + (left_over + add) & 63); + ctx->buflen = (left_over + add) & 63; + } + + buffer = (const char *) buffer + add; + len -= add; + } + + /* Process available complete blocks. */ + if (len > 64) { + md5_process_block(ctx, buffer, len & ~63); + buffer = (const char *) buffer + (len & ~63); + len &= 63; + } + + /* Move remaining bytes in internal buffer. */ + if (len > 0) { + memcpy(ctx->buffer, buffer, len); + ctx->buflen = len; + } +} + +/* Process the remaining bytes in the internal buffer and the usual + prolog according to the standard and write the result to RESBUF. + + IMPORTANT: On some systems it is required that RESBUF is correctly + aligned for a 32 bits value. */ +void md5_finish(struct md5_ctx *ctx, md5sum_t resbuf) +{ + /* Take yet unprocessed bytes into account. */ + md5_uint32 bytes = ctx->buflen; + size_t pad; + + /* Now count remaining bytes. */ + ctx->total[0] += bytes; + if (ctx->total[0] < bytes) + ++ctx->total[1]; + + pad = bytes >= 56 ? 64 + 56 - bytes : 56 - bytes; +#if MD5SUM_SIZE_VS_SPEED > 0 + memset(&ctx->buffer[bytes], 0, pad); + ctx->buffer[bytes] = 0x80; +#else + memcpy(&ctx->buffer[bytes], fillbuf, pad); +#endif + + /* Put the 64-bit file length in *bits* at the end of the buffer. */ + *(md5_uint32 *) & ctx->buffer[bytes + pad] = SWAP(ctx->total[0] << 3); + *(md5_uint32 *) & ctx->buffer[bytes + pad + 4] = + SWAP( ((ctx->total[1] << 3) | (ctx->total[0] >> 29)) ); + + /* Process last bytes. */ + md5_process_block(ctx, ctx->buffer, bytes + pad + 8); + + /* Put result from CTX in first 16 bytes following RESBUF. The result is + always in little endian byte order, so that a byte-wise output yields + to the wanted ASCII representation of the message digest. + + IMPORTANT: On some systems it is required that RESBUF is correctly + aligned for a 32 bits value. */ + ((md5_uint32 *) resbuf)[0] = SWAP(ctx->A); + ((md5_uint32 *) resbuf)[1] = SWAP(ctx->B); + ((md5_uint32 *) resbuf)[2] = SWAP(ctx->C); + ((md5_uint32 *) resbuf)[3] = SWAP(ctx->D); +} + diff --git a/src/md5.h b/src/md5.h new file mode 100644 index 0000000..6996c4d --- /dev/null +++ b/src/md5.h @@ -0,0 +1,60 @@ +/* md5.h - Compute MD5 checksum of files or strings according to the + * definition of MD5 in RFC 1321 from April 1992. + * Copyright (C) 1995-1999 Free Software Foundation, Inc. + * + * 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, 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 MD5_H +#define MD5_H + +#include + +typedef unsigned char md5sum_t[16]; +typedef u_int32_t md5_uint32; + +struct md5_ctx +{ + md5_uint32 A; + md5_uint32 B; + md5_uint32 C; + md5_uint32 D; + + md5_uint32 total[2]; + md5_uint32 buflen; + char buffer[128]; +}; + +/* Initialize structure containing state of computation. + (RFC 1321, 3.3: Step 3) */ +void md5_init(struct md5_ctx *ctx); + +/* Starting with the result of former calls of this function (or the + initialization function update the context for the next LEN bytes + starting at BUFFER. + It is NOT required that LEN is a multiple of 64. */ +void md5_process(struct md5_ctx *ctx, const void *buffer, size_t len); + +/* Process the remaining bytes in the buffer and put result from CTX + in first 16 bytes following RESBUF. The result is always in little + endian byte order, so that a byte-wise output yields to the wanted + ASCII representation of the message digest. + + IMPORTANT: On some systems it is required that RESBUF is correctly + aligned for a 32 bits value. */ +void md5_finish(struct md5_ctx *ctx, md5sum_t resbuf); + +#endif + diff --git a/src/package.c b/src/package.c new file mode 100644 index 0000000..88aef70 --- /dev/null +++ b/src/package.c @@ -0,0 +1,483 @@ +/* package.c - Alpine Package Keeper (APK) + * + * Copyright (C) 2005-2008 Natanael Copa + * Copyright (C) 2008 Timo Teräs + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. See http://www.gnu.org/ for details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "apk_defines.h" +#include "apk_archive.h" +#include "apk_package.h" +#include "apk_database.h" +#include "apk_state.h" + +int apk_pkg_parse_name(apk_blob_t apkname, + apk_blob_t *name, + apk_blob_t *version) +{ + int i, dash = 0; + + for (i = apkname.len - 2; i >= 0; i--) { + if (apkname.ptr[i] != '-') + continue; + if (isdigit(apkname.ptr[i+1])) + break; + if (++dash >= 2) + return -1; + } + if (name != NULL) + *name = APK_BLOB_PTR_LEN(apkname.ptr, i); + if (version != NULL) + *version = APK_BLOB_PTR_PTR(&apkname.ptr[i+1], + &apkname.ptr[apkname.len-1]); + + return 0; +} + +static char *trim(apk_blob_t str) +{ + if (str.ptr == NULL || str.len < 1) + return NULL; + + if (str.ptr[str.len-2] == '\n') + str.ptr[str.len-2] = 0; + + return str.ptr; +} + +static void parse_depend(struct apk_database *db, + struct apk_dependency_array **depends, + apk_blob_t blob) +{ + struct apk_dependency *dep; + struct apk_name *name; + char *cname; + + while (blob.len && blob.ptr[0] == ' ') + blob.ptr++, blob.len--; + while (blob.len && (blob.ptr[blob.len-1] == ' ' || + blob.ptr[blob.len-1] == 0)) + blob.len--; + + if (blob.len == 0) + return; + + cname = apk_blob_cstr(blob); + name = apk_db_get_name(db, cname); + free(cname); + + dep = apk_dependency_array_add(depends); + *dep = (struct apk_dependency){ + .prefer_upgrade = 0, + .version_mask = 0, + .name = name, + .version = NULL, + }; +} + +int apk_deps_add(struct apk_dependency_array **depends, + struct apk_dependency *dep) +{ + struct apk_dependency_array *deps = *depends; + int i; + + if (deps != NULL) { + for (i = 0; i < deps->num; i++) { + if (deps->item[i].name == dep->name) + return 0; + } + } + + *apk_dependency_array_add(depends) = *dep; + return 0; +} +void apk_deps_parse(struct apk_database *db, + struct apk_dependency_array **depends, + apk_blob_t blob) +{ + char *start; + int i; + + start = blob.ptr; + for (i = 0; i < blob.len; i++) { + if (blob.ptr[i] != ',' && blob.ptr[i] != '\n') + continue; + + parse_depend(db, depends, + APK_BLOB_PTR_PTR(start, &blob.ptr[i-1])); + start = &blob.ptr[i+1]; + } + parse_depend(db, depends, + APK_BLOB_PTR_PTR(start, &blob.ptr[i-1])); +} + +int apk_deps_format(char *buf, int size, + struct apk_dependency_array *depends) +{ + int i, n = 0; + + if (depends == NULL) + return 0; + + for (i = 0; i < depends->num - 1; i++) + n += snprintf(&buf[n], size-n, + "%s, ", + depends->item[i].name->name); + n += snprintf(&buf[n], size-n, + "%s\n", + depends->item[i].name->name); + return n; +} + +static const char *script_types[] = { + [APK_SCRIPT_PRE_INSTALL] = "pre-install", + [APK_SCRIPT_POST_INSTALL] = "post-install", + [APK_SCRIPT_PRE_DEINSTALL] = "pre-deinstall", + [APK_SCRIPT_POST_DEINSTALL] = "post-deinstall", + [APK_SCRIPT_PRE_UPGRADE] = "pre-upgrade", + [APK_SCRIPT_POST_UPGRADE] = "post-upgrade", +}; + +int apk_script_type(const char *name) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(script_types); i++) + if (script_types[i] && + strcmp(script_types[i], name) == 0) + return i; + + return -1; +} + +struct read_info_ctx { + struct apk_database *db; + struct apk_package *pkg; + int has_install; +}; + +static int read_info_entry(struct apk_archive_entry *ae, void *ctx) +{ + struct read_info_ctx *ri = (struct read_info_ctx *) ctx; + struct apk_database *db = ri->db; + struct apk_package *pkg = ri->pkg; + const int bsize = 4 * 1024; + apk_blob_t name, version; + char *slash, *str; + + if (strncmp(ae->name, "var/db/apk/", 11) != 0) { + pkg->installed_size += (ae->size + bsize - 1) & ~(bsize - 1); + return 0; + } + + if (!S_ISREG(ae->mode)) + return 0; + + slash = strchr(&ae->name[11], '/'); + if (slash == NULL) + return 0; + + if (apk_pkg_parse_name(APK_BLOB_PTR_PTR(&ae->name[11], slash-1), + &name, &version) < 0) + return -1; + + if (pkg->name == NULL) { + str = apk_blob_cstr(name); + pkg->name = apk_db_get_name(db, str); + free(str); + } + if (pkg->version == NULL) + pkg->version = apk_blob_cstr(version); + + if (strcmp(slash, "/DEPEND") == 0) { + apk_blob_t blob = apk_archive_entry_read(ae); + if (blob.ptr) { + apk_deps_parse(db, &pkg->depends, blob); + free(blob.ptr); + } + } else if (strcmp(slash, "/DESC") == 0) { + pkg->description = trim(apk_archive_entry_read(ae)); + } else if (strcmp(slash, "/WWW") == 0) { + pkg->url = trim(apk_archive_entry_read(ae)); + } else if (strcmp(slash, "/LICENSE") == 0) { + pkg->license = trim(apk_archive_entry_read(ae)); + } else if (apk_script_type(slash+1) == APK_SCRIPT_POST_INSTALL || + apk_script_type(slash+1) == APK_SCRIPT_PRE_INSTALL) + ri->has_install = 1; + + return 0; +} + +struct apk_package *apk_pkg_read(struct apk_database *db, const char *file) +{ + struct read_info_ctx ctx; + struct stat st; + pthread_t tid; + int fd; + + ctx.pkg = calloc(1, sizeof(struct apk_package)); + if (ctx.pkg == NULL) + return NULL; + + fd = open(file, O_RDONLY); + if (fd < 0) + goto err; + + fstat(fd, &st); + fcntl(fd, F_SETFD, FD_CLOEXEC); + + tid = apk_checksum_and_tee(&fd, ctx.pkg->csum); + if (fd < 0) + goto err; + + ctx.db = db; + ctx.pkg->size = st.st_size; + ctx.has_install = 0; + if (apk_parse_tar_gz(fd, read_info_entry, &ctx) != 0) { + pthread_join(tid, NULL); + goto err; + } + pthread_join(tid, NULL); + + if (ctx.pkg->name == NULL) + goto err; + + close(fd); + + /* Add implicit busybox dependency if there is scripts */ + if (ctx.has_install) { + struct apk_dependency dep = { + .name = apk_db_get_name(db, "busybox"), + }; + apk_deps_add(&ctx.pkg->depends, &dep); + } + + return ctx.pkg; +err: + apk_pkg_free(ctx.pkg); + return NULL; +} + +void apk_pkg_free(struct apk_package *pkg) +{ + struct apk_script *script; + struct hlist_node *c, *n; + + if (pkg == NULL) + return; + + hlist_for_each_entry_safe(script, c, n, &pkg->scripts, script_list) + free(script); + + if (pkg->version) + free(pkg->version); + if (pkg->url) + free(pkg->url); + if (pkg->description) + free(pkg->description); + if (pkg->license) + free(pkg->license); + free(pkg); +} + +int apk_pkg_get_state(struct apk_package *pkg) +{ + if (hlist_hashed(&pkg->installed_pkgs_list)) + return APK_STATE_INSTALL; + return APK_STATE_NO_INSTALL; +} + +int apk_pkg_add_script(struct apk_package *pkg, int fd, + unsigned int type, unsigned int size) +{ + struct apk_script *script; + int r; + + script = malloc(sizeof(struct apk_script) + size); + script->type = type; + script->size = size; + r = read(fd, script->script, size); + if (r < 0) { + free(script); + return r; + } + + hlist_add_head(&script->script_list, &pkg->scripts); + return r; +} + +int apk_pkg_run_script(struct apk_package *pkg, const char *root, + unsigned int type) +{ + struct apk_script *script; + struct hlist_node *c; + int fd, status; + pid_t pid; + char fn[1024]; + + hlist_for_each_entry(script, c, &pkg->scripts, script_list) { + if (script->type != type) + continue; + + snprintf(fn, sizeof(fn), + "tmp/%s-%s.%s", + pkg->name->name, pkg->version, + script_types[script->type]); + fd = creat(fn, 0777); + if (fd < 0) + return fd; + write(fd, script->script, script->size); + close(fd); + + apk_message("Executing %s", &fn[4]); + + pid = fork(); + if (pid == -1) + return -1; + if (pid == 0) { + chroot(root); + fn[2] = '.'; + execl(&fn[2], script_types[script->type], + pkg->version, "", NULL); + exit(1); + } + waitpid(pid, &status, 0); + unlink(fn); + if (WIFEXITED(status)) + return WEXITSTATUS(status); + return -1; + } + + /* FIXME: Remove this ugly kludge */ + if (strcmp(pkg->name->name, "busybox") == 0 && + type == APK_SCRIPT_POST_INSTALL) { + apk_message("Create busybox links"); + + pid = fork(); + if (pid == -1) + return -1; + if (pid == 0) { + chroot(root); + execl("/bin/busybox", "busybox", "--install", "-s", NULL); + exit(1); + } + waitpid(pid, &status, 0); + if (WIFEXITED(status)) + return WEXITSTATUS(status); + return -1; + + } + + return 0; +} + +static int parse_index_line(struct apk_database *db, struct apk_package *pkg, + apk_blob_t blob) +{ + apk_blob_t d; + char *str; + + if (blob.len < 2 || blob.ptr[1] != ':') + return -1; + + d = APK_BLOB_PTR_LEN(blob.ptr+2, blob.len-2); + switch (blob.ptr[0]) { + case 'P': + str = apk_blob_cstr(d); + pkg->name = apk_db_get_name(db, str); + free(str); + break; + case 'V': + pkg->version = apk_blob_cstr(d); + break; + case 'T': + pkg->description = apk_blob_cstr(d); + break; + case 'U': + pkg->url = apk_blob_cstr(d); + break; + case 'L': + pkg->license = apk_blob_cstr(d); + break; + case 'D': + apk_deps_parse(db, &pkg->depends, d); + break; + case 'C': + apk_hexdump_parse(APK_BLOB_BUF(pkg->csum), d); + break; + case 'S': + pkg->size = apk_blob_uint(d, 10); + break; + case 'I': + pkg->installed_size = apk_blob_uint(d, 10); + break; + } + return 0; +} + +struct apk_package *apk_pkg_parse_index_entry(struct apk_database *db, apk_blob_t blob) +{ + struct apk_package *pkg; + apk_blob_t l, r; + + pkg = calloc(1, sizeof(struct apk_package)); + if (pkg == NULL) + return NULL; + + r = blob; + while (apk_blob_splitstr(r, "\n", &l, &r)) + parse_index_line(db, pkg, l); + parse_index_line(db, pkg, r); + + if (pkg->name == NULL) { + apk_pkg_free(pkg); + printf("%.*s\n", blob.len, blob.ptr); + pkg = NULL; + } + + return pkg; +} + +apk_blob_t apk_pkg_format_index_entry(struct apk_package *info, int size, + char *buf) +{ + int n = 0; + + n += snprintf(&buf[n], size-n, + "P:%s\n" + "V:%s\n" + "S:%u\n" + "I:%u\n" + "T:%s\n" + "U:%s\n" + "L:%s\n", + info->name->name, info->version, + info->size, info->installed_size, + info->description, info->url, info->license); + + if (info->depends != NULL) { + n += snprintf(&buf[n], size-n, "D:"); + n += apk_deps_format(&buf[n], size-n, info->depends); + } + n += snprintf(&buf[n], size-n, "C:"); + n += apk_hexdump_format(size-n, &buf[n], + APK_BLOB_BUF(info->csum)); + n += snprintf(&buf[n], size-n, + "\n\n"); + + return APK_BLOB_PTR_LEN(buf, n); +} diff --git a/src/state.c b/src/state.c new file mode 100644 index 0000000..119bebc --- /dev/null +++ b/src/state.c @@ -0,0 +1,267 @@ +/* state.c - Alpine Package Keeper (APK) + * + * Copyright (C) 2005-2008 Natanael Copa + * Copyright (C) 2008 Timo Teräs + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. See http://www.gnu.org/ for details. + */ + +#include +#include + +#include "apk_state.h" +#include "apk_database.h" + +static int apk_state_commit_deps(struct apk_state *state, + struct apk_database *db, + struct apk_dependency_array *deps); + +struct apk_state *apk_state_new(struct apk_database *db) +{ + struct apk_state *state; + int num_bytes; + + num_bytes = sizeof(struct apk_state) + (db->pkg_id * 2 + 7) / 8; + state = (struct apk_state*) calloc(1, num_bytes); + state->refs = 1; + + return state; +} + +struct apk_state *apk_state_dup(struct apk_state *state) +{ + state->refs++; + return state; +} + +void apk_state_unref(struct apk_state *state) +{ + if (--state->refs > 0) + return; + + free(state); +} + +static void apk_state_set(struct apk_state *state, int pos, int val) +{ + int byte = pos / 4, offs = pos % 4; + + state->bitarray[byte] &= ~(0x3 << (offs * 2)); + state->bitarray[byte] |= (val & 0x3) << (offs * 2); +} + +static int apk_state_get(struct apk_state *state, int pos) +{ + int byte = pos / 4, offs = pos % 4; + + if (state == NULL) + return APK_STATE_NOT_CONSIDERED; + + return (state->bitarray[byte] >> (offs * 2)) & 0x3; +} + +static int apk_state_commit_pkg(struct apk_state *state, + struct apk_database *db, + struct apk_name *name, + struct apk_package *oldpkg, + struct apk_package *newpkg) +{ + const char *msg = NULL; + int r, upgrade = 0; + + if (newpkg != NULL) { + r = apk_state_commit_deps(state, db, newpkg->depends); + if (r != 0) + return r; + } + + if (oldpkg == NULL) { + apk_message("Installing %s (%s)", + name->name, newpkg->version); + } else if (newpkg == NULL) { + apk_message("Purging %s (%s)", + name->name, oldpkg->version); + } else { + r = apk_version_compare(APK_BLOB_STR(newpkg->version), + APK_BLOB_STR(oldpkg->version)); + switch (r) { + case APK_VERSION_LESS: + msg = "Downgrading"; + upgrade = 1; + break; + case APK_VERSION_EQUAL: + msg = "Re-installing"; + break; + case APK_VERSION_GREATER: + msg = "Upgrading"; + upgrade = 1; + break; + } + apk_message("%s %s (%s -> %s)", + msg, name->name, oldpkg->version, newpkg->version); + } + + return apk_db_install_pkg(db, oldpkg, newpkg); +} + +static int apk_state_commit_name(struct apk_state *state, + struct apk_database *db, + struct apk_name *name) +{ + struct apk_package *oldpkg = NULL, *newpkg = NULL; + int i; + + for (i = 0; i < name->pkgs->num; i++) { + if (apk_pkg_get_state(name->pkgs->item[i]) == APK_STATE_INSTALL) + oldpkg = name->pkgs->item[i]; + if (apk_state_get(state, name->pkgs->item[i]->id) == APK_STATE_INSTALL) + newpkg = name->pkgs->item[i]; + } + + if (oldpkg == NULL && newpkg == NULL) + return 0; + + /* No reinstallations for now */ + if (newpkg == oldpkg) + return 0; + + return apk_state_commit_pkg(state, db, name, oldpkg, newpkg); +} + +static int apk_state_commit_deps(struct apk_state *state, + struct apk_database *db, + struct apk_dependency_array *deps) +{ + int r, i; + + if (deps == NULL) + return 0; + + for (i = 0; i < deps->num; i++) { + r = apk_state_commit_name(state, db, deps->item[i].name); + if (r != 0) + return r; + } + + return 0; +} + +int apk_state_commit(struct apk_state *state, + struct apk_database *db) +{ + struct apk_package *pkg; + struct hlist_node *c, *n; + int r; + + /* Check all dependencies */ + r = apk_state_commit_deps(state, db, db->world); + if (r != 0) + return r; + + /* And purge all installed packages that were not considered */ + hlist_for_each_entry_safe(pkg, c, n, &db->installed.packages, installed_pkgs_list) + apk_state_commit_name(state, db, pkg->name); + + return 0; +} + +int apk_state_satisfy_name(struct apk_state *state, + struct apk_name *name) +{ + struct apk_package *preferred = NULL; + int i; + int upgrading = 1; + + /* Is something already installed? Or figure out the preferred + * package. */ + for (i = 0; i < name->pkgs->num; i++) { + if (apk_state_get(state, name->pkgs->item[i]->id) == + APK_STATE_INSTALL) + return 0; + + if (preferred == NULL) { + preferred = name->pkgs->item[i]; + continue; + } + + if (upgrading) { + if (apk_version_compare(APK_BLOB_STR(name->pkgs->item[i]->version), + APK_BLOB_STR(preferred->version)) == + APK_VERSION_GREATER) { + preferred = name->pkgs->item[i]; + continue; + } + } else { + if (apk_pkg_get_state(name->pkgs->item[i]) == + APK_STATE_INSTALL) { + preferred = name->pkgs->item[i]; + continue; + } + } + } + + /* Mark conflicting names as no install */ + for (i = 0; i < name->pkgs->num; i++) { + if (name->pkgs->item[i] != preferred) + apk_state_set(state, name->pkgs->item[i]->id, + APK_STATE_NO_INSTALL); + } + + return apk_state_pkg_install(state, preferred); +} + +int apk_state_satisfy_deps(struct apk_state *state, + struct apk_dependency_array *deps) +{ + struct apk_name *name; + int r, i; + + if (deps == NULL) + return 0; + + for (i = 0; i < deps->num; i++) { + name = deps->item[i].name; + if (name->pkgs == NULL) { + apk_error("No providers for '%s'", name->name); + return -1; + } + r = apk_state_satisfy_name(state, name); + if (r != 0) + return r; + } + return 0; +} + +void apk_state_pkg_set(struct apk_state *state, + struct apk_package *pkg) +{ + apk_state_set(state, pkg->id, APK_STATE_INSTALL); +} + +int apk_state_pkg_install(struct apk_state *state, + struct apk_package *pkg) +{ + switch (apk_state_get(state, pkg->id)) { + case APK_STATE_INSTALL: + return 0; + case APK_STATE_NO_INSTALL: + return -1; + } + + apk_state_set(state, pkg->id, APK_STATE_INSTALL); + + if (pkg->depends == NULL) + return 0; + + return apk_state_satisfy_deps(state, pkg->depends); +} + +int apk_state_pkg_is_installed(struct apk_state *state, + struct apk_package *pkg) +{ + return apk_state_get(state, pkg->id); +} + diff --git a/src/template.c b/src/template.c new file mode 100644 index 0000000..8578444 --- /dev/null +++ b/src/template.c @@ -0,0 +1,15 @@ +/* template.c - Alpine Package Keeper (APK) + * + * Copyright (C) 2005-2008 Natanael Copa + * Copyright (C) 2008 Timo Teräs + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. See http://www.gnu.org/ for details. + */ + +#include "apk_database.h" + + + diff --git a/src/ver.c b/src/ver.c new file mode 100644 index 0000000..c8b2e2b --- /dev/null +++ b/src/ver.c @@ -0,0 +1,76 @@ +/* ver.c - Alpine Package Keeper (APK) + * + * Copyright (C) 2005-2008 Natanael Copa + * Copyright (C) 2008 Timo Teräs + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. See http://www.gnu.org/ for details. + */ + +#include +#include "apk_defines.h" +#include "apk_applet.h" +#include "apk_database.h" +#include "apk_version.h" + +static int res2char(int res) +{ + switch (res) { + case APK_VERSION_LESS: + return '<'; + case APK_VERSION_GREATER: + return '>'; + case APK_VERSION_EQUAL: + return '='; + default: + return '?'; + } +} + +static int ver_main(int argc, char **argv) +{ + struct apk_database db; + struct apk_name *name; + struct apk_package *pkg, *upg, *tmp; + struct hlist_node *c; + int i, r; + + if (argc == 2) { + r = apk_version_compare(APK_BLOB_STR(argv[0]), + APK_BLOB_STR(argv[1])); + printf("%c\n", res2char(r)); + return 0; + } + + apk_db_init(&db, "/home/fabled/tmproot/"); + apk_db_read_config(&db); + + hlist_for_each_entry(pkg, c, &db.installed.packages, installed_pkgs_list) { + name = pkg->name; + upg = pkg; + for (i = 0; i < name->pkgs->num; i++) { + tmp = name->pkgs->item[i]; + if (tmp->name != name) + continue; + r = apk_version_compare(APK_BLOB_STR(tmp->version), + APK_BLOB_STR(upg->version)); + if (r == APK_VERSION_GREATER) + upg = tmp; + } + printf("%-40s%c\n", name->name, pkg != upg ? '<' : '='); + } + apk_db_free(&db); + + return 0; +} + +static struct apk_applet apk_ver = { + .name = "version", + .usage = "[version1 version2]", + .main = ver_main, +}; + +APK_DEFINE_APPLET(apk_ver); + diff --git a/src/version.c b/src/version.c new file mode 100644 index 0000000..13f3c1a --- /dev/null +++ b/src/version.c @@ -0,0 +1,165 @@ +/* version.c - Alpine Package Keeper (APK) + * + * Copyright (C) 2005-2008 Natanael Copa + * Copyright (C) 2008 Timo Teräs + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. See http://www.gnu.org/ for details. + */ +#include + +#include +#include "apk_defines.h" +#include "apk_version.h" + +/* Gentoo version: {digit}{.digit}...{letter}{_suf{#}}...{-r#} */ + +enum PARTS { + TOKEN_INVALID = -1, + TOKEN_DIGIT_OR_ZERO, + TOKEN_DIGIT, + TOKEN_LETTER, + TOKEN_SUFFIX, + TOKEN_SUFFIX_NO, + TOKEN_REVISION_NO, + TOKEN_END, +}; + +static void next_token(int *type, apk_blob_t *blob) +{ + int n = TOKEN_INVALID; + + if (blob->len == 0 || blob->ptr[0] == 0) { + n = TOKEN_END; + } else if (islower(blob->ptr[0])) { + n = TOKEN_LETTER; + } else if (*type == TOKEN_SUFFIX && isdigit(blob->ptr[0])) { + n = TOKEN_SUFFIX_NO; + } else { + switch (blob->ptr[0]) { + case '.': + n = TOKEN_DIGIT_OR_ZERO; + break; + case '_': + n = TOKEN_SUFFIX; + break; + case '-': + if (blob->len > 1 && blob->ptr[1] == 'r') { + n = TOKEN_REVISION_NO; + blob->ptr++; + blob->len--; + } else + n = TOKEN_INVALID; + break; + } + blob->ptr++; + blob->len--; + } + + if (n < *type) { + if (! ((n == TOKEN_DIGIT_OR_ZERO && *type == TOKEN_DIGIT) || + (n == TOKEN_SUFFIX && *type == TOKEN_SUFFIX_NO))) + n = TOKEN_INVALID; + } + *type = n; +} + +static int get_token(int *type, apk_blob_t *blob) +{ + static const char *pre_suffixes[] = { "alpha", "beta", "pre", "rc" }; + int v = 0, i = 0, nt = TOKEN_INVALID; + + switch (*type) { + case TOKEN_DIGIT_OR_ZERO: + /* Leading zero digits get a special treatment */ + if (blob->ptr[i] == '0') { + while (blob->ptr[i] == '0' && i < blob->len) + i++; + nt = TOKEN_DIGIT; + v = -i; + break; + } + case TOKEN_DIGIT: + case TOKEN_SUFFIX_NO: + case TOKEN_REVISION_NO: + while (isdigit(blob->ptr[i]) && i < blob->len) { + v *= 10; + v += blob->ptr[i++] - '0'; + } + break; + case TOKEN_LETTER: + v = blob->ptr[i++]; + break; + case TOKEN_SUFFIX: + for (v = 0; v < ARRAY_SIZE(pre_suffixes); v++) { + i = strlen(pre_suffixes[v]); + if (i < blob->len && + strncmp(pre_suffixes[v], blob->ptr, i) == 0) + break; + } + if (v < ARRAY_SIZE(pre_suffixes)) { + nt = TOKEN_SUFFIX_NO; + v = v - ARRAY_SIZE(pre_suffixes); + break; + } + if (strncmp("p", blob->ptr, 1) == 0) { + nt = TOKEN_SUFFIX_NO; + v = 1; + break; + } + /* fallthrough: invalid suffix */ + default: + *type = TOKEN_INVALID; + return -1; + } + blob->ptr += i; + blob->len -= i; + if (nt != TOKEN_INVALID) + *type = nt; + else + next_token(type, blob); + + return v; +} + +int apk_version_validate(apk_blob_t ver) +{ + int t = TOKEN_DIGIT; + + while (t != TOKEN_END && t != TOKEN_INVALID) + get_token(&t, &ver); + + return t == TOKEN_END; +} + +int apk_version_compare(apk_blob_t a, apk_blob_t b) +{ + int at = TOKEN_DIGIT, bt = TOKEN_DIGIT; + int av = 0, bv = 0; + + while (at == bt && at != TOKEN_END && av == bv) { + av = get_token(&at, &a); + bv = get_token(&bt, &b); +#if 0 + fprintf(stderr, + "av=%d, at=%d, a.len=%d\n" + "bv=%d, bt=%d, b.len=%d\n", + av, at, a.len, bv, bt, b.len); +#endif + } + + /* value of this token differs? */ + if (av < bv) + return APK_VERSION_LESS; + if (av > bv) + return APK_VERSION_GREATER; + if (at < bt) + return get_token(&at, &a) < 0 ? + APK_VERSION_LESS : APK_VERSION_GREATER; + if (bt < at) + return get_token(&bt, &b) > 0 ? + APK_VERSION_LESS : APK_VERSION_GREATER; + return APK_VERSION_EQUAL; +} -- cgit v1.2.3