: SYSTEM = struct
let wrap_deferred_system cmd f =
wrap_deferred f ~on_exn:(fun e -> `System (cmd, `Exn e))
let fail_sys r = fail (`System r)
module Shell = struct
let discriminate_process_status s ret =
begin match ret with
| Lwt_unix.WEXITED 0 -> return ()
| Lwt_unix.WEXITED n -> fail (`Shell (s, `Exited n))
| Lwt_unix.WSIGNALED n -> fail (`Shell (s, `Signaled n))
| Lwt_unix.WSTOPPED n -> fail (`Shell (s, `Stopped n))
end
let status_to_string = function
| `Exited i -> sprintf "Exited with %d" i
| `Exn e -> sprintf "Exception %s" (exn e)
| `Signaled i -> sprintf "Signaled (%d)" i
| `Stopped i -> sprintf "Stopped (%d)" i
let do_or_fail s =
wrap_deferred Lwt_io.(fun () -> Lwt_unix.system s)
~on_exn:(fun e -> `Shell (s, `Exn e))
>>= fun ret ->
discriminate_process_status s ret
let execute s =
wrap_deferred ~on_exn:(fun e -> `Shell (s, `Exn e))
Lwt.(fun () ->
let inprocess = Lwt_process.(open_process_full (shell s)) in
Lwt_list.map_p Lwt_io.read
[inprocess#stdout; inprocess#stderr; ]
>>= fun output ->
inprocess#status >>= fun status ->
return (status, output))
>>= fun (ret, output) ->
let code =
match ret with
| Lwt_unix.WEXITED n -> (`Exited n)
| Lwt_unix.WSIGNALED n -> (`Signaled n)
| Lwt_unix.WSTOPPED n -> (`Stopped n)
in
begin match output with
| [out; err] -> return (out, err, code)
| _ -> assert false
end
end
let sleep f =
wrap_deferred_system (`Sleep f) (fun () -> Lwt_unix.sleep f)
let with_timeout time ~f =
Lwt.catch
begin fun () ->
Lwt_unix.with_timeout time f
end
begin function
| Lwt_unix.Timeout -> fail (`Timeout time)
| e -> fail_sys (`With_timeout time, `Exn e)
end
let mkdir_or_fail ?(perm=0o700) dirname =
let fail_here e =
fail_sys (`Make_directory dirname, e) in
Lwt.catch
Lwt.(fun () -> Lwt_unix.mkdir dirname perm >>= fun () -> return (`Ok ()))
begin function
| Unix.Unix_error (Unix.EACCES, cmd, arg) ->
fail_here (`Wrong_access_rights perm)
| Unix.Unix_error (Unix.EEXIST, cmd, arg) ->
fail_here (`Already_exists)
| Unix.Unix_error (Unix.EISDIR, cmd, arg) ->
fail_here (`Already_exists)
| e -> fail_here (`Exn e)
end
let mkdir_even_if_exists ?(perm=0o700) dirname =
let fail_here e =
fail_sys (`Make_directory dirname, e) in
Lwt.catch
Lwt.(fun () -> Lwt_unix.mkdir dirname perm >>= fun () -> return (`Ok ()))
begin function
| Unix.Unix_error (Unix.EACCES, cmd, arg) ->
fail_here (`Wrong_access_rights perm)
| Unix.Unix_error (Unix.EISDIR, cmd, arg) ->
return ()
| Unix.Unix_error (Unix.EEXIST, cmd, arg) -> return ()
| e -> fail_here (`Exn e)
end
let make_new_directory ?perm dirname =
mkdir_or_fail ?perm dirname
let ensure_directory_path ?perm dirname =
let init, dirs =
match Filename.parts dirname with
| [] -> ksprintf failwith "Sys.mkdir_p: BUG! Filename.parts %s -> []" dirname
| init :: dirs -> (init, dirs)
in
mkdir_even_if_exists ?perm init
>>= fun () ->
List.fold_left dirs ~init:(return init) ~f:(fun m part ->
m >>= fun previous ->
let dir = Filename.concat previous part in
mkdir_even_if_exists ?perm dir
>>= fun () ->
return dir)
>>= fun _ ->
return ()
type file_info =
[ `Absent
| `Regular_file of int
| `Symlink of string
| `Block_device
| `Character_device
| `Directory
| `Fifo
| `Socket]
let lwt_unix_readlink l =
let open Lwt in
Lwt_preemptive.detach Unix.readlink l
let file_info ?(follow_symlink=false) path =
let stat_fun =
if follow_symlink then Lwt_unix.stat else Lwt_unix.lstat in
Lwt.catch
Lwt.(fun () -> stat_fun path >>= fun s -> return (`Ok (`Unix_stats s)))
begin function
| Unix.Unix_error (Unix.ENOENT, cmd, arg) -> return `Absent
| e -> fail_sys (`File_info path, `Exn e)
end
>>= fun m ->
let open Lwt_unix in
begin match m with
| `Absent -> return `Absent
| `Unix_stats stats ->
begin match stats.st_kind with
| S_DIR -> return (`Directory)
| S_REG -> return (`Regular_file (stats.st_size))
| S_LNK ->
begin
catch_deferred (fun () -> lwt_unix_readlink path)
>>< begin function
| `Ok s -> return s
| `Error e -> fail (`System (`File_info path, `Exn e))
end
end
>>= fun destination ->
return (`Symlink destination)
| S_CHR -> return (`Character_device)
| S_BLK -> return (`Block_device)
| S_FIFO -> return (`Fifo)
| S_SOCK -> return (`Socket)
end
end
let list_directory path =
let f_stream = Lwt_unix.files_of_directory path in
let next s =
wrap_deferred ~on_exn:(fun e -> `System (`List_directory path, `Exn e))
Lwt.(fun () ->
catch (fun () -> Lwt_stream.next s >>= fun n -> return (Some n))
(function Lwt_stream.Empty -> return None | e -> fail e)) in
`Stream (fun () -> (next f_stream))
let remove path =
let rec remove_aux path =
file_info path
>>= begin function
| `Absent -> return ()
| `Block_device
| `Character_device
| `Symlink _
| `Fifo
| `Socket
| `Regular_file _-> wrap_deferred_system (`Remove path) (fun () -> Lwt_unix.unlink path)
| `Directory ->
let `Stream next_dir = list_directory path in
let rec loop () =
next_dir ()
>>= begin function
| Some ".."
| Some "." -> loop ()
| Some name ->
remove_aux (Filename.concat path name)
>>= fun () ->
loop ()
| None -> return ()
end
in
loop ()
>>= fun () ->
wrap_deferred_system (`Remove path) (fun () -> Lwt_unix.rmdir path)
end
in
remove_aux path
>>< begin function
| `Ok () -> return ()
| `Error (`System_exn e) -> fail (`System (`Remove path, `Exn e))
| `Error (`System e) -> fail (`System e)
end
let make_symlink ~target ~link_path =
wrap_deferred (fun () -> Lwt_unix.symlink target link_path)
~on_exn:(fun e ->
begin match e with
| Unix.Unix_error (Unix.EEXIST, cmd, arg) ->
(`System (`Make_symlink (target, link_path), `File_exists link_path))
| e -> (`System (`Make_symlink (target, link_path), `Exn e))
end)
type file_destination = [
| `Into of string
| `Onto of string
]
let path_of_destination ~src ~dst =
match dst with
| `Into p -> Filename.(concat p (basename src))
| `Onto p -> p
let copy
?(ignore_strange=false) ?(symlinks=`Fail) ?(buffer_size=64_000)
?(if_exists=`Fail)
~src dst =
let rec copy_aux ~src ~dst =
file_info src
>>= begin function
| `Absent -> fail (`File_not_found src)
| `Block_device
| `Character_device
| `Fifo
| `Socket as k ->
if ignore_strange then return () else fail (`Wrong_file_kind (src, k))
| `Symlink content ->
begin match symlinks with
| `Fail -> fail (`Wrong_file_kind (src, `Symlink content))
| `Follow -> copy_aux ~src:content ~dst
| `Redo ->
let link_path = path_of_destination ~src ~dst in
begin match if_exists with
| `Fail ->
return ()
| `Overwrite
| `Update -> remove link_path
end
>>= fun () ->
make_symlink ~target:content ~link_path
end
| `Regular_file _->
let output_path = path_of_destination ~src ~dst in
let open_spec =
match if_exists with
| `Fail -> `Create_file output_path
| `Overwrite | `Update -> `Overwrite_file output_path
in
IO.with_out_channel ~buffer_size open_spec ~f:(fun outchan ->
IO.with_in_channel ~buffer_size (`File src) ~f:(fun inchan ->
let rec loop () =
IO.read ~count:buffer_size inchan
>>= begin function
| "" -> return ()
| buf ->
IO.write outchan buf >>= fun () ->
loop ()
end
in
loop ()))
| `Directory ->
let new_dir = path_of_destination ~src ~dst in
file_info new_dir
>>= begin function
| `Absent ->
make_new_directory new_dir
| smth_else ->
begin match if_exists with
| `Fail -> fail (`File_exists new_dir)
| `Overwrite ->
remove new_dir
>>= fun () ->
make_new_directory new_dir
| `Update ->
if smth_else = `Directory
then return ()
else fail (`Not_a_directory new_dir)
end
end
>>= fun () ->
let `Stream next_dir = list_directory src in
let rec loop () =
next_dir ()
>>= begin function
| Some ".."
| Some "." -> loop ()
| Some name ->
copy_aux
~src:(Filename.concat src name)
~dst:(`Into new_dir)
>>= fun () ->
loop ()
| None -> return ()
end
in
loop ()
end
in
(copy_aux ~src ~dst
>>< begin function
| `Ok () -> return ()
| `Error err ->
begin match err with
| `IO (`Exn e) -> fail (`System (`Copy src, `Exn e))
| `IO (`File_exists _)
| `IO (`Wrong_path _)
| `File_exists _
| `File_not_found _
| `Not_a_directory _
| `Wrong_file_kind _ as e -> fail (`System (`Copy src, e))
| `System e -> fail (`System e)
end
end)
let move_in_same_device ?(if_exists=`Fail) ~src dst =
let real_dest = path_of_destination ~src ~dst in
begin match if_exists with
| `Fail ->
file_info real_dest
>>= begin function
| `Absent -> return ()
| _ -> fail (`System (`Move src, `File_exists real_dest))
end
| _ -> return ()
end
>>= fun () ->
Lwt.catch
Lwt.(fun () -> Lwt_unix.rename src real_dest >>= fun () -> return (`Ok `Moved))
begin function
| Unix.Unix_error (Unix.EXDEV, cmd, arg) -> return `Must_copy
| Unix.Unix_error (Unix.ENOTEMPTY, cmd, arg) -> return `Must_copy
| e -> fail (`System (`Move src, `Exn e))
end
let move ?ignore_strange ?symlinks ?buffer_size ?if_exists ~src dst =
move_in_same_device ?if_exists ~src dst
>>= begin function
| `Moved -> return ()
| `Must_copy ->
copy ~src ?buffer_size ?ignore_strange ?symlinks ?if_exists dst
>>= fun () ->
remove src
end
type file_tree = [
| `Node of string * file_tree list
| `Leaf of string * file_info
]
let file_tree ?(follow_symlinks=false) path =
let directory p l = return (`Node (p, l)) in
let file p l = return (`Leaf (p, l)) in
let rec find_aux ?name_to_report path =
let name =
match name_to_report with
| Some s -> s
| None -> Filename.basename path in
file_info path
>>= begin function
| `Absent -> fail (`File_not_found path)
| `Block_device
| `Character_device
| `Fifo
| `Regular_file _
| `Socket as k -> file name k
| `Symlink content as k ->
begin match follow_symlinks with
| true ->
let continue =
if Filename.is_relative content
then (Filename.concat path content)
else content in
find_aux ~name_to_report:(Filename.basename path) continue
| false ->
file name k
end
| `Directory ->
let `Stream next_dir = list_directory path in
let rec loop acc =
next_dir ()
>>= begin function
| Some ".."
| Some "." -> loop acc
| Some name -> loop (name :: acc)
| None -> return acc
end
in
loop []
>>= fun sub_list ->
List.fold_left
~init:(return []) (List.sort ~cmp:String.compare sub_list)
~f:(fun prev dir ->
prev >>= fun l ->
find_aux (Filename.concat path dir)
>>= fun newone ->
return (newone :: l))
>>| List.rev
>>= fun sub_tree ->
directory name sub_tree
end
in
(find_aux path
>>< function
| `Ok o -> return o
| `Error e ->
begin match e with
| `Io_exn e -> fail (`System (`File_tree path, `Exn e))
| `File_not_found _ as e -> fail (`System (`File_tree path, e))
| `System e -> fail (`System e)
end)
let file_info_to_string = function
| `Absent -> "Absent"
| `Regular_file i -> sprintf "Regular file (size %d B)" i
| `Symlink s -> sprintf "Sym-link to %S" s
| `Block_device -> "Block device"
| `Character_device -> "Character device"
| `Directory -> "Directory"
| `Fifo -> "FIFO"
| `Socket -> "Socket"
let error_to_string = function
| `System (where, what) ->
sprintf "System error while %s: %s"
begin match where with
| `File_info s -> sprintf "getting info on %S" s
| `Make_directory s -> sprintf "making directory %S" s
| `File_tree s -> sprintf "getting file tree from %S" s
| `Move s -> sprintf "moving %S" s
| `Copy s -> sprintf "copying %S" s
| `Make_symlink (s1, s2) -> sprintf "Making symlink from target %S to %s" s1 s2
| `Remove s -> sprintf "removing %S" s
| `List_directory s -> sprintf "listing directory %S" s
end
begin match what with
| `Already_exists -> "Already exists"
| `IO _ as e -> sprintf "I/O Error %S" (IO.error_to_string e)
| `File_not_found s -> sprintf "File not found (%S)" s
| `Not_a_directory s -> sprintf "Not a directory (%S)" s
| `File_exists s -> sprintf "File exists (%S)" s
| `Wrong_path s -> sprintf "Wrong path (%S)" s
| `Wrong_file_kind (s, k) ->
sprintf "Wrong kind of file (%S: %s)" s (file_info_to_string k)
| `Exn e -> sprintf "Exception %s" (exn e)
| `Wrong_access_rights o -> sprintf "wrong access rights: 0o%o" o
end
| `Shell (cmd, err) ->
sprintf "Shell command %S failed: %s" cmd
(Shell.status_to_string err)
end