mirror of
https://github.com/haiwen/seafile-server.git
synced 2025-06-28 07:56:52 +00:00
* Fix fsck crash when failed to save dir * Modify comment --------- Co-authored-by: 杨赫然 <heran.yang@seafile.com>
1192 lines
37 KiB
C
1192 lines
37 KiB
C
#include "common.h"
|
|
|
|
#include <fcntl.h>
|
|
|
|
#include "seafile-session.h"
|
|
#include "log.h"
|
|
#include "utils.h"
|
|
|
|
#include "fsck.h"
|
|
|
|
typedef struct FsckData {
|
|
gboolean repair;
|
|
SeafRepo *repo;
|
|
GHashTable *existing_blocks;
|
|
GList *repaired_files;
|
|
GList *repaired_folders;
|
|
} FsckData;
|
|
|
|
typedef struct CheckAndRecoverRepoObj {
|
|
char *repo_id;
|
|
gboolean repair;
|
|
} CheckAndRecoverRepoObj;
|
|
|
|
typedef enum VerifyType {
|
|
VERIFY_FILE,
|
|
VERIFY_DIR
|
|
} VerifyType;
|
|
|
|
static gboolean
|
|
fsck_verify_seafobj (const char *store_id,
|
|
int version,
|
|
const char *obj_id,
|
|
gboolean *io_error,
|
|
VerifyType type,
|
|
gboolean repair)
|
|
{
|
|
gboolean valid = TRUE;
|
|
|
|
valid = seaf_fs_manager_object_exists (seaf->fs_mgr, store_id,
|
|
version, obj_id);
|
|
if (!valid) {
|
|
if (type == VERIFY_FILE) {
|
|
seaf_message ("File %s is missing.\n", obj_id);
|
|
} else if (type == VERIFY_DIR) {
|
|
seaf_message ("Dir %s is missing.\n", obj_id);
|
|
}
|
|
return valid;
|
|
}
|
|
|
|
if (type == VERIFY_FILE) {
|
|
valid = seaf_fs_manager_verify_seafile (seaf->fs_mgr, store_id, version,
|
|
obj_id, TRUE, io_error);
|
|
if (!valid && !*io_error && repair) {
|
|
seaf_message ("File %s is damaged, remove it.\n", obj_id);
|
|
seaf_fs_manager_delete_object (seaf->fs_mgr, store_id, version, obj_id);
|
|
}
|
|
} else if (type == VERIFY_DIR) {
|
|
valid = seaf_fs_manager_verify_seafdir (seaf->fs_mgr, store_id, version,
|
|
obj_id, TRUE, io_error);
|
|
if (!valid && !*io_error && repair) {
|
|
seaf_message ("Dir %s is damaged, remove it.\n", obj_id);
|
|
seaf_fs_manager_delete_object (seaf->fs_mgr, store_id, version, obj_id);
|
|
}
|
|
}
|
|
|
|
return valid;
|
|
}
|
|
|
|
static int
|
|
check_blocks (const char *file_id, FsckData *fsck_data, gboolean *io_error)
|
|
{
|
|
Seafile *seafile;
|
|
int i;
|
|
char *block_id;
|
|
int ret = 0;
|
|
int dummy;
|
|
|
|
gboolean ok = TRUE;
|
|
SeafRepo *repo = fsck_data->repo;
|
|
const char *store_id = repo->store_id;
|
|
int version = repo->version;
|
|
|
|
seafile = seaf_fs_manager_get_seafile (seaf->fs_mgr, store_id,
|
|
version, file_id);
|
|
if (!seafile) {
|
|
seaf_warning ("Failed to get seafile: %s/%s\n", store_id, file_id);
|
|
return -1;
|
|
}
|
|
|
|
for (i = 0; i < seafile->n_blocks; ++i) {
|
|
block_id = seafile->blk_sha1s[i];
|
|
|
|
if (g_hash_table_lookup (fsck_data->existing_blocks, block_id))
|
|
continue;
|
|
|
|
if (!seaf_block_manager_block_exists (seaf->block_mgr,
|
|
store_id, version,
|
|
block_id)) {
|
|
seaf_warning ("Repo[%.8s] block %s:%s is missing.\n", repo->id, store_id, block_id);
|
|
ret = -1;
|
|
continue;
|
|
}
|
|
|
|
// check block integrity, if not remove it
|
|
ok = seaf_block_manager_verify_block (seaf->block_mgr,
|
|
store_id, version,
|
|
block_id, io_error);
|
|
if (!ok) {
|
|
if (*io_error) {
|
|
if (ret < 0) {
|
|
*io_error = FALSE;
|
|
}
|
|
ret = -1;
|
|
break;
|
|
} else {
|
|
if (fsck_data->repair) {
|
|
seaf_message ("Repo[%.8s] block %s is damaged, remove it.\n", repo->id, block_id);
|
|
seaf_block_manager_remove_block (seaf->block_mgr,
|
|
store_id, version,
|
|
block_id);
|
|
} else {
|
|
seaf_message ("Repo[%.8s] block %s is damaged.\n", repo->id, block_id);
|
|
}
|
|
ret = -1;
|
|
}
|
|
}
|
|
|
|
g_hash_table_insert (fsck_data->existing_blocks, g_strdup(block_id), &dummy);
|
|
}
|
|
|
|
seafile_unref (seafile);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static char*
|
|
fsck_check_dir_recursive (const char *id, const char *parent_dir, FsckData *fsck_data)
|
|
{
|
|
SeafDir *dir;
|
|
SeafDir *new_dir;
|
|
GList *p;
|
|
SeafDirent *seaf_dent;
|
|
char *dir_id = NULL;
|
|
char *path = NULL;
|
|
gboolean io_error = FALSE;
|
|
|
|
SeafFSManager *mgr = seaf->fs_mgr;
|
|
char *store_id = fsck_data->repo->store_id;
|
|
int version = fsck_data->repo->version;
|
|
gboolean is_corrupted = FALSE;
|
|
|
|
dir = seaf_fs_manager_get_seafdir (mgr, store_id, version, id);
|
|
if (!dir) {
|
|
goto out;
|
|
}
|
|
|
|
for (p = dir->entries; p; p = p->next) {
|
|
seaf_dent = p->data;
|
|
io_error = FALSE;
|
|
|
|
if (S_ISREG(seaf_dent->mode)) {
|
|
path = g_strdup_printf ("%s%s", parent_dir, seaf_dent->name);
|
|
if (!path) {
|
|
seaf_warning ("Out of memory, stop to run fsck for repo %.8s.\n",
|
|
fsck_data->repo->id);
|
|
goto out;
|
|
}
|
|
if (!fsck_verify_seafobj (store_id, version,
|
|
seaf_dent->id, &io_error,
|
|
VERIFY_FILE, fsck_data->repair)) {
|
|
if (io_error) {
|
|
g_free (path);
|
|
goto out;
|
|
}
|
|
is_corrupted = TRUE;
|
|
if (fsck_data->repair) {
|
|
seaf_message ("Repo[%.8s] file %s(%.8s) is damaged, recreate an empty file.\n",
|
|
fsck_data->repo->id, path, seaf_dent->id);
|
|
} else {
|
|
seaf_message ("Repo[%.8s] file %s(%.8s) is damaged.\n",
|
|
fsck_data->repo->id, path, seaf_dent->id);
|
|
}
|
|
// file damaged, set it empty
|
|
memcpy (seaf_dent->id, EMPTY_SHA1, 40);
|
|
seaf_dent->mtime = (gint64)time(NULL);
|
|
seaf_dent->size = 0;
|
|
} else {
|
|
if (check_blocks (seaf_dent->id, fsck_data, &io_error) < 0) {
|
|
if (io_error) {
|
|
seaf_message ("Failed to check blocks for repo[%.8s] file %s(%.8s).\n",
|
|
fsck_data->repo->id, path, seaf_dent->id);
|
|
g_free (path);
|
|
goto out;
|
|
}
|
|
is_corrupted = TRUE;
|
|
if (fsck_data->repair) {
|
|
seaf_message ("Repo[%.8s] file %s(%.8s) is damaged, recreate an empty file.\n",
|
|
fsck_data->repo->id, path, seaf_dent->id);
|
|
} else {
|
|
seaf_message ("Repo[%.8s] file %s(%.8s) is damaged.\n",
|
|
fsck_data->repo->id, path, seaf_dent->id);
|
|
}
|
|
// file damaged, set it empty
|
|
memcpy (seaf_dent->id, EMPTY_SHA1, 40);
|
|
seaf_dent->mtime = (gint64)time(NULL);
|
|
seaf_dent->size = 0;
|
|
}
|
|
}
|
|
|
|
if (is_corrupted)
|
|
fsck_data->repaired_files = g_list_prepend (fsck_data->repaired_files,
|
|
g_strdup(path));
|
|
g_free (path);
|
|
} else if (S_ISDIR(seaf_dent->mode)) {
|
|
path = g_strdup_printf ("%s%s/", parent_dir, seaf_dent->name);
|
|
if (!path) {
|
|
seaf_warning ("Out of memory, stop to run fsck for repo [%.8s].\n",
|
|
fsck_data->repo->id);
|
|
goto out;
|
|
}
|
|
if (!fsck_verify_seafobj (store_id, version,
|
|
seaf_dent->id, &io_error,
|
|
VERIFY_DIR, fsck_data->repair)) {
|
|
if (io_error) {
|
|
g_free (path);
|
|
goto out;
|
|
}
|
|
if (fsck_data->repair) {
|
|
seaf_message ("Repo[%.8s] dir %s(%.8s) is damaged, recreate an empty dir.\n",
|
|
fsck_data->repo->id, path, seaf_dent->id);
|
|
} else {
|
|
seaf_message ("Repo[%.8s] dir %s(%.8s) is damaged.\n",
|
|
fsck_data->repo->id, path, seaf_dent->id);
|
|
}
|
|
is_corrupted = TRUE;
|
|
// dir damaged, set it empty
|
|
memcpy (seaf_dent->id, EMPTY_SHA1, 40);
|
|
|
|
fsck_data->repaired_folders = g_list_prepend (fsck_data->repaired_folders,
|
|
g_strdup(path));
|
|
} else {
|
|
char *sub_dir_id = fsck_check_dir_recursive (seaf_dent->id, path, fsck_data);
|
|
if (sub_dir_id == NULL) {
|
|
// IO error
|
|
g_free (path);
|
|
goto out;
|
|
}
|
|
if (strcmp (sub_dir_id, seaf_dent->id) != 0) {
|
|
is_corrupted = TRUE;
|
|
// dir damaged, set it to new dir_id
|
|
memcpy (seaf_dent->id, sub_dir_id, 41);
|
|
}
|
|
g_free (sub_dir_id);
|
|
}
|
|
g_free (path);
|
|
}
|
|
}
|
|
|
|
if (is_corrupted) {
|
|
new_dir = seaf_dir_new (NULL, dir->entries, version);
|
|
if (fsck_data->repair) {
|
|
if (seaf_dir_save (mgr, store_id, version, new_dir) < 0) {
|
|
seaf_warning ("Repo[%.8s] failed to save dir\n", fsck_data->repo->id);
|
|
seaf_dir_free (new_dir);
|
|
// dir->entries was taken by new_dir, which has been freed.
|
|
dir->entries = NULL;
|
|
goto out;
|
|
}
|
|
}
|
|
dir_id = g_strdup (new_dir->dir_id);
|
|
seaf_dir_free (new_dir);
|
|
dir->entries = NULL;
|
|
} else {
|
|
dir_id = g_strdup (dir->dir_id);
|
|
}
|
|
|
|
out:
|
|
seaf_dir_free (dir);
|
|
|
|
return dir_id;
|
|
}
|
|
|
|
static gboolean
|
|
collect_token_list (SeafDBRow *row, void *data)
|
|
{
|
|
GList **p_tokens = data;
|
|
const char *token;
|
|
|
|
token = seaf_db_row_get_column_text (row, 0);
|
|
*p_tokens = g_list_prepend (*p_tokens, g_strdup(token));
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
int
|
|
delete_repo_tokens (SeafRepo *repo)
|
|
{
|
|
int ret = 0;
|
|
const char *template;
|
|
GList *token_list = NULL;
|
|
GList *ptr;
|
|
GString *token_list_str = g_string_new ("");
|
|
GString *sql = g_string_new ("");
|
|
int rc;
|
|
|
|
template = "SELECT u.token FROM RepoUserToken as u WHERE u.repo_id=?";
|
|
rc = seaf_db_statement_foreach_row (seaf->db, template,
|
|
collect_token_list, &token_list,
|
|
1, "string", repo->id);
|
|
if (rc < 0) {
|
|
goto out;
|
|
}
|
|
|
|
if (rc == 0)
|
|
goto out;
|
|
|
|
for (ptr = token_list; ptr; ptr = ptr->next) {
|
|
const char *token = (char *)ptr->data;
|
|
if (token_list_str->len == 0)
|
|
g_string_append_printf (token_list_str, "'%s'", token);
|
|
else
|
|
g_string_append_printf (token_list_str, ",'%s'", token);
|
|
}
|
|
|
|
/* Note that there is a size limit on sql query. In MySQL it's 1MB by default.
|
|
* Normally the token_list won't be that long.
|
|
*/
|
|
g_string_printf (sql, "DELETE FROM RepoUserToken WHERE token in (%s)",
|
|
token_list_str->str);
|
|
rc = seaf_db_statement_query (seaf->db, sql->str, 0);
|
|
if (rc < 0) {
|
|
goto out;
|
|
}
|
|
|
|
g_string_printf (sql, "DELETE FROM RepoTokenPeerInfo WHERE token in (%s)",
|
|
token_list_str->str);
|
|
rc = seaf_db_statement_query (seaf->db, sql->str, 0);
|
|
if (rc < 0) {
|
|
goto out;
|
|
}
|
|
|
|
out:
|
|
g_string_free (token_list_str, TRUE);
|
|
g_string_free (sql, TRUE);
|
|
g_list_free_full (token_list, (GDestroyNotify)g_free);
|
|
|
|
if (rc < 0) {
|
|
ret = -1;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static char *
|
|
gen_repair_commit_desc (GList *repaired_files, GList *repaired_folders)
|
|
{
|
|
GString *desc = g_string_new("Repaired by system.");
|
|
GList *p;
|
|
char *path;
|
|
|
|
if (!repaired_files && !repaired_folders)
|
|
return g_string_free (desc, FALSE);
|
|
|
|
if (repaired_files) {
|
|
g_string_append (desc, "\nDamaged files:\n");
|
|
for (p = repaired_files; p; p = p->next) {
|
|
path = p->data;
|
|
g_string_append_printf (desc, "%s\n", path);
|
|
}
|
|
}
|
|
|
|
if (repaired_folders) {
|
|
g_string_append (desc, "\nDamaged folders:\n");
|
|
for (p = repaired_folders; p; p = p->next) {
|
|
path = p->data;
|
|
g_string_append_printf (desc, "%s\n", path);
|
|
}
|
|
}
|
|
|
|
return g_string_free (desc, FALSE);
|
|
}
|
|
|
|
static void
|
|
reset_commit_to_repair (SeafRepo *repo, SeafCommit *parent, char *new_root_id,
|
|
GList *repaired_files, GList *repaired_folders)
|
|
{
|
|
if (delete_repo_tokens (repo) < 0) {
|
|
seaf_warning ("Failed to delete repo sync tokens, abort repair.\n");
|
|
return;
|
|
}
|
|
|
|
char *desc = gen_repair_commit_desc (repaired_files, repaired_folders);
|
|
|
|
SeafCommit *new_commit = NULL;
|
|
new_commit = seaf_commit_new (NULL, repo->id, new_root_id,
|
|
parent->creator_name, parent->creator_id,
|
|
desc, 0);
|
|
g_free (desc);
|
|
if (!new_commit) {
|
|
seaf_warning ("Out of memory, stop to run fsck for repo %.8s.\n",
|
|
repo->id);
|
|
return;
|
|
}
|
|
|
|
new_commit->parent_id = g_strdup (parent->commit_id);
|
|
seaf_repo_to_commit (repo, new_commit);
|
|
|
|
seaf_message ("Update repo %.8s status to commit %.8s.\n",
|
|
repo->id, new_commit->commit_id);
|
|
seaf_branch_set_commit (repo->head, new_commit->commit_id);
|
|
if (seaf_branch_manager_add_branch (seaf->branch_mgr, repo->head) < 0) {
|
|
seaf_warning ("Update head of repo %.8s to commit %.8s failed, "
|
|
"recover failed.\n", repo->id, new_commit->commit_id);
|
|
} else {
|
|
seaf_commit_manager_add_commit (seaf->commit_mgr, new_commit);
|
|
}
|
|
seaf_commit_unref (new_commit);
|
|
}
|
|
|
|
/*
|
|
* check and recover repo, for damaged file or folder set it empty
|
|
*/
|
|
static void
|
|
check_and_recover_repo (SeafRepo *repo, gboolean reset, gboolean repair)
|
|
{
|
|
FsckData fsck_data;
|
|
SeafCommit *rep_commit = NULL;
|
|
char *root_id = NULL;
|
|
|
|
seaf_message ("Checking file system integrity of repo %s(%.8s)...\n",
|
|
repo->name, repo->id);
|
|
|
|
rep_commit = seaf_commit_manager_get_commit (seaf->commit_mgr, repo->id,
|
|
repo->version, repo->head->commit_id);
|
|
if (!rep_commit) {
|
|
seaf_warning ("Failed to load commit %s of repo %s\n",
|
|
repo->head->commit_id, repo->id);
|
|
return;
|
|
}
|
|
|
|
memset (&fsck_data, 0, sizeof(fsck_data));
|
|
fsck_data.repair = repair;
|
|
fsck_data.repo = repo;
|
|
fsck_data.existing_blocks = g_hash_table_new_full (g_str_hash, g_str_equal,
|
|
g_free, NULL);
|
|
|
|
root_id = fsck_check_dir_recursive (rep_commit->root_id, "/", &fsck_data);
|
|
g_hash_table_destroy (fsck_data.existing_blocks);
|
|
if (root_id == NULL) {
|
|
goto out;
|
|
}
|
|
|
|
if (repair) {
|
|
if (strcmp (root_id, rep_commit->root_id) != 0) {
|
|
// some fs objects damaged for the head commit,
|
|
// create new head commit using the new root_id
|
|
reset_commit_to_repair (repo, rep_commit, root_id,
|
|
fsck_data.repaired_files,
|
|
fsck_data.repaired_folders);
|
|
} else if (reset) {
|
|
// for reset commit but fs objects not damaged, also create a repaired commit
|
|
reset_commit_to_repair (repo, rep_commit, rep_commit->root_id,
|
|
NULL, NULL);
|
|
}
|
|
}
|
|
|
|
out:
|
|
g_list_free_full (fsck_data.repaired_files, g_free);
|
|
g_list_free_full (fsck_data.repaired_folders, g_free);
|
|
g_free (root_id);
|
|
seaf_commit_unref (rep_commit);
|
|
}
|
|
|
|
static gint
|
|
compare_commit_by_ctime (gconstpointer a, gconstpointer b)
|
|
{
|
|
const SeafCommit *commit_a = a;
|
|
const SeafCommit *commit_b = b;
|
|
|
|
return (commit_b->ctime - commit_a->ctime);
|
|
}
|
|
|
|
static gboolean
|
|
fsck_get_repo_commit (const char *repo_id, int version,
|
|
const char *obj_id, void *commit_list)
|
|
{
|
|
void *data = NULL;
|
|
int data_len;
|
|
GList **cur_list = (GList **)commit_list;
|
|
|
|
int ret = seaf_obj_store_read_obj (seaf->commit_mgr->obj_store, repo_id,
|
|
version, obj_id, &data, &data_len);
|
|
if (ret < 0 || data == NULL)
|
|
return TRUE;
|
|
|
|
SeafCommit *cur_commit = seaf_commit_from_data (obj_id, data, data_len);
|
|
if (cur_commit != NULL) {
|
|
*cur_list = g_list_prepend (*cur_list, cur_commit);
|
|
}
|
|
|
|
g_free(data);
|
|
return TRUE;
|
|
}
|
|
|
|
static SeafRepo*
|
|
get_available_repo (char *repo_id, gboolean repair)
|
|
{
|
|
GList *commit_list = NULL;
|
|
GList *temp_list = NULL;
|
|
SeafCommit *temp_commit = NULL;
|
|
SeafBranch *branch = NULL;
|
|
SeafRepo *repo = NULL;
|
|
SeafVirtRepo *vinfo = NULL;
|
|
gboolean io_error;
|
|
|
|
seaf_message ("Scanning available commits...\n");
|
|
|
|
seaf_obj_store_foreach_obj (seaf->commit_mgr->obj_store, repo_id,
|
|
1, fsck_get_repo_commit, &commit_list);
|
|
|
|
if (commit_list == NULL) {
|
|
seaf_warning ("No available commits for repo %.8s, can't be repaired.\n",
|
|
repo_id);
|
|
return NULL;
|
|
}
|
|
|
|
commit_list = g_list_sort (commit_list, compare_commit_by_ctime);
|
|
|
|
repo = seaf_repo_new (repo_id, NULL, NULL);
|
|
if (repo == NULL) {
|
|
seaf_warning ("Out of memory, stop to run fsck for repo %.8s.\n",
|
|
repo_id);
|
|
goto out;
|
|
}
|
|
|
|
vinfo = seaf_repo_manager_get_virtual_repo_info (seaf->repo_mgr, repo_id);
|
|
if (vinfo) {
|
|
repo->is_virtual = TRUE;
|
|
memcpy (repo->store_id, vinfo->origin_repo_id, 36);
|
|
seaf_virtual_repo_info_free (vinfo);
|
|
} else {
|
|
repo->is_virtual = FALSE;
|
|
memcpy (repo->store_id, repo->id, 36);
|
|
}
|
|
|
|
for (temp_list = commit_list; temp_list; temp_list = temp_list->next) {
|
|
temp_commit = temp_list->data;
|
|
io_error = FALSE;
|
|
|
|
if (!fsck_verify_seafobj (repo->store_id, 1, temp_commit->root_id,
|
|
&io_error, VERIFY_DIR, repair)) {
|
|
if (io_error) {
|
|
seaf_repo_unref (repo);
|
|
repo = NULL;
|
|
goto out;
|
|
}
|
|
// fs object of this commit is damaged,
|
|
// continue to verify next
|
|
continue;
|
|
}
|
|
|
|
branch = seaf_branch_new ("master", repo_id, temp_commit->commit_id);
|
|
if (branch == NULL) {
|
|
seaf_warning ("Out of memory, stop to run fsck for repo %.8s.\n",
|
|
repo_id);
|
|
seaf_repo_unref (repo);
|
|
repo = NULL;
|
|
goto out;
|
|
}
|
|
repo->head = branch;
|
|
seaf_repo_from_commit (repo, temp_commit);
|
|
|
|
char time_buf[64];
|
|
strftime (time_buf, 64, "%Y-%m-%d %H:%M:%S", localtime((time_t *)&temp_commit->ctime));
|
|
seaf_message ("Find available commit %.8s(created at %s) for repo %.8s.\n",
|
|
temp_commit->commit_id, time_buf, repo_id);
|
|
break;
|
|
}
|
|
|
|
out:
|
|
for (temp_list = commit_list; temp_list; temp_list = temp_list->next) {
|
|
temp_commit = temp_list->data;
|
|
seaf_commit_unref (temp_commit);
|
|
}
|
|
g_list_free (commit_list);
|
|
|
|
if (!repo || !repo->head) {
|
|
seaf_warning("No available commits found for repo %.8s, can't be repaired.\n",
|
|
repo_id);
|
|
seaf_repo_unref (repo);
|
|
return NULL;
|
|
}
|
|
|
|
return repo;
|
|
}
|
|
|
|
static void
|
|
repair_repo(char *repo_id, gboolean repair)
|
|
{
|
|
gboolean exists;
|
|
gboolean reset = FALSE;
|
|
SeafRepo *repo;
|
|
gboolean io_error;
|
|
|
|
seaf_message ("Running fsck for repo %s.\n", repo_id);
|
|
|
|
if (!is_uuid_valid (repo_id)) {
|
|
seaf_warning ("Invalid repo id %s.\n", repo_id);
|
|
goto next;
|
|
}
|
|
|
|
exists = seaf_repo_manager_repo_exists (seaf->repo_mgr, repo_id);
|
|
if (!exists) {
|
|
seaf_warning ("Repo %.8s doesn't exist.\n", repo_id);
|
|
goto next;
|
|
}
|
|
|
|
repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id);
|
|
|
|
if (!repo) {
|
|
seaf_message ("Repo %.8s HEAD commit is damaged, "
|
|
"need to restore to an old version.\n", repo_id);
|
|
repo = get_available_repo (repo_id, repair);
|
|
if (!repo) {
|
|
goto next;
|
|
}
|
|
reset = TRUE;
|
|
} else {
|
|
SeafCommit *commit = seaf_commit_manager_get_commit (seaf->commit_mgr, repo->id,
|
|
repo->version,
|
|
repo->head->commit_id);
|
|
if (!commit) {
|
|
seaf_warning ("Failed to get head commit %s of repo %s\n",
|
|
repo->head->commit_id, repo->id);
|
|
seaf_repo_unref (repo);
|
|
goto next;
|
|
}
|
|
|
|
io_error = FALSE;
|
|
if (!fsck_verify_seafobj (repo->store_id, repo->version,
|
|
commit->root_id, &io_error,
|
|
VERIFY_DIR, repair)) {
|
|
if (io_error) {
|
|
seaf_commit_unref (commit);
|
|
seaf_repo_unref (repo);
|
|
goto next;
|
|
} else {
|
|
// root fs object is damaged, get available commit
|
|
seaf_message ("Repo %.8s HEAD commit is damaged, "
|
|
"need to restore to an old version.\n", repo_id);
|
|
seaf_commit_unref (commit);
|
|
seaf_repo_unref (repo);
|
|
repo = get_available_repo (repo_id, repair);
|
|
if (!repo) {
|
|
goto next;
|
|
}
|
|
reset = TRUE;
|
|
}
|
|
} else {
|
|
// head commit is available
|
|
seaf_commit_unref (commit);
|
|
}
|
|
}
|
|
|
|
check_and_recover_repo (repo, reset, repair);
|
|
|
|
seaf_repo_unref (repo);
|
|
next:
|
|
seaf_message ("Fsck finished for repo %.8s.\n\n", repo_id);
|
|
}
|
|
|
|
static void
|
|
repair_repo_with_thread_pool(gpointer data, gpointer user_data)
|
|
{
|
|
CheckAndRecoverRepoObj *obj = data;
|
|
|
|
repair_repo(obj->repo_id, obj->repair);
|
|
|
|
g_free(obj);
|
|
}
|
|
|
|
static void
|
|
repair_repos (GList *repo_id_list, gboolean repair, int max_thread_num)
|
|
{
|
|
GList *ptr;
|
|
char *repo_id;
|
|
GThreadPool *pool;
|
|
|
|
if (max_thread_num) {
|
|
pool = g_thread_pool_new(
|
|
(GFunc)repair_repo_with_thread_pool, NULL, max_thread_num, FALSE, NULL);
|
|
if (!pool) {
|
|
seaf_warning ("Failed to create check and recover repo thread pool.\n");
|
|
return;
|
|
}
|
|
}
|
|
|
|
for (ptr = repo_id_list; ptr; ptr = ptr->next) {
|
|
repo_id = ptr->data;
|
|
|
|
if (max_thread_num) {
|
|
CheckAndRecoverRepoObj *obj = g_new0(CheckAndRecoverRepoObj, 1);
|
|
obj->repo_id = repo_id;
|
|
obj->repair = repair;
|
|
g_thread_pool_push(pool, obj, NULL);
|
|
} else {
|
|
repair_repo(repo_id, repair);
|
|
}
|
|
}
|
|
|
|
if (max_thread_num) {
|
|
g_thread_pool_free(pool, FALSE, TRUE);
|
|
}
|
|
}
|
|
|
|
int
|
|
seaf_fsck (GList *repo_id_list, gboolean repair, int max_thread_num)
|
|
{
|
|
if (!repo_id_list)
|
|
repo_id_list = seaf_repo_manager_get_repo_id_list (seaf->repo_mgr);
|
|
|
|
repair_repos (repo_id_list, repair, max_thread_num);
|
|
|
|
while (repo_id_list) {
|
|
g_free (repo_id_list->data);
|
|
repo_id_list = g_list_delete_link (repo_id_list, repo_id_list);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Export files. */
|
|
|
|
/*static gboolean
|
|
write_enc_block_to_file (const char *repo_id,
|
|
int version,
|
|
const char *block_id,
|
|
SeafileCrypt *crypt,
|
|
int fd,
|
|
const char *path)
|
|
{
|
|
BlockHandle *handle;
|
|
BlockMetadata *bmd;
|
|
char buf[64 * 1024];
|
|
int n;
|
|
int remain;
|
|
EVP_CIPHER_CTX ctx;
|
|
char *dec_out;
|
|
int dec_out_len;
|
|
gboolean ret = TRUE;
|
|
|
|
bmd = seaf_block_manager_stat_block (seaf->block_mgr,
|
|
repo_id, version,
|
|
block_id);
|
|
if (!bmd) {
|
|
seaf_warning ("Failed to stat block %s.\n", block_id);
|
|
return FALSE;
|
|
}
|
|
|
|
handle = seaf_block_manager_open_block (seaf->block_mgr,
|
|
repo_id, version,
|
|
block_id, BLOCK_READ);
|
|
if (!handle) {
|
|
seaf_warning ("Failed to open block %s.\n", block_id);
|
|
g_free (bmd);
|
|
return FALSE;
|
|
}
|
|
|
|
if (seafile_decrypt_init (&ctx, crypt->version,
|
|
crypt->key, crypt->iv) < 0) {
|
|
seaf_warning ("Failed to init decrypt.\n");
|
|
ret = FALSE;
|
|
goto out;
|
|
}
|
|
|
|
remain = bmd->size;
|
|
while (1) {
|
|
n = seaf_block_manager_read_block (seaf->block_mgr, handle, buf, sizeof(buf));
|
|
if (n < 0) {
|
|
seaf_warning ("Failed to read block %s.\n", block_id);
|
|
ret = FALSE;
|
|
break;
|
|
} else if (n == 0) {
|
|
break;
|
|
}
|
|
remain -= n;
|
|
|
|
dec_out = g_new0 (char, n + 16);
|
|
if (!dec_out) {
|
|
seaf_warning ("Failed to alloc memory.\n");
|
|
ret = FALSE;
|
|
break;
|
|
}
|
|
|
|
if (EVP_DecryptUpdate (&ctx,
|
|
(unsigned char *)dec_out,
|
|
&dec_out_len,
|
|
(unsigned char *)buf,
|
|
n) == 0) {
|
|
seaf_warning ("Failed to decrypt block %s .\n", block_id);
|
|
g_free (dec_out);
|
|
ret = FALSE;
|
|
break;
|
|
}
|
|
|
|
if (writen (fd, dec_out, dec_out_len) != dec_out_len) {
|
|
seaf_warning ("Failed to write block %s to file %s.\n",
|
|
block_id, path);
|
|
g_free (dec_out);
|
|
ret = FALSE;
|
|
break;
|
|
}
|
|
|
|
if (remain == 0) {
|
|
if (EVP_DecryptFinal_ex (&ctx,
|
|
(unsigned char *)dec_out,
|
|
&dec_out_len) == 0) {
|
|
seaf_warning ("Failed to decrypt block %s .\n", block_id);
|
|
g_free (dec_out);
|
|
ret = FALSE;
|
|
break;
|
|
}
|
|
if (dec_out_len > 0) {
|
|
if (writen (fd, dec_out, dec_out_len) != dec_out_len) {
|
|
seaf_warning ("Failed to write block %s to file %s.\n",
|
|
block_id, path);
|
|
g_free (dec_out);
|
|
ret = FALSE;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
g_free (dec_out);
|
|
}
|
|
|
|
EVP_CIPHER_CTX_cleanup (&ctx);
|
|
|
|
out:
|
|
g_free (bmd);
|
|
seaf_block_manager_close_block (seaf->block_mgr, handle);
|
|
seaf_block_manager_block_handle_free (seaf->block_mgr, handle);
|
|
|
|
return ret;
|
|
}*/
|
|
|
|
static gboolean
|
|
write_nonenc_block_to_file (const char *repo_id,
|
|
int version,
|
|
const char *block_id,
|
|
const gint64 mtime,
|
|
int fd,
|
|
const char *path)
|
|
{
|
|
BlockHandle *handle;
|
|
char buf[64 * 1024];
|
|
gboolean ret = TRUE;
|
|
int n;
|
|
|
|
handle = seaf_block_manager_open_block (seaf->block_mgr,
|
|
repo_id, version,
|
|
block_id, BLOCK_READ);
|
|
if (!handle) {
|
|
return FALSE;
|
|
}
|
|
|
|
while (1) {
|
|
n = seaf_block_manager_read_block (seaf->block_mgr, handle, buf, sizeof(buf));
|
|
if (n < 0) {
|
|
seaf_warning ("Failed to read block %s.\n", block_id);
|
|
ret = FALSE;
|
|
break;
|
|
} else if (n == 0) {
|
|
break;
|
|
}
|
|
|
|
if (writen (fd, buf, n) != n) {
|
|
seaf_warning ("Failed to write block %s to file %s.\n",
|
|
block_id, path);
|
|
ret = FALSE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
struct utimbuf timebuf;
|
|
|
|
timebuf.modtime = mtime;
|
|
timebuf.actime = mtime;
|
|
|
|
if(utime(path, &timebuf) == -1) {
|
|
seaf_warning ("Current file (%s) lose it\"s mtime.\n", path);
|
|
}
|
|
|
|
seaf_block_manager_close_block (seaf->block_mgr, handle);
|
|
seaf_block_manager_block_handle_free (seaf->block_mgr, handle);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void
|
|
create_file (const char *repo_id,
|
|
const char *file_id,
|
|
const gint64 mtime,
|
|
const char *path)
|
|
{
|
|
int i;
|
|
char *block_id;
|
|
int fd;
|
|
Seafile *seafile;
|
|
gboolean ret = TRUE;
|
|
int version = 1;
|
|
|
|
fd = g_open (path, O_CREAT | O_WRONLY | O_BINARY, 0666);
|
|
if (fd < 0) {
|
|
seaf_warning ("Open file %s failed: %s.\n", path, strerror (errno));
|
|
return;
|
|
}
|
|
|
|
seafile = seaf_fs_manager_get_seafile (seaf->fs_mgr, repo_id,
|
|
version, file_id);
|
|
if (!seafile) {
|
|
ret = FALSE;
|
|
goto out;
|
|
}
|
|
|
|
for (i = 0; i < seafile->n_blocks; ++i) {
|
|
block_id = seafile->blk_sha1s[i];
|
|
|
|
ret = write_nonenc_block_to_file (repo_id, version, block_id, mtime,
|
|
fd, path);
|
|
if (!ret) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
out:
|
|
close (fd);
|
|
if (!ret) {
|
|
if (g_unlink (path) < 0) {
|
|
seaf_warning ("Failed to delete file %s: %s.\n", path, strerror (errno));
|
|
}
|
|
seaf_message ("Failed to export file %s.\n", path);
|
|
} else {
|
|
seaf_message ("Export file %s.\n", path);
|
|
}
|
|
seafile_unref (seafile);
|
|
}
|
|
|
|
static void
|
|
export_repo_files_recursive (const char *repo_id,
|
|
const char *id,
|
|
const char *parent_dir)
|
|
{
|
|
SeafDir *dir;
|
|
GList *p;
|
|
SeafDirent *seaf_dent;
|
|
char *path;
|
|
|
|
SeafFSManager *mgr = seaf->fs_mgr;
|
|
int version = 1;
|
|
|
|
dir = seaf_fs_manager_get_seafdir (mgr, repo_id, version, id);
|
|
if (!dir) {
|
|
return;
|
|
}
|
|
|
|
for (p = dir->entries; p; p = p->next) {
|
|
seaf_dent = p->data;
|
|
path = g_build_filename (parent_dir, seaf_dent->name, NULL);
|
|
|
|
if (S_ISREG(seaf_dent->mode)) {
|
|
// create file
|
|
create_file (repo_id, seaf_dent->id, seaf_dent->mtime, path);
|
|
} else if (S_ISDIR(seaf_dent->mode)) {
|
|
if (g_mkdir (path, 0777) < 0) {
|
|
seaf_warning ("Failed to mkdir %s: %s.\n", path,
|
|
strerror (errno));
|
|
g_free (path);
|
|
continue;
|
|
} else {
|
|
seaf_message ("Export dir %s.\n", path);
|
|
}
|
|
|
|
export_repo_files_recursive (repo_id, seaf_dent->id, path);
|
|
}
|
|
g_free (path);
|
|
}
|
|
|
|
seaf_dir_free (dir);
|
|
}
|
|
|
|
static SeafCommit*
|
|
get_available_commit (const char *repo_id)
|
|
{
|
|
GList *commit_list = NULL;
|
|
GList *temp_list = NULL;
|
|
GList *next_list = NULL;
|
|
SeafCommit *temp_commit = NULL;
|
|
gboolean io_error;
|
|
|
|
seaf_message ("Scanning available commits for repo %s...\n", repo_id);
|
|
|
|
seaf_obj_store_foreach_obj (seaf->commit_mgr->obj_store, repo_id,
|
|
1, fsck_get_repo_commit, &commit_list);
|
|
|
|
if (commit_list == NULL) {
|
|
seaf_warning ("No available commits for repo %.8s, export failed.\n\n",
|
|
repo_id);
|
|
return NULL;
|
|
}
|
|
|
|
commit_list = g_list_sort (commit_list, compare_commit_by_ctime);
|
|
temp_list = commit_list;
|
|
while (temp_list) {
|
|
next_list = temp_list->next;
|
|
temp_commit = temp_list->data;
|
|
io_error = FALSE;
|
|
|
|
if (memcmp (temp_commit->root_id, EMPTY_SHA1, 40) == 0) {
|
|
seaf_commit_unref (temp_commit);
|
|
temp_commit = NULL;
|
|
temp_list = next_list;
|
|
continue;
|
|
} else if (!fsck_verify_seafobj (repo_id, 1, temp_commit->root_id,
|
|
&io_error, VERIFY_DIR, FALSE)) {
|
|
seaf_commit_unref (temp_commit);
|
|
temp_commit = NULL;
|
|
temp_list = next_list;
|
|
|
|
if (io_error) {
|
|
break;
|
|
}
|
|
// fs object of this commit is damaged,
|
|
// continue to verify next
|
|
continue;
|
|
}
|
|
|
|
char time_buf[64];
|
|
strftime (time_buf, 64, "%Y-%m-%d %H:%M:%S", localtime((time_t *)&temp_commit->ctime));
|
|
seaf_message ("Find available commit %.8s(created at %s), will export files from it.\n",
|
|
temp_commit->commit_id, time_buf);
|
|
temp_list = next_list;
|
|
break;
|
|
}
|
|
|
|
while (temp_list) {
|
|
seaf_commit_unref (temp_list->data);
|
|
temp_list = temp_list->next;
|
|
}
|
|
g_list_free (commit_list);
|
|
|
|
if (!temp_commit && !io_error) {
|
|
seaf_warning ("No available commits for repo %.8s, export failed.\n\n",
|
|
repo_id);
|
|
}
|
|
|
|
return temp_commit;
|
|
}
|
|
|
|
void
|
|
export_repo_files (const char *repo_id,
|
|
const char *init_path,
|
|
GHashTable *enc_repos)
|
|
{
|
|
SeafCommit *commit = get_available_commit (repo_id);
|
|
if (!commit) {
|
|
return;
|
|
}
|
|
if (commit->encrypted) {
|
|
g_hash_table_insert (enc_repos, g_strdup (repo_id),
|
|
g_strdup (commit->repo_name));
|
|
seaf_commit_unref (commit);
|
|
return;
|
|
}
|
|
|
|
seaf_message ("Start to export files for repo %.8s(%s).\n",
|
|
repo_id, commit->repo_name);
|
|
|
|
char *dir_name = g_strdup_printf ("%.8s_%s_%s", repo_id,
|
|
commit->repo_name,
|
|
commit->creator_name);
|
|
char * export_path = g_build_filename (init_path, dir_name, NULL);
|
|
g_free (dir_name);
|
|
if (g_mkdir (export_path, 0777) < 0) {
|
|
seaf_warning ("Failed to create export dir %s: %s, export failed.\n",
|
|
export_path, strerror (errno));
|
|
g_free (export_path);
|
|
seaf_commit_unref (commit);
|
|
return;
|
|
}
|
|
|
|
export_repo_files_recursive (repo_id, commit->root_id, export_path);
|
|
|
|
seaf_message ("Finish exporting files for repo %.8s.\n\n", repo_id);
|
|
|
|
g_free (export_path);
|
|
seaf_commit_unref (commit);
|
|
}
|
|
|
|
static GList *
|
|
get_repo_ids (const char *seafile_dir)
|
|
{
|
|
GList *repo_ids = NULL;
|
|
char *commit_path = g_build_filename (seafile_dir, "storage",
|
|
"commits", NULL);
|
|
GError *error = NULL;
|
|
|
|
GDir *dir = g_dir_open (commit_path, 0, &error);
|
|
if (!dir) {
|
|
seaf_warning ("Open dir %s failed: %s.\n",
|
|
commit_path, error->message);
|
|
g_clear_error (&error);
|
|
g_free (commit_path);
|
|
return NULL;
|
|
}
|
|
|
|
const char *file_name;
|
|
while ((file_name = g_dir_read_name (dir)) != NULL) {
|
|
repo_ids = g_list_prepend (repo_ids, g_strdup (file_name));
|
|
}
|
|
g_dir_close (dir);
|
|
|
|
g_free (commit_path);
|
|
|
|
return repo_ids;
|
|
}
|
|
|
|
static void
|
|
print_enc_repo (gpointer key, gpointer value, gpointer user_data)
|
|
{
|
|
seaf_message ("%s(%s)\n", (char *)key, (char *)value);
|
|
}
|
|
|
|
void
|
|
export_file (GList *repo_id_list, const char *seafile_dir, char *export_path)
|
|
{
|
|
struct stat dir_st;
|
|
|
|
if (stat (export_path, &dir_st) < 0) {
|
|
if (errno == ENOENT) {
|
|
if (g_mkdir (export_path, 0777) < 0) {
|
|
seaf_warning ("Mkdir %s failed: %s.\n",
|
|
export_path, strerror (errno));
|
|
return;
|
|
}
|
|
} else {
|
|
seaf_warning ("Stat path: %s failed: %s.\n",
|
|
export_path, strerror (errno));
|
|
return;
|
|
}
|
|
} else {
|
|
if (!S_ISDIR(dir_st.st_mode)) {
|
|
seaf_warning ("%s already exist, but it is not a directory.\n",
|
|
export_path);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (!repo_id_list) {
|
|
repo_id_list = get_repo_ids (seafile_dir);
|
|
if (!repo_id_list)
|
|
return;
|
|
}
|
|
|
|
GList *iter = repo_id_list;
|
|
char *repo_id;
|
|
GHashTable *enc_repos = g_hash_table_new_full (g_str_hash, g_str_equal,
|
|
g_free, g_free);
|
|
|
|
for (; iter; iter=iter->next) {
|
|
repo_id = iter->data;
|
|
if (!is_uuid_valid (repo_id)) {
|
|
seaf_warning ("Invalid repo id: %s.\n", repo_id);
|
|
continue;
|
|
}
|
|
|
|
export_repo_files (repo_id, export_path, enc_repos);
|
|
}
|
|
|
|
if (g_hash_table_size (enc_repos) > 0) {
|
|
seaf_message ("The following repos are encrypted and are not exported:\n");
|
|
g_hash_table_foreach (enc_repos, print_enc_repo, NULL);
|
|
}
|
|
|
|
while (repo_id_list) {
|
|
g_free (repo_id_list->data);
|
|
repo_id_list = g_list_delete_link (repo_id_list, repo_id_list);
|
|
}
|
|
g_hash_table_destroy (enc_repos);
|
|
g_free (export_path);
|
|
}
|