Initial commit.
[statserve.git] / statdbput.c
1 #include <stdlib.h>
2 #include <stdio.h>
3 #include <unistd.h>
4 #include <string.h>
5 #include <errno.h>
6 #include <db.h>
7 #include <fcntl.h>
8 #include <dirent.h>
9 #include <sys/stat.h>
10 #include <ashd/utils.h>
11
12 static DB_ENV *env;
13 static DB *db;
14 static DB_TXN *txn;
15 static struct charvbuf files;
16 static int verbose = 0;
17
18 static void opendb(char *path)
19 {
20     char *envpath, *p;
21     int ret;
22     
23     envpath = strdup(path);
24     if((p = strrchr(envpath, '/')) == NULL) {
25         free(envpath);
26         envpath = strdup(".");
27     } else {
28         *p = 0;
29     }
30     if((ret = db_env_create(&env, 0)) != 0) {
31         fprintf(stderr, "statdbput: could not create db environment handle: %s\n", db_strerror(ret));
32         exit(1);
33     }
34     if((ret = env->open(env, envpath, DB_CREATE | DB_INIT_MPOOL | DB_INIT_LOCK | DB_INIT_LOG | DB_INIT_TXN, 0666)) != 0) {
35         fprintf(stderr, "statdbput: environment %s: %s\n", envpath, db_strerror(ret));
36         exit(1);
37     }
38     env->set_lk_detect(env, DB_LOCK_RANDOM);
39     if((ret = db_create(&db, env, 0)) != 0) {
40         fprintf(stderr, "statdbput: could not create db handle: %s\n", db_strerror(ret));
41         exit(1);
42     }
43     if((ret = db->open(db, NULL, path, NULL, DB_HASH, DB_AUTO_COMMIT | DB_CREATE, 0666)) != 0) {
44         fprintf(stderr, "statdbput: %s: %s\n", path, db_strerror(ret));
45         exit(1);
46     }
47 }
48
49 static void bufcatbe(struct charbuf *buf, intmax_t num, int nb)
50 {
51     if(nb < 1)
52         return;
53     bufcatbe(buf, num >> 8, nb - 1);
54     bufadd(*buf, (uint8_t)(num & 0xff));
55 }
56
57 static int dofile2(int fd, char *name, time_t mtime, char *ctype)
58 {
59     int ret;
60     DBT k, v;
61     struct charbuf buf;
62     
63     bufinit(buf);
64     bufadd(buf, 1);
65     bufcatbe(&buf, mtime, 8);
66     bufcatstr2(buf, ctype);
67     while(1) {
68         sizebuf(buf, buf.d + 4096);
69         if((ret = read(fd, buf.b + buf.d, buf.s - buf.d)) < 0) {
70             fprintf(stderr, "statdbput: %s: %s\n", name, strerror(errno));
71             buffree(buf);
72             return(1);
73         }
74         if(ret == 0)
75             break;
76         buf.d += ret;
77     }
78     k = (DBT){.data = name, .size = strlen(name)};
79     v = (DBT){.data = buf.b, .size = buf.d};
80     ret = db->put(db, txn, &k, &v, 0);
81     buffree(buf);
82     if(ret) {
83         if(ret == DB_LOCK_DEADLOCK)
84             return(2);
85         fprintf(stderr, "statdbput: %s: %s\n", name, db_strerror(ret));
86         return(1);
87     } else {
88         if(verbose)
89             fprintf(stderr, "put: %s\n", name);
90     }
91     return(0);
92 }
93
94 static int dofile(char *path, char *name, char *ctype)
95 {
96     int fd, ret;
97     char *p;
98     struct stat sb;
99     
100     if((fd = open(path, O_RDONLY)) < 0) {
101         fprintf(stderr, "statdbput: %s: %s\n", path, strerror(errno));
102         return(1);
103     }
104     if(fstat(fd, &sb)) {
105         fprintf(stderr, "statdbput: %s: %s\n", path, strerror(errno));
106         close(fd);
107         return(1);
108     }
109     if(name == NULL) {
110         if((p = strrchr(path, '/')) != NULL)
111             name = p + 1;
112         else
113             name = path;
114     }
115     ret = dofile2(fd, name, sb.st_mtime, ctype);
116     close(fd);
117     if(!ret)
118         bufadd(files, sstrdup(path));
119     return(ret);
120 }
121
122 static int dodir(char *path, char *ctype)
123 {
124     int rv, ret;
125     DIR *dir;
126     struct stat sb;
127     struct dirent *dent;
128     struct charbuf fnbuf;
129     
130     if((dir = opendir(path)) == NULL) {
131         fprintf(stderr, "statdbput: %s: %s\n", path, strerror(errno));
132         return(1);
133     }
134     rv = 0;
135     bufinit(fnbuf);
136     while((dent = readdir(dir)) != NULL) {
137         fnbuf.d = 0;
138         bprintf(&fnbuf, "%s/%s", path, dent->d_name);
139         bufadd(fnbuf, 0);
140         if(stat(fnbuf.b, &sb)) {
141             fprintf(stderr, "statdbput: %s: %s\n", fnbuf.b, strerror(errno));
142             rv = 1;
143             continue;
144         }
145         if(S_ISREG(sb.st_mode)) {
146             ret = dofile(fnbuf.b, NULL, ctype);
147             if(ret == 2)
148                 return(2);
149             else if(ret == 1)
150                 rv = 1;
151         }
152     }
153     buffree(fnbuf);
154     closedir(dir);
155     return(0);
156 }
157
158 static void usage(FILE *out)
159 {
160     fprintf(out, "usage: statdbput [-hvD] [-n NAME] DB CONTENT-TYPE {FILE|-}...\n");
161     fprintf(out, "       statdbput [-hvD] [-d] DB CONTENT-TYPE DIR...\n");
162 }
163
164 int main(int argc, char **argv)
165 {
166     int c, rv, ret;
167     int dm, ul, i, a;
168     char *name, *ctype, *dbpath;
169     
170     dm = ul = 0;
171     name = NULL;
172     while((c = getopt(argc, argv, "+hvDdn:")) >= 0) {
173         switch(c) {
174         case 'd':
175             dm = 1;
176             break;
177         case 'D':
178             ul = 1;
179             break;
180         case 'v':
181             verbose++;
182             break;
183         case 'n':
184             name = optarg;
185             break;
186         case 'h':
187             usage(stdout);
188             return(0);
189         default:
190             usage(stderr);
191             return(1);
192         }
193     }
194     if(optind > argc - 3) {
195         usage(stderr);
196         return(1);
197     }
198     dbpath = argv[optind++];
199     ctype = argv[optind++];
200     opendb(dbpath);
201     while(1) {
202         if((ret = env->txn_begin(env, NULL, &txn, 0)) != 0) {
203             fprintf(stderr, "statdbput: could not begin transaction in %s: %s\n", dbpath, db_strerror(ret));
204             return(1);
205         }
206         rv = 0;
207         a = optind;
208         if(dm) {
209             for(a = optind; a < argc; a++) {
210                 ret = dodir(argv[a], ctype);
211                 if(ret == 2)
212                     break;
213                 else if(ret)
214                     rv = 1;
215             }
216         } else {
217             for(a = optind; a < argc; a++) {
218                 if(!strcmp(argv[a], "-")) {
219                     if(name == NULL) {
220                         fprintf(stderr, "statdbput: must give -n when putting stdin\n");
221                         ret = 1;
222                     } else {
223                         ret = dofile2(0, name, time(NULL), ctype);
224                     }
225                 } else {
226                     ret = dofile(argv[a], name, ctype);
227                 }
228                 if(ret == 2)
229                     break;
230                 else if(ret)
231                     rv = 1;
232             }
233         }
234         if(ret == 2) {
235             if(verbose)
236                 fprintf(stderr, "deadlocked, restarting and trying again\n");
237             for(i = 0; i < files.d; i++)
238                 free(files.b[i]);
239             files.d = 0;
240             txn->abort(txn);
241             continue;
242         }
243         break;
244     }
245     if((ret = txn->commit(txn, 0)) != 0) {
246         fprintf(stderr, "statdbput: could not commit transaction in %s: %s\n", dbpath, db_strerror(ret));
247         return(1);
248     }
249     if(ul) {
250         for(i = 0; i < files.d; i++) {
251             if(verbose)
252                 fprintf(stderr, "unlink %s\n", files.b[i]);
253             if(unlink(files.b[i]))
254                 fprintf(stderr, "statdbput: unlink %s: %s\n", files.b[i], strerror(errno));
255         }
256     }
257     return(rv);
258 }