unison

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

props.ml (53790B)


      1 (* Unison file synchronizer: src/props.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 
     19 let debug = Util.debug "props"
     20 let debugverbose = Util.debug "props+"
     21 
     22 module type S = sig
     23   type t
     24   val m : t Umarshal.t
     25   val dummy : t
     26   val hash : t -> int -> int
     27   val similar : t -> t -> bool
     28   val override : t -> t -> t
     29   val strip : t -> t
     30   val diff : t -> t -> t
     31   val toString : t -> string
     32   val syncedPartsToString : t -> string
     33   val set : Fspath.t -> t -> unit
     34   val get : Unix.LargeFile.stats -> t
     35 end
     36 
     37 (* Nb: the syncedPartsToString call is only used for archive dumping, for    *)
     38 (* debugging purposes.  It could be deleted without losing functionality.    *)
     39 
     40 (**** Permissions ****)
     41 
     42 module Perm : sig
     43   include S
     44   val fileDefault : t
     45   val fileSafe : t
     46   val dirDefault : t
     47   val extract : t -> int
     48   val set : Fspath.t -> [`Set | `Update] -> t -> unit
     49   val check : Fspath.t -> Path.local -> Unix.LargeFile.stats -> t -> unit
     50   val validatePrefs : unit -> unit
     51   val permMask : int Prefs.t
     52   val dontChmod : bool Prefs.t
     53   val init : bool -> unit
     54 end = struct
     55 
     56 (* We introduce a type, Perm.t, that holds a file's permissions along with   *)
     57 (* the operating system where the file resides. Different operating systems  *)
     58 (* have different permission systems, so we have to take the OS into account *)
     59 (* when comparing and setting permissions.  We also need an "impossible"     *)
     60 (* permission that to take care of a tricky special case in update           *)
     61 (* detection.  It can be that the archive contains a directory that has      *)
     62 (* never been synchronized, although some of its children have been.  In     *)
     63 (* this case, the directory's permissions have never been synchronized and   *)
     64 (* might be different on the two replicas.  We use NullPerm for the          *)
     65 (* permissions of such an archive entry, and ensure (in similarPerms) that   *)
     66 (* NullPerm is never similar to any real permission.                         *)
     67 
     68 (* NOTE: IF YOU CHANGE TYPE "PERM", THE ARCHIVE FORMAT CHANGES; INCREMENT    *)
     69 (* "UPDATE.ARCHIVEFORMAT"                                                    *)
     70 type t = int * int
     71 
     72 let m = Umarshal.(prod2 int int id id)
     73 
     74 (* This allows us to export NullPerm while keeping the type perm abstract    *)
     75 let dummy = (0, 0)
     76 
     77 let extract = fst
     78 
     79 let unix_mask =
     80     0o7777 (* All bits *)
     81 let wind_mask =
     82     0o200 (* -w------- : only the write bit can be changed in Windows *)
     83 
     84 let permMask =
     85   Prefs.createInt "perms"
     86     (0o777 (* rwxrwxrwx *) + 0o1000 (* Sticky bit *))
     87     ~category:(`Basic `Sync)
     88     "part of the permissions which is synchronized"
     89     "The integer value of this preference is a mask indicating which \
     90      permission bits should be synchronized.  It is set by default to \
     91      $0o1777$: all bits but the set-uid and set-gid bits are \
     92      synchronised (synchronizing these latter bits can be a security \
     93      hazard).  If you want to synchronize all bits, you can set the \
     94      value of this preference to $-1$.  If one of the replica is on \
     95      a FAT [Windows] filesystem, you should consider using the \
     96      {\\tt fat} preference instead of this preference.  If you need \
     97      Unison not to set permissions at all, set the value of this \
     98      preference to $0$ and set the preference {\\tt dontchmod} to {\\tt true}."
     99 
    100 (* Os-specific local conventions on file permissions                         *)
    101 let (fileDefault, dirDefault, fileSafe, dirSafe) =
    102   match Sys.win32 with
    103   | true ->
    104       debug
    105         (fun() ->
    106            Util.msg "Using windows defaults for file permissions");
    107       ((0o600, -1), (* rw------- *)
    108        (0o700, -1), (* rwx------ *)
    109        (0o600, -1), (* rw------- *)
    110        (0o700, -1)) (* rwx------ *)
    111   | false ->
    112       let umask =
    113         let u = Unix.umask 0 in
    114         ignore (Unix.umask u);
    115         debug
    116           (fun() ->
    117              Util.msg "Umask: %s" (Printf.sprintf "%o" u));
    118         (fun fp -> (lnot u) land fp) in
    119       ((umask 0o666, -1), (* rw-rw-rw- *)
    120        (umask 0o777, -1), (* rwxrwxrwx *)
    121        (umask 0o600, -1), (* rw------- *)
    122        (umask 0o700, -1)) (* rwx------ *)
    123 
    124 let hash (p, m) h = Uutil.hash2 (p land m) (Uutil.hash2 m h)
    125 
    126 let perm2fileperm (p, m) = p
    127 let fileperm2perm p = (p, Prefs.read permMask)
    128 
    129 (* Are two perms similar (for update detection and recon)                    *)
    130 let similar (p1, m1) (p2, m2) =
    131   let m = Prefs.read permMask in
    132   m1 land m = m && m2 land m = m &&
    133   p1 land m = p2 land m
    134 
    135 (* overrideCommonPermsIn p1 p2 : gives the perm that would result from       *)
    136 (* propagating p2 to p1. We expect the following invariants: similarPerms    *)
    137 (* (overrideCommonPermsIn p1 p2) p2 (whenever similarPerms p2 p2) and        *)
    138 (* hashPerm (overrideCommonPermsIn p1 p2) = hashPerm p2                      *)
    139 let override (p1, m1) (p2, m2) =
    140   let m = Prefs.read permMask land m2 in
    141   ((p1 land (lnot m)) lor (p2 land m), m)
    142 
    143 let strip (p, m) = (p, m land (Prefs.read permMask))
    144 
    145 let diff (p, m) (p', m') = (p', (p lxor p') land m land m')
    146 
    147 let toString =
    148   function
    149     (_, 0) -> "unknown permissions"
    150   | (fp, _) when Prefs.read permMask = wind_mask ->
    151       if fp land wind_mask <> 0 then "read-write" else "read-only"
    152   | (fp, _) ->
    153      let m = Prefs.read permMask in
    154      let bit mb unknown off on =
    155        if mb land m = 0 then
    156          unknown
    157        else if fp land mb <> 0 then
    158          on
    159        else
    160          off
    161      in
    162      bit 0o4000 "" "-"  "S" ^
    163      bit 0o2000 "" "-"  "s" ^
    164      bit 0o1000 "?" ""  "t" ^
    165      bit 0o0400 "?" "-" "r" ^
    166      bit 0o0200 "?" "-" "w" ^
    167      bit 0o0100 "?" "-" "x" ^
    168      bit 0o0040 "?" "-" "r" ^
    169      bit 0o0020 "?" "-" "w" ^
    170      bit 0o0010 "?" "-" "x" ^
    171      bit 0o0004 "?" "-" "r" ^
    172      bit 0o0002 "?" "-" "w" ^
    173      bit 0o0001 "?" "-" "x"
    174 
    175 let syncedPartsToString =
    176   function
    177     (_, 0) -> "unknown permissions"
    178   | (fp, m) ->
    179      let bit mb unknown off on =
    180        if mb land m = 0 then
    181          unknown
    182        else if fp land mb <> 0 then
    183          on
    184        else
    185          off
    186      in
    187      bit 0o4000 "" "-"  "S" ^
    188      bit 0o2000 "" "-"  "s" ^
    189      bit 0o1000 "?" ""  "t" ^
    190      bit 0o0400 "?" "-" "r" ^
    191      bit 0o0200 "?" "-" "w" ^
    192      bit 0o0100 "?" "-" "x" ^
    193      bit 0o0040 "?" "-" "r" ^
    194      bit 0o0020 "?" "-" "w" ^
    195      bit 0o0010 "?" "-" "x" ^
    196      bit 0o0004 "?" "-" "r" ^
    197      bit 0o0002 "?" "-" "w" ^
    198      bit 0o0001 "?" "-" "x"
    199 
    200 let dontChmod =
    201   Prefs.createBool "dontchmod"
    202   false
    203   ~category:(`Advanced `Syncprocess)
    204   "when set, never use the chmod system call"
    205   (  "By default, Unison uses the 'chmod' system call to set the permission bits"
    206   ^ " of files after it has copied them.  But in some circumstances (and under "
    207   ^ " some operating systems), the chmod call always fails.  Setting this "
    208   ^ " preference completely prevents Unison from ever calling chmod.")
    209 
    210 let validatePrefs () =
    211   if Prefs.read dontChmod && (Prefs.read permMask <> 0) then raise (Util.Fatal
    212     "If the 'dontchmod' preference is set, the 'perms' preference should be 0")
    213 
    214 let set abspath kind (fp, mask) =
    215   (* BCP: removed "|| kind <> `Update" on 10/2005, but reinserted it on 11/2008.
    216      I'd removed it to make Dale Worley happy -- he wanted a way to make sure that
    217      Unison would never call chmod, and setting prefs to 0 seemed like a reasonable
    218      way to do this.  But in fact it caused new files to be created with wrong prefs.
    219    *)
    220   if (mask <> 0 || kind = `Set) && (not (Prefs.read dontChmod)) then
    221     Util.convertUnixErrorsToTransient
    222     "setting permissions"
    223       (fun () ->
    224         debug
    225           (fun() ->
    226             Util.msg "Setting permissions for %s to %s (%s)\n"
    227               (Fspath.toDebugString abspath) (toString (fileperm2perm fp))
    228               (Printf.sprintf "%o/%o" fp mask));
    229         try
    230           Fs.chmod abspath fp
    231         with Unix.Unix_error (Unix.EOPNOTSUPP, _, _) as e ->
    232           try
    233             Util.convertUnixErrorsToTransient "setting permissions"
    234               (fun () -> raise e)
    235           with Util.Transient msg ->
    236             raise (Util.Transient
    237                      (msg ^
    238                       ". You can use preference \"fat\",\
    239                        or else set preference \"perms\" to 0 and \
    240                        preference \"dontchmod\" to true to avoid this error")))
    241 
    242 let get stats = (stats.Unix.LargeFile.st_perm, Prefs.read permMask)
    243 
    244 let check fspath path stats (fp, mask) =
    245   let fp' = stats.Unix.LargeFile.st_perm in
    246   if fp land mask <> fp' land mask then
    247     raise
    248       (Util.Transient
    249          (Format.sprintf
    250             "Failed to set permissions of file %s to %s: \
    251              the permissions was set to %s instead. \
    252              The filesystem probably does not support all permission bits. \
    253              If this is a FAT filesystem, you should set the \"fat\" option \
    254              to true. \
    255              Otherwise, you should probably set the \"perms\" option to 0o%o \
    256              (or to 0 if you don't need to synchronize permissions)."
    257             (Fspath.toPrintString (Fspath.concat fspath path))
    258             (syncedPartsToString (fp, mask))
    259             (syncedPartsToString (fp', mask))
    260             ((Prefs.read permMask) land (lnot (fp lxor fp')))))
    261 
    262 let init someHostIsRunningWindows =
    263   let mask = if someHostIsRunningWindows then wind_mask else unix_mask in
    264   let oldMask = Prefs.read permMask in
    265   let newMask = oldMask land mask in
    266   debug
    267     (fun() ->
    268       Util.msg "Setting permission mask to %s (%s and %s)\n"
    269         (Printf.sprintf "%o" newMask)
    270         (Printf.sprintf "%o" oldMask)
    271         (Printf.sprintf "%o" mask));
    272   Prefs.set permMask newMask
    273 
    274 end
    275 
    276 (* ------------------------------------------------------------------------- *)
    277 (*                         User and group ids                                *)
    278 (* ------------------------------------------------------------------------- *)
    279 
    280 let numericIds =
    281   Prefs.createBool "numericids" false
    282     ~category:(`Advanced `Syncprocess)
    283     "don't map uid/gid values by user/group names"
    284     "When this flag is set to \\verb|true|, groups and users are \
    285      synchronized numerically, rather than by name. \n\
    286      \n\
    287      The special uid 0 and the special group 0 are never mapped via \
    288      user/group names even if this preference is not set."
    289 
    290 (* For backward compatibility *)
    291 let _ = Prefs.alias numericIds "numericIds"
    292 
    293 module Id (M : sig
    294   val sync : bool Prefs.t
    295   val kind : string
    296   val to_num : string -> int
    297   val toString : int -> string
    298   val syncedPartsToString : int -> string
    299   val set : Fspath.t -> int -> unit
    300   val get : Unix.LargeFile.stats -> int
    301 end) : sig
    302   include S
    303   val init : bool -> unit
    304 end = struct
    305 
    306 type t =
    307     IdIgnored
    308   | IdNamed of string
    309   | IdNumeric of int
    310 
    311 let m = Umarshal.(sum3 unit string int
    312                     (function
    313                      | IdIgnored -> I31 ()
    314                      | IdNamed a -> I32 a
    315                      | IdNumeric a -> I33 a)
    316                     (function
    317                      | I31 () -> IdIgnored
    318                      | I32 a -> IdNamed a
    319                      | I33 a -> IdNumeric a))
    320 
    321 let dummy = IdIgnored
    322 
    323 let hash id h =
    324   Uutil.hash2
    325     (match id with
    326        IdIgnored   -> -1
    327      | IdNumeric i -> i
    328      | IdNamed nm  -> Uutil.hash nm)
    329     h
    330 
    331 let similar id id' =
    332   not (Prefs.read M.sync)
    333     ||
    334   (id <> IdIgnored && id' <> IdIgnored && id = id')
    335 
    336 let override id id' = id'
    337 
    338 let strip id = if Prefs.read M.sync then id else IdIgnored
    339 
    340 let diff id id' = if similar id id' then IdIgnored else id'
    341 
    342 let toString id =
    343   match id with
    344     IdIgnored   -> ""
    345   | IdNumeric i -> " " ^ M.kind ^ "=" ^ string_of_int i
    346   | IdNamed n   -> " " ^ M.kind ^ "=" ^ n
    347 
    348 let syncedPartsToString = toString
    349 
    350 let tbl = Hashtbl.create 17
    351 
    352 let extern id =
    353   match id with
    354     IdIgnored   -> -1
    355   | IdNumeric i -> i
    356   | IdNamed nm  ->
    357       try
    358         Hashtbl.find tbl nm
    359       with Not_found ->
    360         let id =
    361           try M.to_num nm with Not_found ->
    362             raise (Util.Transient ("No " ^ M.kind ^ " " ^ nm))
    363         in
    364         if id = 0 then
    365           raise (Util.Transient
    366                    (Printf.sprintf "Trying to map the non-root %s %s to %s 0"
    367                       M.kind nm M.kind));
    368         Hashtbl.add tbl nm id;
    369         id
    370 
    371 let set abspath id =
    372   match extern id with
    373     -1 ->
    374       ()
    375   | id ->
    376       Util.convertUnixErrorsToTransient
    377         "setting file ownership"
    378         (fun () ->
    379            M.set abspath id)
    380 
    381 let tbl = Hashtbl.create 17
    382 
    383 let get stats =
    384   if not (Prefs.read M.sync) then IdIgnored else
    385   let id = M.get stats in
    386   if id = 0 || Prefs.read numericIds then IdNumeric id else
    387   try
    388     Hashtbl.find tbl id
    389   with Not_found ->
    390     let id' = try IdNamed (M.toString id) with Not_found -> IdNumeric id in
    391     Hashtbl.add tbl id id';
    392     id'
    393 
    394 let init someHostIsRunningWindows =
    395   if someHostIsRunningWindows then
    396     Prefs.set M.sync false;
    397 
    398 end
    399 
    400 module Uid = Id (struct
    401 
    402 let sync =
    403   Prefs.createBool "owner" false
    404     ~category:(`Basic `Sync)
    405     "synchronize owner"
    406     ("When this flag is set to \\verb|true|, the owner attributes "
    407      ^ "of the files are synchronized.  "
    408      ^ "Whether the owner names or the owner identifiers are synchronized"
    409      ^ "depends on the preference \\texttt{numerids}.")
    410 
    411 let kind = "user"
    412 
    413 let to_num nm = (Unix.getpwnam nm).Unix.pw_uid
    414 let toString id = (Unix.getpwuid id).Unix.pw_name
    415 let syncedPartsToString = toString
    416 
    417 let set path id = Fs.chown path id (-1)
    418 let get stats = stats.Unix.LargeFile.st_uid
    419 
    420 end)
    421 
    422 module Gid = Id (struct
    423 
    424 let sync =
    425   Prefs.createBool "group" false
    426     ~category:(`Basic `Sync)
    427     "synchronize group attributes"
    428     ("When this flag is set to \\verb|true|, the group attributes "
    429      ^ "of the files are synchronized.  "
    430      ^ "Whether the group names or the group identifiers are synchronized "
    431      ^ "depends on the preference \\texttt{numerids}.")
    432 
    433 let kind = "group"
    434 
    435 let to_num nm = (Unix.getgrnam nm).Unix.gr_gid
    436 let toString id = (Unix.getgrgid id).Unix.gr_name
    437 let syncedPartsToString = toString
    438 
    439 let set path id = Fs.chown path (-1) id
    440 let get stats = stats.Unix.LargeFile.st_gid
    441 
    442 end)
    443 
    444 (* ------------------------------------------------------------------------- *)
    445 (*                          Modification time                                *)
    446 (* ------------------------------------------------------------------------- *)
    447 
    448 module Time : sig
    449   include S
    450   val same : t -> t -> bool
    451   val extract : t -> float
    452   val sync : bool Prefs.t
    453   val replace : t -> float -> t
    454   val check : Fspath.t -> Path.local -> Unix.LargeFile.stats -> t -> unit
    455 end = struct
    456 
    457 let sync =
    458   Prefs.createBool "times" false
    459     ~category:(`Basic `Sync)
    460     "synchronize modification times"
    461     "When this flag is set to \\verb|true|, \
    462      file modification times (but not directory modtimes) are propagated."
    463 
    464 type t = Synced of float | NotSynced of float
    465 
    466 let m = Umarshal.(sum2 float float
    467                     (function
    468                      | Synced a -> I21 a
    469                      | NotSynced a -> I22 a)
    470                     (function
    471                      | I21 a -> Synced a
    472                      | I22 a -> NotSynced a))
    473 
    474 let dummy = NotSynced 0.
    475 
    476 let extract t = match t with Synced v -> v | NotSynced v -> v
    477 
    478 let minus_two = Int64.of_int (-2)
    479 let approximate t = Int64.logand (Int64.of_float t) minus_two
    480 
    481 (* Set up for ignoring 1s differences in function similar, below *)
    482 let possible_deltas =
    483   [ -1L; 1L; 0L ]
    484 
    485 let hash t h =
    486   Uutil.hash2
    487     (match t with
    488        Synced _    -> 1 (* As we are ignoring one-second differences,
    489                            we cannot provide a more accurate hash. *)
    490      | NotSynced _ -> 0)
    491     h
    492 
    493 (* Times have a two-second granularity on FAT filesystems.  They are
    494    approximated upward under Windows, downward under Linux...
    495    Ignoring one-second changes also makes Unison more robust when
    496    dealing with systems with sub-second granularity (we have no control
    497    on how this is may be rounded). *)
    498 let similar t t' =
    499   not (Prefs.read sync)
    500     ||
    501   match t, t' with
    502     Synced v, Synced v'      ->
    503       List.mem (Int64.sub (Int64.of_float v)  (Int64.of_float v'))
    504         possible_deltas
    505   | NotSynced _, NotSynced _ ->
    506       true
    507   | _                        ->
    508       false
    509 
    510 let override t t' =
    511   match t, t' with
    512     _, Synced _ -> t'
    513   | Synced v, _ -> NotSynced v
    514   | _           -> t
    515 
    516 let replace t v =
    517   match t with
    518     Synced _    -> Synced v
    519   | NotSynced _ -> NotSynced v
    520 
    521 let strip t =
    522   match t with
    523     Synced v when not (Prefs.read sync) -> NotSynced v
    524   |  _                                  -> t
    525 
    526 let diff t t' = if similar t t' then NotSynced (extract t') else t'
    527 
    528 let toString t = Util.time2string (extract t)
    529 
    530 let syncedPartsToString t = match t with
    531   Synced _    -> Format.sprintf "%s (%f)" (toString t) (extract t)
    532 | NotSynced _ -> ""
    533 
    534 (* FIX: Probably there should be a check here that prevents us from ever     *)
    535 (* setting a file's modtime into the future.                                 *)
    536 let set abspath t =
    537   match t with
    538     Synced v ->
    539       Util.convertUnixErrorsToTransient
    540         "setting modification time"
    541         (fun () ->
    542            if false then begin
    543              (* A special hack for Rasmus, who has a special situation that
    544                 requires the utimes-setting program to run 'setuid root'
    545                 (and we do not want all of Unison to run setuid, so we just
    546                 spin off an external utility to do it). *)
    547              let time = Unix.localtime v in
    548              let tstr = Printf.sprintf
    549                           "%4d%02d%02d%02d%02d.%02d"
    550                           (time.Unix.tm_year + 1900)
    551                           (time.Unix.tm_mon + 1)
    552                           time.Unix.tm_mday
    553                           time.Unix.tm_hour
    554                           time.Unix.tm_min
    555                           time.Unix.tm_sec in
    556              let cmd = "/usr/local/bin/sudo -u root /usr/bin/touch -m -a -t "
    557                        ^ tstr ^ " " ^ Fspath.quotes abspath in
    558              Util.msg "Running external program to set utimes:\n  %s\n" cmd;
    559              let r = System.close_process_in (System.open_process_in cmd) in
    560              if r<>(Unix.WEXITED 0) then raise (Util.Transient "External time-setting command failed")
    561            end else
    562              Fs.utimes abspath (if v = 0. then 1e-12 else v) v)
    563              (* If atime and mtime arguments are both 0 then Unix.utimes
    564                 will set actual atime and mtime on the file to be the
    565                 current timestamp, which is not the desired result.
    566                 To sync the exact mtime value of 0, atime must be non-zero.
    567                 Setting atime to be different from zero by less than a
    568                 nanosecond allows to achieve the desired result.
    569                 https://github.com/bcpierce00/unison/issues/223 *)
    570   | _ ->
    571       ()
    572 
    573 let get stats =
    574   let v = stats.Unix.LargeFile.st_mtime in
    575   if stats.Unix.LargeFile.st_kind = Unix.S_REG && Prefs.read sync then
    576     Synced v
    577   else
    578     NotSynced v
    579 
    580 let check fspath path stats t =
    581   match t with
    582     NotSynced _ ->
    583       ()
    584   | Synced v ->
    585       let t' = Synced (stats.Unix.LargeFile.st_mtime) in
    586       if not (similar t t') then
    587         raise
    588           (Util.Transient
    589              (Format.sprintf
    590                 "Failed to set modification time of file %s to %s: \
    591              the time was set to %s instead"
    592             (Fspath.toPrintString (Fspath.concat fspath path))
    593             (syncedPartsToString t)
    594             (syncedPartsToString t')))
    595 
    596 (* When modification time are synchronized, we cannot update the
    597    archive when they are changed due to daylight saving time.  Thus,
    598    we have to compare then using "similar". *)
    599 let same p p' =
    600   match p, p' with
    601     Synced _, Synced _ ->
    602       similar p p'
    603   | _                  ->
    604       let delta = extract p -. extract p' in
    605       delta = 0. || delta = 3600. || delta = -3600.
    606 
    607 end
    608 
    609 (* ------------------------------------------------------------------------- *)
    610 (*                          Type and creator                                 *)
    611 (* ------------------------------------------------------------------------- *)
    612 
    613 module TypeCreator :
    614   sig
    615     include S
    616     val set : Fspath.t -> Path.local -> t -> unit
    617     val get : Unix.LargeFile.stats -> Osx.info -> t
    618   end = struct
    619 
    620 type t = string option
    621 
    622 let m = Umarshal.(option string)
    623 
    624 let dummy = None
    625 
    626 let hash t h = Uutil.hash2 (Uutil.hash t) h
    627 
    628 let similar t t' =
    629   not (Prefs.read Osx.rsrc) || t = t'
    630 
    631 let override t t' = t'
    632 
    633 let strip t = t
    634 
    635 let diff t t' = if similar t t' then None else t'
    636 
    637 let zeroes = "\000\000\000\000\000\000\000\000"
    638 
    639 let toString t =
    640   match t with
    641     Some s when String.length s > 0 && s.[0] = 'F' &&
    642                 String.sub (s ^ zeroes) 1 8 <> zeroes ->
    643       let s = s ^ zeroes in
    644       " " ^ String.escaped (String.sub s 1 4) ^
    645       " " ^ String.escaped (String.sub s 5 4)
    646   | _ ->
    647       ""
    648 
    649 let syncedPartsToString = toString
    650 
    651 let set fspath path t =
    652   match t with
    653     None   -> ()
    654   | Some t -> Osx.setFileInfos fspath path t
    655 
    656 let get stats info =
    657   if
    658     Prefs.read Osx.rsrc &&
    659     (stats.Unix.LargeFile.st_kind = Unix.S_REG ||
    660      stats.Unix.LargeFile.st_kind = Unix.S_DIR)
    661   then
    662     Some info.Osx.finfo
    663   else
    664     None
    665 
    666 end
    667 
    668 (* ------------------------------------------------------------------------- *)
    669 (*                               Change time                                 *)
    670 (* ------------------------------------------------------------------------- *)
    671 
    672 (* ctime itself is never synchronized. It is only leveraged for faster
    673    metadata update detection; and stored in archive for this purpose. *)
    674 
    675 module CTime : sig
    676   type t
    677   val m : t Umarshal.t
    678   val dummy : t
    679   val override : t -> t -> t
    680   val get : Unix.LargeFile.stats -> t
    681   val same_time : t -> t -> bool
    682 end = struct
    683 
    684 type t = float
    685 
    686 let m = Umarshal.float
    687 
    688 let dummy = -1.
    689 
    690 (* Currently [override] does not work for ctime because the real on-disk
    691    ctime will inevitably change when the final props are set on disk by
    692    [Files.setProp] or the final rename after copying is done in [Files.copy]
    693    (these happen after [override]). There is no [stat] done after these
    694    operations, so this final ctime will not get stored in the archive.
    695    It is not a major issue and doesn't break anything. The only side-effect is
    696    that at next updates scan the entire set of metadata for this file/dir is
    697    scanned (as if fastcheck was disabled); which may even be a good thing.
    698    Not worth changing or adding the cost of an additional [stat]. But if it
    699    is changed in future then the proper ctime value must be extracted in
    700    [Props.get']. *)
    701 let override t t' = t
    702 
    703 let get stats = stats.Unix.LargeFile.st_ctime
    704 
    705 let same_time t t' = System.hasCorrectCTime && t = t'
    706 
    707 end
    708 
    709 (* ------------------------------------------------------------------------- *)
    710 (*                        Extended attributes (xattr)                        *)
    711 (* ------------------------------------------------------------------------- *)
    712 
    713 let featXattrValid = ref (fun _ _ -> None)
    714 
    715 let featXattr =
    716   Features.register "Sync: xattr" ~arcFormatChange:true
    717   (Some (fun a b -> !featXattrValid a b))
    718 
    719 let xattrEnabled () = Features.enabled featXattr
    720 
    721 let syncXattrs =
    722   Prefs.createBool "xattrs" false
    723     ~category:(`Advanced `Sync)
    724     ~send:xattrEnabled
    725     "synchronize extended attributes (xattrs)"
    726     ("When this flag is set to \\verb|true|, the extended attributes of \
    727      files and directories are synchronized. System extended attributes \
    728      are not synchronized.")
    729 
    730 let () = featXattrValid :=
    731   fun _ enabledThis ->
    732     if not enabledThis && Prefs.read syncXattrs then
    733       Some ("You have requested synchronization of extended attributes (the \
    734         \"xattrs\" preference) but the server does not support this.")
    735     else None
    736 
    737 let xattrIgnorePred =
    738   Pred.create "xattrignore"
    739     ~category:(`Advanced `Sync)
    740     ~send:xattrEnabled
    741     (* By default ignore the Linux xattr security and trusted namespaces *)
    742     ~initial:["Regex !(security|trusted)[.].*"; "Path !system.posix_acl_*"]
    743     ("Preference \\texttt{-xattrignore \\ARG{namespec}} causes Unison to \
    744      ignore extended attributes with names that match \\ARG{namespec}. \
    745      This can be used to exclude extended attributes that would fail \
    746      synchronization due to lack of permissions or technical differences \
    747      at replicas. The syntax of \\ARG{namespec} is the same as used \
    748      for path specification (described in \
    749      \\sectionref{pathspec}{Path Specification}); prefer the \\verb|Path| \
    750      and \\verb|Regex| forms over the \\verb|Name| form. The pattern is \
    751      applied to the {\\em name} of extended attribute, not to path. \
    752      {\\em On Linux}, attributes in the security and trusted namespaces \
    753      are ignored by default (this is achieved by pattern \\texttt{Regex \
    754      !(security|trusted)[.].*}); also attributes used to store POSIX ACL \
    755      are ignored by default (this is achieved by pattern \\texttt{Path \
    756      !system.posix\\_acl\\_*}). To sync attributes in one or both of \
    757      these namespaces, see the \\verb|xattrignorenot| preference. \
    758      Note that the namespace name must be prefixed with a \"!\" (applies \
    759      on Linux only). All names not prefixed with a \"!\" are taken \
    760      as strictly belonging to the user namespace and therefore the \
    761      \"!user.\" prefix is never used.")
    762 
    763 let xattrIgnorenotPred =
    764   Pred.create "xattrignorenot"
    765     ~category:(`Advanced `Sync)
    766     ~send:xattrEnabled
    767     ("This preference overrides the preference \\texttt{xattrignore}. \
    768      It gives a list of patterns (in the same format as \
    769      \\verb|xattrignore|) for extended attributes that should {\\em not} \
    770      be ignored, whether or not they happen to match one of the \
    771      \\verb|xattrignore| patterns. It is possible to synchronize only \
    772      desired attributes by ignoring all attributes (for example, by \
    773      setting \\verb|xattrignore| to \\texttt{Path *} and then adding \
    774      \\verb|xattrignorenot| for extended attributes that should be \
    775      synchronized. \
    776      {\\em On Linux}, attributes in the security and trusted namespaces \
    777      are ignored by default. To sync attributes in one or both of these \
    778      namespaces, you may add an \\verb|xattrignorenot| pattern like \
    779      \\texttt{Path !security.*} to sync all attributes in the \
    780      security namespace, or \\texttt{Path !security.selinux} to sync \
    781      a specific attribute in an otherwise ignored namespace. A pattern \
    782      like \\texttt{Path !system.posix\\_acl\\_*} can be used to sync \
    783      POSIX ACLs on Linux. \
    784      Note that the namespace name must be prefixed with a \"!\" (applies \
    785      on Linux only). All names not prefixed with a \"!\" are taken \
    786      as strictly belonging to the user namespace and therefore the \
    787      \"!user.\" prefix is never used.")
    788 
    789 module Xattr : sig
    790   include S
    791   val ctimeDetect : bool
    792   val get : Fspath.t -> Unix.LargeFile.stats -> t
    793   val readAll : Fspath.t -> t -> t
    794   val getAll : t -> t
    795   val purge : t -> t
    796   val check : Fspath.t -> Path.local -> Unix.LargeFile.stats -> t -> unit
    797   module Data : Propsdata.S
    798 end = struct
    799 
    800 module Size = Uutil.Filesize
    801 
    802 module Data = Propsdata.Xattr
    803 
    804 module Cache = struct
    805   let get key = Data.find_opt key
    806 
    807   let add key value =
    808     (* Cache relatively small data in a relatively small quantity to keep
    809        the memory pressure and network traffic at updates scanning low.
    810 
    811        There is no cache management. Once it's full, it's full. This can be
    812        enhanced in future, if needed. *)
    813     if String.length value < 1024 && Data.length () < 200 then
    814       Data.add key value;
    815     value
    816 end
    817 
    818 type attrvalue =
    819   | String of string
    820   | Hash of string
    821   | Loaded of (string * string) (* full value, hash *)
    822 
    823 let mattrvalue = Umarshal.(sum3 string string (prod2 string string id id)
    824                             (function
    825                              | String v -> I31 v
    826                              | Hash v -> I32 v
    827                              | Loaded v -> I33 v)
    828                             (function
    829                              | I31 v -> String v
    830                              | I32 v -> Hash v
    831                              | I33 v -> Loaded v))
    832 
    833 type attrlist = (string * attrvalue) list
    834 
    835 let mattrlist = Umarshal.(list (prod2 string mattrvalue id id))
    836 
    837 type sizeandattrs = attrlist * Uutil.Filesize.t
    838 
    839 let msizeandattrs = Umarshal.(prod2 mattrlist Uutil.Filesize.m id id)
    840 
    841 (* None indicates xattrs are not supported. This is not synchronized.
    842  * An empty list means xattrs are supported but there are none on the file.
    843  * This will be synchronized. *)
    844 type t = sizeandattrs option
    845 
    846 let dummy = None
    847 
    848 let m = Umarshal.cond xattrEnabled dummy Umarshal.(option msizeandattrs)
    849 
    850 let ctimeDetect = System.xattrUpdatesCTime
    851 
    852 (* Since [hash] is supposed to be run after [purge] (resulting in the
    853    data that is stored in the archives) then we don't need to take
    854    into account the difference between Hash and Loaded.
    855 
    856    The attribute list must be sorted to get a stable hash. The list
    857    is sorted once, when retrieving it from fs. If sorting conditions
    858    are changed in future then this hash function may have to be
    859    changed to retain backwards compatibility. *)
    860 let hash t h = if Prefs.read syncXattrs then Uutil.hash2 (Uutil.hash t) h else h
    861 
    862 let attrToString = function
    863   | (n, String v) ->
    864       Printf.sprintf "Name: %s    Value: %s" n (String.escaped v)
    865   | (n, Hash h) ->
    866       Printf.sprintf "Name: %s    Fingerprint: %s" n (Digest.to_hex h)
    867   | (n, Loaded (_, h)) ->
    868       Printf.sprintf "Name: %s    Fingerprint: %s" n (Digest.to_hex h)
    869 
    870 let toString' style = function
    871   | Some ([], _) -> "0 xattrs"
    872   | Some ([(n, _) as x], z) ->
    873       Printf.sprintf "1 xattr (%s bytes)%s" (Size.toString z)
    874         (match style with
    875         | `Summary -> ""
    876         | `Simple -> ": " ^ n
    877         | `Verbose -> ": " ^ attrToString x)
    878   | Some (l, z) ->
    879       Printf.sprintf "%u xattrs (%s bytes)%s" (Safelist.length l) (Size.toString z)
    880         (match style with
    881         | `Summary -> ""
    882         | `Simple -> ": " ^ (String.concat ", " (Safelist.map (fun (n, _) -> n) l))
    883         | `Verbose -> "\n  " ^ (String.concat "\n  " (Safelist.map attrToString l)))
    884   | None -> ""
    885 
    886 let toString = function
    887   | None -> ""
    888   | t -> " " ^ toString' `Summary t
    889 
    890 let syncedPartsToString t = " " ^ toString' `Simple t
    891 
    892 let toDebugString t = toString' `Simple t
    893 
    894 let toStringVerb t = toString' `Verbose t
    895 
    896 let attrEqual (n, v) (n', v') =
    897   String.equal n n' &&
    898   match v, v' with
    899   | String a, String b
    900   | String a, Loaded (b, _)
    901   | Hash a, Hash b
    902   | Hash a, Loaded (_, b)
    903   | Loaded (a, _), String b
    904   | Loaded (_, a), Hash b
    905   | Loaded (_, a), Loaded (_, b) -> String.equal a b
    906   | String s, Hash h
    907   | Hash h, String s -> String.equal h (Digest.string s)
    908 
    909 let rec attrlist_mem x = function
    910   | [] -> false
    911   | a :: l -> attrEqual a x || attrlist_mem x l
    912 
    913 let similar t t' =
    914   not (Prefs.read syncXattrs)
    915     ||
    916   match t, t' with
    917   | None, None -> true
    918   | Some (l, z), Some (l', z') ->
    919       Int64.equal (Size.toInt64 z) (Size.toInt64 z') &&
    920       Safelist.length l = Safelist.length l' &&
    921         Safelist.for_all (fun m -> attrlist_mem m l') l
    922   | _ -> false
    923 
    924 let override t t' = t'
    925 
    926 let strip t = if Prefs.read syncXattrs then t else None
    927 
    928 let diff t t' = if similar t t' then None else t'
    929 
    930 let wrapFail default f =
    931   try f () with
    932   | Fs.XattrNotSupported -> default
    933   | Failure msg ->
    934       raise (Util.Transient (msg ^
    935         ". You can set preference \"xattrs\" to false to avoid this error."))
    936 
    937 let optMap f = function None -> None | Some x -> Some (f x)
    938 let optAttrsMap f = optMap (fun (l, z) -> (Safelist.map f l, z))
    939 
    940 let purge t =
    941   optAttrsMap (function (n, Loaded (_, h)) -> (n, Hash h) | x -> x) t
    942 
    943 let readAll path t =
    944   let f = function
    945     | (n, Hash h) ->
    946         debugverbose (fun () ->
    947           Util.msg "Reading xattr %s for %s\n" n (Fspath.toDebugString path));
    948         let v' =
    949           match Cache.get h with
    950           | Some v ->
    951               debugverbose (fun () -> Util.msg "Read xattr %s from cache\n" n);
    952               v
    953           | None ->
    954               let v = Fs.xattr_get path n in
    955               if Digest.string v <> h then
    956                 raise (Util.Transient (
    957                   Printf.sprintf "The value of extended attribute '%s' has \
    958                     changed on source file %s" n (Fspath.toPrintString path)))
    959               else
    960                 Cache.add h v
    961         in
    962         (n, Loaded (v', h))
    963     | x -> x
    964   in
    965   if Prefs.read syncXattrs then
    966     wrapFail t (fun () -> optAttrsMap f t)
    967   else
    968     t
    969 
    970 let getAll t =
    971   let f = function
    972     | (n, Hash h) ->
    973         begin match Cache.get h with
    974         | Some v ->
    975             debugverbose (fun () -> Util.msg "Got xattr %s from cache\n" n);
    976             (n, Loaded (v, h))
    977         | None -> raise Not_found
    978         end
    979     | x -> x
    980   in
    981   if Prefs.read syncXattrs then
    982     wrapFail t (fun () -> optAttrsMap f t)
    983   else
    984     t
    985 
    986 let skipIgnoredXattr l =
    987   Safelist.filter (fun (n, _) ->
    988     let keep =
    989       not (Pred.test xattrIgnorePred n) || (Pred.test xattrIgnorenotPred n) in
    990     debugverbose (fun () ->
    991       Util.msg "Xattr: attribute %s %s\n" n
    992         (if keep then "not ignored" else "IGNORED by user request"));
    993     keep) l
    994 
    995 let getXattrs path =
    996   let sumSize total (_, len) = total + len in (* No fear of overflow *)
    997   let xattrNameCompare (a, _) (b, _) = String.compare a b in
    998   let sortXattrs = Safelist.sort xattrNameCompare in
    999   let readXattr (n, len) =
   1000     if len > 16777211 then (* Max length of strings on 32-bit OCaml *)
   1001       failwith ("The value of extended attribute '" ^ n ^
   1002         "' is larger than 16 MB. This is currently not supported") else
   1003     let v = Fs.xattr_get path n in
   1004     let value =
   1005       if len <= 32 then String v
   1006       else
   1007         let h = Digest.string v in
   1008         let _ = Cache.add h v in
   1009         Hash h
   1010     in
   1011     (n, value)
   1012   in
   1013   wrapFail None (fun () ->
   1014     let names = Fs.xattr_list path |> skipIgnoredXattr |> sortXattrs in
   1015     let size = Size.ofInt (Safelist.fold_left sumSize 0 names) in
   1016     Some (Safelist.map readXattr names, size))
   1017 
   1018 let setXattrs path t =
   1019   match t with
   1020   | Some (l, _) -> begin
   1021       match getXattrs path with
   1022       | Some (xattrs0, _) -> begin
   1023           try
   1024             let xattrs = skipIgnoredXattr l in
   1025             xattrs |> Safelist.iter (fun ((n, v) as m) ->
   1026               if not (attrlist_mem m xattrs0) then
   1027               begin
   1028                 debugverbose (fun () -> Util.msg "Writing xattr: %s\n" n);
   1029                 match v with
   1030                 | String x | Loaded (x, _) -> Fs.xattr_set path n x
   1031                 | Hash _ -> () (* This should not happen; just skip it *)
   1032               end);
   1033             xattrs0 |> Safelist.iter (fun (n, _) ->
   1034               if not (Safelist.exists (fun (n', _) -> n' = n) xattrs) then
   1035               begin
   1036                 debugverbose (fun () -> Util.msg "Removing xattr: %s\n" n);
   1037                 Fs.xattr_remove path n
   1038               end)
   1039           with
   1040           | Fs.XattrNotSupported ->
   1041               raise (Util.Transient ("Extended attributes are not supported. \
   1042                        You can set preference \"xattrs\" to false \
   1043                        to avoid this error."))
   1044           | Failure msg ->
   1045               raise (Util.Transient (msg ^
   1046                        ". You can set preference \"xattrs\" to false \
   1047                        to avoid this error. You can add a 'debug' preference \
   1048                        with value \"props+\" to see more details."))
   1049         end
   1050       | _ -> ()
   1051     end
   1052   | _ -> ()
   1053 
   1054 let set abspath t =
   1055   match t with
   1056   | Some _ when Prefs.read syncXattrs ->
   1057       debug (fun () ->
   1058         Util.msg "Setting xattrs for %s (%s)\n"
   1059           (Fspath.toDebugString abspath) (toDebugString t));
   1060       setXattrs abspath t
   1061   | _ -> ()
   1062 
   1063 let get abspath stats =
   1064   if Prefs.read syncXattrs &&
   1065     (stats.Unix.LargeFile.st_kind = Unix.S_REG ||
   1066      stats.Unix.LargeFile.st_kind = Unix.S_DIR)
   1067     (* Theoretically could sync xattrs on symlinks (if C stubs are
   1068        enhanced accordingly). However, in the current implementation
   1069        there are no props stored for symlinks in the archive. *)
   1070   then
   1071     let xattrs = getXattrs abspath in
   1072     debug (fun () ->
   1073       Util.msg "Xattr: got %s for %s\n"
   1074         (toDebugString xattrs) (Fspath.toDebugString abspath));
   1075     xattrs
   1076   else
   1077     None
   1078 
   1079 let check fspath path stats t =
   1080   match t with
   1081   | None -> ()
   1082   | Some _ ->
   1083       let abspath = Fspath.concat fspath path in
   1084       let t' = get abspath stats in
   1085       if not (similar t t') then
   1086         let msg = Format.sprintf ("Failed to set requested extended attributes \
   1087           on %s.\nThe following attributes were requested to be set:\n%s\n\
   1088           Actual attributes after setting:\n%s")
   1089           (Fspath.toPrintString abspath) (toStringVerb t) (toStringVerb t') in
   1090         raise (Util.Transient msg)
   1091 
   1092 end
   1093 
   1094 (* ------------------------------------------------------------------------- *)
   1095 (*                                   ACL                                     *)
   1096 (* ------------------------------------------------------------------------- *)
   1097 
   1098 let featACLValid = ref (fun _ _ -> None)
   1099 
   1100 let featACL =
   1101   Features.register "Sync: ACL" ~arcFormatChange:true
   1102   (Some (fun a b -> !featACLValid a b))
   1103 
   1104 let aclEnabled () = Features.enabled featACL
   1105 
   1106 let syncACL =
   1107   Prefs.createBool "acl" false
   1108     ~category:(`Advanced `Sync)
   1109     ~send:aclEnabled
   1110     "synchronize ACLs"
   1111     ("When this flag is set to \\verb|true|, the ACLs of files and \
   1112      directories are synchronized. The type of ACLs depends on the \
   1113      platform and filesystem support. On Unix-like platforms it \
   1114      can be NFSv4 ACLs, for example.")
   1115 
   1116 let () = featACLValid :=
   1117   fun _ enabledThis ->
   1118     if not enabledThis && Prefs.read syncACL then
   1119       Some ("You have requested synchronization of ACLs (the \
   1120         \"acl\" preference) but the server does not support this.")
   1121     else None
   1122 
   1123 module ACL : sig
   1124   include S
   1125   val get : Fspath.t -> Unix.LargeFile.stats -> t
   1126   val check : Fspath.t -> Path.local -> Unix.LargeFile.stats -> t -> unit
   1127   module Data : sig
   1128     include Propsdata.S
   1129     val keep : t -> unit
   1130   end
   1131 end = struct
   1132 
   1133 module Data = struct
   1134   include Propsdata.ACL
   1135 
   1136   let keep = function
   1137     | None | Some "" -> ()
   1138     | Some s -> keep s (* [keep] of Propsdata.ACL *)
   1139 end
   1140 
   1141 (* The result value of this function must be deterministic for its input
   1142    (over both roots, and over time, as long as it is the same archive). *)
   1143 let deflate acl =
   1144   if acl = "" then acl
   1145   else begin
   1146     let key = Digest.string acl in
   1147     Data.add key acl;
   1148     key
   1149   end
   1150 
   1151 let inflate t = if t = "" then t else Data.find t
   1152 
   1153 (* None indicates ACLs are not supported. This is not synchronized.
   1154    An empty string represents a trivial/removed ACL. This will be
   1155    synchronized. *)
   1156 type t = string option
   1157 
   1158 let dummy = None
   1159 
   1160 let m = Umarshal.cond aclEnabled dummy Umarshal.(option string)
   1161 
   1162 let hash t h = if Prefs.read syncACL then Uutil.hash2 (Uutil.hash t) h else h
   1163 
   1164 let toString = function
   1165   | Some "" -> " <trivial ACL>"
   1166   | Some s -> " A=" ^ (inflate s)
   1167   | None -> if not (Prefs.read syncACL) then "" else " !No ACL support!"
   1168 
   1169 let syncedPartsToString = toString
   1170 
   1171 let aclIds = Str.regexp
   1172   "\\(\\(user\\|group\\):\\)[^:]+:\\([^:]+:[^:]+:[^:]+:[0-9]+\\($\\|,\\)\\)"
   1173 let removeAclNames s =
   1174   Str.global_replace aclIds "\\1\\3" (inflate s)
   1175 
   1176 let similar2 t t' =
   1177   Prefs.read numericIds
   1178     &&
   1179   (* Try to strip out the user/group names and compare only numeric ids.
   1180      Format of ACE is expected to be as follows:
   1181        user:name:rw------------:------I:allow:1300 *)
   1182   String.equal (removeAclNames t) (removeAclNames t')
   1183 
   1184 let similar t t' =
   1185   not (Prefs.read syncACL)
   1186     ||
   1187   (* This is a direct string comparison. It does not take into account
   1188      changes in ACE ordering because ACE ordering is considered to be
   1189      significant and different ordering means different ACL. *)
   1190   let result =
   1191     match t, t' with
   1192     | None, None -> true
   1193     | Some acl, Some acl' when String.equal acl acl' -> true
   1194     | Some acl, Some acl' -> similar2 acl acl'
   1195     | _ -> false in
   1196   debugverbose (fun () ->
   1197     Util.msg "Comparing ACLs |%s| and |%s| => %s%s\n"
   1198       (toString t) (toString t')
   1199       (match result with true -> "same" | false -> "different")
   1200       (if Prefs.read numericIds then
   1201         " (comparing numeric user/group ids)" else ""));
   1202   result
   1203 
   1204 let override t t' = t'
   1205 
   1206 let strip t = if Prefs.read syncACL then t else None
   1207 
   1208 let diff t t' = if similar t t' then None else t'
   1209 
   1210 let wrapFail f =
   1211   try f () with
   1212   | Failure msg ->
   1213       raise (Util.Transient (msg ^
   1214         ". You can set preference \"acl\" to false to avoid this error."))
   1215 
   1216 let getACLAsText path =
   1217   wrapFail (fun () ->
   1218     match Fs.acl_get_text path with
   1219     | "-1" -> None (* "-1" is used as a special code for no ACL support *)
   1220     | acl -> Some (deflate acl))
   1221 
   1222 let setACLFromText path t =
   1223   match t with
   1224   | Some acl -> wrapFail (fun () -> Fs.acl_set_text path (inflate acl))
   1225   | _ -> ()
   1226 
   1227 let set abspath t =
   1228   match t with
   1229   | Some _ when Prefs.read syncACL ->
   1230       debug (fun () ->
   1231         Util.msg "Setting ACL for %s from text |%s|\n"
   1232           (Fspath.toDebugString abspath) (toString t));
   1233       setACLFromText abspath t
   1234   | _ -> ()
   1235 
   1236 let get abspath stats =
   1237   if Prefs.read syncACL &&
   1238     (stats.Unix.LargeFile.st_kind = Unix.S_REG ||
   1239      stats.Unix.LargeFile.st_kind = Unix.S_DIR)
   1240     (* Theoretically could sync ACLs on symlinks (if C stubs are
   1241        enhanced accordingly). However, in the current implementation
   1242        there are no props stored for symlinks in the archive. *)
   1243   then
   1244     let acltext = getACLAsText abspath in
   1245     debug (fun () ->
   1246       Util.msg "Got text ACL |%s| for %s\n"
   1247         (toString acltext) (Fspath.toDebugString abspath));
   1248     acltext
   1249   else
   1250     None
   1251 
   1252 let check fspath path stats acl =
   1253   match acl with
   1254   | None -> ()
   1255   | Some _ ->
   1256       let abspath = Fspath.concat fspath path in
   1257       let acl' = get abspath stats in
   1258       if not (similar acl acl') then
   1259         let msg = Format.sprintf
   1260           "Failed to set ACL of file %s to\n%s\n\
   1261           The ACL was instead set to\n%s\n\
   1262           The filesystem probably does not have full ACL support or \
   1263           the synchronized ACL is of different type, or there \
   1264           are other incompatibilities between systems. \
   1265           If this is a filesystem without correct ACL support, you \
   1266           should set the \"acl\" preference to false.%s"
   1267           (Fspath.toPrintString abspath) (toString acl) (toString acl')
   1268           (if Prefs.read numericIds then "" else " Or, you may want to \
   1269              try setting the \"numericids\" preference to true if the \
   1270              user/group names don't match on both systems.") in
   1271         raise (Util.Transient msg)
   1272 
   1273 end
   1274 
   1275 (* ------------------------------------------------------------------------- *)
   1276 (*                           Properties                                      *)
   1277 (* ------------------------------------------------------------------------- *)
   1278 
   1279 (* IMPORTANT!
   1280    This is the 2.51-compatible version of type [Props.t]. It must always remain
   1281    exactly the same as the type [Props.t] in version 2.51.5. This means that if
   1282    any of the types it is composed of changes then for each changed type also a
   1283    2.51-compatible version must be created. *)
   1284 type t251 =
   1285   { perm : Perm.t;
   1286     uid : Uid.t;
   1287     gid : Gid.t;
   1288     time : Time.t;
   1289     typeCreator : TypeCreator.t;
   1290     length : Uutil.Filesize.t }
   1291 
   1292 type t =
   1293   { perm : Perm.t;
   1294     uid : Uid.t;
   1295     gid : Gid.t;
   1296     time : Time.t;
   1297     typeCreator : TypeCreator.t;
   1298     length : Uutil.Filesize.t;
   1299     ctime : CTime.t;
   1300     xattr : Xattr.t;
   1301     acl : ACL.t;
   1302   }
   1303 
   1304 type _ props = t
   1305 type basic = [`Basic] props
   1306 type x = [`ExtLoaded] props
   1307 
   1308 let m = Umarshal.(prod4
   1309                     (prod6 Perm.m Uid.m Gid.m Time.m TypeCreator.m Uutil.Filesize.m id id)
   1310                     (cond (fun () -> xattrEnabled () || aclEnabled ()) CTime.dummy CTime.m)
   1311                     Xattr.m
   1312                     ACL.m
   1313                     (fun {perm; uid; gid; time; typeCreator; length; ctime; xattr; acl} ->
   1314                        ((perm, uid, gid, time, typeCreator, length), ctime, xattr, acl))
   1315                     (fun ((perm, uid, gid, time, typeCreator, length), ctime, xattr, acl) ->
   1316                        {perm; uid; gid; time; typeCreator; length; ctime; xattr; acl}))
   1317 
   1318 let mbasic = m
   1319 let mx = m
   1320 
   1321 let to_compat251 (p : t) : t251 =
   1322   { perm = p.perm;
   1323     uid = p.uid;
   1324     gid = p.gid;
   1325     time = p.time;
   1326     typeCreator = p.typeCreator;
   1327     length = p.length }
   1328 
   1329 let of_compat251 (p : t251) : t =
   1330   { perm = p.perm;
   1331     uid = p.uid;
   1332     gid = p.gid;
   1333     time = p.time;
   1334     typeCreator = p.typeCreator;
   1335     length = p.length;
   1336     ctime = CTime.dummy;
   1337     xattr = Xattr.dummy;
   1338     acl = ACL.dummy;
   1339   }
   1340 
   1341 let template perm =
   1342   { perm = perm; uid = Uid.dummy; gid = Gid.dummy;
   1343     time = Time.dummy; typeCreator = TypeCreator.dummy;
   1344     length = Uutil.Filesize.dummy;
   1345     ctime = CTime.dummy;
   1346     xattr = Xattr.dummy;
   1347     acl = ACL.dummy;
   1348   }
   1349 
   1350 let dummy = template Perm.dummy
   1351 
   1352 let hash p h =
   1353   h
   1354   |> ACL.hash p.acl
   1355   |> Xattr.hash p.xattr
   1356   |> TypeCreator.hash p.typeCreator
   1357   |> Time.hash p.time
   1358   |> Gid.hash p.gid
   1359   |> Uid.hash p.uid
   1360   |> Perm.hash p.perm
   1361 
   1362 (* IMPORTANT!
   1363    This is the 2.51-compatible version of [hash]. It must always produce exactly
   1364    the same result as the [hash] in version 2.51.5.
   1365    If code changes elsewhere make this function produce a different result then
   1366    it must be updated accordingly to again return the 2.51-compatible result. *)
   1367 let hash251 (p : t251) h =
   1368   Perm.hash p.perm
   1369     (Uid.hash p.uid
   1370        (Gid.hash p.gid
   1371           (Time.hash p.time
   1372              (TypeCreator.hash p.typeCreator h))))
   1373 
   1374 let similar p p' =
   1375   Perm.similar p.perm p'.perm
   1376     &&
   1377   Uid.similar p.uid p'.uid
   1378     &&
   1379   Gid.similar p.gid p'.gid
   1380     &&
   1381   Time.similar p.time p'.time
   1382     &&
   1383   TypeCreator.similar p.typeCreator p'.typeCreator
   1384     &&
   1385   Xattr.similar p.xattr p'.xattr
   1386     &&
   1387   ACL.similar p.acl p'.acl
   1388 
   1389 let override p p' =
   1390   { perm = Perm.override p.perm p'.perm;
   1391     uid = Uid.override p.uid p'.uid;
   1392     gid = Gid.override p.gid p'.gid;
   1393     time = Time.override p.time p'.time;
   1394     typeCreator = TypeCreator.override p.typeCreator p'.typeCreator;
   1395     length = p'.length;
   1396     ctime = CTime.override p.ctime p'.ctime;
   1397     xattr = Xattr.override p.xattr p'.xattr;
   1398     acl = ACL.override p.acl p'.acl;
   1399   }
   1400 
   1401 let strip p =
   1402   { perm = Perm.strip p.perm;
   1403     uid = Uid.strip p.uid;
   1404     gid = Gid.strip p.gid;
   1405     time = Time.strip p.time;
   1406     typeCreator = TypeCreator.strip p.typeCreator;
   1407     length = p.length;
   1408     ctime = p.ctime;
   1409     xattr = Xattr.strip p.xattr;
   1410     acl = ACL.strip p.acl;
   1411   }
   1412 
   1413 let toString p =
   1414   Printf.sprintf
   1415     "modified on %s  size %-9.0f %s%s%s%s%s%s"
   1416     (Time.toString p.time)
   1417     (Uutil.Filesize.toFloat p.length)
   1418     (Perm.toString p.perm)
   1419     (Uid.toString p.uid)
   1420     (Gid.toString p.gid)
   1421     (Xattr.toString p.xattr)
   1422     (TypeCreator.toString p.typeCreator)
   1423     (ACL.toString p.acl)
   1424 
   1425 let syncedPartsToString p =
   1426   let tm = Time.syncedPartsToString p.time in
   1427   Printf.sprintf
   1428     "%s%s  size %-9.0f %s%s%s%s%s%s"
   1429     (if tm = "" then "" else "modified at ")
   1430     tm
   1431     (Uutil.Filesize.toFloat p.length)
   1432     (Perm.syncedPartsToString p.perm)
   1433     (Uid.syncedPartsToString p.uid)
   1434     (Gid.syncedPartsToString p.gid)
   1435     (Xattr.syncedPartsToString p.xattr)
   1436     (TypeCreator.syncedPartsToString p.typeCreator)
   1437     (ACL.syncedPartsToString p.acl)
   1438 
   1439 let diff p p' =
   1440   { perm = Perm.diff p.perm p'.perm;
   1441     uid = Uid.diff p.uid p'.uid;
   1442     gid = Gid.diff p.gid p'.gid;
   1443     time = Time.diff p.time p'.time;
   1444     typeCreator = TypeCreator.diff p.typeCreator p'.typeCreator;
   1445     length = p'.length;
   1446     ctime = p'.ctime;
   1447     xattr = Xattr.diff p.xattr p'.xattr;
   1448     acl = ACL.diff p.acl p'.acl;
   1449   }
   1450 
   1451 let get' stats =
   1452   { perm = Perm.get stats;
   1453     uid = Uid.get stats;
   1454     gid = Gid.get stats;
   1455     time = Time.get stats;
   1456     typeCreator = TypeCreator.dummy;
   1457     length =
   1458       if stats.Unix.LargeFile.st_kind = Unix.S_REG then
   1459         Uutil.Filesize.fromStats stats
   1460       else
   1461         Uutil.Filesize.zero;
   1462     ctime = CTime.dummy;
   1463     xattr = Xattr.dummy;
   1464     acl = ACL.dummy;
   1465   }
   1466 
   1467 (* Important note about [fspath] and [path] arguments to [get]:
   1468    If the path points to a symlink then the [stats] argument may be the
   1469    result of either stat(2) or lstat(2) on said path. When this distinction
   1470    is important then it can be easily checked by seeing if [stats.st_kind]
   1471    is S_LNK or not. If it is not S_LNK then any syscalls/functions on this
   1472    path are expected to follow symlinks (and not follow otherwise). *)
   1473 let get ?(archProps = dummy) fspath path stats infos =
   1474   let abspath = Fspath.concat fspath path in
   1475   (* Note for future: ctime could very well be included in [get'] but it
   1476      does not seem necessary at the moment. See the comment at
   1477      [CTime.override]. *)
   1478   let ctime = CTime.get stats in
   1479   let ctimeChanged = not (CTime.same_time ctime archProps.ctime) in
   1480   let props = get' stats in
   1481   { props with
   1482     typeCreator = TypeCreator.get stats infos;
   1483     ctime;
   1484     xattr =
   1485       if ctimeChanged || not Xattr.ctimeDetect then Xattr.get abspath stats
   1486       else archProps.xattr;
   1487     acl =
   1488       if ctimeChanged then ACL.get abspath stats
   1489       else archProps.acl;
   1490   }
   1491 
   1492 let getWithRess stats osXinfo =
   1493   let props = get' stats in
   1494   { props with
   1495     typeCreator = TypeCreator.get stats osXinfo;
   1496   }
   1497 
   1498 let set fspath path kind p =
   1499   let abspath = Fspath.concat fspath path in
   1500   Uid.set abspath p.uid;
   1501   Gid.set abspath p.gid;
   1502   TypeCreator.set fspath path p.typeCreator;
   1503   Xattr.set abspath p.xattr;
   1504   Time.set abspath p.time;
   1505   Perm.set abspath kind p.perm;
   1506   (* ACLs must always be set after chmod,
   1507    * otherwise chmod may replace the ACL. *)
   1508   ACL.set abspath p.acl
   1509 
   1510 (* Paranoid checks *)
   1511 let check fspath path stats p =
   1512   ACL.check fspath path stats p.acl;
   1513   Xattr.check fspath path stats p.xattr;
   1514   Time.check fspath path stats p.time;
   1515   Perm.check fspath path stats p.perm
   1516 
   1517 let init someHostIsRunningWindows =
   1518   Perm.init someHostIsRunningWindows;
   1519   Uid.init someHostIsRunningWindows;
   1520   Gid.init someHostIsRunningWindows
   1521 
   1522 let fileDefault = template Perm.fileDefault
   1523 let fileSafe = template Perm.fileSafe
   1524 let dirDefault = template Perm.dirDefault
   1525 
   1526 let same_time p p' = Time.same p.time p'.time
   1527 let same_ctime p p' = CTime.same_time p.ctime p'.ctime
   1528 let length p = p.length
   1529 let setLength p l = {p with length=l}
   1530 
   1531 let time p = Time.extract p.time
   1532 let setTime p p' = {p with time = Time.replace p.time (time p'); ctime = p'.ctime}
   1533 let resetCTime p p' = {p with ctime = p'.ctime}
   1534 
   1535 let perms p = Perm.extract p.perm
   1536 
   1537 let syncModtimes = Time.sync
   1538 let permMask = Perm.permMask
   1539 let dontChmod = Perm.dontChmod
   1540 
   1541 let validatePrefs = Perm.validatePrefs
   1542 
   1543 let loadExtData fspath path p =
   1544   let abspath = Fspath.concat fspath path in
   1545   { p with
   1546     xattr = Xattr.readAll abspath p.xattr;
   1547   }
   1548 
   1549 let purgeExtData p =
   1550   { p with
   1551     xattr = Xattr.purge p.xattr;
   1552   }
   1553 
   1554 let withExtData p =
   1555   { p with
   1556     xattr = Xattr.getAll p.xattr;
   1557   }
   1558 
   1559 (* ------------------------------------------------------------------------- *)
   1560 (*                          Shared data for props                            *)
   1561 (* ------------------------------------------------------------------------- *)
   1562 
   1563 module Data = struct
   1564 
   1565   type e = string * (string * string) list
   1566   type d = e list
   1567 
   1568   let m = Umarshal.(list (prod2 string (list (prod2 string string id id)) id id))
   1569 
   1570   let enabled () =
   1571     xattrEnabled () || aclEnabled ()
   1572 
   1573   let extract k pd = try Safelist.assoc k pd with Not_found -> []
   1574 
   1575   let extern kind =
   1576     let add_nonempty k v pd =
   1577       match v with
   1578       | [] -> pd
   1579       | _ -> (k, v) :: pd
   1580     in
   1581     []
   1582     |> add_nonempty "xattr" (Xattr.Data.get kind)
   1583     |> add_nonempty "ACL" (ACL.Data.get kind)
   1584 
   1585   let intern pd =
   1586     Xattr.Data.set (extract "xattr" pd);
   1587     ACL.Data.set (extract "ACL" pd);
   1588     ()
   1589 
   1590   let merge pd =
   1591     Xattr.Data.merge (extract "xattr" pd);
   1592     ACL.Data.merge (extract "ACL" pd);
   1593     ()
   1594 
   1595   let gcInit () =
   1596     Xattr.Data.clear `Kept;
   1597     ACL.Data.clear `Kept;
   1598     ()
   1599 
   1600   let gcKeep p =
   1601     (* Xattr data cache is not persisted *)
   1602     ACL.Data.keep p.acl;
   1603     ()
   1604 
   1605   let gcDone () = extern `Kept
   1606 
   1607 end
   1608 
   1609 (* ------------------------------------------------------------------------- *)
   1610 (*                          Directory change stamps                          *)
   1611 (* ------------------------------------------------------------------------- *)
   1612 
   1613 (* We are reusing the directory length to store a flag indicating that
   1614    the directory is unchanged *)
   1615 
   1616 type dirChangedStamp = Uutil.Filesize.t
   1617 
   1618 let mdirChangedStamp = Uutil.Filesize.m
   1619 
   1620 let freshDirStamp () =
   1621   let t =
   1622     (Unix.gettimeofday () +. sqrt 2. *. float (Unix.getpid ())) *. 1000.
   1623   in
   1624   Uutil.Filesize.ofFloat t
   1625 
   1626 let changedDirStamp = Uutil.Filesize.zero
   1627 
   1628 let setDirChangeFlag p stamp inode =
   1629   let stamp = Uutil.Filesize.add stamp (Uutil.Filesize.ofInt inode) in
   1630   (setLength p stamp, length p <> stamp)
   1631 
   1632 let dirMarkedUnchanged p stamp inode =
   1633   let stamp = Uutil.Filesize.add stamp (Uutil.Filesize.ofInt inode) in
   1634   stamp <> changedDirStamp && length p = stamp