pcloudcc-lneely/pclsync/pfs.c

3748 lines
119 KiB
C

/*
Copyright (c) 2013-2016 Anton Titov.
Copyright (c) 2013-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.
*/
#define FUSE_USE_VERSION 26
#define _FILE_OFFSET_BITS 64
#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 "pcloudcrypto.h"
#include "pcompat.h"
#include "pfolder.h"
#include "pfs.h"
#include "pfscrypto.h"
#include "pfsfolder.h"
#include "pfsstatic.h"
#include "pfstasks.h"
#include "pfsupload.h"
#include "pfsxattr.h"
#include "plibs.h"
#include "pnetlibs.h"
#include "ppagecache.h"
#include "psettings.h"
#include "pssl.h"
#include "pstatus.h"
#include "ptimer.h"
#include <errno.h>
#include <fuse.h>
#include <pthread.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#ifndef FUSE_STAT
#define FUSE_STAT stat
#endif
#ifndef HAS_FUSE_OFF_T
typedef off_t fuse_off_t;
#endif
#include <signal.h>
#if defined(P_OS_LINUX)
#include <sys/mount.h>
#endif
#if IS_DEBUG
#define psync_fs_set_thread_name() \
do { \
psync_thread_name = __FUNCTION__; \
} while (0)
#else
#define psync_fs_set_thread_name() \
do { \
} while (0)
#endif
#define fh_to_openfile(x) ((psync_openfile_t *)((uintptr_t)x))
#define openfile_to_fh(x) ((uintptr_t)x)
#define FS_BLOCK_SIZE 4096
// #define FS_MAX_WRITE 16*1024*1024 // unused, maybe important later
#if defined(P_OS_LINUX)
#define PSYNC_FS_ERR_CRYPTO_EXPIRED EROFS
#define PSYNC_FS_ERR_MOVE_ACROSS_CRYPTO EXDEV
#else
#define PSYNC_FS_ERR_CRYPTO_EXPIRED EIO
#define PSYNC_FS_ERR_MOVE_ACROSS_CRYPTO EXDEV
#endif
static struct fuse_chan *psync_fuse_channel = NULL;
static struct fuse *psync_fuse = NULL;
static char *psync_current_mountpoint = NULL;
static psync_generic_callback_t psync_start_callback = NULL;
char *psync_fake_prefix = NULL;
size_t psync_fake_prefix_len = 0;
static int64_t psync_fake_fileid = INT64_MIN;
static pthread_mutex_t start_mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t start_cond = PTHREAD_COND_INITIALIZER;
static int started = 0;
static int initonce = 0;
static int waitingforlogin = 0;
static uid_t myuid = 0;
static gid_t mygid = 0;
extern int errno;
static psync_tree *openfiles = PSYNC_TREE_EMPTY;
static int psync_fs_ftruncate_of_locked(psync_openfile_t *of, fuse_off_t size);
static void delete_log_files(psync_openfile_t *of) {
char fileidhex[sizeof(psync_fsfileid_t) * 2 + 2];
const char *cachepath;
char *filename;
psync_fsfileid_t fileid;
cachepath = psync_setting_get_string(_PS(fscachepath));
fileid = -of->fileid;
psync_binhex(fileidhex, &fileid, sizeof(psync_fsfileid_t));
fileidhex[sizeof(psync_fsfileid_t)] = 'l';
fileidhex[sizeof(psync_fsfileid_t) + 1] = 0;
filename =
psync_strcat(cachepath, PSYNC_DIRECTORY_SEPARATOR, fileidhex, NULL);
psync_file_delete(filename);
psync_free(filename);
fileidhex[sizeof(psync_fsfileid_t)] = 'f';
filename =
psync_strcat(cachepath, PSYNC_DIRECTORY_SEPARATOR, fileidhex, NULL);
psync_file_delete(filename);
psync_free(filename);
}
int psync_fs_update_openfile(uint64_t taskid, uint64_t writeid,
psync_fileid_t newfileid, uint64_t hash,
uint64_t size, time_t ctime) {
psync_sql_res *res;
psync_uint_row row;
psync_openfile_t *fl;
psync_tree *tr;
psync_fsfileid_t fileid;
int64_t d;
int ret;
fileid = -(psync_fsfileid_t)taskid;
psync_sql_lock();
tr = openfiles;
while (tr) {
d = fileid - psync_tree_element(tr, psync_openfile_t, tree)->fileid;
if (d < 0)
tr = tr->left;
else if (d > 0)
tr = tr->right;
else {
fl = psync_tree_element(tr, psync_openfile_t, tree);
psync_fs_lock_file(fl);
if (fl->writeid == writeid) {
if (fl->encrypted) {
if (fl->logfile) {
psync_file_close(fl->logfile);
fl->logfile = INVALID_HANDLE_VALUE;
}
delete_log_files(fl);
if (fl->authenticatedints) {
psync_interval_tree_free(fl->authenticatedints);
fl->authenticatedints = NULL;
}
size = psync_fs_crypto_plain_size(size);
}
debug(D_NOTICE, "updating fileid %ld to %lu, hash %lu size %lu",
(long)fileid, (unsigned long)newfileid, (unsigned long)hash,
(unsigned long)size);
fl->fileid = newfileid;
fl->remotefileid = newfileid;
fl->hash = hash;
fl->modified = 0;
fl->newfile = 0;
fl->currentsize = size;
fl->initialsize = size;
fl->releasedforupload = 0;
fl->origctime = ctime;
if (fl->datafile != INVALID_HANDLE_VALUE) {
psync_file_close(fl->datafile);
fl->datafile = INVALID_HANDLE_VALUE;
}
if (fl->indexfile != INVALID_HANDLE_VALUE) {
psync_file_close(fl->indexfile);
fl->indexfile = INVALID_HANDLE_VALUE;
}
psync_tree_del(&openfiles, &fl->tree);
tr = openfiles;
d = -1;
while (tr) {
d = newfileid -
psync_tree_element(tr, psync_openfile_t, tree)->fileid;
if (d < 0) {
if (tr->left)
tr = tr->left;
else
break;
} else if (d > 0) {
if (tr->right)
tr = tr->right;
else
break;
} else {
debug(D_BUG, "found already open file %lu, should not happen",
(unsigned long)newfileid);
break;
}
}
if (d < 0)
psync_tree_add_before(&openfiles, tr, &fl->tree);
else
psync_tree_add_after(&openfiles, tr, &fl->tree);
ret = 0;
} else {
debug(D_NOTICE, "writeid of fileid %ld (%s) differs %lu!=%lu",
(long)fileid, fl->currentname, (unsigned long)fl->writeid,
(unsigned long)writeid);
if (fl->newfile) {
res = psync_sql_prep_statement(
"REPLACE INTO fstaskfileid (fstaskid, fileid) VALUES (?, ?)");
psync_sql_bind_uint(res, 1, taskid);
psync_sql_bind_uint(res, 2, newfileid);
psync_sql_run_free(res);
}
ret = -1;
}
pthread_mutex_unlock(&fl->mutex);
psync_sql_unlock();
return ret;
}
}
res = psync_sql_query("SELECT int1 FROM fstask WHERE id=?");
psync_sql_bind_uint(res, 1, taskid);
if ((row = psync_sql_fetch_rowint(res)) && row[0] == writeid)
ret = 0;
else {
if (row)
debug(D_NOTICE, "writeid of fileid %ld differs %lu!=%lu", (long)fileid,
(unsigned long)row[0], (unsigned long)writeid);
ret = -1;
}
psync_sql_free_result(res);
psync_sql_unlock();
return ret;
}
/*void psync_fs_uploading_openfile(uint64_t taskid){
psync_openfile_t *fl;
psync_tree *tr;
psync_fsfileid_t fileid;
int64_t d;
fileid=-taskid;
psync_sql_lock();
tr=openfiles;
while (tr){
d=fileid-psync_tree_element(tr, psync_openfile_t, tree)->fileid;
if (d<0)
tr=tr->left;
else if (d>0)
tr=tr->right;
else{
fl=psync_tree_element(tr, psync_openfile_t, tree);
psync_fs_lock_file(fl);
fl->uploading=1;
pthread_mutex_unlock(&fl->mutex);
break;
}
}
psync_sql_unlock();
}*/
int psync_fs_rename_openfile_locked(psync_fsfileid_t fileid,
psync_fsfolderid_t folderid,
const char *name) {
psync_openfile_t *fl;
psync_tree *tr;
int64_t d;
tr = openfiles;
while (tr) {
d = fileid - psync_tree_element(tr, psync_openfile_t, tree)->fileid;
if (d < 0)
tr = tr->left;
else if (d > 0)
tr = tr->right;
else {
fl = psync_tree_element(tr, psync_openfile_t, tree);
psync_fs_lock_file(fl);
if (fl->currentfolder->folderid != folderid) {
psync_fstask_release_folder_tasks_locked(fl->currentfolder);
fl->currentfolder =
psync_fstask_get_or_create_folder_tasks_locked(folderid);
}
psync_free(fl->currentname);
fl->currentname = psync_strdup(name);
pthread_mutex_unlock(&fl->mutex);
return 1;
}
}
return 0;
}
void psync_fs_mark_openfile_deleted(uint64_t taskid) {
psync_sql_res *res;
psync_openfile_t *fl;
psync_tree *tr;
int64_t d;
psync_fsfileid_t fileid;
fileid = -(psync_fsfileid_t)taskid;
psync_sql_lock();
tr = openfiles;
while (tr) {
d = fileid - psync_tree_element(tr, psync_openfile_t, tree)->fileid;
if (d < 0)
tr = tr->left;
else if (d > 0)
tr = tr->right;
else {
fl = psync_tree_element(tr, psync_openfile_t, tree);
debug(D_NOTICE, "file being deleted %s is still open, marking as deleted",
fl->currentname);
psync_fs_lock_file(fl);
fl->deleted = 1;
pthread_mutex_unlock(&fl->mutex);
res = psync_sql_prep_statement("UPDATE fstask SET status=12 WHERE id=?");
psync_sql_bind_uint(res, 1, taskid);
psync_sql_run_free(res);
break;
}
}
psync_sql_unlock();
}
int64_t psync_fs_get_file_writeid(uint64_t taskid) {
psync_openfile_t *fl;
psync_tree *tr;
psync_sql_res *res;
psync_uint_row row;
psync_fsfileid_t fileid;
int64_t d;
fileid = -(psync_fsfileid_t)taskid;
psync_sql_rdlock();
tr = openfiles;
while (tr) {
d = fileid - psync_tree_element(tr, psync_openfile_t, tree)->fileid;
if (d < 0)
tr = tr->left;
else if (d > 0)
tr = tr->right;
else {
fl = psync_tree_element(tr, psync_openfile_t, tree);
psync_fs_lock_file(fl);
d = fl->writeid;
pthread_mutex_unlock(&fl->mutex);
psync_sql_rdunlock();
return d;
}
}
res = psync_sql_query_nolock("SELECT int1 FROM fstask WHERE id=?");
psync_sql_bind_uint(res, 1, taskid);
if ((row = psync_sql_fetch_rowint(res)))
d = row[0];
else
d = -1;
psync_sql_free_result(res);
psync_sql_rdunlock();
return d;
}
void psync_fs_update_openfile_fileid_locked(psync_openfile_t *of,
psync_fsfileid_t fileid) {
psync_tree *tr;
int64_t d;
assertw(of->fileid != fileid);
psync_tree_del(&openfiles, &of->tree);
of->fileid = fileid;
tr = openfiles;
if (tr)
while (1) {
d = fileid - psync_tree_element(tr, psync_openfile_t, tree)->fileid;
if (d < 0) {
if (tr->left)
tr = tr->left;
else {
tr->left = &of->tree;
break;
}
} else {
assertw(d > 0);
if (tr->right)
tr = tr->right;
else {
tr->right = &of->tree;
break;
}
}
}
else
openfiles = &of->tree;
psync_tree_added_at(&openfiles, tr, &of->tree);
}
#define folderid_to_inode(folderid) ((folderid) * 3)
#define fileid_to_inode(fileid) ((fileid) * 3 + 1)
#define taskid_to_inode(taskid) ((taskid) * 3 + 2)
static void psync_row_to_folder_stat(psync_variant_row row,
struct FUSE_STAT *stbuf) {
psync_folderid_t folderid;
uint64_t mtime;
psync_fstask_folder_t *folder;
folderid = psync_get_number(row[0]);
mtime = psync_get_number(row[3]);
folder = psync_fstask_get_folder_tasks_rdlocked(folderid);
if (folder && folder->mtime)
mtime = folder->mtime;
memset(stbuf, 0, sizeof(struct FUSE_STAT));
stbuf->st_ino = folderid_to_inode(folderid);
#ifdef FUSE_STAT_HAS_BIRTHTIME
stbuf->st_birthtime = psync_get_number(row[2]);
#endif
stbuf->st_ctime = mtime;
stbuf->st_mtime = mtime;
stbuf->st_atime = mtime;
stbuf->st_mode = S_IFDIR | 0755;
stbuf->st_nlink = psync_get_number(row[4]) + 2;
stbuf->st_size = FS_BLOCK_SIZE;
stbuf->st_blocks = 1;
stbuf->st_blksize = FS_BLOCK_SIZE;
stbuf->st_uid = myuid;
stbuf->st_gid = mygid;
}
static void psync_row_to_file_stat(psync_variant_row row,
struct FUSE_STAT *stbuf, uint32_t flags) {
uint64_t size;
stbuf->st_ino = fileid_to_inode(psync_get_number(row[4]));
size = psync_get_number(row[1]);
if (flags & PSYNC_FOLDER_FLAG_ENCRYPTED)
size = psync_fs_crypto_plain_size(size);
memset(stbuf, 0, sizeof(struct FUSE_STAT));
#ifdef FUSE_STAT_HAS_BIRTHTIME
stbuf->st_birthtime = psync_get_number(row[2]);
#endif
stbuf->st_ctime = psync_get_number(row[3]);
stbuf->st_mtime = stbuf->st_ctime;
stbuf->st_atime = stbuf->st_ctime;
stbuf->st_mode = S_IFREG | 0644;
stbuf->st_nlink = 1;
stbuf->st_size = size;
stbuf->st_blocks = (size + 511) / 512;
stbuf->st_blksize = FS_BLOCK_SIZE;
stbuf->st_uid = myuid;
stbuf->st_gid = mygid;
}
static void psync_mkdir_to_folder_stat(psync_fstask_mkdir_t *mk,
struct FUSE_STAT *stbuf) {
uint64_t mtime;
psync_fstask_folder_t *folder;
folder = psync_fstask_get_folder_tasks_rdlocked(mk->folderid);
if (folder && folder->mtime)
mtime = folder->mtime;
else
mtime = mk->mtime;
memset(stbuf, 0, sizeof(struct FUSE_STAT));
if (mk->folderid >= 0)
stbuf->st_ino = folderid_to_inode(mk->folderid);
else
stbuf->st_ino = taskid_to_inode(-mk->folderid);
#ifdef FUSE_STAT_HAS_BIRTHTIME
stbuf->st_birthtime = mk->ctime;
#endif
stbuf->st_ctime = mtime;
stbuf->st_mtime = mtime;
stbuf->st_atime = mtime;
stbuf->st_mode = S_IFDIR | 0755;
stbuf->st_nlink = mk->subdircnt + 2;
stbuf->st_size = FS_BLOCK_SIZE;
stbuf->st_blocks = 1;
stbuf->st_blksize = FS_BLOCK_SIZE;
stbuf->st_uid = myuid;
stbuf->st_gid = mygid;
}
static int psync_creat_db_to_file_stat(psync_fileid_t fileid,
struct FUSE_STAT *stbuf,
uint32_t flags) {
psync_sql_res *res;
psync_variant_row row;
res = psync_sql_query_rdlock("SELECT name, size, ctime, mtime, id, "
"parentfolderid FROM file WHERE id=?");
psync_sql_bind_uint(res, 1, fileid);
if ((row = psync_sql_fetch_row(res)))
psync_row_to_file_stat(row, stbuf, flags);
else
debug(D_NOTICE, "fileid %lu not found in database", (unsigned long)fileid);
psync_sql_free_result(res);
return row ? 0 : -1;
}
static int psync_creat_stat_fake_file(struct FUSE_STAT *stbuf) {
time_t ctime;
memset(stbuf, 0, sizeof(struct FUSE_STAT));
ctime = psync_timer_time();
#ifdef FUSE_STAT_HAS_BIRTHTIME
stbuf->st_birthtime = ctime;
#endif
stbuf->st_ctime = ctime;
stbuf->st_mtime = ctime;
stbuf->st_atime = ctime;
stbuf->st_mode = S_IFREG | 0644;
stbuf->st_nlink = 1;
stbuf->st_size = 0;
stbuf->st_blocks = 0;
stbuf->st_blksize = FS_BLOCK_SIZE;
stbuf->st_uid = myuid;
stbuf->st_gid = mygid;
return 0;
}
static int fill_stat_from_open_file(psync_fsfileid_t fileid,
struct FUSE_STAT *stbuf) {
psync_openfile_t *fl;
psync_tree *tr;
psync_stat_t st;
int64_t d;
psync_sql_rdlock();
tr = openfiles;
while (tr) {
d = fileid - psync_tree_element(tr, psync_openfile_t, tree)->fileid;
if (d < 0)
tr = tr->left;
else if (d > 0)
tr = tr->right;
else {
fl = psync_tree_element(tr, psync_openfile_t, tree);
psync_fs_lock_file(fl);
stbuf->st_size = fl->currentsize;
debug(D_NOTICE, "found open file with size %lu",
(unsigned long)fl->currentsize);
if (!psync_fstat(fl->logfile, &st))
stbuf->st_mtime = psync_stat_mtime(&st);
pthread_mutex_unlock(&fl->mutex);
psync_sql_rdunlock();
return 1;
}
}
psync_sql_rdunlock();
return 0;
}
static int psync_creat_local_to_file_stat(psync_fstask_creat_t *cr,
struct FUSE_STAT *stbuf,
uint32_t folderflags) {
psync_stat_t st;
psync_fsfileid_t fileid;
uint64_t size;
const char *cachepath;
char *filename;
psync_openfile_t *fl;
psync_tree *tr;
int64_t d;
// psync_file_t fd;
char fileidhex[sizeof(psync_fsfileid_t) * 2 + 2];
int stret;
if (unlikely(psync_fs_need_per_folder_refresh_const() &&
cr->fileid < psync_fake_fileid))
return psync_creat_stat_fake_file(stbuf);
fl = NULL;
fileid = -cr->fileid;
psync_sql_rdlock();
tr = openfiles;
while (tr) {
d = cr->fileid - psync_tree_element(tr, psync_openfile_t, tree)->fileid;
if (d < 0)
tr = tr->left;
else if (d > 0)
tr = tr->right;
else {
fl = psync_tree_element(tr, psync_openfile_t, tree);
psync_fs_lock_file(fl);
break;
}
}
psync_sql_rdunlock();
if (fl && fl->datafile != INVALID_HANDLE_VALUE) {
stret = psync_fstat(fl->datafile, &st);
pthread_mutex_unlock(&fl->mutex);
if (stret)
debug(D_NOTICE, "could not stat open file %ld", (long)cr->fileid);
else
debug(D_NOTICE, "got stat from open file %ld", (long)cr->fileid);
} else {
if (fl)
pthread_mutex_unlock(&fl->mutex);
psync_binhex(fileidhex, &fileid, sizeof(psync_fsfileid_t));
fileidhex[sizeof(psync_fsfileid_t)] = 'd';
fileidhex[sizeof(psync_fsfileid_t) + 1] = 0;
cachepath = psync_setting_get_string(_PS(fscachepath));
filename =
psync_strcat(cachepath, PSYNC_DIRECTORY_SEPARATOR, fileidhex, NULL);
stret = psync_stat(filename, &st);
if (stret)
debug(D_NOTICE, "could not stat file %s", filename);
psync_free(filename);
}
if (stret)
return -1;
/* if (cr->newfile)
osize=0;
else{
fileidhex[sizeof(psync_fsfileid_t)]='i';
filename=psync_strcat(cachepath, PSYNC_DIRECTORY_SEPARATOR, fileidhex,
NULL); fd=psync_file_open(filename, P_O_RDONLY, 0); psync_free(filename); if
(fd==INVALID_HANDLE_VALUE) return -EIO; stret=psync_file_pread(fd, &osize,
sizeof(osize), offsetof(index_header, copyfromoriginal));
psync_file_close(fd);
if (stret!=sizeof(osize))
return -EIO;
}*/
memset(stbuf, 0, sizeof(struct FUSE_STAT));
stbuf->st_ino = taskid_to_inode(fileid);
#ifdef FUSE_STAT_HAS_BIRTHTIME
stbuf->st_birthtime = psync_stat_birthtime(&st);
#endif
stbuf->st_mtime = psync_stat_mtime(&st);
stbuf->st_ctime = stbuf->st_mtime;
stbuf->st_atime = stbuf->st_mtime;
stbuf->st_mode = S_IFREG | 0644;
stbuf->st_nlink = 1;
if (folderflags & PSYNC_FOLDER_FLAG_ENCRYPTED) {
if (fill_stat_from_open_file(cr->fileid, stbuf))
size = stbuf->st_size;
else {
size = psync_fs_crypto_plain_size(psync_stat_size(&st));
stbuf->st_size = size;
}
} else {
size = psync_stat_size(&st);
stbuf->st_size = size;
}
stbuf->st_blocks = (size + 511) / 512;
stbuf->st_blksize = FS_BLOCK_SIZE;
stbuf->st_uid = myuid;
stbuf->st_gid = mygid;
return 0;
}
static int psync_creat_static_to_file_stat(psync_fstask_creat_t *cr,
struct FUSE_STAT *stbuf,
uint32_t folderflags) {
psync_fstask_local_creat_t *lc;
lc = psync_fstask_creat_get_local(cr);
memset(stbuf, 0, sizeof(struct FUSE_STAT));
stbuf->st_ino = cr->taskid;
#ifdef FUSE_STAT_HAS_BIRTHTIME
stbuf->st_birthtime = lc->ctime;
#endif
stbuf->st_ctime = lc->ctime;
stbuf->st_mtime = lc->ctime;
stbuf->st_atime = lc->ctime;
stbuf->st_mode = S_IFREG | 0644;
stbuf->st_nlink = 1;
stbuf->st_size = lc->datalen;
stbuf->st_blocks = (lc->datalen + 511) / 512;
stbuf->st_blksize = FS_BLOCK_SIZE;
stbuf->st_uid = myuid;
stbuf->st_gid = mygid;
return 0;
}
static int psync_creat_to_file_stat(psync_fstask_creat_t *cr,
struct FUSE_STAT *stbuf,
uint32_t folderflags) {
debug(D_NOTICE, "getting stat from creat for file %s fileid %ld taskid %lu",
cr->name, (long)cr->fileid, (unsigned long)cr->taskid);
if (cr->fileid > 0)
return psync_creat_db_to_file_stat(cr->fileid, stbuf, folderflags);
else if (cr->fileid < 0)
return psync_creat_local_to_file_stat(cr, stbuf, folderflags);
else
return psync_creat_static_to_file_stat(cr, stbuf, folderflags);
}
int psync_fs_crypto_err_to_errno(int cryptoerr) {
switch (cryptoerr) {
case PSYNC_CRYPTO_NOT_STARTED:
return EACCES;
case PSYNC_CRYPTO_RSA_ERROR:
return EIO;
case PSYNC_CRYPTO_FOLDER_NOT_FOUND:
return ENOENT;
case PSYNC_CRYPTO_FILE_NOT_FOUND:
return ENOENT;
case PSYNC_CRYPTO_INVALID_KEY:
return EIO;
case PSYNC_CRYPTO_CANT_CONNECT:
return ENOTCONN;
case PSYNC_CRYPTO_FOLDER_NOT_ENCRYPTED:
return EINVAL;
case PSYNC_CRYPTO_INTERNAL_ERROR:
return EINVAL;
default:
return EINVAL;
}
}
static int psync_fs_getrootattr(struct FUSE_STAT *stbuf) {
psync_sql_res *res;
psync_variant_row row;
res = psync_sql_query_rdlock(
"SELECT 0, 0, IFNULL(s.value, 1414766136)*1, f.mtime, f.subdircnt FROM "
"folder f LEFT JOIN setting s ON s.id='registered' WHERE f.id=0");
if ((row = psync_sql_fetch_row(res)))
psync_row_to_folder_stat(row, stbuf);
psync_sql_free_result(res);
return 0;
}
#define CHECK_LOGIN_LOCKED() \
do { \
if (unlikely(waitingforlogin)) { \
psync_sql_unlock(); \
debug(D_NOTICE, "returning EACCES for not logged in"); \
return -EACCES; \
} \
} while (0)
#define CHECK_LOGIN_RDLOCKED() \
do { \
if (unlikely(waitingforlogin)) { \
psync_sql_rdunlock(); \
debug(D_NOTICE, "returning EACCES for not logged in"); \
return -EACCES; \
} \
} while (0)
static int psync_fs_getattr(const char *path, struct FUSE_STAT *stbuf) {
psync_sql_res *res;
psync_variant_row row;
psync_fspath_t *fpath;
psync_fstask_folder_t *folder;
psync_fstask_creat_t *cr;
int crr;
psync_fs_set_thread_name();
// debug(D_NOTICE, "getattr %s", path);
if (path[1] == 0 && path[0] == '/')
return psync_fs_getrootattr(stbuf);
psync_sql_rdlock();
CHECK_LOGIN_RDLOCKED();
fpath = psync_fsfolder_resolve_path(path);
if (!fpath) {
psync_sql_rdunlock();
crr = psync_fsfolder_crypto_error();
if (crr) {
crr = -psync_fs_crypto_err_to_errno(crr);
debug(D_NOTICE, "got crypto error for %s, returning %d", path, crr);
return crr;
} else {
debug(D_NOTICE, "could not find path component of %s, returning ENOENT",
path);
return -ENOENT;
}
}
folder = psync_fstask_get_folder_tasks_rdlocked(fpath->folderid);
if (folder) {
psync_fstask_mkdir_t *mk;
mk = psync_fstask_find_mkdir(folder, fpath->name, 0);
if (mk) {
if (mk->flags & PSYNC_FOLDER_FLAG_INVISIBLE) {
psync_sql_rdunlock();
psync_free(fpath);
return -ENOENT;
}
psync_mkdir_to_folder_stat(mk, stbuf);
psync_sql_rdunlock();
psync_free(fpath);
return 0;
}
}
if (!folder || !psync_fstask_find_rmdir(folder, fpath->name, 0)) {
res = psync_sql_query_nolock(
"SELECT id, permissions, ctime, mtime, subdircnt FROM folder WHERE "
"parentfolderid=? AND name=?");
psync_sql_bind_uint(res, 1, fpath->folderid);
psync_sql_bind_string(res, 2, fpath->name);
if ((row = psync_sql_fetch_row(res)))
psync_row_to_folder_stat(row, stbuf);
psync_sql_free_result(res);
if (row) {
psync_sql_rdunlock();
psync_free(fpath);
return 0;
}
}
res = psync_sql_query_nolock("SELECT name, size, ctime, mtime, id FROM file "
"WHERE parentfolderid=? AND name=?");
psync_sql_bind_uint(res, 1, fpath->folderid);
psync_sql_bind_string(res, 2, fpath->name);
if ((row = psync_sql_fetch_row(res)))
psync_row_to_file_stat(row, stbuf, fpath->flags);
psync_sql_free_result(res);
if (folder) {
if (psync_fstask_find_unlink(folder, fpath->name, 0))
row = NULL;
if (!row && (cr = psync_fstask_find_creat(folder, fpath->name, 0)))
crr = psync_creat_to_file_stat(cr, stbuf, fpath->flags);
else
crr = -1;
} else
crr = -1;
psync_sql_rdunlock();
psync_free(fpath);
if (row || !crr)
return 0;
debug(D_NOTICE, "returning ENOENT for %s", path);
return -ENOENT;
}
static int filler_decoded(psync_crypto_aes256_text_decoder_t dec,
fuse_fill_dir_t filler, void *buf, const char *name,
struct FUSE_STAT *st, fuse_off_t off) {
if (dec) {
char *namedec;
int ret;
namedec = psync_cloud_crypto_decode_filename(dec, name);
if (!namedec)
return 0;
ret = filler(buf, namedec, st, off);
psync_free(namedec);
return ret;
} else
return filler(buf, name, st, off);
}
static int psync_fs_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
fuse_off_t offset, struct fuse_file_info *fi) {
psync_sql_res *res;
psync_variant_row row;
psync_fsfolderid_t folderid;
psync_fstask_folder_t *folder;
psync_tree *trel;
const char *name;
psync_crypto_aes256_text_decoder_t dec;
uint32_t flags;
size_t namelen;
struct FUSE_STAT st;
psync_fs_set_thread_name();
debug(D_NOTICE, "readdir %s", path);
psync_sql_rdlock();
CHECK_LOGIN_RDLOCKED();
folderid = psync_fsfolderid_by_path(path, &flags);
if (unlikely_log(folderid == PSYNC_INVALID_FSFOLDERID)) {
psync_sql_rdunlock();
if (psync_fsfolder_crypto_error())
return PRINT_RETURN(
-psync_fs_crypto_err_to_errno(psync_fsfolder_crypto_error()));
else
return -PRINT_RETURN_CONST(ENOENT);
}
if (flags & PSYNC_FOLDER_FLAG_ENCRYPTED) {
dec = psync_cloud_crypto_get_folder_decoder(folderid);
if (psync_crypto_is_error(dec)) {
psync_sql_rdunlock();
return PRINT_RETURN(
-psync_fs_crypto_err_to_errno(psync_crypto_to_error(dec)));
}
} else
dec = NULL;
filler(buf, ".", NULL, 0);
if (folderid != 0)
filler(buf, "..", NULL, 0);
folder = psync_fstask_get_folder_tasks_rdlocked(folderid);
if (folderid >= 0) {
res = psync_sql_query_nolock(
"SELECT id, permissions, ctime, mtime, subdircnt, name FROM folder "
"WHERE parentfolderid=?");
psync_sql_bind_uint(res, 1, folderid);
while ((row = psync_sql_fetch_row(res))) {
name = psync_get_lstring(row[5], &namelen);
#if defined(FS_MAX_ACCEPTABLE_FILENAME_LEN)
if (unlikely_log(namelen > FS_MAX_ACCEPTABLE_FILENAME_LEN))
continue;
#endif
if (!name || !name[0])
continue;
if (folder && (psync_fstask_find_rmdir(folder, name, 0) ||
psync_fstask_find_mkdir(folder, name, 0)))
continue;
psync_row_to_folder_stat(row, &st);
filler_decoded(dec, filler, buf, name, &st, 0);
}
psync_sql_free_result(res);
res = psync_sql_query_nolock(
"SELECT name, size, ctime, mtime, id FROM file WHERE parentfolderid=?");
psync_sql_bind_uint(res, 1, folderid);
while ((row = psync_sql_fetch_row(res))) {
name = psync_get_lstring(row[0], &namelen);
#if defined(FS_MAX_ACCEPTABLE_FILENAME_LEN)
if (unlikely_log(namelen > FS_MAX_ACCEPTABLE_FILENAME_LEN))
continue;
#endif
if (!name || !name[0])
continue;
if (folder && psync_fstask_find_unlink(folder, name, 0))
continue;
psync_row_to_file_stat(row, &st, flags);
filler_decoded(dec, filler, buf, name, &st, 0);
}
psync_sql_free_result(res);
}
if (folder) {
psync_tree_for_each(trel, folder->mkdirs) {
#if defined(FS_MAX_ACCEPTABLE_FILENAME_LEN)
if (unlikely_log(
strlen(
psync_tree_element(trel, psync_fstask_mkdir_t, tree)->name) >
FS_MAX_ACCEPTABLE_FILENAME_LEN))
continue;
#endif
if (psync_tree_element(trel, psync_fstask_mkdir_t, tree)->flags &
PSYNC_FOLDER_FLAG_INVISIBLE)
continue;
psync_mkdir_to_folder_stat(
psync_tree_element(trel, psync_fstask_mkdir_t, tree), &st);
filler_decoded(dec, filler, buf,
psync_tree_element(trel, psync_fstask_mkdir_t, tree)->name,
&st, 0);
}
psync_tree_for_each(trel, folder->creats) {
#if defined(FS_MAX_ACCEPTABLE_FILENAME_LEN)
if (unlikely_log(
strlen(
psync_tree_element(trel, psync_fstask_creat_t, tree)->name) >
FS_MAX_ACCEPTABLE_FILENAME_LEN))
continue;
#endif
if (!psync_creat_to_file_stat(
psync_tree_element(trel, psync_fstask_creat_t, tree), &st, flags))
filler_decoded(
dec, filler, buf,
psync_tree_element(trel, psync_fstask_creat_t, tree)->name, &st, 0);
}
}
psync_sql_rdunlock();
if (dec)
psync_cloud_crypto_release_folder_decoder(folderid, dec);
return PRINT_RETURN(0);
}
static psync_openfile_t *
psync_fs_create_file(psync_fsfileid_t fileid, psync_fsfileid_t remotefileid,
uint64_t size, uint64_t hash, int lock, uint32_t writeid,
psync_fstask_folder_t *folder, const char *name,
psync_crypto_aes256_sector_encoder_decoder_t encoder) {
psync_openfile_t *fl;
psync_tree *tr;
int64_t d;
psync_sql_lock();
tr = openfiles;
d = -1;
while (tr) {
d = fileid - psync_tree_element(tr, psync_openfile_t, tree)->fileid;
if (d < 0) {
if (tr->left)
tr = tr->left;
else
break;
} else if (d > 0) {
if (tr->right)
tr = tr->right;
else
break;
} else {
fl = psync_tree_element(tr, psync_openfile_t, tree);
if (lock) {
psync_fs_lock_file(fl);
psync_fs_inc_of_refcnt_locked(fl);
} else
psync_fs_inc_of_refcnt(fl);
assertw(fl->currentfolder == folder);
assertw(!strcmp(fl->currentname, name));
psync_fstask_release_folder_tasks_locked(folder);
psync_sql_unlock();
if (encoder != PSYNC_CRYPTO_INVALID_ENCODER &&
encoder != PSYNC_CRYPTO_UNLOADED_SECTOR_ENCODER)
psync_cloud_crypto_release_file_encoder(fileid, hash, encoder);
debug(D_NOTICE, "found open file %ld, refcnt %u, currentsize=%lu",
(long int)fileid, (unsigned)fl->refcnt,
(unsigned long)fl->currentsize);
return fl;
}
}
if (encoder == PSYNC_CRYPTO_INVALID_ENCODER) {
fl = (psync_openfile_t *)psync_malloc(offsetof(psync_openfile_t, encoder));
memset(fl, 0, offsetof(psync_openfile_t, encoder));
} else {
fl = psync_new(psync_openfile_t);
memset(fl, 0, sizeof(psync_openfile_t));
size = psync_fs_crypto_plain_size(size);
}
if (d < 0)
psync_tree_add_before(&openfiles, tr, &fl->tree);
else
psync_tree_add_after(&openfiles, tr, &fl->tree);
#if IS_DEBUG
{
pthread_mutexattr_t mattr;
pthread_mutexattr_init(&mattr);
pthread_mutexattr_settype(&mattr, PTHREAD_MUTEX_ERRORCHECK);
pthread_mutex_init(&fl->mutex, &mattr);
pthread_mutexattr_destroy(&mattr);
}
#else
pthread_mutex_init(&fl->mutex, NULL);
#endif
fl->currentfolder = folder;
fl->currentname = psync_strdup(name);
fl->fileid = fileid;
fl->remotefileid = remotefileid;
fl->hash = hash;
fl->initialsize = size;
fl->currentsize = size;
fl->writeid = writeid;
fl->datafile = INVALID_HANDLE_VALUE;
fl->indexfile = INVALID_HANDLE_VALUE;
fl->writetimer = PSYNC_INVALID_TIMER;
fl->refcnt = 1;
fl->modified = fileid < 0 ? 1 : 0;
if (encoder != PSYNC_CRYPTO_INVALID_ENCODER) {
fl->encrypted = 1;
fl->encoder = encoder;
fl->logfile = INVALID_HANDLE_VALUE;
}
if (lock)
psync_fs_lock_file(fl);
psync_sql_unlock();
return fl;
}
int64_t psync_fs_load_interval_tree(psync_file_t fd, uint64_t size,
psync_interval_tree_t **tree) {
psync_fs_index_record records[512];
uint64_t cnt;
uint64_t i;
ssize_t rrd, rd, j;
if (unlikely(size < sizeof(psync_fs_index_header)))
return 0;
size -= sizeof(psync_fs_index_header);
assertw(size % sizeof(psync_fs_index_record) == 0);
cnt = size / sizeof(psync_fs_index_record);
debug(D_NOTICE, "loading %lu intervals", (unsigned long)cnt);
for (i = 0; i < cnt; i += ARRAY_SIZE(records)) {
rd = ARRAY_SIZE(records) > cnt - i ? cnt - i : ARRAY_SIZE(records);
rrd = psync_file_pread(fd, records, rd * sizeof(psync_fs_index_record),
i * sizeof(psync_fs_index_record) +
sizeof(psync_fs_index_header));
if (unlikely_log(rrd != rd * sizeof(psync_fs_index_record)))
return -1;
for (j = 0; j < rd; j++)
psync_interval_tree_add(tree, records[j].offset,
records[j].offset + records[j].length);
}
if (IS_DEBUG && *tree) {
psync_interval_tree_t *tr;
tr = *tree;
debug(D_NOTICE, "loaded approx %lu intervals",
(unsigned long)1 << (tr->tree.height - 1));
tr = psync_interval_tree_get_first(*tree);
debug(D_NOTICE, "first interval from %lu to %lu", (unsigned long)tr->from,
(unsigned long)tr->to);
// while ((tr=psync_interval_tree_get_next(tr)))
// debug(D_NOTICE, "next interval from %lu to %lu", (unsigned
// long)tr->from, (unsigned long)tr->to);
tr = psync_interval_tree_get_last(*tree);
debug(D_NOTICE, "last interval from %lu to %lu", (unsigned long)tr->from,
(unsigned long)tr->to);
}
return cnt;
}
static int load_interval_tree(psync_openfile_t *of) {
psync_fs_index_header hdr;
int64_t ifs;
ifs = psync_file_size(of->indexfile);
if (unlikely_log(ifs == -1))
return -1;
if (ifs < sizeof(psync_fs_index_header)) {
assertw(ifs == 0);
if (psync_file_pwrite(of->indexfile, &hdr, sizeof(psync_fs_index_header),
0) != sizeof(psync_fs_index_header))
return -1;
else
return 0;
}
ifs = psync_fs_load_interval_tree(of->indexfile, ifs, &of->writeintervals);
if (ifs == -1)
return -1;
else {
of->indexoff = ifs;
return 0;
}
}
static int open_write_files(psync_openfile_t *of, int trunc) {
psync_fsfileid_t fileid;
const char *cachepath;
char *filename;
char fileidhex[sizeof(psync_fsfileid_t) * 2 + 2];
int64_t fs;
int ret;
debug(D_NOTICE, "opening write files of %s, trunc=%d", of->currentname,
trunc != 0);
fileid = -of->fileid;
psync_binhex(fileidhex, &fileid, sizeof(psync_fsfileid_t));
fileidhex[sizeof(psync_fsfileid_t)] = 'd';
fileidhex[sizeof(psync_fsfileid_t) + 1] = 0;
cachepath = psync_setting_get_string(_PS(fscachepath));
if (of->datafile == INVALID_HANDLE_VALUE) {
filename =
psync_strcat(cachepath, PSYNC_DIRECTORY_SEPARATOR, fileidhex, NULL);
of->datafile = psync_file_open(filename, P_O_RDWR,
P_O_CREAT | (trunc ? P_O_TRUNC : 0));
psync_free(filename);
if (of->datafile == INVALID_HANDLE_VALUE) {
debug(D_ERROR, "could not open cache file for fileid %ld",
(long)of->fileid);
return -EIO;
}
fs = psync_file_size(of->datafile);
if (unlikely_log(fs == -1))
return -EIO;
if (of->encrypted)
of->currentsize = psync_fs_crypto_plain_size(fs);
else
of->currentsize = fs;
} else {
debug(D_NOTICE, "data file already open");
if (trunc)
return psync_fs_ftruncate_of_locked(of, 0);
else
return 0;
}
if (!of->newfile && of->indexfile == INVALID_HANDLE_VALUE) {
fileidhex[sizeof(psync_fsfileid_t)] = 'i';
filename =
psync_strcat(cachepath, PSYNC_DIRECTORY_SEPARATOR, fileidhex, NULL);
of->indexfile = psync_file_open(filename, P_O_RDWR,
P_O_CREAT | (trunc ? P_O_TRUNC : 0));
psync_free(filename);
if (of->indexfile == INVALID_HANDLE_VALUE) {
debug(D_ERROR, "could not open cache index file for fileid %ld",
(long)of->fileid);
return -EIO;
}
if (load_interval_tree(of)) {
debug(D_ERROR,
"could not load cache file for fileid %ld to interval tree",
(long)of->fileid);
return -EIO;
}
}
if (of->encrypted) {
if (of->logfile == INVALID_HANDLE_VALUE) {
fileidhex[sizeof(psync_fsfileid_t)] = 'l';
filename =
psync_strcat(cachepath, PSYNC_DIRECTORY_SEPARATOR, fileidhex, NULL);
of->logfile = psync_file_open(filename, P_O_RDWR, P_O_CREAT | P_O_TRUNC);
psync_free(filename);
if (of->logfile == INVALID_HANDLE_VALUE) {
debug(D_ERROR, "could not open log file for fileid %ld",
(long)of->fileid);
return -EIO;
}
ret = psync_fs_crypto_init_log(of);
if (ret) {
debug(D_ERROR, "could not init log file for fileid %ld",
(long)of->fileid);
return ret;
}
}
}
return 0;
}
static void psync_fs_del_creat(psync_fspath_t *fpath, psync_openfile_t *of) {
psync_fstask_creat_t *cr;
psync_fstask_folder_t *folder;
psync_sql_res *res;
psync_sql_lock();
psync_sql_start_transaction();
res = psync_sql_prep_statement(
"DELETE FROM fstaskdepend WHERE dependfstaskid=?");
psync_sql_bind_uint(res, 1, -of->fileid);
psync_sql_run_free(res);
if (psync_sql_affected_rows())
psync_fsupload_wake();
res = psync_sql_prep_statement("DELETE FROM fstask WHERE id=?");
psync_sql_bind_uint(res, 1, -of->fileid);
psync_sql_run_free(res);
psync_sql_commit_transaction();
folder = psync_fstask_get_or_create_folder_tasks_locked(fpath->folderid);
if (likely(folder)) {
if (likely((cr = psync_fstask_find_creat(folder, fpath->name, 0)))) {
psync_tree_del(&folder->creats, &cr->tree);
folder->taskscnt--;
psync_free(cr);
}
psync_fstask_release_folder_tasks_locked(folder);
}
psync_fs_dec_of_refcnt(of);
psync_sql_unlock();
psync_free(fpath);
}
static int psync_fs_open(const char *path, struct fuse_file_info *fi) {
psync_sql_res *res;
psync_uint_row row;
psync_fsfileid_t fileid;
uint64_t size, hash, writeid;
psync_fspath_t *fpath;
psync_fstask_creat_t *cr;
psync_fstask_folder_t *folder;
psync_openfile_t *of;
psync_crypto_aes256_sector_encoder_decoder_t encoder;
char *encsymkey;
size_t encsymkeylen;
time_t ctime;
int ret, status, type;
psync_fs_set_thread_name();
debug(D_NOTICE, "open %s", path);
fileid = writeid = hash = size = ctime = 0;
psync_sql_lock();
CHECK_LOGIN_LOCKED();
fpath = psync_fsfolder_resolve_path(path);
if (!fpath) {
psync_sql_unlock();
ret = psync_fsfolder_crypto_error();
if (ret) {
ret = -psync_fs_crypto_err_to_errno(ret);
return PRINT_RETURN(ret);
} else {
debug(D_NOTICE, "returning ENOENT for %s, folder not found", path);
return -ENOENT;
}
}
if ((fi->flags & 3) != O_RDONLY &&
!(fpath->permissions & PSYNC_PERM_MODIFY)) {
psync_sql_unlock();
psync_free(fpath);
return -EACCES;
}
// even if there are existing files there, just don't allow opening those
if (fpath->flags & (PSYNC_FOLDER_FLAG_BACKUP_DEVICE_LIST |
PSYNC_FOLDER_FLAG_BACKUP_DEVICE)) {
psync_sql_unlock();
psync_free(fpath);
return -EACCES;
}
folder = psync_fstask_get_or_create_folder_tasks_locked(fpath->folderid);
row = NULL;
if ((cr = psync_fstask_find_creat(folder, fpath->name, 0))) {
if (cr->fileid > 0) {
res =
psync_sql_query("SELECT id, size, hash, ctime FROM file WHERE id=?");
psync_sql_bind_uint(res, 1, cr->fileid);
row = psync_sql_fetch_rowint(res);
if (row) {
fileid = row[0];
size = row[1];
hash = row[2];
ctime = row[3];
debug(D_NOTICE, "opening moved regular file %lu %s size %lu hash %lu",
(unsigned long)fileid, fpath->name, (unsigned long)size,
(unsigned long)hash);
}
psync_sql_free_result(res);
if (unlikely_log(!row)) {
ret = -ENOENT;
goto ex0;
}
} else if (cr->fileid < 0) {
status = type = 0; // prevent (stupid) warnings
res = psync_sql_query(
"SELECT type, status, fileid, int1, int2 FROM fstask WHERE id=?");
psync_sql_bind_uint(res, 1, -cr->fileid);
row = psync_sql_fetch_rowint(res);
if (row) {
type = row[0];
status = row[1];
fileid = row[2];
writeid = row[3];
hash = row[4];
}
psync_sql_free_result(res);
if (unlikely_log(!row)) {
ret = -ENOENT;
goto ex0;
}
if (type == PSYNC_FS_TASK_CREAT) {
fileid = cr->fileid;
if (fpath->flags & PSYNC_FOLDER_FLAG_ENCRYPTED) {
encoder = psync_cloud_crypto_get_file_encoder(fileid, hash, 1);
if (unlikely_log(psync_crypto_is_error(encoder))) {
ret = -psync_fs_crypto_err_to_errno(psync_crypto_to_error(encoder));
goto ex0;
}
} else
encoder = PSYNC_CRYPTO_INVALID_ENCODER;
of = psync_fs_create_file(fileid, 0, 0, 0, 1, writeid,
psync_fstask_get_ref_locked(folder),
fpath->name, encoder);
psync_fstask_release_folder_tasks_locked(folder);
psync_sql_unlock();
debug(D_NOTICE, "opening new file %ld %s", (long)fileid, fpath->name);
psync_free(fpath);
of->newfile = 1;
of->releasedforupload = status != 1;
ret = open_write_files(of, fi->flags & O_TRUNC);
pthread_mutex_unlock(&of->mutex);
fi->fh = openfile_to_fh(of);
if (unlikely_log(ret)) {
psync_fs_dec_of_refcnt(of);
return ret;
} else
return ret;
} else if (type == PSYNC_FS_TASK_MODIFY) {
debug(D_NOTICE, "opening sparse file %ld %s", (long)cr->fileid,
fpath->name);
if (fi->flags & O_TRUNC)
size = 0;
else {
res = psync_sql_query(
"SELECT size FROM filerevision WHERE fileid=? AND hash=?");
psync_sql_bind_uint(res, 1, fileid);
psync_sql_bind_uint(res, 2, hash);
row = psync_sql_fetch_rowint(res);
if (row)
size = row[0];
psync_sql_free_result(res);
if (unlikely(!row)) {
debug(
D_WARNING,
"could not find fileid %lu with hash %lu (%ld) in filerevision",
(unsigned long)fileid, (unsigned long)hash, (long)hash);
ret = -ENOENT;
goto ex0;
}
}
} else {
debug(D_BUG, "trying to open file %s with id %ld but task type is %d",
fpath->name, (long)cr->fileid, type);
ret = -EIO;
goto ex0;
}
if (fpath->flags & PSYNC_FOLDER_FLAG_ENCRYPTED) {
encoder = psync_cloud_crypto_get_file_encoder(fileid, hash, 1);
if (unlikely_log(psync_crypto_is_error(encoder))) {
ret = -psync_fs_crypto_err_to_errno(psync_crypto_to_error(encoder));
goto ex0;
}
} else
encoder = PSYNC_CRYPTO_INVALID_ENCODER;
of = psync_fs_create_file(cr->fileid, fileid, size, hash, 1, writeid,
psync_fstask_get_ref_locked(folder),
fpath->name, encoder);
psync_fstask_release_folder_tasks_locked(folder);
psync_sql_unlock();
psync_free(fpath);
of->newfile = 0;
of->releasedforupload = status != 1;
ret = open_write_files(of, fi->flags & O_TRUNC);
pthread_mutex_unlock(&of->mutex);
fi->fh = openfile_to_fh(of);
if (unlikely_log(ret)) {
psync_fs_dec_of_refcnt(of);
return ret;
} else
return ret;
} else { /* cr->fileid==0 */
psync_fstask_local_creat_t *lc;
lc = psync_fstask_creat_get_local(cr);
of = psync_fs_create_file(INT64_MAX - (UINT64_MAX - cr->taskid), 0,
lc->datalen, 0, 1, 0,
psync_fstask_get_ref_locked(folder),
fpath->name, PSYNC_CRYPTO_INVALID_ENCODER);
psync_fstask_release_folder_tasks_locked(folder);
psync_sql_unlock();
psync_free(fpath);
of->modified = 1;
of->staticfile = 1;
of->staticdata = (const char *)lc->data;
of->staticctime = lc->ctime;
pthread_mutex_unlock(&of->mutex);
fi->fh = openfile_to_fh(of);
return 0;
}
}
if (!row && fpath->folderid >= 0 &&
!psync_fstask_find_unlink(folder, fpath->name, 0)) {
res = psync_sql_query("SELECT id, size, hash, ctime FROM file WHERE "
"parentfolderid=? AND name=?");
psync_sql_bind_uint(res, 1, fpath->folderid);
psync_sql_bind_string(res, 2, fpath->name);
row = psync_sql_fetch_rowint(res);
if (row) {
fileid = row[0];
size = row[1];
hash = row[2];
ctime = row[3];
debug(D_NOTICE, "opening regular file %lu %s size %lu hash %lu",
(unsigned long)fileid, fpath->name, (unsigned long)size,
(unsigned long)hash);
}
psync_sql_free_result(res);
}
if (fi->flags & O_TRUNC || (fi->flags & O_CREAT && !row)) {
if (fi->flags & O_TRUNC)
debug(D_NOTICE, "truncating file %s", path);
else
debug(D_NOTICE, "creating file %s", path);
if (fpath->flags & PSYNC_FOLDER_FLAG_ENCRYPTED) {
if (row) {
encoder = psync_cloud_crypto_get_file_encoder(fileid, hash, 0);
if (unlikely_log(psync_crypto_is_error(encoder))) {
ret = -psync_fs_crypto_err_to_errno(psync_crypto_to_error(encoder));
goto ex0;
}
encsymkey = psync_cloud_crypto_get_file_encoded_key(fileid, hash,
&encsymkeylen);
if (unlikely_log(psync_crypto_is_error(encsymkey))) {
psync_cloud_crypto_release_file_encoder(fileid, hash, encoder);
ret = -psync_fs_crypto_err_to_errno(psync_crypto_to_error(encsymkey));
goto ex0;
}
} else {
psync_symmetric_key_t symkey;
encsymkey = psync_cloud_crypto_get_new_encoded_and_plain_key(
0, &encsymkeylen, &symkey);
if (unlikely_log(psync_crypto_is_error(encsymkey))) {
ret = -psync_fs_crypto_err_to_errno(psync_crypto_to_error(encsymkey));
goto ex0;
}
encoder = psync_crypto_aes256_sector_encoder_decoder_create(symkey);
psync_ssl_free_symmetric_key(symkey);
if (unlikely_log(encoder == PSYNC_CRYPTO_INVALID_ENCODER)) {
psync_free(encsymkey);
ret = -ENOMEM;
goto ex0;
}
}
} else {
encoder = PSYNC_CRYPTO_INVALID_ENCODER;
encsymkey = NULL;
encsymkeylen = 0;
}
cr =
psync_fstask_add_creat(folder, fpath->name, 0, encsymkey, encsymkeylen);
psync_free(encsymkey);
if (unlikely_log(!cr)) {
ret = -EIO;
goto ex0;
}
of = psync_fs_create_file(cr->fileid, 0, 0, 0, 1, 0,
psync_fstask_get_ref_locked(folder), fpath->name,
encoder);
psync_fstask_release_folder_tasks_locked(folder);
psync_sql_unlock();
of->newfile = 1;
of->modified = 1;
ret = open_write_files(of, 1);
pthread_mutex_unlock(&of->mutex);
if (unlikely_log(ret)) {
psync_fs_del_creat(fpath, of);
return ret;
}
psync_free(fpath);
fi->fh = openfile_to_fh(of);
return 0;
} else if (row) {
if (fpath->flags & PSYNC_FOLDER_FLAG_ENCRYPTED) {
encoder = psync_cloud_crypto_get_file_encoder(fileid, hash, 1);
if (unlikely_log(psync_crypto_is_error(encoder))) {
ret = -psync_fs_crypto_err_to_errno(psync_crypto_to_error(encoder));
goto ex0;
}
} else
encoder = PSYNC_CRYPTO_INVALID_ENCODER;
of = psync_fs_create_file(fileid, fileid, size, hash, 0, 0,
psync_fstask_get_ref_locked(folder), fpath->name,
encoder);
of->origctime = ctime;
fi->fh = openfile_to_fh(of);
ret = 0;
} else
ret = -ENOENT;
ex0:
psync_fstask_release_folder_tasks_locked(folder);
psync_sql_unlock();
psync_free(fpath);
return ret;
}
static int psync_fs_file_exists_in_folder(psync_fstask_folder_t *folder,
const char *name) {
psync_fstask_creat_t *cr;
psync_sql_res *res;
psync_uint_row row;
cr = psync_fstask_find_creat(folder, name, 0);
if (cr)
return 1;
if (folder->folderid < 0)
return 0;
if (psync_fstask_find_unlink(folder, name, 0))
return 0;
res =
psync_sql_query("SELECT id FROM file WHERE parentfolderid=? AND name=?");
psync_sql_bind_uint(res, 1, folder->folderid);
psync_sql_bind_string(res, 2, name);
row = psync_sql_fetch_rowint(res);
psync_sql_free_result(res);
return row ? 1 : 0;
}
static int psync_fs_creat_fake_locked(psync_fspath_t *fpath,
struct fuse_file_info *fi) {
psync_fstask_creat_t *cr;
psync_fstask_folder_t *folder;
psync_openfile_t *of;
psync_fsfileid_t fileid;
size_t len;
fileid = psync_fake_fileid++;
len = strlen(fpath->name) + 1;
cr = (psync_fstask_creat_t *)psync_malloc(
offsetof(psync_fstask_creat_t, name) + len);
cr->fileid = fileid;
cr->rfileid = 0;
cr->taskid = fileid;
memcpy(cr->name, fpath->name, len);
folder = psync_fstask_get_or_create_folder_tasks_locked(fpath->folderid);
psync_fstask_inject_creat(folder, cr);
of = psync_fs_create_file(fileid, 0, 0, 0, 0, 0,
psync_fstask_get_ref_locked(folder), fpath->name,
PSYNC_CRYPTO_INVALID_ENCODER);
psync_fstask_release_folder_tasks_locked(folder);
of->newfile = 0;
of->modified = 0;
psync_sql_unlock();
psync_free(fpath);
fi->fh = openfile_to_fh(of);
return 0;
}
static int psync_fs_creat(const char *path, mode_t mode,
struct fuse_file_info *fi) {
psync_fspath_t *fpath;
psync_fstask_folder_t *folder;
psync_fstask_creat_t *cr;
psync_symmetric_key_t symkey;
psync_crypto_aes256_sector_encoder_decoder_t encoder;
char *encsymkey;
size_t encsymkeylen;
psync_openfile_t *of;
int ret;
psync_fs_set_thread_name();
debug(D_NOTICE, "creat %s", path);
psync_sql_lock();
CHECK_LOGIN_LOCKED();
fpath = psync_fsfolder_resolve_path(path);
if (!fpath) {
psync_sql_unlock();
ret = psync_fsfolder_crypto_error();
if (ret) {
ret = psync_fs_crypto_err_to_errno(ret);
return PRINT_RETURN(-ret);
} else {
debug(D_NOTICE, "returning ENOENT for %s, folder not found", path);
return -ENOENT;
}
}
if (unlikely(psync_fs_need_per_folder_refresh_const() &&
!strncmp(psync_fake_prefix, fpath->name, psync_fake_prefix_len)))
return psync_fs_creat_fake_locked(fpath, fi);
if (!(fpath->permissions & PSYNC_PERM_CREATE) ||
(fpath->flags & (PSYNC_FOLDER_FLAG_BACKUP_DEVICE_LIST |
PSYNC_FOLDER_FLAG_BACKUP_DEVICE))) {
psync_sql_unlock();
psync_free(fpath);
return -EACCES;
}
folder = psync_fstask_get_or_create_folder_tasks_locked(fpath->folderid);
if (psync_fs_file_exists_in_folder(folder, fpath->name)) {
psync_fstask_release_folder_tasks_locked(folder);
debug(D_NOTICE, "file %s already exists, processing as open", path);
ret = psync_fs_open(path, fi);
psync_sql_unlock();
psync_free(fpath);
return ret;
}
if (fpath->flags & PSYNC_FOLDER_FLAG_ENCRYPTED) {
if (psync_crypto_isexpired()) {
psync_fstask_release_folder_tasks_locked(folder);
psync_sql_unlock();
psync_free(fpath);
return -PRINT_RETURN_CONST(PSYNC_FS_ERR_CRYPTO_EXPIRED);
}
encsymkey = psync_cloud_crypto_get_new_encoded_and_plain_key(
0, &encsymkeylen, &symkey);
if (unlikely_log(psync_crypto_is_error(encsymkey))) {
psync_fstask_release_folder_tasks_locked(folder);
psync_sql_unlock();
psync_free(fpath);
return -psync_fs_crypto_err_to_errno(psync_crypto_to_error(encsymkey));
}
encoder = psync_crypto_aes256_sector_encoder_decoder_create(symkey);
psync_ssl_free_symmetric_key(symkey);
if (unlikely_log(encoder == PSYNC_CRYPTO_INVALID_ENCODER)) {
psync_fstask_release_folder_tasks_locked(folder);
psync_sql_unlock();
psync_free(fpath);
psync_free(encsymkey);
return -ENOMEM;
}
} else {
encoder = PSYNC_CRYPTO_INVALID_ENCODER;
encsymkey = NULL;
encsymkeylen = 0;
}
cr = psync_fstask_add_creat(folder, fpath->name, 0, encsymkey, encsymkeylen);
if (encsymkey)
psync_free(encsymkey);
if (unlikely_log(!cr)) {
psync_fstask_release_folder_tasks_locked(folder);
psync_sql_unlock();
psync_free(fpath);
return -EIO;
}
of = psync_fs_create_file(cr->fileid, 0, 0, 0, 1, 0,
psync_fstask_get_ref_locked(folder), fpath->name,
encoder);
psync_fstask_release_folder_tasks_locked(folder);
psync_sql_unlock();
of->newfile = 1;
of->modified = 1;
ret = open_write_files(of, 1);
pthread_mutex_unlock(&of->mutex);
if (unlikely_log(ret)) {
psync_fs_del_creat(fpath, of);
return ret;
}
psync_free(fpath);
fi->fh = openfile_to_fh(of);
return 0;
}
void psync_fs_inc_of_refcnt_locked(psync_openfile_t *of) { of->refcnt++; }
void psync_fs_inc_of_refcnt(psync_openfile_t *of) {
psync_fs_lock_file(of);
psync_fs_inc_of_refcnt_locked(of);
pthread_mutex_unlock(&of->mutex);
}
static void close_if_valid(psync_file_t fd) {
if (fd != INVALID_HANDLE_VALUE)
psync_file_close(fd);
}
static void psync_fs_free_openfile(psync_openfile_t *of) {
debug(D_NOTICE, "releasing file %s", of->currentname);
if (unlikely(of->writetimer != PSYNC_INVALID_TIMER))
debug(D_BUG,
"file %s with active timer is set to free, this is not supposed to "
"happen",
of->currentname);
if (of->deleted && of->fileid < 0) {
psync_sql_res *res;
debug(D_NOTICE, "file %s marked for deletion, releasing cancel tasks",
of->currentname);
res = psync_sql_prep_statement(
"UPDATE fstask SET status=11 WHERE id=? AND status=12");
psync_sql_bind_uint(res, 1, -of->fileid);
psync_sql_run_free(res);
psync_fsupload_wake();
}
if (of->encrypted) {
if (of->encoder != PSYNC_CRYPTO_UNLOADED_SECTOR_ENCODER &&
of->encoder != PSYNC_CRYPTO_FAILED_SECTOR_ENCODER) {
assert(of->encoder != PSYNC_CRYPTO_LOADING_SECTOR_ENCODER);
psync_crypto_aes256_sector_encoder_decoder_free(of->encoder);
}
close_if_valid(of->logfile);
psync_tree_for_each_element_call_safe(
of->sectorsinlog, psync_sector_inlog_t, tree, psync_free);
delete_log_files(of);
if (of->authenticatedints)
psync_interval_tree_free(of->authenticatedints);
}
pthread_mutex_destroy(&of->mutex);
close_if_valid(of->datafile);
close_if_valid(of->indexfile);
if (of->writeintervals)
psync_interval_tree_free(of->writeintervals);
if (unlikely(psync_fs_need_per_folder_refresh_const() &&
of->fileid < psync_fake_fileid)) {
psync_fstask_creat_t *cr;
psync_sql_lock();
cr = psync_fstask_find_creat(of->currentfolder, of->currentname, 0);
if (cr) {
psync_tree_del(&of->currentfolder->creats, &cr->tree);
of->currentfolder->taskscnt--;
psync_free(cr);
}
psync_sql_unlock();
}
psync_fstask_release_folder_tasks(of->currentfolder);
psync_free(of->currentname);
psync_free(of);
}
static void psync_fs_get_both_locks(psync_openfile_t *of) {
retry:
psync_sql_lock();
if (pthread_mutex_trylock(&of->mutex)) {
psync_sql_unlock();
psync_fs_lock_file(of);
if (psync_sql_trylock()) {
pthread_mutex_unlock(&of->mutex);
psync_milisleep(1);
goto retry;
}
}
}
void psync_fs_dec_of_refcnt(psync_openfile_t *of) {
uint32_t refcnt;
psync_fs_get_both_locks(of);
refcnt = --of->refcnt;
if (!refcnt)
psync_tree_del(&openfiles, &of->tree);
psync_sql_unlock();
pthread_mutex_unlock(&of->mutex);
if (!refcnt)
psync_fs_free_openfile(of);
}
void psync_fs_inc_of_refcnt_and_readers(psync_openfile_t *of) {
psync_fs_lock_file(of);
of->refcnt++;
of->runningreads++;
pthread_mutex_unlock(&of->mutex);
}
void psync_fs_dec_of_refcnt_and_readers(psync_openfile_t *of) {
uint32_t refcnt;
psync_fs_get_both_locks(of);
of->runningreads--;
refcnt = --of->refcnt;
if (refcnt == 0)
psync_tree_del(&openfiles, &of->tree);
psync_sql_unlock();
pthread_mutex_unlock(&of->mutex);
if (!refcnt)
psync_fs_free_openfile(of);
}
typedef struct {
psync_openfile_t *of;
uint64_t writeid;
} psync_openfile_writeid_t;
static void psync_fs_upload_release_timer(void *ptr) {
psync_sql_res *res;
psync_openfile_writeid_t *ofw;
uint32_t aff;
ofw = (psync_openfile_writeid_t *)ptr;
debug(D_NOTICE, "releasing file %s for upload, size=%lu, writeid=%u",
ofw->of->currentname, (unsigned long)ofw->of->currentsize,
(unsigned)ofw->writeid);
res = psync_sql_prep_statement(
"UPDATE fstask SET status=0, int1=? WHERE id=? AND status=1");
psync_sql_bind_uint(res, 1, ofw->writeid);
psync_sql_bind_uint(res, 2, -ofw->of->fileid);
psync_sql_run(res);
aff = psync_sql_affected_rows();
psync_sql_free_result(res);
if (aff)
psync_fsupload_wake();
else {
res = psync_sql_prep_statement(
"UPDATE fstask SET int1=? WHERE id=? AND int1<?");
psync_sql_bind_uint(res, 1, ofw->writeid);
psync_sql_bind_uint(res, 2, -ofw->of->fileid);
psync_sql_bind_uint(res, 3, ofw->writeid);
psync_sql_run_free(res);
}
psync_fs_dec_of_refcnt(ofw->of);
psync_free(ofw);
psync_status_recalc_to_upload_async();
}
static void psync_fs_write_timer(psync_timer_t timer, void *ptr) {
psync_openfile_t *of;
of = (psync_openfile_t *)ptr;
psync_fs_lock_file(of);
psync_timer_stop(timer);
of->writetimer = PSYNC_INVALID_TIMER;
debug(D_NOTICE, "got write timer for file %s", of->currentname);
if (of->releasedforupload)
debug(D_NOTICE, "file seems to be already released for upload");
else if (of->modified) {
psync_openfile_writeid_t *ofw;
if (unlikely(of->staticfile)) {
debug(D_ERROR, "file is static file, which should not generally happen");
goto unlock_ex;
}
if (unlikely(of->encrypted && psync_fs_crypto_flush_file(of))) {
debug(D_WARNING,
"we are in timer and we failed to flush crypto file, life sux");
goto unlock_ex;
}
of->releasedforupload = 1;
ofw = psync_new(psync_openfile_writeid_t);
ofw->of = of;
ofw->writeid = of->writeid;
pthread_mutex_unlock(&of->mutex);
debug(D_NOTICE, "running separate thread to release file for upload");
psync_run_thread1("upload release timer", psync_fs_upload_release_timer,
ofw);
return;
} else
debug(D_NOTICE, "file seems to be already uploaded");
unlock_ex:
pthread_mutex_unlock(&of->mutex);
psync_fs_dec_of_refcnt(of);
}
static int psync_fs_flush(const char *path, struct fuse_file_info *fi) {
psync_openfile_t *of;
psync_fs_set_thread_name();
debug(D_NOTICE, "flush %s", path);
of = fh_to_openfile(fi->fh);
psync_fs_lock_file(of);
if (of->modified) {
psync_sql_res *res;
uint64_t writeid;
uint32_t aff;
int ret;
if (of->staticfile) {
pthread_mutex_unlock(&of->mutex);
return 0;
}
writeid = of->writeid;
if (of->encrypted) {
ret = psync_fs_crypto_flush_file(of);
if (unlikely_log(ret)) {
pthread_mutex_unlock(&of->mutex);
return ret;
}
}
of->releasedforupload = 1;
if (of->writetimer && !psync_timer_stop(of->writetimer)) {
if (--of->refcnt == 0) {
debug(D_BUG, "zero refcnt in flush after canceling timer");
assert(of->refcnt);
}
of->writetimer = PSYNC_INVALID_TIMER;
}
pthread_mutex_unlock(&of->mutex);
debug(D_NOTICE, "releasing file %s for upload, size=%lu, writeid=%u", path,
(unsigned long)of->currentsize, (unsigned)writeid);
res = psync_sql_prep_statement(
"UPDATE fstask SET status=0, int1=? WHERE id=? AND status=1");
psync_sql_bind_uint(res, 1, writeid);
psync_sql_bind_uint(res, 2, -of->fileid);
psync_sql_run(res);
aff = psync_sql_affected_rows();
psync_sql_free_result(res);
if (aff)
psync_fsupload_wake();
else {
res = psync_sql_prep_statement(
"UPDATE fstask SET int1=? WHERE id=? AND int1<?");
psync_sql_bind_uint(res, 1, writeid);
psync_sql_bind_uint(res, 2, -of->fileid);
psync_sql_bind_uint(res, 3, writeid);
psync_sql_run_free(res);
}
psync_status_recalc_to_upload_async();
return 0;
}
pthread_mutex_unlock(&of->mutex);
return 0;
}
static int psync_fs_release(const char *path, struct fuse_file_info *fi) {
psync_fs_set_thread_name();
debug(D_NOTICE, "release %s", path);
psync_fs_flush(path, fi);
psync_fs_dec_of_refcnt(fh_to_openfile(fi->fh));
return 0;
}
static int psync_fs_fsync(const char *path, int datasync,
struct fuse_file_info *fi) {
psync_openfile_t *of;
int ret;
psync_fs_set_thread_name();
debug(D_NOTICE, "fsync %s", path);
of = fh_to_openfile(fi->fh);
psync_fs_lock_file(of);
if (!of->modified || of->staticfile) {
pthread_mutex_unlock(&of->mutex);
return 0;
}
if (of->encrypted) {
ret = psync_fs_crypto_flush_file(of);
if (unlikely_log(ret)) {
pthread_mutex_unlock(&of->mutex);
return ret;
}
}
if (unlikely_log(psync_file_sync(of->datafile)) ||
unlikely_log(!of->newfile && psync_file_sync(of->indexfile))) {
pthread_mutex_unlock(&of->mutex);
return -EIO;
}
pthread_mutex_unlock(&of->mutex);
if (unlikely_log(psync_sql_sync()))
return -EIO;
return 0;
}
static int psync_fs_fsyncdir(const char *path, int datasync,
struct fuse_file_info *fi) {
psync_fs_set_thread_name();
debug(D_NOTICE, "fsyncdir %s", path);
if (unlikely_log(psync_sql_sync()))
return -EIO;
else
return 0;
}
static int psync_read_newfile(psync_openfile_t *of, char *buf, uint64_t size,
uint64_t offset) {
ssize_t br = psync_file_pread(of->datafile, buf, size, offset);
pthread_mutex_unlock(&of->mutex);
if (br == -1) {
debug(D_NOTICE,
"error reading from new file offset %lu, size %lu, error %d",
(unsigned long)offset, (unsigned long)size, (int)psync_fs_err());
br = -EIO;
}
return br;
}
static int psync_read_staticfile(psync_openfile_t *of, char *buf, uint64_t size,
uint64_t offset) {
int ret;
if (of->currentsize < offset)
ret = 0;
else {
if (offset + size > of->currentsize)
ret = of->currentsize - offset;
else
ret = size;
memcpy(buf, of->staticdata + offset, ret);
}
pthread_mutex_unlock(&of->mutex);
return ret;
}
static int psync_fs_read(const char *path, char *buf, size_t size,
fuse_off_t offset, struct fuse_file_info *fi) {
psync_openfile_t *of;
time_t currenttime;
psync_fs_set_thread_name();
of = fh_to_openfile(fi->fh);
currenttime = psync_timer_time();
psync_fs_lock_file(of);
if (of->currentsec == currenttime) {
of->bytesthissec += size;
if (of->currentspeed < of->bytesthissec)
of->currentspeed = of->bytesthissec;
} else {
if (of->currentsec < currenttime - 10)
of->currentspeed = size;
else if (of->currentspeed == 0)
of->currentspeed = of->bytesthissec;
else
of->currentspeed = (of->bytesthissec / (currenttime - of->currentsec) +
of->currentspeed * 3) /
4;
of->currentsec = currenttime;
of->bytesthissec = size;
}
if (of->encrypted) {
if (of->newfile)
return psync_fs_crypto_read_newfile_locked(of, buf, size, offset);
else if (of->modified)
return psync_fs_crypto_read_modified_locked(of, buf, size, offset);
else
return psync_pagecache_read_unmodified_encrypted_locked(of, buf, size,
offset);
} else {
if (of->newfile)
return psync_read_newfile(of, buf, size, offset);
else if (of->modified) {
if (unlikely(of->staticfile))
return psync_read_staticfile(of, buf, size, offset);
else
return psync_pagecache_read_modified_locked(of, buf, size, offset);
} else
return psync_pagecache_read_unmodified_locked(of, buf, size, offset);
}
}
static void psync_fs_inc_writeid_locked(psync_openfile_t *of) {
if (unlikely(of->releasedforupload)) {
if (unlikely(psync_sql_trylock())) {
pthread_mutex_unlock(&of->mutex);
psync_sql_lock();
psync_fs_lock_file(of);
}
if (of->releasedforupload) {
of->releasedforupload = 0;
debug(D_NOTICE, "stopping upload of file %s as new write arrived",
of->currentname);
assertw(of->fileid < 0);
psync_fsupload_stop_upload_locked(-of->fileid);
}
psync_sql_unlock();
}
of->writeid++;
if (of->writetimer == PSYNC_INVALID_TIMER ||
!psync_timer_stop(of->writetimer)) {
if (of->writetimer == PSYNC_INVALID_TIMER)
psync_fs_inc_of_refcnt_locked(of);
of->writetimer = psync_timer_register(psync_fs_write_timer,
PSYNC_UPLOAD_NOWRITE_TIMER, of);
}
}
static int psync_fs_modfile_check_size_ok(psync_openfile_t *of, uint64_t size) {
if (unlikely(of->currentsize < size)) {
debug(D_NOTICE, "extending file %s from %lu to %lu bytes", of->currentname,
(unsigned long)of->currentsize, (unsigned long)size);
if (psync_file_seek(of->datafile, size, P_SEEK_SET) == -1 ||
psync_file_truncate(of->datafile))
return -1;
if (of->newfile)
return 0;
else {
psync_fs_index_record rec;
uint64_t ioff;
assertw(of->modified);
ioff = of->indexoff++;
rec.offset = of->currentsize;
rec.length = size - of->currentsize;
if (unlikely_log(psync_file_pwrite(of->indexfile, &rec, sizeof(rec),
sizeof(rec) * ioff +
sizeof(psync_fs_index_header)) !=
sizeof(rec)))
return -1;
psync_interval_tree_add(&of->writeintervals, of->currentsize, size);
of->currentsize = size;
}
}
return 0;
}
PSYNC_NOINLINE static int
psync_fs_reopen_file_for_writing(psync_openfile_t *of) {
psync_fstask_creat_t *cr;
uint64_t size;
char *encsymkey;
size_t encsymkeylen;
int ret;
debug(D_NOTICE, "reopening file %s for writing size %lu", of->currentname,
(unsigned long)of->currentsize);
if (unlikely(of->encrypted &&
of->encoder == PSYNC_CRYPTO_UNLOADED_SECTOR_ENCODER)) {
psync_crypto_aes256_sector_encoder_decoder_t enc;
// we should unlock of->mutex as it can deadlock with sqllock and taking
// sqllock before network operation is not a good idea
pthread_mutex_unlock(&of->mutex);
enc = psync_cloud_crypto_get_file_encoder(of->remotefileid, of->hash, 0);
if (unlikely(psync_crypto_is_error(enc)))
return -psync_fs_crypto_err_to_errno(psync_crypto_to_error(enc));
psync_fs_lock_file(of);
if (of->encoder == PSYNC_CRYPTO_UNLOADED_SECTOR_ENCODER)
of->encoder = enc;
else
psync_cloud_crypto_release_file_encoder(of->remotefileid, of->hash, enc);
if (of->newfile || of->modified)
return 1;
}
if (unlikely(psync_sql_trylock())) {
// we have to take sql_lock and retake of->mutex AFTER, then check if the
// case is still !of->newfile && !of->modified
pthread_mutex_unlock(&of->mutex);
psync_sql_lock();
psync_fs_lock_file(of);
if (of->newfile || of->modified) {
psync_sql_unlock();
return 1;
}
}
if (of->encrypted) {
if (unlikely(psync_crypto_isexpired())) {
psync_sql_unlock();
return -PRINT_RETURN_CONST(PSYNC_FS_ERR_CRYPTO_EXPIRED);
}
size = psync_fs_crypto_crypto_size(of->initialsize);
encsymkey = psync_cloud_crypto_get_file_encoded_key(of->fileid, of->hash,
&encsymkeylen);
if (unlikely_log(psync_crypto_is_error(encsymkey))) {
psync_sql_unlock();
return -psync_fs_crypto_err_to_errno(psync_crypto_to_error(encsymkey));
}
} else {
encsymkey = NULL;
encsymkeylen = 0;
size = of->initialsize;
}
if (size == 0 || (size <= PSYNC_FS_MAX_SIZE_CONVERT_NEWFILE &&
psync_pagecache_have_all_pages_in_cache(of->hash, size) &&
!psync_pagecache_lock_pages_in_cache())) {
debug(D_NOTICE,
"we have all pages of file %s, convert it to new file as they are "
"cheaper to work with",
of->currentname);
cr = psync_fstask_add_creat(of->currentfolder, of->currentname, of->fileid,
encsymkey, encsymkeylen);
if (unlikely_log(!cr)) {
psync_sql_unlock();
psync_pagecache_unlock_pages_from_cache();
psync_free(encsymkey);
return -EIO;
}
psync_fs_update_openfile_fileid_locked(of, cr->fileid);
psync_fs_file_to_task(of->remotefileid, cr->taskid);
psync_sql_unlock();
of->newfile = 1;
of->modified = 1;
ret = open_write_files(of, 0);
if (unlikely_log(ret)) {
psync_pagecache_unlock_pages_from_cache();
psync_free(encsymkey);
return ret;
}
if (of->origctime)
psync_file_set_creation(of->datafile, of->origctime);
if (size) {
ret = psync_pagecache_copy_all_pages_from_cache_to_file_locked(
of, of->hash, size);
psync_pagecache_unlock_pages_from_cache();
if (unlikely_log(ret)) {
psync_free(encsymkey);
return -EIO;
}
}
of->currentsize = of->initialsize;
return 1;
}
cr = psync_fstask_add_modified_file(of->currentfolder, of->currentname,
of->fileid, of->hash, encsymkey,
encsymkeylen);
psync_free(encsymkey);
if (unlikely_log(!cr)) {
psync_sql_unlock();
return -EIO;
}
psync_fs_update_openfile_fileid_locked(of, cr->fileid);
psync_sql_unlock();
ret = open_write_files(of, 0);
if (unlikely_log(ret) ||
psync_file_seek(of->datafile, size, P_SEEK_SET) == -1 ||
psync_file_truncate(of->datafile)) {
if (!ret)
ret = -EIO;
return ret;
}
if (of->origctime)
psync_file_set_creation(of->datafile, of->origctime);
of->modified = 1;
of->indexoff = 0;
of->currentsize = of->initialsize;
return 0;
}
PSYNC_NOINLINE static int
psync_fs_reopen_static_file_for_writing(psync_openfile_t *of) {
psync_fstask_creat_t *cr;
psync_fstask_unlink_t *un;
uint64_t taskid;
int ret;
assert(!of->encrypted);
assert(of->staticfile);
if (unlikely(psync_sql_trylock())) {
// we have to take sql_lock and retake of->mutex AFTER, then check if the
// case is still !of->newfile && !of->modified
pthread_mutex_unlock(&of->mutex);
psync_sql_lock();
psync_fs_lock_file(of);
if (!of->staticfile) {
psync_sql_unlock();
return 1;
}
}
taskid = UINT64_MAX - (INT64_MAX - of->fileid);
debug(D_NOTICE, "reopening static file %s for writing size %lu, taskid %lu",
of->currentname, (unsigned long)of->currentsize, (unsigned long)taskid);
cr = psync_fstask_add_creat(of->currentfolder, of->currentname, 0, NULL, 0);
if (unlikely_log(!cr)) {
psync_sql_unlock();
return -EIO;
}
psync_fs_update_openfile_fileid_locked(of, cr->fileid);
psync_fs_static_to_task(taskid, cr->taskid);
cr = psync_fstask_find_creat(of->currentfolder, of->currentname, taskid);
if (likely_log(cr)) {
psync_tree_del(&of->currentfolder->creats, &cr->tree);
of->currentfolder->taskscnt--;
psync_free(cr);
}
un = psync_fstask_find_unlink(of->currentfolder, of->currentname, taskid);
if (likely_log(un)) {
psync_tree_del(&of->currentfolder->unlinks, &un->tree);
of->currentfolder->taskscnt--;
psync_free(un);
}
psync_sql_unlock();
of->writeid = 0;
of->newfile = 1;
of->modified = 1;
of->staticfile = 0;
ret = open_write_files(of, 1);
if (unlikely_log(ret))
return ret;
if (psync_file_pwrite(of->datafile, of->staticdata, of->currentsize, 0) !=
of->currentsize)
ret = -PRINT_RETURN_CONST(EIO);
else
ret = 1;
return ret;
}
PSYNC_NOINLINE static int
psync_fs_check_modified_file_write_space(psync_openfile_t *of, size_t size,
fuse_off_t offset) {
uint64_t from, to;
psync_interval_tree_t *tr;
if (of->encrypted) {
from = psync_fs_crypto_data_sectorid_by_sectorid(offset /
PSYNC_CRYPTO_SECTOR_SIZE) *
PSYNC_CRYPTO_SECTOR_SIZE;
to = psync_fs_crypto_data_sectorid_by_sectorid((offset + size) /
PSYNC_CRYPTO_SECTOR_SIZE) *
PSYNC_CRYPTO_SECTOR_SIZE +
(offset + size) % PSYNC_CRYPTO_SECTOR_SIZE;
} else {
from = offset;
to = offset + size;
}
tr = psync_interval_tree_first_interval_containing_or_after(
of->writeintervals, from);
if (tr && tr->from <= from && tr->to >= to)
return 1;
else
return 0;
}
static void psync_fs_throttle(size_t size, uint64_t speed) {
static pthread_mutex_t throttle_mutex = PTHREAD_MUTEX_INITIALIZER;
static uint64_t writtenthissec = 0;
static time_t thissec = 0;
time_t currsec;
int cnt;
assert(speed > 0);
cnt = 0;
while (++cnt <= PSYNC_FS_MAX_SHAPER_SLEEP_SEC) {
currsec = psync_timer_time();
pthread_mutex_lock(&throttle_mutex);
if (currsec != thissec) {
thissec = currsec;
writtenthissec = 0;
}
if (writtenthissec < speed) {
if (writtenthissec + size > speed) {
size -= speed - writtenthissec;
writtenthissec = speed;
} else {
writtenthissec += size;
pthread_mutex_unlock(&throttle_mutex);
assert(size <= speed);
psync_milisleep(size * 1000 / speed);
return;
}
}
pthread_mutex_unlock(&throttle_mutex);
psync_timer_wait_next_sec();
}
}
PSYNC_NOINLINE static int psync_fs_do_check_write_space(psync_openfile_t *of,
size_t size) {
const char *cachepath;
uint64_t minlocal, mult, speed;
int64_t freespc;
int freed;
cachepath = psync_setting_get_string(_PS(fscachepath));
minlocal = psync_setting_get_uint(_PS(minlocalfreespace));
freespc = psync_get_free_space_by_path(cachepath);
if (unlikely(freespc == -1)) {
debug(D_WARNING, "could not get free space of path %s", cachepath);
return 1;
}
// debug(D_NOTICE, "free space of %s is %lld minlocal %llu", cachepath,
// freespc, minlocal);
if (freespc >= minlocal + size) {
psync_set_local_full(0);
of->throttle = 0;
return 1;
}
of->throttle = 1;
pthread_mutex_unlock(&of->mutex);
debug(D_NOTICE, "free space is %lu, less than minimum %lu+%lu",
(unsigned long)freespc, (unsigned long)minlocal, (unsigned long)size);
psync_set_local_full(1);
if ((freespc <= minlocal / 2 || minlocal <= PSYNC_FS_PAGE_SIZE ||
freespc <= size)) {
if (psync_pagecache_free_from_read_cache(size * 2) < size * 2) {
debug(D_WARNING,
"free space is %lu, less than half of minimum %lu+%lu, returning "
"error",
(unsigned long)freespc, (unsigned long)minlocal,
(unsigned long)size);
psync_milisleep(5000);
return -EINTR;
} else {
debug(D_NOTICE,
"free space is %lu, less than half of minimum %lu+%lu, but we "
"managed to free from read cache",
(unsigned long)freespc, (unsigned long)minlocal,
(unsigned long)size);
freespc = minlocal / 2 + 1;
freed = 1;
}
} else if (freespc <= minlocal / 4 * 3) {
debug(D_NOTICE,
"free space is %lu, less than 3/4 of minimum %lu+%lu, will try to "
"free read cache pages",
(unsigned long)freespc, (unsigned long)minlocal, (unsigned long)size);
freed = psync_pagecache_free_from_read_cache(size) >= size;
} else
freed = 0;
if (psync_status.uploadspeed == 0) {
if (freed || psync_pagecache_free_from_read_cache(size) >= size) {
debug(D_NOTICE, "there is no active upload and we managed to free from "
"cache, not throttling write");
psync_fs_lock_file(of);
return 1;
}
}
minlocal /= 2;
mult = (freespc - minlocal) * 1023 / minlocal + 1;
assert(mult >= 1 && mult <= 1024);
speed = psync_status.uploadspeed * 3 / 2;
if (speed < PSYNC_FS_MIN_INITIAL_WRITE_SHAPER)
speed = PSYNC_FS_MIN_INITIAL_WRITE_SHAPER;
speed = speed * mult / 1024;
debug(D_NOTICE,
"limiting write speed to %luKb (%lub)/sec, speed multiplier %lu",
(unsigned long)speed / 1024, (unsigned long)speed, (unsigned long)mult);
psync_fs_throttle(size, speed);
debug(D_NOTICE, "continuing write");
psync_fs_lock_file(of);
return 1;
}
static int psync_fs_check_write_space(psync_openfile_t *of, size_t size,
fuse_off_t offset) {
if (!of->throttle && of->writeid % 64 != 0)
return 1;
if (of->currentsize >= offset + size) {
// if (of->newfile)
// return 1;
if (of->modified &&
psync_fs_check_modified_file_write_space(of, size, offset))
return 1;
}
return psync_fs_do_check_write_space(of, size);
}
static int psync_fs_write_modified(psync_openfile_t *of, const char *buf,
size_t size, fuse_off_t offset) {
psync_fs_index_record rec;
uint64_t ioff;
ssize_t bw;
if (unlikely_log(psync_fs_modfile_check_size_ok(of, offset)))
return -EIO;
ioff = of->indexoff++;
bw = psync_file_pwrite(of->datafile, buf, size, offset);
if (unlikely_log(bw == -1))
return -EIO;
rec.offset = offset;
rec.length = bw;
if (unlikely_log(psync_file_pwrite(of->indexfile, &rec, sizeof(rec),
sizeof(rec) * ioff +
sizeof(psync_fs_index_header)) !=
sizeof(rec)))
return -EIO;
psync_interval_tree_add(&of->writeintervals, offset, offset + bw);
if (of->currentsize < offset + size)
of->currentsize = offset + size;
return bw;
}
static int psync_fs_write_newfile(psync_openfile_t *of, const char *buf,
size_t size, fuse_off_t offset) {
ssize_t bw;
bw = psync_file_pwrite(of->datafile, buf, size, offset);
if (of->currentsize < offset + size && bw != -1)
of->currentsize = offset + size;
return bw;
}
static int psync_fs_write(const char *path, const char *buf, size_t size,
fuse_off_t offset, struct fuse_file_info *fi) {
psync_openfile_t *of;
int ret;
psync_fs_set_thread_name();
// debug(D_NOTICE, "write to %s of %lu at %lu", path, (unsigned long)size,
// (unsigned long)offset);
of = fh_to_openfile(fi->fh);
psync_fs_lock_file(of);
ret = psync_fs_check_write_space(of, size, offset);
if (unlikely_log(ret <= 0))
return ret;
psync_fs_inc_writeid_locked(of);
retry:
if (of->newfile) {
if (of->encrypted)
return psync_fs_crypto_write_newfile_locked(of, buf, size, offset);
else
ret = psync_fs_write_newfile(of, buf, size, offset);
pthread_mutex_unlock(&of->mutex);
if (unlikely_log(ret == -1))
return -EIO;
else
return ret;
} else {
if (unlikely(!of->modified)) {
ret = psync_fs_reopen_file_for_writing(of);
if (ret == 1)
goto retry;
else if (ret < 0) {
pthread_mutex_unlock(&of->mutex);
return ret;
}
}
if (of->encrypted)
return psync_fs_crypto_write_modified_locked(of, buf, size, offset);
else {
debug(D_NOTICE, "write of %lu bytes at offset %lu", (unsigned long)size,
(unsigned long)offset);
if (unlikely(of->staticfile)) {
ret = psync_fs_reopen_static_file_for_writing(of);
if (ret == 1)
goto retry;
else {
pthread_mutex_unlock(&of->mutex);
return ret;
}
} else
ret = psync_fs_write_modified(of, buf, size, offset);
}
pthread_mutex_unlock(&of->mutex);
return ret;
}
}
static int psync_fs_mkdir(const char *path, mode_t mode) {
psync_fspath_t *fpath;
int ret;
psync_fs_set_thread_name();
debug(D_NOTICE, "mkdir %s", path);
psync_sql_lock();
CHECK_LOGIN_LOCKED();
fpath = psync_fsfolder_resolve_path(path);
if (!fpath)
ret = -ENOENT;
else if (!(fpath->permissions & PSYNC_PERM_CREATE))
ret = -EACCES;
else if (fpath->flags & (PSYNC_FOLDER_FLAG_BACKUP_DEVICE_LIST |
PSYNC_FOLDER_FLAG_BACKUP_DEVICE))
ret = -EACCES;
else if (fpath->flags & PSYNC_FOLDER_FLAG_ENCRYPTED &&
psync_crypto_isexpired())
ret = -PSYNC_FS_ERR_CRYPTO_EXPIRED;
else
ret = psync_fstask_mkdir(fpath->folderid, fpath->name, fpath->flags);
psync_sql_unlock();
psync_free(fpath);
debug(D_NOTICE, "mkdir %s=%d", path, ret);
return ret;
}
#if defined(FUSE_HAS_CAN_UNLINK)
static int psync_fs_can_rmdir(const char *path) {
psync_fspath_t *fpath;
int ret;
psync_fs_set_thread_name();
debug(D_NOTICE, "can_rmdir %s", path);
psync_sql_lock();
fpath = psync_fsfolder_resolve_path(path);
if (!fpath)
ret = -ENOENT;
else if (!(fpath->permissions & PSYNC_PERM_DELETE))
ret = -EACCES;
else
ret = psync_fstask_can_rmdir(fpath->folderid, fpath->flags, fpath->name);
psync_sql_unlock();
psync_free(fpath);
debug(D_NOTICE, "can_rmdir %s=%d", path, ret);
return ret;
}
#endif
static int psync_fs_rmdir(const char *path) {
psync_fspath_t *fpath;
int ret;
psync_fs_set_thread_name();
debug(D_NOTICE, "rmdir %s", path);
psync_sql_lock();
CHECK_LOGIN_LOCKED();
fpath = psync_fsfolder_resolve_path(path);
if (!fpath)
ret = -ENOENT;
else if (!(fpath->permissions & PSYNC_PERM_DELETE))
ret = -EACCES;
else
ret = psync_fstask_rmdir(fpath->folderid, fpath->flags, fpath->name);
psync_sql_unlock();
psync_free(fpath);
debug(D_NOTICE, "rmdir %s=%d", path, ret);
return ret;
}
#if defined(FUSE_HAS_CAN_UNLINK)
static int psync_fs_can_unlink(const char *path) {
psync_fspath_t *fpath;
int ret;
psync_fs_set_thread_name();
debug(D_NOTICE, "can_unlink %s", path);
psync_sql_lock();
fpath = psync_fsfolder_resolve_path(path);
if (!fpath)
ret = -ENOENT;
else if (!(fpath->permissions & PSYNC_PERM_DELETE))
ret = -EACCES;
else
ret = psync_fstask_can_unlink(fpath->folderid, fpath->name);
psync_sql_unlock();
psync_free(fpath);
debug(D_NOTICE, "can_unlink %s=%d", path, ret);
return ret;
}
#endif
static int psync_fs_unlink(const char *path) {
psync_fspath_t *fpath;
int ret;
psync_fs_set_thread_name();
debug(D_NOTICE, "unlink %s", path);
psync_sql_lock();
CHECK_LOGIN_LOCKED();
fpath = psync_fsfolder_resolve_path(path);
if (!fpath)
ret = -ENOENT;
else if (!(fpath->permissions & PSYNC_PERM_DELETE))
ret = -EACCES;
else
ret = psync_fstask_unlink(fpath->folderid, fpath->name);
psync_sql_unlock();
if ((fpath->flags & PSYNC_FOLDER_FLAG_BACKUP) && ret == 0) {
// Send async event to UI to notify the user that he is deleting a backedup
// file.
debug(D_NOTICE, "Backedup file deleted in P drive. Send event. Flags: [%d]",
fpath->flags);
psync_run_thread1("psync_async_sync_delete", psync_async_ui_callback,
(void *)PEVENT_BKUP_F_DEL_DRIVE);
}
psync_free(fpath);
debug(D_NOTICE, "unlink %s=%d", path, ret);
return ret;
}
static int psync_fs_rename_static_file(psync_fstask_folder_t *srcfolder,
psync_fstask_creat_t *srccr,
psync_fsfolderid_t to_folderid,
const char *new_name) {
psync_fstask_creat_t *cr;
psync_fstask_unlink_t *un;
psync_fstask_folder_t *dstfolder;
size_t len, addlen;
dstfolder = psync_fstask_get_or_create_folder_tasks_locked(to_folderid);
cr = psync_fstask_find_creat(dstfolder, new_name, 0);
if (unlikely(cr)) {
debug(D_NOTICE, "renaming over creat of file %s in folderid %ld", new_name,
(long)to_folderid);
un = psync_fstask_find_unlink(dstfolder, new_name, cr->taskid);
if (un) {
psync_tree_del(&dstfolder->unlinks, &un->tree);
psync_free(un);
dstfolder->taskscnt--;
}
psync_tree_del(&dstfolder->creats, &cr->tree);
psync_free(cr);
dstfolder->taskscnt--;
}
len = strlen(new_name) + 1;
un = (psync_fstask_unlink_t *)psync_malloc(
offsetof(psync_fstask_unlink_t, name) + len);
un->fileid = 0;
un->taskid = srccr->taskid;
memcpy(un->name, new_name, len);
psync_fstask_inject_unlink(dstfolder, un);
addlen = psync_fstask_creat_local_offset(len - 1);
cr = (psync_fstask_creat_t *)psync_malloc(addlen +
sizeof(psync_fstask_local_creat_t));
cr->fileid = 0;
cr->rfileid = 0;
cr->taskid = srccr->taskid;
memcpy(cr->name, new_name, len);
memcpy(((char *)cr) + addlen, psync_fstask_creat_get_local(srccr),
sizeof(psync_fstask_local_creat_t));
psync_fstask_inject_creat(dstfolder, cr);
psync_fstask_release_folder_tasks_locked(dstfolder);
un = psync_fstask_find_unlink(srcfolder, srccr->name, srccr->taskid);
if (likely_log(un)) {
psync_tree_del(&srcfolder->unlinks, &un->tree);
psync_free(un);
srcfolder->taskscnt--;
}
psync_tree_del(&srcfolder->creats, &srccr->tree);
psync_free(srccr);
srcfolder->taskscnt--;
return 0;
}
static int psync_fs_can_move(psync_fsfolderid_t fromfolderid,
uint32_t frompermissions,
psync_fsfolderid_t tofolderid,
uint32_t topermissions, int sameshare) {
if (fromfolderid == tofolderid)
return (frompermissions & PSYNC_PERM_MODIFY) == PSYNC_PERM_MODIFY;
if ((frompermissions & PSYNC_PERM_ALL) == PSYNC_PERM_ALL &&
(topermissions & PSYNC_PERM_ALL) == PSYNC_PERM_ALL)
return 1;
if ((frompermissions & (PSYNC_PERM_DELETE | PSYNC_PERM_MODIFY)) == 0 ||
(topermissions & (PSYNC_PERM_CREATE | PSYNC_PERM_MODIFY)) == 0)
return 0;
if (sameshare)
return (frompermissions & PSYNC_PERM_MODIFY) != 0;
else
return (frompermissions & PSYNC_PERM_DELETE) &&
(topermissions & PSYNC_PERM_CREATE);
}
static int psync_fs_rename_folder(psync_fsfolderid_t folderid,
psync_fsfolderid_t parentfolderid,
const char *name, uint32_t srcpermissions,
psync_fsfolderid_t to_folderid,
const char *new_name, uint32_t targetperms,
uint32_t targetflags, int sameshare) {
if (!psync_fs_can_move(folderid, srcpermissions, to_folderid, targetperms,
sameshare))
return -EACCES;
return psync_fstask_rename_folder(folderid, parentfolderid, name, to_folderid,
new_name, targetflags);
}
static int psync_fs_rename_file(psync_fsfileid_t fileid,
psync_fsfolderid_t parentfolderid,
const char *name, uint32_t srcpermissions,
psync_fsfolderid_t to_folderid,
const char *new_name, uint32_t targetperms,
int sameshare) {
if (!psync_fs_can_move(parentfolderid, srcpermissions, to_folderid,
targetperms, sameshare))
return -EACCES;
return psync_fstask_rename_file(fileid, parentfolderid, name, to_folderid,
new_name);
}
static int psync_fs_is_file(psync_fsfolderid_t folderid, const char *name) {
psync_fstask_folder_t *folder;
psync_sql_res *res;
int ret;
folder = psync_fstask_get_folder_tasks_locked(folderid);
if (folder) {
if (psync_fstask_find_creat(folder, name, 0))
ret = 2;
else if (psync_fstask_find_unlink(folder, name, 0))
ret = 1;
else
ret = 0;
psync_fstask_release_folder_tasks_locked(folder);
if (ret)
return ret - 1;
}
res =
psync_sql_query("SELECT id FROM file WHERE parentfolderid=? AND name=?");
psync_sql_bind_uint(res, 1, folderid);
psync_sql_bind_string(res, 2, name);
if (psync_sql_fetch_rowint(res))
ret = 1;
else
ret = 0;
psync_sql_free_result(res);
return ret;
}
static int psync_fs_is_folder(psync_fsfolderid_t folderid, const char *name) {
psync_fstask_folder_t *folder;
psync_sql_res *res;
int ret;
folder = psync_fstask_get_folder_tasks_locked(folderid);
if (folder) {
if (psync_fstask_find_mkdir(folder, name, 0))
ret = 2;
else if (psync_fstask_find_rmdir(folder, name, 0))
ret = 1;
else
ret = 0;
psync_fstask_release_folder_tasks_locked(folder);
if (ret)
return ret - 1;
}
res = psync_sql_query(
"SELECT id FROM folder WHERE parentfolderid=? AND name=?");
psync_sql_bind_uint(res, 1, folderid);
psync_sql_bind_string(res, 2, name);
if (psync_sql_fetch_rowint(res))
ret = 1;
else
ret = 0;
psync_sql_free_result(res);
return ret;
}
static int psync_fs_is_folder_nonempty(psync_fsfolderid_t folderid) {
psync_fstask_folder_t *folder;
psync_sql_res *res;
psync_str_row row;
folder = psync_fstask_get_folder_tasks_locked(folderid);
if (folder && (folder->creats || folder->mkdirs)) {
psync_fstask_release_folder_tasks_locked(folder);
return 1;
}
if (folderid >= 0) {
res = psync_sql_query("SELECT name FROM file WHERE parentfolderid=?");
psync_sql_bind_uint(res, 1, folderid);
while ((row = psync_sql_fetch_rowstr(res)))
if (!folder || !psync_fstask_find_unlink(folder, row[0], 0)) {
psync_sql_free_result(res);
if (folder)
psync_fstask_release_folder_tasks_locked(folder);
return 1;
}
psync_sql_free_result(res);
res = psync_sql_query("SELECT name FROM folder WHERE parentfolderid=?");
psync_sql_bind_uint(res, 1, folderid);
while ((row = psync_sql_fetch_rowstr(res)))
if (!folder || !psync_fstask_find_rmdir(folder, row[0], 0)) {
psync_sql_free_result(res);
if (folder)
psync_fstask_release_folder_tasks_locked(folder);
return 1;
}
psync_sql_free_result(res);
}
if (folder)
psync_fstask_release_folder_tasks_locked(folder);
return 0;
}
static int psync_fs_is_nonempty_folder(psync_fsfolderid_t parent_folderid,
const char *name) {
psync_fstask_folder_t *folder;
psync_fstask_mkdir_t *mk;
psync_sql_res *res;
psync_uint_row row;
int ret;
folder = psync_fstask_get_folder_tasks_locked(parent_folderid);
if (folder) {
if ((mk = psync_fstask_find_mkdir(folder, name, 0))) {
ret = psync_fs_is_folder_nonempty(mk->folderid) + 1;
} else if (psync_fstask_find_rmdir(folder, name, 0)) {
ret = 1;
} else {
ret = 0;
}
psync_fstask_release_folder_tasks_locked(folder);
if (ret)
return ret - 1;
}
res = psync_sql_query(
"SELECT id FROM folder WHERE parentfolderid=? AND name=?");
psync_sql_bind_uint(res, 1, parent_folderid);
psync_sql_bind_string(res, 2, name);
if ((row = psync_sql_fetch_rowint(res))) {
ret = psync_fs_is_folder_nonempty(row[0]);
} else {
ret = 0;
}
psync_sql_free_result(res);
return ret;
}
static int psync_fs_rename(const char *old_path, const char *new_path) {
psync_fspath_t *fold_path, *fnew_path;
psync_sql_res *res;
psync_fstask_folder_t *folder;
psync_fstask_mkdir_t *mkdir;
psync_fstask_creat_t *creat;
psync_uint_row row;
psync_fileorfolderid_t fid;
uint64_t flags;
psync_fsfolderid_t new_fid, old_fid;
int ret;
psync_fs_set_thread_name();
debug(D_NOTICE, "rename %s to %s", old_path, new_path);
folder = NULL;
psync_sql_lock();
CHECK_LOGIN_LOCKED();
fold_path = psync_fsfolder_resolve_path(old_path);
fnew_path = psync_fsfolder_resolve_path(new_path);
if (!fold_path || !fnew_path)
goto err_enoent;
if ((fold_path->flags & PSYNC_FOLDER_FLAG_ENCRYPTED) !=
(fnew_path->flags & PSYNC_FOLDER_FLAG_ENCRYPTED)) {
ret = -PSYNC_FS_ERR_MOVE_ACROSS_CRYPTO;
goto finish;
}
if (fold_path->folderid != fnew_path->folderid &&
((fold_path->flags | fnew_path->flags) &
(PSYNC_FOLDER_FLAG_BACKUP_DEVICE_LIST |
PSYNC_FOLDER_FLAG_BACKUP_DEVICE))) {
ret = -EACCES;
goto finish;
}
folder = psync_fstask_get_folder_tasks_locked(fold_path->folderid);
new_fid = psync_get_folderid(fnew_path->folderid, fnew_path->name);
old_fid = psync_get_folderid(fold_path->folderid, fold_path->name);
if (folder) {
if ((mkdir = psync_fstask_find_mkdir(folder, fold_path->name, 0))) {
if (psync_fs_is_file(fnew_path->folderid, fnew_path->name)) {
ret = -ENOTDIR;
} else if (psync_fs_is_nonempty_folder(fnew_path->folderid,
fnew_path->name) &&
(new_fid != old_fid)) {
ret = -ENOTEMPTY;
} else {
ret = psync_fs_rename_folder(mkdir->folderid, fold_path->folderid,
fold_path->name, fold_path->permissions,
fnew_path->folderid, fnew_path->name,
fnew_path->permissions, fnew_path->flags,
fold_path->shareid == fnew_path->shareid);
}
goto finish;
} else if ((creat = psync_fstask_find_creat(folder, fold_path->name, 0))) {
if (psync_fs_is_folder(fnew_path->folderid, fnew_path->name))
ret = -EISDIR;
else if (unlikely(creat->fileid == 0))
ret = psync_fs_rename_static_file(folder, creat, fnew_path->folderid,
fnew_path->name);
else
ret = psync_fs_rename_file(
creat->fileid, fold_path->folderid, fold_path->name,
fold_path->permissions, fnew_path->folderid, fnew_path->name,
fnew_path->permissions, fold_path->shareid == fnew_path->shareid);
goto finish;
}
}
if (!folder || !psync_fstask_find_rmdir(folder, fold_path->name, 0)) {
res = psync_sql_query(
"SELECT id, flags FROM folder WHERE parentfolderid=? AND name=?");
psync_sql_bind_uint(res, 1, fold_path->folderid);
psync_sql_bind_string(res, 2, fold_path->name);
if ((row = psync_sql_fetch_rowint(res))) {
fid = row[0];
flags = row[1];
psync_sql_free_result(res);
if (fold_path->folderid != fnew_path->folderid &&
(flags &
(PSYNC_FOLDER_FLAG_PUBLIC_ROOT |
PSYNC_FOLDER_FLAG_BACKUP_DEVICE_LIST |
PSYNC_FOLDER_FLAG_BACKUP_DEVICE | PSYNC_FOLDER_FLAG_BACKUP_ROOT)))
ret = -EPERM;
else if (psync_fs_is_file(fnew_path->folderid, fnew_path->name))
ret = -ENOTDIR;
else if (psync_fs_is_nonempty_folder(fnew_path->folderid,
fnew_path->name) &&
(new_fid != old_fid)) {
ret = -ENOTEMPTY;
} else
ret = psync_fs_rename_folder(
fid, fold_path->folderid, fold_path->name, fold_path->permissions,
fnew_path->folderid, fnew_path->name, fnew_path->permissions,
fnew_path->flags, fold_path->shareid == fnew_path->shareid);
goto finish;
}
psync_sql_free_result(res);
}
if (!folder || !psync_fstask_find_unlink(folder, fold_path->name, 0)) {
res = psync_sql_query(
"SELECT id FROM file WHERE parentfolderid=? AND name=?");
psync_sql_bind_uint(res, 1, fold_path->folderid);
psync_sql_bind_string(res, 2, fold_path->name);
if ((row = psync_sql_fetch_rowint(res))) {
fid = row[0];
psync_sql_free_result(res);
if (psync_fs_is_folder(fnew_path->folderid, fnew_path->name))
ret = -EISDIR;
else
ret = psync_fs_rename_file(fid, fold_path->folderid, fold_path->name,
fold_path->permissions, fnew_path->folderid,
fnew_path->name, fnew_path->permissions,
fold_path->shareid == fnew_path->shareid);
goto finish;
}
psync_sql_free_result(res);
}
goto err_enoent;
finish:
if (folder)
psync_fstask_release_folder_tasks_locked(folder);
psync_sql_unlock();
psync_free(fold_path);
psync_free(fnew_path);
return PRINT_RETURN_FORMAT(ret, " for rename from %s to %s", old_path,
new_path);
err_enoent:
if (folder)
psync_fstask_release_folder_tasks_locked(folder);
psync_sql_unlock();
psync_free(fold_path);
psync_free(fnew_path);
debug(D_NOTICE, "returning ENOENT, folder not found");
return -ENOENT;
}
static int psync_fs_statfs(const char *path, struct statvfs *stbuf) {
uint64_t q, uq;
psync_fs_set_thread_name();
debug(D_NOTICE, "statfs %s", path);
if (waitingforlogin)
return -EACCES;
/* TODO:
return -ENOENT if path is invalid if fuse does not call getattr first
*/
memset(stbuf, 0, sizeof(struct statvfs));
q = psync_get_uint_value("quota");
uq = psync_get_uint_value("usedquota");
if (uq > q)
uq = q;
stbuf->f_bsize = FS_BLOCK_SIZE;
stbuf->f_frsize = FS_BLOCK_SIZE;
stbuf->f_blocks = q / FS_BLOCK_SIZE;
stbuf->f_bfree = stbuf->f_blocks - uq / FS_BLOCK_SIZE;
stbuf->f_bavail = stbuf->f_bfree;
stbuf->f_flag = ST_NOSUID;
stbuf->f_namemax = 1024;
return 0;
}
static int psync_fs_chmod(const char *path, mode_t mode) {
psync_fs_set_thread_name();
debug(D_NOTICE, "chmod %s %u", path, (unsigned)mode);
return 0;
}
int psync_fs_chown(const char *path, uid_t uid, gid_t gid) {
psync_fs_set_thread_name();
debug(D_NOTICE, "chown %s %u %u", path, (unsigned)uid, (unsigned)gid);
return 0;
}
static int psync_fs_set_filetime_locked(psync_fsfileid_t fileid,
const struct timespec *tv, int crtime,
uint64_t current) {
if (fileid > 0)
return psync_fstask_set_mtime(fileid, current, tv->tv_sec, crtime);
else {
char fileidhex[sizeof(psync_fsfileid_t) * 2 + 2], *filename;
const char *cachepath;
psync_tree *tr;
psync_openfile_t *fl;
int64_t d;
int ret;
tr = openfiles;
fl = NULL;
while (tr) {
d = fileid - psync_tree_element(tr, psync_openfile_t, tree)->fileid;
if (d < 0)
tr = tr->left;
else if (d > 0)
tr = tr->right;
else {
fl = psync_tree_element(tr, psync_openfile_t, tree);
break;
}
}
fileid = -fileid;
psync_binhex(fileidhex, &fileid, sizeof(psync_fsfileid_t));
fileidhex[sizeof(psync_fsfileid_t)] = 'd';
fileidhex[sizeof(psync_fsfileid_t) + 1] = 0;
cachepath = psync_setting_get_string(_PS(fscachepath));
filename =
psync_strcat(cachepath, PSYNC_DIRECTORY_SEPARATOR, fileidhex, NULL);
if (fl && fl->datafile != INVALID_HANDLE_VALUE) {
debug(D_NOTICE, "found open file for file id %ld", (long)fl->fileid);
if (crtime)
ret =
psync_set_crtime_mtime_by_fd(fl->datafile, filename, tv->tv_sec, 0);
else
ret =
psync_set_crtime_mtime_by_fd(fl->datafile, filename, 0, tv->tv_sec);
} else {
if (crtime)
ret = psync_set_crtime_mtime(filename, tv->tv_sec, 0);
else
ret = psync_set_crtime_mtime(filename, 0, tv->tv_sec);
}
debug(D_NOTICE, "setting %s time of %s to %lu=%d",
crtime ? "creation" : "modification", filename,
(unsigned long)tv->tv_sec, ret);
psync_free(filename);
return ret ? -EACCES : 0;
}
}
static int psync_fs_set_foldertime_locked(psync_fsfolderid_t folderid,
const struct timespec *tv, int crtime,
uint64_t current) {
debug(D_NOTICE, "request to set time of folderid %ld ignored",
(long)folderid);
return 0;
}
static int psync_fs_set_time_locked(psync_fsfolderid_t folderid,
const char *name, const struct timespec *tv,
int crtime) {
psync_fstask_folder_t *folder;
psync_fstask_creat_t *creat;
psync_fstask_mkdir_t *mkdir;
psync_fstask_unlink_t *un;
psync_fstask_rmdir_t *rm;
psync_sql_res *res;
psync_uint_row row;
folder = psync_fstask_get_folder_tasks_rdlocked(folderid);
if (folder) {
if ((creat = psync_fstask_find_creat(folder, name, 0))) {
if (creat->fileid > 0) {
res =
psync_sql_query_nolock("SELECT mtime, ctime FROM file WHERE id=?");
psync_sql_bind_uint(res, 1, creat->fileid);
if ((row = psync_sql_fetch_rowint(res))) {
uint64_t ctm = row[crtime];
psync_sql_free_result(res);
return psync_fs_set_filetime_locked(creat->fileid, tv, crtime, ctm);
} else {
psync_sql_free_result(res);
debug(D_WARNING,
"found creat in folderid %lu for %s with fileid %lu not "
"present in the database",
(unsigned long)folderid, name, (unsigned long)creat->fileid);
return -ENOENT;
}
} else
return psync_fs_set_filetime_locked(creat->fileid, tv, crtime, 0);
}
if ((mkdir = psync_fstask_find_mkdir(folder, name, 0)))
return psync_fs_set_foldertime_locked(mkdir->folderid, tv, crtime, 0);
un = psync_fstask_find_unlink(folder, name, 0);
rm = psync_fstask_find_rmdir(folder, name, 0);
} else {
un = NULL;
rm = NULL;
}
if (!un && folderid >= 0) {
res = psync_sql_query_nolock(
"SELECT id, mtime, ctime FROM file WHERE parentfolderid=? AND name=?");
psync_sql_bind_uint(res, 1, folderid);
psync_sql_bind_string(res, 2, name);
if ((row = psync_sql_fetch_rowint(res))) {
uint64_t fileid = row[0];
uint64_t ctm = row[1 + crtime];
psync_sql_free_result(res);
return psync_fs_set_filetime_locked(fileid, tv, crtime, ctm);
}
psync_sql_free_result(res);
}
if (!rm && folderid >= 0) {
res = psync_sql_query_nolock("SELECT id, permissions, mtime, ctime FROM "
"folder WHERE parentfolderid=? AND name=?");
psync_sql_bind_uint(res, 1, folderid);
psync_sql_bind_string(res, 2, name);
if ((row = psync_sql_fetch_rowint(res))) {
uint64_t folderid = row[0];
uint64_t permissions = row[1];
uint64_t ctm = row[2 + crtime];
psync_sql_free_result(res);
if (!(permissions & PSYNC_PERM_MODIFY))
return -EACCES;
return psync_fs_set_foldertime_locked(folderid, tv, crtime, ctm);
}
psync_sql_free_result(res);
}
return -ENOENT;
}
static int psync_fs_set_time(const char *path, const struct timespec *tv,
int crtime) {
psync_fspath_t *fpath;
int ret;
psync_sql_lock();
CHECK_LOGIN_LOCKED();
fpath = psync_fsfolder_resolve_path(path);
if (!fpath)
ret = -ENOENT;
else if (!(fpath->permissions & PSYNC_PERM_MODIFY))
ret = -EACCES;
else
ret = psync_fs_set_time_locked(fpath->folderid, fpath->name, tv, crtime);
psync_sql_unlock();
psync_free(fpath);
return ret;
}
#if defined(FUSE_HAS_SETCRTIME)
static int psync_fs_setcrtime(const char *path, const struct timespec *tv) {
psync_fs_set_thread_name();
debug(D_NOTICE, "setcrtime %s %lu", path, tv->tv_sec);
return psync_fs_set_time(path, tv, 1);
}
#endif
static int psync_fs_utimens(const char *path, const struct timespec tv[2]) {
psync_fs_set_thread_name();
debug(D_NOTICE, "utimens %s %lu", path, tv[1].tv_sec);
return psync_fs_set_time(path, &tv[1], 0);
}
static int psync_fs_ftruncate_of_locked(psync_openfile_t *of, fuse_off_t size) {
int ret;
if (of->currentsize == size) {
debug(D_NOTICE, "not truncating as size is already %lu",
(long unsigned)size);
return 0;
}
psync_fs_inc_writeid_locked(of);
retry:
if (unlikely(!of->newfile && !of->modified)) {
ret = psync_fs_reopen_file_for_writing(of);
if (ret == 1)
goto retry;
else if (ret < 0)
return ret;
}
if (of->encrypted)
return psync_fs_crypto_ftruncate(of, size);
else {
if (psync_fs_modfile_check_size_ok(of, size))
ret = -PRINT_RETURN_CONST(EIO);
else if (of->currentsize != size &&
(psync_file_seek(of->datafile, size, P_SEEK_SET) == -1 ||
psync_file_truncate(of->datafile)))
ret = -PRINT_RETURN_CONST(EIO);
else {
ret = 0;
of->currentsize = size;
}
}
return ret;
}
static int psync_fs_ftruncate(const char *path, fuse_off_t size,
struct fuse_file_info *fi) {
psync_openfile_t *of;
int ret;
psync_fs_set_thread_name();
debug(D_NOTICE, "ftruncate %s %lu", path, (unsigned long)size);
of = fh_to_openfile(fi->fh);
psync_fs_lock_file(of);
ret = psync_fs_ftruncate_of_locked(of, size);
pthread_mutex_unlock(&of->mutex);
return PRINT_RETURN_FORMAT(ret, " for ftruncate of %s to %lu", path,
(unsigned long)size);
}
static int psync_fs_truncate(const char *path, fuse_off_t size) {
struct fuse_file_info fi;
int ret;
psync_fs_set_thread_name();
debug(D_NOTICE, "truncate %s %lu", path, (unsigned long)size);
memset(&fi, 0, sizeof(fi));
ret = psync_fs_open(path, &fi);
if (ret)
return ret;
ret = psync_fs_ftruncate(path, size, &fi);
psync_fs_flush(path, &fi);
psync_fs_release(path, &fi);
return ret;
}
static void psync_fs_start_callback_timer(psync_timer_t timer, void *ptr) {
psync_generic_callback_t callback;
psync_timer_stop(timer);
callback = psync_start_callback;
if (callback)
psync_run_thread("fs start callback", callback);
}
static void *psync_fs_init(struct fuse_conn_info *conn) {
#if defined(FUSE_CAP_ASYNC_READ)
conn->want |= FUSE_CAP_ASYNC_READ;
#endif
#if defined(FUSE_CAP_ATOMIC_O_TRUNC)
conn->want |= FUSE_CAP_ATOMIC_O_TRUNC;
#endif
#if defined(FUSE_CAP_BIG_WRITES)
conn->want |= FUSE_CAP_BIG_WRITES;
#endif
conn->max_readahead = 1024 * 1024;
#if !defined(P_OS_LINUX)
conn->max_write = FS_MAX_WRITE;
#endif
if (psync_start_callback)
psync_timer_register(psync_fs_start_callback_timer, 1, NULL);
return 0;
}
static pthread_mutex_t fsrefreshmutex = PTHREAD_MUTEX_INITIALIZER;
static time_t lastfsrefresh = 0;
static int fsrefreshtimerscheduled = 0;
#define REFRESH_SEC 3
static void psync_invalidate_os_cache_noret() {
char *path;
pthread_mutex_lock(&start_mutex);
if (started == 1)
path = psync_strdup(psync_current_mountpoint);
else
path = NULL;
pthread_mutex_unlock(&start_mutex);
if (path) {
psync_invalidate_os_cache(path);
psync_free(path);
}
}
static void psync_fs_refresh_timer(psync_timer_t timer, void *ptr) {
time_t ct;
ct = psync_timer_time();
psync_timer_stop(timer);
pthread_mutex_lock(&fsrefreshmutex);
fsrefreshtimerscheduled = 0;
lastfsrefresh = ct;
pthread_mutex_unlock(&fsrefreshmutex);
psync_run_thread("os cache invalidate timer",
psync_invalidate_os_cache_noret);
}
void psync_fs_refresh() {
time_t ct;
int todo;
if (!psync_invalidate_os_cache_needed())
return;
ct = psync_timer_time();
todo = 0;
pthread_mutex_lock(&fsrefreshmutex);
if (fsrefreshtimerscheduled)
todo = 2;
else if (lastfsrefresh + REFRESH_SEC < ct)
lastfsrefresh = ct;
else {
todo = 1;
fsrefreshtimerscheduled = 1;
}
pthread_mutex_unlock(&fsrefreshmutex);
if (todo == 0) {
debug(D_NOTICE, "running cache invalidate direct");
psync_run_thread("os cache invalidate", psync_invalidate_os_cache_noret);
} else if (todo == 1) {
debug(D_NOTICE, "setting timer to invalidate cache");
psync_timer_register(psync_fs_refresh_timer, REFRESH_SEC, NULL);
}
}
int psync_fs_need_per_folder_refresh_f() {
#if psync_fs_need_per_folder_refresh_const()
return started == 1;
#else
return 0;
#endif
}
void psync_fs_refresh_folder(psync_folderid_t folderid) {
char *path, *fpath;
unsigned char rndbuff[20];
char rndhex[42];
psync_file_t fd;
path =
psync_get_path_by_folderid_sep(folderid, PSYNC_DIRECTORY_SEPARATOR, NULL);
if (path == PSYNC_INVALID_PATH)
return;
psync_ssl_rand_weak(rndbuff, sizeof(rndbuff));
psync_binhex(rndhex, rndbuff, sizeof(rndbuff));
rndhex[2 * sizeof(rndbuff)] = 0;
pthread_mutex_lock(&start_mutex);
if (started == 1) {
if (psync_invalidate_os_cache_needed())
fpath = psync_strcat(psync_current_mountpoint, path, NULL);
else
fpath = psync_strcat(psync_current_mountpoint, path, "/",
psync_fake_prefix, rndhex, NULL);
} else
fpath = NULL;
pthread_mutex_unlock(&start_mutex);
psync_free(path);
if (!fpath)
return;
if (psync_invalidate_os_cache_needed())
psync_invalidate_os_cache(fpath);
else {
debug(D_NOTICE, "creating fake file %s", fpath);
fd = psync_file_open(fpath, P_O_WRONLY, P_O_CREAT);
if (fd != INVALID_HANDLE_VALUE) {
psync_file_close(fd);
psync_file_delete(fpath);
}
}
psync_free(fpath);
}
static char *psync_fuse_get_mountpoint() {
psync_stat_t st;
char *mp;
mp = psync_strdup(psync_setting_get_string(_PS(fsroot)));
if (psync_stat(mp, &st) && psync_mkdir(mp)) {
psync_free(mp);
return NULL;
}
return mp;
}
char *psync_fs_getmountpoint() {
char *ret;
pthread_mutex_lock(&start_mutex);
if (started == 1)
ret = psync_strdup(psync_current_mountpoint);
else
ret = NULL;
pthread_mutex_unlock(&start_mutex);
return ret;
}
void psync_fs_register_start_callback(psync_generic_callback_t callback) {
psync_start_callback = callback;
}
char *psync_fs_get_path_by_folderid(psync_folderid_t folderid) {
char *mp, *path, *ret;
pthread_mutex_lock(&start_mutex);
if (started == 1)
mp = psync_strdup(psync_current_mountpoint);
else
mp = NULL;
pthread_mutex_unlock(&start_mutex);
if (!mp || folderid == 0)
return mp;
path =
psync_get_path_by_folderid_sep(folderid, PSYNC_DIRECTORY_SEPARATOR, NULL);
if (path == PSYNC_INVALID_PATH) {
psync_free(mp);
return NULL;
}
ret = psync_strcat(mp, path, NULL);
psync_free(mp);
psync_free(path);
return ret;
}
#if IS_DEBUG
static void psync_fs_dump_internals() {
psync_openfile_t *of;
debug(D_NOTICE, "dumping internal state");
psync_sql_rdlock();
psync_tree_for_each_element(of, openfiles, psync_openfile_t, tree)
debug(D_NOTICE, "open file %s fileid %ld folderid %ld", of->currentname,
(long)of->fileid, (long)of->currentfolder->folderid);
psync_fstask_dump_state();
psync_sql_rdunlock();
}
#endif
static void psync_fs_do_stop(void) {
struct timespec ts;
debug(D_NOTICE, "stopping");
pthread_mutex_lock(&start_mutex);
if (started == 1) {
#if defined(P_OS_LINUX)
char *mp;
mp = psync_fuse_get_mountpoint();
fuse_unmount(mp, psync_fuse_channel);
#endif
debug(D_NOTICE, "running fuse_exit");
fuse_exit(psync_fuse);
started = 2;
debug(D_NOTICE, "fuse_exit exited, flushing cache");
psync_pagecache_flush();
debug(D_NOTICE, "cache flushed, waiting for fuse to exit");
psync_nanotime(&ts);
ts.tv_sec += 2;
if (pthread_cond_timedwait(&start_cond, &start_mutex, &ts))
debug(D_NOTICE, "timeouted waiting for fuse to exit");
else
debug(D_NOTICE, "waited for fuse to exit");
#if IS_DEBUG
psync_fs_dump_internals();
#endif
psync_free(mp);
}
pthread_mutex_unlock(&start_mutex);
}
void psync_fs_stop() { psync_fs_do_stop(); }
static void psync_signal_handler(int sig) {
debug(D_NOTICE, "got signal %d", sig);
psync_fs_do_stop();
exit(1);
}
#if IS_DEBUG
static void psync_usr1_handler(int sig) {
// debug(D_NOTICE, "got signal %d", sig);
psync_run_thread("dump signal", psync_fs_dump_internals);
}
#endif
static void psync_set_signal(int sig, void (*handler)(int)) {
struct sigaction sa;
if (unlikely_log(sigaction(sig, NULL, &sa)))
return;
if (sa.sa_handler == SIG_DFL) {
memset(&sa, 0, sizeof(struct sigaction));
sigemptyset(&(sa.sa_mask));
sa.sa_handler = handler;
sa.sa_flags = 0;
sigaction(sig, &sa, NULL);
}
}
static void psync_setup_signals() {
psync_set_signal(SIGTERM, psync_signal_handler);
psync_set_signal(SIGINT, psync_signal_handler);
psync_set_signal(SIGHUP, psync_signal_handler);
#if IS_DEBUG
psync_set_signal(SIGUSR1, psync_usr1_handler);
#endif
}
static void psync_fs_init_once() {
#if psync_fs_need_per_folder_refresh_const()
unsigned char rndbuff[16];
char rndhex[34];
psync_ssl_rand_weak(rndbuff, sizeof(rndbuff));
psync_binhex(rndhex, rndbuff, sizeof(rndbuff));
rndhex[2 * sizeof(rndbuff)] = 0;
psync_fake_prefix = psync_strcat(".refresh", rndhex, NULL);
psync_fake_prefix_len = strlen(psync_fake_prefix);
#endif
psync_fstask_init();
psync_pagecache_init();
atexit(psync_fs_do_stop);
psync_setup_signals();
psync_fsstatic_add_files();
psync_fstask_add_banned_folders();
}
static void psync_fuse_thread() {
int fr;
pthread_mutex_lock(&start_mutex);
if (!initonce) {
psync_fs_init_once();
initonce = 1;
}
pthread_mutex_unlock(&start_mutex);
debug(D_NOTICE, "running fuse_loop_mt");
fr = fuse_loop_mt(psync_fuse);
debug(D_NOTICE, "fuse_loop_mt exited with code %d, running fuse_destroy", fr);
pthread_mutex_lock(&start_mutex);
fuse_destroy(psync_fuse);
debug(D_NOTICE, "fuse_destroy exited");
psync_free(psync_current_mountpoint);
started = 0;
pthread_cond_broadcast(&start_cond);
pthread_mutex_unlock(&start_mutex);
}
// Returns true if FUSE 3 is installed on the user's machine.
// Returns false if FUSE version is less than 3.
static char is_fuse3_installed_on_system() {
// Assuming that fusermount3 is only available on FUSE 3.
FILE *pipe = popen("which fusermount3", "r");
if (!pipe) {
return 0;
}
char output[1024];
memset(output, 0, sizeof(output));
if (fgets(output, sizeof(output), pipe) != NULL) {
return 0;
}
pclose(pipe);
size_t outlen = strlen(output);
return outlen > 0;
}
static int psync_fs_do_start() {
char *mp;
struct fuse_operations psync_oper;
struct fuse_args args = FUSE_ARGS_INIT(0, NULL);
// it seems that fuse option parser ignores the first argument
// it is ignored as it's like in the exec() parameters, argv[0] is the program
#if defined(P_OS_LINUX)
fuse_opt_add_arg(&args, "argv");
fuse_opt_add_arg(&args, "-oauto_unmount");
// fuse_opt_add_arg(&args, "-ouse_ino");
fuse_opt_add_arg(&args, "-ofsname=" DEFAULT_FUSE_MOUNT_POINT ".fs");
if (!is_fuse3_installed_on_system()) {
fuse_opt_add_arg(&args, "-ononempty");
}
fuse_opt_add_arg(&args, "-ohard_remove");
// fuse_opt_add_arg(&args, "-d");
#endif
memset(&psync_oper, 0, sizeof(psync_oper));
psync_oper.init = psync_fs_init;
psync_oper.getattr = psync_fs_getattr;
psync_oper.readdir = psync_fs_readdir;
psync_oper.open = psync_fs_open;
psync_oper.create = psync_fs_creat;
psync_oper.release = psync_fs_release;
psync_oper.flush = psync_fs_flush;
psync_oper.fsync = psync_fs_fsync;
psync_oper.fsyncdir = psync_fs_fsyncdir;
psync_oper.read = psync_fs_read;
psync_oper.write = psync_fs_write;
psync_oper.mkdir = psync_fs_mkdir;
psync_oper.rmdir = psync_fs_rmdir;
psync_oper.unlink = psync_fs_unlink;
psync_oper.rename = psync_fs_rename;
psync_oper.statfs = psync_fs_statfs;
psync_oper.chmod = psync_fs_chmod;
psync_oper.chown = psync_fs_chown;
psync_oper.utimens = psync_fs_utimens;
psync_oper.ftruncate = psync_fs_ftruncate;
psync_oper.truncate = psync_fs_truncate;
psync_oper.setxattr = psync_fs_setxattr;
psync_oper.getxattr = psync_fs_getxattr;
psync_oper.listxattr = psync_fs_listxattr;
psync_oper.removexattr = psync_fs_removexattr;
#if defined(FUSE_HAS_CAN_UNLINK)
psync_oper.can_unlink = psync_fs_can_unlink;
psync_oper.can_rmdir = psync_fs_can_rmdir;
#endif
#if defined(FUSE_HAS_SETCRTIME)
psync_oper.setcrtime = psync_fs_setcrtime;
#endif
myuid = getuid();
mygid = getgid();
pthread_mutex_lock(&start_mutex);
if (started)
goto err00;
mp = psync_fuse_get_mountpoint();
psync_fuse_channel = fuse_mount(mp, &args);
if (unlikely_log(!psync_fuse_channel))
goto err0;
psync_fuse = fuse_new(psync_fuse_channel, &args, &psync_oper,
sizeof(psync_oper), NULL);
if (unlikely_log(!psync_fuse))
goto err1;
psync_current_mountpoint = mp;
started = 1;
pthread_mutex_unlock(&start_mutex);
fuse_opt_free_args(&args);
psync_run_thread("fuse", psync_fuse_thread);
return 0;
err1:
fuse_unmount(mp, psync_fuse_channel);
err0:
psync_free(mp);
err00:
pthread_mutex_unlock(&start_mutex);
fuse_opt_free_args(&args);
return -1;
}
static void psync_fs_wait_start() {
debug(D_NOTICE, "waiting for online status");
psync_wait_status(PSTATUS_TYPE_ONLINE, PSTATUS_ONLINE_ONLINE);
if (psync_do_run) {
debug(D_NOTICE, "starting fs");
psync_fs_do_start();
}
}
static void psync_fs_wait_login() {
debug(D_NOTICE, "waiting for online status");
psync_wait_status(PSTATUS_TYPE_ONLINE, PSTATUS_ONLINE_ONLINE);
debug(D_NOTICE, "waited for online status");
psync_sql_lock();
waitingforlogin = 0;
psync_sql_unlock();
}
void psync_fs_pause_until_login() {
psync_sql_lock();
if (waitingforlogin == 0) {
waitingforlogin = 1;
debug(D_NOTICE, "stopping fs until login");
psync_run_thread("fs wait login", psync_fs_wait_login);
}
psync_sql_unlock();
}
void psync_fs_clean_tasks() { psync_fstask_clean(); }
int psync_fs_start() {
uint32_t status;
int ret;
pthread_mutex_lock(&start_mutex);
if (started)
ret = -1;
else
ret = 0;
pthread_mutex_unlock(&start_mutex);
if (ret)
return ret;
status = psync_status_get(PSTATUS_TYPE_AUTH);
debug(D_NOTICE, "auth status=%u", status);
if (status == PSTATUS_AUTH_PROVIDED)
return psync_fs_do_start();
else {
psync_run_thread("fs wait login", psync_fs_wait_start);
return 0;
}
}
int psync_fs_isstarted() {
int s;
pthread_mutex_lock(&start_mutex);
s = started;
pthread_mutex_unlock(&start_mutex);
return s == 1;
}
int psync_fs_remount() {
int s;
pthread_mutex_lock(&start_mutex);
s = started;
pthread_mutex_unlock(&start_mutex);
if (s) {
psync_fs_stop();
return psync_fs_start();
} else
return 0;
}