+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include "zpm.h"
+
+#include "sha256.h"
+#if 0
+struct zpm {
+ sqlite3 *db;
+ char *path; /* path to package file */
+ char *version;
+ int release;
+ char *pkgname;
+ time_t installed; /* install time, 0 for not installed */
+};
+
+struct zpm_file {
+ char *path;
+ int mode;
+ uint32_t filetype;
+ char *tags;
+ char *owner;
+ char *group;
+ char *hash; /* could be fixed length */
+ time_t mtime;
+ struct zpm_file *next; /* so you can make a linked list */
+};
+
+/* NULL? Create? */
+/* associate with a package ? if only one? first? */
+int zpm_open(struct zpm *pkg, char *path);
+int zpm_pkgname(char *base, char *version, int release); /* construct a package file name */
+
+/* flags for preserving mode, owner, etc */
+/* puts hash of import in hash */
+/* path can be a hash, with an "INTERNAL" flag, i.e. internally import */
+#define ZPM_MODE 0x1
+#define ZPM_OWNER 0x2
+#define ZPM_MTIME 0x4
+#define ZPM_INTERNAL 0x8
+#define ZPM_NOBLOB 0x10
+/* don't run scripts on install */
+#define ZPM_NOSCRIPTS 0x10
+/* don't associate the file with a package, just do a raw insert */
+/* otherwise, associate it with the current package */
+#define ZPM_NOPACKAGE 0x20
+
+int zpm_import(struct zpm *zp, char *path, uint32_t flags, uint8_t *hash);
+
+/* link and unlink hashes to packages */
+int zpm_link(struct zpm *pkg, char *path, char *hash, struct zpm_file *fileinfo);
+int zpm_unlink(struct zpm *pkg, char *path);
+
+/* tag a file. relative to "current package" */
+int zpm_tag(struct zpm *zp, char *path, char *tags);
+/* should this be broken up into separage functions ? */
+int zpm_md(struct zpm *zp, char *path, int mode, char *owner, char *group, time_t mtime);
+
+/* export hash to dest */
+int zpm_extract(struct zpm *pkg, char *hash, char *path, int mode);
+
+/* export path to dest */
+int zpm_export(struct zpm *zp, char *path, uint32_t flags, char *dest);
+
+int zpm_close(struct zpm *zp);
+
+/* attach a signature to a package */
+int zpm_sign(struct zpm *z, size_t s, void *signature);
+
+/* set the package info to the nth package, -1 to return count? */
+/* further import/exports and such will be relative to this package */
+int zpm_package(struct zpm *zp, int n);
+
+/* get file information */
+int zpm_stat(struct zpm *z, struct zpm_file *f, int n);
+
+/* will also set the package context to the new package */
+int zpm_newpkg(struct zpm *z, char *base, char *version, int release);
+
+/* transactions */
+int zpm_begin(struct zpm *z);
+int zpm_commit(struct zpm *z);
+int zpm_rollback(struct zpm *z);
+
+/* higher level operations */
+
+/* install or uninstall the package */
+/* flag for keeping the blobs in local */
+/* what about tag filtering */
+int zpm_install(struct zpm *z, struct zpm *local, uint32_t flags);
+int zpm_uninstall(struct zpm *local);
+
+/* slurp up the actual blobs */
+/* what about versioning them if they change */
+int zpm_preserve(struct zpm *local);
+
+/* check file integrity */
+int zpm_checkinstall(struct zpm *local);
+
+int zpm_merge(struct zpm *z, struct zpm *src, uint32_t flags);
+
+void uncompresslzma(void *buf, size_t bufsize, FILE *out);
+#define SQLERROR(x) fprintf(stderr, "%s %d: %s\n", __func__, __LINE__, (x))
+#endif
+
+static char *dupstr(char *s) {
+ size_t n;
+ char *d;
+
+ n = strlen(s);
+ d = malloc(n+1);
+ if (d) {
+ d = strcpy(d, s);
+ }
+ return d;
+}
+
+#if 0
+int zpm_newpkg(struct zpm *z, char *base, char *version, int release) {
+ char *sql = "insert or ignore into packages (package,version,release) values (?,?,?)";
+ int rc;
+ sqlite3_stmt *ifile;
+
+ rc = sqlite3_prepare(db, sql, -1, &ifile,0);
+ if (rc != SQLITE_OK) {
+ SQLERROR(sqlite3_errmsg(db));
+ return 0;
+ }
+ rc = sqlite3_bind_text(ifile, 1, base, strlen(base), SQLITE_STATIC);
+ if (rc != SQLITE_OK) {
+ SQLERROR(sqlite3_errmsg(db));
+ fprintf(stderr, "cant bind package name\n");
+ zpm_rollback(pkg);
+ return 0;
+ }
+ sqlite3_bind_text(ifile, 2, version, strlen(version), SQLITE_STATIC);
+ sqlite3_bind_int(ifile, 3, release)
+
+ rc = sqlite3_step(ifile);
+
+ if (rc != SQLITE_DONE) {
+ SQLERROR(sqlite3_errmsg(db));
+ sqlite3_finalize(ifile);
+ return 0;
+ }
+ sqlite3_finalize(ifile);
+ z->pkg = dupstr(base);
+ z->version = dupstr(version);
+ z->release = release;
+}
+#endif
+
+int zpm_begin(struct zpm *z) {
+ char *errstr = 0;
+ sqlite3_exec(z->db, "begin;", NULL, NULL, &errstr);
+ if (errstr) {
+ fprintf(stderr, "sqlite begin error: %s\n", errstr);
+ sqlite3_free(errstr);
+ return 0;
+ }
+ return 1;
+}
+
+int zpm_commit(struct zpm *z) {
+ char *errstr = 0;
+ sqlite3_exec(z->db, "commit;", NULL, NULL, &errstr);
+ if (errstr) {
+ fprintf(stderr, "sqlite commit error: %s\n", errstr);
+ sqlite3_free(errstr);
+ return 0;
+ }
+ return 1;
+}
+
+int zpm_rollback(struct zpm *z) {
+ char *errstr = 0;
+ sqlite3_exec(z->db, "rollback;", NULL, NULL, &errstr);
+ if (errstr) {
+ fprintf(stderr, "sqlite rollback error: %s\n", errstr);
+ sqlite3_free(errstr);
+ return 0;
+ }
+ return 1;
+}
+
+/* NULL? Create? */
+int zpm_open(struct zpm *pkg, char *path) {
+ int rc;
+ char *errstr = 0;
+ sqlite3 *db = 0;
+
+ pkg->db = 0;
+ pkg->path = 0;
+ pkg->version = 0;
+ pkg->release = 0;
+ pkg->pkgname = 0;
+ pkg->installed = 0;
+
+ /* TODO some way to determine if the DB is newly created ? */
+ /* could check for tables, if there are any, then check version, etc */
+ rc = sqlite3_open(path, &db);
+ if (rc) {
+ SQLERROR(sqlite3_errmsg(db));
+ sqlite3_close(db);
+ return 0;
+ }
+ sqlite3_exec(pkg->db, "pragma foreign_keys = ON;", NULL, NULL, &errstr);
+ if (errstr) {
+ fprintf(stderr, "sqlite foreign key error: %s\n", errstr);
+ sqlite3_free(errstr);
+ sqlite3_close(db);
+ return 0;
+ }
+
+ pkg->path = dupstr(path);
+ pkg->db = db;
+
+ /* TODO if this is a new database, create structures */
+
+ /* get a package. what if more than one? what if none? */
+ return 1;
+}
+
+int zpm_close(struct zpm *pkg) {
+ if (pkg) {
+ sqlite3_close(pkg->db);
+ free(pkg->path);
+ }
+ /* TODO free any malloced names and such */
+ return 1;
+}
+
+int zpm_extract(struct zpm *pkg, char *hash, char *path, int mode) {
+ int rc;
+
+ int blobsize;
+ int64_t size;
+ void *xzdata;
+ int type;
+ FILE *out;
+ sqlite3_stmt *ifile;
+
+ /* TODO check null */
+ sqlite3 *db = pkg->db;
+
+ rc = sqlite3_prepare(db, "select size, content from files where hash = ?", -1, &ifile,0);
+ if (rc != SQLITE_OK) {
+ SQLERROR(sqlite3_errmsg(db));
+ return 0;
+ }
+
+ /* hash, filename */
+
+ sqlite3_bind_text(ifile, 1, hash, 64, SQLITE_STATIC);
+
+ rc = sqlite3_step(ifile);
+
+ if (rc == SQLITE_DONE) {
+ /* didn't find a row */
+ sqlite3_finalize(ifile);
+ sqlite3_close(db);
+ fprintf(stderr, "no such hash\n");
+ return 0;
+ }
+ /* either way we're done with this now */
+
+ if (rc != SQLITE_ROW) {
+ SQLERROR(sqlite3_errmsg(db));
+ sqlite3_finalize(ifile);
+ sqlite3_close(db);
+ return 0;
+ }
+
+ type = sqlite3_column_type(ifile, 0);
+ if (type == SQLITE_NULL) {
+ fprintf(stderr, "no file size\n");
+ sqlite3_finalize(ifile);
+ sqlite3_close(db);
+ return 0;
+ }
+ type = sqlite3_column_type(ifile, 1);
+ if (type == SQLITE_NULL) {
+ fprintf(stderr, "no file data\n");
+ sqlite3_finalize(ifile);
+ sqlite3_close(db);
+ return 0;
+ }
+ size = sqlite3_column_int64(ifile, 0);
+ xzdata = (void *)sqlite3_column_blob(ifile, 1);
+ blobsize = sqlite3_column_bytes(ifile, 1);
+
+ out = fopen(path, "w");
+ if (!out) {
+ fprintf(stderr, "can't open output file %s\n", path);
+ sqlite3_finalize(ifile);
+ sqlite3_close(db);
+ return 5;
+ }
+ //fwrite(xzdata, blobsize, 1, stdout);
+
+ fprintf(stderr, "uncompressing %d bytes at %p, expect %lld\n", blobsize, xzdata, (long long int)size);
+ uncompresslzma(xzdata, blobsize, out);
+ fclose(out);
+
+ sqlite3_finalize(ifile);
+
+ return 0;
+
+}
+
+#if 1
+int zpm_import(struct zpm *pkg, char *path, uint32_t flags, char *hash) {
+ int fd;
+ void *content;
+ struct stat sbuf;
+ unsigned char tmp[32];
+ hash_state md;
+ sqlite3_stmt *ifile;
+ int haverow,havedata;
+ int j,rc,type;
+ char hashbuf[65];
+
+ /* xz compress it */
+ size_t outlen = 0;
+ void *outbuf;
+
+ if (!pkg || !pkg->db || !path) {
+ return 0;
+ }
+
+ /* use local if caller didn't pass in room */
+ if (!hash) {
+ hash = hashbuf;
+ }
+
+ /* mmap the file */
+ fd = open(path, O_RDONLY);
+ if (fd == -1) {
+ return 0;
+ }
+ if (fstat(fd, &sbuf) == -1) {
+ return 0;
+ }
+ /* not a regular file? */
+ if (!S_ISREG(sbuf.st_mode)) {
+ /* TODO this is ok, just stored differently */
+ return 0;
+ }
+
+ content = mmap(0, sbuf.st_size, PROT_READ,MAP_PRIVATE, fd, 0);
+ if (!content) {
+ return 0;
+ }
+
+ /* get hash */
+ sha256_init(&md);
+ sha256_process(&md, content, sbuf.st_size);
+ sha256_done(&md, tmp);
+ for (j=0;j<32;j++) {
+ sprintf(hash+j*2, "%02x", (unsigned)tmp[j]);
+ }
+ hash[64] = 0;
+// fprintf(stderr, "file %s: %s\n", path, hash);
+
+ /* compress */
+ outbuf = compresslzma(content, sbuf.st_size, &outlen);
+// fprintf(stderr, "compressed to %zu\n", outlen);
+
+ /* don't need the original file now */
+ munmap(content, sbuf.st_size);
+ close(fd);
+
+ /* prepare and bind */
+ /* TODO check null */
+ sqlite3 *db = pkg->db;
+
+ rc = sqlite3_prepare(db, "select size, content is not null from files where hash = ?", -1, &ifile,0);
+ if (rc != SQLITE_OK) {
+ SQLERROR(sqlite3_errmsg(db));
+ return 1;
+ }
+
+ /* hash, filename */
+
+ sqlite3_bind_text(ifile, 1, hash, 64, SQLITE_STATIC);
+
+ rc = sqlite3_step(ifile);
+
+ if (rc != SQLITE_DONE) {
+ if (rc != SQLITE_ROW) {
+ /* didn't find a row */
+ SQLERROR(sqlite3_errmsg(db));
+ zpm_rollback(pkg);
+ return 0;
+ }
+ haverow = 1;
+// fprintf(stderr, "have row for hash\n");
+ type = sqlite3_column_type(ifile, 0);
+ if (type == SQLITE_NULL) {
+ /* TODO assert, this shouldn't be possible? */
+ fprintf(stderr, "no file size\n");
+ sqlite3_finalize(ifile);
+ return 0;
+ }
+ type = sqlite3_column_type(ifile, 1);
+ if (type == SQLITE_NULL) {
+ /* TODO assert, this shouldn't be possible? */
+ fprintf(stderr, "no file data\n");
+ sqlite3_finalize(ifile);
+ return 0;
+ /* which is fine, just need to update the row then */
+ }
+ havedata = sqlite3_column_int(ifile, 1);
+ }
+
+ sqlite3_finalize(ifile);
+
+ if (!havedata) {
+ /* start a transaction */
+ // do that outside of here
+ //zpm_begin(pkg);
+
+ /* insert */
+ if (haverow) {
+ rc = sqlite3_prepare(db, "update files set size = ?, content = ? where hash = ?", -1, &ifile,0);
+ } else {
+// fprintf(stderr, "missing file data\n");
+ rc = sqlite3_prepare(db, "insert into files (size, content, hash) values (?,?,?)", -1, &ifile,0);
+ }
+ if (rc != SQLITE_OK) {
+ SQLERROR(sqlite3_errmsg(db));
+ fprintf(stderr, "cant prepare data\n");
+ zpm_rollback(pkg);
+ return 0;
+ }
+
+ sqlite3_bind_int64(ifile, 1, (sqlite3_int64)sbuf.st_size);
+ if (rc != SQLITE_OK) {
+ SQLERROR(sqlite3_errmsg(db));
+ fprintf(stderr, "cant bind size\n");
+ zpm_rollback(pkg);
+ return 0;
+ }
+ sqlite3_bind_blob64(ifile, 2, outbuf, (sqlite3_int64)outlen, SQLITE_STATIC);
+ if (rc != SQLITE_OK) {
+ SQLERROR(sqlite3_errmsg(db));
+ fprintf(stderr, "cant bind content\n");
+ zpm_rollback(pkg);
+ return 0;
+ }
+ sqlite3_bind_text(ifile, 3, hash, 64, SQLITE_STATIC);
+ if (rc != SQLITE_OK) {
+ SQLERROR(sqlite3_errmsg(db));
+ fprintf(stderr, "cant bind hash\n");
+ zpm_rollback(pkg);
+ return 0;
+ }
+ rc = sqlite3_step(ifile);
+ if (rc != SQLITE_DONE) {
+ SQLERROR(sqlite3_errmsg(db));
+ sqlite3_finalize(ifile);
+ zpm_rollback(pkg);
+ return 0;
+ }
+ sqlite3_finalize(ifile);
+
+ /* commit */
+ //zpm_commit(pkg);
+
+ }
+
+ /* if package and not nopackage flag, add to package */
+ if (pkg->pkgname && (!ZPM_NOPACKAGE)) {
+ /* TODO */
+ }
+
+ /* return */
+ return 1;
+}
+#endif
+
+#if 0
+int main(int ac, char **av){
+ sqlite3 *db = 0;
+ int rc;
+
+ int blobsize;
+ int64_t size;
+ void *xzdata;
+ int type;
+ FILE *out;
+ sqlite3_stmt *ifile;
+
+ char *hash;
+ char *filename;
+
+ if (ac < 3) {
+ fprintf(stderr, "usage: db hash file\n");
+ return 1;
+ }
+
+ rc = sqlite3_open(av[1], &db);
+ if (rc) {
+ SQLERROR(sqlite3_errmsg(db));
+ sqlite3_close(db);
+ return 1;
+ }
+
+}
+#endif
+
+#if 0
+Packages are sqlite databases
+
+get application id and userver
+
+Primitive operations:
+
+add path to repo
+associate path with package
+associate blob with path?
+add blob to repo
+* extract blob to a path
+compare blob to filesystem path
+create package with info
+
+Extra primitives:
+
+record elf information about blob
+compress blob
+uncompress blob
+sign a package? What are we verifying?
+#endif