/* Copyright (c) 2015 Anton Titov. Copyright (c) 2015 pCloud Ltd. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of pCloud Ltd nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL pCloud Ltd BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include #include "papi.h" #include "pasyncnet.h" #include "pcompat.h" #include "pcompression.h" #include "plibs.h" #include "pnetlibs.h" #include "psettings.h" #include "pssl.h" #include "ptree.h" #include #define TASK_WITH_HEADER(t1, t2) \ typedef struct { \ task_header_t head; \ t2 task; \ } t1 #define get_len(t) (sizeof(t) - offsetof(t, task)) #define TASK_TYPE_EXIT 0 #define TASK_TYPE_FILE_DWL 1 #define TASK_TYPE_FILE_DWL_NM 2 #define STREAM_FLAG_ACTIVE 1 #define STREAM_HEADER_LEN 6 // 4 bytes stream id, 2 bytes length typedef struct { uint32_t type; uint32_t len; } task_header_t; typedef struct { psync_fileid_t fileid; const char *localpath; psync_async_callback_t cb; void *cbext; } task_file_download_t; typedef struct { psync_fileid_t fileid; const char *localpath; uint64_t size; char sha1hex[PSYNC_SHA1_DIGEST_HEXLEN]; psync_async_callback_t cb; void *cbext; } task_file_download_if_not_mod_t; typedef struct { uint32_t error; uint32_t errorflags; uint64_t size; uint64_t hash; uint64_t mtime; uint64_t oldhash; uint64_t oldmtime; unsigned char sha1hex[PSYNC_SHA1_DIGEST_HEXLEN]; } task_file_download_resp_t; typedef struct _async_thread_params_t { psync_deflate_t *enc; psync_deflate_t *dec; psync_socket *api; psync_tree *streams; uint64_t datapendingsince; int (*process_buf)(struct _async_thread_params_t *); char *curreadbuff; psync_socket_t privsock; uint32_t curreadbuffrem; uint32_t curreadbufflen; uint32_t currentstreamid; uint32_t laststreamid; uint32_t pendingrequests; char buffer[64 * 1024]; } async_thread_params_t; typedef struct _stream_t { psync_tree tree; uint32_t streamid; uint32_t flags; psync_async_callback_t cb; void (*free)(struct _stream_t *, uint32_t); void *cbext; int (*process_data)(struct _stream_t *, async_thread_params_t *, const char *, uint32_t); } stream_t; typedef struct { const char *localpath; uint64_t fileid; uint64_t size; uint64_t hash; unsigned char osize; unsigned char sha1hex[PSYNC_SHA1_DIGEST_HEXLEN]; unsigned char osha1hex[PSYNC_SHA1_DIGEST_HEXLEN]; psync_sha1_ctx sha1ctx; uint64_t remsize; psync_file_t fd; } file_download_add_t; TASK_WITH_HEADER(task_hdr_file_download_t, task_file_download_t); TASK_WITH_HEADER(task_hdr_file_download_if_not_mod_t, task_file_download_if_not_mod_t); static pthread_mutex_t amutex = PTHREAD_MUTEX_INITIALIZER; static int running = 0; static psync_socket_t at_sock = INVALID_SOCKET; static int send_pending_data(async_thread_params_t *prms) { char buff[4096]; int ret; while (1) { ret = psync_deflate_read(prms->enc, buff, sizeof(buff)); if (ret == PSYNC_DEFLATE_NODATA || ret == PSYNC_DEFLATE_EOF) return 0; if (ret > 0) { if (psync_socket_writeall(prms->api, buff, ret) != ret) { debug(D_WARNING, "write of %d bytes to socket failed", ret); return -1; } else debug(D_NOTICE, "sent %d bytes of compressed data to socket", ret); } else { debug(D_ERROR, "read from deflate compressor returned %d", ret); return -1; } } } static int flush_pending_data(async_thread_params_t *prms) { int ret; ret = psync_deflate_write(prms->enc, "", 0, PSYNC_DEFLATE_FLUSH); if (ret != 0) { debug(D_WARNING, "psync_deflate_write returned %d when flushing", ret); return -1; } prms->pendingrequests = 0; return send_pending_data(prms); } static int socket_t_readall(psync_socket_t sock, void *buff, size_t len) { ssize_t rd; while (len) { if (psync_wait_socket_read_timeout(sock)) return -1; rd = psync_read_socket(sock, buff, len); if (rd > 0) { len -= rd; buff = (char *)buff + rd; } else if (rd == PSYNC_SOCKET_ERROR && (psync_sock_err() == P_INTR || psync_sock_err() == P_AGAIN || psync_sock_err() == P_WOULDBLOCK)) continue; else { debug(D_WARNING, "read from socket of %lu bytes returned %ld, errno %d", (unsigned long)len, (long)rd, (int)psync_sock_err()); return -1; } } return 0; } static int socket_t_writeall(psync_socket_t sock, const void *buff, size_t len) { ssize_t wr; while (len) { if (psync_wait_socket_write_timeout(sock)) return -1; wr = psync_write_socket(sock, buff, len); if (wr > 0) { len -= wr; buff = (const char *)buff + wr; } else if (wr == PSYNC_SOCKET_ERROR && (psync_sock_err() == P_INTR || psync_sock_err() == P_AGAIN || psync_sock_err() == P_WOULDBLOCK)) continue; else { debug(D_WARNING, "write to socket of %lu bytes returned %ld, errno %d", (unsigned long)len, (long)wr, (int)psync_sock_err()); return -1; } } return 0; } static stream_t *create_stream(async_thread_params_t *prms, size_t addsize) { psync_tree *parent; stream_t *ret; ret = (stream_t *)psync_malloc(sizeof(stream_t) + addsize); ret->streamid = ++prms->laststreamid; ret->flags = 0; ret->free = NULL; parent = psync_tree_get_last(prms->streams); if (parent) parent->right = &ret->tree; else prms->streams = &ret->tree; psync_tree_added_at(&prms->streams, parent, &ret->tree); return ret; } static int send_data(async_thread_params_t *prms, const void *data, int len) { int wr; while (len) { wr = psync_deflate_write(prms->enc, data, len, PSYNC_DEFLATE_NOFLUSH); if (wr > 0) { len -= wr; data = (const char *)data + wr; } else if (wr != PSYNC_DEFLATE_FULL) { debug(D_ERROR, "write to deflate compressor of %d bytes returned %d", len, wr); return -1; } if (send_pending_data(prms)) return -1; } if (!prms->pendingrequests) prms->datapendingsince = psync_millitime(); prms->pendingrequests++; return 0; } static void close_stream(stream_t *s, async_thread_params_t *prms, uint32_t error) { debug(D_NOTICE, "closing stream %u", (unsigned)s->streamid); psync_tree_del(&prms->streams, &s->tree); if (s->free) s->free(s, error); psync_free(s); } static void file_download_free(stream_t *s, uint32_t error) { file_download_add_t *fda; fda = (file_download_add_t *)(s + 1); if (fda->fd != INVALID_HANDLE_VALUE) { psync_file_close(fda->fd); if (error) psync_file_delete(fda->localpath); } } static int file_download_send_error(stream_t *s, async_thread_params_t *prms, file_download_add_t *fda, uint32_t error, uint32_t errorflags) { psync_async_result_t r; if (error) debug(D_NOTICE, "got error %u(%u) for file %s", (unsigned)error, (unsigned)errorflags, fda->localpath); else debug(D_NOTICE, "download of %s finished", fda->localpath); r.error = error; r.errorflags = errorflags; r.file.size = fda->size; r.file.hash = fda->hash; memcpy(r.file.sha1hex, fda->sha1hex, PSYNC_SHA1_DIGEST_HEXLEN); s->cb(s->cbext, &r); close_stream(s, prms, error); return 0; } static int file_download_checksum(file_download_add_t *fda) { unsigned char sha1b[PSYNC_SHA1_DIGEST_LEN], sha1h[PSYNC_SHA1_DIGEST_HEXLEN]; psync_sha1_final(sha1b, &fda->sha1ctx); psync_binhex(sha1h, sha1b, PSYNC_SHA1_DIGEST_LEN); if (memcmp(sha1h, fda->sha1hex, PSYNC_SHA1_DIGEST_HEXLEN)) { debug(D_WARNING, "checksum verification for file %s failed, expected %40s got %40s", fda->localpath, (char *)fda->sha1hex, (char *)sha1h); return -1; } else return 0; } static int process_file_download_data(stream_t *s, async_thread_params_t *prms, const char *buff, uint32_t datalen) { file_download_add_t *fda; ssize_t wr; int err; fda = (file_download_add_t *)(s + 1); if (datalen > fda->remsize) { debug(D_ERROR, "got packed of size %u for stream %u file %s when the remaining data " "is %lu", (unsigned)datalen, (unsigned)s->streamid, fda->localpath, (unsigned long)fda->remsize); file_download_send_error(s, prms, fda, PSYNC_ASYNC_ERROR_NET, PSYNC_ASYNC_ERR_FLAG_RETRY_AS_IS); return -1; } fda->remsize -= datalen; psync_account_downloaded_bytes(datalen); psync_sha1_update(&fda->sha1ctx, buff, datalen); while (datalen) { wr = psync_file_write(fda->fd, buff, datalen); if (wr == -1) { err = (int)psync_fs_err(); debug(D_WARNING, "writing to file %s failed, errno %d", fda->localpath, err); return file_download_send_error( s, prms, fda, err == P_NOSPC ? PSYNC_ASYNC_ERROR_DISK_FULL : PSYNC_ASYNC_ERROR_IO, 0); } datalen -= wr; buff += wr; } if (fda->remsize == 0) { if (file_download_checksum(fda)) return file_download_send_error(s, prms, fda, PSYNC_ASYNC_ERROR_CHECKSUM, 0); else return file_download_send_error(s, prms, fda, 0, 0); } else return 0; } static int process_file_download_headers(stream_t *s, async_thread_params_t *prms, const char *buff, uint32_t datalen) { task_file_download_resp_t r; file_download_add_t *fda; psync_sql_res *res; if (unlikely(datalen < sizeof(task_file_download_resp_t))) { debug(D_ERROR, "got packet of size %u while expecting at least %u, disconnecting", (unsigned)datalen, (unsigned)sizeof(task_file_download_resp_t)); return -1; } memcpy(&r, buff, sizeof(task_file_download_resp_t)); fda = (file_download_add_t *)(s + 1); fda->size = r.size; fda->hash = r.hash; memcpy(fda->sha1hex, r.sha1hex, PSYNC_SHA1_DIGEST_HEXLEN); if (r.error) return file_download_send_error(s, prms, fda, r.error + 100, r.errorflags); debug(D_NOTICE, "got headers for file %s size %" P_PRI_U64 " hash %" P_PRI_U64 " sha1 %.40s", fda->localpath, fda->size, fda->hash, fda->sha1hex); psync_sql_start_transaction(); res = psync_sql_prep_statement( "REPLACE INTO hashchecksum (hash, size, checksum) VALUES (?, ?, ?)"); psync_sql_bind_uint(res, 1, r.hash); psync_sql_bind_uint(res, 2, r.size); psync_sql_bind_lstring(res, 3, (const char *)r.sha1hex, PSYNC_SHA1_DIGEST_HEXLEN); if (r.oldmtime) { psync_sql_run(res); psync_sql_bind_uint(res, 1, r.oldhash); psync_sql_bind_uint(res, 2, fda->osize); psync_sql_bind_lstring(res, 3, (const char *)fda->osha1hex, PSYNC_SHA1_DIGEST_HEXLEN); psync_sql_run_free(res); } else psync_sql_run_free(res); res = psync_sql_prep_statement("REPLACE INTO filerevision (fileid, hash, " "ctime, size) VALUES (?, ?, ?, ?)"); psync_sql_bind_uint(res, 1, fda->fileid); psync_sql_bind_uint(res, 2, r.hash); psync_sql_bind_uint(res, 3, r.mtime); psync_sql_bind_uint(res, 4, r.size); if (r.oldmtime) { psync_sql_run(res); psync_sql_bind_uint(res, 1, fda->fileid); psync_sql_bind_uint(res, 2, r.oldhash); psync_sql_bind_uint(res, 3, r.oldmtime); psync_sql_bind_uint(res, 4, fda->osize); psync_sql_run_free(res); } else psync_sql_run_free(res); psync_sql_commit_transaction(); fda->fd = psync_file_open(fda->localpath, P_O_WRONLY, P_O_CREAT | P_O_TRUNC); if (fda->fd == INVALID_HANDLE_VALUE) { debug(D_WARNING, "could not open file %s, errno %d", fda->localpath, (int)psync_fs_err()); return file_download_send_error(s, prms, fda, PSYNC_ASYNC_ERROR_FILE, 0); } fda->remsize = fda->size; if (!fda->remsize) return file_download_send_error(s, prms, fda, 0, 0); s->process_data = process_file_download_data; psync_sha1_init(&fda->sha1ctx); if (datalen > sizeof(task_file_download_resp_t)) return process_file_download_data( s, prms, buff + sizeof(task_file_download_resp_t), datalen - sizeof(task_file_download_resp_t)); else return 0; } static int handle_file_download(async_thread_params_t *prms, task_file_download_t *dwl) { char buff[256]; stream_t *s; file_download_add_t *fda; int len; s = create_stream(prms, sizeof(file_download_add_t)); fda = (file_download_add_t *)(s + 1); fda->fileid = dwl->fileid; s->free = file_download_free; s->cb = dwl->cb; s->cbext = dwl->cbext; s->process_data = process_file_download_headers; fda->localpath = dwl->localpath; fda->fd = INVALID_HANDLE_VALUE; len = psync_slprintf(buff, sizeof(buff), "act=dwl,strm=%" P_PRI_U64 ",fileid=%" P_PRI_U64 "\n", (uint64_t)s->streamid, (uint64_t)dwl->fileid); if (send_data(prms, buff, len)) { debug(D_WARNING, "failed to send request for fileid %lu", (unsigned long)dwl->fileid); return -1; } s->flags |= STREAM_FLAG_ACTIVE; debug(D_NOTICE, "requested data of fileid %lu to be saved in %s", (unsigned long)dwl->fileid, dwl->localpath); return 0; } static int handle_file_download_nm(async_thread_params_t *prms, task_file_download_if_not_mod_t *dwl) { char buff[256]; stream_t *s; file_download_add_t *fda; int len; s = create_stream(prms, sizeof(file_download_add_t)); fda = (file_download_add_t *)(s + 1); fda->fileid = dwl->fileid; s->free = file_download_free; s->cb = dwl->cb; s->cbext = dwl->cbext; s->process_data = process_file_download_headers; fda->osize = dwl->size; memcpy(fda->osha1hex, dwl->sha1hex, PSYNC_SHA1_DIGEST_HEXLEN); fda->localpath = dwl->localpath; fda->fd = INVALID_HANDLE_VALUE; len = psync_slprintf( buff, sizeof(buff), "act=dwlnm,strm=%" P_PRI_U64 ",fileid=%" P_PRI_U64 ",sha1=%.40s\n", (uint64_t)s->streamid, (uint64_t)dwl->fileid, dwl->sha1hex); if (send_data(prms, buff, len)) { debug(D_WARNING, "failed to send request for fileid %lu", (unsigned long)dwl->fileid); return -1; } s->flags |= STREAM_FLAG_ACTIVE; debug(D_NOTICE, "requested data of fileid %lu to be saved in %s", (unsigned long)dwl->fileid, dwl->localpath); return 0; } #define CHECK_LEN(l) \ do { \ if (unlikely(len != l)) { \ debug(D_BUG, \ "wrong size for packet of type %u, expected size %u but got %u", \ (unsigned)type, (unsigned)l, (unsigned)len); \ return 2; \ } \ } while (0) static int handle_command_data(async_thread_params_t *prms, char *data, uint32_t type, uint32_t len) { switch (type) { case TASK_TYPE_EXIT: CHECK_LEN(0); debug(D_NOTICE, "exiting"); return -1; case TASK_TYPE_FILE_DWL: CHECK_LEN(sizeof(task_file_download_t)); return handle_file_download(prms, (task_file_download_t *)data); case TASK_TYPE_FILE_DWL_NM: CHECK_LEN(sizeof(task_file_download_if_not_mod_t)); return handle_file_download_nm(prms, (task_file_download_if_not_mod_t *)data); default: debug(D_BUG, "got packet of unknown type %u", (unsigned)type); return 1; } } static int handle_command(async_thread_params_t *prms) { char buff[4096]; task_header_t hdr; int ret; unsigned char r; if (socket_t_readall(prms->privsock, &hdr, sizeof(hdr))) { debug(D_WARNING, "could not read header from socket pair"); return -1; } if (hdr.len > sizeof(buff)) { debug(D_WARNING, "too large length of packet %u provided, maximum supported is %u", (unsigned)hdr.len, (unsigned)sizeof(buff)); return -1; } if (socket_t_readall(prms->privsock, buff, hdr.len)) { debug(D_WARNING, "could not read %u bytes of data from socket pair", (unsigned)hdr.len); return -1; } ret = handle_command_data(prms, buff, hdr.type, hdr.len); if (ret < 0) { r = 255; ret = -1; } else { r = (unsigned char)ret; ret = 0; } if (socket_t_writeall(prms->privsock, &r, sizeof(r))) { debug(D_WARNING, "failed to write response to socket pair"); return -1; } return ret; } static int handle_decompressed_data(async_thread_params_t *prms) { int rd; while (1) { rd = psync_deflate_read(prms->dec, prms->curreadbuff, prms->curreadbuffrem); if (rd > 0) { prms->curreadbuff += rd; prms->curreadbuffrem -= rd; if (!prms->curreadbuffrem && prms->process_buf(prms)) return -1; } else if (rd == PSYNC_DEFLATE_NODATA) return 0; else { debug(D_ERROR, "psync_deflate_read returned %d", rd); return -1; } } } static int handle_incoming_data(async_thread_params_t *prms) { char buff[4096]; char *ptr; int rdsock, wrdecomp; while (1) { rdsock = psync_socket_read_noblock(prms->api, buff, sizeof(buff)); if (rdsock == PSYNC_SOCKET_WOULDBLOCK) return 0; else if (rdsock <= 0) { debug(D_WARNING, "read from socket returned %d", rdsock); return -1; } ptr = buff; while (rdsock) { wrdecomp = psync_deflate_write(prms->dec, ptr, rdsock, PSYNC_DEFLATE_FLUSH); if (wrdecomp == PSYNC_DEFLATE_ERROR) { debug(D_ERROR, "psync_deflate_write returned PSYNC_DEFLATE_ERROR"); return -1; } else if (wrdecomp != PSYNC_DEFLATE_FULL) { assert(wrdecomp > 0); rdsock -= wrdecomp; ptr += wrdecomp; } if (handle_decompressed_data(prms)) return -1; } } } static void free_stream(stream_t *s) { debug(D_NOTICE, "freeing unfinished stream %u", (unsigned)s->streamid); if (s->flags & STREAM_FLAG_ACTIVE) { psync_async_result_t ar; memset(&ar, 0, sizeof(ar)); ar.error = PSYNC_ASYNC_ERROR_NET; ar.errorflags = PSYNC_ASYNC_ERR_FLAG_RETRY_AS_IS; s->cb(s->cbext, &ar); } if (s->free) s->free(s, PSYNC_ASYNC_ERROR_NET); psync_free(s); } static int process_stream_header(async_thread_params_t *prms); static void read_stream_header_setup(async_thread_params_t *prms) { prms->curreadbuff = prms->buffer; prms->curreadbuffrem = STREAM_HEADER_LEN; prms->process_buf = process_stream_header; } static int process_stream_data(async_thread_params_t *prms) { psync_tree *tr; stream_t *s; int ret; tr = prms->streams; while (tr) { s = psync_tree_element(tr, stream_t, tree); if (prms->currentstreamid < s->streamid) tr = tr->left; else if (prms->currentstreamid > s->streamid) tr = tr->right; else { ret = s->process_data(s, prms, prms->buffer, prms->curreadbufflen); break; } } if (!tr) { debug(D_NOTICE, "throwing out %u bytes of data for unknown streamid %u", (unsigned)prms->curreadbufflen, (unsigned)prms->currentstreamid); ret = 0; } read_stream_header_setup(prms); return ret; } static int process_stream_header(async_thread_params_t *prms) { uint32_t len; memcpy(&prms->currentstreamid, prms->buffer, 4); len = 0; memcpy(&len, prms->buffer + 4, 2); prms->curreadbuff = prms->buffer; prms->curreadbufflen = prms->curreadbuffrem = len + 1; prms->process_buf = process_stream_data; return 0; } static void psync_async_thread(void *ptr) { async_thread_params_t *prms = (async_thread_params_t *)ptr; psync_socket_t sel[2]; int ret; sel[0] = prms->api->sock; sel[1] = prms->privsock; read_stream_header_setup(prms); while (1) { if (prms->pendingrequests) { if ((prms->pendingrequests >= PSYNC_ASYNC_MAX_GROUPED_REQUESTS || prms->datapendingsince + PSYNC_ASYNC_GROUP_REQUESTS_FOR < psync_millitime()) && flush_pending_data(prms)) break; ret = psync_select_in(sel, 2, PSYNC_ASYNC_GROUP_REQUESTS_FOR / 4); if (ret == -1) continue; } else { if (psync_socket_pendingdata(prms->api)) ret = 0; else ret = psync_select_in(sel, 2, PSYNC_ASYNC_THREAD_TIMEOUT); } if (ret == 0) { if (handle_incoming_data(prms)) break; } else if (ret == 1) { if (handle_command(prms)) break; } else { debug(D_NOTICE, "psync_select_in returned %d, exiting, errno %d", ret, (int)psync_sock_err()); break; } } // close prms->privsock before locking as there might be somebody who keeps // the mutex locked while waiting for us to reply psync_close_socket(prms->privsock); pthread_mutex_lock(&amutex); psync_close_socket(at_sock); at_sock = INVALID_SOCKET; running--; pthread_mutex_unlock(&amutex); psync_apipool_release_bad(prms->api); psync_deflate_destroy(prms->enc); psync_deflate_destroy(prms->dec); psync_tree_for_each_element_call_safe(prms->streams, stream_t, tree, free_stream); psync_free(prms); } static int psync_async_start_thread_locked() { /* If some form of protocol version negotiation is to be performed, here is * the place to pass any needed parameters. The assumption will be that server * supports everything and clients inform the server what they support. */ binparam params[] = {P_STR("auth", psync_my_auth), P_STR("checksum", "sha1")}; async_thread_params_t *tparams; psync_deflate_t *enc, *dec; binresult *res; psync_socket *api; psync_socket_t pair[2]; int tries; tries = 0; while (1) { api = psync_apipool_get(); if (!api) { debug(D_NOTICE, "could not connect to API, failing"); goto err0; } res = send_command(api, "asynctransfer", params); if (likely(res)) break; psync_apipool_release_bad(api); if (++tries >= 5) { debug(D_NOTICE, "failing after %d tries to send asynctransfer call", tries); goto err0; } } if (psync_find_result(res, "result", PARAM_NUM)->num) { debug(D_WARNING, "asynctransfer returned error %d: %s", (int)psync_find_result(res, "result", PARAM_NUM)->num, psync_find_result(res, "error", PARAM_STR)->str); psync_process_api_error(psync_find_result(res, "result", PARAM_NUM)->num); psync_free(res); psync_apipool_release(api); goto err0; } psync_free(res); if (psync_socket_pair(pair)) { debug(D_NOTICE, "psync_socket_pair() failed"); goto err1; } enc = psync_deflate_init(PSYNC_DEFLATE_COMP_FAST); if (!enc) { debug(D_NOTICE, "psync_deflate_init() failed"); goto err2; } dec = psync_deflate_init(PSYNC_DEFLATE_DECOMPRESS); if (!dec) { debug(D_NOTICE, "psync_deflate_init() failed"); goto err3; } tparams = psync_new(async_thread_params_t); memset(tparams, 0, sizeof(async_thread_params_t)); tparams->enc = enc; tparams->dec = dec; tparams->api = api; tparams->privsock = pair[1]; at_sock = pair[0]; psync_run_thread1("async transfer", psync_async_thread, tparams); running++; return 0; err3: psync_deflate_destroy(enc); err2: psync_close_socket(pair[0]); psync_close_socket(pair[1]); err1: psync_apipool_release_bad(api); err0: return -1; } static int psync_async_send_task_locked(const void *task, size_t len) { unsigned char ch; if (socket_t_writeall(at_sock, task, len)) { debug(D_WARNING, "failed to write %lu bytes of task to socket", (unsigned long)len); return -1; } if (socket_t_readall(at_sock, &ch, 1)) { debug(D_WARNING, "failed to read response from socket"); return -1; } if (ch == 0) return 0; else { debug(D_WARNING, "got error %d from async thread", (int)ch); return -1; } } static int psync_async_send_task(const void *task, size_t len) { int ret; pthread_mutex_lock(&amutex); if (running) ret = psync_async_send_task_locked(task, len); else { ret = psync_async_start_thread_locked(); if (!ret) ret = psync_async_send_task_locked(task, len); } pthread_mutex_unlock(&amutex); return ret; } void psync_async_stop() { task_header_t task; task.type = TASK_TYPE_EXIT; task.len = 0; pthread_mutex_lock(&amutex); if (running) psync_async_send_task_locked(&task, sizeof(task)); pthread_mutex_unlock(&amutex); } int psync_async_download_file(psync_fileid_t fileid, const char *localpath, psync_async_callback_t cb, void *cbext) { task_hdr_file_download_t task; task.head.type = TASK_TYPE_FILE_DWL; task.head.len = get_len(task_hdr_file_download_t); task.task.fileid = fileid; task.task.localpath = localpath; task.task.cb = cb; task.task.cbext = cbext; return psync_async_send_task(&task, sizeof(task)); } int psync_async_download_file_if_changed(psync_fileid_t fileid, const char *localpath, uint64_t size, const void *sha1hex, psync_async_callback_t cb, void *cbext) { task_hdr_file_download_if_not_mod_t task; task.head.type = TASK_TYPE_FILE_DWL_NM; task.head.len = get_len(task_hdr_file_download_if_not_mod_t); task.task.fileid = fileid; task.task.localpath = localpath; task.task.size = size; memcpy(task.task.sha1hex, sha1hex, PSYNC_SHA1_DIGEST_HEXLEN); task.task.cb = cb; task.task.cbext = cbext; return psync_async_send_task(&task, sizeof(task)); }