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, &param);
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