csync_reconcile.c 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372
  1. /*
  2. * libcsync -- a library to sync a directory with another
  3. *
  4. * Copyright (c) 2008-2013 by Andreas Schneider <asn@cryptomilk.org>
  5. *
  6. * This library is free software; you can redistribute it and/or
  7. * modify it under the terms of the GNU Lesser General Public
  8. * License as published by the Free Software Foundation; either
  9. * version 2.1 of the License, or (at your option) any later version.
  10. *
  11. * This library is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14. * Lesser General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Lesser General Public
  17. * License along with this library; if not, write to the Free Software
  18. * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  19. */
  20. #include "config_csync.h"
  21. #include "csync_private.h"
  22. #include "csync_reconcile.h"
  23. #include "csync_util.h"
  24. #include "csync_statedb.h"
  25. #include "csync_rename.h"
  26. #include "c_jhash.h"
  27. #define CSYNC_LOG_CATEGORY_NAME "csync.reconciler"
  28. #include "csync_log.h"
  29. #include "inttypes.h"
  30. /* Check if a file is ignored because one parent is ignored.
  31. * return the node of the ignored directoy if it's the case, or NULL if it is not ignored */
  32. static c_rbnode_t *_csync_check_ignored(c_rbtree_t *tree, const char *path, int pathlen) {
  33. uint64_t h = 0;
  34. c_rbnode_t *node = NULL;
  35. /* compute the size of the parent directory */
  36. int parentlen = pathlen - 1;
  37. while (parentlen > 0 && path[parentlen] != '/') {
  38. parentlen--;
  39. }
  40. if (parentlen <= 0) {
  41. return NULL;
  42. }
  43. h = c_jhash64((uint8_t *) path, parentlen, 0);
  44. node = c_rbtree_find(tree, &h);
  45. if (node) {
  46. csync_file_stat_t *n = (csync_file_stat_t*)node->data;
  47. if (n->instruction == CSYNC_INSTRUCTION_IGNORE) {
  48. /* Yes, we are ignored */
  49. return node;
  50. } else {
  51. /* Not ignored */
  52. return NULL;
  53. }
  54. } else {
  55. /* Try if the parent itself is ignored */
  56. return _csync_check_ignored(tree, path, parentlen);
  57. }
  58. }
  59. /*
  60. * We merge replicas at the file level. The merged replica contains the
  61. * superset of files that are on the local machine and server copies of
  62. * the replica. In the case where the same file is in both the local
  63. * and server copy, the file that was modified most recently is used.
  64. * This means that new files are not deleted, and updated versions of
  65. * existing files are not overwritten.
  66. *
  67. * When a file is updated, the merge algorithm compares the destination
  68. * file with the the source file. If the destination file is newer
  69. * (timestamp is newer), it is not overwritten. If both files, on the
  70. * source and the destination, have been changed, the newer file wins.
  71. */
  72. static int _csync_merge_algorithm_visitor(void *obj, void *data) {
  73. csync_file_stat_t *cur = NULL;
  74. csync_file_stat_t *other = NULL;
  75. csync_file_stat_t *tmp = NULL;
  76. uint64_t h = 0;
  77. int len = 0;
  78. CSYNC *ctx = NULL;
  79. c_rbtree_t *tree = NULL;
  80. c_rbnode_t *node = NULL;
  81. cur = (csync_file_stat_t *) obj;
  82. ctx = (CSYNC *) data;
  83. /* we need the opposite tree! */
  84. switch (ctx->current) {
  85. case LOCAL_REPLICA:
  86. tree = ctx->remote.tree;
  87. break;
  88. case REMOTE_REPLICA:
  89. tree = ctx->local.tree;
  90. break;
  91. default:
  92. break;
  93. }
  94. node = c_rbtree_find(tree, &cur->phash);
  95. if (!node) {
  96. /* Check the renamed path as well. */
  97. char *renamed_path = csync_rename_adjust_path(ctx, cur->path);
  98. if (!c_streq(renamed_path, cur->path)) {
  99. len = strlen( renamed_path );
  100. h = c_jhash64((uint8_t *) renamed_path, len, 0);
  101. node = c_rbtree_find(tree, &h);
  102. }
  103. SAFE_FREE(renamed_path);
  104. }
  105. if (!node) {
  106. /* Check if it is ignored */
  107. node = _csync_check_ignored(tree, cur->path, cur->pathlen);
  108. /* If it is ignored, other->instruction will be IGNORE so this one will also be ignored */
  109. }
  110. /* file only found on current replica */
  111. if (node == NULL) {
  112. switch(cur->instruction) {
  113. /* file has been modified */
  114. case CSYNC_INSTRUCTION_EVAL:
  115. cur->instruction = CSYNC_INSTRUCTION_NEW;
  116. break;
  117. /* file has been removed on the opposite replica */
  118. case CSYNC_INSTRUCTION_NONE:
  119. if (cur->has_ignored_files) {
  120. /* Do not remove a directory that has ignored files */
  121. break;
  122. }
  123. if (cur->child_modified) {
  124. /* re-create directory that has modified contents */
  125. cur->instruction = CSYNC_INSTRUCTION_NEW;
  126. break;
  127. }
  128. cur->instruction = CSYNC_INSTRUCTION_REMOVE;
  129. break;
  130. case CSYNC_INSTRUCTION_EVAL_RENAME:
  131. if(ctx->current == LOCAL_REPLICA ) {
  132. /* use the old name to find the "other" node */
  133. tmp = csync_statedb_get_stat_by_inode(ctx, cur->inode);
  134. CSYNC_LOG(CSYNC_LOG_PRIORITY_TRACE, "Finding opposite temp through inode %" PRIu64 ": %s",
  135. cur->inode, tmp ? "true":"false");
  136. } else if( ctx->current == REMOTE_REPLICA ) {
  137. tmp = csync_statedb_get_stat_by_file_id(ctx, cur->file_id);
  138. CSYNC_LOG(CSYNC_LOG_PRIORITY_TRACE, "Finding opposite temp through file ID %s: %s",
  139. cur->file_id, tmp ? "true":"false");
  140. } else {
  141. CSYNC_LOG(CSYNC_LOG_PRIORITY_DEBUG, "Unknown replica...");
  142. }
  143. if( tmp ) {
  144. len = strlen( tmp->path );
  145. if( len > 0 ) {
  146. h = c_jhash64((uint8_t *) tmp->path, len, 0);
  147. /* First, check that the file is NOT in our tree (another file with the same name was added) */
  148. node = c_rbtree_find(ctx->current == REMOTE_REPLICA ? ctx->remote.tree : ctx->local.tree, &h);
  149. if (node) {
  150. CSYNC_LOG(CSYNC_LOG_PRIORITY_TRACE, "Origin found in our tree : %s", tmp->path);
  151. } else {
  152. /* Find the temporar file in the other tree. */
  153. node = c_rbtree_find(tree, &h);
  154. CSYNC_LOG(CSYNC_LOG_PRIORITY_TRACE, "PHash of temporary opposite (%s): %" PRIu64 " %s",
  155. tmp->path , h, node ? "found": "not found" );
  156. if (node) {
  157. other = (csync_file_stat_t*)node->data;
  158. } else {
  159. /* the renamed file could not be found in the opposite tree. That is because it
  160. * is not longer existing there, maybe because it was renamed or deleted.
  161. * The journal is cleaned up later after propagation.
  162. */
  163. }
  164. }
  165. }
  166. if(!other) {
  167. cur->instruction = CSYNC_INSTRUCTION_NEW;
  168. if (cur->type == CSYNC_FTW_TYPE_DIR) {
  169. // For new directories we always want to update the etag once
  170. // the directory has been propagated. Otherwise the directory
  171. // could appear locally without being added to the database.
  172. cur->should_update_metadata = true;
  173. }
  174. } else if (other->instruction == CSYNC_INSTRUCTION_NONE
  175. || cur->type == CSYNC_FTW_TYPE_DIR) {
  176. other->instruction = CSYNC_INSTRUCTION_RENAME;
  177. other->destpath = c_strdup( cur->path );
  178. if( !c_streq(cur->file_id, "") ) {
  179. csync_vio_set_file_id( other->file_id, cur->file_id );
  180. }
  181. other->inode = cur->inode;
  182. other->should_update_metadata = true;
  183. cur->instruction = CSYNC_INSTRUCTION_NONE;
  184. } else if (other->instruction == CSYNC_INSTRUCTION_REMOVE) {
  185. other->instruction = CSYNC_INSTRUCTION_RENAME;
  186. other->destpath = c_strdup( cur->path );
  187. if( !c_streq(cur->file_id, "") ) {
  188. csync_vio_set_file_id( other->file_id, cur->file_id );
  189. }
  190. other->inode = cur->inode;
  191. other->should_update_metadata = true;
  192. cur->instruction = CSYNC_INSTRUCTION_NONE;
  193. } else if (other->instruction == CSYNC_INSTRUCTION_NEW) {
  194. CSYNC_LOG(CSYNC_LOG_PRIORITY_TRACE, "OOOO=> NEW detected in other tree!");
  195. cur->instruction = CSYNC_INSTRUCTION_CONFLICT;
  196. } else {
  197. cur->instruction = CSYNC_INSTRUCTION_NONE;
  198. other->instruction = CSYNC_INSTRUCTION_SYNC;
  199. }
  200. csync_file_stat_free(tmp);
  201. }
  202. break;
  203. default:
  204. break;
  205. }
  206. } else {
  207. bool is_equal_files = false;
  208. /*
  209. * file found on the other replica
  210. */
  211. other = (csync_file_stat_t *) node->data;
  212. switch (cur->instruction) {
  213. case CSYNC_INSTRUCTION_EVAL_RENAME:
  214. /* If the file already exist on the other side, we have a conflict.
  215. Abort the rename and consider it is a new file. */
  216. cur->instruction = CSYNC_INSTRUCTION_NEW;
  217. /* fall trough */
  218. /* file on current replica is changed or new */
  219. case CSYNC_INSTRUCTION_EVAL:
  220. case CSYNC_INSTRUCTION_NEW:
  221. // This operation is usually a no-op and will by default return false
  222. if (csync_file_locked_or_open(ctx->local.uri, cur->path)) {
  223. CSYNC_LOG(CSYNC_LOG_PRIORITY_DEBUG, "[Reconciler] IGNORING file %s/%s since it is locked / open", ctx->local.uri, cur->path);
  224. cur->instruction = CSYNC_INSTRUCTION_ERROR;
  225. if (cur->error_status == CSYNC_STATUS_OK) // don't overwrite error
  226. cur->error_status = CYSNC_STATUS_FILE_LOCKED_OR_OPEN;
  227. break;
  228. } else {
  229. //CSYNC_LOG(CSYNC_LOG_PRIORITY_DEBUG, "[Reconciler] not ignoring file %s/%s", ctx->local.uri, cur->path);
  230. }
  231. switch (other->instruction) {
  232. /* file on other replica is changed or new */
  233. case CSYNC_INSTRUCTION_NEW:
  234. case CSYNC_INSTRUCTION_EVAL:
  235. if (other->type == CSYNC_FTW_TYPE_DIR &&
  236. cur->type == CSYNC_FTW_TYPE_DIR) {
  237. is_equal_files = (other->modtime == cur->modtime);
  238. } else {
  239. is_equal_files = ((other->size == cur->size) && (other->modtime == cur->modtime));
  240. // FIXME: do a binary comparision of the file here because of the following
  241. // edge case:
  242. // The files could still have different content, even though the mtime
  243. // and size are the same.
  244. }
  245. if (is_equal_files) {
  246. /* The files are considered equal. */
  247. cur->instruction = CSYNC_INSTRUCTION_NONE;
  248. other->instruction = CSYNC_INSTRUCTION_NONE;
  249. /* update DB with new etag from remote */
  250. if (ctx->current == LOCAL_REPLICA) {
  251. other->should_update_metadata = true;
  252. } else {
  253. cur->should_update_metadata = true;
  254. }
  255. } else if(ctx->current == REMOTE_REPLICA) {
  256. cur->instruction = CSYNC_INSTRUCTION_CONFLICT;
  257. other->instruction = CSYNC_INSTRUCTION_NONE;
  258. } else {
  259. cur->instruction = CSYNC_INSTRUCTION_NONE;
  260. other->instruction = CSYNC_INSTRUCTION_CONFLICT;
  261. }
  262. break;
  263. /* file on the other replica has not been modified */
  264. case CSYNC_INSTRUCTION_NONE:
  265. if (cur->type != other->type) {
  266. // If the type of the entity changed, it's like NEW, but
  267. // needs to delete the other entity first.
  268. cur->instruction = CSYNC_INSTRUCTION_TYPE_CHANGE;
  269. } else {
  270. cur->instruction = CSYNC_INSTRUCTION_SYNC;
  271. }
  272. break;
  273. case CSYNC_INSTRUCTION_IGNORE:
  274. cur->instruction = CSYNC_INSTRUCTION_IGNORE;
  275. break;
  276. default:
  277. break;
  278. }
  279. default:
  280. break;
  281. }
  282. }
  283. //hide instruction NONE messages when log level is set to debug,
  284. //only show these messages on log level trace
  285. const char *repo = ctx->current == REMOTE_REPLICA ? "server" : "client";
  286. if(cur->instruction ==CSYNC_INSTRUCTION_NONE)
  287. {
  288. if(cur->type == CSYNC_FTW_TYPE_DIR)
  289. {
  290. CSYNC_LOG(CSYNC_LOG_PRIORITY_TRACE,
  291. "%-20s %s dir: %s",
  292. csync_instruction_str(cur->instruction),
  293. repo,
  294. cur->path);
  295. }
  296. else
  297. {
  298. CSYNC_LOG(CSYNC_LOG_PRIORITY_TRACE,
  299. "%-20s %s file: %s",
  300. csync_instruction_str(cur->instruction),
  301. repo,
  302. cur->path);
  303. }
  304. }
  305. else
  306. {
  307. if(cur->type == CSYNC_FTW_TYPE_DIR)
  308. {
  309. CSYNC_LOG(CSYNC_LOG_PRIORITY_DEBUG,
  310. "%-20s %s dir: %s",
  311. csync_instruction_str(cur->instruction),
  312. repo,
  313. cur->path);
  314. }
  315. else
  316. {
  317. CSYNC_LOG(CSYNC_LOG_PRIORITY_DEBUG,
  318. "%-20s %s file: %s",
  319. csync_instruction_str(cur->instruction),
  320. repo,
  321. cur->path);
  322. }
  323. }
  324. return 0;
  325. }
  326. int csync_reconcile_updates(CSYNC *ctx) {
  327. int rc;
  328. c_rbtree_t *tree = NULL;
  329. switch (ctx->current) {
  330. case LOCAL_REPLICA:
  331. tree = ctx->local.tree;
  332. break;
  333. case REMOTE_REPLICA:
  334. tree = ctx->remote.tree;
  335. break;
  336. default:
  337. break;
  338. }
  339. rc = c_rbtree_walk(tree, (void *) ctx, _csync_merge_algorithm_visitor);
  340. if( rc < 0 ) {
  341. ctx->status_code = CSYNC_STATUS_RECONCILE_ERROR;
  342. }
  343. return rc;
  344. }
  345. /* vim: set ts=8 sw=2 et cindent: */