Blame src/FileUtilCP.ml

Packit 9ff65e
(******************************************************************************)
Packit 9ff65e
(*  ocaml-fileutils: files and filenames common operations                    *)
Packit 9ff65e
(*                                                                            *)
Packit 9ff65e
(*  Copyright (C) 2003-2014, Sylvain Le Gall                                  *)
Packit 9ff65e
(*                                                                            *)
Packit 9ff65e
(*  This library is free software; you can redistribute it and/or modify it   *)
Packit 9ff65e
(*  under the terms of the GNU Lesser General Public License as published by  *)
Packit 9ff65e
(*  the Free Software Foundation; either version 2.1 of the License, or (at   *)
Packit 9ff65e
(*  your option) any later version, with the OCaml static compilation         *)
Packit 9ff65e
(*  exception.                                                                *)
Packit 9ff65e
(*                                                                            *)
Packit 9ff65e
(*  This library is distributed in the hope that it will be useful, but       *)
Packit 9ff65e
(*  WITHOUT ANY WARRANTY; without even the implied warranty of                *)
Packit 9ff65e
(*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the file         *)
Packit 9ff65e
(*  COPYING for more details.                                                 *)
Packit 9ff65e
(*                                                                            *)
Packit 9ff65e
(*  You should have received a copy of the GNU Lesser General Public License  *)
Packit 9ff65e
(*  along with this library; if not, write to the Free Software Foundation,   *)
Packit 9ff65e
(*  Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA             *)
Packit 9ff65e
(******************************************************************************)
Packit 9ff65e
Packit 9ff65e
open FileUtilTypes
Packit 9ff65e
open FilePath
Packit 9ff65e
open FileUtilMisc
Packit 9ff65e
open FileUtilPermission
Packit 9ff65e
open FileUtilTOUCH
Packit 9ff65e
open FileUtilRM
Packit 9ff65e
open FileUtilSTAT
Packit 9ff65e
open FileUtilUMASK
Packit 9ff65e
open FileUtilMKDIR
Packit 9ff65e
open FileUtilCHMOD
Packit 9ff65e
open FileUtilTEST
Packit 9ff65e
Packit 9ff65e
exception CpError of string
Packit 9ff65e
exception CpSkip
Packit 9ff65e
Packit 9ff65e
type cp_error =
Packit 9ff65e
  [ `CannotChmodDstDir of filename * exn
Packit 9ff65e
  | `CannotCopyDir of filename
Packit 9ff65e
  | `CannotCopyFilesToFile of filename list * filename
Packit 9ff65e
  | `CannotCreateDir of filename * exn
Packit 9ff65e
  | `CannotListSrcDir of filename * exn
Packit 9ff65e
  | `CannotOpenDstFile of filename * exn
Packit 9ff65e
  | `CannotOpenSrcFile of filename * exn
Packit 9ff65e
  | `CannotRemoveDstFile of filename * exn
Packit 9ff65e
  | `DstDirNotDir of filename
Packit 9ff65e
  | `ErrorRead of filename * exn
Packit 9ff65e
  | `ErrorWrite of filename * exn
Packit 9ff65e
  | `Exc of exn
Packit 9ff65e
  | `NoSourceFile of filename
Packit 9ff65e
  | `PartialWrite of filename * int * int
Packit 9ff65e
  | `SameFile of filename * filename
Packit 9ff65e
  | `UnhandledType of filename * kind ]
Packit 9ff65e
Packit 9ff65e
Packit 9ff65e
let same_file st1 st2 =
Packit 9ff65e
  st1.device = st2.device && st1.inode = st2.inode
Packit 9ff65e
Packit 9ff65e
Packit 9ff65e
let cp
Packit 9ff65e
    ?(follow=Skip)
Packit 9ff65e
    ?(force=Force)
Packit 9ff65e
    ?(recurse=false)
Packit 9ff65e
    ?(preserve=false)
Packit 9ff65e
    ?(error=(fun str _ -> raise (CpError str)))
Packit 9ff65e
    fln_src_lst
Packit 9ff65e
    fln_dst =
Packit 9ff65e
Packit 9ff65e
  let herror, _ =
Packit 9ff65e
    let spf fmt = Printf.sprintf fmt in
Packit 9ff65e
    let exs () e =
Packit 9ff65e
      match e with
Packit 9ff65e
      | Unix.Unix_error(err, _, _) -> Unix.error_message err
Packit 9ff65e
      | e -> Printexc.to_string e
Packit 9ff65e
    in
Packit 9ff65e
    handle_error_gen "cp" error
