seafile/daemon/filelock-mgr.c
feiniks 3290f3fd80
Get notification from server (#2527)
* Get notification from server

Don't sync repo when repo is subscribed

Get notification from server

* Disconnect notification server when subscribed repos is 0 and modify log info

* Add jwt certification

* Use server lock

* VS add libwebsockets

* Add check notification server

* Check notif server once

* Check locks and folder perms after fileserver is recovered

* Add comment

Co-authored-by: heran yang <heran.yang@seafile.com>
2022-12-24 16:51:55 +08:00

765 lines
22 KiB
C

/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
#include "common.h"
#include <pthread.h>
#include "seafile-session.h"
#include "filelock-mgr.h"
#include "set-perm.h"
#include "log.h"
#include "db.h"
struct _FilelockMgrPriv {
GHashTable *repo_locked_files;
pthread_mutex_t hash_lock;
sqlite3 *db;
pthread_mutex_t db_lock;
};
typedef struct _FilelockMgrPriv FilelockMgrPriv;
typedef struct _LockInfo {
int locked_by_me;
} LockInfo;
/* When a file is locked by me, it can have two reasons:
* - Locked by the user manually
* - Auto-Locked by Seafile when it detects Office opens the file.
*/
struct _SeafFilelockManager *
seaf_filelock_manager_new (struct _SeafileSession *session)
{
SeafFilelockManager *mgr = g_new0 (SeafFilelockManager, 1);
FilelockMgrPriv *priv = g_new0 (FilelockMgrPriv, 1);
mgr->session = session;
mgr->priv = priv;
priv->repo_locked_files = g_hash_table_new_full (g_str_hash, g_str_equal,
g_free,
(GDestroyNotify)g_hash_table_destroy);
pthread_mutex_init (&priv->hash_lock, NULL);
pthread_mutex_init (&priv->db_lock, NULL);
return mgr;
}
static void
lock_info_free (LockInfo *info)
{
g_free (info);
}
static gboolean
load_locked_files (sqlite3_stmt *stmt, void *data)
{
GHashTable *repo_locked_files = data, *files;
const char *repo_id, *path;
int locked_by_me;
repo_id = (const char *)sqlite3_column_text (stmt, 0);
path = (const char *)sqlite3_column_text (stmt, 1);
locked_by_me = sqlite3_column_int (stmt, 2);
files = g_hash_table_lookup (repo_locked_files, repo_id);
if (!files) {
files = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify)lock_info_free);
g_hash_table_insert (repo_locked_files, g_strdup(repo_id), files);
}
char *key = g_strdup(path);
LockInfo *info = g_new0 (LockInfo, 1);
info->locked_by_me = locked_by_me;
g_hash_table_replace (files, key, info);
return TRUE;
}
int
seaf_filelock_manager_init (SeafFilelockManager *mgr)
{
char *db_path;
sqlite3 *db;
char *sql;
db_path = g_build_filename (seaf->seaf_dir, "filelocks.db", NULL);
if (sqlite_open_db (db_path, &db) < 0)
return -1;
g_free (db_path);
mgr->priv->db = db;
sql = "CREATE TABLE IF NOT EXISTS ServerLockedFiles ("
"repo_id TEXT, path TEXT, locked_by_me INTEGER);";
sqlite_query_exec (db, sql);
sql = "CREATE INDEX IF NOT EXISTS server_locked_files_repo_id_idx "
"ON ServerLockedFiles (repo_id);";
sqlite_query_exec (db, sql);
sql = "CREATE TABLE IF NOT EXISTS ServerLockedFilesTimestamp ("
"repo_id TEXT, timestamp INTEGER, PRIMARY KEY (repo_id));";
sqlite_query_exec (db, sql);
sql = "SELECT repo_id, path, locked_by_me FROM ServerLockedFiles";
pthread_mutex_lock (&mgr->priv->db_lock);
pthread_mutex_lock (&mgr->priv->hash_lock);
if (sqlite_foreach_selected_row (mgr->priv->db, sql,
load_locked_files,
mgr->priv->repo_locked_files) < 0) {
pthread_mutex_unlock (&mgr->priv->db_lock);
pthread_mutex_unlock (&mgr->priv->hash_lock);
g_hash_table_destroy (mgr->priv->repo_locked_files);
return -1;
}
pthread_mutex_unlock (&mgr->priv->hash_lock);
pthread_mutex_unlock (&mgr->priv->db_lock);
return 0;
}
static void
init_locks (gpointer key, gpointer value, gpointer user_data)
{
char *repo_id = user_data;
char *path = key;
LockInfo *info = value;
if (!info->locked_by_me) {
seaf_filelock_manager_lock_wt_file (seaf->filelock_mgr,
repo_id,
path);
}
}
int
seaf_filelock_manager_start (SeafFilelockManager *mgr)
{
GHashTableIter iter;
gpointer key, value;
char *repo_id;
GHashTable *locks;
pthread_mutex_lock (&mgr->priv->hash_lock);
g_hash_table_iter_init (&iter, mgr->priv->repo_locked_files);
while (g_hash_table_iter_next (&iter, &key, &value)) {
repo_id = key;
locks = value;
g_hash_table_foreach (locks, init_locks, repo_id);
}
pthread_mutex_unlock (&mgr->priv->hash_lock);
return 0;
}
gboolean
seaf_filelock_manager_is_file_locked (SeafFilelockManager *mgr,
const char *repo_id,
const char *path)
{
gboolean ret;
pthread_mutex_lock (&mgr->priv->hash_lock);
GHashTable *locks = g_hash_table_lookup (mgr->priv->repo_locked_files, repo_id);
if (!locks) {
pthread_mutex_unlock (&mgr->priv->hash_lock);
return FALSE;
}
LockInfo *info = g_hash_table_lookup (locks, path);
if (!info) {
pthread_mutex_unlock (&mgr->priv->hash_lock);
return FALSE;
}
ret = !info->locked_by_me;
pthread_mutex_unlock (&mgr->priv->hash_lock);
return ret;
}
gboolean
seaf_filelock_manager_is_file_locked_by_me (SeafFilelockManager *mgr,
const char *repo_id,
const char *path)
{
gboolean ret;
pthread_mutex_lock (&mgr->priv->hash_lock);
GHashTable *locks = g_hash_table_lookup (mgr->priv->repo_locked_files, repo_id);
if (!locks) {
pthread_mutex_unlock (&mgr->priv->hash_lock);
return FALSE;
}
LockInfo *info = g_hash_table_lookup (locks, path);
if (!info) {
pthread_mutex_unlock (&mgr->priv->hash_lock);
return FALSE;
}
ret = (info->locked_by_me > 0);
pthread_mutex_unlock (&mgr->priv->hash_lock);
return ret;
}
int
seaf_filelock_manager_get_lock_status (SeafFilelockManager *mgr,
const char *repo_id,
const char *path)
{
int ret;
pthread_mutex_lock (&mgr->priv->hash_lock);
GHashTable *locks = g_hash_table_lookup (mgr->priv->repo_locked_files, repo_id);
if (!locks) {
pthread_mutex_unlock (&mgr->priv->hash_lock);
return FILE_NOT_LOCKED;
}
LockInfo *info = g_hash_table_lookup (locks, path);
if (!info) {
pthread_mutex_unlock (&mgr->priv->hash_lock);
return FILE_NOT_LOCKED;
}
if (info->locked_by_me == LOCKED_MANUAL)
ret = FILE_LOCKED_BY_ME_MANUAL;
else if (info->locked_by_me == LOCKED_AUTO)
ret = FILE_LOCKED_BY_ME_AUTO;
else
ret = FILE_LOCKED_BY_OTHERS;
pthread_mutex_unlock (&mgr->priv->hash_lock);
return ret;
}
void
seaf_filelock_manager_lock_wt_file (SeafFilelockManager *mgr,
const char *repo_id,
const char *path)
{
SeafRepo *repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id);
if (!repo)
return;
char *fullpath = g_build_filename (repo->worktree, path, NULL);
if (seaf_util_exists (fullpath))
seaf_set_path_permission (fullpath, SEAF_PATH_PERM_RO, FALSE);
g_free (fullpath);
}
void
seaf_filelock_manager_unlock_wt_file (SeafFilelockManager *mgr,
const char *repo_id,
const char *path)
{
SeafRepo *repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id);
if (!repo)
return;
char *fullpath = g_build_filename (repo->worktree, path, NULL);
#ifdef WIN32
if (seaf_util_exists (fullpath))
seaf_unset_path_permission (fullpath, FALSE);
#else
if (seaf_util_exists (fullpath))
seaf_set_path_permission (fullpath, SEAF_PATH_PERM_RW, FALSE);
#endif
g_free (fullpath);
}
static void
update_in_memory (SeafFilelockManager *mgr, const char *repo_id, GHashTable *new_locks)
{
GHashTable *repo_hash = mgr->priv->repo_locked_files;
pthread_mutex_lock (&mgr->priv->hash_lock);
GHashTable *locks = g_hash_table_lookup (repo_hash, repo_id);
if (!locks) {
if (g_hash_table_size (new_locks) == 0) {
pthread_mutex_unlock (&mgr->priv->hash_lock);
return;
}
locks = g_hash_table_new_full (g_str_hash, g_str_equal,
g_free, (GDestroyNotify)lock_info_free);
g_hash_table_insert (repo_hash, g_strdup(repo_id), locks);
}
GHashTableIter iter;
gpointer key, value;
gpointer new_key, new_val;
char *path;
#ifdef WIN32
char *fullpath;
#endif
LockInfo *info;
gboolean exists;
int locked_by_me;
SeafRepo *repo;
repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id);
if (!repo) {
seaf_warning ("Failed to find repo %s\n", repo_id);
return;
}
g_hash_table_iter_init (&iter, locks);
while (g_hash_table_iter_next (&iter, &key, &value)) {
path = key;
info = value;
exists = g_hash_table_lookup_extended (new_locks, path, &new_key, &new_val);
if (!exists) {
#ifdef WIN32
fullpath = g_build_path ("/", repo->worktree, path, NULL);
seaf_sync_manager_add_refresh_path (seaf->sync_mgr, fullpath);
g_free (fullpath);
#endif
seaf_filelock_manager_unlock_wt_file (mgr, repo_id, path);
g_hash_table_iter_remove (&iter);
} else {
locked_by_me = (int)(long)new_val;
if (!info->locked_by_me && locked_by_me) {
#ifdef WIN32
fullpath = g_build_path ("/", repo->worktree, path, NULL);
seaf_sync_manager_add_refresh_path (seaf->sync_mgr, fullpath);
g_free (fullpath);
#endif
seaf_filelock_manager_unlock_wt_file (mgr, repo_id, path);
info->locked_by_me = locked_by_me;
} else if (info->locked_by_me && !locked_by_me) {
#ifdef WIN32
fullpath = g_build_path ("/", repo->worktree, path, NULL);
seaf_sync_manager_add_refresh_path (seaf->sync_mgr, fullpath);
g_free (fullpath);
#endif
seaf_filelock_manager_lock_wt_file (mgr, repo_id, path);
info->locked_by_me = locked_by_me;
}
}
}
g_hash_table_iter_init (&iter, new_locks);
while (g_hash_table_iter_next (&iter, &new_key, &new_val)) {
path = new_key;
locked_by_me = (int)(long)new_val;
if (!g_hash_table_lookup (locks, path)) {
info = g_new0 (LockInfo, 1);
info->locked_by_me = locked_by_me;
g_hash_table_insert (locks, g_strdup(path), info);
#ifdef WIN32
fullpath = g_build_path ("/", repo->worktree, path, NULL);
seaf_sync_manager_add_refresh_path (seaf->sync_mgr, fullpath);
g_free (fullpath);
#endif
if (!locked_by_me) {
seaf_filelock_manager_lock_wt_file (mgr, repo_id, path);
}
}
}
pthread_mutex_unlock (&mgr->priv->hash_lock);
}
static gint
compare_paths (gconstpointer a, gconstpointer b)
{
const char *patha = a, *pathb = b;
return strcmp (patha, pathb);
}
static int
update_db (SeafFilelockManager *mgr, const char *repo_id)
{
char *sql;
sqlite3_stmt *stmt;
GHashTable *locks;
GList *paths, *ptr;
char *path;
LockInfo *info;
pthread_mutex_lock (&mgr->priv->db_lock);
sql = "DELETE FROM ServerLockedFiles WHERE repo_id = ?";
stmt = sqlite_query_prepare (mgr->priv->db, sql);
sqlite3_bind_text (stmt, 1, repo_id, -1, SQLITE_TRANSIENT);
if (sqlite3_step (stmt) != SQLITE_DONE) {
seaf_warning ("Failed to remove server locked files for %.8s: %s.\n",
repo_id, sqlite3_errmsg (mgr->priv->db));
sqlite3_finalize (stmt);
pthread_mutex_unlock (&mgr->priv->db_lock);
return -1;
}
sqlite3_finalize (stmt);
locks = g_hash_table_lookup (mgr->priv->repo_locked_files, repo_id);
if (!locks || g_hash_table_size (locks) == 0) {
pthread_mutex_unlock (&mgr->priv->db_lock);
return 0;
}
paths = g_hash_table_get_keys (locks);
paths = g_list_sort (paths, compare_paths);
sql = "INSERT INTO ServerLockedFiles (repo_id, path, locked_by_me) VALUES (?, ?, ?)";
stmt = sqlite_query_prepare (mgr->priv->db, sql);
for (ptr = paths; ptr; ptr = ptr->next) {
path = ptr->data;
info = g_hash_table_lookup (locks, path);
sqlite3_bind_text (stmt, 1, repo_id, -1, SQLITE_TRANSIENT);
sqlite3_bind_text (stmt, 2, path, -1, SQLITE_TRANSIENT);
sqlite3_bind_int (stmt, 3, info->locked_by_me);
if (sqlite3_step (stmt) != SQLITE_DONE) {
seaf_warning ("Failed to insert server file lock for %.8s: %s.\n",
repo_id, sqlite3_errmsg (mgr->priv->db));
sqlite3_finalize (stmt);
pthread_mutex_unlock (&mgr->priv->db_lock);
return -1;
}
sqlite3_reset (stmt);
sqlite3_clear_bindings (stmt);
}
sqlite3_finalize (stmt);
g_list_free (paths);
pthread_mutex_unlock (&mgr->priv->db_lock);
return 0;
}
int
seaf_filelock_manager_update (SeafFilelockManager *mgr,
const char *repo_id,
GHashTable *new_locked_files)
{
update_in_memory (mgr, repo_id, new_locked_files);
int ret = update_db (mgr, repo_id);
return ret;
}
int
seaf_filelock_manager_update_timestamp (SeafFilelockManager *mgr,
const char *repo_id,
gint64 timestamp)
{
char sql[256];
int ret;
snprintf (sql, sizeof(sql),
"REPLACE INTO ServerLockedFilesTimestamp VALUES ('%s', %"G_GINT64_FORMAT")",
repo_id, timestamp);
pthread_mutex_lock (&mgr->priv->db_lock);
ret = sqlite_query_exec (mgr->priv->db, sql);
pthread_mutex_unlock (&mgr->priv->db_lock);
return ret;
}
gint64
seaf_filelock_manager_get_timestamp (SeafFilelockManager *mgr,
const char *repo_id)
{
char sql[256];
gint64 ret;
sqlite3_snprintf (sizeof(sql), sql,
"SELECT timestamp FROM ServerLockedFilesTimestamp WHERE repo_id = '%q'",
repo_id);
pthread_mutex_lock (&mgr->priv->db_lock);
ret = sqlite_get_int64 (mgr->priv->db, sql);
pthread_mutex_unlock (&mgr->priv->db_lock);
return ret;
}
int
seaf_filelock_manager_remove (SeafFilelockManager *mgr,
const char *repo_id)
{
char *sql;
sqlite3_stmt *stmt;
pthread_mutex_lock (&mgr->priv->db_lock);
sql = "DELETE FROM ServerLockedFiles WHERE repo_id = ?";
stmt = sqlite_query_prepare (mgr->priv->db, sql);
sqlite3_bind_text (stmt, 1, repo_id, -1, SQLITE_TRANSIENT);
if (sqlite3_step (stmt) != SQLITE_DONE) {
seaf_warning ("Failed to remove server locked files for %.8s: %s.\n",
repo_id, sqlite3_errmsg (mgr->priv->db));
sqlite3_finalize (stmt);
pthread_mutex_unlock (&mgr->priv->db_lock);
return -1;
}
sqlite3_finalize (stmt);
sql = "DELETE FROM ServerLockedFilesTimestamp WHERE repo_id = ?";
stmt = sqlite_query_prepare (mgr->priv->db, sql);
sqlite3_bind_text (stmt, 1, repo_id, -1, SQLITE_TRANSIENT);
if (sqlite3_step (stmt) != SQLITE_DONE) {
seaf_warning ("Failed to remove server locked files timestamp for %.8s: %s.\n",
repo_id, sqlite3_errmsg (mgr->priv->db));
sqlite3_finalize (stmt);
pthread_mutex_unlock (&mgr->priv->db_lock);
return -1;
}
sqlite3_finalize (stmt);
pthread_mutex_unlock (&mgr->priv->db_lock);
pthread_mutex_lock (&mgr->priv->hash_lock);
g_hash_table_remove (mgr->priv->repo_locked_files, repo_id);
pthread_mutex_unlock (&mgr->priv->hash_lock);
return 0;
}
#ifdef WIN32
static void
refresh_locked_path_status (const char *repo_id, const char *path)
{
SeafRepo *repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id);
if (!repo)
return;
char *fullpath = g_build_path ("/", repo->worktree, path, NULL);
seaf_sync_manager_refresh_path (seaf->sync_mgr, fullpath);
g_free (fullpath);
}
#endif
static int
mark_file_locked_in_db (SeafFilelockManager *mgr,
const char *repo_id,
const char *path,
int locked_by_me)
{
char *sql;
sqlite3_stmt *stmt;
pthread_mutex_lock (&mgr->priv->db_lock);
sql = "REPLACE INTO ServerLockedFiles (repo_id, path, locked_by_me) VALUES (?, ?, ?)";
stmt = sqlite_query_prepare (mgr->priv->db, sql);
sqlite3_bind_text (stmt, 1, repo_id, -1, SQLITE_TRANSIENT);
sqlite3_bind_text (stmt, 2, path, -1, SQLITE_TRANSIENT);
sqlite3_bind_int (stmt, 3, locked_by_me);
if (sqlite3_step (stmt) != SQLITE_DONE) {
seaf_warning ("Failed to update server locked files for %.8s: %s.\n",
repo_id, sqlite3_errmsg (mgr->priv->db));
sqlite3_finalize (stmt);
pthread_mutex_unlock (&mgr->priv->db_lock);
return -1;
}
sqlite3_finalize (stmt);
pthread_mutex_unlock (&mgr->priv->db_lock);
return 0;
}
int
seaf_filelock_manager_mark_file_locked (SeafFilelockManager *mgr,
const char *repo_id,
const char *path,
FileLockType type)
{
GHashTable *locks;
LockInfo *info;
pthread_mutex_lock (&mgr->priv->hash_lock);
locks = g_hash_table_lookup (mgr->priv->repo_locked_files, repo_id);
if (!locks) {
locks = g_hash_table_new_full (g_str_hash, g_str_equal,
g_free, (GDestroyNotify)lock_info_free);
g_hash_table_insert (mgr->priv->repo_locked_files,
g_strdup(repo_id), locks);
}
info = g_hash_table_lookup (locks, path);
if (!info) {
info = g_new0 (LockInfo, 1);
g_hash_table_insert (locks, g_strdup(path), info);
}
info->locked_by_me = type;
pthread_mutex_unlock (&mgr->priv->hash_lock);
#ifdef WIN32
refresh_locked_path_status (repo_id, path);
#endif
return mark_file_locked_in_db (mgr, repo_id, path, info->locked_by_me);
}
static int
remove_locked_file_from_db (SeafFilelockManager *mgr,
const char *repo_id,
const char *path)
{
char *sql;
sqlite3_stmt *stmt;
pthread_mutex_lock (&mgr->priv->db_lock);
sql = "DELETE FROM ServerLockedFiles WHERE repo_id = ? AND path = ?";
stmt = sqlite_query_prepare (mgr->priv->db, sql);
sqlite3_bind_text (stmt, 1, repo_id, -1, SQLITE_TRANSIENT);
sqlite3_bind_text (stmt, 2, path, -1, SQLITE_TRANSIENT);
if (sqlite3_step (stmt) != SQLITE_DONE) {
seaf_warning ("Failed to remove locked file %s from %.8s: %s.\n",
path, repo_id, sqlite3_errmsg (mgr->priv->db));
sqlite3_finalize (stmt);
pthread_mutex_unlock (&mgr->priv->db_lock);
return -1;
}
sqlite3_finalize (stmt);
pthread_mutex_unlock (&mgr->priv->db_lock);
return 0;
}
int
seaf_filelock_manager_mark_file_unlocked (SeafFilelockManager *mgr,
const char *repo_id,
const char *path)
{
GHashTable *locks;
pthread_mutex_lock (&mgr->priv->hash_lock);
locks = g_hash_table_lookup (mgr->priv->repo_locked_files, repo_id);
if (!locks) {
pthread_mutex_unlock (&mgr->priv->hash_lock);
return 0;
}
g_hash_table_remove (locks, path);
pthread_mutex_unlock (&mgr->priv->hash_lock);
#ifdef WIN32
refresh_locked_path_status (repo_id, path);
#endif
return remove_locked_file_from_db (mgr, repo_id, path);
}
void file_lock_info_free (FileLockInfo *info)
{
if (!info)
return;
g_free (info->path);
g_free (info);
}
static gboolean
collect_auto_locked_files (sqlite3_stmt *stmt, void *vret)
{
GList **pret = vret;
const char *repo_id, *path;
FileLockInfo *info;
repo_id = (const char *)sqlite3_column_text (stmt, 0);
path = (const char *)sqlite3_column_text (stmt, 1);
info = g_new0 (FileLockInfo, 1);
memcpy (info->repo_id, repo_id, 36);
info->path = g_strdup(path);
*pret = g_list_prepend (*pret, info);
return TRUE;
}
GList *
seaf_filelock_manager_get_auto_locked_files (SeafFilelockManager *mgr)
{
char *sql;
GList *ret = NULL;
pthread_mutex_lock (&mgr->priv->db_lock);
sql = sqlite3_mprintf ("SELECT repo_id, path FROM ServerLockedFiles "
"WHERE locked_by_me = %d", LOCKED_AUTO);
sqlite_foreach_selected_row (mgr->priv->db, sql,
collect_auto_locked_files,
&ret);
pthread_mutex_unlock (&mgr->priv->db_lock);
ret = g_list_reverse (ret);
sqlite3_free (sql);
return ret;
}
int
seaf_filelock_manager_lock_file (SeafFilelockManager *mgr,
const char *repo_id,
const char *path,
FileLockType type)
{
int ret = -1;
ret = seaf_filelock_manager_mark_file_locked (seaf->filelock_mgr, repo_id, path, type);
if (ret < 0) {
return ret;
}
if (!type) {
seaf_filelock_manager_lock_wt_file (mgr, repo_id, path);
}
return 0;
}
int
seaf_filelock_manager_unlock_file (SeafFilelockManager *mgr,
const char *repo_id,
const char *path)
{
int ret = -1;
ret = seaf_filelock_manager_mark_file_unlocked (mgr, repo_id, path);
if (ret < 0) {
return ret;
}
seaf_filelock_manager_unlock_wt_file (mgr, repo_id, path);
return 0;
}