pcloudcc-lneely/pclsync/ppathstatus.c

1481 lines
49 KiB
C

/*
Copyright (c) 2016 Anton Titov.
Copyright (c) 2016 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 <mbedtls/ctr_drbg.h>
#include <mbedtls/debug.h>
#include <mbedtls/entropy.h>
#include <mbedtls/pkcs5.h>
#include <mbedtls/ssl.h>
#include <pthread.h>
#include "pcryptofolder.h"
#include "pfoldersync.h"
#include "pfs.h"
#include "plibs.h"
#include "plist.h"
#include "ppathstatus.h"
#include "pstatus.h"
#include "ptask.h"
#include "ptree.h"
#include <string.h>
#define PATH_CACHE_SIZE 512
#define PATH_HASH_SIZE 512
#define PARENT_CACHE_SIZE 128
#define PARENT_HASH_SIZE 128
#define SYNC_PARENT_CACHE_SIZE 64
#define SYNC_PARENT_HASH_SIZE 64
#define ENTRY_FLAG_FOLDER 1
#define ENTRY_FLAG_ENCRYPTED 2
#define ENTRY_FLAG_PROG 4
typedef struct {
uint32_t hash[4];
psync_list list_hash;
psync_list list_lru;
uint64_t itemid;
uint64_t flags;
} path_cache_entry_t;
typedef struct {
psync_folderid_t folderid;
psync_folderid_t parentfolderid;
psync_list list_hash;
psync_list list_lru;
} parent_cache_entry_t;
typedef struct _folder_tasks_t {
psync_tree tree;
psync_folderid_t folderid;
uint32_t child_task_cnt;
uint32_t own_tasks; // this is not a count (just 0 or 1), so can be changed to
// a "flags" with multiple flags if needed
} folder_tasks_t;
typedef struct {
psync_tree tree;
psync_tree *folder_tasks;
psync_syncid_t syncid;
psync_list parent_cache_lru;
psync_list cache_free;
psync_list parent_cache_hash[SYNC_PARENT_HASH_SIZE];
parent_cache_entry_t parent_cache_entries[SYNC_PARENT_CACHE_SIZE];
} sync_data_t;
typedef struct {
char *path;
size_t path_len;
psync_folderid_t folderid;
psync_syncid_t syncid;
uint32_t flags;
} path_sync_list_entry_t;
typedef struct {
size_t sync_cnt;
path_sync_list_entry_t syncs[];
} path_sync_list_t;
typedef struct {
psync_folderid_t folderid;
psync_folderid_t old_parent_folderid;
psync_folderid_t new_parent_folderid;
} folder_moved_params_t;
typedef struct {
psync_folderid_t folderid;
psync_folderid_t old_parent_folderid;
psync_folderid_t new_parent_folderid;
psync_syncid_t old_syncid;
psync_syncid_t new_syncid;
} sync_folder_moved_params_t;
static char *drive_path = NULL;
static size_t drive_path_len;
static path_sync_list_t *syncs = NULL;
static psync_tree *sync_data = PSYNC_TREE_EMPTY;
static uint64_t drv_hash_seed;
static uint64_t sync_hash_seed;
static psync_list cache_free;
static psync_list path_cache_lru;
static psync_list path_cache_hash[PATH_HASH_SIZE];
static path_cache_entry_t path_cache_entries[PATH_CACHE_SIZE];
static psync_list parent_cache_lru;
static psync_list parent_cache_hash[PARENT_HASH_SIZE];
static parent_cache_entry_t parent_cache_entries[PARENT_CACHE_SIZE];
static psync_tree *folder_tasks = PSYNC_TREE_EMPTY;
static void sync_data_free(sync_data_t *sd);
static void load_sync_tasks();
static inline int psync_crypto_is_error(const void *ptr) {
return (uintptr_t)ptr <= PSYNC_CRYPTO_MAX_ERROR;
}
static inline int psync_crypto_to_error(const void *ptr) {
return -((int)(uintptr_t)ptr);
}
void ppathstatus_init() {
size_t i;
psync_sql_lock();
psync_list_init(&cache_free);
psync_list_init(&path_cache_lru);
psync_list_init(&parent_cache_lru);
for (i = 0; i < PATH_HASH_SIZE; i++)
psync_list_init(&path_cache_hash[i]);
for (i = 0; i < PATH_CACHE_SIZE; i++) {
psync_list_add_tail(&path_cache_lru, &path_cache_entries[i].list_lru);
psync_list_add_tail(&cache_free, &path_cache_entries[i].list_hash);
}
for (i = 0; i < PARENT_HASH_SIZE; i++)
psync_list_init(&parent_cache_hash[i]);
for (i = 0; i < PARENT_CACHE_SIZE; i++) {
psync_list_add_tail(&parent_cache_lru, &parent_cache_entries[i].list_lru);
psync_list_add_tail(&cache_free, &parent_cache_entries[i].list_hash);
}
ptree_for_each_element_call_safe(folder_tasks, folder_tasks_t, tree,
psync_free);
folder_tasks = PSYNC_TREE_EMPTY;
ptree_for_each_element_call_safe(sync_data, sync_data_t, tree,
sync_data_free);
sync_data = PSYNC_TREE_EMPTY;
ppathstatus_reload_syncs();
ppathstatus_clear_cache();
load_sync_tasks();
psync_sql_unlock();
}
static int create_sync_list_entry(psync_list_builder_t *builder, void *element,
psync_variant_row row) {
path_sync_list_entry_t *entry;
const char *str;
size_t len;
entry = (path_sync_list_entry_t *)element;
str = psync_get_lstring(row[0], &len);
entry->path = (char *)str;
entry->path_len = len;
psync_list_add_lstring_offset(builder, offsetof(path_sync_list_entry_t, path),
len);
entry->syncid = psync_get_number(row[1]);
entry->folderid = psync_get_number(row[2]);
entry->flags = 0;
return 0;
}
void ppathstatus_reload_syncs() {
psync_list_builder_t *builder;
psync_sql_res *res;
path_sync_list_t *old;
psync_sql_lock();
builder = psync_list_builder_create(sizeof(path_sync_list_entry_t),
offsetof(path_sync_list_t, syncs));
res = psync_sql_query_nolock(
"SELECT sf.localpath, sf.id, sf.folderid, f.flags FROM syncfolder sf, "
"folder f WHERE sf.folderid=f.id");
psync_list_bulder_add_sql(builder, res, create_sync_list_entry);
old = syncs;
syncs = (path_sync_list_t *)psync_list_builder_finalize(builder);
ppathstatus_clear_sync_cache();
psync_sql_unlock();
if (old)
psync_free_after_sec(old, 30);
}
void ppathstatus_clear_cache() {
uint64_t ndrv_hash_seed, nsync_hash_seed;
do {
pssl_rand_strong((unsigned char *)&ndrv_hash_seed,
sizeof(ndrv_hash_seed));
} while (unlikely(!ndrv_hash_seed));
nsync_hash_seed = 0x49916a8e891d1dafULL * ndrv_hash_seed;
psync_sql_lock();
drv_hash_seed = ndrv_hash_seed;
sync_hash_seed = nsync_hash_seed;
psync_sql_unlock();
}
void ppathstatus_clear_sync_cache() {
uint64_t nsync_hash_seed;
do {
pssl_rand_strong((unsigned char *)&nsync_hash_seed,
sizeof(nsync_hash_seed));
} while (unlikely(nsync_hash_seed == drv_hash_seed));
psync_sql_lock();
sync_hash_seed = nsync_hash_seed;
psync_sql_unlock();
}
static uint32_t hash_folderid(psync_folderid_t folderid) {
uint32_t h;
h = (uint32_t)folderid;
h *= 0x4c8cbb55;
h ^= h >> 19;
return h;
}
void ppathstatus_del_from_parent_cache(psync_folderid_t folderid) {
parent_cache_entry_t *p;
uint32_t h;
h = hash_folderid(folderid) % PARENT_HASH_SIZE;
psync_list_for_each_element(p, &parent_cache_hash[h], parent_cache_entry_t,
list_hash) if (p->folderid == folderid) {
psync_list_del(&p->list_lru);
psync_list_add_head(&parent_cache_lru, &p->list_lru);
psync_list_del(&p->list_hash);
psync_list_add_tail(&cache_free, &p->list_hash);
break;
}
}
static folder_tasks_t *get_folder_tasks(psync_folderid_t folderid, int create) {
psync_tree *tr, **atr;
folder_tasks_t *ft;
tr = folder_tasks;
atr = &folder_tasks;
while (tr) {
ft = ptree_element(tr, folder_tasks_t, tree);
if (folderid < ft->folderid) {
if (tr->left) {
tr = tr->left;
} else {
atr = &tr->left;
break;
}
} else if (folderid > ft->folderid) {
if (tr->right) {
tr = tr->right;
} else {
atr = &tr->right;
break;
}
} else {
return ft;
}
}
if (create) {
ft = psync_new(folder_tasks_t);
*atr = &ft->tree;
ptree_added_at(&folder_tasks, tr, &ft->tree);
ft->folderid = folderid;
ft->child_task_cnt = 0;
ft->own_tasks = 0;
debug(D_NOTICE, "marking folderid %lu as having (sub)tasks",
(unsigned long)folderid);
return ft;
} else {
return NULL;
}
}
static void free_folder_tasks(folder_tasks_t *ft) {
debug(D_NOTICE, "marking folderid %lu as clean", (unsigned long)ft->folderid);
ptree_del(&folder_tasks, &ft->tree);
psync_free(ft);
}
static psync_folderid_t get_parent_folder(psync_folderid_t folderid) {
psync_sql_res *res;
psync_uint_row row;
parent_cache_entry_t *p;
uint32_t h;
if (folderid == 0)
return PSYNC_INVALID_FOLDERID;
h = hash_folderid(folderid) % PARENT_HASH_SIZE;
psync_list_for_each_element(p, &parent_cache_hash[h], parent_cache_entry_t,
list_hash) if (p->folderid == folderid) {
psync_list_del(&p->list_lru);
psync_list_add_tail(&parent_cache_lru, &p->list_lru);
return p->parentfolderid;
}
res = psync_sql_query_nolock("SELECT parentfolderid FROM folder WHERE id=?");
psync_sql_bind_uint(res, 1, folderid);
row = psync_sql_fetch_rowint(res);
if (unlikely(!row)) {
debug(D_WARNING, "can not find parent folder for folderid %lu",
(unsigned long)folderid);
psync_sql_free_result(res);
return PSYNC_INVALID_FOLDERID;
}
p = psync_list_remove_head_element(&parent_cache_lru, parent_cache_entry_t,
list_lru);
psync_list_del(&p->list_hash);
p->folderid = folderid;
p->parentfolderid = row[0];
psync_list_add_head(&parent_cache_hash[h], &p->list_hash);
psync_list_add_tail(&parent_cache_lru, &p->list_lru);
psync_sql_free_result(res);
return p->parentfolderid;
}
void ppathstatus_drive_fldr_changed(psync_folderid_t folderid) {
psync_fstask_folder_t *folder;
folder_tasks_t *ft;
int changed;
psync_sql_lock();
folder = psync_fstask_get_folder_tasks_rdlocked(folderid);
changed = folder && (folder->creats || folder->mkdirs);
ft = get_folder_tasks(folderid, changed);
if ((!changed && (!ft || ft->child_task_cnt)) ||
(changed && (ft->own_tasks || ft->child_task_cnt))) {
if (changed && !ft->own_tasks)
ft->own_tasks = 1;
else if (!changed && ft && ft->own_tasks)
ft->own_tasks = 0;
psync_sql_unlock();
return;
}
if (changed) {
assert(!ft->own_tasks);
assert(!ft->child_task_cnt);
ft->own_tasks = 1;
while ((folderid = get_parent_folder(folderid)) != PSYNC_INVALID_FOLDERID) {
ft = get_folder_tasks(folderid, 1);
ft->child_task_cnt++;
if (ft->child_task_cnt > 1 || ft->own_tasks)
break;
}
} else {
assert(ft);
assert(!ft->child_task_cnt);
assert(ft->own_tasks);
free_folder_tasks(ft);
while ((folderid = get_parent_folder(folderid)) != PSYNC_INVALID_FOLDERID) {
ft = get_folder_tasks(folderid, 0);
assert(ft); // if assert fails, the problem is not the assert, don't
// change it to "if (!ft) break;"
ft->child_task_cnt--;
if (ft->child_task_cnt || ft->own_tasks)
break;
free_folder_tasks(ft);
}
}
psync_sql_unlock();
}
static void folder_moved(psync_folderid_t folderid,
psync_folderid_t old_parent_folderid,
psync_folderid_t new_parent_folderid) {
folder_tasks_t *pft;
pft = get_folder_tasks(new_parent_folderid, 1);
pft->child_task_cnt++;
while (pft->child_task_cnt == 1 && !pft->own_tasks &&
(new_parent_folderid = get_parent_folder(new_parent_folderid)) !=
PSYNC_INVALID_FOLDERID) {
pft = get_folder_tasks(new_parent_folderid, 1);
pft->child_task_cnt++;
}
pft = get_folder_tasks(old_parent_folderid, 0);
assert(pft);
pft->child_task_cnt--;
while (!pft->child_task_cnt && !pft->own_tasks) {
free_folder_tasks(pft);
old_parent_folderid = get_parent_folder(old_parent_folderid);
if (old_parent_folderid == PSYNC_INVALID_FOLDERID)
break;
pft = get_folder_tasks(old_parent_folderid, 0);
assert(pft);
pft->child_task_cnt--;
}
}
static void folder_moved_commit(void *ptr) {
folder_moved_params_t *mp = (folder_moved_params_t *)ptr;
folder_moved(mp->folderid, mp->old_parent_folderid, mp->new_parent_folderid);
psync_free(mp);
}
void ppathstatus_fldr_moved(psync_folderid_t folderid,
psync_folderid_t old_parent_folderid,
psync_folderid_t new_parent_folderid) {
folder_moved_params_t *mp;
if (old_parent_folderid == new_parent_folderid)
return;
ppathstatus_del_from_parent_cache(folderid);
if (!get_folder_tasks(folderid, 0))
return;
mp = psync_new(folder_moved_params_t);
mp->folderid = folderid;
mp->old_parent_folderid = old_parent_folderid;
mp->new_parent_folderid = new_parent_folderid;
psync_sql_transation_add_callbacks(folder_moved_commit, psync_free, mp);
}
void ppathstatus_fldr_deleted(psync_folderid_t folderid) {
folder_tasks_t *ft;
ft = get_folder_tasks(folderid, 0);
if (ft) {
free_folder_tasks(ft);
while ((folderid = get_parent_folder(folderid)) != PSYNC_INVALID_FOLDERID) {
ft = get_folder_tasks(folderid, 0);
assert(ft); // if assert fails, the problem is not the assert, don't
// change it to "if (!ft) break;"
ft->child_task_cnt--;
if (ft->child_task_cnt || ft->own_tasks)
break;
free_folder_tasks(ft);
}
}
ppathstatus_del_from_parent_cache(folderid);
}
static void sync_data_free(sync_data_t *sd) {
ptree_for_each_element_call_safe(sd->folder_tasks, folder_tasks_t, tree,
psync_free);
psync_free(sd);
}
static sync_data_t *get_sync_data(psync_syncid_t syncid, int create) {
psync_tree *tr, **atr;
sync_data_t *sd;
tr = sync_data;
atr = &sync_data;
while (tr) {
sd = ptree_element(tr, sync_data_t, tree);
if (syncid < sd->syncid) {
if (tr->left) {
tr = tr->left;
} else {
atr = &tr->left;
break;
}
} else if (syncid > sd->syncid) {
if (tr->right) {
tr = tr->right;
} else {
atr = &tr->right;
break;
}
} else {
return sd;
}
}
if (!create) {
return NULL;
} else {
size_t i;
sd = psync_new(sync_data_t);
sd->syncid = syncid;
*atr = &sd->tree;
ptree_added_at(&sync_data, tr, &sd->tree);
psync_list_init(&sd->cache_free);
psync_list_init(&sd->parent_cache_lru);
for (i = 0; i < SYNC_PARENT_HASH_SIZE; i++)
psync_list_init(&sd->parent_cache_hash[i]);
for (i = 0; i < SYNC_PARENT_CACHE_SIZE; i++) {
psync_list_add_tail(&sd->parent_cache_lru,
&sd->parent_cache_entries[i].list_lru);
psync_list_add_tail(&sd->cache_free,
&sd->parent_cache_entries[i].list_hash);
}
sd->folder_tasks = PSYNC_TREE_EMPTY;
return sd;
}
}
static folder_tasks_t *
get_sync_folder_tasks(sync_data_t *sd, psync_folderid_t folderid, int create) {
psync_tree *tr, **atr;
folder_tasks_t *ft;
tr = sd->folder_tasks;
atr = &sd->folder_tasks;
while (tr) {
ft = ptree_element(tr, folder_tasks_t, tree);
if (folderid < ft->folderid) {
if (tr->left) {
tr = tr->left;
} else {
atr = &tr->left;
break;
}
} else if (folderid > ft->folderid) {
if (tr->right) {
tr = tr->right;
} else {
atr = &tr->right;
break;
}
} else {
return ft;
}
}
if (create) {
ft = psync_new(folder_tasks_t);
*atr = &ft->tree;
ptree_added_at(&sd->folder_tasks, tr, &ft->tree);
ft->folderid = folderid;
ft->child_task_cnt = 0;
ft->own_tasks = 0;
debug(D_NOTICE, "marking folderid %lu in syncid %u as having (sub)tasks",
(unsigned long)folderid, (unsigned)sd->syncid);
return ft;
} else {
return NULL;
}
}
static psync_folderid_t get_sync_parent_folder(sync_data_t *sd,
psync_folderid_t folderid) {
psync_sql_res *res;
psync_uint_row row;
parent_cache_entry_t *p;
uint32_t h;
if (folderid == 0)
return PSYNC_INVALID_FOLDERID;
h = hash_folderid(folderid) % SYNC_PARENT_HASH_SIZE;
psync_list_for_each_element(p, &sd->parent_cache_hash[h],
parent_cache_entry_t,
list_hash) if (p->folderid == folderid) {
psync_list_del(&p->list_lru);
psync_list_add_tail(&sd->parent_cache_lru, &p->list_lru);
return p->parentfolderid;
}
res = psync_sql_query_nolock(
"SELECT localparentfolderid FROM localfolder WHERE id=?");
psync_sql_bind_uint(res, 1, folderid);
row = psync_sql_fetch_rowint(res);
if (unlikely(!row)) {
debug(D_WARNING, "can not find parent folder for localfolderid %lu",
(unsigned long)folderid);
psync_sql_free_result(res);
return PSYNC_INVALID_FOLDERID;
}
p = psync_list_remove_head_element(&sd->parent_cache_lru,
parent_cache_entry_t, list_lru);
psync_list_del(&p->list_hash);
p->folderid = folderid;
p->parentfolderid = row[0];
psync_list_add_head(&sd->parent_cache_hash[h], &p->list_hash);
psync_list_add_tail(&sd->parent_cache_lru, &p->list_lru);
psync_sql_free_result(res);
return p->parentfolderid;
}
void ppath_syncfldr_task_added_locked(
psync_syncid_t syncid, psync_folderid_t localfolderid) {
sync_data_t *sd;
folder_tasks_t *ft;
// debug(D_NOTICE, "syncid %u localfolderid %lu", (unsigned)syncid, (unsigned
// long)localfolderid);
sd = get_sync_data(syncid, 1);
ft = get_sync_folder_tasks(sd, localfolderid, 1);
if (ft->child_task_cnt || ft->own_tasks) {
ft->own_tasks = 1;
return;
}
ft->own_tasks = 1;
while ((localfolderid = get_sync_parent_folder(sd, localfolderid)) !=
PSYNC_INVALID_FOLDERID) {
ft = get_sync_folder_tasks(sd, localfolderid, 1);
ft->child_task_cnt++;
if (ft->child_task_cnt > 1 || ft->own_tasks)
break;
}
}
void ppath_syncfldr_task_added(psync_syncid_t syncid,
psync_folderid_t localfolderid) {
psync_sql_lock();
ppathstatus_clear_sync_cache();
ppath_syncfldr_task_added_locked(syncid, localfolderid);
psync_sql_unlock();
}
static void load_sync_tasks() {
psync_sql_res *res;
psync_uint_row row;
res = psync_sql_query_nolock(
"SELECT syncid, localitemid FROM task WHERE type=?");
psync_sql_bind_uint(res, 1, PSYNC_DOWNLOAD_FILE);
while ((row = psync_sql_fetch_rowint(res)))
ppath_syncfldr_task_added_locked(row[0], row[1]);
psync_sql_free_result(res);
res = psync_sql_query_nolock(
"SELECT lf.syncid, lf.localparentfolderid FROM task t, localfile lf "
"WHERE t.type=? AND t.localitemid=lf.id AND t.syncid=lf.syncid");
psync_sql_bind_uint(res, 1, PSYNC_UPLOAD_FILE);
while ((row = psync_sql_fetch_rowint(res)))
ppath_syncfldr_task_added_locked(row[0], row[1]);
psync_sql_free_result(res);
}
static int local_folder_has_tasks(psync_syncid_t syncid,
psync_folderid_t localfolderid) {
psync_sql_res *res;
psync_uint_row row;
res = psync_sql_query_nolock(
"SELECT 1 FROM task WHERE type=? AND syncid=? AND localitemid=?");
psync_sql_bind_uint(res, 1, PSYNC_DOWNLOAD_FILE);
psync_sql_bind_uint(res, 2, syncid);
psync_sql_bind_uint(res, 3, localfolderid);
row = psync_sql_fetch_rowint(res);
psync_sql_free_result(res);
if (row)
return 1;
res = psync_sql_query_nolock(
"SELECT 1 FROM localfile lf, task t WHERE lf.syncid=? AND "
"lf.localparentfolderid=? AND lf.id=t.localitemid AND "
"t.syncid=? AND t.type=?");
psync_sql_bind_uint(res, 1, syncid);
psync_sql_bind_uint(res, 2, localfolderid);
psync_sql_bind_uint(res, 3, syncid);
psync_sql_bind_uint(res, 4, PSYNC_UPLOAD_FILE);
row = psync_sql_fetch_rowint(res);
psync_sql_free_result(res);
if (row)
return 1;
return 0;
}
static void free_sync_folder_tasks(sync_data_t *sd, folder_tasks_t *ft) {
debug(D_NOTICE, "marking folderid %lu from syncid %u as clean",
(unsigned long)ft->folderid, (unsigned)sd->syncid);
ptree_del(&sd->folder_tasks, &ft->tree);
psync_free(ft);
}
void ppathstatus_syncfldr_task_completed(
psync_syncid_t syncid, psync_folderid_t localfolderid) {
sync_data_t *sd;
folder_tasks_t *ft;
// debug(D_NOTICE, "syncid %u localfolderid %lu lfht %d", (unsigned)syncid,
// (unsigned long)localfolderid, local_folder_has_tasks(syncid,
// localfolderid));
psync_sql_lock();
ppathstatus_clear_sync_cache();
if (local_folder_has_tasks(syncid, localfolderid) ||
!(sd = get_sync_data(syncid, 0)) ||
!(ft = get_sync_folder_tasks(sd, localfolderid, 0))) {
psync_sql_unlock();
return;
}
ft->own_tasks = 0;
while (!ft->child_task_cnt && !ft->own_tasks) {
free_sync_folder_tasks(sd, ft);
localfolderid = get_sync_parent_folder(sd, localfolderid);
if (localfolderid == PSYNC_INVALID_FOLDERID)
break;
ft = get_sync_folder_tasks(sd, localfolderid, 0);
assert(ft);
assert(ft->child_task_cnt);
ft->child_task_cnt--;
}
psync_sql_unlock();
}
void ppathstatus_syncfldr_delete(psync_syncid_t syncid) {
sync_data_t *sd;
psync_sql_lock();
sd = get_sync_data(syncid, 0);
if (sd)
ptree_del(&sync_data, &sd->tree);
psync_sql_unlock();
if (sd)
sync_data_free(sd);
}
static void sync_del_from_parent_cache(sync_data_t *sd,
psync_folderid_t folderid) {
parent_cache_entry_t *p;
uint32_t h;
h = hash_folderid(folderid) % SYNC_PARENT_HASH_SIZE;
psync_list_for_each_element(p, &sd->parent_cache_hash[h],
parent_cache_entry_t,
list_hash) if (p->folderid == folderid) {
psync_list_del(&p->list_lru);
psync_list_add_head(&sd->parent_cache_lru, &p->list_lru);
psync_list_del(&p->list_hash);
psync_list_add_tail(&sd->cache_free, &p->list_hash);
break;
}
}
static void sync_folder_moved(psync_folderid_t folderid,
psync_syncid_t old_syncid,
psync_folderid_t old_parent_folderid,
psync_syncid_t new_syncid,
psync_folderid_t new_parent_folderid) {
sync_data_t *sd;
folder_tasks_t *pft;
sd = get_sync_data(new_syncid, 1);
pft = get_sync_folder_tasks(sd, new_parent_folderid, 1);
pft->child_task_cnt++;
while (pft->child_task_cnt == 1 && !pft->own_tasks &&
(new_parent_folderid = get_sync_parent_folder(
sd, new_parent_folderid)) != PSYNC_INVALID_FOLDERID) {
pft = get_sync_folder_tasks(sd, new_parent_folderid, 1);
pft->child_task_cnt++;
}
sd = get_sync_data(old_syncid, 0);
assert(sd);
pft = get_sync_folder_tasks(sd, old_parent_folderid, 0);
assert(pft);
pft->child_task_cnt--;
while (!pft->child_task_cnt && !pft->own_tasks) {
free_sync_folder_tasks(sd, pft);
old_parent_folderid = get_sync_parent_folder(sd, old_parent_folderid);
if (old_parent_folderid == PSYNC_INVALID_FOLDERID)
break;
pft = get_sync_folder_tasks(sd, old_parent_folderid, 0);
assert(pft);
pft->child_task_cnt--;
}
}
static void sync_folder_moved_commit(void *ptr) {
sync_folder_moved_params_t *mp = (sync_folder_moved_params_t *)ptr;
sync_folder_moved(mp->folderid, mp->old_syncid, mp->old_parent_folderid,
mp->new_syncid, mp->new_parent_folderid);
psync_free(mp);
}
void ppathstatus_syncfldr_moved(psync_folderid_t folderid,
psync_syncid_t old_syncid,
psync_folderid_t old_parent_folderid,
psync_syncid_t new_syncid,
psync_folderid_t new_parent_folderid) {
sync_folder_moved_params_t *mp;
sync_data_t *sd;
if (old_parent_folderid == new_parent_folderid && old_syncid == new_syncid)
return;
sd = get_sync_data(old_syncid, 0);
if (!sd)
return;
sync_del_from_parent_cache(sd, folderid);
if (!get_folder_tasks(folderid, 0))
return;
mp = psync_new(sync_folder_moved_params_t);
mp->folderid = folderid;
mp->old_parent_folderid = old_parent_folderid;
mp->new_parent_folderid = new_parent_folderid;
mp->old_syncid = old_syncid;
mp->new_syncid = new_syncid;
psync_sql_transation_add_callbacks(sync_folder_moved_commit, psync_free, mp);
}
void ppathstatus_syncfldr_deleted(psync_syncid_t syncid,
psync_folderid_t folderid) {
sync_data_t *sd;
folder_tasks_t *ft;
if ((sd = get_sync_data(syncid, 0)) &&
(ft = get_sync_folder_tasks(sd, folderid, 0))) {
free_sync_folder_tasks(sd, ft);
while ((folderid = get_sync_parent_folder(sd, folderid)) !=
PSYNC_INVALID_FOLDERID) {
ft = get_sync_folder_tasks(sd, folderid, 0);
assert(ft); // if assert fails, the problem is not the assert, don't
// change it to "if (!ft) break;"
ft->child_task_cnt--;
if (ft->child_task_cnt || ft->own_tasks)
break;
free_sync_folder_tasks(sd, ft);
}
}
if (sd)
sync_del_from_parent_cache(sd, folderid);
}
static inline int is_slash(char ch) { return ch == '/'; }
static inline int valid_last_char(char ch) { return is_slash(ch) || ch == 0; }
static psync_path_status_t rdunlock_return(psync_path_status_t st) {
psync_sql_rdunlock();
return st;
}
static psync_path_status_t rdunlock_return_in_prog() {
static const uint32_t requiredstatuses[] = {
PSTATUS_COMBINE(PSTATUS_TYPE_RUN, PSTATUS_RUN_RUN),
PSTATUS_COMBINE(PSTATUS_TYPE_ONLINE, PSTATUS_ONLINE_CONNECTING |
PSTATUS_ONLINE_SCANNING |
PSTATUS_ONLINE_ONLINE),
PSTATUS_COMBINE(PSTATUS_TYPE_ACCFULL, PSTATUS_ACCFULL_QUOTAOK),
PSTATUS_COMBINE(PSTATUS_TYPE_DISKFULL, PSTATUS_DISKFULL_OK)};
psync_sql_rdunlock();
if (pstatus_ok_status_arr(requiredstatuses, ARRAY_SIZE(requiredstatuses)))
return PSYNC_PATH_STATUS_IN_PROG;
else
return PSYNC_PATH_STATUS_PAUSED; // TODO: fix to return proper status
}
static psync_path_status_t
psync_path_status_drive_folder_locked(psync_folderid_t folderid) {
if (get_folder_tasks(folderid, 0))
return rdunlock_return_in_prog();
psync_sql_rdunlock();
return PSYNC_PATH_STATUS_IN_SYNC;
}
#define HASH_ROUND() \
a += n; \
a *= 0x5724c9d1; \
b ^= n; \
b *= 0x6a511717; \
c += n; \
c *= 0x76459457; \
d ^= n; \
d *= 0x51a97a23;
static void comp_hash(const char *s, size_t len, uint32_t *out, uint64_t seed1,
uint64_t seed2) {
uint32_t a, b, c, d, n;
a = seed1 & 0xffffffffU;
b = seed1 >> 32;
c = seed2 & 0xffffffffU;
d = seed2 >> 32;
b ^= len;
b *= 0x6a511717;
c += len;
c *= 0x76459457;
while (len >= sizeof(uint32_t)) {
n = *(const uint32_t *)s;
s += sizeof(uint32_t);
len -= sizeof(uint32_t);
HASH_ROUND();
}
n = len << 24;
switch (len) {
case 3:
n += 0xffffU * s[2];
case 2:
n += 0xffU * s[1];
case 1:
n += s[0];
HASH_ROUND();
}
out[0] = a ^ (a >> 17);
out[1] = b;
out[2] = c ^ (c >> 15);
out[3] = d;
}
static int move_encname_to_buff(psync_folderid_t folderid, char *buff,
size_t buff_size, const char *name,
size_t namelen) {
pcrypto_textenc_t enc;
char *encname;
size_t len;
if (unlikely(namelen >= buff_size))
return -1;
memcpy(buff, name, namelen);
buff[namelen] = 0;
enc = pcryptofolder_fldencoder_get(folderid);
if (unlikely(psync_crypto_is_error(enc)))
return -1;
encname = pcryptofolder_fldencode_filename(enc, buff);
pcryptofolder_fldencoder_release(folderid, enc);
len = strlen(encname);
if (unlikely(len >= sizeof(buff)))
return -1;
memcpy(buff, encname, len + 1);
psync_free(encname);
return 0;
}
static psync_path_status_t psync_path_status_drive(const char *path,
size_t path_len) {
char buff[2048];
uint32_t hash[4], h;
psync_fstask_folder_t *folder;
path_cache_entry_t *ce;
psync_sql_res *res;
psync_uint_row row;
psync_folderid_t folderid;
uint64_t flags;
size_t off, poff;
const char *name;
size_t namelen;
int wrlocked, found;
while (is_slash(*path)) {
path++;
path_len--;
}
wrlocked = 0;
psync_sql_rdlock();
if (!get_folder_tasks(0, 0))
return rdunlock_return(PSYNC_PATH_STATUS_IN_SYNC);
if (!path_len)
return psync_path_status_drive_folder_locked(0);
restart:
poff = 0;
folderid = 0;
flags = ENTRY_FLAG_FOLDER;
for (off = 0; off < path_len; off++)
if (is_slash(path[off])) {
if (off && is_slash(path[off - 1])) {
poff = off + 1;
} else {
folder = psync_fstask_get_folder_tasks_rdlocked(folderid);
if (folder) {
if (unlikely(off - poff >= sizeof(buff)))
return rdunlock_return(PSYNC_PATH_STATUS_IN_SYNC);
if (likely((flags & ENTRY_FLAG_ENCRYPTED) == 0)) {
memcpy(buff, path + poff, off - poff);
buff[off - poff] = 0;
} else {
if (unlikely(move_encname_to_buff(folderid, buff, sizeof(buff),
path + poff, off - poff)))
return rdunlock_return(PSYNC_PATH_STATUS_IN_SYNC);
}
if (psync_fstask_find_mkdir(folder, buff, 0))
return rdunlock_return_in_prog();
if (psync_fstask_find_rmdir(folder, buff, 0))
return rdunlock_return(PSYNC_PATH_STATUS_NOT_FOUND);
}
comp_hash(path + poff, off - poff, hash, folderid, drv_hash_seed);
h = (hash[0] + hash[2]) % PATH_HASH_SIZE;
found = 0;
psync_list_for_each_element(
ce, &path_cache_hash[h], path_cache_entry_t,
list_hash) if (!memcmp(hash, ce->hash, sizeof(hash)) &&
(ce->flags & ENTRY_FLAG_FOLDER)) {
if (wrlocked) {
psync_list_del(&ce->list_lru);
psync_list_add_tail(&path_cache_lru, &ce->list_lru);
}
folderid = ce->itemid;
flags = ce->flags;
found = 1;
poff = off + 1;
if (!get_folder_tasks(folderid, 0))
return rdunlock_return(PSYNC_PATH_STATUS_IN_SYNC);
break;
}
if (found)
continue;
res =
psync_sql_query_nolock("SELECT id, permissions, flags, userid FROM "
"folder WHERE parentfolderid=? AND name=?");
psync_sql_bind_uint(res, 1, folderid);
if (likely((flags & ENTRY_FLAG_ENCRYPTED) == 0)) {
psync_sql_bind_lstring(res, 2, path + poff, off - poff);
} else {
psync_sql_free_result(res);
if (unlikely(move_encname_to_buff(folderid, buff, sizeof(buff),
path + poff, off - poff)))
return rdunlock_return(PSYNC_PATH_STATUS_IN_SYNC);
psync_sql_bind_string(res, 2, buff);
}
row = psync_sql_fetch_rowint(res);
if (!row) {
psync_sql_free_result(res);
return rdunlock_return(PSYNC_PATH_STATUS_NOT_FOUND);
}
if (!wrlocked) {
psync_sql_free_result(res);
wrlocked = 1;
if (psync_sql_tryupgradelock()) {
psync_sql_rdunlock();
psync_sql_lock();
}
goto restart;
}
folderid = row[0];
flags = ENTRY_FLAG_FOLDER;
if (row[2] & PSYNC_FOLDER_FLAG_ENCRYPTED)
flags |= ENTRY_FLAG_ENCRYPTED;
psync_sql_free_result(res);
ce = psync_list_remove_head_element(&path_cache_lru, path_cache_entry_t,
list_lru);
psync_list_add_tail(&path_cache_lru, &ce->list_lru);
psync_list_del(&ce->list_hash);
psync_list_add_tail(&path_cache_hash[h], &ce->list_hash);
memcpy(ce->hash, hash, sizeof(hash));
ce->itemid = folderid;
ce->flags = flags;
poff = off + 1;
if (!get_folder_tasks(folderid, 0))
return rdunlock_return(PSYNC_PATH_STATUS_IN_SYNC);
}
}
if (poff == path_len)
return psync_path_status_drive_folder_locked(folderid);
folder = psync_fstask_get_folder_tasks_rdlocked(folderid);
name = path + poff;
namelen = path_len - poff;
if (folder) {
if (unlikely(flags & ENTRY_FLAG_ENCRYPTED)) {
if (unlikely(move_encname_to_buff(folderid, buff, sizeof(buff),
path + poff, path_len - poff)))
return rdunlock_return(PSYNC_PATH_STATUS_IN_SYNC);
name = buff;
namelen = strlen(buff);
}
if (psync_fstask_find_mkdir(folder, name, 0))
return rdunlock_return_in_prog();
if (psync_fstask_find_creat(folder, name, 0))
return rdunlock_return_in_prog();
}
// we do the hash with the unencrypted name
comp_hash(path + poff, path_len - poff, hash, folderid, drv_hash_seed);
h = (hash[0] + hash[2]) % PATH_HASH_SIZE;
found = 0;
psync_list_for_each_element(
ce, &path_cache_hash[h], path_cache_entry_t,
list_hash) if (!memcmp(hash, ce->hash, sizeof(hash))) {
if (wrlocked) {
psync_list_del(&ce->list_lru);
psync_list_add_tail(&path_cache_lru, &ce->list_lru);
}
folderid = ce->itemid;
flags = ce->flags;
found = 1;
break;
}
if (found) {
if (flags & ENTRY_FLAG_FOLDER) {
if (folder && psync_fstask_find_rmdir(folder, name, 0))
return rdunlock_return(PSYNC_PATH_STATUS_NOT_FOUND);
else
return psync_path_status_drive_folder_locked(folderid);
} else {
if (folder && psync_fstask_find_unlink(folder, name, 0))
return rdunlock_return(PSYNC_PATH_STATUS_NOT_FOUND);
else
return rdunlock_return(PSYNC_PATH_STATUS_IN_SYNC);
}
}
if (unlikely(!folder && (flags & ENTRY_FLAG_ENCRYPTED))) {
if (unlikely(move_encname_to_buff(folderid, buff, sizeof(buff), path + poff,
path_len - poff)))
return rdunlock_return(PSYNC_PATH_STATUS_IN_SYNC);
name = buff;
namelen = strlen(buff);
}
res = psync_sql_query_nolock("SELECT id, permissions, flags, userid FROM "
"folder WHERE parentfolderid=? AND name=?");
psync_sql_bind_uint(res, 1, folderid);
psync_sql_bind_lstring(res, 2, name, namelen);
row = psync_sql_fetch_rowint(res);
if (!row || (folder && psync_fstask_find_rmdir(folder, path + poff, 0))) {
psync_sql_free_result(res);
if (folder && psync_fstask_find_unlink(folder, path + poff, 0))
return rdunlock_return(PSYNC_PATH_STATUS_NOT_FOUND);
res = psync_sql_query_nolock(
"SELECT id FROM file WHERE parentfolderid=? AND name=?");
psync_sql_bind_uint(res, 1, folderid);
psync_sql_bind_lstring(res, 2, name, namelen);
row = psync_sql_fetch_rowint(res);
if (!row) {
psync_sql_free_result(res);
return rdunlock_return(PSYNC_PATH_STATUS_NOT_FOUND);
}
if (!wrlocked) {
psync_sql_free_result(res);
wrlocked = 1;
if (psync_sql_tryupgradelock()) {
psync_sql_rdunlock();
psync_sql_lock();
}
goto restart;
}
folderid = row[0];
flags &= ~ENTRY_FLAG_FOLDER;
psync_sql_free_result(res);
ce = psync_list_remove_head_element(&path_cache_lru, path_cache_entry_t,
list_lru);
psync_list_add_tail(&path_cache_lru, &ce->list_lru);
psync_list_del(&ce->list_hash);
psync_list_add_tail(&path_cache_hash[h], &ce->list_hash);
memcpy(ce->hash, hash, sizeof(hash));
ce->itemid = folderid;
ce->flags = flags;
return rdunlock_return(PSYNC_PATH_STATUS_IN_SYNC);
}
if (!wrlocked) {
psync_sql_free_result(res);
wrlocked = 1;
if (psync_sql_tryupgradelock()) {
psync_sql_rdunlock();
psync_sql_lock();
}
goto restart;
}
folderid = row[0];
flags = ENTRY_FLAG_FOLDER;
psync_sql_free_result(res);
ce = psync_list_remove_head_element(&path_cache_lru, path_cache_entry_t,
list_lru);
psync_list_add_tail(&path_cache_lru, &ce->list_lru);
psync_list_del(&ce->list_hash);
psync_list_add_tail(&path_cache_hash[h], &ce->list_hash);
memcpy(ce->hash, hash, sizeof(hash));
ce->itemid = folderid;
ce->flags = flags;
return psync_path_status_drive_folder_locked(folderid);
}
static psync_path_status_t
psync_path_status_sync_folder_locked(psync_syncid_t syncid,
psync_folderid_t folderid) {
sync_data_t *sd = get_sync_data(syncid, 0);
if (sd && get_sync_folder_tasks(sd, folderid, 0))
return rdunlock_return_in_prog();
psync_sql_rdunlock();
return PSYNC_PATH_STATUS_IN_SYNC;
}
#define SYNCID_SHIFT 40
static inline uint64_t combine_syncid_lf(psync_syncid_t syncid,
psync_folderid_t localfolderid) {
return localfolderid | ((uint64_t)syncid << SYNCID_SHIFT);
}
static inline psync_syncid_t extract_syncid(uint64_t combined) {
return combined >> SYNCID_SHIFT;
}
static inline psync_folderid_t extract_localfolderid(uint64_t combined) {
return combined & ((1ULL << SYNCID_SHIFT) - 1);
}
static int sync_file_in_progress(psync_syncid_t syncid,
psync_folderid_t localfolderid,
psync_fileid_t localfileid, const char *name,
size_t namelen) {
sync_data_t *sd;
folder_tasks_t *ft;
psync_sql_res *res;
psync_uint_row row;
sd = get_sync_data(syncid, 0);
if (!sd)
return 0;
ft = get_sync_folder_tasks(sd, localfolderid, 0);
if (!ft || !ft->own_tasks)
return 0;
res = psync_sql_query_nolock("SELECT 1 FROM task WHERE type=? AND syncid=? "
"AND localitemid=? AND name=?");
psync_sql_bind_uint(res, 1, PSYNC_DOWNLOAD_FILE);
psync_sql_bind_uint(res, 2, syncid);
psync_sql_bind_uint(res, 3, localfolderid);
psync_sql_bind_lstring(res, 4, name, namelen);
row = psync_sql_fetch_rowint(res);
psync_sql_free_result(res);
if (row)
return 1;
res = psync_sql_query_nolock(
"SELECT 1 FROM task WHERE type=? AND syncid=? AND localitemid=?");
psync_sql_bind_uint(res, 1, PSYNC_UPLOAD_FILE);
psync_sql_bind_uint(res, 2, syncid);
psync_sql_bind_uint(res, 3, localfileid);
row = psync_sql_fetch_rowint(res);
psync_sql_free_result(res);
if (row)
return 1;
return 0;
}
static psync_path_status_t psync_path_status_sync(const char *path,
size_t path_len,
psync_folderid_t folderid,
psync_syncid_t syncid,
uint32_t sflags) {
uint32_t hash[4], h;
path_cache_entry_t *ce;
psync_sql_res *res;
psync_uint_row row;
uint64_t flags;
size_t off, poff;
int wrlocked, found;
while (is_slash(*path)) {
path++;
path_len--;
}
wrlocked = 0;
psync_sql_rdlock();
if (!path_len)
return psync_path_status_sync_folder_locked(syncid, 0);
restart:
poff = 0;
folderid = combine_syncid_lf(syncid, 0);
flags = ENTRY_FLAG_FOLDER;
for (off = 0; off < path_len; off++)
if (is_slash(path[off])) {
if (off && is_slash(path[off - 1])) {
poff = off + 1;
} else {
if (psync_is_lname_to_ignore(path + poff, off - poff))
return rdunlock_return(PSYNC_PATH_STATUS_IN_SYNC |
PSYNC_PATH_STATUS_IS_IGNORED);
comp_hash(path + poff, off - poff, hash, folderid, sync_hash_seed);
h = (hash[0] + hash[2]) % PATH_HASH_SIZE;
found = 0;
psync_list_for_each_element(
ce, &path_cache_hash[h], path_cache_entry_t,
list_hash) if (!memcmp(hash, ce->hash, sizeof(hash)) &&
(ce->flags & ENTRY_FLAG_FOLDER) &&
extract_syncid(ce->itemid) == syncid) {
if (wrlocked) {
psync_list_del(&ce->list_lru);
psync_list_add_tail(&path_cache_lru, &ce->list_lru);
}
folderid = ce->itemid;
flags = ce->flags;
found = 1;
poff = off + 1;
break;
}
if (found)
continue;
res =
psync_sql_query_nolock("SELECT id FROM localfolder WHERE syncid=? "
"AND localparentfolderid=? AND name=?");
psync_sql_bind_uint(res, 1, syncid);
psync_sql_bind_uint(res, 2, extract_localfolderid(folderid));
psync_sql_bind_lstring(res, 3, path + poff, off - poff);
row = psync_sql_fetch_rowint(res);
if (!row) {
psync_sql_free_result(res);
return rdunlock_return_in_prog();
}
if (!wrlocked) {
psync_sql_free_result(res);
wrlocked = 1;
if (psync_sql_tryupgradelock()) {
psync_sql_rdunlock();
psync_sql_lock();
}
goto restart;
}
folderid = combine_syncid_lf(syncid, row[0]);
flags = ENTRY_FLAG_FOLDER;
psync_sql_free_result(res);
ce = psync_list_remove_head_element(&path_cache_lru, path_cache_entry_t,
list_lru);
psync_list_add_tail(&path_cache_lru, &ce->list_lru);
psync_list_del(&ce->list_hash);
psync_list_add_tail(&path_cache_hash[h], &ce->list_hash);
memcpy(ce->hash, hash, sizeof(hash));
ce->itemid = folderid;
ce->flags = flags;
poff = off + 1;
}
}
if (poff == path_len)
return psync_path_status_sync_folder_locked(
syncid, extract_localfolderid(folderid));
if (psync_is_lname_to_ignore(path + poff, path_len - poff))
return rdunlock_return(PSYNC_PATH_STATUS_IN_SYNC |
PSYNC_PATH_STATUS_IS_IGNORED);
comp_hash(path + poff, path_len - poff, hash, folderid, sync_hash_seed);
h = (hash[0] + hash[2]) % PATH_HASH_SIZE;
found = 0;
psync_list_for_each_element(
ce, &path_cache_hash[h], path_cache_entry_t,
list_hash) if (!memcmp(hash, ce->hash, sizeof(hash)) &&
extract_syncid(ce->itemid) == syncid) {
if (wrlocked) {
psync_list_del(&ce->list_lru);
psync_list_add_tail(&path_cache_lru, &ce->list_lru);
}
folderid = ce->itemid;
flags = ce->flags;
found = 1;
break;
}
if (found) {
if (flags & ENTRY_FLAG_FOLDER)
return psync_path_status_sync_folder_locked(
syncid, extract_localfolderid(folderid));
else if (flags & ENTRY_FLAG_PROG)
return rdunlock_return_in_prog();
else
return rdunlock_return(PSYNC_PATH_STATUS_IN_SYNC);
}
res = psync_sql_query_nolock("SELECT id FROM localfolder WHERE syncid=? AND "
"localparentfolderid=? AND name=?");
psync_sql_bind_uint(res, 1, syncid);
psync_sql_bind_uint(res, 2, extract_localfolderid(folderid));
psync_sql_bind_lstring(res, 3, path + poff, path_len - poff);
if ((row = psync_sql_fetch_rowint(res))) {
folderid = row[0];
psync_sql_free_result(res);
if (!wrlocked) {
wrlocked = 1;
if (psync_sql_tryupgradelock()) {
psync_sql_rdunlock();
psync_sql_lock();
}
goto restart;
}
ce = psync_list_remove_head_element(&path_cache_lru, path_cache_entry_t,
list_lru);
psync_list_add_tail(&path_cache_lru, &ce->list_lru);
psync_list_del(&ce->list_hash);
psync_list_add_tail(&path_cache_hash[h], &ce->list_hash);
memcpy(ce->hash, hash, sizeof(hash));
ce->itemid = combine_syncid_lf(syncid, folderid);
ce->flags = ENTRY_FLAG_FOLDER;
return psync_path_status_sync_folder_locked(syncid, folderid);
}
psync_sql_free_result(res);
res = psync_sql_query_nolock("SELECT id FROM localfile WHERE syncid=? AND "
"localparentfolderid=? AND name=?");
psync_sql_bind_uint(res, 1, syncid);
psync_sql_bind_uint(res, 2, extract_localfolderid(folderid));
psync_sql_bind_lstring(res, 3, path + poff, path_len - poff);
if ((row = psync_sql_fetch_rowint(res))) {
flags = row[0];
psync_sql_free_result(res);
if (!wrlocked) {
wrlocked = 1;
if (psync_sql_tryupgradelock()) {
psync_sql_rdunlock();
psync_sql_lock();
}
goto restart;
}
ce = psync_list_remove_head_element(&path_cache_lru, path_cache_entry_t,
list_lru);
psync_list_add_tail(&path_cache_lru, &ce->list_lru);
psync_list_del(&ce->list_hash);
psync_list_add_tail(&path_cache_hash[h], &ce->list_hash);
memcpy(ce->hash, hash, sizeof(hash));
ce->itemid = combine_syncid_lf(syncid, flags);
ce->flags = sync_file_in_progress(syncid, extract_localfolderid(folderid),
flags, path + poff, path_len - poff)
? ENTRY_FLAG_PROG
: 0;
if (ce->flags & ENTRY_FLAG_PROG)
return rdunlock_return_in_prog();
else
return rdunlock_return(PSYNC_PATH_STATUS_IN_SYNC);
}
psync_sql_free_result(res);
return rdunlock_return_in_prog();
}
static int psync_path_is_prefix_of(const char *prefix, size_t prefix_len,
const char **ppath, size_t *ppath_len) {
const char *path = *ppath;
size_t path_len = *ppath_len;
while (1) {
while (prefix_len && path_len && !is_slash(*prefix) && *prefix == *path) {
prefix++;
path++;
prefix_len--;
path_len--;
}
if (!path_len || !prefix_len)
break;
if (is_slash(*prefix) != is_slash(*path))
return 0;
if (is_slash(*prefix)) {
do {
prefix_len--;
prefix++;
} while (prefix_len && is_slash(*prefix));
do {
path_len--;
path++;
} while (path_len && is_slash(*path));
if (!prefix_len) {
*ppath = path;
*ppath_len = path_len;
return 1;
} else if (!path_len)
return 0;
continue;
}
return 0;
}
if (!prefix_len && valid_last_char(*path)) {
*ppath = path;
*ppath_len = path_len;
return 1;
} else {
return 0;
}
}
psync_path_status_t ppathstatus_get(const char *path) {
char *dp;
path_sync_list_t *sn;
size_t i, len;
len = strlen(path);
if (drive_path) {
if (len >= drive_path_len && !memcmp(drive_path, path, drive_path_len) &&
valid_last_char(path[drive_path_len]))
return psync_path_status_drive(path + drive_path_len,
len - drive_path_len);
} else {
dp = psync_fs_getmountpoint();
if (dp) {
// assign the len before the mutex, so we use it as a barrier
drive_path_len = strlen(dp);
psync_sql_lock();
if (!drive_path) {
drive_path = dp;
dp = NULL;
}
psync_sql_unlock();
psync_free(dp);
if (len >= drive_path_len && !memcmp(drive_path, path, drive_path_len) &&
valid_last_char(path[drive_path_len]))
return psync_path_status_drive(path + drive_path_len,
len - drive_path_len);
}
}
sn = syncs;
if (!sn) {
psync_sql_rdlock();
sn = syncs;
psync_sql_rdunlock();
if (!sn)
goto slower_check;
}
for (i = 0; i < sn->sync_cnt; i++)
if (sn->syncs[i].path_len <= len &&
!memcmp(sn->syncs[i].path, path, sn->syncs[i].path_len) &&
valid_last_char(path[sn->syncs[i].path_len]))
return psync_path_status_sync(
path + sn->syncs[i].path_len, len - sn->syncs[i].path_len,
sn->syncs[i].folderid, sn->syncs[i].syncid, sn->syncs[i].flags);
slower_check:
if (drive_path &&
psync_path_is_prefix_of(drive_path, drive_path_len, &path, &len))
return psync_path_status_drive(path, len);
sn = syncs;
if (sn)
for (i = 0; i < sn->sync_cnt; i++)
if (sn->syncs[i].path_len <= len &&
psync_path_is_prefix_of(sn->syncs[i].path, sn->syncs[i].path_len,
&path, &len))
return psync_path_status_sync(path, len, sn->syncs[i].folderid,
sn->syncs[i].syncid, sn->syncs[i].flags);
return PSYNC_PATH_STATUS_NOT_OURS;
}