Packit 9ff65e
      (function
Packit 9ff65e
       | `CannotRemoveDstFile(fn_dst, e) ->
Packit 9ff65e
           spf "Cannot remove destination file '%s': %a." fn_dst exs e
Packit 9ff65e
       | `CannotOpenDstFile(fn_dst, e) ->
Packit 9ff65e
           spf "Cannot open destination file '%s': %a." fn_dst exs e
Packit 9ff65e
       | `CannotOpenSrcFile(fn_src, e) ->
Packit 9ff65e
           spf "Cannot open source file '%s': %a." fn_src exs e
Packit 9ff65e
       | `ErrorRead(fn_src, e) ->
Packit 9ff65e
           spf "Error reading file '%s': %a." fn_src exs e
Packit 9ff65e
       | `ErrorWrite(fn_dst, e) ->
Packit 9ff65e
           spf "Error writing file '%s': %a." fn_dst exs e
Packit 9ff65e
       | `PartialWrite(fn_dst, read, written) ->
Packit 9ff65e
           spf
Packit 9ff65e
             "Partial write to file '%s': %d read, %d written."
Packit 9ff65e
             fn_dst
Packit 9ff65e
             read
Packit 9ff65e
             written
Packit 9ff65e
       | `CannotCopyDir fn_src ->
Packit 9ff65e
           spf "Cannot copy directory '%s' recursively." fn_src
Packit 9ff65e
       | `DstDirNotDir fn_dst ->
Packit 9ff65e
           spf "Destination '%s' is not a directory." fn_dst
Packit 9ff65e
       | `CannotCreateDir(fn_dst, e) ->
Packit 9ff65e
           spf "Cannot create directory '%s': %a." fn_dst exs e
Packit 9ff65e
       | `CannotListSrcDir(fn_src, e) ->
Packit 9ff65e
           spf "Cannot list directory '%s': %a." fn_src exs e
Packit 9ff65e
       | `CannotChmodDstDir(fn_dst, e) ->
Packit 9ff65e
           spf "'Cannot chmod directory %s': %a." fn_dst exs e
Packit 9ff65e
       | `NoSourceFile fn_src ->
Packit 9ff65e
           spf "Source file '%s' doesn't exist." fn_src
Packit 9ff65e
       | `SameFile(fn_src, fn_dst) ->
Packit 9ff65e
           spf "'%s' and '%s' are the same file." fn_src fn_dst
Packit 9ff65e
       | `UnhandledType(fn_src, _) ->
Packit 9ff65e
           spf "Cannot handle the type of kind for file '%s'." fn_src
Packit 9ff65e
       | `CannotCopyFilesToFile(fn_src_lst, fn_dst) ->
Packit 9ff65e
           spf "Cannot copy a list of files to another file '%s'." fn_dst
Packit 9ff65e
       | #exc -> "")
Packit 9ff65e
  in
Packit 9ff65e
  let handle_error e =
Packit 9ff65e
    herror ~fatal:false e;
Packit 9ff65e
    raise CpSkip
Packit 9ff65e
  in
Packit 9ff65e
  let handle_exception f a h =
Packit 9ff65e
    try
Packit 9ff65e
      f a
Packit 9ff65e
    with e ->
Packit 9ff65e
      herror ~fatal:false (h e);
Packit 9ff65e
      raise CpSkip
Packit 9ff65e
  in
Packit 9ff65e
Packit 9ff65e
  let copy_time_props st_src fln_dst =
Packit 9ff65e
    if preserve then begin
Packit 9ff65e
      touch
Packit 9ff65e
        ~time:(Touch_timestamp st_src.modification_time)
Packit 9ff65e
        ~mtime:true
Packit 9ff65e
        ~create:false
Packit 9ff65e
        fln_dst;
Packit 9ff65e
      touch
Packit 9ff65e
        ~time:(Touch_timestamp st_src.access_time)
Packit 9ff65e
        ~atime:true
Packit 9ff65e
        ~create:false
Packit 9ff65e
        fln_dst;
Packit 9ff65e
    end
Packit 9ff65e
  in
Packit 9ff65e
Packit Service 4ae7da
  let buffer = Bytes.make 1024 ' ' in
Packit 9ff65e
Packit 9ff65e
  let cp_file st_src dst_exists fn_src fn_dst =
Packit 9ff65e
    let mode = int_of_permission st_src.permission in
Packit 9ff65e
    (* POSIX conditions: *)
Packit 9ff65e
    (* 3a *)
Packit 9ff65e
    let fd_dst =
Packit 9ff65e
      (* 3ai *)
Packit 9ff65e
      if dst_exists && doit force fn_dst then begin
Packit 9ff65e
        try
Packit 9ff65e
          (* 3aii *)
Packit 9ff65e
          Unix.openfile fn_dst [Unix.O_WRONLY; Unix.O_TRUNC] mode
Packit 9ff65e
        with _ ->
Packit 9ff65e
          (* 3aii *)
Packit 9ff65e
          handle_exception
Packit 9ff65e
            (fun lst -> rm lst) [fn_dst]
Packit 9ff65e
            (fun e -> `CannotRemoveDstFile(fn_dst, e));
