1 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 
3 #include "common.h"
4 
5 #define DEBUG_FLAG SEAFILE_DEBUG_SYNC
6 #include "log.h"
7 
8 #include "seafile-error-impl.h"
9 #include "seafile-session.h"
10 #include "vc-utils.h"
11 #include "utils.h"
12 #include "seafile-config.h"
13 
14 #include "timer.h"
15 
16 #define CLONE_DB "clone.db"
17 
18 #define CHECK_CONNECT_INTERVAL 5
19 
20 static void
21 on_repo_http_fetched (SeafileSession *seaf,
22                       HttpTxTask *tx_task,
23                       SeafCloneManager *mgr);
24 
25 static void
26 transition_state (CloneTask *task, int new_state);
27 
28 static void
29 transition_to_error (CloneTask *task, int error);
30 
31 static int
32 add_transfer_task (CloneTask *task, GError **error);
33 
34 static const char *state_str[] = {
35     "init",
36     "check server",
37     "fetch",
38     "done",
39     "error",
40     "canceling",
41     "canceled",
42     /* States only used by old protocol. */
43     "connect",
44     "connect",                  /* Use "connect" for CHECK_PROTOCOL */
45     "index",
46     "checkout",
47     "merge",
48 };
49 
50 static void
mark_clone_done_v2(SeafRepo * repo,CloneTask * task)51 mark_clone_done_v2 (SeafRepo *repo, CloneTask *task)
52 {
53     SeafBranch *local = NULL;
54 
55     seaf_repo_manager_set_repo_worktree (repo->manager,
56                                          repo,
57                                          task->worktree);
58 
59     local = seaf_branch_manager_get_branch (seaf->branch_mgr, repo->id, "local");
60     if (!local) {
61         seaf_warning ("Cannot get branch local for repo %s(%.10s).\n",
62                       repo->name, repo->id);
63         transition_to_error (task, SYNC_ERROR_ID_LOCAL_DATA_CORRUPT);
64         return;
65     }
66     /* Set repo head to mark checkout done. */
67     seaf_repo_set_head (repo, local);
68     seaf_branch_unref (local);
69 
70     if (repo->encrypted) {
71         if (seaf_repo_manager_set_repo_passwd (seaf->repo_mgr,
72                                                repo,
73                                                task->passwd) < 0) {
74             seaf_warning ("[Clone mgr] failed to set passwd for %s.\n", repo->id);
75             transition_to_error (task, SYNC_ERROR_ID_GENERAL_ERROR);
76             return;
77         }
78     }
79 
80     if (task->is_readonly) {
81         seaf_repo_set_readonly (repo);
82     }
83 
84     if (task->sync_wt_name) {
85         seaf_repo_manager_set_repo_property (seaf->repo_mgr,
86                                              repo->id,
87                                              REPO_SYNC_WORKTREE_NAME,
88                                              "true");
89     }
90 
91     if (task->server_url)
92         repo->server_url = g_strdup(task->server_url);
93 
94     if (repo->auto_sync && (repo->sync_interval == 0)) {
95         if (seaf_wt_monitor_watch_repo (seaf->wt_monitor,
96                                         repo->id, repo->worktree) < 0) {
97             seaf_warning ("failed to watch repo %s(%.10s).\n", repo->name, repo->id);
98             transition_to_error (task, SYNC_ERROR_ID_GENERAL_ERROR);
99             return;
100         }
101     }
102 
103     /* For compatibility, still set these two properties.
104      * So that if we downgrade to an old version, the syncing can still work.
105      */
106     seaf_repo_manager_set_repo_property (seaf->repo_mgr,
107                                          repo->id,
108                                          REPO_REMOTE_HEAD,
109                                          repo->head->commit_id);
110     seaf_repo_manager_set_repo_property (seaf->repo_mgr,
111                                          repo->id,
112                                          REPO_LOCAL_HEAD,
113                                          repo->head->commit_id);
114 
115     transition_state (task, CLONE_STATE_DONE);
116 }
117 
118 static void
start_clone_v2(CloneTask * task)119 start_clone_v2 (CloneTask *task)
120 {
121     GError *error = NULL;
122 
123     if (g_access (task->worktree, F_OK) != 0 &&
124         g_mkdir_with_parents (task->worktree, 0777) < 0) {
125         seaf_warning ("[clone mgr] Failed to create worktree %s.\n",
126                       task->worktree);
127         transition_to_error (task, SYNC_ERROR_ID_WRITE_LOCAL_DATA);
128         return;
129     }
130 
131     SeafRepo *repo = seaf_repo_manager_get_repo (seaf->repo_mgr, task->repo_id);
132     if (repo != NULL) {
133         seaf_repo_manager_set_repo_token (seaf->repo_mgr, repo, task->token);
134         seaf_repo_manager_set_repo_email (seaf->repo_mgr, repo, task->email);
135         seaf_repo_manager_set_repo_relay_info (seaf->repo_mgr, repo->id,
136                                                task->peer_addr, task->peer_port);
137         if (task->server_url) {
138             seaf_repo_manager_set_repo_property (seaf->repo_mgr,
139                                                  repo->id,
140                                                  REPO_PROP_SERVER_URL,
141                                                  task->server_url);
142         }
143 
144         mark_clone_done_v2 (repo, task);
145         return;
146     }
147 
148     if (add_transfer_task (task, &error) == 0)
149         transition_state (task, CLONE_STATE_FETCH);
150     else
151         transition_to_error (task, SYNC_ERROR_ID_NOT_ENOUGH_MEMORY);
152 }
153 
154 static void
check_head_commit_done(HttpHeadCommit * result,void * user_data)155 check_head_commit_done (HttpHeadCommit *result, void *user_data)
156 {
157     CloneTask *task = user_data;
158 
159     if (task->state == CLONE_STATE_CANCEL_PENDING) {
160         transition_state (task, CLONE_STATE_CANCELED);
161         return;
162     }
163 
164     if (result->check_success && !result->is_corrupt && !result->is_deleted) {
165         memcpy (task->server_head_id, result->head_commit, 40);
166         start_clone_v2 (task);
167     } else {
168         transition_to_error (task, result->error_code);
169     }
170 }
171 
172 static void
http_check_head_commit(CloneTask * task)173 http_check_head_commit (CloneTask *task)
174 {
175     int ret = http_tx_manager_check_head_commit (seaf->http_tx_mgr,
176                                                  task->repo_id,
177                                                  task->repo_version,
178                                                  task->effective_url,
179                                                  task->token,
180                                                  task->use_fileserver_port,
181                                                  check_head_commit_done,
182                                                  task);
183     if (ret < 0)
184         transition_to_error (task, SYNC_ERROR_ID_NOT_ENOUGH_MEMORY);
185 }
186 
187 static char *
http_fileserver_url(const char * url)188 http_fileserver_url (const char *url)
189 {
190     const char *host;
191     char *colon;
192     char *url_no_port;
193     char *ret = NULL;
194 
195     /* Just return the url itself if it's invalid. */
196     if (strlen(url) <= strlen("http://"))
197         return g_strdup(url);
198 
199     /* Skip protocol schem. */
200     host = url + strlen("http://");
201 
202     colon = strrchr (host, ':');
203     if (colon) {
204         url_no_port = g_strndup(url, colon - url);
205         ret = g_strconcat(url_no_port, ":8082", NULL);
206         g_free (url_no_port);
207     } else {
208         ret = g_strconcat(url, ":8082", NULL);
209     }
210 
211     return ret;
212 }
213 
214 static void
check_http_fileserver_protocol_done(HttpProtocolVersion * result,void * user_data)215 check_http_fileserver_protocol_done (HttpProtocolVersion *result, void *user_data)
216 {
217     CloneTask *task = user_data;
218 
219     if (task->state == CLONE_STATE_CANCEL_PENDING) {
220         transition_state (task, CLONE_STATE_CANCELED);
221         return;
222     }
223 
224     if (result->check_success && !result->not_supported) {
225         task->http_protocol_version = result->version;
226         task->effective_url = http_fileserver_url (task->server_url);
227         task->use_fileserver_port = TRUE;
228         http_check_head_commit (task);
229     } else {
230         /* Wait for periodic retry. */
231         transition_to_error (task, result->error_code);
232     }
233 }
234 
235 static void
check_http_protocol_done(HttpProtocolVersion * result,void * user_data)236 check_http_protocol_done (HttpProtocolVersion *result, void *user_data)
237 {
238     CloneTask *task = user_data;
239 
240     if (task->state == CLONE_STATE_CANCEL_PENDING) {
241         transition_state (task, CLONE_STATE_CANCELED);
242         return;
243     }
244 
245     if (result->check_success && !result->not_supported) {
246         task->http_protocol_version = result->version;
247         task->effective_url = g_strdup(task->server_url);
248         http_check_head_commit (task);
249     } else if (strncmp(task->server_url, "https", 5) != 0) {
250         char *host_fileserver = http_fileserver_url(task->server_url);
251         if (http_tx_manager_check_protocol_version (seaf->http_tx_mgr,
252                                                     host_fileserver,
253                                                     TRUE,
254                                                     check_http_fileserver_protocol_done,
255                                                     task) < 0)
256             transition_to_error (task, SYNC_ERROR_ID_NOT_ENOUGH_MEMORY);
257         g_free (host_fileserver);
258     } else {
259         /* Wait for periodic retry. */
260         transition_to_error (task, result->error_code);
261     }
262 }
263 
264 static void
check_http_protocol(CloneTask * task)265 check_http_protocol (CloneTask *task)
266 {
267     if (http_tx_manager_check_protocol_version (seaf->http_tx_mgr,
268                                                 task->server_url,
269                                                 FALSE,
270                                                 check_http_protocol_done,
271                                                 task) < 0) {
272         transition_to_error (task, SYNC_ERROR_ID_NOT_ENOUGH_MEMORY);
273         return;
274     }
275 
276     transition_state (task, CLONE_STATE_CHECK_SERVER);
277 }
278 
279 static CloneTask *
clone_task_new(const char * repo_id,const char * repo_name,const char * token,const char * worktree,const char * passwd,const char * email)280 clone_task_new (const char *repo_id,
281                 const char *repo_name,
282                 const char *token,
283                 const char *worktree,
284                 const char *passwd,
285                 const char *email)
286 {
287     CloneTask *task = g_new0 (CloneTask, 1);
288 
289     memcpy (task->repo_id, repo_id, 37);
290     task->token = g_strdup (token);
291     task->worktree = g_strdup(worktree);
292     task->email = g_strdup(email);
293     if (repo_name)
294         task->repo_name = g_strdup(repo_name);
295     if (passwd)
296         task->passwd = g_strdup (passwd);
297     task->error = SYNC_ERROR_ID_NO_ERROR;
298 
299     return task;
300 }
301 
302 static void
clone_task_free(CloneTask * task)303 clone_task_free (CloneTask *task)
304 {
305     g_free (task->tx_id);
306     g_free (task->worktree);
307     g_free (task->passwd);
308     g_free (task->token);
309     g_free (task->repo_name);
310     g_free (task->peer_addr);
311     g_free (task->peer_port);
312     g_free (task->email);
313     g_free (task->random_key);
314     g_free (task->server_url);
315     g_free (task->effective_url);
316 
317     g_free (task);
318 }
319 
320 const char *
clone_task_state_to_str(int state)321 clone_task_state_to_str (int state)
322 {
323     if (state < 0 || state >= N_CLONE_STATES)
324         return NULL;
325     return state_str[state];
326 }
327 
328 SeafCloneManager *
seaf_clone_manager_new(SeafileSession * session)329 seaf_clone_manager_new (SeafileSession *session)
330 {
331     SeafCloneManager *mgr = g_new0 (SeafCloneManager, 1);
332 
333     char *db_path = g_build_path ("/", session->seaf_dir, CLONE_DB, NULL);
334     if (sqlite_open_db (db_path, &mgr->db) < 0) {
335         g_critical ("[Clone mgr] Failed to open db\n");
336         g_free (db_path);
337         g_free (mgr);
338         return NULL;
339     }
340 
341     mgr->seaf = session;
342     mgr->tasks = g_hash_table_new_full (g_str_hash, g_str_equal,
343                                         g_free, (GDestroyNotify)clone_task_free);
344     return mgr;
345 }
346 
347 static gboolean
load_enc_info_cb(sqlite3_stmt * stmt,void * data)348 load_enc_info_cb (sqlite3_stmt *stmt, void *data)
349 {
350     CloneTask *task = data;
351     int enc_version;
352     const char *random_key;
353 
354     enc_version = sqlite3_column_int (stmt, 0);
355     random_key = (const char *)sqlite3_column_text (stmt, 1);
356 
357     task->enc_version = enc_version;
358     task->random_key = g_strdup (random_key);
359 
360     return FALSE;
361 }
362 
363 static int
load_clone_enc_info(CloneTask * task)364 load_clone_enc_info (CloneTask *task)
365 {
366     char sql[256];
367 
368     snprintf (sql, sizeof(sql),
369               "SELECT enc_version, random_key FROM CloneEncInfo WHERE repo_id='%s'",
370               task->repo_id);
371 
372     if (sqlite_foreach_selected_row (task->manager->db, sql,
373                                      load_enc_info_cb, task) < 0)
374         return -1;
375 
376     return 0;
377 }
378 
379 static gboolean
load_version_info_cb(sqlite3_stmt * stmt,void * data)380 load_version_info_cb (sqlite3_stmt *stmt, void *data)
381 {
382     CloneTask *task = data;
383     int repo_version;
384 
385     repo_version = sqlite3_column_int (stmt, 0);
386 
387     task->repo_version = repo_version;
388 
389     return FALSE;
390 }
391 
392 static void
load_clone_repo_version_info(CloneTask * task)393 load_clone_repo_version_info (CloneTask *task)
394 {
395     char sql[256];
396 
397     snprintf (sql, sizeof(sql),
398               "SELECT repo_version FROM CloneVersionInfo WHERE repo_id='%s'",
399               task->repo_id);
400 
401     sqlite_foreach_selected_row (task->manager->db, sql,
402                                  load_version_info_cb, task);
403 }
404 
405 static gboolean
load_more_info_cb(sqlite3_stmt * stmt,void * data)406 load_more_info_cb (sqlite3_stmt *stmt, void *data)
407 {
408     CloneTask *task = data;
409     json_error_t jerror;
410     json_t *object = NULL;
411     const char *more_info;
412 
413     more_info = (const char *)sqlite3_column_text (stmt, 0);
414     object = json_loads (more_info, 0, &jerror);
415     if (!object) {
416         seaf_warning ("Failed to load more sync info from json: %s.\n", jerror.text);
417         return FALSE;
418     }
419 
420     json_t *integer = json_object_get (object, "is_readonly");
421     task->is_readonly = json_integer_value (integer);
422     json_t *string = json_object_get (object, "server_url");
423     if (string)
424         task->server_url = g_strdup (json_string_value (string));
425     json_t *repo_salt = json_object_get (object, "repo_salt");
426     if (repo_salt)
427         task->repo_salt = g_strdup (json_string_value (repo_salt));
428     json_decref (object);
429 
430     return FALSE;
431 }
432 
433 static void
load_clone_more_info(CloneTask * task)434 load_clone_more_info (CloneTask *task)
435 {
436     char sql[256];
437 
438     snprintf (sql, sizeof(sql),
439               "SELECT more_info FROM CloneTasksMoreInfo WHERE repo_id='%s'",
440               task->repo_id);
441 
442     sqlite_foreach_selected_row (task->manager->db, sql,
443                                  load_more_info_cb, task);
444 }
445 
446 static gboolean
restart_task(sqlite3_stmt * stmt,void * data)447 restart_task (sqlite3_stmt *stmt, void *data)
448 {
449     SeafCloneManager *mgr = data;
450     const char *repo_id, *repo_name, *token, *worktree, *passwd;
451     const char *email;
452     CloneTask *task;
453     SeafRepo *repo;
454 
455     repo_id = (const char *)sqlite3_column_text (stmt, 0);
456     repo_name = (const char *)sqlite3_column_text (stmt, 1);
457     token = (const char *)sqlite3_column_text (stmt, 2);
458     worktree = (const char *)sqlite3_column_text (stmt, 4);
459     passwd = (const char *)sqlite3_column_text (stmt, 5);
460     email = (const char *)sqlite3_column_text (stmt, 8);
461 
462     task = clone_task_new (repo_id, repo_name, token,
463                            worktree, passwd, email);
464     task->manager = mgr;
465     /* Default to 1. */
466     task->enc_version = 1;
467 
468     if (passwd && load_clone_enc_info (task) < 0) {
469         clone_task_free (task);
470         return TRUE;
471     }
472 
473     task->repo_version = 0;
474     load_clone_repo_version_info (task);
475 
476     load_clone_more_info (task);
477 
478     repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id);
479 
480     if (repo != NULL && repo->head != NULL) {
481         transition_state (task, CLONE_STATE_DONE);
482         return TRUE;
483     }
484 
485     if (task->repo_version > 0) {
486         if (task->server_url) {
487             check_http_protocol (task);
488         } else {
489             transition_to_error (task, SYNC_ERROR_ID_GENERAL_ERROR);
490             return TRUE;
491         }
492     }
493 
494     g_hash_table_insert (mgr->tasks, g_strdup(task->repo_id), task);
495 
496     return TRUE;
497 }
498 
499 int
seaf_clone_manager_init(SeafCloneManager * mgr)500 seaf_clone_manager_init (SeafCloneManager *mgr)
501 {
502     const char *sql;
503 
504     sql = "CREATE TABLE IF NOT EXISTS CloneTasks "
505         "(repo_id TEXT PRIMARY KEY, repo_name TEXT, "
506         "token TEXT, dest_id TEXT,"
507         "worktree_parent TEXT, passwd TEXT, "
508         "server_addr TEXT, server_port TEXT, email TEXT);";
509     if (sqlite_query_exec (mgr->db, sql) < 0)
510         return -1;
511 
512     sql = "CREATE TABLE IF NOT EXISTS CloneTasksMoreInfo "
513         "(repo_id TEXT PRIMARY KEY, more_info TEXT);";
514     if (sqlite_query_exec (mgr->db, sql) < 0)
515         return -1;
516 
517     sql = "CREATE TABLE IF NOT EXISTS CloneEncInfo "
518         "(repo_id TEXT PRIMARY KEY, enc_version INTEGER, random_key TEXT);";
519     if (sqlite_query_exec (mgr->db, sql) < 0)
520         return -1;
521 
522     sql = "CREATE TABLE IF NOT EXISTS CloneVersionInfo "
523         "(repo_id TEXT PRIMARY KEY, repo_version INTEGER);";
524     if (sqlite_query_exec (mgr->db, sql) < 0)
525         return -1;
526 
527     sql = "CREATE TABLE IF NOT EXISTS CloneServerURL "
528         "(repo_id TEXT PRIMARY KEY, server_url TEXT);";
529     if (sqlite_query_exec (mgr->db, sql) < 0)
530         return -1;
531 
532     return 0;
533 }
534 
check_connect_pulse(void * vmanager)535 static int check_connect_pulse (void *vmanager)
536 {
537     SeafCloneManager *mgr = vmanager;
538     CloneTask *task;
539     GHashTableIter iter;
540     gpointer key, value;
541 
542     g_hash_table_iter_init (&iter, mgr->tasks);
543     while (g_hash_table_iter_next (&iter, &key, &value)) {
544         task = value;
545         if (task->state == CLONE_STATE_ERROR &&
546             task->repo_version > 0 &&
547             sync_error_level (task->error) == SYNC_ERROR_LEVEL_NETWORK) {
548             task->error = SYNC_ERROR_ID_NO_ERROR;
549             check_http_protocol (task);
550         }
551     }
552 
553     return TRUE;
554 }
555 
556 int
seaf_clone_manager_start(SeafCloneManager * mgr)557 seaf_clone_manager_start (SeafCloneManager *mgr)
558 {
559     mgr->check_timer = seaf_timer_new (check_connect_pulse, mgr,
560                                        CHECK_CONNECT_INTERVAL * 1000);
561 
562     char *sql = "SELECT * FROM CloneTasks";
563     if (sqlite_foreach_selected_row (mgr->db, sql, restart_task, mgr) < 0)
564         return -1;
565 
566     g_signal_connect (seaf, "repo-http-fetched",
567                       (GCallback)on_repo_http_fetched, mgr);
568 
569     return 0;
570 }
571 
572 static int
save_task_to_db(SeafCloneManager * mgr,CloneTask * task)573 save_task_to_db (SeafCloneManager *mgr, CloneTask *task)
574 {
575     char *sql;
576 
577     if (task->passwd)
578         sql = sqlite3_mprintf ("REPLACE INTO CloneTasks VALUES "
579             "('%q', '%q', '%q', NULL, '%q', '%q', NULL, NULL, '%q')",
580                                 task->repo_id, task->repo_name,
581                                 task->token,
582                                 task->worktree, task->passwd,
583                                 task->email);
584     else
585         sql = sqlite3_mprintf ("REPLACE INTO CloneTasks VALUES "
586             "('%q', '%q', '%q', NULL, '%q', NULL, NULL, NULL, '%q')",
587                                 task->repo_id, task->repo_name,
588                                 task->token,
589                                 task->worktree, task->email);
590 
591     if (sqlite_query_exec (mgr->db, sql) < 0) {
592         sqlite3_free (sql);
593         return -1;
594     }
595     sqlite3_free (sql);
596 
597     if (task->passwd && task->enc_version >= 2 && task->random_key) {
598         sql = sqlite3_mprintf ("REPLACE INTO CloneEncInfo VALUES "
599                                "('%q', %d, '%q')",
600                                task->repo_id, task->enc_version, task->random_key);
601         if (sqlite_query_exec (mgr->db, sql) < 0) {
602             sqlite3_free (sql);
603             return -1;
604         }
605         sqlite3_free (sql);
606     }
607 
608     sql = sqlite3_mprintf ("REPLACE INTO CloneVersionInfo VALUES "
609                            "('%q', %d)",
610                            task->repo_id, task->repo_version);
611     if (sqlite_query_exec (mgr->db, sql) < 0) {
612         sqlite3_free (sql);
613         return -1;
614     }
615     sqlite3_free (sql);
616 
617     if (task->is_readonly || task->server_url || task->repo_salt) {
618         /* need to store more info */
619         json_t *object = NULL;
620         gchar *info = NULL;
621 
622         object = json_object ();
623         json_object_set_new (object, "is_readonly", json_integer (task->is_readonly));
624         if (task->server_url)
625             json_object_set_new (object, "server_url", json_string(task->server_url));
626 
627         info = json_dumps (object, 0);
628         json_decref (object);
629         sql = sqlite3_mprintf ("REPLACE INTO CloneTasksMoreInfo VALUES "
630                            "('%q', '%q')", task->repo_id, info);
631         if (sqlite_query_exec (mgr->db, sql) < 0) {
632             sqlite3_free (sql);
633             g_free (info);
634             return -1;
635         }
636         sqlite3_free (sql);
637         g_free (info);
638     }
639 
640     return 0;
641 }
642 
643 static int
remove_task_from_db(SeafCloneManager * mgr,const char * repo_id)644 remove_task_from_db (SeafCloneManager *mgr, const char *repo_id)
645 {
646     char sql[256];
647 
648     snprintf (sql, sizeof(sql),
649               "DELETE FROM CloneTasks WHERE repo_id='%s'",
650               repo_id);
651     if (sqlite_query_exec (mgr->db, sql) < 0)
652         return -1;
653 
654     snprintf (sql, sizeof(sql),
655               "DELETE FROM CloneEncInfo WHERE repo_id='%s'",
656               repo_id);
657     if (sqlite_query_exec (mgr->db, sql) < 0)
658         return -1;
659 
660     snprintf (sql, sizeof(sql),
661               "DELETE FROM CloneVersionInfo WHERE repo_id='%s'",
662               repo_id);
663     if (sqlite_query_exec (mgr->db, sql) < 0)
664         return -1;
665 
666     snprintf (sql, sizeof(sql),
667               "DELETE FROM CloneTasksMoreInfo WHERE repo_id='%s'",
668               repo_id);
669     if (sqlite_query_exec (mgr->db, sql) < 0)
670         return -1;
671 
672     return 0;
673 }
674 
675 static void
transition_state(CloneTask * task,int new_state)676 transition_state (CloneTask *task, int new_state)
677 {
678     seaf_message ("Transition clone state for %.8s from [%s] to [%s].\n",
679                   task->repo_id,
680                   state_str[task->state], state_str[new_state]);
681 
682     if (new_state == CLONE_STATE_DONE ||
683         new_state == CLONE_STATE_CANCELED) {
684         /* Remove from db but leave in memory. */
685         remove_task_from_db (task->manager, task->repo_id);
686     }
687 
688     task->state = new_state;
689 }
690 
691 static void
transition_to_error(CloneTask * task,int error)692 transition_to_error (CloneTask *task, int error)
693 {
694     seaf_message ("Transition clone state for %.8s from [%s] to [error]: %s.\n",
695                   task->repo_id,
696                   state_str[task->state],
697                   sync_error_id_to_str(error));
698 
699     task->state = CLONE_STATE_ERROR;
700     task->error = error;
701 }
702 
703 static int
add_transfer_task(CloneTask * task,GError ** error)704 add_transfer_task (CloneTask *task, GError **error)
705 {
706     int ret = http_tx_manager_add_download (seaf->http_tx_mgr,
707                                             task->repo_id,
708                                             task->repo_version,
709                                             task->effective_url,
710                                             task->token,
711                                             task->server_head_id,
712                                             TRUE,
713                                             task->passwd,
714                                             task->worktree,
715                                             task->http_protocol_version,
716                                             task->email,
717                                             task->use_fileserver_port,
718                                             task->repo_name,
719                                             error);
720     if (ret < 0)
721         return -1;
722     task->tx_id = g_strdup(task->repo_id);
723     return 0;
724 }
725 
726 static gboolean
is_duplicate_task(SeafCloneManager * mgr,const char * repo_id)727 is_duplicate_task (SeafCloneManager *mgr, const char *repo_id)
728 {
729     CloneTask *task = g_hash_table_lookup (mgr->tasks, repo_id);
730     if (task != NULL &&
731         task->state != CLONE_STATE_DONE &&
732         task->state != CLONE_STATE_CANCELED)
733         return TRUE;
734     return FALSE;
735 }
736 
737 static gboolean
is_worktree_of_repo(SeafCloneManager * mgr,const char * path)738 is_worktree_of_repo (SeafCloneManager *mgr, const char *path)
739 {
740     GList *repos, *ptr;
741     SeafRepo *repo;
742     GHashTableIter iter;
743     gpointer key, value;
744     CloneTask *task;
745 
746     repos = seaf_repo_manager_get_repo_list (seaf->repo_mgr, -1, -1);
747     for (ptr = repos; ptr != NULL; ptr = ptr->next) {
748         repo = ptr->data;
749         if (g_strcmp0 (path, repo->worktree) == 0) {
750             g_list_free (repos);
751             return TRUE;
752         }
753     }
754     g_list_free (repos);
755 
756     g_hash_table_iter_init (&iter, mgr->tasks);
757     while (g_hash_table_iter_next (&iter, &key, &value)) {
758         task = value;
759         if (task->state == CLONE_STATE_DONE ||
760             task->state == CLONE_STATE_CANCELED)
761             continue;
762         if (g_strcmp0 (path, task->worktree) == 0)
763             return TRUE;
764     }
765 
766     return FALSE;
767 }
768 
769 static char *
try_worktree(const char * worktree)770 try_worktree (const char *worktree)
771 {
772     char *tmp;
773     unsigned int cnt;
774 
775     /* There is a repo name conflict, so we try to add a postfix */
776     cnt = 1;
777     while (1) {
778         tmp = g_strdup_printf("%s-%d", worktree, cnt++);
779         if (g_access(tmp, F_OK) < 0) {
780             return tmp;
781         }
782 
783         if (cnt == -1U) {
784             /* we have tried too much times, so give up */
785             g_free(tmp);
786             return NULL;
787         }
788 
789         g_free(tmp);
790     }
791 
792     /* XXX: never reach here */
793 }
794 
795 static inline void
remove_trail_slash(char * path)796 remove_trail_slash (char *path)
797 {
798     int tail = strlen (path) - 1;
799     while (tail >= 0 && (path[tail] == '/' || path[tail] == '\\'))
800         path[tail--] = '\0';
801 }
802 
803 static char *
make_worktree(SeafCloneManager * mgr,const char * worktree,gboolean dry_run,GError ** error)804 make_worktree (SeafCloneManager *mgr,
805                const char *worktree,
806                gboolean dry_run,
807                GError **error)
808 {
809     char *wt = g_strdup (worktree);
810     SeafStat st;
811     int rc;
812     char *ret;
813 
814     remove_trail_slash (wt);
815 
816     rc = seaf_stat (wt, &st);
817     if (rc < 0) {
818         ret = wt;
819         return ret;
820     } else if (!S_ISDIR(st.st_mode)) {
821         if (!dry_run) {
822             g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
823                          "Invalid local directory");
824             g_free (wt);
825             return NULL;
826         }
827         ret = try_worktree (wt);
828         g_free (wt);
829         return ret;
830     }
831 
832     /* OK, wt is an existing dir. Let's see if it's the worktree for
833      * another repo. */
834     if (is_worktree_of_repo (mgr, wt)) {
835         if (!dry_run) {
836             g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
837                          "Already in sync");
838             g_free (wt);
839             return NULL;
840         }
841         ret = try_worktree (wt);
842         g_free (wt);
843     } else {
844         return wt;
845     }
846 
847     return ret;
848 }
849 
850 /*
851  * Generate a conflict-free path to be used as worktree.
852  * This worktree path can be used as the @worktree parameter
853  * for seaf_clone_manager_add_task().
854  */
855 char *
seaf_clone_manager_gen_default_worktree(SeafCloneManager * mgr,const char * worktree_parent,const char * repo_name)856 seaf_clone_manager_gen_default_worktree (SeafCloneManager *mgr,
857                                          const char *worktree_parent,
858                                          const char *repo_name)
859 {
860     char *wt = g_build_filename (worktree_parent, repo_name, NULL);
861     char *worktree;
862 
863     worktree = make_worktree (mgr, wt, TRUE, NULL);
864     if (!worktree)
865         return wt;
866 
867     g_free (wt);
868     return worktree;
869 }
870 
is_separator(char c)871 inline static gboolean is_separator (char c)
872 {
873     return (c == '/' || c == '\\');
874 }
875 
876 /*
877  * Returns < 0 if dira includes dirb or dira == dirb;
878  * Returns 0 if no inclusive relationship;
879  * Returns > 0 if dirb includes dira.
880  */
881 static int
check_dir_inclusiveness(const char * dira,const char * dirb)882 check_dir_inclusiveness (const char *dira, const char *dirb)
883 {
884     char *a, *b;
885     char *p1, *p2;
886     int ret = 0;
887 
888     a = g_strdup(dira);
889     b = g_strdup(dirb);
890     remove_trail_slash (a);
891     remove_trail_slash (b);
892 
893     p1 = a;
894     p2 = b;
895     while (*p1 != 0 && *p2 != 0) {
896         /* Go to the last one in a path separator sequence. */
897         while (is_separator(*p1) && is_separator(p1[1]))
898             ++p1;
899         while (is_separator(*p2) && is_separator(p2[1]))
900             ++p2;
901 
902         if (!(is_separator(*p1) && is_separator(*p2)) && *p1 != *p2)
903             goto out;
904 
905         ++p1;
906         ++p2;
907     }
908 
909     /* Example:
910      *            p1
911      * a: /abc/def/ghi
912      *            p2
913      * b: /abc/def
914      */
915     if (*p1 == 0 && *p2 == 0)
916         ret = -1;
917     else if (*p1 != 0 && is_separator(*p1))
918         ret = 1;
919     else if (*p2 != 0 && is_separator(*p2))
920         ret = -1;
921 
922 out:
923     g_free (a);
924     g_free (b);
925     return ret;
926 }
927 
928 gboolean
seaf_clone_manager_check_worktree_path(SeafCloneManager * mgr,const char * path,GError ** error)929 seaf_clone_manager_check_worktree_path (SeafCloneManager *mgr, const char *path, GError **error)
930 {
931     GList *repos, *ptr;
932     SeafRepo *repo;
933     GHashTableIter iter;
934     gpointer key, value;
935     CloneTask *task;
936 
937     if (check_dir_inclusiveness (path, seaf->seaf_dir) != 0 ||
938         /* It's OK if path is included by the default worktree parent. */
939         check_dir_inclusiveness (path, seaf->worktree_dir) < 0 ||
940         check_dir_inclusiveness (path, seaf->ccnet_dir) != 0) {
941         seaf_warning ("Worktree path conflicts with seafile system path.\n");
942         g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
943                      "Worktree conflicts system path");
944         return FALSE;
945     }
946 
947     repos = seaf_repo_manager_get_repo_list (seaf->repo_mgr, -1, -1);
948     for (ptr = repos; ptr != NULL; ptr = ptr->next) {
949         repo = ptr->data;
950         if (repo->worktree != NULL &&
951             check_dir_inclusiveness (path, repo->worktree) != 0) {
952             seaf_warning ("Worktree path conflict with repo %s.\n", repo->name);
953             g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
954                          "Worktree conflicts existing repo");
955             g_list_free (repos);
956             return FALSE;
957         }
958     }
959     g_list_free (repos);
960 
961     g_hash_table_iter_init (&iter, mgr->tasks);
962     while (g_hash_table_iter_next (&iter, &key, &value)) {
963         task = value;
964         if (task->state == CLONE_STATE_DONE ||
965             task->state == CLONE_STATE_CANCELED)
966             continue;
967         if (check_dir_inclusiveness (path, task->worktree) != 0) {
968             seaf_warning ("Worktree path conflict with clone %.8s.\n",
969                           task->repo_id);
970             g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
971                          "Worktree conflicts existing repo");
972             return FALSE;
973         }
974     }
975 
976     return TRUE;
977 }
978 
979 static char *
add_task_common(SeafCloneManager * mgr,const char * repo_id,int repo_version,const char * repo_name,const char * token,const char * passwd,int enc_version,const char * random_key,const char * worktree,const char * email,const char * more_info,gboolean sync_wt_name,GError ** error)980 add_task_common (SeafCloneManager *mgr,
981                  const char *repo_id,
982                  int repo_version,
983                  const char *repo_name,
984                  const char *token,
985                  const char *passwd,
986                  int enc_version,
987                  const char *random_key,
988                  const char *worktree,
989                  const char *email,
990                  const char *more_info,
991                  gboolean sync_wt_name,
992                  GError **error)
993 {
994     CloneTask *task;
995 
996     task = clone_task_new (repo_id, repo_name,
997                            token, worktree,
998                            passwd, email);
999     task->manager = mgr;
1000     task->enc_version = enc_version;
1001     task->random_key = g_strdup (random_key);
1002     task->repo_version = repo_version;
1003     task->sync_wt_name = sync_wt_name;
1004     if (more_info) {
1005         json_error_t jerror;
1006         json_t *object = NULL;
1007 
1008         object = json_loads (more_info, 0, &jerror);
1009         if (!object) {
1010             seaf_warning ("Failed to load more sync info from json: %s.\n", jerror.text);
1011             clone_task_free (task);
1012             return NULL;
1013         }
1014 
1015         json_t *integer = json_object_get (object, "is_readonly");
1016         task->is_readonly = json_integer_value (integer);
1017         json_t *string = json_object_get (object, "server_url");
1018         if (string)
1019             task->server_url = canonical_server_url (json_string_value (string));
1020         json_t *repo_salt = json_object_get (object, "repo_salt");
1021         if (repo_salt)
1022             task->repo_salt = g_strdup (json_string_value (repo_salt));
1023         json_decref (object);
1024     }
1025 
1026     if (save_task_to_db (mgr, task) < 0) {
1027         seaf_warning ("[Clone mgr] failed to save task.\n");
1028         clone_task_free (task);
1029         return NULL;
1030     }
1031 
1032     if (task->repo_version > 0) {
1033         if (task->server_url) {
1034             check_http_protocol (task);
1035         } else {
1036             clone_task_free (task);
1037             return NULL;
1038         }
1039     }
1040 
1041     /* The old task for this repo will be freed. */
1042     g_hash_table_insert (mgr->tasks, g_strdup(task->repo_id), task);
1043 
1044     return g_strdup(repo_id);
1045 }
1046 
1047 static gboolean
check_encryption_args(const char * magic,int enc_version,const char * random_key,const char * repo_salt,GError ** error)1048 check_encryption_args (const char *magic, int enc_version, const char *random_key,
1049                        const char *repo_salt,
1050                        GError **error)
1051 {
1052     if (!magic) {
1053         g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS,
1054                      "Magic must be specified");
1055         return FALSE;
1056     }
1057 
1058     if (enc_version != 1 && enc_version != 2 && enc_version != 3) {
1059         g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS,
1060                      "Unsupported enc version");
1061         return FALSE;
1062     }
1063 
1064     if (enc_version >= 2) {
1065         if (!random_key || strlen(random_key) != 96) {
1066             g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS,
1067                          "Random key not specified");
1068             return FALSE;
1069         }
1070         if (enc_version == 3 && (!(repo_salt) || strlen(repo_salt) != 64) ) {
1071             g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS,
1072                          "Repo salt not specified");
1073             return FALSE;
1074         }
1075     }
1076 
1077     return TRUE;
1078 }
1079 
1080 static gboolean
is_wt_repo_name_same(const char * worktree,const char * repo_name)1081 is_wt_repo_name_same (const char *worktree, const char *repo_name)
1082 {
1083     char *basename = g_path_get_basename (worktree);
1084     gboolean ret = FALSE;
1085     ret = (strcmp (basename, repo_name) == 0);
1086     g_free (basename);
1087     return ret;
1088 }
1089 
1090 char *
seaf_clone_manager_add_task(SeafCloneManager * mgr,const char * repo_id,int repo_version,const char * repo_name,const char * token,const char * passwd,const char * magic,int enc_version,const char * random_key,const char * worktree_in,const char * email,const char * more_info,GError ** error)1091 seaf_clone_manager_add_task (SeafCloneManager *mgr,
1092                              const char *repo_id,
1093                              int repo_version,
1094                              const char *repo_name,
1095                              const char *token,
1096                              const char *passwd,
1097                              const char *magic,
1098                              int enc_version,
1099                              const char *random_key,
1100                              const char *worktree_in,
1101                              const char *email,
1102                              const char *more_info,
1103                              GError **error)
1104 {
1105     SeafRepo *repo = NULL;
1106     char *worktree = NULL;
1107     char *ret = NULL;
1108     gboolean sync_wt_name = FALSE;
1109     char *repo_salt = NULL;
1110 
1111     if (!seaf->started) {
1112         seaf_message ("System not started, skip adding clone task.\n");
1113         goto out;
1114     }
1115 
1116 #ifdef USE_GPL_CRYPTO
1117     if (repo_version == 0 || (passwd && enc_version < 2)) {
1118         seaf_warning ("Don't support syncing old version libraries.\n");
1119         g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS,
1120                      "Don't support syncing old version libraries");
1121         goto out;
1122     }
1123 #endif
1124 
1125     if (more_info) {
1126         json_error_t jerror;
1127         json_t *object;
1128 
1129         object = json_loads (more_info, 0, &jerror);
1130         if (!object) {
1131             seaf_warning ("Failed to load more sync info from json: %s.\n", jerror.text);
1132             goto out;
1133         }
1134         json_t *string = json_object_get (object, "repo_salt");
1135         if (string)
1136             repo_salt = g_strdup (json_string_value (string));
1137         json_decref (object);
1138     }
1139 
1140     if (passwd &&
1141         !check_encryption_args (magic, enc_version, random_key, repo_salt, error)) {
1142         goto out;
1143     }
1144     /* After a repo was unsynced, the sync task may still be blocked in the
1145      * network, so the repo is not actually deleted yet.
1146      * In this case just return an error to the user.
1147      */
1148     SyncInfo *sync_info = seaf_sync_manager_get_sync_info (seaf->sync_mgr,
1149                                                            repo_id);
1150     if (sync_info && sync_info->in_sync) {
1151         g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
1152                      "Repo already exists");
1153         goto out;
1154     }
1155 
1156     repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id);
1157 
1158     if (repo != NULL && repo->head != NULL) {
1159         g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
1160                      "Repo already exists");
1161         goto out;
1162     }
1163 
1164     if (is_duplicate_task (mgr, repo_id)) {
1165         g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
1166                      "Task is already in progress");
1167         goto out;
1168     }
1169 
1170     if (passwd &&
1171         seafile_verify_repo_passwd(repo_id, passwd, magic, enc_version, repo_salt) < 0) {
1172         g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
1173                      "Incorrect password");
1174         goto out;
1175     }
1176 
1177     if (!seaf_clone_manager_check_worktree_path (mgr, worktree_in, error))
1178         goto out;
1179 
1180     /* Return error if worktree_in conflicts with another repo or
1181      * is not a directory.
1182      */
1183     worktree = make_worktree (mgr, worktree_in, FALSE, error);
1184     if (!worktree) {
1185         goto out;
1186     }
1187 
1188     /* Don't sync worktree folder name with library name later if they're not the same
1189      * at the beginning.
1190      */
1191     sync_wt_name = is_wt_repo_name_same (worktree, repo_name);
1192 
1193     /* If a repo was unsynced and then downloaded again, there may be
1194      * a garbage record for this repo. We don't want the downloaded blocks
1195      * be removed by GC.
1196      */
1197     if (repo_version > 0)
1198         seaf_repo_manager_remove_garbage_repo (seaf->repo_mgr, repo_id);
1199 
1200     /* Delete orphan information in the db in case the repo was corrupt. */
1201     if (!repo)
1202         seaf_repo_manager_remove_repo_ondisk (seaf->repo_mgr, repo_id, FALSE);
1203 
1204     ret = add_task_common (mgr, repo_id, repo_version,
1205                            repo_name, token, passwd,
1206                            enc_version, random_key,
1207                            worktree, email, more_info,
1208                            sync_wt_name,
1209                            error);
1210 
1211 out:
1212     g_free (worktree);
1213     g_free (repo_salt);
1214 
1215     return ret;
1216 }
1217 
1218 static char *
make_worktree_for_download(SeafCloneManager * mgr,const char * wt_tmp,GError ** error)1219 make_worktree_for_download (SeafCloneManager *mgr,
1220                             const char *wt_tmp,
1221                             GError **error)
1222 {
1223     char *worktree;
1224 
1225     if (g_access (wt_tmp, F_OK) == 0) {
1226         worktree = try_worktree (wt_tmp);
1227     } else {
1228         worktree = g_strdup(wt_tmp);
1229     }
1230 
1231     if (!seaf_clone_manager_check_worktree_path (mgr, worktree, error)) {
1232         g_free (worktree);
1233         return NULL;
1234     }
1235 
1236     return worktree;
1237 }
1238 
1239 char *
seaf_clone_manager_add_download_task(SeafCloneManager * mgr,const char * repo_id,int repo_version,const char * repo_name,const char * token,const char * passwd,const char * magic,int enc_version,const char * random_key,const char * wt_parent,const char * email,const char * more_info,GError ** error)1240 seaf_clone_manager_add_download_task (SeafCloneManager *mgr,
1241                                       const char *repo_id,
1242                                       int repo_version,
1243                                       const char *repo_name,
1244                                       const char *token,
1245                                       const char *passwd,
1246                                       const char *magic,
1247                                       int enc_version,
1248                                       const char *random_key,
1249                                       const char *wt_parent,
1250                                       const char *email,
1251                                       const char *more_info,
1252                                       GError **error)
1253 {
1254     SeafRepo *repo = NULL;
1255     char *wt_tmp = NULL;
1256     char *worktree = NULL;
1257     char *ret = NULL;
1258     char *repo_salt = NULL;
1259 
1260     if (!seaf->started) {
1261         seaf_message ("System not started, skip adding clone task.\n");
1262         goto out;
1263     }
1264 
1265 #ifdef USE_GPL_CRYPTO
1266     if (repo_version == 0 || (passwd && enc_version < 2)) {
1267         seaf_warning ("Don't support syncing old version libraries.\n");
1268         g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS,
1269                      "Don't support syncing old version libraries");
1270         goto out;
1271     }
1272 #endif
1273 
1274     if (more_info) {
1275          json_error_t jerror;
1276          json_t *object;
1277 
1278          object = json_loads (more_info, 0, &jerror);
1279          if (!object) {
1280              seaf_warning ("Failed to load more sync info from json: %s.\n", jerror.text);
1281              goto out;
1282          }
1283          json_t *string = json_object_get (object, "repo_salt");
1284          if (string)
1285              repo_salt = g_strdup (json_string_value (string));
1286          json_decref (object);
1287      }
1288 
1289     if (passwd &&
1290         !check_encryption_args (magic, enc_version, random_key, repo_salt, error)) {
1291         goto out;
1292     }
1293 
1294     /* After a repo was unsynced, the sync task may still be blocked in the
1295      * network, so the repo is not actually deleted yet.
1296      * In this case just return an error to the user.
1297      */
1298     SyncInfo *sync_info = seaf_sync_manager_get_sync_info (seaf->sync_mgr,
1299                                                            repo_id);
1300     if (sync_info && sync_info->in_sync) {
1301         g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
1302                      "Repo already exists");
1303         goto out;
1304     }
1305 
1306     repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id);
1307 
1308     if (repo != NULL && repo->head != NULL) {
1309         g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
1310                      "Repo already exists");
1311         goto out;
1312     }
1313 
1314     if (is_duplicate_task (mgr, repo_id)) {
1315         g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
1316                      "Task is already in progress");
1317         goto out;
1318     }
1319 
1320     if (passwd &&
1321         seafile_verify_repo_passwd(repo_id, passwd, magic, enc_version, repo_salt) < 0) {
1322         g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
1323                      "Incorrect password");
1324         goto out;
1325     }
1326 
1327     IgnoreReason reason;
1328     if (should_ignore_on_checkout (repo_name, &reason)) {
1329         if (reason == IGNORE_REASON_END_SPACE_PERIOD)
1330             g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS,
1331                          "Library name ends with space or period character");
1332         else
1333             g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS,
1334                          "Library name contains invalid characters such as ':', '*', '|', '?'");
1335         goto out;
1336     }
1337 
1338     wt_tmp = g_build_filename (wt_parent, repo_name, NULL);
1339 
1340     worktree = make_worktree_for_download (mgr, wt_tmp, error);
1341     if (!worktree) {
1342         goto out;
1343     }
1344 
1345     /* If a repo was unsynced and then downloaded again, there may be
1346      * a garbage record for this repo. We don't want the downloaded blocks
1347      * be removed by GC.
1348      */
1349     if (repo_version > 0)
1350         seaf_repo_manager_remove_garbage_repo (seaf->repo_mgr, repo_id);
1351 
1352     /* Delete orphan information in the db in case the repo was corrupt. */
1353     if (!repo)
1354         seaf_repo_manager_remove_repo_ondisk (seaf->repo_mgr, repo_id, FALSE);
1355 
1356     ret = add_task_common (mgr, repo_id, repo_version,
1357                            repo_name, token, passwd,
1358                            enc_version, random_key,
1359                            worktree, email, more_info,
1360                            TRUE, error);
1361 
1362 out:
1363     g_free (worktree);
1364     g_free (wt_tmp);
1365     g_free (repo_salt);
1366 
1367     return ret;
1368 }
1369 
1370 int
seaf_clone_manager_cancel_task(SeafCloneManager * mgr,const char * repo_id)1371 seaf_clone_manager_cancel_task (SeafCloneManager *mgr,
1372                                 const char *repo_id)
1373 {
1374     CloneTask *task;
1375 
1376     if (!seaf->started) {
1377         seaf_message ("System not started, skip canceling clone task.\n");
1378         return -1;
1379     }
1380 
1381     task = g_hash_table_lookup (mgr->tasks, repo_id);
1382     if (!task)
1383         return -1;
1384 
1385     switch (task->state) {
1386     case CLONE_STATE_INIT:
1387     case CLONE_STATE_CONNECT:
1388     case CLONE_STATE_ERROR:
1389         transition_state (task, CLONE_STATE_CANCELED);
1390         break;
1391     case CLONE_STATE_CHECK_SERVER:
1392         transition_state (task, CLONE_STATE_CANCEL_PENDING);
1393     case CLONE_STATE_FETCH:
1394         http_tx_manager_cancel_task (seaf->http_tx_mgr,
1395                                      task->repo_id,
1396                                      HTTP_TASK_TYPE_DOWNLOAD);
1397         transition_state (task, CLONE_STATE_CANCEL_PENDING);
1398         break;
1399     case CLONE_STATE_INDEX:
1400     case CLONE_STATE_CHECKOUT:
1401     case CLONE_STATE_MERGE:
1402     case CLONE_STATE_CHECK_PROTOCOL:
1403         /* We cannot cancel an in-progress checkout, just
1404          * wait until it finishes.
1405          */
1406         transition_state (task, CLONE_STATE_CANCEL_PENDING);
1407         break;
1408     case CLONE_STATE_CANCEL_PENDING:
1409         break;
1410     default:
1411         seaf_warning ("[Clone mgr] cannot cancel a not-running task.\n");
1412         return -1;
1413     }
1414 
1415     return 0;
1416 }
1417 
1418 CloneTask *
seaf_clone_manager_get_task(SeafCloneManager * mgr,const char * repo_id)1419 seaf_clone_manager_get_task (SeafCloneManager *mgr,
1420                              const char *repo_id)
1421 {
1422     return (CloneTask *) g_hash_table_lookup (mgr->tasks, repo_id);
1423 }
1424 
1425 GList *
seaf_clone_manager_get_tasks(SeafCloneManager * mgr)1426 seaf_clone_manager_get_tasks (SeafCloneManager *mgr)
1427 {
1428     return g_hash_table_get_values (mgr->tasks);
1429 }
1430 
1431 static void
1432 check_folder_permissions (CloneTask *task);
1433 
1434 static void
on_repo_http_fetched(SeafileSession * seaf,HttpTxTask * tx_task,SeafCloneManager * mgr)1435 on_repo_http_fetched (SeafileSession *seaf,
1436                       HttpTxTask *tx_task,
1437                       SeafCloneManager *mgr)
1438 {
1439     CloneTask *task;
1440 
1441     /* Only handle clone task. */
1442     if (!tx_task->is_clone)
1443         return;
1444 
1445     task = g_hash_table_lookup (mgr->tasks, tx_task->repo_id);
1446     g_return_if_fail (task != NULL);
1447 
1448     if (tx_task->state == HTTP_TASK_STATE_CANCELED) {
1449         /* g_assert (task->state == CLONE_STATE_CANCEL_PENDING); */
1450         transition_state (task, CLONE_STATE_CANCELED);
1451         return;
1452     } else if (tx_task->state == HTTP_TASK_STATE_ERROR) {
1453         transition_to_error (task, tx_task->error);
1454         return;
1455     }
1456 
1457     SeafRepo *repo = seaf_repo_manager_get_repo (seaf->repo_mgr,
1458                                                  tx_task->repo_id);
1459     if (repo == NULL) {
1460         seaf_warning ("[Clone mgr] cannot find repo %s after fetched.\n",
1461                    tx_task->repo_id);
1462         transition_to_error (task, SYNC_ERROR_ID_LOCAL_DATA_CORRUPT);
1463         return;
1464     }
1465 
1466     seaf_repo_manager_set_repo_token (seaf->repo_mgr, repo, task->token);
1467     seaf_repo_manager_set_repo_email (seaf->repo_mgr, repo, task->email);
1468     seaf_repo_manager_set_repo_relay_info (seaf->repo_mgr, repo->id,
1469                                            task->peer_addr, task->peer_port);
1470     if (task->server_url) {
1471         seaf_repo_manager_set_repo_property (seaf->repo_mgr,
1472                                              repo->id,
1473                                              REPO_PROP_SERVER_URL,
1474                                              task->server_url);
1475     }
1476 
1477     check_folder_permissions (task);
1478 }
1479 
1480 static void
check_folder_perms_done(HttpFolderPerms * result,void * user_data)1481 check_folder_perms_done (HttpFolderPerms *result, void *user_data)
1482 {
1483     CloneTask *task = user_data;
1484     GList *ptr;
1485     HttpFolderPermRes *res;
1486 
1487     SeafRepo *repo = seaf_repo_manager_get_repo (seaf->repo_mgr,
1488                                                  task->repo_id);
1489     if (repo == NULL) {
1490         seaf_warning ("[Clone mgr] cannot find repo %s after fetched.\n",
1491                    task->repo_id);
1492         transition_to_error (task, SYNC_ERROR_ID_LOCAL_DATA_CORRUPT);
1493         return;
1494     }
1495 
1496     if (!result->success) {
1497         goto out;
1498     }
1499 
1500     for (ptr = result->results; ptr; ptr = ptr->next) {
1501         res = ptr->data;
1502 
1503         seaf_repo_manager_update_folder_perms (seaf->repo_mgr, res->repo_id,
1504                                                FOLDER_PERM_TYPE_USER,
1505                                                res->user_perms);
1506         seaf_repo_manager_update_folder_perms (seaf->repo_mgr, res->repo_id,
1507                                                FOLDER_PERM_TYPE_GROUP,
1508                                                res->group_perms);
1509         seaf_repo_manager_update_folder_perm_timestamp (seaf->repo_mgr,
1510                                                         res->repo_id,
1511                                                         res->timestamp);
1512     }
1513 
1514 out:
1515     mark_clone_done_v2 (repo, task);
1516 }
1517 
1518 static void
check_folder_permissions(CloneTask * task)1519 check_folder_permissions (CloneTask *task)
1520 {
1521     SeafRepo *repo = NULL;
1522     HttpFolderPermReq *req;
1523     GList *requests = NULL;
1524 
1525     repo = seaf_repo_manager_get_repo (seaf->repo_mgr, task->repo_id);
1526     if (repo == NULL) {
1527         seaf_warning ("[Clone mgr] cannot find repo %s after fetched.\n",
1528                       task->repo_id);
1529         transition_to_error (task, SYNC_ERROR_ID_LOCAL_DATA_CORRUPT);
1530         return;
1531     }
1532 
1533     if (!seaf_repo_manager_server_is_pro (seaf->repo_mgr, task->server_url)) {
1534         mark_clone_done_v2 (repo, task);
1535         return;
1536     }
1537 
1538     req = g_new0 (HttpFolderPermReq, 1);
1539     memcpy (req->repo_id, task->repo_id, 36);
1540     req->token = g_strdup(task->token);
1541     req->timestamp = 0;
1542 
1543     requests = g_list_append (requests, req);
1544 
1545     /* The requests list will be freed in http tx manager. */
1546     if (http_tx_manager_get_folder_perms (seaf->http_tx_mgr,
1547                                           task->effective_url,
1548                                           task->use_fileserver_port,
1549                                           requests,
1550                                           check_folder_perms_done,
1551                                           task) < 0)
1552         transition_to_error (task, SYNC_ERROR_ID_NOT_ENOUGH_MEMORY);
1553 }
1554