Makefile.am: Stupid workaround for new Automake pettiness.
[distorted-backup] / rmt.c
1 /* -*-c-*-
2 *
3 * Fake rmt(8) server for hashing and storing files
4 *
5 * (c) 2010 Mark Wooding
6 */
7
8 /*----- Licensing notice --------------------------------------------------*
9 *
10 * This file is part of the distorted.org.uk backup suite.
11 *
12 * distorted-backup is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or
15 * (at your option) any later version.
16 *
17 * distorted-backup is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License along
23 * with distorted-backup; if not, write to the Free Software Foundation,
24 * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
25 */
26
27 /*----- Header files ------------------------------------------------------*/
28
29 #define _FILE_OFFSET_BITS 64
30
31 #include <ctype.h>
32 #include <errno.h>
33 #include <limits.h>
34 #include <signal.h>
35 #include <stdio.h>
36 #include <stdlib.h>
37 #include <string.h>
38
39 #include <sys/types.h>
40 #include <unistd.h>
41 #include <fcntl.h>
42
43 #include <getopt.h>
44
45 #include <pwd.h>
46
47 #include <mLib/dstr.h>
48 #include <mLib/quis.h>
49 #include <mLib/report.h>
50
51 #include <nettle/sha.h>
52
53 /*----- Configuration -----------------------------------------------------*/
54
55 #ifndef BKP
56 # define BKP "/mnt/bkp"
57 #endif
58
59 /*----- Main code ---------------------------------------------------------*/
60
61 #define BUFSZ 10240
62 #define LSEEK_GET_TAPEPOS 10
63 #define LSEEK_GO2_TAPEPOS 11
64
65 struct flag {
66 const char *name;
67 int f;
68 };
69
70 static const struct flag openflag[] = {
71 /* ;;; Emacs Lisp to generate the table below. Place your cursor just
72 ;;; after the closing `)' and press C-x C-e.
73
74 (let ((flags '(rdonly wronly rdwr creat excl trunc nonblock ndelay
75 noctty append dsync rsync sync cloexec async
76 direct noatime nofollow shlock exlock defer)))
77 (save-excursion
78 (goto-char (point-min))
79 (search-forward (concat "***" "BEGIN openflag" "***"))
80 (beginning-of-line 2)
81 (delete-region (point)
82 (progn
83 (search-forward "***END***")
84 (beginning-of-line)
85 (point)))
86 (dolist (f (sort (copy-list flags) #'string<))
87 (let ((up (upcase (symbol-name f))))
88 (insert (format "#ifdef O_%s\n" up))
89 (insert (format " { \"%s\", O_%s },\n" up up))
90 (insert "#endif\n")))))
91 */
92 /***BEGIN openflag***/
93 #ifdef O_APPEND
94 { "APPEND", O_APPEND },
95 #endif
96 #ifdef O_ASYNC
97 { "ASYNC", O_ASYNC },
98 #endif
99 #ifdef O_CLOEXEC
100 { "CLOEXEC", O_CLOEXEC },
101 #endif
102 #ifdef O_CREAT
103 { "CREAT", O_CREAT },
104 #endif
105 #ifdef O_DEFER
106 { "DEFER", O_DEFER },
107 #endif
108 #ifdef O_DIRECT
109 { "DIRECT", O_DIRECT },
110 #endif
111 #ifdef O_DSYNC
112 { "DSYNC", O_DSYNC },
113 #endif
114 #ifdef O_EXCL
115 { "EXCL", O_EXCL },
116 #endif
117 #ifdef O_EXLOCK
118 { "EXLOCK", O_EXLOCK },
119 #endif
120 #ifdef O_NDELAY
121 { "NDELAY", O_NDELAY },
122 #endif
123 #ifdef O_NOATIME
124 { "NOATIME", O_NOATIME },
125 #endif
126 #ifdef O_NOCTTY
127 { "NOCTTY", O_NOCTTY },
128 #endif
129 #ifdef O_NOFOLLOW
130 { "NOFOLLOW", O_NOFOLLOW },
131 #endif
132 #ifdef O_NONBLOCK
133 { "NONBLOCK", O_NONBLOCK },
134 #endif
135 #ifdef O_RDONLY
136 { "RDONLY", O_RDONLY },
137 #endif
138 #ifdef O_RDWR
139 { "RDWR", O_RDWR },
140 #endif
141 #ifdef O_RSYNC
142 { "RSYNC", O_RSYNC },
143 #endif
144 #ifdef O_SHLOCK
145 { "SHLOCK", O_SHLOCK },
146 #endif
147 #ifdef O_SYNC
148 { "SYNC", O_SYNC },
149 #endif
150 #ifdef O_TRUNC
151 { "TRUNC", O_TRUNC },
152 #endif
153 #ifdef O_WRONLY
154 { "WRONLY", O_WRONLY },
155 #endif
156 /***END***/
157 { 0, 0 }
158 };
159
160 int main(int argc, char *argv[])
161 {
162 int ch;
163 dstr d = DSTR_INIT, dd = DSTR_INIT;
164 unsigned char buf[BUFSZ];
165 int fd = -1, hfd = -1;
166 off_t rc;
167 off_t off = 0;
168 struct passwd *pw;
169 uid_t u;
170 unsigned f = 0;
171 #define f_bogus 1u
172 const char *p = 0;
173 const char *bkp = 0, *host = 0;
174 struct sha256_ctx hc;
175
176 ego(argv[0]);
177 setvbuf(stdin, 0, _IONBF, 0);
178 signal(SIGPIPE, SIG_IGN);
179
180 for (;;) {
181 int o = getopt(argc, argv, "H:r:");
182 if (o < 0) break;
183 switch (o) {
184 case 'H': host = optarg; break;
185 case 'r': bkp = optarg; break;
186 default: f |= f_bogus; break;
187 }
188 }
189 argc -= optind; argv += optind;
190 if ((f & f_bogus) || argc) {
191 pquis(stderr, "usage: $ [-r ROOT] [-H HOST]\n");
192 exit(1);
193 }
194
195 if (!bkp) bkp = getenv("BKP");
196 if (!bkp) bkp = BKP;
197
198 if (!host) host = getenv("BKP_HOST");
199
200 if (!host) {
201 p = getenv("USER");
202 if (!p) p = getenv("LOGNAME");
203 if (!p) {
204 u = getuid();
205 if ((pw = getpwuid(u)) == 0)
206 die(1, "no passwd entry (you don't exist?)");
207 p = pw->pw_name;
208 }
209 if (strncmp(p, "bkp-", 4) != 0) {
210 die(1, "can't deduce host name: "
211 "login name `%s' doesn't begin with `bkp-'",
212 p);
213 }
214 host = p + 4;
215 }
216
217 for (;;) {
218 if (fflush(stdout)) goto fail;
219 ch = getchar();
220 if (ch == EOF) break;
221 DRESET(&d); DRESET(&dd); rc = 0;
222
223 #define CHECKFD do { if (fd < 0) goto badf; } while (0)
224 #define ERROR(what) do moan(what ": %s", strerror(errno)); while (0)
225 #define ERROR1(what, arg) \
226 do moan(what ": %s", arg, strerror(errno)); while (0)
227 #define ARG(d) do { \
228 if (dstr_putline(&d, stdin) == EOF || ferror(stdin) || feof(stdin)) \
229 { moan("read (stdin)", strerror(errno)); goto fail; } \
230 } while (0)
231
232 #define SKIPWS do { while (isspace((unsigned char)*p)) p++; } while (0)
233
234 switch (ch) {
235
236 case 'O': {
237 /* Ofile\nmode\n -- open file */
238
239 const struct flag *ff;
240 char *p, *q;
241 size_t n;
242 long mode, f;
243
244 ARG(d); ARG(dd);
245 if (fd >= 0 && close(fd)) ERROR("close (fd)");
246 if (hfd >= 0 && close(hfd)) ERROR("close (hash)");
247 fd = hfd = -1;
248
249 if (chdir(bkp) || chdir(host)) ERROR1("chdir (%s)", host);
250 p = d.buf;
251 if ((q = strchr(p, '/')) == 0)
252 { moan("bad path: missing `/')"); goto inval; }
253 *q++ = 0;
254 if (chdir(p) || chdir("prepare/incoming")) ERROR1("chdir (%s)", p);
255
256 memmove(d.buf, q, d.len - (q - d.buf) + 1);
257 d.len -= q - d.buf;
258
259 errno = 0;
260 mode = strtol(dd.buf, &p, 0);
261 if (errno) ERROR("bad mode");
262 else if (mode < 0 || mode > INT_MAX)
263 { moan("bad mode: range"); goto range; }
264 SKIPWS;
265 if (!*p) {
266 switch (mode & O_ACCMODE) {
267 case O_RDONLY: mode = O_RDONLY; break;
268 case O_WRONLY: mode = O_WRONLY | O_TRUNC | O_CREAT; break;
269 case O_RDWR: mode = O_RDWR;
270 default: moan("bad mode: unknown access type"); goto inval;
271 }
272 } else {
273 mode = 0;
274 for (;;) {
275 if (p[0] == 'O' && p[1] == '_') {
276 p += 2;
277 n = strcspn(p, " \t|");
278 for (ff = openflag; ff->name; ff++) {
279 if (strncmp(p, ff->name, n) == 0 && !ff->name[n])
280 goto ofmatch;
281 }
282 moan("bad mode: unknown flag O_%.*s", (int)n, p);
283 goto inval;
284 ofmatch:
285 mode |= ff->f;
286 p += n;
287 } else if (isdigit((unsigned long)*p)) {
288 errno = 0;
289 f = strtol(p, &p, 0);
290 if (errno) ERROR("bad mode");
291 else if (f < 0 || f > INT_MAX)
292 { moan("bad mode: range"); goto range; }
293 mode |= f;
294 } else {
295 moan("bad mode: unexpected token");
296 goto inval;
297 }
298 SKIPWS;
299 if (!*p)
300 break;
301 else if (*p != '|') {
302 moan("bad mode: expected `|'");
303 goto inval;
304 }
305 p++;
306 SKIPWS;
307 }
308 }
309
310 if ((fd = open(d.buf, mode, 0666)) < 0) ERROR1("open (%s)", d.buf);
311 if ((mode & O_ACCMODE) == O_WRONLY) {
312 DPUTS(&d, ".hash");
313 if ((hfd = open(d.buf,
314 mode & (O_ACCMODE | O_CREAT | O_EXCL | O_TRUNC),
315 0666)) < 0) {
316 close(fd); fd = -1;
317 ERROR1("open (%s)", d.buf);
318 }
319 sha256_init(&hc);
320 }
321 off = 0;
322 } break;
323
324 case 'C': {
325 /* Chunoz\n -- close file */
326
327 uint8_t h[SHA256_DIGEST_SIZE], *p;
328 char hex[SHA256_DIGEST_SIZE * 2 + 1], *q;
329 unsigned i;
330
331 ARG(d); CHECKFD;
332 if (close(fd)) ERROR("close (fd)");
333 fd = -1;
334 if (hfd >= 0) {
335 sha256_digest(&hc, sizeof(h), h);
336 for (p = h, q = hex; p < h + sizeof(h); p++, q += 2)
337 sprintf(q, "%02x", *p);
338 *q++ = '\n';
339 errno = EIO;
340 if (write(hfd, hex, sizeof(hex)) < sizeof(hex) || close(hfd))
341 ERROR("close (hash)");
342 hfd = -1;
343 }
344 } break;
345
346 case 'L': {
347 /* Loffset\nwhence\n -- seek
348 * (warning: the manual page gets these the wrong way round)
349 */
350
351 int whence;
352 off_t offset;
353
354 ARG(d); ARG(dd); CHECKFD;
355 offset = atoi(d.buf); whence = strtoull(dd.buf, 0, 0);
356 switch (whence) {
357 case LSEEK_GET_TAPEPOS: whence = SEEK_CUR; offset = 0; break;
358 case LSEEK_GO2_TAPEPOS: whence = SEEK_SET; break;
359 }
360 switch (whence) {
361 case SEEK_CUR:
362 if (!offset) { rc = off; break; }
363 default:
364 rc = lseek(fd, offset, whence);
365 if (rc == (off_t)-1) ERROR("seek");
366 off = rc;
367 break;
368 }
369 } break;
370
371 case 'W': {
372 /* Wlen\ndata... -- write */
373
374 size_t n, nn, sz;
375 ssize_t ssz;
376 unsigned char *p;
377 int botch = 0;
378
379 ARG(d); CHECKFD;
380 rc = sz = strtoul(d.buf, 0, 0);
381 while (sz) {
382 nn = sz > BUFSZ ? BUFSZ : sz;
383 n = fread(buf, 1, nn, stdin);
384 if (n < nn) {
385 if (feof(stdin)) { moan("eof on stdin"); goto fail;}
386 else ERROR("read (stdin)");
387 }
388 if (hfd >= 0) sha256_update(&hc, n, buf);
389 p = buf;
390 while (!botch && n) {
391 if ((ssz = write(fd, p, n)) > 0) {
392 p += ssz; off += ssz; n -= ssz;
393 } else if (!ssz) { moan("zero-length write"); goto fail; }
394 else if (errno != EINTR) { botch = errno; }
395 }
396 sz -= nn;
397 }
398 if (botch) { errno = botch; ERROR("write"); }
399 } break;
400
401 case 'R': {
402 /* Rlen\n -- read */
403
404 size_t nn;
405 ssize_t ssz;
406
407 ARG(d); CHECKFD;
408 nn = strtoul(d.buf, 0, 0); if (nn > BUFSZ) nn = BUFSZ;
409 if ((ssz = read(fd, buf, nn)) < 0) ERROR("read");
410 off += ssz;
411 printf("A%ld\n", (long)ssz);
412 if (fwrite(buf, 1, ssz, stdout) < ssz)
413 { moan("write (stdout): %s", strerror(errno)); goto fail; }
414 continue;
415 } break;
416
417 case 'i': case 'I':
418 /* Iop\ncount\n -- ioctl */
419 ARG(d); ARG(dd); CHECKFD; goto notty;
420
421 case 'S':
422 /* S -- ioctl */
423 CHECKFD; goto notty;
424 case 's':
425 /* sop -- ioctl */
426
427 if ((ch = getchar()) == EOF) goto fail;
428 CHECKFD; goto notty;
429
430 default:
431 goto fail;
432 }
433
434 printf("A%llu\n", (unsigned long long)rc);
435 continue;
436
437 badf: errno = EBADF; goto error;
438 range: errno = ERANGE; goto error;
439 inval: errno = EINVAL; goto error;
440 notty: errno = ENOTTY; goto error;
441 error:
442 printf("E%d\n%s\n", errno, strerror(errno));
443 continue;
444 }
445 if (fflush(stdout) || ferror(stdout) || ferror(stdin)) goto fail;
446 return (0);
447 fail:
448 return (1);
449 }
450
451 /*----- That's all, folks -------------------------------------------------*/