Packit 9ff65e
          handle_exception
Packit 9ff65e
            (Unix.openfile fn_dst [Unix.O_WRONLY; Unix.O_CREAT]) mode
Packit 9ff65e
            (fun e -> `CannotOpenDstFile(fn_dst, e))
Packit 9ff65e
      end else if not dst_exists then begin
Packit 9ff65e
        handle_exception
Packit 9ff65e
          (Unix.openfile fn_dst [Unix.O_WRONLY; Unix.O_CREAT]) mode
Packit 9ff65e
          (fun e -> `CannotOpenDstFile(fn_dst, e))
Packit 9ff65e
      end else begin
Packit 9ff65e
        raise CpSkip
Packit 9ff65e
      end
Packit 9ff65e
    in
Packit 9ff65e
    let read = ref 0 in
Packit 9ff65e
      try
Packit 9ff65e
        let fd_src =
Packit 9ff65e
          handle_exception
Packit 9ff65e
            (Unix.openfile fn_src [Unix.O_RDONLY]) 0o600
Packit 9ff65e
            (fun e -> `CannotOpenSrcFile(fn_src, e))
Packit 9ff65e
        in
Packit 9ff65e
          try
Packit 9ff65e
            while (read :=
Packit 9ff65e
                   handle_exception
Packit Service 4ae7da
                     (Unix.read fd_src buffer 0) (Bytes.length buffer)
Packit 9ff65e
                     (fun e -> `ErrorRead(fn_src, e));
Packit 9ff65e
                   !read <> 0) do
Packit 9ff65e
              let written =
Packit 9ff65e
                handle_exception
Packit 9ff65e
                  (Unix.write fd_dst buffer 0) !read
Packit 9ff65e
                  (fun e -> `ErrorWrite(fn_dst, e))
Packit 9ff65e
              in
Packit 9ff65e
                if written != !read then
Packit 9ff65e
                  handle_error (`PartialWrite(fn_src, !read, written))
Packit 9ff65e
            done;
Packit 9ff65e
            Unix.close fd_src;
Packit 9ff65e
            Unix.close fd_dst;
Packit 9ff65e
            copy_time_props st_src fn_dst
Packit 9ff65e
          with e ->
Packit 9ff65e
            Unix.close fd_src;
Packit 9ff65e
            raise e
Packit 9ff65e
      with e ->
Packit 9ff65e
        Unix.close fd_dst;
Packit 9ff65e
        raise e
Packit 9ff65e
  in
Packit 9ff65e
Packit 9ff65e
  let cp_symlink fn_src fn_dst =
Packit 9ff65e
    (* No Unix.lutimes to set time of the symlink. *)
Packit 9ff65e
    Unix.symlink (Unix.readlink fn_src) fn_dst
Packit 9ff65e
  in
Packit 9ff65e
Packit 9ff65e
  let rec cp_dir st_src dst_exists fn_src fn_dst =
Packit 9ff65e
    (* 2a *)
Packit 9ff65e
    if not recurse then begin
Packit 9ff65e
      handle_error (`CannotCopyDir fn_src)
Packit 9ff65e
    (* 2d, 2c *)
Packit 9ff65e
    end else if dst_exists && (stat fn_dst).kind <> Dir then begin
Packit 9ff65e
      handle_error (`DstDirNotDir fn_dst)
Packit 9ff65e
    end else begin
Packit 9ff65e
      (* 2e *)
Packit 9ff65e
      let dst_created =
Packit 9ff65e
        if not dst_exists then begin
Packit 9ff65e
          let mode =
Packit 9ff65e
            let src_mode = int_of_permission st_src.permission in
Packit 9ff65e
            let dst_mode =
Packit 9ff65e
              if preserve then src_mode else umask_apply src_mode
Packit 9ff65e
            in
