diff --git a/modules/csync_owncloud.c b/modules/csync_owncloud.c index 5f1e239c5e..d5e0f438cc 100644 --- a/modules/csync_owncloud.c +++ b/modules/csync_owncloud.c @@ -164,6 +164,8 @@ struct dav_session_s { CSYNC *csync_ctx; void *userdata; + + csync_hbf_info_t *chunk_info; }; /* The list of properties that is fetched in PropFind on a collection */ @@ -1666,6 +1668,11 @@ static int owncloud_sendfile(csync_vio_method_handle_t *src, csync_vio_method_ha rc = 1; } else { Hbf_State state = hbf_splitlist(trans, fd); + if (dav_session.chunk_info && dav_session.chunk_info->transfer_id) { + DEBUG_WEBDAV("We have chunk info %d %d ", dav_session.chunk_info->start_id, dav_session.chunk_info->transfer_id); + trans->start_id = dav_session.chunk_info->start_id; + trans->transfer_id = dav_session.chunk_info->transfer_id; + } if( state == HBF_SUCCESS ) { chunked_total_size = sb.st_size; /* Transfer all the chunks through the HTTP session using PUT. */ @@ -1675,8 +1682,13 @@ static int owncloud_sendfile(csync_vio_method_handle_t *src, csync_vio_method_ha if ( state != HBF_SUCCESS ) { error_string = hbf_error_string(state); rc = 1; + if (dav_session.chunk_info) { + dav_session.chunk_info->start_id = trans->start_id; + dav_session.chunk_info->transfer_id = trans->transfer_id; + } } } + hbf_free_transfer(trans); } if (_progresscb) { ne_set_notifier(dav_session.ctx, 0, 0); @@ -1730,7 +1742,9 @@ static int owncloud_sendfile(csync_vio_method_handle_t *src, csync_vio_method_ha set_errno_from_neon_errcode(neon_stat); DEBUG_WEBDAV("Error GET: Neon: %d, errno %d", neon_stat, errno); - rc = -1; + error_string = dav_session.error_string; + error_code = errno; + rc = 1; } else { status = ne_get_status( write_ctx->req ); DEBUG_WEBDAV("GET http result %d (%s)", status->code, status->reason_phrase ? status->reason_phrase : "progress) { + csync_progressinfo_t *next = ctx->progress->next; + csync_statedb_free_progressinfo(ctx->progress); + ctx->progress = next; + } + /* destroy the rbtrees */ if (c_rbtree_size(ctx->local.tree) > 0) { c_rbtree_destroy(ctx->local.tree, _tree_destructor); diff --git a/src/csync_misc.h b/src/csync_misc.h index 69ea4c5565..b6f19ceda7 100644 --- a/src/csync_misc.h +++ b/src/csync_misc.h @@ -38,4 +38,10 @@ int csync_fnmatch(__const char *__pattern, __const char *__name, int __flags); */ CSYNC_ERROR_CODE csync_errno_to_csync_error(CSYNC_ERROR_CODE default_err); +struct csync_hbf_info_s { + int start_id; + int transfer_id; +}; +typedef struct csync_hbf_info_s csync_hbf_info_t; + #endif /* _CSYNC_MISC_H */ diff --git a/src/csync_private.h b/src/csync_private.h index c9e30c60ba..29a960fcc9 100644 --- a/src/csync_private.h +++ b/src/csync_private.h @@ -143,6 +143,8 @@ struct csync_s { uid_t euid; } pwd; + struct csync_progressinfo_s *progress; + /* replica we are currently walking */ enum csync_replica_e current; diff --git a/src/csync_propagate.c b/src/csync_propagate.c index e2072673ff..b86731d39a 100644 --- a/src/csync_propagate.c +++ b/src/csync_propagate.c @@ -32,12 +32,14 @@ #include "csync_private.h" #include "csync_propagate.h" +#include "csync_statedb.h" #include "vio/csync_vio.h" #include "c_jhash.h" #define CSYNC_LOG_CATEGORY_NAME "csync.propagator" #include "csync_log.h" #include "csync_util.h" +#include "csync_misc.h" static int _csync_cleanup_cmp(const void *a, const void *b) { csync_file_stat_t *st_a, *st_b; @@ -69,6 +71,28 @@ static void _store_id_update(CSYNC *ctx, csync_file_stat_t *st) { } } + +/* Record the error in the ctx->progress + pi may be a previous csync_progressinfo_t from the database. + If pi is NULL, a new one is created, else it is re-used + */ +static void _csync_record_error(CSYNC *ctx, csync_file_stat_t *st, csync_progressinfo_t *pi) { + if (pi) { + pi->error++; + } else { + pi = c_malloc(sizeof(csync_progressinfo_t)); + pi->chunk = 0; + pi->transferId = 0; + pi->tmpfile = NULL; + pi->md5 = st->md5 ? c_strdup(st->md5) : NULL; + pi->modtime = st->modtime; + pi->phash = st->phash; + pi->error = 1; + } + pi->next = ctx->progress; + ctx->progress = pi; +} + static bool _push_to_tmp_first(CSYNC *ctx) { if( !ctx ) return true; @@ -131,6 +155,21 @@ static int _csync_push_file(CSYNC *ctx, csync_file_stat_t *st) { int count = 0; int flags = 0; + csync_hbf_info_t hbf_info = { 0, 0 }; + csync_progressinfo_t *pi = NULL; + pi = csync_statedb_get_progressinfo(ctx, st->phash, st->modtime, st->md5); + if (pi && pi->error > 3) { + rc = 1; + goto out; + } + if (pi) { + CSYNC_LOG(CSYNC_LOG_PRIORITY_TRACE, + "continuation: %d %d", + pi->chunk, pi->transferId ); + hbf_info.start_id = pi->chunk; + hbf_info.transfer_id = pi->transferId; + } + rep_bak = ctx->replica; switch (ctx->current) { @@ -188,6 +227,17 @@ static int _csync_push_file(CSYNC *ctx, csync_file_stat_t *st) { } if (_push_to_tmp_first(ctx)) { + int re = 0; + if (pi && pi->tmpfile && pi->tmpfile[0] && _push_to_tmp_first(ctx)) { + turi = c_strdup(pi->tmpfile); + /* Try to see if we can resume. */ + ctx->replica = drep; + dfp = csync_vio_open(ctx, turi, O_APPEND|O_NOCTTY, 0); + if (dfp) { + goto start_fd_based; + } + } + /* create the temporary file name */ #ifdef _WIN32 if (asprintf(&turi, "%s.~XXXXXX", duri) < 0) { @@ -300,19 +350,66 @@ static int _csync_push_file(CSYNC *ctx, csync_file_stat_t *st) { /* copy file */ if( _use_fd_based_push(ctx) ) { - if (ctx->current == REMOTE_REPLICA) - csync_win32_set_file_hidden(turi, true); +start_fd_based: + + if (ctx->current == REMOTE_REPLICA) { + csync_win32_set_file_hidden(turi, true); + } + + if (!_push_to_tmp_first(ctx)) { + csync_vio_set_property(ctx, "hbf_info", &hbf_info); + } rc = csync_vio_sendfile( ctx, sfp, dfp ); - if (ctx->current == REMOTE_REPLICA) + if (ctx->current == REMOTE_REPLICA) { csync_win32_set_file_hidden(turi, false); + } if( rc != 0 ) { strerror_r(errno, errbuf, sizeof(errbuf)); CSYNC_LOG(CSYNC_LOG_PRIORITY_ERROR, "file: %s, command: sendfile, error: %s from errno %d", suri, errbuf, errno); + + if (_push_to_tmp_first(ctx)) { + csync_vio_file_stat_t* sb = csync_vio_file_stat_new(); + if (csync_vio_stat(ctx, turi, sb) == 0 && sb->size > 0) { + CSYNC_LOG(CSYNC_LOG_PRIORITY_TRACE, + "keeping tmp file: %s", turi); + if (!pi) { + pi = c_malloc(sizeof(csync_progressinfo_t)); + pi->error = 0; + pi->transferId = 0; + pi->md5 = st->md5 ? c_strdup(st->md5) : NULL; + pi->modtime = st->modtime; + pi->phash = st->phash; + } else { + SAFE_FREE(pi->tmpfile); + } + pi->chunk = 0; + pi->tmpfile = turi; + pi->error <<= 1; + turi = NULL; + } + csync_vio_file_stat_destroy(sb); + } else { + CSYNC_LOG(CSYNC_LOG_PRIORITY_TRACE, + "remember chunk: %d (transfer id %d )", hbf_info.start_id, hbf_info.transfer_id); + if (!pi) { + pi = c_malloc(sizeof(csync_progressinfo_t)); + pi->error = 0; + pi->md5 = st->md5 ? c_strdup(st->md5) : NULL; + pi->modtime = st->modtime; + pi->phash = st->phash; + pi->tmpfile = NULL; + } else { + SAFE_FREE(pi->tmpfile); + } + pi->transferId = hbf_info.transfer_id; + pi->chunk = hbf_info.start_id; + csync_vio_set_property(ctx, "hbf_info", 0); + } goto out; } } else { @@ -527,14 +624,20 @@ out: /* set instruction for the statedb merger */ if (rc != 0) { st->instruction = CSYNC_INSTRUCTION_ERROR; - if (turi != NULL) { - if (_push_to_tmp_first(ctx)) { - /* Remove the tmp file in error case. */ - csync_vio_unlink(ctx, turi); + if (_push_to_tmp_first(ctx)) { + if (turi != NULL) { + /* Remove the tmp file in error case. */ + csync_vio_unlink(ctx, turi); } } + if (rc != 123) { + _csync_record_error(ctx, st, pi); + pi = NULL; + } } + csync_statedb_free_progressinfo(pi); + SAFE_FREE(prev_tdir); SAFE_FREE(suri); SAFE_FREE(duri); @@ -687,6 +790,13 @@ static int _csync_rename_file(CSYNC *ctx, csync_file_stat_t *st) { const char *tmd5 = NULL; c_rbnode_t *node = NULL; + csync_progressinfo_t *pi = NULL; + pi = csync_statedb_get_progressinfo(ctx, st->phash, st->modtime, st->md5); + if (pi && pi->error > 3) { + rc = 1; + goto out; + } + switch (ctx->current) { case REMOTE_REPLICA: if( !(st->path && st->destpath) ) { @@ -774,8 +884,12 @@ out: /* set instruction for the statedb merger */ if (rc != 0) { st->instruction = CSYNC_INSTRUCTION_NONE; + + _csync_record_error(ctx, st, pi); + pi = NULL; } + csync_statedb_free_progressinfo(pi); return rc; } @@ -805,6 +919,14 @@ static int _csync_remove_file(CSYNC *ctx, csync_file_stat_t *st) { char *uri = NULL; int rc = -1; + csync_progressinfo_t *pi = NULL; + pi = csync_statedb_get_progressinfo(ctx, st->phash, st->modtime, st->md5); + if (pi && pi->error > 3) { + rc = 1; + goto out; + } + + switch (ctx->current) { case LOCAL_REPLICA: if (asprintf(&uri, "%s/%s", ctx->local.uri, st->path) < 0) { @@ -855,8 +977,11 @@ out: if (rc != 0) { /* Write file to statedb, to try to sync again on the next run. */ st->instruction = CSYNC_INSTRUCTION_NONE; + _csync_record_error(ctx, st, pi); + pi = NULL; } + csync_statedb_free_progressinfo(pi); return rc; } @@ -868,6 +993,13 @@ static int _csync_new_dir(CSYNC *ctx, csync_file_stat_t *st) { struct timeval times[2]; int rc = -1; + csync_progressinfo_t *pi = NULL; + pi = csync_statedb_get_progressinfo(ctx, st->phash, st->modtime, st->md5); + if (pi && pi->error > 3) { + rc = 1; + goto out; + } + replica_bak = ctx->replica; switch (ctx->current) { @@ -952,8 +1084,11 @@ out: /* set instruction for the statedb merger */ if (rc != 0) { st->instruction = CSYNC_INSTRUCTION_ERROR; + _csync_record_error(ctx, st, pi); + pi = NULL; } + csync_statedb_free_progressinfo(pi); return rc; } @@ -965,6 +1100,13 @@ static int _csync_sync_dir(CSYNC *ctx, csync_file_stat_t *st) { struct timeval times[2]; int rc = -1; + csync_progressinfo_t *pi = NULL; + pi = csync_statedb_get_progressinfo(ctx, st->phash, st->modtime, st->md5); + if (pi && pi->error > 3) { + rc = 1; + goto out; + } + replica_bak = ctx->replica; switch (ctx->current) { @@ -1032,8 +1174,11 @@ out: /* set instruction for the statedb merger */ if (rc != 0) { st->instruction = CSYNC_INSTRUCTION_ERROR; + _csync_record_error(ctx, st, pi); + pi = NULL; } + csync_statedb_free_progressinfo(pi); return rc; } @@ -1347,6 +1492,7 @@ static int _csync_propagation_cleanup(CSYNC *ctx) { static int _csync_propagation_file_visitor(void *obj, void *data) { csync_file_stat_t *st = NULL; CSYNC *ctx = NULL; + int rc = 0; st = (csync_file_stat_t *) obj; ctx = (CSYNC *) data; @@ -1357,32 +1503,32 @@ static int _csync_propagation_file_visitor(void *obj, void *data) { case CSYNC_FTW_TYPE_FILE: switch (st->instruction) { case CSYNC_INSTRUCTION_NEW: - if (_csync_new_file(ctx, st) < 0) { + if ((rc = _csync_new_file(ctx, st)) < 0) { CSYNC_LOG(CSYNC_LOG_PRIORITY_TRACE,"FAIL NEW: %s",st->path); goto err; } break; case CSYNC_INSTRUCTION_RENAME: - if (_csync_rename_file(ctx, st) < 0) { + if ((rc = _csync_rename_file(ctx, st)) < 0) { CSYNC_LOG(CSYNC_LOG_PRIORITY_TRACE,"FAIL RENAME: %s",st->path); goto err; } break; case CSYNC_INSTRUCTION_SYNC: - if (_csync_sync_file(ctx, st) < 0) { + if ((rc = _csync_sync_file(ctx, st)) < 0) { CSYNC_LOG(CSYNC_LOG_PRIORITY_TRACE,"FAIL SYNC: %s",st->path); goto err; } break; case CSYNC_INSTRUCTION_REMOVE: - if (_csync_remove_file(ctx, st) < 0) { + if ((rc = _csync_remove_file(ctx, st)) < 0) { CSYNC_LOG(CSYNC_LOG_PRIORITY_TRACE,"FAIL REMOVE: %s",st->path); goto err; } break; case CSYNC_INSTRUCTION_CONFLICT: CSYNC_LOG(CSYNC_LOG_PRIORITY_TRACE,"case CSYNC_INSTRUCTION_CONFLICT: %s",st->path); - if (_csync_conflict_file(ctx, st) < 0) { + if ((rc = _csync_conflict_file(ctx, st)) < 0) { goto err; } break; @@ -1401,7 +1547,7 @@ static int _csync_propagation_file_visitor(void *obj, void *data) { break; } - return 0; + return rc; err: return -1; } diff --git a/src/csync_statedb.c b/src/csync_statedb.c index 9e708f7b65..2175077a80 100644 --- a/src/csync_statedb.c +++ b/src/csync_statedb.c @@ -251,6 +251,11 @@ int csync_statedb_write(CSYNC *ctx) { return -1; } + /* progress info */ + if (csync_statedb_write_progressinfo(ctx, ctx->progress) < 0) { + return -1; + } + return 0; } @@ -360,6 +365,24 @@ int csync_statedb_create_tables(CSYNC *ctx) { return -1; } c_strlist_destroy(result); + + result = csync_statedb_query(ctx, + "CREATE TABLE IF NOT EXISTS progress(" + "phash INTEGER(8)," + "modtime INTEGER(8)," + "md5 VARCHAR(32)," + "chunk INTEGER(4)," + "transferid INTEGER(4)," + "error_count INTEGER(8)," + "tmpfile VARCHAR(4096)," + "PRIMARY KEY(phash)" + ");" + ); + if (result == NULL) { + return -1; + } + c_strlist_destroy(result); + /* write the version table. */ stmt = sqlite3_mprintf( "INSERT INTO version (major, minor, patch) VALUES (%d, %d, %d);", @@ -372,6 +395,7 @@ int csync_statedb_create_tables(CSYNC *ctx) { return -1; } sqlite3_free(stmt); + return 0; } @@ -386,6 +410,14 @@ int csync_statedb_drop_tables(CSYNC *ctx) { return -1; } c_strlist_destroy(result); + + result = csync_statedb_query(ctx, + "DROP TABLE IF EXISTS progress;" + ); + if (result == NULL) { + return -1; + } + c_strlist_destroy(result); return 0; } @@ -844,4 +876,77 @@ int csync_statedb_insert(CSYNC *ctx, const char *statement) { return sqlite3_last_insert_rowid(ctx->statedb.db); } +csync_progressinfo_t* csync_statedb_get_progressinfo(CSYNC *ctx, uint64_t phash, uint64_t modtime, const char* md5) { + char *stmt = NULL; + csync_progressinfo_t *ret = NULL; + c_strlist_t *result = NULL; + + if( ! csync_get_statedb_exists(ctx)) return ret; + stmt = sqlite3_mprintf("SELECT error_count, chunk, transferid, tmpfile FROM progress WHERE phash='%llu' AND modtime='%lld' AND md5='%q'", + (long long unsigned int) phash, (long long signed int) modtime, md5); + if (!stmt) return ret; + + result = csync_statedb_query(ctx, stmt); + sqlite3_free(stmt); + if (result == NULL) { + return NULL; + } + + if (result->count == 4) { + ret = c_malloc(sizeof(csync_progressinfo_t)); + if (!ret) goto out; + ret->next = NULL; + ret->error = atoi(result->vector[0]); + ret->chunk = atoi(result->vector[1]); + ret->transferId = atoi(result->vector[2]); + ret->tmpfile = c_strdup(result->vector[3]); + ret->md5 = md5 ? c_strdup(md5) : NULL; + ret->modtime = modtime; + ret->phash = phash; + } +out: + c_strlist_destroy(result); + return ret; +} + +void csync_statedb_free_progressinfo(csync_progressinfo_t* pi) +{ + if (!pi) return; + SAFE_FREE(pi->md5); + SAFE_FREE(pi->tmpfile); + SAFE_FREE(pi); +} + +int csync_statedb_write_progressinfo(CSYNC* ctx, csync_progressinfo_t* pi) +{ + int rc = 0; + char *stmt = NULL; + + while (rc > -1 && pi) { + stmt = sqlite3_mprintf("INSERT INTO progress " + "(phash, modtime, md5, chunk, transferid, error_count, tmpfile) VALUES" + "(%llu, %lld, '%q', %d, %d, %d, '%q');", + (long long signed int) pi->phash, + (long long int) pi->modtime, + pi->md5, + pi->chunk, + pi->transferId, + pi->error, + pi->tmpfile); + + if (stmt == NULL) { + return -1; + } + + CSYNC_LOG(CSYNC_LOG_PRIORITY_TRACE, "%s" , stmt); + + rc = csync_statedb_insert(ctx, stmt); + sqlite3_free(stmt); + pi = pi->next; + } + return 0; +} + + + /* vim: set ts=8 sw=2 et cindent: */ diff --git a/src/csync_statedb.h b/src/csync_statedb.h index 21ca917f9e..6bbc1ff7d3 100644 --- a/src/csync_statedb.h +++ b/src/csync_statedb.h @@ -110,6 +110,22 @@ int csync_statedb_drop_tables(CSYNC *ctx); int csync_statedb_insert_metadata(CSYNC *ctx); +typedef struct csync_progressinfo_s { + struct csync_progressinfo_s *next; + uint64_t phash; + uint64_t modtime; + char *md5; + int error; + int chunk; + int transferId; + char *tmpfile; +} csync_progressinfo_t; + +csync_progressinfo_t *csync_statedb_get_progressinfo(CSYNC *ctx, uint64_t phash, uint64_t modtime, const char *md5); +void csync_statedb_free_progressinfo(csync_progressinfo_t *pi); +int csync_statedb_write_progressinfo(CSYNC *ctx, csync_progressinfo_t *pi); + + /** * }@ */ diff --git a/src/httpbf/src/httpbf.c b/src/httpbf/src/httpbf.c index 52c9153816..4c2f114f5b 100644 --- a/src/httpbf/src/httpbf.c +++ b/src/httpbf/src/httpbf.c @@ -80,6 +80,7 @@ hbf_transfer_t *hbf_init_transfer( const char *dest_uri ) { transfer->url = strdup(dest_uri); transfer->status_code = 200; transfer->error_string = NULL; + transfer->start_id = 0; return transfer; } @@ -131,6 +132,7 @@ Hbf_State hbf_splitlist(hbf_transfer_t *transfer, int fd ) { transfer->block_arr = calloc(num_blocks, sizeof(hbf_block_t*)); transfer->block_cnt = num_blocks; transfer->transfer_id = transfer_id(&sb); + transfer->start_id = 0; for( cnt=0; cnt < num_blocks; cnt++ ) { /* allocate a block struct and fill */ @@ -206,9 +208,10 @@ char* get_transfer_url( hbf_transfer_t *transfer, int indx ) { } static int dav_request( ne_request *req, int fd, hbf_block_t *blk ) { - Hbf_State state = HBF_SUCCESS; + Hbf_State state = HBF_TRANSFER_SUCCESS; int res; const ne_status *req_status = NULL; + const char *etag = NULL; if( ! (blk && req) ) return HBF_PARAM_FAIL; @@ -222,6 +225,9 @@ static int dav_request( ne_request *req, int fd, hbf_block_t *blk ) { case NE_OK: state = HBF_SUCCESS; blk->state = HBF_TRANSFER_SUCCESS; + etag = ne_get_response_header(req, "ETag"); + if (etag && etag[0]) + state = HBF_SUCCESS; break; case NE_AUTH: state = HBF_AUTH_FAIL; @@ -254,7 +260,7 @@ static int dav_request( ne_request *req, int fd, hbf_block_t *blk ) { } Hbf_State hbf_transfer( ne_session *session, hbf_transfer_t *transfer, const char *verb ) { - Hbf_State state = HBF_SUCCESS; + Hbf_State state = HBF_TRANSFER_SUCCESS; int cnt; int fail_cnt = 0; @@ -269,20 +275,20 @@ Hbf_State hbf_transfer( ne_session *session, hbf_transfer_t *transfer, const cha } for( cnt=0; state == HBF_SUCCESS && cnt < transfer->block_cnt; cnt++ ) { - hbf_block_t *block = transfer->block_arr[cnt]; + int block_id = (cnt + transfer->start_id) % transfer->block_cnt; Hbf_State block_state = HBF_SUCCESS; - + hbf_block_t *block = transfer->block_arr[block_id]; char *transfer_url = NULL; if( ! block ) state = HBF_PARAM_FAIL; - if( state == HBF_SUCCESS ) { - transfer_url = get_transfer_url( transfer, cnt ); + if( state == HBF_TRANSFER_SUCCESS ) { + transfer_url = get_transfer_url( transfer, block_id ); if( ! transfer_url ) { state = HBF_PARAM_FAIL; } } - if( state == HBF_SUCCESS ) { + if( state == HBF_TRANSFER_SUCCESS ) { ne_request *req = ne_request_create(session, "PUT", transfer_url); if( req ) { @@ -292,20 +298,11 @@ Hbf_State hbf_transfer( ne_session *session, hbf_transfer_t *transfer, const cha if( block_state != HBF_SUCCESS ) { if( transfer->error_string ) free( transfer->error_string ); - transfer->error_string = strdup( ne_get_error(session) ); - - /* Set the code of the last transmission. */ - transfer->status_code = transfer->block_arr[cnt]->http_result_code; - state = HBF_FAIL; - fail_cnt++; - - if( block_state == HBF_FAIL ) { - /* not every problem here is a critical one. Try other parts */ - if( transfer->status_code == 405 ) { - /* continue to upload... */ - state = HBF_SUCCESS; - } - } + transfer->error_string = strdup( ne_get_error(session) ); + transfer->start_id = block_id % transfer->block_cnt; + /* Set the code of the last transmission. */ + state = HBF_FAIL; + transfer->status_code = transfer->block_arr[block_id]->http_result_code; } ne_request_destroy(req); } else { @@ -314,13 +311,6 @@ Hbf_State hbf_transfer( ne_session *session, hbf_transfer_t *transfer, const cha free( transfer_url ); } } - if( state == HBF_SUCCESS && fail_cnt > 0 ) { - state = HBF_FAIL; /* All failed! */ - if( fail_cnt < transfer->block_cnt ) { - /* Not all failed actually. */ - state = HBF_PARTIAL_TRANSFER_SUCCESS; - } - } return state; } diff --git a/src/httpbf/src/httpbf.h b/src/httpbf/src/httpbf.h index b9d9560f7f..25d25d977c 100644 --- a/src/httpbf/src/httpbf.h +++ b/src/httpbf/src/httpbf.h @@ -72,6 +72,7 @@ struct hbf_transfer_s { int fd; int transfer_id; char *url; + int start_id; int status_code; char *error_string;