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