--- /dev/null
+/*
+ * add a file/files to an sqlite db
+ * in the 'files' table.
+ */
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include <sys/mman.h>
+
+#include <sqlite3.h>
+#include "sha256.h"
+
+#include "lzma.h"
+
+
+void uncompresslzma(void *buf, size_t bufsize, FILE *out) {
+ lzma_stream s = LZMA_STREAM_INIT;
+ lzma_stream *strm;
+
+ uint8_t outbuf[BUFSIZ];
+
+ int ret;
+
+ strm = &s;
+
+ ret = lzma_stream_decoder(strm, UINT64_MAX, 0);
+ /* The only reasonable error here is LZMA_MEM_ERROR. */
+ if (ret != LZMA_OK) {
+ fprintf(stderr, "%s", ret == LZMA_MEM_ERROR ? strerror(ENOMEM)
+ : "Internal error (bug)");
+ exit(EXIT_FAILURE);
+ }
+
+ strm->avail_in = bufsize;
+ strm->next_in = buf;
+ strm->avail_out = BUFSIZ;
+ strm->next_out = outbuf;
+
+ lzma_action action = LZMA_RUN;
+
+ while (1) {
+ ret = lzma_code(strm, action);
+
+ // Write and check write error before checking decoder error.
+ // This way as much data as possible gets written to output
+ // even if decoder detected an error.
+ if (strm->avail_out == 0 || ret != LZMA_OK) {
+ const size_t write_size = BUFSIZ - strm->avail_out;
+
+ if (fwrite(outbuf, 1, write_size, out) != write_size) {
+ // Wouldn't be a surprise if writing to stderr
+ // would fail too but at least try to show an
+ // error message.
+ fprintf(stderr, "Cannot write to output file stream: "
+ "%s", strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+
+ strm->next_out = outbuf;
+ strm->avail_out = BUFSIZ;
+ }
+
+ if (ret != LZMA_OK) {
+ if (ret == LZMA_STREAM_END) {
+ // lzma_stream_decoder() already guarantees
+ // that there's no trailing garbage.
+ assert(strm->avail_in == 0);
+ //assert(action == LZMA_FINISH);
+ return;
+ }
+
+ const char *msg;
+ switch (ret) {
+ case LZMA_MEM_ERROR:
+ msg = strerror(ENOMEM);
+ break;
+
+ case LZMA_FORMAT_ERROR:
+ msg = "File format not recognized";
+ break;
+
+ case LZMA_OPTIONS_ERROR:
+ // FIXME: Better message?
+ msg = "Unsupported compression options";
+ break;
+
+ case LZMA_DATA_ERROR:
+ msg = "File is corrupt";
+ break;
+
+ case LZMA_BUF_ERROR:
+ msg = "Unexpected end of input";
+ break;
+
+ default:
+ msg = "Internal error (bug)";
+ break;
+ }
+
+ fprintf(stderr, "xz: %s\n", msg);
+ exit(EXIT_FAILURE);
+ }
+ }
+}
+
+#define SQLERROR(x) fprintf(stderr, "%s %d: %s\n", __func__, __LINE__, (x))
+
+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;
+ }
+
+ rc = sqlite3_prepare(db, "select size, content from files where hash = ?", -1, &ifile,0);
+ if (rc != SQLITE_OK) {
+ SQLERROR(sqlite3_errmsg(db));
+ return 1;
+ }
+
+ /* hash, filename */
+ hash = av[2];
+ filename = av[3];
+
+ 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 1;
+ }
+ /* either way we're done with this now */
+
+ if (rc != SQLITE_ROW) {
+ SQLERROR(sqlite3_errmsg(db));
+ sqlite3_finalize(ifile);
+ sqlite3_close(db);
+ return 2;
+ }
+
+ type = sqlite3_column_type(ifile, 0);
+ if (type == SQLITE_NULL) {
+ fprintf(stderr, "no file size\n");
+ sqlite3_finalize(ifile);
+ sqlite3_close(db);
+ return 3;
+ }
+ type = sqlite3_column_type(ifile, 1);
+ if (type == SQLITE_NULL) {
+ fprintf(stderr, "no file data\n");
+ sqlite3_finalize(ifile);
+ sqlite3_close(db);
+ return 4;
+ }
+ size = sqlite3_column_int64(ifile, 0);
+ xzdata = (void *)sqlite3_column_blob(ifile, 1);
+ blobsize = sqlite3_column_bytes(ifile, 1);
+
+ out = fopen(filename, "w");
+ if (!out) {
+ fprintf(stderr, "can't open output file %s\n", filename);
+ 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);
+ sqlite3_close(db);
+ return 0;
+}
+void *compresslzma(void *buf, size_t bufsize, size_t *len) {
+ /* xz compress it */
+ size_t outsize;
+ void *outbuf;
+ size_t outlen = 0;
+
+ outsize = lzma_stream_buffer_bound(bufsize);
+ outbuf = malloc(outsize);
+ if (!outbuf) {
+ *len = 0;
+ return NULL;
+ }
+ /* TODO adjust encoding level for size */
+ lzma_easy_buffer_encode(6, LZMA_CHECK_CRC64, NULL,
+ buf, bufsize,
+ outbuf, &outlen, outsize
+ );
+ *len = outlen;
+ return outbuf;
+}
+
+static int callback(void *NotUsed, int argc, char **argv, char **azColName){
+ int i;
+ for(i=0; i<argc; i++){
+ printf("%s = %s\n", azColName[i], argv[i] ? argv[i] : "NULL");
+ }
+ printf("\n");
+ return 0;
+}
+
+static char *create_table = "create table if not exists files (hash text primary key, size integer, compression text, content blob)";
+
+struct dbh {
+ sqlite3 *db;
+ char *errmsg;
+ int rc;
+};
+
+#define SQLERROR(x) fprintf(stderr, "%s %d: %s\n", __func__, __LINE__, (x))
+static int begin(sqlite3 *db) {
+ int rc;
+ char *err;
+
+ rc = sqlite3_exec(db, "begin;", callback, 0, &err);
+ if (rc != SQLITE_OK) {
+ SQLERROR(err);
+ sqlite3_free(err);
+ }
+ return rc;
+}
+
+static int commit(sqlite3 *db) {
+ int rc;
+ char *err;
+
+ rc = sqlite3_exec(db, "commit;", callback, 0, &err);
+ if (rc != SQLITE_OK) {
+ SQLERROR(err);
+ sqlite3_free(err);
+ }
+ return rc;
+}
+static int rollback(sqlite3 *db) {
+ int rc;
+ char *err;
+
+ rc = sqlite3_exec(db, "rollback;", callback, 0, &err);
+ if (rc != SQLITE_OK) {
+ SQLERROR(err);
+ sqlite3_free(err);
+ }
+ return rc;
+}
+
+int main(int ac, char **av){
+ sqlite3 *db = 0;
+ char *errmsg = 0;
+ int rc, i, j;
+
+ rc = sqlite3_open(av[1], &db);
+ if (rc) {
+ SQLERROR(sqlite3_errmsg(db));
+ sqlite3_close(db);
+ return 1;
+ }
+
+ rc = sqlite3_exec(db, create_table, callback, 0, &errmsg);
+ if (rc != SQLITE_OK) {
+ SQLERROR(errmsg);
+ sqlite3_free(errmsg);
+ sqlite3_close(db);
+ return 1;
+ }
+
+ sqlite3_stmt *ifile;
+
+ rc = sqlite3_prepare(db, "insert or ignore into files (hash,size,compression,content) values (?,?,?,?)", -1, &ifile,0);
+ if (rc != SQLITE_OK) {
+ SQLERROR(sqlite3_errmsg(db));
+ return 1;
+ }
+
+ begin(db);
+ /* insert each file */
+ for (i = 2; i < ac; i++) {
+ int fd;
+ void *content;
+ struct stat sbuf;
+ unsigned char tmp[32];
+ char hash[65];
+ hash_state md;
+
+ fd = open(av[i], O_RDONLY);
+ if (fd == -1) {
+ rollback(db);
+ sqlite3_close(db);
+ exit(1);
+ }
+ if (fstat(fd, &sbuf) == -1) {
+ rollback(db);
+ sqlite3_close(db);
+ exit(1);
+ }
+ /* not a regular file? */
+ if (!S_ISREG(sbuf.st_mode)) {
+ rollback(db);
+ sqlite3_close(db);
+ exit(1);
+ }
+
+ content = mmap(0, sbuf.st_size, PROT_READ,MAP_PRIVATE, fd, 0);
+ if (!content) {
+ rollback(db);
+ sqlite3_close(db);
+ exit(2);
+ }
+
+ 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", av[i], hash);
+
+ void *xzcompressed;
+ size_t compresslen;
+
+ xzcompressed = compresslzma(content, sbuf.st_size, &compresslen);
+
+ sqlite3_bind_text(ifile, 1, hash, 64, SQLITE_STATIC);
+ sqlite3_bind_int64(ifile, 2, sbuf.st_size);
+ sqlite3_bind_text(ifile, 3, "xz", 2, SQLITE_STATIC);
+ sqlite3_bind_blob64(ifile, 4, xzcompressed, compresslen, SQLITE_STATIC);
+
+ rc = sqlite3_step(ifile);
+
+ /* either way we're done with this now */
+ munmap(content, sbuf.st_size);
+
+ if (rc != SQLITE_DONE) {
+ SQLERROR(sqlite3_errmsg(db));
+ sqlite3_finalize(ifile);
+ rollback(db);
+ sqlite3_close(db);
+ return 1;
+ }
+ sqlite3_reset(ifile);
+ free(xzcompressed);
+ printf("%s\n", hash);
+ }
+ sqlite3_finalize(ifile);
+ commit(db);
+
+ sqlite3_close(db);
+ return 0;
+}