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