/* Copyright (c) 2013-2014 Anton Titov. Copyright (c) 2013-2014 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 "plibs.h" #include "pcache.h" #include "pdatabase.h" #include "plocks.h" #include "pnetlibs.h" #include "ppath.h" #include "prun.h" #include "psettings.h" #include "ptimer.h" #include "psys.h" #include #include #include #include #include #include "pfile.h" // required by psync_debug extern PSYNC_THREAD const char *psync_thread_name; #define return_error(err) \ do { \ psync_error = err; \ return -1; \ } while (0) #define SQL_NO_LOCK 0 #define SQL_READ_LOCK 1 #define SQL_WRITE_LOCK 2 struct run_after_ptr { struct run_after_ptr *next; psync_run_after_t run; void *ptr; }; typedef struct { psync_list list; psync_transaction_callback_t commit_callback; psync_transaction_callback_t rollback_callback; void *ptr; } tran_callback_t; static const uint8_t __hex_lookupl[513] = {"000102030405060708090a0b0c0d0e0f" "101112131415161718191a1b1c1d1e1f" "202122232425262728292a2b2c2d2e2f" "303132333435363738393a3b3c3d3e3f" "404142434445464748494a4b4c4d4e4f" "505152535455565758595a5b5c5d5e5f" "606162636465666768696a6b6c6d6e6f" "707172737475767778797a7b7c7d7e7f" "808182838485868788898a8b8c8d8e8f" "909192939495969798999a9b9c9d9e9f" "a0a1a2a3a4a5a6a7a8a9aaabacadaeaf" "b0b1b2b3b4b5b6b7b8b9babbbcbdbebf" "c0c1c2c3c4c5c6c7c8c9cacbcccdcecf" "d0d1d2d3d4d5d6d7d8d9dadbdcdddedf" "e0e1e2e3e4e5e6e7e8e9eaebecedeeef" "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"}; const char base64_table[] = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_'}; static const char base64_reverse_table[256] = { -2, -2, -2, -2, -2, -2, -2, -2, -2, -1, -1, -2, -2, -1, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -1, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, 62, -2, 62, -2, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -2, -2, -2, -1, -2, -2, -2, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -2, -2, -2, -2, 63, -2, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2}; uint16_t const *__hex_lookup = (uint16_t *)__hex_lookupl; static char normalize_table[256]; const static char *psync_typenames[] = { "[invalid type]", "[number]", "[string]", "[float]", "[null]", "[bool]"}; char psync_my_auth[64] = "", psync_my_2fa_code[32], *psync_my_user = NULL, *psync_my_pass = NULL, *psync_my_2fa_token = NULL, *psync_my_verify_token = NULL; int psync_my_2fa_code_type = 0, psync_my_2fa_trust = 0, psync_my_2fa_has_devices = 0, psync_my_2fa_type = 1; uint64_t psync_my_userid = 0; pthread_mutex_t psync_my_auth_mutex = PTHREAD_MUTEX_INITIALIZER; psync_rwlock_t psync_db_lock; sqlite3 *psync_db; pstatus_t psync_status; int psync_do_run = 1; int psync_recache_contacts = 1; PSYNC_THREAD uint32_t psync_error = 0; static pthread_mutex_t psync_db_checkpoint_mutex; static int in_transaction = 0; static int transaction_failed = 0; static psync_list tran_callbacks; char *psync_strdup(const char *str) { size_t len; len = strlen(str) + 1; return (char *)memcpy(psync_new_cnt(char, len), str, len); } char *psync_strnormalize_filename(const char *str) { size_t len, i; char *ptr; len = strlen(str) + 1; ptr = psync_new_cnt(char, len); for (i = 0; i < len; i++) ptr[i] = normalize_table[(unsigned char)str[i]]; return ptr; } char *psync_strndup(const char *str, size_t len) { char *ptr; ptr = (char *)memcpy(psync_new_cnt(char, len + 1), str, len); ptr[len] = 0; return ptr; } char *psync_strcat(const char *str, ...) { size_t i, size, len; const char *strs[64]; size_t lengths[64]; const char *ptr; char *ptr2, *ptr3; va_list ap; va_start(ap, str); strs[0] = str; len = strlen(str); lengths[0] = len; size = len + 1; i = 1; while ((ptr = va_arg(ap, const char *))) { assert(i < ARRAY_SIZE(strs)); len = strlen(ptr); lengths[i] = len; strs[i++] = ptr; size += len; } va_end(ap); ptr2 = ptr3 = (char *)psync_malloc(size); for (size = 0; size < i; size++) { memcpy(ptr2, strs[size], lengths[size]); ptr2 += lengths[size]; } *ptr2 = 0; return ptr3; } int psync_slprintf(char *str, size_t size, const char *format, ...) { va_list ap; int ret; va_start(ap, format); ret = vsnprintf(str, size, format, ap); va_end(ap); if (unlikely_log(ret >= size)) str[size - 1] = 0; return ret; } unsigned char *psync_base32_encode(const unsigned char *str, size_t length, size_t *ret_length) { static const unsigned char *table = (const unsigned char *)"ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; unsigned char *result; unsigned char *p; uint32_t bits, buff; result = (unsigned char *)psync_malloc(((length + 4) / 5) * 8 + 1); p = result; bits = 0; buff = 0; // don't really have to initialize this one, but a compiler that // will detect that this is safe is yet to be born while (length) { if (bits < 5) { buff = (buff << 8) | (*str++); length--; bits += 8; } bits -= 5; *p++ = table[0x1f & (buff >> bits)]; } while (bits) { if (bits < 5) { buff <<= (5 - bits); bits = 5; } bits -= 5; *p++ = table[0x1f & (buff >> bits)]; } *ret_length = p - result; *p = 0; return result; } unsigned char *psync_base32_decode(const unsigned char *str, size_t length, size_t *ret_length) { unsigned char *result, *p; uint32_t bits, buff; unsigned char ch; result = (unsigned char *)psync_malloc((length + 7) / 8 * 5 + 1); p = result; bits = 0; buff = 0; while (length) { ch = *str++; length--; if (ch >= 'A' && ch <= 'Z') ch = (ch & 0x1f) - 1; else if (ch >= '2' && ch <= '7') ch -= '2' - 26; else { psync_free(result); return NULL; } buff = (buff << 5) + ch; bits += 5; if (bits >= 8) { bits -= 8; *p++ = buff >> bits; } } *p = 0; *ret_length = p - result; return result; } unsigned char *psync_base64_encode(const unsigned char *str, size_t length, size_t *ret_length) { const unsigned char *current = str; unsigned char *p; unsigned char *result; result = (unsigned char *)psync_malloc(((length + 2) / 3) * 4 + 1); p = result; while (length > 2) { *p++ = base64_table[current[0] >> 2]; *p++ = base64_table[((current[0] & 0x03) << 4) + (current[1] >> 4)]; *p++ = base64_table[((current[1] & 0x0f) << 2) + (current[2] >> 6)]; *p++ = base64_table[current[2] & 0x3f]; current += 3; length -= 3; } if (length != 0) { *p++ = base64_table[current[0] >> 2]; if (length > 1) { *p++ = base64_table[((current[0] & 0x03) << 4) + (current[1] >> 4)]; *p++ = base64_table[(current[1] & 0x0f) << 2]; } else *p++ = base64_table[(current[0] & 0x03) << 4]; } *ret_length = p - result; *p = 0; return result; } unsigned char *psync_base64_decode(const unsigned char *str, size_t length, size_t *ret_length) { const unsigned char *current = str; unsigned char *result; size_t i = 0, j = 0; ssize_t ch; result = (unsigned char *)psync_malloc((length + 3) / 4 * 3 + 1); while (length-- > 0) { ch = base64_reverse_table[*current++]; if (ch == -1) continue; else if (ch == -2) { psync_free(result); return NULL; } switch (i % 4) { case 0: result[j] = ch << 2; break; case 1: result[j++] |= ch >> 4; result[j] = (ch & 0x0f) << 4; break; case 2: result[j++] |= ch >> 2; result[j] = (ch & 0x03) << 6; break; case 3: result[j++] |= ch; break; } i++; } *ret_length = j; result[j] = 0; return result; } int psync_is_valid_utf8(const char *str) { static const int8_t trailing[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, -1, -1, -1, -1, -1, -1, -1, -1}; int8_t t; while (*str) { t = trailing[(unsigned char)*str++]; if (unlikely(t)) { if (t < 0) return 0; while (t--) if ((((unsigned char)*str++) & 0xc0) != 0x80) return 0; } } return 1; } void psync_sql_err_callback(void *ptr, int code, const char *msg) { debug(D_WARNING, "database warning %d: %s", code, msg); } static void psync_sql_wal_checkpoint() { int code; psync_sql_lock(); psync_sql_unlock(); if (pthread_mutex_trylock(&psync_db_checkpoint_mutex)) { debug(D_NOTICE, "checkpoint already in progress"); return; } debug(D_NOTICE, "checkpointing database"); code = sqlite3_wal_checkpoint(psync_db, NULL); while (code == SQLITE_LOCKED) { psys_sleep_milliseconds(2); code = sqlite3_wal_checkpoint(psync_db, NULL); } pthread_mutex_unlock(&psync_db_checkpoint_mutex); if (unlikely(code != SQLITE_OK)) debug(D_CRITICAL, "sqlite3_wal_checkpoint returned error %d", code); else debug(D_NOTICE, "checkpoint finished"); } static int psync_sql_wal_hook(void *ptr, sqlite3 *db, const char *name, int numpages) { if (numpages >= PSYNC_DB_CHECKPOINT_AT_PAGES) prun_thread("checkpoint charlie", psync_sql_wal_checkpoint); return SQLITE_OK; } int psync_sql_connect(const char *db) { static int initmutex = 1; pthread_mutexattr_t mattr; struct stat st; uint64_t dbver; int initdbneeded = 0; int code; assert(sqlite3_libversion_number() == SQLITE_VERSION_NUMBER); assert(!strcmp(sqlite3_sourceid(), SQLITE_SOURCE_ID)); assert(!strcmp(sqlite3_libversion(), SQLITE_VERSION)); debug(D_NOTICE, "Using sqlite version %s source %s", sqlite3_libversion(), sqlite3_sourceid()); if (!sqlite3_threadsafe()) { debug(D_CRITICAL, "sqlite is compiled without thread support"); return -1; } if (stat(db, &st) != 0) initdbneeded = 1; code = sqlite3_open(db, &psync_db); if (likely(code == SQLITE_OK)) { if (initmutex) { plocks_init(&psync_db_lock); pthread_mutexattr_init(&mattr); pthread_mutexattr_settype(&mattr, PTHREAD_MUTEX_RECURSIVE); pthread_mutex_init(&psync_db_checkpoint_mutex, &mattr); pthread_mutexattr_destroy(&mattr); initmutex = 0; } if (IS_DEBUG) sqlite3_config(SQLITE_CONFIG_LOG, psync_sql_err_callback, NULL); sqlite3_wal_hook(psync_db, psync_sql_wal_hook, NULL); psync_sql_statement(PSYNC_DATABASE_CONFIG); if (initdbneeded == 1) return psync_sql_statement(PSYNC_DATABASE_STRUCTURE); else if (psync_sql_statement( "DELETE FROM setting WHERE id='justcheckingiflocked'")) { debug(D_ERROR, "database is locked"); sqlite3_close(psync_db); plocks_destroy(&psync_db_lock); return -1; } dbver = psync_sql_cellint("SELECT value FROM setting WHERE id='dbversion'", 0); if (dbver < PSYNC_DATABASE_VERSION) { uint64_t i; debug(D_NOTICE, "database version %d detected, upgrading to %d", (int)dbver, (int)PSYNC_DATABASE_VERSION); for (i = dbver; i < PSYNC_DATABASE_VERSION; i++) if (psync_sql_statement(psync_db_upgrade[i])) { debug(D_ERROR, "error running statement %s on sqlite %s", psync_db_upgrade[i], sqlite3_libversion()); if (IS_DEBUG) return_error(PERROR_DATABASE_OPEN); } } return 0; } else { debug(D_CRITICAL, "could not open sqlite database %s: %d", db, code); return -1; } } int psync_sql_close() { int code, tries; tries = 0; while (1) { code = sqlite3_close(psync_db); if (code == SQLITE_BUSY) { pcache_clean(); tries++; if (tries > 100) { psys_sleep_milliseconds_fast(tries - 90); if (tries > 200) { debug(D_ERROR, "failed to close database"); break; } } } else break; } psync_db = NULL; if (unlikely(code != SQLITE_OK)) { debug(D_CRITICAL, "error when closing database: %d", code); code = sqlite3_close_v2(psync_db); if (unlikely(code != SQLITE_OK)) { debug(D_CRITICAL, "error when closing database even with sqlite3_close_v2: %d", code); return -1; } } return 0; } int psync_sql_reopen(const char *path) { sqlite3 *db; int code; debug(D_NOTICE, "reopening database %s", path); code = sqlite3_open(path, &db); if (likely(code == SQLITE_OK)) { code = sqlite3_wal_checkpoint(db, NULL); if (unlikely(code != SQLITE_OK)) { debug(D_CRITICAL, "sqlite3_wal_checkpoint returned error %d", code); sqlite3_close(db); return -1; } code = sqlite3_close(db); if (unlikely(code != SQLITE_OK)) { debug(D_CRITICAL, "sqlite3_close returned error %d", code); return -1; } return 0; } else { debug(D_CRITICAL, "could not open sqlite dabase %s: %d", path, code); return -1; } } void psync_sql_checkpoint_lock() { pthread_mutex_lock(&psync_db_checkpoint_mutex); } void psync_sql_checkpoint_unlock() { pthread_mutex_unlock(&psync_db_checkpoint_mutex); } #if IS_DEBUG typedef struct { psync_list list; const char *file; const char *thread; struct timespec tm; unsigned line; } rd_lock_data; static PSYNC_THREAD rd_lock_data *rdlock = NULL; static PSYNC_THREAD unsigned long sqlrdlockcnt = 0; static PSYNC_THREAD struct timespec sqlrdlockstart; unsigned long sqllockcnt = 0; static struct timespec sqllockstart; static const char *wrlockfile = "none"; static const char *wrlockthread = ""; static unsigned wrlockline = 0; static unsigned wrlocked = 0; static pthread_t wrlocker; static psync_list rdlocks = PSYNC_LIST_STATIC_INIT(rdlocks); static pthread_mutex_t rdmutex = PTHREAD_MUTEX_INITIALIZER; static void record_wrlock(const char *file, unsigned line) { if (unlikely(rdlock)) { debug(D_BUG, "trying to get write lock at %s:%u, but read lock is already taken " "at %s:%u, aborting", file, line, rdlock->file, rdlock->line); senddebug("trying to get write lock at %s:%u, but read lock is already " "taken at %s:%u, aborting", file, line, rdlock->file, rdlock->line); abort(); } sendassert(!wrlocked); assert(!wrlocked); wrlockfile = file; wrlockline = line; wrlockthread = psync_thread_name; wrlocked = 1; wrlocker = pthread_self(); } static void record_wrunlock() { sendassert(pthread_equal(pthread_self(), wrlocker)); sendassert(wrlocked); assert(pthread_equal(pthread_self(), wrlocker)); assert(wrlocked); wrlocked = 0; } static void record_rdlock(const char *file, unsigned line, struct timespec *tm) { rd_lock_data *lock; lock = psync_new(rd_lock_data); lock->file = file; lock->thread = psync_thread_name; lock->line = line; memcpy(&lock->tm, tm, sizeof(struct timespec)); pthread_mutex_lock(&rdmutex); psync_list_add_tail(&rdlocks, &lock->list); pthread_mutex_unlock(&rdmutex); rdlock = lock; } static rd_lock_data *record_rdunlock() { rd_lock_data *lock; assert(rdlock); lock = rdlock; rdlock = NULL; pthread_mutex_lock(&rdmutex); psync_list_del(&lock->list); pthread_mutex_unlock(&rdmutex); return lock; } static void time_format(time_t tm, unsigned long ns, char *result); void psync_sql_dump_locks() { rd_lock_data *lock; char dttime[36]; if (wrlocked) { time_format(sqllockstart.tv_sec, sqllockstart.tv_nsec, dttime); debug(D_ERROR, "write lock taken by thread %s from %s:%u at %s", wrlockthread, wrlockfile, wrlockline, dttime); senddebug("write lock taken by thread %s from %s:%u at %s", wrlockthread, wrlockfile, wrlockline, dttime); } pthread_mutex_lock(&rdmutex); psync_list_for_each_element(lock, &rdlocks, rd_lock_data, list) { time_format(lock->tm.tv_sec, lock->tm.tv_nsec, dttime); debug(D_ERROR, "read lock taken by thread %s from %s:%u at %s", lock->thread, lock->file, lock->line, dttime); senddebug("read lock taken by thread %s from %s:%u at %s", lock->thread, lock->file, lock->line, dttime); } pthread_mutex_unlock(&rdmutex); } #endif #if IS_DEBUG int psync_sql_do_trylock(const char *file, unsigned line) { if (plocks_trywrlock(&psync_db_lock)) return -1; if (++sqllockcnt == 1) { clock_gettime(CLOCK_REALTIME, &sqllockstart); record_wrlock(file, line); } return 0; } #else int psync_sql_trylock() { return plocks_trywrlock(&psync_db_lock); } #endif #if IS_DEBUG void psync_sql_do_lock(const char *file, unsigned line) { if (plocks_trywrlock(&psync_db_lock)) { struct timespec start, end; unsigned long msec; clock_gettime(CLOCK_REALTIME, &start); memcpy(&end, &start, sizeof(end)); end.tv_sec += PSYNC_DEBUG_LOCK_TIMEOUT; if (plocks_timedwrlock(&psync_db_lock, &end)) { debug(D_BUG, "sql write lock timed out called from %s:%u", file, line); senddebug("sql write lock timed out called from %s:%u", file, line); psync_sql_dump_locks(); abort(); } clock_gettime(CLOCK_REALTIME, &end); msec = (end.tv_sec - start.tv_sec) * 1000 + end.tv_nsec / 1000000 - start.tv_nsec / 1000000; if (msec >= 5) debug(D_WARNING, "waited %lu milliseconds for database write lock", msec); assert(sqllockcnt == 0); sqllockcnt++; memcpy(&sqllockstart, &end, sizeof(struct timespec)); record_wrlock(file, line); } else if (++sqllockcnt == 1) { clock_gettime(CLOCK_REALTIME, &sqllockstart); record_wrlock(file, line); } } #else void psync_sql_lock() { plocks_wrlock(&psync_db_lock); } #endif void psync_sql_unlock() { #if IS_DEBUG assert(sqllockcnt > 0); if (--sqllockcnt == 0) { struct timespec end; unsigned long msec; clock_gettime(CLOCK_REALTIME, &end); msec = (end.tv_sec - sqllockstart.tv_sec) * 1000 + end.tv_nsec / 1000000 - sqllockstart.tv_nsec / 1000000; if (msec >= 10) debug(D_WARNING, "held database write lock for %lu milliseconds taken from %s:%u", msec, wrlockfile, wrlockline); record_wrunlock(); plocks_unlock(&psync_db_lock); } else plocks_unlock(&psync_db_lock); #else plocks_unlock(&psync_db_lock); #endif } #if IS_DEBUG void psync_sql_do_rdlock(const char *file, unsigned line) { if (plocks_tryrdlock(&psync_db_lock)) { struct timespec start, end; unsigned long msec; clock_gettime(CLOCK_REALTIME, &start); memcpy(&end, &start, sizeof(end)); end.tv_sec += PSYNC_DEBUG_LOCK_TIMEOUT; if (plocks_timedrdlock(&psync_db_lock, &end)) { debug(D_BUG, "sql read lock timed out, called from %s:%u", file, line); senddebug("sql read lock timed out, called from %s:%u", file, line); psync_sql_dump_locks(); abort(); } clock_gettime(CLOCK_REALTIME, &end); msec = (end.tv_sec - start.tv_sec) * 1000 + end.tv_nsec / 1000000 - start.tv_nsec / 1000000; if (msec >= 5) debug(D_WARNING, "waited %lu milliseconds for database read lock", msec); sqlrdlockcnt++; memcpy(&sqlrdlockstart, &end, sizeof(struct timespec)); record_rdlock(file, line, &sqlrdlockstart); } else if (++sqlrdlockcnt == 1) { clock_gettime(CLOCK_REALTIME, &sqlrdlockstart); record_rdlock(file, line, &sqlrdlockstart); } } #else void psync_sql_rdlock() { plocks_rdlock(&psync_db_lock); } #endif void psync_sql_rdunlock() { #if IS_DEBUG if (unlikely(sqlrdlockcnt == 0)) { psync_sql_unlock(); return; } if (--sqlrdlockcnt == 0) { struct timespec end; unsigned long msec; rd_lock_data *lock; plocks_unlock(&psync_db_lock); clock_gettime(CLOCK_REALTIME, &end); lock = record_rdunlock(); msec = (end.tv_sec - sqlrdlockstart.tv_sec) * 1000 + end.tv_nsec / 1000000 - sqlrdlockstart.tv_nsec / 1000000; if (msec >= 20) debug(D_WARNING, "held database read lock for %lu milliseconds taken at %s:%u", msec, lock->file, lock->line); psync_free(lock); } else plocks_unlock(&psync_db_lock); #else plocks_unlock(&psync_db_lock); #endif } int psync_sql_has_waiters() { return plocks_num_waiters(&psync_db_lock) > 0; } int psync_sql_isrdlocked() { return plocks_holding_rdlock(&psync_db_lock); } int psync_sql_islocked() { return plocks_holding_lock(&psync_db_lock); } int psync_sql_tryupgradelock() { #if IS_DEBUG if (plocks_holding_wrlock(&psync_db_lock)) return 0; assert(plocks_holding_rdlock(&psync_db_lock)); if (plocks_towrlock(&psync_db_lock)) return -1; else { rd_lock_data *lock = record_rdunlock(); sqllockcnt = sqlrdlockcnt; sqlrdlockcnt = 0; assert(sqllockcnt == 1); sqllockstart = sqlrdlockstart; record_wrlock(lock->file, lock->line); psync_free(lock); return 0; } #else return plocks_towrlock(&psync_db_lock); #endif } int psync_sql_sync() { int code; pthread_mutex_lock(&psync_db_checkpoint_mutex); code = sqlite3_wal_checkpoint(psync_db, NULL); if (unlikely(code == SQLITE_BUSY || code == SQLITE_LOCKED)) { psync_sql_lock(); code = sqlite3_wal_checkpoint(psync_db, NULL); psync_sql_unlock(); } pthread_mutex_unlock(&psync_db_checkpoint_mutex); if (unlikely(code != SQLITE_OK)) { debug(D_CRITICAL, "sqlite3_wal_checkpoint returned error %d", code); return -1; } else return 0; } #if IS_DEBUG int psync_sql_do_statement(const char *sql, const char *file, unsigned line) { char *errmsg; int code; psync_sql_do_lock(file, line); #else int psync_sql_statement(const char *sql) { char *errmsg; int code; psync_sql_lock(); #endif code = sqlite3_exec(psync_db, sql, NULL, NULL, &errmsg); psync_sql_unlock(); if (likely(code == SQLITE_OK)) return 0; else { #if IS_DEBUG debug(D_ERROR, "error running sql statement: %s: %s called from %s:%u", sql, errmsg, file, line); #else debug(D_ERROR, "error running sql statement: %s: %s", sql, errmsg); #endif sqlite3_free(errmsg); return -1; } } #if IS_DEBUG int psync_sql_do_start_transaction(const char *file, unsigned line) { psync_sql_res *res; psync_sql_do_lock(file, line); res = psync_sql_do_prep_statement("BEGIN", file, line); #else int psync_sql_start_transaction() { psync_sql_res *res; psync_sql_lock(); res = psync_sql_prep_statement("BEGIN"); #endif assert(!in_transaction); if (unlikely(!res || psync_sql_run_free(res))) return -1; in_transaction = 1; transaction_failed = 0; psync_list_init(&tran_callbacks); return 0; } static void run_commit_callbacks(int success) { tran_callback_t *cb; psync_list *l1, *l2; psync_list_for_each_safe(l1, l2, &tran_callbacks) { cb = psync_list_element(l1, tran_callback_t, list); if (success) cb->commit_callback(cb->ptr); else cb->rollback_callback(cb->ptr); psync_free(cb); } } int psync_sql_commit_transaction() { assert(in_transaction); if (likely(!transaction_failed)) { psync_sql_res *res = psync_sql_prep_statement("COMMIT"); if (likely(!psync_sql_run_free(res))) { run_commit_callbacks(1); in_transaction = 0; psync_sql_unlock(); return 0; } } else debug(D_ERROR, "rolling back transaction as some statements failed"); psync_sql_rollback_transaction(); return -1; } int psync_sql_rollback_transaction() { psync_sql_res *res = psync_sql_prep_statement("ROLLBACK"); assert(in_transaction); psync_sql_run_free(res); run_commit_callbacks(0); in_transaction = 0; psync_sql_unlock(); return 0; } void psync_sql_transation_add_callbacks( psync_transaction_callback_t commit_callback, psync_transaction_callback_t rollback_callback, void *ptr) { tran_callback_t *cb; assert(in_transaction); cb = psync_new(tran_callback_t); cb->commit_callback = commit_callback; cb->rollback_callback = rollback_callback; cb->ptr = ptr; psync_list_add_tail(&tran_callbacks, &cb->list); } #if IS_DEBUG && 0 typedef struct { psync_tree tree; const char *sql; } psync_sql_tree_t; static psync_tree *sql_tree = PSYNC_TREE_EMPTY; static void psync_sql_do_check_query_plan(const char *sql) { sqlite3_stmt *stmt; char *exsql; const char *detail; int code; exsql = psync_strcat("EXPLAIN QUERY PLAN ", sql, NULL); code = sqlite3_prepare_v2(psync_db, exsql, -1, &stmt, 0); psync_free(exsql); if (code != SQLITE_OK) { debug(D_ERROR, "EXPLAIN QUERY PLAN %s returned error: %d", sql, code); return; } while (sqlite3_step(stmt) == SQLITE_ROW) { detail = (const char *)sqlite3_column_text(stmt, 3); if (!strncmp(detail, "SCAN TABLE", strlen("SCAN TABLE"))) debug(D_WARNING, "doing %s on sql %s", detail, sql); } sqlite3_finalize(stmt); } static psync_tree *psync_sql_new_tree_node(const char *sql) { psync_sql_tree_t *node; node = psync_new(psync_sql_tree_t); node->sql = sql; return &node->tree; } static void psync_sql_check_query_plan_locked(const char *sql) { psync_tree *node; int cmp; if (!sql_tree) { ptree_add_after(&sql_tree, NULL, psync_sql_new_tree_node(sql)); return; } node = sql_tree; while (1) { cmp = strcmp(sql, ptree_element(node, psync_sql_tree_t, tree)->sql); if (cmp < 0) { if (node->left) node = node->left; else { ptree_add_before(&sql_tree, node, psync_sql_new_tree_node(sql)); break; } } else if (cmp > 0) { if (node->right) node = node->right; else { ptree_add_after(&sql_tree, node, psync_sql_new_tree_node(sql)); break; } } else return; } psync_sql_do_check_query_plan(sql); } static void psync_sql_check_query_plan(const char *sql) { psync_sql_lock(); psync_sql_check_query_plan_locked(sql); psync_sql_unlock(); } #else #define psync_sql_check_query_plan(s) ((void)0) #endif char *psync_sql_cellstr(const char *sql) { sqlite3_stmt *stmt; int code; psync_sql_check_query_plan(sql); psync_sql_rdlock(); code = sqlite3_prepare_v2(psync_db, sql, -1, &stmt, NULL); if (unlikely(code != SQLITE_OK)) { psync_sql_rdunlock(); debug(D_ERROR, "error running sql statement: %s: %s", sql, sqlite3_errmsg(psync_db)); sendtdebug("error running sql statement: %s: %s", sql, sqlite3_errmsg(psync_db)); return NULL; } code = sqlite3_step(stmt); if (code == SQLITE_ROW) { char *ret; ret = (char *)sqlite3_column_text(stmt, 0); if (ret) ret = psync_strdup(ret); sqlite3_finalize(stmt); psync_sql_rdunlock(); return ret; } else { sqlite3_finalize(stmt); psync_sql_rdunlock(); if (unlikely(code != SQLITE_DONE)) { debug(D_ERROR, "sqlite3_step returned error: %s: %s", sql, sqlite3_errmsg(psync_db)); sendtdebug("sqlite3_step returned error: %s: %s", sql, sqlite3_errmsg(psync_db)); } return NULL; } } int64_t psync_sql_cellint(const char *sql, int64_t dflt) { sqlite3_stmt *stmt; int code; psync_sql_check_query_plan(sql); psync_sql_rdlock(); code = sqlite3_prepare_v2(psync_db, sql, -1, &stmt, NULL); if (unlikely(code != SQLITE_OK)) { debug(D_ERROR, "error running sql statement: %s: %s", sql, sqlite3_errmsg(psync_db)); sendtdebug("error running sql statement: %s: %s", sql, sqlite3_errmsg(psync_db)); } else { code = sqlite3_step(stmt); if (code == SQLITE_ROW) dflt = sqlite3_column_int64(stmt, 0); else if (unlikely(code != SQLITE_DONE)) { debug(D_ERROR, "sqlite3_step returned error: %s: %s", sql, sqlite3_errmsg(psync_db)); sendtdebug("sqlite3_step returned error: %s: %s", sql, sqlite3_errmsg(psync_db)); } sqlite3_finalize(stmt); } psync_sql_rdunlock(); return dflt; } char **psync_sql_rowstr(const char *sql) { sqlite3_stmt *stmt; int code, cnt; psync_sql_check_query_plan(sql); psync_sql_rdlock(); code = sqlite3_prepare_v2(psync_db, sql, -1, &stmt, NULL); if (unlikely(code != SQLITE_OK)) { psync_sql_rdunlock(); debug(D_ERROR, "error running sql statement: %s: %s", sql, sqlite3_errmsg(psync_db)); sendtdebug("error running sql statement: %s: %s", sql, sqlite3_errmsg(psync_db)); return NULL; } cnt = sqlite3_column_count(stmt); code = sqlite3_step(stmt); if (code == SQLITE_ROW) { char **arr, *nstr, *str; size_t l, ln; VAR_ARRAY(lens, size_t, cnt); int i; ln = 0; for (i = 0; i < cnt; i++) { l = sqlite3_column_bytes(stmt, i); ln += l; lens[i] = l; } ln += (sizeof(char *) + 1) * cnt; arr = (char **)psync_malloc(ln); nstr = ((char *)arr) + sizeof(char *) * cnt; for (i = 0; i < cnt; i++) { str = (char *)sqlite3_column_blob(stmt, i); if (str) { ln = lens[i]; memcpy(nstr, str, ln); nstr[ln] = 0; arr[i] = nstr; nstr += ln + 1; } else arr[i] = NULL; } sqlite3_finalize(stmt); psync_sql_rdunlock(); return arr; } else { sqlite3_finalize(stmt); psync_sql_rdunlock(); if (unlikely(code != SQLITE_DONE)) { debug(D_ERROR, "sqlite3_step returned error: %s: %s", sql, sqlite3_errmsg(psync_db)); sendtdebug("sqlite3_step returned error: %s: %s", sql, sqlite3_errmsg(psync_db)); } return NULL; } } psync_variant *psync_sql_row(const char *sql) { sqlite3_stmt *stmt; int code, cnt; psync_sql_check_query_plan(sql); psync_sql_rdlock(); code = sqlite3_prepare_v2(psync_db, sql, -1, &stmt, NULL); if (unlikely(code != SQLITE_OK)) { psync_sql_rdunlock(); debug(D_ERROR, "error running sql statement: %s: %s", sql, sqlite3_errmsg(psync_db)); sendtdebug("error running sql statement: %s: %s", sql, sqlite3_errmsg(psync_db)); return NULL; } cnt = sqlite3_column_count(stmt); code = sqlite3_step(stmt); if (code == SQLITE_ROW) { psync_variant *arr; char *nstr, *str; size_t l, ln; VAR_ARRAY(lens, size_t, cnt); int i, t; VAR_ARRAY(types, int, cnt); ln = sizeof(psync_variant) * cnt; for (i = 0; i < cnt; i++) { t = sqlite3_column_type(stmt, i); types[i] = t; if (t == SQLITE_TEXT || t == SQLITE_BLOB) { l = sqlite3_column_bytes(stmt, i); ln += l + 1; lens[i] = l; } } arr = (psync_variant *)psync_malloc(ln); nstr = ((char *)arr) + sizeof(psync_variant) * cnt; for (i = 0; i < cnt; i++) { t = types[i]; if (t == SQLITE_INTEGER) { arr[i].type = PSYNC_TNUMBER; arr[i].snum = sqlite3_column_int64(stmt, i); } else if (t == SQLITE_TEXT || t == SQLITE_BLOB) { str = (char *)sqlite3_column_blob(stmt, i); ln = lens[i]; memcpy(nstr, str, ln); nstr[ln] = 0; arr[i].type = PSYNC_TSTRING; arr[i].str = nstr; nstr += ln + 1; } else if (t == SQLITE_FLOAT) { arr[i].type = PSYNC_TREAL; arr[i].real = sqlite3_column_double(stmt, i); } else { arr[i].type = PSYNC_TNULL; } } sqlite3_finalize(stmt); psync_sql_rdunlock(); return arr; } else { sqlite3_finalize(stmt); psync_sql_rdunlock(); if (unlikely(code != SQLITE_DONE)) { debug(D_ERROR, "sqlite3_step returned error: %s: %s", sql, sqlite3_errmsg(psync_db)); sendtdebug("sqlite3_step returned error: %s: %s", sql, sqlite3_errmsg(psync_db)); } return NULL; } } #if IS_DEBUG psync_sql_res *psync_sql_do_query_nocache(const char *sql, const char *file, unsigned line) { #else psync_sql_res *psync_sql_query_nocache(const char *sql) { #endif sqlite3_stmt *stmt; psync_sql_res *res; int code, cnt; psync_sql_check_query_plan(sql); #if IS_DEBUG psync_sql_do_lock(file, line); #else psync_sql_lock(); #endif code = sqlite3_prepare_v2(psync_db, sql, -1, &stmt, NULL); if (unlikely(code != SQLITE_OK)) { psync_sql_unlock(); #if IS_DEBUG debug(D_ERROR, "error running sql statement: %s: %s called from %s:%u", sql, sqlite3_errmsg(psync_db), file, line); senddebug("error running sql statement: %s: %s called from %s:%u", sql, sqlite3_errmsg(psync_db), file, line); #else debug(D_ERROR, "error running sql statement: %s: %s", sql, sqlite3_errmsg(psync_db)); senddebug("error running sql statement: %s: %s", sql, sqlite3_errmsg(psync_db)); #endif return NULL; } cnt = sqlite3_column_count(stmt); res = (psync_sql_res *)psync_malloc(sizeof(psync_sql_res) + cnt * sizeof(psync_variant)); res->stmt = stmt; res->sql = sql; res->column_count = cnt; res->locked = SQL_WRITE_LOCK; return res; } #if IS_DEBUG psync_sql_res *psync_sql_do_query(const char *sql, const char *file, unsigned line) { #else psync_sql_res *psync_sql_query(const char *sql) { #endif psync_sql_res *ret; ret = (psync_sql_res *)pcache_get(sql); if (ret) { // debug(D_NOTICE, "got query %s from cache", sql); ret->locked = SQL_WRITE_LOCK; ret->sql = sql; #if IS_DEBUG psync_sql_do_lock(file, line); #else psync_sql_lock(); #endif return ret; } else #if IS_DEBUG return psync_sql_do_query_nocache(sql, file, line); #else return psync_sql_query_nocache(sql); #endif } #if IS_DEBUG psync_sql_res *psync_sql_do_query_rdlock_nocache(const char *sql, const char *file, unsigned line) { #else psync_sql_res *psync_sql_query_rdlock_nocache(const char *sql) { #endif sqlite3_stmt *stmt; psync_sql_res *res; int code, cnt; psync_sql_check_query_plan(sql); #if IS_DEBUG psync_sql_do_rdlock(file, line); #else psync_sql_rdlock(); #endif code = sqlite3_prepare_v2(psync_db, sql, -1, &stmt, NULL); if (unlikely(code != SQLITE_OK)) { psync_sql_rdunlock(); #if IS_DEBUG debug(D_ERROR, "error running sql statement: %s: %s called from %s:%u", sql, sqlite3_errmsg(psync_db), file, line); senddebug("error running sql statement: %s: %s called from %s:%u", sql, sqlite3_errmsg(psync_db), file, line); #else debug(D_ERROR, "error running sql statement: %s: %s", sql, sqlite3_errmsg(psync_db)); senddebug("error running sql statement: %s: %s", sql, sqlite3_errmsg(psync_db)); #endif return NULL; } cnt = sqlite3_column_count(stmt); res = (psync_sql_res *)psync_malloc(sizeof(psync_sql_res) + cnt * sizeof(psync_variant)); res->stmt = stmt; res->sql = sql; res->column_count = cnt; res->locked = SQL_READ_LOCK; return res; } #if IS_DEBUG psync_sql_res *psync_sql_do_query_rdlock(const char *sql, const char *file, unsigned line) { #else psync_sql_res *psync_sql_query_rdlock(const char *sql) { #endif psync_sql_res *ret; ret = (psync_sql_res *)pcache_get(sql); if (ret) { // debug(D_NOTICE, "got query %s from cache", sql); ret->locked = SQL_READ_LOCK; ret->sql = sql; #if IS_DEBUG psync_sql_do_rdlock(file, line); #else psync_sql_rdlock(); #endif return ret; } else #if IS_DEBUG return psync_sql_do_query_rdlock_nocache(sql, file, line); #else return psync_sql_query_rdlock_nocache(sql); #endif } #if IS_DEBUG psync_sql_res *psync_sql_do_query_nolock_nocache(const char *sql, const char *file, unsigned line) { #else psync_sql_res *psync_sql_query_nolock_nocache(const char *sql) { #endif sqlite3_stmt *stmt; psync_sql_res *res; int code, cnt; #if IS_DEBUG if (!psync_sql_islocked()) { debug(D_BUG, "illegal use of psync_sql_query_nolock, can only be used while " "holding lock, invoked from %s:%u, sql: %s", file, line, sql); senddebug("illegal use of psync_sql_query_nolock, can only be used while " "holding lock, invoked from %s:%u, sql: %s", file, line, sql); abort(); } #endif psync_sql_check_query_plan(sql); code = sqlite3_prepare_v2(psync_db, sql, -1, &stmt, NULL); if (unlikely(code != SQLITE_OK)) { debug(D_ERROR, "error running sql statement: %s: %s", sql, sqlite3_errmsg(psync_db)); senddebug("error running sql statement: %s: %s", sql, sqlite3_errmsg(psync_db)); return NULL; } cnt = sqlite3_column_count(stmt); res = (psync_sql_res *)psync_malloc(sizeof(psync_sql_res) + cnt * sizeof(psync_variant)); res->stmt = stmt; res->sql = sql; res->column_count = cnt; res->locked = SQL_NO_LOCK; return res; } #if IS_DEBUG psync_sql_res *psync_sql_do_query_nolock(const char *sql, const char *file, unsigned line) { #else psync_sql_res *psync_sql_query_nolock(const char *sql) { #endif psync_sql_res *ret; #if IS_DEBUG if (!psync_sql_islocked()) { debug(D_BUG, "illegal use of psync_sql_query_nolock, can only be used while " "holding lock, invoked from %s:%u, sql: %s", file, line, sql); senddebug("illegal use of psync_sql_query_nolock, can only be used while " "holding lock, invoked from %s:%u, sql: %s", file, line, sql); abort(); } #endif ret = (psync_sql_res *)pcache_get(sql); if (ret) { // debug(D_NOTICE, "got query %s from cache", sql); ret->locked = SQL_NO_LOCK; ret->sql = sql; return ret; } else #if IS_DEBUG return psync_sql_do_query_nolock_nocache(sql, file, line); #else return psync_sql_query_nolock_nocache(sql); #endif } static void psync_sql_free_cache(void *ptr) { psync_sql_res *res = (psync_sql_res *)ptr; sqlite3_finalize(res->stmt); #if IS_DEBUG memset(res, 0xff, sizeof(psync_sql_res)); #endif psync_free(res); } static void psync_sql_res_unlock(psync_sql_res *res) { switch (res->locked) { case SQL_NO_LOCK: break; case SQL_READ_LOCK: psync_sql_rdunlock(); break; case SQL_WRITE_LOCK: psync_sql_unlock(); break; #if IS_DEBUG default: debug(D_ERROR, "unknown value for locked %d", res->locked); abort(); #endif } } void psync_sql_free_result(psync_sql_res *res) { int code = sqlite3_reset(res->stmt); psync_sql_res_unlock(res); #if IS_DEBUG memset(res->row, 0xff, res->column_count * sizeof(psync_variant)); #endif if (code == SQLITE_OK) pcache_add(res->sql, res, PSYNC_QUERY_CACHE_SEC, psync_sql_free_cache, PSYNC_QUERY_MAX_CNT); else psync_sql_free_cache(res); } void psync_sql_free_result_nocache(psync_sql_res *res) { sqlite3_finalize(res->stmt); psync_sql_res_unlock(res); #if IS_DEBUG memset(res, 0xff, sizeof(psync_sql_res)); #endif psync_free(res); } #if IS_DEBUG psync_sql_res *psync_sql_do_prep_statement_nocache(const char *sql, const char *file, unsigned line) { #else psync_sql_res *psync_sql_prep_statement_nocache(const char *sql) { #endif sqlite3_stmt *stmt; psync_sql_res *res; int code; psync_sql_check_query_plan(sql); #if IS_DEBUG psync_sql_do_lock(file, line); #else psync_sql_lock(); #endif code = sqlite3_prepare_v2(psync_db, sql, -1, &stmt, NULL); if (unlikely(code != SQLITE_OK)) { psync_sql_unlock(); #if IS_DEBUG debug(D_ERROR, "error running sql statement: %s: %s called from %s:%u", sql, sqlite3_errmsg(psync_db), file, line); senddebug("error running sql statement: %s: %s called from %s:%u", sql, sqlite3_errmsg(psync_db), file, line); #else debug(D_ERROR, "error running sql statement: %s: %s", sql, sqlite3_errmsg(psync_db)); senddebug("error running sql statement: %s: %s", sql, sqlite3_errmsg(psync_db)); #endif return NULL; } res = psync_new(psync_sql_res); res->stmt = stmt; res->sql = sql; #if IS_DEBUG res->column_count = 0; #endif res->locked = SQL_WRITE_LOCK; return res; } #if IS_DEBUG psync_sql_res *psync_sql_do_prep_statement(const char *sql, const char *file, unsigned line) { #else psync_sql_res *psync_sql_prep_statement(const char *sql) { #endif psync_sql_res *ret; ret = pcache_get(sql); if (ret) { // debug(D_NOTICE, "got statement %s from cache", sql); ret->locked = SQL_WRITE_LOCK; #if IS_DEBUG psync_sql_do_lock(file, line); #else psync_sql_lock(); #endif return ret; } else #if IS_DEBUG return psync_sql_do_prep_statement_nocache(sql, file, line); #else return psync_sql_prep_statement_nocache(sql); #endif } int psync_sql_reset(psync_sql_res *res) { int code = sqlite3_reset(res->stmt); if (unlikely(code != SQLITE_OK)) { debug(D_ERROR, "sqlite3_reset returned error: %s", sqlite3_errmsg(psync_db)); return -1; } else return 0; } int psync_sql_run(psync_sql_res *res) { int code = sqlite3_step(res->stmt); if (unlikely(code != SQLITE_DONE)) { debug(D_ERROR, "sqlite3_step returned error: %s: %s", sqlite3_errmsg(psync_db), res->sql); sendtdebug("sqlite3_step returned error (in_transaction=%d): %s: %s", in_transaction, sqlite3_errmsg(psync_db), res->sql); transaction_failed = 1; if (in_transaction) debug(D_BUG, "transaction query failed, this may lead to restarting " "transaction over and over"); return -1; } code = sqlite3_reset(res->stmt); if (unlikely(code != SQLITE_OK)) debug(D_ERROR, "sqlite3_reset returned error: %s", sqlite3_errmsg(psync_db)); return 0; } int psync_sql_run_free_nocache(psync_sql_res *res) { int code = sqlite3_step(res->stmt); if (unlikely(code != SQLITE_DONE)) { debug(D_ERROR, "sqlite3_step returned error: %s: %s", sqlite3_errmsg(psync_db), res->sql); sendtdebug("sqlite3_step returned error (in_transaction=%d): %s: %s", in_transaction, sqlite3_errmsg(psync_db), res->sql); code = -1; transaction_failed = 1; if (in_transaction) debug(D_BUG, "transaction query failed, this may lead to restarting " "transaction over and over"); } else code = 0; sqlite3_finalize(res->stmt); psync_sql_res_unlock(res); psync_free(res); return code; } int psync_sql_run_free(psync_sql_res *res) { int code = sqlite3_step(res->stmt); if (unlikely(code != SQLITE_DONE || (code = sqlite3_reset(res->stmt)) != SQLITE_OK)) { debug(D_ERROR, "sqlite3_step returned error: %s: %s", sqlite3_errmsg(psync_db), res->sql); sendtdebug("sqlite3_step returned error (in_transaction=%d): %s: %s", in_transaction, sqlite3_errmsg(psync_db), res->sql); sqlite3_finalize(res->stmt); transaction_failed = 1; if (in_transaction) debug(D_BUG, "transaction query failed, this may lead to restarting " "transaction over and over"); psync_sql_res_unlock(res); psync_free(res); return -1; } else { psync_sql_res_unlock(res); pcache_add(res->sql, res, PSYNC_QUERY_CACHE_SEC, psync_sql_free_cache, PSYNC_QUERY_MAX_CNT); return 0; } } void psync_sql_bind_int(psync_sql_res *res, int n, int64_t val) { int code = sqlite3_bind_int64(res->stmt, n, val); if (unlikely(code != SQLITE_OK)) debug(D_ERROR, "error binding value: %s", sqlite3_errmsg(psync_db)); } void psync_sql_bind_uint(psync_sql_res *res, int n, uint64_t val) { int code = sqlite3_bind_int64(res->stmt, n, val); if (unlikely(code != SQLITE_OK)) debug(D_ERROR, "error binding value: %s", sqlite3_errmsg(psync_db)); } void psync_sql_bind_double(psync_sql_res *res, int n, double val) { int code = sqlite3_bind_double(res->stmt, n, val); if (unlikely(code != SQLITE_OK)) debug(D_ERROR, "error binding value: %s", sqlite3_errmsg(psync_db)); } void psync_sql_bind_string(psync_sql_res *res, int n, const char *str) { int code = sqlite3_bind_text(res->stmt, n, str, -1, SQLITE_STATIC); if (unlikely(code != SQLITE_OK)) debug(D_ERROR, "error binding value: %s", sqlite3_errmsg(psync_db)); } void psync_sql_bind_lstring(psync_sql_res *res, int n, const char *str, size_t len) { int code = sqlite3_bind_text(res->stmt, n, str, len, SQLITE_STATIC); if (unlikely(code != SQLITE_OK)) debug(D_ERROR, "error binding value: %s", sqlite3_errmsg(psync_db)); } void psync_sql_bind_blob(psync_sql_res *res, int n, const char *str, size_t len) { int code = sqlite3_bind_blob(res->stmt, n, str, len, SQLITE_STATIC); if (unlikely(code != SQLITE_OK)) debug(D_ERROR, "error binding value: %s", sqlite3_errmsg(psync_db)); } void psync_sql_bind_null(psync_sql_res *res, int n) { int code = sqlite3_bind_null(res->stmt, n); if (unlikely(code != SQLITE_OK)) debug(D_ERROR, "error binding value: %s", sqlite3_errmsg(psync_db)); } psync_variant_row psync_sql_fetch_row(psync_sql_res *res) { int code, i; code = sqlite3_step(res->stmt); if (code == SQLITE_ROW) { for (i = 0; i < res->column_count; i++) { code = sqlite3_column_type(res->stmt, i); if (code == SQLITE_INTEGER) { res->row[i].type = PSYNC_TNUMBER; res->row[i].snum = sqlite3_column_int64(res->stmt, i); } else if (code == SQLITE_TEXT || code == SQLITE_BLOB) { res->row[i].type = PSYNC_TSTRING; res->row[i].length = sqlite3_column_bytes(res->stmt, i); res->row[i].str = (char *)sqlite3_column_text(res->stmt, i); } else if (code == SQLITE_FLOAT) { res->row[i].type = PSYNC_TREAL; res->row[i].real = sqlite3_column_double(res->stmt, i); } else res->row[i].type = PSYNC_TNULL; } return res->row; } else { if (unlikely(code != SQLITE_DONE)) debug(D_ERROR, "sqlite3_step returned error: %s", sqlite3_errmsg(psync_db)); return NULL; } } psync_str_row psync_sql_fetch_rowstr(psync_sql_res *res) { int code, i; const char **strs; code = sqlite3_step(res->stmt); if (code == SQLITE_ROW) { strs = (const char **)res->row; for (i = 0; i < res->column_count; i++) strs[i] = (const char *)sqlite3_column_text(res->stmt, i); return strs; } else { if (unlikely(code != SQLITE_DONE)) debug(D_ERROR, "sqlite3_step returned error: %s", sqlite3_errmsg(psync_db)); return NULL; } } const uint64_t *psync_sql_fetch_rowint(psync_sql_res *res) { int code, i; uint64_t *ret; code = sqlite3_step(res->stmt); if (code == SQLITE_ROW) { ret = (uint64_t *)res->row; for (i = 0; i < res->column_count; i++) ret[i] = sqlite3_column_int64(res->stmt, i); return ret; } else { if (unlikely(code != SQLITE_DONE)) debug(D_ERROR, "sqlite3_step returned error: %s", sqlite3_errmsg(psync_db)); return NULL; } } psync_full_result_int *psync_sql_fetchall_int(psync_sql_res *res) { uint64_t *data; psync_full_result_int *ret; unsigned long rows, cols, off, i, all; int code; cols = res->column_count; rows = 0; off = 0; all = 0; data = NULL; while ((code = sqlite3_step(res->stmt)) == SQLITE_ROW) { if (rows >= all) { all = 10 + all * 2; data = (uint64_t *)psync_realloc(data, sizeof(uint64_t) * cols * all); } for (i = 0; i < cols; i++) data[off + i] = sqlite3_column_int64(res->stmt, i); off += cols; rows++; } if (unlikely(code != SQLITE_DONE)) debug(D_ERROR, "sqlite3_step returned error: %s", sqlite3_errmsg(psync_db)); psync_sql_free_result(res); ret = (psync_full_result_int *)psync_malloc( offsetof(psync_full_result_int, data) + sizeof(uint64_t) * off); ret->rows = rows; ret->cols = cols; memcpy(ret->data, data, sizeof(uint64_t) * off); psync_free(data); return ret; } uint32_t psync_sql_affected_rows() { return sqlite3_changes(psync_db); } uint64_t psync_sql_insertid() { return sqlite3_last_insert_rowid(psync_db); } int psync_rename_conflicted_file(const char *path) { char *npath; size_t plen, dotidx; struct stat st; long num, l; plen = strlen(path); dotidx = plen; while (dotidx && path[dotidx] != '.') dotidx--; if (!dotidx) dotidx = plen; npath = (char *)psync_malloc(plen + 32); memcpy(npath, path, dotidx); num = 0; while (1) { if (num) l = psync_slprintf(npath + dotidx, 32, " (conflicted %ld)", num); else { l = 13; memcpy(npath + dotidx, " (conflicted)", l); } memcpy(npath + dotidx + l, path + dotidx, plen - dotidx + 1); if (stat(npath, &st)) { debug(D_NOTICE, "renaming conflict %s to %s", path, npath); l = pfile_rename(path, npath); psync_free(npath); return l; } num++; } } void psync_libs_init() { unsigned long i; for (i = 0; i < 256; i++) normalize_table[i] = i; normalize_table[':'] = '_'; normalize_table['/'] = '_'; normalize_table['\\'] = '_'; } static void run_after_sec(psync_timer_t timer, void *ptr) { struct run_after_ptr *fp = (struct run_after_ptr *)ptr; ptimer_stop(timer); fp->run(fp->ptr); psync_free(fp); } void psync_run_after_sec(psync_run_after_t run, void *ptr, uint32_t seconds) { struct run_after_ptr *fp; fp = psync_new(struct run_after_ptr); fp->run = run; fp->ptr = ptr; ptimer_register(run_after_sec, seconds, fp); } static void free_after_sec(psync_timer_t timer, void *ptr) { ptimer_stop(timer); psync_free(ptr); } void psync_free_after_sec(void *ptr, uint32_t seconds) { ptimer_register(free_after_sec, seconds, ptr); } int psync_match_pattern(const char *name, const char *pattern, size_t plen) { size_t i; for (i = 0; i < plen; i++) { if (pattern[i] == '*') { name += i; while (1) { if (++i == plen) return 1; switch (pattern[i]) { case '?': if (!*name++) return 0; case '*': break; default: name = strchr(name, pattern[i]); pattern += i + 1; plen -= i + 1; while (name) { name++; if (psync_match_pattern(name, pattern, plen)) return 1; name = strchr(name, *(pattern - 1)); } return 0; } } } else if (!name[i] || (pattern[i] != name[i] && pattern[i] != '?')) return 0; } return name[i] == 0; } uint64_t psync_ato64(const char *str) { uint64_t n = 0; while (*str >= '0' && *str <= '9') n = n * 10 + (*str++) - '0'; return n; } uint32_t psync_ato32(const char *str) { uint32_t n = 0; while (*str >= '0' && *str <= '9') n = n * 10 + (*str++) - '0'; return n; } typedef struct { psync_list list; unsigned long used; char elements[]; } psync_list_element_list; typedef struct { psync_list list; char *next; char *end; } psync_list_string_list; typedef struct { psync_list list; unsigned long used; uint32_t numbers[1000]; } psync_list_num_list; struct psync_list_builder_t_ { size_t element_size; size_t elements_offset; size_t elements_per_list; size_t stringalloc; uint64_t cnt; psync_list element_list; psync_list_element_list *last_elements; psync_list string_list; psync_list_string_list *last_strings; psync_list number_list; psync_list_num_list *last_numbers; unsigned long popoff; char *current_element; uint32_t *cstrcnt; }; psync_list_builder_t *psync_list_builder_create(size_t element_size, size_t offset) { psync_list_builder_t *builder; builder = psync_new(psync_list_builder_t); builder->element_size = element_size; builder->elements_offset = offset; if (element_size <= 200) builder->elements_per_list = 40; else builder->elements_per_list = 12; builder->cnt = 0; builder->stringalloc = 0; psync_list_init(&builder->element_list); builder->last_elements = NULL; psync_list_init(&builder->string_list); builder->last_strings = NULL; psync_list_init(&builder->number_list); builder->last_numbers = NULL; return builder; } static uint32_t *psync_list_bulder_push_num(psync_list_builder_t *builder) { if (!builder->last_numbers || builder->last_numbers->used >= sizeof(builder->last_numbers->numbers) / sizeof(uint32_t)) { psync_list_num_list *l = psync_new(psync_list_num_list); l->used = 0; builder->last_numbers = l; psync_list_add_tail(&builder->number_list, &l->list); } return &builder->last_numbers->numbers[builder->last_numbers->used++]; } static uint32_t psync_list_bulder_pop_num(psync_list_builder_t *builder) { uint32_t ret; ret = builder->last_numbers->numbers[builder->popoff++]; if (builder->popoff >= builder->last_numbers->used) { builder->last_numbers = psync_list_element(builder->last_numbers->list.next, psync_list_num_list, list); builder->popoff = 0; } return ret; } void psync_list_bulder_add_sql(psync_list_builder_t *builder, psync_sql_res *res, psync_list_builder_sql_callback callback) { psync_variant_row row; while ((row = psync_sql_fetch_row(res))) { if (!builder->last_elements || builder->last_elements->used >= builder->elements_per_list) { builder->last_elements = (psync_list_element_list *)psync_malloc( offsetof(psync_list_element_list, elements) + builder->element_size * builder->elements_per_list); psync_list_add_tail(&builder->element_list, &builder->last_elements->list); builder->last_elements->used = 0; } builder->current_element = builder->last_elements->elements + builder->last_elements->used * builder->element_size; builder->cstrcnt = psync_list_bulder_push_num(builder); *builder->cstrcnt = 0; while (callback(builder, builder->current_element, row)) { row = psync_sql_fetch_row(res); if (!row) break; *builder->cstrcnt = 0; } builder->last_elements->used++; builder->cnt++; } psync_sql_free_result(res); } void *psync_list_bulder_add_element(psync_list_builder_t *builder) { if (!builder->last_elements || builder->last_elements->used >= builder->elements_per_list) { builder->last_elements = (psync_list_element_list *)psync_malloc( offsetof(psync_list_element_list, elements) + builder->element_size * builder->elements_per_list); psync_list_add_tail(&builder->element_list, &builder->last_elements->list); builder->last_elements->used = 0; } builder->current_element = builder->last_elements->elements + builder->last_elements->used * builder->element_size; builder->cstrcnt = psync_list_bulder_push_num(builder); *builder->cstrcnt = 0; builder->last_elements->used++; builder->cnt++; return builder->current_element; } void psync_list_add_lstring_offset(psync_list_builder_t *builder, size_t offset, size_t length) { char **str, *s; psync_list_string_list *l; length++; str = (char **)(builder->current_element + offset); builder->stringalloc += length; if (unlikely(length > 2000)) { l = (psync_list_string_list *)psync_malloc(sizeof(psync_list_string_list) + length); s = (char *)(l + 1); psync_list_add_tail(&builder->string_list, &l->list); } else if (!builder->last_strings || builder->last_strings->next + length > builder->last_strings->end) { l = (psync_list_string_list *)psync_malloc(sizeof(psync_list_string_list) + 4000); s = (char *)(l + 1); l->next = s + length; l->end = s + 4000; psync_list_add_tail(&builder->string_list, &l->list); builder->last_strings = l; } else { s = builder->last_strings->next; builder->last_strings->next += length; } memcpy(s, *str, length); *str = s; *(psync_list_bulder_push_num(builder)) = offset; *(psync_list_bulder_push_num(builder)) = length; (*builder->cstrcnt)++; } void psync_list_add_string_offset(psync_list_builder_t *builder, size_t offset) { psync_list_add_lstring_offset( builder, offset, strlen(*((char **)(builder->current_element + offset)))); } void *psync_list_builder_finalize(psync_list_builder_t *builder) { char *ret, *elem, *str; char **pstr; psync_list_element_list *el; unsigned long i; uint32_t j, scnt, offset, length; size_t sz; sz = builder->elements_offset + builder->element_size * builder->cnt + builder->stringalloc; debug(D_NOTICE, "allocating %lu bytes, %lu of which for strings", (unsigned long)sz, (unsigned long)builder->stringalloc); ret = psync_new_cnt(char, sz); if (builder->elements_offset <= sizeof(builder->cnt)) memcpy(ret, &builder->cnt, builder->elements_offset); else memcpy(ret, &builder->cnt, sizeof(builder->cnt)); elem = ret + builder->elements_offset; str = elem + builder->element_size * builder->cnt; builder->last_numbers = psync_list_element(builder->number_list.next, psync_list_num_list, list); builder->popoff = 0; psync_list_for_each_element(el, &builder->element_list, psync_list_element_list, list) { for (i = 0; i < el->used; i++) { memcpy(elem, el->elements + (i * builder->element_size), builder->element_size); scnt = psync_list_bulder_pop_num(builder); for (j = 0; j < scnt; j++) { offset = psync_list_bulder_pop_num(builder); length = psync_list_bulder_pop_num(builder); pstr = (char **)(elem + offset); memcpy(str, *pstr, length); *pstr = str; str += length; } elem += builder->element_size; } } psync_list_for_each_element_call(&builder->element_list, psync_list_element_list, list, psync_free); psync_list_for_each_element_call(&builder->string_list, psync_list_string_list, list, psync_free); psync_list_for_each_element_call(&builder->number_list, psync_list_num_list, list, psync_free); psync_free(builder); return ret; } #define PSYNC_TASK_STATUS_RUNNING 0 #define PSYNC_TASK_STATUS_READY 1 #define PSYNC_TASK_STATUS_DONE 2 #define PSYNC_TASK_STATUS_RETURNED 3 struct psync_task_t_ { psync_task_callback_t callback; void *param; pthread_cond_t cond; int id; int status; }; // #define PSYNC_WAIT_ANYBODY -1 // unused, but may be important later #define PSYNC_WAIT_NOBODY -2 #define PSYNC_WAIT_FREED -3 struct psync_task_manager_t_ { pthread_mutex_t mutex; int taskcnt; int refcnt; int waitfor; struct psync_task_t_ tasks[]; }; static void psync_task_destroy(psync_task_manager_t tm) { int i; for (i = 0; i < tm->taskcnt; i++) pthread_cond_destroy(&tm->tasks[i].cond); pthread_mutex_destroy(&tm->mutex); psync_free(tm); } static void psync_task_dec_refcnt(psync_task_manager_t tm) { int refcnt; pthread_mutex_lock(&tm->mutex); refcnt = --tm->refcnt; pthread_mutex_unlock(&tm->mutex); if (!refcnt) psync_task_destroy(tm); } static psync_task_manager_t psync_get_manager_of_task(struct psync_task_t_ *t) { return (psync_task_manager_t)(((char *)(t - t->id)) - offsetof(struct psync_task_manager_t_, tasks)); } static void psync_task_entry(void *ptr) { struct psync_task_t_ *t; t = (struct psync_task_t_ *)ptr; t->callback(ptr, t->param); psync_task_dec_refcnt(psync_get_manager_of_task(t)); } psync_task_manager_t psync_task_run_tasks(psync_task_callback_t const *callbacks, void *const *params, int cnt) { psync_task_manager_t ret; struct psync_task_t_ *t; int i; ret = (psync_task_manager_t)psync_malloc( offsetof(struct psync_task_manager_t_, tasks) + sizeof(struct psync_task_t_) * cnt); pthread_mutex_init(&ret->mutex, NULL); ret->taskcnt = cnt; ret->refcnt = cnt + 1; ret->waitfor = PSYNC_WAIT_NOBODY; for (i = 0; i < cnt; i++) { t = &ret->tasks[i]; t->callback = callbacks[i]; t->param = params[i]; pthread_cond_init(&t->cond, NULL); t->id = i; t->status = PSYNC_TASK_STATUS_RUNNING; prun_thread1("task", psync_task_entry, t); } return ret; } void *psync_task_papi_result(psync_task_manager_t tm, int id) { void *ret; pthread_mutex_lock(&tm->mutex); if (tm->tasks[id].status == PSYNC_TASK_STATUS_RUNNING) { do { tm->waitfor = id; pthread_cond_wait(&tm->tasks[id].cond, &tm->mutex); tm->waitfor = PSYNC_WAIT_NOBODY; } while (tm->tasks[id].status == PSYNC_TASK_STATUS_RUNNING); ret = tm->tasks[id].param; tm->tasks[id].status = PSYNC_TASK_STATUS_DONE; } else if (tm->tasks[id].status == PSYNC_TASK_STATUS_READY) { ret = tm->tasks[id].param; tm->tasks[id].status = PSYNC_TASK_STATUS_DONE; pthread_cond_signal(&tm->tasks[id].cond); } else { debug(D_BUG, "invalid status %d of task id %d", (int)tm->tasks[id].status, id); ret = NULL; } pthread_mutex_unlock(&tm->mutex); return ret; } void psync_task_free(psync_task_manager_t tm) { if (tm->refcnt == 1) psync_task_destroy(tm); else { int refcnt, i; pthread_mutex_lock(&tm->mutex); tm->waitfor = PSYNC_WAIT_FREED; for (i = 0; i < tm->taskcnt; i++) if (tm->tasks[i].status == PSYNC_TASK_STATUS_READY) { tm->tasks[i].status = PSYNC_TASK_STATUS_RETURNED; pthread_cond_signal(&tm->tasks[i].cond); } refcnt = --tm->refcnt; pthread_mutex_unlock(&tm->mutex); if (!refcnt) psync_task_destroy(tm); } } int psync_task_complete(void *h, void *data) { psync_task_manager_t tm; struct psync_task_t_ *t; int ret; t = (struct psync_task_t_ *)h; tm = psync_get_manager_of_task(t); pthread_mutex_lock(&tm->mutex); if (tm->waitfor == t->id) { t->param = data; t->status = PSYNC_TASK_STATUS_READY; pthread_cond_signal(&t->cond); ret = 0; } else if (tm->waitfor == PSYNC_WAIT_NOBODY || tm->waitfor >= 0) { t->param = data; t->status = PSYNC_TASK_STATUS_READY; do { pthread_cond_wait(&t->cond, &tm->mutex); } while (t->status == PSYNC_TASK_STATUS_READY); if (t->status == PSYNC_TASK_STATUS_RETURNED) ret = -1; else ret = 0; } else if (tm->waitfor == PSYNC_WAIT_FREED) ret = -1; else { debug(D_BUG, "invalid waitfor value %d", tm->waitfor); ret = -1; } pthread_mutex_unlock(&tm->mutex); return ret; } #define rot(x, k) (((x) << (k)) | ((x) >> (32 - (k)))) static uint32_t pq_rnd() { static uint32_t a = 0x95ae3d25, b = 0xe225d755, c = 0xc63a2ae7, d = 0xe4556265; uint32_t e = a - rot(b, 27); a = b ^ rot(c, 17); b = c + d; c = d + e; d = e + a; return d; } #define QSORT_TRESH 8 #define QSORT_MTR 64 #define QSORT_REC_M (16 * 1024) static inline void sw2(unsigned char **a, unsigned char **b) { unsigned char *tmp = *a; *a = *b; *b = tmp; } static unsigned char *med5(unsigned char *a, unsigned char *b, unsigned char *c, unsigned char *d, unsigned char *e, int (*compar)(const void *, const void *)) { if (compar(b, a) < 0) sw2(&a, &b); if (compar(d, c) < 0) sw2(&c, &d); if (compar(a, c) < 0) { a = e; if (compar(b, a) < 0) sw2(&a, &b); } else { c = e; if (compar(d, c) < 0) sw2(&c, &d); } if (compar(a, c) < 0) a = b; else c = d; if (compar(a, c) < 0) return a; else return c; } unsigned char *pq_choose_part(unsigned char *base, size_t cnt, size_t size, int (*compar)(const void *, const void *)) { if (cnt >= QSORT_REC_M) { cnt /= 5; return med5(pq_choose_part(base, cnt, size, compar), pq_choose_part(base + cnt * size, cnt, size, compar), pq_choose_part(base + cnt * size * 2, cnt, size, compar), pq_choose_part(base + cnt * size * 3, cnt, size, compar), pq_choose_part(base + cnt * size * 4, cnt, size, compar), compar); } else { return med5(base + (pq_rnd() % cnt) * size, base + (pq_rnd() % cnt) * size, base + (pq_rnd() % cnt) * size, base + (pq_rnd() % cnt) * size, base + (pq_rnd() % cnt) * size, compar); } } static inline void pqsswap(unsigned char *a, unsigned char *b, size_t size) { unsigned char tmp; do { tmp = *a; *a++ = *b; *b++ = tmp; } while (--size); } static inline void pqsswap32(unsigned char *a, unsigned char *b, size_t size) { uint32_t tmp; do { tmp = *(uint32_t *)a; *(uint32_t *)a = *(uint32_t *)b; *(uint32_t *)b = tmp; a += sizeof(uint32_t); b += sizeof(uint32_t); } while (--size); } typedef struct { unsigned char *lo; unsigned char *hi; } psq_stack_t; void psync_pqsort(void *base, size_t cnt, size_t sort_first, size_t size, int (*compar)(const void *, const void *)) { psq_stack_t stack[sizeof(size_t) * 8]; psq_stack_t *top; unsigned char *lo, *hi, *mid, *l, *r, *sf; size_t tresh, n, u32size; lo = NULL; hi = NULL; mid = NULL; l = NULL; r = NULL; sf = NULL; tresh = QSORT_TRESH * size; sf = (unsigned char *)base + sort_first * size; if (size % sizeof(uint32_t) == 0 && (uintptr_t)base % sizeof(uint32_t) == 0) u32size = size / sizeof(uint32_t); else u32size = 0; if (cnt > QSORT_TRESH) { top = stack + 1; lo = (unsigned char *)base; hi = lo + (cnt - 1) * size; do { n = (hi - lo) / size; if (n <= QSORT_MTR) { mid = lo + (n >> 1) * size; if (compar(mid, lo) < 0) pqsswap(mid, lo, size); if (compar(hi, mid) < 0) { pqsswap(mid, hi, size); if (compar(mid, lo) < 0) pqsswap(mid, lo, size); } // we already sure *hi and *lo are good, so they will be skipped without // checking l = lo; r = hi; } else { mid = pq_choose_part(lo, n, size, compar); l = lo - size; r = hi + size; } if (u32size) { do { do { l += size; } while (compar(l, mid) < 0); do { r -= size; } while (compar(mid, r) < 0); if (l >= r) break; pqsswap32(l, r, u32size); if (mid == l) { mid = r; r += size; } else if (mid == r) { mid = l; l -= size; } } while (1); } else { do { do { l += size; } while (compar(l, mid) < 0); do { r -= size; } while (compar(mid, r) < 0); if (l >= r) break; pqsswap(l, r, size); if (mid == l) { mid = r; r += size; } else if (mid == r) { mid = l; l -= size; } } while (1); } if (hi - mid <= tresh || mid >= sf) { if (mid - lo <= tresh) { top--; lo = top->lo; hi = top->hi; } else { hi = mid - size; } } else if (mid - lo <= tresh) { lo = mid + size; } else if (hi - mid < mid - lo) { top->lo = lo; top->hi = mid - size; top++; lo = mid + size; } else { top->lo = mid + size; top->hi = hi; top++; hi = mid - size; } } while (top != stack); } else if (cnt <= 1) { return; } lo = (unsigned char *)base; hi = lo + (cnt - 1) * size; sf += size * QSORT_TRESH; if (sf < hi) hi = sf; r = lo + QSORT_TRESH * size + 4; if (r > hi) r = hi; for (l = lo + size; l <= r; l += size) if (compar(l, lo) < 0) lo = l; pqsswap((unsigned char *)base, lo, size); l = (unsigned char *)base + size; hi -= size; while (l <= hi) { lo = l; l += size; while (compar(l, lo) < 0) lo -= size; lo += size; if (lo != l) { unsigned char *t = l + size; if (u32size) { while ((t -= sizeof(uint32_t)) >= l) { uint32_t tmp = *(uint32_t *)t; for (r = mid = t; (mid -= size) >= lo; r = mid) *(uint32_t *)r = *(uint32_t *)mid; *(uint32_t *)r = tmp; } } else { while (--t >= l) { unsigned char tmp = *t; for (r = mid = t; (mid -= size) >= lo; r = mid) *r = *mid; *r = tmp; } } } } } void psync_qpartition(void *base, size_t cnt, size_t sort_first, size_t size, int (*compar)(const void *, const void *)) { unsigned char *lo, *hi, *mid, *l, *r, *sf; size_t n, u32size; sf = (unsigned char *)base + sort_first * size; if (size % sizeof(uint32_t) == 0 && (uintptr_t)base % sizeof(uint32_t) == 0) u32size = size / sizeof(uint32_t); else u32size = 0; if (cnt <= 1) // otherwise cnt-1 will underflow return; lo = (unsigned char *)base; hi = lo + (cnt - 1) * size; while (1) { n = (hi - lo) / size; if (n <= QSORT_MTR) { mid = lo + (n >> 1) * size; if (compar(mid, lo) < 0) pqsswap(mid, lo, size); if (compar(hi, mid) < 0) { pqsswap(mid, hi, size); if (compar(mid, lo) < 0) pqsswap(mid, lo, size); } // we already sure *hi and *lo are good, so they will be skipped without // checking if (n <= 2) // when n is 2, we have 3 elements return; l = lo; r = hi; } else { mid = pq_choose_part(lo, n, size, compar); l = lo - size; r = hi + size; } if (u32size) { do { do { l += size; } while (compar(l, mid) < 0); do { r -= size; } while (compar(mid, r) < 0); if (l >= r) break; pqsswap32(l, r, u32size); if (mid == l) { mid = r; r += size; } else if (mid == r) { mid = l; l -= size; } } while (1); } else { do { do { l += size; } while (compar(l, mid) < 0); do { r -= size; } while (compar(mid, r) < 0); if (l >= r) break; pqsswap(l, r, size); if (mid == l) { mid = r; r += size; } else if (mid == r) { mid = l; l -= size; } } while (1); } if (mid < sf) lo = mid + size; else if (mid > sf) hi = mid - size; else return; } } void psync_try_free_memory() { sqlite3_db_release_memory(psync_db); pcache_clean(); } static void time_format(time_t tm, unsigned long ns, char *result) { static const char month_names[12][4] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; static const char day_names[7][4] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; struct tm dt; unsigned long y; ns /= 1000000; gmtime_r(&tm, &dt); memcpy(result, day_names[dt.tm_wday], 3); result += 3; *result++ = ','; *result++ = ' '; *result++ = dt.tm_mday / 10 + '0'; *result++ = dt.tm_mday % 10 + '0'; *result++ = ' '; memcpy(result, month_names[dt.tm_mon], 3); result += 3; *result++ = ' '; y = dt.tm_year + 1900; *result++ = '0' + y / 1000; y = y % 1000; *result++ = '0' + y / 100; y = y % 100; *result++ = '0' + y / 10; y = y % 10; *result++ = '0' + y; *result++ = ' '; *result++ = dt.tm_hour / 10 + '0'; *result++ = dt.tm_hour % 10 + '0'; *result++ = ':'; *result++ = dt.tm_min / 10 + '0'; *result++ = dt.tm_min % 10 + '0'; *result++ = ':'; *result++ = dt.tm_sec / 10 + '0'; *result++ = dt.tm_sec % 10 + '0'; *result++ = '.'; *result++ = ns / 100 + '0'; *result++ = (ns / 10) % 10 + '0'; *result++ = ns % 10 + '0'; memcpy(result, " +0000", 7); // copies the null byte } char *psync_debug_path() { char *home = ppath_home(); if (!home) { return NULL; } const char *subdir = "/.pcloud/debug.log"; size_t len = strlen(home) + strlen(subdir) + 1; char *sockpath = (char *)malloc(len); if (!sockpath) { return NULL; } snprintf(sockpath, len, "%s%s", home, subdir); return sockpath; } int psync_debug(const char *file, const char *function, int unsigned line, int unsigned level, const char *fmt, ...) { if (!IS_DEBUG) return 1; static const struct { unsigned long level; const char *name; } debug_levels[] = DEBUG_LEVELS; static FILE *log = NULL; struct timespec ts; char dttime[36], format[512]; va_list ap; const char *errname; unsigned long i; unsigned int u; pthread_t threadid; errname = "BAD_ERROR_CODE"; for (i = 0; i < ARRAY_SIZE(debug_levels); i++) if (debug_levels[i].level == level) { errname = debug_levels[i].name; break; } if (unlikely(!log)) { char *path = psync_debug_path(); log = fopen(path, "a+"); free(path); if (!log) return 1; } clock_gettime(CLOCK_REALTIME, &ts); time_format(ts.tv_sec, ts.tv_nsec, dttime); threadid = pthread_self(); memcpy(&u, &threadid, sizeof(u)); snprintf(format, sizeof(format), "%s %u %s %s: %s:%u (function %s): %s\n", dttime, u, psync_thread_name, errname, file, line, function, fmt); format[sizeof(format) - 1] = 0; va_start(ap, fmt); vfprintf(log, format, ap); va_end(ap); fflush(log); return 1; } static const char *PSYNC_CONST get_type_name(uint32_t t) { if (unlikely(t >= ARRAY_SIZE(psync_typenames))) t = 0; return psync_typenames[t]; } uint64_t psync_err_number_expected(const char *file, const char *function, int unsigned line, const psync_variant *v) { if (D_CRITICAL <= DEBUG_LEVEL) psync_debug(file, function, line, D_CRITICAL, "type error, wanted %s got %s", get_type_name(PSYNC_TNUMBER), get_type_name(v->type)); return 0; } const char *psync_err_string_expected(const char *file, const char *function, int unsigned line, const psync_variant *v) { if (D_CRITICAL <= DEBUG_LEVEL) psync_debug(file, function, line, D_CRITICAL, "type error, wanted %s got %s", get_type_name(PSYNC_TSTRING), get_type_name(v->type)); return ""; } const char *psync_lstring_expected(const char *file, const char *function, int unsigned line, const psync_variant *v, size_t *len) { if (likely(v->type == PSYNC_TSTRING)) { *len = v->length; return v->str; } else { if (D_CRITICAL <= DEBUG_LEVEL) psync_debug(file, function, line, D_CRITICAL, "type error, wanted %s got %s", get_type_name(PSYNC_TSTRING), get_type_name(v->type)); *len = 0; return ""; } } double psync_err_real_expected(const char *file, const char *function, int unsigned line, const psync_variant *v) { if (D_CRITICAL <= DEBUG_LEVEL) psync_debug(file, function, line, D_CRITICAL, "type error, wanted %s got %s", get_type_name(PSYNC_TREAL), get_type_name(v->type)); return 0.0; }