unison

Fork of Unison, a bi-directional file synchronization tool
git clone git://git.laack.co/unison.git
Log | Files | Refs | README | LICENSE

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)