1 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 
3 #include "common.h"
4 #include <glib/gstdio.h>
5 
6 #ifdef WIN32
7 #include <windows.h>
8 #include <shlobj.h>
9 #endif
10 
11 #include <pthread.h>
12 
13 #include "utils.h"
14 #define DEBUG_FLAG SEAFILE_DEBUG_SYNC
15 #include "log.h"
16 
17 #include "vc-utils.h"
18 
19 #include "seafile-session.h"
20 #include "seafile-config.h"
21 #include "commit-mgr.h"
22 #include "branch-mgr.h"
23 #include "repo-mgr.h"
24 #include "fs-mgr.h"
25 #include "seafile-error.h"
26 #include "seafile-crypt.h"
27 #include "index/index.h"
28 #include "index/cache-tree.h"
29 #include "diff-simple.h"
30 #include "change-set.h"
31 
32 #include "db.h"
33 
34 #include "seafile-object.h"
35 
36 #define INDEX_DIR "index"
37 #define IGNORE_FILE "seafile-ignore.txt"
38 
39 #ifdef HAVE_KEYSTORAGE_GK
40 #include "repokey/seafile-gnome-keyring.h"
41 #endif // HAVE_KEYSTORAGE_GK
42 
43 #ifndef SEAFILE_CLIENT_VERSION
44 #define SEAFILE_CLIENT_VERSION PACKAGE_VERSION
45 #endif
46 
47 struct _SeafRepoManagerPriv {
48     GHashTable *repo_hash;
49     sqlite3    *db;
50     pthread_mutex_t db_lock;
51     GHashTable *checkout_tasks_hash;
52     pthread_rwlock_t lock;
53 
54     GHashTable *user_perms;     /* repo_id -> folder user perms */
55     GHashTable *group_perms;    /* repo_id -> folder group perms */
56     pthread_mutex_t perm_lock;
57 
58     GAsyncQueue *lock_office_job_queue;
59 };
60 
61 static const char *ignore_table[] = {
62     /* tmp files under Linux */
63     "*~",
64     /* Seafile's backup file */
65     "*.sbak",
66     /* Emacs tmp files */
67     "#*#",
68     /* windows image cache */
69     "Thumbs.db",
70     /* For Mac */
71     ".DS_Store",
72     "._*",
73     NULL,
74 };
75 
76 #define CONFLICT_PATTERN " \\(SFConflict .+\\)"
77 
78 #define OFFICE_LOCK_PATTERN "~\\$(.+)$"
79 
80 static GPatternSpec** ignore_patterns;
81 static GPatternSpec* office_temp_ignore_patterns[4];
82 static GRegex *conflict_pattern = NULL;
83 static GRegex *office_lock_pattern = NULL;
84 
85 static SeafRepo *
86 load_repo (SeafRepoManager *manager, const char *repo_id);
87 
88 static void load_repos (SeafRepoManager *manager, const char *seaf_dir);
89 static void seaf_repo_manager_del_repo_property (SeafRepoManager *manager,
90                                                  const char *repo_id);
91 
92 static int save_branch_repo_map (SeafRepoManager *manager, SeafBranch *branch);
93 static void save_repo_property (SeafRepoManager *manager,
94                                 const char *repo_id,
95                                 const char *key, const char *value);
96 
97 static void
locked_file_free(LockedFile * file)98 locked_file_free (LockedFile *file)
99 {
100     if (!file)
101         return;
102     g_free (file->operation);
103     g_free (file);
104 }
105 
106 static gboolean
load_locked_file(sqlite3_stmt * stmt,void * data)107 load_locked_file (sqlite3_stmt *stmt, void *data)
108 {
109     GHashTable *ret = data;
110     LockedFile *file;
111     const char *path, *operation, *file_id;
112     gint64 old_mtime;
113 
114     path = (const char *)sqlite3_column_text (stmt, 0);
115     operation = (const char *)sqlite3_column_text (stmt, 1);
116     old_mtime = sqlite3_column_int64 (stmt, 2);
117     file_id = (const char *)sqlite3_column_text (stmt, 3);
118 
119     file = g_new0 (LockedFile, 1);
120     file->operation = g_strdup(operation);
121     file->old_mtime = old_mtime;
122     if (file_id)
123         memcpy (file->file_id, file_id, 40);
124 
125     g_hash_table_insert (ret, g_strdup(path), file);
126 
127     return TRUE;
128 }
129 
130 LockedFileSet *
seaf_repo_manager_get_locked_file_set(SeafRepoManager * mgr,const char * repo_id)131 seaf_repo_manager_get_locked_file_set (SeafRepoManager *mgr, const char *repo_id)
132 {
133     GHashTable *locked_files = g_hash_table_new_full (g_str_hash, g_str_equal,
134                                                       g_free,
135                                                       (GDestroyNotify)locked_file_free);
136     char sql[256];
137 
138     sqlite3_snprintf (sizeof(sql), sql,
139                       "SELECT path, operation, old_mtime, file_id FROM LockedFiles "
140                       "WHERE repo_id = '%q'",
141                       repo_id);
142 
143     pthread_mutex_lock (&mgr->priv->db_lock);
144 
145     /* Ingore database error. We return an empty set on error. */
146     sqlite_foreach_selected_row (mgr->priv->db, sql,
147                                  load_locked_file, locked_files);
148 
149     pthread_mutex_unlock (&mgr->priv->db_lock);
150 
151     LockedFileSet *ret = g_new0 (LockedFileSet, 1);
152     ret->mgr = mgr;
153     memcpy (ret->repo_id, repo_id, 36);
154     ret->locked_files = locked_files;
155 
156     return ret;
157 }
158 
159 void
locked_file_set_free(LockedFileSet * fset)160 locked_file_set_free (LockedFileSet *fset)
161 {
162     if (!fset)
163         return;
164     g_hash_table_destroy (fset->locked_files);
165     g_free (fset);
166 }
167 
168 int
locked_file_set_add_update(LockedFileSet * fset,const char * path,const char * operation,gint64 old_mtime,const char * file_id)169 locked_file_set_add_update (LockedFileSet *fset,
170                             const char *path,
171                             const char *operation,
172                             gint64 old_mtime,
173                             const char *file_id)
174 {
175     SeafRepoManager *mgr = fset->mgr;
176     char *sql;
177     sqlite3_stmt *stmt;
178     LockedFile *file;
179     gboolean exists;
180 
181     exists = (g_hash_table_lookup (fset->locked_files, path) != NULL);
182 
183     pthread_mutex_lock (&mgr->priv->db_lock);
184 
185     if (!exists) {
186         seaf_debug ("New locked file record %.8s, %s, %s, %"
187                     G_GINT64_FORMAT".\n",
188                     fset->repo_id, path, operation, old_mtime);
189 
190         sql = "INSERT INTO LockedFiles VALUES (?, ?, ?, ?, ?, NULL)";
191         stmt = sqlite_query_prepare (mgr->priv->db, sql);
192         sqlite3_bind_text (stmt, 1, fset->repo_id, -1, SQLITE_TRANSIENT);
193         sqlite3_bind_text (stmt, 2, path, -1, SQLITE_TRANSIENT);
194         sqlite3_bind_text (stmt, 3, operation, -1, SQLITE_TRANSIENT);
195         sqlite3_bind_int64 (stmt, 4, old_mtime);
196         sqlite3_bind_text (stmt, 5, file_id, -1, SQLITE_TRANSIENT);
197         if (sqlite3_step (stmt) != SQLITE_DONE) {
198             seaf_warning ("Failed to insert locked file %s to db: %s.\n",
199                           path, sqlite3_errmsg (mgr->priv->db));
200             sqlite3_finalize (stmt);
201             pthread_mutex_unlock (&mgr->priv->db_lock);
202             return -1;
203         }
204         sqlite3_finalize (stmt);
205 
206         file = g_new0 (LockedFile, 1);
207         file->operation = g_strdup(operation);
208         file->old_mtime = old_mtime;
209         if (file_id)
210             memcpy (file->file_id, file_id, 40);
211 
212         g_hash_table_insert (fset->locked_files, g_strdup(path), file);
213     } else {
214         seaf_debug ("Update locked file record %.8s, %s, %s.\n",
215                     fset->repo_id, path, operation);
216 
217         /* If a UPDATE record exists, don't update the old_mtime.
218          * We need to keep the old mtime when the locked file was first detected.
219          */
220 
221         sql = "UPDATE LockedFiles SET operation = ?, file_id = ? "
222             "WHERE repo_id = ? AND path = ?";
223         stmt = sqlite_query_prepare (mgr->priv->db, sql);
224         sqlite3_bind_text (stmt, 1, operation, -1, SQLITE_TRANSIENT);
225         sqlite3_bind_text (stmt, 2, file_id, -1, SQLITE_TRANSIENT);
226         sqlite3_bind_text (stmt, 3, fset->repo_id, -1, SQLITE_TRANSIENT);
227         sqlite3_bind_text (stmt, 4, path, -1, SQLITE_TRANSIENT);
228         if (sqlite3_step (stmt) != SQLITE_DONE) {
229             seaf_warning ("Failed to update locked file %s to db: %s.\n",
230                           path, sqlite3_errmsg (mgr->priv->db));
231             sqlite3_finalize (stmt);
232             pthread_mutex_unlock (&mgr->priv->db_lock);
233             return -1;
234         }
235         sqlite3_finalize (stmt);
236 
237         file = g_hash_table_lookup (fset->locked_files, path);
238         g_free (file->operation);
239         file->operation = g_strdup(operation);
240         if (file_id)
241             memcpy (file->file_id, file_id, 40);
242     }
243 
244     pthread_mutex_unlock (&mgr->priv->db_lock);
245 
246     return 0;
247 }
248 
249 int
locked_file_set_remove(LockedFileSet * fset,const char * path,gboolean db_only)250 locked_file_set_remove (LockedFileSet *fset, const char *path, gboolean db_only)
251 {
252     SeafRepoManager *mgr = fset->mgr;
253     char *sql;
254     sqlite3_stmt *stmt;
255 
256     if (g_hash_table_lookup (fset->locked_files, path) == NULL)
257         return 0;
258 
259     seaf_debug ("Remove locked file record %.8s, %s.\n",
260                 fset->repo_id, path);
261 
262     pthread_mutex_lock (&mgr->priv->db_lock);
263 
264     sql = "DELETE FROM LockedFiles WHERE repo_id = ? AND path = ?";
265     stmt = sqlite_query_prepare (mgr->priv->db, sql);
266     sqlite3_bind_text (stmt, 1, fset->repo_id, -1, SQLITE_TRANSIENT);
267     sqlite3_bind_text (stmt, 2, path, -1, SQLITE_TRANSIENT);
268     if (sqlite3_step (stmt) != SQLITE_DONE) {
269         seaf_warning ("Failed to remove locked file %s from db: %s.\n",
270                       path, sqlite3_errmsg (mgr->priv->db));
271         sqlite3_finalize (stmt);
272         pthread_mutex_unlock (&mgr->priv->db_lock);
273         return -1;
274     }
275     sqlite3_finalize (stmt);
276     pthread_mutex_unlock (&mgr->priv->db_lock);
277 
278     if (!db_only)
279         g_hash_table_remove (fset->locked_files, path);
280 
281     return 0;
282 }
283 
284 LockedFile *
locked_file_set_lookup(LockedFileSet * fset,const char * path)285 locked_file_set_lookup (LockedFileSet *fset, const char *path)
286 {
287     return (LockedFile *) g_hash_table_lookup (fset->locked_files, path);
288 }
289 
290 /* Folder permissions. */
291 
292 FolderPerm *
folder_perm_new(const char * path,const char * permission)293 folder_perm_new (const char *path, const char *permission)
294 {
295     FolderPerm *perm = g_new0 (FolderPerm, 1);
296 
297     perm->path = g_strdup(path);
298     perm->permission = g_strdup(permission);
299 
300     return perm;
301 }
302 
303 void
folder_perm_free(FolderPerm * perm)304 folder_perm_free (FolderPerm *perm)
305 {
306     if (!perm)
307         return;
308 
309     g_free (perm->path);
310     g_free (perm->permission);
311     g_free (perm);
312 }
313 
314 static GList *
folder_perm_list_copy(GList * perms)315 folder_perm_list_copy (GList *perms)
316 {
317     GList *ret = NULL, *ptr;
318     FolderPerm *perm, *new_perm;
319 
320     for (ptr = perms; ptr; ptr = ptr->next) {
321         perm = ptr->data;
322         new_perm = folder_perm_new (perm->path, perm->permission);
323         ret = g_list_append (ret, new_perm);
324     }
325 
326     return ret;
327 }
328 
329 static gint
comp_folder_perms(gconstpointer a,gconstpointer b)330 comp_folder_perms (gconstpointer a, gconstpointer b)
331 {
332     const FolderPerm *perm_a = a, *perm_b = b;
333 
334     return (strcmp (perm_b->path, perm_a->path));
335 }
336 
337 int
seaf_repo_manager_update_folder_perms(SeafRepoManager * mgr,const char * repo_id,FolderPermType type,GList * folder_perms)338 seaf_repo_manager_update_folder_perms (SeafRepoManager *mgr,
339                                        const char *repo_id,
340                                        FolderPermType type,
341                                        GList *folder_perms)
342 {
343     char *sql;
344     sqlite3_stmt *stmt;
345     GList *ptr;
346     FolderPerm *perm;
347 
348     g_return_val_if_fail ((type == FOLDER_PERM_TYPE_USER ||
349                            type == FOLDER_PERM_TYPE_GROUP),
350                           -1);
351 
352     /* Update db. */
353 
354     pthread_mutex_lock (&mgr->priv->db_lock);
355 
356     if (type == FOLDER_PERM_TYPE_USER)
357         sql = "DELETE FROM FolderUserPerms WHERE repo_id = ?";
358     else
359         sql = "DELETE FROM FolderGroupPerms WHERE repo_id = ?";
360     stmt = sqlite_query_prepare (mgr->priv->db, sql);
361     sqlite3_bind_text (stmt, 1, repo_id, -1, SQLITE_TRANSIENT);
362     if (sqlite3_step (stmt) != SQLITE_DONE) {
363         seaf_warning ("Failed to remove folder perms for %.8s: %s.\n",
364                       repo_id, sqlite3_errmsg (mgr->priv->db));
365         sqlite3_finalize (stmt);
366         pthread_mutex_unlock (&mgr->priv->db_lock);
367         return -1;
368     }
369     sqlite3_finalize (stmt);
370 
371     if (!folder_perms) {
372         pthread_mutex_unlock (&mgr->priv->db_lock);
373         return 0;
374     }
375 
376     if (type == FOLDER_PERM_TYPE_USER)
377         sql = "INSERT INTO FolderUserPerms VALUES (?, ?, ?)";
378     else
379         sql = "INSERT INTO FolderGroupPerms VALUES (?, ?, ?)";
380     stmt = sqlite_query_prepare (mgr->priv->db, sql);
381 
382     for (ptr = folder_perms; ptr; ptr = ptr->next) {
383         perm = ptr->data;
384 
385         sqlite3_bind_text (stmt, 1, repo_id, -1, SQLITE_TRANSIENT);
386         sqlite3_bind_text (stmt, 2, perm->path, -1, SQLITE_TRANSIENT);
387         sqlite3_bind_text (stmt, 3, perm->permission, -1, SQLITE_TRANSIENT);
388 
389         if (sqlite3_step (stmt) != SQLITE_DONE) {
390             seaf_warning ("Failed to insert folder perms for %.8s: %s.\n",
391                           repo_id, sqlite3_errmsg (mgr->priv->db));
392             sqlite3_finalize (stmt);
393             pthread_mutex_unlock (&mgr->priv->db_lock);
394             return -1;
395         }
396 
397         sqlite3_reset (stmt);
398         sqlite3_clear_bindings (stmt);
399     }
400 
401     sqlite3_finalize (stmt);
402 
403     pthread_mutex_unlock (&mgr->priv->db_lock);
404 
405     /* Update in memory */
406     GList *new, *old;
407     new = folder_perm_list_copy (folder_perms);
408     new = g_list_sort (new, comp_folder_perms);
409 
410     pthread_mutex_lock (&mgr->priv->perm_lock);
411     if (type == FOLDER_PERM_TYPE_USER) {
412         old = g_hash_table_lookup (mgr->priv->user_perms, repo_id);
413         if (old)
414             g_list_free_full (old, (GDestroyNotify)folder_perm_free);
415         g_hash_table_insert (mgr->priv->user_perms, g_strdup(repo_id), new);
416     } else if (type == FOLDER_PERM_TYPE_GROUP) {
417         old = g_hash_table_lookup (mgr->priv->group_perms, repo_id);
418         if (old)
419             g_list_free_full (old, (GDestroyNotify)folder_perm_free);
420         g_hash_table_insert (mgr->priv->group_perms, g_strdup(repo_id), new);
421     }
422     pthread_mutex_unlock (&mgr->priv->perm_lock);
423 
424     return 0;
425 }
426 
427 static gboolean
load_folder_perm(sqlite3_stmt * stmt,void * data)428 load_folder_perm (sqlite3_stmt *stmt, void *data)
429 {
430     GList **p_perms = data;
431     const char *path, *permission;
432 
433     path = (const char *)sqlite3_column_text (stmt, 0);
434     permission = (const char *)sqlite3_column_text (stmt, 1);
435 
436     FolderPerm *perm = folder_perm_new (path, permission);
437     *p_perms = g_list_prepend (*p_perms, perm);
438 
439     return TRUE;
440 }
441 
442 static GList *
load_folder_perms_for_repo(SeafRepoManager * mgr,const char * repo_id,FolderPermType type)443 load_folder_perms_for_repo (SeafRepoManager *mgr,
444                             const char *repo_id,
445                             FolderPermType type)
446 {
447     GList *perms = NULL;
448     char sql[256];
449 
450     g_return_val_if_fail ((type == FOLDER_PERM_TYPE_USER ||
451                            type == FOLDER_PERM_TYPE_GROUP),
452                           NULL);
453 
454     if (type == FOLDER_PERM_TYPE_USER)
455         sqlite3_snprintf (sizeof(sql), sql,
456                           "SELECT path, permission FROM FolderUserPerms "
457                           "WHERE repo_id = '%q'",
458                           repo_id);
459     else
460         sqlite3_snprintf (sizeof(sql), sql,
461                           "SELECT path, permission FROM FolderGroupPerms "
462                           "WHERE repo_id = '%q'",
463                           repo_id);
464 
465     pthread_mutex_lock (&mgr->priv->db_lock);
466 
467     if (sqlite_foreach_selected_row (mgr->priv->db, sql,
468                                      load_folder_perm, &perms) < 0) {
469         pthread_mutex_unlock (&mgr->priv->db_lock);
470         GList *ptr;
471         for (ptr = perms; ptr; ptr = ptr->next)
472             folder_perm_free ((FolderPerm *)ptr->data);
473         g_list_free (perms);
474         return NULL;
475     }
476 
477     pthread_mutex_unlock (&mgr->priv->db_lock);
478 
479     /* Sort list in descending order by perm->path (longer path first). */
480     perms = g_list_sort (perms, comp_folder_perms);
481 
482     return perms;
483 }
484 
485 static void
init_folder_perms(SeafRepoManager * mgr)486 init_folder_perms (SeafRepoManager *mgr)
487 {
488     SeafRepoManagerPriv *priv = mgr->priv;
489     GList *repo_ids = g_hash_table_get_keys (priv->repo_hash);
490     GList *ptr;
491     GList *perms;
492     char *repo_id;
493 
494     priv->user_perms = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
495     priv->group_perms = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
496     pthread_mutex_init (&priv->perm_lock, NULL);
497 
498     for (ptr = repo_ids; ptr; ptr = ptr->next) {
499         repo_id = ptr->data;
500         perms = load_folder_perms_for_repo (mgr, repo_id, FOLDER_PERM_TYPE_USER);
501         if (perms) {
502             pthread_mutex_lock (&priv->perm_lock);
503             g_hash_table_insert (priv->user_perms, g_strdup(repo_id), perms);
504             pthread_mutex_unlock (&priv->perm_lock);
505         }
506         perms = load_folder_perms_for_repo (mgr, repo_id, FOLDER_PERM_TYPE_GROUP);
507         if (perms) {
508             pthread_mutex_lock (&priv->perm_lock);
509             g_hash_table_insert (priv->group_perms, g_strdup(repo_id), perms);
510             pthread_mutex_unlock (&priv->perm_lock);
511         }
512     }
513 
514     g_list_free (repo_ids);
515 }
516 
517 static void
remove_folder_perms(SeafRepoManager * mgr,const char * repo_id)518 remove_folder_perms (SeafRepoManager *mgr, const char *repo_id)
519 {
520     GList *perms = NULL;
521 
522     pthread_mutex_lock (&mgr->priv->perm_lock);
523 
524     perms = g_hash_table_lookup (mgr->priv->user_perms, repo_id);
525     if (perms) {
526         g_list_free_full (perms, (GDestroyNotify)folder_perm_free);
527         g_hash_table_remove (mgr->priv->user_perms, repo_id);
528     }
529 
530     perms = g_hash_table_lookup (mgr->priv->group_perms, repo_id);
531     if (perms) {
532         g_list_free_full (perms, (GDestroyNotify)folder_perm_free);
533         g_hash_table_remove (mgr->priv->group_perms, repo_id);
534     }
535 
536     pthread_mutex_unlock (&mgr->priv->perm_lock);
537 }
538 
539 int
seaf_repo_manager_update_folder_perm_timestamp(SeafRepoManager * mgr,const char * repo_id,gint64 timestamp)540 seaf_repo_manager_update_folder_perm_timestamp (SeafRepoManager *mgr,
541                                                 const char *repo_id,
542                                                 gint64 timestamp)
543 {
544     char sql[256];
545     int ret;
546 
547     snprintf (sql, sizeof(sql),
548               "REPLACE INTO FolderPermTimestamp VALUES ('%s', %"G_GINT64_FORMAT")",
549               repo_id, timestamp);
550 
551     pthread_mutex_lock (&mgr->priv->db_lock);
552 
553     ret = sqlite_query_exec (mgr->priv->db, sql);
554 
555     pthread_mutex_unlock (&mgr->priv->db_lock);
556 
557     return ret;
558 }
559 
560 gint64
seaf_repo_manager_get_folder_perm_timestamp(SeafRepoManager * mgr,const char * repo_id)561 seaf_repo_manager_get_folder_perm_timestamp (SeafRepoManager *mgr,
562                                              const char *repo_id)
563 {
564     char sql[256];
565     gint64 ret;
566 
567     sqlite3_snprintf (sizeof(sql), sql,
568                       "SELECT timestamp FROM FolderPermTimestamp WHERE repo_id = '%q'",
569                       repo_id);
570 
571     pthread_mutex_lock (&mgr->priv->db_lock);
572 
573     ret = sqlite_get_int64 (mgr->priv->db, sql);
574 
575     pthread_mutex_unlock (&mgr->priv->db_lock);
576 
577     return ret;
578 }
579 
580 static char *
lookup_folder_perm(GList * perms,const char * path)581 lookup_folder_perm (GList *perms, const char *path)
582 {
583     GList *ptr;
584     FolderPerm *perm;
585     char *folder;
586     int len;
587     char *permission = NULL;
588 
589     for (ptr = perms; ptr; ptr = ptr->next) {
590         perm = ptr->data;
591 
592         if (strcmp (perm->path, "/") != 0)
593             folder = g_strconcat (perm->path, "/", NULL);
594         else
595             folder = g_strdup(perm->path);
596 
597         len = strlen(folder);
598         if (strcmp (perm->path, path) == 0 || strncmp(folder, path, len) == 0) {
599             permission = perm->permission;
600             g_free (folder);
601             break;
602         }
603         g_free (folder);
604     }
605 
606     return permission;
607 }
608 
609 static gboolean
is_path_writable(const char * repo_id,gboolean is_repo_readonly,const char * path)610 is_path_writable (const char *repo_id,
611                   gboolean is_repo_readonly,
612                   const char *path)
613 {
614     SeafRepoManager *mgr = seaf->repo_mgr;
615     GList *user_perms = NULL, *group_perms = NULL;
616     char *permission = NULL;
617     char *abs_path = NULL;
618 
619     pthread_mutex_lock (&mgr->priv->perm_lock);
620 
621     user_perms = g_hash_table_lookup (mgr->priv->user_perms, repo_id);
622     group_perms = g_hash_table_lookup (mgr->priv->group_perms, repo_id);
623 
624     if (user_perms || group_perms)
625         abs_path = g_strconcat ("/", path, NULL);
626 
627     if (user_perms)
628         permission = lookup_folder_perm (user_perms, abs_path);
629     if (!permission && group_perms)
630         permission = lookup_folder_perm (group_perms, abs_path);
631 
632     pthread_mutex_unlock (&mgr->priv->perm_lock);
633 
634     g_free (abs_path);
635 
636     if (!permission)
637         return !is_repo_readonly;
638 
639     if (strcmp (permission, "rw") == 0)
640         return TRUE;
641     else
642         return FALSE;
643 }
644 
645 gboolean
seaf_repo_manager_is_path_writable(SeafRepoManager * mgr,const char * repo_id,const char * path)646 seaf_repo_manager_is_path_writable (SeafRepoManager *mgr,
647                                     const char *repo_id,
648                                     const char *path)
649 {
650     SeafRepo *repo = seaf_repo_manager_get_repo (mgr, repo_id);
651     if (!repo) {
652         seaf_warning ("Failed to get repo %s.\n", repo_id);
653         return FALSE;
654     }
655 
656     return is_path_writable (repo_id, repo->is_readonly, path);
657 }
658 
659 gboolean
is_repo_id_valid(const char * id)660 is_repo_id_valid (const char *id)
661 {
662     if (!id)
663         return FALSE;
664 
665     return is_uuid_valid (id);
666 }
667 
668 /*
669  * Sync error related. These functions should belong to the sync-mgr module.
670  * But since we have to store the errors in repo database, we have to put the code here.
671  */
672 
673 int
seaf_repo_manager_record_sync_error(const char * repo_id,const char * repo_name,const char * path,int error_id)674 seaf_repo_manager_record_sync_error (const char *repo_id,
675                                      const char *repo_name,
676                                      const char *path,
677                                      int error_id)
678 {
679     char *sql;
680     int ret;
681 
682     pthread_mutex_lock (&seaf->repo_mgr->priv->db_lock);
683 
684     if (path != NULL)
685         sql = sqlite3_mprintf ("DELETE FROM FileSyncError WHERE repo_id='%q' AND path='%q'",
686                                repo_id, path);
687     else
688         sql = sqlite3_mprintf ("DELETE FROM FileSyncError WHERE repo_id='%q' AND path IS NULL",
689                                repo_id);
690     ret = sqlite_query_exec (seaf->repo_mgr->priv->db, sql);
691     sqlite3_free (sql);
692     if (ret < 0)
693         goto out;
694 
695     /* REPLACE INTO will update the primary key id automatically.
696      * So new errors are always on top.
697      */
698     if (path != NULL)
699         sql = sqlite3_mprintf ("INSERT INTO FileSyncError "
700                                "(repo_id, repo_name, path, err_id, timestamp) "
701                                "VALUES ('%q', '%q', '%q', %d, %"G_GINT64_FORMAT")",
702                                repo_id, repo_name, path, error_id, (gint64)time(NULL));
703     else
704         sql = sqlite3_mprintf ("INSERT INTO FileSyncError "
705                                "(repo_id, repo_name, err_id, timestamp) "
706                                "VALUES ('%q', '%q', %d, %"G_GINT64_FORMAT")",
707                                repo_id, repo_name, error_id, (gint64)time(NULL));
708 
709     ret = sqlite_query_exec (seaf->repo_mgr->priv->db, sql);
710     sqlite3_free (sql);
711 
712 out:
713     pthread_mutex_unlock (&seaf->repo_mgr->priv->db_lock);
714     return ret;
715 }
716 
717 static gboolean
collect_file_sync_errors(sqlite3_stmt * stmt,void * data)718 collect_file_sync_errors (sqlite3_stmt *stmt, void *data)
719 {
720     GList **pret = data;
721     const char *repo_id, *repo_name, *path;
722     int id, err_id;
723     gint64 timestamp;
724     SeafileFileSyncError *error;
725 
726     id = sqlite3_column_int (stmt, 0);
727     repo_id = (const char *)sqlite3_column_text (stmt, 1);
728     repo_name = (const char *)sqlite3_column_text (stmt, 2);
729     path = (const char *)sqlite3_column_text (stmt, 3);
730     err_id = sqlite3_column_int (stmt, 4);
731     timestamp = sqlite3_column_int64 (stmt, 5);
732 
733     error = g_object_new (SEAFILE_TYPE_FILE_SYNC_ERROR,
734                           "id", id,
735                           "repo_id", repo_id,
736                           "repo_name", repo_name,
737                           "path", path,
738                           "err_id", err_id,
739                           "timestamp", timestamp,
740                           NULL);
741     *pret = g_list_prepend (*pret, error);
742 
743     return TRUE;
744 }
745 
746 int
seaf_repo_manager_del_file_sync_error_by_id(SeafRepoManager * mgr,int id)747 seaf_repo_manager_del_file_sync_error_by_id (SeafRepoManager *mgr, int id)
748 {
749     int ret = 0;
750     char *sql = NULL;
751 
752     pthread_mutex_lock (&mgr->priv->db_lock);
753 
754     sql = sqlite3_mprintf ("DELETE FROM FileSyncError WHERE id=%d",
755                            id);
756     ret = sqlite_query_exec (mgr->priv->db, sql);
757     sqlite3_free (sql);
758 
759     pthread_mutex_unlock (&mgr->priv->db_lock);
760 
761     return ret;
762 }
763 
764 GList *
seaf_repo_manager_get_file_sync_errors(SeafRepoManager * mgr,int offset,int limit)765 seaf_repo_manager_get_file_sync_errors (SeafRepoManager *mgr, int offset, int limit)
766 {
767     GList *ret = NULL;
768     char *sql;
769 
770     pthread_mutex_lock (&mgr->priv->db_lock);
771 
772     sql = sqlite3_mprintf ("SELECT id, repo_id, repo_name, path, err_id, timestamp FROM "
773                            "FileSyncError ORDER BY id DESC LIMIT %d OFFSET %d",
774                            limit, offset);
775     sqlite_foreach_selected_row (mgr->priv->db, sql,
776                                  collect_file_sync_errors, &ret);
777     sqlite3_free (sql);
778 
779     pthread_mutex_unlock (&mgr->priv->db_lock);
780 
781     ret = g_list_reverse (ret);
782 
783     return ret;
784 }
785 
786 /*
787  * Record file-level sync errors and send system notification.
788  */
789 void
send_file_sync_error_notification(const char * repo_id,const char * repo_name,const char * path,int err_id)790 send_file_sync_error_notification (const char *repo_id,
791                                    const char *repo_name,
792                                    const char *path,
793                                    int err_id)
794 {
795     if (!repo_name) {
796         SeafRepo *repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id);
797         if (!repo)
798             return;
799         repo_name = repo->name;
800     }
801 
802     seaf_repo_manager_record_sync_error (repo_id, repo_name, path, err_id);
803 
804     seaf_sync_manager_set_task_error_code (seaf->sync_mgr, repo_id, err_id);
805 
806     json_t *object;
807     char *str;
808 
809     object = json_object ();
810     json_object_set_new (object, "repo_id", json_string(repo_id));
811     json_object_set_new (object, "repo_name", json_string(repo_name));
812     json_object_set_new (object, "path", json_string(path));
813     json_object_set_new (object, "err_id", json_integer(err_id));
814 
815     str = json_dumps (object, 0);
816 
817     seaf_mq_manager_publish_notification (seaf->mq_mgr,
818                                           "sync.error",
819                                           str);
820 
821     free (str);
822     json_decref (object);
823 }
824 
825 SeafRepo*
seaf_repo_new(const char * id,const char * name,const char * desc)826 seaf_repo_new (const char *id, const char *name, const char *desc)
827 {
828     SeafRepo* repo;
829 
830     /* valid check */
831 
832 
833     repo = g_new0 (SeafRepo, 1);
834     memcpy (repo->id, id, 36);
835     repo->id[36] = '\0';
836 
837     repo->name = g_strdup(name);
838     repo->desc = g_strdup(desc);
839 
840     repo->worktree_invalid = TRUE;
841     repo->auto_sync = 1;
842     pthread_mutex_init (&repo->lock, NULL);
843 
844     return repo;
845 }
846 
847 int
seaf_repo_check_worktree(SeafRepo * repo)848 seaf_repo_check_worktree (SeafRepo *repo)
849 {
850     SeafStat st;
851 
852     if (repo->worktree == NULL) {
853         seaf_warning ("Worktree for repo '%s'(%.8s) is not set.\n",
854                       repo->name, repo->id);
855         return -1;
856     }
857 
858     /* check repo worktree */
859     if (g_access(repo->worktree, F_OK) < 0) {
860         seaf_warning ("Failed to access worktree %s for repo '%s'(%.8s)\n",
861                       repo->worktree, repo->name, repo->id);
862         return -1;
863     }
864     if (seaf_stat(repo->worktree, &st) < 0) {
865         seaf_warning ("Failed to stat worktree %s for repo '%s'(%.8s)\n",
866                       repo->worktree, repo->name, repo->id);
867         return -1;
868     }
869     if (!S_ISDIR(st.st_mode)) {
870         seaf_warning ("Worktree %s for repo '%s'(%.8s) is not a directory.\n",
871                       repo->worktree, repo->name, repo->id);
872         return -1;
873     }
874 
875     return 0;
876 }
877 
878 
879 static gboolean
check_worktree_common(SeafRepo * repo)880 check_worktree_common (SeafRepo *repo)
881 {
882     if (!repo->head) {
883         seaf_warning ("Head for repo '%s'(%.8s) is not set.\n",
884                       repo->name, repo->id);
885         return FALSE;
886     }
887 
888     if (seaf_repo_check_worktree (repo) < 0) {
889         return FALSE;
890     }
891 
892     return TRUE;
893 }
894 
895 void
seaf_repo_free(SeafRepo * repo)896 seaf_repo_free (SeafRepo *repo)
897 {
898     if (repo->head) seaf_branch_unref (repo->head);
899 
900     g_free (repo->name);
901     g_free (repo->desc);
902     g_free (repo->category);
903     g_free (repo->worktree);
904     g_free (repo->relay_id);
905     g_free (repo->email);
906     g_free (repo->token);
907     g_free (repo);
908 }
909 
910 static void
set_head_common(SeafRepo * repo,SeafBranch * branch)911 set_head_common (SeafRepo *repo, SeafBranch *branch)
912 {
913     if (repo->head)
914         seaf_branch_unref (repo->head);
915     repo->head = branch;
916     seaf_branch_ref(branch);
917 }
918 
919 int
seaf_repo_set_head(SeafRepo * repo,SeafBranch * branch)920 seaf_repo_set_head (SeafRepo *repo, SeafBranch *branch)
921 {
922     if (save_branch_repo_map (repo->manager, branch) < 0)
923         return -1;
924     set_head_common (repo, branch);
925     return 0;
926 }
927 
928 SeafCommit *
seaf_repo_get_head_commit(const char * repo_id)929 seaf_repo_get_head_commit (const char *repo_id)
930 {
931     SeafRepo *repo;
932     SeafCommit *head;
933 
934     repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id);
935     if (!repo) {
936         seaf_warning ("Failed to get repo %s.\n", repo_id);
937         return NULL;
938     }
939 
940     head = seaf_commit_manager_get_commit (seaf->commit_mgr,
941                                            repo_id, repo->version,
942                                            repo->head->commit_id);
943     if (!head) {
944         seaf_warning ("Failed to get head for repo %s.\n", repo_id);
945         return NULL;
946     }
947 
948     return head;
949 }
950 
951 void
seaf_repo_from_commit(SeafRepo * repo,SeafCommit * commit)952 seaf_repo_from_commit (SeafRepo *repo, SeafCommit *commit)
953 {
954     repo->name = g_strdup (commit->repo_name);
955     repo->desc = g_strdup (commit->repo_desc);
956     repo->encrypted = commit->encrypted;
957     repo->last_modify = commit->ctime;
958     memcpy (repo->root_id, commit->root_id, 40);
959     if (repo->encrypted) {
960         repo->enc_version = commit->enc_version;
961         if (repo->enc_version == 1)
962             memcpy (repo->magic, commit->magic, 32);
963         else if (repo->enc_version == 2) {
964             memcpy (repo->magic, commit->magic, 64);
965             memcpy (repo->random_key, commit->random_key, 96);
966         }
967         else if (repo->enc_version == 3) {
968             memcpy (repo->magic, commit->magic, 64);
969             memcpy (repo->random_key, commit->random_key, 96);
970             memcpy (repo->salt, commit->salt, 64);
971         }
972     }
973     repo->no_local_history = commit->no_local_history;
974     repo->version = commit->version;
975 }
976 
977 void
seaf_repo_to_commit(SeafRepo * repo,SeafCommit * commit)978 seaf_repo_to_commit (SeafRepo *repo, SeafCommit *commit)
979 {
980     commit->repo_name = g_strdup (repo->name);
981     commit->repo_desc = g_strdup (repo->desc);
982     commit->encrypted = repo->encrypted;
983     if (commit->encrypted) {
984         commit->enc_version = repo->enc_version;
985         if (commit->enc_version == 1)
986             commit->magic = g_strdup (repo->magic);
987         else if (commit->enc_version == 2) {
988             commit->magic = g_strdup (repo->magic);
989             commit->random_key = g_strdup (repo->random_key);
990         }
991         else if (commit->enc_version == 3) {
992             commit->magic = g_strdup (repo->magic);
993             commit->random_key = g_strdup (repo->random_key);
994             commit->salt = g_strdup (repo->salt);
995         }
996     }
997     commit->no_local_history = repo->no_local_history;
998     commit->version = repo->version;
999 }
1000 
1001 static gboolean
need_to_sync_worktree_name(const char * repo_id)1002 need_to_sync_worktree_name (const char *repo_id)
1003 {
1004     char *need_sync_wt_name = seaf_repo_manager_get_repo_property (seaf->repo_mgr,
1005                                                                    repo_id,
1006                                                                    REPO_SYNC_WORKTREE_NAME);
1007     gboolean ret = (g_strcmp0(need_sync_wt_name, "true") == 0);
1008     g_free (need_sync_wt_name);
1009     return ret;
1010 }
1011 
1012 static void
update_repo_worktree_name(SeafRepo * repo,const char * new_name,gboolean rewatch)1013 update_repo_worktree_name (SeafRepo *repo, const char *new_name, gboolean rewatch)
1014 {
1015     char *dirname = NULL, *basename = NULL;
1016     char *new_worktree = NULL;
1017 
1018     seaf_message ("Update worktree folder name of repo %s to %s.\n",
1019                   repo->id, new_name);
1020 
1021     dirname = g_path_get_dirname (repo->worktree);
1022     if (g_strcmp0 (dirname, ".") == 0)
1023         return;
1024     basename = g_path_get_basename (repo->worktree);
1025 
1026     new_worktree = g_build_filename (dirname, new_name, NULL);
1027 
1028     /* This can possibly fail on Windows if some files are opened under the worktree.
1029      * The rename operation will be retried on next restart.
1030      */
1031     if (seaf_util_rename (repo->worktree, new_worktree) < 0) {
1032         seaf_warning ("Failed to rename worktree from %s to %s: %s.\n",
1033                       repo->worktree, new_worktree, strerror(errno));
1034         goto out;
1035     }
1036 
1037     if (seaf_repo_manager_set_repo_worktree (seaf->repo_mgr, repo, new_worktree) < 0) {
1038         goto out;
1039     }
1040 
1041     if (rewatch) {
1042         if (seaf_wt_monitor_unwatch_repo (seaf->wt_monitor, repo->id) < 0) {
1043             seaf_warning ("Failed to unwatch repo %s old worktree.\n", repo->id);
1044             goto out;
1045         }
1046 
1047         if (seaf_wt_monitor_watch_repo (seaf->wt_monitor, repo->id, repo->worktree) < 0) {
1048             seaf_warning ("Failed to watch repo %s new worktree.\n", repo->id);
1049         }
1050     }
1051 
1052 out:
1053     g_free (dirname);
1054     g_free (basename);
1055     g_free (new_worktree);
1056 }
1057 
1058 void
seaf_repo_set_name(SeafRepo * repo,const char * new_name)1059 seaf_repo_set_name (SeafRepo *repo, const char *new_name)
1060 {
1061     char *old_name = repo->name;
1062     repo->name = g_strdup(new_name);
1063     g_free (old_name);
1064 
1065     if (need_to_sync_worktree_name (repo->id))
1066         update_repo_worktree_name (repo, new_name, TRUE);
1067 }
1068 
1069 static gboolean
collect_commit(SeafCommit * commit,void * vlist,gboolean * stop)1070 collect_commit (SeafCommit *commit, void *vlist, gboolean *stop)
1071 {
1072     GList **commits = vlist;
1073 
1074     /* The traverse function will unref the commit, so we need to ref it.
1075      */
1076     seaf_commit_ref (commit);
1077     *commits = g_list_prepend (*commits, commit);
1078     return TRUE;
1079 }
1080 
1081 GList *
seaf_repo_get_commits(SeafRepo * repo)1082 seaf_repo_get_commits (SeafRepo *repo)
1083 {
1084     GList *branches;
1085     GList *ptr;
1086     SeafBranch *branch;
1087     GList *commits = NULL;
1088 
1089     branches = seaf_branch_manager_get_branch_list (seaf->branch_mgr, repo->id);
1090     if (branches == NULL) {
1091         seaf_warning ("Failed to get branch list of repo %s.\n", repo->id);
1092         return NULL;
1093     }
1094 
1095     for (ptr = branches; ptr != NULL; ptr = ptr->next) {
1096         branch = ptr->data;
1097         gboolean res = seaf_commit_manager_traverse_commit_tree (seaf->commit_mgr,
1098                                                                  repo->id,
1099                                                                  repo->version,
1100                                                                  branch->commit_id,
1101                                                                  collect_commit,
1102                                                                  &commits, FALSE);
1103         if (!res) {
1104             for (ptr = commits; ptr != NULL; ptr = ptr->next)
1105                 seaf_commit_unref ((SeafCommit *)(ptr->data));
1106             g_list_free (commits);
1107             goto out;
1108         }
1109     }
1110 
1111     commits = g_list_reverse (commits);
1112 
1113 out:
1114     for (ptr = branches; ptr != NULL; ptr = ptr->next) {
1115         seaf_branch_unref ((SeafBranch *)ptr->data);
1116     }
1117     return commits;
1118 }
1119 
1120 void
seaf_repo_set_readonly(SeafRepo * repo)1121 seaf_repo_set_readonly (SeafRepo *repo)
1122 {
1123     repo->is_readonly = TRUE;
1124     save_repo_property (repo->manager, repo->id, REPO_PROP_IS_READONLY, "true");
1125 }
1126 
1127 void
seaf_repo_unset_readonly(SeafRepo * repo)1128 seaf_repo_unset_readonly (SeafRepo *repo)
1129 {
1130     repo->is_readonly = FALSE;
1131     save_repo_property (repo->manager, repo->id, REPO_PROP_IS_READONLY, "false");
1132 }
1133 
1134 gboolean
seaf_repo_manager_is_ignored_hidden_file(const char * filename)1135 seaf_repo_manager_is_ignored_hidden_file (const char *filename)
1136 {
1137     GPatternSpec **spec = ignore_patterns;
1138 
1139     while (*spec) {
1140         if (g_pattern_match_string(*spec, filename))
1141             return TRUE;
1142         spec++;
1143     }
1144 
1145     return FALSE;
1146 }
1147 
1148 static gboolean
should_ignore(const char * basepath,const char * filename,void * data)1149 should_ignore(const char *basepath, const char *filename, void *data)
1150 {
1151     GPatternSpec **spec = ignore_patterns;
1152     GList *ignore_list = (GList *)data;
1153 
1154     if (!g_utf8_validate (filename, -1, NULL)) {
1155         seaf_warning ("File name %s contains non-UTF8 characters, skip.\n", filename);
1156         return TRUE;
1157     }
1158 
1159     /* Ignore file/dir if its name is too long. */
1160     if (strlen(filename) >= SEAF_DIR_NAME_LEN)
1161         return TRUE;
1162 
1163     if (strchr (filename, '/'))
1164         return TRUE;
1165 
1166     while (*spec) {
1167         if (g_pattern_match_string(*spec, filename))
1168             return TRUE;
1169         spec++;
1170     }
1171 
1172     if (!seaf->sync_extra_temp_file) {
1173         spec = office_temp_ignore_patterns;
1174         while (*spec) {
1175             if (g_pattern_match_string(*spec, filename))
1176                 return TRUE;
1177             spec++;
1178         }
1179     }
1180 
1181     if (basepath) {
1182         char *fullpath = g_build_path ("/", basepath, filename, NULL);
1183         if (seaf_repo_check_ignore_file (ignore_list, fullpath)) {
1184             g_free (fullpath);
1185             return TRUE;
1186         }
1187         g_free (fullpath);
1188     }
1189 
1190     return FALSE;
1191 }
1192 
1193 static int
index_cb(const char * repo_id,int version,const char * path,unsigned char sha1[],SeafileCrypt * crypt,gboolean write_data)1194 index_cb (const char *repo_id,
1195           int version,
1196           const char *path,
1197           unsigned char sha1[],
1198           SeafileCrypt *crypt,
1199           gboolean write_data)
1200 {
1201     gint64 size;
1202 
1203     /* Check in blocks and get object ID. */
1204     if (seaf_fs_manager_index_blocks (seaf->fs_mgr, repo_id, version,
1205                                       path, sha1, &size, crypt, write_data, !seaf->disable_block_hash) < 0) {
1206         seaf_warning ("Failed to index file %s.\n", path);
1207         return -1;
1208     }
1209     return 0;
1210 }
1211 
1212 #define MAX_COMMIT_SIZE 100 * (1 << 20) /* 100MB */
1213 
1214 typedef struct _AddOptions {
1215     LockedFileSet *fset;
1216     ChangeSet *changeset;
1217     gboolean is_repo_ro;
1218     gboolean startup_scan;
1219 } AddOptions;
1220 
1221 static int
add_file(const char * repo_id,int version,const char * modifier,struct index_state * istate,const char * path,const char * full_path,SeafStat * st,SeafileCrypt * crypt,gint64 * total_size,GQueue ** remain_files,AddOptions * options)1222 add_file (const char *repo_id,
1223           int version,
1224           const char *modifier,
1225           struct index_state *istate,
1226           const char *path,
1227           const char *full_path,
1228           SeafStat *st,
1229           SeafileCrypt *crypt,
1230           gint64 *total_size,
1231           GQueue **remain_files,
1232           AddOptions *options)
1233 {
1234     gboolean added = FALSE;
1235     int ret = 0;
1236     gboolean is_writable = TRUE, is_locked = FALSE;
1237     struct cache_entry *ce;
1238 
1239     if (options)
1240         is_writable = is_path_writable(repo_id,
1241                                        options->is_repo_ro, path);
1242 
1243     is_locked = seaf_filelock_manager_is_file_locked (seaf->filelock_mgr,
1244                                                       repo_id, path);
1245     if (is_locked && options && !(options->startup_scan)) {
1246         /* send_sync_error_notification (repo_id, NULL, path, */
1247         /*                               SYNC_ERROR_ID_FILE_LOCKED); */
1248     }
1249 
1250     if (options && options->startup_scan) {
1251         SyncStatus status;
1252 
1253         ce = index_name_exists (istate, path, strlen(path), 0);
1254         if (!ce || ie_match_stat(ce, st, 0) != 0)
1255             status = SYNC_STATUS_SYNCING;
1256         else
1257             status = SYNC_STATUS_SYNCED;
1258 
1259         /* Don't set "syncing" status for read-only path. */
1260         if (status == SYNC_STATUS_SYNCED || (is_writable && !is_locked))
1261             seaf_sync_manager_update_active_path (seaf->sync_mgr,
1262                                                   repo_id,
1263                                                   path,
1264                                                   S_IFREG,
1265                                                   status,
1266                                                   FALSE);
1267         /* send an error notification for read-only repo when modifying a file. */
1268         if (status == SYNC_STATUS_SYNCING && !is_writable)
1269             send_file_sync_error_notification (repo_id, NULL, path,
1270                                                SYNC_ERROR_ID_UPDATE_TO_READ_ONLY_REPO);
1271     }
1272 
1273     if (!is_writable || is_locked)
1274         return ret;
1275 
1276 #if defined WIN32 || defined __APPLE__
1277     if (options && options->fset) {
1278         LockedFile *file = locked_file_set_lookup (options->fset, path);
1279         if (file) {
1280             if (strcmp (file->operation, LOCKED_OP_DELETE) == 0) {
1281                 /* Only remove the lock record if the file is changed. */
1282                 if (st->st_mtime == file->old_mtime) {
1283                     return ret;
1284                 }
1285                 locked_file_set_remove (options->fset, path, FALSE);
1286             } else if (strcmp (file->operation, LOCKED_OP_UPDATE) == 0) {
1287                 return ret;
1288             }
1289         }
1290     }
1291 #endif
1292 
1293     if (!remain_files) {
1294         ret = add_to_index (repo_id, version, istate, path, full_path,
1295                             st, 0, crypt, index_cb, modifier, &added);
1296         if (!added) {
1297             /* If the contents of the file doesn't change, move it to
1298                synced status.
1299             */
1300             seaf_sync_manager_update_active_path (seaf->sync_mgr,
1301                                                   repo_id,
1302                                                   path,
1303                                                   S_IFREG,
1304                                                   SYNC_STATUS_SYNCED,
1305                                                   FALSE);
1306         } else {
1307             if (total_size)
1308                 *total_size += (gint64)(st->st_size);
1309             if (options && options->changeset) {
1310                 /* ce may be updated. */
1311                 ce = index_name_exists (istate, path, strlen(path), 0);
1312                 add_to_changeset (options->changeset,
1313                                   DIFF_STATUS_ADDED,
1314                                   ce->sha1,
1315                                   st,
1316                                   modifier,
1317                                   path,
1318                                   NULL);
1319             }
1320         }
1321     } else if (*remain_files == NULL) {
1322         ret = add_to_index (repo_id, version, istate, path, full_path,
1323                             st, 0, crypt, index_cb, modifier, &added);
1324         if (added) {
1325             *total_size += (gint64)(st->st_size);
1326             if (*total_size >= MAX_COMMIT_SIZE)
1327                 *remain_files = g_queue_new ();
1328         } else {
1329             seaf_sync_manager_update_active_path (seaf->sync_mgr,
1330                                                   repo_id,
1331                                                   path,
1332                                                   S_IFREG,
1333                                                   SYNC_STATUS_SYNCED,
1334                                                   FALSE);
1335         }
1336         if (added && options && options->changeset) {
1337             /* ce may be updated. */
1338             ce = index_name_exists (istate, path, strlen(path), 0);
1339             add_to_changeset (options->changeset,
1340                               DIFF_STATUS_ADDED,
1341                               ce->sha1,
1342                               st,
1343                               modifier,
1344                               path,
1345                               NULL);
1346         }
1347     } else {
1348         *total_size += (gint64)(st->st_size);
1349         g_queue_push_tail (*remain_files, g_strdup(path));
1350     }
1351 
1352     if (ret < 0) {
1353         seaf_sync_manager_update_active_path (seaf->sync_mgr,
1354                                               repo_id,
1355                                               path,
1356                                               S_IFREG,
1357                                               SYNC_STATUS_ERROR,
1358                                               TRUE);
1359         send_file_sync_error_notification (repo_id, NULL, path,
1360                                            SYNC_ERROR_ID_INDEX_ERROR);
1361     }
1362 
1363     return ret;
1364 }
1365 
1366 typedef struct AddParams {
1367     const char *repo_id;
1368     int version;
1369     const char *modifier;
1370     struct index_state *istate;
1371     const char *worktree;
1372     SeafileCrypt *crypt;
1373     gboolean ignore_empty_dir;
1374     GList *ignore_list;
1375     gint64 *total_size;
1376     GQueue **remain_files;
1377     AddOptions *options;
1378 } AddParams;
1379 
1380 #ifndef WIN32
1381 
1382 static int
add_dir_recursive(const char * path,const char * full_path,SeafStat * st,AddParams * params,gboolean ignored)1383 add_dir_recursive (const char *path, const char *full_path, SeafStat *st,
1384                    AddParams *params, gboolean ignored)
1385 {
1386     AddOptions *options = params->options;
1387     GDir *dir;
1388     const char *dname;
1389     char *subpath, *full_subpath;
1390     int n, total;
1391     gboolean is_writable = TRUE;
1392     struct stat sub_st;
1393 
1394     dir = g_dir_open (full_path, 0, NULL);
1395     if (!dir) {
1396         seaf_warning ("Failed to open dir %s: %s.\n", full_path, strerror(errno));
1397 
1398         seaf_sync_manager_update_active_path (seaf->sync_mgr,
1399                                               params->repo_id,
1400                                               path,
1401                                               S_IFDIR,
1402                                               SYNC_STATUS_ERROR,
1403                                               TRUE);
1404 
1405         return 0;
1406     }
1407 
1408     n = 0;
1409     total = 0;
1410     while ((dname = g_dir_read_name(dir)) != NULL) {
1411         ++total;
1412 
1413 #ifdef __APPLE__
1414         char *norm_dname = g_utf8_normalize (dname, -1, G_NORMALIZE_NFC);
1415         subpath = g_build_path (PATH_SEPERATOR, path, norm_dname, NULL);
1416         g_free (norm_dname);
1417 #else
1418         subpath = g_build_path (PATH_SEPERATOR, path, dname, NULL);
1419 #endif
1420         full_subpath = g_build_filename (params->worktree, subpath, NULL);
1421 
1422         if (stat (full_subpath, &sub_st) < 0) {
1423             seaf_warning ("Failed to stat %s: %s.\n", full_subpath, strerror(errno));
1424             g_free (subpath);
1425             g_free (full_subpath);
1426             continue;
1427         }
1428 
1429         if (ignored || should_ignore(full_path, dname, params->ignore_list)) {
1430             if (options && options->startup_scan) {
1431                 if (S_ISDIR(sub_st.st_mode))
1432                     add_dir_recursive (subpath, full_subpath, &sub_st, params, TRUE);
1433                 else
1434                     seaf_sync_manager_update_active_path (seaf->sync_mgr,
1435                                                           params->repo_id,
1436                                                           subpath,
1437                                                           S_IFREG,
1438                                                           SYNC_STATUS_IGNORED,
1439                                                           TRUE);
1440             }
1441             g_free (subpath);
1442             g_free (full_subpath);
1443             continue;
1444         }
1445 
1446         ++n;
1447 
1448         if (S_ISDIR(sub_st.st_mode))
1449             add_dir_recursive (subpath, full_subpath, &sub_st, params, FALSE);
1450         else if (S_ISREG(sub_st.st_mode))
1451             add_file (params->repo_id,
1452                       params->version,
1453                       params->modifier,
1454                       params->istate,
1455                       subpath,
1456                       full_subpath,
1457                       &sub_st,
1458                       params->crypt,
1459                       params->total_size,
1460                       params->remain_files,
1461                       params->options);
1462 
1463         g_free (subpath);
1464         g_free (full_subpath);
1465     }
1466     g_dir_close (dir);
1467 
1468     if (ignored) {
1469         seaf_sync_manager_update_active_path (seaf->sync_mgr,
1470                                               params->repo_id,
1471                                               path,
1472                                               S_IFDIR,
1473                                               SYNC_STATUS_IGNORED,
1474                                               TRUE);
1475         return 0;
1476     }
1477 
1478     if (options)
1479         is_writable = is_path_writable(params->repo_id,
1480                                        options->is_repo_ro, path);
1481 
1482     /* Update active path status for empty dir */
1483     if (options && options->startup_scan && total == 0) {
1484         SyncStatus status;
1485         struct cache_entry *ce = index_name_exists (params->istate, path,
1486                                                     strlen(path), 0);
1487         if (!ce)
1488             status = SYNC_STATUS_SYNCING;
1489         else
1490             status = SYNC_STATUS_SYNCED;
1491 
1492 
1493         if (status == SYNC_STATUS_SYNCED || is_writable)
1494             seaf_sync_manager_update_active_path (seaf->sync_mgr,
1495                                                   params->repo_id,
1496                                                   path,
1497                                                   S_IFDIR,
1498                                                   status,
1499                                                   FALSE);
1500     }
1501 
1502     if (n == 0 && path[0] != 0 && is_writable) {
1503         if (!params->remain_files || *(params->remain_files) == NULL) {
1504             int rc = add_empty_dir_to_index (params->istate, path, st);
1505             if (rc == 1 && options && options->changeset) {
1506                 unsigned char allzero[20] = {0};
1507                 add_to_changeset (options->changeset,
1508                                   DIFF_STATUS_DIR_ADDED,
1509                                   allzero,
1510                                   st,
1511                                   NULL,
1512                                   path,
1513                                   NULL);
1514             }
1515         } else
1516             g_queue_push_tail (*(params->remain_files), g_strdup(path));
1517     }
1518 
1519     return 0;
1520 }
1521 
1522 /*
1523  * @remain_files: returns the files haven't been added under this path.
1524  *                If it's set to NULL, no partial commit will be created.
1525  */
1526 static int
add_recursive(const char * repo_id,int version,const char * modifier,struct index_state * istate,const char * worktree,const char * path,SeafileCrypt * crypt,gboolean ignore_empty_dir,GList * ignore_list,gint64 * total_size,GQueue ** remain_files,AddOptions * options)1527 add_recursive (const char *repo_id,
1528                int version,
1529                const char *modifier,
1530                struct index_state *istate,
1531                const char *worktree,
1532                const char *path,
1533                SeafileCrypt *crypt,
1534                gboolean ignore_empty_dir,
1535                GList *ignore_list,
1536                gint64 *total_size,
1537                GQueue **remain_files,
1538                AddOptions *options)
1539 {
1540     char *full_path;
1541     SeafStat st;
1542 
1543     full_path = g_build_path (PATH_SEPERATOR, worktree, path, NULL);
1544     if (seaf_stat (full_path, &st) < 0) {
1545         /* Ignore broken symlinks on Linux and Mac OS X */
1546         if (lstat (full_path, &st) == 0 && S_ISLNK(st.st_mode)) {
1547             g_free (full_path);
1548             return 0;
1549         }
1550         seaf_warning ("Failed to stat %s.\n", full_path);
1551         g_free (full_path);
1552         /* Ignore error. */
1553 
1554         seaf_sync_manager_update_active_path (seaf->sync_mgr,
1555                                               repo_id,
1556                                               path,
1557                                               0,
1558                                               SYNC_STATUS_ERROR,
1559                                               TRUE);
1560 
1561         return 0;
1562     }
1563 
1564     if (S_ISREG(st.st_mode)) {
1565         add_file (repo_id,
1566                   version,
1567                   modifier,
1568                   istate,
1569                   path,
1570                   full_path,
1571                   &st,
1572                   crypt,
1573                   total_size,
1574                   remain_files,
1575                   options);
1576     } else if (S_ISDIR(st.st_mode)) {
1577         AddParams params = {
1578             .repo_id = repo_id,
1579             .version = version,
1580             .modifier = modifier,
1581             .istate = istate,
1582             .worktree = worktree,
1583             .crypt = crypt,
1584             .ignore_empty_dir = ignore_empty_dir,
1585             .ignore_list = ignore_list,
1586             .total_size = total_size,
1587             .remain_files = remain_files,
1588             .options = options,
1589         };
1590 
1591         add_dir_recursive (path, full_path, &st, &params, FALSE);
1592     }
1593 
1594     g_free (full_path);
1595     return 0;
1596 }
1597 
1598 static gboolean
is_empty_dir(const char * path,GList * ignore_list)1599 is_empty_dir (const char *path, GList *ignore_list)
1600 {
1601     GDir *dir;
1602     const char *dname;
1603     gboolean ret = TRUE;
1604 
1605     dir = g_dir_open (path, 0, NULL);
1606     if (!dir) {
1607         return FALSE;
1608     }
1609 
1610     while ((dname = g_dir_read_name(dir)) != NULL) {
1611         if (!should_ignore(path, dname, ignore_list)) {
1612             ret = FALSE;
1613             break;
1614         }
1615     }
1616     g_dir_close (dir);
1617 
1618     return ret;
1619 }
1620 
1621 #else
1622 
1623 typedef struct IterCBData {
1624     AddParams *add_params;
1625     const char *parent;
1626     const char *full_parent;
1627     int n;
1628 
1629     /* If parent dir is ignored, all children are ignored too. */
1630     gboolean ignored;
1631 } IterCBData;
1632 
1633 static int
1634 add_dir_recursive (const char *path, const char *full_path, SeafStat *st,
1635                    AddParams *params, gboolean ignored);
1636 
1637 static int
iter_dir_cb(wchar_t * full_parent_w,WIN32_FIND_DATAW * fdata,void * user_data,gboolean * stop)1638 iter_dir_cb (wchar_t *full_parent_w,
1639              WIN32_FIND_DATAW *fdata,
1640              void *user_data,
1641              gboolean *stop)
1642 {
1643     IterCBData *data = user_data;
1644     AddParams *params = data->add_params;
1645     AddOptions *options = params->options;
1646     char *dname = NULL, *path = NULL, *full_path = NULL;
1647     SeafStat st;
1648     int ret = 0;
1649 
1650     dname = g_utf16_to_utf8 (fdata->cFileName, -1, NULL, NULL, NULL);
1651     if (!dname) {
1652         goto out;
1653     }
1654 
1655     path = g_build_path ("/", data->parent, dname, NULL);
1656     full_path = g_build_path ("/", params->worktree, path, NULL);
1657 
1658     seaf_stat_from_find_data (fdata, &st);
1659 
1660     if (data->ignored ||
1661         should_ignore(data->full_parent, dname, params->ignore_list)) {
1662         if (options && options->startup_scan) {
1663             if (fdata->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
1664                 add_dir_recursive (path, full_path, &st, params, TRUE);
1665             else
1666                 seaf_sync_manager_update_active_path (seaf->sync_mgr,
1667                                                       params->repo_id,
1668                                                       path,
1669                                                       S_IFREG,
1670                                                       SYNC_STATUS_IGNORED,
1671                                                       TRUE);
1672         }
1673         goto out;
1674     }
1675 
1676     if (fdata->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
1677         ret = add_dir_recursive (path, full_path, &st, params, FALSE);
1678     else
1679         ret = add_file (params->repo_id,
1680                         params->version,
1681                         params->modifier,
1682                         params->istate,
1683                         path,
1684                         full_path,
1685                         &st,
1686                         params->crypt,
1687                         params->total_size,
1688                         params->remain_files,
1689                         params->options);
1690 
1691     ++(data->n);
1692 
1693 out:
1694     g_free (dname);
1695     g_free (path);
1696     g_free (full_path);
1697 
1698     return 0;
1699 }
1700 
1701 static int
add_dir_recursive(const char * path,const char * full_path,SeafStat * st,AddParams * params,gboolean ignored)1702 add_dir_recursive (const char *path, const char *full_path, SeafStat *st,
1703                    AddParams *params, gboolean ignored)
1704 {
1705     AddOptions *options = params->options;
1706     IterCBData data;
1707     wchar_t *full_path_w;
1708     int ret = 0;
1709     gboolean is_writable = TRUE;
1710 
1711     memset (&data, 0, sizeof(data));
1712     data.add_params = params;
1713     data.parent = path;
1714     data.full_parent = full_path;
1715     data.ignored = ignored;
1716 
1717     full_path_w = win32_long_path (full_path);
1718     ret = traverse_directory_win32 (full_path_w, iter_dir_cb, &data);
1719     g_free (full_path_w);
1720 
1721     /* Ignore traverse dir error. */
1722     if (ret < 0) {
1723         seaf_sync_manager_update_active_path (seaf->sync_mgr,
1724                                               params->repo_id,
1725                                               path,
1726                                               S_IFDIR,
1727                                               SYNC_STATUS_ERROR,
1728                                               TRUE);
1729         return 0;
1730     }
1731 
1732     if (ignored) {
1733         seaf_sync_manager_update_active_path (seaf->sync_mgr,
1734                                               params->repo_id,
1735                                               path,
1736                                               S_IFDIR,
1737                                               SYNC_STATUS_IGNORED,
1738                                               TRUE);
1739         return 0;
1740     }
1741 
1742     if (options)
1743         is_writable = is_path_writable(params->repo_id,
1744                                         options->is_repo_ro, path);
1745 
1746     /* Update active path status for empty dir */
1747     if (options && options->startup_scan && ret == 0) {
1748         SyncStatus status;
1749         struct cache_entry *ce = index_name_exists (params->istate, path,
1750                                                     strlen(path), 0);
1751         if (!ce)
1752             status = SYNC_STATUS_SYNCING;
1753         else
1754             status = SYNC_STATUS_SYNCED;
1755 
1756 
1757         if (status == SYNC_STATUS_SYNCED || is_writable)
1758             seaf_sync_manager_update_active_path (seaf->sync_mgr,
1759                                                   params->repo_id,
1760                                                   path,
1761                                                   S_IFDIR,
1762                                                   status,
1763                                                   FALSE);
1764     }
1765 
1766     if (data.n == 0 && path[0] != 0 && !params->ignore_empty_dir && is_writable) {
1767         if (!params->remain_files || *(params->remain_files) == NULL) {
1768             int rc = add_empty_dir_to_index (params->istate, path, st);
1769             if (rc == 1 && options && options->changeset) {
1770                 unsigned char allzero[20] = {0};
1771                 add_to_changeset (options->changeset,
1772                                   DIFF_STATUS_DIR_ADDED,
1773                                   allzero,
1774                                   st,
1775                                   NULL,
1776                                   path,
1777                                   NULL);
1778             }
1779         } else
1780             g_queue_push_tail (*(params->remain_files), g_strdup(path));
1781     }
1782 
1783     return ret;
1784 }
1785 
1786 static int
add_recursive(const char * repo_id,int version,const char * modifier,struct index_state * istate,const char * worktree,const char * path,SeafileCrypt * crypt,gboolean ignore_empty_dir,GList * ignore_list,gint64 * total_size,GQueue ** remain_files,AddOptions * options)1787 add_recursive (const char *repo_id,
1788                int version,
1789                const char *modifier,
1790                struct index_state *istate,
1791                const char *worktree,
1792                const char *path,
1793                SeafileCrypt *crypt,
1794                gboolean ignore_empty_dir,
1795                GList *ignore_list,
1796                gint64 *total_size,
1797                GQueue **remain_files,
1798                AddOptions *options)
1799 {
1800     char *full_path;
1801     SeafStat st;
1802     int ret = 0;
1803 
1804     full_path = g_build_path (PATH_SEPERATOR, worktree, path, NULL);
1805     if (seaf_stat (full_path, &st) < 0) {
1806         seaf_warning ("Failed to stat %s.\n", full_path);
1807         g_free (full_path);
1808         seaf_sync_manager_update_active_path (seaf->sync_mgr,
1809                                               repo_id,
1810                                               path,
1811                                               0,
1812                                               SYNC_STATUS_ERROR,
1813                                               TRUE);
1814         /* Ignore error */
1815         return 0;
1816     }
1817 
1818     if (S_ISREG(st.st_mode)) {
1819         ret = add_file (repo_id,
1820                         version,
1821                         modifier,
1822                         istate,
1823                         path,
1824                         full_path,
1825                         &st,
1826                         crypt,
1827                         total_size,
1828                         remain_files,
1829                         options);
1830     } else if (S_ISDIR(st.st_mode)) {
1831         AddParams params = {
1832             .repo_id = repo_id,
1833             .version = version,
1834             .modifier = modifier,
1835             .istate = istate,
1836             .worktree = worktree,
1837             .crypt = crypt,
1838             .ignore_empty_dir = ignore_empty_dir,
1839             .ignore_list = ignore_list,
1840             .total_size = total_size,
1841             .remain_files = remain_files,
1842             .options = options,
1843         };
1844 
1845         ret = add_dir_recursive (path, full_path, &st, &params, FALSE);
1846     }
1847 
1848     g_free (full_path);
1849     return ret;
1850 }
1851 
1852 static gboolean
is_empty_dir(const char * path,GList * ignore_list)1853 is_empty_dir (const char *path, GList *ignore_list)
1854 {
1855     WIN32_FIND_DATAW fdata;
1856     HANDLE handle;
1857     wchar_t *pattern;
1858     wchar_t *path_w;
1859     char *dname;
1860     int path_len_w;
1861     DWORD error;
1862     gboolean ret = TRUE;
1863 
1864     path_w = win32_long_path (path);
1865 
1866     path_len_w = wcslen(path_w);
1867 
1868     pattern = g_new0 (wchar_t, (path_len_w + 3));
1869     wcscpy (pattern, path_w);
1870     wcscat (pattern, L"\\*");
1871 
1872     handle = FindFirstFileW (pattern, &fdata);
1873     if (handle == INVALID_HANDLE_VALUE) {
1874         seaf_warning ("FindFirstFile failed %s: %lu.\n",
1875                       path, GetLastError());
1876         ret = FALSE;
1877         goto out;
1878     }
1879 
1880     do {
1881         if (wcscmp (fdata.cFileName, L".") == 0 ||
1882             wcscmp (fdata.cFileName, L"..") == 0)
1883             continue;
1884 
1885         dname = g_utf16_to_utf8 (fdata.cFileName, -1, NULL, NULL, NULL);
1886         if (!dname || !should_ignore (path, dname, ignore_list)) {
1887             ret = FALSE;
1888             g_free (dname);
1889             FindClose (handle);
1890             goto out;
1891         }
1892         g_free (dname);
1893     } while (FindNextFileW (handle, &fdata) != 0);
1894 
1895     error = GetLastError();
1896     if (error != ERROR_NO_MORE_FILES) {
1897         seaf_warning ("FindNextFile failed %s: %lu.\n",
1898                       path, error);
1899     }
1900 
1901     FindClose (handle);
1902 
1903 out:
1904     g_free (path_w);
1905     g_free (pattern);
1906     return ret;
1907 }
1908 
1909 #endif  /* WIN32 */
1910 
1911 /* Returns whether the file should be removed from index. */
1912 static gboolean
check_locked_file_before_remove(LockedFileSet * fset,const char * path)1913 check_locked_file_before_remove (LockedFileSet *fset, const char *path)
1914 {
1915 #if defined WIN32 || defined __APPLE__
1916     if (!fset)
1917         return TRUE;
1918 
1919     LockedFile *file = locked_file_set_lookup (fset, path);
1920     gboolean ret = TRUE;
1921 
1922     if (file)
1923         ret = FALSE;
1924 
1925     return ret;
1926 #else
1927     return TRUE;
1928 #endif
1929 }
1930 
1931 static void
remove_deleted(struct index_state * istate,const char * worktree,const char * prefix,GList * ignore_list,LockedFileSet * fset,const char * repo_id,gboolean is_repo_ro,ChangeSet * changeset)1932 remove_deleted (struct index_state *istate, const char *worktree, const char *prefix,
1933                 GList *ignore_list, LockedFileSet *fset,
1934                 const char *repo_id, gboolean is_repo_ro,
1935                 ChangeSet *changeset)
1936 {
1937     struct cache_entry **ce_array = istate->cache;
1938     struct cache_entry *ce;
1939     char path[SEAF_PATH_MAX];
1940     unsigned int i;
1941     SeafStat st;
1942     int ret;
1943     gboolean not_exist;
1944 
1945     char *full_prefix = g_strconcat (prefix, "/", NULL);
1946     int len = strlen(full_prefix);
1947 
1948     for (i = 0; i < istate->cache_nr; ++i) {
1949         ce = ce_array[i];
1950 
1951         if (!is_path_writable (repo_id, is_repo_ro, ce->name))
1952             continue;
1953 
1954         if (seaf_filelock_manager_is_file_locked (seaf->filelock_mgr,
1955                                                   repo_id, ce->name)) {
1956             seaf_debug ("Remove deleted: %s is locked on server, ignore.\n", ce->name);
1957             continue;
1958         }
1959 
1960         if (prefix[0] != 0 && strcmp (ce->name, prefix) != 0 &&
1961             strncmp (ce->name, full_prefix, len) != 0)
1962             continue;
1963 
1964         snprintf (path, SEAF_PATH_MAX, "%s/%s", worktree, ce->name);
1965         not_exist = FALSE;
1966         ret = seaf_stat (path, &st);
1967         if (ret < 0 && errno == ENOENT)
1968             not_exist = TRUE;
1969 
1970         if (S_ISDIR (ce->ce_mode)) {
1971             if (ce->ce_ctime.sec != 0 || ce_stage(ce) != 0) {
1972                 if (not_exist || (ret == 0 && !S_ISDIR (st.st_mode))) {
1973                     /* Add to changeset only if dir is removed. */
1974                     ce->ce_flags |= CE_REMOVE;
1975                     if (changeset)
1976                         /* Remove the parent dir from change set if it becomes
1977                          * empty. If in the work tree the empty dir still exist,
1978                          * we'll add it back to changeset in add_recursive() later.
1979                          */
1980                         remove_from_changeset (changeset,
1981                                                DIFF_STATUS_DIR_DELETED,
1982                                                ce->name,
1983                                                TRUE,
1984                                                prefix);
1985                 } else if (!is_empty_dir (path, ignore_list)) {
1986                     /* Don't add to changeset if empty dir became non-empty. */
1987                     ce->ce_flags |= CE_REMOVE;
1988                 }
1989             }
1990         } else {
1991             /* If ce->ctime is 0 and stage is 0, it was not successfully checked out.
1992              * In this case we don't want to mistakenly remove the file
1993              * from the repo.
1994              */
1995             if ((not_exist || (ret == 0 && !S_ISREG (st.st_mode))) &&
1996                 (ce->ce_ctime.sec != 0 || ce_stage(ce) != 0) &&
1997                 check_locked_file_before_remove (fset, ce->name))
1998             {
1999                 ce_array[i]->ce_flags |= CE_REMOVE;
2000                 if (changeset)
2001                     remove_from_changeset (changeset,
2002                                            DIFF_STATUS_DELETED,
2003                                            ce->name,
2004                                            TRUE,
2005                                            prefix);
2006             }
2007         }
2008     }
2009 
2010     remove_marked_cache_entries (istate);
2011 
2012     g_free (full_prefix);
2013 }
2014 
2015 static int
scan_worktree_for_changes(struct index_state * istate,SeafRepo * repo,SeafileCrypt * crypt,GList * ignore_list,LockedFileSet * fset)2016 scan_worktree_for_changes (struct index_state *istate, SeafRepo *repo,
2017                            SeafileCrypt *crypt, GList *ignore_list,
2018                            LockedFileSet *fset)
2019 {
2020     remove_deleted (istate, repo->worktree, "", ignore_list, fset,
2021                     repo->id, repo->is_readonly, repo->changeset);
2022 
2023     AddOptions options;
2024     memset (&options, 0, sizeof(options));
2025     options.fset = fset;
2026     options.is_repo_ro = repo->is_readonly;
2027     options.changeset = repo->changeset;
2028 
2029     if (add_recursive (repo->id, repo->version, repo->email,
2030                        istate, repo->worktree, "", crypt, FALSE, ignore_list,
2031                        NULL, NULL, &options) < 0)
2032         return -1;
2033 
2034     return 0;
2035 }
2036 
2037 static gboolean
check_full_path_ignore(const char * worktree,const char * path,GList * ignore_list)2038 check_full_path_ignore (const char *worktree, const char *path, GList *ignore_list)
2039 {
2040     char **tokens;
2041     guint i;
2042     guint n;
2043     gboolean ret = FALSE;
2044 
2045     tokens = g_strsplit (path, "/", 0);
2046     n = g_strv_length (tokens);
2047     for (i = 0; i < n; ++i) {
2048         /* don't check ignore_list */
2049         if (should_ignore (NULL, tokens[i], ignore_list)) {
2050             ret = TRUE;
2051             goto out;
2052         }
2053     }
2054 
2055     char *full_path = g_build_path ("/", worktree, path, NULL);
2056     if (seaf_repo_check_ignore_file (ignore_list, full_path))
2057         ret = TRUE;
2058     g_free (full_path);
2059 
2060 out:
2061     g_strfreev (tokens);
2062     return ret;
2063 }
2064 
2065 static int
add_path_to_index(SeafRepo * repo,struct index_state * istate,SeafileCrypt * crypt,const char * path,GList * ignore_list,GList ** scanned_dirs,gint64 * total_size,GQueue ** remain_files,LockedFileSet * fset)2066 add_path_to_index (SeafRepo *repo, struct index_state *istate,
2067                    SeafileCrypt *crypt, const char *path, GList *ignore_list,
2068                    GList **scanned_dirs, gint64 *total_size, GQueue **remain_files,
2069                    LockedFileSet *fset)
2070 {
2071     char *full_path;
2072     SeafStat st;
2073     AddOptions options;
2074 
2075     /* When a repo is initially added, a SCAN_DIR event will be created
2076      * for the worktree root "".
2077      */
2078     if (path[0] == 0) {
2079         remove_deleted (istate, repo->worktree, "", ignore_list, fset,
2080                         repo->id, repo->is_readonly, repo->changeset);
2081 
2082         memset (&options, 0, sizeof(options));
2083         options.fset = fset;
2084         options.is_repo_ro = repo->is_readonly;
2085         options.startup_scan = TRUE;
2086         options.changeset = repo->changeset;
2087 
2088         add_recursive (repo->id, repo->version, repo->email, istate,
2089                        repo->worktree, path,
2090                        crypt, FALSE, ignore_list,
2091                        total_size, remain_files, &options);
2092 
2093         return 0;
2094     }
2095 
2096     /* If we've recursively scanned the parent directory, don't need to scan
2097      * any files under it any more.
2098      */
2099     GList *ptr;
2100     char *dir, *full_dir;
2101     for (ptr = *scanned_dirs; ptr; ptr = ptr->next) {
2102         dir = ptr->data;
2103         /* exact match */
2104         if (strcmp (dir, path) == 0) {
2105             seaf_debug ("%s has been scanned before, skip adding.\n", path);
2106             return 0;
2107         }
2108 
2109         /* prefix match. */
2110         full_dir = g_strconcat (dir, "/", NULL);
2111         if (strncmp (full_dir, path, strlen(full_dir)) == 0) {
2112             g_free (full_dir);
2113             seaf_debug ("%s has been scanned before, skip adding.\n", path);
2114             return 0;
2115         }
2116         g_free (full_dir);
2117     }
2118 
2119     if (check_full_path_ignore (repo->worktree, path, ignore_list))
2120         return 0;
2121 
2122     full_path = g_build_filename (repo->worktree, path, NULL);
2123 
2124     if (seaf_stat (full_path, &st) < 0) {
2125         if (errno != ENOENT)
2126             send_file_sync_error_notification (repo->id, repo->name, path,
2127                                                SYNC_ERROR_ID_INDEX_ERROR);
2128         seaf_warning ("Failed to stat %s: %s.\n", path, strerror(errno));
2129         g_free (full_path);
2130         return -1;
2131     }
2132 
2133     if (S_ISDIR(st.st_mode))
2134         *scanned_dirs = g_list_prepend (*scanned_dirs, g_strdup(path));
2135 
2136     memset (&options, 0, sizeof(options));
2137     options.fset = fset;
2138     options.is_repo_ro = repo->is_readonly;
2139     options.changeset = repo->changeset;
2140 
2141     /* Add is always recursive */
2142     add_recursive (repo->id, repo->version, repo->email, istate, repo->worktree, path,
2143                    crypt, FALSE, ignore_list, total_size, remain_files, &options);
2144 
2145     g_free (full_path);
2146     return 0;
2147 }
2148 
2149 #if 0
2150 
2151 static int
2152 add_path_to_index (SeafRepo *repo, struct index_state *istate,
2153                    SeafileCrypt *crypt, const char *path, GList *ignore_list,
2154                    GList **scanned_dirs, gint64 *total_size, GQueue **remain_files,
2155                    LockedFileSet *fset)
2156 {
2157     /* If we've recursively scanned the parent directory, don't need to scan
2158      * any files under it any more.
2159      */
2160     GList *ptr;
2161     char *dir, *full_dir;
2162     for (ptr = *scanned_dirs; ptr; ptr = ptr->next) {
2163         dir = ptr->data;
2164 
2165         /* Have scanned from root directory. */
2166         if (dir[0] == 0) {
2167             seaf_debug ("%s has been scanned before, skip adding.\n", path);
2168             return 0;
2169         }
2170 
2171         /* exact match */
2172         if (strcmp (dir, path) == 0) {
2173             seaf_debug ("%s has been scanned before, skip adding.\n", path);
2174             return 0;
2175         }
2176 
2177         /* prefix match. */
2178         full_dir = g_strconcat (dir, "/", NULL);
2179         if (strncmp (full_dir, path, strlen(full_dir)) == 0) {
2180             g_free (full_dir);
2181             seaf_debug ("%s has been scanned before, skip adding.\n", path);
2182             return 0;
2183         }
2184         g_free (full_dir);
2185     }
2186 
2187     if (path[0] != 0 && check_full_path_ignore (repo->worktree, path, ignore_list))
2188         return 0;
2189 
2190     remove_deleted (istate, repo->worktree, path, ignore_list, NULL,
2191                     repo->id, repo->is_readonly, repo->changeset);
2192 
2193     *scanned_dirs = g_list_prepend (*scanned_dirs, g_strdup(path));
2194 
2195     AddOptions options;
2196     memset (&options, 0, sizeof(options));
2197     options.fset = fset;
2198     options.is_repo_ro = repo->is_readonly;
2199     options.changeset = repo->changeset;
2200     /* When something is changed in the root directory, update active path
2201      * sync status when scanning the worktree. This is inaccurate. This will
2202      * be changed after we process fs events on Mac more precisely.
2203      */
2204     if (path[0] == 0)
2205         options.startup_scan = TRUE;
2206 
2207     /* Add is always recursive */
2208     add_recursive (repo->id, repo->version, repo->email, istate, repo->worktree, path,
2209                    crypt, FALSE, ignore_list, total_size, remain_files, &options);
2210 
2211     return 0;
2212 }
2213 
2214 #endif  /* __APPLE__ */
2215 
2216 static int
add_remain_files(SeafRepo * repo,struct index_state * istate,SeafileCrypt * crypt,GQueue * remain_files,GList * ignore_list,gint64 * total_size)2217 add_remain_files (SeafRepo *repo, struct index_state *istate,
2218                   SeafileCrypt *crypt, GQueue *remain_files,
2219                   GList *ignore_list, gint64 *total_size)
2220 {
2221     char *path;
2222     char *full_path;
2223     SeafStat st;
2224     struct cache_entry *ce;
2225 
2226     while ((path = g_queue_pop_head (remain_files)) != NULL) {
2227         full_path = g_build_filename (repo->worktree, path, NULL);
2228         if (seaf_stat (full_path, &st) < 0) {
2229             seaf_warning ("Failed to stat %s: %s.\n", full_path, strerror(errno));
2230             g_free (path);
2231             g_free (full_path);
2232             continue;
2233         }
2234 
2235         if (S_ISREG(st.st_mode)) {
2236             gboolean added = FALSE;
2237             int ret = 0;
2238             ret = add_to_index (repo->id, repo->version, istate, path, full_path,
2239                                 &st, 0, crypt, index_cb, repo->email, &added);
2240             if (added) {
2241                 ce = index_name_exists (istate, path, strlen(path), 0);
2242                 add_to_changeset (repo->changeset,
2243                                   DIFF_STATUS_ADDED,
2244                                   ce->sha1,
2245                                   &st,
2246                                   repo->email,
2247                                   path,
2248                                   NULL);
2249 
2250                 *total_size += (gint64)(st.st_size);
2251                 if (*total_size >= MAX_COMMIT_SIZE) {
2252                     g_free (path);
2253                     g_free (full_path);
2254                     break;
2255                 }
2256             } else {
2257                 seaf_sync_manager_update_active_path (seaf->sync_mgr,
2258                                                       repo->id,
2259                                                       path,
2260                                                       S_IFREG,
2261                                                       SYNC_STATUS_SYNCED,
2262                                                       TRUE);
2263             }
2264             if (ret < 0) {
2265                 seaf_sync_manager_update_active_path (seaf->sync_mgr,
2266                                                       repo->id,
2267                                                       path,
2268                                                       S_IFREG,
2269                                                       SYNC_STATUS_ERROR,
2270                                                       TRUE);
2271                 send_file_sync_error_notification (repo->id, NULL, path,
2272                                                    SYNC_ERROR_ID_INDEX_ERROR);
2273             }
2274         } else if (S_ISDIR(st.st_mode)) {
2275             if (is_empty_dir (full_path, ignore_list)) {
2276                 int rc = add_empty_dir_to_index (istate, path, &st);
2277                 if (rc == 1) {
2278                     unsigned char allzero[20] = {0};
2279                     add_to_changeset (repo->changeset,
2280                                       DIFF_STATUS_DIR_ADDED,
2281                                       allzero,
2282                                       &st,
2283                                       NULL,
2284                                       path,
2285                                       NULL);
2286                 }
2287             }
2288         }
2289         g_free (path);
2290         g_free (full_path);
2291     }
2292 
2293     return 0;
2294 }
2295 
2296 static void
try_add_empty_parent_dir_entry(const char * worktree,struct index_state * istate,const char * path)2297 try_add_empty_parent_dir_entry (const char *worktree,
2298                                 struct index_state *istate,
2299                                 const char *path)
2300 {
2301     if (index_name_exists (istate, path, strlen(path), 0) != NULL)
2302         return;
2303 
2304     char *parent_dir = g_path_get_dirname (path);
2305 
2306     /* Parent dir is the worktree dir. */
2307     if (strcmp (parent_dir, ".") == 0) {
2308         g_free (parent_dir);
2309         return;
2310     }
2311 
2312     char *full_dir = g_build_filename (worktree, parent_dir, NULL);
2313     SeafStat st;
2314     if (seaf_stat (full_dir, &st) < 0) {
2315         goto out;
2316     }
2317 
2318     add_empty_dir_to_index_with_check (istate, parent_dir, &st);
2319 
2320 out:
2321     g_free (parent_dir);
2322     g_free (full_dir);
2323 }
2324 
2325 static void
try_add_empty_parent_dir_entry_from_wt(const char * worktree,struct index_state * istate,GList * ignore_list,const char * path)2326 try_add_empty_parent_dir_entry_from_wt (const char *worktree,
2327                                         struct index_state *istate,
2328                                         GList *ignore_list,
2329                                         const char *path)
2330 {
2331     if (index_name_exists (istate, path, strlen(path), 0) != NULL)
2332         return;
2333 
2334     char *parent_dir = g_path_get_dirname (path);
2335 
2336     /* Parent dir is the worktree dir. */
2337     if (strcmp (parent_dir, ".") == 0) {
2338         g_free (parent_dir);
2339         return;
2340     }
2341 
2342     char *full_dir = g_build_filename (worktree, parent_dir, NULL);
2343     SeafStat st;
2344     if (seaf_stat (full_dir, &st) < 0) {
2345         goto out;
2346     }
2347 
2348     if (is_empty_dir (full_dir, ignore_list)) {
2349 #ifdef WIN32
2350         wchar_t *parent_dir_w = g_utf8_to_utf16 (parent_dir, -1, NULL, NULL, NULL);
2351         wchar_t *pw;
2352         for (pw = parent_dir_w; *pw != L'\0'; ++pw)
2353             if (*pw == L'/')
2354                 *pw = L'\\';
2355 
2356         wchar_t *long_path = win32_83_path_to_long_path (worktree,
2357                                                          parent_dir_w,
2358                                                          wcslen(parent_dir_w));
2359         g_free (parent_dir_w);
2360         if (!long_path) {
2361             seaf_warning ("Convert %s to long path failed.\n", parent_dir);
2362             goto out;
2363         }
2364 
2365         char *utf8_path = g_utf16_to_utf8 (long_path, -1, NULL, NULL, NULL);
2366         if (!utf8_path) {
2367             g_free (long_path);
2368             goto out;
2369         }
2370 
2371         char *p;
2372         for (p = utf8_path; *p != 0; ++p)
2373             if (*p == '\\')
2374                 *p = '/';
2375         g_free (long_path);
2376 
2377         add_empty_dir_to_index (istate, utf8_path, &st);
2378 #else
2379         add_empty_dir_to_index (istate, parent_dir, &st);
2380 #endif
2381     }
2382 
2383 out:
2384     g_free (parent_dir);
2385     g_free (full_dir);
2386 }
2387 
2388 static void
update_attributes(SeafRepo * repo,struct index_state * istate,const char * worktree,const char * path)2389 update_attributes (SeafRepo *repo,
2390                    struct index_state *istate,
2391                    const char *worktree,
2392                    const char *path)
2393 {
2394     ChangeSet *changeset = repo->changeset;
2395     char *full_path;
2396     struct cache_entry *ce;
2397     SeafStat st;
2398 
2399     ce = index_name_exists (istate, path, strlen(path), 0);
2400     if (!ce)
2401         return;
2402 
2403     full_path = g_build_filename (worktree, path, NULL);
2404     if (seaf_stat (full_path, &st) < 0) {
2405         seaf_warning ("Failed to stat %s: %s.\n", full_path, strerror(errno));
2406         g_free (full_path);
2407         return;
2408     }
2409 
2410     unsigned int new_mode = create_ce_mode (st.st_mode);
2411     if (new_mode != ce->ce_mode || st.st_mtime != ce->ce_mtime.sec) {
2412         ce->ce_mode = new_mode;
2413         ce->ce_mtime.sec = st.st_mtime;
2414         istate->cache_changed = 1;
2415         add_to_changeset (changeset,
2416                           DIFF_STATUS_MODIFIED,
2417                           ce->sha1,
2418                           &st,
2419                           repo->email,
2420                           path,
2421                           NULL);
2422     }
2423     g_free (full_path);
2424 }
2425 
2426 #ifdef WIN32
2427 static void
scan_subtree_for_deletion(const char * repo_id,struct index_state * istate,const char * worktree,const char * path,GList * ignore_list,LockedFileSet * fset,gboolean is_readonly,GList ** scanned_dirs,ChangeSet * changeset)2428 scan_subtree_for_deletion (const char *repo_id,
2429                            struct index_state *istate,
2430                            const char *worktree,
2431                            const char *path,
2432                            GList *ignore_list,
2433                            LockedFileSet *fset,
2434                            gboolean is_readonly,
2435                            GList **scanned_dirs,
2436                            ChangeSet *changeset)
2437 {
2438     wchar_t *path_w = NULL;
2439     wchar_t *dir_w = NULL;
2440     wchar_t *p;
2441     char *dir = NULL;
2442     char *p2;
2443 
2444     /* In most file systems, like NTFS, 8.3 format path should contain ~.
2445      * Also note that *~ files are ignored.
2446      */
2447     if (!strchr (path, '~') || path[strlen(path)-1] == '~')
2448         return;
2449 
2450     path_w = g_utf8_to_utf16 (path, -1, NULL, NULL, NULL);
2451 
2452     for (p = path_w; *p != L'\0'; ++p)
2453         if (*p == L'/')
2454             *p = L'\\';
2455 
2456     while (1) {
2457         p = wcsrchr (path_w, L'\\');
2458         if (p)
2459             *p = L'\0';
2460         else
2461             break;
2462 
2463         dir_w = win32_83_path_to_long_path (worktree, path_w, wcslen(path_w));
2464         if (dir_w)
2465             break;
2466     }
2467 
2468     if (!dir_w)
2469         dir_w = wcsdup(L"");
2470 
2471     dir = g_utf16_to_utf8 (dir_w, -1, NULL, NULL, NULL);
2472     if (!dir)
2473         goto out;
2474 
2475     for (p2 = dir; *p2 != 0; ++p2)
2476         if (*p2 == '\\')
2477             *p2 = '/';
2478 
2479     /* If we've recursively scanned the parent directory, don't need to scan
2480      * any files under it any more.
2481      */
2482     GList *ptr;
2483     char *s, *full_s;
2484     for (ptr = *scanned_dirs; ptr; ptr = ptr->next) {
2485         s = ptr->data;
2486 
2487         /* Have scanned from root directory. */
2488         if (s[0] == 0) {
2489             goto out;
2490         }
2491 
2492         /* exact match */
2493         if (strcmp (s, path) == 0) {
2494             goto out;
2495         }
2496 
2497         /* prefix match. */
2498         full_s = g_strconcat (s, "/", NULL);
2499         if (strncmp (full_s, dir, strlen(full_s)) == 0) {
2500             g_free (full_s);
2501             goto out;
2502         }
2503         g_free (full_s);
2504     }
2505 
2506     *scanned_dirs = g_list_prepend (*scanned_dirs, g_strdup(dir));
2507 
2508     remove_deleted (istate, worktree, dir, ignore_list, fset,
2509                     repo_id, is_readonly, changeset);
2510 
2511     /* After remove_deleted(), empty dirs are left not removed in changeset.
2512      * This can be fixed by removing the accurate deleted path. In most cases,
2513      * basename doesn't contain ~, so we can always get the accurate path.
2514      */
2515     /* if (!convertion_failed) { */
2516     /*     char *basename = strrchr (path, '/'); */
2517     /*     char *deleted_path = NULL; */
2518     /*     if (basename) { */
2519     /*         deleted_path = g_build_path ("/", dir, basename, NULL); */
2520     /*         add_to_changeset (changeset, */
2521     /*                           DIFF_STATUS_DELETED, */
2522     /*                           NULL, */
2523     /*                           NULL, */
2524     /*                           NULL, */
2525     /*                           deleted_path, */
2526     /*                           NULL, */
2527     /*                           FALSE); */
2528     /*         g_free (deleted_path); */
2529     /*     } */
2530     /* } */
2531 
2532 out:
2533     g_free (path_w);
2534     g_free (dir_w);
2535     g_free (dir);
2536 }
2537 #else
2538 static void
scan_subtree_for_deletion(const char * repo_id,struct index_state * istate,const char * worktree,const char * path,GList * ignore_list,LockedFileSet * fset,gboolean is_readonly,GList ** scanned_dirs,ChangeSet * changeset)2539 scan_subtree_for_deletion (const char *repo_id,
2540                            struct index_state *istate,
2541                            const char *worktree,
2542                            const char *path,
2543                            GList *ignore_list,
2544                            LockedFileSet *fset,
2545                            gboolean is_readonly,
2546                            GList **scanned_dirs,
2547                            ChangeSet *changeset)
2548 {
2549 }
2550 #endif
2551 
2552 /* Return TRUE if the caller should stop processing next event. */
2553 static gboolean
handle_add_files(SeafRepo * repo,struct index_state * istate,SeafileCrypt * crypt,GList * ignore_list,LockedFileSet * fset,WTStatus * status,WTEvent * event,GList ** scanned_dirs,gint64 * total_size)2554 handle_add_files (SeafRepo *repo, struct index_state *istate,
2555                   SeafileCrypt *crypt, GList *ignore_list,
2556                   LockedFileSet *fset,
2557                   WTStatus *status, WTEvent *event,
2558                   GList **scanned_dirs, gint64 *total_size)
2559 {
2560     SyncInfo *info;
2561 
2562     if (!repo->create_partial_commit) {
2563         /* XXX: We now use remain_files = NULL to signify not creating
2564          * partial commits. It's better to use total_size = NULL for
2565          * that purpose.
2566          */
2567         add_path_to_index (repo, istate, crypt, event->path,
2568                            ignore_list, scanned_dirs,
2569                            total_size, NULL, NULL);
2570     } else if (!event->remain_files) {
2571         GQueue *remain_files = NULL;
2572         add_path_to_index (repo, istate, crypt, event->path,
2573                            ignore_list, scanned_dirs,
2574                            total_size, &remain_files, fset);
2575         if (*total_size >= MAX_COMMIT_SIZE) {
2576             seaf_message ("Creating partial commit after adding %s.\n",
2577                           event->path);
2578 
2579             status->partial_commit = TRUE;
2580 
2581             /* An event for a new folder may contain many files.
2582              * If the total_size become larger than 100MB after adding
2583              * some of these files, the remaining file paths will be
2584              * cached in remain files. This way we don't need to scan
2585              * the folder again next time.
2586              */
2587             if (remain_files) {
2588                 if (g_queue_get_length (remain_files) == 0) {
2589                     g_queue_free (remain_files);
2590                     return TRUE;
2591                 }
2592 
2593                 seaf_message ("Remain files for %s.\n", event->path);
2594 
2595                 /* Cache remaining files in the event structure. */
2596                 event->remain_files = remain_files;
2597 
2598                 pthread_mutex_lock (&status->q_lock);
2599                 g_queue_push_head (status->event_q, event);
2600                 pthread_mutex_unlock (&status->q_lock);
2601 
2602                 info = seaf_sync_manager_get_sync_info (seaf->sync_mgr, repo->id);
2603                 if (!info->multipart_upload) {
2604                     info->multipart_upload = TRUE;
2605                     info->total_bytes = *total_size;
2606                 }
2607             }
2608 
2609             return TRUE;
2610         }
2611     } else {
2612         seaf_message ("Adding remaining files for %s.\n", event->path);
2613 
2614         add_remain_files (repo, istate, crypt, event->remain_files,
2615                           ignore_list, total_size);
2616         if (g_queue_get_length (event->remain_files) != 0) {
2617             pthread_mutex_lock (&status->q_lock);
2618             g_queue_push_head (status->event_q, event);
2619             pthread_mutex_unlock (&status->q_lock);
2620             return TRUE;
2621         } else {
2622             info = seaf_sync_manager_get_sync_info (seaf->sync_mgr, repo->id);
2623             info->end_multipart_upload = TRUE;
2624             return TRUE;
2625         }
2626         if (*total_size >= MAX_COMMIT_SIZE)
2627             return TRUE;
2628     }
2629 
2630     return FALSE;
2631 }
2632 
2633 #ifdef __APPLE__
2634 
2635 /* struct _WTDirent { */
2636 /*     char *dname; */
2637 /*     struct stat st; */
2638 /* }; */
2639 /* typedef struct _WTDirent WTDirent; */
2640 
2641 /* static gint */
2642 /* compare_wt_dirents (gconstpointer a, gconstpointer b) */
2643 /* { */
2644 /*     const WTDirent *dent_a = a, *dent_b = b; */
2645 
2646 /*     return (strcmp (dent_a->dname, dent_b->dname)); */
2647 /* } */
2648 
2649 /* static GList * */
2650 /* get_sorted_wt_dirents (const char *dir_path, const char *full_dir_path, */
2651 /*                        gboolean *error) */
2652 /* { */
2653 /*     GDir *dir; */
2654 /*     GError *err = NULL; */
2655 /*     const char *name; */
2656 /*     char *dname; */
2657 /*     char *full_sub_path, *sub_path; */
2658 /*     WTDirent *dent; */
2659 /*     GList *ret = NULL; */
2660 
2661 /*     dir = g_dir_open (full_dir_path, 0, &err); */
2662 /*     if (!dir) { */
2663 /*         seaf_warning ("Failed to open dir %s: %s.\n", full_dir_path, err->message); */
2664 /*         *error = TRUE; */
2665 /*         return NULL; */
2666 /*     } */
2667 
2668 /*     while ((name = g_dir_read_name(dir)) != NULL) { */
2669 /*         dname = g_utf8_normalize (name, -1, G_NORMALIZE_NFC); */
2670 /*         sub_path = g_strconcat (dir_path, "/", dname, NULL); */
2671 /*         full_sub_path = g_strconcat (full_dir_path, "/", dname, NULL); */
2672 
2673 /*         dent = g_new0 (WTDirent, 1); */
2674 /*         dent->dname = dname; */
2675 
2676 /*         if (stat (full_sub_path, &dent->st) < 0) { */
2677 /*             seaf_warning ("Failed to stat %s: %s.\n", full_sub_path, strerror(errno)); */
2678 /*             g_free (dname); */
2679 /*             g_free (sub_path); */
2680 /*             g_free (full_sub_path); */
2681 /*             g_free (dent); */
2682 /*             continue; */
2683 /*         } */
2684 
2685 /*         ret = g_list_prepend (ret, dent); */
2686 
2687 /*         g_free (sub_path); */
2688 /*         g_free (full_sub_path); */
2689 /*     } */
2690 
2691 /*     g_dir_close (dir); */
2692 
2693 /*     ret = g_list_sort (ret, compare_wt_dirents); */
2694 /*     return ret; */
2695 /* } */
2696 
2697 /* static void */
2698 /* wt_dirent_free (WTDirent *dent) */
2699 /* { */
2700 /*     if (!dent) */
2701 /*         return; */
2702 /*     g_free (dent->dname); */
2703 /*     g_free (dent); */
2704 /* } */
2705 
2706 /* inline static char * */
2707 /* concat_sub_path (const char *dir, const char *dname) */
2708 /* { */
2709 /*     if (dir[0] != 0) */
2710 /*         return g_strconcat(dir, "/", dname, NULL); */
2711 /*     else */
2712 /*         return g_strdup(dname); */
2713 /* } */
2714 
2715 /* static int */
2716 /* get_changed_paths_in_folder (SeafRepo *repo, struct index_state *istate, */
2717 /*                              const char *dir_path, */
2718 /*                              GList **add, GList **mod, GList **del) */
2719 /* { */
2720 /*     char *full_dir_path; */
2721 /*     GList *wt_dents = NULL, *index_dents = NULL; */
2722 /*     gboolean error = FALSE; */
2723 
2724 /*     full_dir_path = g_build_filename(repo->worktree, dir_path, NULL); */
2725 
2726 /*     wt_dents = get_sorted_wt_dirents (dir_path, full_dir_path, &error); */
2727 /*     if (error) { */
2728 /*         g_free (full_dir_path); */
2729 /*         return -1; */
2730 /*     } */
2731 
2732 /*     index_dents = list_dirents_from_index (istate, dir_path); */
2733 
2734 /*     GList *p; */
2735 /*     IndexDirent *dent; */
2736 /*     for (p = index_dents; p; p = p->next) { */
2737 /*         dent = p->data; */
2738 /*     } */
2739 
2740 /*     GList *p1 = wt_dents, *p2 = index_dents; */
2741 /*     WTDirent *dent1; */
2742 /*     IndexDirent *dent2; */
2743 
2744 /*     while (p1 && p2) { */
2745 /*         dent1 = p1->data; */
2746 /*         dent2 = p2->data; */
2747 
2748 /*         int rc = strcmp (dent1->dname, dent2->dname); */
2749 /*         if (rc == 0) { */
2750 /*             if (S_ISREG(dent1->st.st_mode) && !dent2->is_dir) { */
2751 /*                 if (dent1->st.st_mtime != dent2->ce->ce_mtime.sec) */
2752 /*                     *mod = g_list_prepend (*mod, concat_sub_path(dir_path, dent1->dname)); */
2753 /*             } else if ((S_ISREG(dent1->st.st_mode) && dent2->is_dir) || */
2754 /*                        (S_ISDIR(dent1->st.st_mode) && !dent2->is_dir)) { */
2755 /*                 *add = g_list_prepend (*add, concat_sub_path(dir_path, dent1->dname)); */
2756 /*                 *del = g_list_prepend (*del, concat_sub_path(dir_path, dent1->dname)); */
2757 /*             } */
2758 /*             p1 = p1->next; */
2759 /*             p2 = p2->next; */
2760 /*         } else if (rc < 0) { */
2761 /*             *add = g_list_prepend (*add, concat_sub_path(dir_path, dent1->dname)); */
2762 /*             p1 = p1->next; */
2763 /*         } else { */
2764 /*             *del = g_list_prepend (*del, concat_sub_path(dir_path, dent2->dname)); */
2765 /*             p2 = p2->next; */
2766 /*         } */
2767 /*     } */
2768 
2769 /*     while (p1) { */
2770 /*         dent1 = p1->data; */
2771 /*         *add = g_list_prepend (*add, concat_sub_path(dir_path, dent1->dname)); */
2772 /*         p1 = p1->next; */
2773 /*     } */
2774 
2775 /*     while (p2) { */
2776 /*         dent2 = p2->data; */
2777 /*         *del = g_list_prepend (*del, concat_sub_path(dir_path, dent2->dname)); */
2778 /*         p2 = p2->next; */
2779 /*     } */
2780 
2781 /*     g_free (full_dir_path); */
2782 /*     g_list_free_full (wt_dents, (GDestroyNotify)wt_dirent_free); */
2783 /*     g_list_free_full (index_dents, (GDestroyNotify)index_dirent_free); */
2784 /*     return 0; */
2785 /* } */
2786 
2787 #endif  /* __APPLE__ */
2788 
2789 static void
update_active_file(SeafRepo * repo,const char * path,SeafStat * st,struct index_state * istate,gboolean ignored)2790 update_active_file (SeafRepo *repo,
2791                     const char *path,
2792                     SeafStat *st,
2793                     struct index_state *istate,
2794                     gboolean ignored)
2795 {
2796     if (ignored) {
2797         seaf_sync_manager_update_active_path (seaf->sync_mgr,
2798                                               repo->id,
2799                                               path,
2800                                               S_IFREG,
2801                                               SYNC_STATUS_IGNORED,
2802                                               TRUE);
2803     } else {
2804         SyncStatus status;
2805         gboolean is_writable;
2806 
2807         struct cache_entry *ce = index_name_exists(istate, path, strlen(path), 0);
2808         if (!ce || ie_match_stat(ce, st, 0) != 0)
2809             status = SYNC_STATUS_SYNCING;
2810         else
2811             status = SYNC_STATUS_SYNCED;
2812 
2813         is_writable = is_path_writable (repo->id, repo->is_readonly, path);
2814 
2815         if (!is_writable && status == SYNC_STATUS_SYNCING)
2816             seaf_sync_manager_delete_active_path (seaf->sync_mgr,
2817                                                   repo->id,
2818                                                   path);
2819         else
2820             seaf_sync_manager_update_active_path (seaf->sync_mgr,
2821                                                   repo->id,
2822                                                   path,
2823                                                   S_IFREG,
2824                                                   status,
2825                                                   TRUE);
2826     }
2827 }
2828 
2829 #ifdef WIN32
2830 
2831 typedef struct _UpdatePathData {
2832     SeafRepo *repo;
2833     struct index_state *istate;
2834     GList *ignore_list;
2835 
2836     const char *parent;
2837     const char *full_parent;
2838     gboolean ignored;
2839 } UpdatePathData;
2840 
2841 static void
2842 update_active_path_recursive (SeafRepo *repo,
2843                               const char *path,
2844                               struct index_state *istate,
2845                               GList *ignore_list,
2846                               gboolean ignored);
2847 
2848 static int
update_active_path_cb(wchar_t * full_parent_w,WIN32_FIND_DATAW * fdata,void * user_data,gboolean * stop)2849 update_active_path_cb (wchar_t *full_parent_w,
2850                        WIN32_FIND_DATAW *fdata,
2851                        void *user_data,
2852                        gboolean *stop)
2853 {
2854     UpdatePathData *upd_data = user_data;
2855     char *dname;
2856     char *path;
2857     gboolean ignored = FALSE;
2858     SeafStat st;
2859 
2860     dname = g_utf16_to_utf8 (fdata->cFileName, -1, NULL, NULL, NULL);
2861     if (!dname)
2862         return 0;
2863 
2864     path = g_build_path ("/", upd_data->parent, dname, NULL);
2865 
2866     if (upd_data->ignored || should_ignore (upd_data->full_parent, dname, upd_data->ignore_list))
2867         ignored = TRUE;
2868 
2869     seaf_stat_from_find_data (fdata, &st);
2870 
2871     if (fdata->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
2872         update_active_path_recursive (upd_data->repo,
2873                                       path,
2874                                       upd_data->istate,
2875                                       upd_data->ignore_list,
2876                                       ignored);
2877     } else {
2878         update_active_file (upd_data->repo,
2879                             path,
2880                             &st,
2881                             upd_data->istate,
2882                             ignored);
2883     }
2884 
2885     g_free (dname);
2886     g_free (path);
2887 
2888     return 0;
2889 }
2890 
2891 static void
update_active_path_recursive(SeafRepo * repo,const char * path,struct index_state * istate,GList * ignore_list,gboolean ignored)2892 update_active_path_recursive (SeafRepo *repo,
2893                               const char *path,
2894                               struct index_state *istate,
2895                               GList *ignore_list,
2896                               gboolean ignored)
2897 {
2898     char *full_path;
2899     wchar_t *full_path_w;
2900     int ret = 0;
2901     UpdatePathData upd_data;
2902 
2903     full_path = g_build_filename (repo->worktree, path, NULL);
2904 
2905     memset (&upd_data, 0, sizeof(upd_data));
2906     upd_data.repo = repo;
2907     upd_data.istate = istate;
2908     upd_data.ignore_list = ignore_list;
2909     upd_data.parent = path;
2910     upd_data.full_parent = full_path;
2911     upd_data.ignored = ignored;
2912 
2913     full_path_w = win32_long_path (full_path);
2914     ret = traverse_directory_win32 (full_path_w, update_active_path_cb, &upd_data);
2915     g_free (full_path_w);
2916     g_free (full_path);
2917 
2918     if (ret < 0)
2919         return;
2920 
2921     /* Don't set sync status for read-only paths, since changes to read-only
2922      * files are ignored.
2923      */
2924     if (!is_path_writable (repo->id, repo->is_readonly, path))
2925         return;
2926 
2927     /* traverse_directory_win32() returns number of entries in the directory. */
2928     if (ret == 0 && path[0] != 0) {
2929         if (ignored) {
2930             seaf_sync_manager_update_active_path (seaf->sync_mgr,
2931                                                   repo->id,
2932                                                   path,
2933                                                   S_IFDIR,
2934                                                   SYNC_STATUS_IGNORED,
2935                                                   TRUE);
2936         } else {
2937             /* There is no need to update an empty dir. */
2938             SyncStatus status;
2939             struct cache_entry *ce = index_name_exists(istate, path, strlen(path), 0);
2940             if (!ce)
2941                 status = SYNC_STATUS_SYNCING;
2942             else
2943                 status = SYNC_STATUS_SYNCED;
2944             seaf_sync_manager_update_active_path (seaf->sync_mgr,
2945                                                   repo->id,
2946                                                   path,
2947                                                   S_IFDIR,
2948                                                   status,
2949                                                   TRUE);
2950         }
2951     }
2952 }
2953 
2954 #else
2955 
2956 static void
update_active_path_recursive(SeafRepo * repo,const char * path,struct index_state * istate,GList * ignore_list,gboolean ignored)2957 update_active_path_recursive (SeafRepo *repo,
2958                               const char *path,
2959                               struct index_state *istate,
2960                               GList *ignore_list,
2961                               gboolean ignored)
2962 {
2963     GDir *dir;
2964     GError *error = NULL;
2965     const char *name;
2966     char *dname;
2967     char *full_path, *full_sub_path, *sub_path;
2968     struct stat st;
2969     gboolean ignore_sub;
2970 
2971     full_path = g_build_filename(repo->worktree, path, NULL);
2972 
2973     dir = g_dir_open (full_path, 0, &error);
2974     if (!dir) {
2975         seaf_warning ("Failed to open dir %s: %s.\n", full_path, error->message);
2976         g_free (full_path);
2977         return;
2978     }
2979 
2980     int n = 0;
2981     while ((name = g_dir_read_name(dir)) != NULL) {
2982         ++n;
2983 
2984         dname = g_utf8_normalize (name, -1, G_NORMALIZE_NFC);
2985         sub_path = g_strconcat (path, "/", dname, NULL);
2986         full_sub_path = g_strconcat (full_path, "/", dname, NULL);
2987 
2988         ignore_sub = FALSE;
2989         if (ignored || should_ignore(full_path, dname, ignore_list))
2990             ignore_sub = TRUE;
2991 
2992         if (stat (full_sub_path, &st) < 0) {
2993             seaf_warning ("Failed to stat %s: %s.\n", full_sub_path, strerror(errno));
2994             g_free (dname);
2995             g_free (sub_path);
2996             g_free (full_sub_path);
2997             continue;
2998         }
2999 
3000         if (S_ISDIR(st.st_mode)) {
3001             update_active_path_recursive (repo, sub_path, istate, ignore_list,
3002                                           ignore_sub);
3003         } else if (S_ISREG(st.st_mode)) {
3004             update_active_file (repo, sub_path, &st, istate,
3005                                 ignore_sub);
3006         }
3007 
3008         g_free (dname);
3009         g_free (sub_path);
3010         g_free (full_sub_path);
3011     }
3012 
3013     g_dir_close (dir);
3014 
3015     g_free (full_path);
3016 
3017     /* Don't set sync status for read-only paths, since changes to read-only
3018      * files are ignored.
3019      */
3020     if (!is_path_writable (repo->id, repo->is_readonly, path))
3021         return;
3022 
3023     if (n == 0 && path[0] != 0) {
3024         if (ignored) {
3025             seaf_sync_manager_update_active_path (seaf->sync_mgr,
3026                                                   repo->id,
3027                                                   path,
3028                                                   S_IFDIR,
3029                                                   SYNC_STATUS_IGNORED,
3030                                                   TRUE);
3031         } else {
3032             /* There is no need to update an empty dir. */
3033             SyncStatus status;
3034             struct cache_entry *ce = index_name_exists(istate, path, strlen(path), 0);
3035             if (!ce)
3036                 status = SYNC_STATUS_SYNCING;
3037             else
3038                 status = SYNC_STATUS_SYNCED;
3039             seaf_sync_manager_update_active_path (seaf->sync_mgr,
3040                                                   repo->id,
3041                                                   path,
3042                                                   S_IFDIR,
3043                                                   status,
3044                                                   TRUE);
3045         }
3046     }
3047 }
3048 
3049 #endif  /* WIN32 */
3050 
3051 static void
process_active_path(SeafRepo * repo,const char * path,struct index_state * istate,GList * ignore_list)3052 process_active_path (SeafRepo *repo, const char *path,
3053                      struct index_state *istate, GList *ignore_list)
3054 {
3055     SeafStat st;
3056     gboolean ignored = FALSE;
3057 
3058     char *fullpath = g_build_filename (repo->worktree, path, NULL);
3059     if (seaf_stat (fullpath, &st) < 0) {
3060         g_free (fullpath);
3061         return;
3062     }
3063 
3064     if (check_full_path_ignore (repo->worktree, path, ignore_list))
3065         ignored = TRUE;
3066 
3067     if (S_ISREG(st.st_mode)) {
3068         if (!seaf_filelock_manager_is_file_locked(seaf->filelock_mgr,
3069                                                   repo->id, path)) {
3070             update_active_file (repo, path, &st, istate, ignored);
3071         }
3072     } else {
3073         update_active_path_recursive (repo, path, istate, ignore_list, ignored);
3074     }
3075 
3076     g_free (fullpath);
3077 }
3078 
3079 #ifdef __APPLE__
3080 
3081 /* static void */
3082 /* process_active_folder (SeafRepo *repo, const char *dir, */
3083 /*                        struct index_state *istate, GList *ignore_list) */
3084 /* { */
3085 /*     GList *add = NULL, *mod = NULL, *del = NULL; */
3086 /*     GList *p; */
3087 /*     char *path; */
3088 
3089 /*     /\* Delete event will be triggered on the deleted dir too. *\/ */
3090 /*     if (!g_file_test (dir, G_FILE_TEST_IS_DIR)) */
3091 /*         return; */
3092 
3093 /*     if (get_changed_paths_in_folder (repo, istate, dir, &add, &mod, &del) < 0) { */
3094 /*         seaf_warning ("Failed to get changed paths under %s.\n", dir); */
3095 /*         return; */
3096 /*     } */
3097 
3098 /*     for (p = add; p; p = p->next) { */
3099 /*         path = p->data; */
3100 /*         process_active_path (repo, path, istate, ignore_list); */
3101 /*     } */
3102 
3103 /*     for (p = mod; p; p = p->next) { */
3104 /*         path = p->data; */
3105 /*         process_active_path (repo, path, istate, ignore_list); */
3106 /*     } */
3107 
3108 /*     g_list_free_full (add, g_free); */
3109 /*     g_list_free_full (mod, g_free); */
3110 /*     g_list_free_full (del, g_free); */
3111 /* } */
3112 
3113 #endif  /* __APPLE__ */
3114 
3115 static void
update_path_sync_status(SeafRepo * repo,WTStatus * status,struct index_state * istate,GList * ignore_list)3116 update_path_sync_status (SeafRepo *repo, WTStatus *status,
3117                          struct index_state *istate, GList *ignore_list)
3118 {
3119     char *path;
3120 
3121     while (1) {
3122         pthread_mutex_lock (&status->ap_q_lock);
3123         path = g_queue_pop_head (status->active_paths);
3124         pthread_mutex_unlock (&status->ap_q_lock);
3125 
3126         if (!path)
3127             break;
3128 
3129 /* #ifdef __APPLE__ */
3130 /*         process_active_folder (repo, path, istate, ignore_list); */
3131 /* #else */
3132         process_active_path (repo, path, istate, ignore_list);
3133 /* #endif */
3134 
3135         g_free (path);
3136     }
3137 }
3138 
3139 /* Excel first writes update to a temporary file and then rename the file to
3140  * xlsx. Unfortunately the temp file dosen't have specific pattern.
3141  * We can only ignore renaming from non xlsx file to xlsx file.
3142  */
3143 static gboolean
ignore_xlsx_update(const char * src_path,const char * dst_path)3144 ignore_xlsx_update (const char *src_path, const char *dst_path)
3145 {
3146     GPatternSpec *pattern = g_pattern_spec_new ("*.xlsx");
3147     int ret = FALSE;
3148 
3149     if (!g_pattern_match_string(pattern, src_path) &&
3150         g_pattern_match_string(pattern, dst_path))
3151         ret = TRUE;
3152 
3153     g_pattern_spec_free (pattern);
3154     return ret;
3155 }
3156 
3157 static gboolean
is_seafile_backup_file(const char * path)3158 is_seafile_backup_file (const char *path)
3159 {
3160     GPatternSpec *pattern = g_pattern_spec_new ("*.sbak");
3161     int ret = FALSE;
3162 
3163     if (g_pattern_match_string(pattern, path))
3164         ret = TRUE;
3165 
3166     g_pattern_spec_free (pattern);
3167     return ret;
3168 }
3169 
3170 static void
handle_rename(SeafRepo * repo,struct index_state * istate,SeafileCrypt * crypt,GList * ignore_list,LockedFileSet * fset,WTEvent * event,GList ** scanned_del_dirs,gint64 * total_size)3171 handle_rename (SeafRepo *repo, struct index_state *istate,
3172                SeafileCrypt *crypt, GList *ignore_list,
3173                LockedFileSet *fset,
3174                WTEvent *event, GList **scanned_del_dirs,
3175                gint64 *total_size)
3176 {
3177     gboolean not_found, src_ignored, dst_ignored;
3178 
3179     seaf_sync_manager_delete_active_path (seaf->sync_mgr, repo->id, event->path);
3180 
3181     if (!is_path_writable(repo->id,
3182                           repo->is_readonly, event->path) ||
3183         !is_path_writable(repo->id,
3184                           repo->is_readonly, event->new_path)) {
3185         seaf_debug ("Rename: %s or %s is not writable, ignore.\n",
3186                     event->path, event->new_path);
3187         return;
3188     }
3189 
3190     if (seaf_filelock_manager_is_file_locked (seaf->filelock_mgr,
3191                                               repo->id, event->path)) {
3192         seaf_debug ("Rename: %s is locked on server, ignore.\n", event->path);
3193         /* send_sync_error_notification (repo->id, NULL, event->path, */
3194         /*                               SYNC_ERROR_ID_FILE_LOCKED); */
3195         return;
3196     }
3197 
3198     if (seaf_filelock_manager_is_file_locked (seaf->filelock_mgr,
3199                                               repo->id, event->new_path)) {
3200         seaf_debug ("Rename: %s is locked on server, ignore.\n", event->new_path);
3201         /* send_sync_error_notification (repo->id, NULL, event->new_path, */
3202         /*                               SYNC_ERROR_ID_FILE_LOCKED); */
3203         return;
3204     }
3205 
3206     src_ignored = check_full_path_ignore(repo->worktree, event->path, ignore_list);
3207     dst_ignored = check_full_path_ignore(repo->worktree, event->new_path, ignore_list);
3208 
3209     /* If the destination path is ignored, just remove the source path. */
3210     if (dst_ignored) {
3211         if (!src_ignored &&
3212             !is_seafile_backup_file (event->new_path) &&
3213             check_locked_file_before_remove (fset, event->path)) {
3214             not_found = FALSE;
3215             remove_from_index_with_prefix (istate, event->path, &not_found);
3216             if (not_found)
3217                 scan_subtree_for_deletion (repo->id,
3218                                            istate,
3219                                            repo->worktree, event->path,
3220                                            ignore_list, fset,
3221                                            repo->is_readonly,
3222                                            scanned_del_dirs,
3223                                            repo->changeset);
3224 
3225             remove_from_changeset (repo->changeset,
3226                                    DIFF_STATUS_DELETED,
3227                                    event->path,
3228                                    FALSE,
3229                                    NULL);
3230         }
3231         return;
3232     }
3233 
3234     /* Now the destination path is not ignored. */
3235 
3236     if (!src_ignored && !ignore_xlsx_update (event->path, event->new_path) &&
3237         check_locked_file_before_remove (fset, event->path)) {
3238         not_found = FALSE;
3239         rename_index_entries (istate, event->path, event->new_path, &not_found,
3240                               NULL, NULL);
3241         if (not_found)
3242             scan_subtree_for_deletion (repo->id,
3243                                        istate,
3244                                        repo->worktree, event->path,
3245                                        ignore_list, fset,
3246                                        repo->is_readonly,
3247                                        scanned_del_dirs,
3248                                        repo->changeset);
3249 
3250         /* Moving files out of a dir may make it empty. */
3251         try_add_empty_parent_dir_entry_from_wt (repo->worktree,
3252                                                 istate,
3253                                                 ignore_list,
3254                                                 event->path);
3255 
3256         add_to_changeset (repo->changeset,
3257                           DIFF_STATUS_RENAMED,
3258                           NULL,
3259                           NULL,
3260                           NULL,
3261                           event->path,
3262                           event->new_path);
3263     }
3264 
3265     AddOptions options;
3266     memset (&options, 0, sizeof(options));
3267     options.fset = fset;
3268     options.is_repo_ro = repo->is_readonly;
3269     options.changeset = repo->changeset;
3270 
3271     /* We should always scan the destination to compare with the renamed
3272      * index entries. For example, in the following case:
3273      * 1. file a.txt is updated;
3274      * 2. a.txt is moved to test/a.txt;
3275      * If the two operations are executed in a batch, the updated content
3276      * of a.txt won't be committed if we don't scan the destination, because
3277      * when we process the update event, a.txt is already not in its original
3278      * place.
3279      */
3280     add_recursive (repo->id, repo->version, repo->email,
3281                    istate, repo->worktree, event->new_path,
3282                    crypt, FALSE, ignore_list,
3283                    total_size, NULL, &options);
3284 }
3285 
3286 #ifdef WIN32
3287 
3288 typedef struct FindOfficeData {
3289     const char *lock_file_name;
3290     char *office_file_name;
3291 } FindOfficeData;
3292 
3293 static int
find_office_file_cb(wchar_t * parent,WIN32_FIND_DATAW * fdata,void * user_data,gboolean * stop)3294 find_office_file_cb (wchar_t *parent,
3295                      WIN32_FIND_DATAW *fdata,
3296                      void *user_data,
3297                      gboolean *stop)
3298 {
3299     FindOfficeData *data = user_data;
3300     const wchar_t *dname_w = fdata->cFileName;
3301     wchar_t *lock_name_w = NULL;
3302 
3303     if (wcslen(dname_w) < 2)
3304         return 0;
3305     if (wcsncmp (dname_w, L"~$", 2) == 0)
3306         return 0;
3307 
3308     lock_name_w = g_utf8_to_utf16 (data->lock_file_name,
3309                                    -1, NULL, NULL, NULL);
3310     /* Skip "~$" at the beginning. */
3311     if (wcscmp (dname_w + 2, lock_name_w) == 0) {
3312         data->office_file_name = g_utf16_to_utf8 (dname_w, -1, NULL, NULL, NULL);
3313         *stop = TRUE;
3314     }
3315     g_free (lock_name_w);
3316 
3317     return 0;
3318 }
3319 
3320 static gboolean
find_office_file_path(const char * worktree,const char * parent_dir,const char * lock_file_name,char ** office_path)3321 find_office_file_path (const char *worktree,
3322                        const char *parent_dir,
3323                        const char *lock_file_name,
3324                        char **office_path)
3325 {
3326     char *fullpath = NULL;
3327     wchar_t *fullpath_w = NULL;
3328     FindOfficeData data;
3329     gboolean ret = FALSE;
3330 
3331     fullpath = g_build_path ("/", worktree, parent_dir, NULL);
3332     fullpath_w = win32_long_path (fullpath);
3333 
3334     data.lock_file_name = lock_file_name;
3335     data.office_file_name = NULL;
3336 
3337     if (traverse_directory_win32 (fullpath_w, find_office_file_cb, &data) < 0) {
3338         goto out;
3339     }
3340 
3341     if (data.office_file_name != NULL) {
3342         *office_path = g_build_path ("/", parent_dir, data.office_file_name, NULL);
3343         ret = TRUE;
3344     }
3345 
3346 out:
3347     g_free (fullpath);
3348     g_free (fullpath_w);
3349     return ret;
3350 }
3351 
3352 #endif
3353 
3354 #ifdef __APPLE__
3355 
3356 static gboolean
find_office_file_path(const char * worktree,const char * parent_dir,const char * lock_file_name,char ** office_path)3357 find_office_file_path (const char *worktree,
3358                        const char *parent_dir,
3359                        const char *lock_file_name,
3360                        char **office_path)
3361 {
3362     GDir *dir = NULL;
3363     GError *error = NULL;
3364     char *fullpath = NULL;
3365     const char *dname;
3366     char *dname_nfc = NULL;
3367     char *dname_skip_head = NULL;
3368     gboolean ret = FALSE;
3369 
3370     fullpath = g_build_path ("/", worktree, parent_dir, NULL);
3371     dir = g_dir_open (fullpath, 0, &error);
3372     if (error) {
3373         seaf_warning ("Failed to open dir %s: %s.\n", fullpath, error->message);
3374         g_clear_error (&error);
3375         g_free (fullpath);
3376         return ret;
3377     }
3378 
3379     while ((dname = g_dir_read_name (dir)) != NULL) {
3380         dname_nfc = g_utf8_normalize (dname, -1, G_NORMALIZE_NFC);
3381         if (!dname_nfc)
3382             continue;
3383 
3384         if (g_utf8_strlen(dname_nfc, -1) < 2 || strncmp (dname_nfc, "~$", 2) == 0) {
3385             g_free (dname_nfc);
3386             continue;
3387         }
3388 
3389         dname_skip_head = g_utf8_find_next_char(g_utf8_find_next_char(dname_nfc, NULL), NULL);
3390 
3391         if (g_strcmp0 (dname_skip_head, lock_file_name) == 0) {
3392             *office_path = g_build_path ("/", parent_dir, dname_nfc, NULL);
3393             ret = TRUE;
3394             g_free (dname_nfc);
3395             break;
3396         }
3397 
3398         g_free (dname_nfc);
3399     }
3400 
3401     g_free (fullpath);
3402     g_dir_close (dir);
3403     return ret;
3404 }
3405 
3406 #endif
3407 
3408 #if defined WIN32 || defined __APPLE__
3409 
3410 static gboolean
is_office_lock_file(const char * worktree,const char * path,char ** office_path)3411 is_office_lock_file (const char *worktree,
3412                      const char *path,
3413                      char **office_path)
3414 {
3415     gboolean ret;
3416 
3417     if (!g_regex_match (office_lock_pattern, path, 0, NULL))
3418         return FALSE;
3419 
3420     /* Replace ~$abc.docx with abc.docx */
3421     *office_path = g_regex_replace (office_lock_pattern,
3422                                     path, -1, 0,
3423                                     "\\1", 0, NULL);
3424 
3425     /* When the filename is long, sometimes the first two characters
3426        in the filename will be directly replaced with ~$.
3427        So if the office_path file doesn't exist, we have to match
3428        against all filenames in this directory, to find the office
3429        file's name.
3430     */
3431     char *fullpath = g_build_path ("/", worktree, *office_path, NULL);
3432     if (seaf_util_exists (fullpath)) {
3433         g_free (fullpath);
3434         return TRUE;
3435     }
3436     g_free (fullpath);
3437 
3438     char *lock_file_name = g_path_get_basename(*office_path);
3439     char *parent_dir = g_path_get_dirname(*office_path);
3440     if (strcmp(parent_dir, ".") == 0) {
3441         g_free (parent_dir);
3442         parent_dir = g_strdup("");
3443     }
3444     g_free (*office_path);
3445     *office_path = NULL;
3446 
3447     ret = find_office_file_path (worktree, parent_dir, lock_file_name,
3448                                  office_path);
3449 
3450     g_free (lock_file_name);
3451     g_free (parent_dir);
3452     return ret;
3453 }
3454 
3455 typedef struct LockOfficeJob {
3456     char repo_id[37];
3457     char *path;
3458     gboolean lock;              /* False if unlock */
3459 } LockOfficeJob;
3460 
3461 static void
lock_office_job_free(LockOfficeJob * job)3462 lock_office_job_free (LockOfficeJob *job)
3463 {
3464     if (!job)
3465         return;
3466     g_free (job->path);
3467     g_free (job);
3468 }
3469 
3470 static void
do_lock_office_file(LockOfficeJob * job)3471 do_lock_office_file (LockOfficeJob *job)
3472 {
3473     SeafRepo *repo;
3474     char *fullpath = NULL;
3475     SeafStat st;
3476 
3477     repo = seaf_repo_manager_get_repo (seaf->repo_mgr, job->repo_id);
3478     if (!repo)
3479         return;
3480 
3481     fullpath = g_build_path ("/", repo->worktree, job->path, NULL);
3482     if (seaf_stat (fullpath, &st) < 0 || !S_ISREG(st.st_mode)) {
3483         g_free (fullpath);
3484         return;
3485     }
3486     g_free (fullpath);
3487 
3488     seaf_message ("Auto lock file %s/%s\n", repo->name, job->path);
3489 
3490     int status = seaf_filelock_manager_get_lock_status (seaf->filelock_mgr,
3491                                                         repo->id, job->path);
3492     if (status != FILE_NOT_LOCKED) {
3493         return;
3494     }
3495 
3496     if (http_tx_manager_lock_file (seaf->http_tx_mgr,
3497                                    repo->effective_host,
3498                                    repo->use_fileserver_port,
3499                                    repo->token,
3500                                    repo->id,
3501                                    job->path) < 0) {
3502         seaf_warning ("Failed to lock %s in repo %.8s on server.\n",
3503                       job->path, repo->id);
3504         return;
3505     }
3506 
3507     /* Mark file as locked locally so that the user can see the effect immediately. */
3508     seaf_filelock_manager_mark_file_locked (seaf->filelock_mgr, repo->id, job->path, TRUE);
3509 }
3510 
3511 static void
do_unlock_office_file(LockOfficeJob * job)3512 do_unlock_office_file (LockOfficeJob *job)
3513 {
3514     SeafRepo *repo;
3515     char *fullpath = NULL;
3516     SeafStat st;
3517 
3518     repo = seaf_repo_manager_get_repo (seaf->repo_mgr, job->repo_id);
3519     if (!repo)
3520         return;
3521 
3522     fullpath = g_build_path ("/", repo->worktree, job->path, NULL);
3523     if (seaf_stat (fullpath, &st) < 0 || !S_ISREG(st.st_mode)) {
3524         g_free (fullpath);
3525         return;
3526     }
3527     g_free (fullpath);
3528 
3529     seaf_message ("Auto unlock file %s/%s\n", repo->name, job->path);
3530 
3531     int status = seaf_filelock_manager_get_lock_status (seaf->filelock_mgr,
3532                                                         repo->id, job->path);
3533     if (status != FILE_LOCKED_BY_ME_AUTO) {
3534         return;
3535     }
3536 
3537     if (http_tx_manager_unlock_file (seaf->http_tx_mgr,
3538                                      repo->effective_host,
3539                                      repo->use_fileserver_port,
3540                                      repo->token,
3541                                      repo->id,
3542                                      job->path) < 0) {
3543         seaf_warning ("Failed to unlock %s in repo %.8s on server.\n",
3544                       job->path, repo->id);
3545         return;
3546     }
3547 
3548     /* Mark file as unlocked locally so that the user can see the effect immediately. */
3549     seaf_filelock_manager_mark_file_unlocked (seaf->filelock_mgr, repo->id, job->path);
3550 }
3551 
3552 #if 0
3553 static void
3554 unlock_closed_office_files ()
3555 {
3556     GList *locked_files, *ptr;
3557     SeafRepo *repo;
3558     FileLockInfo *info;
3559     LockOfficeJob *job;
3560 
3561     locked_files = seaf_filelock_manager_get_auto_locked_files (seaf->filelock_mgr);
3562     for (ptr = locked_files; ptr; ptr = ptr->next) {
3563         info = ptr->data;
3564 
3565         seaf_message ("%s %s.\n", info->repo_id, info->path);
3566 
3567         repo = seaf_repo_manager_get_repo (seaf->repo_mgr, info->repo_id);
3568         if (!repo)
3569             continue;
3570 
3571         seaf_message ("1\n");
3572 
3573         if (!do_check_file_locked (info->path, repo->worktree, FALSE)) {
3574             seaf_message ("2\n");
3575 
3576             job = g_new0 (LockOfficeJob, 1);
3577             memcpy (job->repo_id, info->repo_id, 36);
3578             job->path = g_strdup(info->path);
3579             do_unlock_office_file (job);
3580             lock_office_job_free (job);
3581         }
3582     }
3583 
3584     g_list_free_full (locked_files, (GDestroyNotify)file_lock_info_free);
3585 }
3586 #endif
3587 
3588 static void *
lock_office_file_worker(void * vdata)3589 lock_office_file_worker (void *vdata)
3590 {
3591     GAsyncQueue *queue = (GAsyncQueue *)vdata;
3592     LockOfficeJob *job;
3593 
3594     /* unlock_closed_office_files (); */
3595 
3596     while (1) {
3597         job = g_async_queue_pop (queue);
3598         if (!job)
3599             break;
3600 
3601         if (job->lock)
3602             do_lock_office_file (job);
3603         else
3604             do_unlock_office_file (job);
3605 
3606         lock_office_job_free (job);
3607     }
3608 
3609     return NULL;
3610 }
3611 
3612 static void
lock_office_file_on_server(SeafRepo * repo,const char * path)3613 lock_office_file_on_server (SeafRepo *repo, const char *path)
3614 {
3615     LockOfficeJob *job;
3616     GAsyncQueue *queue = seaf->repo_mgr->priv->lock_office_job_queue;
3617 
3618     if (!seaf_repo_manager_server_is_pro (seaf->repo_mgr, repo->server_url))
3619         return;
3620 
3621     job = g_new0 (LockOfficeJob, 1);
3622     memcpy (job->repo_id, repo->id, 36);
3623     job->path = g_strdup(path);
3624     job->lock = TRUE;
3625 
3626     g_async_queue_push (queue, job);
3627 }
3628 
3629 static void
unlock_office_file_on_server(SeafRepo * repo,const char * path)3630 unlock_office_file_on_server (SeafRepo *repo, const char *path)
3631 {
3632     LockOfficeJob *job;
3633     GAsyncQueue *queue = seaf->repo_mgr->priv->lock_office_job_queue;
3634 
3635     if (!seaf_repo_manager_server_is_pro (seaf->repo_mgr, repo->server_url))
3636         return;
3637 
3638     job = g_new0 (LockOfficeJob, 1);
3639     memcpy (job->repo_id, repo->id, 36);
3640     job->path = g_strdup(path);
3641     job->lock = FALSE;
3642 
3643     g_async_queue_push (queue, job);
3644 }
3645 
3646 #endif
3647 
3648 static int
apply_worktree_changes_to_index(SeafRepo * repo,struct index_state * istate,SeafileCrypt * crypt,GList * ignore_list,LockedFileSet * fset)3649 apply_worktree_changes_to_index (SeafRepo *repo, struct index_state *istate,
3650                                  SeafileCrypt *crypt, GList *ignore_list,
3651                                  LockedFileSet *fset)
3652 {
3653     WTStatus *status;
3654     WTEvent *event, *next_event;
3655     gboolean not_found;
3656 #if defined WIN32 || defined __APPLE__
3657     char *office_path = NULL;
3658 #endif
3659 
3660     status = seaf_wt_monitor_get_worktree_status (seaf->wt_monitor, repo->id);
3661     if (!status) {
3662         seaf_warning ("Can't find worktree status for repo %s(%.8s).\n",
3663                       repo->name, repo->id);
3664         return -1;
3665     }
3666 
3667     update_path_sync_status (repo, status, istate, ignore_list);
3668 
3669     GList *scanned_dirs = NULL, *scanned_del_dirs = NULL;
3670 
3671     WTEvent *last_event;
3672 
3673     pthread_mutex_lock (&status->q_lock);
3674     last_event = g_queue_peek_tail (status->event_q);
3675     pthread_mutex_unlock (&status->q_lock);
3676 
3677     if (!last_event) {
3678         seaf_message ("All events are processed for repo %s.\n", repo->id);
3679         status->partial_commit = FALSE;
3680         goto out;
3681     }
3682 
3683     gint64 total_size = 0;
3684 
3685     while (1) {
3686         pthread_mutex_lock (&status->q_lock);
3687         event = g_queue_pop_head (status->event_q);
3688         next_event = g_queue_peek_head (status->event_q);
3689         pthread_mutex_unlock (&status->q_lock);
3690         if (!event)
3691             break;
3692 
3693         /* Scanned dirs list is used to avoid redundant scan of consecutive
3694            CREATE_OR_UPDATE events. When we see other events, we should
3695            clear the list. Otherwise in some cases we'll get wrong result.
3696            For example, the following sequence (run with a script):
3697            1. Add a dir with files
3698            2. Delete the dir with files
3699            3. Add back the same dir again.
3700         */
3701         if (event->ev_type != WT_EVENT_CREATE_OR_UPDATE) {
3702             g_list_free_full (scanned_dirs, g_free);
3703             scanned_dirs = NULL;
3704         }
3705 
3706         switch (event->ev_type) {
3707         case WT_EVENT_CREATE_OR_UPDATE:
3708             /* If consecutive CREATE_OR_UPDATE events present
3709                in the event queue, only process the last one.
3710             */
3711             if (next_event &&
3712                 next_event->ev_type == event->ev_type &&
3713                 strcmp (next_event->path, event->path) == 0)
3714                 break;
3715 
3716             /* CREATE_OR_UPDATE event tells us the exact path of changed file/dir.
3717              * If the event path is not writable, we don't need to check the paths
3718              * under the event path.
3719              */
3720             if (!is_path_writable(repo->id,
3721                                   repo->is_readonly, event->path)) {
3722                 char *filename = g_path_get_basename (event->path);
3723                 if (seaf_repo_manager_is_ignored_hidden_file(filename)) {
3724                     g_free (filename);
3725                     break;
3726                 }
3727                 g_free (filename);
3728 
3729                 char *fullpath = g_build_path(PATH_SEPERATOR, repo->worktree, event->path, NULL);
3730                 struct cache_entry *ce = index_name_exists(istate, event->path, strlen(event->path), 0);
3731                 SeafStat st;
3732                 if (ce != NULL &&
3733                     seaf_stat (fullpath, &st) == 0 &&
3734                     ce->ce_mtime.sec == st.st_mtime &&
3735                     ce->ce_size == st.st_size) {
3736                     g_free (fullpath);
3737                     break;
3738                 }
3739 
3740                 send_file_sync_error_notification (repo->id, repo->name, event->path,
3741                                                    SYNC_ERROR_ID_UPDATE_TO_READ_ONLY_REPO);
3742                 seaf_debug ("%s is not writable, ignore.\n", event->path);
3743 
3744                 g_free (fullpath);
3745                 break;
3746             }
3747 
3748 #if defined WIN32 || defined __APPLE__
3749             office_path = NULL;
3750             if (is_office_lock_file (repo->worktree, event->path, &office_path))
3751                 lock_office_file_on_server (repo, office_path);
3752             g_free (office_path);
3753 #endif
3754 
3755             if (handle_add_files (repo, istate, crypt, ignore_list,
3756                                   fset,
3757                                   status, event,
3758                                   &scanned_dirs, &total_size))
3759                 goto out;
3760 
3761             break;
3762         case WT_EVENT_SCAN_DIR:
3763             if (handle_add_files (repo, istate, crypt, ignore_list,
3764                                   fset,
3765                                   status, event,
3766                                   &scanned_dirs, &total_size))
3767                 goto out;
3768 
3769             break;
3770         case WT_EVENT_DELETE:
3771             seaf_sync_manager_delete_active_path (seaf->sync_mgr,
3772                                                   repo->id,
3773                                                   event->path);
3774 
3775 #if defined WIN32 || defined __APPLE__
3776             office_path = NULL;
3777             if (is_office_lock_file (repo->worktree, event->path, &office_path))
3778                 unlock_office_file_on_server (repo, office_path);
3779             g_free (office_path);
3780 #endif
3781 
3782             if (check_full_path_ignore(repo->worktree, event->path, ignore_list))
3783                 break;
3784 
3785             if (!is_path_writable(repo->id,
3786                                   repo->is_readonly, event->path)) {
3787                 seaf_debug ("%s is not writable, ignore.\n", event->path);
3788                 break;
3789             }
3790 
3791             if (seaf_filelock_manager_is_file_locked (seaf->filelock_mgr,
3792                                                       repo->id, event->path)) {
3793                 seaf_debug ("Delete: %s is locked on server, ignore.\n", event->path);
3794                 /* send_sync_error_notification (repo->id, NULL, event->path, */
3795                 /*                               SYNC_ERROR_ID_FILE_LOCKED); */
3796                 break;
3797             }
3798 
3799             if (check_locked_file_before_remove (fset, event->path)) {
3800                 not_found = FALSE;
3801                 remove_from_index_with_prefix (istate, event->path, &not_found);
3802                 if (not_found)
3803                     scan_subtree_for_deletion (repo->id,
3804                                                istate,
3805                                                repo->worktree, event->path,
3806                                                ignore_list, fset,
3807                                                repo->is_readonly,
3808                                                &scanned_del_dirs,
3809                                                repo->changeset);
3810 
3811                 remove_from_changeset (repo->changeset,
3812                                        DIFF_STATUS_DELETED,
3813                                        event->path,
3814                                        FALSE,
3815                                        NULL);
3816 
3817                 try_add_empty_parent_dir_entry_from_wt (repo->worktree,
3818                                                         istate,
3819                                                         ignore_list,
3820                                                         event->path);
3821             }
3822             break;
3823         case WT_EVENT_RENAME:
3824             handle_rename (repo, istate, crypt, ignore_list, fset, event, &scanned_del_dirs, &total_size);
3825             break;
3826         case WT_EVENT_ATTRIB:
3827             if (!is_path_writable(repo->id,
3828                                   repo->is_readonly, event->path)) {
3829                 seaf_debug ("%s is not writable, ignore.\n", event->path);
3830                 break;
3831             }
3832             update_attributes (repo, istate, repo->worktree, event->path);
3833             break;
3834         case WT_EVENT_OVERFLOW:
3835             seaf_warning ("Kernel event queue overflowed, fall back to scan.\n");
3836             scan_worktree_for_changes (istate, repo, crypt, ignore_list, fset);
3837             break;
3838         }
3839 
3840         if (event == last_event) {
3841             wt_event_free (event);
3842             seaf_message ("All events are processed for repo %s.\n", repo->id);
3843             status->partial_commit = FALSE;
3844             break;
3845         } else
3846             wt_event_free (event);
3847     }
3848 
3849 out:
3850     wt_status_unref (status);
3851     string_list_free (scanned_dirs);
3852     string_list_free (scanned_del_dirs);
3853 
3854     return 0;
3855 }
3856 
3857 static int
index_add(SeafRepo * repo,struct index_state * istate,gboolean is_force_commit)3858 index_add (SeafRepo *repo, struct index_state *istate, gboolean is_force_commit)
3859 {
3860     SeafileCrypt *crypt = NULL;
3861     LockedFileSet *fset = NULL;
3862     GList *ignore_list = NULL;
3863     int ret = 0;
3864 
3865     if (repo->encrypted) {
3866         crypt = seafile_crypt_new (repo->enc_version, repo->enc_key, repo->enc_iv);
3867     }
3868 
3869 #if defined WIN32 || defined __APPLE__
3870     if (repo->version > 0)
3871         fset = seaf_repo_manager_get_locked_file_set (seaf->repo_mgr, repo->id);
3872 #endif
3873 
3874     ignore_list = seaf_repo_load_ignore_files (repo->worktree);
3875 
3876     if (!is_force_commit) {
3877         if (apply_worktree_changes_to_index (repo, istate, crypt, ignore_list, fset) < 0) {
3878             seaf_warning ("Failed to apply worktree changes to index.\n");
3879             ret = -1;
3880         }
3881     } else if (scan_worktree_for_changes (istate, repo, crypt, ignore_list, fset) < 0) {
3882         seaf_warning ("Failed to scan worktree for changes.\n");
3883         ret = -1;
3884     }
3885 
3886     seaf_repo_free_ignore_files (ignore_list);
3887 
3888 #if defined WIN32 || defined __APPLE__
3889     locked_file_set_free (fset);
3890 #endif
3891 
3892     g_free (crypt);
3893 
3894     return ret;
3895 }
3896 
3897 static int
commit_tree(SeafRepo * repo,const char * root_id,const char * desc,char commit_id[])3898 commit_tree (SeafRepo *repo, const char *root_id,
3899              const char *desc, char commit_id[])
3900 {
3901     SeafCommit *commit;
3902 
3903     commit = seaf_commit_new (NULL, repo->id, root_id,
3904                               repo->email ? repo->email
3905                               : "unknown",
3906                               seaf->client_id,
3907                               desc, 0);
3908 
3909     commit->parent_id = g_strdup (repo->head->commit_id);
3910 
3911     /* Add this computer's name to commit. */
3912     commit->device_name = g_strdup(seaf->client_name);
3913     commit->client_version = g_strdup (SEAFILE_CLIENT_VERSION);
3914 
3915     seaf_repo_to_commit (repo, commit);
3916 
3917     if (seaf_commit_manager_add_commit (seaf->commit_mgr, commit) < 0)
3918         return -1;
3919 
3920     seaf_branch_set_commit (repo->head, commit->commit_id);
3921     seaf_branch_manager_update_branch (seaf->branch_mgr, repo->head);
3922 
3923     strcpy (commit_id, commit->commit_id);
3924     seaf_commit_unref (commit);
3925 
3926     return 0;
3927 }
3928 
3929 static gboolean
compare_index_changeset(struct index_state * istate,ChangeSet * changeset)3930 compare_index_changeset (struct index_state *istate, ChangeSet *changeset)
3931 {
3932     struct cache_entry *ce;
3933     int i;
3934     gboolean ret = TRUE;
3935 
3936     for (i = 0; i < istate->cache_nr; ++i) {
3937         ce = istate->cache[i];
3938 
3939         if (!(ce->ce_flags & CE_ADDED))
3940             continue;
3941 
3942         seaf_message ("checking %s in changeset.\n", ce->name);
3943 
3944         if (!changeset_check_path (changeset, ce->name,
3945                                    ce->sha1, ce->ce_mode, ce->ce_mtime.sec))
3946             ret = FALSE;
3947     }
3948 
3949     return ret;
3950 }
3951 
3952 #if 0
3953 static int
3954 print_index (struct index_state *istate)
3955 {
3956     int i;
3957     struct cache_entry *ce;
3958     char id[41];
3959     seaf_message ("Totally %u entries in index, version %u.\n",
3960                   istate->cache_nr, istate->version);
3961     for (i = 0; i < istate->cache_nr; ++i) {
3962         ce = istate->cache[i];
3963         rawdata_to_hex (ce->sha1, id, 20);
3964         seaf_message ("%s, %s, %o, %"G_GINT64_FORMAT", %s, %"G_GINT64_FORMAT", %d\n",
3965                       ce->name, id, ce->ce_mode,
3966                       ce->ce_mtime.sec, ce->modifier, ce->ce_size, ce_stage(ce));
3967     }
3968 
3969     return 0;
3970 }
3971 #endif
3972 
3973 char *
seaf_repo_index_commit(SeafRepo * repo,gboolean is_force_commit,gboolean is_initial_commit,GError ** error)3974 seaf_repo_index_commit (SeafRepo *repo,
3975                         gboolean is_force_commit,
3976                         gboolean is_initial_commit,
3977                         GError **error)
3978 {
3979     SeafRepoManager *mgr = repo->manager;
3980     struct index_state istate;
3981     char index_path[SEAF_PATH_MAX];
3982     SeafCommit *head = NULL;
3983     char *new_root_id = NULL;
3984     char commit_id[41];
3985     ChangeSet *changeset = NULL;
3986     GList *diff_results = NULL;
3987     char *desc = NULL;
3988     char *ret = NULL;
3989 
3990     if (!check_worktree_common (repo))
3991         return NULL;
3992 
3993     memset (&istate, 0, sizeof(istate));
3994     snprintf (index_path, SEAF_PATH_MAX, "%s/%s", mgr->index_dir, repo->id);
3995     if (read_index_from (&istate, index_path, repo->version) < 0) {
3996         seaf_warning ("Failed to load index.\n");
3997         g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_INTERNAL, "Internal data structure error");
3998         return NULL;
3999     }
4000 
4001     changeset = changeset_new (repo->id);
4002     if (!changeset) {
4003         g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_INTERNAL, "Internal data structure error");
4004         goto out;
4005     }
4006 
4007     repo->changeset = changeset;
4008 
4009     if (index_add (repo, &istate, is_force_commit) < 0) {
4010         g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL, "Failed to add");
4011         goto out;
4012     }
4013 
4014     if (!istate.cache_changed)
4015         goto out;
4016 
4017     if (!is_initial_commit && !is_force_commit) {
4018         new_root_id = commit_tree_from_changeset (changeset);
4019         if (!new_root_id) {
4020             seaf_warning ("Create commit tree failed for repo %s\n", repo->id);
4021             g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
4022                          "Failed to generate commit");
4023             goto out;
4024         }
4025     } else {
4026         char hex[41];
4027         struct cache_tree *it = cache_tree ();
4028         if (cache_tree_update (repo->id, repo->version,
4029                                repo->worktree,
4030                                it, istate.cache,
4031                                istate.cache_nr, 0, 0, commit_trees_cb) < 0) {
4032             seaf_warning ("Failed to build cache tree");
4033             g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_INTERNAL,
4034                          "Internal data structure error");
4035             cache_tree_free (&it);
4036             goto out;
4037         }
4038         rawdata_to_hex (it->sha1, hex, 20);
4039         new_root_id = g_strdup(hex);
4040         cache_tree_free (&it);
4041     }
4042 
4043     head = seaf_commit_manager_get_commit (seaf->commit_mgr,
4044                                            repo->id, repo->version,
4045                                            repo->head->commit_id);
4046     if (!head) {
4047         seaf_warning ("Head commit %s for repo %s not found\n",
4048                       repo->head->commit_id, repo->id);
4049         g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_INTERNAL, "Data corrupt");
4050         goto out;
4051     }
4052 
4053     if (strcmp (head->root_id, new_root_id) == 0) {
4054         seaf_message ("No change to the fs tree of repo %s\n", repo->id);
4055         /* If no file modification and addition are missing, and the new root
4056          * id is the same as the old one, skip commiting.
4057          */
4058         if (!is_initial_commit && !is_force_commit)
4059             compare_index_changeset (&istate, changeset);
4060 
4061         update_index (&istate, index_path);
4062         goto out;
4063     }
4064 
4065     diff_commit_roots (repo->id, repo->version, head->root_id, new_root_id, &diff_results, TRUE);
4066     desc = diff_results_to_description (diff_results);
4067     if (!desc)
4068         desc = g_strdup("");
4069 
4070     if (commit_tree (repo, new_root_id, desc, commit_id) < 0) {
4071         seaf_warning ("Failed to save commit file");
4072         g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_INTERNAL, "Internal error");
4073         goto out;
4074     }
4075 
4076     if (update_index (&istate, index_path) < 0) {
4077         g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_INTERNAL, "Internal error");
4078         goto out;
4079     }
4080 
4081     g_signal_emit_by_name (seaf, "repo-committed", repo);
4082 
4083     ret = g_strdup(commit_id);
4084 
4085 out:
4086     g_free (desc);
4087     seaf_commit_unref (head);
4088     g_free (new_root_id);
4089     changeset_free (changeset);
4090     g_list_free_full (diff_results, (GDestroyNotify)diff_entry_free);
4091     discard_index (&istate);
4092     return ret;
4093 }
4094 
4095 #ifdef DEBUG_UNPACK_TREES
4096 static void
print_unpack_result(struct index_state * result)4097 print_unpack_result (struct index_state *result)
4098 {
4099 	int i;
4100 	struct cache_entry *ce;
4101 
4102 	for (i = 0; i < result->cache_nr; ++i) {
4103 		ce = result->cache[i];
4104 		printf ("%s\t", ce->name);
4105 		if (ce->ce_flags & CE_UPDATE)
4106 			printf ("update/add\n");
4107 		else if (ce->ce_flags & CE_WT_REMOVE)
4108 			printf ("remove\n");
4109 		else
4110 			printf ("unchange\n");
4111 	}
4112 }
4113 
4114 static int
print_index(struct index_state * istate)4115 print_index (struct index_state *istate)
4116 {
4117     printf ("Index timestamp: %d\n", istate->timestamp.sec);
4118 
4119     int i;
4120     struct cache_entry *ce;
4121     char id[41];
4122     printf ("Totally %u entries in index.\n", istate->cache_nr);
4123     for (i = 0; i < istate->cache_nr; ++i) {
4124         ce = istate->cache[i];
4125         rawdata_to_hex (ce->sha1, id, 20);
4126         printf ("%s\t%s\t%o\t%d\t%d\n", ce->name, id, ce->ce_mode,
4127                 ce->ce_ctime.sec, ce->ce_mtime.sec);
4128     }
4129 
4130     return 0;
4131 }
4132 #endif  /* DEBUG_UNPACK_TREES */
4133 
4134 GList *
seaf_repo_diff(SeafRepo * repo,const char * old,const char * new,int fold_dir_diff,char ** error)4135 seaf_repo_diff (SeafRepo *repo, const char *old, const char *new, int fold_dir_diff, char **error)
4136 {
4137     SeafCommit *c1 = NULL, *c2 = NULL;
4138     int ret = 0;
4139     GList *diff_entries = NULL;
4140 
4141     c2 = seaf_commit_manager_get_commit (seaf->commit_mgr,
4142                                          repo->id, repo->version,
4143                                          new);
4144     if (!c2) {
4145         *error = g_strdup("Can't find new commit");
4146         return NULL;
4147     }
4148 
4149     if (old == NULL || old[0] == '\0') {
4150         if (c2->parent_id && c2->second_parent_id) {
4151             ret = diff_merge (c2, &diff_entries, fold_dir_diff);
4152             seaf_commit_unref (c2);
4153             if (ret < 0) {
4154                 *error = g_strdup("Failed to do diff");
4155                 g_list_free_full (diff_entries, (GDestroyNotify)diff_entry_free);
4156                 return NULL;
4157             }
4158             return diff_entries;
4159         }
4160 
4161         if (!c2->parent_id) {
4162             seaf_commit_unref (c2);
4163             return NULL;
4164         }
4165         c1 = seaf_commit_manager_get_commit (seaf->commit_mgr,
4166                                              repo->id, repo->version,
4167                                              c2->parent_id);
4168     } else {
4169         c1 = seaf_commit_manager_get_commit (seaf->commit_mgr,
4170                                              repo->id, repo->version, old);
4171     }
4172 
4173     if (!c1) {
4174         *error = g_strdup("Can't find old commit");
4175         seaf_commit_unref (c2);
4176         return NULL;
4177     }
4178 
4179     /* do diff */
4180     ret = diff_commits (c1, c2, &diff_entries, fold_dir_diff);
4181     if (ret < 0) {
4182         g_list_free_full (diff_entries, (GDestroyNotify)diff_entry_free);
4183         diff_entries = NULL;
4184         *error = g_strdup("Failed to do diff");
4185     }
4186 
4187     seaf_commit_unref (c1);
4188     seaf_commit_unref (c2);
4189 
4190     return diff_entries;
4191 }
4192 
4193 int
checkout_empty_dir(const char * worktree,const char * name,gint64 mtime,struct cache_entry * ce,GHashTable * conflict_hash,GHashTable * no_conflict_hash)4194 checkout_empty_dir (const char *worktree,
4195                     const char *name,
4196                     gint64 mtime,
4197                     struct cache_entry *ce,
4198                     GHashTable *conflict_hash,
4199                     GHashTable *no_conflict_hash)
4200 {
4201     char *path;
4202     gboolean case_conflict = FALSE;
4203 
4204     path = build_checkout_path (worktree, name, strlen(name));
4205 
4206     if (!path)
4207         return FETCH_CHECKOUT_FAILED;
4208 
4209     if (!seaf_util_exists (path) && seaf_util_mkdir (path, 0777) < 0) {
4210         seaf_warning ("Failed to create empty dir %s in checkout.\n", path);
4211         g_free (path);
4212         return FETCH_CHECKOUT_FAILED;
4213     }
4214 
4215     if (mtime != 0 && seaf_set_file_time (path, mtime) < 0) {
4216         seaf_warning ("Failed to set mtime for %s.\n", path);
4217     }
4218 
4219     if (case_conflict) {
4220         ce->ce_flags |= CE_REMOVE;
4221         g_free (path);
4222         return FETCH_CHECKOUT_SUCCESS;
4223     }
4224 
4225     SeafStat st;
4226     seaf_stat (path, &st);
4227     fill_stat_cache_info (ce, &st);
4228 
4229     g_free (path);
4230     return FETCH_CHECKOUT_SUCCESS;
4231 }
4232 
4233 static struct cache_entry *
cache_entry_from_diff_entry(DiffEntry * de)4234 cache_entry_from_diff_entry (DiffEntry *de)
4235 {
4236     int size, namelen;
4237     struct cache_entry *ce;
4238 
4239     namelen = strlen(de->name);
4240     size = cache_entry_size(namelen);
4241     ce = calloc(1, size);
4242     memcpy(ce->name, de->name, namelen);
4243     ce->ce_flags = namelen;
4244 
4245     memcpy (ce->sha1, de->sha1, 20);
4246     ce->modifier = g_strdup(de->modifier);
4247     ce->ce_size = de->size;
4248     ce->ce_mtime.sec = de->mtime;
4249 
4250     if (S_ISREG(de->mode))
4251         ce->ce_mode = create_ce_mode (de->mode);
4252     else
4253         ce->ce_mode = S_IFDIR;
4254 
4255     return ce;
4256 }
4257 
4258 #define UPDATE_CACHE_SIZE_LIMIT 100 * (1 << 20) /* 100MB */
4259 
4260 typedef struct FileTxData {
4261     char repo_id[37];
4262     int repo_version;
4263     SeafileCrypt *crypt;
4264     HttpTxTask *http_task;
4265     char conflict_head_id[41];
4266     GAsyncQueue *finished_tasks;
4267 } FileTxData;
4268 
4269 typedef struct FileTxTask {
4270     char *path;
4271     struct cache_entry *ce;
4272     DiffEntry *de;
4273     gboolean new_ce;
4274     gboolean skip_fetch;
4275 
4276     int result;
4277     gboolean no_checkout;
4278     gboolean force_conflict;
4279 } FileTxTask;
4280 
4281 static void
file_tx_task_free(FileTxTask * task)4282 file_tx_task_free (FileTxTask *task)
4283 {
4284     if (!task)
4285         return;
4286 
4287     g_free (task->path);
4288     g_free (task);
4289 }
4290 
4291 static int
fetch_file_http(FileTxData * data,FileTxTask * file_task)4292 fetch_file_http (FileTxData *data, FileTxTask *file_task)
4293 {
4294     int repo_version = data->repo_version;
4295     struct cache_entry *ce = file_task->ce;
4296     DiffEntry *de = file_task->de;
4297     SeafileCrypt *crypt = data->crypt;
4298     char *path = file_task->path;
4299     HttpTxTask *http_task = data->http_task;
4300     SeafStat st;
4301     char file_id[41];
4302     gboolean path_exists = FALSE;
4303 
4304     rawdata_to_hex (de->sha1, file_id, 20);
4305 
4306     path_exists = (seaf_stat (path, &st) == 0);
4307 
4308     if (path_exists && S_ISREG(st.st_mode)) {
4309         if (st.st_mtime == ce->ce_mtime.sec) {
4310             /* Worktree and index are consistent. */
4311             if (memcmp (de->sha1, ce->sha1, 20) == 0) {
4312                 seaf_debug ("wt and index are consistent. no need to checkout.\n");
4313                 file_task->no_checkout = TRUE;
4314 
4315                 /* Update mode if necessary. */
4316                 if (de->mode != ce->ce_mode) {
4317 #ifndef WIN32
4318                     chmod (path, de->mode & ~S_IFMT);
4319                     ce->ce_mode = de->mode;
4320 #endif
4321                 }
4322 
4323                 /* Update mtime if necessary. */
4324                 if (de->mtime != ce->ce_mtime.sec) {
4325                     seaf_set_file_time (path, de->mtime);
4326                     ce->ce_mtime.sec = de->mtime;
4327                 }
4328 
4329                 fill_stat_cache_info (ce, &st);
4330 
4331                 return FETCH_CHECKOUT_SUCCESS;
4332             }
4333             /* otherwise we have to checkout the file. */
4334         } else {
4335             if (compare_file_content (path, &st, de->sha1, crypt, repo_version) == 0) {
4336                 /* This happens after the worktree file was updated,
4337                  * but the index was not. Just need to update the index.
4338                  */
4339                 seaf_debug ("update index only.\n");
4340                 file_task->no_checkout = TRUE;
4341                 fill_stat_cache_info (ce, &st);
4342                 return FETCH_CHECKOUT_SUCCESS;
4343             } else {
4344                 /* Conflict. The worktree file was updated by the user. */
4345                 seaf_message ("File %s is updated by user. "
4346                               "Will checkout to conflict file later.\n", path);
4347                 file_task->force_conflict = TRUE;
4348             }
4349         }
4350     }
4351 
4352     /* Download the blocks of this file. */
4353     int rc;
4354     rc = http_tx_task_download_file_blocks (http_task, file_id);
4355     if (http_task->state == HTTP_TASK_STATE_CANCELED) {
4356         return FETCH_CHECKOUT_CANCELED;
4357     }
4358     if (rc < 0) {
4359         return FETCH_CHECKOUT_TRANSFER_ERROR;
4360     }
4361 
4362     return FETCH_CHECKOUT_SUCCESS;
4363 }
4364 
4365 static void
fetch_file_thread_func(gpointer data,gpointer user_data)4366 fetch_file_thread_func (gpointer data, gpointer user_data)
4367 {
4368     FileTxTask *task = data;
4369     FileTxData *tx_data = user_data;
4370     GAsyncQueue *finished_tasks = tx_data->finished_tasks;
4371     DiffEntry *de = task->de;
4372     char *repo_id = tx_data->repo_id;
4373     char file_id[41];
4374     gboolean is_clone = tx_data->http_task->is_clone;
4375     int rc = FETCH_CHECKOUT_SUCCESS;
4376 
4377     if (task->skip_fetch)
4378         goto out;
4379 
4380     rawdata_to_hex (de->sha1, file_id, 20);
4381 
4382     /* seaf_message ("Download file %s for repo %s\n", de->name, repo_id); */
4383 
4384     if (!is_clone)
4385         seaf_sync_manager_update_active_path (seaf->sync_mgr,
4386                                               repo_id,
4387                                               de->name,
4388                                               de->mode,
4389                                               SYNC_STATUS_SYNCING,
4390                                               TRUE);
4391 
4392     rc = fetch_file_http (tx_data, task);
4393 
4394     /* Even if the file failed to check out, still need to update index.
4395      * But we have to stop after transfer errors.
4396      */
4397     if (rc == FETCH_CHECKOUT_CANCELED) {
4398         seaf_debug ("Transfer canceled.\n");
4399     } else if (rc == FETCH_CHECKOUT_TRANSFER_ERROR) {
4400         seaf_warning ("Transfer failed.\n");
4401     }
4402 
4403 out:
4404     task->result = rc;
4405     g_async_queue_push (finished_tasks, task);
4406 }
4407 
4408 static int
schedule_file_fetch(GThreadPool * tpool,const char * repo_id,const char * repo_name,const char * worktree,struct index_state * istate,DiffEntry * de,GHashTable * pending_tasks,GHashTable * conflict_hash,GHashTable * no_conflict_hash)4409 schedule_file_fetch (GThreadPool *tpool,
4410                      const char *repo_id,
4411                      const char *repo_name,
4412                      const char *worktree,
4413                      struct index_state *istate,
4414                      DiffEntry *de,
4415                      GHashTable *pending_tasks,
4416                      GHashTable *conflict_hash,
4417                      GHashTable *no_conflict_hash)
4418 {
4419     struct cache_entry *ce;
4420     gboolean new_ce = FALSE;
4421     gboolean skip_fetch = FALSE;
4422     char *path = NULL;
4423     FileTxTask *file_task;
4424 
4425     ce = index_name_exists (istate, de->name, strlen(de->name), 0);
4426     if (!ce) {
4427         ce = cache_entry_from_diff_entry (de);
4428         new_ce = TRUE;
4429     }
4430 
4431     IgnoreReason reason;
4432     if (should_ignore_on_checkout (de->name, &reason)) {
4433         seaf_message ("Path %s is invalid on Windows, skip checkout\n",
4434                       de->name);
4435         if (reason == IGNORE_REASON_END_SPACE_PERIOD)
4436             send_file_sync_error_notification (repo_id, repo_name, de->name,
4437                                                SYNC_ERROR_ID_PATH_END_SPACE_PERIOD);
4438         else if (reason == IGNORE_REASON_INVALID_CHARACTER)
4439             send_file_sync_error_notification (repo_id, repo_name, de->name,
4440                                                SYNC_ERROR_ID_PATH_INVALID_CHARACTER);
4441         skip_fetch = TRUE;
4442     }
4443 
4444     if (!skip_fetch) {
4445         path = build_checkout_path (worktree, de->name, strlen(de->name));
4446         if (!path) {
4447             if (new_ce)
4448                 cache_entry_free (ce);
4449             return FETCH_CHECKOUT_FAILED;
4450         }
4451     }
4452 
4453     file_task = g_new0 (FileTxTask, 1);
4454     file_task->de = de;
4455     file_task->ce = ce;
4456     file_task->path = path;
4457     file_task->new_ce = new_ce;
4458     file_task->skip_fetch = skip_fetch;
4459 
4460     if (!g_hash_table_lookup (pending_tasks, de->name)) {
4461         g_hash_table_insert (pending_tasks, g_strdup(de->name), file_task);
4462         g_thread_pool_push (tpool, file_task, NULL);
4463     } else {
4464         file_tx_task_free (file_task);
4465     }
4466 
4467     return FETCH_CHECKOUT_SUCCESS;
4468 }
4469 
4470 static void
cleanup_file_blocks_http(HttpTxTask * task,const char * file_id)4471 cleanup_file_blocks_http (HttpTxTask *task, const char *file_id)
4472 {
4473     Seafile *file;
4474     int i;
4475     char *block_id;
4476     int *pcnt;
4477 
4478     file = seaf_fs_manager_get_seafile (seaf->fs_mgr,
4479                                         task->repo_id, task->repo_version,
4480                                         file_id);
4481     if (!file) {
4482         seaf_warning ("Failed to load seafile object %s:%s\n",
4483                       task->repo_id, file_id);
4484         return;
4485     }
4486 
4487     for (i = 0; i < file->n_blocks; ++i) {
4488         block_id = file->blk_sha1s[i];
4489 
4490         pthread_mutex_lock (&task->ref_cnt_lock);
4491 
4492         pcnt = g_hash_table_lookup (task->blk_ref_cnts, block_id);
4493         if (pcnt) {
4494             --(*pcnt);
4495             if (*pcnt > 0) {
4496                 pthread_mutex_unlock (&task->ref_cnt_lock);
4497                 continue;
4498             }
4499         }
4500 
4501         seaf_block_manager_remove_block (seaf->block_mgr,
4502                                          task->repo_id, task->repo_version,
4503                                          block_id);
4504         g_hash_table_remove (task->blk_ref_cnts, block_id);
4505 
4506         pthread_mutex_unlock (&task->ref_cnt_lock);
4507     }
4508 
4509     seafile_unref (file);
4510 }
4511 
4512 static gboolean
check_path_conflict(const char * path,char ** orig_path)4513 check_path_conflict (const char *path, char **orig_path)
4514 {
4515     gboolean is_conflict = FALSE;
4516     GError *error = NULL;
4517 
4518     is_conflict = g_regex_match (conflict_pattern, path, 0, NULL);
4519     if (is_conflict) {
4520         *orig_path = g_regex_replace_literal (conflict_pattern, path, -1,
4521                                               0, "", 0, &error);
4522         if (!*orig_path)
4523             is_conflict = FALSE;
4524     }
4525 
4526     return is_conflict;
4527 }
4528 
4529 static int
checkout_file_http(FileTxData * data,FileTxTask * file_task,const char * worktree,GHashTable * conflict_hash,GHashTable * no_conflict_hash,const char * conflict_head_id,LockedFileSet * fset)4530 checkout_file_http (FileTxData *data,
4531                     FileTxTask *file_task,
4532                     const char *worktree,
4533                     GHashTable *conflict_hash,
4534                     GHashTable *no_conflict_hash,
4535                     const char *conflict_head_id,
4536                     LockedFileSet *fset)
4537 {
4538     char *repo_id = data->repo_id;
4539     int repo_version = data->repo_version;
4540     struct cache_entry *ce = file_task->ce;
4541     DiffEntry *de = file_task->de;
4542     SeafileCrypt *crypt = data->crypt;
4543     gboolean no_checkout = file_task->no_checkout;
4544     gboolean force_conflict = file_task->force_conflict;
4545     HttpTxTask *http_task = data->http_task;
4546     gboolean path_exists;
4547     gboolean case_conflict = FALSE;
4548     SeafStat st;
4549     char file_id[41];
4550     gboolean locked_on_server = FALSE;
4551 
4552     if (no_checkout)
4553         return FETCH_CHECKOUT_SUCCESS;
4554 
4555     if (should_ignore_on_checkout (de->name, NULL))
4556         return FETCH_CHECKOUT_SUCCESS;
4557 
4558     rawdata_to_hex (de->sha1, file_id, 20);
4559 
4560     locked_on_server = seaf_filelock_manager_is_file_locked (seaf->filelock_mgr,
4561                                                              repo_id, de->name);
4562 
4563 #if defined WIN32 || defined __APPLE__
4564     if (do_check_file_locked (de->name, worktree, locked_on_server)) {
4565         if (!locked_file_set_lookup (fset, de->name))
4566             send_file_sync_error_notification (repo_id, NULL, de->name,
4567                                                SYNC_ERROR_ID_FILE_LOCKED_BY_APP);
4568 
4569         locked_file_set_add_update (fset, de->name, LOCKED_OP_UPDATE,
4570                                     ce->ce_mtime.sec, file_id);
4571         /* Stay in syncing status if the file is locked. */
4572 
4573         return FETCH_CHECKOUT_SUCCESS;
4574     }
4575 #endif
4576 
4577     path_exists = (seaf_stat (file_task->path, &st) == 0);
4578 
4579     /* The worktree file may have been changed when we're downloading the blocks. */
4580     if (!file_task->new_ce &&
4581         path_exists && S_ISREG(st.st_mode) &&
4582         !force_conflict) {
4583         if (st.st_mtime != ce->ce_mtime.sec) {
4584             seaf_message ("File %s is updated by user. "
4585                           "Will checkout to conflict file later.\n", file_task->path);
4586             force_conflict = TRUE;
4587         }
4588     }
4589 
4590     /* Temporarily unlock the file if it's locked on server, so that the client
4591      * itself can write to it.
4592      */
4593     if (locked_on_server)
4594         seaf_filelock_manager_unlock_wt_file (seaf->filelock_mgr,
4595                                               repo_id, de->name);
4596 
4597     /* then checkout the file. */
4598     gboolean conflicted = FALSE;
4599     if (seaf_fs_manager_checkout_file (seaf->fs_mgr,
4600                                        repo_id,
4601                                        repo_version,
4602                                        file_id,
4603                                        file_task->path,
4604                                        de->mode,
4605                                        de->mtime,
4606                                        crypt,
4607                                        de->name,
4608                                        conflict_head_id,
4609                                        force_conflict,
4610                                        &conflicted,
4611                                        http_task->email) < 0) {
4612         seaf_warning ("Failed to checkout file %s.\n", file_task->path);
4613 
4614         if (seaf_filelock_manager_is_file_locked (seaf->filelock_mgr,
4615                                                   repo_id, de->name))
4616             seaf_filelock_manager_lock_wt_file (seaf->filelock_mgr,
4617                                                 repo_id, de->name);
4618 
4619         return FETCH_CHECKOUT_FAILED;
4620     }
4621 
4622     if (locked_on_server)
4623         seaf_filelock_manager_lock_wt_file (seaf->filelock_mgr,
4624                                             repo_id, de->name);
4625 
4626     cleanup_file_blocks_http (http_task, file_id);
4627 
4628     if (conflicted) {
4629         send_file_sync_error_notification (repo_id, NULL, de->name, SYNC_ERROR_ID_CONFLICT);
4630     } else if (!http_task->is_clone) {
4631         char *orig_path = NULL;
4632         if (check_path_conflict (de->name, &orig_path))
4633             send_file_sync_error_notification (repo_id, NULL, orig_path, SYNC_ERROR_ID_CONFLICT);
4634         g_free (orig_path);
4635     }
4636 
4637     /* If case conflict, this file will be checked out to another path.
4638      * Remove the current entry, otherwise it won't be removed later
4639      * since it's timestamp is 0.
4640      */
4641     if (case_conflict)
4642         ce->ce_flags |= CE_REMOVE;
4643 
4644     /* finally fill cache_entry info */
4645     /* Only update index if we checked out the file without any error
4646      * or conflicts. The ctime of the entry will remain 0 if error.
4647      */
4648     seaf_stat (file_task->path, &st);
4649     fill_stat_cache_info (ce, &st);
4650 
4651     return FETCH_CHECKOUT_SUCCESS;
4652 }
4653 
4654 static void
handle_dir_added_de(const char * repo_id,const char * repo_name,const char * worktree,struct index_state * istate,DiffEntry * de,GHashTable * conflict_hash,GHashTable * no_conflict_hash)4655 handle_dir_added_de (const char *repo_id,
4656                      const char *repo_name,
4657                      const char *worktree,
4658                      struct index_state *istate,
4659                      DiffEntry *de,
4660                      GHashTable *conflict_hash,
4661                      GHashTable *no_conflict_hash)
4662 {
4663     seaf_debug ("Checkout empty dir %s.\n", de->name);
4664 
4665     struct cache_entry *ce;
4666     gboolean add_ce = FALSE;
4667 
4668     ce = index_name_exists (istate, de->name, strlen(de->name), 0);
4669     if (!ce) {
4670         ce = cache_entry_from_diff_entry (de);
4671         add_ce = TRUE;
4672     }
4673 
4674     IgnoreReason reason;
4675     if (should_ignore_on_checkout (de->name, &reason)) {
4676         seaf_message ("Path %s is invalid on Windows, skip checkout\n",
4677                       de->name);
4678         if (reason == IGNORE_REASON_END_SPACE_PERIOD)
4679             send_file_sync_error_notification (repo_id, repo_name, de->name,
4680                                                SYNC_ERROR_ID_PATH_END_SPACE_PERIOD);
4681         else if (reason == IGNORE_REASON_INVALID_CHARACTER)
4682             send_file_sync_error_notification (repo_id, repo_name, de->name,
4683                                                SYNC_ERROR_ID_PATH_INVALID_CHARACTER);
4684         goto update_index;
4685     }
4686 
4687     checkout_empty_dir (worktree,
4688                         de->name,
4689                         de->mtime,
4690                         ce,
4691                         conflict_hash,
4692                         no_conflict_hash);
4693 
4694     seaf_sync_manager_update_active_path (seaf->sync_mgr,
4695                                           repo_id,
4696                                           de->name,
4697                                           de->mode,
4698                                           SYNC_STATUS_SYNCED,
4699                                           TRUE);
4700 
4701 update_index:
4702     if (add_ce) {
4703         if (!(ce->ce_flags & CE_REMOVE)) {
4704             add_index_entry (istate, ce,
4705                              (ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE));
4706         }
4707     } else
4708         ce->ce_mtime.sec = de->mtime;
4709 }
4710 
4711 #define DEFAULT_DOWNLOAD_THREADS 3
4712 
4713 static int
download_files_http(const char * repo_id,int repo_version,const char * worktree,struct index_state * istate,const char * index_path,SeafileCrypt * crypt,HttpTxTask * http_task,GList * results,GHashTable * conflict_hash,GHashTable * no_conflict_hash,const char * conflict_head_id,LockedFileSet * fset)4714 download_files_http (const char *repo_id,
4715                      int repo_version,
4716                      const char *worktree,
4717                      struct index_state *istate,
4718                      const char *index_path,
4719                      SeafileCrypt *crypt,
4720                      HttpTxTask *http_task,
4721                      GList *results,
4722                      GHashTable *conflict_hash,
4723                      GHashTable *no_conflict_hash,
4724                      const char *conflict_head_id,
4725                      LockedFileSet *fset)
4726 {
4727     struct cache_entry *ce;
4728     DiffEntry *de;
4729     gint64 checkout_size = 0;
4730     GThreadPool *tpool;
4731     GAsyncQueue *finished_tasks;
4732     GHashTable *pending_tasks;
4733     GList *ptr;
4734     FileTxTask *task;
4735     int ret = FETCH_CHECKOUT_SUCCESS;
4736 
4737     finished_tasks = g_async_queue_new ();
4738 
4739     FileTxData data;
4740     memset (&data, 0, sizeof(data));
4741     memcpy (data.repo_id, repo_id, 36);
4742     data.repo_version = repo_version;
4743     data.crypt = crypt;
4744     data.http_task = http_task;
4745     memcpy (data.conflict_head_id, conflict_head_id, 40);
4746     data.finished_tasks = finished_tasks;
4747 
4748     tpool = g_thread_pool_new (fetch_file_thread_func, &data,
4749                                DEFAULT_DOWNLOAD_THREADS, FALSE, NULL);
4750 
4751     pending_tasks = g_hash_table_new_full (g_str_hash, g_str_equal,
4752                                            g_free, (GDestroyNotify)file_tx_task_free);
4753 
4754     for (ptr = results; ptr != NULL; ptr = ptr->next) {
4755         de = ptr->data;
4756 
4757         if (de->status == DIFF_STATUS_DIR_ADDED) {
4758             handle_dir_added_de (repo_id, http_task->repo_name, worktree, istate, de,
4759                                  conflict_hash, no_conflict_hash);
4760         } else if (de->status == DIFF_STATUS_ADDED ||
4761                    de->status == DIFF_STATUS_MODIFIED) {
4762             if (FETCH_CHECKOUT_FAILED == schedule_file_fetch (tpool,
4763                                                               repo_id,
4764                                                               http_task->repo_name,
4765                                                               worktree,
4766                                                               istate,
4767                                                               de,
4768                                                               pending_tasks,
4769                                                               conflict_hash,
4770                                                               no_conflict_hash))
4771                 continue;
4772         }
4773     }
4774 
4775     /* If there is no file need to be downloaded, return immediately. */
4776     if (g_hash_table_size(pending_tasks) == 0) {
4777         if (results != NULL)
4778             update_index (istate, index_path);
4779         goto out;
4780     }
4781 
4782     char file_id[41];
4783     while ((task = g_async_queue_pop (finished_tasks)) != NULL) {
4784         ce = task->ce;
4785         de = task->de;
4786 
4787         rawdata_to_hex (de->sha1, file_id, 20);
4788         /* seaf_message ("Finished downloading file %s for repo %s\n", */
4789         /*               de->name, repo_id); */
4790 
4791         if (task->result == FETCH_CHECKOUT_CANCELED ||
4792             task->result == FETCH_CHECKOUT_TRANSFER_ERROR) {
4793             ret = task->result;
4794             if (task->new_ce)
4795                 cache_entry_free (task->ce);
4796             http_task->all_stop = TRUE;
4797             goto out;
4798         }
4799 
4800         int rc = checkout_file_http (&data, task, worktree,
4801                                      conflict_hash, no_conflict_hash,
4802                                      conflict_head_id, fset);
4803 
4804         if (!http_task->is_clone) {
4805             SyncStatus status;
4806             if (rc == FETCH_CHECKOUT_FAILED)
4807                 status = SYNC_STATUS_ERROR;
4808             else
4809                 status = SYNC_STATUS_SYNCED;
4810             seaf_sync_manager_update_active_path (seaf->sync_mgr,
4811                                                   repo_id,
4812                                                   de->name,
4813                                                   de->mode,
4814                                                   status,
4815                                                   TRUE);
4816         }
4817 
4818         if (task->new_ce) {
4819             if (!(ce->ce_flags & CE_REMOVE)) {
4820                 add_index_entry (istate, task->ce,
4821                                  (ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE));
4822             }
4823         } else {
4824             ce->ce_mtime.sec = de->mtime;
4825             ce->ce_size = de->size;
4826             memcpy (ce->sha1, de->sha1, 20);
4827             if (ce->modifier) g_free (ce->modifier);
4828             ce->modifier = g_strdup(de->modifier);
4829             ce->ce_mode = create_ce_mode (de->mode);
4830         }
4831 
4832         g_hash_table_remove (pending_tasks, de->name);
4833 
4834         if (g_hash_table_size (pending_tasks) == 0)
4835             break;
4836 
4837         /* Save index file to disk after checking out some size of files.
4838          * This way we don't need to re-compare too many files if this
4839          * checkout is interrupted.
4840          */
4841         checkout_size += ce->ce_size;
4842         if (checkout_size >= UPDATE_CACHE_SIZE_LIMIT) {
4843             update_index (istate, index_path);
4844             checkout_size = 0;
4845         }
4846     }
4847 
4848     update_index (istate, index_path);
4849 
4850 out:
4851     /* Wait until all threads exit.
4852      * This is necessary when the download is canceled or encountered error.
4853      */
4854     g_thread_pool_free (tpool, TRUE, TRUE);
4855 
4856     /* Free all pending file task structs. */
4857     g_hash_table_destroy (pending_tasks);
4858 
4859     g_async_queue_unref (finished_tasks);
4860 
4861     return ret;
4862 }
4863 
4864 static gboolean
expand_dir_added_cb(SeafFSManager * mgr,const char * path,SeafDirent * dent,void * user_data,gboolean * stop)4865 expand_dir_added_cb (SeafFSManager *mgr,
4866                      const char *path,
4867                      SeafDirent *dent,
4868                      void *user_data,
4869                      gboolean *stop)
4870 {
4871     GList **expanded = user_data;
4872     DiffEntry *de = NULL;
4873     unsigned char sha1[20];
4874 
4875     hex_to_rawdata (dent->id, sha1, 20);
4876 
4877     if (S_ISDIR(dent->mode) && strcmp(dent->id, EMPTY_SHA1) == 0)
4878         de = diff_entry_new (DIFF_TYPE_COMMITS, DIFF_STATUS_DIR_ADDED, sha1, path);
4879     else if (S_ISREG(dent->mode))
4880         de = diff_entry_new (DIFF_TYPE_COMMITS, DIFF_STATUS_ADDED, sha1, path);
4881 
4882     if (de) {
4883         de->mtime = dent->mtime;
4884         de->mode = dent->mode;
4885         de->modifier = g_strdup(dent->modifier);
4886         de->size = dent->size;
4887         *expanded = g_list_prepend (*expanded, de);
4888     }
4889 
4890     return TRUE;
4891 }
4892 
4893 /*
4894  * Expand DIR_ADDED results into multiple ADDED results.
4895  */
4896 static int
expand_diff_results(const char * repo_id,int version,const char * remote_root,const char * local_root,GList ** results)4897 expand_diff_results (const char *repo_id, int version,
4898                      const char *remote_root, const char *local_root,
4899                      GList **results)
4900 {
4901     GList *ptr, *next;
4902     DiffEntry *de;
4903     char obj_id[41];
4904     GList *expanded = NULL;
4905 
4906     ptr = *results;
4907     while (ptr) {
4908         de = ptr->data;
4909 
4910         next = ptr->next;
4911 
4912         if (de->status == DIFF_STATUS_DIR_ADDED) {
4913             *results = g_list_delete_link (*results, ptr);
4914 
4915             rawdata_to_hex (de->sha1, obj_id, 20);
4916             if (seaf_fs_manager_traverse_path (seaf->fs_mgr,
4917                                                repo_id, version,
4918                                                remote_root,
4919                                                de->name,
4920                                                expand_dir_added_cb,
4921                                                &expanded) < 0) {
4922                 diff_entry_free (de);
4923                 goto error;
4924             }
4925             diff_entry_free (de);
4926         }
4927 
4928         ptr = next;
4929     }
4930 
4931     expanded = g_list_reverse (expanded);
4932     *results = g_list_concat (*results, expanded);
4933 
4934     return 0;
4935 
4936 error:
4937     g_list_free_full (expanded, (GDestroyNotify)diff_entry_free);
4938     return -1;
4939 }
4940 
4941 static int
do_rename_in_worktree(DiffEntry * de,const char * worktree,GHashTable * conflict_hash,GHashTable * no_conflict_hash)4942 do_rename_in_worktree (DiffEntry *de, const char *worktree,
4943                        GHashTable *conflict_hash, GHashTable *no_conflict_hash)
4944 {
4945     char *old_path, *new_path;
4946     int ret = 0;
4947 
4948     old_path = g_build_filename (worktree, de->name, NULL);
4949 
4950     if (seaf_util_exists (old_path)) {
4951         new_path = build_checkout_path (worktree, de->new_name, strlen(de->new_name));
4952         if (!new_path) {
4953             ret = -1;
4954             goto out;
4955         }
4956 
4957         if (seaf_util_rename (old_path, new_path) < 0) {
4958             seaf_warning ("Failed to rename %s to %s: %s.\n",
4959                           old_path, new_path, strerror(errno));
4960             ret = -1;
4961         }
4962 
4963         g_free (new_path);
4964     }
4965 
4966 out:
4967     g_free (old_path);
4968     return ret;
4969 }
4970 
4971 static gboolean
is_built_in_ignored_file(const char * filename)4972 is_built_in_ignored_file (const char *filename)
4973 {
4974     GPatternSpec **spec = ignore_patterns;
4975 
4976     while (*spec) {
4977         if (g_pattern_match_string(*spec, filename))
4978             return TRUE;
4979         spec++;
4980     }
4981 
4982     if (!seaf->sync_extra_temp_file) {
4983         spec = office_temp_ignore_patterns;
4984         while (*spec) {
4985             if (g_pattern_match_string(*spec, filename))
4986                 return TRUE;
4987             spec++;
4988         }
4989     }
4990 
4991     return FALSE;
4992 }
4993 
4994 #ifdef WIN32
4995 
4996 /*
4997  * @path: path relative to the worktree, utf-8 encoded
4998  * @path_w: absolute path include worktree, utf-16 encoded.
4999  * Return 0 when successfully deleted the folder; otherwise -1.
5000  */
5001 static int
delete_worktree_dir_recursive_win32(struct index_state * istate,const char * path,const wchar_t * path_w)5002 delete_worktree_dir_recursive_win32 (struct index_state *istate,
5003                                      const char *path,
5004                                      const wchar_t *path_w)
5005 {
5006     WIN32_FIND_DATAW fdata;
5007     HANDLE handle;
5008     wchar_t *pattern;
5009     wchar_t *sub_path_w;
5010     char *sub_path, *dname;
5011     int path_len_w;
5012     DWORD error;
5013     int ret = 0;
5014     guint64 mtime;
5015     gboolean builtin_ignored = FALSE;
5016 
5017     path_len_w = wcslen(path_w);
5018 
5019     pattern = g_new0 (wchar_t, (path_len_w + 3));
5020     wcscpy (pattern, path_w);
5021     wcscat (pattern, L"\\*");
5022 
5023     handle = FindFirstFileW (pattern, &fdata);
5024     g_free (pattern);
5025 
5026     if (handle == INVALID_HANDLE_VALUE) {
5027         seaf_warning ("FindFirstFile failed %s: %lu.\n",
5028                       path, GetLastError());
5029         return -1;
5030     }
5031 
5032     do {
5033         if (wcscmp (fdata.cFileName, L".") == 0 ||
5034             wcscmp (fdata.cFileName, L"..") == 0)
5035             continue;
5036 
5037         dname = g_utf16_to_utf8 (fdata.cFileName, -1, NULL, NULL, NULL);
5038         if (!dname)
5039             continue;
5040 
5041         sub_path_w = g_new0 (wchar_t, path_len_w + wcslen(fdata.cFileName) + 2);
5042         wcscpy (sub_path_w, path_w);
5043         wcscat (sub_path_w, L"\\");
5044         wcscat (sub_path_w, fdata.cFileName);
5045 
5046         sub_path = g_strconcat (path, "/", dname, NULL);
5047         builtin_ignored = is_built_in_ignored_file(dname);
5048         g_free (dname);
5049 
5050         if (fdata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
5051             if (delete_worktree_dir_recursive_win32 (istate, sub_path, sub_path_w) < 0) {
5052                 ret = -1;
5053             }
5054         } else {
5055             struct cache_entry *ce;
5056             /* Files like .DS_Store and Thumbs.db should be deleted any way. */
5057             if (!builtin_ignored) {
5058                 mtime = (guint64)file_time_to_unix_time (&fdata.ftLastWriteTime);
5059                 ce = index_name_exists (istate, sub_path, strlen(sub_path), 0);
5060                 if (!ce || (!is_eml_file (dname) && ce->ce_mtime.sec != mtime)) {
5061                     seaf_message ("File %s is changed, skip deleting it.\n", sub_path);
5062                     g_free (sub_path_w);
5063                     g_free (sub_path);
5064                     ret = -1;
5065                     continue;
5066                 }
5067             }
5068 
5069             if (!DeleteFileW (sub_path_w)) {
5070                 error = GetLastError();
5071                 seaf_warning ("Failed to delete file %s: %lu.\n",
5072                               sub_path, error);
5073                 ret = -1;
5074             }
5075         }
5076 
5077         g_free (sub_path_w);
5078         g_free (sub_path);
5079     } while (FindNextFileW (handle, &fdata) != 0);
5080 
5081     error = GetLastError();
5082     if (error != ERROR_NO_MORE_FILES) {
5083         seaf_warning ("FindNextFile failed %s: %lu.\n",
5084                       path, error);
5085         ret = -1;
5086     }
5087 
5088     FindClose (handle);
5089 
5090     if (ret < 0)
5091         return ret;
5092 
5093     int n = 0;
5094     while (!RemoveDirectoryW (path_w)) {
5095         error = GetLastError();
5096         seaf_warning ("Failed to remove dir %s: %lu.\n",
5097                       path, error);
5098         if (error != ERROR_DIR_NOT_EMPTY) {
5099             ret = -1;
5100             break;
5101         }
5102         if (++n >= 3) {
5103             ret = -1;
5104             break;
5105         }
5106         /* Sleep 100ms and retry. */
5107         g_usleep (100000);
5108         seaf_warning ("Retry remove dir %s.\n", path);
5109     }
5110 
5111     return ret;
5112 }
5113 
5114 #else
5115 
5116 static int
delete_worktree_dir_recursive(struct index_state * istate,const char * path,const char * full_path)5117 delete_worktree_dir_recursive (struct index_state *istate,
5118                                const char *path,
5119                                const char *full_path)
5120 {
5121     GDir *dir;
5122     const char *dname;
5123     char *dname_nfc;
5124     GError *error = NULL;
5125     char *sub_path, *full_sub_path;
5126     SeafStat st;
5127     int ret = 0;
5128     gboolean builtin_ignored = FALSE;
5129 
5130     dir = g_dir_open (full_path, 0, &error);
5131     if (!dir) {
5132         seaf_warning ("Failed to open dir %s: %s.\n", full_path, error->message);
5133         return -1;
5134     }
5135 
5136     while ((dname = g_dir_read_name (dir)) != NULL) {
5137         dname_nfc = g_utf8_normalize (dname, -1, G_NORMALIZE_NFC);
5138         sub_path = g_build_path ("/", path, dname_nfc, NULL);
5139         full_sub_path = g_build_path ("/", full_path, dname_nfc, NULL);
5140         builtin_ignored = is_built_in_ignored_file (dname_nfc);
5141         g_free (dname_nfc);
5142 
5143         if (lstat (full_sub_path, &st) < 0) {
5144             seaf_warning ("Failed to stat %s.\n", full_sub_path);
5145             g_free (sub_path);
5146             g_free (full_sub_path);
5147             ret = -1;
5148             continue;
5149         }
5150 
5151         if (S_ISDIR(st.st_mode)) {
5152             if (delete_worktree_dir_recursive (istate, sub_path, full_sub_path) < 0)
5153                 ret = -1;
5154         } else {
5155             struct cache_entry *ce;
5156             /* Files like .DS_Store and Thumbs.db should be deleted any way. */
5157             if (!builtin_ignored) {
5158                 ce = index_name_exists (istate, sub_path, strlen(sub_path), 0);
5159                 if (!ce || ce->ce_mtime.sec != st.st_mtime) {
5160                     seaf_message ("File %s is changed, skip deleting it.\n", full_sub_path);
5161                     g_free (sub_path);
5162                     g_free (full_sub_path);
5163                     ret = -1;
5164                     continue;
5165                 }
5166             }
5167 
5168             /* Delete all other file types. */
5169             if (seaf_util_unlink (full_sub_path) < 0) {
5170                 seaf_warning ("Failed to delete file %s: %s.\n",
5171                               full_sub_path, strerror(errno));
5172                 ret = -1;
5173             }
5174         }
5175 
5176         g_free (sub_path);
5177         g_free (full_sub_path);
5178     }
5179 
5180     g_dir_close (dir);
5181 
5182     if (ret < 0)
5183         return ret;
5184 
5185     if (g_rmdir (full_path) < 0) {
5186         seaf_warning ("Failed to delete dir %s: %s.\n", full_path, strerror(errno));
5187         ret = -1;
5188     }
5189 
5190     return ret;
5191 }
5192 
5193 #endif  /* WIN32 */
5194 
5195 #define SEAFILE_RECYCLE_BIN_FOLDER "recycle-bin"
5196 
5197 static int
move_dir_to_recycle_bin(const char * dir_path)5198 move_dir_to_recycle_bin (const char *dir_path)
5199 {
5200     char *trash_folder = g_build_path ("/", seaf->worktree_dir, SEAFILE_RECYCLE_BIN_FOLDER, NULL);
5201     if (checkdir_with_mkdir (trash_folder) < 0) {
5202         seaf_warning ("Seafile trash folder %s doesn't exist and cannot be created.\n",
5203                       trash_folder);
5204         g_free (trash_folder);
5205         return -1;
5206     }
5207     g_free (trash_folder);
5208 
5209     char *basename = g_path_get_basename (dir_path);
5210     char *dst_path = g_build_path ("/", seaf->worktree_dir, SEAFILE_RECYCLE_BIN_FOLDER, basename, NULL);
5211     int ret = 0;
5212 
5213     int n;
5214     char *tmp_path;
5215     for (n = 1; n < 10; ++n) {
5216         if (g_file_test (dst_path, G_FILE_TEST_EXISTS)) {
5217             tmp_path = g_strdup_printf ("%s(%d)", dst_path, n);
5218             g_free (dst_path);
5219             dst_path = tmp_path;
5220             continue;
5221         }
5222         break;
5223     }
5224 
5225     if (seaf_util_rename (dir_path, dst_path) < 0) {
5226         seaf_warning ("Failed to move %s to Seafile recycle bin %s: %s\n",
5227                       dir_path, dst_path, strerror(errno));
5228         ret = -1;
5229         goto out;
5230     }
5231 
5232     seaf_message ("Moved folder %s to Seafile recyle bin %s.\n",
5233                   dir_path, dst_path);
5234 
5235 out:
5236     g_free (basename);
5237     g_free (dst_path);
5238     return ret;
5239 }
5240 
5241 static void
delete_worktree_dir(const char * repo_id,const char * repo_name,struct index_state * istate,const char * worktree,const char * path)5242 delete_worktree_dir (const char *repo_id,
5243                      const char *repo_name,
5244                      struct index_state *istate,
5245                      const char *worktree,
5246                      const char *path)
5247 {
5248     char *full_path = g_build_path ("/", worktree, path, NULL);
5249 
5250 #ifdef WIN32
5251     wchar_t *full_path_w = win32_long_path (full_path);
5252     delete_worktree_dir_recursive_win32 (istate, path, full_path_w);
5253     g_free (full_path_w);
5254 #else
5255     delete_worktree_dir_recursive(istate, path, full_path);
5256 #endif
5257 
5258     /* If for some reason the dir cannot be removed, try to move it to a trash folder
5259      * under Seafile folder. Otherwise the removed folder will be created agian on the
5260      * server, which will confuse the users.
5261      */
5262     if (g_file_test (full_path, G_FILE_TEST_EXISTS)) {
5263         if (move_dir_to_recycle_bin (full_path) == 0)
5264             send_file_sync_error_notification (repo_id, repo_name, path,
5265                                                SYNC_ERROR_ID_REMOVE_UNCOMMITTED_FOLDER);
5266     }
5267 
5268     g_free (full_path);
5269 }
5270 
5271 static void
update_sync_status(struct cache_entry * ce,void * user_data)5272 update_sync_status (struct cache_entry *ce, void *user_data)
5273 {
5274     char *repo_id = user_data;
5275 
5276     seaf_sync_manager_update_active_path (seaf->sync_mgr,
5277                                           repo_id,
5278                                           ce->name,
5279                                           ce->ce_mode,
5280                                           SYNC_STATUS_SYNCED,
5281                                           TRUE);
5282 }
5283 
5284 #ifdef WIN32
5285 static int
convert_rename_to_checkout(const char * repo_id,int repo_version,const char * root_id,DiffEntry * de,GList ** entries)5286 convert_rename_to_checkout (const char *repo_id,
5287                             int repo_version,
5288                             const char *root_id,
5289                             DiffEntry *de,
5290                             GList **entries)
5291 {
5292     if (de->status == DIFF_STATUS_RENAMED) {
5293         char file_id[41];
5294         SeafDirent *dent = NULL;
5295         DiffEntry *new_de = NULL;
5296 
5297         rawdata_to_hex (de->sha1, file_id, 20);
5298         dent = seaf_fs_manager_get_dirent_by_path (seaf->fs_mgr,
5299                                                    repo_id,
5300                                                    repo_version,
5301                                                    root_id,
5302                                                    de->new_name,
5303                                                    NULL);
5304         if (!dent) {
5305             seaf_warning ("Failed to find %s in repo %s\n",
5306                           de->new_name, repo_id);
5307             return -1;
5308         }
5309 
5310         new_de = diff_entry_new (DIFF_TYPE_COMMITS, DIFF_STATUS_ADDED,
5311                                  de->sha1, de->new_name);
5312         if (new_de) {
5313             new_de->mtime = dent->mtime;
5314             new_de->mode = dent->mode;
5315             new_de->modifier = g_strdup(dent->modifier);
5316             new_de->size = dent->size;
5317             *entries = g_list_prepend (*entries, new_de);
5318         }
5319 
5320         seaf_dirent_free (dent);
5321     } else if (de->status == DIFF_STATUS_DIR_RENAMED) {
5322         GList *expanded = NULL;
5323 
5324         if (seaf_fs_manager_traverse_path (seaf->fs_mgr,
5325                                            repo_id, repo_version,
5326                                            root_id,
5327                                            de->new_name,
5328                                            expand_dir_added_cb,
5329                                            &expanded) < 0) {
5330             g_list_free_full (expanded, (GDestroyNotify)diff_entry_free);
5331             return -1;
5332         }
5333 
5334         *entries = g_list_concat (*entries, expanded);
5335     }
5336 
5337     return 0;
5338 }
5339 #endif  /* WIN32 */
5340 
5341 int
seaf_repo_fetch_and_checkout(HttpTxTask * http_task,const char * remote_head_id)5342 seaf_repo_fetch_and_checkout (HttpTxTask *http_task, const char *remote_head_id)
5343 {
5344     char *repo_id;
5345     int repo_version;
5346     gboolean is_clone;
5347     char *worktree;
5348     char *passwd;
5349 
5350     SeafRepo *repo = NULL;
5351     SeafBranch *master = NULL;
5352     SeafCommit *remote_head = NULL, *master_head = NULL;
5353     char index_path[SEAF_PATH_MAX];
5354     struct index_state istate;
5355     int ret = FETCH_CHECKOUT_SUCCESS;
5356     GList *results = NULL;
5357     SeafileCrypt *crypt = NULL;
5358     GHashTable *conflict_hash = NULL, *no_conflict_hash = NULL;
5359     GList *ignore_list = NULL;
5360     LockedFileSet *fset = NULL;
5361 
5362     repo_id = http_task->repo_id;
5363     repo_version = http_task->repo_version;
5364     is_clone = http_task->is_clone;
5365     worktree = http_task->worktree;
5366     passwd = http_task->passwd;
5367 
5368     memset (&istate, 0, sizeof(istate));
5369     snprintf (index_path, SEAF_PATH_MAX, "%s/%s",
5370               seaf->repo_mgr->index_dir, repo_id);
5371     if (read_index_from (&istate, index_path, repo_version) < 0) {
5372         seaf_warning ("Failed to load index.\n");
5373         return FETCH_CHECKOUT_FAILED;
5374     }
5375 
5376     if (!is_clone) {
5377         repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id);
5378         if (!repo) {
5379             seaf_warning ("Failed to get repo %.8s.\n", repo_id);
5380             goto out;
5381         }
5382 
5383         master = seaf_branch_manager_get_branch (seaf->branch_mgr,
5384                                                  repo_id, "master");
5385         if (!master) {
5386             seaf_warning ("Failed to get master branch for repo %.8s.\n",
5387                           repo_id);
5388             ret = FETCH_CHECKOUT_FAILED;
5389             goto out;
5390         }
5391 
5392         master_head = seaf_commit_manager_get_commit (seaf->commit_mgr,
5393                                                       repo_id,
5394                                                       repo_version,
5395                                                       master->commit_id);
5396         if (!master_head) {
5397             seaf_warning ("Failed to get master head %s of repo %.8s.\n",
5398                           repo_id, master->commit_id);
5399             ret = FETCH_CHECKOUT_FAILED;
5400             goto out;
5401         }
5402     }
5403 
5404     if (!is_clone)
5405         worktree = repo->worktree;
5406 
5407     remote_head = seaf_commit_manager_get_commit (seaf->commit_mgr,
5408                                                   repo_id,
5409                                                   repo_version,
5410                                                   remote_head_id);
5411     if (!remote_head) {
5412         seaf_warning ("Failed to get remote head %s of repo %.8s.\n",
5413                       repo_id, remote_head_id);
5414         ret = FETCH_CHECKOUT_FAILED;
5415         goto out;
5416     }
5417 
5418     if (diff_commit_roots (repo_id, repo_version,
5419                            master_head ? master_head->root_id : EMPTY_SHA1,
5420                            remote_head->root_id,
5421                            &results, TRUE) < 0) {
5422         seaf_warning ("Failed to diff for repo %.8s.\n", repo_id);
5423         ret = FETCH_CHECKOUT_FAILED;
5424         goto out;
5425     }
5426 
5427     GList *ptr;
5428     DiffEntry *de;
5429 
5430     /* Expand DIR_ADDED diff entries. */
5431     if (expand_diff_results (repo_id, repo_version,
5432                              remote_head->root_id,
5433                              master_head ? master_head->root_id : EMPTY_SHA1,
5434                              &results) < 0) {
5435         ret = FETCH_CHECKOUT_FAILED;
5436         goto out;
5437     }
5438 
5439 #ifdef WIN32
5440     for (ptr = results; ptr; ptr = ptr->next) {
5441         de = ptr->data;
5442         if (de->status == DIFF_STATUS_DIR_RENAMED ||
5443             de->status == DIFF_STATUS_DIR_DELETED) {
5444             if (do_check_dir_locked (de->name, worktree)) {
5445                 seaf_message ("File(s) in dir %s are locked by other program, "
5446                               "skip rename/delete.\n", de->name);
5447                 send_file_sync_error_notification (repo_id, NULL, de->name,
5448                                                    SYNC_ERROR_ID_FOLDER_LOCKED_BY_APP);
5449                 ret = FETCH_CHECKOUT_LOCKED;
5450                 goto out;
5451             }
5452         } else if (de->status == DIFF_STATUS_RENAMED) {
5453             gboolean locked_on_server = seaf_filelock_manager_is_file_locked (seaf->filelock_mgr,
5454                                                                               repo_id,
5455                                                                               de->name);
5456 
5457             if (do_check_file_locked (de->name, worktree, locked_on_server)) {
5458                 seaf_message ("File %s is locked by other program, skip rename.\n",
5459                               de->name);
5460                 send_file_sync_error_notification (repo_id, NULL, de->name,
5461                                                    SYNC_ERROR_ID_FILE_LOCKED_BY_APP);
5462                 ret = FETCH_CHECKOUT_LOCKED;
5463                 goto out;
5464             }
5465         }
5466     }
5467 #endif
5468 
5469     if (remote_head->encrypted) {
5470         if (!is_clone) {
5471             crypt = seafile_crypt_new (repo->enc_version,
5472                                        repo->enc_key,
5473                                        repo->enc_iv);
5474         } else {
5475             unsigned char enc_key[32], enc_iv[16];
5476             seafile_decrypt_repo_enc_key (remote_head->enc_version,
5477                                           passwd,
5478                                           remote_head->random_key,
5479                                           remote_head->salt,
5480                                           enc_key, enc_iv);
5481             crypt = seafile_crypt_new (remote_head->enc_version,
5482                                        enc_key, enc_iv);
5483         }
5484     }
5485 
5486     conflict_hash = g_hash_table_new_full (g_str_hash, g_str_equal,
5487                                            g_free, g_free);
5488     no_conflict_hash = g_hash_table_new_full (g_str_hash, g_str_equal,
5489                                               g_free, NULL);
5490 
5491     ignore_list = seaf_repo_load_ignore_files (worktree);
5492 
5493     struct cache_entry *ce;
5494 
5495 #if defined WIN32 || defined __APPLE__
5496     fset = seaf_repo_manager_get_locked_file_set (seaf->repo_mgr, repo_id);
5497 #endif
5498 
5499     for (ptr = results; ptr; ptr = ptr->next) {
5500         de = ptr->data;
5501         if (de->status == DIFF_STATUS_DELETED) {
5502             seaf_debug ("Delete file %s.\n", de->name);
5503 
5504             if (should_ignore_on_checkout (de->name, NULL)) {
5505                 seaf_message ("Path %s is invalid on Windows, skip delete.\n",
5506                               de->name);
5507                 continue;
5508             }
5509 
5510             ce = index_name_exists (&istate, de->name, strlen(de->name), 0);
5511             if (!ce)
5512                 continue;
5513 
5514             gboolean locked_on_server = seaf_filelock_manager_is_file_locked (seaf->filelock_mgr,
5515                                                                               repo_id,
5516                                                                               de->name);
5517             if (locked_on_server)
5518                 seaf_filelock_manager_unlock_wt_file (seaf->filelock_mgr,
5519                                                       repo_id, de->name);
5520 
5521 #if defined WIN32 || defined __APPLE__
5522             if (!do_check_file_locked (de->name, worktree, locked_on_server)) {
5523                 locked_file_set_remove (fset, de->name, FALSE);
5524                 delete_path (worktree, de->name, de->mode, ce->ce_mtime.sec);
5525             } else {
5526                 if (!locked_file_set_lookup (fset, de->name))
5527                     send_file_sync_error_notification (repo_id, http_task->repo_name, de->name,
5528                                                        SYNC_ERROR_ID_FILE_LOCKED_BY_APP);
5529 
5530                 locked_file_set_add_update (fset, de->name, LOCKED_OP_DELETE,
5531                                             ce->ce_mtime.sec, NULL);
5532             }
5533 #else
5534             delete_path (worktree, de->name, de->mode, ce->ce_mtime.sec);
5535 #endif
5536 
5537             /* No need to lock wt file again since it's deleted. */
5538 
5539             remove_from_index_with_prefix (&istate, de->name, NULL);
5540             try_add_empty_parent_dir_entry (worktree, &istate, de->name);
5541         } else if (de->status == DIFF_STATUS_DIR_DELETED) {
5542             seaf_debug ("Delete dir %s.\n", de->name);
5543 
5544             if (should_ignore_on_checkout (de->name, NULL)) {
5545                 seaf_message ("Path %s is invalid on Windows, skip delete.\n",
5546                               de->name);
5547                 continue;
5548             }
5549 
5550             /* Nothing to delete. */
5551             if (!master_head || strcmp(master_head->root_id, EMPTY_SHA1) == 0)
5552                 continue;
5553 
5554             delete_worktree_dir (repo_id, http_task->repo_name, &istate, worktree, de->name);
5555 
5556             /* Remove all index entries under this directory */
5557             remove_from_index_with_prefix (&istate, de->name, NULL);
5558 
5559             try_add_empty_parent_dir_entry (worktree, &istate, de->name);
5560         }
5561     }
5562 
5563     for (ptr = results; ptr; ptr = ptr->next) {
5564         de = ptr->data;
5565         if (de->status == DIFF_STATUS_RENAMED ||
5566             de->status == DIFF_STATUS_DIR_RENAMED) {
5567             seaf_debug ("Rename %s to %s.\n", de->name, de->new_name);
5568 
5569 #ifdef WIN32
5570             IgnoreReason reason;
5571             if (should_ignore_on_checkout (de->new_name, &reason)) {
5572                 seaf_message ("Path %s is invalid on Windows, skip rename.\n", de->new_name);
5573 
5574                 if (reason == IGNORE_REASON_END_SPACE_PERIOD)
5575                     send_file_sync_error_notification (repo_id, http_task->repo_name,
5576                                                        de->new_name,
5577                                                        SYNC_ERROR_ID_PATH_END_SPACE_PERIOD);
5578                 else if (reason == IGNORE_REASON_INVALID_CHARACTER)
5579                     send_file_sync_error_notification (repo_id, http_task->repo_name,
5580                                                        de->new_name,
5581                                                        SYNC_ERROR_ID_PATH_INVALID_CHARACTER);
5582                 continue;
5583             } else if (should_ignore_on_checkout (de->name, NULL)) {
5584                 /* If the server renames an invalid path to a valid path,
5585                  * directly checkout the valid path. The checkout will merge
5586                  * with any existing files.
5587                  */
5588                 convert_rename_to_checkout (repo_id, repo_version,
5589                                             remote_head->root_id,
5590                                             de, &results);
5591                 continue;
5592             }
5593 #endif
5594 
5595             if (seaf_filelock_manager_is_file_locked (seaf->filelock_mgr,
5596                                                       repo_id, de->name))
5597                 seaf_filelock_manager_unlock_wt_file (seaf->filelock_mgr,
5598                                                       repo_id, de->name);
5599 
5600             do_rename_in_worktree (de, worktree, conflict_hash, no_conflict_hash);
5601 
5602             /* update_sync_status updates the sync status for each renamed path.
5603              * The renamed file/folder becomes "synced" immediately after rename.
5604              */
5605             if (!is_clone)
5606                 rename_index_entries (&istate, de->name, de->new_name, NULL,
5607                                       update_sync_status, repo_id);
5608             else
5609                 rename_index_entries (&istate, de->name, de->new_name, NULL,
5610                                       NULL, NULL);
5611 
5612             /* Moving files out of a dir may make it empty. */
5613             try_add_empty_parent_dir_entry (worktree, &istate, de->name);
5614         }
5615     }
5616 
5617     if (istate.cache_changed)
5618         update_index (&istate, index_path);
5619 
5620     for (ptr = results; ptr; ptr = ptr->next) {
5621         de = ptr->data;
5622         if (de->status == DIFF_STATUS_ADDED || de->status == DIFF_STATUS_MODIFIED) {
5623             http_task->total_download += de->size;
5624         }
5625     }
5626 
5627     ret = download_files_http (repo_id,
5628                                repo_version,
5629                                worktree,
5630                                &istate,
5631                                index_path,
5632                                crypt,
5633                                http_task,
5634                                results,
5635                                conflict_hash,
5636                                no_conflict_hash,
5637                                remote_head_id,
5638                                fset);
5639 
5640 out:
5641     discard_index (&istate);
5642 
5643     seaf_branch_unref (master);
5644     seaf_commit_unref (master_head);
5645     seaf_commit_unref (remote_head);
5646 
5647     g_list_free_full (results, (GDestroyNotify)diff_entry_free);
5648 
5649     g_free (crypt);
5650     if (conflict_hash)
5651         g_hash_table_destroy (conflict_hash);
5652     if (no_conflict_hash)
5653         g_hash_table_destroy (no_conflict_hash);
5654 
5655     if (ignore_list)
5656         seaf_repo_free_ignore_files (ignore_list);
5657 
5658 #if defined WIN32 || defined __APPLE__
5659     locked_file_set_free (fset);
5660 #endif
5661 
5662     return ret;
5663 }
5664 
5665 int
seaf_repo_manager_set_repo_worktree(SeafRepoManager * mgr,SeafRepo * repo,const char * worktree)5666 seaf_repo_manager_set_repo_worktree (SeafRepoManager *mgr,
5667                                      SeafRepo *repo,
5668                                      const char *worktree)
5669 {
5670     if (g_access(worktree, F_OK) != 0)
5671         return -1;
5672 
5673     if (repo->worktree)
5674         g_free (repo->worktree);
5675     repo->worktree = g_strdup(worktree);
5676 
5677     if (seaf_repo_manager_set_repo_property (mgr, repo->id,
5678                                              "worktree",
5679                                              repo->worktree) < 0)
5680         return -1;
5681 
5682     repo->worktree_invalid = FALSE;
5683 
5684     return 0;
5685 }
5686 
5687 void
seaf_repo_manager_invalidate_repo_worktree(SeafRepoManager * mgr,SeafRepo * repo)5688 seaf_repo_manager_invalidate_repo_worktree (SeafRepoManager *mgr,
5689                                             SeafRepo *repo)
5690 {
5691     if (repo->worktree_invalid)
5692         return;
5693 
5694     repo->worktree_invalid = TRUE;
5695 
5696     if (repo->auto_sync && (repo->sync_interval == 0)) {
5697         if (seaf_wt_monitor_unwatch_repo (seaf->wt_monitor, repo->id) < 0) {
5698             seaf_warning ("failed to unwatch repo %s.\n", repo->id);
5699         }
5700     }
5701 }
5702 
5703 void
seaf_repo_manager_validate_repo_worktree(SeafRepoManager * mgr,SeafRepo * repo)5704 seaf_repo_manager_validate_repo_worktree (SeafRepoManager *mgr,
5705                                           SeafRepo *repo)
5706 {
5707     if (!repo->worktree_invalid)
5708         return;
5709 
5710     repo->worktree_invalid = FALSE;
5711 
5712     if (repo->auto_sync && (repo->sync_interval == 0)) {
5713         if (seaf_wt_monitor_watch_repo (seaf->wt_monitor, repo->id, repo->worktree) < 0) {
5714             seaf_warning ("failed to watch repo %s.\n", repo->id);
5715         }
5716     }
5717 }
5718 
5719 SeafRepoManager*
seaf_repo_manager_new(SeafileSession * seaf)5720 seaf_repo_manager_new (SeafileSession *seaf)
5721 {
5722     SeafRepoManager *mgr = g_new0 (SeafRepoManager, 1);
5723 
5724     mgr->priv = g_new0 (SeafRepoManagerPriv, 1);
5725     mgr->seaf = seaf;
5726     mgr->index_dir = g_build_path (PATH_SEPERATOR, seaf->seaf_dir, INDEX_DIR, NULL);
5727 
5728     pthread_mutex_init (&mgr->priv->db_lock, NULL);
5729 
5730     mgr->priv->checkout_tasks_hash = g_hash_table_new_full
5731         (g_str_hash, g_str_equal, g_free, g_free);
5732 
5733     ignore_patterns = g_new0 (GPatternSpec*, G_N_ELEMENTS(ignore_table));
5734     int i;
5735     for (i = 0; ignore_table[i] != NULL; i++) {
5736         ignore_patterns[i] = g_pattern_spec_new (ignore_table[i]);
5737     }
5738 
5739     office_temp_ignore_patterns[0] = g_pattern_spec_new("~$*");
5740     /* for files like ~WRL0001.tmp for docx and *.tmp for xlsx and pptx */
5741     office_temp_ignore_patterns[1] = g_pattern_spec_new("*.tmp");
5742     office_temp_ignore_patterns[2] = g_pattern_spec_new(".~lock*#");
5743     office_temp_ignore_patterns[3] = NULL;
5744 
5745     GError *error = NULL;
5746     conflict_pattern = g_regex_new (CONFLICT_PATTERN, 0, 0, &error);
5747     if (error) {
5748         seaf_warning ("Failed to create regex '%s': %s\n",
5749                       CONFLICT_PATTERN, error->message);
5750         g_clear_error (&error);
5751     }
5752 
5753     office_lock_pattern = g_regex_new (OFFICE_LOCK_PATTERN, 0, 0, &error);
5754     if (error) {
5755         seaf_warning ("Failed to create regex '%s': %s\n",
5756                       OFFICE_LOCK_PATTERN, error->message);
5757         g_clear_error (&error);
5758     }
5759 
5760     mgr->priv->repo_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
5761 
5762     pthread_rwlock_init (&mgr->priv->lock, NULL);
5763 
5764     mgr->priv->lock_office_job_queue = g_async_queue_new ();
5765 
5766     return mgr;
5767 }
5768 
5769 int
seaf_repo_manager_init(SeafRepoManager * mgr)5770 seaf_repo_manager_init (SeafRepoManager *mgr)
5771 {
5772     if (checkdir_with_mkdir (mgr->index_dir) < 0) {
5773         seaf_warning ("Index dir %s does not exist and is unable to create\n",
5774                    mgr->index_dir);
5775         return -1;
5776     }
5777 
5778     /* Load all the repos into memory on the client side. */
5779     load_repos (mgr, mgr->seaf->seaf_dir);
5780 
5781     /* Load folder permissions from db. */
5782     init_folder_perms (mgr);
5783 
5784     return 0;
5785 }
5786 
5787 static void
watch_repos(SeafRepoManager * mgr)5788 watch_repos (SeafRepoManager *mgr)
5789 {
5790     GHashTableIter iter;
5791     SeafRepo *repo;
5792     gpointer key, value;
5793 
5794     g_hash_table_iter_init (&iter, mgr->priv->repo_hash);
5795     while (g_hash_table_iter_next (&iter, &key, &value)) {
5796         repo = value;
5797         if (repo->auto_sync && !repo->worktree_invalid && (repo->sync_interval == 0)) {
5798             if (seaf_wt_monitor_watch_repo (seaf->wt_monitor, repo->id, repo->worktree) < 0) {
5799                 seaf_warning ("failed to watch repo %s.\n", repo->id);
5800                 /* If we fail to add watch at the beginning, sync manager
5801                  * will periodically check repo status and retry.
5802                  */
5803             }
5804         }
5805     }
5806 }
5807 
5808 #define REMOVE_OBJECTS_BATCH 1000
5809 
5810 static int
remove_store(const char * top_store_dir,const char * store_id,int * count)5811 remove_store (const char *top_store_dir, const char *store_id, int *count)
5812 {
5813     char *obj_dir = NULL;
5814     GDir *dir1, *dir2;
5815     const char *dname1, *dname2;
5816     char *path1, *path2;
5817 
5818     obj_dir = g_build_filename (top_store_dir, store_id, NULL);
5819 
5820     dir1 = g_dir_open (obj_dir, 0, NULL);
5821     if (!dir1) {
5822         g_free (obj_dir);
5823         return 0;
5824     }
5825 
5826     seaf_message ("Removing store %s\n", obj_dir);
5827 
5828     while ((dname1 = g_dir_read_name(dir1)) != NULL) {
5829         path1 = g_build_filename (obj_dir, dname1, NULL);
5830 
5831         dir2 = g_dir_open (path1, 0, NULL);
5832         if (!dir2) {
5833             seaf_warning ("Failed to open obj dir %s.\n", path1);
5834             g_dir_close (dir1);
5835             g_free (path1);
5836             g_free (obj_dir);
5837             return -1;
5838         }
5839 
5840         while ((dname2 = g_dir_read_name(dir2)) != NULL) {
5841             path2 = g_build_filename (path1, dname2, NULL);
5842             g_unlink (path2);
5843 
5844             /* To prevent using too much IO, only remove 1000 objects per 5 seconds.
5845              */
5846             if (++(*count) > REMOVE_OBJECTS_BATCH) {
5847                 g_usleep (5 * G_USEC_PER_SEC);
5848                 *count = 0;
5849             }
5850 
5851             g_free (path2);
5852         }
5853         g_dir_close (dir2);
5854 
5855         g_rmdir (path1);
5856         g_free (path1);
5857     }
5858 
5859     g_dir_close (dir1);
5860     g_rmdir (obj_dir);
5861     g_free (obj_dir);
5862 
5863     return 0;
5864 }
5865 
5866 static void
cleanup_deleted_stores_by_type(const char * type)5867 cleanup_deleted_stores_by_type (const char *type)
5868 {
5869     char *top_store_dir;
5870     const char *repo_id;
5871 
5872     top_store_dir = g_build_filename (seaf->seaf_dir, "deleted_store", type, NULL);
5873 
5874     GError *error = NULL;
5875     GDir *dir = g_dir_open (top_store_dir, 0, &error);
5876     if (!dir) {
5877         seaf_warning ("Failed to open store dir %s: %s.\n",
5878                       top_store_dir, error->message);
5879         g_free (top_store_dir);
5880         return;
5881     }
5882 
5883     int count = 0;
5884     while ((repo_id = g_dir_read_name(dir)) != NULL) {
5885         remove_store (top_store_dir, repo_id, &count);
5886     }
5887 
5888     g_free (top_store_dir);
5889     g_dir_close (dir);
5890 }
5891 
5892 static void *
cleanup_deleted_stores(void * vdata)5893 cleanup_deleted_stores (void *vdata)
5894 {
5895     while (1) {
5896         cleanup_deleted_stores_by_type ("commits");
5897         cleanup_deleted_stores_by_type ("fs");
5898         cleanup_deleted_stores_by_type ("blocks");
5899         g_usleep (60 * G_USEC_PER_SEC);
5900     }
5901     return NULL;
5902 }
5903 
5904 int
seaf_repo_manager_start(SeafRepoManager * mgr)5905 seaf_repo_manager_start (SeafRepoManager *mgr)
5906 {
5907     pthread_t tid;
5908     pthread_attr_t attr;
5909     pthread_attr_init(&attr);
5910     pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
5911     int rc;
5912 
5913     watch_repos (mgr);
5914 
5915     rc = pthread_create (&tid, &attr, cleanup_deleted_stores, NULL);
5916     if (rc != 0) {
5917         seaf_warning ("Failed to start cleanup thread: %s\n", strerror(rc));
5918     }
5919 
5920 #if defined WIN32 || defined __APPLE__
5921     rc = pthread_create (&tid, &attr, lock_office_file_worker,
5922                          mgr->priv->lock_office_job_queue);
5923     if (rc != 0) {
5924         seaf_warning ("Failed to start lock office file thread: %s\n", strerror(rc));
5925     }
5926 #endif
5927 
5928     return 0;
5929 }
5930 
5931 SeafRepo*
seaf_repo_manager_create_new_repo(SeafRepoManager * mgr,const char * name,const char * desc)5932 seaf_repo_manager_create_new_repo (SeafRepoManager *mgr,
5933                                    const char *name,
5934                                    const char *desc)
5935 {
5936     SeafRepo *repo;
5937     char *repo_id;
5938 
5939     repo_id = gen_uuid ();
5940     repo = seaf_repo_new (repo_id, name, desc);
5941     if (!repo) {
5942         g_free (repo_id);
5943         return NULL;
5944     }
5945     g_free (repo_id);
5946 
5947     /* we directly create dir because it shouldn't exist */
5948     /* if (seaf_repo_mkdir (repo, base) < 0) { */
5949     /*     seaf_repo_free (repo); */
5950     /*     goto out; */
5951     /* } */
5952 
5953     seaf_repo_manager_add_repo (mgr, repo);
5954     return repo;
5955 }
5956 
5957 int
seaf_repo_manager_add_repo(SeafRepoManager * manager,SeafRepo * repo)5958 seaf_repo_manager_add_repo (SeafRepoManager *manager,
5959                             SeafRepo *repo)
5960 {
5961     char sql[256];
5962     sqlite3 *db = manager->priv->db;
5963 
5964     pthread_mutex_lock (&manager->priv->db_lock);
5965 
5966     snprintf (sql, sizeof(sql), "REPLACE INTO Repo VALUES ('%s');", repo->id);
5967     sqlite_query_exec (db, sql);
5968 
5969     pthread_mutex_unlock (&manager->priv->db_lock);
5970 
5971     /* There may be a "deletion record" for this repo when it was deleted
5972      * last time.
5973      */
5974     seaf_repo_manager_remove_garbage_repo (manager, repo->id);
5975 
5976     repo->manager = manager;
5977 
5978     if (pthread_rwlock_wrlock (&manager->priv->lock) < 0) {
5979         seaf_warning ("[repo mgr] failed to lock repo cache.\n");
5980         return -1;
5981     }
5982 
5983     g_hash_table_insert (manager->priv->repo_hash, g_strdup(repo->id), repo);
5984 
5985     pthread_rwlock_unlock (&manager->priv->lock);
5986 
5987     return 0;
5988 }
5989 
5990 int
seaf_repo_manager_mark_repo_deleted(SeafRepoManager * mgr,SeafRepo * repo)5991 seaf_repo_manager_mark_repo_deleted (SeafRepoManager *mgr, SeafRepo *repo)
5992 {
5993     char sql[256];
5994 
5995     pthread_mutex_lock (&mgr->priv->db_lock);
5996 
5997     snprintf (sql, sizeof(sql), "INSERT INTO DeletedRepo VALUES ('%s')",
5998               repo->id);
5999     if (sqlite_query_exec (mgr->priv->db, sql) < 0) {
6000         pthread_mutex_unlock (&mgr->priv->db_lock);
6001         return -1;
6002     }
6003 
6004     pthread_mutex_unlock (&mgr->priv->db_lock);
6005 
6006     repo->delete_pending = TRUE;
6007 
6008     return 0;
6009 }
6010 
6011 static gboolean
get_garbage_repo_id(sqlite3_stmt * stmt,void * vid_list)6012 get_garbage_repo_id (sqlite3_stmt *stmt, void *vid_list)
6013 {
6014     GList **ret = vid_list;
6015     char *repo_id;
6016 
6017     repo_id = g_strdup((const char *)sqlite3_column_text (stmt, 0));
6018     *ret = g_list_prepend (*ret, repo_id);
6019 
6020     return TRUE;
6021 }
6022 
6023 GList *
seaf_repo_manager_list_garbage_repos(SeafRepoManager * mgr)6024 seaf_repo_manager_list_garbage_repos (SeafRepoManager *mgr)
6025 {
6026     GList *repo_ids = NULL;
6027 
6028     pthread_mutex_lock (&mgr->priv->db_lock);
6029 
6030     sqlite_foreach_selected_row (mgr->priv->db,
6031                                  "SELECT repo_id FROM GarbageRepos",
6032                                  get_garbage_repo_id, &repo_ids);
6033     pthread_mutex_unlock (&mgr->priv->db_lock);
6034 
6035     return repo_ids;
6036 }
6037 
6038 void
seaf_repo_manager_remove_garbage_repo(SeafRepoManager * mgr,const char * repo_id)6039 seaf_repo_manager_remove_garbage_repo (SeafRepoManager *mgr, const char *repo_id)
6040 {
6041     char sql[256];
6042 
6043     pthread_mutex_lock (&mgr->priv->db_lock);
6044 
6045     snprintf (sql, sizeof(sql), "DELETE FROM GarbageRepos WHERE repo_id='%s'",
6046               repo_id);
6047     sqlite_query_exec (mgr->priv->db, sql);
6048 
6049     pthread_mutex_unlock (&mgr->priv->db_lock);
6050 }
6051 
6052 void
seaf_repo_manager_remove_repo_ondisk(SeafRepoManager * mgr,const char * repo_id,gboolean add_deleted_record)6053 seaf_repo_manager_remove_repo_ondisk (SeafRepoManager *mgr,
6054                                       const char *repo_id,
6055                                       gboolean add_deleted_record)
6056 {
6057     char sql[256];
6058 
6059     /* We don't need to care about I/O errors here, since we can
6060      * GC any unreferenced repo data later.
6061      */
6062 
6063     if (add_deleted_record) {
6064         snprintf (sql, sizeof(sql), "REPLACE INTO GarbageRepos VALUES ('%s')",
6065                   repo_id);
6066         if (sqlite_query_exec (mgr->priv->db, sql) < 0)
6067             goto out;
6068     }
6069 
6070     /* Once the item in Repo table is deleted, the repo is gone.
6071      * This is the "commit point".
6072      */
6073     pthread_mutex_lock (&mgr->priv->db_lock);
6074 
6075     snprintf (sql, sizeof(sql), "DELETE FROM Repo WHERE repo_id = '%s'", repo_id);
6076     if (sqlite_query_exec (mgr->priv->db, sql) < 0)
6077         goto out;
6078 
6079     snprintf (sql, sizeof(sql),
6080               "DELETE FROM DeletedRepo WHERE repo_id = '%s'", repo_id);
6081     sqlite_query_exec (mgr->priv->db, sql);
6082 
6083     pthread_mutex_unlock (&mgr->priv->db_lock);
6084 
6085     /* remove index */
6086     char path[SEAF_PATH_MAX];
6087     snprintf (path, SEAF_PATH_MAX, "%s/%s", mgr->index_dir, repo_id);
6088     seaf_util_unlink (path);
6089 
6090     /* remove branch */
6091     GList *p;
6092     GList *branch_list =
6093         seaf_branch_manager_get_branch_list (seaf->branch_mgr, repo_id);
6094     for (p = branch_list; p; p = p->next) {
6095         SeafBranch *b = (SeafBranch *)p->data;
6096         seaf_repo_manager_branch_repo_unmap (mgr, b);
6097         seaf_branch_manager_del_branch (seaf->branch_mgr, repo_id, b->name);
6098     }
6099     seaf_branch_list_free (branch_list);
6100 
6101     /* delete repo property firstly */
6102     seaf_repo_manager_del_repo_property (mgr, repo_id);
6103 
6104     pthread_mutex_lock (&mgr->priv->db_lock);
6105 
6106     snprintf (sql, sizeof(sql), "DELETE FROM RepoPasswd WHERE repo_id = '%s'",
6107               repo_id);
6108     sqlite_query_exec (mgr->priv->db, sql);
6109     snprintf (sql, sizeof(sql), "DELETE FROM RepoKeys WHERE repo_id = '%s'",
6110               repo_id);
6111     sqlite_query_exec (mgr->priv->db, sql);
6112 
6113     snprintf (sql, sizeof(sql), "DELETE FROM MergeInfo WHERE repo_id = '%s'",
6114               repo_id);
6115     sqlite_query_exec (mgr->priv->db, sql);
6116 
6117     snprintf (sql, sizeof(sql), "DELETE FROM LockedFiles WHERE repo_id = '%s'",
6118               repo_id);
6119     sqlite_query_exec (mgr->priv->db, sql);
6120 
6121     snprintf (sql, sizeof(sql), "DELETE FROM FolderUserPerms WHERE repo_id = '%s'",
6122               repo_id);
6123     sqlite_query_exec (mgr->priv->db, sql);
6124 
6125     snprintf (sql, sizeof(sql), "DELETE FROM FolderGroupPerms WHERE repo_id = '%s'",
6126               repo_id);
6127     sqlite_query_exec (mgr->priv->db, sql);
6128 
6129     snprintf (sql, sizeof(sql), "DELETE FROM FolderPermTimestamp WHERE repo_id = '%s'",
6130               repo_id);
6131     sqlite_query_exec (mgr->priv->db, sql);
6132 
6133     seaf_filelock_manager_remove (seaf->filelock_mgr, repo_id);
6134 
6135 out:
6136     pthread_mutex_unlock (&mgr->priv->db_lock);
6137 }
6138 
6139 static char *
gen_deleted_store_path(const char * type,const char * repo_id)6140 gen_deleted_store_path (const char *type, const char *repo_id)
6141 {
6142     int n = 1;
6143     char *path = NULL;
6144     char *name = NULL;
6145 
6146     path = g_build_filename (seaf->deleted_store, type, repo_id, NULL);
6147     while (g_access(path, F_OK) == 0 && n < 10) {
6148         g_free (path);
6149         name = g_strdup_printf ("%s(%d)", repo_id, n);
6150         path = g_build_filename (seaf->deleted_store, type, name, NULL);
6151         g_free (name);
6152         ++n;
6153     }
6154 
6155     if (n == 10) {
6156         g_free (path);
6157         return NULL;
6158     }
6159 
6160     return path;
6161 }
6162 
6163 void
seaf_repo_manager_move_repo_store(SeafRepoManager * mgr,const char * type,const char * repo_id)6164 seaf_repo_manager_move_repo_store (SeafRepoManager *mgr,
6165                                    const char *type,
6166                                    const char *repo_id)
6167 {
6168     char *src = NULL;
6169     char *dst = NULL;
6170 
6171     src = g_build_filename (seaf->seaf_dir, "storage", type, repo_id, NULL);
6172     dst = gen_deleted_store_path (type, repo_id);
6173     if (dst) {
6174         g_rename (src, dst);
6175     }
6176     g_free (src);
6177     g_free (dst);
6178 }
6179 
6180 /* Move commits, fs stores into "deleted_store" directory. */
6181 static void
move_repo_stores(SeafRepoManager * mgr,SeafRepo * repo)6182 move_repo_stores (SeafRepoManager *mgr, SeafRepo *repo)
6183 {
6184     seaf_repo_manager_move_repo_store (mgr, "commits", repo->id);
6185     seaf_repo_manager_move_repo_store (mgr, "fs", repo->id);
6186 }
6187 
6188 int
seaf_repo_manager_del_repo(SeafRepoManager * mgr,SeafRepo * repo)6189 seaf_repo_manager_del_repo (SeafRepoManager *mgr,
6190                             SeafRepo *repo)
6191 {
6192     seaf_repo_manager_remove_repo_ondisk (mgr, repo->id,
6193                                           (repo->version > 0) ? TRUE : FALSE);
6194 
6195     seaf_sync_manager_remove_active_path_info (seaf->sync_mgr, repo->id);
6196 
6197     remove_folder_perms (mgr, repo->id);
6198 
6199     move_repo_stores (mgr, repo);
6200 
6201     if (pthread_rwlock_wrlock (&mgr->priv->lock) < 0) {
6202         seaf_warning ("[repo mgr] failed to lock repo cache.\n");
6203         return -1;
6204     }
6205 
6206     g_hash_table_remove (mgr->priv->repo_hash, repo->id);
6207 
6208     pthread_rwlock_unlock (&mgr->priv->lock);
6209 
6210     seaf_repo_free (repo);
6211 
6212     return 0;
6213 }
6214 
6215 /*
6216   Return the internal Repo in hashtable. The caller should not free the returned Repo.
6217  */
6218 SeafRepo*
seaf_repo_manager_get_repo(SeafRepoManager * manager,const gchar * id)6219 seaf_repo_manager_get_repo (SeafRepoManager *manager, const gchar *id)
6220 {
6221     SeafRepo *res;
6222 
6223     if (pthread_rwlock_rdlock (&manager->priv->lock) < 0) {
6224         seaf_warning ("[repo mgr] failed to lock repo cache.\n");
6225         return NULL;
6226     }
6227 
6228     res = g_hash_table_lookup (manager->priv->repo_hash, id);
6229 
6230     pthread_rwlock_unlock (&manager->priv->lock);
6231 
6232     if (res && !res->delete_pending)
6233         return res;
6234 
6235     return NULL;
6236 }
6237 
6238 gboolean
seaf_repo_manager_repo_exists(SeafRepoManager * manager,const gchar * id)6239 seaf_repo_manager_repo_exists (SeafRepoManager *manager, const gchar *id)
6240 {
6241     SeafRepo *res;
6242 
6243     if (pthread_rwlock_rdlock (&manager->priv->lock) < 0) {
6244         seaf_warning ("[repo mgr] failed to lock repo cache.\n");
6245         return FALSE;
6246     }
6247 
6248     res = g_hash_table_lookup (manager->priv->repo_hash, id);
6249 
6250     pthread_rwlock_unlock (&manager->priv->lock);
6251 
6252     if (res && !res->delete_pending)
6253         return TRUE;
6254 
6255     return FALSE;
6256 }
6257 
6258 static int
save_branch_repo_map(SeafRepoManager * manager,SeafBranch * branch)6259 save_branch_repo_map (SeafRepoManager *manager, SeafBranch *branch)
6260 {
6261     char *sql;
6262     sqlite3 *db = manager->priv->db;
6263 
6264     pthread_mutex_lock (&manager->priv->db_lock);
6265 
6266     sql = sqlite3_mprintf ("REPLACE INTO RepoBranch VALUES (%Q, %Q)",
6267                            branch->repo_id, branch->name);
6268     sqlite_query_exec (db, sql);
6269     sqlite3_free (sql);
6270 
6271     pthread_mutex_unlock (&manager->priv->db_lock);
6272 
6273     return 0;
6274 }
6275 
6276 int
seaf_repo_manager_branch_repo_unmap(SeafRepoManager * manager,SeafBranch * branch)6277 seaf_repo_manager_branch_repo_unmap (SeafRepoManager *manager, SeafBranch *branch)
6278 {
6279     char *sql;
6280     sqlite3 *db = manager->priv->db;
6281 
6282     pthread_mutex_lock (&manager->priv->db_lock);
6283 
6284     sql = sqlite3_mprintf ("DELETE FROM RepoBranch WHERE branch_name = %Q"
6285                            " AND repo_id = %Q",
6286                            branch->name, branch->repo_id);
6287     if (sqlite_query_exec (db, sql) < 0) {
6288         seaf_warning ("Unmap branch repo failed\n");
6289         pthread_mutex_unlock (&manager->priv->db_lock);
6290         sqlite3_free (sql);
6291         return -1;
6292     }
6293 
6294     sqlite3_free (sql);
6295     pthread_mutex_unlock (&manager->priv->db_lock);
6296 
6297     return 0;
6298 }
6299 
6300 static void
load_repo_commit(SeafRepoManager * manager,SeafRepo * repo,SeafBranch * branch)6301 load_repo_commit (SeafRepoManager *manager,
6302                   SeafRepo *repo,
6303                   SeafBranch *branch)
6304 {
6305     SeafCommit *commit;
6306 
6307     commit = seaf_commit_manager_get_commit_compatible (manager->seaf->commit_mgr,
6308                                                         repo->id,
6309                                                         branch->commit_id);
6310     if (!commit) {
6311         seaf_warning ("Commit %s is missing\n", branch->commit_id);
6312         repo->is_corrupted = TRUE;
6313         return;
6314     }
6315 
6316     set_head_common (repo, branch);
6317     seaf_repo_from_commit (repo, commit);
6318 
6319     seaf_commit_unref (commit);
6320 }
6321 
6322 static gboolean
load_keys_cb(sqlite3_stmt * stmt,void * vrepo)6323 load_keys_cb (sqlite3_stmt *stmt, void *vrepo)
6324 {
6325     SeafRepo *repo = vrepo;
6326     const char *key, *iv;
6327 
6328     key = (const char *)sqlite3_column_text(stmt, 0);
6329     iv = (const char *)sqlite3_column_text(stmt, 1);
6330 
6331     if (repo->enc_version == 1) {
6332         hex_to_rawdata (key, repo->enc_key, 16);
6333         hex_to_rawdata (iv, repo->enc_iv, 16);
6334     } else if (repo->enc_version >= 2) {
6335         hex_to_rawdata (key, repo->enc_key, 32);
6336         hex_to_rawdata (iv, repo->enc_iv, 16);
6337     }
6338 
6339     return FALSE;
6340 }
6341 
6342 static int
load_repo_passwd(SeafRepoManager * manager,SeafRepo * repo)6343 load_repo_passwd (SeafRepoManager *manager, SeafRepo *repo)
6344 {
6345     sqlite3 *db = manager->priv->db;
6346     char sql[256];
6347     int n;
6348 
6349     pthread_mutex_lock (&manager->priv->db_lock);
6350 
6351     snprintf (sql, sizeof(sql),
6352               "SELECT key, iv FROM RepoKeys WHERE repo_id='%s'",
6353               repo->id);
6354     n = sqlite_foreach_selected_row (db, sql, load_keys_cb, repo);
6355     if (n < 0) {
6356         pthread_mutex_unlock (&manager->priv->db_lock);
6357         return -1;
6358     }
6359 
6360     pthread_mutex_unlock (&manager->priv->db_lock);
6361 
6362     return 0;
6363 
6364 }
6365 
6366 static gboolean
load_property_cb(sqlite3_stmt * stmt,void * pvalue)6367 load_property_cb (sqlite3_stmt *stmt, void *pvalue)
6368 {
6369     char **value = pvalue;
6370 
6371     char *v = (char *) sqlite3_column_text (stmt, 0);
6372     *value = g_strdup (v);
6373 
6374     /* Only one result. */
6375     return FALSE;
6376 }
6377 
6378 static char *
load_repo_property(SeafRepoManager * manager,const char * repo_id,const char * key)6379 load_repo_property (SeafRepoManager *manager,
6380                     const char *repo_id,
6381                     const char *key)
6382 {
6383     sqlite3 *db = manager->priv->db;
6384     char sql[256];
6385     char *value = NULL;
6386 
6387     pthread_mutex_lock (&manager->priv->db_lock);
6388 
6389     snprintf(sql, 256, "SELECT value FROM RepoProperty WHERE "
6390              "repo_id='%s' and key='%s'", repo_id, key);
6391     if (sqlite_foreach_selected_row (db, sql, load_property_cb, &value) < 0) {
6392         seaf_warning ("Error read property %s for repo %s.\n", key, repo_id);
6393         pthread_mutex_unlock (&manager->priv->db_lock);
6394         return NULL;
6395     }
6396 
6397     pthread_mutex_unlock (&manager->priv->db_lock);
6398 
6399     return value;
6400 }
6401 
6402 static gboolean
load_branch_cb(sqlite3_stmt * stmt,void * vrepo)6403 load_branch_cb (sqlite3_stmt *stmt, void *vrepo)
6404 {
6405     SeafRepo *repo = vrepo;
6406     SeafRepoManager *manager = repo->manager;
6407 
6408     char *branch_name = (char *) sqlite3_column_text (stmt, 0);
6409     SeafBranch *branch =
6410         seaf_branch_manager_get_branch (manager->seaf->branch_mgr,
6411                                         repo->id, branch_name);
6412     if (branch == NULL) {
6413         seaf_warning ("Broken branch name for repo %s\n", repo->id);
6414         repo->is_corrupted = TRUE;
6415         return FALSE;
6416     }
6417     load_repo_commit (manager, repo, branch);
6418     seaf_branch_unref (branch);
6419 
6420     /* Only one result. */
6421     return FALSE;
6422 }
6423 
6424 static gboolean
is_wt_repo_name_same(const char * worktree,const char * repo_name)6425 is_wt_repo_name_same (const char *worktree, const char *repo_name)
6426 {
6427     char *basename = g_path_get_basename (worktree);
6428     gboolean ret = FALSE;
6429     ret = (g_strcmp0 (basename, repo_name) == 0);
6430     g_free (basename);
6431     return ret;
6432 }
6433 
6434 static SeafRepo *
load_repo(SeafRepoManager * manager,const char * repo_id)6435 load_repo (SeafRepoManager *manager, const char *repo_id)
6436 {
6437     char sql[256];
6438 
6439     SeafRepo *repo = seaf_repo_new(repo_id, NULL, NULL);
6440     if (!repo) {
6441         seaf_warning ("[repo mgr] failed to alloc repo.\n");
6442         return NULL;
6443     }
6444 
6445     repo->manager = manager;
6446 
6447     snprintf(sql, 256, "SELECT branch_name FROM RepoBranch WHERE repo_id='%s'",
6448              repo->id);
6449     if (sqlite_foreach_selected_row (manager->priv->db, sql,
6450                                      load_branch_cb, repo) < 0) {
6451         seaf_warning ("Error read branch for repo %s.\n", repo->id);
6452         seaf_repo_free (repo);
6453         return NULL;
6454     }
6455 
6456     /* If repo head is set but failed to load branch or commit. */
6457     if (repo->is_corrupted) {
6458         seaf_repo_free (repo);
6459         /* remove_repo_ondisk (manager, repo_id); */
6460         return NULL;
6461     }
6462 
6463     /* Repo head may be not set if it's just cloned but not checked out yet. */
6464     if (repo->head == NULL) {
6465         /* the repo do not have a head branch, try to load 'master' branch */
6466         SeafBranch *branch =
6467             seaf_branch_manager_get_branch (manager->seaf->branch_mgr,
6468                                             repo->id, "master");
6469         if (branch != NULL) {
6470              SeafCommit *commit;
6471 
6472              commit =
6473                  seaf_commit_manager_get_commit_compatible (manager->seaf->commit_mgr,
6474                                                             repo->id,
6475                                                             branch->commit_id);
6476              if (commit) {
6477                  seaf_repo_from_commit (repo, commit);
6478                  seaf_commit_unref (commit);
6479              } else {
6480                  seaf_warning ("[repo-mgr] Can not find commit %s\n",
6481                             branch->commit_id);
6482                  repo->is_corrupted = TRUE;
6483              }
6484 
6485              seaf_branch_unref (branch);
6486         } else {
6487             seaf_warning ("[repo-mgr] Failed to get branch master");
6488             repo->is_corrupted = TRUE;
6489         }
6490     }
6491 
6492     if (repo->is_corrupted) {
6493         seaf_repo_free (repo);
6494         /* remove_repo_ondisk (manager, repo_id); */
6495         return NULL;
6496     }
6497 
6498     load_repo_passwd (manager, repo);
6499 
6500     char *value;
6501 
6502     value = load_repo_property (manager, repo->id, REPO_AUTO_SYNC);
6503     if (g_strcmp0(value, "false") == 0) {
6504         repo->auto_sync = 0;
6505     }
6506     g_free (value);
6507 
6508     repo->worktree = load_repo_property (manager, repo->id, "worktree");
6509     if (repo->worktree)
6510         repo->worktree_invalid = FALSE;
6511 
6512     repo->email = load_repo_property (manager, repo->id, REPO_PROP_EMAIL);
6513     repo->token = load_repo_property (manager, repo->id, REPO_PROP_TOKEN);
6514 
6515     /* May be NULL if this property is not set in db. */
6516     repo->server_url = load_repo_property (manager, repo->id, REPO_PROP_SERVER_URL);
6517 
6518     if (repo->head != NULL && seaf_repo_check_worktree (repo) < 0) {
6519         if (seafile_session_config_get_allow_invalid_worktree(seaf)) {
6520             seaf_warning ("Worktree for repo \"%s\" is invalid, but still keep it.\n",
6521                           repo->name);
6522             repo->worktree_invalid = TRUE;
6523         } else {
6524             seaf_message ("Worktree for repo \"%s\" is invalid, delete it.\n",
6525                           repo->name);
6526             seaf_repo_manager_del_repo (manager, repo);
6527             return NULL;
6528         }
6529     }
6530 
6531     /* load readonly property */
6532     value = load_repo_property (manager, repo->id, REPO_PROP_IS_READONLY);
6533     if (g_strcmp0(value, "true") == 0)
6534         repo->is_readonly = TRUE;
6535     else
6536         repo->is_readonly = FALSE;
6537     g_free (value);
6538 
6539     /* load sync period property */
6540     value = load_repo_property (manager, repo->id, REPO_PROP_SYNC_INTERVAL);
6541     if (value) {
6542         int interval = atoi(value);
6543         if (interval > 0)
6544             repo->sync_interval = interval;
6545     }
6546     g_free (value);
6547 
6548     if (repo->worktree) {
6549         gboolean wt_repo_name_same = is_wt_repo_name_same (repo->worktree, repo->name);
6550         value = load_repo_property (manager, repo->id, REPO_SYNC_WORKTREE_NAME);
6551         if (g_strcmp0 (value, "true") == 0) {
6552             /* If need to sync worktree name with library name, update worktree folder name. */
6553             if (!wt_repo_name_same)
6554                 update_repo_worktree_name (repo, repo->name, FALSE);
6555         } else {
6556             /* If an existing repo's worktree folder name is the same as repo name, but
6557              * sync_worktree_name property is not set, set it.
6558              */
6559             if (wt_repo_name_same)
6560                 save_repo_property (manager, repo->id, REPO_SYNC_WORKTREE_NAME, "true");
6561         }
6562         g_free (value);
6563     }
6564 
6565     g_hash_table_insert (manager->priv->repo_hash, g_strdup(repo->id), repo);
6566 
6567     return repo;
6568 }
6569 
6570 static sqlite3*
open_db(SeafRepoManager * manager,const char * seaf_dir)6571 open_db (SeafRepoManager *manager, const char *seaf_dir)
6572 {
6573     sqlite3 *db;
6574     char *db_path;
6575 
6576     db_path = g_build_filename (seaf_dir, "repo.db", NULL);
6577     if (sqlite_open_db (db_path, &db) < 0)
6578         return NULL;
6579     g_free (db_path);
6580     manager->priv->db = db;
6581 
6582     char *sql = "CREATE TABLE IF NOT EXISTS Repo (repo_id TEXT PRIMARY KEY);";
6583     sqlite_query_exec (db, sql);
6584 
6585     sql = "CREATE TABLE IF NOT EXISTS DeletedRepo (repo_id TEXT PRIMARY KEY);";
6586     sqlite_query_exec (db, sql);
6587 
6588     sql = "CREATE TABLE IF NOT EXISTS RepoBranch ("
6589         "repo_id TEXT PRIMARY KEY, branch_name TEXT);";
6590     sqlite_query_exec (db, sql);
6591 
6592     sql = "CREATE TABLE IF NOT EXISTS RepoLanToken ("
6593         "repo_id TEXT PRIMARY KEY, token TEXT);";
6594     sqlite_query_exec (db, sql);
6595 
6596     sql = "CREATE TABLE IF NOT EXISTS RepoTmpToken ("
6597         "repo_id TEXT, peer_id TEXT, token TEXT, timestamp INTEGER, "
6598         "PRIMARY KEY (repo_id, peer_id));";
6599     sqlite_query_exec (db, sql);
6600 
6601     sql = "CREATE TABLE IF NOT EXISTS RepoPasswd "
6602         "(repo_id TEXT PRIMARY KEY, passwd TEXT NOT NULL);";
6603     sqlite_query_exec (db, sql);
6604 
6605     sql = "CREATE TABLE IF NOT EXISTS RepoKeys "
6606         "(repo_id TEXT PRIMARY KEY, key TEXT NOT NULL, iv TEXT NOT NULL);";
6607     sqlite_query_exec (db, sql);
6608 
6609     sql = "CREATE TABLE IF NOT EXISTS RepoProperty ("
6610         "repo_id TEXT, key TEXT, value TEXT);";
6611     sqlite_query_exec (db, sql);
6612 
6613     sql = "CREATE INDEX IF NOT EXISTS RepoIndex ON RepoProperty (repo_id);";
6614     sqlite_query_exec (db, sql);
6615 
6616     sql = "CREATE TABLE IF NOT EXISTS MergeInfo ("
6617         "repo_id TEXT PRIMARY KEY, in_merge INTEGER, branch TEXT);";
6618     sqlite_query_exec (db, sql);
6619 
6620     sql = "CREATE TABLE IF NOT EXISTS CommonAncestor ("
6621         "repo_id TEXT PRIMARY KEY, ca_id TEXT, head_id TEXT);";
6622     sqlite_query_exec (db, sql);
6623 
6624     /* Version 1 repos will be added to this table after deletion.
6625      * GC will scan this table and remove the objects and blocks for the repos.
6626      */
6627     sql = "CREATE TABLE IF NOT EXISTS GarbageRepos (repo_id TEXT PRIMARY KEY);";
6628     sqlite_query_exec (db, sql);
6629 
6630     sql = "CREATE TABLE IF NOT EXISTS LockedFiles (repo_id TEXT, path TEXT, "
6631         "operation TEXT, old_mtime INTEGER, file_id TEXT, new_path TEXT, "
6632         "PRIMARY KEY (repo_id, path));";
6633     sqlite_query_exec (db, sql);
6634 
6635     sql = "CREATE TABLE IF NOT EXISTS FolderUserPerms ("
6636         "repo_id TEXT, path TEXT, permission TEXT);";
6637     sqlite_query_exec (db, sql);
6638 
6639     sql = "CREATE INDEX IF NOT EXISTS folder_user_perms_repo_id_idx "
6640         "ON FolderUserPerms (repo_id);";
6641     sqlite_query_exec (db, sql);
6642 
6643     sql = "CREATE TABLE IF NOT EXISTS FolderGroupPerms ("
6644         "repo_id TEXT, path TEXT, permission TEXT);";
6645     sqlite_query_exec (db, sql);
6646 
6647     sql = "CREATE INDEX IF NOT EXISTS folder_group_perms_repo_id_idx "
6648         "ON FolderGroupPerms (repo_id);";
6649     sqlite_query_exec (db, sql);
6650 
6651     sql = "CREATE TABLE IF NOT EXISTS FolderPermTimestamp ("
6652         "repo_id TEXT, timestamp INTEGER, PRIMARY KEY (repo_id));";
6653     sqlite_query_exec (db, sql);
6654 
6655     sql = "CREATE TABLE IF NOT EXISTS ServerProperty ("
6656         "server_url TEXT, key TEXT, value TEXT, PRIMARY KEY (server_url, key));";
6657     sqlite_query_exec (db, sql);
6658 
6659     sql = "CREATE INDEX IF NOT EXISTS ServerIndex ON ServerProperty (server_url);";
6660     sqlite_query_exec (db, sql);
6661 
6662     sql = "CREATE TABLE IF NOT EXISTS FileSyncError ("
6663         "id INTEGER PRIMARY KEY AUTOINCREMENT, repo_id TEXT, repo_name TEXT, "
6664         "path TEXT, err_id INTEGER, timestamp INTEGER);";
6665     sqlite_query_exec (db, sql);
6666 
6667     sql = "CREATE INDEX IF NOT EXISTS FileSyncErrorIndex ON FileSyncError (repo_id, path)";
6668     sqlite_query_exec (db, sql);
6669 
6670     return db;
6671 }
6672 
6673 static gboolean
load_repo_cb(sqlite3_stmt * stmt,void * vmanager)6674 load_repo_cb (sqlite3_stmt *stmt, void *vmanager)
6675 {
6676     SeafRepoManager *manager = vmanager;
6677     const char *repo_id;
6678 
6679     repo_id = (const char *) sqlite3_column_text (stmt, 0);
6680 
6681     load_repo (manager, repo_id);
6682 
6683     return TRUE;
6684 }
6685 
6686 static gboolean
remove_deleted_repo(sqlite3_stmt * stmt,void * vmanager)6687 remove_deleted_repo (sqlite3_stmt *stmt, void *vmanager)
6688 {
6689     SeafRepoManager *manager = vmanager;
6690     const char *repo_id;
6691 
6692     repo_id = (const char *) sqlite3_column_text (stmt, 0);
6693 
6694     seaf_repo_manager_remove_repo_ondisk (manager, repo_id, TRUE);
6695 
6696     return TRUE;
6697 }
6698 
6699 static void
load_repos(SeafRepoManager * manager,const char * seaf_dir)6700 load_repos (SeafRepoManager *manager, const char *seaf_dir)
6701 {
6702     sqlite3 *db = open_db(manager, seaf_dir);
6703     if (!db) return;
6704 
6705     char *sql;
6706 
6707     sql = "SELECT repo_id FROM DeletedRepo";
6708     if (sqlite_foreach_selected_row (db, sql, remove_deleted_repo, manager) < 0) {
6709         seaf_warning ("Error removing deleted repos.\n");
6710         return;
6711     }
6712 
6713     sql = "SELECT repo_id FROM Repo;";
6714     if (sqlite_foreach_selected_row (db, sql, load_repo_cb, manager) < 0) {
6715         seaf_warning ("Error read repo db.\n");
6716         return;
6717     }
6718 }
6719 
6720 static void
save_repo_property(SeafRepoManager * manager,const char * repo_id,const char * key,const char * value)6721 save_repo_property (SeafRepoManager *manager,
6722                     const char *repo_id,
6723                     const char *key, const char *value)
6724 {
6725     char *sql;
6726     sqlite3 *db = manager->priv->db;
6727 
6728     pthread_mutex_lock (&manager->priv->db_lock);
6729 
6730     sql = sqlite3_mprintf ("SELECT repo_id FROM RepoProperty WHERE repo_id=%Q AND key=%Q",
6731                            repo_id, key);
6732     if (sqlite_check_for_existence(db, sql)) {
6733         sqlite3_free (sql);
6734         sql = sqlite3_mprintf ("UPDATE RepoProperty SET value=%Q"
6735                                "WHERE repo_id=%Q and key=%Q",
6736                                value, repo_id, key);
6737         sqlite_query_exec (db, sql);
6738         sqlite3_free (sql);
6739     } else {
6740         sqlite3_free (sql);
6741         sql = sqlite3_mprintf ("INSERT INTO RepoProperty VALUES (%Q, %Q, %Q)",
6742                                repo_id, key, value);
6743         sqlite_query_exec (db, sql);
6744         sqlite3_free (sql);
6745     }
6746 
6747     pthread_mutex_unlock (&manager->priv->db_lock);
6748 }
6749 
6750 int
seaf_repo_manager_set_repo_property(SeafRepoManager * manager,const char * repo_id,const char * key,const char * value)6751 seaf_repo_manager_set_repo_property (SeafRepoManager *manager,
6752                                      const char *repo_id,
6753                                      const char *key,
6754                                      const char *value)
6755 {
6756     SeafRepo *repo;
6757 
6758     repo = seaf_repo_manager_get_repo (manager, repo_id);
6759     if (!repo)
6760         return -1;
6761 
6762     if (strcmp(key, REPO_AUTO_SYNC) == 0) {
6763         if (!seaf->started) {
6764             seaf_message ("System not started, skip setting auto sync value.\n");
6765             return 0;
6766         }
6767 
6768         if (g_strcmp0(value, "true") == 0) {
6769             repo->auto_sync = 1;
6770             if (repo->sync_interval == 0)
6771                 seaf_wt_monitor_watch_repo (seaf->wt_monitor, repo->id,
6772                                             repo->worktree);
6773             repo->last_sync_time = 0;
6774         } else {
6775             repo->auto_sync = 0;
6776             if (repo->sync_interval == 0)
6777                 seaf_wt_monitor_unwatch_repo (seaf->wt_monitor, repo->id);
6778             /* Cancel current sync task if any. */
6779             seaf_sync_manager_cancel_sync_task (seaf->sync_mgr, repo->id);
6780             seaf_sync_manager_remove_active_path_info (seaf->sync_mgr, repo->id);
6781         }
6782     }
6783 
6784     if (strcmp(key, REPO_PROP_SYNC_INTERVAL) == 0) {
6785         if (!seaf->started) {
6786             seaf_message ("System not started, skip setting auto sync value.\n");
6787             return 0;
6788         }
6789 
6790         int interval = atoi(value);
6791 
6792         if (interval > 0) {
6793             repo->sync_interval = interval;
6794             if (repo->auto_sync)
6795                 seaf_wt_monitor_unwatch_repo (seaf->wt_monitor, repo->id);
6796         } else {
6797             repo->sync_interval = 0;
6798             if (repo->auto_sync)
6799                 seaf_wt_monitor_watch_repo (seaf->wt_monitor, repo->id,
6800                                             repo->worktree);
6801         }
6802     }
6803 
6804     if (strcmp (key, REPO_PROP_SERVER_URL) == 0) {
6805         char *url = canonical_server_url (value);
6806 
6807         if (!repo->server_url) {
6808             /* Called from clone-mgr. */
6809             repo->server_url = url;
6810         } else {
6811             g_free (repo->server_url);
6812             repo->server_url = url;
6813 
6814             g_free (repo->effective_host);
6815             repo->effective_host = NULL;
6816         }
6817 
6818         save_repo_property (manager, repo_id, key, url);
6819         return 0;
6820     }
6821 
6822     if (strcmp (key, REPO_PROP_IS_READONLY) == 0) {
6823        if (g_strcmp0 (value, "true") == 0)
6824            repo->is_readonly = TRUE;
6825        else
6826            repo->is_readonly = FALSE;
6827     }
6828 
6829     save_repo_property (manager, repo_id, key, value);
6830     return 0;
6831 }
6832 
6833 char *
seaf_repo_manager_get_repo_property(SeafRepoManager * manager,const char * repo_id,const char * key)6834 seaf_repo_manager_get_repo_property (SeafRepoManager *manager,
6835                                      const char *repo_id,
6836                                      const char *key)
6837 {
6838     return load_repo_property (manager, repo_id, key);
6839 }
6840 
6841 static void
seaf_repo_manager_del_repo_property(SeafRepoManager * manager,const char * repo_id)6842 seaf_repo_manager_del_repo_property (SeafRepoManager *manager,
6843                                      const char *repo_id)
6844 {
6845     char *sql;
6846     sqlite3 *db = manager->priv->db;
6847 
6848     pthread_mutex_lock (&manager->priv->db_lock);
6849 
6850     sql = sqlite3_mprintf ("DELETE FROM RepoProperty WHERE repo_id = %Q", repo_id);
6851     sqlite_query_exec (db, sql);
6852     sqlite3_free (sql);
6853 
6854     pthread_mutex_unlock (&manager->priv->db_lock);
6855 }
6856 
6857 static void
seaf_repo_manager_del_repo_property_by_key(SeafRepoManager * manager,const char * repo_id,const char * key)6858 seaf_repo_manager_del_repo_property_by_key (SeafRepoManager *manager,
6859                                             const char *repo_id,
6860                                             const char *key)
6861 {
6862     char *sql;
6863     sqlite3 *db = manager->priv->db;
6864 
6865     pthread_mutex_lock (&manager->priv->db_lock);
6866 
6867     sql = sqlite3_mprintf ("DELETE FROM RepoProperty "
6868                            "WHERE repo_id = %Q "
6869                            "  AND key = %Q", repo_id, key);
6870     sqlite_query_exec (db, sql);
6871     sqlite3_free (sql);
6872 
6873     pthread_mutex_unlock (&manager->priv->db_lock);
6874 }
6875 
6876 static int
save_repo_enc_info(SeafRepoManager * manager,SeafRepo * repo)6877 save_repo_enc_info (SeafRepoManager *manager,
6878                     SeafRepo *repo)
6879 {
6880     sqlite3 *db = manager->priv->db;
6881     char sql[512];
6882     char key[65], iv[33];
6883 
6884     if (repo->enc_version == 1) {
6885         rawdata_to_hex (repo->enc_key, key, 16);
6886         rawdata_to_hex (repo->enc_iv, iv, 16);
6887     } else if (repo->enc_version >= 2) {
6888         rawdata_to_hex (repo->enc_key, key, 32);
6889         rawdata_to_hex (repo->enc_iv, iv, 16);
6890     }
6891 
6892     snprintf (sql, sizeof(sql), "REPLACE INTO RepoKeys VALUES ('%s', '%s', '%s')",
6893               repo->id, key, iv);
6894     if (sqlite_query_exec (db, sql) < 0)
6895         return -1;
6896 
6897     return 0;
6898 }
6899 
6900 int
seaf_repo_manager_set_repo_passwd(SeafRepoManager * manager,SeafRepo * repo,const char * passwd)6901 seaf_repo_manager_set_repo_passwd (SeafRepoManager *manager,
6902                                    SeafRepo *repo,
6903                                    const char *passwd)
6904 {
6905     int ret;
6906 
6907     if (seafile_decrypt_repo_enc_key (repo->enc_version, passwd, repo->random_key,
6908                                       repo->salt,
6909                                       repo->enc_key, repo->enc_iv) < 0)
6910         return -1;
6911 
6912     pthread_mutex_lock (&manager->priv->db_lock);
6913 
6914     ret = save_repo_enc_info (manager, repo);
6915 
6916     pthread_mutex_unlock (&manager->priv->db_lock);
6917 
6918     return ret;
6919 }
6920 
6921 GList*
seaf_repo_manager_get_repo_list(SeafRepoManager * manager,int start,int limit)6922 seaf_repo_manager_get_repo_list (SeafRepoManager *manager, int start, int limit)
6923 {
6924     GList *repo_list = NULL;
6925     GHashTableIter iter;
6926     SeafRepo *repo;
6927     gpointer key, value;
6928 
6929     if (pthread_rwlock_rdlock (&manager->priv->lock) < 0) {
6930         seaf_warning ("[repo mgr] failed to lock repo cache.\n");
6931         return NULL;
6932     }
6933     g_hash_table_iter_init (&iter, manager->priv->repo_hash);
6934 
6935     while (g_hash_table_iter_next (&iter, &key, &value)) {
6936         repo = value;
6937         if (!repo->delete_pending)
6938             repo_list = g_list_prepend (repo_list, repo);
6939     }
6940 
6941     pthread_rwlock_unlock (&manager->priv->lock);
6942 
6943     return repo_list;
6944 }
6945 
6946 GList *
seaf_repo_manager_get_repo_id_list_by_server(SeafRepoManager * manager,const char * server_url)6947 seaf_repo_manager_get_repo_id_list_by_server (SeafRepoManager *manager, const char *server_url)
6948 {
6949     GList *repo_id_list = NULL;
6950     GHashTableIter iter;
6951     SeafRepo *repo;
6952     gpointer key, value;
6953 
6954     if (pthread_rwlock_rdlock (&manager->priv->lock) < 0) {
6955         seaf_warning ("[repo mgr] failed to lock repo cache.\n");
6956         return NULL;
6957     }
6958     g_hash_table_iter_init (&iter, manager->priv->repo_hash);
6959 
6960     while (g_hash_table_iter_next (&iter, &key, &value)) {
6961         repo = value;
6962         if (!repo->delete_pending && g_strcmp0 (repo->server_url, server_url) == 0)
6963             repo_id_list = g_list_prepend (repo_id_list, g_strdup(repo->id));
6964     }
6965 
6966     pthread_rwlock_unlock (&manager->priv->lock);
6967 
6968     return repo_id_list;
6969 }
6970 
6971 int
seaf_repo_manager_set_repo_email(SeafRepoManager * mgr,SeafRepo * repo,const char * email)6972 seaf_repo_manager_set_repo_email (SeafRepoManager *mgr,
6973                                   SeafRepo *repo,
6974                                   const char *email)
6975 {
6976     g_free (repo->email);
6977     repo->email = g_strdup(email);
6978 
6979     save_repo_property (mgr, repo->id, REPO_PROP_EMAIL, email);
6980     return 0;
6981 }
6982 
6983 int
seaf_repo_manager_set_repo_token(SeafRepoManager * manager,SeafRepo * repo,const char * token)6984 seaf_repo_manager_set_repo_token (SeafRepoManager *manager,
6985                                   SeafRepo *repo,
6986                                   const char *token)
6987 {
6988     g_free (repo->token);
6989     repo->token = g_strdup(token);
6990 
6991     save_repo_property (manager, repo->id, REPO_PROP_TOKEN, token);
6992     return 0;
6993 }
6994 
6995 
6996 int
seaf_repo_manager_remove_repo_token(SeafRepoManager * manager,SeafRepo * repo)6997 seaf_repo_manager_remove_repo_token (SeafRepoManager *manager,
6998                                      SeafRepo *repo)
6999 {
7000     g_free (repo->token);
7001     repo->token = NULL;
7002     seaf_repo_manager_del_repo_property_by_key(manager, repo->id, REPO_PROP_TOKEN);
7003     return 0;
7004 }
7005 
7006 int
seaf_repo_manager_set_repo_relay_info(SeafRepoManager * mgr,const char * repo_id,const char * relay_addr,const char * relay_port)7007 seaf_repo_manager_set_repo_relay_info (SeafRepoManager *mgr,
7008                                        const char *repo_id,
7009                                        const char *relay_addr,
7010                                        const char *relay_port)
7011 {
7012     save_repo_property (mgr, repo_id, REPO_PROP_RELAY_ADDR, relay_addr);
7013     save_repo_property (mgr, repo_id, REPO_PROP_RELAY_PORT, relay_port);
7014     return 0;
7015 }
7016 
7017 void
seaf_repo_manager_get_repo_relay_info(SeafRepoManager * mgr,const char * repo_id,char ** relay_addr,char ** relay_port)7018 seaf_repo_manager_get_repo_relay_info (SeafRepoManager *mgr,
7019                                        const char *repo_id,
7020                                        char **relay_addr,
7021                                        char **relay_port)
7022 {
7023     char *addr, *port;
7024 
7025     addr = load_repo_property (mgr, repo_id, REPO_PROP_RELAY_ADDR);
7026     port = load_repo_property (mgr, repo_id, REPO_PROP_RELAY_PORT);
7027 
7028     if (relay_addr && addr)
7029         *relay_addr = addr;
7030     if (relay_port && port)
7031         *relay_port = port;
7032 }
7033 
7034 static void
update_server_properties(SeafRepoManager * mgr,const char * repo_id,const char * new_server_url)7035 update_server_properties (SeafRepoManager *mgr,
7036                           const char *repo_id,
7037                           const char *new_server_url)
7038 {
7039     char *old_server_url = NULL;
7040     char *sql = NULL;
7041 
7042     old_server_url = seaf_repo_manager_get_repo_property (mgr, repo_id,
7043                                                           REPO_PROP_SERVER_URL);
7044     if (!old_server_url)
7045         return;
7046 
7047     pthread_mutex_lock (&mgr->priv->db_lock);
7048 
7049     sql = sqlite3_mprintf ("UPDATE ServerProperty SET server_url=%Q WHERE "
7050                            "server_url=%Q;", new_server_url, old_server_url);
7051     sqlite_query_exec (mgr->priv->db, sql);
7052 
7053     pthread_mutex_unlock (&mgr->priv->db_lock);
7054 
7055     sqlite3_free (sql);
7056     g_free (old_server_url);
7057 }
7058 
7059 int
seaf_repo_manager_update_repos_server_host(SeafRepoManager * mgr,const char * old_server_url,const char * new_server_url)7060 seaf_repo_manager_update_repos_server_host (SeafRepoManager *mgr,
7061                                             const char *old_server_url,
7062                                             const char *new_server_url)
7063 {
7064     GList *ptr, *repos = seaf_repo_manager_get_repo_list (seaf->repo_mgr, 0, -1);
7065     SeafRepo *r;
7066     char *canon_old_server_url = canonical_server_url(old_server_url);
7067     char *canon_new_server_url = canonical_server_url(new_server_url);
7068 
7069     for (ptr = repos; ptr; ptr = ptr->next) {
7070         r = ptr->data;
7071 
7072         char *server_url = seaf_repo_manager_get_repo_property (seaf->repo_mgr,
7073                                                                 r->id,
7074                                                                 REPO_PROP_SERVER_URL);
7075 
7076         if (g_strcmp0(server_url, canon_old_server_url) == 0) {
7077             /* Update server property before server_url is changed. */
7078             update_server_properties (mgr, r->id, canon_new_server_url);
7079 
7080             seaf_repo_manager_set_repo_property (
7081                 seaf->repo_mgr, r->id, REPO_PROP_SERVER_URL, canon_new_server_url);
7082         }
7083         g_free (server_url);
7084 
7085     }
7086 
7087     g_list_free (repos);
7088     g_free (canon_old_server_url);
7089     g_free (canon_new_server_url);
7090 
7091     return 0;
7092 }
7093 
7094 char *
seaf_repo_manager_get_server_property(SeafRepoManager * mgr,const char * server_url,const char * key)7095 seaf_repo_manager_get_server_property (SeafRepoManager *mgr,
7096                                        const char *server_url,
7097                                        const char *key)
7098 {
7099     char *sql = sqlite3_mprintf ("SELECT value FROM ServerProperty WHERE "
7100                                  "server_url=%Q AND key=%Q;",
7101                                  server_url, key);
7102     char *value;
7103 
7104     pthread_mutex_lock (&mgr->priv->db_lock);
7105 
7106     value = sqlite_get_string (mgr->priv->db, sql);
7107 
7108     pthread_mutex_unlock (&mgr->priv->db_lock);
7109 
7110     sqlite3_free (sql);
7111     return value;
7112 }
7113 
7114 int
seaf_repo_manager_set_server_property(SeafRepoManager * mgr,const char * server_url,const char * key,const char * value)7115 seaf_repo_manager_set_server_property (SeafRepoManager *mgr,
7116                                        const char *server_url,
7117                                        const char *key,
7118                                        const char *value)
7119 {
7120     char *sql;
7121     int ret;
7122     char *canon_server_url = canonical_server_url(server_url);
7123 
7124     pthread_mutex_lock (&mgr->priv->db_lock);
7125 
7126     sql = sqlite3_mprintf ("REPLACE INTO ServerProperty VALUES (%Q, %Q, %Q);",
7127                            canon_server_url, key, value);
7128     ret = sqlite_query_exec (mgr->priv->db, sql);
7129 
7130     pthread_mutex_unlock (&mgr->priv->db_lock);
7131 
7132     sqlite3_free (sql);
7133     g_free (canon_server_url);
7134     return ret;
7135 }
7136 
7137 gboolean
seaf_repo_manager_server_is_pro(SeafRepoManager * mgr,const char * server_url)7138 seaf_repo_manager_server_is_pro (SeafRepoManager *mgr,
7139                                  const char *server_url)
7140 {
7141     gboolean ret = FALSE;
7142 
7143     char *is_pro = seaf_repo_manager_get_server_property (seaf->repo_mgr,
7144                                                           server_url,
7145                                                           SERVER_PROP_IS_PRO);
7146     if (is_pro != NULL && strcasecmp (is_pro, "true") == 0)
7147         ret = TRUE;
7148 
7149     g_free (is_pro);
7150     return ret;
7151 }
7152 
7153 /*
7154  * Read ignored files from ignore.txt
7155  */
seaf_repo_load_ignore_files(const char * worktree)7156 GList *seaf_repo_load_ignore_files (const char *worktree)
7157 {
7158     GList *list = NULL;
7159     SeafStat st;
7160     FILE *fp;
7161     char *full_path, *pattern;
7162     char path[PATH_MAX];
7163 
7164     full_path = g_build_path (PATH_SEPERATOR, worktree,
7165                               IGNORE_FILE, NULL);
7166     if (seaf_stat (full_path, &st) < 0)
7167         goto error;
7168     if (!S_ISREG(st.st_mode))
7169         goto error;
7170     fp = g_fopen(full_path, "r");
7171     if (fp == NULL)
7172         goto error;
7173 
7174     while (fgets(path, PATH_MAX, fp) != NULL) {
7175         /* remove leading and trailing whitespace, including \n \r. */
7176         g_strstrip (path);
7177 
7178         /* ignore comment and blank line */
7179         if (path[0] == '#' || path[0] == '\0')
7180             continue;
7181 
7182         /* Change 'foo/' to 'foo/ *'. */
7183         if (path[strlen(path)-1] == '/')
7184             pattern = g_strdup_printf("%s/%s*", worktree, path);
7185         else
7186             pattern = g_strdup_printf("%s/%s", worktree, path);
7187 
7188         list = g_list_prepend(list, pattern);
7189     }
7190 
7191     fclose(fp);
7192     g_free (full_path);
7193     return list;
7194 
7195 error:
7196     g_free (full_path);
7197     return NULL;
7198 }
7199 
7200 gboolean
seaf_repo_check_ignore_file(GList * ignore_list,const char * fullpath)7201 seaf_repo_check_ignore_file (GList *ignore_list, const char *fullpath)
7202 {
7203     char *str;
7204     SeafStat st;
7205     GPatternSpec *ignore_spec;
7206     GList *p;
7207 
7208     str = g_strdup(fullpath);
7209 
7210     int rc = seaf_stat(str, &st);
7211     if (rc == 0 && S_ISDIR(st.st_mode)) {
7212         g_free (str);
7213         str = g_strconcat (fullpath, "/", NULL);
7214     }
7215 
7216     for (p = ignore_list; p != NULL; p = p->next) {
7217         char *pattern = (char *)p->data;
7218 
7219         ignore_spec = g_pattern_spec_new(pattern);
7220         if (g_pattern_match_string(ignore_spec, str)) {
7221             g_free (str);
7222             g_pattern_spec_free(ignore_spec);
7223             return TRUE;
7224         }
7225         g_pattern_spec_free(ignore_spec);
7226     }
7227 
7228     g_free (str);
7229     return FALSE;
7230 }
7231 
7232 /*
7233  * Free ignored file list
7234  */
seaf_repo_free_ignore_files(GList * ignore_list)7235 void seaf_repo_free_ignore_files (GList *ignore_list)
7236 {
7237     GList *p;
7238 
7239     if (ignore_list == NULL)
7240         return;
7241 
7242     for (p = ignore_list; p != NULL; p = p->next)
7243         free(p->data);
7244 
7245     g_list_free (ignore_list);
7246 }
7247