Packit 9ff65e
              `Octal (dst_mode lor 0o0700)
Packit 9ff65e
          in
Packit 9ff65e
            handle_exception
Packit 9ff65e
              (fun fn -> mkdir ~mode fn) fn_dst
Packit 9ff65e
              (fun e -> `CannotCreateDir(fn_dst, e));
Packit 9ff65e
            true
Packit 9ff65e
        end else begin
Packit 9ff65e
          false
Packit 9ff65e
        end
Packit 9ff65e
      in
Packit 9ff65e
        (* 2f *)
Packit 9ff65e
        Array.iter
Packit 9ff65e
          (fun bn ->
Packit 9ff65e
             if not (is_current bn || is_parent bn) then
Packit 9ff65e
               cp_one (concat fn_src bn) (concat fn_dst bn))
Packit 9ff65e
          (handle_exception
Packit 9ff65e
             Sys.readdir fn_src
Packit 9ff65e
             (fun e -> `CannotListSrcDir(fn_src, e)));
Packit 9ff65e
        (* 2g *)
Packit 9ff65e
        if dst_created then begin
Packit 9ff65e
          let mode =
Packit 9ff65e
            let src_mode = int_of_permission st_src.permission in
Packit 9ff65e
              `Octal (if preserve then src_mode else umask_apply src_mode)
Packit 9ff65e
          in
Packit 9ff65e
            handle_exception
Packit 9ff65e
              (chmod mode) [fn_dst]
Packit 9ff65e
              (fun e -> `CannotChmodDstDir(fn_dst, e));
Packit 9ff65e
            copy_time_props st_src fn_dst
Packit 9ff65e
        end
Packit 9ff65e
    end
Packit 9ff65e
Packit 9ff65e
  and cp_one fn_src fn_dst =
Packit 9ff65e
    let st_src, st_src_deref =
Packit 9ff65e
      (* Check existence of source files. *)
Packit 9ff65e
      if test_exists fn_src then begin
Packit 9ff65e
        let st = stat fn_src in
Packit 9ff65e
        if st.kind = Symlink && not recurse then begin
Packit 9ff65e
          st, stat ~dereference:true fn_src
Packit 9ff65e
        end else begin
Packit 9ff65e
          st, st
Packit 9ff65e
        end
Packit 9ff65e
      end else begin
Packit 9ff65e
        handle_error (`NoSourceFile fn_src)
Packit 9ff65e
      end
Packit 9ff65e
    in
Packit 9ff65e
Packit 9ff65e
    let same_file, dst_exists =
Packit 9ff65e
      (* Test if fn_dst exists and if it is the same file as fn_src. *)
Packit 9ff65e
      try
Packit 9ff65e
        same_file st_src (stat fn_dst), true
Packit 9ff65e
      with FileDoesntExist _ ->
Packit 9ff65e
        false, false
Packit 9ff65e
    in
Packit 9ff65e
Packit 9ff65e
      if same_file then begin
Packit 9ff65e
        handle_error (`SameFile(fn_src, fn_dst))
Packit 9ff65e
      end;
Packit 9ff65e
      try
Packit 9ff65e
        match st_src.kind with
Packit 9ff65e
          | Dir -> cp_dir st_src dst_exists fn_src fn_dst
Packit 9ff65e
          | File -> cp_file st_src dst_exists fn_src fn_dst
Packit 9ff65e
          | Symlink ->
Packit 9ff65e
            if st_src_deref.kind = Dir || recurse then
Packit 9ff65e
              cp_symlink fn_src fn_dst
Packit 9ff65e
            else
Packit 9ff65e
              cp_file st_src_deref dst_exists fn_src fn_dst
Packit 9ff65e
          | Fifo | Dev_char | Dev_block | Socket ->
Packit 9ff65e
              handle_error (`UnhandledType(fn_src, st_src.kind))
Packit 9ff65e
      with CpSkip ->
Packit 9ff65e
        ()
Packit 9ff65e
  in
Packit 9ff65e
    if test Is_dir fln_dst then
Packit 9ff65e
      List.iter
Packit 9ff65e
        (fun fn_src ->
Packit 9ff65e
           cp_one fn_src (concat fln_dst (basename fn_src)))
Packit 9ff65e
        fln_src_lst
Packit 9ff65e
    else if List.length fln_src_lst <= 1 then
Packit 9ff65e
      List.iter
Packit 9ff65e
        (fun fn_src -> cp_one fn_src fln_dst)
Packit 9ff65e
        fln_src_lst
Packit 9ff65e
    else
Packit 9ff65e
      handle_error (`CannotCopyFilesToFile(fln_src_lst, fln_dst))