1 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
3 #include "common.h"
4
5 #include <glib/gstdio.h>
6
7 #include <jansson.h>
8 #include <openssl/sha.h>
9
10 #include "utils.h"
11 #define DEBUG_FLAG SEAFILE_DEBUG_OTHER
12 #include "log.h"
13 #include "seafile-object.h"
14
15 #include "seafile-session.h"
16 #include "commit-mgr.h"
17 #include "branch-mgr.h"
18 #include "repo-mgr.h"
19 #include "fs-mgr.h"
20 #include "seafile-error.h"
21 #include "seafile-crypt.h"
22 #include "diff-simple.h"
23 #include "merge-new.h"
24
25 #include "seaf-db.h"
26
27 #define INDEX_DIR "index"
28
29 #define PREFIX_DEL_FILE "Deleted \""
30 #define PREFIX_DEL_DIR "Removed directory \""
31 #define PREFIX_DEL_DIRS "Removed \""
32
33 gboolean
34 should_ignore_file(const char *filename, void *data);
35
36 static gboolean
37 is_virtual_repo_and_origin (SeafRepo *repo1, SeafRepo *repo2);
38
39 static gboolean
40 check_file_count_and_size (SeafRepo *repo, SeafDirent *dent, gint64 total_files,
41 gint64 *total_size_all, char **err_str);
42
43 int
44 post_files_and_gen_commit (GList *filenames,
45 SeafRepo *repo,
46 const char *user,
47 char **ret_json,
48 int replace_existed,
49 const char *canon_path,
50 GList *id_list,
51 GList *size_list,
52 GError **error);
53
54 /*
55 * Repo operations.
56 */
57
58 static gint
compare_dirents(gconstpointer a,gconstpointer b)59 compare_dirents (gconstpointer a, gconstpointer b)
60 {
61 const SeafDirent *ent_a = a, *ent_b = b;
62
63 return strcmp (ent_b->name, ent_a->name);
64 }
65
66 static inline GList *
dup_seafdir_entries(const GList * entries)67 dup_seafdir_entries (const GList *entries)
68 {
69 const GList *p;
70 GList *newentries = NULL;
71 SeafDirent *dent;
72
73 for (p = entries; p; p = p->next) {
74 dent = p->data;
75 newentries = g_list_prepend (newentries, seaf_dirent_dup(dent));
76 }
77
78 return g_list_reverse(newentries);
79 }
80
81 static gboolean
filename_exists(GList * entries,const char * filename)82 filename_exists (GList *entries, const char *filename)
83 {
84 GList *ptr;
85 SeafDirent *dent;
86
87 for (ptr = entries; ptr != NULL; ptr = ptr->next) {
88 dent = ptr->data;
89 if (strcmp (dent->name, filename) == 0)
90 return TRUE;
91 }
92
93 return FALSE;
94 }
95
96 static void
split_filename(const char * filename,char ** name,char ** ext)97 split_filename (const char *filename, char **name, char **ext)
98 {
99 char *dot;
100
101 dot = strrchr (filename, '.');
102 if (dot) {
103 *ext = g_strdup (dot + 1);
104 *name = g_strndup (filename, dot - filename);
105 } else {
106 *name = g_strdup (filename);
107 *ext = NULL;
108 }
109 }
110
111 static char *
generate_unique_filename(const char * file,GList * entries)112 generate_unique_filename (const char *file, GList *entries)
113 {
114 int i = 1;
115 char *name, *ext, *unique_name;
116
117 unique_name = g_strdup(file);
118 split_filename (unique_name, &name, &ext);
119 while (filename_exists (entries, unique_name) && i <= 100) {
120 g_free (unique_name);
121 if (ext)
122 unique_name = g_strdup_printf ("%s (%d).%s", name, i, ext);
123 else
124 unique_name = g_strdup_printf ("%s (%d)", name, i);
125 i++;
126 }
127
128 g_free (name);
129 g_free (ext);
130
131 if (i <= 100)
132 return unique_name;
133 else {
134 g_free (unique_name);
135 return NULL;
136 }
137 }
138
139 /* We need to call this function recursively because every dirs in canon_path
140 * need to be updated.
141 */
142 static char *
post_file_recursive(SeafRepo * repo,const char * dir_id,const char * to_path,int replace_existed,SeafDirent * newdent)143 post_file_recursive (SeafRepo *repo,
144 const char *dir_id,
145 const char *to_path,
146 int replace_existed,
147 SeafDirent *newdent)
148 {
149 SeafDir *olddir, *newdir;
150 SeafDirent *dent;
151 GList *ptr;
152 char *slash;
153 char *to_path_dup = NULL;
154 char *remain = NULL;
155 char *id = NULL;
156 char *ret = NULL;
157
158 olddir = seaf_fs_manager_get_seafdir_sorted(seaf->fs_mgr,
159 repo->store_id, repo->version,
160 dir_id);
161 if (!olddir)
162 return NULL;
163
164 /* we reach the target dir. new dir entry is added */
165 if (*to_path == '\0') {
166 GList *newentries = NULL;
167 char *unique_name;
168 SeafDirent *dent_dup;
169 if (replace_existed && filename_exists(olddir->entries, newdent->name)) {
170 GList *p;
171 SeafDirent *dent;
172
173 for (p = olddir->entries; p; p = p->next) {
174 dent = p->data;
175 if (strcmp(dent->name, newdent->name) == 0) {
176 newentries = g_list_prepend (newentries, seaf_dirent_dup(newdent));
177 } else {
178 newentries = g_list_prepend (newentries, seaf_dirent_dup(dent));
179 }
180 }
181 newentries = g_list_reverse (newentries);
182 newdir = seaf_dir_new (NULL, newentries,
183 dir_version_from_repo_version(repo->version));
184 if (seaf_dir_save (seaf->fs_mgr, repo->store_id, repo->version, newdir) == 0) {
185 ret = g_strdup (newdir->dir_id);
186 }
187 seaf_dir_free (newdir);
188 goto out;
189 }
190
191 unique_name = generate_unique_filename (newdent->name, olddir->entries);
192 if (!unique_name)
193 goto out;
194 dent_dup = seaf_dirent_new (newdent->version,
195 newdent->id, newdent->mode, unique_name,
196 newdent->mtime, newdent->modifier, newdent->size);
197 g_free (unique_name);
198
199 newentries = dup_seafdir_entries (olddir->entries);
200
201 newentries = g_list_insert_sorted (newentries,
202 dent_dup,
203 compare_dirents);
204
205 newdir = seaf_dir_new (NULL, newentries,
206 dir_version_from_repo_version(repo->version));
207 if (seaf_dir_save (seaf->fs_mgr, repo->store_id, repo->version, newdir) == 0)
208 ret = g_strdup (newdir->dir_id);
209 seaf_dir_free (newdir);
210
211 goto out;
212 }
213
214 to_path_dup = g_strdup (to_path);
215 slash = strchr (to_path_dup, '/');
216
217 if (!slash) {
218 remain = to_path_dup + strlen(to_path_dup);
219 } else {
220 *slash = '\0';
221 remain = slash + 1;
222 }
223
224 for (ptr = olddir->entries; ptr; ptr = ptr->next) {
225 dent = (SeafDirent *)ptr->data;
226
227 if (strcmp(dent->name, to_path_dup) != 0)
228 continue;
229
230 id = post_file_recursive (repo, dent->id, remain, replace_existed, newdent);
231 if (id != NULL) {
232 memcpy(dent->id, id, 40);
233 dent->id[40] = '\0';
234 if (repo->version > 0)
235 dent->mtime = (guint64)time(NULL);
236 }
237 break;
238 }
239
240 if (id != NULL) {
241 /* Create a new SeafDir. */
242 GList *new_entries;
243
244 new_entries = dup_seafdir_entries (olddir->entries);
245 newdir = seaf_dir_new (NULL, new_entries,
246 dir_version_from_repo_version(repo->version));
247 if (seaf_dir_save (seaf->fs_mgr, repo->store_id, repo->version, newdir) == 0)
248 ret = g_strndup (newdir->dir_id, 40);
249 seaf_dir_free (newdir);
250 }
251
252 out:
253 g_free (to_path_dup);
254 g_free (id);
255 seaf_dir_free(olddir);
256 return ret;
257 }
258
259 static char *
do_post_file_replace(SeafRepo * repo,const char * root_id,const char * parent_dir,int replace_existed,SeafDirent * dent)260 do_post_file_replace (SeafRepo *repo,
261 const char *root_id,
262 const char *parent_dir,
263 int replace_existed,
264 SeafDirent *dent)
265 {
266 /* if parent_dir is a absolutely path, we will remove the first '/' */
267 if (*parent_dir == '/')
268 parent_dir = parent_dir + 1;
269
270 return post_file_recursive(repo, root_id, parent_dir, replace_existed, dent);
271 }
272
273 static char *
do_post_file(SeafRepo * repo,const char * root_id,const char * parent_dir,SeafDirent * dent)274 do_post_file (SeafRepo *repo,
275 const char *root_id,
276 const char *parent_dir,
277 SeafDirent *dent)
278 {
279 return do_post_file_replace(repo, root_id, parent_dir, 0, dent);
280 }
281
282 static char *
get_canonical_path(const char * path)283 get_canonical_path (const char *path)
284 {
285 char *ret = g_strdup (path);
286 char *p;
287
288 for (p = ret; *p != 0; ++p) {
289 if (*p == '\\')
290 *p = '/';
291 }
292
293 /* Remove trailing slashes from dir path. */
294 int len = strlen(ret);
295 int i = len - 1;
296 while (i >= 0 && ret[i] == '/')
297 ret[i--] = 0;
298
299 return ret;
300 }
301
302 /* Return TRUE if @filename already existing in @parent_dir. If exists, and
303 @mode is not NULL, set its value to the mode of the dirent.
304 */
305 static gboolean
check_file_exists(const char * store_id,int repo_version,const char * root_id,const char * parent_dir,const char * filename,int * mode)306 check_file_exists (const char *store_id,
307 int repo_version,
308 const char *root_id,
309 const char *parent_dir,
310 const char *filename,
311 int *mode)
312 {
313 SeafDir *dir;
314 GList *p;
315 SeafDirent *dent;
316 int ret = FALSE;
317
318 dir = seaf_fs_manager_get_seafdir_by_path (seaf->fs_mgr,
319 store_id, repo_version,
320 root_id,
321 parent_dir, NULL);
322 if (!dir) {
323 seaf_warning ("parent_dir %s doesn't exist in repo %s.\n",
324 parent_dir, store_id);
325 return FALSE;
326 }
327
328 for (p = dir->entries; p != NULL; p = p->next) {
329 dent = p->data;
330 int r = strcmp (dent->name, filename);
331 if (r == 0) {
332 ret = TRUE;
333 if (mode) {
334 *mode = dent->mode;
335 }
336 break;
337 }
338 }
339
340 seaf_dir_free (dir);
341
342 return ret;
343 }
344
345 /**
346 Various online file/directory operations:
347
348 Put a file:
349 1. find parent seafdir
350 2. add a new dirent to parent seafdir
351 2. recursively update all seafdir in the path, in a bottom-up manner
352 3. commit it
353
354 Del a file/dir:
355 basically the same as put a file
356
357 copy a file/dir:
358 1. get src dirent from src repo
359 2. duplicate src dirent with the new file name
360 3. put the new dirent to dst repo and commit it.
361
362 Move a file/dir:
363 basically the same as a copy operation. Just one more step:
364 4. remove src dirent from src repo and commit it
365
366 Rename a file/dir:
367 1. find parent seafdir
368 2. update this seafdir with the old dirent replaced by a new dirent.
369 3. recursively update all seafdir in the path
370
371 NOTE:
372
373 All operations which add a new dirent would check if a dirent with the same
374 name already exists. If found, they would raise errors.
375
376 All operations which remove a dirent would check if the dirent to be removed
377 already exists. If not, they would do nothing and just return OK.
378
379 */
380
381 #define GET_REPO_OR_FAIL(repo_var,repo_id) \
382 do { \
383 repo_var = seaf_repo_manager_get_repo (seaf->repo_mgr, (repo_id)); \
384 if (!(repo_var)) { \
385 seaf_warning ("Repo %s doesn't exist.\n", (repo_id)); \
386 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, "Invalid repo"); \
387 ret = -1; \
388 goto out; \
389 } \
390 } while (0);
391
392 #define GET_COMMIT_OR_FAIL(commit_var,repo_id,repo_version,commit_id) \
393 do { \
394 commit_var = seaf_commit_manager_get_commit(seaf->commit_mgr, (repo_id), (repo_version), (commit_id)); \
395 if (!(commit_var)) { \
396 seaf_warning ("commit %s:%s doesn't exist.\n", (repo_id), (commit_id)); \
397 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, "Invalid commit"); \
398 ret = -1; \
399 goto out; \
400 } \
401 } while (0);
402
403 #define FAIL_IF_FILE_EXISTS(store_id,repo_version,root_id,parent_dir,filename,mode) \
404 do { \
405 if (check_file_exists ((store_id), (repo_version), (root_id), (parent_dir), (filename), (mode))) { \
406 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, \
407 "file already exists"); \
408 ret = -1; \
409 goto out; \
410 } \
411 } while (0);
412
413 #define FAIL_IF_FILE_NOT_EXISTS(store_id,repo_version,root_id,parent_dir,filename,mode) \
414 do { \
415 if (!check_file_exists ((store_id), (repo_version), (root_id), (parent_dir), (filename), (mode))) { \
416 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, \
417 "file does not exist"); \
418 ret = -1; \
419 goto out; \
420 } \
421 } while (0);
422
423 #define STD_FILE_MODE (S_IFREG | 0644)
424
425 static char *
gen_merge_description(SeafRepo * repo,const char * merged_root,const char * p1_root,const char * p2_root)426 gen_merge_description (SeafRepo *repo,
427 const char *merged_root,
428 const char *p1_root,
429 const char *p2_root)
430 {
431 GList *p;
432 GList *results = NULL;
433 char *desc;
434
435 diff_merge_roots (repo->store_id, repo->version,
436 merged_root, p1_root, p2_root, &results, TRUE);
437
438 desc = diff_results_to_description (results);
439
440 for (p = results; p; p = p->next) {
441 DiffEntry *de = p->data;
442 diff_entry_free (de);
443 }
444 g_list_free (results);
445
446 return desc;
447 }
448
449 static int
gen_new_commit(const char * repo_id,SeafCommit * base,const char * new_root,const char * user,const char * desc,char * new_commit_id,GError ** error)450 gen_new_commit (const char *repo_id,
451 SeafCommit *base,
452 const char *new_root,
453 const char *user,
454 const char *desc,
455 char *new_commit_id,
456 GError **error)
457 {
458 #define MAX_RETRY_COUNT 3
459
460 SeafRepo *repo = NULL;
461 SeafCommit *new_commit = NULL, *current_head = NULL, *merged_commit = NULL;
462 int retry_cnt = 0;
463 int ret = 0;
464
465 repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id);
466 if (!repo) {
467 seaf_warning ("Repo %s doesn't exist.\n", repo_id);
468 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL, "Invalid repo");
469 ret = -1;
470 goto out;
471 }
472
473 /* Create a new commit pointing to new_root. */
474 new_commit = seaf_commit_new(NULL, repo->id, new_root,
475 user, EMPTY_SHA1,
476 desc, 0);
477 new_commit->parent_id = g_strdup (base->commit_id);
478 seaf_repo_to_commit (repo, new_commit);
479
480 if (seaf_commit_manager_add_commit (seaf->commit_mgr, new_commit) < 0) {
481 seaf_warning ("Failed to add commit.\n");
482 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
483 "Failed to add commit");
484 ret = -1;
485 goto out;
486 }
487
488 retry:
489 current_head = seaf_commit_manager_get_commit (seaf->commit_mgr,
490 repo->id, repo->version,
491 repo->head->commit_id);
492 if (!current_head) {
493 seaf_warning ("Failed to find head commit %s of %s.\n",
494 repo->head->commit_id, repo_id);
495 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL, "Invalid repo");
496 ret = -1;
497 goto out;
498 }
499
500 /* Merge if base and head are not the same. */
501 if (strcmp (base->commit_id, current_head->commit_id) != 0) {
502 MergeOptions opt;
503 const char *roots[3];
504 char *desc = NULL;
505
506 memset (&opt, 0, sizeof(opt));
507 opt.n_ways = 3;
508 memcpy (opt.remote_repo_id, repo_id, 36);
509 memcpy (opt.remote_head, new_commit->commit_id, 40);
510 opt.do_merge = TRUE;
511
512 roots[0] = base->root_id; /* base */
513 roots[1] = current_head->root_id; /* head */
514 roots[2] = new_root; /* remote */
515
516 if (seaf_merge_trees (repo->store_id, repo->version, 3, roots, &opt) < 0) {
517 seaf_warning ("Failed to merge.\n");
518 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
519 "Internal error");
520 ret = -1;
521 goto out;
522 }
523
524 seaf_debug ("Number of dirs visted in merge %.8s: %d.\n",
525 repo_id, opt.visit_dirs);
526
527 if (!opt.conflict)
528 desc = g_strdup("Auto merge by system");
529 else {
530 desc = gen_merge_description (repo,
531 opt.merged_tree_root,
532 current_head->root_id,
533 new_root);
534 if (!desc)
535 desc = g_strdup("Auto merge by system");
536 }
537
538 merged_commit = seaf_commit_new(NULL, repo->id, opt.merged_tree_root,
539 user, EMPTY_SHA1,
540 desc,
541 0);
542 g_free (desc);
543
544 merged_commit->parent_id = g_strdup (current_head->commit_id);
545 merged_commit->second_parent_id = g_strdup (new_commit->commit_id);
546 merged_commit->new_merge = TRUE;
547 if (opt.conflict)
548 merged_commit->conflict = TRUE;
549 seaf_repo_to_commit (repo, merged_commit);
550
551 if (seaf_commit_manager_add_commit (seaf->commit_mgr, merged_commit) < 0) {
552 seaf_warning ("Failed to add commit.\n");
553 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
554 "Failed to add commit");
555 ret = -1;
556 goto out;
557 }
558 } else {
559 seaf_commit_ref (new_commit);
560 merged_commit = new_commit;
561 }
562
563 seaf_branch_set_commit(repo->head, merged_commit->commit_id);
564
565 if (seaf_branch_manager_test_and_update_branch(seaf->branch_mgr,
566 repo->head,
567 current_head->commit_id) < 0)
568 {
569 seaf_repo_unref (repo);
570 repo = NULL;
571 seaf_commit_unref (current_head);
572 current_head = NULL;
573 seaf_commit_unref (merged_commit);
574 merged_commit = NULL;
575
576 if (++retry_cnt <= MAX_RETRY_COUNT) {
577 /* Sleep random time between 100 and 1000 millisecs. */
578 usleep (g_random_int_range(1, 11) * 100 * 1000);
579
580 repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id);
581 if (!repo) {
582 seaf_warning ("Repo %s doesn't exist.\n", repo_id);
583 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL, "Invalid repo");
584 ret = -1;
585 goto out;
586 }
587
588 goto retry;
589 } else {
590 seaf_warning ("Stop updating repo %s after %d retries.\n", repo_id, MAX_RETRY_COUNT);
591 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL, "Concurrent update");
592 ret = -1;
593 goto out;
594 }
595 }
596
597 if (new_commit_id)
598 memcpy (new_commit_id, merged_commit->commit_id, 41);
599
600 out:
601 seaf_commit_unref (new_commit);
602 seaf_commit_unref (current_head);
603 seaf_commit_unref (merged_commit);
604 seaf_repo_unref (repo);
605 return ret;
606 }
607
608 static void
update_repo_size(const char * repo_id)609 update_repo_size(const char *repo_id)
610 {
611 schedule_repo_size_computation (seaf->size_sched, repo_id);
612 }
613
614 int
seaf_repo_manager_post_file(SeafRepoManager * mgr,const char * repo_id,const char * temp_file_path,const char * parent_dir,const char * file_name,const char * user,GError ** error)615 seaf_repo_manager_post_file (SeafRepoManager *mgr,
616 const char *repo_id,
617 const char *temp_file_path,
618 const char *parent_dir,
619 const char *file_name,
620 const char *user,
621 GError **error)
622 {
623 SeafRepo *repo = NULL;
624 SeafCommit *head_commit = NULL;
625 char *canon_path = NULL;
626 unsigned char sha1[20];
627 char buf[SEAF_PATH_MAX];
628 char *root_id = NULL;
629 SeafileCrypt *crypt = NULL;
630 SeafDirent *new_dent = NULL;
631 char hex[41];
632 int ret = 0;
633
634 if (g_access (temp_file_path, R_OK) != 0) {
635 seaf_warning ("[post file] File %s doesn't exist or not readable.\n",
636 temp_file_path);
637 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS,
638 "Invalid input file");
639 return -1;
640 }
641
642 GET_REPO_OR_FAIL(repo, repo_id);
643 GET_COMMIT_OR_FAIL(head_commit, repo->id, repo->version, repo->head->commit_id);
644
645 if (!canon_path)
646 canon_path = get_canonical_path (parent_dir);
647
648 if (should_ignore_file (file_name, NULL)) {
649 seaf_debug ("[post file] Invalid filename %s.\n", file_name);
650 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS,
651 "Invalid filename");
652 ret = -1;
653 goto out;
654 }
655
656 if (strstr (parent_dir, "//") != NULL) {
657 seaf_debug ("[post file] parent_dir cantains // sequence.\n");
658 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS,
659 "Invalid parent dir");
660 ret = -1;
661 goto out;
662 }
663
664 /* Write blocks. */
665 if (repo->encrypted) {
666 unsigned char key[32], iv[16];
667 if (seaf_passwd_manager_get_decrypt_key_raw (seaf->passwd_mgr,
668 repo_id, user,
669 key, iv) < 0) {
670 seaf_debug ("Passwd for repo %s is not set.\n", repo_id);
671 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
672 "Passwd is not set");
673 ret = -1;
674 goto out;
675 }
676 crypt = seafile_crypt_new (repo->enc_version, key, iv);
677 }
678
679 gint64 size;
680 if (seaf_fs_manager_index_blocks (seaf->fs_mgr,
681 repo->store_id, repo->version,
682 temp_file_path,
683 sha1, &size, crypt, TRUE, FALSE, NULL) < 0) {
684 seaf_warning ("failed to index blocks");
685 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
686 "Failed to index blocks");
687 ret = -1;
688 goto out;
689 }
690
691 rawdata_to_hex(sha1, hex, 20);
692 new_dent = seaf_dirent_new (dir_version_from_repo_version (repo->version),
693 hex, STD_FILE_MODE, file_name,
694 (gint64)time(NULL), user, size);
695
696 root_id = do_post_file (repo,
697 head_commit->root_id, canon_path, new_dent);
698 if (!root_id) {
699 seaf_warning ("[post file] Failed to post file %s to %s in repo %s.\n",
700 file_name, canon_path, repo->id);
701 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
702 "Failed to post file");
703 ret = -1;
704 goto out;
705 }
706
707 snprintf(buf, SEAF_PATH_MAX, "Added \"%s\"", file_name);
708 if (gen_new_commit (repo_id, head_commit, root_id,
709 user, buf, NULL, error) < 0) {
710 ret = -1;
711 goto out;
712 }
713
714 seaf_repo_manager_merge_virtual_repo (mgr, repo_id, NULL);
715
716 out:
717 if (repo)
718 seaf_repo_unref (repo);
719 if (head_commit)
720 seaf_commit_unref(head_commit);
721 seaf_dirent_free (new_dent);
722 g_free (root_id);
723 g_free (canon_path);
724 g_free (crypt);
725
726 if (ret == 0)
727 update_repo_size(repo_id);
728
729 return ret;
730 }
731
732 static int
add_new_entries(SeafRepo * repo,const char * user,GList ** entries,GList * dents,int replace_existed,GList ** name_list)733 add_new_entries (SeafRepo *repo, const char *user, GList **entries,
734 GList *dents, int replace_existed, GList **name_list)
735 {
736 GList *ptr;
737 SeafDirent *dent;
738
739 for (ptr = dents; ptr; ptr = ptr->next) {
740 dent = ptr->data;
741
742 char *unique_name;
743 SeafDirent *newdent;
744 gboolean replace = FALSE;
745
746 if (replace_existed) {
747 GList *p;
748 SeafDirent *tmp_dent;
749 for (p = *entries; p; p = p->next) {
750 tmp_dent = p->data;
751 if (strcmp(tmp_dent->name, dent->name) == 0) {
752 replace = TRUE;
753 *entries = g_list_delete_link (*entries, p);
754 seaf_dirent_free (tmp_dent);
755 break;
756 }
757 }
758 }
759
760 if (replace)
761 unique_name = g_strdup (dent->name);
762 else
763 unique_name = generate_unique_filename (dent->name, *entries);
764
765 if (unique_name != NULL) {
766 newdent = seaf_dirent_new (dir_version_from_repo_version(repo->version),
767 dent->id, dent->mode, unique_name,
768 dent->mtime, user, dent->size);
769 *entries = g_list_insert_sorted (*entries, newdent, compare_dirents);
770 *name_list = g_list_append (*name_list, unique_name);
771 /* No need to free unique_name */
772 } else {
773 return -1;
774 }
775 }
776
777 return 0;
778 }
779
780 static char *
post_multi_files_recursive(SeafRepo * repo,const char * dir_id,const char * to_path,GList * dents,const char * user,int replace_existed,GList ** name_list)781 post_multi_files_recursive (SeafRepo *repo,
782 const char *dir_id,
783 const char *to_path,
784 GList *dents,
785 const char *user,
786 int replace_existed,
787 GList **name_list)
788 {
789 SeafDir *olddir, *newdir;
790 SeafDirent *dent;
791 GList *ptr;
792 char *slash;
793 char *to_path_dup = NULL;
794 char *remain = NULL;
795 char *id = NULL;
796 char *ret = NULL;
797
798 olddir = seaf_fs_manager_get_seafdir_sorted(seaf->fs_mgr,
799 repo->store_id,
800 repo->version,
801 dir_id);
802 if (!olddir)
803 return NULL;
804
805 /* we reach the target dir. new dir entry is added */
806 if (*to_path == '\0') {
807 GList *newentries;
808
809 newentries = dup_seafdir_entries (olddir->entries);
810
811 if (add_new_entries (repo, user,
812 &newentries, dents, replace_existed, name_list) < 0)
813 goto out;
814
815 newdir = seaf_dir_new (NULL, newentries,
816 dir_version_from_repo_version(repo->version));
817 if (seaf_dir_save (seaf->fs_mgr, repo->store_id, repo->version, newdir) == 0)
818 ret = g_strdup (newdir->dir_id);
819 seaf_dir_free (newdir);
820
821 goto out;
822 }
823
824 to_path_dup = g_strdup (to_path);
825 slash = strchr (to_path_dup, '/');
826
827 if (!slash) {
828 remain = to_path_dup + strlen(to_path_dup);
829 } else {
830 *slash = '\0';
831 remain = slash + 1;
832 }
833
834 for (ptr = olddir->entries; ptr; ptr = ptr->next) {
835 dent = (SeafDirent *)ptr->data;
836
837 if (strcmp(dent->name, to_path_dup) != 0)
838 continue;
839
840 id = post_multi_files_recursive (repo, dent->id, remain, dents, user,
841 replace_existed, name_list);
842 if (id != NULL) {
843 memcpy(dent->id, id, 40);
844 dent->id[40] = '\0';
845 if (repo->version > 0)
846 dent->mtime = (guint64)time(NULL);
847 }
848 break;
849 }
850
851 if (id != NULL) {
852 /* Create a new SeafDir. */
853 GList *new_entries;
854
855 new_entries = dup_seafdir_entries (olddir->entries);
856 newdir = seaf_dir_new (NULL, new_entries,
857 dir_version_from_repo_version(repo->version));
858 if (seaf_dir_save (seaf->fs_mgr, repo->store_id, repo->version, newdir) == 0)
859 ret = g_strdup (newdir->dir_id);
860 seaf_dir_free (newdir);
861 }
862
863 out:
864 g_free (to_path_dup);
865 g_free (id);
866 seaf_dir_free(olddir);
867 return ret;
868 }
869
870 static char *
do_post_multi_files(SeafRepo * repo,const char * root_id,const char * parent_dir,GList * filenames,GList * id_list,GList * size_list,const char * user,int replace_existed,GList ** name_list)871 do_post_multi_files (SeafRepo *repo,
872 const char *root_id,
873 const char *parent_dir,
874 GList *filenames,
875 GList *id_list,
876 GList *size_list,
877 const char *user,
878 int replace_existed,
879 GList **name_list)
880 {
881 SeafDirent *dent;
882 GList *dents = NULL;
883 GList *ptr1, *ptr2, *ptr3;
884 char *ret;
885
886 for (ptr1 = filenames, ptr2 = id_list, ptr3 = size_list;
887 ptr1 && ptr2 && ptr3;
888 ptr1 = ptr1->next, ptr2 = ptr2->next, ptr3 = ptr3->next) {
889
890 char *name = ptr1->data;
891 char *id = ptr2->data;
892 gint64 *size = ptr3->data;
893
894 dent = g_new0 (SeafDirent, 1);
895 dent->name = name;
896 memcpy(dent->id, id, 40);
897 dent->id[40] = '\0';
898 dent->size = *size;
899 dent->mode = STD_FILE_MODE;
900 dent->mtime = (gint64)time(NULL);
901
902 dents = g_list_append (dents, dent);
903 }
904 /* if parent_dir is a absolutely path, we will remove the first '/' */
905 if (*parent_dir == '/')
906 parent_dir = parent_dir + 1;
907
908 ret = post_multi_files_recursive(repo, root_id, parent_dir,
909 dents, user, replace_existed, name_list);
910 g_list_free_full (dents, g_free);
911
912 return ret;
913 }
914
915 static GList *
json_to_file_list(const char * files_json)916 json_to_file_list (const char *files_json)
917 {
918 json_t *array;
919 GList *files = NULL;
920 json_error_t jerror;
921 size_t index;
922 json_t *value;
923 const char *file;
924 char *norm_file;
925
926 array = json_loadb (files_json, strlen(files_json), 0, &jerror);
927 if (!array) {
928 seaf_warning ("Failed to load json file list: %s.\n", jerror.text);
929 return NULL;
930 }
931
932 size_t n = json_array_size (array);
933 for (index = 0; index < n; index++) {
934 value = json_array_get (array, index);
935 file = json_string_value (value);
936 if (!file) {
937 g_list_free_full (files, g_free);
938 files = NULL;
939 break;
940 }
941
942 norm_file = normalize_utf8_path (file);
943 if (!norm_file) {
944 g_list_free_full (files, g_free);
945 files = NULL;
946 break;
947 }
948
949 files = g_list_prepend (files, norm_file);
950 }
951
952 json_decref (array);
953 return g_list_reverse(files);
954 }
955
956 /*
957 * Return [{'name': 'file1', 'id': 'id1', 'size': num1}, {'name': 'file2', 'id': 'id2', 'size': num2}]
958 */
959 static char *
format_json_ret(GList * name_list,GList * id_list,GList * size_list)960 format_json_ret (GList *name_list, GList *id_list, GList *size_list)
961 {
962 json_t *array, *obj;
963 GList *ptr, *ptr2;
964 GList *sptr;
965 char *filename, *id;
966 gint64 *size;
967 char *json_data;
968 char *ret;
969
970 array = json_array ();
971
972 for (ptr = name_list, ptr2 = id_list, sptr = size_list;
973 ptr && ptr2 && sptr;
974 ptr = ptr->next, ptr2 = ptr2->next, sptr = sptr->next) {
975 filename = ptr->data;
976 id = ptr2->data;
977 size = sptr->data;
978 obj = json_object ();
979 json_object_set_string_member (obj, "name", filename);
980 json_object_set_string_member (obj, "id", id);
981 json_object_set_int_member (obj, "size", *size);
982 json_array_append_new (array, obj);
983 }
984
985 json_data = json_dumps (array, 0);
986 json_decref (array);
987
988 ret = g_strdup (json_data);
989 free (json_data);
990 return ret;
991 }
992
993 int
seaf_repo_manager_post_multi_files(SeafRepoManager * mgr,const char * repo_id,const char * parent_dir,const char * filenames_json,const char * paths_json,const char * user,int replace_existed,char ** ret_json,char ** task_id,GError ** error)994 seaf_repo_manager_post_multi_files (SeafRepoManager *mgr,
995 const char *repo_id,
996 const char *parent_dir,
997 const char *filenames_json,
998 const char *paths_json,
999 const char *user,
1000 int replace_existed,
1001 char **ret_json,
1002 char **task_id,
1003 GError **error)
1004 {
1005 SeafRepo *repo = NULL;
1006 char *canon_path = NULL;
1007 GList *filenames = NULL, *paths = NULL, *id_list = NULL, *size_list = NULL, *ptr;
1008 char *filename, *path;
1009 unsigned char sha1[20];
1010 SeafileCrypt *crypt = NULL;
1011 char hex[41];
1012 int ret = 0;
1013
1014 GET_REPO_OR_FAIL(repo, repo_id);
1015
1016 canon_path = get_canonical_path (parent_dir);
1017
1018 /* Decode file name and tmp file paths from json. */
1019 filenames = json_to_file_list (filenames_json);
1020 paths = json_to_file_list (paths_json);
1021 if (!filenames || !paths) {
1022 seaf_debug ("[post files] Invalid filenames or paths.\n");
1023 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, "Invalid files");
1024 ret = -1;
1025 goto out;
1026 }
1027
1028 /* Check inputs. */
1029 for (ptr = filenames; ptr; ptr = ptr->next) {
1030 filename = ptr->data;
1031 if (should_ignore_file (filename, NULL)) {
1032 seaf_debug ("[post files] Invalid filename %s.\n", filename);
1033 g_set_error (error, SEAFILE_DOMAIN, POST_FILE_ERR_FILENAME,
1034 "%s", filename);
1035 ret = -1;
1036 goto out;
1037 }
1038 }
1039
1040 if (strstr (parent_dir, "//") != NULL) {
1041 seaf_debug ("[post file] parent_dir cantains // sequence.\n");
1042 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS,
1043 "Invalid parent dir");
1044 ret = -1;
1045 goto out;
1046 }
1047
1048 /* Index tmp files and get file id list. */
1049 if (repo->encrypted) {
1050 unsigned char key[32], iv[16];
1051 if (seaf_passwd_manager_get_decrypt_key_raw (seaf->passwd_mgr,
1052 repo_id, user,
1053 key, iv) < 0) {
1054 seaf_debug ("Passwd for repo %s is not set.\n", repo_id);
1055 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
1056 "Passwd is not set");
1057 ret = -1;
1058 goto out;
1059 }
1060 crypt = seafile_crypt_new (repo->enc_version, key, iv);
1061 }
1062
1063 if (!task_id) {
1064 gint64 *size;
1065 for (ptr = paths; ptr; ptr = ptr->next) {
1066 path = ptr->data;
1067
1068 size = g_new (gint64, 1);
1069 if (seaf_fs_manager_index_blocks (seaf->fs_mgr,
1070 repo->store_id, repo->version,
1071 path, sha1, size, crypt, TRUE, FALSE, NULL) < 0) {
1072 seaf_warning ("failed to index blocks");
1073 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
1074 "Failed to index blocks");
1075 ret = -1;
1076 goto out;
1077 }
1078
1079 rawdata_to_hex(sha1, hex, 20);
1080 id_list = g_list_prepend (id_list, g_strdup(hex));
1081 size_list = g_list_prepend (size_list, size);
1082 }
1083 id_list = g_list_reverse (id_list);
1084 size_list = g_list_reverse (size_list);
1085
1086 ret = post_files_and_gen_commit (filenames,
1087 repo,
1088 user,
1089 ret_json,
1090 replace_existed,
1091 canon_path,
1092 id_list,
1093 size_list,
1094 error);
1095 } else {
1096 ret = index_blocks_mgr_start_index (seaf->index_blocks_mgr,
1097 filenames,
1098 paths,
1099 repo_id,
1100 user,
1101 replace_existed,
1102 ret_json == NULL ? FALSE : TRUE,
1103 canon_path,
1104 crypt,
1105 task_id);
1106 }
1107
1108 out:
1109 if (repo)
1110 seaf_repo_unref (repo);
1111 string_list_free (filenames);
1112 string_list_free (paths);
1113 string_list_free (id_list);
1114 for (ptr = size_list; ptr; ptr = ptr->next)
1115 g_free (ptr->data);
1116 g_list_free (size_list);
1117 g_free (canon_path);
1118 g_free (crypt);
1119
1120 return ret;
1121 }
1122
1123 int
post_files_and_gen_commit(GList * filenames,SeafRepo * repo,const char * user,char ** ret_json,int replace_existed,const char * canon_path,GList * id_list,GList * size_list,GError ** error)1124 post_files_and_gen_commit (GList *filenames,
1125 SeafRepo *repo,
1126 const char *user,
1127 char **ret_json,
1128 int replace_existed,
1129 const char *canon_path,
1130 GList *id_list,
1131 GList *size_list,
1132 GError **error)
1133 {
1134 GList *name_list = NULL;
1135 GString *buf = g_string_new (NULL);
1136 SeafCommit *head_commit = NULL;
1137 char *root_id = NULL;
1138 int ret = 0;
1139
1140 GET_COMMIT_OR_FAIL(head_commit, repo->id, repo->version, repo->head->commit_id);
1141
1142 /* Add the files to parent dir and commit. */
1143 root_id = do_post_multi_files (repo, head_commit->root_id, canon_path,
1144 filenames, id_list, size_list, user,
1145 replace_existed, &name_list);
1146 if (!root_id) {
1147 seaf_warning ("[post multi-file] Failed to post files to %s in repo %s.\n",
1148 canon_path, repo->id);
1149 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_INTERNAL,
1150 "Failed to put file");
1151 ret = -1;
1152 goto out;
1153 }
1154 guint len = g_list_length (filenames);
1155 if (len > 1)
1156 g_string_printf (buf, "Added \"%s\" and %u more files.",
1157 (char *)(filenames->data), len - 1);
1158 else
1159 g_string_printf (buf, "Added \"%s\".", (char *)(filenames->data));
1160
1161 if (gen_new_commit (repo->id, head_commit, root_id,
1162 user, buf->str, NULL, error) < 0) {
1163 ret = -1;
1164 goto out;
1165 }
1166
1167 seaf_repo_manager_merge_virtual_repo (seaf->repo_mgr, repo->id, NULL);
1168
1169 if (ret_json)
1170 *ret_json = format_json_ret (name_list, id_list, size_list);
1171
1172 update_repo_size(repo->id);
1173
1174 out:
1175 if (head_commit)
1176 seaf_commit_unref(head_commit);
1177 string_list_free (name_list);
1178 g_string_free (buf, TRUE);
1179 g_free (root_id);
1180
1181 return ret;
1182 }
1183
1184 /* int */
1185 /* seaf_repo_manager_post_file_blocks (SeafRepoManager *mgr, */
1186 /* const char *repo_id, */
1187 /* const char *parent_dir, */
1188 /* const char *file_name, */
1189 /* const char *blockids_json, */
1190 /* const char *paths_json, */
1191 /* const char *user, */
1192 /* gint64 file_size, */
1193 /* int replace_existed, */
1194 /* char **new_id, */
1195 /* GError **error) */
1196 /* { */
1197 /* SeafRepo *repo = NULL; */
1198 /* SeafCommit *head_commit = NULL; */
1199 /* char *canon_path = NULL; */
1200 /* unsigned char sha1[20]; */
1201 /* char buf[SEAF_PATH_MAX]; */
1202 /* char *root_id = NULL; */
1203 /* SeafDirent *new_dent = NULL; */
1204 /* GList *blockids = NULL, *paths = NULL, *ptr; */
1205 /* char hex[41]; */
1206 /* int ret = 0; */
1207
1208 /* blockids = json_to_file_list (blockids_json); */
1209 /* paths = json_to_file_list (paths_json); */
1210 /* if (g_list_length(blockids) != g_list_length(paths)) { */
1211 /* seaf_debug ("[post-blks] Invalid blockids or paths.\n"); */
1212 /* g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, "Invalid files"); */
1213 /* ret = -1; */
1214 /* goto out; */
1215 /* } */
1216
1217 /* for (ptr = paths; ptr; ptr = ptr->next) { */
1218 /* char *temp_file_path = ptr->data; */
1219 /* if (g_access (temp_file_path, R_OK) != 0) { */
1220 /* seaf_warning ("[post-blks] File block %s doesn't exist or not readable.\n", */
1221 /* temp_file_path); */
1222 /* g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, */
1223 /* "Invalid input file"); */
1224 /* ret = -1; */
1225 /* goto out; */
1226 /* } */
1227 /* } */
1228
1229 /* GET_REPO_OR_FAIL(repo, repo_id); */
1230 /* GET_COMMIT_OR_FAIL(head_commit, repo->id, repo->version, repo->head->commit_id); */
1231
1232 /* if (!canon_path) */
1233 /* canon_path = get_canonical_path (parent_dir); */
1234
1235 /* if (should_ignore_file (file_name, NULL)) { */
1236 /* seaf_debug ("[post-blks] Invalid filename %s.\n", file_name); */
1237 /* g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, */
1238 /* "Invalid filename"); */
1239 /* ret = -1; */
1240 /* goto out; */
1241 /* } */
1242
1243 /* if (strstr (parent_dir, "//") != NULL) { */
1244 /* seaf_debug ("[post-blks] parent_dir cantains // sequence.\n"); */
1245 /* g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, */
1246 /* "Invalid parent dir"); */
1247 /* ret = -1; */
1248 /* goto out; */
1249 /* } */
1250
1251 /* /\* Write blocks. *\/ */
1252 /* if (seaf_fs_manager_index_file_blocks (seaf->fs_mgr, */
1253 /* repo->store_id, repo->version, */
1254 /* paths, */
1255 /* blockids, sha1, file_size) < 0) { */
1256 /* seaf_warning ("Failed to index file blocks"); */
1257 /* g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL, */
1258 /* "Failed to index blocks"); */
1259 /* ret = -1; */
1260 /* goto out; */
1261 /* } */
1262
1263 /* rawdata_to_hex(sha1, hex, 20); */
1264 /* new_dent = seaf_dirent_new (dir_version_from_repo_version(repo->version), */
1265 /* hex, STD_FILE_MODE, file_name, */
1266 /* (gint64)time(NULL), user, file_size); */
1267
1268 /* root_id = do_post_file_replace (repo, head_commit->root_id, */
1269 /* canon_path, replace_existed, new_dent); */
1270 /* if (!root_id) { */
1271 /* seaf_warning ("[post-blks] Failed to post file to %s in repo %s.\n", */
1272 /* canon_path, repo->id); */
1273 /* g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL, */
1274 /* "Failed to put file"); */
1275 /* ret = -1; */
1276 /* goto out; */
1277 /* } */
1278
1279 /* *new_id = g_strdup(hex); */
1280 /* snprintf(buf, SEAF_PATH_MAX, "Added \"%s\"", file_name); */
1281 /* if (gen_new_commit (repo_id, head_commit, root_id, */
1282 /* user, buf, NULL, error) < 0) */
1283 /* ret = -1; */
1284
1285 /* out: */
1286 /* if (repo) */
1287 /* seaf_repo_unref (repo); */
1288 /* if (head_commit) */
1289 /* seaf_commit_unref(head_commit); */
1290 /* string_list_free (blockids); */
1291 /* string_list_free (paths); */
1292 /* seaf_dirent_free (new_dent); */
1293 /* g_free (root_id); */
1294 /* g_free (canon_path); */
1295
1296 /* if (ret == 0) */
1297 /* update_repo_size(repo_id); */
1298
1299 /* return ret; */
1300 /* } */
1301
1302 int
seaf_repo_manager_post_blocks(SeafRepoManager * mgr,const char * repo_id,const char * blockids_json,const char * paths_json,const char * user,GError ** error)1303 seaf_repo_manager_post_blocks (SeafRepoManager *mgr,
1304 const char *repo_id,
1305 const char *blockids_json,
1306 const char *paths_json,
1307 const char *user,
1308 GError **error)
1309 {
1310 SeafRepo *repo = NULL;
1311 GList *blockids = NULL, *paths = NULL, *ptr;
1312 int ret = 0;
1313
1314 blockids = json_to_file_list (blockids_json);
1315 paths = json_to_file_list (paths_json);
1316 if (g_list_length(blockids) != g_list_length(paths)) {
1317 seaf_warning ("[post-blks] Invalid blockids or paths.\n");
1318 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, "Invalid files");
1319 ret = -1;
1320 goto out;
1321 }
1322
1323 for (ptr = paths; ptr; ptr = ptr->next) {
1324 char *temp_file_path = ptr->data;
1325 if (g_access (temp_file_path, R_OK) != 0) {
1326 seaf_warning ("[post-blks] File block %s doesn't exist or not readable.\n",
1327 temp_file_path);
1328 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS,
1329 "Invalid input file");
1330 ret = -1;
1331 goto out;
1332 }
1333 }
1334
1335 GET_REPO_OR_FAIL(repo, repo_id);
1336
1337 /* Write blocks. */
1338 if (seaf_fs_manager_index_raw_blocks (seaf->fs_mgr,
1339 repo->store_id,
1340 repo->version,
1341 paths,
1342 blockids) < 0) {
1343 seaf_warning ("Failed to index file blocks.\n");
1344 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
1345 "Failed to index blocks");
1346 ret = -1;
1347 goto out;
1348 }
1349
1350 out:
1351 if (repo)
1352 seaf_repo_unref (repo);
1353 string_list_free (blockids);
1354 string_list_free (paths);
1355
1356 if (ret == 0)
1357 update_repo_size(repo_id);
1358
1359 return ret;
1360 }
1361
1362 static int
check_quota_before_commit_blocks(const char * store_id,int version,GList * blockids)1363 check_quota_before_commit_blocks (const char *store_id,
1364 int version,
1365 GList *blockids)
1366 {
1367 GList *ptr;
1368 char *blockid;
1369 gint64 total_size = 0;
1370 BlockMetadata *bmd;
1371
1372 for (ptr = blockids; ptr; ptr = ptr->next) {
1373 blockid = ptr->data;
1374 bmd = seaf_block_manager_stat_block (seaf->block_mgr, store_id, version, blockid);
1375 if (!bmd) {
1376 seaf_warning ("Failed to stat block %s in store %s.\n",
1377 blockid, store_id);
1378 return -1;
1379 }
1380
1381 total_size += (gint64)bmd->size;
1382 g_free (bmd);
1383 }
1384
1385 return seaf_quota_manager_check_quota_with_delta (seaf->quota_mgr, store_id, total_size);
1386 }
1387
1388 int
seaf_repo_manager_commit_file_blocks(SeafRepoManager * mgr,const char * repo_id,const char * parent_dir,const char * file_name,const char * blockids_json,const char * user,gint64 file_size,int replace_existed,char ** new_id,GError ** error)1389 seaf_repo_manager_commit_file_blocks (SeafRepoManager *mgr,
1390 const char *repo_id,
1391 const char *parent_dir,
1392 const char *file_name,
1393 const char *blockids_json,
1394 const char *user,
1395 gint64 file_size,
1396 int replace_existed,
1397 char **new_id,
1398 GError **error)
1399 {
1400 SeafRepo *repo = NULL;
1401 SeafCommit *head_commit = NULL;
1402 char *canon_path = NULL;
1403 unsigned char sha1[20];
1404 char buf[SEAF_PATH_MAX];
1405 char *root_id = NULL;
1406 SeafDirent *new_dent = NULL;
1407 GList *blockids = NULL;
1408 char hex[41];
1409 int ret = 0;
1410
1411 blockids = json_to_file_list (blockids_json);
1412
1413 GET_REPO_OR_FAIL(repo, repo_id);
1414 GET_COMMIT_OR_FAIL(head_commit, repo->id, repo->version, repo->head->commit_id);
1415
1416 if (!canon_path)
1417 canon_path = get_canonical_path (parent_dir);
1418
1419 if (should_ignore_file (file_name, NULL)) {
1420 seaf_warning ("[post-blks] Invalid filename %s.\n", file_name);
1421 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS,
1422 "Invalid filename");
1423 ret = -1;
1424 goto out;
1425 }
1426
1427 if (strstr (parent_dir, "//") != NULL) {
1428 seaf_warning ("[post-blks] parent_dir cantains // sequence.\n");
1429 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS,
1430 "Invalid parent dir");
1431 ret = -1;
1432 goto out;
1433 }
1434
1435 int rc = check_quota_before_commit_blocks (repo->store_id, repo->version, blockids);
1436 if (rc != 0) {
1437 g_set_error (error, SEAFILE_DOMAIN, POST_FILE_ERR_QUOTA_FULL,
1438 "Quota full");
1439 ret = -1;
1440 goto out;
1441 }
1442
1443 /* Write blocks. */
1444 if (seaf_fs_manager_index_existed_file_blocks (
1445 seaf->fs_mgr, repo->store_id, repo->version,
1446 blockids, sha1, file_size) < 0) {
1447 seaf_warning ("Failed to index existed file blocks.\n");
1448 g_set_error (error, SEAFILE_DOMAIN, POST_FILE_ERR_BLOCK_MISSING,
1449 "Failed to index file blocks");
1450 ret = -1;
1451 goto out;
1452 }
1453
1454 rawdata_to_hex(sha1, hex, 20);
1455 new_dent = seaf_dirent_new (dir_version_from_repo_version(repo->version),
1456 hex, STD_FILE_MODE, file_name,
1457 (gint64)time(NULL), user, file_size);
1458
1459 root_id = do_post_file_replace (repo, head_commit->root_id,
1460 canon_path, replace_existed, new_dent);
1461 if (!root_id) {
1462 seaf_warning ("[post-blks] Failed to post file to %s in repo %s.\n",
1463 canon_path, repo->id);
1464 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
1465 "Failed to put file");
1466 ret = -1;
1467 goto out;
1468 }
1469
1470 *new_id = g_strdup(hex);
1471 snprintf(buf, SEAF_PATH_MAX, "Added \"%s\"", file_name);
1472 if (gen_new_commit (repo_id, head_commit, root_id,
1473 user, buf, NULL, error) < 0)
1474 ret = -1;
1475
1476 out:
1477 if (repo)
1478 seaf_repo_unref (repo);
1479 if (head_commit)
1480 seaf_commit_unref(head_commit);
1481 string_list_free (blockids);
1482 seaf_dirent_free (new_dent);
1483 g_free (root_id);
1484 g_free (canon_path);
1485
1486 if (ret == 0)
1487 update_repo_size(repo_id);
1488
1489 return ret;
1490 }
1491
1492 static char *
del_file_recursive(SeafRepo * repo,const char * dir_id,const char * to_path,const char * filename,int * mode,int * p_deleted_num,char ** desc_file)1493 del_file_recursive(SeafRepo *repo,
1494 const char *dir_id,
1495 const char *to_path,
1496 const char *filename,
1497 int *mode, int *p_deleted_num, char **desc_file)
1498 {
1499 SeafDir *olddir, *newdir;
1500 SeafDirent *dent;
1501 GList *ptr;
1502 char *to_path_dup = NULL;
1503 char *remain = NULL;
1504 char *slash;
1505 char *id = NULL;
1506 char *ret = NULL;
1507 int deleted_num = 0;
1508
1509 olddir = seaf_fs_manager_get_seafdir_sorted(seaf->fs_mgr,
1510 repo->store_id, repo->version,
1511 dir_id);
1512 if (!olddir)
1513 return NULL;
1514
1515 /* we reach the target dir. Remove the given entry from it. */
1516 if (*to_path == '\0') {
1517 SeafDirent *old, *new;
1518 GList *newentries = NULL, *p;
1519
1520 if (strchr(filename, '\t')) {
1521 char **file_names = g_strsplit (filename, "\t", -1);
1522 int file_num = g_strv_length (file_names);
1523 int i, found_flag;
1524
1525 for (p = olddir->entries; p != NULL; p = p->next) {
1526 found_flag = 0;
1527 old = p->data;
1528 for (i = 0; i < file_num; i++) {
1529 if (strcmp(old->name, file_names[i]) == 0) {
1530 found_flag = 1;
1531 deleted_num++;
1532 if (mode)
1533 *mode = old->mode;
1534 if (desc_file && *desc_file==NULL)
1535 *desc_file = g_strdup(old->name);
1536 break;
1537 }
1538 }
1539 if (!found_flag) {
1540 new = seaf_dirent_dup (old);
1541 newentries = g_list_prepend (newentries, new);
1542 }
1543 }
1544 g_strfreev (file_names);
1545 } else {
1546 for (p = olddir->entries; p != NULL; p = p->next) {
1547 old = p->data;
1548 if (strcmp(old->name, filename) != 0) {
1549 new = seaf_dirent_dup (old);
1550 newentries = g_list_prepend (newentries, new);
1551 } else {
1552 deleted_num++;
1553 if (mode)
1554 *mode = old->mode;
1555 if (desc_file && *desc_file==NULL)
1556 *desc_file = g_strdup(old->name);
1557 }
1558 }
1559 }
1560
1561 if (deleted_num == 0) {
1562 ret = g_strdup(olddir->dir_id);
1563 if (newentries)
1564 g_list_free_full (newentries, (GDestroyNotify)seaf_dirent_free);
1565 goto out;
1566 }
1567
1568 newentries = g_list_reverse (newentries);
1569
1570 newdir = seaf_dir_new(NULL, newentries,
1571 dir_version_from_repo_version(repo->version));
1572 if (seaf_dir_save(seaf->fs_mgr, repo->store_id, repo->version, newdir) == 0)
1573 ret = g_strdup(newdir->dir_id);
1574 seaf_dir_free(newdir);
1575 goto out;
1576 }
1577
1578 to_path_dup = g_strdup (to_path);
1579 slash = strchr (to_path_dup, '/');
1580
1581 if (!slash) {
1582 remain = to_path_dup + strlen(to_path_dup);
1583 } else {
1584 *slash = '\0';
1585 remain = slash + 1;
1586 }
1587
1588 for (ptr = olddir->entries; ptr; ptr = ptr->next) {
1589 dent = (SeafDirent *)ptr->data;
1590
1591 if (strcmp(dent->name, to_path_dup) != 0)
1592 continue;
1593
1594 id = del_file_recursive(repo, dent->id, remain, filename,
1595 mode, &deleted_num, desc_file);
1596 if (id != NULL && deleted_num > 0) {
1597 memcpy(dent->id, id, 40);
1598 dent->id[40] = '\0';
1599 if (repo->version > 0)
1600 dent->mtime = (guint64)time(NULL);
1601 }
1602 break;
1603 }
1604 if (id != NULL) {
1605 if (deleted_num == 0) {
1606 ret = g_strdup(olddir->dir_id);
1607 } else {
1608 /* Create a new SeafDir. */
1609 GList *new_entries;
1610
1611 new_entries = dup_seafdir_entries (olddir->entries);
1612 newdir = seaf_dir_new (NULL, new_entries,
1613 dir_version_from_repo_version(repo->version));
1614 if (seaf_dir_save (seaf->fs_mgr, repo->store_id, repo->version, newdir) == 0)
1615 ret = g_strdup (newdir->dir_id);
1616 seaf_dir_free (newdir);
1617 }
1618 }
1619
1620 out:
1621 if (p_deleted_num)
1622 *p_deleted_num = deleted_num;
1623
1624 g_free (to_path_dup);
1625 g_free (id);
1626 seaf_dir_free(olddir);
1627 return ret;
1628 }
1629
1630 static char *
do_del_file(SeafRepo * repo,const char * root_id,const char * parent_dir,const char * file_name,int * mode,int * deleted_num,char ** desc_file)1631 do_del_file(SeafRepo *repo,
1632 const char *root_id,
1633 const char *parent_dir,
1634 const char *file_name,
1635 int *mode, int *deleted_num, char **desc_file)
1636 {
1637 /* if parent_dir is a absolutely path, we will remove the first '/' */
1638 if (*parent_dir == '/')
1639 parent_dir = parent_dir + 1;
1640
1641 return del_file_recursive(repo, root_id, parent_dir, file_name,
1642 mode, deleted_num, desc_file);
1643 }
1644
1645 int
seaf_repo_manager_del_file(SeafRepoManager * mgr,const char * repo_id,const char * parent_dir,const char * file_name,const char * user,GError ** error)1646 seaf_repo_manager_del_file (SeafRepoManager *mgr,
1647 const char *repo_id,
1648 const char *parent_dir,
1649 const char *file_name,
1650 const char *user,
1651 GError **error)
1652 {
1653 SeafRepo *repo = NULL;
1654 SeafCommit *head_commit = NULL;
1655 SeafDir *dir = NULL;
1656 char *canon_path = NULL;
1657 char buf[SEAF_PATH_MAX];
1658 char *root_id = NULL;
1659 char *desc_file = NULL;
1660 int mode = 0;
1661 int ret = 0;
1662 int deleted_num = 0;
1663
1664 GET_REPO_OR_FAIL(repo, repo_id);
1665 GET_COMMIT_OR_FAIL(head_commit, repo->id, repo->version, repo->head->commit_id);
1666
1667 if (!canon_path)
1668 canon_path = get_canonical_path (parent_dir);
1669
1670 dir = seaf_fs_manager_get_seafdir_by_path (seaf->fs_mgr,
1671 repo->store_id, repo->version,
1672 head_commit->root_id, canon_path, NULL);
1673 if (!dir) {
1674 seaf_warning ("parent_dir %s doesn't exist in repo %s.\n",
1675 canon_path, repo->store_id);
1676 ret = -1;
1677 goto out;
1678 }
1679
1680 root_id = do_del_file (repo,
1681 head_commit->root_id, canon_path, file_name, &mode,
1682 &deleted_num, &desc_file);
1683 if (!root_id) {
1684 seaf_warning ("[del file] Failed to del file from %s in repo %s.\n",
1685 canon_path, repo->id);
1686 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
1687 "Failed to del file");
1688 ret = -1;
1689 goto out;
1690 }
1691 if (deleted_num == 0) {
1692 goto out;
1693 }
1694
1695 /* Commit. */
1696 if (deleted_num > 1) {
1697 snprintf(buf, SEAF_PATH_MAX, "Deleted \"%s\" and %d more files",
1698 desc_file, deleted_num - 1);
1699 } else if (S_ISDIR(mode)) {
1700 snprintf(buf, SEAF_PATH_MAX, "Removed directory \"%s\"", desc_file);
1701 } else {
1702 snprintf(buf, SEAF_PATH_MAX, "Deleted \"%s\"", desc_file);
1703 }
1704
1705 if (gen_new_commit (repo_id, head_commit, root_id,
1706 user, buf, NULL, error) < 0) {
1707 ret = -1;
1708 goto out;
1709 }
1710
1711 seaf_repo_manager_merge_virtual_repo (mgr, repo_id, NULL);
1712
1713 out:
1714 if (repo)
1715 seaf_repo_unref (repo);
1716 if (head_commit)
1717 seaf_commit_unref(head_commit);
1718 if (dir)
1719 seaf_dir_free (dir);
1720 g_free (root_id);
1721 g_free (canon_path);
1722 g_free (desc_file);
1723
1724 if (ret == 0) {
1725 update_repo_size (repo_id);
1726 }
1727
1728 return ret;
1729 }
1730
1731 static SeafDirent *
get_dirent_by_path(SeafRepo * repo,const char * root_id,const char * path,const char * file_name,GError ** error)1732 get_dirent_by_path (SeafRepo *repo,
1733 const char *root_id,
1734 const char *path,
1735 const char *file_name,
1736 GError **error)
1737 {
1738 SeafCommit *head_commit = NULL;
1739 SeafDirent *dent = NULL;
1740 SeafDir *dir = NULL;
1741
1742 if (!root_id) {
1743 head_commit = seaf_commit_manager_get_commit(seaf->commit_mgr,
1744 repo->id, repo->version,
1745 repo->head->commit_id);
1746 if (!head_commit) {
1747 seaf_warning ("commit %s:%s doesn't exist.\n",
1748 repo->id, repo->head->commit_id);
1749 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, "Invalid commit");
1750 goto out;
1751 }
1752 root_id = head_commit->root_id;
1753 }
1754
1755 dir = seaf_fs_manager_get_seafdir_by_path (seaf->fs_mgr,
1756 repo->store_id, repo->version,
1757 root_id,
1758 path, NULL);
1759 if (!dir) {
1760 seaf_warning ("dir %s doesn't exist in repo %s.\n", path, repo->id);
1761 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, "Invalid dir");
1762 goto out;
1763 }
1764
1765 GList *p;
1766 for (p = dir->entries; p; p = p->next) {
1767 SeafDirent *d = p->data;
1768 int r = strcmp (d->name, file_name);
1769 if (r == 0) {
1770 dent = seaf_dirent_dup(d);
1771 break;
1772 }
1773 }
1774
1775 if (!dent && error && !(*error)) {
1776 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS,
1777 "failed to get dirent");
1778 }
1779
1780 out:
1781 if (head_commit)
1782 seaf_commit_unref (head_commit);
1783 if (dir)
1784 seaf_dir_free (dir);
1785
1786 return dent;
1787 }
1788
1789 static int
put_dirent_and_commit(SeafRepo * repo,const char * path,SeafDirent * dents[],int n_dents,int replace,const char * user,GError ** error)1790 put_dirent_and_commit (SeafRepo *repo,
1791 const char *path,
1792 SeafDirent *dents[],
1793 int n_dents,
1794 int replace,
1795 const char *user,
1796 GError **error)
1797 {
1798 SeafCommit *head_commit = NULL;
1799 char *root_id = NULL;
1800 char buf[SEAF_PATH_MAX];
1801 int ret = 0, i = 0;
1802
1803 GET_COMMIT_OR_FAIL(head_commit, repo->id, repo->version, repo->head->commit_id);
1804
1805 root_id = head_commit->root_id;
1806
1807 GList *dent_list = NULL;
1808 GList *name_list = NULL;
1809 for (i = 0; i < n_dents; i++)
1810 dent_list = g_list_append (dent_list, dents[i]);
1811
1812 if (*path == '/')
1813 path = path + 1;
1814 root_id = post_multi_files_recursive (repo, root_id, path, dent_list, user,
1815 replace, &name_list);
1816 g_list_free (dent_list);
1817 g_list_free_full (name_list, (GDestroyNotify)g_free);
1818
1819 if (!root_id) {
1820 if (n_dents > 1)
1821 seaf_warning ("[cp file] Failed to cp %s and other %d files to %s in repo %s.\n",
1822 dents[0]->name, n_dents - 1, path, repo->id);
1823 else
1824 seaf_warning ("[cp file] Failed to cp %s to %s in repo %s.\n",
1825 dents[0]->name, path, repo->id);
1826 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
1827 "Failed to cp file");
1828 ret = -1;
1829 goto out;
1830 }
1831
1832 /* Commit. */
1833 if (n_dents > 1) {
1834 snprintf(buf, sizeof(buf), "Added \"%s\" and %d more files",
1835 dents[0]->name, n_dents - 1);
1836 } else if (S_ISDIR(dents[0]->mode)) {
1837 snprintf(buf, sizeof(buf), "Added directory \"%s\"", dents[0]->name);
1838 } else {
1839 snprintf(buf, sizeof(buf), "Added \"%s\"", dents[0]->name);
1840 }
1841
1842 if (gen_new_commit (repo->id, head_commit, root_id,
1843 user, buf, NULL, error) < 0)
1844 ret = -1;
1845
1846 out:
1847 if (head_commit)
1848 seaf_commit_unref (head_commit);
1849 if (root_id)
1850 g_free (root_id);
1851
1852 return ret;
1853 }
1854
1855 static char *
copy_seafile(SeafRepo * src_repo,SeafRepo * dst_repo,const char * file_id,CopyTask * task,guint64 * size)1856 copy_seafile (SeafRepo *src_repo, SeafRepo *dst_repo, const char *file_id,
1857 CopyTask *task, guint64 *size)
1858 {
1859 Seafile *file;
1860
1861 file = seaf_fs_manager_get_seafile (seaf->fs_mgr,
1862 src_repo->store_id, src_repo->version,
1863 file_id);
1864 if (!file) {
1865 seaf_warning ("Failed to get file object %s from repo %s.\n",
1866 file_id, src_repo->id);
1867 return NULL;
1868 }
1869
1870 /* We may be copying from v0 repo to v1 repo or vise versa. */
1871 file->version = seafile_version_from_repo_version(dst_repo->version);
1872
1873 if (seafile_save (seaf->fs_mgr,
1874 dst_repo->store_id,
1875 dst_repo->version,
1876 file) < 0) {
1877 seaf_warning ("Failed to copy file object %s from repo %s to %s.\n",
1878 file_id, src_repo->id, dst_repo->id);
1879 seafile_unref (file);
1880 return NULL;
1881 }
1882
1883 int i;
1884 char *block_id;
1885 for (i = 0; i < file->n_blocks; ++i) {
1886 /* Check cancel before copying a block. */
1887 if (task && g_atomic_int_get (&task->canceled)) {
1888 seafile_unref (file);
1889 return NULL;
1890 }
1891
1892 block_id = file->blk_sha1s[i];
1893 if (seaf_block_manager_copy_block (seaf->block_mgr,
1894 src_repo->store_id, src_repo->version,
1895 dst_repo->store_id, dst_repo->version,
1896 block_id) < 0) {
1897 seaf_warning ("Failed to copy block %s from repo %s to %s.\n",
1898 block_id, src_repo->id, dst_repo->id);
1899 seafile_unref (file);
1900 return NULL;
1901 }
1902 }
1903
1904 if (task)
1905 ++(task->done);
1906
1907 *size = file->file_size;
1908 char *ret = g_strdup(file->file_id);
1909
1910 seafile_unref (file);
1911 return ret;
1912 }
1913
1914 static char *
copy_recursive(SeafRepo * src_repo,SeafRepo * dst_repo,const char * obj_id,guint32 mode,const char * modifier,CopyTask * task,guint64 * size)1915 copy_recursive (SeafRepo *src_repo, SeafRepo *dst_repo,
1916 const char *obj_id, guint32 mode, const char *modifier,
1917 CopyTask *task, guint64 *size)
1918 {
1919 if (S_ISREG(mode)) {
1920 return copy_seafile (src_repo, dst_repo, obj_id, task, size);
1921 } else if (S_ISDIR(mode)) {
1922 SeafDir *src_dir = NULL, *dst_dir = NULL;
1923 GList *dst_ents = NULL, *ptr;
1924 char *new_id = NULL;
1925 SeafDirent *dent, *new_dent = NULL;
1926
1927 src_dir = seaf_fs_manager_get_seafdir (seaf->fs_mgr,
1928 src_repo->store_id,
1929 src_repo->version,
1930 obj_id);
1931 if (!src_dir) {
1932 seaf_warning ("Seafdir %s doesn't exist in repo %s.\n",
1933 obj_id, src_repo->id);
1934 return NULL;
1935 }
1936
1937 for (ptr = src_dir->entries; ptr; ptr = ptr->next) {
1938 dent = ptr->data;
1939
1940 guint64 new_size = 0;
1941 new_id = copy_recursive (src_repo, dst_repo,
1942 dent->id, dent->mode, modifier, task, &new_size);
1943 if (!new_id) {
1944 seaf_dir_free (src_dir);
1945 return NULL;
1946 }
1947
1948 new_dent = seaf_dirent_new (dir_version_from_repo_version(dst_repo->version),
1949 new_id, dent->mode, dent->name,
1950 dent->mtime, modifier, new_size);
1951 dst_ents = g_list_prepend (dst_ents, new_dent);
1952 g_free (new_id);
1953 }
1954 dst_ents = g_list_reverse (dst_ents);
1955
1956 seaf_dir_free (src_dir);
1957
1958 dst_dir = seaf_dir_new (NULL, dst_ents,
1959 dir_version_from_repo_version(dst_repo->version));
1960 if (seaf_dir_save (seaf->fs_mgr,
1961 dst_repo->store_id, dst_repo->version,
1962 dst_dir) < 0) {
1963 seaf_warning ("Failed to save new dir.\n");
1964 seaf_dir_free (dst_dir);
1965 return NULL;
1966 }
1967
1968 char *ret = g_strdup(dst_dir->dir_id);
1969 *size = 0;
1970 seaf_dir_free (dst_dir);
1971 return ret;
1972 }
1973
1974 return NULL;
1975 }
1976
1977 static GHashTable *
get_sub_dirents_hash_map(SeafRepo * repo,const char * parent_dir)1978 get_sub_dirents_hash_map(SeafRepo *repo, const char *parent_dir)
1979 {
1980 GError *error;
1981 GList *p;
1982 SeafDir *dir = seaf_fs_manager_get_seafdir_by_path (seaf->fs_mgr, repo->store_id,
1983 repo->version, repo->root_id, parent_dir, &error);
1984 if (!dir) {
1985 if (error) {
1986 seaf_warning ("Failed to get dir %s repo %.8s: %s.\n",
1987 parent_dir, repo->store_id, error->message);
1988 g_clear_error(&error);
1989 } else {
1990 seaf_warning ("dir %s doesn't exist in repo %.8s.\n",
1991 parent_dir, repo->store_id);
1992 }
1993 return NULL;
1994 }
1995
1996 GHashTable *dirent_hash = g_hash_table_new_full(g_str_hash,
1997 g_str_equal,
1998 g_free,
1999 (GDestroyNotify)seaf_dirent_free);
2000 for (p = dir->entries; p; p = p->next) {
2001 SeafDirent *d = p->data;
2002 g_hash_table_insert(dirent_hash, g_strdup(d->name), d);
2003 }
2004
2005 g_list_free (dir->entries);
2006 g_free (dir->ondisk);
2007 g_free(dir);
2008
2009 return dirent_hash;
2010 }
2011
2012 static void
set_failed_reason(char ** failed_reason,char * err_str)2013 set_failed_reason (char **failed_reason, char *err_str)
2014 {
2015 *failed_reason = g_strdup (err_str);
2016 }
2017
2018 static int
cross_repo_copy(const char * src_repo_id,const char * src_path,const char * src_filename,const char * dst_repo_id,const char * dst_path,const char * dst_filename,int replace,const char * modifier,CopyTask * task)2019 cross_repo_copy (const char *src_repo_id,
2020 const char *src_path,
2021 const char *src_filename,
2022 const char *dst_repo_id,
2023 const char *dst_path,
2024 const char *dst_filename,
2025 int replace,
2026 const char *modifier,
2027 CopyTask *task)
2028 {
2029 SeafRepo *src_repo = NULL, *dst_repo = NULL;
2030 SeafDirent *src_dent = NULL, *dst_dent = NULL;
2031 SeafDirent **src_dents = NULL, **dst_dents = NULL;
2032 char **src_names = NULL, **dst_names = NULL;
2033 char *new_id = NULL;
2034 guint64 new_size = 0;
2035 int ret = 0, i = 0;
2036 int file_num = 1;
2037 GHashTable *dirent_hash = NULL;
2038 gint64 total_size_all = 0;
2039 char *err_str = COPY_ERR_INTERNAL;
2040 int check_quota_ret;
2041
2042 src_repo = seaf_repo_manager_get_repo (seaf->repo_mgr, src_repo_id);
2043 if (!src_repo) {
2044 err_str = COPY_ERR_INTERNAL;
2045 ret = -1;
2046 seaf_warning ("Failed to get source repo.\n");
2047 goto out;
2048 }
2049
2050 dst_repo = seaf_repo_manager_get_repo (seaf->repo_mgr, dst_repo_id);
2051 if (!dst_repo) {
2052 err_str = COPY_ERR_INTERNAL;
2053 ret = -1;
2054 seaf_warning ("Failed to get destination repo.\n");
2055 goto out;
2056 }
2057
2058 /* get src dirents */
2059 if (strchr(src_filename, '\t') && strchr(dst_filename, '\t')) {
2060 src_names = g_strsplit (src_filename, "\t", -1);
2061 dst_names = g_strsplit (dst_filename, "\t", -1);
2062 file_num = g_strv_length (src_names);
2063
2064 src_dents = g_new0 (SeafDirent *, file_num);
2065 dst_dents = g_new0 (SeafDirent *, file_num);
2066
2067 dirent_hash = get_sub_dirents_hash_map (src_repo, src_path);
2068 if (!dirent_hash) {
2069 err_str = COPY_ERR_INTERNAL;
2070 ret = -1;
2071 goto out;
2072 }
2073
2074 gint64 total_files = -1;
2075 gint64 total_files_all = 0;
2076 /* check filename, size and file count */
2077 for (i = 0; i < file_num; i++) {
2078 if (strcmp(src_names[i], "") == 0) {
2079 err_str = COPY_ERR_BAD_ARG;
2080 ret = -1;
2081 seaf_warning ("[copy files] Bad args: Empty src_filename.\n");
2082 goto out;
2083 }
2084 src_dents[i] = g_hash_table_lookup (dirent_hash, src_names[i]);
2085 if (!src_dents[i]) {
2086 err_str = COPY_ERR_INTERNAL;
2087 ret = -1;
2088 seaf_warning ("[copy files] File %s not Found.\n", src_names[i]);
2089 goto out;
2090 }
2091 if (S_ISDIR(src_dents[i]->mode))
2092 total_files = seaf_fs_manager_count_fs_files (seaf->fs_mgr,
2093 src_repo->store_id,
2094 src_repo->version,
2095 src_dents[i]->id);
2096 else
2097 total_files = 1;
2098 if (total_files < 0) {
2099 err_str = COPY_ERR_INTERNAL;
2100 seaf_warning ("Failed to get file count.\n");
2101 ret = -1;
2102 goto out;
2103 }
2104 total_files_all += total_files;
2105 if (!check_file_count_and_size (src_repo, src_dents[i], total_files_all,
2106 &total_size_all, &err_str)) {
2107 ret = -1;
2108 goto out;
2109 }
2110 }
2111
2112 check_quota_ret = seaf_quota_manager_check_quota_with_delta (seaf->quota_mgr, dst_repo_id, total_size_all);
2113 if (check_quota_ret != 0) {
2114 if (check_quota_ret == -1) {
2115 err_str = COPY_ERR_INTERNAL;
2116 seaf_warning ("Failed to check quota.\n");
2117 } else {
2118 err_str = COPY_ERR_QUOTA_IS_FULL;
2119 }
2120 ret = -1;
2121 goto out;
2122 }
2123
2124 if (task)
2125 task->total = total_files_all;
2126
2127 /* do copy */
2128 for (i = 0; i < file_num; i++) {
2129 new_id = copy_recursive (src_repo, dst_repo,
2130 src_dents[i]->id, src_dents[i]->mode, modifier, task,
2131 &new_size);
2132 if (!new_id) {
2133 err_str = COPY_ERR_INTERNAL;
2134 ret = -1;
2135 seaf_warning ("[copy files] Failed to copy file %s.\n", src_dents[i]->name);
2136 goto out;
2137 }
2138 dst_dents[i] = seaf_dirent_new (dir_version_from_repo_version(dst_repo->version),
2139 new_id, src_dents[i]->mode, dst_names[i],
2140 src_dents[i]->mtime, modifier, new_size);
2141 g_free (new_id);
2142 }
2143
2144 } else {
2145 src_dent = get_dirent_by_path (src_repo, NULL,
2146 src_path, src_filename, NULL);
2147 if (!src_dent) {
2148 err_str = COPY_ERR_INTERNAL;
2149 seaf_warning ("[move file] File %s not Found.\n", src_filename);
2150 ret = -1;
2151 goto out;
2152 }
2153
2154 gint64 total_files = -1;
2155 if (S_ISDIR(src_dent->mode))
2156 total_files = seaf_fs_manager_count_fs_files (seaf->fs_mgr,
2157 src_repo->store_id,
2158 src_repo->version,
2159 src_dent->id);
2160 else
2161 total_files = 1;
2162 if (total_files < 0) {
2163 err_str = COPY_ERR_INTERNAL;
2164 seaf_warning ("Failed to get file count.\n");
2165 ret = -1;
2166 goto out;
2167 }
2168
2169 if (!check_file_count_and_size (src_repo, src_dent, total_files, &total_size_all, &err_str)) {
2170 ret = -1;
2171 goto out;
2172 }
2173
2174 check_quota_ret = seaf_quota_manager_check_quota_with_delta (seaf->quota_mgr, dst_repo_id, total_size_all);
2175 if (check_quota_ret != 0) {
2176 if (check_quota_ret == -1) {
2177 err_str = COPY_ERR_INTERNAL;
2178 seaf_warning ("Failed to check quota.\n");
2179 } else {
2180 err_str = COPY_ERR_QUOTA_IS_FULL;
2181 }
2182 ret = -1;
2183 goto out;
2184 }
2185
2186 if (task)
2187 task->total = total_files;
2188
2189 new_id = copy_recursive (src_repo, dst_repo,
2190 src_dent->id, src_dent->mode, modifier, task,
2191 &new_size);
2192 if (!new_id) {
2193 err_str = COPY_ERR_INTERNAL;
2194 ret = -1;
2195 goto out;
2196 }
2197
2198 dst_dent = seaf_dirent_new (dir_version_from_repo_version(dst_repo->version),
2199 new_id, src_dent->mode, dst_filename,
2200 src_dent->mtime, modifier, new_size);
2201 g_free (new_id);
2202
2203 }
2204 if (put_dirent_and_commit (dst_repo,
2205 dst_path,
2206 file_num > 1 ? dst_dents : &dst_dent,
2207 file_num,
2208 replace,
2209 modifier,
2210 NULL) < 0) {
2211 err_str = COPY_ERR_INTERNAL;
2212 ret = -1;
2213 goto out;
2214 }
2215
2216 if (task)
2217 task->successful = TRUE;
2218
2219 seaf_repo_manager_merge_virtual_repo (seaf->repo_mgr, dst_repo_id, NULL);
2220
2221 out:
2222 if (src_repo)
2223 seaf_repo_unref (src_repo);
2224 if (dst_repo)
2225 seaf_repo_unref (dst_repo);
2226 if (src_dent)
2227 seaf_dirent_free(src_dent);
2228 if (dst_dent)
2229 seaf_dirent_free(dst_dent);
2230 if (dirent_hash)
2231 g_hash_table_unref(dirent_hash);
2232 if (file_num > 1) {
2233 g_free(src_dents);
2234 for (i = 0; i < file_num; i++)
2235 seaf_dirent_free (dst_dents[i]);
2236 g_free (dst_dents);
2237 g_strfreev(src_names);
2238 g_strfreev(dst_names);
2239 }
2240
2241 if (ret == 0) {
2242 update_repo_size (dst_repo_id);
2243 } else {
2244 if (task && !task->canceled) {
2245 task->failed = TRUE;
2246 set_failed_reason (&(task->failed_reason), err_str);
2247 }
2248 }
2249
2250 return ret;
2251 }
2252
2253 static gboolean
is_virtual_repo_and_origin(SeafRepo * repo1,SeafRepo * repo2)2254 is_virtual_repo_and_origin (SeafRepo *repo1, SeafRepo *repo2)
2255 {
2256 if (repo1->virtual_info &&
2257 strcmp (repo1->virtual_info->origin_repo_id, repo2->id) == 0)
2258 return TRUE;
2259 if (repo2->virtual_info &&
2260 strcmp (repo2->virtual_info->origin_repo_id, repo1->id) == 0)
2261 return TRUE;
2262 return FALSE;
2263 }
2264
2265 static gboolean
check_file_count_and_size(SeafRepo * repo,SeafDirent * dent,gint64 total_files,gint64 * total_size_all,char ** err_str)2266 check_file_count_and_size (SeafRepo *repo, SeafDirent *dent, gint64 total_files,
2267 gint64 *total_size_all, char **err_str)
2268 {
2269 gint64 total_file_size = 0;
2270 gint64 size = -1;
2271
2272 if (seaf->copy_mgr->max_files > 0 &&
2273 total_files > seaf->copy_mgr->max_files) {
2274 *err_str = COPY_ERR_TOO_MANY_FILES;
2275 seaf_warning("Failed to copy/move file from repo %.8s: Too many files\n", repo->id);
2276 return FALSE;
2277 }
2278
2279 if (S_ISREG(dent->mode)) {
2280 if (repo->version > 0)
2281 size = dent->size;
2282 else
2283 size = seaf_fs_manager_get_file_size (seaf->fs_mgr,
2284 repo->store_id,
2285 repo->version,
2286 dent->id);
2287 } else {
2288 size = seaf_fs_manager_get_fs_size (seaf->fs_mgr,
2289 repo->store_id,
2290 repo->version,
2291 dent->id);
2292 }
2293
2294 if (size < 0) {
2295 *err_str = COPY_ERR_INTERNAL;
2296 seaf_warning ("Failed to get dir size of %s:%s.\n",
2297 repo->store_id, dent->id);
2298 return FALSE;
2299 }
2300
2301 if (total_size_all) {
2302 *total_size_all += size;
2303 total_file_size = *total_size_all;
2304 }
2305
2306 if (seaf->copy_mgr->max_size > 0) {
2307 if (total_file_size > seaf->copy_mgr->max_size) {
2308 *err_str = COPY_ERR_SIZE_TOO_LARGE;
2309 seaf_warning("Failed to copy/move file from repo %.8s: "
2310 "Folder or file size is too large.\n", repo->id);
2311 return FALSE;
2312 }
2313 }
2314
2315 return TRUE;
2316 }
2317
2318 /**
2319 * Copy a SeafDirent from a SeafDir to another.
2320 *
2321 * 1. When @src_repo and @dst_repo are not the same repo, neither of them
2322 * should be encrypted.
2323 *
2324 * 2. the file being copied must not exist in the dst path of the dst repo.
2325 */
2326 SeafileCopyResult *
seaf_repo_manager_copy_file(SeafRepoManager * mgr,const char * src_repo_id,const char * src_path,const char * src_filename,const char * dst_repo_id,const char * dst_path,const char * dst_filename,const char * user,int need_progress,int synchronous,GError ** error)2327 seaf_repo_manager_copy_file (SeafRepoManager *mgr,
2328 const char *src_repo_id,
2329 const char *src_path,
2330 const char *src_filename,
2331 const char *dst_repo_id,
2332 const char *dst_path,
2333 const char *dst_filename,
2334 const char *user,
2335 int need_progress,
2336 int synchronous,
2337 GError **error)
2338 {
2339 SeafRepo *src_repo = NULL, *dst_repo = NULL;
2340 SeafDirent *src_dent = NULL, *dst_dent = NULL;
2341 char *src_canon_path = NULL, *dst_canon_path = NULL;
2342 SeafCommit *dst_head_commit = NULL;
2343 int ret = 0;
2344 gboolean background = FALSE;
2345 char *task_id = NULL;
2346 SeafileCopyResult *res= NULL;
2347
2348 GET_REPO_OR_FAIL(src_repo, src_repo_id);
2349
2350 if (strcmp(src_repo_id, dst_repo_id) != 0) {
2351 GET_REPO_OR_FAIL(dst_repo, dst_repo_id);
2352
2353 if (src_repo->encrypted || dst_repo->encrypted) {
2354 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS,
2355 "Can't copy files between encrypted repo(s)");
2356 ret = -1;
2357 goto out;
2358 }
2359
2360 } else {
2361 seaf_repo_ref (src_repo);
2362 dst_repo = src_repo;
2363 }
2364
2365 src_canon_path = get_canonical_path (src_path);
2366 dst_canon_path = get_canonical_path (dst_path);
2367
2368 GET_COMMIT_OR_FAIL(dst_head_commit,
2369 dst_repo->id, dst_repo->version,
2370 dst_repo->head->commit_id);
2371
2372 /* FAIL_IF_FILE_EXISTS(dst_repo->store_id, dst_repo->version,
2373 dst_head_commit->root_id, dst_canon_path, dst_filename, NULL); */
2374
2375 if (strcmp (src_repo_id, dst_repo_id) == 0 ||
2376 is_virtual_repo_and_origin (src_repo, dst_repo)) {
2377
2378 /* get src dirent */
2379 src_dent = get_dirent_by_path (src_repo, NULL,
2380 src_canon_path, src_filename, error);
2381 if (!src_dent) {
2382 seaf_warning("[copy file] file %s/%s doesn't exist.\n", src_canon_path, src_filename);
2383 ret = -1;
2384 goto out;
2385 }
2386
2387 gint64 file_size = (src_dent->version > 0) ? src_dent->size : -1;
2388
2389 /* duplicate src dirent with new name */
2390 dst_dent = seaf_dirent_new (dir_version_from_repo_version(dst_repo->version),
2391 src_dent->id, src_dent->mode, dst_filename,
2392 src_dent->mtime, user, file_size);
2393
2394 if (put_dirent_and_commit (dst_repo,
2395 dst_canon_path,
2396 &dst_dent,
2397 1,
2398 0,
2399 user,
2400 error) < 0) {
2401 if (!error)
2402 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
2403 "failed to put dirent");
2404 ret = -1;
2405 goto out;
2406 }
2407
2408 seaf_repo_manager_merge_virtual_repo (mgr, dst_repo_id, NULL);
2409
2410 update_repo_size (dst_repo_id);
2411 } else if (!synchronous) {
2412 background = TRUE;
2413 task_id = seaf_copy_manager_add_task (seaf->copy_mgr,
2414 src_repo_id,
2415 src_canon_path,
2416 src_filename,
2417 dst_repo_id,
2418 dst_canon_path,
2419 dst_filename,
2420 0,
2421 user,
2422 cross_repo_copy,
2423 need_progress);
2424 if (need_progress && !task_id) {
2425 seaf_warning ("Failed to start copy task.\n");
2426 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
2427 "failed to start copy task");
2428 ret = -1;
2429 goto out;
2430 }
2431 } else {
2432 /* Synchronous for cross-repo copy */
2433 if (cross_repo_copy (src_repo_id,
2434 src_canon_path,
2435 src_filename,
2436 dst_repo_id,
2437 dst_canon_path,
2438 dst_filename,
2439 0,
2440 user,
2441 NULL) < 0) {
2442 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
2443 "Failed to move");
2444 ret = -1;
2445 goto out;
2446 }
2447 }
2448
2449 out:
2450 if (src_repo)
2451 seaf_repo_unref (src_repo);
2452 if (dst_repo)
2453 seaf_repo_unref (dst_repo);
2454 if (dst_head_commit)
2455 seaf_commit_unref(dst_head_commit);
2456 if (src_canon_path)
2457 g_free (src_canon_path);
2458 if (dst_canon_path)
2459 g_free (dst_canon_path);
2460 if (src_dent)
2461 seaf_dirent_free(src_dent);
2462 if (dst_dent)
2463 seaf_dirent_free(dst_dent);
2464
2465 if (ret == 0) {
2466 res = seafile_copy_result_new ();
2467 g_object_set (res, "background", background, "task_id", task_id, NULL);
2468 g_free (task_id);
2469 }
2470
2471 return res;
2472 }
2473
2474 SeafileCopyResult *
seaf_repo_manager_copy_multiple_files(SeafRepoManager * mgr,const char * src_repo_id,const char * src_path,const char * src_filenames,const char * dst_repo_id,const char * dst_path,const char * dst_filenames,const char * user,int need_progress,int synchronous,GError ** error)2475 seaf_repo_manager_copy_multiple_files (SeafRepoManager *mgr,
2476 const char *src_repo_id,
2477 const char *src_path,
2478 const char *src_filenames,
2479 const char *dst_repo_id,
2480 const char *dst_path,
2481 const char *dst_filenames,
2482 const char *user,
2483 int need_progress,
2484 int synchronous,
2485 GError **error)
2486 {
2487 SeafRepo *src_repo = NULL, *dst_repo = NULL;
2488 SeafDirent **src_dents = NULL, **dst_dents = NULL;
2489 char *src_canon_path = NULL, *dst_canon_path = NULL;
2490 SeafCommit *dst_head_commit = NULL;
2491 int i = 0, ret = 0;
2492 int file_num = 1;
2493 gint64 *file_sizes = NULL;
2494 gboolean background = FALSE;
2495 char *task_id = NULL;
2496 char **src_names = NULL, **dst_names = NULL;
2497 SeafileCopyResult *res = NULL;
2498 GHashTable *dirent_hash = NULL;
2499
2500 GET_REPO_OR_FAIL(src_repo, src_repo_id);
2501
2502 if (strcmp(src_repo_id, dst_repo_id) != 0) {
2503 GET_REPO_OR_FAIL(dst_repo, dst_repo_id);
2504
2505 if (src_repo->encrypted || dst_repo->encrypted) {
2506 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS,
2507 "Can't copy files between encrypted repo(s)");
2508 ret = -1;
2509 goto out;
2510 }
2511
2512 } else {
2513 seaf_repo_ref (src_repo);
2514 dst_repo = src_repo;
2515 }
2516
2517 src_canon_path = get_canonical_path (src_path);
2518 dst_canon_path = get_canonical_path (dst_path);
2519
2520 GET_COMMIT_OR_FAIL(dst_head_commit,
2521 dst_repo->id, dst_repo->version,
2522 dst_repo->head->commit_id);
2523 /*FAIL_IF_FILE_EXISTS(dst_repo->store_id, dst_repo->version,
2524 dst_head_commit->root_id, dst_canon_path, dst_filename, NULL);*/
2525
2526 if (!strchr(src_filenames, '\t') || !strchr(dst_filenames, '\t')) {
2527 ret = -1;
2528 seaf_warning ("[copy files] Bad args: Split filenames with '\\t'.\n");
2529 goto out;
2530 }
2531 src_names = g_strsplit (src_filenames, "\t", -1);
2532 dst_names = g_strsplit (dst_filenames, "\t", -1);
2533 file_num = g_strv_length (src_names);
2534 int dst_file_num = g_strv_length (dst_names);
2535 if (dst_file_num != file_num) {
2536 ret = -1;
2537 seaf_warning ("[copy files] Bad args.\n");
2538 goto out;
2539 }
2540
2541 /* copy file within the same repo */
2542 if (src_repo == dst_repo ||
2543 is_virtual_repo_and_origin (src_repo, dst_repo)) {
2544
2545 /* get src dirents */
2546 src_dents = g_new0 (SeafDirent *, file_num);
2547 file_sizes = g_new0 (gint64, file_num);
2548
2549 dirent_hash = get_sub_dirents_hash_map (src_repo, src_path);
2550 if (!dirent_hash) {
2551 ret = -1;
2552 goto out;
2553 }
2554
2555 for (i = 0; i < file_num; i++) {
2556 if (strcmp(src_names[i], "") == 0) {
2557 ret = -1;
2558 seaf_warning ("[copy files] Bad args: Empty src_filenames.\n");
2559 goto out;
2560 }
2561 src_dents[i] = g_hash_table_lookup(dirent_hash, src_names[i]);
2562 if (!src_dents[i]) {
2563 ret = -1;
2564 seaf_warning ("[copy files] File %s not Found.\n", src_names[i]);
2565 goto out;
2566 }
2567 file_sizes[i] = (src_dents[i]->version > 0) ? src_dents[i]->size : -1;
2568 }
2569
2570 dst_dents = g_new0 (SeafDirent *, file_num);
2571 for (i = 0; i < file_num; i++) {
2572 if (strcmp(dst_names[i], "") == 0) {
2573 ret = -1;
2574 seaf_warning ("[copy files] Bad args: Empty dst_filenames.\n");
2575 goto out;
2576 }
2577 /* duplicate src dirents with new names */
2578 dst_dents[i] = seaf_dirent_new (dir_version_from_repo_version (dst_repo->version),
2579 src_dents[i]->id, src_dents[i]->mode, dst_names[i],
2580 src_dents[i]->mtime, user, file_sizes[i]);
2581 }
2582 if (put_dirent_and_commit (dst_repo,
2583 dst_canon_path,
2584 dst_dents,
2585 file_num,
2586 0,
2587 user,
2588 error) < 0) {
2589 if (!error)
2590 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
2591 "failed to put dirents");
2592 ret = -1;
2593 goto out;
2594 }
2595
2596 seaf_repo_manager_merge_virtual_repo (mgr, src_repo_id, NULL);
2597
2598 update_repo_size (dst_repo_id);
2599 } else {
2600 /* copy between different repos */
2601 if (!synchronous) {
2602 background = TRUE;
2603
2604 task_id = seaf_copy_manager_add_task (seaf->copy_mgr,
2605 src_repo_id,
2606 src_canon_path,
2607 src_filenames,
2608 dst_repo_id,
2609 dst_canon_path,
2610 dst_filenames,
2611 0,
2612 user,
2613 cross_repo_copy,
2614 need_progress);
2615 if (need_progress && !task_id) {
2616 seaf_warning ("Failed to start copy task.\n");
2617 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
2618 "failed to start copy task");
2619 ret = -1;
2620 goto out;
2621 }
2622 } else {
2623 /* Synchronous for cross-repo copy */
2624 if (cross_repo_copy (src_repo_id,
2625 src_canon_path,
2626 src_filenames,
2627 dst_repo_id,
2628 dst_canon_path,
2629 dst_filenames,
2630 0,
2631 user,
2632 NULL) < 0) {
2633 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
2634 "Failed to move");
2635 ret = -1;
2636 goto out;
2637 }
2638 } // Synchronous copy
2639 } //else diffrent repo
2640
2641 out:
2642 if (src_repo) seaf_repo_unref (src_repo);
2643 if (dst_repo) seaf_repo_unref (dst_repo);
2644
2645 if (dst_head_commit) seaf_commit_unref(dst_head_commit);
2646
2647 if (src_canon_path) g_free (src_canon_path);
2648 if (dst_canon_path) g_free (dst_canon_path);
2649
2650 if (src_names)
2651 g_strfreev (src_names);
2652 if (dst_names)
2653 g_strfreev (dst_names);
2654 if (file_sizes)
2655 g_free (file_sizes);
2656 if (src_dents)
2657 g_free (src_dents);
2658 if (dst_dents) {
2659 for (i = 0; i < file_num; i++)
2660 seaf_dirent_free (dst_dents[i]);
2661 g_free (dst_dents);
2662 }
2663 if (dirent_hash)
2664 g_hash_table_unref(dirent_hash);
2665 if (ret == 0) {
2666 res = seafile_copy_result_new ();
2667 g_object_set (res, "background", background, "task_id", task_id, NULL);
2668 g_free (task_id);
2669 }
2670
2671 return res;
2672 }
2673
2674 static int
move_file_same_repo(const char * repo_id,const char * src_path,SeafDirent * src_dents[],const char * dst_path,SeafDirent * dst_dents[],int file_num,int replace,const char * user,GError ** error)2675 move_file_same_repo (const char *repo_id,
2676 const char *src_path, SeafDirent *src_dents[],
2677 const char *dst_path, SeafDirent *dst_dents[],
2678 int file_num,
2679 int replace,
2680 const char *user,
2681 GError **error)
2682 {
2683 SeafRepo *repo = NULL;
2684 SeafCommit *head_commit = NULL;
2685 char *root_id_after_put = NULL, *root_id = NULL;
2686 char buf[SEAF_PATH_MAX];
2687 int ret = 0, i = 0;
2688 GString *filenames_str = NULL;
2689
2690 GET_REPO_OR_FAIL(repo, repo_id);
2691 GET_COMMIT_OR_FAIL(head_commit, repo->id, repo->version, repo->head->commit_id);
2692
2693 filenames_str = g_string_new ("");
2694 root_id_after_put = head_commit->root_id;
2695
2696 GList *dent_list = NULL;
2697 GList *name_list = NULL;
2698 for (i = 0; i < file_num; i++) {
2699 dent_list = g_list_append (dent_list, dst_dents[i]);
2700 g_string_append_printf (filenames_str, "%s", src_dents[i]->name);
2701 if ((i + 1) < file_num)
2702 g_string_append_printf (filenames_str, "\t");
2703 }
2704 if (*dst_path == '/')
2705 dst_path = dst_path + 1;
2706
2707 root_id_after_put = post_multi_files_recursive (repo, head_commit->root_id, dst_path, dent_list, user,
2708 replace, &name_list);
2709 g_list_free (dent_list);
2710 g_list_free_full (name_list, (GDestroyNotify)g_free);
2711
2712 if (!root_id_after_put) {
2713 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, "move file failed");
2714 ret = -1;
2715 goto out;
2716 }
2717 root_id = do_del_file (repo, root_id_after_put, src_path, filenames_str->str,
2718 NULL, NULL, NULL);
2719
2720 if (!root_id) {
2721 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, "move file failed");
2722 ret = -1;
2723 goto out;
2724 }
2725
2726 /* Commit. */
2727 if (file_num > 1) {
2728 snprintf(buf, SEAF_PATH_MAX, "Moved \"%s\" and %d more files",
2729 src_dents[0]->name,file_num - 1);
2730 } else if (S_ISDIR(src_dents[0]->mode)) {
2731 snprintf(buf, SEAF_PATH_MAX, "Moved directory \"%s\"", src_dents[0]->name);
2732 } else {
2733 snprintf(buf, SEAF_PATH_MAX, "Moved \"%s\"", src_dents[0]->name);
2734 }
2735
2736 if (gen_new_commit (repo_id, head_commit, root_id,
2737 user, buf, NULL, error) < 0)
2738 ret = -1;
2739
2740 out:
2741 if (repo)
2742 seaf_repo_unref (repo);
2743 if (head_commit)
2744 seaf_commit_unref (head_commit);
2745 if (filenames_str)
2746 g_string_free (filenames_str, TRUE);
2747
2748 g_free (root_id_after_put);
2749 g_free (root_id);
2750
2751 return ret;
2752 }
2753
2754 static int
cross_repo_move(const char * src_repo_id,const char * src_path,const char * src_filename,const char * dst_repo_id,const char * dst_path,const char * dst_filename,int replace,const char * modifier,CopyTask * task)2755 cross_repo_move (const char *src_repo_id,
2756 const char *src_path,
2757 const char *src_filename,
2758 const char *dst_repo_id,
2759 const char *dst_path,
2760 const char *dst_filename,
2761 int replace,
2762 const char *modifier,
2763 CopyTask *task)
2764 {
2765 SeafRepo *src_repo = NULL, *dst_repo = NULL;
2766 SeafDirent *src_dent = NULL, *dst_dent = NULL;
2767 SeafDirent **src_dents = NULL, **dst_dents = NULL;
2768 char **src_names = NULL, **dst_names = NULL;
2769 char *new_id = NULL;
2770 guint64 new_size = 0;
2771 int ret = 0, i = 0;
2772 int file_num = 1;
2773 GHashTable *dirent_hash = NULL;
2774 gint64 total_size_all = 0;
2775 char *err_str = COPY_ERR_INTERNAL;
2776 int check_quota_ret;
2777
2778 src_repo = seaf_repo_manager_get_repo (seaf->repo_mgr, src_repo_id);
2779 if (!src_repo) {
2780 err_str = COPY_ERR_INTERNAL;
2781 ret = -1;
2782 seaf_warning ("Failed to get source repo.\n");
2783 goto out;
2784 }
2785
2786 dst_repo = seaf_repo_manager_get_repo (seaf->repo_mgr, dst_repo_id);
2787 if (!dst_repo) {
2788 err_str = COPY_ERR_INTERNAL;
2789 ret = -1;
2790 seaf_warning ("Failed to get destination repo.\n");
2791 goto out;
2792 }
2793
2794 /* get src dirents */
2795 if (strchr(src_filename, '\t') && strchr(dst_filename, '\t')) {
2796 src_names = g_strsplit (src_filename, "\t", -1);
2797 dst_names = g_strsplit (dst_filename, "\t", -1);
2798 file_num = g_strv_length (src_names);
2799
2800 src_dents = g_new0 (SeafDirent *, file_num);
2801 dst_dents = g_new0 (SeafDirent *, file_num);
2802
2803 dirent_hash = get_sub_dirents_hash_map (src_repo, src_path);
2804 if (!dirent_hash) {
2805 err_str = COPY_ERR_INTERNAL;
2806 ret = -1;
2807 goto out;
2808 }
2809
2810 gint64 total_files = -1;
2811 gint64 total_files_all = 0;
2812 /* check filename, size and file count */
2813 for (i = 0; i < file_num; i++) {
2814 if (strcmp(src_names[i], "") == 0) {
2815 err_str = COPY_ERR_BAD_ARG;
2816 ret = -1;
2817 seaf_warning ("[move files] Bad args: Empty src_filename.\n");
2818 goto out;
2819 }
2820 src_dents[i] = g_hash_table_lookup (dirent_hash, src_names[i]);
2821 if (!src_dents[i]) {
2822 err_str = COPY_ERR_INTERNAL;
2823 ret = -1;
2824 seaf_warning ("[move files] File %s not Found.\n", src_names[i]);
2825 goto out;
2826 }
2827 if (S_ISDIR(src_dents[i]->mode))
2828 total_files = seaf_fs_manager_count_fs_files (seaf->fs_mgr,
2829 src_repo->store_id,
2830 src_repo->version,
2831 src_dents[i]->id);
2832 else
2833 total_files = 1;
2834 if (total_files < 0) {
2835 err_str = COPY_ERR_INTERNAL;
2836 seaf_warning ("Failed to get file count.\n");
2837 ret = -1;
2838 goto out;
2839 }
2840 total_files_all += total_files;
2841 if (!check_file_count_and_size (src_repo, src_dents[i], total_files_all,
2842 &total_size_all, &err_str)) {
2843 ret = -1;
2844 goto out;
2845 }
2846 }
2847
2848 check_quota_ret = seaf_quota_manager_check_quota_with_delta (seaf->quota_mgr, dst_repo_id, total_size_all);
2849 if (check_quota_ret != 0) {
2850 if (check_quota_ret == -1) {
2851 err_str = COPY_ERR_INTERNAL;
2852 seaf_warning ("Failed to check quota.\n");
2853 } else {
2854 err_str = COPY_ERR_QUOTA_IS_FULL;
2855 }
2856 ret = -1;
2857 goto out;
2858 }
2859
2860 if (task)
2861 task->total = total_files_all;
2862
2863 /* do copy */
2864 for (i = 0; i < file_num; i++) {
2865 new_id = copy_recursive (src_repo, dst_repo,
2866 src_dents[i]->id, src_dents[i]->mode, modifier, task,
2867 &new_size);
2868 if (!new_id) {
2869 err_str = COPY_ERR_INTERNAL;
2870 ret = -1;
2871 seaf_warning ("[move files] Failed to copy file %s.\n", src_dents[i]->name);
2872 goto out;
2873 }
2874 dst_dents[i] = seaf_dirent_new (dir_version_from_repo_version(dst_repo->version),
2875 new_id, src_dents[i]->mode, dst_names[i],
2876 src_dents[i]->mtime, modifier, new_size);
2877 g_free (new_id);
2878 }
2879 } else {
2880 src_dent = get_dirent_by_path (src_repo, NULL,
2881 src_path, src_filename, NULL);
2882 if (!src_dent) {
2883 err_str = COPY_ERR_INTERNAL;
2884 seaf_warning ("[move file] File %s not Found.\n", src_filename);
2885 ret = -1;
2886 goto out;
2887 }
2888
2889 gint64 total_files = -1;
2890 if (S_ISDIR(src_dent->mode))
2891 total_files = seaf_fs_manager_count_fs_files (seaf->fs_mgr,
2892 src_repo->store_id,
2893 src_repo->version,
2894 src_dent->id);
2895 else
2896 total_files = 1;
2897 if (total_files < 0) {
2898 err_str = COPY_ERR_INTERNAL;
2899 seaf_warning ("Failed to get file count.\n");
2900 ret = -1;
2901 goto out;
2902 }
2903
2904 if (!check_file_count_and_size (src_repo, src_dent, total_files, &total_size_all, &err_str)) {
2905 ret = -1;
2906 goto out;
2907 }
2908
2909 check_quota_ret = seaf_quota_manager_check_quota_with_delta (seaf->quota_mgr, dst_repo_id, total_size_all);
2910 if (check_quota_ret != 0) {
2911 if (check_quota_ret == -1) {
2912 err_str = COPY_ERR_INTERNAL;
2913 seaf_warning ("Failed to check quota.\n");
2914 } else {
2915 err_str = COPY_ERR_QUOTA_IS_FULL;
2916 }
2917 ret = -1;
2918 goto out;
2919 }
2920
2921 if (task)
2922 task->total = total_files;
2923
2924 new_id = copy_recursive (src_repo, dst_repo,
2925 src_dent->id, src_dent->mode, modifier, task,
2926 &new_size);
2927 if (!new_id) {
2928 err_str = COPY_ERR_INTERNAL;
2929 ret = -1;
2930 goto out;
2931 }
2932
2933 dst_dent = seaf_dirent_new (dir_version_from_repo_version(dst_repo->version),
2934 new_id, src_dent->mode, dst_filename,
2935 src_dent->mtime, modifier, new_size);
2936 g_free (new_id);
2937
2938 }
2939
2940 if (put_dirent_and_commit (dst_repo,
2941 dst_path,
2942 file_num > 1 ? dst_dents : &dst_dent,
2943 file_num,
2944 replace,
2945 modifier,
2946 NULL) < 0) {
2947 err_str = COPY_ERR_INTERNAL;
2948 ret = -1;
2949 goto out;
2950 }
2951
2952 seaf_repo_manager_merge_virtual_repo (seaf->repo_mgr, dst_repo_id, NULL);
2953
2954 if (seaf_repo_manager_del_file (seaf->repo_mgr, src_repo_id, src_path,
2955 src_filename, modifier, NULL) < 0) {
2956 err_str = COPY_ERR_INTERNAL;
2957 ret = -1;
2958 goto out;
2959 }
2960
2961 if (task)
2962 task->successful = TRUE;
2963
2964 seaf_repo_manager_merge_virtual_repo (seaf->repo_mgr, src_repo_id, NULL);
2965
2966 out:
2967 if (src_repo)
2968 seaf_repo_unref (src_repo);
2969 if (dst_repo)
2970 seaf_repo_unref (dst_repo);
2971 if (src_dent)
2972 seaf_dirent_free(src_dent);
2973 if (dst_dent)
2974 seaf_dirent_free(dst_dent);
2975 if (dirent_hash)
2976 g_hash_table_unref(dirent_hash);
2977 if (file_num > 1) {
2978 g_free (src_dents);
2979 for (i = 0; i < file_num; i++)
2980 seaf_dirent_free(dst_dents[i]);
2981 g_free (dst_dents);
2982 g_strfreev(src_names);
2983 g_strfreev(dst_names);
2984 }
2985
2986 if (ret == 0) {
2987 update_repo_size (dst_repo_id);
2988 } else {
2989 if (task && !task->canceled) {
2990 task->failed = TRUE;
2991 set_failed_reason (&(task->failed_reason), err_str);
2992 }
2993 }
2994
2995 return ret;
2996 }
2997
2998 SeafileCopyResult *
seaf_repo_manager_move_file(SeafRepoManager * mgr,const char * src_repo_id,const char * src_path,const char * src_filename,const char * dst_repo_id,const char * dst_path,const char * dst_filename,int replace,const char * user,int need_progress,int synchronous,GError ** error)2999 seaf_repo_manager_move_file (SeafRepoManager *mgr,
3000 const char *src_repo_id,
3001 const char *src_path,
3002 const char *src_filename,
3003 const char *dst_repo_id,
3004 const char *dst_path,
3005 const char *dst_filename,
3006 int replace,
3007 const char *user,
3008 int need_progress,
3009 int synchronous,
3010 GError **error)
3011 {
3012 SeafRepo *src_repo = NULL, *dst_repo = NULL;
3013 SeafDirent *src_dent = NULL, *dst_dent = NULL;
3014 char *src_canon_path = NULL, *dst_canon_path = NULL;
3015 SeafCommit *dst_head_commit = NULL;
3016 int ret = 0;
3017 gboolean background = FALSE;
3018 char *task_id = NULL;
3019 SeafileCopyResult *res = NULL;
3020
3021 GET_REPO_OR_FAIL(src_repo, src_repo_id);
3022
3023 if (strcmp(src_repo_id, dst_repo_id) != 0) {
3024 GET_REPO_OR_FAIL(dst_repo, dst_repo_id);
3025
3026 if (src_repo->encrypted || dst_repo->encrypted) {
3027 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS,
3028 "Can't copy files between encrypted repo(s)");
3029 ret = -1;
3030 goto out;
3031 }
3032
3033 } else {
3034 seaf_repo_ref (src_repo);
3035 dst_repo = src_repo;
3036 }
3037
3038 src_canon_path = get_canonical_path (src_path);
3039 dst_canon_path = get_canonical_path (dst_path);
3040 /* first check whether a file with file_name already exists in destination dir */
3041 GET_COMMIT_OR_FAIL(dst_head_commit,
3042 dst_repo->id, dst_repo->version,
3043 dst_repo->head->commit_id);
3044 /*FAIL_IF_FILE_EXISTS(dst_repo->store_id, dst_repo->version,
3045 dst_head_commit->root_id, dst_canon_path, dst_filename, NULL);*/
3046
3047 /* same repo */
3048 if (src_repo == dst_repo ) {
3049 /* get src dirent */
3050 src_dent = get_dirent_by_path (src_repo, NULL,
3051 src_canon_path, src_filename, error);
3052 if (!src_dent) {
3053 seaf_warning("[move file] file %s/%s doesn't exist.\n", src_canon_path, src_filename);
3054 ret = -1;
3055 goto out;
3056 }
3057 gint64 file_size = (src_dent->version > 0) ? src_dent->size : -1;
3058
3059 /* duplicate src dirent with new name */
3060 dst_dent = seaf_dirent_new (dir_version_from_repo_version (dst_repo->version),
3061 src_dent->id, src_dent->mode, dst_filename,
3062 src_dent->mtime, user, file_size);
3063
3064 /* move file within the same repo */
3065 if (move_file_same_repo (src_repo_id,
3066 src_canon_path, &src_dent,
3067 dst_canon_path, &dst_dent,
3068 1, replace, user, error) < 0) {
3069 ret = -1;
3070 goto out;
3071 }
3072
3073 seaf_repo_manager_merge_virtual_repo (mgr, src_repo_id, NULL);
3074
3075 update_repo_size (dst_repo_id);
3076 } else {
3077 /* move between different repos */
3078 /* virtual repo */
3079 if (is_virtual_repo_and_origin (src_repo, dst_repo)) {
3080 /* get src dirent */
3081 src_dent = get_dirent_by_path (src_repo, NULL,
3082 src_canon_path, src_filename, error);
3083 if (!src_dent) {
3084 ret = -1;
3085 goto out;
3086 }
3087 gint64 file_size = (src_dent->version > 0) ? src_dent->size : -1;
3088
3089 /* duplicate src dirent with new name */
3090 dst_dent = seaf_dirent_new (dir_version_from_repo_version(dst_repo->version),
3091 src_dent->id, src_dent->mode, dst_filename,
3092 src_dent->mtime, user, file_size);
3093
3094 /* add this dirent to dst repo */
3095 if (put_dirent_and_commit (dst_repo,
3096 dst_canon_path,
3097 &dst_dent,
3098 1,
3099 replace,
3100 user,
3101 error) < 0) {
3102 if (!error)
3103 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
3104 "failed to put dirent");
3105 ret = -1;
3106 goto out;
3107 }
3108
3109 seaf_repo_manager_merge_virtual_repo (mgr, dst_repo_id, NULL);
3110
3111 if (seaf_repo_manager_del_file (mgr, src_repo_id, src_path,
3112 src_filename, user, error) < 0) {
3113 ret = -1;
3114 goto out;
3115 }
3116
3117 seaf_repo_manager_merge_virtual_repo (mgr, src_repo_id, NULL);
3118
3119 update_repo_size (dst_repo_id);
3120 } else if (!synchronous) {
3121 background = TRUE;
3122
3123 task_id = seaf_copy_manager_add_task (seaf->copy_mgr,
3124 src_repo_id,
3125 src_canon_path,
3126 src_filename,
3127 dst_repo_id,
3128 dst_canon_path,
3129 dst_filename,
3130 replace,
3131 user,
3132 cross_repo_move,
3133 need_progress);
3134 if (need_progress && !task_id) {
3135 seaf_warning ("Failed to start copy task.\n");
3136 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
3137 "failed to start copy task");
3138 ret = -1;
3139 goto out;
3140 }
3141 } else {
3142 /* Synchronous for cross-repo move */
3143 if (cross_repo_move (src_repo_id,
3144 src_canon_path,
3145 src_filename,
3146 dst_repo_id,
3147 dst_canon_path,
3148 dst_filename,
3149 replace,
3150 user,
3151 NULL) < 0) {
3152 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
3153 "Failed to move");
3154 ret = -1;
3155 goto out;
3156 }
3157 }
3158 }
3159
3160 out:
3161 if (src_repo) seaf_repo_unref (src_repo);
3162 if (dst_repo) seaf_repo_unref (dst_repo);
3163
3164 if (dst_head_commit) seaf_commit_unref(dst_head_commit);
3165
3166 if (src_canon_path) g_free (src_canon_path);
3167 if (dst_canon_path) g_free (dst_canon_path);
3168
3169 seaf_dirent_free(src_dent);
3170 seaf_dirent_free(dst_dent);
3171
3172 if (ret == 0) {
3173 res = seafile_copy_result_new ();
3174 g_object_set (res, "background", background, "task_id", task_id, NULL);
3175 g_free (task_id);
3176 }
3177
3178 return res;
3179 }
3180
3181 SeafileCopyResult *
seaf_repo_manager_move_multiple_files(SeafRepoManager * mgr,const char * src_repo_id,const char * src_path,const char * src_filenames,const char * dst_repo_id,const char * dst_path,const char * dst_filenames,int replace,const char * user,int need_progress,int synchronous,GError ** error)3182 seaf_repo_manager_move_multiple_files (SeafRepoManager *mgr,
3183 const char *src_repo_id,
3184 const char *src_path,
3185 const char *src_filenames,
3186 const char *dst_repo_id,
3187 const char *dst_path,
3188 const char *dst_filenames,
3189 int replace,
3190 const char *user,
3191 int need_progress,
3192 int synchronous,
3193 GError **error)
3194 {
3195 SeafRepo *src_repo = NULL, *dst_repo = NULL;
3196 SeafDirent **src_dents = NULL, **dst_dents = NULL;
3197 char *src_canon_path = NULL, *dst_canon_path = NULL;
3198 SeafCommit *dst_head_commit = NULL;
3199 int i = 0, ret = 0;
3200 int file_num = 1;
3201 gint64 *file_sizes = NULL;
3202 gboolean background = FALSE;
3203 char *task_id = NULL;
3204 char **src_names = NULL, **dst_names = NULL;
3205 SeafileCopyResult *res = NULL;
3206 GHashTable *dirent_hash = NULL;
3207
3208 GET_REPO_OR_FAIL(src_repo, src_repo_id);
3209
3210 if (strcmp(src_repo_id, dst_repo_id) != 0) {
3211 GET_REPO_OR_FAIL(dst_repo, dst_repo_id);
3212
3213 if (src_repo->encrypted || dst_repo->encrypted) {
3214 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS,
3215 "Can't copy files between encrypted repo(s)");
3216 ret = -1;
3217 goto out;
3218 }
3219
3220 } else {
3221 seaf_repo_ref (src_repo);
3222 dst_repo = src_repo;
3223 }
3224
3225 src_canon_path = get_canonical_path (src_path);
3226 dst_canon_path = get_canonical_path (dst_path);
3227
3228 GET_COMMIT_OR_FAIL(dst_head_commit,
3229 dst_repo->id, dst_repo->version,
3230 dst_repo->head->commit_id);
3231 /*FAIL_IF_FILE_EXISTS(dst_repo->store_id, dst_repo->version,
3232 dst_head_commit->root_id, dst_canon_path, dst_filename, NULL);*/
3233
3234 if (!strchr(src_filenames, '\t') || !strchr(dst_filenames, '\t')) {
3235 ret = -1;
3236 seaf_warning ("[move files] Bad args: Split filenames with '\\t'.\n");
3237 goto out;
3238 }
3239 src_names = g_strsplit (src_filenames, "\t", -1);
3240 dst_names = g_strsplit (dst_filenames, "\t", -1);
3241 file_num = g_strv_length (src_names);
3242 int dst_file_num = g_strv_length (dst_names);
3243 if (dst_file_num != file_num) {
3244 ret = -1;
3245 seaf_warning ("[move files] Bad args.\n");
3246 goto out;
3247 }
3248
3249 gboolean is_virtual_origin = is_virtual_repo_and_origin (src_repo, dst_repo);
3250 if (src_repo == dst_repo || is_virtual_origin) {
3251 /* get src dirents */
3252
3253 src_dents = g_new0 (SeafDirent *, file_num);
3254 file_sizes = g_new0 (gint64, file_num);
3255
3256 dirent_hash = get_sub_dirents_hash_map (src_repo, src_path);
3257 if (!dirent_hash) {
3258 ret = -1;
3259 goto out;
3260 }
3261
3262 for (i = 0; i < file_num; i++) {
3263 if (strcmp(src_names[i], "") == 0) {
3264 ret = -1;
3265 seaf_warning ("[move files] Bad args: Empty src_filenames.\n");
3266 goto out;
3267 }
3268 src_dents[i] = g_hash_table_lookup(dirent_hash, src_names[i]);
3269 if (!src_dents[i]) {
3270 ret = -1;
3271 seaf_warning ("[move files] File %s not Found.\n", src_names[i]);
3272 goto out;
3273 }
3274 file_sizes[i] = (src_dents[i]->version > 0) ? src_dents[i]->size : -1;
3275 }
3276
3277 dst_dents = g_new0 (SeafDirent *, file_num);
3278 for (i = 0; i < file_num; i++) {
3279 if (strcmp(dst_names[i], "") == 0) {
3280 ret = -1;
3281 seaf_warning ("[move files] Bad args: Empty dst_filenames.\n");
3282 goto out;
3283 }
3284 /* duplicate src dirents with new names */
3285 dst_dents[i] = seaf_dirent_new (dir_version_from_repo_version (dst_repo->version),
3286 src_dents[i]->id, src_dents[i]->mode, dst_names[i],
3287 src_dents[i]->mtime, user, file_sizes[i]);
3288 }
3289 /* move file within the same repo */
3290 if (src_repo == dst_repo) {
3291 if (move_file_same_repo (src_repo_id,
3292 src_canon_path, src_dents,
3293 dst_canon_path, dst_dents,
3294 file_num, replace, user, error) < 0) {
3295 ret = -1;
3296 goto out;
3297 }
3298 } else {
3299 /* move between virtual and origin repo */
3300 if (put_dirent_and_commit (dst_repo,
3301 dst_path,
3302 dst_dents,
3303 file_num,
3304 replace,
3305 user,
3306 NULL) < 0) {
3307 ret = -1;
3308 goto out;
3309 }
3310 seaf_repo_manager_merge_virtual_repo (mgr, dst_repo->id, NULL);
3311
3312 if (seaf_repo_manager_del_file (mgr, src_repo->id, src_path,
3313 src_filenames, user, error) < 0) {
3314 ret = -1;
3315 goto out;
3316 }
3317 }
3318 seaf_repo_manager_merge_virtual_repo (mgr, src_repo_id, NULL);
3319
3320 update_repo_size (dst_repo_id);
3321 } else {
3322 /* move between different repos */
3323 if (!synchronous) {
3324 background = TRUE;
3325
3326 task_id = seaf_copy_manager_add_task (seaf->copy_mgr,
3327 src_repo_id,
3328 src_canon_path,
3329 src_filenames,
3330 dst_repo_id,
3331 dst_canon_path,
3332 dst_filenames,
3333 0,
3334 user,
3335 cross_repo_move,
3336 need_progress);
3337 if (need_progress && !task_id) {
3338 seaf_warning ("Failed to start copy task.\n");
3339 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
3340 "failed to start copy task");
3341 ret = -1;
3342 goto out;
3343 }
3344 } else {
3345 /* Synchronous for cross-repo move */
3346 if (cross_repo_move (src_repo_id,
3347 src_canon_path,
3348 src_filenames,
3349 dst_repo_id,
3350 dst_canon_path,
3351 dst_filenames,
3352 replace,
3353 user,
3354 NULL) < 0) {
3355 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
3356 "Failed to move");
3357 ret = -1;
3358 goto out;
3359 }
3360 } // Synchronous move
3361 } //else diffrent repo
3362
3363 out:
3364 if (src_repo) seaf_repo_unref (src_repo);
3365 if (dst_repo) seaf_repo_unref (dst_repo);
3366
3367 if (dst_head_commit) seaf_commit_unref(dst_head_commit);
3368
3369 if (src_canon_path) g_free (src_canon_path);
3370 if (dst_canon_path) g_free (dst_canon_path);
3371
3372 if (src_names)
3373 g_strfreev (src_names);
3374 if (dst_names)
3375 g_strfreev (dst_names);
3376 if (file_sizes)
3377 g_free (file_sizes);
3378
3379 if (dirent_hash)
3380 g_hash_table_unref(dirent_hash);
3381 if (src_dents)
3382 g_free (src_dents);
3383 if (dst_dents) {
3384 for (i = 0; i < file_num; i++)
3385 seaf_dirent_free (dst_dents[i]);
3386 g_free (dst_dents);
3387 }
3388
3389 if (ret == 0) {
3390 res = seafile_copy_result_new ();
3391 g_object_set (res, "background", background, "task_id", task_id, NULL);
3392 g_free (task_id);
3393 }
3394
3395 return res;
3396 }
3397
3398 int
seaf_repo_manager_mkdir_with_parents(SeafRepoManager * mgr,const char * repo_id,const char * parent_dir,const char * new_dir_path,const char * user,GError ** error)3399 seaf_repo_manager_mkdir_with_parents (SeafRepoManager *mgr,
3400 const char *repo_id,
3401 const char *parent_dir,
3402 const char *new_dir_path,
3403 const char *user,
3404 GError **error)
3405 {
3406 SeafRepo *repo = NULL;
3407 SeafCommit *head_commit = NULL;
3408 char **sub_folders = NULL;
3409 int nfolder;
3410 char buf[SEAF_PATH_MAX];
3411 char *root_id = NULL;
3412 SeafDirent *new_dent = NULL;
3413 char *parent_dir_can = NULL;
3414 char *relative_dir_can = NULL;
3415 char *abs_path = NULL;
3416 int total_path_len;
3417 int sub_folder_len;
3418 GList *uncre_dir_list = NULL;
3419 GList *iter_list = NULL;
3420 char *uncre_dir;
3421 int ret = 0;
3422
3423 if (new_dir_path[0] == '/' || new_dir_path[0] == '\\') {
3424 seaf_warning ("[mkdir with parent] Invalid relative path %s.\n", new_dir_path);
3425 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS,
3426 "Invalid relative path");
3427 return -1;
3428 }
3429
3430 GET_REPO_OR_FAIL(repo, repo_id);
3431 GET_COMMIT_OR_FAIL(head_commit, repo->id, repo->version, repo->head->commit_id);
3432
3433 relative_dir_can = get_canonical_path (new_dir_path);
3434 sub_folders = g_strsplit (relative_dir_can, "/", 0);
3435 nfolder = g_strv_length (sub_folders);
3436
3437 int i = 0;
3438 for (; i < nfolder; ++i) {
3439 if (strcmp (sub_folders[i], "") == 0)
3440 continue;
3441
3442 if (should_ignore_file (sub_folders[i], NULL)) {
3443 seaf_warning ("[post dir] Invalid dir name %s.\n", sub_folders[i]);
3444 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS,
3445 "Invalid dir name");
3446 ret = -1;
3447 goto out;
3448 }
3449 }
3450
3451 if (strcmp (parent_dir, "/") == 0 ||
3452 strcmp (parent_dir, "\\") == 0) {
3453 parent_dir_can = g_strdup ("/");
3454 abs_path = g_strdup_printf ("%s%s", parent_dir_can, relative_dir_can);
3455 } else {
3456 parent_dir_can = get_canonical_path (parent_dir);
3457 abs_path = g_strdup_printf ("%s/%s", parent_dir_can, relative_dir_can);
3458 }
3459 if (!abs_path) {
3460 seaf_warning ("[mkdir with parent] Out of memory.\n");
3461 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_INTERNAL,
3462 "Out of memory");
3463 ret = -1;
3464 goto out;
3465 }
3466 total_path_len = strlen (abs_path);
3467
3468 // from the last, to check the folder exist
3469 i = nfolder - 1;
3470 for (; i >= 0; --i) {
3471 if (strcmp (sub_folders[i], "") == 0)
3472 continue;
3473
3474 sub_folder_len = strlen (sub_folders[i]) + 1;
3475 total_path_len -= sub_folder_len;
3476 memset (abs_path + total_path_len, '\0', sub_folder_len);
3477
3478 if (check_file_exists (repo->store_id, repo->version,
3479 head_commit->root_id, abs_path, sub_folders[i], NULL)) {
3480 // folder exist, skip loop to create unexist subfolder
3481 strcat (abs_path, "/");
3482 strcat (abs_path, sub_folders[i]);
3483 break;
3484 } else {
3485 // folder not exist, cache it to create later
3486 uncre_dir_list = g_list_prepend (uncre_dir_list, sub_folders[i]);
3487 }
3488 }
3489
3490 if (uncre_dir_list) {
3491 // exist parent folder has been found, based on it to create unexist subfolder
3492 char new_root_id[41];
3493 memcpy (new_root_id, head_commit->root_id, 40);
3494 new_root_id[40] = '\0';
3495
3496 for (iter_list = uncre_dir_list; iter_list; iter_list = iter_list->next) {
3497 uncre_dir = iter_list->data;
3498 new_dent = seaf_dirent_new (dir_version_from_repo_version(repo->version),
3499 EMPTY_SHA1, S_IFDIR, uncre_dir,
3500 (gint64)time(NULL), NULL, -1);
3501
3502 root_id = do_post_file (repo,
3503 new_root_id, abs_path, new_dent);
3504 if (!root_id) {
3505 seaf_warning ("[put dir] Failed to put dir.\n");
3506 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
3507 "Failed to put dir");
3508 ret = -1;
3509 seaf_dirent_free (new_dent);
3510 goto out;
3511 }
3512
3513 // the last folder has been created
3514 if (!iter_list->next) {
3515 seaf_dirent_free (new_dent);
3516 break;
3517 }
3518
3519 strcat (abs_path, "/");
3520 strcat (abs_path, uncre_dir);
3521 memcpy (new_root_id, root_id, 40);
3522
3523 seaf_dirent_free (new_dent);
3524 g_free (root_id);
3525 }
3526
3527 /* Commit. */
3528 snprintf(buf, SEAF_PATH_MAX, "Added directory \"%s\"", relative_dir_can);
3529 if (gen_new_commit (repo_id, head_commit, root_id,
3530 user, buf, NULL, error) < 0) {
3531 ret = -1;
3532 g_free (root_id);
3533 goto out;
3534 }
3535
3536 seaf_repo_manager_merge_virtual_repo (mgr, repo_id, NULL);
3537 g_free (root_id);
3538 }
3539
3540 out:
3541 if (repo)
3542 seaf_repo_unref (repo);
3543 if (head_commit)
3544 seaf_commit_unref(head_commit);
3545 if (sub_folders)
3546 g_strfreev (sub_folders);
3547 if (uncre_dir_list)
3548 g_list_free (uncre_dir_list);
3549 if (relative_dir_can)
3550 g_free (relative_dir_can);
3551 if (parent_dir_can)
3552 g_free (parent_dir_can);
3553 if (abs_path)
3554 g_free (abs_path);
3555
3556 return ret;
3557 }
3558
3559 int
seaf_repo_manager_post_dir(SeafRepoManager * mgr,const char * repo_id,const char * parent_dir,const char * new_dir_name,const char * user,GError ** error)3560 seaf_repo_manager_post_dir (SeafRepoManager *mgr,
3561 const char *repo_id,
3562 const char *parent_dir,
3563 const char *new_dir_name,
3564 const char *user,
3565 GError **error)
3566 {
3567 SeafRepo *repo = NULL;
3568 SeafCommit *head_commit = NULL;
3569 char *canon_path = NULL;
3570 char buf[SEAF_PATH_MAX];
3571 char *root_id = NULL;
3572 SeafDirent *new_dent = NULL;
3573 int ret = 0;
3574
3575 GET_REPO_OR_FAIL(repo, repo_id);
3576 GET_COMMIT_OR_FAIL(head_commit, repo->id, repo->version, repo->head->commit_id);
3577
3578 canon_path = get_canonical_path (parent_dir);
3579
3580 if (should_ignore_file (new_dir_name, NULL)) {
3581 seaf_warning ("[post dir] Invalid dir name %s.\n", new_dir_name);
3582 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS,
3583 "Invalid dir name");
3584 ret = -1;
3585 goto out;
3586 }
3587
3588 FAIL_IF_FILE_EXISTS(repo->store_id, repo->version,
3589 head_commit->root_id, canon_path, new_dir_name, NULL);
3590
3591 if (!new_dent) {
3592 new_dent = seaf_dirent_new (dir_version_from_repo_version(repo->version),
3593 EMPTY_SHA1, S_IFDIR, new_dir_name,
3594 (gint64)time(NULL), NULL, -1);
3595 }
3596
3597 root_id = do_post_file (repo,
3598 head_commit->root_id, canon_path, new_dent);
3599 if (!root_id) {
3600 seaf_warning ("[put dir] Failed to put dir %s to %s in repo %s.\n",
3601 new_dir_name, canon_path, repo->id);
3602 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
3603 "Failed to put dir");
3604 ret = -1;
3605 goto out;
3606 }
3607
3608 /* Commit. */
3609 snprintf(buf, SEAF_PATH_MAX, "Added directory \"%s\"", new_dir_name);
3610 if (gen_new_commit (repo_id, head_commit, root_id,
3611 user, buf, NULL, error) < 0) {
3612 ret = -1;
3613 goto out;
3614 }
3615
3616 seaf_repo_manager_merge_virtual_repo (mgr, repo_id, NULL);
3617
3618 out:
3619 if (repo)
3620 seaf_repo_unref (repo);
3621 if (head_commit)
3622 seaf_commit_unref(head_commit);
3623 seaf_dirent_free (new_dent);
3624 g_free (root_id);
3625 g_free (canon_path);
3626
3627 return ret;
3628 }
3629
3630 int
seaf_repo_manager_post_empty_file(SeafRepoManager * mgr,const char * repo_id,const char * parent_dir,const char * new_file_name,const char * user,GError ** error)3631 seaf_repo_manager_post_empty_file (SeafRepoManager *mgr,
3632 const char *repo_id,
3633 const char *parent_dir,
3634 const char *new_file_name,
3635 const char *user,
3636 GError **error)
3637 {
3638 SeafRepo *repo = NULL;
3639 SeafCommit *head_commit = NULL;
3640 char *canon_path = NULL;
3641 char buf[SEAF_PATH_MAX];
3642 char *root_id = NULL;
3643 SeafDirent *new_dent = NULL;
3644 int ret = 0;
3645
3646 GET_REPO_OR_FAIL(repo, repo_id);
3647 GET_COMMIT_OR_FAIL(head_commit, repo->id, repo->version, repo->head->commit_id);
3648
3649 if (!canon_path)
3650 /* no need to call get_canonical_path again when retry */
3651 canon_path = get_canonical_path (parent_dir);
3652
3653 if (should_ignore_file (new_file_name, NULL)) {
3654 seaf_warning ("[post file] Invalid file name %s.\n", new_file_name);
3655 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS,
3656 "Invalid file name");
3657 ret = -1;
3658 goto out;
3659 }
3660
3661 FAIL_IF_FILE_EXISTS(repo->store_id, repo->version,
3662 head_commit->root_id, canon_path, new_file_name, NULL);
3663
3664 if (!new_dent) {
3665 new_dent = seaf_dirent_new (dir_version_from_repo_version(repo->version),
3666 EMPTY_SHA1, STD_FILE_MODE, new_file_name,
3667 (gint64)time(NULL), user, 0);
3668 }
3669
3670 root_id = do_post_file (repo,
3671 head_commit->root_id, canon_path, new_dent);
3672 if (!root_id) {
3673 seaf_warning ("[put dir] Failed to create empty file dir.\n");
3674 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
3675 "Failed to put dir");
3676 ret = -1;
3677 goto out;
3678 }
3679
3680 /* Commit. */
3681 snprintf(buf, SEAF_PATH_MAX, "Added \"%s\"", new_file_name);
3682 if (gen_new_commit (repo_id, head_commit, root_id,
3683 user, buf, NULL, error) < 0) {
3684 ret = -1;
3685 goto out;
3686 }
3687
3688 seaf_repo_manager_merge_virtual_repo (mgr, repo_id, NULL);
3689
3690 update_repo_size (repo_id);
3691
3692 out:
3693 if (repo)
3694 seaf_repo_unref (repo);
3695 if (head_commit)
3696 seaf_commit_unref(head_commit);
3697 seaf_dirent_free (new_dent);
3698 g_free (root_id);
3699 g_free (canon_path);
3700
3701 return ret;
3702 }
3703
3704 static char *
rename_file_recursive(SeafRepo * repo,const char * dir_id,const char * to_path,const char * oldname,const char * newname)3705 rename_file_recursive(SeafRepo *repo,
3706 const char *dir_id,
3707 const char *to_path,
3708 const char *oldname,
3709 const char *newname)
3710 {
3711 SeafDir *olddir, *newdir;
3712 SeafDirent *dent;
3713 GList *ptr;
3714 char *to_path_dup = NULL;
3715 char *remain = NULL;
3716 char *slash;
3717 char *id = NULL;
3718 char *ret = NULL;
3719
3720 olddir = seaf_fs_manager_get_seafdir_sorted(seaf->fs_mgr,
3721 repo->store_id, repo->version,
3722 dir_id);
3723 if (!olddir)
3724 return NULL;
3725
3726 /* we reach the target dir. */
3727 if (*to_path == '\0') {
3728 SeafDirent *old, *newdent = NULL;
3729 GList *newentries = NULL, *p;
3730
3731 /* When renameing, there is a pitfall: we can't simply rename the
3732 * dirent, since the dirents are required to be sorted in descending
3733 * order. We need to copy all old dirents except the target dirent,
3734 * and then rename the target dirent, and then insert the new
3735 * dirent, so that we can maintain the descending order of dirents. */
3736 for (p = olddir->entries; p != NULL; p = p->next) {
3737 old = p->data;
3738 if (strcmp(old->name, oldname) != 0) {
3739 newentries = g_list_prepend (newentries, seaf_dirent_dup(old));
3740 } else {
3741 newdent = seaf_dirent_new (old->version, old->id, old->mode,
3742 newname, old->mtime,
3743 old->modifier, old->size);
3744 }
3745 }
3746
3747 newentries = g_list_reverse (newentries);
3748
3749 if (newdent) {
3750 newentries = g_list_insert_sorted(newentries, newdent, compare_dirents);
3751 }
3752
3753 newdir = seaf_dir_new (NULL, newentries,
3754 dir_version_from_repo_version(repo->version));
3755 if (seaf_dir_save (seaf->fs_mgr, repo->store_id, repo->version, newdir) == 0)
3756 ret = g_strndup (newdir->dir_id, 40);
3757 seaf_dir_free (newdir);
3758
3759 goto out;
3760 }
3761
3762 to_path_dup = g_strdup (to_path);
3763 slash = strchr (to_path_dup, '/');
3764
3765 if (!slash) {
3766 remain = to_path_dup + strlen(to_path_dup);
3767 } else {
3768 *slash = '\0';
3769 remain = slash + 1;
3770 }
3771
3772 for (ptr = olddir->entries; ptr; ptr = ptr->next) {
3773 dent = (SeafDirent *)ptr->data;
3774
3775 if (strcmp(dent->name, to_path_dup) != 0)
3776 continue;
3777
3778 id = rename_file_recursive (repo, dent->id, remain, oldname, newname);
3779 if (id != NULL) {
3780 memcpy(dent->id, id, 40);
3781 dent->id[40] = '\0';
3782 }
3783 break;
3784 }
3785
3786 if (id != NULL) {
3787 /* Create a new SeafDir. */
3788 GList *new_entries;
3789
3790 new_entries = dup_seafdir_entries (olddir->entries);
3791 newdir = seaf_dir_new (NULL, new_entries,
3792 dir_version_from_repo_version(repo->version));
3793 if (seaf_dir_save (seaf->fs_mgr, repo->store_id, repo->version, newdir) == 0)
3794 ret = g_strdup(newdir->dir_id);
3795 seaf_dir_free (newdir);
3796 }
3797
3798 out:
3799 g_free (to_path_dup);
3800 g_free (id);
3801 seaf_dir_free(olddir);
3802 return ret;
3803 }
3804
3805 static char *
do_rename_file(SeafRepo * repo,const char * root_id,const char * parent_dir,const char * oldname,const char * newname)3806 do_rename_file(SeafRepo *repo,
3807 const char *root_id,
3808 const char *parent_dir,
3809 const char *oldname,
3810 const char *newname)
3811 {
3812 /* if parent_dir is a absolutely path, we will remove the first '/' */
3813 if (*parent_dir == '/')
3814 parent_dir = parent_dir + 1;
3815
3816 return rename_file_recursive(repo, root_id, parent_dir, oldname, newname);
3817 }
3818
3819
3820 int
seaf_repo_manager_rename_file(SeafRepoManager * mgr,const char * repo_id,const char * parent_dir,const char * oldname,const char * newname,const char * user,GError ** error)3821 seaf_repo_manager_rename_file (SeafRepoManager *mgr,
3822 const char *repo_id,
3823 const char *parent_dir,
3824 const char *oldname,
3825 const char *newname,
3826 const char *user,
3827 GError **error)
3828 {
3829 SeafRepo *repo = NULL;
3830 SeafCommit *head_commit = NULL;
3831 char *root_id = NULL;
3832 char *canon_path = NULL;
3833 char buf[SEAF_PATH_MAX];
3834 int mode = 0;
3835 int ret = 0;
3836
3837 if (strcmp(oldname, newname) == 0)
3838 return 0;
3839
3840 GET_REPO_OR_FAIL(repo, repo_id);
3841 GET_COMMIT_OR_FAIL(head_commit, repo->id, repo->version, repo->head->commit_id);
3842
3843 if (!canon_path)
3844 canon_path = get_canonical_path (parent_dir);
3845
3846 if (should_ignore_file (newname, NULL)) {
3847 seaf_warning ("[rename file] Invalid filename %s.\n", newname);
3848 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS,
3849 "Invalid filename");
3850 ret = -1;
3851 goto out;
3852 }
3853
3854 FAIL_IF_FILE_NOT_EXISTS(repo->store_id, repo->version,
3855 head_commit->root_id, canon_path, oldname, &mode);
3856 FAIL_IF_FILE_EXISTS(repo->store_id, repo->version,
3857 head_commit->root_id, canon_path, newname, NULL);
3858
3859 root_id = do_rename_file (repo, head_commit->root_id, canon_path,
3860 oldname, newname);
3861 if (!root_id) {
3862 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS,
3863 "faile to rename file %s", oldname);
3864 ret = -1;
3865 goto out;
3866 }
3867
3868 /* Commit. */
3869 if (S_ISDIR(mode)) {
3870 snprintf(buf, SEAF_PATH_MAX, "Renamed directory \"%s\"", oldname);
3871 } else {
3872 snprintf(buf, SEAF_PATH_MAX, "Renamed \"%s\"", oldname);
3873 }
3874
3875 if (gen_new_commit (repo_id, head_commit, root_id,
3876 user, buf, NULL, error) < 0) {
3877 ret = -1;
3878 goto out;
3879 }
3880
3881 seaf_repo_manager_merge_virtual_repo (mgr, repo_id, NULL);
3882
3883 out:
3884 if (repo)
3885 seaf_repo_unref (repo);
3886 if (head_commit)
3887 seaf_commit_unref (head_commit);
3888 g_free (canon_path);
3889 g_free (root_id);
3890
3891 return ret;
3892 }
3893
3894 static char *
put_file_recursive(SeafRepo * repo,const char * dir_id,const char * to_path,SeafDirent * newdent)3895 put_file_recursive(SeafRepo *repo,
3896 const char *dir_id,
3897 const char *to_path,
3898 SeafDirent *newdent)
3899 {
3900 SeafDir *olddir, *newdir;
3901 SeafDirent *dent;
3902 GList *ptr;
3903 char *to_path_dup = NULL;
3904 char *remain = NULL;
3905 char *slash;
3906 char *id = NULL;
3907 char *ret = NULL;
3908
3909 olddir = seaf_fs_manager_get_seafdir_sorted(seaf->fs_mgr,
3910 repo->store_id, repo->version,
3911 dir_id);
3912 if (!olddir)
3913 return NULL;
3914
3915 /* we reach the target dir. Update the target dirent. */
3916 if (*to_path == '\0') {
3917 GList *newentries = NULL, *p;
3918 SeafDirent *dent;
3919
3920 for (p = olddir->entries; p; p = p->next) {
3921 dent = p->data;
3922 if (strcmp(dent->name, newdent->name) == 0) {
3923 newentries = g_list_prepend (newentries, seaf_dirent_dup(newdent));
3924 } else {
3925 newentries = g_list_prepend (newentries, seaf_dirent_dup(dent));
3926 }
3927 }
3928
3929 newentries = g_list_reverse (newentries);
3930 newdir = seaf_dir_new (NULL, newentries,
3931 dir_version_from_repo_version(repo->version));
3932 if (seaf_dir_save (seaf->fs_mgr, repo->store_id, repo->version, newdir) == 0)
3933 ret = g_strdup (newdir->dir_id);
3934 seaf_dir_free (newdir);
3935
3936 goto out;
3937 }
3938
3939 to_path_dup = g_strdup (to_path);
3940 slash = strchr (to_path_dup, '/');
3941
3942 if (!slash) {
3943 remain = to_path_dup + strlen(to_path_dup);
3944 } else {
3945 *slash = '\0';
3946 remain = slash + 1;
3947 }
3948
3949 for (ptr = olddir->entries; ptr; ptr = ptr->next) {
3950 dent = (SeafDirent *)ptr->data;
3951
3952 if (strcmp(dent->name, to_path_dup) != 0)
3953 continue;
3954
3955 id = put_file_recursive (repo, dent->id, remain, newdent);
3956 if (id != NULL) {
3957 memcpy(dent->id, id, 40);
3958 dent->id[40] = '\0';
3959 if (repo->version > 0)
3960 dent->mtime = (guint64)time(NULL);
3961 }
3962 break;
3963 }
3964
3965 if (id != NULL) {
3966 /* Create a new SeafDir. */
3967 GList *new_entries;
3968
3969 new_entries = dup_seafdir_entries (olddir->entries);
3970 newdir = seaf_dir_new (NULL, new_entries,
3971 dir_version_from_repo_version(repo->version));
3972 if (seaf_dir_save (seaf->fs_mgr, repo->store_id, repo->version, newdir) == 0)
3973 ret = g_strdup(newdir->dir_id);
3974 seaf_dir_free (newdir);
3975 }
3976
3977 out:
3978 g_free (to_path_dup);
3979 g_free (id);
3980 seaf_dir_free(olddir);
3981 return ret;
3982 }
3983
3984 static char *
do_put_file(SeafRepo * repo,const char * root_id,const char * parent_dir,SeafDirent * dent)3985 do_put_file (SeafRepo *repo,
3986 const char *root_id,
3987 const char *parent_dir,
3988 SeafDirent *dent)
3989 {
3990 /* if parent_dir is a absolutely path, we will remove the first '/' */
3991 if (*parent_dir == '/')
3992 parent_dir = parent_dir + 1;
3993
3994 return put_file_recursive(repo, root_id, parent_dir, dent);
3995 }
3996
3997 int
seaf_repo_manager_put_file(SeafRepoManager * mgr,const char * repo_id,const char * temp_file_path,const char * parent_dir,const char * file_name,const char * user,const char * head_id,char ** new_file_id,GError ** error)3998 seaf_repo_manager_put_file (SeafRepoManager *mgr,
3999 const char *repo_id,
4000 const char *temp_file_path,
4001 const char *parent_dir,
4002 const char *file_name,
4003 const char *user,
4004 const char *head_id,
4005 char **new_file_id,
4006 GError **error)
4007 {
4008 SeafRepo *repo = NULL;
4009 SeafCommit *head_commit = NULL;
4010 char *canon_path = NULL;
4011 unsigned char sha1[20];
4012 char buf[SEAF_PATH_MAX];
4013 char *root_id = NULL;
4014 SeafileCrypt *crypt = NULL;
4015 SeafDirent *new_dent = NULL;
4016 char hex[41];
4017 char *old_file_id = NULL, *fullpath = NULL;
4018 int ret = 0;
4019
4020 if (g_access (temp_file_path, R_OK) != 0) {
4021 seaf_warning ("[put file] File %s doesn't exist or not readable.\n",
4022 temp_file_path);
4023 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS,
4024 "Invalid input file");
4025 return -1;
4026 }
4027
4028 GET_REPO_OR_FAIL(repo, repo_id);
4029 const char *base = head_id ? head_id : repo->head->commit_id;
4030 GET_COMMIT_OR_FAIL(head_commit, repo->id, repo->version, base);
4031
4032 if (!canon_path)
4033 canon_path = get_canonical_path (parent_dir);
4034
4035 if (should_ignore_file (file_name, NULL)) {
4036 seaf_warning ("[put file] Invalid filename %s.\n", file_name);
4037 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS,
4038 "Invalid filename");
4039 ret = -1;
4040 goto out;
4041 }
4042
4043 if (strstr (parent_dir, "//") != NULL) {
4044 seaf_warning ("[put file] parent_dir cantains // sequence.\n");
4045 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS,
4046 "Invalid parent dir");
4047 ret = -1;
4048 goto out;
4049 }
4050
4051 FAIL_IF_FILE_NOT_EXISTS(repo->store_id, repo->version,
4052 head_commit->root_id, canon_path, file_name, NULL);
4053
4054 /* Write blocks. */
4055 if (repo->encrypted) {
4056 unsigned char key[32], iv[16];
4057 if (seaf_passwd_manager_get_decrypt_key_raw (seaf->passwd_mgr,
4058 repo_id, user,
4059 key, iv) < 0) {
4060 seaf_warning ("Passwd for repo %s is not set.\n", repo_id);
4061 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
4062 "Passwd is not set");
4063 ret = -1;
4064 goto out;
4065 }
4066 crypt = seafile_crypt_new (repo->enc_version, key, iv);
4067 }
4068
4069 gint64 size;
4070 if (seaf_fs_manager_index_blocks (seaf->fs_mgr,
4071 repo->store_id, repo->version,
4072 temp_file_path,
4073 sha1, &size, crypt, TRUE, FALSE, NULL) < 0) {
4074 seaf_warning ("failed to index blocks");
4075 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
4076 "Failed to index blocks");
4077 ret = -1;
4078 goto out;
4079 }
4080
4081 rawdata_to_hex(sha1, hex, 20);
4082 new_dent = seaf_dirent_new (dir_version_from_repo_version(repo->version),
4083 hex, STD_FILE_MODE, file_name,
4084 (gint64)time(NULL), user, size);
4085
4086 if (!fullpath)
4087 fullpath = g_build_filename(parent_dir, file_name, NULL);
4088
4089 old_file_id = seaf_fs_manager_path_to_obj_id (seaf->fs_mgr,
4090 repo->store_id, repo->version,
4091 head_commit->root_id,
4092 fullpath, NULL, NULL);
4093
4094 if (g_strcmp0(old_file_id, new_dent->id) == 0) {
4095 if (new_file_id)
4096 *new_file_id = g_strdup(new_dent->id);
4097 goto out;
4098 }
4099
4100 root_id = do_put_file (repo, head_commit->root_id, canon_path, new_dent);
4101 if (!root_id) {
4102 seaf_warning ("[put file] Failed to put file %s to %s in repo %s.\n",
4103 file_name, canon_path, repo->id);
4104 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
4105 "Failed to put file");
4106 ret = -1;
4107 goto out;
4108 }
4109
4110 /* Commit. */
4111 snprintf(buf, SEAF_PATH_MAX, "Modified \"%s\"", file_name);
4112 if (gen_new_commit (repo_id, head_commit, root_id, user, buf, NULL, error) < 0) {
4113 ret = -1;
4114 goto out;
4115 }
4116
4117 if (new_file_id)
4118 *new_file_id = g_strdup(new_dent->id);
4119
4120 seaf_repo_manager_merge_virtual_repo (mgr, repo_id, NULL);
4121
4122 out:
4123 if (repo)
4124 seaf_repo_unref (repo);
4125 if (head_commit)
4126 seaf_commit_unref(head_commit);
4127 seaf_dirent_free (new_dent);
4128 g_free (root_id);
4129 g_free (canon_path);
4130 g_free (crypt);
4131 g_free (old_file_id);
4132 g_free (fullpath);
4133
4134 if (ret == 0) {
4135 update_repo_size (repo_id);
4136 }
4137
4138 return ret;
4139 }
4140
4141 static char *
gen_commit_description(SeafRepo * repo,const char * root,const char * parent_root)4142 gen_commit_description (SeafRepo *repo,
4143 const char *root,
4144 const char *parent_root)
4145 {
4146 GList *p;
4147 GList *results = NULL;
4148 char *desc;
4149
4150 diff_commit_roots (repo->store_id, repo->version,
4151 parent_root, root, &results, TRUE);
4152
4153 desc = diff_results_to_description (results);
4154
4155 for (p = results; p; p = p->next) {
4156 DiffEntry *de = p->data;
4157 diff_entry_free (de);
4158 }
4159 g_list_free (results);
4160
4161 return desc;
4162 }
4163
4164 int
seaf_repo_manager_update_dir(SeafRepoManager * mgr,const char * repo_id,const char * dir_path,const char * new_dir_id,const char * user,const char * head_id,char * new_commit_id,GError ** error)4165 seaf_repo_manager_update_dir (SeafRepoManager *mgr,
4166 const char *repo_id,
4167 const char *dir_path,
4168 const char *new_dir_id,
4169 const char *user,
4170 const char *head_id,
4171 char *new_commit_id,
4172 GError **error)
4173 {
4174 SeafRepo *repo = NULL;
4175 SeafCommit *head_commit = NULL;
4176 char *canon_path = NULL;
4177 char *parent = NULL, *dirname = NULL;
4178 SeafDirent *new_dent = NULL;
4179 char *root_id = NULL;
4180 char *commit_desc = NULL;
4181 int ret = 0;
4182
4183 GET_REPO_OR_FAIL(repo, repo_id);
4184 const char *base = head_id ? head_id : repo->head->commit_id;
4185 GET_COMMIT_OR_FAIL(head_commit, repo->id, repo->version, base);
4186
4187 /* Are we updating the root? */
4188 if (strcmp (dir_path, "/") == 0) {
4189 commit_desc = gen_commit_description (repo, new_dir_id, head_commit->root_id);
4190 if (!commit_desc)
4191 commit_desc = g_strdup("Auto merge by system");
4192
4193 if (gen_new_commit (repo_id, head_commit, new_dir_id,
4194 user, commit_desc, new_commit_id, error) < 0)
4195 ret = -1;
4196 g_free (commit_desc);
4197 goto out;
4198 }
4199
4200 parent = g_path_get_dirname (dir_path);
4201 canon_path = get_canonical_path (parent);
4202 g_free (parent);
4203
4204 dirname = g_path_get_basename (dir_path);
4205
4206 FAIL_IF_FILE_NOT_EXISTS(repo->store_id, repo->version,
4207 head_commit->root_id, canon_path, dirname, NULL);
4208
4209 new_dent = seaf_dirent_new (dir_version_from_repo_version(repo->version),
4210 new_dir_id, S_IFDIR, dirname,
4211 (gint64)time(NULL), NULL, -1);
4212
4213 root_id = do_put_file (repo, head_commit->root_id, canon_path, new_dent);
4214 if (!root_id) {
4215 seaf_warning ("[update dir] Failed to put file.\n");
4216 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
4217 "Failed to update dir");
4218 ret = -1;
4219 goto out;
4220 }
4221
4222 commit_desc = gen_commit_description (repo, root_id, head_commit->root_id);
4223 if (!commit_desc)
4224 commit_desc = g_strdup("Auto merge by system");
4225
4226 if (gen_new_commit (repo_id, head_commit, root_id,
4227 user, commit_desc, new_commit_id, error) < 0) {
4228 ret = -1;
4229 g_free (commit_desc);
4230 goto out;
4231 }
4232 g_free (commit_desc);
4233
4234 out:
4235 seaf_repo_unref (repo);
4236 seaf_commit_unref (head_commit);
4237 seaf_dirent_free (new_dent);
4238 g_free (canon_path);
4239 g_free (dirname);
4240 g_free (root_id);
4241
4242 if (ret == 0)
4243 update_repo_size (repo_id);
4244
4245 return ret;
4246 }
4247
4248 /* int */
4249 /* seaf_repo_manager_put_file_blocks (SeafRepoManager *mgr, */
4250 /* const char *repo_id, */
4251 /* const char *parent_dir, */
4252 /* const char *file_name, */
4253 /* const char *blockids_json, */
4254 /* const char *paths_json, */
4255 /* const char *user, */
4256 /* const char *head_id, */
4257 /* gint64 file_size, */
4258 /* char **new_file_id, */
4259 /* GError **error) */
4260 /* { */
4261 /* SeafRepo *repo = NULL; */
4262 /* SeafCommit *head_commit = NULL; */
4263 /* char *canon_path = NULL; */
4264 /* unsigned char sha1[20]; */
4265 /* char buf[SEAF_PATH_MAX]; */
4266 /* char *root_id = NULL; */
4267 /* SeafDirent *new_dent = NULL; */
4268 /* char hex[41]; */
4269 /* GList *blockids = NULL, *paths = NULL, *ptr; */
4270 /* char *old_file_id = NULL, *fullpath = NULL; */
4271 /* int ret = 0; */
4272
4273 /* blockids = json_to_file_list (blockids_json); */
4274 /* paths = json_to_file_list (paths_json); */
4275 /* if (g_list_length(blockids) != g_list_length(paths)) { */
4276 /* seaf_warning ("[put-blks] Invalid blockids or paths.\n"); */
4277 /* g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, "Invalid files"); */
4278 /* ret = -1; */
4279 /* goto out; */
4280 /* } */
4281
4282
4283 /* for (ptr = paths; ptr; ptr = ptr->next) { */
4284 /* char *temp_file_path = ptr->data; */
4285 /* if (g_access (temp_file_path, R_OK) != 0) { */
4286 /* seaf_warning ("[put-blks] File block %s doesn't exist or not readable.\n", */
4287 /* temp_file_path); */
4288 /* g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, */
4289 /* "Invalid input file"); */
4290 /* ret = -1; */
4291 /* goto out; */
4292 /* } */
4293 /* } */
4294
4295 /* GET_REPO_OR_FAIL(repo, repo_id); */
4296 /* const char *base = head_id ? head_id : repo->head->commit_id; */
4297 /* GET_COMMIT_OR_FAIL(head_commit, repo->id, repo->version, base); */
4298
4299 /* if (!canon_path) */
4300 /* canon_path = get_canonical_path (parent_dir); */
4301
4302 /* if (should_ignore_file (file_name, NULL)) { */
4303 /* seaf_warning ("[put-blks] Invalid filename %s.\n", file_name); */
4304 /* g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, */
4305 /* "Invalid filename"); */
4306 /* ret = -1; */
4307 /* goto out; */
4308 /* } */
4309
4310 /* if (strstr (parent_dir, "//") != NULL) { */
4311 /* seaf_warning ("[put-blks] parent_dir cantains // sequence.\n"); */
4312 /* g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, */
4313 /* "Invalid parent dir"); */
4314 /* ret = -1; */
4315 /* goto out; */
4316 /* } */
4317
4318 /* FAIL_IF_FILE_NOT_EXISTS(repo->store_id, repo->version, */
4319 /* head_commit->root_id, canon_path, file_name, NULL); */
4320
4321 /* /\* Write blocks. *\/ */
4322 /* if (seaf_fs_manager_index_file_blocks (seaf->fs_mgr, */
4323 /* repo->store_id, repo->version, */
4324 /* paths, */
4325 /* blockids, sha1, file_size) < 0) { */
4326 /* seaf_warning ("failed to index blocks"); */
4327 /* g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL, */
4328 /* "Failed to index blocks"); */
4329 /* ret = -1; */
4330 /* goto out; */
4331 /* } */
4332
4333 /* rawdata_to_hex(sha1, hex, 20); */
4334 /* new_dent = seaf_dirent_new (dir_version_from_repo_version(repo->version), */
4335 /* hex, STD_FILE_MODE, file_name, */
4336 /* (gint64)time(NULL), user, file_size); */
4337
4338 /* if (!fullpath) */
4339 /* fullpath = g_build_filename(parent_dir, file_name, NULL); */
4340
4341 /* old_file_id = seaf_fs_manager_path_to_obj_id (seaf->fs_mgr, */
4342 /* repo->store_id, repo->version, */
4343 /* head_commit->root_id, */
4344 /* fullpath, NULL, NULL); */
4345
4346 /* if (g_strcmp0(old_file_id, new_dent->id) == 0) { */
4347 /* if (new_file_id) */
4348 /* *new_file_id = g_strdup(new_dent->id); */
4349 /* goto out; */
4350 /* } */
4351
4352 /* root_id = do_put_file (repo, head_commit->root_id, canon_path, new_dent); */
4353 /* if (!root_id) { */
4354 /* seaf_warning ("[put-blks] Failed to put file.\n"); */
4355 /* g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL, */
4356 /* "Failed to put file"); */
4357 /* ret = -1; */
4358 /* goto out; */
4359 /* } */
4360
4361 /* /\* Commit. *\/ */
4362 /* snprintf(buf, SEAF_PATH_MAX, "Modified \"%s\"", file_name); */
4363 /* if (gen_new_commit (repo_id, head_commit, root_id, user, buf, NULL, error) < 0) { */
4364 /* ret = -1; */
4365 /* goto out; */
4366 /* } */
4367
4368 /* if (new_file_id) */
4369 /* *new_file_id = g_strdup(new_dent->id); */
4370
4371 /* out: */
4372 /* if (repo) */
4373 /* seaf_repo_unref (repo); */
4374 /* if (head_commit) */
4375 /* seaf_commit_unref(head_commit); */
4376 /* string_list_free (blockids); */
4377 /* string_list_free (paths); */
4378 /* seaf_dirent_free (new_dent); */
4379 /* g_free (root_id); */
4380 /* g_free (canon_path); */
4381 /* g_free (old_file_id); */
4382 /* g_free (fullpath); */
4383
4384 /* if (ret == 0) { */
4385 /* update_repo_size (repo_id); */
4386 /* } */
4387
4388 /* return ret; */
4389 /* } */
4390
4391 /* split filename into base and extension */
4392 static void
filename_splitext(const char * filename,char ** base,char ** ext)4393 filename_splitext (const char *filename,
4394 char **base,
4395 char **ext)
4396 {
4397 char *dot = strrchr(filename, '.');
4398 if (!dot) {
4399 *base = g_strdup(filename);
4400 *ext = NULL;
4401 } else {
4402 *dot = '\0';
4403 *base = g_strdup(filename);
4404 *dot = '.';
4405
4406 *ext = g_strdup(dot);
4407 }
4408 }
4409
4410 static char *
revert_file_to_root(SeafRepo * repo,const char * root_id,SeafDirent * old_dent,gboolean * skipped,GError ** error)4411 revert_file_to_root (SeafRepo *repo,
4412 const char *root_id,
4413 SeafDirent *old_dent,
4414 gboolean *skipped,
4415 GError **error)
4416 {
4417 SeafDir *dir = NULL;
4418 SeafDirent *dent = NULL, *newdent = NULL;
4419 char *basename = NULL, *ext = NULL;
4420 char new_file_name[SEAF_PATH_MAX];
4421 char *new_root_id = NULL;
4422 int i = 1;
4423 GList *p;
4424
4425 *skipped = FALSE;
4426
4427 dir = seaf_fs_manager_get_seafdir_by_path (seaf->fs_mgr,
4428 repo->store_id, repo->version,
4429 root_id,
4430 "/", error);
4431 if (*error) {
4432 return NULL;
4433 }
4434
4435 snprintf (new_file_name, sizeof(new_file_name), "%s", old_dent->name);
4436
4437 filename_splitext(old_dent->name, &basename, &ext);
4438 for (;;) {
4439 for (p = dir->entries; p; p = p->next) {
4440 dent = p->data;
4441 if (strcmp(dent->name, new_file_name) != 0)
4442 continue;
4443
4444 if (S_ISREG(dent->mode)) {
4445 /* same named file */
4446 if (strcmp(dent->id, old_dent->id) == 0) {
4447 *skipped = TRUE;
4448 goto out;
4449 } else {
4450 /* rename and retry */
4451 snprintf (new_file_name, sizeof(new_file_name), "%s (%d)%s",
4452 basename, i++, ext);
4453 break;
4454 }
4455
4456 } else if (S_ISDIR(dent->mode)) {
4457 /* rename and retry */
4458 snprintf (new_file_name, sizeof(new_file_name), "%s (%d)%s",
4459 basename, i++, ext);
4460 break;
4461 }
4462 }
4463
4464 if (p == NULL)
4465 break;
4466 }
4467
4468 newdent = seaf_dirent_new (old_dent->version,
4469 old_dent->id, STD_FILE_MODE, new_file_name,
4470 old_dent->mtime, old_dent->modifier, old_dent->size);
4471 new_root_id = do_post_file (repo, root_id, "/", newdent);
4472
4473 out:
4474 if (dir)
4475 seaf_dir_free (dir);
4476
4477 g_free (basename);
4478 g_free (ext);
4479 seaf_dirent_free (newdent);
4480
4481 return new_root_id;
4482 }
4483
4484 static char *
revert_file_to_parent_dir(SeafRepo * repo,const char * root_id,const char * parent_dir,SeafDirent * old_dent,gboolean * skipped,GError ** error)4485 revert_file_to_parent_dir (SeafRepo *repo,
4486 const char *root_id,
4487 const char *parent_dir,
4488 SeafDirent *old_dent,
4489 gboolean *skipped,
4490 GError **error)
4491 {
4492 SeafDir *dir = NULL;
4493 SeafDirent *dent = NULL, *newdent = NULL;
4494 char *basename = NULL, *ext = NULL;
4495 char new_file_name[SEAF_PATH_MAX];
4496 char *new_root_id = NULL;
4497 gboolean is_overwrite = FALSE;
4498 int i = 1;
4499 GList *p;
4500
4501 *skipped = FALSE;
4502
4503 dir = seaf_fs_manager_get_seafdir_by_path (seaf->fs_mgr,
4504 repo->store_id, repo->version,
4505 root_id,
4506 parent_dir, error);
4507 if (*error) {
4508 return NULL;
4509 }
4510
4511 snprintf (new_file_name, sizeof(new_file_name), "%s", old_dent->name);
4512 filename_splitext(old_dent->name, &basename, &ext);
4513 while(TRUE) {
4514 for (p = dir->entries; p; p = p->next) {
4515 dent = p->data;
4516 if (strcmp(dent->name, new_file_name) != 0)
4517 continue;
4518
4519 if (S_ISREG(dent->mode)) {
4520 /* same named file */
4521 if (strcmp(dent->id, old_dent->id) == 0) {
4522 *skipped = TRUE;
4523 goto out;
4524 } else {
4525 /* same name, different id: just overwrite */
4526 is_overwrite = TRUE;
4527 goto do_revert;
4528 }
4529
4530 } else if (S_ISDIR(dent->mode)) {
4531 /* rename and retry */
4532 snprintf (new_file_name, sizeof(new_file_name), "%s (%d)%s",
4533 basename, i++, ext);
4534 break;
4535 }
4536 }
4537
4538 if (p == NULL)
4539 break;
4540 }
4541
4542 do_revert:
4543 newdent = seaf_dirent_new (old_dent->version,
4544 old_dent->id, STD_FILE_MODE, new_file_name,
4545 old_dent->mtime, old_dent->modifier, old_dent->size);
4546 if (is_overwrite) {
4547 new_root_id = do_put_file (repo,
4548 root_id, parent_dir, newdent);
4549 } else {
4550 new_root_id = do_post_file (repo,
4551 root_id, parent_dir, newdent);
4552 }
4553
4554 out:
4555 if (dir)
4556 seaf_dir_free (dir);
4557
4558 g_free (basename);
4559 g_free (ext);
4560 seaf_dirent_free (newdent);
4561
4562 return new_root_id;
4563 }
4564
4565 static gboolean
detect_path_exist(SeafRepo * repo,const char * root_id,const char * path,GError ** error)4566 detect_path_exist (SeafRepo *repo,
4567 const char *root_id,
4568 const char *path,
4569 GError **error)
4570 {
4571 SeafDir *dir;
4572
4573 dir = seaf_fs_manager_get_seafdir_by_path (seaf->fs_mgr,
4574 repo->store_id, repo->version,
4575 root_id, path, error);
4576 if (*error) {
4577 if (g_error_matches(*error, SEAFILE_DOMAIN, SEAF_ERR_PATH_NO_EXIST)) {
4578 /* path does not exist */
4579 g_clear_error(error);
4580 return FALSE;
4581 } else {
4582 /* Other error */
4583 return FALSE;
4584 }
4585 }
4586
4587 seaf_dir_free(dir);
4588 return TRUE;
4589 }
4590
4591 int
seaf_repo_manager_revert_file(SeafRepoManager * mgr,const char * repo_id,const char * old_commit_id,const char * file_path,const char * user,GError ** error)4592 seaf_repo_manager_revert_file (SeafRepoManager *mgr,
4593 const char *repo_id,
4594 const char *old_commit_id,
4595 const char *file_path,
4596 const char *user,
4597 GError **error)
4598 {
4599 SeafRepo *repo = NULL;
4600 SeafCommit *head_commit = NULL, *old_commit = NULL;
4601 char *parent_dir = NULL, *filename = NULL;
4602 SeafDirent *old_dent = NULL;
4603 char *canon_path = NULL, *root_id = NULL;
4604 char buf[SEAF_PATH_MAX];
4605 char time_str[512];
4606 gboolean parent_dir_exist = FALSE;
4607 gboolean revert_to_root = FALSE;
4608 gboolean skipped = FALSE;
4609 int ret = 0;
4610
4611 GET_REPO_OR_FAIL(repo, repo_id);
4612 GET_COMMIT_OR_FAIL(head_commit, repo->id, repo->version, repo->head->commit_id);
4613
4614 /* If old_commit_id is head commit, do nothing. */
4615 if (strcmp(repo->head->commit_id, old_commit_id) == 0) {
4616 g_debug ("[revert file] commit is head, do nothing\n");
4617 goto out;
4618 }
4619
4620 if (!old_commit) {
4621 GET_COMMIT_OR_FAIL(old_commit, repo->id, repo->version, old_commit_id);
4622 if (strcmp(old_commit->repo_id, repo_id) != 0) {
4623 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_COMMIT,
4624 "bad commit id");
4625 ret = -1;
4626 goto out;
4627 }
4628 }
4629
4630 if (!canon_path) {
4631 canon_path = get_canonical_path (file_path);
4632 if (canon_path[strlen(canon_path) -1 ] == '/') {
4633 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_COMMIT,
4634 "bad target file path");
4635 ret = -1;
4636 goto out;
4637 }
4638
4639 parent_dir = g_path_get_dirname(canon_path);
4640 filename = g_path_get_basename(canon_path);
4641
4642 old_dent = get_dirent_by_path (repo, old_commit->root_id,
4643 parent_dir, filename, error);
4644 if (!old_dent || S_ISDIR(old_dent->mode)) {
4645 ret = -1;
4646 goto out;
4647 }
4648 if (*error) {
4649 seaf_warning ("[revert file] error: %s\n", (*error)->message);
4650 g_clear_error (error);
4651 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
4652 "internal error");
4653 ret = -1;
4654 goto out;
4655 }
4656 }
4657
4658 parent_dir_exist = detect_path_exist (repo,
4659 head_commit->root_id,
4660 parent_dir, error);
4661 if (*error) {
4662 seaf_warning ("[revert file] error: %s\n", (*error)->message);
4663 g_clear_error (error);
4664 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
4665 "internal error");
4666 ret = -1;
4667 goto out;
4668 }
4669
4670 if (!parent_dir_exist) {
4671 /* When parent dir does not exist, revert this file to root dir. */
4672 revert_to_root = TRUE;
4673 root_id = revert_file_to_root (repo,
4674 head_commit->root_id,
4675 old_dent,
4676 &skipped, error);
4677 } else {
4678 revert_to_root = FALSE;
4679 root_id = revert_file_to_parent_dir (repo,
4680 head_commit->root_id, parent_dir,
4681 old_dent,
4682 &skipped, error);
4683 }
4684
4685 if (*error) {
4686 seaf_warning ("[revert file] error: %s\n", (*error)->message);
4687 g_clear_error (error);
4688 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
4689 "internal error");
4690 ret = -1;
4691 goto out;
4692 }
4693
4694 if (skipped) {
4695 goto out;
4696 }
4697
4698 if (!root_id) {
4699 seaf_warning ("[revert file] Failed to revert file.\n");
4700 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
4701 "Failed to revert file");
4702 ret = -1;
4703 goto out;
4704 }
4705
4706 /* Commit. */
4707 #ifndef WIN32
4708 strftime (time_str, sizeof(time_str), "%F %T",
4709 localtime((time_t *)(&old_commit->ctime)));
4710 #else
4711 strftime (time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S",
4712 localtime((time_t *)(&old_commit->ctime)));
4713 #endif
4714 snprintf(buf, SEAF_PATH_MAX, "Reverted file \"%s\" to status at %s", filename, time_str);
4715 if (gen_new_commit (repo_id, head_commit, root_id,
4716 user, buf, NULL, error) < 0) {
4717 ret = -1;
4718 goto out;
4719 }
4720
4721 seaf_repo_manager_merge_virtual_repo (mgr, repo_id, NULL);
4722
4723 out:
4724 if (repo)
4725 seaf_repo_unref (repo);
4726 if (head_commit)
4727 seaf_commit_unref (head_commit);
4728 if (old_commit)
4729 seaf_commit_unref (old_commit);
4730
4731 g_free (root_id);
4732 g_free (parent_dir);
4733 g_free (filename);
4734
4735 g_free (canon_path);
4736 seaf_dirent_free (old_dent);
4737
4738 #define REVERT_TO_ROOT 0x1
4739 if (ret == 0) {
4740 if (revert_to_root)
4741 ret |= REVERT_TO_ROOT;
4742
4743 update_repo_size (repo_id);
4744 }
4745
4746 return ret;
4747 }
4748
4749 static char *
revert_dir(SeafRepo * repo,const char * root_id,const char * parent_dir,SeafDirent * old_dent,gboolean * skipped,GError ** error)4750 revert_dir (SeafRepo *repo,
4751 const char *root_id,
4752 const char *parent_dir,
4753 SeafDirent *old_dent,
4754 gboolean *skipped,
4755 GError **error)
4756 {
4757 SeafDir *dir = NULL;
4758 SeafDirent *dent = NULL, *newdent = NULL;
4759 char new_dir_name[SEAF_PATH_MAX];
4760 char *new_root_id = NULL;
4761 int i = 1;
4762 GList *p;
4763
4764 *skipped = FALSE;
4765
4766 dir = seaf_fs_manager_get_seafdir_by_path (seaf->fs_mgr,
4767 repo->store_id, repo->version,
4768 root_id,
4769 parent_dir, error);
4770 if (*error) {
4771 return NULL;
4772 }
4773
4774 snprintf (new_dir_name, sizeof(new_dir_name), "%s", old_dent->name);
4775
4776 for (;;) {
4777 for (p = dir->entries; p; p = p->next) {
4778 dent = p->data;
4779 if (strcmp(dent->name, new_dir_name) != 0)
4780 continue;
4781
4782 /* the same dir */
4783 if (S_ISDIR(dent->mode) && strcmp(dent->id, old_dent->id) == 0) {
4784 *skipped = TRUE;
4785 goto out;
4786 } else {
4787 /* rename and retry */
4788 snprintf (new_dir_name, sizeof(new_dir_name), "%s (%d)",
4789 old_dent->name, i++);
4790 break;
4791 }
4792 }
4793
4794 if (p == NULL)
4795 break;
4796 }
4797
4798 newdent = seaf_dirent_new (old_dent->version,
4799 old_dent->id, S_IFDIR, new_dir_name,
4800 old_dent->mtime, NULL, -1);
4801 new_root_id = do_post_file (repo, root_id, parent_dir, newdent);
4802
4803 out:
4804 if (dir)
4805 seaf_dir_free (dir);
4806
4807 seaf_dirent_free (newdent);
4808
4809 return new_root_id;
4810 }
4811
4812 int
seaf_repo_manager_revert_dir(SeafRepoManager * mgr,const char * repo_id,const char * old_commit_id,const char * dir_path,const char * user,GError ** error)4813 seaf_repo_manager_revert_dir (SeafRepoManager *mgr,
4814 const char *repo_id,
4815 const char *old_commit_id,
4816 const char *dir_path,
4817 const char *user,
4818 GError **error)
4819 {
4820 SeafRepo *repo = NULL;
4821 SeafCommit *head_commit = NULL, *old_commit = NULL;
4822 char *parent_dir = NULL, *dirname = NULL;
4823 SeafDirent *old_dent = NULL;
4824 char *canon_path = NULL, *root_id = NULL;
4825 char buf[SEAF_PATH_MAX];
4826 gboolean parent_dir_exist = FALSE;
4827 gboolean revert_to_root = FALSE;
4828 gboolean skipped = FALSE;
4829 int ret = 0;
4830
4831 GET_REPO_OR_FAIL(repo, repo_id);
4832 GET_COMMIT_OR_FAIL(head_commit, repo->id, repo->version, repo->head->commit_id);
4833
4834 /* If old_commit_id is head commit, do nothing. */
4835 if (strcmp(repo->head->commit_id, old_commit_id) == 0) {
4836 g_debug ("[revert dir] commit is head, do nothing\n");
4837 goto out;
4838 }
4839
4840 if (!old_commit) {
4841 GET_COMMIT_OR_FAIL(old_commit, repo->id, repo->version, old_commit_id);
4842 if (strcmp(old_commit->repo_id, repo_id) != 0) {
4843 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_COMMIT,
4844 "bad commit id");
4845 ret = -1;
4846 goto out;
4847 }
4848 }
4849
4850 if (!canon_path) {
4851 canon_path = get_canonical_path (dir_path);
4852
4853 parent_dir = g_path_get_dirname(canon_path);
4854 dirname = g_path_get_basename(canon_path);
4855
4856 old_dent = get_dirent_by_path (repo, old_commit->root_id,
4857 parent_dir, dirname, error);
4858 if (!old_dent || S_ISREG(old_dent->mode)) {
4859 ret = -1;
4860 goto out;
4861 }
4862 if (*error) {
4863 seaf_warning ("[revert dir] error: %s\n", (*error)->message);
4864 g_clear_error (error);
4865 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
4866 "internal error");
4867 ret = -1;
4868 goto out;
4869 }
4870 }
4871
4872 parent_dir_exist = detect_path_exist (repo,
4873 head_commit->root_id,
4874 parent_dir, error);
4875 if (*error) {
4876 seaf_warning ("[revert dir] error: %s\n", (*error)->message);
4877 g_clear_error (error);
4878 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
4879 "internal error");
4880 ret = -1;
4881 goto out;
4882 }
4883
4884 if (!parent_dir_exist) {
4885 /* When parent dir does not exist, revert this file to root dir. */
4886 revert_to_root = TRUE;
4887 root_id = revert_dir (repo,
4888 head_commit->root_id,
4889 "/",
4890 old_dent,
4891 &skipped, error);
4892 } else {
4893 revert_to_root = FALSE;
4894 root_id = revert_dir (repo,
4895 head_commit->root_id,
4896 parent_dir,
4897 old_dent,
4898 &skipped, error);
4899 }
4900
4901 if (*error) {
4902 seaf_warning ("[revert dir] error: %s\n", (*error)->message);
4903 g_clear_error (error);
4904 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
4905 "internal error");
4906 ret = -1;
4907 goto out;
4908 }
4909
4910 if (skipped) {
4911 goto out;
4912 }
4913
4914 if (!root_id) {
4915 seaf_warning ("[revert dir] Failed to revert dir.\n");
4916 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
4917 "Failed to revert dir");
4918 ret = -1;
4919 goto out;
4920 }
4921
4922 /* Commit. */
4923 snprintf(buf, SEAF_PATH_MAX, "Recovered deleted directory \"%s\"", dirname);
4924 if (gen_new_commit (repo_id, head_commit, root_id,
4925 user, buf, NULL, error) < 0) {
4926 ret = -1;
4927 goto out;
4928 }
4929
4930 seaf_repo_manager_merge_virtual_repo (mgr, repo_id, NULL);
4931
4932 out:
4933 if (repo)
4934 seaf_repo_unref (repo);
4935 if (head_commit)
4936 seaf_commit_unref (head_commit);
4937 if (old_commit)
4938 seaf_commit_unref (old_commit);
4939
4940 g_free (root_id);
4941 g_free (parent_dir);
4942 g_free (dirname);
4943
4944 g_free (canon_path);
4945 seaf_dirent_free (old_dent);
4946
4947 #define REVERT_TO_ROOT 0x1
4948 if (ret == 0) {
4949 if (revert_to_root)
4950 ret |= REVERT_TO_ROOT;
4951
4952 update_repo_size (repo_id);
4953 }
4954
4955 return ret;
4956 }
4957
4958 typedef struct CollectRevisionParam CollectRevisionParam;
4959
4960 struct CollectRevisionParam {
4961 SeafRepo *repo;
4962 const char *path;
4963 GList *wanted_commits;
4964 GList *file_id_list;
4965 GList *file_size_list;
4966 int n_commits;
4967 GHashTable *file_info_cache;
4968
4969 /* > 0: keep a period of history;
4970 * == 0: N/A
4971 * < 0: keep all history data.
4972 */
4973 gint64 truncate_time;
4974 gboolean got_latest;
4975 gboolean got_second;
4976 gboolean not_found_file;
4977
4978 GError **error;
4979 };
4980
4981 typedef struct FileInfo {
4982 gint64 file_size;
4983 char *file_id;
4984 GList *dir_ids;
4985 } FileInfo;
4986
4987 static void
free_file_info(gpointer info)4988 free_file_info (gpointer info)
4989 {
4990 if (!info)
4991 return;
4992
4993 FileInfo *file_info = info;
4994 g_free (file_info->file_id);
4995 g_list_free_full (file_info->dir_ids, g_free);
4996 g_free (file_info);
4997 }
4998
4999 // compare current commit dir_id with pre commit
5000 // if dir_id doesn't change, it means subdir doesn't change, append all sub_dir ids of prev to current
5001 // that is it is no need to traverse all sub dir, if root doesn't change
5002 static gboolean
compare_or_add_id(GList * dir_ids,GList ** cur_dir_ids,const char * dir_id)5003 compare_or_add_id (GList *dir_ids,
5004 GList **cur_dir_ids,
5005 const char *dir_id)
5006 {
5007 gboolean ret = FALSE;
5008 GList *tmp = dir_ids;
5009
5010 if (tmp == NULL ||
5011 strcmp ((char *)tmp->data, dir_id) != 0) {
5012 *cur_dir_ids = g_list_append (*cur_dir_ids, g_strdup (dir_id));
5013 } else {
5014 // file doesn't changed, append all dir ids to this commit cache
5015 while (tmp) {
5016 *cur_dir_ids = g_list_append (*cur_dir_ids,
5017 g_strdup ((char *)tmp->data));
5018 tmp = tmp->next;
5019 }
5020 ret = TRUE;
5021 }
5022
5023 return ret;
5024 }
5025
5026 // dir_ids: all dir_ids in prev commit, in the order of fs tree
5027 // cur_dir_ids: all dir_ids in current commit
5028 // if no error and returned seafdir is NULL, then it means
5029 // searched dir doesn't change in pre and current commit
5030 static SeafDir*
get_seafdir_by_path(const char * repo_id,int version,const char * root_id,const char * path,GList * dir_ids,GList ** cur_dir_ids,GError ** error)5031 get_seafdir_by_path (const char *repo_id,
5032 int version,
5033 const char *root_id,
5034 const char *path,
5035 GList *dir_ids,
5036 GList **cur_dir_ids,
5037 GError **error)
5038 {
5039 SeafDir *dir = NULL;
5040 SeafDirent *dent;
5041 const char *dir_id = root_id;
5042 char *name, *saveptr;
5043 char *tmp_path = NULL;
5044 GList *tmp = dir_ids;
5045
5046 dir = seaf_fs_manager_get_seafdir (seaf->fs_mgr, repo_id, version, dir_id);
5047 if (!dir) {
5048 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_DIR_MISSING, "directory is missing");
5049 goto out;
5050 }
5051
5052 if (compare_or_add_id (tmp, cur_dir_ids, dir_id)) {
5053 seaf_dir_free (dir);
5054 dir = NULL;
5055 goto out;
5056 } else if (tmp) {
5057 tmp = tmp->next;
5058 }
5059
5060 if (strcmp (path, ".") == 0 ||
5061 strcmp (path, "/") == 0) {
5062 goto out;
5063 } else {
5064 tmp_path = g_strdup (path);
5065 }
5066
5067 name = strtok_r (tmp_path, "/", &saveptr);
5068 while (name != NULL) {
5069 GList *l;
5070 for (l = dir->entries; l != NULL; l = l->next) {
5071 dent = l->data;
5072
5073 if (strcmp(dent->name, name) == 0 && S_ISDIR(dent->mode)) {
5074 dir_id = dent->id;
5075 break;
5076 }
5077 }
5078
5079 if (!l) {
5080 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_PATH_NO_EXIST,
5081 "Path does not exists %s", path);
5082 seaf_dir_free (dir);
5083 dir = NULL;
5084 break;
5085 }
5086
5087 if (compare_or_add_id (tmp, cur_dir_ids, dir_id)) {
5088 seaf_dir_free (dir);
5089 dir = NULL;
5090 goto out;
5091 } else if (tmp) {
5092 tmp = tmp->next;
5093 }
5094
5095 SeafDir *prev = dir;
5096 dir = seaf_fs_manager_get_seafdir (seaf->fs_mgr, repo_id, version, dir_id);
5097 seaf_dir_free (prev);
5098
5099 if (!dir) {
5100 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_DIR_MISSING,
5101 "directory is missing");
5102 break;
5103 }
5104
5105 name = strtok_r (NULL, "/", &saveptr);
5106 }
5107
5108 out:
5109 g_free (tmp_path);
5110 return dir;
5111 }
5112
5113 /*
5114 * Return NULL if file is not found, error is still NULL;
5115 * If we have IO errors, error is set.
5116 */
5117 static FileInfo*
get_file_info(SeafRepo * repo,SeafCommit * commit,const char * path,GHashTable * file_info_cache,FileInfo * last_info,GError ** error)5118 get_file_info (SeafRepo *repo,
5119 SeafCommit *commit,
5120 const char *path,
5121 GHashTable *file_info_cache,
5122 FileInfo *last_info,
5123 GError **error)
5124 {
5125 SeafDir *dir = NULL;
5126 SeafDirent *dirent = NULL;
5127 FileInfo *file_info = NULL;
5128 GList *tmp;
5129
5130 file_info = g_hash_table_lookup (file_info_cache, commit->commit_id);
5131 if (file_info)
5132 return file_info;
5133
5134 char *dir_name = g_path_get_dirname (path);
5135 char *file_name = g_path_get_basename (path);
5136 GList *cur_dir_ids = NULL;
5137 GList *dir_ids = last_info ? last_info->dir_ids : NULL;
5138
5139 dir = get_seafdir_by_path (repo->store_id, repo->version,
5140 commit->root_id, dir_name, dir_ids,
5141 &cur_dir_ids, error);
5142 if (*error) {
5143 if ((*error)->code == SEAF_ERR_PATH_NO_EXIST)
5144 g_clear_error (error);
5145 goto out;
5146 }
5147
5148 if (!dir) {
5149 // if no error and return is null from get_seafdir_by_path, it means dir doesn't
5150 // change in pre and current commit, so the last_info (file info of pre commit)
5151 // is also the current file info
5152 file_info = g_new0 (FileInfo, 1);
5153 file_info->file_id = g_strdup (last_info->file_id);
5154 file_info->dir_ids = cur_dir_ids;
5155 file_info->file_size = last_info->file_size;
5156 g_hash_table_insert (file_info_cache, g_strdup (commit->commit_id),
5157 file_info);
5158 } else {
5159 for (tmp = dir->entries; tmp; tmp = tmp->next) {
5160 dirent = tmp->data;
5161 if (strcmp (file_name, dirent->name) == 0 &&
5162 S_ISREG (dirent->mode)) {
5163 break;
5164 }
5165 }
5166 if (tmp) {
5167 // from parent dir find the file, cache file info for the next compare
5168 file_info = g_new0 (FileInfo, 1);
5169 file_info->file_id = g_strdup (dirent->id);
5170 file_info->dir_ids = cur_dir_ids;
5171 if (repo->version > 0) {
5172 file_info->file_size = dirent->size;
5173 } else {
5174 file_info->file_size = seaf_fs_manager_get_file_size (seaf->fs_mgr,
5175 repo->store_id,
5176 repo->version,
5177 dirent->id);
5178 }
5179 g_hash_table_insert (file_info_cache, g_strdup (commit->commit_id),
5180 file_info);
5181 }
5182 }
5183
5184 out:
5185 if (dir)
5186 seaf_dir_free (dir);
5187 if (!file_info) {
5188 g_list_free_full (cur_dir_ids, g_free);
5189 }
5190 g_free (file_name);
5191 g_free (dir_name);
5192
5193 return file_info;
5194 }
5195
5196 static void
add_revision_info(CollectRevisionParam * data,SeafCommit * commit,const char * file_id,gint64 file_size)5197 add_revision_info (CollectRevisionParam *data,
5198 SeafCommit *commit, const char *file_id, gint64 file_size)
5199 {
5200 seaf_commit_ref (commit);
5201 data->wanted_commits = g_list_prepend (data->wanted_commits, commit);
5202 data->file_id_list = g_list_prepend (data->file_id_list, g_strdup(file_id));
5203 gint64 *size = g_malloc(sizeof(gint64));
5204 *size = file_size;
5205 data->file_size_list = g_list_prepend (data->file_size_list, size);
5206 ++(data->n_commits);
5207 }
5208
5209 static gboolean
collect_file_revisions(SeafCommit * commit,void * vdata,gboolean * stop)5210 collect_file_revisions (SeafCommit *commit, void *vdata, gboolean *stop)
5211 {
5212 CollectRevisionParam *data = vdata;
5213 SeafRepo *repo = data->repo;
5214 const char *path = data->path;
5215 GError **error = data->error;
5216 GHashTable *file_info_cache = data->file_info_cache;
5217 FileInfo *file_info = NULL;
5218 FileInfo *parent1_info = NULL;
5219 FileInfo *parent2_info = NULL;
5220
5221 SeafCommit *parent_commit = NULL;
5222 SeafCommit *parent_commit2 = NULL;
5223
5224 gboolean ret = TRUE;
5225
5226 /* At least find the latest revision. */
5227 if (data->got_latest && data->truncate_time == 0) {
5228 *stop = TRUE;
5229 return TRUE;
5230 }
5231
5232 if (data->got_latest &&
5233 data->truncate_time > 0 &&
5234 (gint64)(commit->ctime) < data->truncate_time &&
5235 data->got_second)
5236 {
5237 *stop = TRUE;
5238 data->not_found_file = TRUE;
5239 return TRUE;
5240 }
5241
5242 g_clear_error (error);
5243
5244 file_info = get_file_info (data->repo, commit, path,
5245 file_info_cache, NULL, error);
5246 if (*error) {
5247 seaf_warning ("Error when finding %s under %s:%s\n",
5248 path, data->repo->id, commit->commit_id);
5249 ret = FALSE;
5250 goto out;
5251 }
5252
5253 if (!file_info) {
5254 /* Target file is not present in this commit.
5255 * Stop traversing after finding the initial version.
5256 * Deleted files with the same path are not included in history.
5257 */
5258 *stop = TRUE;
5259 data->not_found_file = TRUE;
5260 goto out;
5261 }
5262
5263 if (!commit->parent_id) {
5264 /* Initial commit */
5265 add_revision_info (data, commit, file_info->file_id, file_info->file_size);
5266 goto out;
5267 }
5268
5269 parent_commit = seaf_commit_manager_get_commit (seaf->commit_mgr,
5270 repo->id, repo->version,
5271 commit->parent_id);
5272 if (!parent_commit) {
5273 seaf_warning ("Failed to get commit %s:%s\n", repo->id, commit->parent_id);
5274 ret = FALSE;
5275 goto out;
5276 }
5277
5278 parent1_info = get_file_info (data->repo, parent_commit, path,
5279 file_info_cache, file_info, error);
5280 if (*error) {
5281 seaf_warning ("Error when finding %s under %s:%s\n",
5282 path, data->repo->id, parent_commit->commit_id);
5283 ret = FALSE;
5284 goto out;
5285 }
5286
5287 if (parent1_info &&
5288 g_strcmp0 (parent1_info->file_id, file_info->file_id) == 0) {
5289 /* This commit does not modify the target file */
5290 goto out;
5291 }
5292
5293 /* In case of a merge, the second parent also need compare */
5294 if (commit->second_parent_id) {
5295 parent_commit2 = seaf_commit_manager_get_commit (seaf->commit_mgr,
5296 repo->id, repo->version,
5297 commit->second_parent_id);
5298 if (!parent_commit2) {
5299 seaf_warning ("Failed to get commit %s:%s\n",
5300 repo->id, commit->second_parent_id);
5301 ret = FALSE;
5302 goto out;
5303 }
5304
5305 parent2_info = get_file_info (data->repo, parent_commit2, path,
5306 file_info_cache, file_info, error);
5307 if (*error) {
5308 seaf_warning ("Error when finding %s under %s:%s\n",
5309 path, data->repo->id, parent_commit2->commit_id);
5310 ret = FALSE;
5311 goto out;
5312 }
5313
5314 if (parent2_info &&
5315 g_strcmp0 (parent2_info->file_id, file_info->file_id) == 0) {
5316 /* This commit does not modify the target file */
5317 goto out;
5318 }
5319 }
5320
5321 if (!data->got_latest) {
5322 data->got_latest = TRUE;
5323 } else {
5324 if (!data->got_second)
5325 data->got_second = TRUE;
5326 }
5327 add_revision_info (data, commit, file_info->file_id, file_info->file_size);
5328
5329 out:
5330 if (parent_commit) seaf_commit_unref (parent_commit);
5331 if (parent_commit2) seaf_commit_unref (parent_commit2);
5332
5333 g_hash_table_remove (file_info_cache, commit->commit_id);
5334
5335 return ret;
5336 }
5337
5338 static gboolean
path_exists_in_commit(SeafRepo * repo,const char * commit_id,const char * path)5339 path_exists_in_commit (SeafRepo *repo, const char *commit_id, const char *path)
5340 {
5341 SeafCommit *c = NULL;
5342 char *obj_id;
5343 guint32 mode;
5344
5345 c = seaf_commit_manager_get_commit (seaf->commit_mgr,
5346 repo->id, repo->version,
5347 commit_id);
5348 if (!c) {
5349 seaf_warning ("Failed to get commit %s:%.8s.\n", repo->id, commit_id);
5350 return FALSE;
5351 }
5352 obj_id = seaf_fs_manager_path_to_obj_id (seaf->fs_mgr,
5353 repo->store_id,
5354 repo->version,
5355 c->root_id,
5356 path,
5357 &mode,
5358 NULL);
5359 seaf_commit_unref (c);
5360 if (!obj_id)
5361 return FALSE;
5362 g_free (obj_id);
5363 return TRUE;
5364 }
5365
5366 static gboolean
detect_rename_revision(SeafRepo * repo,SeafCommit * commit,const char * path,char ** parent_id,char ** old_path)5367 detect_rename_revision (SeafRepo *repo,
5368 SeafCommit *commit,
5369 const char *path,
5370 char **parent_id,
5371 char **old_path)
5372 {
5373 GList *diff_res = NULL;
5374 SeafCommit *p1 = NULL;
5375 int rc;
5376 gboolean is_renamed = FALSE;
5377
5378 while (*path == '/' && *path != 0)
5379 ++path;
5380
5381 if (!commit->second_parent_id) {
5382 p1 = seaf_commit_manager_get_commit (seaf->commit_mgr,
5383 repo->id, repo->version,
5384 commit->parent_id);
5385 if (!p1) {
5386 seaf_warning ("Failed to get commit %s:%.8s.\n",
5387 repo->id, commit->parent_id);
5388 return FALSE;
5389 }
5390 /* Don't fold diff results for directories. We need to know a file was
5391 * renamed when its parent folder was renamed.
5392 */
5393 rc = diff_commits (p1, commit, &diff_res, FALSE);
5394 seaf_commit_unref (p1);
5395 if (rc < 0) {
5396 seaf_warning ("Failed to diff.\n");
5397 return FALSE;
5398 }
5399 } else {
5400 rc = diff_merge (commit, &diff_res, FALSE);
5401 if (rc < 0) {
5402 seaf_warning ("Failed to diff merge.\n");
5403 return FALSE;
5404 }
5405 }
5406
5407 GList *ptr;
5408 DiffEntry *de;
5409 for (ptr = diff_res; ptr; ptr = ptr->next) {
5410 de = ptr->data;
5411 if (de->status == DIFF_STATUS_RENAMED && strcmp (de->new_name, path) == 0) {
5412 *old_path = g_strdup(de->name);
5413 is_renamed = TRUE;
5414 break;
5415 }
5416 }
5417 for (ptr = diff_res; ptr; ptr = ptr->next)
5418 diff_entry_free ((DiffEntry *)ptr->data);
5419 g_list_free (diff_res);
5420
5421 if (!is_renamed)
5422 return FALSE;
5423
5424 /* Determine parent commit containing the old path. */
5425 if (!commit->second_parent_id)
5426 *parent_id = g_strdup(commit->parent_id);
5427 else {
5428 if (path_exists_in_commit (repo, commit->parent_id, *old_path))
5429 *parent_id = g_strdup(commit->parent_id);
5430 else if (path_exists_in_commit (repo, commit->second_parent_id, *old_path))
5431 *parent_id = g_strdup(commit->second_parent_id);
5432 else {
5433 g_free (*old_path);
5434 *old_path = NULL;
5435 return FALSE;
5436 }
5437 }
5438
5439 return TRUE;
5440 }
5441
5442 static SeafileCommit *
convert_to_seafile_commit(SeafCommit * c)5443 convert_to_seafile_commit (SeafCommit *c)
5444 {
5445 SeafileCommit *commit = seafile_commit_new ();
5446 g_object_set (commit,
5447 "id", c->commit_id,
5448 "creator_name", c->creator_name,
5449 "creator", c->creator_id,
5450 "desc", c->desc,
5451 "ctime", c->ctime,
5452 "repo_id", c->repo_id,
5453 "root_id", c->root_id,
5454 "parent_id", c->parent_id,
5455 "second_parent_id", c->second_parent_id,
5456 "version", c->version,
5457 "new_merge", c->new_merge,
5458 "conflict", c->conflict,
5459 "device_name", c->device_name,
5460 "client_version", c->client_version,
5461 NULL);
5462 return commit;
5463 }
5464
5465 static GList *
convert_rpc_commit_list(GList * commit_list,GList * file_id_list,GList * file_size_list,gboolean is_renamed,const char * renamed_old_path)5466 convert_rpc_commit_list (GList *commit_list,
5467 GList *file_id_list,
5468 GList *file_size_list,
5469 gboolean is_renamed,
5470 const char *renamed_old_path)
5471 {
5472 GList *ret = NULL;
5473 GList *ptr1, *ptr2, *ptr3;
5474 SeafCommit *c;
5475 char *file_id;
5476 gint64 *file_size;
5477 SeafileCommit *commit;
5478
5479 for (ptr1 = commit_list, ptr2 = file_id_list, ptr3 = file_size_list;
5480 ptr1 && ptr2 && ptr3;
5481 ptr1 = ptr1->next, ptr2 = ptr2->next, ptr3 = ptr3->next) {
5482 c = ptr1->data;
5483 file_id = ptr2->data;
5484 file_size = ptr3->data;
5485 commit = convert_to_seafile_commit (c);
5486 g_object_set (commit, "rev_file_id", file_id, "rev_file_size", *file_size,
5487 NULL);
5488 if (ptr1->next == NULL && is_renamed)
5489 g_object_set (commit, "rev_renamed_old_path", renamed_old_path, NULL);
5490 ret = g_list_prepend (ret, commit);
5491 }
5492
5493 ret = g_list_reverse (ret);
5494 return ret;
5495 }
5496
5497 GList *
seaf_repo_manager_list_file_revisions(SeafRepoManager * mgr,const char * repo_id,const char * start_commit_id,const char * path,int limit,gboolean got_latest,gboolean got_second,GError ** error)5498 seaf_repo_manager_list_file_revisions (SeafRepoManager *mgr,
5499 const char *repo_id,
5500 const char *start_commit_id,
5501 const char *path,
5502 int limit,
5503 gboolean got_latest,
5504 gboolean got_second,
5505 GError **error)
5506 {
5507 SeafRepo *repo = NULL;
5508 GList *commit_list = NULL, *file_id_list = NULL, *file_size_list = NULL;
5509 GList *ret = NULL, *ptr;
5510 CollectRevisionParam data = {0};
5511 SeafCommit *last_commit = NULL;
5512 const char *head_id;
5513 gboolean is_renamed = FALSE;
5514 char *parent_id = NULL, *old_path = NULL;
5515 char *next_start_commit= NULL;
5516
5517 repo = seaf_repo_manager_get_repo (mgr, repo_id);
5518 if (!repo) {
5519 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
5520 "No such repo %s", repo_id);
5521 goto out;
5522 }
5523
5524 data.repo = repo;
5525
5526 if (!start_commit_id)
5527 head_id = repo->head->commit_id;
5528 else
5529 head_id = start_commit_id;
5530
5531 data.path = path;
5532 data.error = error;
5533
5534 data.truncate_time = seaf_repo_manager_get_repo_truncate_time (mgr, repo_id);
5535
5536 data.wanted_commits = NULL;
5537 data.file_id_list = NULL;
5538 data.file_size_list = NULL;
5539 data.got_latest = got_latest;
5540 data.got_second = got_second;
5541 data.not_found_file = FALSE;
5542
5543 /* A hash table to cache caculated file info of <path> in <commit> */
5544 data.file_info_cache = g_hash_table_new_full (g_str_hash, g_str_equal,
5545 g_free, free_file_info);
5546
5547 if (!seaf_commit_manager_traverse_commit_tree_with_limit (seaf->commit_mgr,
5548 repo->id,
5549 repo->version,
5550 head_id,
5551 (CommitTraverseFunc)collect_file_revisions,
5552 limit, &data, &next_start_commit, TRUE)) {
5553 g_clear_error (error);
5554 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
5555 "failed to traverse commit of repo %s", repo_id);
5556 goto out;
5557 }
5558
5559 if (data.wanted_commits) {
5560 last_commit = data.wanted_commits->data;
5561 is_renamed = detect_rename_revision (repo,
5562 last_commit, path, &parent_id, &old_path);
5563 if (data.not_found_file && !is_renamed) { // reached file initial commit.
5564 g_free (next_start_commit);
5565 next_start_commit = NULL;
5566 } else if (is_renamed){ // file renamed.
5567 g_free (next_start_commit);
5568 next_start_commit = g_strdup (parent_id);
5569 }
5570 commit_list = g_list_reverse (data.wanted_commits);
5571 file_id_list = g_list_reverse (data.file_id_list);
5572 file_size_list = g_list_reverse (data.file_size_list);
5573
5574 char *rename_path = NULL;
5575 if (old_path && *old_path != '/')
5576 rename_path = g_strconcat ("/", old_path, NULL);
5577 else
5578 rename_path = g_strdup (old_path);
5579
5580 ret = convert_rpc_commit_list (commit_list, file_id_list, file_size_list,
5581 is_renamed, rename_path);
5582 g_free (rename_path);
5583 } else {
5584 if (data.not_found_file) {
5585 g_free (next_start_commit);
5586 next_start_commit = NULL;
5587 }
5588 }
5589
5590 /* Append one commit that only contains 'next_start_commit' */
5591 SeafileCommit *commit = seafile_commit_new ();
5592 g_object_set (commit, "next_start_commit", next_start_commit, NULL);
5593 ret = g_list_append (ret, commit);
5594
5595 out:
5596 if (repo)
5597 seaf_repo_unref (repo);
5598 for (ptr = commit_list; ptr; ptr = ptr->next)
5599 seaf_commit_unref ((SeafCommit *)ptr->data);
5600 g_list_free (commit_list);
5601 string_list_free (file_id_list);
5602 for (ptr = file_size_list; ptr; ptr = ptr->next)
5603 g_free (ptr->data);
5604 g_list_free (file_size_list);
5605 if (data.file_info_cache)
5606 g_hash_table_destroy (data.file_info_cache);
5607 g_free (old_path);
5608 g_free (parent_id);
5609 g_free (next_start_commit);
5610
5611 return ret;
5612 }
5613
5614 typedef struct CalcFilesLastModifiedParam CalcFilesLastModifiedParam;
5615
5616 struct CalcFilesLastModifiedParam {
5617 SeafRepo *repo;
5618 GError **error;
5619 const char *parent_dir;
5620 GHashTable *last_modified_hash;
5621 GHashTable *current_file_id_hash;
5622 SeafCommit *current_commit;
5623 };
5624
5625 static gboolean
check_non_existing_files(void * key,void * value,void * vdata)5626 check_non_existing_files (void *key, void *value, void *vdata)
5627 {
5628 CalcFilesLastModifiedParam *data = vdata;
5629 gboolean remove = FALSE;
5630
5631 char *file_name = key;
5632 gint64 *ctime = g_hash_table_lookup (data->last_modified_hash, file_name);
5633 if (!ctime) {
5634 /* Impossible */
5635 remove = TRUE;
5636 } else if (*ctime != data->current_commit->ctime) {
5637 /* This file does not exist in this commit. So it's last modified in
5638 * the previous commit.
5639 */
5640 remove = TRUE;
5641 }
5642
5643 return remove;
5644 }
5645
5646 static gboolean
collect_files_last_modified(SeafCommit * commit,void * vdata,gboolean * stop)5647 collect_files_last_modified (SeafCommit *commit, void *vdata, gboolean *stop)
5648 {
5649 CalcFilesLastModifiedParam *data = vdata;
5650 GError **error = data->error;
5651 SeafDirent *dent = NULL;
5652 char *file_id = NULL;
5653 SeafDir *dir = NULL;
5654 GList *ptr;
5655 gboolean ret = TRUE;
5656
5657 data->current_commit = commit;
5658 dir = seaf_fs_manager_get_seafdir_by_path (seaf->fs_mgr,
5659 data->repo->store_id,
5660 data->repo->version,
5661 commit->root_id,
5662 data->parent_dir,
5663 error);
5664 if (*error) {
5665 if (!g_error_matches(*error, SEAFILE_DOMAIN, SEAF_ERR_PATH_NO_EXIST)) {
5666 *stop = TRUE;
5667 ret = FALSE;
5668 goto out;
5669 } else {
5670 g_clear_error (error);
5671 }
5672 }
5673
5674 if (!dir) {
5675 /* The directory does not exist in this commit. So all files are last
5676 * modified in the previous commit;
5677 */
5678 *stop = TRUE;
5679 goto out;
5680 }
5681
5682 for (ptr = dir->entries; ptr; ptr = ptr->next) {
5683 dent = ptr->data;
5684 file_id = g_hash_table_lookup (data->current_file_id_hash, dent->name);
5685 if (file_id) {
5686 if (strcmp(file_id, dent->id) != 0) {
5687 g_hash_table_remove (data->current_file_id_hash, dent->name);
5688 } else {
5689 gint64 *ctime = g_new (gint64, 1);
5690 *ctime = commit->ctime;
5691 g_hash_table_replace (data->last_modified_hash, g_strdup(dent->name), ctime);
5692 }
5693 }
5694
5695 if (g_hash_table_size(data->current_file_id_hash) == 0) {
5696 *stop = TRUE;
5697 goto out;
5698 }
5699 }
5700
5701 /* Files not found in the current commit are last modified in the previous
5702 * commit */
5703 g_hash_table_foreach_remove (data->current_file_id_hash,
5704 check_non_existing_files, data);
5705
5706 if (g_hash_table_size(data->current_file_id_hash) == 0) {
5707 /* All files under this diretory have been calculated */
5708 *stop = TRUE;
5709 goto out;
5710 }
5711
5712 out:
5713 seaf_dir_free (dir);
5714
5715 return ret;
5716 }
5717
5718 /**
5719 * Give a directory, return the last modification timestamps of all the files
5720 * under this directory.
5721 *
5722 * First we record the current id of every file, then traverse the commit
5723 * tree. Give a commit, for each file, if the file id in that commit is
5724 * different than its current id, then this file is last modified in the
5725 * commit previous to that commit.
5726 */
5727 GList *
seaf_repo_manager_calc_files_last_modified(SeafRepoManager * mgr,const char * repo_id,const char * parent_dir,int limit,GError ** error)5728 seaf_repo_manager_calc_files_last_modified (SeafRepoManager *mgr,
5729 const char *repo_id,
5730 const char *parent_dir,
5731 int limit,
5732 GError **error)
5733 {
5734 SeafRepo *repo = NULL;
5735 SeafCommit *head_commit = NULL;
5736 SeafDir *dir = NULL;
5737 GList *ptr = NULL;
5738 SeafDirent *dent = NULL;
5739 CalcFilesLastModifiedParam data = {0};
5740 GList *ret_list = NULL;
5741
5742 repo = seaf_repo_manager_get_repo (mgr, repo_id);
5743 if (!repo) {
5744 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
5745 "No such repo %s", repo_id);
5746 goto out;
5747 }
5748
5749 head_commit = seaf_commit_manager_get_commit (seaf->commit_mgr,
5750 repo->id, repo->version,
5751 repo->head->commit_id);
5752 if (!head_commit) {
5753 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
5754 "Failed to get commit %s", repo->head->commit_id);
5755 goto out;
5756 }
5757
5758 dir = seaf_fs_manager_get_seafdir_by_path (seaf->fs_mgr,
5759 repo->store_id, repo->version,
5760 head_commit->root_id,
5761 parent_dir, error);
5762 if (*error || !dir) {
5763 goto out;
5764 }
5765
5766 data.repo = repo;
5767
5768 /* A hash table of pattern (file_name, current_file_id) */
5769 data.current_file_id_hash = g_hash_table_new_full (g_str_hash, g_str_equal,
5770 g_free, g_free);
5771 /* A (file_name, last_modified) hashtable. <last_modified> is a heap
5772 allocated gint64
5773 */
5774 data.last_modified_hash = g_hash_table_new_full (g_str_hash, g_str_equal,
5775 g_free, g_free);
5776 for (ptr = dir->entries; ptr; ptr = ptr->next) {
5777 dent = ptr->data;
5778 g_hash_table_insert (data.current_file_id_hash,
5779 g_strdup(dent->name),
5780 g_strdup(dent->id));
5781
5782 gint64 *ctime = g_new (gint64, 1);
5783 *ctime = head_commit->ctime;
5784 g_hash_table_insert (data.last_modified_hash,
5785 g_strdup(dent->name),
5786 ctime);
5787 }
5788
5789 if (g_hash_table_size (data.current_file_id_hash) == 0) {
5790 /* An empty directory, no need to traverse */
5791 goto out;
5792 }
5793
5794 data.parent_dir = parent_dir;
5795 data.error = error;
5796
5797 if (!seaf_commit_manager_traverse_commit_tree_with_limit (seaf->commit_mgr,
5798 repo->id, repo->version,
5799 repo->head->commit_id,
5800 (CommitTraverseFunc)collect_files_last_modified,
5801 limit, &data, NULL, FALSE)) {
5802 if (*error)
5803 seaf_warning ("error when traversing commits: %s\n", (*error)->message);
5804 else
5805 seaf_warning ("error when traversing commits.\n");
5806 g_clear_error (error);
5807 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
5808 "failed to traverse commit of repo %s", repo_id);
5809 goto out;
5810 }
5811
5812 GHashTableIter iter;
5813 gpointer key, value;
5814
5815 g_hash_table_iter_init (&iter, data.last_modified_hash);
5816 while (g_hash_table_iter_next (&iter, &key, &value)) {
5817 SeafileFileLastModifiedInfo *info;
5818 gint64 last_modified = *(gint64 *)value;
5819 info = g_object_new (SEAFILE_TYPE_FILE_LAST_MODIFIED_INFO,
5820 "file_name", key,
5821 "last_modified", last_modified,
5822 NULL);
5823 ret_list = g_list_prepend (ret_list, info);
5824 }
5825
5826 out:
5827 if (repo)
5828 seaf_repo_unref (repo);
5829 if (head_commit)
5830 seaf_commit_unref(head_commit);
5831 if (data.last_modified_hash)
5832 g_hash_table_destroy (data.last_modified_hash);
5833 if (data.current_file_id_hash)
5834 g_hash_table_destroy (data.current_file_id_hash);
5835 if (dir)
5836 seaf_dir_free (dir);
5837
5838 return g_list_reverse(ret_list);
5839 }
5840
5841 int
seaf_repo_manager_revert_on_server(SeafRepoManager * mgr,const char * repo_id,const char * commit_id,const char * user_name,GError ** error)5842 seaf_repo_manager_revert_on_server (SeafRepoManager *mgr,
5843 const char *repo_id,
5844 const char *commit_id,
5845 const char *user_name,
5846 GError **error)
5847 {
5848 SeafRepo *repo;
5849 SeafCommit *commit = NULL, *new_commit = NULL;
5850 char desc[512];
5851 int ret = 0;
5852
5853 retry:
5854 repo = seaf_repo_manager_get_repo (mgr, repo_id);
5855 if (!repo) {
5856 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
5857 "No such repo");
5858 return -1;
5859 }
5860
5861 commit = seaf_commit_manager_get_commit (seaf->commit_mgr,
5862 repo->id, repo->version,
5863 commit_id);
5864 if (!commit) {
5865 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
5866 "Commit doesn't exist");
5867 ret = -1;
5868 goto out;
5869 }
5870
5871 #ifndef WIN32
5872 strftime (desc, sizeof(desc), "Reverted repo to status at %F %T.",
5873 localtime((time_t *)(&commit->ctime)));
5874 #else
5875 strftime (desc, sizeof(desc), "Reverted repo to status at %Y-%m-%d %H:%M:%S.",
5876 localtime((time_t *)(&commit->ctime)));
5877 #endif
5878
5879 new_commit = seaf_commit_new (NULL, repo->id, commit->root_id,
5880 user_name, EMPTY_SHA1,
5881 desc, 0);
5882
5883 new_commit->parent_id = g_strdup (repo->head->commit_id);
5884 seaf_repo_to_commit (repo, new_commit);
5885
5886 if (seaf_commit_manager_add_commit (seaf->commit_mgr, new_commit) < 0) {
5887 ret = -1;
5888 goto out;
5889 }
5890
5891 seaf_branch_set_commit (repo->head, new_commit->commit_id);
5892 if (seaf_branch_manager_test_and_update_branch (seaf->branch_mgr,
5893 repo->head,
5894 new_commit->parent_id) < 0)
5895 {
5896 seaf_repo_unref (repo);
5897 seaf_commit_unref (commit);
5898 seaf_commit_unref (new_commit);
5899 repo = NULL;
5900 commit = new_commit = NULL;
5901 goto retry;
5902 }
5903
5904 seaf_repo_manager_merge_virtual_repo (mgr, repo_id, NULL);
5905
5906 out:
5907 if (new_commit)
5908 seaf_commit_unref (new_commit);
5909 if (commit)
5910 seaf_commit_unref (commit);
5911 if (repo)
5912 seaf_repo_unref (repo);
5913
5914 if (ret == 0) {
5915 update_repo_size (repo_id);
5916 }
5917
5918 return ret;
5919 }
5920
5921 static void
add_deleted_entry(SeafRepo * repo,GHashTable * entries,SeafDirent * dent,const char * base,SeafCommit * child,SeafCommit * parent)5922 add_deleted_entry (SeafRepo *repo,
5923 GHashTable *entries,
5924 SeafDirent *dent,
5925 const char *base,
5926 SeafCommit *child,
5927 SeafCommit *parent)
5928 {
5929 char *path = g_strconcat (base, dent->name, NULL);
5930 SeafileDeletedEntry *entry;
5931 Seafile *file;
5932
5933 if (g_hash_table_lookup (entries, path) != NULL) {
5934 /* g_debug ("found dup deleted entry for %s.\n", path); */
5935 g_free (path);
5936 return;
5937 }
5938
5939 /* g_debug ("Add deleted entry for %s.\n", path); */
5940
5941 entry = g_object_new (SEAFILE_TYPE_DELETED_ENTRY,
5942 "commit_id", parent->commit_id,
5943 "obj_id", dent->id,
5944 "obj_name", dent->name,
5945 "basedir", base,
5946 "mode", dent->mode,
5947 "delete_time", child->ctime,
5948 NULL);
5949
5950 if (S_ISREG(dent->mode)) {
5951 file = seaf_fs_manager_get_seafile (seaf->fs_mgr,
5952 repo->store_id, repo->version,
5953 dent->id);
5954 if (!file) {
5955 g_free (path);
5956 g_object_unref (entry);
5957 return;
5958 }
5959 g_object_set (entry, "file_size", file->file_size, NULL);
5960 seafile_unref (file);
5961 }
5962
5963 g_hash_table_insert (entries, path, entry);
5964 }
5965
5966 static int
find_deleted_recursive(SeafRepo * repo,SeafDir * d1,SeafDir * d2,const char * base,SeafCommit * child,SeafCommit * parent,GHashTable * entries)5967 find_deleted_recursive (SeafRepo *repo,
5968 SeafDir *d1,
5969 SeafDir *d2,
5970 const char *base,
5971 SeafCommit *child,
5972 SeafCommit *parent,
5973 GHashTable *entries)
5974 {
5975 GList *p1, *p2;
5976 SeafDirent *dent1, *dent2;
5977 int res, ret = 0;
5978
5979 p1 = d1->entries;
5980 p2 = d2->entries;
5981
5982 /* Since dirents are sorted in descending order, we can use merge
5983 * algorithm to find out deleted entries.
5984 * Deleted entries are those:
5985 * 1. exists in d2 but absent in d1.
5986 * 2. exists in both d1 and d2 but with different type.
5987 */
5988
5989 while (p1 && p2) {
5990 dent1 = p1->data;
5991 dent2 = p2->data;
5992
5993 res = g_strcmp0 (dent1->name, dent2->name);
5994 if (res < 0) {
5995 /* exists in d2 but absent in d1. */
5996 add_deleted_entry (repo, entries, dent2, base, child, parent);
5997 p2 = p2->next;
5998 } else if (res == 0) {
5999 if ((dent1->mode & S_IFMT) != (dent2->mode & S_IFMT)) {
6000 /* both exists but with diffent type. */
6001 add_deleted_entry (repo, entries, dent2, base, child, parent);
6002 } else if (S_ISDIR(dent1->mode) && strcmp(dent1->id, dent2->id) != 0) {
6003 SeafDir *n1 = seaf_fs_manager_get_seafdir_sorted (seaf->fs_mgr,
6004 repo->store_id,
6005 repo->version,
6006 dent1->id);
6007 if (!n1) {
6008 seaf_warning ("Failed to find dir %s:%s.\n", repo->id, dent1->id);
6009 return -1;
6010 }
6011
6012 SeafDir *n2 = seaf_fs_manager_get_seafdir_sorted (seaf->fs_mgr,
6013 repo->store_id,
6014 repo->version,
6015 dent2->id);
6016 if (!n2) {
6017 seaf_warning ("Failed to find dir %s:%s.\n", repo->id, dent2->id);
6018 seaf_dir_free (n1);
6019 return -1;
6020 }
6021
6022 char *new_base = g_strconcat (base, dent1->name, "/", NULL);
6023 ret = find_deleted_recursive (repo, n1, n2, new_base,
6024 child, parent, entries);
6025 g_free (new_base);
6026 seaf_dir_free (n1);
6027 seaf_dir_free (n2);
6028 if (ret < 0)
6029 return ret;
6030 }
6031 p1 = p1->next;
6032 p2 = p2->next;
6033 } else {
6034 p1 = p1->next;
6035 }
6036 }
6037
6038 for ( ; p2 != NULL; p2 = p2->next) {
6039 dent2 = p2->data;
6040 add_deleted_entry (repo, entries, dent2, base, child, parent);
6041 }
6042
6043 return ret;
6044 }
6045
6046 static int
find_deleted(SeafRepo * repo,SeafCommit * child,SeafCommit * parent,const char * base,GHashTable * entries)6047 find_deleted (SeafRepo *repo,
6048 SeafCommit *child,
6049 SeafCommit *parent,
6050 const char *base,
6051 GHashTable *entries)
6052 {
6053 SeafDir *d1, *d2;
6054 int ret = 0;
6055
6056 d1 = seaf_fs_manager_get_seafdir_sorted_by_path (seaf->fs_mgr,
6057 repo->store_id,
6058 repo->version,
6059 child->root_id, base);
6060 if (!d1) {
6061 return ret;
6062 }
6063
6064 d2 = seaf_fs_manager_get_seafdir_sorted_by_path (seaf->fs_mgr,
6065 repo->store_id,
6066 repo->version,
6067 parent->root_id, base);
6068 if (!d2) {
6069 seaf_dir_free (d1);
6070 return ret;
6071 }
6072
6073 ret = find_deleted_recursive (repo, d1, d2, base, child, parent, entries);
6074
6075 seaf_dir_free (d2);
6076 seaf_dir_free (d1);
6077
6078 return ret;
6079 }
6080
6081 typedef struct CollectDelData {
6082 SeafRepo *repo;
6083 GHashTable *entries;
6084 gint64 truncate_time;
6085 char *path;
6086 } CollectDelData;
6087
6088 #define DEFAULT_RECYCLE_DAYS 7
6089
6090 static gboolean
collect_deleted(SeafCommit * commit,void * vdata,gboolean * stop)6091 collect_deleted (SeafCommit *commit, void *vdata, gboolean *stop)
6092 {
6093 CollectDelData *data = vdata;
6094 SeafRepo *repo = data->repo;
6095 GHashTable *entries = data->entries;
6096 gint64 truncate_time = data->truncate_time;
6097 SeafCommit *p1, *p2;
6098
6099 /* We use <= here. This is for handling clean trash and history.
6100 * If the user cleans all history, truncate time will be equal to
6101 * the head commit's ctime. In such case, we don't actually want to display
6102 * any deleted file.
6103 */
6104 if ((gint64)(commit->ctime) <= truncate_time) {
6105 *stop = TRUE;
6106 return TRUE;
6107 }
6108
6109 if (commit->parent_id == NULL)
6110 return TRUE;
6111
6112 if (!(strstr (commit->desc, PREFIX_DEL_FILE) != NULL ||
6113 strstr (commit->desc, PREFIX_DEL_DIR) != NULL ||
6114 strstr (commit->desc, PREFIX_DEL_DIRS) != NULL)) {
6115 return TRUE;
6116 }
6117
6118 p1 = seaf_commit_manager_get_commit (commit->manager,
6119 repo->id, repo->version,
6120 commit->parent_id);
6121 if (!p1) {
6122 seaf_warning ("Failed to find commit %s:%s.\n", repo->id, commit->parent_id);
6123 return FALSE;
6124 }
6125
6126 if (find_deleted (data->repo, commit, p1, data->path, entries) < 0) {
6127 seaf_commit_unref (p1);
6128 return FALSE;
6129 }
6130
6131 seaf_commit_unref (p1);
6132
6133 if (commit->second_parent_id) {
6134 p2 = seaf_commit_manager_get_commit (commit->manager,
6135 repo->id, repo->version,
6136 commit->second_parent_id);
6137 if (!p2) {
6138 seaf_warning ("Failed to find commit %s:%s.\n",
6139 repo->id, commit->second_parent_id);
6140 return FALSE;
6141 }
6142
6143 if (find_deleted (data->repo, commit, p2, data->path, entries) < 0) {
6144 seaf_commit_unref (p2);
6145 return FALSE;
6146 }
6147
6148 seaf_commit_unref (p2);
6149 }
6150
6151 return TRUE;
6152 }
6153
6154 typedef struct RemoveExistingParam {
6155 SeafRepo *repo;
6156 SeafCommit *head;
6157 } RemoveExistingParam;
6158
6159 static gboolean
remove_existing(gpointer key,gpointer value,gpointer user_data)6160 remove_existing (gpointer key, gpointer value, gpointer user_data)
6161 {
6162 SeafileDeletedEntry *e = value;
6163 RemoveExistingParam *param = user_data;
6164 SeafRepo *repo = param->repo;
6165 SeafCommit *head = param->head;
6166 guint32 mode = seafile_deleted_entry_get_mode(e), mode_out = 0;
6167 char *path = key;
6168
6169 char *obj_id = seaf_fs_manager_path_to_obj_id (seaf->fs_mgr,
6170 repo->store_id, repo->version,
6171 head->root_id,
6172 path, &mode_out, NULL);
6173 if (obj_id == NULL)
6174 return FALSE;
6175 g_free (obj_id);
6176
6177 /* If path exist in head commit and with the same type,
6178 * remove it from deleted entries.
6179 */
6180 if ((mode & S_IFMT) == (mode_out & S_IFMT)) {
6181 /* g_debug ("%s exists in head commit.\n", path); */
6182 return TRUE;
6183 }
6184
6185 return FALSE;
6186 }
6187
6188 static int
filter_out_existing_entries(GHashTable * entries,SeafRepo * repo,const char * head_id)6189 filter_out_existing_entries (GHashTable *entries,
6190 SeafRepo *repo,
6191 const char *head_id)
6192 {
6193 SeafCommit *head;
6194 RemoveExistingParam param;
6195
6196 head = seaf_commit_manager_get_commit (seaf->commit_mgr,
6197 repo->id, repo->version,
6198 head_id);
6199 if (!head) {
6200 seaf_warning ("Failed to find head commit %s of repo %s.\n",
6201 head_id, repo->id);
6202 return -1;
6203 }
6204
6205 param.repo = repo;
6206 param.head = head;
6207
6208 g_hash_table_foreach_remove (entries, remove_existing, ¶m);
6209
6210 seaf_commit_unref (head);
6211 return 0;
6212 }
6213
6214 static gboolean
hash_to_list(gpointer key,gpointer value,gpointer user_data)6215 hash_to_list (gpointer key, gpointer value, gpointer user_data)
6216 {
6217 GList **plist = (GList **)user_data;
6218
6219 g_free (key);
6220 *plist = g_list_prepend (*plist, value);
6221
6222 return TRUE;
6223 }
6224
6225 static gint
compare_commit_by_time(gconstpointer a,gconstpointer b,gpointer unused)6226 compare_commit_by_time (gconstpointer a, gconstpointer b, gpointer unused)
6227 {
6228 const SeafCommit *commit_a = a;
6229 const SeafCommit *commit_b = b;
6230
6231 /* Latest commit comes first in the list. */
6232 return (commit_b->ctime - commit_a->ctime);
6233 }
6234
6235 static int
insert_parent_commit(GList ** list,GHashTable * hash,const char * repo_id,int version,const char * parent_id)6236 insert_parent_commit (GList **list, GHashTable *hash,
6237 const char *repo_id, int version,
6238 const char *parent_id)
6239 {
6240 SeafCommit *p;
6241 char *key;
6242
6243 if (g_hash_table_lookup (hash, parent_id) != NULL)
6244 return 0;
6245
6246 p = seaf_commit_manager_get_commit (seaf->commit_mgr,
6247 repo_id, version,
6248 parent_id);
6249 if (!p) {
6250 seaf_warning ("Failed to find commit %s\n", parent_id);
6251 return -1;
6252 }
6253
6254 *list = g_list_insert_sorted_with_data (*list, p,
6255 compare_commit_by_time,
6256 NULL);
6257
6258 key = g_strdup (parent_id);
6259 g_hash_table_replace (hash, key, key);
6260
6261 return 0;
6262 }
6263
6264 static GList *
scan_stat_to_list(const char * scan_stat,GHashTable * commit_hash,SeafRepo * repo)6265 scan_stat_to_list(const char *scan_stat, GHashTable *commit_hash, SeafRepo *repo)
6266 {
6267 json_t *commit_array = NULL, *commit_obj = NULL;
6268 char *commit_id = NULL;
6269 SeafCommit *commit = NULL;
6270 GList *list = NULL;
6271 char *key;
6272 commit_array = json_loadb (scan_stat, strlen(scan_stat), 0, NULL);
6273 if (!commit_array) {
6274 return NULL;
6275 }
6276 int i;
6277 for (i = 0; i < json_array_size (commit_array); i++) {
6278 commit_obj = json_array_get (commit_array, i);
6279 commit_id = json_string_value (commit_obj);
6280 if (commit_id && strlen(commit_id) == 40) {
6281 commit = seaf_commit_manager_get_commit (seaf->commit_mgr, repo->id,
6282 repo->version, commit_id);
6283 if (!commit) {
6284 return NULL;
6285 }
6286 list = g_list_prepend (list, commit);
6287 key = g_strdup (commit->commit_id);
6288 g_hash_table_replace (commit_hash, key, key);
6289 }
6290 }
6291 json_decref (commit_array);
6292 list = g_list_sort (list, compare_commit_by_time);
6293 return list;
6294 }
6295
6296 static int
scan_commits_for_collect_deleted(CollectDelData * data,const char * prev_scan_stat,int limit,char ** next_scan_stat)6297 scan_commits_for_collect_deleted (CollectDelData *data,
6298 const char *prev_scan_stat,
6299 int limit,
6300 char **next_scan_stat)
6301 {
6302 GList *list = NULL;
6303 SeafCommit *commit;
6304 GHashTable *commit_hash;
6305 SeafRepo *repo = data->repo;
6306 int scan_num = 0;
6307 gboolean ret = TRUE;
6308
6309 /* A hash table for recording id of traversed commits. */
6310 commit_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
6311
6312 if (prev_scan_stat == NULL) {
6313 commit = seaf_commit_manager_get_commit (seaf->commit_mgr, repo->id,
6314 repo->version, repo->head->commit_id);
6315 if (!commit) {
6316 ret = FALSE;
6317 goto out;
6318 }
6319 list = g_list_prepend (list, commit);
6320 char *key = g_strdup (commit->commit_id);
6321 g_hash_table_replace (commit_hash, key, key);
6322 } else {
6323 list = scan_stat_to_list (prev_scan_stat, commit_hash, repo);
6324 if (list == NULL) {
6325 ret = FALSE;
6326 goto out;
6327 }
6328 }
6329
6330 while (list) {
6331 gboolean stop = FALSE;
6332 commit = list->data;
6333 list = g_list_delete_link (list, list);
6334
6335 if (!collect_deleted (commit, data, &stop)) {
6336 seaf_warning("[comit-mgr] CommitTraverseFunc failed\n");
6337 seaf_commit_unref (commit);
6338 ret = FALSE;
6339 goto out;
6340 }
6341
6342 if (stop) {
6343 seaf_commit_unref (commit);
6344 /* stop traverse down from this commit,
6345 * but not stop traversing the tree
6346 */
6347 continue;
6348 }
6349
6350 if (commit->parent_id) {
6351 if (insert_parent_commit (&list, commit_hash, repo->id,
6352 repo->version,
6353 commit->parent_id) < 0) {
6354 seaf_warning("[comit-mgr] insert parent commit failed\n");
6355 seaf_commit_unref (commit);
6356 ret = FALSE;
6357 goto out;
6358 }
6359 }
6360 if (commit->second_parent_id) {
6361 if (insert_parent_commit (&list, commit_hash, repo->id,
6362 repo->version,
6363 commit->second_parent_id) < 0) {
6364 seaf_warning("[comit-mgr]insert second parent commit failed\n");
6365 seaf_commit_unref (commit);
6366 ret = FALSE;
6367 goto out;
6368 }
6369 }
6370 seaf_commit_unref (commit);
6371
6372 if (++scan_num >= limit) {
6373 break;
6374 }
6375 }
6376
6377 json_t *commit_array = json_array ();
6378 while (list) {
6379 commit = list->data;
6380 json_array_append_new (commit_array, json_string (commit->commit_id));
6381 seaf_commit_unref (commit);
6382 list = g_list_delete_link (list, list);
6383 }
6384 if (json_array_size(commit_array) > 0) {
6385 char *commits = json_dumps (commit_array, JSON_COMPACT);
6386 *next_scan_stat = commits;
6387 }
6388 json_decref (commit_array);
6389 g_hash_table_destroy (commit_hash);
6390
6391 return ret;
6392
6393 out:
6394 g_hash_table_destroy (commit_hash);
6395 while (list) {
6396 commit = list->data;
6397 seaf_commit_unref (commit);
6398 list = g_list_delete_link (list, list);
6399 }
6400
6401 return ret;
6402 }
6403
6404 GList *
seaf_repo_manager_get_deleted_entries(SeafRepoManager * mgr,const char * repo_id,int show_days,const char * path,const char * scan_stat,int limit,GError ** error)6405 seaf_repo_manager_get_deleted_entries (SeafRepoManager *mgr,
6406 const char *repo_id,
6407 int show_days,
6408 const char *path,
6409 const char *scan_stat,
6410 int limit,
6411 GError **error)
6412 {
6413 SeafRepo *repo;
6414 gint64 truncate_time, show_time;
6415 GList *ret = NULL;
6416 char *next_scan_stat = NULL;
6417
6418 truncate_time = seaf_repo_manager_get_repo_truncate_time (mgr, repo_id);
6419 if (truncate_time == 0) {
6420 // Don't keep history, set scan_stat as NULL, indicate no need for next scan
6421 ret = g_list_append (ret, g_object_new (SEAFILE_TYPE_DELETED_ENTRY,
6422 "scan_stat", NULL,
6423 NULL));
6424 return ret;
6425 }
6426
6427 if (show_days <= 0)
6428 show_time = -1;
6429 else
6430 show_time = (gint64)time(NULL) - show_days * 24 * 3600;
6431
6432 repo = seaf_repo_manager_get_repo (mgr, repo_id);
6433 if (!repo) {
6434 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
6435 "Invalid repo id");
6436 return NULL;
6437 }
6438
6439 CollectDelData data = {0};
6440 GHashTable *entries = g_hash_table_new_full (g_str_hash, g_str_equal,
6441 g_free, g_object_unref);
6442 data.repo = repo;
6443 data.entries = entries;
6444 data.truncate_time = MAX (show_time, truncate_time);
6445 if (path) {
6446 if (path[strlen(path) - 1] == '/') {
6447 data.path = g_strdup (path);
6448 } else {
6449 data.path = g_strconcat (path, "/", NULL);
6450 }
6451 } else {
6452 data.path = g_strdup ("/");
6453 }
6454
6455 if (!scan_commits_for_collect_deleted (&data, scan_stat, limit, &next_scan_stat)) {
6456 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_INTERNAL,
6457 "Internal error");
6458 g_hash_table_destroy (entries);
6459 seaf_repo_unref (repo);
6460 g_free (data.path);
6461 return NULL;
6462 }
6463
6464 /* Remove entries exist in the current commit.
6465 * This is necessary because some files may be added back after deletion.
6466 */
6467 if (filter_out_existing_entries (entries, repo,
6468 repo->head->commit_id) == 0) {
6469 // filter success, then add collected result to list
6470 g_hash_table_foreach_steal (entries, hash_to_list, &ret);
6471 }
6472
6473 // Append scan_stat entry to the end to indicate the end of scan result
6474 ret = g_list_append (ret, g_object_new (SEAFILE_TYPE_DELETED_ENTRY,
6475 "scan_stat", next_scan_stat,
6476 NULL));
6477
6478 g_hash_table_destroy (entries);
6479
6480 seaf_repo_unref (repo);
6481 g_free (data.path);
6482
6483 return ret;
6484 }
6485
6486 static SeafCommit *
get_commit(SeafRepo * repo,const char * branch_or_commit)6487 get_commit(SeafRepo *repo, const char *branch_or_commit)
6488 {
6489 SeafBranch *b;
6490 SeafCommit *c;
6491
6492 b = seaf_branch_manager_get_branch (seaf->branch_mgr, repo->id,
6493 branch_or_commit);
6494 if (!b) {
6495 if (strcmp(branch_or_commit, "HEAD") == 0)
6496 c = seaf_commit_manager_get_commit (seaf->commit_mgr,
6497 repo->id, repo->version,
6498 repo->head->commit_id);
6499 else
6500 c = seaf_commit_manager_get_commit (seaf->commit_mgr,
6501 repo->id, repo->version,
6502 branch_or_commit);
6503 } else {
6504 c = seaf_commit_manager_get_commit (seaf->commit_mgr,
6505 repo->id, repo->version,
6506 b->commit_id);
6507 }
6508
6509 if (b)
6510 seaf_branch_unref (b);
6511
6512 return c;
6513 }
6514
6515 GList *
seaf_repo_diff(SeafRepo * repo,const char * old,const char * new,int fold_dir_results,char ** error)6516 seaf_repo_diff (SeafRepo *repo, const char *old, const char *new, int fold_dir_results, char **error)
6517 {
6518 SeafCommit *c1 = NULL, *c2 = NULL;
6519 int ret = 0;
6520 GList *diff_entries = NULL;
6521
6522 g_return_val_if_fail (*error == NULL, NULL);
6523
6524 c2 = get_commit (repo, new);
6525 if (!c2) {
6526 *error = g_strdup("Can't find new commit");
6527 return NULL;
6528 }
6529
6530 if (old == NULL || old[0] == '\0') {
6531 if (c2->parent_id && c2->second_parent_id) {
6532 ret = diff_merge (c2, &diff_entries, fold_dir_results);
6533 seaf_commit_unref (c2);
6534 if (ret < 0) {
6535 *error = g_strdup("Failed to do diff");
6536 g_list_free_full (diff_entries, (GDestroyNotify)diff_entry_free);
6537 return NULL;
6538 }
6539 return diff_entries;
6540 }
6541
6542 if (!c2->parent_id) {
6543 seaf_commit_unref (c2);
6544 return NULL;
6545 }
6546 c1 = seaf_commit_manager_get_commit (seaf->commit_mgr,
6547 repo->id, repo->version,
6548 c2->parent_id);
6549 } else {
6550 c1 = get_commit (repo, old);
6551 }
6552
6553 if (!c1) {
6554 *error = g_strdup("Can't find old commit");
6555 seaf_commit_unref (c2);
6556 return NULL;
6557 }
6558
6559 /* do diff */
6560 ret = diff_commits (c1, c2, &diff_entries, fold_dir_results);
6561 if (ret < 0) {
6562 g_list_free_full (diff_entries, (GDestroyNotify)diff_entry_free);
6563 diff_entries = NULL;
6564 *error = g_strdup("Failed to do diff");
6565 }
6566
6567 seaf_commit_unref (c1);
6568 seaf_commit_unref (c2);
6569
6570 return diff_entries;
6571 }
6572
6573