chiark / gitweb /
Fix up licensing banners on the various files.
[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 -------------------------------------------------*/