+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <db.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <sys/stat.h>
+#include <ashd/utils.h>
+
+static DB_ENV *env;
+static DB *db;
+static DB_TXN *txn;
+static struct charvbuf files;
+static int verbose = 0;
+
+static void opendb(char *path)
+{
+ char *envpath, *p;
+ int ret;
+
+ envpath = strdup(path);
+ if((p = strrchr(envpath, '/')) == NULL) {
+ free(envpath);
+ envpath = strdup(".");
+ } else {
+ *p = 0;
+ }
+ if((ret = db_env_create(&env, 0)) != 0) {
+ fprintf(stderr, "statdbput: could not create db environment handle: %s\n", db_strerror(ret));
+ exit(1);
+ }
+ if((ret = env->open(env, envpath, DB_CREATE | DB_INIT_MPOOL | DB_INIT_LOCK | DB_INIT_LOG | DB_INIT_TXN, 0666)) != 0) {
+ fprintf(stderr, "statdbput: environment %s: %s\n", envpath, db_strerror(ret));
+ exit(1);
+ }
+ env->set_lk_detect(env, DB_LOCK_RANDOM);
+ if((ret = db_create(&db, env, 0)) != 0) {
+ fprintf(stderr, "statdbput: could not create db handle: %s\n", db_strerror(ret));
+ exit(1);
+ }
+ if((ret = db->open(db, NULL, path, NULL, DB_HASH, DB_AUTO_COMMIT | DB_CREATE, 0666)) != 0) {
+ fprintf(stderr, "statdbput: %s: %s\n", path, db_strerror(ret));
+ exit(1);
+ }
+}
+
+static void bufcatbe(struct charbuf *buf, intmax_t num, int nb)
+{
+ if(nb < 1)
+ return;
+ bufcatbe(buf, num >> 8, nb - 1);
+ bufadd(*buf, (uint8_t)(num & 0xff));
+}
+
+static int dofile2(int fd, char *name, time_t mtime, char *ctype)
+{
+ int ret;
+ DBT k, v;
+ struct charbuf buf;
+
+ bufinit(buf);
+ bufadd(buf, 1);
+ bufcatbe(&buf, mtime, 8);
+ bufcatstr2(buf, ctype);
+ while(1) {
+ sizebuf(buf, buf.d + 4096);
+ if((ret = read(fd, buf.b + buf.d, buf.s - buf.d)) < 0) {
+ fprintf(stderr, "statdbput: %s: %s\n", name, strerror(errno));
+ buffree(buf);
+ return(1);
+ }
+ if(ret == 0)
+ break;
+ buf.d += ret;
+ }
+ k = (DBT){.data = name, .size = strlen(name)};
+ v = (DBT){.data = buf.b, .size = buf.d};
+ ret = db->put(db, txn, &k, &v, 0);
+ buffree(buf);
+ if(ret) {
+ if(ret == DB_LOCK_DEADLOCK)
+ return(2);
+ fprintf(stderr, "statdbput: %s: %s\n", name, db_strerror(ret));
+ return(1);
+ } else {
+ if(verbose)
+ fprintf(stderr, "put: %s\n", name);
+ }
+ return(0);
+}
+
+static int dofile(char *path, char *name, char *ctype)
+{
+ int fd, ret;
+ char *p;
+ struct stat sb;
+
+ if((fd = open(path, O_RDONLY)) < 0) {
+ fprintf(stderr, "statdbput: %s: %s\n", path, strerror(errno));
+ return(1);
+ }
+ if(fstat(fd, &sb)) {
+ fprintf(stderr, "statdbput: %s: %s\n", path, strerror(errno));
+ close(fd);
+ return(1);
+ }
+ if(name == NULL) {
+ if((p = strrchr(path, '/')) != NULL)
+ name = p + 1;
+ else
+ name = path;
+ }
+ ret = dofile2(fd, name, sb.st_mtime, ctype);
+ close(fd);
+ if(!ret)
+ bufadd(files, sstrdup(path));
+ return(ret);
+}
+
+static int dodir(char *path, char *ctype)
+{
+ int rv, ret;
+ DIR *dir;
+ struct stat sb;
+ struct dirent *dent;
+ struct charbuf fnbuf;
+
+ if((dir = opendir(path)) == NULL) {
+ fprintf(stderr, "statdbput: %s: %s\n", path, strerror(errno));
+ return(1);
+ }
+ rv = 0;
+ bufinit(fnbuf);
+ while((dent = readdir(dir)) != NULL) {
+ fnbuf.d = 0;
+ bprintf(&fnbuf, "%s/%s", path, dent->d_name);
+ bufadd(fnbuf, 0);
+ if(stat(fnbuf.b, &sb)) {
+ fprintf(stderr, "statdbput: %s: %s\n", fnbuf.b, strerror(errno));
+ rv = 1;
+ continue;
+ }
+ if(S_ISREG(sb.st_mode)) {
+ ret = dofile(fnbuf.b, NULL, ctype);
+ if(ret == 2)
+ return(2);
+ else if(ret == 1)
+ rv = 1;
+ }
+ }
+ buffree(fnbuf);
+ closedir(dir);
+ return(0);
+}
+
+static void usage(FILE *out)
+{
+ fprintf(out, "usage: statdbput [-hvD] [-n NAME] DB CONTENT-TYPE {FILE|-}...\n");
+ fprintf(out, " statdbput [-hvD] [-d] DB CONTENT-TYPE DIR...\n");
+}
+
+int main(int argc, char **argv)
+{
+ int c, rv, ret;
+ int dm, ul, i, a;
+ char *name, *ctype, *dbpath;
+
+ dm = ul = 0;
+ name = NULL;
+ while((c = getopt(argc, argv, "+hvDdn:")) >= 0) {
+ switch(c) {
+ case 'd':
+ dm = 1;
+ break;
+ case 'D':
+ ul = 1;
+ break;
+ case 'v':
+ verbose++;
+ break;
+ case 'n':
+ name = optarg;
+ break;
+ case 'h':
+ usage(stdout);
+ return(0);
+ default:
+ usage(stderr);
+ return(1);
+ }
+ }
+ if(optind > argc - 3) {
+ usage(stderr);
+ return(1);
+ }
+ dbpath = argv[optind++];
+ ctype = argv[optind++];
+ opendb(dbpath);
+ while(1) {
+ if((ret = env->txn_begin(env, NULL, &txn, 0)) != 0) {
+ fprintf(stderr, "statdbput: could not begin transaction in %s: %s\n", dbpath, db_strerror(ret));
+ return(1);
+ }
+ rv = 0;
+ a = optind;
+ if(dm) {
+ for(a = optind; a < argc; a++) {
+ ret = dodir(argv[a], ctype);
+ if(ret == 2)
+ break;
+ else if(ret)
+ rv = 1;
+ }
+ } else {
+ for(a = optind; a < argc; a++) {
+ if(!strcmp(argv[a], "-")) {
+ if(name == NULL) {
+ fprintf(stderr, "statdbput: must give -n when putting stdin\n");
+ ret = 1;
+ } else {
+ ret = dofile2(0, name, time(NULL), ctype);
+ }
+ } else {
+ ret = dofile(argv[a], name, ctype);
+ }
+ if(ret == 2)
+ break;
+ else if(ret)
+ rv = 1;
+ }
+ }
+ if(ret == 2) {
+ if(verbose)
+ fprintf(stderr, "deadlocked, restarting and trying again\n");
+ for(i = 0; i < files.d; i++)
+ free(files.b[i]);
+ files.d = 0;
+ txn->abort(txn);
+ continue;
+ }
+ break;
+ }
+ if((ret = txn->commit(txn, 0)) != 0) {
+ fprintf(stderr, "statdbput: could not commit transaction in %s: %s\n", dbpath, db_strerror(ret));
+ return(1);
+ }
+ if(ul) {
+ for(i = 0; i < files.d; i++) {
+ if(verbose)
+ fprintf(stderr, "unlink %s\n", files.b[i]);
+ if(unlink(files.b[i]))
+ fprintf(stderr, "statdbput: unlink %s: %s\n", files.b[i], strerror(errno));
+ }
+ }
+ return(rv);
+}