1*b7041c07Sderaadt /* $OpenBSD: downloader.c,v 1.23 2021/10/24 21:24:17 deraadt Exp $ */
260a32ee9Sbenno /*
360a32ee9Sbenno * Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
460a32ee9Sbenno *
560a32ee9Sbenno * Permission to use, copy, modify, and distribute this software for any
660a32ee9Sbenno * purpose with or without fee is hereby granted, provided that the above
760a32ee9Sbenno * copyright notice and this permission notice appear in all copies.
860a32ee9Sbenno *
960a32ee9Sbenno * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
1060a32ee9Sbenno * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
1160a32ee9Sbenno * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
1260a32ee9Sbenno * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
1360a32ee9Sbenno * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
1460a32ee9Sbenno * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
1560a32ee9Sbenno * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1660a32ee9Sbenno */
1760a32ee9Sbenno #include <sys/mman.h>
1860a32ee9Sbenno #include <sys/stat.h>
1960a32ee9Sbenno
2060a32ee9Sbenno #include <assert.h>
2160a32ee9Sbenno #include <errno.h>
2260a32ee9Sbenno #include <fcntl.h>
2360a32ee9Sbenno #include <inttypes.h>
2460a32ee9Sbenno #include <math.h>
2560a32ee9Sbenno #include <stdio.h>
2660a32ee9Sbenno #include <stdlib.h>
2760a32ee9Sbenno #include <string.h>
2860a32ee9Sbenno #include <time.h>
2960a32ee9Sbenno #include <unistd.h>
3060a32ee9Sbenno
3134b470cbStb #include <openssl/md4.h>
3234b470cbStb
3360a32ee9Sbenno #include "extern.h"
3460a32ee9Sbenno
3560a32ee9Sbenno /*
3660a32ee9Sbenno * A small optimisation: have a 1 MB pre-write buffer.
3760a32ee9Sbenno * Disable the pre-write buffer by having this be zero.
3860a32ee9Sbenno * (It doesn't affect performance much.)
3960a32ee9Sbenno */
4060a32ee9Sbenno #define OBUF_SIZE (1024 * 1024)
4160a32ee9Sbenno
4260a32ee9Sbenno enum downloadst {
4360a32ee9Sbenno DOWNLOAD_READ_NEXT = 0,
4460a32ee9Sbenno DOWNLOAD_READ_LOCAL,
4560a32ee9Sbenno DOWNLOAD_READ_REMOTE
4660a32ee9Sbenno };
4760a32ee9Sbenno
4860a32ee9Sbenno /*
4960a32ee9Sbenno * Like struct upload, but used to keep track of what we're downloading.
5060a32ee9Sbenno * This also is managed by the receiver process.
5160a32ee9Sbenno */
5260a32ee9Sbenno struct download {
5360a32ee9Sbenno enum downloadst state; /* state of affairs */
5460a32ee9Sbenno size_t idx; /* index of current file */
5560a32ee9Sbenno struct blkset blk; /* its blocks */
5660a32ee9Sbenno void *map; /* mmap of current file */
5760a32ee9Sbenno size_t mapsz; /* length of mapsz */
5860a32ee9Sbenno int ofd; /* open origin file */
5960a32ee9Sbenno int fd; /* open output file */
6060a32ee9Sbenno char *fname; /* output filename */
6160a32ee9Sbenno MD4_CTX ctx; /* current hashing context */
6260a32ee9Sbenno off_t downloaded; /* total downloaded */
6360a32ee9Sbenno off_t total; /* total in file */
6460a32ee9Sbenno const struct flist *fl; /* file list */
6560a32ee9Sbenno size_t flsz; /* size of file list */
6660a32ee9Sbenno int rootfd; /* destination directory */
6760a32ee9Sbenno int fdin; /* read descriptor from sender */
6860a32ee9Sbenno char *obuf; /* pre-write buffer */
6960a32ee9Sbenno size_t obufsz; /* current size of obuf */
7060a32ee9Sbenno size_t obufmax; /* max size we'll wbuffer */
7160a32ee9Sbenno };
7260a32ee9Sbenno
7360a32ee9Sbenno
7460a32ee9Sbenno /*
7560a32ee9Sbenno * Simply log the filename.
7660a32ee9Sbenno */
7760a32ee9Sbenno static void
log_file(struct sess * sess,const struct download * dl,const struct flist * f)7860a32ee9Sbenno log_file(struct sess *sess,
7960a32ee9Sbenno const struct download *dl, const struct flist *f)
8060a32ee9Sbenno {
8160a32ee9Sbenno float frac, tot = dl->total;
8260a32ee9Sbenno int prec = 0;
8360a32ee9Sbenno const char *unit = "B";
8460a32ee9Sbenno
8560a32ee9Sbenno if (sess->opts->server)
8660a32ee9Sbenno return;
8760a32ee9Sbenno
88ed5cc9fbSderaadt frac = (dl->total == 0) ? 100.0 :
8960a32ee9Sbenno 100.0 * dl->downloaded / dl->total;
9060a32ee9Sbenno
9160a32ee9Sbenno if (dl->total > 1024 * 1024 * 1024) {
9260a32ee9Sbenno tot = dl->total / (1024. * 1024. * 1024.);
9360a32ee9Sbenno prec = 3;
9460a32ee9Sbenno unit = "GB";
9560a32ee9Sbenno } else if (dl->total > 1024 * 1024) {
9660a32ee9Sbenno tot = dl->total / (1024. * 1024.);
9760a32ee9Sbenno prec = 2;
9860a32ee9Sbenno unit = "MB";
9960a32ee9Sbenno } else if (dl->total > 1024) {
10060a32ee9Sbenno tot = dl->total / 1024.;
10160a32ee9Sbenno prec = 1;
10260a32ee9Sbenno unit = "KB";
10360a32ee9Sbenno }
10460a32ee9Sbenno
105b2a7eac7Sbenno LOG1("%s (%.*f %s, %.1f%% downloaded)",
10660a32ee9Sbenno f->path, prec, tot, unit, frac);
10760a32ee9Sbenno }
10860a32ee9Sbenno
10960a32ee9Sbenno /*
11060a32ee9Sbenno * Reinitialise a download context w/o overwriting the persistent parts
11160a32ee9Sbenno * of the structure (like p->fl or p->flsz) for index "idx".
11260a32ee9Sbenno * The MD4 context is pre-seeded.
11360a32ee9Sbenno */
11460a32ee9Sbenno static void
download_reinit(struct sess * sess,struct download * p,size_t idx)11560a32ee9Sbenno download_reinit(struct sess *sess, struct download *p, size_t idx)
11660a32ee9Sbenno {
11760a32ee9Sbenno int32_t seed = htole32(sess->seed);
11860a32ee9Sbenno
119f1dcb30aSderaadt assert(p->state == DOWNLOAD_READ_NEXT);
12060a32ee9Sbenno
12160a32ee9Sbenno p->idx = idx;
12260a32ee9Sbenno memset(&p->blk, 0, sizeof(struct blkset));
12360a32ee9Sbenno p->map = MAP_FAILED;
12460a32ee9Sbenno p->mapsz = 0;
12560a32ee9Sbenno p->ofd = -1;
12660a32ee9Sbenno p->fd = -1;
12760a32ee9Sbenno p->fname = NULL;
12860a32ee9Sbenno MD4_Init(&p->ctx);
12960a32ee9Sbenno p->downloaded = p->total = 0;
13060a32ee9Sbenno /* Don't touch p->fl. */
13160a32ee9Sbenno /* Don't touch p->flsz. */
13260a32ee9Sbenno /* Don't touch p->rootfd. */
13360a32ee9Sbenno /* Don't touch p->fdin. */
13460a32ee9Sbenno MD4_Update(&p->ctx, &seed, sizeof(int32_t));
13560a32ee9Sbenno }
13660a32ee9Sbenno
13760a32ee9Sbenno /*
13860a32ee9Sbenno * Free a download context.
13960a32ee9Sbenno * If "cleanup" is non-zero, we also try to clean up the temporary file,
14060a32ee9Sbenno * assuming that it has been opened in p->fd.
14160a32ee9Sbenno */
14260a32ee9Sbenno static void
download_cleanup(struct download * p,int cleanup)14360a32ee9Sbenno download_cleanup(struct download *p, int cleanup)
14460a32ee9Sbenno {
14560a32ee9Sbenno
146f1dcb30aSderaadt if (p->map != MAP_FAILED) {
14760a32ee9Sbenno assert(p->mapsz);
14860a32ee9Sbenno munmap(p->map, p->mapsz);
14960a32ee9Sbenno p->map = MAP_FAILED;
15060a32ee9Sbenno p->mapsz = 0;
15160a32ee9Sbenno }
152f1dcb30aSderaadt if (p->ofd != -1) {
15360a32ee9Sbenno close(p->ofd);
15460a32ee9Sbenno p->ofd = -1;
15560a32ee9Sbenno }
156f1dcb30aSderaadt if (p->fd != -1) {
15760a32ee9Sbenno close(p->fd);
158f1dcb30aSderaadt if (cleanup && p->fname != NULL)
15960a32ee9Sbenno unlinkat(p->rootfd, p->fname, 0);
16060a32ee9Sbenno p->fd = -1;
16160a32ee9Sbenno }
16260a32ee9Sbenno free(p->fname);
16360a32ee9Sbenno p->fname = NULL;
16460a32ee9Sbenno p->state = DOWNLOAD_READ_NEXT;
16560a32ee9Sbenno }
16660a32ee9Sbenno
16760a32ee9Sbenno /*
16860a32ee9Sbenno * Initial allocation of the download object using the file list "fl" of
16960a32ee9Sbenno * size "flsz", the destination "rootfd", and the sender read "fdin".
17060a32ee9Sbenno * Returns NULL on allocation failure.
17160a32ee9Sbenno * On success, download_free() must be called with the pointer.
17260a32ee9Sbenno */
17360a32ee9Sbenno struct download *
download_alloc(struct sess * sess,int fdin,const struct flist * fl,size_t flsz,int rootfd)17460a32ee9Sbenno download_alloc(struct sess *sess, int fdin,
17560a32ee9Sbenno const struct flist *fl, size_t flsz, int rootfd)
17660a32ee9Sbenno {
17760a32ee9Sbenno struct download *p;
17860a32ee9Sbenno
179f1dcb30aSderaadt if ((p = malloc(sizeof(struct download))) == NULL) {
180b2a7eac7Sbenno ERR("malloc");
18160a32ee9Sbenno return NULL;
18260a32ee9Sbenno }
18360a32ee9Sbenno
18460a32ee9Sbenno p->state = DOWNLOAD_READ_NEXT;
18560a32ee9Sbenno p->fl = fl;
18660a32ee9Sbenno p->flsz = flsz;
18760a32ee9Sbenno p->rootfd = rootfd;
18860a32ee9Sbenno p->fdin = fdin;
18960a32ee9Sbenno download_reinit(sess, p, 0);
19060a32ee9Sbenno p->obufsz = 0;
19160a32ee9Sbenno p->obuf = NULL;
19260a32ee9Sbenno p->obufmax = OBUF_SIZE;
193f1dcb30aSderaadt if (p->obufmax && (p->obuf = malloc(p->obufmax)) == NULL) {
194b2a7eac7Sbenno ERR("malloc");
19560a32ee9Sbenno free(p);
19660a32ee9Sbenno return NULL;
19760a32ee9Sbenno }
19860a32ee9Sbenno return p;
19960a32ee9Sbenno }
20060a32ee9Sbenno
20160a32ee9Sbenno /*
20260a32ee9Sbenno * Perform all cleanups (including removing stray files) and free.
20360a32ee9Sbenno * Passing a NULL to this function is ok.
20460a32ee9Sbenno */
20560a32ee9Sbenno void
download_free(struct download * p)20660a32ee9Sbenno download_free(struct download *p)
20760a32ee9Sbenno {
20860a32ee9Sbenno
209f1dcb30aSderaadt if (p == NULL)
21060a32ee9Sbenno return;
21160a32ee9Sbenno download_cleanup(p, 1);
21260a32ee9Sbenno free(p->obuf);
21360a32ee9Sbenno free(p);
21460a32ee9Sbenno }
21560a32ee9Sbenno
21660a32ee9Sbenno /*
21760a32ee9Sbenno * Optimisation: instead of dumping directly into the output file, keep
21860a32ee9Sbenno * a buffer and write as much as we can into the buffer.
21960a32ee9Sbenno * That way, we can avoid calling write() too much, and instead call it
22060a32ee9Sbenno * with big buffers.
22160a32ee9Sbenno * To flush the buffer w/o changing it, pass 0 as "sz".
22260a32ee9Sbenno * Returns zero on failure, non-zero on success.
22360a32ee9Sbenno */
22460a32ee9Sbenno static int
buf_copy(const char * buf,size_t sz,struct download * p)225ba617adaSbenno buf_copy(const char *buf, size_t sz, struct download *p)
22660a32ee9Sbenno {
22760a32ee9Sbenno size_t rem, tocopy;
22860a32ee9Sbenno ssize_t ssz;
22960a32ee9Sbenno
23060a32ee9Sbenno assert(p->obufsz <= p->obufmax);
23160a32ee9Sbenno
23260a32ee9Sbenno /*
23360a32ee9Sbenno * Copy as much as we can.
23460a32ee9Sbenno * If we've copied everything, exit.
23560a32ee9Sbenno * If we have no pre-write buffer (obufmax of zero), this never
23660a32ee9Sbenno * gets called, so we never buffer anything.
23760a32ee9Sbenno */
23860a32ee9Sbenno
23960a32ee9Sbenno if (sz && p->obufsz < p->obufmax) {
240f1dcb30aSderaadt assert(p->obuf != NULL);
24160a32ee9Sbenno rem = p->obufmax - p->obufsz;
24260a32ee9Sbenno assert(rem > 0);
24360a32ee9Sbenno tocopy = rem < sz ? rem : sz;
24460a32ee9Sbenno memcpy(p->obuf + p->obufsz, buf, tocopy);
24560a32ee9Sbenno sz -= tocopy;
24660a32ee9Sbenno buf += tocopy;
24760a32ee9Sbenno p->obufsz += tocopy;
24860a32ee9Sbenno assert(p->obufsz <= p->obufmax);
249f1dcb30aSderaadt if (sz == 0)
25060a32ee9Sbenno return 1;
25160a32ee9Sbenno }
25260a32ee9Sbenno
25360a32ee9Sbenno /* Drain the main buffer. */
25460a32ee9Sbenno
25560a32ee9Sbenno if (p->obufsz) {
25660a32ee9Sbenno assert(p->obufmax);
25760a32ee9Sbenno assert(p->obufsz <= p->obufmax);
258f1dcb30aSderaadt assert(p->obuf != NULL);
25960a32ee9Sbenno if ((ssz = write(p->fd, p->obuf, p->obufsz)) < 0) {
260b2a7eac7Sbenno ERR("%s: write", p->fname);
26160a32ee9Sbenno return 0;
26260a32ee9Sbenno } else if ((size_t)ssz != p->obufsz) {
263b2a7eac7Sbenno ERRX("%s: short write", p->fname);
26460a32ee9Sbenno return 0;
26560a32ee9Sbenno }
26660a32ee9Sbenno p->obufsz = 0;
26760a32ee9Sbenno }
26860a32ee9Sbenno
26960a32ee9Sbenno /*
27060a32ee9Sbenno * Now drain anything left.
27160a32ee9Sbenno * If we have no pre-write buffer, this is it.
27260a32ee9Sbenno */
27360a32ee9Sbenno
27460a32ee9Sbenno if (sz) {
27560a32ee9Sbenno if ((ssz = write(p->fd, buf, sz)) < 0) {
276b2a7eac7Sbenno ERR("%s: write", p->fname);
27760a32ee9Sbenno return 0;
27860a32ee9Sbenno } else if ((size_t)ssz != sz) {
279b2a7eac7Sbenno ERRX("%s: short write", p->fname);
28060a32ee9Sbenno return 0;
28160a32ee9Sbenno }
28260a32ee9Sbenno }
28360a32ee9Sbenno return 1;
28460a32ee9Sbenno }
28560a32ee9Sbenno
28660a32ee9Sbenno /*
28760a32ee9Sbenno * The downloader waits on a file the sender is going to give us, opens
28860a32ee9Sbenno * and mmaps the existing file, opens a temporary file, dumps the file
28960a32ee9Sbenno * (or metadata) into the temporary file, then renames.
29060a32ee9Sbenno * This happens in several possible phases to avoid blocking.
29160a32ee9Sbenno * Returns <0 on failure, 0 on no more data (end of phase), >0 on
29260a32ee9Sbenno * success (more data to be read from the sender).
29360a32ee9Sbenno */
29460a32ee9Sbenno int
rsync_downloader(struct download * p,struct sess * sess,int * ofd)29560a32ee9Sbenno rsync_downloader(struct download *p, struct sess *sess, int *ofd)
29660a32ee9Sbenno {
297f1c3f17cSflorian int c;
29860a32ee9Sbenno int32_t idx, rawtok;
29960a32ee9Sbenno const struct flist *f;
300dbed5971Sflorian size_t sz, tok;
30160a32ee9Sbenno struct stat st;
30260a32ee9Sbenno char *buf = NULL;
30360a32ee9Sbenno unsigned char ourmd[MD4_DIGEST_LENGTH],
30460a32ee9Sbenno md[MD4_DIGEST_LENGTH];
30560a32ee9Sbenno
30660a32ee9Sbenno /*
30760a32ee9Sbenno * If we don't have a download already in session, then the next
30860a32ee9Sbenno * one is coming in.
30960a32ee9Sbenno * Read either the stop (phase) signal from the sender or block
31060a32ee9Sbenno * metadata, in which case we open our file and wait for data.
31160a32ee9Sbenno */
31260a32ee9Sbenno
313f1dcb30aSderaadt if (p->state == DOWNLOAD_READ_NEXT) {
31460a32ee9Sbenno if (!io_read_int(sess, p->fdin, &idx)) {
315b2a7eac7Sbenno ERRX1("io_read_int");
31660a32ee9Sbenno return -1;
31760a32ee9Sbenno } else if (idx >= 0 && (size_t)idx >= p->flsz) {
318b2a7eac7Sbenno ERRX("index out of bounds");
31960a32ee9Sbenno return -1;
32060a32ee9Sbenno } else if (idx < 0) {
321b2a7eac7Sbenno LOG3("downloader: phase complete");
32260a32ee9Sbenno return 0;
32360a32ee9Sbenno }
32460a32ee9Sbenno
32560a32ee9Sbenno /* Short-circuit: dry_run mode does nothing. */
32660a32ee9Sbenno
32760a32ee9Sbenno if (sess->opts->dry_run)
32860a32ee9Sbenno return 1;
32960a32ee9Sbenno
33060a32ee9Sbenno /*
33160a32ee9Sbenno * Now get our block information.
33260a32ee9Sbenno * This is all we'll need to reconstruct the file from
33360a32ee9Sbenno * the map, as block sizes are regular.
33460a32ee9Sbenno */
33560a32ee9Sbenno
33660a32ee9Sbenno download_reinit(sess, p, idx);
33760a32ee9Sbenno if (!blk_send_ack(sess, p->fdin, &p->blk)) {
338b2a7eac7Sbenno ERRX1("blk_send_ack");
33960a32ee9Sbenno goto out;
34060a32ee9Sbenno }
34160a32ee9Sbenno
34260a32ee9Sbenno /*
34360a32ee9Sbenno * Next, we want to open the existing file for using as
34460a32ee9Sbenno * block input.
34560a32ee9Sbenno * We do this in a non-blocking way, so if the open
34660a32ee9Sbenno * succeeds, then we'll go reentrant til the file is
34760a32ee9Sbenno * readable and we can mmap() it.
34860a32ee9Sbenno * Set the file descriptor that we want to wait for.
34960a32ee9Sbenno */
35060a32ee9Sbenno
35160a32ee9Sbenno p->state = DOWNLOAD_READ_LOCAL;
35260a32ee9Sbenno f = &p->fl[idx];
353*b7041c07Sderaadt p->ofd = openat(p->rootfd, f->path, O_RDONLY | O_NONBLOCK);
35460a32ee9Sbenno
355f1dcb30aSderaadt if (p->ofd == -1 && errno != ENOENT) {
356b2a7eac7Sbenno ERR("%s: openat", f->path);
35760a32ee9Sbenno goto out;
358f1dcb30aSderaadt } else if (p->ofd != -1) {
35960a32ee9Sbenno *ofd = p->ofd;
36060a32ee9Sbenno return 1;
36160a32ee9Sbenno }
36260a32ee9Sbenno
36360a32ee9Sbenno /* Fall-through: there's no file. */
36460a32ee9Sbenno }
36560a32ee9Sbenno
36660a32ee9Sbenno /*
36760a32ee9Sbenno * At this point, the server is sending us data and we want to
36860a32ee9Sbenno * hoover it up as quickly as possible or we'll deadlock.
36960a32ee9Sbenno * We want to be pulling off of f->fdin as quickly as possible,
37060a32ee9Sbenno * so perform as much buffering as we can.
37160a32ee9Sbenno */
37260a32ee9Sbenno
37360a32ee9Sbenno f = &p->fl[p->idx];
37460a32ee9Sbenno
37560a32ee9Sbenno /*
37660a32ee9Sbenno * Next in sequence: we have an open download session but
37760a32ee9Sbenno * haven't created our temporary file.
37860a32ee9Sbenno * This means that we've already opened (or tried to open) the
37960a32ee9Sbenno * original file in a nonblocking way, and we can map it.
38060a32ee9Sbenno */
38160a32ee9Sbenno
382f1dcb30aSderaadt if (p->state == DOWNLOAD_READ_LOCAL) {
383f1dcb30aSderaadt assert(p->fname == NULL);
38460a32ee9Sbenno
38560a32ee9Sbenno /*
38660a32ee9Sbenno * Try to fstat() the file descriptor if valid and make
38760a32ee9Sbenno * sure that we're still a regular file.
38860a32ee9Sbenno * Then, if it has non-zero size, mmap() it for hashing.
38960a32ee9Sbenno */
39060a32ee9Sbenno
391f1dcb30aSderaadt if (p->ofd != -1 &&
392f1dcb30aSderaadt fstat(p->ofd, &st) == -1) {
393b2a7eac7Sbenno ERR("%s: fstat", f->path);
39460a32ee9Sbenno goto out;
395f1dcb30aSderaadt } else if (p->ofd != -1 && !S_ISREG(st.st_mode)) {
396b2a7eac7Sbenno WARNX("%s: not regular", f->path);
39760a32ee9Sbenno goto out;
39860a32ee9Sbenno }
39960a32ee9Sbenno
400f1dcb30aSderaadt if (p->ofd != -1 && st.st_size > 0) {
40160a32ee9Sbenno p->mapsz = st.st_size;
40260a32ee9Sbenno p->map = mmap(NULL, p->mapsz,
40360a32ee9Sbenno PROT_READ, MAP_SHARED, p->ofd, 0);
404f1dcb30aSderaadt if (p->map == MAP_FAILED) {
405b2a7eac7Sbenno ERR("%s: mmap", f->path);
40660a32ee9Sbenno goto out;
40760a32ee9Sbenno }
40860a32ee9Sbenno }
40960a32ee9Sbenno
41060a32ee9Sbenno /* Success either way: we don't need this. */
41160a32ee9Sbenno
41260a32ee9Sbenno *ofd = -1;
41360a32ee9Sbenno
414dbed5971Sflorian /* Create the temporary file. */
41560a32ee9Sbenno
416ba617adaSbenno if (mktemplate(&p->fname, f->path, sess->opts->recursive) ==
417ba617adaSbenno -1) {
418b2a7eac7Sbenno ERRX1("mktemplate");
41960a32ee9Sbenno goto out;
42060a32ee9Sbenno }
42160a32ee9Sbenno
422dbed5971Sflorian if ((p->fd = mkstempat(p->rootfd, p->fname)) == -1) {
423b2a7eac7Sbenno ERR("mkstempat");
424dbed5971Sflorian goto out;
425dbed5971Sflorian }
426dbed5971Sflorian
42760a32ee9Sbenno /*
42860a32ee9Sbenno * FIXME: we can technically wait until the temporary
42960a32ee9Sbenno * file is writable, but since it's guaranteed to be
43060a32ee9Sbenno * empty, I don't think this is a terribly expensive
43160a32ee9Sbenno * operation as it doesn't involve reading the file into
43260a32ee9Sbenno * memory beforehand.
43360a32ee9Sbenno */
43460a32ee9Sbenno
435b2a7eac7Sbenno LOG3("%s: temporary: %s", f->path, p->fname);
43660a32ee9Sbenno p->state = DOWNLOAD_READ_REMOTE;
43760a32ee9Sbenno return 1;
43860a32ee9Sbenno }
43960a32ee9Sbenno
44060a32ee9Sbenno /*
44160a32ee9Sbenno * This matches the sequence in blk_flush().
44260a32ee9Sbenno * If we've gotten here, then we have a possibly-open map file
44360a32ee9Sbenno * (not for new files) and our temporary file is writable.
44460a32ee9Sbenno * We read the size/token, then optionally the data.
44560a32ee9Sbenno * The size >0 for reading data, 0 for no more data, and <0 for
44660a32ee9Sbenno * a token indicator.
44760a32ee9Sbenno */
44860a32ee9Sbenno
449f1c3f17cSflorian again:
450f1dcb30aSderaadt assert(p->state == DOWNLOAD_READ_REMOTE);
451f1dcb30aSderaadt assert(p->fname != NULL);
452f1dcb30aSderaadt assert(p->fd != -1);
453f1dcb30aSderaadt assert(p->fdin != -1);
45460a32ee9Sbenno
45560a32ee9Sbenno if (!io_read_int(sess, p->fdin, &rawtok)) {
456b2a7eac7Sbenno ERRX1("io_read_int");
45760a32ee9Sbenno goto out;
45860a32ee9Sbenno }
45960a32ee9Sbenno
46060a32ee9Sbenno if (rawtok > 0) {
46160a32ee9Sbenno sz = rawtok;
462f1dcb30aSderaadt if ((buf = malloc(sz)) == NULL) {
463b2a7eac7Sbenno ERR("realloc");
46460a32ee9Sbenno goto out;
46560a32ee9Sbenno }
46660a32ee9Sbenno if (!io_read_buf(sess, p->fdin, buf, sz)) {
467b2a7eac7Sbenno ERRX1("io_read_int");
46860a32ee9Sbenno goto out;
469ba617adaSbenno } else if (!buf_copy(buf, sz, p)) {
470b2a7eac7Sbenno ERRX1("buf_copy");
47160a32ee9Sbenno goto out;
47260a32ee9Sbenno }
47360a32ee9Sbenno p->total += sz;
47460a32ee9Sbenno p->downloaded += sz;
475b2a7eac7Sbenno LOG4("%s: received %zu B block", p->fname, sz);
47660a32ee9Sbenno MD4_Update(&p->ctx, buf, sz);
47760a32ee9Sbenno free(buf);
478f1c3f17cSflorian
479f1c3f17cSflorian /* Fast-track more reads as they arrive. */
480f1c3f17cSflorian
481ba617adaSbenno if ((c = io_read_check(p->fdin)) < 0) {
482b2a7eac7Sbenno ERRX1("io_read_check");
483f1c3f17cSflorian goto out;
484f1c3f17cSflorian } else if (c > 0)
485f1c3f17cSflorian goto again;
486f1c3f17cSflorian
48760a32ee9Sbenno return 1;
48860a32ee9Sbenno } else if (rawtok < 0) {
48960a32ee9Sbenno tok = -rawtok - 1;
49060a32ee9Sbenno if (tok >= p->blk.blksz) {
491b2a7eac7Sbenno ERRX("%s: token not in block set: %zu (have %zu blocks)",
49260a32ee9Sbenno p->fname, tok, p->blk.blksz);
49360a32ee9Sbenno goto out;
49460a32ee9Sbenno }
49560a32ee9Sbenno sz = tok == p->blk.blksz - 1 ? p->blk.rem : p->blk.len;
49660a32ee9Sbenno assert(sz);
497f1dcb30aSderaadt assert(p->map != MAP_FAILED);
49860a32ee9Sbenno buf = p->map + (tok * p->blk.len);
49960a32ee9Sbenno
50060a32ee9Sbenno /*
50160a32ee9Sbenno * Now we read from our block.
50260a32ee9Sbenno * We should only be at this point if we have a
50360a32ee9Sbenno * block to read from, i.e., if we were able to
50460a32ee9Sbenno * map our origin file and create a block
50560a32ee9Sbenno * profile from it.
50660a32ee9Sbenno */
50760a32ee9Sbenno
508f1dcb30aSderaadt assert(p->map != MAP_FAILED);
509ba617adaSbenno if (!buf_copy(buf, sz, p)) {
510b2a7eac7Sbenno ERRX1("buf_copy");
51160a32ee9Sbenno goto out;
51260a32ee9Sbenno }
51360a32ee9Sbenno p->total += sz;
514b2a7eac7Sbenno LOG4("%s: copied %zu B", p->fname, sz);
51560a32ee9Sbenno MD4_Update(&p->ctx, buf, sz);
516f1c3f17cSflorian
517f1c3f17cSflorian /* Fast-track more reads as they arrive. */
518f1c3f17cSflorian
519ba617adaSbenno if ((c = io_read_check(p->fdin)) < 0) {
520b2a7eac7Sbenno ERRX1("io_read_check");
521f1c3f17cSflorian goto out;
522f1c3f17cSflorian } else if (c > 0)
523f1c3f17cSflorian goto again;
524f1c3f17cSflorian
52560a32ee9Sbenno return 1;
52660a32ee9Sbenno }
52760a32ee9Sbenno
528ba617adaSbenno if (!buf_copy(NULL, 0, p)) {
529b2a7eac7Sbenno ERRX1("buf_copy");
53060a32ee9Sbenno goto out;
53160a32ee9Sbenno }
53260a32ee9Sbenno
533f1dcb30aSderaadt assert(rawtok == 0);
534f1dcb30aSderaadt assert(p->obufsz == 0);
53560a32ee9Sbenno
53660a32ee9Sbenno /*
53760a32ee9Sbenno * Make sure our resulting MD4 hashes match.
53860a32ee9Sbenno * FIXME: if the MD4 hashes don't match, then our file has
53960a32ee9Sbenno * changed out from under us.
54060a32ee9Sbenno * This should require us to re-run the sequence in another
54160a32ee9Sbenno * phase.
54260a32ee9Sbenno */
54360a32ee9Sbenno
54460a32ee9Sbenno MD4_Final(ourmd, &p->ctx);
54560a32ee9Sbenno
54660a32ee9Sbenno if (!io_read_buf(sess, p->fdin, md, MD4_DIGEST_LENGTH)) {
547b2a7eac7Sbenno ERRX1("io_read_buf");
54860a32ee9Sbenno goto out;
54960a32ee9Sbenno } else if (memcmp(md, ourmd, MD4_DIGEST_LENGTH)) {
550b2a7eac7Sbenno ERRX("%s: hash does not match", p->fname);
55160a32ee9Sbenno goto out;
55260a32ee9Sbenno }
55360a32ee9Sbenno
554cc85e6afSflorian /* Adjust our file metadata (uid, mode, etc.). */
5552c8ddb5fSbenno
556cc85e6afSflorian if (!rsync_set_metadata(sess, 1, p->fd, f, p->fname)) {
557b2a7eac7Sbenno ERRX1("rsync_set_metadata");
5588778f60eSbenno goto out;
5598778f60eSbenno }
56060a32ee9Sbenno
56160a32ee9Sbenno /* Finally, rename the temporary to the real file. */
56260a32ee9Sbenno
563f1dcb30aSderaadt if (renameat(p->rootfd, p->fname, p->rootfd, f->path) == -1) {
564b2a7eac7Sbenno ERR("%s: renameat: %s", p->fname, f->path);
56560a32ee9Sbenno goto out;
56660a32ee9Sbenno }
56760a32ee9Sbenno
56860a32ee9Sbenno log_file(sess, p, f);
56960a32ee9Sbenno download_cleanup(p, 0);
57060a32ee9Sbenno return 1;
57160a32ee9Sbenno out:
57260a32ee9Sbenno download_cleanup(p, 1);
57360a32ee9Sbenno return -1;
57460a32ee9Sbenno }
575