update.ml (132198B)
1 (* Unison file synchronizer: src/update.ml *) 2 (* Copyright 1999-2020, Benjamin C. Pierce 3 4 This program is free software: you can redistribute it and/or modify 5 it under the terms of the GNU General Public License as published by 6 the Free Software Foundation, either version 3 of the License, or 7 (at your option) any later version. 8 9 This program is distributed in the hope that it will be useful, 10 but WITHOUT ANY WARRANTY; without even the implied warranty of 11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 GNU General Public License for more details. 13 14 You should have received a copy of the GNU General Public License 15 along with this program. If not, see <http://www.gnu.org/licenses/>. 16 *) 17 18 open Common 19 let (>>=) = Lwt.(>>=) 20 21 let debug = Trace.debug "update" 22 let debugverbose = Trace.debug "update+" 23 let debugalias = Trace.debug "rootalias" 24 let debugignore = Trace.debug "ignore" 25 26 let ignoreArchives = 27 Prefs.createBool "ignorearchives" false 28 ~category:(`Advanced `Archive) 29 "ignore existing archive files" 30 ("When this preference is set, Unison will ignore any existing " 31 ^ "archive files and behave as though it were being run for the first " 32 ^ "time on these replicas. It is " 33 ^ "not a good idea to set this option in a profile: it is intended for " 34 ^ "command-line use.") 35 36 (*****************************************************************************) 37 (* ARCHIVE DATATYPE *) 38 (*****************************************************************************) 39 40 (* Remember to increment archiveFormat each time the representation of the 41 archive changes: old archives will then automatically be discarded. (We 42 do not use the unison version number for this because usually the archive 43 representation does not change between unison versions.) *) 44 (*FIX: consider changing the way case-sensitivity mode is stored in 45 the archive *) 46 let archiveFormat = 23 47 48 module NameMap = MyMap.Make (Name) 49 50 (* IMPORTANT! 51 This is the 2.51-compatible version of type [archive]. It must always remain 52 exactly the same as the type [archive] in version 2.51.5. This means that if 53 any of the types it is composed of changes, for each changed type a 2.51- 54 compatible version must be created (like has been done for [Props.t]). *) 55 type archive251 = 56 ArchiveDir of Props.t251 * archive251 NameMap.t 57 | ArchiveFile of Props.t251 * Os.fullfingerprint * Fileinfo.stamp251 * Osx.ressStamp 58 | ArchiveSymlink of string 59 | NoArchive 60 61 type archive = 62 ArchiveDir of Props.t * archive NameMap.t 63 | ArchiveFile of Props.t * Os.fullfingerprint * Fileinfo.stamp * Osx.ressStamp 64 | ArchiveSymlink of string 65 | NoArchive 66 67 let marchive_rec marchive = 68 Umarshal.(sum4 69 (prod2 Props.m (NameMap.m marchive) id id) 70 (prod4 Props.m Os.mfullfingerprint Fileinfo.mstamp Osx.mressStamp id id) 71 string unit 72 (function 73 | ArchiveDir (a, b) -> I41 (a, b) 74 | ArchiveFile (a, b, c, d) -> I42 (a, b, c, d) 75 | ArchiveSymlink a -> I43 a 76 | NoArchive -> I44 ()) 77 (function 78 | I41 (a, b) -> ArchiveDir (a, b) 79 | I42 (a, b, c, d) -> ArchiveFile (a, b, c, d) 80 | I43 a -> ArchiveSymlink a 81 | I44 () -> NoArchive)) 82 83 let marchive = Umarshal.rec1 marchive_rec 84 85 (* For directories, only the permissions part of the file description (desc) 86 is used for synchronization at the moment. *) 87 88 let rec to_compat251 (arch : archive) : archive251 = 89 match arch with 90 | ArchiveDir (desc, children) -> 91 ArchiveDir (Props.to_compat251 desc, NameMap.map to_compat251 children) 92 | ArchiveFile (desc, dig, stamp, ress) -> 93 ArchiveFile (Props.to_compat251 desc, dig, Fileinfo.stamp_to_compat251 stamp, ress) 94 | ArchiveSymlink content -> ArchiveSymlink content 95 | NoArchive -> NoArchive 96 97 let rec of_compat251 (arch : archive251) : archive = 98 match arch with 99 | ArchiveDir (desc, children) -> 100 ArchiveDir (Props.of_compat251 desc, NameMap.map of_compat251 children) 101 | ArchiveFile (desc, dig, stamp, ress) -> 102 ArchiveFile (Props.of_compat251 desc, dig, Fileinfo.stamp_of_compat251 stamp, ress) 103 | ArchiveSymlink content -> ArchiveSymlink content 104 | NoArchive -> NoArchive 105 106 let archive2string = function 107 ArchiveDir(_) -> "ArchiveDir" 108 | ArchiveFile(_) -> "ArchiveFile" 109 | ArchiveSymlink(_) -> "ArchiveSymlink" 110 | NoArchive -> "NoArchive" 111 112 (*****************************************************************************) 113 (* ARCHIVE NAMING *) 114 (*****************************************************************************) 115 116 (* DETERMINING THE ARCHIVE NAME *) 117 118 (* The canonical name of a root consists of its canonical host name and 119 canonical fspath. 120 121 The canonical name of a set of roots consists of the canonical names of 122 the roots in sorted order. 123 124 There is one archive for each root to be synchronized. The canonical 125 name of the archive is the canonical name of the root plus the canonical 126 name of the set of all roots to be synchronized. Because this is a long 127 string we store the archive in a file whose name is the hash of the 128 canonical archive name. 129 130 For example, suppose we are synchronizing roots A and B, with canonical 131 names A' and B', where A' < B'. Then the canonical archive name for root 132 A is A' + A' + B', and the canonical archive name for root B is B' + A' + 133 B'. 134 135 Currently, we determine A' + B' during startup and store this in the 136 ref cell rootsName, below. This rootsName is passed as an argument to 137 functions that need to determine a canonical archive name. Note, since 138 we have a client/server architecture, there are TWO rootsName ref cells 139 (one in the client's address space, one in the server's). It is vital 140 therefore that the rootsName be determined on the client and passed to 141 the server. This is not good and we should get rid of the ref cell in 142 the future; we have implemented it this way at first for historical 143 reasons. *) 144 145 let rootsName : string Prefs.t = 146 Prefs.createString "rootsName" "" 147 ~category:(`Internal `Pseudo) 148 "*Canonical root names" "" 149 150 let getRootsName () = Prefs.read rootsName 151 152 let foundArchives = ref true 153 154 (*****************************************************************************) 155 (* COMMON DEFINITIONS *) 156 (*****************************************************************************) 157 158 let rootAliases : string list Prefs.t = 159 Prefs.createStringList "rootalias" 160 ~category:(`Advanced `General) 161 "register alias for canonical root names" 162 ("When calculating the name of the archive files for a given pair of roots," 163 ^ " Unison replaces any roots matching the left-hand side of any rootalias" 164 ^ " rule by the corresponding right-hand side.") 165 166 (* [root2stringOrAlias root] returns the string form of [root], taking into 167 account the preference [rootAliases], whose items are of the form `<a> -> 168 <b>' *) 169 let root2stringOrAlias (root: Common.root): string = 170 let r = Common.root2string root in 171 let aliases : (string * string) list = 172 Safelist.map 173 (fun s -> match Util.splitIntoWordsByString s " -> " with 174 [n;n'] -> (Util.trimWhitespace n, Util.trimWhitespace n') 175 | _ -> raise (Util.Fatal (Printf.sprintf 176 "rootalias %s should be two strings separated by ' -> '" s))) 177 (Prefs.read rootAliases) in 178 let r' = try Safelist.assoc r aliases with Not_found -> r in 179 if r<>r' then debugalias (fun()-> 180 Util.msg "Canonical root name %s is aliased to %s\n" r r'); 181 r' 182 183 (* (Called from the UI startup sequence...) `normalize' root names, 184 sort them, get their string form, and put into the preference [rootsname] 185 as a comma-separated string *) 186 let storeRootsName () = 187 let n = 188 String.concat ", " 189 (Safelist.sort compare 190 (Safelist.map root2stringOrAlias 191 (Safelist.map 192 (function 193 (Common.Local,f) -> 194 (Common.Remote (Os.myCanonicalHostName ()),f) 195 | r -> 196 r) 197 (Globals.rootsInCanonicalOrder())))) in 198 Prefs.set rootsName n 199 200 let thisRootsGlobalName (fspath: Fspath.t): string = 201 root2stringOrAlias (Common.Remote (Os.myCanonicalHostName ()), fspath) 202 203 (* ----- *) 204 205 (* The status of an archive *) 206 type archiveVersion = MainArch | NewArch | ScratchArch | Lock | FPCache 207 208 let marchiveVersion = Umarshal.(sum5 unit unit unit unit unit 209 (function 210 | MainArch -> I51 () 211 | NewArch -> I52 () 212 | ScratchArch -> I53 () 213 | Lock -> I54 () 214 | FPCache -> I55 ()) 215 (function 216 | I51 () -> MainArch 217 | I52 () -> NewArch 218 | I53 () -> ScratchArch 219 | I54 () -> Lock 220 | I55 () -> FPCache)) 221 222 let showArchiveName = 223 Prefs.createBool "showarchive" false 224 ~category:(`Advanced `General) 225 "show 'true names' (for rootalias) of roots and archive" 226 ("When this preference is set, Unison will print out the 'true names'" 227 ^ "of the roots, in the same form as is expected by the {\\tt rootalias} " 228 ^ "preference.") 229 230 let _ = Prefs.alias showArchiveName "showArchiveName" 231 232 let archiveHash fspath = 233 (* Conjoin the canonical name of the current host and the canonical 234 presentation of the current fspath with the list of names/fspaths of 235 all the roots and the current archive format *) 236 let thisRoot = thisRootsGlobalName fspath in 237 let r = Prefs.read rootsName in 238 let n = Printf.sprintf "%s;%s;%d" thisRoot r archiveFormat in 239 let d = Digest.to_hex (Digest.string n) in 240 debugverbose (fun()-> Util.msg "Archive name is %s; hashcode is %s\n" n d); 241 if Prefs.read showArchiveName then 242 Util.msg "Archive name is %s; hashcode is %s\n" n d; 243 d 244 245 (* We include the hash part of the archive name in the names of temp files 246 created by this run of Unison. The reason for this is that, during 247 update detection, we are going to silently delete any old temp files that 248 we find along the way, and we want to prevent ourselves from deleting 249 temp files belonging to other instances of Unison that may be running 250 in parallel, e.g. synchronizing with a different host. *) 251 let addHashToTempNames fspath = Os.includeInTempNames (archiveHash fspath) 252 253 (* [archiveName fspath] returns a pair (arcName, thisRootsGlobalName) *) 254 let archiveName fspath (v: archiveVersion): string * string = 255 let n = archiveHash fspath in 256 let temp = match v with 257 MainArch -> "ar" | NewArch -> "tm" | ScratchArch -> "sc" 258 | Lock -> "lk" | FPCache -> "fp" 259 in 260 (Printf.sprintf "%s%s" temp n, 261 thisRootsGlobalName fspath) 262 263 (* IMPORTANT! 264 This is the 2.51-compatible version of [archiveName]. It must produce 265 exactly the same result as [archiveName] would in version 2.51.5. 266 If code changes elsewhere make this function produce a different result then 267 it must be updated accordingly to again return the 2.51-compatible result. 268 269 This code is here only to support a smooth upgrade from versions <= 2.51.5 270 It is safe to delete it when that support is no longer required. *) 271 let archiveName251 fspath (v: archiveVersion): string * string = 272 let archiveHash251 fspath = 273 (* How many characters of the filename should be used for the unique id of 274 the archive? On Unix systems, we use the full fingerprint (32 bytes). 275 On windows systems, filenames longer than 8 bytes can cause problems, so 276 we chop off all but the first 6 from the fingerprint. *) 277 let significantDigits = 278 match Sys.unix with 279 | false -> 6 280 | true -> 32 281 in 282 let thisRoot = thisRootsGlobalName fspath in 283 let r = Prefs.read rootsName in 284 let n = Printf.sprintf "%s;%s;22" thisRoot r in 285 let d = Digest.to_hex (Digest.string n) in 286 (String.sub d 0 significantDigits) 287 in 288 let n = archiveHash251 fspath in 289 let temp = match v with 290 MainArch -> "ar" | NewArch -> "tm" | ScratchArch -> "sc" 291 | Lock -> "lk" | FPCache -> "fp" 292 in 293 (Printf.sprintf "%s%s" temp n, 294 thisRootsGlobalName fspath) 295 296 297 (*****************************************************************************) 298 (* SANITY CHECKS *) 299 (*****************************************************************************) 300 301 (* [checkArchive] checks the sanity of an archive, and returns its 302 hash-value. 'Sanity' means (1) no repeated name under any path, and (2) 303 NoArchive appears only at root-level (indicated by [top]). Property: Two 304 archives of the same labeled-tree structure have the same hash-value. 305 NB: [h] is the hash accumulator *) 306 (* Note that we build the current path as a list of names, as this is 307 much cheaper than using values of type [Path.t] *) 308 let rec checkArchive 309 (top: bool) (path: Name.t list) (arch: archive) (h: int): int = 310 match arch with 311 ArchiveDir (desc, children) -> 312 begin match NameMap.validate children with 313 `Ok -> 314 () 315 | `Duplicate nm -> 316 let path = 317 List.fold_right (fun n p -> Path.child p n) path Path.empty in 318 raise 319 (Util.Fatal (Printf.sprintf 320 "Corrupted archive: \ 321 the file %s occurs twice in path %s" 322 (Name.toString nm) (Path.toString path))); 323 | `Invalid (nm, nm') -> 324 let path = 325 List.fold_right (fun n p -> Path.child p n) path Path.empty in 326 raise 327 (Util.Fatal (Printf.sprintf 328 "Corrupted archive: the files %s and %s are not \ 329 correctly ordered in directory %s" 330 (Name.toString nm) (Name.toString nm') 331 (Path.toString path))); 332 end; 333 NameMap.fold 334 (fun n a h -> 335 Uutil.hash2 (Name.hash n) 336 (checkArchive false (n :: path) a h)) 337 children (Props.hash desc h) 338 | ArchiveFile (desc, dig, _, ress) -> 339 Uutil.hash2 (Uutil.hash dig) (Props.hash desc h) 340 | ArchiveSymlink content -> 341 Uutil.hash2 (Uutil.hash content) h 342 | NoArchive -> 343 135 344 345 (* IMPORTANT! 346 This is the 2.51-compatible version of [checkArchive]. It must produce 347 exactly the same result as [checkArchive] in version 2.51.5. 348 If code changes elsewhere make this function produce a different result then 349 it must be updated accordingly to again return the 2.51-compatible result. *) 350 let rec checkArchive251 351 (top: bool) (path: Name.t list) (arch: archive251) (h: int): int = 352 match arch with 353 ArchiveDir (desc, children) -> 354 begin match NameMap.validate children with 355 `Ok -> 356 () 357 | `Duplicate nm -> 358 let path = 359 List.fold_right (fun n p -> Path.child p n) path Path.empty in 360 raise 361 (Util.Fatal (Printf.sprintf 362 "Corrupted archive: \ 363 the file %s occurs twice in path %s" 364 (Name.toString nm) (Path.toString path))); 365 | `Invalid (nm, nm') -> 366 let path = 367 List.fold_right (fun n p -> Path.child p n) path Path.empty in 368 raise 369 (Util.Fatal (Printf.sprintf 370 "Corrupted archive: the files %s and %s are not \ 371 correctly ordered in directory %s" 372 (Name.toString nm) (Name.toString nm') 373 (Path.toString path))); 374 end; 375 NameMap.fold 376 (fun n a h -> 377 Uutil.hash2 (Name.hash n) 378 (checkArchive251 false (n :: path) a h)) 379 children (Props.hash251 desc h) 380 | ArchiveFile (desc, dig, _, ress) -> 381 Uutil.hash2 (Uutil.hash dig) (Props.hash251 desc h) 382 | ArchiveSymlink content -> 383 Uutil.hash2 (Uutil.hash content) h 384 | NoArchive -> 385 135 386 387 (* [archivesIdentical l] returns true if all elements in [l] are the 388 same and distinct from None *) 389 let archivesIdentical l = 390 match l with 391 h::r -> h <> None && Safelist.for_all (fun h' -> h = h') r 392 | _ -> true 393 394 let (archiveNameOnRoot 395 : Common.root -> archiveVersion -> (string * string * bool) Lwt.t) 396 = 397 Remote.registerRootCmd 398 "archiveName" marchiveVersion Umarshal.(prod3 string string bool id id) 399 (fun (fspath, v) -> 400 let (name,_) = archiveName fspath v in 401 Lwt.return 402 (name, 403 Os.myCanonicalHostName (), 404 System.file_exists (Util.fileInUnisonDir name))) 405 406 407 (*****************************************************************************) 408 (* LOADING AND SAVING ARCHIVES *) 409 (*****************************************************************************) 410 411 (* [formatString] and [verboseArchiveName thisRoot] are the verbose forms of 412 archiveFormat and root names. They appear in the header of the archive 413 files *) 414 let formatString = Printf.sprintf "Unison archive format %d" archiveFormat 415 let compatFormatString = "Unison archive format 22" 416 (* Every supported version released prior to the new archive encoding 417 uses this archive format string. *) 418 419 let verboseArchiveName thisRoot = 420 Printf.sprintf "Archive for root %s synchronizing roots %s" 421 thisRoot (Prefs.read rootsName) 422 423 module PathMap = MyMap.Make (Path) 424 425 let mpaths = PathMap.m Proplist.m 426 427 let propPathKey : Proplist.t PathMap.t Proplist.key = 428 Proplist.register "paths" mpaths 429 430 let mpayload = Umarshal.prod4 431 marchive Umarshal.int Umarshal.string Proplist.m 432 Umarshal.id Umarshal.id 433 434 (* Load in the archive in [fspath]; check that archiveFormat (first line) 435 and roots (second line) match skip the third line (time stamp), and read 436 in the archive *) 437 let loadArchiveLocal fspath (thisRoot: string) : 438 (archive * int * string * Proplist.t) option = 439 debug (fun() -> 440 Util.msg "Loading archive from %s\n" (System.fspathToDebugString fspath)); 441 Util.convertUnixErrorsToFatal "loading archive" (fun () -> 442 if System.file_exists fspath then 443 let c = System.open_in_bin fspath in 444 let close_on_error f = 445 try f () with e -> close_in_noerr c; raise e 446 in 447 close_on_error (fun () -> 448 let header = input_line c in 449 (* Sanity check on archive format *) 450 if header<>formatString then begin 451 Util.warn 452 (Printf.sprintf 453 "Archive format mismatch: found\n '%s'\n\ 454 but expected\n '%s'.\n\ 455 I will delete the old archive and start from scratch.\n" 456 header formatString); 457 None 458 end else 459 let roots = input_line c in 460 (* Sanity check on roots. *) 461 if roots <> verboseArchiveName thisRoot then begin 462 Util.warn 463 (Printf.sprintf 464 "Archive mismatch: found\n '%s'\n\ 465 but expected\n '%s'.\n\ 466 I will delete the old archive and start from scratch.\n" 467 roots (verboseArchiveName thisRoot)); 468 None 469 end else 470 let featrs = 471 match String.split_on_char '\030' (input_line c) with 472 | [] -> [] (* This is not possible, but compiler doesn't know it *) 473 | _ :: rest -> (* Ignore the first part of the timestamp line *) 474 Safelist.filter (fun x -> x <> "") rest 475 in 476 let commonFts = Features.inter featrs (Features.all ()) in 477 if Safelist.length featrs <> Safelist.length commonFts then 478 raise 479 (Util.Fatal ("Archive format mismatch: the archive was stored with \ 480 features that are currently not available.\n\ 481 Missing features: " 482 ^ (String.concat ", " (Safelist.filter 483 (fun x -> not (Safelist.mem x commonFts)) featrs)) 484 ^ "\nArchive file: " 485 ^ fspath ^ "\n\ 486 You should either upgrade Unison or invoke Unison \ 487 once with -ignorearchives flag and then try again.")); 488 try 489 (* Temporarily enable features that were used when storing the archive 490 to make sure the types are correct when loading the archive. *) 491 let negotiatedFts = Features.getEnabled () in 492 let () = Features.setEnabled commonFts in 493 (* Load the datastructure *) 494 let ((archive, hash, magic, properties) : archive * int * string * Proplist.t) = 495 Umarshal.from_channel mpayload c in 496 (* "paths" is stored separately to keep the archive file readable 497 for versions <= 2.53.3 *) 498 let properties = 499 try 500 let paths = Umarshal.from_channel mpaths c in 501 Proplist.add propPathKey paths properties 502 with End_of_file -> properties 503 in 504 close_in c; 505 (* Restore to the negotiated features *) 506 let () = Features.setEnabled negotiatedFts in 507 Some (archive, hash, magic, properties) 508 with Failure s | Umarshal.Error s -> raise (Util.Fatal (Printf.sprintf 509 "Archive file seems damaged (%s): \ 510 use the -ignorearchives option, or \ 511 throw away archives on both machines and try again" s))) 512 else 513 (debug (fun() -> 514 Util.msg "Archive %s not found\n" 515 (System.fspathToDebugString fspath)); 516 None)) 517 518 (* IMPORTANT! 519 This is the 2.51-compatible version of [loadArchiveLocal]. It must remain 520 capable of reading archives written by version 2.51.5. Be careful, as code 521 changes elsewhere may break this function unintentionally. 522 523 This code is here only to support a smooth upgrade from versions <= 2.51.5 524 It is safe to delete it when that support is no longer required. *) 525 let loadArchiveLocal251 fspath (thisRoot: string) : 526 (archive * int * string * Proplist.t) option = 527 debug (fun() -> 528 Util.msg "Loading archive from %s\n" (System.fspathToDebugString fspath)); 529 Util.convertUnixErrorsToFatal "loading archive" (fun () -> 530 if System.file_exists fspath then 531 let c = System.open_in_bin fspath in 532 let header = input_line c in 533 (* Sanity check on archive format *) 534 if header<>compatFormatString then begin 535 debug (fun () -> 536 Util.msg 537 "Archive format mismatch: found\n '%s'\n\ 538 but expected\n '%s'.\n\ 539 I will delete the old archive and start from scratch.\n" 540 header compatFormatString); 541 None 542 end else 543 let roots = input_line c in 544 (* Sanity check on roots. *) 545 if roots <> verboseArchiveName thisRoot then begin 546 debug (fun () -> 547 Util.msg 548 "Archive mismatch: found\n '%s'\n\ 549 but expected\n '%s'.\n\ 550 I will delete the old archive and start from scratch.\n" 551 roots (verboseArchiveName thisRoot)); 552 None 553 end else 554 (* Throw away the timestamp line *) 555 let _ = input_line c in 556 (* Load the datastructure *) 557 try 558 let ((archive, hash, magic) : archive251 * int * string) = 559 Marshal.from_channel c in 560 let properties = 561 try 562 ignore (input_char c); (* Marker *) 563 Marshal.from_channel c 564 with End_of_file -> 565 Proplist.empty 566 in 567 close_in c; 568 Some (of_compat251 archive, hash, magic, properties) 569 with Failure s -> raise (Util.Fatal (Printf.sprintf 570 "Archive file seems damaged (%s): \ 571 use the -ignorearchives option, or throw away archives on both machines and try again" s)) 572 else 573 (debug (fun() -> 574 Util.msg "Archive %s not found\n" 575 (System.fspathToDebugString fspath)); 576 None)) 577 578 (* Inverse to loadArchiveLocal *) 579 let storeArchiveLocal fspath thisRoot archive hash magic properties = 580 debug (fun() -> 581 Util.msg "Saving archive in %s\n" (System.fspathToDebugString fspath)); 582 Util.convertUnixErrorsToFatal "saving archive" (fun () -> 583 let c = 584 System.open_out_gen 585 [Open_wronly; Open_creat; Open_trunc; Open_binary] 0o600 fspath 586 in 587 let close_on_error f = 588 try f () with e -> close_out_noerr c; raise e 589 in 590 close_on_error (fun () -> 591 output_string c formatString; 592 output_string c "\n"; 593 output_string c (verboseArchiveName thisRoot); 594 output_string c "\n"; 595 (* First part of third line is purely informative *) 596 output_string c (Printf.sprintf "Written at %s - %s mode" 597 (Util.time2string (Util.time())) 598 ((Case.ops())#modeDesc)); 599 (* Second part of third line is not informative. 600 Record the features that change the archive format and must exist to 601 be able to load the archive later. *) 602 output_string c "\030"; 603 output_string c (String.concat "\030" (Features.changingArchiveFormat ())); 604 output_string c "\n"; 605 (* "paths" is stored separately to keep the archive file readable 606 for versions <= 2.53.3. Otherwise the older versions would fail 607 with a fatal error "Property lists: paths not yet registered!" *) 608 let paths = 609 try Proplist.find propPathKey properties with Not_found -> PathMap.empty in 610 let properties = Proplist.remove propPathKey properties in 611 Umarshal.to_channel mpayload c (archive, hash, magic, properties); 612 if not (PathMap.is_empty paths) then Umarshal.to_channel mpaths c paths; 613 close_out c)) 614 615 (* IMPORTANT! This val is here for smoother upgrades from versions <= 2.51.5 616 It can be removed when this compatibility is no longer required. *) 617 let loadedCompatArchive = ref [] 618 619 (* Remove the archive under the root path [fspath] with archiveVersion [v] *) 620 let removeArchiveLocal ((fspath: Fspath.t), (v: archiveVersion)): unit Lwt.t = 621 let f' name = Lwt.return ( 622 let fspath = Util.fileInUnisonDir name in 623 debug (fun() -> 624 Util.msg "Removing archive %s\n" (System.fspathToDebugString fspath)); 625 Util.convertUnixErrorsToFatal "removing archive" (fun () -> 626 try System.unlink fspath 627 with Unix.Unix_error (Unix.ENOENT, _, _) -> ())) 628 in 629 let ret = f' (fst (archiveName fspath v)) in 630 (* IMPORTANT! This code is for smoother upgrades from versions <= 2.51.5 631 It can be removed when this compatibility is no longer required. *) 632 if Safelist.exists (fun x -> x = fspath) !loadedCompatArchive then begin 633 loadedCompatArchive := Safelist.filter (fun x -> x <> fspath) 634 !loadedCompatArchive; 635 (try 636 ignore (f' (fst (archiveName251 fspath MainArch))) 637 with Util.Fatal _ -> ()); 638 try 639 ignore (f' (fst (archiveName251 fspath FPCache))) 640 with Util.Fatal _ -> () 641 end; 642 ret 643 644 (* [removeArchiveOnRoot root v] invokes [removeArchive fspath v] on the 645 server, where [fspath] is the path to root on the server *) 646 let removeArchiveOnRoot: Common.root -> archiveVersion -> unit Lwt.t = 647 Remote.registerRootCmd "removeArchive" marchiveVersion Umarshal.unit removeArchiveLocal 648 649 (* [commitArchive (fspath, ())] commits the archive for [fspath] by changing 650 the filenames from ScratchArch-ones to a NewArch-ones *) 651 let commitArchiveLocal ((fspath: Fspath.t), ()) 652 : unit Lwt.t = 653 Lwt.return 654 (let (fromname,_) = archiveName fspath ScratchArch in 655 let (toname,_) = archiveName fspath NewArch in 656 let ffrom = Util.fileInUnisonDir fromname in 657 let fto = Util.fileInUnisonDir toname in 658 Util.convertUnixErrorsToFatal 659 "committing" 660 (fun () -> System.rename ffrom fto)) 661 662 (* [commitArchiveOnRoot root v] invokes [commitArchive fspath v] on the 663 server, where [fspath] is the path to root on the server *) 664 let commitArchiveOnRoot: Common.root -> unit -> unit Lwt.t = 665 Remote.registerRootCmd "commitArchive" Umarshal.unit Umarshal.unit commitArchiveLocal 666 667 let getArchiveInfo f = 668 Util.convertUnixErrorsToTransient "querying file information" 669 (fun () -> 670 try 671 Some (System.stat f) 672 with Unix.Unix_error ((Unix.ENOENT | Unix.ENOTDIR), _, _) -> 673 None) 674 675 let archiveInfoCache = Hashtbl.create 7 676 (* [postCommitArchive (fspath, v)] finishes the committing protocol by 677 copying files from NewArch-files to MainArch-files *) 678 let postCommitArchiveLocal (fspath,()) 679 : unit Lwt.t = 680 Lwt.return 681 (let (fromname,_) = archiveName fspath NewArch in 682 let (toname, thisRoot) = archiveName fspath MainArch in 683 let ffrom = Util.fileInUnisonDir fromname in 684 let fto = Util.fileInUnisonDir toname in 685 debug (fun() -> 686 Util.msg "Copying archive %s to %s\n" 687 (System.fspathToDebugString ffrom) 688 (System.fspathToDebugString fto)); 689 Util.convertUnixErrorsToFatal "copying archive" (fun () -> 690 begin try 691 System.unlink fto 692 with Unix.Unix_error (Unix.ENOENT, _, _) -> () end; 693 begin try 694 System.link ffrom fto 695 with Unix.Unix_error _ -> 696 let outFd = 697 System.open_out_gen 698 [Open_wronly; Open_creat; Open_trunc; Open_binary] 0o600 fto in 699 let close_on_error f = 700 try f () with e -> close_out_noerr outFd; raise e 701 in 702 close_on_error (fun () -> 703 begin try 704 System.chmod fto 0o600 (* In case the file already existed *) 705 with Unix.Unix_error _ -> () end; 706 let inFd = System.open_in_bin ffrom in 707 let close_on_error f = 708 try f () with e -> close_in_noerr inFd; raise e 709 in 710 close_on_error (fun () -> 711 Uutil.readWrite inFd outFd (fun _ -> ()); 712 close_in inFd; 713 close_out outFd)) 714 end; 715 let arcFspath = Util.fileInUnisonDir toname in 716 Hashtbl.replace archiveInfoCache thisRoot (getArchiveInfo arcFspath))) 717 718 (* [postCommitArchiveOnRoot root v] invokes [postCommitArchive fspath v] on 719 the server, where [fspath] is the path to root on the server *) 720 let postCommitArchiveOnRoot: Common.root -> unit -> unit Lwt.t = 721 Remote.registerRootCmd "postCommitArchive" Umarshal.unit Umarshal.unit postCommitArchiveLocal 722 723 724 (*************************************************************************) 725 (* Archive cache *) 726 (*************************************************************************) 727 728 (* archiveCache: map(rootGlobalName, archive) *) 729 let archiveCache = Hashtbl.create 7 730 731 (* Retrieve an archive from the cache *) 732 let getArchive (thisRoot: string): archive = 733 Hashtbl.find archiveCache thisRoot 734 735 (* Update the cache. *) 736 let setArchiveLocal (thisRoot: string) (archive: archive) = 737 (* Also this: *) 738 debug (fun () -> Printf.eprintf "Setting archive for %s\n" thisRoot); 739 Hashtbl.replace archiveCache thisRoot archive 740 741 (* archiveCache: map(rootGlobalName, property list) *) 742 let archivePropCache = Hashtbl.create 7 743 744 (* Retrieve an archive property list from the cache *) 745 let getArchiveProps (thisRoot: string): Proplist.t = 746 Hashtbl.find archivePropCache thisRoot 747 748 (* Update the property list cache. *) 749 let setArchivePropsLocal (thisRoot: string) (props: Proplist.t) = 750 Hashtbl.replace archivePropCache thisRoot props 751 752 let fileUnchanged oldInfo newInfo = 753 match oldInfo, newInfo with 754 | None, _ | _, None -> false 755 | Some o, Some n -> 756 o.Unix.LargeFile.st_kind = S_REG && n.Unix.LargeFile.st_kind = S_REG 757 && 758 o.Unix.LargeFile.st_mtime = n.Unix.LargeFile.st_mtime 759 && 760 o.Unix.LargeFile.st_size = n.Unix.LargeFile.st_size 761 && 762 (o.Unix.LargeFile.st_ino = n.Unix.LargeFile.st_ino 763 || 764 Prefs.read Fileinfo.ignoreInodeNumbers 765 || 766 not (System.hasInodeNumbers ())) 767 768 let archiveUnchanged thisRoot newInfo = 769 try 770 fileUnchanged (Hashtbl.find archiveInfoCache thisRoot) newInfo 771 with Not_found -> 772 false 773 774 775 (*************************************************************************) 776 (* Shared props data in archive *) 777 (*************************************************************************) 778 779 let debugpd = Util.debug "propsdata+" 780 781 let propsDataKey = Proplist.register "props data" Props.Data.m 782 783 let prunePropsdata archive = 784 (* Do propsdata-GC by keeping live props *) 785 let rec prunePropsdata = function 786 | ArchiveDir (props, children) -> 787 Props.Data.gcKeep props; 788 NameMap.iter (fun _ c -> prunePropsdata c) children 789 | ArchiveFile (props, _, _, _) -> 790 Props.Data.gcKeep props 791 | ArchiveSymlink _ -> () 792 | NoArchive -> () 793 in 794 let t0 = Unix.gettimeofday () in 795 debugpd (fun () -> Util.msg "Pruning shared props data...\n"); 796 Props.Data.gcInit (); 797 prunePropsdata archive; 798 let pd = Props.Data.gcDone () in 799 debugpd (fun () -> 800 let t1 = Unix.gettimeofday () in 801 Util.msg "Shared props data pruning took %.3f milliseconds\n" 802 ((t1 -. t0) *. 1000.)); 803 pd 804 805 let externArchivePropsdata archive props = 806 match prunePropsdata archive with 807 | [] -> props 808 | pd -> Proplist.add propsDataKey pd props 809 810 let internArchivePropsdata props = 811 let t0 = Unix.gettimeofday () in 812 debugpd (fun () -> Util.msg "Restoring shared props data...\n"); 813 let data = try Proplist.find propsDataKey props with Not_found -> [] in 814 Props.Data.intern data; 815 debugpd (fun () -> 816 let t1 = Unix.gettimeofday () in 817 Util.msg "Shared props data restoring took %.3f milliseconds\n" 818 ((t1 -. t0) *. 1000.)) 819 820 821 (************************************************************************* 822 DUMPING ARCHIVES 823 *************************************************************************) 824 825 let rec showArchive = function 826 ArchiveDir (props, children) -> 827 Format.printf "Directory, %s@\n @[" (Props.syncedPartsToString props); 828 NameMap.iter (fun n c -> 829 Format.printf "%s -> @\n " (Name.toString n); 830 showArchive c) 831 children; 832 Format.printf "@]" 833 | ArchiveFile (props, fingerprint, _, _) -> 834 Format.printf "File, %s %s@\n" 835 (Props.syncedPartsToString props) 836 (Os.fullfingerprint_to_string fingerprint) 837 | ArchiveSymlink(s) -> 838 Format.printf "Symbolic link: %s@\n" s 839 | NoArchive -> 840 Format.printf "No archive@\n" 841 842 let dumpArchiveLocal (fspath,()) = 843 let (name, root) = archiveName fspath MainArch in 844 let archive = getArchive root in 845 let f = Util.fileInUnisonDir (name ^ ".unison.dump") in 846 debug (fun () -> Printf.eprintf "Dumping archive into `%s'\n" 847 (System.fspathToDebugString f)); 848 let ch = System.open_out_gen [Open_wronly; Open_creat; Open_trunc] 0o600 f in 849 let close_on_error f = 850 try f () with e -> close_out_noerr ch; raise e 851 in 852 close_on_error (fun () -> 853 let (outfn,flushfn) = Format.get_formatter_output_functions () in 854 Format.set_formatter_out_channel ch; 855 Format.printf "Contents of archive for %s\n" root; 856 Format.printf "Written at %s\n\n" (Util.time2string (Util.time())); 857 showArchive archive; 858 Format.print_flush(); 859 Format.set_formatter_output_functions outfn flushfn; 860 flush ch; 861 close_out ch); 862 Lwt.return () 863 864 let dumpArchiveOnRoot : Common.root -> unit -> unit Lwt.t = 865 Remote.registerRootCmd "dumpArchive" Umarshal.unit Umarshal.unit dumpArchiveLocal 866 867 (*****************************************************************************) 868 (* ARCHIVE CASE CONVERSION *) 869 (*****************************************************************************) 870 871 (* Stamp for marking unchange directories *) 872 let dirStampKey : Props.dirChangedStamp Proplist.key = 873 Proplist.register "unchanged directory stamp" Props.mdirChangedStamp 874 875 (* Property containing a description of the archive case sensitivity mode *) 876 let caseKey : string Proplist.key = Proplist.register "case mode" Umarshal.string 877 878 (* Turn a case sensitive archive into a case insensitive archive. 879 Directory children are resorted and duplicates are removed. 880 *) 881 let rec makeCaseSensitiveRec arch = 882 match arch with 883 ArchiveDir (desc, children) -> 884 let dups = ref [] in 885 let children = 886 NameMap.fold 887 (fun nm ch chs -> 888 if Name.badEncoding nm then chs else begin 889 if NameMap.mem nm chs then dups := nm :: !dups; 890 NameMap.add nm (makeCaseSensitiveRec ch) chs 891 end) 892 children NameMap.empty 893 in 894 let children = 895 List.fold_left (fun chs nm -> NameMap.remove nm chs) children !dups in 896 ArchiveDir (desc, children) 897 | ArchiveFile _ | ArchiveSymlink _ | NoArchive -> 898 arch 899 900 let makeCaseSensitive thisRoot = 901 setArchiveLocal thisRoot (makeCaseSensitiveRec (getArchive thisRoot)); 902 (* We need to recheck all directories, so we mark them possibly changed *) 903 setArchivePropsLocal thisRoot 904 (Proplist.add dirStampKey (Props.freshDirStamp ()) 905 (Proplist.add caseKey (Case.ops ())#modeDesc 906 (getArchiveProps thisRoot))) 907 908 let makeCaseSensitiveOnRoot = 909 Remote.registerRootCmd "makeCaseSensitive" Umarshal.unit Umarshal.unit 910 (fun (fspath, ()) -> 911 makeCaseSensitive (thisRootsGlobalName fspath); 912 Lwt.return ()) 913 914 (****) 915 916 (* Get the archive case sensitivity mode from the archive magic. *) 917 let archiveMode magic = 918 let currentMode = (Case.ops ())#modeDesc in 919 if magic = "" then currentMode (* Newly created archive *) else 920 try 921 String.sub magic 0 (String.index magic '\000') 922 with Not_found -> 923 (* Legacy format. Cannot be Unicode case insensitive. *) 924 if (Case.ops ())#mode = Case.UnicodeInsensitive then 925 "some non-Unicode" 926 else 927 currentMode 928 929 let checkArchiveCaseSensitivity l = 930 let root = thisRootsGlobalName (snd (Globals.localRoot ())) in 931 let curMode = (Case.ops ())#modeDesc in 932 let archMode = Proplist.find caseKey (getArchiveProps root) in 933 if curMode = archMode then 934 Lwt.return () 935 else begin 936 if archMode = Case.caseSensitiveModeDesc then 937 Globals.allRootsIter (fun r -> makeCaseSensitiveOnRoot r ()) 938 else begin 939 (* We cannot compute the archive name locally as it 940 currently depends on the os type *) 941 Globals.allRootsMap 942 (fun r -> archiveNameOnRoot r MainArch) >>= fun names -> 943 let l = 944 List.map 945 (fun (name, host, _) -> 946 Format.sprintf " archive %s on host %s" name host) 947 names 948 in 949 Lwt.fail 950 (Util.Fatal 951 (String.concat "\n" 952 ("Warning: incompatible case sensitivity settings." :: 953 Format.sprintf "Unison is currently in %s mode," curMode :: 954 Format.sprintf 955 "while the archives were created in %s mode." archMode :: 956 "You should either change Unison's setup or delete" :: 957 "the following archives from the .unison directories:" :: 958 l @ 959 ["(or invoke Unison once with -ignorearchives flag)."; 960 "Then, try again."]))) 961 end 962 end 963 964 (****) 965 966 let rec populateCacheFromArchiveRec path arch = 967 match arch with 968 ArchiveDir (_, children) -> 969 NameMap.iter 970 (fun nm ch -> populateCacheFromArchiveRec (Path.child path nm) ch) 971 children 972 | ArchiveFile (desc, dig, stamp, ress) -> 973 Fpcache.save path (desc, dig, stamp, ress) 974 | ArchiveSymlink _ | NoArchive -> 975 () 976 977 let populateCacheFromArchive fspath arch = 978 let (cacheFilename, _) = archiveName fspath FPCache in 979 let cacheFile = Util.fileInUnisonDir cacheFilename in 980 Fpcache.init true (Prefs.read ignoreArchives) cacheFile; 981 populateCacheFromArchiveRec Path.empty arch; 982 Fpcache.finish () 983 984 (*************************************************************************) 985 (* Loading archives *) 986 (*************************************************************************) 987 988 let setArchiveData thisRoot fspath (arch, hash, magic, properties) info = 989 let archMode = archiveMode magic in 990 let curMode = (Case.ops ())#modeDesc in 991 let properties = Proplist.add caseKey archMode properties in 992 setArchiveLocal thisRoot arch; 993 setArchivePropsLocal thisRoot properties; 994 internArchivePropsdata properties; 995 Hashtbl.replace archiveInfoCache thisRoot info; 996 if archMode <> curMode then populateCacheFromArchive fspath arch; 997 Lwt.return (Some (hash, magic)) 998 999 let clearArchiveData thisRoot = 1000 setArchiveLocal thisRoot NoArchive; 1001 setArchivePropsLocal thisRoot 1002 (Proplist.add caseKey (Case.ops ())#modeDesc Proplist.empty); 1003 internArchivePropsdata Proplist.empty; 1004 Hashtbl.remove archiveInfoCache thisRoot; 1005 Lwt.return (Some (0, "")) 1006 1007 (* Load (main) root archive and cache it on the given server *) 1008 (* FIXME? 1009 Due to a slight bug (or possibly a design oversight) in the current code, 1010 under some circumstances the archives will be loaded twice. In particular, 1011 [archivesIdentical checksums] in [loadArchives] below fails at [optimistic] 1012 loading when one of the hosts has the archive cached in memory and the other 1013 one loads from the disk, causing both hosts to load the archive from disk 1014 again, this time with [optimistic = false]. The verification fails because 1015 [optimistic] loading of an archive that is already cached in memory returns 1016 [Some (0, "")], a value that can't be verified against an archive loaded 1017 from disk. 1018 1019 In practice, this scenario will happen in a very specific situation only: A 1020 long-running socket server to which a client connects repeatedly (client has 1021 to have exited between connects because this is the only way it would not 1022 have the archive cached in memory; the caches are currently not purged). It 1023 can't happen with ssh server as that has the same life cycle as the client. 1024 This means that the vast majority of users will never even hit this bug; and 1025 those few who do will never notice it. 1026 1027 In future, should purging of the in-memory archive cache be implemented, 1028 this scenario could become more common when the server and client don't 1029 purge their caches at the same time. 1030 1031 Additionally, while not directly conflicting with the situation above, it is 1032 unfortunate that [clearArchiveData] also returns [Some (0, "")] to signal an 1033 empty/missing archive. *) 1034 let loadArchiveOnRoot: Common.root -> bool -> (int * string) option Lwt.t = 1035 Remote.registerRootCmd 1036 "loadArchive" Umarshal.bool Umarshal.(option (prod2 int string id id)) 1037 (fun (fspath, optimistic) -> 1038 let (arcName,thisRoot) = archiveName fspath MainArch in 1039 let arcFspath = Util.fileInUnisonDir arcName in 1040 1041 if Prefs.read ignoreArchives then begin 1042 foundArchives := false; 1043 clearArchiveData thisRoot 1044 end else if optimistic then begin 1045 let (newArcName, _) = archiveName fspath NewArch in 1046 if 1047 (* If the archive is not in a stable state, we need to 1048 perform archive recovery. So, the optimistic loading 1049 fails. *) 1050 System.file_exists (Util.fileInUnisonDir newArcName) 1051 || 1052 let (lockFilename, _) = archiveName fspath Lock in 1053 let lockFile = Util.fileInUnisonDir lockFilename in 1054 Lock.is_locked lockFile 1055 then 1056 Lwt.return None 1057 else 1058 let info = getArchiveInfo arcFspath in 1059 if archiveUnchanged thisRoot info then 1060 (* The archive is unchanged. So, we don't need to do 1061 anything. *) 1062 Lwt.return (Some (0, "")) 1063 else begin 1064 match loadArchiveLocal arcFspath thisRoot with 1065 Some archData -> 1066 let info' = getArchiveInfo arcFspath in 1067 if fileUnchanged info info' then 1068 setArchiveData thisRoot fspath archData info 1069 else 1070 (* The archive was modified during loading. We fail. *) 1071 Lwt.return None 1072 | None -> 1073 (* No archive found, try 2.51 upgrade mode *) 1074 (* IMPORTANT! This code is for smoother upgrades from 1075 versions <= 2.51.5 1076 It can be removed when this compatibility is no longer 1077 required. *) 1078 let (arcName, thisRoot) = archiveName251 fspath MainArch in 1079 let arcFspath = Util.fileInUnisonDir arcName in 1080 match loadArchiveLocal251 arcFspath thisRoot with 1081 | Some archData -> 1082 loadedCompatArchive := fspath :: !loadedCompatArchive; 1083 setArchiveData thisRoot fspath archData 1084 (getArchiveInfo arcFspath) 1085 | None -> Lwt.return None 1086 end 1087 end else begin 1088 match loadArchiveLocal arcFspath thisRoot with 1089 Some archData -> 1090 setArchiveData thisRoot fspath archData (getArchiveInfo arcFspath) 1091 | None -> 1092 (* No archive found, try 2.51 upgrade mode *) 1093 (* IMPORTANT! This code is for smoother upgrades from 1094 versions <= 2.51.5 1095 It can be removed when this compatibility is no longer 1096 required. *) 1097 let (arcName, thisRoot) = archiveName251 fspath MainArch in 1098 let arcFspath = Util.fileInUnisonDir arcName in 1099 match loadArchiveLocal251 arcFspath thisRoot with 1100 | Some archData -> 1101 loadedCompatArchive := fspath :: !loadedCompatArchive; 1102 setArchiveData thisRoot fspath archData (getArchiveInfo arcFspath) 1103 | None -> clearArchiveData thisRoot 1104 end) 1105 1106 let dumpArchives = 1107 Prefs.createBool "dumparchives" false 1108 ~category:`Expert 1109 ~cli_only:true 1110 "dump contents of archives just after loading" 1111 ("When this preference is set, Unison will create a file unison.dump " 1112 ^ "on each host, containing a text summary of the archive, immediately " 1113 ^ "after loading it.") 1114 1115 (* For all roots (local or remote), load the archive and cache *) 1116 let loadArchives (optimistic: bool) = 1117 Globals.allRootsMap (fun r -> loadArchiveOnRoot r optimistic) 1118 >>= (fun checksums -> 1119 let identicals = archivesIdentical checksums in 1120 if not (optimistic || identicals) then 1121 raise (Util.Fatal( 1122 "Internal error: On-disk archives are not identical.\n" 1123 ^ "\n" 1124 ^ "This can happen when both machines have the same hostname.\n" 1125 ^ "It can also happen when one copy of Unison has been compiled with\n" 1126 ^ "OCaml version 3 and one with OCaml version 4.\n" 1127 ^ "\n" 1128 ^ "If this is not the case and you get this message repeatedly, please:\n" 1129 ^ " a) Send a bug report to unison-users@seas.upenn.edu (you may need\n" 1130 ^ " to join the group before you will be allowed to post).\n" 1131 ^ " For information, see https://github.com/bcpierce00/unison/wiki\n" 1132 ^ " b) Move the archive files on each machine to some other directory\n" 1133 ^ " (in case they may be useful for debugging).\n" 1134 ^ " The archive files on this machine are in the directory\n" 1135 ^ (Printf.sprintf " %s\n" 1136 Util.unisonDir) 1137 ^ " and have names of the form\n" 1138 ^ " arXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\n" 1139 ^ " where the X's are hexadecimal numbers.\n" 1140 ^ " c) Run unison again to synchronize from scratch.\n")); 1141 Lwt.return (identicals, checksums)) 1142 1143 1144 (*****************************************************************************) 1145 (* Archive locking *) 1146 (*****************************************************************************) 1147 1148 let lockArchiveLocal fspath = 1149 let (lockFilename, _) = archiveName fspath Lock in 1150 let lockFile = Util.fileInUnisonDir lockFilename in 1151 if Lock.acquire lockFile then 1152 None 1153 else 1154 Some (Printf.sprintf "The file %s on host %s should be deleted" 1155 lockFile (Os.myCanonicalHostName ())) 1156 1157 let lockArchiveOnRoot: Common.root -> unit -> string option Lwt.t = 1158 Remote.registerRootCmd 1159 "lockArchive" Umarshal.unit Umarshal.(option string) (fun (fspath, ()) -> Lwt.return (lockArchiveLocal fspath)) 1160 1161 let unlockArchiveLocal fspath = 1162 Lock.release 1163 (Util.fileInUnisonDir (fst (archiveName fspath Lock))) 1164 1165 let unlockArchiveOnRoot: Common.root -> unit -> unit Lwt.t = 1166 Remote.registerRootCmd 1167 "unlockArchive" Umarshal.unit Umarshal.unit 1168 (fun (fspath, ()) -> Lwt.return (unlockArchiveLocal fspath)) 1169 1170 let ignorelocks = 1171 Prefs.createBool "ignorelocks" false 1172 ~category:(`Advanced `General) 1173 "ignore locks left over from previous run (dangerous!)" 1174 ("When this preference is set, Unison will ignore any lock files " 1175 ^ "that may have been left over from a previous run of Unison that " 1176 ^ "was interrupted while reading or writing archive files; by default, " 1177 ^ "when Unison sees these lock files it will stop and request manual " 1178 ^ "intervention. This " 1179 ^ "option should be set only if you are {\\em positive} that no other " 1180 ^ "instance of Unison might be concurrently accessing the same archive " 1181 ^ "files (e.g., because there was only one instance of unison running " 1182 ^ "and it has just crashed or you have just killed it). It is probably " 1183 ^ "not a good idea to set this option in a profile: it is intended for " 1184 ^ "command-line use.") 1185 1186 let locked = ref false 1187 1188 let lockArchives () = 1189 assert (!locked = false); 1190 Globals.allRootsMap 1191 (fun r -> lockArchiveOnRoot r ()) >>= (fun result -> 1192 if Safelist.exists (fun x -> x <> None) result 1193 && not (Prefs.read ignorelocks) then begin 1194 Globals.allRootsIter2 1195 (fun r st -> 1196 match st with 1197 None -> unlockArchiveOnRoot r () 1198 | Some _ -> Lwt.return ()) 1199 result >>= (fun () -> 1200 let whatToDo = Safelist.filterMap (fun st -> st) result in 1201 raise 1202 (Util.Fatal 1203 (String.concat "\n" 1204 (["Warning: the archives are locked. "; 1205 "If no other instance of " ^ Uutil.myName ^ " is running, \ 1206 the locks should be removed."] 1207 @ whatToDo @ 1208 ["Please delete lock files as appropriate and try again."])))) 1209 end else begin 1210 locked := true; 1211 Lwt.return () 1212 end) 1213 1214 let unlockArchives () = 1215 if !locked then begin 1216 Globals.allRootsIter (fun r -> unlockArchiveOnRoot r ()) >>= (fun () -> 1217 locked := false; 1218 Lwt.return ()) 1219 end else 1220 Lwt.return () 1221 1222 (*************************************************************************) 1223 (* CRASH RECOVERY *) 1224 (*************************************************************************) 1225 1226 (* We avoid getting into an unsafe situation if the synchronizer is 1227 interrupted during the writing of the archive files by adopting a 1228 simple joint commit protocol. 1229 1230 The invariant that we maintain at all times is: 1231 if all hosts have a temp archive, 1232 then these temp archives contain coherent information 1233 if NOT all hosts have a temp archive, 1234 then the regular archives contain coherent information 1235 1236 When we WRITE archives (markUpdated), we maintain this invariant 1237 as follows: 1238 - first, write all archives to a temporary filename 1239 - then copy all the temp files to the corresponding regular archive 1240 files 1241 - finally, delete all the temp files 1242 1243 Before we LOAD archives (findUpdates), we perform a crash recovery 1244 procedure, in case there was a crash during any of the above operations. 1245 - if all hosts have a temporary archive, we copy these to the 1246 regular archive names 1247 - otherwise, if some hosts have temporary archives, we delete them 1248 *) 1249 1250 let archivesExistOnRoot: Common.root -> unit -> (bool * bool) Lwt.t = 1251 Remote.registerRootCmd 1252 "archivesExist" Umarshal.unit Umarshal.(prod2 bool bool id id) 1253 (fun (fspath,rootsName) -> 1254 let (oldname,_) = archiveName fspath MainArch in 1255 let oldexists = 1256 System.file_exists (Util.fileInUnisonDir oldname) in 1257 let (newname,_) = archiveName fspath NewArch in 1258 let newexists = 1259 System.file_exists (Util.fileInUnisonDir newname) in 1260 let oldexists = 1261 if oldexists || newexists then oldexists else 1262 (* No archive found, try 2.51 upgrade mode *) 1263 (* IMPORTANT! This code is for smoother upgrades from 1264 versions <= 2.51.5 1265 It can be removed when this compatibility is no longer 1266 required. *) 1267 let (oldname, _) = archiveName251 fspath MainArch in 1268 System.file_exists (Util.fileInUnisonDir oldname) 1269 in 1270 Lwt.return (oldexists, newexists)) 1271 1272 let forall = Safelist.for_all (fun x -> x) 1273 let exists = Safelist.exists (fun x -> x) 1274 1275 let doArchiveCrashRecovery () = 1276 (* Check which hosts have copies of the old/new archive *) 1277 Globals.allRootsMap (fun r -> archivesExistOnRoot r ()) >>= (fun exl -> 1278 let oldnamesExist,newnamesExist = 1279 Safelist.split exl 1280 in 1281 1282 (* Do something with the new archives, if there are any *) 1283 begin if forall newnamesExist then begin 1284 (* All new versions were written: use them *) 1285 Util.warn 1286 (Printf.sprintf 1287 "Warning: %s may have terminated abnormally last time.\n\ 1288 A new archive exists on all hosts: I'll use them.\n" 1289 Uutil.myName); 1290 Globals.allRootsIter (fun r -> postCommitArchiveOnRoot r ()) >>= (fun () -> 1291 Globals.allRootsIter (fun r -> removeArchiveOnRoot r NewArch)) 1292 end else if exists newnamesExist then begin 1293 Util.warn 1294 (Printf.sprintf 1295 "Warning: %s may have terminated abnormally last time.\n\ 1296 A new archive exists on some hosts only; it will be ignored.\n" 1297 Uutil.myName); 1298 Globals.allRootsIter (fun r -> removeArchiveOnRoot r NewArch) 1299 end else 1300 Lwt.return () 1301 end >>= (fun () -> 1302 1303 (* Now verify that there are old archives on all hosts *) 1304 if forall oldnamesExist then begin 1305 (* We're happy *) 1306 foundArchives := true; 1307 Lwt.return () 1308 end else if exists oldnamesExist then 1309 Globals.allRootsMap 1310 (fun r -> archiveNameOnRoot r MainArch) >>= (fun names -> 1311 let whatToDo = 1312 Safelist.map 1313 (fun (name,host,exists) -> 1314 Printf.sprintf " Archive %s on host %s %s" 1315 name 1316 host 1317 (if exists then "should be DELETED" else "is MISSING")) 1318 names in 1319 raise 1320 (Util.Fatal 1321 (String.concat "\n" 1322 (["Warning: inconsistent state. "; 1323 "The archive file is missing on some hosts."; 1324 "For safety, the remaining copies should be deleted."] 1325 @ whatToDo @ 1326 ["Please delete archive files as appropriate and try again"; 1327 "or invoke Unison with -ignorearchives flag."])))) 1328 else begin 1329 foundArchives := false; 1330 let expectedRoots = 1331 String.concat "\n\t" (Safelist.map root2string (Globals.rootsList ())) in 1332 Util.warn 1333 ("No archive files were found for these roots, whose canonical names are:\n\t" 1334 ^ expectedRoots ^ "\nThis can happen either\n" 1335 ^ "because this is the first time you have synchronized these roots, \n" 1336 ^ "or because you have upgraded Unison to a new version with a different\n" 1337 ^ "archive format. \n\n" 1338 ^ "Update detection may take a while on this run if the replicas are \n" 1339 ^ "large.\n\n" 1340 ^ "Unison will assume that the 'last synchronized state' of both replicas\n" 1341 ^ "was completely empty. This means that any files that are different\n" 1342 ^ "will be reported as conflicts, and any files that exist only on one\n" 1343 ^ "replica will be judged as new and propagated to the other replica.\n" 1344 ^ "If the two replicas are identical, then no changes will be reported.\n\n" 1345 ^ "If you see this message repeatedly, it may be because one of your machines\n" 1346 ^ "is getting its address from DHCP, which is causing its host name to change\n" 1347 ^ "between synchronizations. See the documentation for the UNISONLOCALHOSTNAME\n" 1348 ^ "environment variable for advice on how to correct this.\n" 1349 ^ "\n" 1350 (* ^ "\nThe expected archive names were:\n" ^ expectedNames *) ); 1351 Lwt.return () 1352 end)) 1353 1354 (************************************************************************* 1355 Update a part of an archive 1356 *************************************************************************) 1357 1358 (* perform [action] on the relative path [rest] in the archive. If it 1359 returns [(ar, result)], then update archive with [ar] at [rest] and 1360 return [result]. *) 1361 let rec updatePathInArchive archive fspath 1362 (here: Path.local) (rest: 'a Path.path) 1363 (action: archive -> Path.local -> archive): 1364 archive 1365 = 1366 debugverbose 1367 (fun() -> 1368 Printf.eprintf "updatePathInArchive %s %s [%s] [%s]\n" 1369 (archive2string archive) (Fspath.toDebugString fspath) 1370 (Path.toString here) (Path.toString rest)); 1371 match Path.deconstruct rest with 1372 None -> 1373 action archive here 1374 | Some(name, rest') -> 1375 let (desc, name', child, otherChildren) = 1376 match archive with 1377 ArchiveDir (desc, children) -> 1378 begin try 1379 let (name', child) = NameMap.findi name children in 1380 (desc, name', child, NameMap.remove name children) 1381 with Not_found -> 1382 (desc, name, NoArchive, children) 1383 end 1384 | _ -> 1385 (Props.dummy, name, NoArchive, NameMap.empty) in 1386 match 1387 updatePathInArchive child fspath (Path.child here name') rest' action 1388 with 1389 NoArchive -> 1390 if NameMap.is_empty otherChildren && desc == Props.dummy then 1391 NoArchive 1392 else 1393 ArchiveDir (desc, otherChildren) 1394 | child -> 1395 ArchiveDir (desc, NameMap.add name' child otherChildren) 1396 1397 (*************************************************************************) 1398 (* Extract of a part of a archive *) 1399 (*************************************************************************) 1400 1401 (* Get the archive found at [rest] of [archive] *) 1402 let rec getPathInArchive archive here rest = 1403 match Path.deconstruct rest with 1404 None -> 1405 (here, archive) 1406 | Some (name, rest') -> 1407 let (name', child) = 1408 match archive with 1409 ArchiveDir (desc, children) -> 1410 begin try 1411 NameMap.findi name children 1412 with Not_found -> 1413 (name, NoArchive) 1414 end 1415 | _ -> 1416 (name, NoArchive) 1417 in 1418 getPathInArchive child (Path.child here name') rest' 1419 1420 let translatePathLocal fspath path = 1421 let root = thisRootsGlobalName fspath in 1422 let (localPath, _) = getPathInArchive (getArchive root) Path.empty path in 1423 localPath 1424 1425 let translatePath = 1426 Remote.registerRootCmd "translatePath" Path.m Path.mlocal 1427 (fun (fspath, path) -> Lwt.return (translatePathLocal fspath path)) 1428 1429 (*********************************************************************** 1430 MOUNT POINTS 1431 ************************************************************************) 1432 1433 let mountpoints = 1434 Prefs.createStringList "mountpoint" 1435 ~category:(`Advanced `General) 1436 "abort if this path does not exist" 1437 ("Including the preference \\texttt{-mountpoint PATH} causes Unison to " 1438 ^ "double-check, at the end of update detection, that \\texttt{PATH} exists " 1439 ^ "and abort if it does not. This is useful when Unison is used to synchronize " 1440 ^ "removable media. This preference can be given more than once. " 1441 ^ "See \\sectionref{mountpoints}{Mount Points and Removable Media}.") 1442 1443 let abortIfAnyMountpointsAreMissing fspath = 1444 Safelist.iter 1445 (fun s -> 1446 let path = Path.fromString s in 1447 if not (Os.exists fspath path) then 1448 raise (Util.Fatal 1449 (Printf.sprintf "Path %s/%s is designated as a mountpoint, but points to nothing on host %s\n" 1450 (Fspath.toPrintString fspath) (Path.toString path) 1451 (Os.myCanonicalHostName ())))) 1452 (Prefs.read mountpoints) 1453 1454 (*********************************************************************** 1455 Set of paths 1456 ************************************************************************) 1457 1458 type pathTree = PathTreeLeaf 1459 | PathTreeNode of pathTree NameMap.t 1460 1461 let rec addPathToTree path tree = 1462 match Path.deconstruct path, tree with 1463 None, _ | _, Some PathTreeLeaf -> 1464 PathTreeLeaf 1465 | Some (nm, p), None -> 1466 PathTreeNode (NameMap.add nm (addPathToTree p None) NameMap.empty) 1467 | Some (nm, p), Some (PathTreeNode children) -> 1468 let t = try Some (NameMap.find nm children) with Not_found -> None in 1469 PathTreeNode (NameMap.add nm (addPathToTree p t) children) 1470 1471 let rec removePathFromTree path tree = 1472 match Path.deconstruct path, tree with 1473 None, _ -> 1474 None 1475 | Some (nm, p), PathTreeLeaf -> 1476 Some tree 1477 | Some (nm, p), PathTreeNode children -> 1478 try 1479 let t = NameMap.find nm children in 1480 match removePathFromTree p t with 1481 None -> 1482 let newChildren = NameMap.remove nm children in 1483 if NameMap.is_empty children then None else 1484 Some (PathTreeNode newChildren) 1485 | Some t -> 1486 Some (PathTreeNode (NameMap.add nm t children)) 1487 with Not_found -> 1488 Some tree 1489 1490 let pathTreeOfList l = 1491 Safelist.fold_left (fun t p -> Some (addPathToTree p t)) None l 1492 1493 let removePathsFromTree l treeOpt = 1494 Safelist.fold_left 1495 (fun t p -> 1496 match t with 1497 None -> None 1498 | Some t -> removePathFromTree p t) 1499 treeOpt l 1500 1501 let rec getSubTree path tree = 1502 match Path.deconstruct path, tree with 1503 None, _ -> 1504 Some tree 1505 | Some (nm, p), PathTreeLeaf -> 1506 Some PathTreeLeaf 1507 | Some (nm, p), PathTreeNode children -> 1508 try 1509 let t = NameMap.find nm children in 1510 getSubTree p t 1511 with Not_found -> 1512 None 1513 1514 (*********************************************************************** 1515 UPDATE DETECTION 1516 ************************************************************************) 1517 1518 (* Generate a tree of changes. Also, update the archive in case some 1519 timestamps have been changed without the files being actually updated. *) 1520 1521 let fastcheck = 1522 Prefs.createBoolWithDefault "fastcheck" 1523 ~category:(`Advanced `Syncprocess) 1524 "do fast update detection (true/false/default)" 1525 ( "When this preference is set to \\verb|true|, \ 1526 Unison will use the modification time and length of a file as a 1527 `pseudo inode number' \ 1528 when scanning replicas for updates, \ 1529 instead of reading the full contents of every file. (This does not \ 1530 apply to the very first run, when Unison will always scan \ 1531 all files regardless of this switch). Under \ 1532 Windows, this may cause Unison to miss propagating an update \ 1533 if the modification time and length of the \ 1534 file are both unchanged by the update. However, Unison will never \ 1535 {\\em overwrite} such an update with a change from the other \ 1536 replica, since it always does a safe check for updates just \ 1537 before propagating a change. Thus, it is reasonable to use \ 1538 this switch under Windows most of the time and occasionally \ 1539 run Unison once with {\\tt fastcheck} set to \ 1540 \\verb|false|, if you are \ 1541 worried that Unison may have overlooked an update. \ 1542 For backward compatibility, \ 1543 \\verb|yes|, \\verb|no|, and \\verb|default| can be used in place \ 1544 of \\verb|true|, \\verb|false|, and \\verb|auto|. See \ 1545 \\sectionref{fastcheck}{Fast Update Detection} for more information.") 1546 1547 let useFastChecking () = 1548 Prefs.read fastcheck = `True 1549 || (Prefs.read fastcheck = `Default (*&& Sys.unix*)) 1550 1551 let immutable = Pred.create "immutable" 1552 ~category:(`Advanced `Sync) 1553 ("This preference specifies paths for directories whose \ 1554 immediate children are all immutable files --- i.e., once a file has been \ 1555 created, its contents never changes. When scanning for updates, \ 1556 Unison does not check whether these files have been modified; \ 1557 this can speed update detection significantly (in particular, for mail \ 1558 directories).") 1559 1560 let immutablenot = Pred.create "immutablenot" 1561 ~category:(`Advanced `Sync) 1562 ("This preference overrides {\\tt immutable}.") 1563 1564 type scanInfo = 1565 { fastCheck : bool; 1566 dirFastCheck : bool; 1567 dirStamp : Props.dirChangedStamp; 1568 rescanProps : bool; 1569 archHash : string; 1570 showStatus : bool } 1571 1572 (** Status display **) 1573 1574 let bigFileLength = 10 * 1024 1575 let bigFileLengthFS = Uutil.Filesize.ofInt bigFileLength 1576 let smallFileLength = 1024 1577 let fileLength = ref 0 1578 let t0 = ref 0. 1579 1580 (* Note that we do *not* want to do any status displays from the server 1581 side, since this will cause the server to block until the client has 1582 finished its own update detection and can receive and acknowledge 1583 the status display message -- thus effectively serializing the client 1584 and server! *) 1585 let showStatusAddLength scanInfo info = 1586 let len1 = Props.length info.Fileinfo.desc in 1587 let len2 = Osx.ressLength info.Fileinfo.osX.Osx.ressInfo in 1588 if len1 >= bigFileLengthFS || len2 >= bigFileLengthFS then 1589 fileLength := bigFileLength 1590 else 1591 fileLength := 1592 min bigFileLength 1593 (!fileLength + Uutil.Filesize.toInt len1 + Uutil.Filesize.toInt len2) 1594 1595 let showStatus scanInfo path = 1596 fileLength := !fileLength + smallFileLength; 1597 if !fileLength >= bigFileLength then begin 1598 fileLength := 0; 1599 let t = Unix.gettimeofday () in 1600 if t -. !t0 > 0.05 then begin 1601 if scanInfo.showStatus then 1602 Uutil.showUpdateStatus (Path.toString path); 1603 t0 := t 1604 end 1605 end 1606 1607 let showStatusDir path = () 1608 1609 (* BCP (4/09) The code above tries to be smart about showing status messages 1610 at regular intervals, but people seem to find this confusing. 1611 I tried replace all this with something simpler -- just show directories as 1612 they are scanned -- but this seems worse: it prints far too much stuff. 1613 So I'm going to revert to the old version. *) 1614 (* 1615 let showStatus path = () 1616 let showStatusAddLength info = () 1617 let showStatusDir path = 1618 if not !Trace.runningasserver then begin 1619 Trace.statusDetail ("scanning... " ^ Path.toString path); 1620 end 1621 *) 1622 1623 (* ------- *) 1624 1625 let symlinkInfo = 1626 Common.Previous (`SYMLINK, Props.dummy, Os.fullfingerprint_dummy, Osx.ressDummy) 1627 1628 let absentInfo = Common.New 1629 1630 let oldInfoOf archive = 1631 match archive with 1632 ArchiveDir (oldDesc, _) -> 1633 Common.Previous (`DIRECTORY, oldDesc, Os.fullfingerprint_dummy, Osx.ressDummy) 1634 | ArchiveFile (oldDesc, dig, _, ress) -> 1635 Common.Previous (`FILE, oldDesc, dig, ress) 1636 | ArchiveSymlink _ -> 1637 symlinkInfo 1638 | NoArchive -> 1639 absentInfo 1640 1641 (* Check whether the directory immediate children may have changed *) 1642 let rec noChildChange childUpdates = 1643 match childUpdates with 1644 [] -> 1645 true 1646 | (_, Updates (File _, Previous (`FILE, _, _, _))) :: rem 1647 | (_, Updates (Dir _, Previous (`DIRECTORY, _, _, _))) :: rem 1648 | (_, Updates (Symlink _, Previous (`SYMLINK, _, _, _))) :: rem -> 1649 noChildChange rem 1650 | _ -> 1651 false 1652 1653 (* Check whether the directory contents is different from what is in 1654 the archive *) 1655 let directoryCheckContentUnchanged 1656 currfspath path info archDesc childUpdates scanInfo = 1657 if 1658 noChildChange childUpdates 1659 && 1660 let (info', dataUnchanged, ressUnchanged) = 1661 Fileinfo.unchanged currfspath path info in 1662 dataUnchanged 1663 then begin 1664 let (archDesc, updated) = 1665 let inode = 1666 match Fileinfo.stamp info with Fileinfo.InodeStamp i -> i | _ -> 0 in 1667 Props.setDirChangeFlag archDesc scanInfo.dirStamp inode in 1668 if updated then 1669 debugverbose (fun()-> 1670 Util.msg "Contents of directory %s marked unchanged\n" 1671 (Fspath.toDebugString (Fspath.concat currfspath path))); 1672 (archDesc, updated) 1673 end else begin 1674 let (archDesc, updated) = 1675 Props.setDirChangeFlag archDesc Props.changedDirStamp 0 in 1676 if updated then 1677 debugverbose (fun()-> 1678 Util.msg "Contents of directory %s marked changed\n" 1679 (Fspath.toDebugString (Fspath.concat currfspath path))); 1680 (archDesc, updated) 1681 end 1682 1683 (* Check whether the list of children of a directory is clearly unchanged *) 1684 let dirContentsClearlyUnchanged info archDesc scanInfo = 1685 scanInfo.dirFastCheck 1686 && 1687 let inode = 1688 match Fileinfo.stamp info with Fileinfo.InodeStamp i -> i | _ -> 0 in 1689 Props.dirMarkedUnchanged archDesc scanInfo.dirStamp inode 1690 && 1691 Props.same_time info.Fileinfo.desc archDesc 1692 && 1693 (* Check the date is meaningful: the root directory of a FAT 1694 filesystem does not have modification time, so the time returned 1695 by [stat] is usually way in the past. *) 1696 Props.time archDesc >= 631152000. (* Jan 1, 1990 *) 1697 1698 (* Check whether a file's permissions have not changed *) 1699 let isPropUnchanged desc archiveDesc = Props.similar desc archiveDesc 1700 1701 (* Handle file permission change *) 1702 let checkPropChange desc archive archDesc = 1703 if isPropUnchanged desc archDesc then begin 1704 debugverbose (fun() -> Util.msg " Unchanged file\n"); 1705 NoUpdates 1706 end else begin 1707 debug (fun() -> Util.msg " File permissions updated\n"); 1708 Updates (File (desc, ContentsSame), oldInfoOf archive) 1709 end 1710 1711 (* Check whether a file has changed has changed, by comparing its digest and 1712 properties against [archDesc], [archFp], and [archStamp]. 1713 Returns a pair (optArch, ui) where [optArch] is *not* None when the file remains 1714 unchanged but time might be changed. [optArch] is used by [buildUpdate] 1715 series functions to compute the _old_ archive with updated time stamp 1716 (thus, there will no false update the next time) *) 1717 let checkContentsChange 1718 currfspath path info archive archDesc archFp archStamp archRess scanInfo 1719 : archive option * Common.updateItem 1720 = 1721 debug (fun () -> 1722 Util.msg "checkContentsChange: "; 1723 begin 1724 match archStamp with 1725 Fileinfo.InodeStamp inode -> 1726 (Util.msg "archStamp is inode (%d)" inode; 1727 Util.msg " / info.inode (%d)" info.Fileinfo.inode) 1728 | Fileinfo.NoStamp -> 1729 (Util.msg "archStamp is no-stamp") 1730 | Fileinfo.RescanStamp -> 1731 (Util.msg "archStamp is rescan-possibly-updated") 1732 end; 1733 Util.msg " / times: %f = %f... %b" 1734 (Props.time archDesc) (Props.time info.Fileinfo.desc) 1735 (Props.same_time info.Fileinfo.desc archDesc); 1736 Util.msg " / lengths: %s - %s" 1737 (Uutil.Filesize.toString (Props.length archDesc)) 1738 (Uutil.Filesize.toString (Props.length info.Fileinfo.desc)); 1739 Util.msg "\n"); 1740 let resetCTimeAtRescan () = 1741 if not scanInfo.rescanProps || Props.same_ctime archDesc Props.dummy then 1742 None 1743 else (* Props changed when props rescan was requested: reset ctime *) 1744 let newprops = Props.resetCTime archDesc Props.dummy in 1745 Some (ArchiveFile (newprops, archFp, archStamp, archRess)) 1746 in 1747 let fastCheck = scanInfo.fastCheck in 1748 let dataClearlyUnchanged = 1749 Fpcache.dataClearlyUnchanged fastCheck path info archDesc archStamp in 1750 let ressClearlyUnchanged = 1751 Fpcache.ressClearlyUnchanged fastCheck info archRess dataClearlyUnchanged 1752 in 1753 if dataClearlyUnchanged && ressClearlyUnchanged then begin 1754 Xferhint.insertEntry currfspath path archFp; 1755 let propsUpdates = checkPropChange info.Fileinfo.desc archive archDesc in 1756 let propsChanged = propsUpdates <> NoUpdates in 1757 (* ctime in the archive is updated under two conditions only: if there is 1758 nothing to propagate, or props changed while a props rescan was 1759 requested (in this case the ctime is reset to force a rescan every time 1760 until the sync is completed). Otherwise, if propagation fails (or the 1761 user skips this file) and times in archive are updated anyway then the 1762 changes that failed to propagate may be missed at the next scan. *) 1763 let optArch = 1764 if propsChanged then resetCTimeAtRescan () 1765 else if Props.same_ctime info.Fileinfo.desc archDesc then None 1766 else (* Nothing, other than ctime, changed: update ctime in archive *) 1767 let newprops = Props.setTime archDesc info.Fileinfo.desc in 1768 Some (ArchiveFile (newprops, archFp, archStamp, archRess)) 1769 in 1770 optArch, propsUpdates 1771 end else begin 1772 debugverbose (fun() -> Util.msg " Double-check possibly updated file\n"); 1773 showStatusAddLength scanInfo info; 1774 let (newDesc, newFp, newStamp, newRess) = 1775 Fpcache.fingerprint fastCheck currfspath path info 1776 (if dataClearlyUnchanged then Some archFp else None) in 1777 Xferhint.insertEntry currfspath path newFp; 1778 debug (fun() -> Util.msg " archive digest = %s current digest = %s\n" 1779 (Os.fullfingerprint_to_string archFp) 1780 (Os.fullfingerprint_to_string newFp)); 1781 if archFp = newFp then begin 1782 let propsUpdates = checkPropChange newDesc archive archDesc in 1783 let propsChanged = propsUpdates <> NoUpdates in 1784 (* Only update the archive if there is nothing to propagate (with one 1785 exception, see the comment about resetting ctime above). Otherwise, 1786 if propagation fails and times in archive are updated anyway then the 1787 changes that failed to propagate may be missed at the next scan. *) 1788 begin if propsChanged then 1789 resetCTimeAtRescan () 1790 else 1791 let newprops = Props.setTime archDesc newDesc in 1792 let newarch = ArchiveFile (newprops, archFp, newStamp, newRess) in 1793 debugverbose (fun() -> 1794 Util.msg " Contents match: update archive with new time...%f\n" 1795 (Props.time newprops)); 1796 Some newarch end, propsUpdates 1797 end else begin 1798 debug (fun() -> Util.msg " Updated file\n"); 1799 (* [BCP 5/2011] We might add a sanity check here: if the file contents 1800 have changed but the modtime has not, signal an error. I.e., abort if 1801 Props.same_time info.Fileinfo.desc archDesc 1802 is true at this point. 1803 *) 1804 None, 1805 Updates (File (newDesc, ContentsUpdated (newFp, newStamp, newRess)), 1806 oldInfoOf archive) 1807 end 1808 end 1809 1810 1811 (* getChildren = childrenOf + repetition check 1812 1813 Find the children of fspath+path, and return them, sorted, and 1814 partitioned into those with case conflicts, those with illegal 1815 cross platform filenames, and those without problems. 1816 1817 Note that case conflicts and illegal filenames can only occur under Unix, 1818 when syncing with a Windows file system. *) 1819 let checkFilename s = 1820 if Name.badEncoding s then 1821 `BadEnc 1822 else if 1823 (* Don't check unless we are syncing with Windows *) 1824 Prefs.read Globals.someHostIsRunningWindows && 1825 Name.badFile s 1826 then 1827 `BadName 1828 else 1829 `Ok 1830 1831 let getChildren fspath path = 1832 let children = 1833 (* We sort them in reverse order, as findDuplicate will reverse 1834 the list again *) 1835 Safelist.sort (fun nm1 nm2 -> - (Name.compare nm1 nm2)) 1836 (Os.childrenOf fspath path) in 1837 (* If Unison overall is running in case-insensitive mode but the 1838 local filesystem is case sensitive, then we need to check that 1839 two local files do not have the same name modulo case... *) 1840 (* We do it all the time, as this may happen anyway due to race 1841 conditions... *) 1842 let childStatus nm count = 1843 if count > 1 then 1844 `Dup 1845 else 1846 checkFilename nm 1847 in 1848 let rec findDuplicates' res nm count l = 1849 match l with 1850 [] -> 1851 (nm, childStatus nm count) :: res 1852 | nm' :: rem -> 1853 if Name.eq nm nm' then 1854 findDuplicates' res nm (count + 1) rem 1855 else 1856 findDuplicates' ((nm, childStatus nm count) :: res) nm' 1 rem 1857 and findDuplicates l = 1858 match l with 1859 [] -> [] 1860 | nm :: rem -> findDuplicates' [] nm 1 rem 1861 in 1862 findDuplicates children 1863 1864 (* from a list of (name, archive) pairs {usually the items in the same 1865 directory}, build two lists: the first a named list of the _old_ 1866 archives, with their timestamps updated for the files whose contents 1867 remain unchanged, the second a named list of updates; also returns 1868 whether the directory is now empty *) 1869 let rec buildUpdateChildren 1870 fspath path (archChi: archive NameMap.t) unchangedChildren scanInfo 1871 : archive NameMap.t option * (Name.t * Common.updateItem) list * 1872 bool * bool 1873 = 1874 showStatusDir path; 1875 Fswatch.scanDirectory path; 1876 let skip = 1877 Pred.test immutable (Path.toString path) && 1878 not (Pred.test immutablenot (Path.toString path)) in 1879 1880 if unchangedChildren then begin 1881 if skip then begin 1882 if Prefs.read Xferhint.xferbycopying then 1883 NameMap.iter 1884 (fun nm archive -> 1885 match archive with 1886 ArchiveFile (_, archFp, _, _) -> 1887 Xferhint.insertEntry fspath (Path.child path nm) archFp 1888 | _ -> 1889 ()) 1890 archChi; 1891 (None, [], false, false) 1892 end else begin 1893 let updates = ref [] in 1894 let archUpdated = ref false in 1895 let handleChild nm archive = 1896 let path' = Path.child path nm in 1897 debugverbose (fun () -> Util.msg 1898 "buildUpdateChildren(handleChild): %s\n" (Path.toString path')); 1899 if Globals.shouldIgnore path' then begin 1900 (* We have to ignore paths which are in the archive but no 1901 longer exists in the filesystem. Note that we cannot 1902 reach this point for files that exists on the filesystem 1903 ([hasIgnoredChildren] below would have been true). *) 1904 debugignore (fun()->Util.msg "buildUpdateChildren: ignoring path %s\n" 1905 (Path.toString path')); 1906 archive 1907 end else begin 1908 showStatus scanInfo path'; 1909 let (arch,uiChild) = 1910 buildUpdateRec archive fspath path' scanInfo in 1911 if uiChild <> NoUpdates then 1912 updates := (nm, uiChild) :: !updates; 1913 match arch with 1914 None -> archive 1915 | Some arch -> archUpdated := true; arch 1916 end in 1917 let newChi = NameMap.mapi handleChild archChi in 1918 (* The Recon module relies on the updates to be sorted *) 1919 ((if !archUpdated then Some newChi else None), 1920 Safelist.rev !updates, false, false) 1921 end 1922 end else 1923 let curChildren = ref (getChildren fspath path) in 1924 let emptied = not (NameMap.is_empty archChi) && !curChildren = [] in 1925 let hasIgnoredChildren = ref false in 1926 let updates = ref [] in 1927 let archUpdated = ref false in 1928 let handleChild nm archive status = 1929 let path' = Path.child path nm in 1930 if Globals.shouldIgnore path' then begin 1931 hasIgnoredChildren := !hasIgnoredChildren || (archive <> NoArchive); 1932 debugignore (fun()->Util.msg "buildUpdateChildren: ignoring path %s\n" 1933 (Path.toString path')); 1934 archive 1935 end else begin 1936 showStatus scanInfo path'; 1937 match status with 1938 `Ok | `Abs -> 1939 if skip && archive <> NoArchive && status <> `Abs then begin 1940 begin match archive with 1941 ArchiveFile (_, archFp, _, _) -> 1942 Xferhint.insertEntry fspath path' archFp 1943 | _ -> 1944 () 1945 end; 1946 archive 1947 end else begin 1948 let (arch,uiChild) = 1949 buildUpdateRec archive fspath path' scanInfo in 1950 if uiChild <> NoUpdates then 1951 updates := (nm, uiChild) :: !updates; 1952 match arch with 1953 None -> archive 1954 | Some arch -> archUpdated := true; arch 1955 end 1956 | `Dup -> 1957 let uiChild = 1958 Error 1959 ("Two or more files on a case-sensitive system have names \ 1960 identical except for case. They cannot be synchronized to a \ 1961 file system being treated as case-insensitive. (File '" ^ 1962 Path.toString path' ^ "')") 1963 in 1964 updates := (nm, uiChild) :: !updates; 1965 archive 1966 | `BadEnc -> 1967 let uiChild = 1968 Error ("The file name is not encoded in Unicode. (File '" 1969 ^ Path.toString path' ^ "')") 1970 in 1971 updates := (nm, uiChild) :: !updates; 1972 archive 1973 | `BadName -> 1974 let uiChild = 1975 Error ("The name of this Unix file is not allowed under Windows. \ 1976 (File '" ^ Path.toString path' ^ "')") 1977 in 1978 updates := (nm, uiChild) :: !updates; 1979 archive 1980 end 1981 in 1982 let rec matchChild nm archive = 1983 match !curChildren with 1984 [] -> 1985 (nm, handleChild nm archive `Abs) 1986 | (nm', st) :: rem -> 1987 let c = Name.compare nm nm' in 1988 if c < 0 then 1989 (nm, handleChild nm archive `Abs) 1990 else begin 1991 curChildren := rem; 1992 if c = 0 then begin 1993 if nm <> nm' then archUpdated := true; 1994 (nm', handleChild nm' archive st) 1995 end else begin 1996 let arch = handleChild nm' NoArchive st in 1997 assert (arch = NoArchive); 1998 matchChild nm archive 1999 end 2000 end 2001 in 2002 let newChi = NameMap.mapii matchChild archChi in 2003 Safelist.iter 2004 (fun (nm, st) -> 2005 let arch = handleChild nm NoArchive st in 2006 assert (arch = NoArchive)) 2007 !curChildren; 2008 (* The Recon module relies on the updates to be sorted *) 2009 ((if !archUpdated then Some newChi else None), 2010 Safelist.rev !updates, emptied, !hasIgnoredChildren) 2011 2012 and buildUpdateRec archive currfspath path scanInfo = 2013 try 2014 debug (fun() -> 2015 Util.msg "buildUpdateRec: %s\n" 2016 (Fspath.toDebugString (Fspath.concat currfspath path))); 2017 let archProps = 2018 match scanInfo.fastCheck, scanInfo.rescanProps, archive with 2019 | true, false, ArchiveFile (archDesc, _, _, _) -> Some archDesc 2020 | true, false, ArchiveDir (archDesc, _) -> Some archDesc 2021 | _ -> None 2022 in 2023 let info = Fileinfo.get ?archProps true currfspath path in 2024 match (info.Fileinfo.typ, archive) with 2025 (`ABSENT, NoArchive) -> 2026 debug (fun() -> Util.msg " buildUpdate -> Absent and no archive\n"); 2027 None, NoUpdates 2028 | (`ABSENT, _) -> 2029 debug (fun() -> Util.msg " buildUpdate -> Deleted\n"); 2030 None, Updates (Absent, oldInfoOf archive) 2031 (* --- *) 2032 | (`FILE, ArchiveFile (archDesc, archFp, archStamp, archRess)) -> 2033 checkContentsChange 2034 currfspath path info archive 2035 archDesc archFp archStamp archRess scanInfo 2036 | (`FILE, _) -> 2037 debug (fun() -> Util.msg " buildUpdate -> New file\n"); 2038 None, 2039 begin 2040 showStatusAddLength scanInfo info; 2041 let (desc, fp, stamp, ress) = 2042 Fpcache.fingerprint ~newfile:true 2043 scanInfo.fastCheck currfspath path info None in 2044 Xferhint.insertEntry currfspath path fp; 2045 Updates (File (desc, ContentsUpdated (fp, stamp, ress)), 2046 oldInfoOf archive) 2047 end 2048 (* --- *) 2049 | (`SYMLINK, ArchiveSymlink prevl) -> 2050 let l = Os.readLink currfspath path in 2051 debug (fun() -> 2052 if l = prevl then 2053 Util.msg " buildUpdate -> Symlink %s (unchanged)\n" l 2054 else 2055 Util.msg " buildUpdate -> Symlink %s (previously: %s)\n" l prevl); 2056 (None, 2057 if l = prevl then NoUpdates else 2058 Updates (Symlink l, oldInfoOf archive)) 2059 | (`SYMLINK, _) -> 2060 let l = Os.readLink currfspath path in 2061 debug (fun() -> Util.msg " buildUpdate -> New symlink %s\n" l); 2062 None, Updates (Symlink l, oldInfoOf archive) 2063 (* --- *) 2064 | (`DIRECTORY, ArchiveDir (archDesc, prevChildren)) -> 2065 debugverbose (fun() -> Util.msg " buildUpdate -> Directory\n"); 2066 let (permchange, desc) = 2067 (* BCP 10/17: If this directory is being treated atomically, 2068 then we want to use its real modtime; otherwise, we don't 2069 want to consider it as modified unless its own properties 2070 have changed (i.e., we don't want touching a file inside 2071 the directory to count as a modification to the 2072 directory). *) 2073 if isPropUnchanged info.Fileinfo.desc archDesc then 2074 if Pred.test Globals.atomic (Path.toString path) then 2075 (PropsSame, info.Fileinfo.desc) 2076 else 2077 (PropsSame, archDesc) 2078 else 2079 (PropsUpdated, info.Fileinfo.desc) in 2080 let unchanged = 2081 dirContentsClearlyUnchanged info archDesc scanInfo in 2082 let (newChildren, childUpdates, emptied, hasIgnoredChildren) = 2083 buildUpdateChildren 2084 currfspath path prevChildren unchanged scanInfo in 2085 let (archDesc, updated) = 2086 (* If the archive contain ignored children, we cannot use it to 2087 skip reading the directory contents from the filesystem. 2088 Actually, we could check for ignored children in the archive, 2089 but this has a significant cost. We could mark directories 2090 with ignored children, and only perform the checks for them, 2091 but that does not seem worthwhile, as directories with 2092 ignored children are expected to be rare in the archive. 2093 (These are files or directories which used not to be 2094 ignored and are now ignored.) *) 2095 if hasIgnoredChildren then (archDesc, true) else 2096 directoryCheckContentUnchanged 2097 currfspath path info archDesc childUpdates scanInfo in 2098 let (archDesc, updated) = 2099 (* Only update the times in archive if there is nothing to propagate 2100 for the dir itself (with the exception of ctime). ctime in the 2101 archive must be updated if props changed while a props rescan was 2102 requested (in this case the ctime is reset to force a rescan every 2103 time until the sync is completed). Otherwise, if propagation fails 2104 and times in archive are updated anyway then the changes that 2105 failed to propagate may be missed at the next scan. If there is 2106 something to propagate then all archive changes must go through 2107 propagation. With the exception of dirChangeFlag, which is safe to 2108 update without updating mtime. *) 2109 if permchange <> PropsSame then begin 2110 if not scanInfo.rescanProps || Props.same_ctime archDesc Props.dummy then 2111 (archDesc, updated) 2112 else (* Props changed when props rescan was requested: reset ctime *) 2113 (Props.resetCTime archDesc Props.dummy, true) 2114 end else begin 2115 let updated = 2116 updated || not (Props.same_time info.Fileinfo.desc archDesc) 2117 || not (Props.same_ctime info.desc archDesc) in 2118 (Props.setTime archDesc info.Fileinfo.desc, updated) 2119 end 2120 in 2121 (begin match newChildren with 2122 Some ch -> 2123 Some (ArchiveDir (archDesc, ch)) 2124 | None -> 2125 if updated then Some (ArchiveDir (archDesc, prevChildren)) 2126 else None 2127 end, 2128 if childUpdates <> [] || permchange = PropsUpdated then 2129 Updates (Dir (desc, childUpdates, permchange, emptied), 2130 oldInfoOf archive) 2131 else 2132 NoUpdates) 2133 | (`DIRECTORY, _) -> 2134 debug (fun() -> Util.msg " buildUpdate -> New directory\n"); 2135 let (newChildren, childUpdates, _, _) = 2136 buildUpdateChildren 2137 currfspath path NameMap.empty false scanInfo in 2138 (None, 2139 Updates (Dir (info.Fileinfo.desc, childUpdates, PropsUpdated, false), 2140 oldInfoOf archive)) 2141 with 2142 Util.Transient(s) -> None, Error(s) 2143 2144 (* Compute the updates for the tree of paths [tree] against archive. *) 2145 let rec buildUpdatePathTree archive fspath here tree scanInfo = 2146 match tree, archive with 2147 PathTreeNode children, ArchiveDir (archDesc, archChildren) -> 2148 let curChildren = 2149 lazy (List.fold_left (fun m (nm, st) -> NameMap.add nm st m) 2150 NameMap.empty (getChildren fspath here)) 2151 in 2152 let updates = ref [] in 2153 let archUpdated = ref false in 2154 let newChi = ref archChildren in 2155 let handleChild nm archive status tree' = 2156 let path' = Path.child here nm in 2157 if Os.isTempFile (Name.toString nm) || Globals.shouldIgnore path' then 2158 archive 2159 else begin 2160 match status with 2161 `Ok | `Abs -> 2162 let (arch,uiChild) = 2163 buildUpdatePathTree archive fspath path' tree' scanInfo in 2164 if uiChild <> NoUpdates then 2165 updates := (nm, uiChild) :: !updates; 2166 begin match arch with 2167 None -> archive 2168 | Some arch -> archUpdated := true; arch 2169 end 2170 | `Dup -> 2171 let uiChild = 2172 Error 2173 ("Two or more files on a case-sensitive system have names \ 2174 identical except for case. They cannot be synchronized \ 2175 to a file system being treated as case-insensitive. (File '" ^ 2176 Path.toString path' ^ "')") 2177 in 2178 updates := (nm, uiChild) :: !updates; 2179 archive 2180 | `BadEnc -> 2181 let uiChild = 2182 Error ("The file name is not encoded in Unicode. (File '" 2183 ^ Path.toString path' ^ "')") 2184 in 2185 updates := (nm, uiChild) :: !updates; 2186 archive 2187 | `BadName -> 2188 let uiChild = 2189 Error 2190 ("The name of this Unix file is not allowed under Windows. \ 2191 (File '" ^ Path.toString path' ^ "')") 2192 in 2193 updates := (nm, uiChild) :: !updates; 2194 archive 2195 end 2196 in 2197 NameMap.iter 2198 (fun nm tree' -> 2199 let inArchive = NameMap.mem nm archChildren in 2200 let arch = 2201 if tree' = PathTreeLeaf || not inArchive then begin 2202 let (nm', st) = 2203 try 2204 NameMap.findi nm (Lazy.force curChildren) 2205 with Not_found -> try 2206 (fst (NameMap.findi nm archChildren), `Abs) 2207 with Not_found -> 2208 (nm, `Abs) 2209 in 2210 let arch = 2211 try NameMap.find nm archChildren with Not_found -> NoArchive 2212 in 2213 handleChild nm' arch st tree' 2214 end else begin 2215 let (nm', arch) = NameMap.findi nm archChildren in 2216 handleChild nm' arch `Ok tree' 2217 end 2218 in 2219 if inArchive then newChi := NameMap.add nm arch !newChi) 2220 children; 2221 (begin if !archUpdated then 2222 Some (ArchiveDir (archDesc, !newChi)) 2223 else 2224 None 2225 end, 2226 if !updates <> [] then 2227 (* The Recon module relies on the updates to be sorted *) 2228 Updates (Dir (archDesc, Safelist.rev !updates, PropsSame, false), 2229 oldInfoOf archive) 2230 else 2231 NoUpdates) 2232 | _ -> 2233 showStatus scanInfo here; 2234 Fswatch.startScanning scanInfo.archHash fspath here; 2235 let res = buildUpdateRec archive fspath here scanInfo in 2236 Fswatch.stopScanning (); 2237 res 2238 2239 (* Compute the updates for [path] against archive. Also returns an 2240 archive, which is the old archive with time stamps updated 2241 appropriately (i.e., for those files whose contents remain 2242 unchanged). The filenames are also updated to match the filesystem 2243 contents. The directory permissions along the path are also 2244 collected, in case we need to build the directory hierarchy 2245 on one side. *) 2246 let rec buildUpdate archive fspath fullpath here path pathTree scanInfo = 2247 match Path.deconstruct path with 2248 None -> 2249 let (arch, ui) = 2250 buildUpdatePathTree archive fspath here pathTree scanInfo in 2251 (begin match arch with 2252 None -> archive 2253 | Some arch -> arch 2254 end, 2255 ui, here, []) 2256 | Some(name, path') -> 2257 let archProps = 2258 match scanInfo.fastCheck, scanInfo.rescanProps, archive with 2259 | true, false, ArchiveFile (archDesc, _, _, _) -> Some archDesc 2260 | true, false, ArchiveDir (archDesc, _) -> Some archDesc 2261 | _ -> None 2262 in 2263 let info = Fileinfo.get ?archProps true fspath here in 2264 if info.Fileinfo.typ <> `DIRECTORY && info.Fileinfo.typ <> `ABSENT then 2265 let error = 2266 if Path.isEmpty here then 2267 Printf.sprintf 2268 "path %s is not valid because the root of one of the replicas \ 2269 is not a directory" 2270 (Path.toString fullpath) 2271 else 2272 Printf.sprintf 2273 "path %s is not valid because %s is not a directory in one of \ 2274 the replicas" 2275 (Path.toString fullpath) (Path.toString here) 2276 in 2277 (archive, Error error, translatePathLocal fspath fullpath, []) 2278 else 2279 let (name', status) = 2280 if info.Fileinfo.typ = `ABSENT then 2281 (name, checkFilename name) 2282 else 2283 let children = getChildren fspath here in 2284 try 2285 Safelist.find (fun (name', _) -> Name.eq name name') children 2286 with Not_found -> 2287 (name, checkFilename name) 2288 in 2289 match status with 2290 | `BadEnc -> 2291 let error = 2292 Format.sprintf 2293 "The filename %s in path %s is not encoded in Unicode" 2294 (Name.toString name) (Path.toString fullpath) 2295 in 2296 (archive, Error error, translatePathLocal fspath fullpath, []) 2297 | `BadName -> 2298 let error = 2299 Format.sprintf 2300 "The filename %s in path %s is not allowed under Windows" 2301 (Name.toString name) (Path.toString fullpath) 2302 in 2303 (archive, Error error, translatePathLocal fspath fullpath, []) 2304 | `Dup -> 2305 let error = 2306 Format.sprintf 2307 "The path %s is ambiguous at filename %s (i.e., the name \ 2308 of this path is the same, modulo capitalization, as \ 2309 another path in a case-sensitive filesystem, and you are \ 2310 synchronizing this filesystem with a \ 2311 filesystem being treated as case-insensitive." 2312 (Path.toString fullpath) (Name.toString name) 2313 in 2314 (archive, Error error, translatePathLocal fspath fullpath, []) 2315 | `Ok -> 2316 match archive with 2317 ArchiveDir (desc, children) -> 2318 let archChild = 2319 try NameMap.find name children with Not_found -> NoArchive in 2320 let otherChildren = NameMap.remove name children in 2321 let (arch, updates, localPath, props) = 2322 buildUpdate 2323 archChild fspath fullpath (Path.child here name') 2324 path' pathTree scanInfo 2325 in 2326 let children = 2327 if arch = NoArchive then otherChildren else 2328 NameMap.add name' arch otherChildren 2329 in 2330 (ArchiveDir (desc, children), updates, localPath, 2331 if info.Fileinfo.typ = `ABSENT then [] else 2332 info.Fileinfo.desc :: props) 2333 | _ -> 2334 let (arch, updates, localPath, props) = 2335 buildUpdate 2336 NoArchive fspath fullpath (Path.child here name') 2337 path' pathTree scanInfo 2338 in 2339 assert (arch = NoArchive); 2340 (archive, updates, localPath, 2341 if info.Fileinfo.typ = `ABSENT then [] else 2342 info.Fileinfo.desc :: props) 2343 2344 (* All the predicates that may change the set of files scanned during 2345 update detection *) 2346 let updatePredicates = 2347 [("immutable", immutable); ("immutablenot", immutablenot); 2348 ("ignore", Globals.ignorePred); ("ignorenot", Globals.ignorenotPred); 2349 ("follow", Path.followPred)] 2350 2351 let predKey : (string * string list) list Proplist.key = 2352 Proplist.register "update predicates" Umarshal.(list (prod2 string (list string) id id)) 2353 let rsrcKey : bool Proplist.key = Proplist.register "rsrc pref" Umarshal.bool 2354 2355 let updatePredicateChanged props setProps = 2356 let oldPreds = try Proplist.find predKey props with Not_found -> [] in 2357 let newPreds = 2358 Safelist.map (fun (nm, p) -> (nm, Pred.extern p)) updatePredicates in 2359 (* 2360 List.iter 2361 (fun (nm, l) -> 2362 Format.eprintf "%s@." nm; 2363 List.iter (fun s -> Format.eprintf " %s@." s) l) 2364 newPreds; 2365 Format.eprintf "==> %b@." (oldPreds = newPreds); 2366 *) 2367 let oldRsrc = 2368 try Some (Proplist.find rsrcKey props) with Not_found -> None in 2369 let newRsrc = Prefs.read Osx.rsrc in 2370 if oldPreds <> newPreds || oldRsrc <> Some newRsrc then begin 2371 setProps 2372 (Proplist.add predKey newPreds 2373 (Proplist.add rsrcKey newRsrc props)); 2374 true 2375 end else 2376 false 2377 2378 (* All the predicates that may change the set of props scanned during 2379 update detection *) 2380 let propsPredicates = 2381 [ ("xattrignore", Props.xattrIgnorePred, Props.xattrEnabled); 2382 ("xattrignorenot", Props.xattrIgnorenotPred, Props.xattrEnabled); 2383 ] 2384 2385 let pred2Key : (string * string list) list Proplist.key = 2386 Proplist.register "props predicates" Umarshal.(list (prod2 string (list string) id id)) 2387 let xattrsKey : bool Proplist.key = Proplist.register "xattrs pref" Umarshal.bool 2388 let aclKey : bool Proplist.key = Proplist.register "acl pref" Umarshal.bool 2389 2390 let mustRescanProps props setProps = 2391 let oldPreds = try Proplist.find pred2Key props with Not_found -> [] in 2392 let newPreds = 2393 Safelist.filterMap (fun (nm, p, c) -> 2394 if c () then Some (nm, Pred.extern p) else None) propsPredicates in 2395 let oldXattrs = 2396 try Some (Proplist.find xattrsKey props) with Not_found -> None in 2397 let newXattrs = 2398 if Props.xattrEnabled () then Some (Prefs.read Props.syncXattrs) else None in 2399 let oldACL = 2400 try Some (Proplist.find aclKey props) with Not_found -> None in 2401 let newACL = 2402 if Props.aclEnabled () then Some (Prefs.read Props.syncACL) else None in 2403 if oldPreds = newPreds && oldXattrs = newXattrs && oldACL = newACL then 2404 false 2405 else begin 2406 let props = 2407 match newACL with 2408 | Some x -> Proplist.add aclKey x props 2409 | None -> props in 2410 let props = 2411 match newXattrs with 2412 | Some x -> Proplist.add xattrsKey x props 2413 | None -> props in 2414 let props = 2415 if newPreds <> [] then Proplist.add pred2Key newPreds props 2416 else props in 2417 let () = setProps props in 2418 newXattrs = Some true || newACL = Some true 2419 end 2420 2421 let getArchivePropsForPath thisRoot path = 2422 let props = getArchiveProps thisRoot in 2423 try 2424 PathMap.find path (Proplist.find propPathKey props) 2425 with Not_found -> Proplist.empty 2426 2427 let mapPropPaths f props = 2428 let propPaths = try Proplist.find propPathKey props with Not_found -> PathMap.empty in 2429 Proplist.add propPathKey (f propPaths) props 2430 2431 let setArchivePropsForPath thisRoot path pathProps = 2432 mapPropPaths (PathMap.add path pathProps) (getArchiveProps thisRoot) 2433 |> setArchivePropsLocal thisRoot 2434 2435 let purgeArchivePropsOverriddenChildren thisRoot paths = 2436 let f propPaths = 2437 let clearChildren propPaths path = 2438 let rec isParent p c = 2439 match Path.deconstruct p, Path.deconstruct c with 2440 | None, Some _ -> true 2441 | Some (p, px), Some (c, cx) -> Name.compare p c = 0 && isParent px cx 2442 | _ -> false 2443 in 2444 let overrideChildren k v acc = 2445 (* If a child path is not ignored within a parent path then the properties 2446 specific to this child must be removed to avoid any conflicts between 2447 child and parent properties. Otherwise, the files under the child path 2448 could be synced with overlapping properties (once within the parent, 2449 once within the child path), which makes detecting predicate changes 2450 difficult. *) 2451 if not (isParent path k) then 2452 PathMap.add k v acc 2453 else if Globals.shouldIgnore k then 2454 PathMap.add k v acc 2455 else 2456 acc 2457 in 2458 PathMap.fold overrideChildren propPaths PathMap.empty 2459 in 2460 Safelist.fold_left clearChildren propPaths paths 2461 in 2462 mapPropPaths f (getArchiveProps thisRoot) 2463 |> setArchivePropsLocal thisRoot 2464 2465 (* Purge archive properties for paths that are no longer present 2466 in the archive. *) 2467 let purgePropsForPaths archive props = 2468 let f propPaths = 2469 let keepExisting k v acc = 2470 match getPathInArchive archive Path.empty k with 2471 | (_, NoArchive) -> acc 2472 | _ -> PathMap.add k v acc 2473 in 2474 PathMap.fold keepExisting propPaths PathMap.empty 2475 in 2476 mapPropPaths f props 2477 2478 (* Remove old-style props used by versions <= 2.53.3 as they will be recorded 2479 in the per-path format. *) 2480 let clearOldStyleProps props = 2481 props 2482 |> Proplist.remove predKey 2483 |> Proplist.remove rsrcKey 2484 |> Proplist.remove pred2Key 2485 |> Proplist.remove xattrsKey 2486 |> Proplist.remove aclKey 2487 2488 (* Extract props to be converted to the per-path format from old-style props 2489 used by versions <= 2.53.3 *) 2490 let extractOldStyleProps props = 2491 let maybeGet k m = 2492 try Proplist.add k (Proplist.find k props) m with Not_found -> m 2493 in 2494 Proplist.empty 2495 |> maybeGet predKey 2496 |> maybeGet rsrcKey 2497 |> maybeGet pred2Key 2498 |> maybeGet xattrsKey 2499 |> maybeGet aclKey 2500 2501 let checkNoUpdatePredicateChange thisRoot paths = 2502 (* Default to old style (<= 2.53.3) and then the new style, per path *) 2503 let hasNewPropPaths = 2504 try 2505 ignore (Proplist.find propPathKey (getArchiveProps thisRoot)); 2506 true 2507 with Not_found -> false 2508 in 2509 let oldprops = 2510 if hasNewPropPaths then Proplist.empty else getArchiveProps thisRoot in 2511 (* FIXME: Enable in some future version: setArchivePropsLocal thisRoot (clearOldStyleProps oldprops); *) 2512 (* FIXME: Remove in some future version. 2513 Store global paths props for versions <= 2.53.3. Only for compatibility. *) 2514 ignore (mustRescanProps (getArchiveProps thisRoot) (setArchivePropsLocal thisRoot)); 2515 ignore (updatePredicateChanged (getArchiveProps thisRoot) (setArchivePropsLocal thisRoot)); 2516 (* FIXME: ^ Remove the above in some future version ^ *) 2517 let getPropsForPath path = 2518 let pprops = getArchivePropsForPath thisRoot path in 2519 if pprops <> Proplist.empty then pprops 2520 else 2521 let newprops = extractOldStyleProps oldprops in 2522 let () = setArchivePropsForPath thisRoot path newprops in 2523 newprops 2524 in 2525 let rescanProps = Safelist.fold_left (fun acc path -> 2526 mustRescanProps (getPropsForPath path) 2527 (fun props -> setArchivePropsForPath thisRoot path props) || acc) 2528 false paths 2529 in 2530 let predsChanged = Safelist.fold_left (fun acc path -> 2531 updatePredicateChanged (getPropsForPath path) 2532 (fun props -> setArchivePropsForPath thisRoot path props) || acc) 2533 false paths 2534 in 2535 purgeArchivePropsOverriddenChildren thisRoot paths; 2536 debug (fun () -> 2537 Util.msg "Optim: rescan ext props = %b; rescan dir entries \ 2538 (dir stamp changed) = %b\n" rescanProps predsChanged); 2539 (* If the list of scanned files changes then must also force rescan of all 2540 file properties because previously ignored files may already be in the 2541 archive (for example, some were synced before being ignored). *) 2542 let rescanProps = rescanProps || predsChanged in 2543 let dirStamp = 2544 try 2545 if predsChanged || rescanProps then raise_notrace Not_found; 2546 Proplist.find dirStampKey (getArchiveProps thisRoot) 2547 with Not_found -> 2548 let stamp = Props.freshDirStamp () in 2549 (* dirStampKey is intentionally kept as global property (while not 2550 strictly correct) because managing it per path is too difficult 2551 and fragile. *) 2552 setArchivePropsLocal thisRoot 2553 (Proplist.add dirStampKey stamp (getArchiveProps thisRoot)); 2554 stamp 2555 in 2556 (rescanProps, dirStamp) 2557 2558 (* This contains the list of synchronized paths and the directory stamps 2559 used by the previous update detection, when a watcher process is used. 2560 This make it possible to know when the state of the watcher process 2561 needs to be reset. *) 2562 let previousFindOptions = Hashtbl.create 7 2563 2564 (* for the given path, find the archive and compute the list of update 2565 items; as a side effect, update the local archive w.r.t. time-stamps for 2566 unchanged files *) 2567 let findLocal wantWatcher fspath pathList subpaths : 2568 (Path.local * Common.updateItem * Props.t list) list = 2569 debug (fun() -> Util.msg 2570 "findLocal %s (%s)\n" (Fspath.toDebugString fspath) 2571 (String.concat " " (Safelist.map Path.toString pathList))); 2572 addHashToTempNames fspath; 2573 (* Maybe we should remember the device number where the root lives at 2574 the beginning of update detection, so that we can check, below, that 2575 the device has not changed. This check would allow us to abort in case 2576 the root is on a removable device and this device gets removed during 2577 update detection, causing all the files to appear to have been 2578 deleted. --BCP 2006 *) 2579 let (arcName,thisRoot) = archiveName fspath MainArch in 2580 let archive = getArchive thisRoot in 2581 let (rescanProps, dirStamp) = checkNoUpdatePredicateChange thisRoot pathList in 2582 (* 2583 let t1 = Unix.gettimeofday () in 2584 *) 2585 let scanInfo = 2586 { fastCheck = useFastChecking (); 2587 (* Directory optimization is disabled under Windows, 2588 as Windows does not update directory modification times 2589 on FAT filesystems. *) 2590 dirFastCheck = useFastChecking () && Sys.unix; 2591 dirStamp; rescanProps; archHash = archiveHash fspath; 2592 showStatus = not !Trace.runningasserver } 2593 in 2594 let (cacheFilename, _) = archiveName fspath FPCache in 2595 let cacheFile = Util.fileInUnisonDir cacheFilename in 2596 Fpcache.init scanInfo.fastCheck (Prefs.read ignoreArchives) cacheFile; 2597 let unchangedOptions = 2598 try 2599 Hashtbl.find previousFindOptions scanInfo.archHash 2600 = (scanInfo.dirStamp, pathList) 2601 with Not_found -> 2602 false 2603 in 2604 let paths = 2605 match subpaths with 2606 Some (unsynchronizedPaths, blacklistedPaths) when unchangedOptions 2607 && Fswatchold.running scanInfo.archHash -> 2608 let (>>) x f = f x in 2609 let paths = 2610 Fswatchold.getChanges scanInfo.archHash 2611 (* We do not really need to filter here (they are filtered also 2612 by [buildUpdatePathTree], but that might reduce greatly and 2613 cheaply number of paths to consider... *) 2614 >> List.filter (fun path -> not (Globals.shouldIgnore path)) 2615 in 2616 let filterPaths paths subpaths = 2617 let number_list l = 2618 let i = ref (-1) in 2619 Safelist.map (fun x -> incr i; (!i, x)) l 2620 in 2621 paths >> (* We number paths, to be able to recover their 2622 initial order. *) 2623 number_list 2624 >> (* We put longest paths first, in order to deal 2625 correctly with nested paths (tough that might be 2626 overkill...) *) 2627 List.sort (fun (_, p1) (_, p2) -> Path.compare p2 p1) 2628 >> (* We extract the set of changed paths included in 2629 each synchronized path *) 2630 List.fold_left 2631 (fun (l, tree) (i, p) -> 2632 match tree with 2633 None -> 2634 ((i, (p, None)) :: l, None) 2635 | Some tree -> 2636 ((i, (p, getSubTree p tree)) :: l, 2637 removePathFromTree p tree)) 2638 ([], pathTreeOfList subpaths) 2639 >> fst 2640 >> (* Finally, we restaure the initial order *) 2641 List.sort (fun (i1, _) (i2, _) -> compare i1 i2) 2642 >> List.map snd 2643 in 2644 filterPaths pathList (Safelist.append unsynchronizedPaths paths) 2645 | _ -> 2646 if wantWatcher && Fswatchold.start scanInfo.archHash fspath then 2647 Hashtbl.replace previousFindOptions 2648 scanInfo.archHash (scanInfo.dirStamp, pathList) 2649 else 2650 Hashtbl.remove previousFindOptions scanInfo.archHash; 2651 Safelist.map (fun p -> (p, Some PathTreeLeaf)) pathList 2652 in 2653 let (archive, updates) = 2654 Safelist.fold_right 2655 (fun (path, pathTreeOpt) (arch, upd) -> 2656 match pathTreeOpt with 2657 Some pathTree when not (Globals.shouldIgnore path) -> 2658 let (arch', ui, localPath, props) = 2659 buildUpdate arch fspath path Path.empty path pathTree scanInfo 2660 in 2661 (arch', (localPath, ui, props) :: upd) 2662 | _ -> 2663 (arch, (translatePathLocal fspath path, NoUpdates, []) :: upd)) 2664 paths (archive, []) 2665 in 2666 Fpcache.finish (); 2667 (* 2668 let t2 = Unix.gettimeofday () in 2669 Format.eprintf "Update detection: %f@." (t2 -. t1); 2670 *) 2671 setArchiveLocal thisRoot archive; 2672 abortIfAnyMountpointsAreMissing fspath; 2673 updates 2674 2675 (* Conversion functions for 2.51-compatible return type: 2676 (Path.local * Common.updateItem * Props.t list) list *) 2677 let convV0 = Remote.makeConvV0FunRet 2678 (fun r -> Safelist.map 2679 (fun (a, b, c) -> a, Common.ui_to_compat251 b, Safelist.map Props.to_compat251 c) r) 2680 (fun r -> Safelist.map 2681 (fun (a, b, c) -> a, Common.ui_of_compat251 b, Safelist.map Props.of_compat251 c) r) 2682 2683 let findOnRoot = 2684 Remote.registerRootCmd 2685 "find" ~convV0 2686 Umarshal.(prod3 bool (list Path.m) (option (prod2 (list Path.m) (list Path.m) id id)) id id) 2687 Umarshal.(list (prod3 Path.mlocal Common.mupdateItem (list Props.m) id id)) 2688 (fun (fspath, (wantWatcher, pathList, subpaths)) -> 2689 Lwt.return (findLocal wantWatcher fspath pathList subpaths)) 2690 2691 let mergePropsdataOnRoot = 2692 Remote.registerRootCmd "propsdata" Props.Data.m Props.Data.m 2693 (fun (fspath, propsdata) -> 2694 Props.Data.merge propsdata; 2695 Lwt.return (Props.Data.extern `New)) 2696 2697 let findUpdatesOnPaths ?(wantWatcher=false) pathList subpaths = 2698 Lwt_unix.run 2699 (loadArchives true >>= (fun (ok, checksums) -> 2700 begin if ok then Lwt.return checksums else begin 2701 lockArchives () >>= (fun () -> 2702 Remote.Thread.unwindProtect 2703 (fun () -> 2704 doArchiveCrashRecovery () >>= (fun () -> 2705 loadArchives false)) 2706 (fun _ -> 2707 unlockArchives ()) >>= (fun (_, checksums) -> 2708 unlockArchives () >>= fun () -> 2709 Lwt.return checksums)) 2710 end end >>= (fun checksums -> 2711 checkArchiveCaseSensitivity checksums >>= fun () -> 2712 begin if Prefs.read dumpArchives then 2713 Globals.allRootsIter (fun r -> dumpArchiveOnRoot r ()) 2714 else 2715 Lwt.return () 2716 end >>= fun () -> 2717 let t = Trace.startTimer "Collecting changes" in 2718 Globals.allRootsMapWithWaitingAction (fun r -> 2719 debug (fun() -> Util.msg "findOnRoot %s\n" (root2string r)); 2720 findOnRoot r (wantWatcher, pathList, subpaths)) 2721 (fun (host, _) -> 2722 begin match host with 2723 Remote _ -> Uutil.showUpdateStatus ""; 2724 Trace.statusDetail "Waiting for changes from server" 2725 | _ -> () 2726 end) 2727 >>= (fun updates -> 2728 begin Globals.allRootsIter (fun r -> 2729 match r with 2730 | (Local, _) -> Lwt.return () 2731 | (Remote _, _) when not (Props.Data.enabled ()) -> Lwt.return () 2732 | (Remote _, _) -> begin 2733 mergePropsdataOnRoot r (Props.Data.extern `New) >>= fun propsdata -> 2734 Props.Data.merge propsdata; 2735 Lwt.return () 2736 end) 2737 end >>= fun () -> 2738 Trace.showTimer t; 2739 let result = 2740 Safelist.map 2741 (fun r -> 2742 match r with 2743 [i1; i2] -> (i1, i2) 2744 | _ -> assert false) 2745 (Safelist.transpose updates) 2746 in 2747 Trace.status ""; 2748 Lwt.return result)))) 2749 2750 let findUpdates ?wantWatcher subpaths = 2751 let compareRev x y = -1 * (Path.compare x y) in (* Sort in reverse *) 2752 let notIgnored p = not (Globals.shouldIgnore p) in 2753 let (//>) p ch = 2754 let rec prefix n1 n2 = 2755 match n1, n2 with 2756 | [], _ -> true 2757 | _, [] -> false 2758 | hd1 :: tl1, hd2 :: tl2 when Name.compare hd1 hd2 = 0 -> prefix tl1 tl2 2759 | _ -> false 2760 in 2761 prefix (Path.toNames p) (Path.toNames ch) 2762 in 2763 let rec keepPrefix acc p = 2764 match acc with 2765 | [] -> [p] 2766 | hd :: tl when p //> hd -> keepPrefix tl p (* p is hd's prefix, drop hd *) 2767 | _ -> p :: acc 2768 in 2769 let filteredPaths = 2770 Prefs.read Globals.paths 2771 |> Safelist.sort compareRev 2772 |> Safelist.fold_left keepPrefix [] 2773 |> Safelist.filter notIgnored 2774 in 2775 findUpdatesOnPaths ?wantWatcher filteredPaths subpaths 2776 2777 2778 (*****************************************************************************) 2779 (* Committing updates to disk *) 2780 (*****************************************************************************) 2781 2782 (* To prepare for committing, write to Scratch Archive *) 2783 let prepareCommitLocal compatMode (fspath, magic) = 2784 let (newName, root) = archiveName fspath ScratchArch in 2785 let archive = getArchive root in 2786 (** 2787 :ZheDebug: 2788 Format.set_formatter_out_channel stdout; 2789 Format.printf "prepareCommitLocal: %s\n" (thisRootsGlobalName fspath); 2790 showArchive archive; 2791 Format.print_flush(); 2792 **) 2793 let archiveHash = 2794 if not compatMode then checkArchive true [] archive 0 2795 else checkArchive251 true [] (to_compat251 archive) 0 in 2796 let props = getArchiveProps root in 2797 let props = purgePropsForPaths archive props in 2798 let props = externArchivePropsdata archive props in 2799 storeArchiveLocal 2800 (Util.fileInUnisonDir newName) root archive archiveHash magic props; 2801 Lwt.return (Some archiveHash) 2802 2803 let prepareCommitOnRoot = 2804 Remote.registerRootCmdWithConnection "prepareCommit" 2805 Umarshal.(prod2 Fspath.m string id id) Umarshal.(option int) 2806 (fun conn (fspath, magic) -> 2807 let compatMode = Remote.connectionVersion conn = 0 in 2808 prepareCommitLocal compatMode (fspath, magic)) 2809 2810 let prepareCommitOnRoots magic = 2811 match Globals.rootsInCanonicalOrder () with 2812 | [(Local, _); (Local, _)] -> 2813 Globals.allRootsMap (fun r -> prepareCommitLocal false (snd r, magic)) 2814 | [(Local, _); (Remote _, _) as r'] -> 2815 Globals.allRootsMap (fun r -> prepareCommitOnRoot r r' (snd r, magic)) 2816 | _ -> assert false 2817 2818 (* To really commit, first prepare (write to scratch arch.), then make sure 2819 the checksum on all archives are equal, finally flip scratch to main. In 2820 the event of checksum mismatch, dump archives on all roots and fail *) 2821 let commitUpdates () = 2822 Lwt_unix.run 2823 (debug (fun() -> Util.msg "Updating archives\n"); 2824 lockArchives () >>= (fun () -> 2825 Remote.Thread.unwindProtect 2826 (fun () -> 2827 let magic = 2828 Format.sprintf "%s\000%.0f.%d" 2829 ((Case.ops ())#modeDesc) (Unix.gettimeofday ()) (Unix.getpid ()) 2830 in 2831 prepareCommitOnRoots magic 2832 >>= (fun checksums -> 2833 if archivesIdentical checksums then begin 2834 (* Move scratch archives to new *) 2835 Globals.allRootsIter (fun r -> commitArchiveOnRoot r ()) 2836 >>= (fun () -> 2837 (* Copy new to main *) 2838 Globals.allRootsIter (fun r -> postCommitArchiveOnRoot r ()) 2839 >>= (fun () -> 2840 (* Clean up *) 2841 Globals.allRootsIter 2842 (fun r -> removeArchiveOnRoot r NewArch))) 2843 end else begin 2844 unlockArchives () >>= (fun () -> 2845 let warn = 2846 if (Unix.isatty Unix.stderr) then Util.msg "%s" 2847 else Trace.log in 2848 warn "Dumping archives to ~/unison.dump on both hosts\n"; 2849 Globals.allRootsIter (fun r -> dumpArchiveOnRoot r ()) 2850 >>= (fun () -> 2851 warn "Finished dumping archives\n"; 2852 raise (Util.Fatal ( 2853 "Internal error: New archives are not identical.\n" 2854 ^ "Retaining original archives. " 2855 ^ "Please run Unison again to bring them up to date.\n" 2856 (* 2857 ^ "If you get this message, please \n " 2858 ^ " a) notify unison-help@cis.upenn.edu\n" 2859 ^ " b) send us the contents of the file unison.dump \n" 2860 ^ " from both hosts (or just do a 'diff'\n" 2861 ^ " on these files and tell us what the differences\n" 2862 ^ " look like)\n" *) 2863 )))) 2864 end)) 2865 (fun _ -> unlockArchives ()) >>= (fun () -> 2866 unlockArchives ()))) 2867 2868 (*****************************************************************************) 2869 (* MARKING UPDATES *) 2870 (*****************************************************************************) 2871 2872 (* the result of patching [archive] using [ui] *) 2873 let rec updateArchiveRec ui archive = 2874 match ui with 2875 NoUpdates -> 2876 archive 2877 | Error _ -> 2878 NoArchive 2879 | Updates (uc, _) -> 2880 match uc with 2881 Absent -> 2882 NoArchive 2883 | File (desc, ContentsSame) -> 2884 begin match archive with 2885 ArchiveFile (_, fp, stamp, ress) -> 2886 ArchiveFile (desc, fp, stamp, ress) 2887 | _ -> 2888 assert false 2889 end 2890 | File (desc, ContentsUpdated (fp, stamp, ress)) -> 2891 ArchiveFile (desc, fp, stamp, ress) 2892 | Symlink l -> 2893 ArchiveSymlink l 2894 | Dir (desc, children, _, _) -> 2895 begin match archive with 2896 ArchiveDir (_, arcCh) -> 2897 let ch = 2898 Safelist.fold_right 2899 (fun (nm, uiChild) ch -> 2900 let ch' = NameMap.remove nm ch in 2901 let child = 2902 try NameMap.find nm ch with Not_found -> NoArchive in 2903 match updateArchiveRec uiChild child with 2904 NoArchive -> ch' 2905 | arch -> NameMap.add nm arch ch') 2906 children arcCh in 2907 ArchiveDir (desc, ch) 2908 | _ -> 2909 ArchiveDir 2910 (desc, 2911 Safelist.fold_right 2912 (fun (nm, uiChild) ch -> 2913 match updateArchiveRec uiChild NoArchive with 2914 NoArchive -> ch 2915 | arch -> NameMap.add nm arch ch) 2916 children NameMap.empty) 2917 end 2918 2919 (* Remove ignored files and properties that are not synchronized *) 2920 let rec stripArchive path arch = 2921 if Globals.shouldIgnore path then NoArchive else 2922 match arch with 2923 ArchiveDir (desc, children) -> 2924 ArchiveDir 2925 (Props.strip desc, 2926 NameMap.fold 2927 (fun nm ar ch -> 2928 match stripArchive (Path.child path nm) ar with 2929 NoArchive -> ch 2930 | ar' -> NameMap.add nm ar' ch) 2931 children NameMap.empty) 2932 | ArchiveFile (desc, fp, stamp, ress) -> 2933 ArchiveFile (Props.strip desc, fp, stamp, ress) 2934 | ArchiveSymlink _ | NoArchive -> 2935 arch 2936 2937 let updateArchive fspath path ui = 2938 debug (fun() -> 2939 Util.msg "updateArchive %s %s\n" 2940 (Fspath.toDebugString fspath) (Path.toString path)); 2941 let root = thisRootsGlobalName fspath in 2942 let archive = getArchive root in 2943 let (_, subArch) = getPathInArchive archive Path.empty path in 2944 updateArchiveRec ui (stripArchive path subArch) 2945 2946 (* (For breaking the dependency loop between update.ml and stasher.ml...) *) 2947 let stashCurrentVersion = ref (fun _ _ -> ()) 2948 let setStasherFun f = stashCurrentVersion := f 2949 2950 (* This function is called for files changed only in identical ways. 2951 It only updates the archives and perhaps makes backups. *) 2952 let markEqualLocal fspath paths = 2953 let root = thisRootsGlobalName fspath in 2954 let archive = ref (getArchive root) in 2955 Tree.iteri paths Path.empty Path.child 2956 (fun path uc -> 2957 debug (fun() -> 2958 Util.msg "markEqualLocal %s %s\n" 2959 (Fspath.toDebugString fspath) (Path.toString path)); 2960 let arch = 2961 updatePathInArchive !archive fspath Path.empty path 2962 (fun archive localPath -> 2963 !stashCurrentVersion fspath localPath; 2964 updateArchiveRec (Updates (uc, New)) archive) 2965 in 2966 archive := arch); 2967 setArchiveLocal root !archive 2968 2969 let convV0 = 2970 let to_compat251 = Tree.map (fun nm -> nm) Common.uc_to_compat251 2971 and of_compat251 = Tree.map (fun nm -> nm) Common.uc_of_compat251 in 2972 Remote.makeConvV0FunArg 2973 (fun (fspath, paths) -> (fspath, to_compat251 paths)) 2974 (fun (fspath, paths) -> (fspath, of_compat251 paths)) 2975 2976 let markEqualOnRoot = 2977 Remote.registerRootCmd 2978 "markEqual" ~convV0 (Tree.m Name.m Common.mupdateContent) Umarshal.unit 2979 (fun (fspath, paths) -> markEqualLocal fspath paths; Lwt.return ()) 2980 2981 let markEqual equals = 2982 debug (fun()-> Util.msg "Marking %d paths equal\n" (Tree.size equals)); 2983 if not (Tree.is_empty equals) then begin 2984 Lwt_unix.run 2985 (Globals.allRootsIter2 2986 markEqualOnRoot 2987 [Tree.map (fun (nm1, nm2) -> nm1) (fun (uc1,uc2) -> uc1) equals; 2988 Tree.map (fun (nm1, nm2) -> nm2) (fun (uc1,uc2) -> uc2) equals]) 2989 end 2990 2991 let replaceArchiveLocal fspath path newArch = 2992 debug (fun() -> Util.msg 2993 "replaceArchiveLocal %s %s\n" 2994 (Fspath.toDebugString fspath) 2995 (Path.toString path) 2996 ); 2997 let root = thisRootsGlobalName fspath in 2998 let archive = getArchive root in 2999 let archive = 3000 updatePathInArchive archive fspath Path.empty path (fun _ _ -> newArch) in 3001 setArchiveLocal root archive 3002 3003 let convV0 = Remote.makeConvV0FunArg 3004 (fun (fspath, (pathTo, arch)) -> (fspath, (pathTo, to_compat251 arch))) 3005 (fun (fspath, (pathTo, arch)) -> (fspath, (pathTo, of_compat251 arch))) 3006 3007 let replaceArchiveOnRoot = 3008 Remote.registerRootCmd 3009 "replaceArchive" ~convV0 3010 Umarshal.(prod2 Path.m marchive id id) Umarshal.unit 3011 (fun (fspath, (pathTo, arch)) -> 3012 replaceArchiveLocal fspath pathTo arch; 3013 Lwt.return ()) 3014 3015 let replaceArchive root pathTo archive = 3016 replaceArchiveOnRoot root (pathTo, archive) 3017 3018 (* Update the archive to reflect 3019 - the last observed state of the file on disk (ui) 3020 - the permission bits that have been propagated from the other 3021 replica, if any (permOpt) *) 3022 let doUpdateProps arch propOpt ui = 3023 let newArch = 3024 match ui with 3025 Updates (File (desc, ContentsSame), _) -> 3026 begin match arch with 3027 ArchiveFile (_, fp, stamp, ress) -> 3028 ArchiveFile (desc, fp, stamp, ress) 3029 | _ -> 3030 assert false 3031 end 3032 | Updates (File (desc, ContentsUpdated (fp, stamp, ress)), _) -> 3033 ArchiveFile(desc, fp, stamp, ress) 3034 | Updates (Dir (desc, _, _, _), _) -> 3035 begin match arch with 3036 ArchiveDir (_, children) -> ArchiveDir (desc, children) 3037 | _ -> ArchiveDir (desc, NameMap.empty) 3038 end 3039 | NoUpdates -> 3040 arch 3041 | Updates _ | Error _ -> 3042 assert false 3043 in 3044 match propOpt with 3045 Some desc' -> 3046 begin match newArch with 3047 ArchiveFile (desc, fp, stamp, ress) -> 3048 ArchiveFile (Props.override desc desc', fp, stamp, ress) 3049 | ArchiveDir (desc, children) -> 3050 ArchiveDir (Props.override desc desc', children) 3051 | _ -> 3052 assert false 3053 end 3054 | None -> newArch 3055 3056 let updateProps fspath path propOpt ui = 3057 debug (fun() -> 3058 Util.msg "updateProps %s %s\n" 3059 (Fspath.toDebugString fspath) (Path.toString path)); 3060 let root = thisRootsGlobalName fspath in 3061 let archive = getArchive root in 3062 let archive = 3063 updatePathInArchive archive fspath Path.empty path 3064 (fun arch _ -> doUpdateProps arch propOpt ui) in 3065 setArchiveLocal root archive 3066 3067 (*************************************************************************) 3068 (* Make sure no change has happened *) 3069 (*************************************************************************) 3070 3071 let fastCheckMiss path desc ress oldDesc oldRess = 3072 useFastChecking() 3073 && 3074 Props.same_time desc oldDesc 3075 && 3076 Props.length desc = Props.length oldDesc 3077 && 3078 not (Fpcache.excelFile path) 3079 && 3080 Osx.ressUnchanged oldRess ress None true 3081 3082 let doMarkPossiblyUpdated arch = 3083 match arch with 3084 ArchiveFile (desc, fp, stamp, ress) -> 3085 ArchiveFile (desc, fp, Fileinfo.RescanStamp, ress) 3086 | _ -> 3087 (* Should not happen, actually. But this is hard to test... *) 3088 arch 3089 3090 let markPossiblyUpdated fspath path = 3091 debug (fun() -> 3092 Util.msg "markPossiblyUpdated %s %s\n" 3093 (Fspath.toDebugString fspath) (Path.toString path)); 3094 let root = thisRootsGlobalName fspath in 3095 let archive = getArchive root in 3096 let archive = 3097 updatePathInArchive archive fspath Path.empty path 3098 (fun arch _ -> doMarkPossiblyUpdated arch) in 3099 setArchiveLocal root archive 3100 3101 let rec markPossiblyUpdatedRec fspath path ui = 3102 match ui with 3103 Updates (File (desc, ContentsUpdated (_, _, ress)), 3104 Previous (`FILE, oldDesc, _, oldRess)) -> 3105 if fastCheckMiss path desc ress oldDesc oldRess then 3106 markPossiblyUpdated fspath path 3107 | Updates (Dir (_, uiChildren, _, _), _) -> 3108 List.iter 3109 (fun (nm, uiChild) -> 3110 markPossiblyUpdatedRec fspath (Path.child path nm) uiChild) 3111 uiChildren 3112 | _ -> 3113 () 3114 3115 let reportUpdate warnFastCheck explanation = 3116 let msg = 3117 "Destination updated during synchronization\n" ^ explanation ^ 3118 if warnFastCheck then 3119 " (if this happens repeatedly on a file that has not been changed, \n\ 3120 \ try running once with 'fastcheck' set to false)" 3121 else 3122 "" 3123 in 3124 raise (Util.Transient msg) 3125 3126 let rec explainUpdate path ui = 3127 match ui with 3128 NoUpdates -> 3129 () 3130 | Error err -> 3131 raise (Util.Transient ("Could not check destination:\n" ^ err)) 3132 | Updates (Absent, _) -> 3133 reportUpdate false 3134 (Format.sprintf "The file %s has been deleted\n" 3135 (Path.toString path)) 3136 | Updates (File (_, ContentsSame), _) -> 3137 reportUpdate false 3138 (Format.sprintf "The properties of file %s have been modified\n" 3139 (Path.toString path)) 3140 | Updates (File (desc, ContentsUpdated (_, _, ress)), 3141 Previous (`FILE, oldDesc, oldFp, oldRess)) -> 3142 if not (Os.isPseudoFingerprint oldFp) then 3143 reportUpdate (fastCheckMiss path desc ress oldDesc oldRess) 3144 (Format.sprintf "The contents of file %s have been modified\n" 3145 (Path.toString path)) 3146 | Updates (File (_, ContentsUpdated _), _) -> 3147 reportUpdate false 3148 (Format.sprintf "The file %s has been created\n" 3149 (Path.toString path)) 3150 | Updates (Symlink _, Previous (`SYMLINK, _, _, _)) -> 3151 reportUpdate false 3152 (Format.sprintf "The symlink %s has been modified\n" 3153 (Path.toString path)) 3154 | Updates (Symlink _, _) -> 3155 reportUpdate false 3156 (Format.sprintf "The symlink %s has been created\n" 3157 (Path.toString path)) 3158 | Updates (Dir (_, _, PropsUpdated, _), Previous (`DIRECTORY, _, _, _)) -> 3159 reportUpdate false 3160 (Format.sprintf 3161 "The properties of directory %s have been modified\n" 3162 (Path.toString path)) 3163 | Updates (Dir (_, _, PropsUpdated, _), _) -> 3164 reportUpdate false 3165 (Format.sprintf "The directory %s has been created\n" 3166 (Path.toString path)) 3167 | Updates (Dir (_, uiChildren, PropsSame, _), _) -> 3168 List.iter 3169 (fun (nm, uiChild) -> explainUpdate (Path.child path nm) uiChild) 3170 uiChildren 3171 3172 let checkNoUpdates fspath pathInArchive ui = 3173 debug (fun() -> 3174 Util.msg "checkNoUpdates %s %s\n" 3175 (Fspath.toDebugString fspath) (Path.toString pathInArchive)); 3176 let archive = getArchive (thisRootsGlobalName fspath) in 3177 let (localPath, archive) = 3178 getPathInArchive archive Path.empty pathInArchive in 3179 (* Update the original archive to reflect what we believe is the current 3180 state of the replica... *) 3181 let archive = updateArchiveRec ui archive in 3182 (* ...and check that this is a good description of what's out in the world *) 3183 let scanInfo = 3184 { fastCheck = false; dirFastCheck = false; 3185 dirStamp = Props.changedDirStamp; rescanProps = true; 3186 archHash = "" (* Not used *); showStatus = false } in 3187 let (_, uiNew) = buildUpdateRec archive fspath localPath scanInfo in 3188 markPossiblyUpdatedRec fspath pathInArchive uiNew; 3189 explainUpdate pathInArchive uiNew; 3190 archive 3191 3192 (*****************************************************************************) 3193 (* UPDATE SIZE *) 3194 (*****************************************************************************) 3195 3196 let sizeZero = (0, Uutil.Filesize.zero) 3197 let sizeOne = (1, Uutil.Filesize.zero) 3198 let sizeAdd (items, bytes) (items', bytes') = 3199 (items + items', Uutil.Filesize.add bytes bytes') 3200 3201 let fileSize desc ress = 3202 (1, Uutil.Filesize.add (Props.length desc) (Osx.ressLength ress)) 3203 3204 let rec archiveSize arch = 3205 match arch with 3206 NoArchive -> 3207 sizeZero 3208 | ArchiveDir (_, arcCh) -> 3209 NameMap.fold 3210 (fun _ ar size -> sizeAdd size (archiveSize ar)) 3211 arcCh sizeOne 3212 | ArchiveFile (desc, _, _, ress) -> 3213 fileSize desc ress 3214 | ArchiveSymlink _ -> 3215 sizeOne 3216 3217 let rec updateSizeRec archive ui = 3218 match ui with 3219 NoUpdates -> 3220 archiveSize archive 3221 | Error _ -> 3222 sizeZero 3223 | Updates (uc, _) -> 3224 match uc with 3225 Absent -> 3226 sizeZero 3227 | File (desc, ContentsSame) -> 3228 begin match archive with 3229 ArchiveFile (_, _, _, ress) -> fileSize desc ress 3230 | _ -> assert false 3231 end 3232 | File (desc, ContentsUpdated (_, _, ress)) -> 3233 fileSize desc ress 3234 | Symlink l -> 3235 sizeOne 3236 | Dir (_, children, _, _) -> 3237 match archive with 3238 ArchiveDir (_, arcCh) -> 3239 let ch = NameMap.map (fun ch -> (ch, NoUpdates)) arcCh in 3240 let ch = 3241 List.fold_left 3242 (fun ch (nm, uiChild) -> 3243 let arcChild = 3244 try fst (NameMap.find nm ch) 3245 with Not_found -> NoArchive 3246 in 3247 NameMap.add nm (arcChild, uiChild) ch) 3248 ch children 3249 in 3250 NameMap.fold 3251 (fun _ (ar, ui) size -> sizeAdd size (updateSizeRec ar ui)) 3252 ch sizeOne 3253 | _ -> 3254 List.fold_left 3255 (fun size (_, uiChild) -> 3256 sizeAdd size (updateSizeRec NoArchive uiChild)) 3257 sizeOne children 3258 3259 let updateSize path ui = 3260 let rootLocal = Globals.localRoot () in 3261 let fspathLocal = snd rootLocal in 3262 let root = thisRootsGlobalName fspathLocal in 3263 let archive = getArchive root in 3264 let (_, subArch) = getPathInArchive archive Path.empty path in 3265 updateSizeRec subArch ui 3266 3267 (*****************************************************************************) 3268 (* MISC *) 3269 (*****************************************************************************) 3270 3271 let rec iterFiles fspath path arch f = 3272 match arch with 3273 ArchiveDir (_, children) -> 3274 NameMap.iter 3275 (fun nm arch -> iterFiles fspath (Path.child path nm) arch f) children 3276 | ArchiveFile (desc, fp, stamp, ress) -> 3277 f fspath path fp 3278 | _ -> 3279 () 3280 3281 (* Hook for filesystem auto-detection (not implemented yet) *) 3282 let inspectFilesystem = 3283 Remote.registerRootCmd 3284 "inspectFilesystem" Umarshal.unit Proplist.m 3285 (fun _ -> Lwt.return Proplist.empty)