version 1.1, 2019/02/10 23:18:28 |
version 1.2, 2019/02/10 23:24:14 |
|
|
*/ |
*/ |
struct download { |
struct download { |
enum downloadst state; /* state of affairs */ |
enum downloadst state; /* state of affairs */ |
size_t idx; /* index of current file */ |
size_t idx; /* index of current file */ |
struct blkset blk; /* its blocks */ |
struct blkset blk; /* its blocks */ |
void *map; /* mmap of current file */ |
void *map; /* mmap of current file */ |
size_t mapsz; /* length of mapsz */ |
size_t mapsz; /* length of mapsz */ |
int ofd; /* open origin file */ |
int ofd; /* open origin file */ |
int fd; /* open output file */ |
int fd; /* open output file */ |
char *fname; /* output filename */ |
char *fname; /* output filename */ |
MD4_CTX ctx; /* current hashing context */ |
MD4_CTX ctx; /* current hashing context */ |
off_t downloaded; /* total downloaded */ |
off_t downloaded; /* total downloaded */ |
off_t total; /* total in file */ |
off_t total; /* total in file */ |
const struct flist *fl; /* file list */ |
const struct flist *fl; /* file list */ |
|
|
* Simply log the filename. |
* Simply log the filename. |
*/ |
*/ |
static void |
static void |
log_file(struct sess *sess, |
log_file(struct sess *sess, |
const struct download *dl, const struct flist *f) |
const struct download *dl, const struct flist *f) |
{ |
{ |
float frac, tot = dl->total; |
float frac, tot = dl->total; |
|
|
if (sess->opts->server) |
if (sess->opts->server) |
return; |
return; |
|
|
frac = 0 == dl->total ? 100.0 : |
frac = 0 == dl->total ? 100.0 : |
100.0 * dl->downloaded / dl->total; |
100.0 * dl->downloaded / dl->total; |
|
|
if (dl->total > 1024 * 1024 * 1024) { |
if (dl->total > 1024 * 1024 * 1024) { |
|
|
unit = "KB"; |
unit = "KB"; |
} |
} |
|
|
LOG1(sess, "%s (%.*f %s, %.1f%% downloaded)", |
LOG1(sess, "%s (%.*f %s, %.1f%% downloaded)", |
f->path, prec, tot, unit, frac); |
f->path, prec, tot, unit, frac); |
} |
} |
|
|
|
|
} |
} |
if (-1 != p->fd) { |
if (-1 != p->fd) { |
close(p->fd); |
close(p->fd); |
if (cleanup && NULL != p->fname) |
if (cleanup && NULL != p->fname) |
unlinkat(p->rootfd, p->fname, 0); |
unlinkat(p->rootfd, p->fname, 0); |
p->fd = -1; |
p->fd = -1; |
} |
} |
|
|
* On success, download_free() must be called with the pointer. |
* On success, download_free() must be called with the pointer. |
*/ |
*/ |
struct download * |
struct download * |
download_alloc(struct sess *sess, int fdin, |
download_alloc(struct sess *sess, int fdin, |
const struct flist *fl, size_t flsz, int rootfd) |
const struct flist *fl, size_t flsz, int rootfd) |
{ |
{ |
struct download *p; |
struct download *p; |
|
|
p->obuf = NULL; |
p->obuf = NULL; |
p->obufmax = OBUF_SIZE; |
p->obufmax = OBUF_SIZE; |
if (p->obufmax && |
if (p->obufmax && |
NULL == (p->obuf = malloc(p->obufmax))) { |
NULL == (p->obuf = malloc(p->obufmax))) { |
ERR(sess, "malloc"); |
ERR(sess, "malloc"); |
free(p); |
free(p); |
return NULL; |
return NULL; |
|
|
* Returns zero on failure, non-zero on success. |
* Returns zero on failure, non-zero on success. |
*/ |
*/ |
static int |
static int |
buf_copy(struct sess *sess, |
buf_copy(struct sess *sess, |
const char *buf, size_t sz, struct download *p) |
const char *buf, size_t sz, struct download *p) |
{ |
{ |
size_t rem, tocopy; |
size_t rem, tocopy; |
|
|
|
|
assert(p->obufsz <= p->obufmax); |
assert(p->obufsz <= p->obufmax); |
|
|
/* |
/* |
* Copy as much as we can. |
* Copy as much as we can. |
* If we've copied everything, exit. |
* If we've copied everything, exit. |
* If we have no pre-write buffer (obufmax of zero), this never |
* If we have no pre-write buffer (obufmax of zero), this never |
|
|
p->obufsz = 0; |
p->obufsz = 0; |
} |
} |
|
|
/* |
/* |
* Now drain anything left. |
* Now drain anything left. |
* If we have no pre-write buffer, this is it. |
* If we have no pre-write buffer, this is it. |
*/ |
*/ |
|
|
size_t sz, dirlen, tok; |
size_t sz, dirlen, tok; |
const char *cp; |
const char *cp; |
mode_t perm; |
mode_t perm; |
struct stat st; |
struct stat st; |
char *buf = NULL; |
char *buf = NULL; |
unsigned char ourmd[MD4_DIGEST_LENGTH], |
unsigned char ourmd[MD4_DIGEST_LENGTH], |
md[MD4_DIGEST_LENGTH]; |
md[MD4_DIGEST_LENGTH]; |
struct timespec tv[2]; |
struct timespec tv[2]; |
|
|
|
|
if (sess->opts->dry_run) |
if (sess->opts->dry_run) |
return 1; |
return 1; |
|
|
/* |
/* |
* Now get our block information. |
* Now get our block information. |
* This is all we'll need to reconstruct the file from |
* This is all we'll need to reconstruct the file from |
* the map, as block sizes are regular. |
* the map, as block sizes are regular. |
|
|
goto out; |
goto out; |
} |
} |
|
|
/* |
/* |
* Next, we want to open the existing file for using as |
* Next, we want to open the existing file for using as |
* block input. |
* block input. |
* We do this in a non-blocking way, so if the open |
* We do this in a non-blocking way, so if the open |
|
|
|
|
p->state = DOWNLOAD_READ_LOCAL; |
p->state = DOWNLOAD_READ_LOCAL; |
f = &p->fl[idx]; |
f = &p->fl[idx]; |
p->ofd = openat(p->rootfd, f->path, |
p->ofd = openat(p->rootfd, f->path, |
O_RDONLY | O_NONBLOCK, 0); |
O_RDONLY | O_NONBLOCK, 0); |
|
|
if (-1 == p->ofd && ENOENT != errno) { |
if (-1 == p->ofd && ENOENT != errno) { |
|
|
if (DOWNLOAD_READ_LOCAL == p->state) { |
if (DOWNLOAD_READ_LOCAL == p->state) { |
assert(NULL == p->fname); |
assert(NULL == p->fname); |
|
|
/* |
/* |
* Try to fstat() the file descriptor if valid and make |
* Try to fstat() the file descriptor if valid and make |
* sure that we're still a regular file. |
* sure that we're still a regular file. |
* Then, if it has non-zero size, mmap() it for hashing. |
* Then, if it has non-zero size, mmap() it for hashing. |
|
|
|
|
if (-1 != p->ofd && st.st_size > 0) { |
if (-1 != p->ofd && st.st_size > 0) { |
p->mapsz = st.st_size; |
p->mapsz = st.st_size; |
p->map = mmap(NULL, p->mapsz, |
p->map = mmap(NULL, p->mapsz, |
PROT_READ, MAP_SHARED, p->ofd, 0); |
PROT_READ, MAP_SHARED, p->ofd, 0); |
if (MAP_FAILED == p->map) { |
if (MAP_FAILED == p->map) { |
ERR(sess, "%s: mmap", f->path); |
ERR(sess, "%s: mmap", f->path); |
|
|
|
|
*ofd = -1; |
*ofd = -1; |
|
|
/* |
/* |
* Create the temporary file. |
* Create the temporary file. |
* Use a simple scheme of path/.FILE.RANDOM, where we |
* Use a simple scheme of path/.FILE.RANDOM, where we |
* fill in RANDOM with an arc4random number. |
* fill in RANDOM with an arc4random number. |
|
|
f->path + dirlen + 1, hash) < 0) |
f->path + dirlen + 1, hash) < 0) |
p->fname = NULL; |
p->fname = NULL; |
} else { |
} else { |
if (asprintf(&p->fname, ".%s.%" PRIu32, |
if (asprintf(&p->fname, ".%s.%" PRIu32, |
f->path, hash) < 0) |
f->path, hash) < 0) |
p->fname = NULL; |
p->fname = NULL; |
} |
} |
|
|
goto out; |
goto out; |
} |
} |
|
|
/* |
/* |
* Inherit permissions from the source file if we're new |
* Inherit permissions from the source file if we're new |
* or specifically told with -p. |
* or specifically told with -p. |
*/ |
*/ |
|
|
else |
else |
perm = f->st.mode; |
perm = f->st.mode; |
|
|
p->fd = openat(p->rootfd, p->fname, |
p->fd = openat(p->rootfd, p->fname, |
O_APPEND|O_WRONLY|O_CREAT|O_EXCL, perm); |
O_APPEND|O_WRONLY|O_CREAT|O_EXCL, perm); |
|
|
if (-1 == p->fd) { |
if (-1 == p->fd) { |
|
|
goto out; |
goto out; |
} |
} |
|
|
/* |
/* |
* FIXME: we can technically wait until the temporary |
* FIXME: we can technically wait until the temporary |
* file is writable, but since it's guaranteed to be |
* file is writable, but since it's guaranteed to be |
* empty, I don't think this is a terribly expensive |
* empty, I don't think this is a terribly expensive |
|
|
if ( ! io_read_int(sess, p->fdin, &rawtok)) { |
if ( ! io_read_int(sess, p->fdin, &rawtok)) { |
ERRX1(sess, "io_read_int"); |
ERRX1(sess, "io_read_int"); |
goto out; |
goto out; |
} |
} |
|
|
if (rawtok > 0) { |
if (rawtok > 0) { |
sz = rawtok; |
sz = rawtok; |
|
|
tok = -rawtok - 1; |
tok = -rawtok - 1; |
if (tok >= p->blk.blksz) { |
if (tok >= p->blk.blksz) { |
ERRX(sess, "%s: token not in block " |
ERRX(sess, "%s: token not in block " |
"set: %zu (have %zu blocks)", |
"set: %zu (have %zu blocks)", |
p->fname, tok, p->blk.blksz); |
p->fname, tok, p->blk.blksz); |
goto out; |
goto out; |
} |
} |
|
|
assert(0 == rawtok); |
assert(0 == rawtok); |
assert(0 == p->obufsz); |
assert(0 == p->obufsz); |
|
|
/* |
/* |
* Make sure our resulting MD4 hashes match. |
* Make sure our resulting MD4 hashes match. |
* FIXME: if the MD4 hashes don't match, then our file has |
* FIXME: if the MD4 hashes don't match, then our file has |
* changed out from under us. |
* changed out from under us. |