/* Copyright (c) 2014 Anton Titov. Copyright (c) 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 #include #include #include "pfsxattr.h" #include "pfsfolder.h" #include "pfstasks.h" #include "psql.h" #include "plibs.h" #include "pmem.h" // needed by pfs_xatr_set_thread_name extern PSYNC_THREAD const char *psync_thread_name; #define pfs_xatr_set_thread_name() \ do { \ if (IS_DEBUG) psync_thread_name = __FUNCTION__; \ } while (0) // Value get from standard xattr.h enum { XATTR_CREATE = 1, /* set value, fail if attr already exists. */ #define XATTR_CREATE XATTR_CREATE XATTR_REPLACE = 2 /* set value, fail if attr does not exist. */ #define XATTR_REPLACE XATTR_REPLACE }; #ifndef ENODATA #define ENODATA 61 #endif #ifndef ENOATTR #define ENOATTR ENODATA #endif #define OBJECT_MULTIPLIER 8 #define OBJECT_FOLDER 0 #define OBJECT_FILE 1 #define OBJECT_TASK 2 #define OBJECT_STATICFILE 3 #define folderid_to_objid(id) ((id) * OBJECT_MULTIPLIER + OBJECT_FOLDER) #define fileid_to_objid(id) ((id) * OBJECT_MULTIPLIER + OBJECT_FILE) #define taskid_to_objid(id) ((id) * OBJECT_MULTIPLIER + OBJECT_TASK) #define static_taskid_to_objid(id) \ ((UINT64_MAX - id + 1) * OBJECT_MULTIPLIER + OBJECT_STATICFILE) static void delete_object_id(uint64_t oid) { psync_sql_res *res; res = psql_prepare("DELETE FROM fsxattr WHERE objectid=?"); psql_bind_uint(res, 1, oid); psql_run_free(res); } void pfs_xatr_file_deleted(psync_fileid_t fileid) { delete_object_id(fileid_to_objid(fileid)); } void pfs_xatr_folder_deleted(psync_folderid_t folderid) { delete_object_id(folderid_to_objid(folderid)); } void pfs_xatr_task_deleted(uint64_t taskid) { delete_object_id(taskid_to_objid(taskid)); } static void update_object_id(uint64_t ooid, uint64_t noid) { psync_sql_res *res; res = psql_prepare( "UPDATE OR REPLACE fsxattr SET objectid=? WHERE objectid=?"); psql_bind_uint(res, 1, noid); psql_bind_uint(res, 2, ooid); psql_run_free(res); } void pfs_xatr_task_to_file(uint64_t taskid, psync_fileid_t fileid) { update_object_id(taskid_to_objid(taskid), fileid_to_objid(fileid)); } void pfs_xatr_task_to_folder(uint64_t taskid, psync_folderid_t folderid) { update_object_id(taskid_to_objid(taskid), folderid_to_objid(folderid)); } void pfs_xatr_static_to_task(uint64_t statictaskid, uint64_t taskid) { update_object_id(static_taskid_to_objid(statictaskid), taskid_to_objid(taskid)); } void pfs_xatr_file_to_task(psync_fileid_t fileid, uint64_t taskid) { update_object_id(fileid_to_objid(fileid), taskid_to_objid(taskid)); } static int64_t xattr_get_object_id_locked(const char *path) { psync_fspath_t *fspath; psync_fstask_folder_t *folder; psync_fstask_mkdir_t *mk; psync_fstask_creat_t *cr; psync_sql_res *res; psync_uint_row row; int64_t ret; int checkfile, checkfolder; if (path[1] == 0 && path[0] == '/') return 0; fspath = pfs_fldr_resolve_path(path); if (!fspath) { pdbg_logf(D_NOTICE, "path component of %s not found", path); return -1; } checkfile = 1; checkfolder = 1; folder = pfs_task_get_folder_tasks_rdlocked(fspath->folderid); if (folder) { mk = pfs_task_find_mkdir(folder, fspath->name, 0); if (mk) { pmem_free(PMEM_SUBSYS_OTHER, fspath); pdbg_assertw(mk->folderid != 0); if (mk->folderid > 0) return folderid_to_objid(mk->folderid); else return taskid_to_objid(-mk->folderid); } cr = pfs_task_find_creat(folder, fspath->name, 0); if (cr) { pmem_free(PMEM_SUBSYS_OTHER, fspath); if (cr->fileid > 0) return fileid_to_objid(cr->fileid); else if (cr->fileid == 0) return static_taskid_to_objid(cr->taskid); else { res = psql_query_nolock( "SELECT type, fileid FROM fstask WHERE id=?"); psql_bind_uint(res, 1, -cr->fileid); if ((row = psql_fetch_int(res))) { if (row[0] == PSYNC_FS_TASK_CREAT) ret = taskid_to_objid(-cr->fileid); else { pdbg_assertw(row[0] == PSYNC_FS_TASK_MODIFY); ret = fileid_to_objid(row[1]); } } else { pdbg_logf(D_WARNING, "found temporary file for path %s but could not find task %lu", path, (unsigned long)(-cr->fileid)); ret = -1; } psql_free(res); return ret; } } checkfolder = !pfs_task_find_rmdir(folder, fspath->name, 0); checkfile = !pfs_task_find_unlink(folder, fspath->name, 0); } if (fspath->folderid < 0) { pmem_free(PMEM_SUBSYS_OTHER, fspath); pdbg_logf(D_NOTICE, "path %s not found in temporary folder", path); return -1; } if (checkfolder) { res = psql_query_nolock( "SELECT id FROM folder WHERE parentfolderid=? AND name=?"); psql_bind_uint(res, 1, fspath->folderid); psql_bind_str(res, 2, fspath->name); if ((row = psql_fetch_int(res))) ret = folderid_to_objid(row[0]); else ret = -1; psql_free(res); if (ret != -1) { pmem_free(PMEM_SUBSYS_OTHER, fspath); return ret; } } if (checkfile) { res = psql_query_nolock( "SELECT id FROM file WHERE parentfolderid=? AND name=?"); psql_bind_uint(res, 1, fspath->folderid); psql_bind_str(res, 2, fspath->name); if ((row = psql_fetch_int(res))) ret = fileid_to_objid(row[0]); else ret = -1; psql_free(res); if (ret != -1) { pmem_free(PMEM_SUBSYS_OTHER, fspath); return ret; } } pmem_free(PMEM_SUBSYS_OTHER, fspath); pdbg_logf(D_NOTICE, "path %s not found", path); return -1; } #define LOCK_AND_LOOKUP() \ do { \ psql_lock(); \ oid = xattr_get_object_id_locked(path); \ if (unlikely(oid == -1)) { \ psql_unlock(); \ return -pdbg_return_const(ENOENT); \ } \ } while (0) #define LOCK_AND_LOOKUPRD() \ do { \ psql_rdlock(); \ oid = xattr_get_object_id_locked(path); \ if (unlikely(oid == -1)) { \ psql_rdunlock(); \ return -pdbg_return_const(ENOENT); \ } \ } while (0) int pfs_xatr_set(const char *path, const char *name, const char *value, size_t size, int flags PFS_XATTR_IGN) { psync_sql_res *res; int64_t oid; int ret; pfs_xatr_set_thread_name(); pdbg_logf(D_NOTICE, "setting attribute %s of %s", name, path); LOCK_AND_LOOKUP(); if (flags & XATTR_CREATE) { res = psql_prepare("INSERT OR IGNORE INTO fsxattr (objectid, " "name, value) VALUES (?, ?, ?)"); psql_bind_uint(res, 1, oid); psql_bind_str(res, 2, name); psql_bind_blob(res, 3, value, size); psql_run_free(res); if (psql_affected()) ret = 0; else ret = -pdbg_return_const(EEXIST); } else if (flags & XATTR_REPLACE) { res = psql_prepare( "UPDATE fsxattr SET value=? WHERE objectid=? AND name=?"); psql_bind_blob(res, 1, value, size); psql_bind_uint(res, 2, oid); psql_bind_str(res, 3, name); psql_run_free(res); if (psql_affected()) ret = 0; else ret = -pdbg_return_const(ENOATTR); } else { res = psql_prepare( "REPLACE INTO fsxattr (objectid, name, value) VALUES (?, ?, ?)"); psql_bind_uint(res, 1, oid); psql_bind_str(res, 2, name); psql_bind_blob(res, 3, value, size); psql_run_free(res); ret = 0; } psql_unlock(); return ret; } int pfs_xatr_get(const char *path, const char *name, char *value, size_t size PFS_XATTR_IGN) { psync_sql_res *res; int64_t oid; int ret; pfs_xatr_set_thread_name(); LOCK_AND_LOOKUPRD(); if (size && value) { psync_variant_row row; const char *str; size_t len; res = psql_query_nolock( "SELECT value FROM fsxattr WHERE objectid=? AND name=?"); psql_bind_uint(res, 1, oid); psql_bind_str(res, 2, name); if ((row = psql_fetch(res))) { str = psync_get_lstring(row[0], &len); if (size >= len) { pdbg_logf(D_NOTICE, "returning attribute %s of %s", name, path); memcpy(value, str, len); ret = len; } else { pdbg_logf(D_NOTICE, "buffer too small for attribute %s of %s", name, path); ret = -ERANGE; } } else { ret = -ENOATTR; // pdbg_logf(D_NOTICE, "attribute %s not found for %s", name, path); } psql_free(res); } else { psync_uint_row row; res = psql_query_nolock( "SELECT LENGTH(value) FROM fsxattr WHERE objectid=? AND name=?"); psql_bind_uint(res, 1, oid); psql_bind_str(res, 2, name); if ((row = psql_fetch_int(res))) { ret = row[0]; pdbg_logf(D_NOTICE, "returning length of attribute %s of %s = %d", name, path, ret); } else { ret = -ENOATTR; // pdbg_logf(D_NOTICE, "attribute %s not found for %s", name, path); } psql_free(res); } psql_rdunlock(); return ret; } int pfs_xatr_list(const char *path, char *list, size_t size) { psync_sql_res *res; int64_t oid; const char *str; size_t len; int ret; pfs_xatr_set_thread_name(); LOCK_AND_LOOKUPRD(); if (size && list) { psync_variant_row row; ret = 0; res = psql_query_nolock("SELECT name FROM fsxattr WHERE objectid=?"); psql_bind_uint(res, 1, oid); while ((row = psql_fetch(res))) { str = psync_get_lstring(row[0], &len); len++; if (ret + len > size) { ret = -ERANGE; break; } memcpy(list + ret, str, len); ret += len; } psql_free(res); pdbg_logf(D_NOTICE, "returning list of attributes of %s = %d", path, ret); } else { psync_uint_row row; res = psql_query_nolock( "SELECT SUM(LENGTH(name)+1) FROM fsxattr WHERE objectid=?"); psql_bind_uint(res, 1, oid); if ((row = psql_fetch_int(res))) ret = row[0]; else ret = 0; psql_free(res); pdbg_logf(D_NOTICE, "returning length of attributes of %s = %d", path, ret); } psql_rdunlock(); return ret; } int pfs_xatr_remove(const char *path, const char *name) { psync_sql_res *res; int64_t oid; uint32_t aff; pfs_xatr_set_thread_name(); LOCK_AND_LOOKUP(); res = psql_prepare( "DELETE FROM fsxattr WHERE objectid=? AND name=?"); psql_bind_uint(res, 1, oid); psql_bind_str(res, 2, name); psql_run_free(res); aff = psql_affected(); psql_unlock(); if (aff) { pdbg_logf(D_NOTICE, "attribute %s deleted for %s", name, path); return 0; } else { pdbg_logf(D_NOTICE, "attribute %s not found for %s", name, path); return -ENOATTR; } }