#include "common.h" #include #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); }