1 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 
3 #include "common.h"
4 
5 #include "log.h"
6 
7 #include <jansson.h>
8 
9 #include "utils.h"
10 #include "db.h"
11 #include "searpc-utils.h"
12 
13 #include "seafile-session.h"
14 #include "commit-mgr.h"
15 
16 #define MAX_TIME_SKEW 259200    /* 3 days */
17 
18 struct _SeafCommitManagerPriv {
19     int dummy;
20 };
21 
22 static SeafCommit *
23 load_commit (SeafCommitManager *mgr,
24              const char *repo_id, int version,
25              const char *commit_id);
26 static int
27 save_commit (SeafCommitManager *manager,
28              const char *repo_id, int version,
29              SeafCommit *commit);
30 static void
31 delete_commit (SeafCommitManager *mgr,
32                const char *repo_id, int version,
33                const char *id);
34 static json_t *
35 commit_to_json_object (SeafCommit *commit);
36 static SeafCommit *
37 commit_from_json_object (const char *id, json_t *object);
38 
compute_commit_id(SeafCommit * commit)39 static void compute_commit_id (SeafCommit* commit)
40 {
41     GChecksum *ctx = g_checksum_new(G_CHECKSUM_SHA1);
42     uint8_t sha1[20];
43     gint64 ctime_n;
44 
45     g_checksum_update (ctx, (guchar *)commit->root_id, 41);
46     g_checksum_update (ctx, (guchar *)commit->creator_id, 41);
47     if (commit->creator_name)
48         g_checksum_update (ctx, (guchar *)commit->creator_name, strlen(commit->creator_name)+1);
49     g_checksum_update (ctx, (guchar *)commit->desc, strlen(commit->desc)+1);
50 
51     /* convert to network byte order */
52     ctime_n = hton64 (commit->ctime);
53     g_checksum_update (ctx, (guchar *)&ctime_n, sizeof(ctime_n));
54 
55     gsize len = 20;
56     g_checksum_get_digest (ctx, sha1, &len);
57 
58     rawdata_to_hex (sha1, commit->commit_id, 20);
59     g_checksum_free (ctx);
60 }
61 
62 SeafCommit*
seaf_commit_new(const char * commit_id,const char * repo_id,const char * root_id,const char * creator_name,const char * creator_id,const char * desc,guint64 ctime)63 seaf_commit_new (const char *commit_id,
64                  const char *repo_id,
65                  const char *root_id,
66                  const char *creator_name,
67                  const char *creator_id,
68                  const char *desc,
69                  guint64 ctime)
70 {
71     SeafCommit *commit;
72 
73     g_return_val_if_fail (repo_id != NULL, NULL);
74     g_return_val_if_fail (root_id != NULL && creator_id != NULL, NULL);
75 
76     commit = g_new0 (SeafCommit, 1);
77 
78     memcpy (commit->repo_id, repo_id, 36);
79     commit->repo_id[36] = '\0';
80 
81     memcpy (commit->root_id, root_id, 40);
82     commit->root_id[40] = '\0';
83 
84     commit->creator_name = g_strdup (creator_name);
85 
86     memcpy (commit->creator_id, creator_id, 40);
87     commit->creator_id[40] = '\0';
88 
89     commit->desc = g_strdup (desc);
90 
91     if (ctime == 0) {
92         /* TODO: use more precise timer */
93         commit->ctime = (gint64)time(NULL);
94     } else
95         commit->ctime = ctime;
96 
97     if (commit_id == NULL)
98         compute_commit_id (commit);
99     else {
100         memcpy (commit->commit_id, commit_id, 40);
101         commit->commit_id[40] = '\0';
102     }
103 
104     commit->ref = 1;
105     return commit;
106 }
107 
108 char *
seaf_commit_to_data(SeafCommit * commit,gsize * len)109 seaf_commit_to_data (SeafCommit *commit, gsize *len)
110 {
111     json_t *object;
112     char *json_data;
113     char *ret;
114 
115     object = commit_to_json_object (commit);
116 
117     json_data = json_dumps (object, 0);
118     *len = strlen (json_data);
119     json_decref (object);
120 
121     ret = g_strdup (json_data);
122     free (json_data);
123     return ret;
124 }
125 
126 SeafCommit *
seaf_commit_from_data(const char * id,char * data,gsize len)127 seaf_commit_from_data (const char *id, char *data, gsize len)
128 {
129     json_t *object;
130     SeafCommit *commit;
131     json_error_t jerror;
132 
133     object = json_loadb (data, len, 0, &jerror);
134     if (!object) {
135         /* Perhaps the commit object contains invalid UTF-8 character. */
136         if (data[len-1] == 0)
137             clean_utf8_data (data, len - 1);
138         else
139             clean_utf8_data (data, len);
140 
141         object = json_loadb (data, len, 0, &jerror);
142         if (!object) {
143             seaf_warning ("Failed to load commit json: %s.\n", jerror.text);
144             return NULL;
145         }
146     }
147 
148     commit = commit_from_json_object (id, object);
149 
150     json_decref (object);
151 
152     return commit;
153 }
154 
155 static void
seaf_commit_free(SeafCommit * commit)156 seaf_commit_free (SeafCommit *commit)
157 {
158     g_free (commit->desc);
159     g_free (commit->creator_name);
160     if (commit->parent_id) g_free (commit->parent_id);
161     if (commit->second_parent_id) g_free (commit->second_parent_id);
162     if (commit->repo_name) g_free (commit->repo_name);
163     if (commit->repo_desc) g_free (commit->repo_desc);
164     if (commit->device_name) g_free (commit->device_name);
165     g_free (commit->client_version);
166     g_free (commit->magic);
167     g_free (commit->random_key);
168     g_free (commit);
169 }
170 
171 void
seaf_commit_ref(SeafCommit * commit)172 seaf_commit_ref (SeafCommit *commit)
173 {
174     commit->ref++;
175 }
176 
177 void
seaf_commit_unref(SeafCommit * commit)178 seaf_commit_unref (SeafCommit *commit)
179 {
180     if (!commit)
181         return;
182 
183     if (--commit->ref <= 0)
184         seaf_commit_free (commit);
185 }
186 
187 SeafCommitManager*
seaf_commit_manager_new(SeafileSession * seaf)188 seaf_commit_manager_new (SeafileSession *seaf)
189 {
190     SeafCommitManager *mgr = g_new0 (SeafCommitManager, 1);
191 
192     mgr->priv = g_new0 (SeafCommitManagerPriv, 1);
193     mgr->seaf = seaf;
194     mgr->obj_store = seaf_obj_store_new (mgr->seaf, "commits");
195 
196     return mgr;
197 }
198 
199 int
seaf_commit_manager_init(SeafCommitManager * mgr)200 seaf_commit_manager_init (SeafCommitManager *mgr)
201 {
202 #ifdef SEAFILE_SERVER
203 
204 #ifdef FULL_FEATURE
205     if (seaf_obj_store_init (mgr->obj_store, TRUE, seaf->ev_mgr) < 0) {
206         seaf_warning ("[commit mgr] Failed to init commit object store.\n");
207         return -1;
208     }
209 #else
210     if (seaf_obj_store_init (mgr->obj_store, FALSE, NULL) < 0) {
211         seaf_warning ("[commit mgr] Failed to init commit object store.\n");
212         return -1;
213     }
214 #endif
215 
216 #else
217     if (seaf_obj_store_init (mgr->obj_store, TRUE, seaf->ev_mgr) < 0) {
218         seaf_warning ("[commit mgr] Failed to init commit object store.\n");
219         return -1;
220     }
221 #endif
222 
223     return 0;
224 }
225 
226 #if 0
227 inline static void
228 add_commit_to_cache (SeafCommitManager *mgr, SeafCommit *commit)
229 {
230     g_hash_table_insert (mgr->priv->commit_cache,
231                          g_strdup(commit->commit_id),
232                          commit);
233     seaf_commit_ref (commit);
234 }
235 
236 inline static void
237 remove_commit_from_cache (SeafCommitManager *mgr, SeafCommit *commit)
238 {
239     g_hash_table_remove (mgr->priv->commit_cache, commit->commit_id);
240     seaf_commit_unref (commit);
241 }
242 #endif
243 
244 int
seaf_commit_manager_add_commit(SeafCommitManager * mgr,SeafCommit * commit)245 seaf_commit_manager_add_commit (SeafCommitManager *mgr,
246                                 SeafCommit *commit)
247 {
248     int ret;
249 
250     /* add_commit_to_cache (mgr, commit); */
251     if ((ret = save_commit (mgr, commit->repo_id, commit->version, commit)) < 0)
252         return -1;
253 
254     return 0;
255 }
256 
257 void
seaf_commit_manager_del_commit(SeafCommitManager * mgr,const char * repo_id,int version,const char * id)258 seaf_commit_manager_del_commit (SeafCommitManager *mgr,
259                                 const char *repo_id,
260                                 int version,
261                                 const char *id)
262 {
263     g_return_if_fail (id != NULL);
264 
265 #if 0
266     commit = g_hash_table_lookup(mgr->priv->commit_cache, id);
267     if (!commit)
268         goto delete;
269 
270     /*
271      * Catch ref count bug here. We have bug in commit ref, the
272      * following assert can't pass. TODO: fix the commit ref bug
273      */
274     /* g_assert (commit->ref <= 1); */
275     remove_commit_from_cache (mgr, commit);
276 
277 delete:
278 #endif
279 
280     delete_commit (mgr, repo_id, version, id);
281 }
282 
283 SeafCommit*
seaf_commit_manager_get_commit(SeafCommitManager * mgr,const char * repo_id,int version,const char * id)284 seaf_commit_manager_get_commit (SeafCommitManager *mgr,
285                                 const char *repo_id,
286                                 int version,
287                                 const char *id)
288 {
289     SeafCommit *commit;
290 
291 #if 0
292     commit = g_hash_table_lookup (mgr->priv->commit_cache, id);
293     if (commit != NULL) {
294         seaf_commit_ref (commit);
295         return commit;
296     }
297 #endif
298 
299     commit = load_commit (mgr, repo_id, version, id);
300     if (!commit)
301         return NULL;
302 
303     /* add_commit_to_cache (mgr, commit); */
304 
305     return commit;
306 }
307 
308 SeafCommit *
seaf_commit_manager_get_commit_compatible(SeafCommitManager * mgr,const char * repo_id,const char * id)309 seaf_commit_manager_get_commit_compatible (SeafCommitManager *mgr,
310                                            const char *repo_id,
311                                            const char *id)
312 {
313     SeafCommit *commit = NULL;
314 
315     /* First try version 1 layout. */
316     commit = seaf_commit_manager_get_commit (mgr, repo_id, 1, id);
317     if (commit)
318         return commit;
319 
320 #if defined MIGRATION || defined SEAFILE_CLIENT
321     /* For compatibility with version 0. */
322     commit = seaf_commit_manager_get_commit (mgr, repo_id, 0, id);
323 #endif
324     return commit;
325 }
326 
327 static gint
compare_commit_by_time(gconstpointer a,gconstpointer b,gpointer unused)328 compare_commit_by_time (gconstpointer a, gconstpointer b, gpointer unused)
329 {
330     const SeafCommit *commit_a = a;
331     const SeafCommit *commit_b = b;
332 
333     /* Latest commit comes first in the list. */
334     return (commit_b->ctime - commit_a->ctime);
335 }
336 
337 inline static int
insert_parent_commit(GList ** list,GHashTable * hash,const char * repo_id,int version,const char * parent_id,gboolean allow_truncate)338 insert_parent_commit (GList **list, GHashTable *hash,
339                       const char *repo_id, int version,
340                       const char *parent_id, gboolean allow_truncate)
341 {
342     SeafCommit *p;
343     char *key;
344 
345     if (g_hash_table_lookup (hash, parent_id) != NULL)
346         return 0;
347 
348     p = seaf_commit_manager_get_commit (seaf->commit_mgr,
349                                         repo_id, version,
350                                         parent_id);
351     if (!p) {
352         if (allow_truncate)
353             return 0;
354         seaf_warning ("Failed to find commit %s\n", parent_id);
355         return -1;
356     }
357 
358     *list = g_list_insert_sorted_with_data (*list, p,
359                                            compare_commit_by_time,
360                                            NULL);
361 
362     key = g_strdup (parent_id);
363     g_hash_table_replace (hash, key, key);
364 
365     return 0;
366 }
367 
368 gboolean
seaf_commit_manager_traverse_commit_tree_with_limit(SeafCommitManager * mgr,const char * repo_id,int version,const char * head,CommitTraverseFunc func,int limit,void * data,gboolean skip_errors)369 seaf_commit_manager_traverse_commit_tree_with_limit (SeafCommitManager *mgr,
370                                                      const char *repo_id,
371                                                      int version,
372                                                      const char *head,
373                                                      CommitTraverseFunc func,
374                                                      int limit,
375                                                      void *data,
376                                                      gboolean skip_errors)
377 {
378     SeafCommit *commit;
379     GList *list = NULL;
380     GHashTable *commit_hash;
381     gboolean ret = TRUE;
382 
383     /* A hash table for recording id of traversed commits. */
384     commit_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
385 
386     commit = seaf_commit_manager_get_commit (mgr, repo_id, version, head);
387     if (!commit) {
388         seaf_warning ("Failed to find commit %s.\n", head);
389         return FALSE;
390     }
391 
392     list = g_list_insert_sorted_with_data (list, commit,
393                                            compare_commit_by_time,
394                                            NULL);
395 
396     char *key = g_strdup (commit->commit_id);
397     g_hash_table_replace (commit_hash, key, key);
398 
399     int count = 0;
400     while (list) {
401         gboolean stop = FALSE;
402         commit = list->data;
403         list = g_list_delete_link (list, list);
404 
405         if (!func (commit, data, &stop)) {
406             if (!skip_errors) {
407                 seaf_commit_unref (commit);
408                 ret = FALSE;
409                 goto out;
410             }
411         }
412 
413         /* Stop when limit is reached. If limit < 0, there is no limit; */
414         if (limit > 0 && ++count == limit) {
415             seaf_commit_unref (commit);
416             break;
417         }
418 
419         if (stop) {
420             seaf_commit_unref (commit);
421             /* stop traverse down from this commit,
422              * but not stop traversing the tree
423              */
424             continue;
425         }
426 
427         if (commit->parent_id) {
428             if (insert_parent_commit (&list, commit_hash, repo_id, version,
429                                       commit->parent_id, FALSE) < 0) {
430                 if (!skip_errors) {
431                     seaf_commit_unref (commit);
432                     ret = FALSE;
433                     goto out;
434                 }
435             }
436         }
437         if (commit->second_parent_id) {
438             if (insert_parent_commit (&list, commit_hash, repo_id, version,
439                                       commit->second_parent_id, FALSE) < 0) {
440                 if (!skip_errors) {
441                     seaf_commit_unref (commit);
442                     ret = FALSE;
443                     goto out;
444                 }
445             }
446         }
447         seaf_commit_unref (commit);
448     }
449 
450 out:
451     g_hash_table_destroy (commit_hash);
452     while (list) {
453         commit = list->data;
454         seaf_commit_unref (commit);
455         list = g_list_delete_link (list, list);
456     }
457     return ret;
458 }
459 
460 static gboolean
traverse_commit_tree_common(SeafCommitManager * mgr,const char * repo_id,int version,const char * head,CommitTraverseFunc func,void * data,gboolean skip_errors,gboolean allow_truncate)461 traverse_commit_tree_common (SeafCommitManager *mgr,
462                              const char *repo_id,
463                              int version,
464                              const char *head,
465                              CommitTraverseFunc func,
466                              void *data,
467                              gboolean skip_errors,
468                              gboolean allow_truncate)
469 {
470     SeafCommit *commit;
471     GList *list = NULL;
472     GHashTable *commit_hash;
473     gboolean ret = TRUE;
474 
475     commit = seaf_commit_manager_get_commit (mgr, repo_id, version, head);
476     if (!commit) {
477         seaf_warning ("Failed to find commit %s.\n", head);
478         // For head commit corrupted, directly return FALSE
479         // user can repair head by fsck then retraverse the tree
480         return FALSE;
481     }
482 
483     /* A hash table for recording id of traversed commits. */
484     commit_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
485 
486     list = g_list_insert_sorted_with_data (list, commit,
487                                            compare_commit_by_time,
488                                            NULL);
489 
490     char *key = g_strdup (commit->commit_id);
491     g_hash_table_replace (commit_hash, key, key);
492 
493     while (list) {
494         gboolean stop = FALSE;
495         commit = list->data;
496         list = g_list_delete_link (list, list);
497 
498         if (!func (commit, data, &stop)) {
499             seaf_warning("[comit-mgr] CommitTraverseFunc failed\n");
500 
501             /* If skip errors, continue to traverse parents. */
502             if (!skip_errors) {
503                 seaf_commit_unref (commit);
504                 ret = FALSE;
505                 goto out;
506             }
507         }
508         if (stop) {
509             seaf_commit_unref (commit);
510             /* stop traverse down from this commit,
511              * but not stop traversing the tree
512              */
513             continue;
514         }
515 
516         if (commit->parent_id) {
517             if (insert_parent_commit (&list, commit_hash, repo_id, version,
518                                       commit->parent_id, allow_truncate) < 0) {
519                 seaf_warning("[comit-mgr] insert parent commit failed\n");
520 
521                 /* If skip errors, try insert second parent. */
522                 if (!skip_errors) {
523                     seaf_commit_unref (commit);
524                     ret = FALSE;
525                     goto out;
526                 }
527             }
528         }
529         if (commit->second_parent_id) {
530             if (insert_parent_commit (&list, commit_hash, repo_id, version,
531                                       commit->second_parent_id, allow_truncate) < 0) {
532                 seaf_warning("[comit-mgr]insert second parent commit failed\n");
533 
534                 if (!skip_errors) {
535                     seaf_commit_unref (commit);
536                     ret = FALSE;
537                     goto out;
538                 }
539             }
540         }
541         seaf_commit_unref (commit);
542     }
543 
544 out:
545     g_hash_table_destroy (commit_hash);
546     while (list) {
547         commit = list->data;
548         seaf_commit_unref (commit);
549         list = g_list_delete_link (list, list);
550     }
551     return ret;
552 }
553 
554 gboolean
seaf_commit_manager_traverse_commit_tree(SeafCommitManager * mgr,const char * repo_id,int version,const char * head,CommitTraverseFunc func,void * data,gboolean skip_errors)555 seaf_commit_manager_traverse_commit_tree (SeafCommitManager *mgr,
556                                           const char *repo_id,
557                                           int version,
558                                           const char *head,
559                                           CommitTraverseFunc func,
560                                           void *data,
561                                           gboolean skip_errors)
562 {
563     return traverse_commit_tree_common (mgr, repo_id, version, head,
564                                         func, data, skip_errors, FALSE);
565 }
566 
567 gboolean
seaf_commit_manager_traverse_commit_tree_truncated(SeafCommitManager * mgr,const char * repo_id,int version,const char * head,CommitTraverseFunc func,void * data,gboolean skip_errors)568 seaf_commit_manager_traverse_commit_tree_truncated (SeafCommitManager *mgr,
569                                                     const char *repo_id,
570                                                     int version,
571                                                     const char *head,
572                                                     CommitTraverseFunc func,
573                                                     void *data,
574                                                     gboolean skip_errors)
575 {
576     return traverse_commit_tree_common (mgr, repo_id, version, head,
577                                         func, data, skip_errors, TRUE);
578 }
579 
580 gboolean
seaf_commit_manager_commit_exists(SeafCommitManager * mgr,const char * repo_id,int version,const char * id)581 seaf_commit_manager_commit_exists (SeafCommitManager *mgr,
582                                    const char *repo_id,
583                                    int version,
584                                    const char *id)
585 {
586 #if 0
587     commit = g_hash_table_lookup (mgr->priv->commit_cache, id);
588     if (commit != NULL)
589         return TRUE;
590 #endif
591 
592     return seaf_obj_store_obj_exists (mgr->obj_store, repo_id, version, id);
593 }
594 
595 static json_t *
commit_to_json_object(SeafCommit * commit)596 commit_to_json_object (SeafCommit *commit)
597 {
598     json_t *object;
599 
600     object = json_object ();
601 
602     json_object_set_string_member (object, "commit_id", commit->commit_id);
603     json_object_set_string_member (object, "root_id", commit->root_id);
604     json_object_set_string_member (object, "repo_id", commit->repo_id);
605     if (commit->creator_name)
606         json_object_set_string_member (object, "creator_name", commit->creator_name);
607     json_object_set_string_member (object, "creator", commit->creator_id);
608     json_object_set_string_member (object, "description", commit->desc);
609     json_object_set_int_member (object, "ctime", (gint64)commit->ctime);
610     json_object_set_string_or_null_member (object, "parent_id", commit->parent_id);
611     json_object_set_string_or_null_member (object, "second_parent_id",
612                                            commit->second_parent_id);
613     /*
614      * also save repo's properties to commit file, for easy sharing of
615      * repo info
616      */
617     json_object_set_string_member (object, "repo_name", commit->repo_name);
618     json_object_set_string_member (object, "repo_desc",
619                                    commit->repo_desc);
620     json_object_set_string_or_null_member (object, "repo_category",
621                                            commit->repo_category);
622     if (commit->device_name)
623         json_object_set_string_member (object, "device_name", commit->device_name);
624 
625     if (commit->client_version)
626         json_object_set_string_member (object, "client_version", commit->client_version);
627 
628     if (commit->encrypted)
629         json_object_set_string_member (object, "encrypted", "true");
630 
631     if (commit->encrypted) {
632         json_object_set_int_member (object, "enc_version", commit->enc_version);
633         if (commit->enc_version >= 1)
634             json_object_set_string_member (object, "magic", commit->magic);
635         if (commit->enc_version >= 2)
636             json_object_set_string_member (object, "key", commit->random_key);
637         if (commit->enc_version >= 3)
638             json_object_set_string_member (object, "salt", commit->salt);
639     }
640     if (commit->no_local_history)
641         json_object_set_int_member (object, "no_local_history", 1);
642     if (commit->version != 0)
643         json_object_set_int_member (object, "version", commit->version);
644     if (commit->conflict)
645         json_object_set_int_member (object, "conflict", 1);
646     if (commit->new_merge)
647         json_object_set_int_member (object, "new_merge", 1);
648     if (commit->repaired)
649         json_object_set_int_member (object, "repaired", 1);
650 
651     return object;
652 }
653 
654 static SeafCommit *
commit_from_json_object(const char * commit_id,json_t * object)655 commit_from_json_object (const char *commit_id, json_t *object)
656 {
657     SeafCommit *commit = NULL;
658     const char *root_id;
659     const char *repo_id;
660     const char *creator_name = NULL;
661     const char *creator;
662     const char *desc;
663     gint64 ctime;
664     const char *parent_id, *second_parent_id;
665     const char *repo_name;
666     const char *repo_desc;
667     const char *repo_category;
668     const char *device_name;
669     const char *client_version;
670     const char *encrypted = NULL;
671     int enc_version = 0;
672     const char *magic = NULL;
673     const char *random_key = NULL;
674     const char *salt = NULL;
675     int no_local_history = 0;
676     int version = 0;
677     int conflict = 0, new_merge = 0;
678     int repaired = 0;
679 
680     root_id = json_object_get_string_member (object, "root_id");
681     repo_id = json_object_get_string_member (object, "repo_id");
682     if (json_object_has_member (object, "creator_name"))
683         creator_name = json_object_get_string_or_null_member (object, "creator_name");
684     creator = json_object_get_string_member (object, "creator");
685     desc = json_object_get_string_member (object, "description");
686     if (!desc)
687         desc = "";
688     ctime = (guint64) json_object_get_int_member (object, "ctime");
689     parent_id = json_object_get_string_or_null_member (object, "parent_id");
690     second_parent_id = json_object_get_string_or_null_member (object, "second_parent_id");
691 
692     repo_name = json_object_get_string_member (object, "repo_name");
693     if (!repo_name)
694         repo_name = "";
695     repo_desc = json_object_get_string_member (object, "repo_desc");
696     if (!repo_desc)
697         repo_desc = "";
698     repo_category = json_object_get_string_or_null_member (object, "repo_category");
699     device_name = json_object_get_string_or_null_member (object, "device_name");
700     client_version = json_object_get_string_or_null_member (object, "client_version");
701 
702     if (json_object_has_member (object, "encrypted"))
703         encrypted = json_object_get_string_or_null_member (object, "encrypted");
704 
705     if (encrypted && strcmp(encrypted, "true") == 0
706         && json_object_has_member (object, "enc_version")) {
707         enc_version = json_object_get_int_member (object, "enc_version");
708         magic = json_object_get_string_member (object, "magic");
709     }
710 
711     if (enc_version >= 2)
712         random_key = json_object_get_string_member (object, "key");
713     if (enc_version >= 3)
714         salt = json_object_get_string_member (object, "salt");
715 
716     if (json_object_has_member (object, "no_local_history"))
717         no_local_history = json_object_get_int_member (object, "no_local_history");
718 
719     if (json_object_has_member (object, "version"))
720         version = json_object_get_int_member (object, "version");
721     if (json_object_has_member (object, "new_merge"))
722         new_merge = json_object_get_int_member (object, "new_merge");
723 
724     if (json_object_has_member (object, "conflict"))
725         conflict = json_object_get_int_member (object, "conflict");
726 
727     if (json_object_has_member (object, "repaired"))
728         repaired = json_object_get_int_member (object, "repaired");
729 
730 
731     /* sanity check for incoming values. */
732     if (!repo_id || !is_uuid_valid(repo_id)  ||
733         !root_id || !is_object_id_valid(root_id) ||
734         !creator || strlen(creator) != 40 ||
735         (parent_id && !is_object_id_valid(parent_id)) ||
736         (second_parent_id && !is_object_id_valid(second_parent_id)))
737         return commit;
738 
739     switch (enc_version) {
740     case 0:
741         break;
742     case 1:
743         if (!magic || strlen(magic) != 32)
744             return NULL;
745         break;
746     case 2:
747         if (!magic || strlen(magic) != 64)
748             return NULL;
749         if (!random_key || strlen(random_key) != 96)
750             return NULL;
751         break;
752     case 3:
753         if (!magic || strlen(magic) != 64)
754             return NULL;
755         if (!random_key || strlen(random_key) != 96)
756             return NULL;
757         if (!salt || strlen(salt) != 64)
758             return NULL;
759         break;
760     default:
761         seaf_warning ("Unknown encryption version %d.\n", enc_version);
762         return NULL;
763     }
764 
765     char *creator_name_l = creator_name ? g_ascii_strdown (creator_name, -1) : NULL;
766     commit = seaf_commit_new (commit_id, repo_id, root_id,
767                               creator_name_l, creator, desc, ctime);
768     g_free (creator_name_l);
769 
770     commit->parent_id = parent_id ? g_strdup(parent_id) : NULL;
771     commit->second_parent_id = second_parent_id ? g_strdup(second_parent_id) : NULL;
772 
773     commit->repo_name = g_strdup(repo_name);
774     commit->repo_desc = g_strdup(repo_desc);
775     if (encrypted && strcmp(encrypted, "true") == 0)
776         commit->encrypted = TRUE;
777     else
778         commit->encrypted = FALSE;
779     if (repo_category)
780         commit->repo_category = g_strdup(repo_category);
781     commit->device_name = g_strdup(device_name);
782     commit->client_version = g_strdup(client_version);
783 
784     if (commit->encrypted) {
785         commit->enc_version = enc_version;
786         if (enc_version >= 1)
787             commit->magic = g_strdup(magic);
788         if (enc_version >= 2)
789             commit->random_key = g_strdup (random_key);
790         if (enc_version >= 3)
791             commit->salt = g_strdup(salt);
792     }
793     if (no_local_history)
794         commit->no_local_history = TRUE;
795     commit->version = version;
796     if (new_merge)
797         commit->new_merge = TRUE;
798     if (conflict)
799         commit->conflict = TRUE;
800     if (repaired)
801         commit->repaired = TRUE;
802 
803     return commit;
804 }
805 
806 static SeafCommit *
load_commit(SeafCommitManager * mgr,const char * repo_id,int version,const char * commit_id)807 load_commit (SeafCommitManager *mgr,
808              const char *repo_id,
809              int version,
810              const char *commit_id)
811 {
812     char *data = NULL;
813     int len;
814     SeafCommit *commit = NULL;
815     json_t *object = NULL;
816     json_error_t jerror;
817 
818     if (!commit_id || strlen(commit_id) != 40)
819         return NULL;
820 
821     if (seaf_obj_store_read_obj (mgr->obj_store, repo_id, version,
822                                  commit_id, (void **)&data, &len) < 0)
823         return NULL;
824 
825     object = json_loadb (data, len, 0, &jerror);
826     if (!object) {
827         /* Perhaps the commit object contains invalid UTF-8 character. */
828         if (data[len-1] == 0)
829             clean_utf8_data (data, len - 1);
830         else
831             clean_utf8_data (data, len);
832 
833         object = json_loadb (data, len, 0, &jerror);
834         if (!object) {
835             seaf_warning ("Failed to load commit json object: %s.\n", jerror.text);
836             goto out;
837         }
838     }
839 
840     commit = commit_from_json_object (commit_id, object);
841     if (commit)
842         commit->manager = mgr;
843 
844 out:
845     if (object) json_decref (object);
846     g_free (data);
847 
848     return commit;
849 }
850 
851 static int
save_commit(SeafCommitManager * manager,const char * repo_id,int version,SeafCommit * commit)852 save_commit (SeafCommitManager *manager,
853              const char *repo_id,
854              int version,
855              SeafCommit *commit)
856 {
857     json_t *object = NULL;
858     char *data;
859     gsize len;
860 
861     object = commit_to_json_object (commit);
862 
863     data = json_dumps (object, 0);
864     len = strlen (data);
865 
866     json_decref (object);
867 
868 #ifdef SEAFILE_SERVER
869     if (seaf_obj_store_write_obj (manager->obj_store,
870                                   repo_id, version,
871                                   commit->commit_id,
872                                   data, (int)len, TRUE) < 0) {
873         g_free (data);
874         return -1;
875     }
876 #else
877     if (seaf_obj_store_write_obj (manager->obj_store,
878                                   repo_id, version,
879                                   commit->commit_id,
880                                   data, (int)len, FALSE) < 0) {
881         g_free (data);
882         return -1;
883     }
884 #endif
885     free (data);
886 
887     return 0;
888 }
889 
890 static void
delete_commit(SeafCommitManager * mgr,const char * repo_id,int version,const char * id)891 delete_commit (SeafCommitManager *mgr,
892                const char *repo_id,
893                int version,
894                const char *id)
895 {
896     seaf_obj_store_delete_obj (mgr->obj_store, repo_id, version, id);
897 }
898 
899 int
seaf_commit_manager_remove_store(SeafCommitManager * mgr,const char * store_id)900 seaf_commit_manager_remove_store (SeafCommitManager *mgr,
901                                   const char *store_id)
902 {
903     return seaf_obj_store_remove_store (mgr->obj_store, store_id);
904 }
905