3 * Fake rmt(8) server for hashing and storing files
5 * (c) 2010 Mark Wooding
8 /*----- Licensing notice --------------------------------------------------*
10 * This file is part of the distorted.org.uk backup suite.
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.
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.
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.
27 /*----- Header files ------------------------------------------------------*/
29 #define _FILE_OFFSET_BITS 64
39 #include <sys/types.h>
47 #include <mLib/dstr.h>
48 #include <mLib/quis.h>
49 #include <mLib/report.h>
51 #include <nettle/sha.h>
53 /*----- Configuration -----------------------------------------------------*/
56 # define BKP "/mnt/bkp"
59 /*----- Main code ---------------------------------------------------------*/
62 #define LSEEK_GET_TAPEPOS 10
63 #define LSEEK_GO2_TAPEPOS 11
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.
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)))
78 (goto-char (point-min))
79 (search-forward (concat "***" "BEGIN openflag" "***"))
81 (delete-region (point)
83 (search-forward "***END***")
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")))))
92 /***BEGIN openflag***/
94 { "APPEND", O_APPEND
},
100 { "CLOEXEC", O_CLOEXEC
},
103 { "CREAT", O_CREAT
},
106 { "DEFER", O_DEFER
},
109 { "DIRECT", O_DIRECT
},
112 { "DSYNC", O_DSYNC
},
118 { "EXLOCK", O_EXLOCK
},
121 { "NDELAY", O_NDELAY
},
124 { "NOATIME", O_NOATIME
},
127 { "NOCTTY", O_NOCTTY
},
130 { "NOFOLLOW", O_NOFOLLOW
},
133 { "NONBLOCK", O_NONBLOCK
},
136 { "RDONLY", O_RDONLY
},
142 { "RSYNC", O_RSYNC
},
145 { "SHLOCK", O_SHLOCK
},
151 { "TRUNC", O_TRUNC
},
154 { "WRONLY", O_WRONLY
},
160 int main(int argc
, char *argv
[])
163 dstr d
= DSTR_INIT
, dd
= DSTR_INIT
;
164 unsigned char buf
[BUFSZ
];
165 int fd
= -1, hfd
= -1;
173 const char *bkp
= 0, *host
= 0;
174 struct sha256_ctx hc
;
177 setvbuf(stdin
, 0, _IONBF
, 0);
178 signal(SIGPIPE
, SIG_IGN
);
181 int o
= getopt(argc
, argv
, "H:r:");
184 case 'H': host
= optarg
; break;
185 case 'r': bkp
= optarg
; break;
186 default: f
|= f_bogus
; break;
189 argc
-= optind
; argv
+= optind
;
190 if ((f
& f_bogus
) || argc
) {
191 pquis(stderr
, "usage: $ [-r ROOT] [-H HOST]\n");
195 if (!bkp
) bkp
= getenv("BKP");
198 if (!host
) host
= getenv("BKP_HOST");
202 if (!p
) p
= getenv("LOGNAME");
205 if ((pw
= getpwuid(u
)) == 0)
206 die(1, "no passwd entry (you don't exist?)");
209 if (strncmp(p
, "bkp-", 4) != 0) {
210 die(1, "can't deduce host name: "
211 "login name `%s' doesn't begin with `bkp-'",
218 if (fflush(stdout
)) goto fail
;
220 if (ch
== EOF
) break;
221 DRESET(&d
); DRESET(&dd
); rc
= 0;
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; } \
232 #define SKIPWS do { while (isspace((unsigned char)*p)) p++; } while (0)
237 /* Ofile\nmode\n -- open file */
239 const struct flag
*ff
;
245 if (fd
>= 0 && close(fd
)) ERROR("close (fd)");
246 if (hfd
>= 0 && close(hfd
)) ERROR("close (hash)");
249 if (chdir(bkp
) || chdir(host
)) ERROR1("chdir (%s)", host
);
251 if ((q
= strchr(p
, '/')) == 0)
252 { moan("bad path: missing `/')"); goto inval
; }
254 if (chdir(p
) || chdir("prepare/incoming")) ERROR1("chdir (%s)", p
);
256 memmove(d
.buf
, q
, d
.len
- (q
- d
.buf
) + 1);
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
; }
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
;
275 if (p
[0] == 'O' && p
[1] == '_') {
277 n
= strcspn(p
, " \t|");
278 for (ff
= openflag
; ff
->name
; ff
++) {
279 if (strncmp(p
, ff
->name
, n
) == 0 && !ff
->name
[n
])
282 moan("bad mode: unknown flag O_%.*s", (int)n
, p
);
287 } else if (isdigit((unsigned long)*p
)) {
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
; }
295 moan("bad mode: unexpected token");
301 else if (*p
!= '|') {
302 moan("bad mode: expected `|'");
310 if ((fd
= open(d
.buf
, mode
, 0666)) < 0) ERROR1("open (%s)", d
.buf
);
311 if ((mode
& O_ACCMODE
) == O_WRONLY
) {
313 if ((hfd
= open(d
.buf
,
314 mode
& (O_ACCMODE
| O_CREAT
| O_EXCL
| O_TRUNC
),
317 ERROR1("open (%s)", d
.buf
);
325 /* Chunoz\n -- close file */
327 uint8_t h
[SHA256_DIGEST_SIZE
], *p
;
328 char hex
[SHA256_DIGEST_SIZE
* 2 + 1], *q
;
332 if (close(fd
)) ERROR("close (fd)");
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
);
340 if (write(hfd
, hex
, sizeof(hex
)) < sizeof(hex
) || close(hfd
))
341 ERROR("close (hash)");
347 /* Loffset\nwhence\n -- seek
348 * (warning: the manual page gets these the wrong way round)
354 ARG(d
); ARG(dd
); CHECKFD
;
355 offset
= atoi(d
.buf
); whence
= strtoull(dd
.buf
, 0, 0);
357 case LSEEK_GET_TAPEPOS
: whence
= SEEK_CUR
; offset
= 0; break;
358 case LSEEK_GO2_TAPEPOS
: whence
= SEEK_SET
; break;
362 if (!offset
) { rc
= off
; break; }
364 rc
= lseek(fd
, offset
, whence
);
365 if (rc
== (off_t
)-1) ERROR("seek");
372 /* Wlen\ndata... -- write */
380 rc
= sz
= strtoul(d
.buf
, 0, 0);
382 nn
= sz
> BUFSZ ? BUFSZ
: sz
;
383 n
= fread(buf
, 1, nn
, stdin
);
385 if (feof(stdin
)) { moan("eof on stdin"); goto fail
;}
386 else ERROR("read (stdin)");
388 if (hfd
>= 0) sha256_update(&hc
, n
, 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
; }
398 if (botch
) { errno
= botch
; ERROR("write"); }
408 nn
= strtoul(d
.buf
, 0, 0); if (nn
> BUFSZ
) nn
= BUFSZ
;
409 if ((ssz
= read(fd
, buf
, nn
)) < 0) ERROR("read");
411 printf("A%ld\n", (long)ssz
);
412 if (fwrite(buf
, 1, ssz
, stdout
) < ssz
)
413 { moan("write (stdout): %s", strerror(errno
)); goto fail
; }
418 /* Iop\ncount\n -- ioctl */
419 ARG(d
); ARG(dd
); CHECKFD
; goto notty
;
427 if ((ch
= getchar()) == EOF
) goto fail
;
434 printf("A%llu\n", (unsigned long long)rc
);
437 badf
: errno
= EBADF
; goto error
;
438 range
: errno
= ERANGE
; goto error
;
439 inval
: errno
= EINVAL
; goto error
;
440 notty
: errno
= ENOTTY
; goto error
;
442 printf("E%d\n%s\n", errno
, strerror(errno
));
445 if (fflush(stdout
) || ferror(stdout
) || ferror(stdin
)) goto fail
;
451 /*----- That's all, folks -------------------------------------------------*/