/* 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 "ppathstatus.h" #include "plist.h" #include "plibs.h" #include "pfs.h" #include "pfolder.h" #include "pcloudcrypto.h" #include "ptree.h" #include "ptasks.h" #include "pstatus.h" #include #include #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(); void psync_path_status_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; ipath=(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 psync_path_status_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); psync_path_status_clear_sync_path_cache(); psync_sql_unlock(); if (old) psync_free_after_sec(old, 30); } void psync_path_status_clear_path_cache() { uint64_t ndrv_hash_seed, nsync_hash_seed; do { psync_ssl_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 psync_path_status_clear_sync_path_cache() { uint64_t nsync_hash_seed; do { psync_ssl_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 psync_path_status_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=psync_tree_element(tr, folder_tasks_t, tree); if (folderidfolderid) { 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; psync_tree_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); psync_tree_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 psync_path_status_drive_folder_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 psync_path_status_folder_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; psync_path_status_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 psync_path_status_folder_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); } } psync_path_status_del_from_parent_cache(folderid); } static void sync_data_free(sync_data_t *sd) { psync_tree_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=psync_tree_element(tr, sync_data_t, tree); if (syncidsyncid) { 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; psync_tree_added_at(&sync_data, tr, &sd->tree); psync_list_init(&sd->cache_free); psync_list_init(&sd->parent_cache_lru); for (i=0; iparent_cache_hash[i]); for (i=0; iparent_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=psync_tree_element(tr, folder_tasks_t, tree); if (folderidfolderid) { 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; psync_tree_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 psync_path_status_sync_folder_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 psync_path_status_sync_folder_task_added(psync_syncid_t syncid, psync_folderid_t localfolderid) { psync_sql_lock(); psync_path_status_clear_sync_path_cache(); psync_path_status_sync_folder_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))) psync_path_status_sync_folder_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))) psync_path_status_sync_folder_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); psync_tree_del(&sd->folder_tasks, &ft->tree); psync_free(ft); } void psync_path_status_sync_folder_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(); psync_path_status_clear_sync_path_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 psync_path_status_sync_delete(psync_syncid_t syncid) { sync_data_t *sd; psync_sql_lock(); sd=get_sync_data(syncid, 0); if (sd) psync_tree_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 psync_path_status_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_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 psync_path_status_sync_folder_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) { #if defined(P_OS_WINDOWS) return ch=='/' || ch=='\\'; #else return ch=='/'; #endif } 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 (psync_statuses_ok_array(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) { psync_crypto_aes256_text_encoder_t enc; char *encname; size_t len; if (unlikely(namelen>=buff_size)) return -1; memcpy(buff, name, namelen); buff[namelen]=0; enc=psync_cloud_crypto_get_folder_encoder(folderid); if (unlikely(psync_crypto_is_error(enc))) return -1; encname=psync_cloud_crypto_encode_filename(enc, buff); psync_cloud_crypto_release_folder_encoder(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=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_folderid_t extract_localfolderid(uint64_t combined) { return combined&((1ULL<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; offhash, 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; } #if defined(P_OS_WINDOWS) if (tolower(*prefix)!=tolower(*path)) return 0; prefix++; path++; prefix_len--; path_len--; #else return 0; #endif } if (!prefix_len && valid_last_char(*path)) { *ppath=path; *ppath_len=path_len; return 1; } else { return 0; } } psync_path_status_t psync_path_status_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; isync_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; isync_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; }