/*
* zsync - client side rsync over http
* Copyright (C) 2004,2005 Colin Phipps <cph@moria.org.uk>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the Artistic License v2 (see the accompanying
* file COPYING for the full license terms), or, at your option, any later
* version of the same license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* COPYING file for details.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <ctype.h>
#include "config.h"
#include "libzsync/zsync.h"
#include "http.h"
#include "url.h"
#include "progress.h"
void read_seed_file(struct zsync_state* z, const char* fname) {
if (zsync_hint_decompress(z) && strlen(fname) > 3 && !strcmp(fname + strlen(fname) - 3,".gz")) {
FILE* f;
{
char* cmd = malloc(6 + strlen(fname)*2);
if (!cmd) return;
strcpy(cmd,"zcat ");
{
int i,j;
for (i=0,j=5; fname[i]; i++) {
if (!isalnum(fname[i])) cmd[j++] = '\\';
cmd[j++] = fname[i];
}
cmd[j] = 0;
}
if (!no_progress) fprintf(stderr,"reading seed %s: ",cmd);
f = popen(cmd,"r");
free(cmd);
}
if (!f) {
perror("popen"); fprintf(stderr,"not using seed file %s\n",fname);
} else {
zsync_submit_source_file(z, f, !no_progress);
if (pclose(f) != 0) {
perror("close");
}
}
} else {
FILE* f = fopen(fname,"r");
if (!no_progress) fprintf(stderr,"reading seed file %s: ",fname);
if (!f) {
perror("open"); fprintf(stderr,"not using seed file %s\n",fname);
} else {
zsync_submit_source_file(z, f, !no_progress);
if (fclose(f) != 0) {
perror("close");
}
}
}
{
long long done,total;
zsync_progress(z, &done, &total);
if (!no_progress) fprintf(stderr,"\rRead %s. Target %02.1f%% complete. \n",fname,(100.0f * done)/total);
}
}
long long http_down;
static void** append_ptrlist(int *n, void** p, void* a) {
if (!a) return p;
p = realloc(p,(*n + 1) * sizeof *p);
if (!p) { fprintf(stderr,"out of memory\n"); exit(1); }
p[*n] = a;
(*n)++;
return p;
}
struct zsync_state* read_zsync_control_file(const char* p, const char* fn)
{
FILE* f;
struct zsync_state* zs;
char* lastpath = NULL;
f = fopen(p,"r");
if (!f) {
if (memcmp(p,"http://",7)) {
perror(p); exit(2);
}
f = http_get(p,&lastpath,fn);
if (!f) {
fprintf(stderr,"could not read control file from URL %s\n",p);
exit(3);
}
referer = lastpath;
}
if ((zs = zsync_begin(f)) == NULL) { exit(1); }
if (fclose(f) != 0) { perror("fclose"); exit(2); }
return zs;
}
static char* get_filename_prefix(const char* p) {
char* s = strdup(p);
char *t = strrchr(s,'/');
char *u;
if (t) *t++ = 0;
else t = s;
u = t;
while (isalnum(*u)) { u++; }
*u = 0;
if (*t > 0)
t = strdup(t);
else
t = NULL;
free(s);
return t;
}
char* get_filename(const struct zsync_state* zs, const char* source_name)
{
char* p = zsync_filename(zs);
char* filename = NULL;
if (p) {
if (strchr(p,'/')) {
fprintf(stderr,"Rejected filename specified in %s, contained path component.\n",source_name);
free(p);
} else {
char *t = get_filename_prefix(source_name);
if (t && !memcmp(p,t,strlen(t)))
filename = p;
else
free(p);
if (t && !filename) {
fprintf(stderr,"Rejected filename specified in %s - prefix %s differed from filename %s.\n",source_name, t, p);
}
free(t);
}
}
if (!filename) {
filename = get_filename_prefix(source_name);
if (!filename) filename = strdup("zsync-download");
}
return filename;
}
static float calc_zsync_progress(const struct zsync_state* zs)
{
long long zgot, ztot;
zsync_progress(zs, &zgot, &ztot);
return (100.0f*zgot / ztot);
}
#define BUFFERSIZE 8192
int fetch_remaining_blocks_http(struct zsync_state* z, const char* url, int type)
{
int ret = 0;
struct range_fetch* rf;
unsigned char* buf;
struct zsync_receiver* zr;
char *u = make_url_absolute(referer, url);
if (!u) {
fprintf(stderr,"URL '%s' from the .zsync file is relative, but I don't know the referer URL (you probably downloaded the .zsync separately and gave it to me as a file). I need to know the referring URL (the URL of the .zsync) in order to locate the download. You can specify this with -u (or edit the URL line(s) in the .zsync file you have).\n",url);
return -1;
}
rf = range_fetch_start(u);
if (!rf) { free(u); return -1; }
zr = zsync_begin_receive(z, type);
if (!zr) { range_fetch_end(rf); free(u); return -1; }
if (!no_progress) fprintf(stderr,"downloading from %s:",u);
buf = malloc(BUFFERSIZE);
if (!buf) { zsync_end_receive(zr); range_fetch_end(rf); free(u); return -1; }
{
int nrange;
off64_t *zbyterange;
zbyterange = zsync_needed_byte_ranges(z, &nrange, type);
if (!zbyterange) return 1;
if (nrange == 0) return 0;
range_fetch_addranges(rf, zbyterange, nrange);
free(zbyterange);
}
{
int len;
off64_t zoffset;
struct progress p = {0,0,0,0};
if (!no_progress) fputc('\n',stderr);
if (!no_progress)
do_progress(&p,calc_zsync_progress(z),range_fetch_bytes_down(rf));
while (!ret && (len = get_range_block(rf, &zoffset, buf, BUFFERSIZE)) > 0) {
if (zsync_receive_data(zr, buf, zoffset, len) != 0)
ret = 1;
if (!no_progress)
do_progress(&p,calc_zsync_progress(z),range_fetch_bytes_down(rf));
zoffset += len; // Needed in case next call returns len=0 and we need to signal where the EOF was.
}
if (len < 0) ret = -1;
else
zsync_receive_data(zr, NULL, zoffset, 0);
if (!no_progress) end_progress(&p,zsync_status(z) >= 2 ? 2 : len == 0 ? 1 : 0);
}
free(buf);
http_down += range_fetch_bytes_down(rf);
zsync_end_receive(zr);
range_fetch_end(rf);
free(u);
return ret;
}
int fetch_remaining_blocks(struct zsync_state* zs)
{
int n, utype;
const char * const * url = zsync_get_urls(zs, &n, &utype);
int *status;
int ok_urls = n;
if (!url) {
fprintf(stderr,"no URLs available from zsync?");
return 1;
}
status = calloc(n, sizeof *status);
while (zsync_status(zs) < 2 && ok_urls) {
/* Still need data */
int try = rand() % n;
if (!status[try]) {
const char* tryurl = url[try];
int rc = fetch_remaining_blocks_http(zs,tryurl, utype);
if (rc != 0) {
fprintf(stderr,"failed to retrieve from %s\n",tryurl);
status[try] = 1; ok_urls--;
}
}
}
free(status);
return 0;
}
int main(int argc, char** argv) {
struct zsync_state* zs;
char *temp_file = NULL;
char **seedfiles = NULL;
int nseedfiles = 0;
char* filename = NULL;
long long local_used;
char* zfname = NULL;
srand(getpid());
{
int opt;
while ((opt = getopt(argc,argv,"k:o:i:Vsu:")) != -1) {
switch (opt) {
case 'k':
free(zfname); zfname = strdup(optarg);
break;
case 'o':
free(filename); filename = strdup(optarg);
break;
case 'i':
seedfiles = append_ptrlist(&nseedfiles,seedfiles,optarg);
break;
case 'V':
printf(PACKAGE " v" VERSION " (compiled " __DATE__ " " __TIME__ ")\n"
"By Colin Phipps <cph@moria.org.uk>\n"
"Published under the Artistic License v2, see the COPYING file for details.\n");
exit(0);
case 's':
no_progress = 1;
break;
case 'u':
referer = strdup(optarg);
break;
}
}
}
if (optind == argc) {
fprintf(stderr,"No .zsync file specified.\nUsage: zsync http://example.com/some/filename.zsync\n");
exit(3);
} else if (optind < argc-1) {
fprintf(stderr,"Usage: zsync http://example.com/some/filename.zsync\n");
exit(3);
}
if (!isatty(0)) no_progress = 1;
{
char *pr = getenv("http_proxy");
if (pr != NULL) set_proxy_from_string(pr);
}
if ((zs = read_zsync_control_file(argv[optind],zfname)) == NULL)
exit(1);
if (!filename) filename = get_filename(zs, argv[optind]);
temp_file = malloc(strlen(filename)+6);
strcpy(temp_file,filename);
strcat(temp_file,".part");
{
int i;
for (i=0; i<nseedfiles; i++) {
read_seed_file(zs, seedfiles[i]);
}
if (!access(filename,R_OK)) {
read_seed_file(zs, filename);
}
if (!access(temp_file,R_OK)) {
read_seed_file(zs, temp_file);
}
zsync_progress(zs, &local_used, NULL);
if (!local_used) {
fputs("No relevent local data found - I will be downloading the whole file. If that's not what you want, CTRL-C out. You should specify the local file is the old version of the file to download with -i (you might have to decompress it with gzip -d first). Or perhaps you just have no data that helps download the file\n",stderr);
}
}
if (zsync_rename_file(zs, temp_file) != 0) {
perror("rename"); exit(1);
}
if (fetch_remaining_blocks(zs) != 0) {
fprintf(stderr,"failed to retrieve all remaining blocks - no valid download URLs remain. Incomplete transfer left in %s.\n(If this is the download filename with .part appended, zsync will automatically pick this up and reuse the data it has already done if you retry in this dir.)\n",temp_file);
exit(3);
}
{
int r;
printf("verifying download...");
r = zsync_complete(zs);
switch (r) {
case -1:
fprintf(stderr,"Aborting, download available in %s\n",temp_file);
exit(2);
case 0:
printf("no recognised checksum found\n");
break;
case 1:
printf("checksum matches OK\n");
break;
}
}
free(temp_file);
temp_file = zsync_end(zs);
if (filename) {
char* oldfile_backup = malloc(strlen(filename)+8);
int ok = 1;
strcpy(oldfile_backup,filename);
strcat(oldfile_backup,".zs-old");
if (!access(filename,F_OK)) {
/* backup of old file */
unlink(oldfile_backup); /* Don't care if this fails - the link below will catch any failure */
if (link(filename,oldfile_backup) != 0) {
perror("link");
fprintf(stderr,"Unable to back up old file %s - completed download left in %s\n",filename,temp_file);
ok = 0; /* Prevent overwrite of old file below */
}
}
if (ok)
if (rename(temp_file,filename) != 0) {
perror("rename");
fprintf(stderr,"Unable to back up old file %s - completed download left in %s\n",filename,temp_file);
}
free(oldfile_backup);
free(filename);
} else {
printf("No filename specified for download - completed download left in %s\n",temp_file);
}
fprintf(stderr,"used %lld local, fetched %lld\n", local_used, http_down);
free(referer);
free(temp_file);
return 0;
}
syntax highlighted by Code2HTML, v. 0.9.1