Commit | Line | Data |
---|---|---|
99248ed2 MW |
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 | * | |
13678d88 MW |
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 | |
99248ed2 MW |
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 | * | |
13678d88 | 17 | * distorted-backup is distributed in the hope that it will be useful, |
99248ed2 MW |
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 | * | |
13678d88 MW |
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, | |
99248ed2 MW |
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 -------------------------------------------------*/ |