aboutsummaryrefslogtreecommitdiff
path: root/lib/system.ml
diff options
context:
space:
mode:
Diffstat (limited to 'lib/system.ml')
-rw-r--r--lib/system.ml313
1 files changed, 313 insertions, 0 deletions
diff --git a/lib/system.ml b/lib/system.ml
new file mode 100644
index 0000000000..c408061852
--- /dev/null
+++ b/lib/system.ml
@@ -0,0 +1,313 @@
+(************************************************************************)
+(* * The Coq Proof Assistant / The Coq Development Team *)
+(* v * INRIA, CNRS and contributors - Copyright 1999-2018 *)
+(* <O___,, * (see CREDITS file for the list of authors) *)
+(* \VV/ **************************************************************)
+(* // * This file is distributed under the terms of the *)
+(* * GNU Lesser General Public License Version 2.1 *)
+(* * (see LICENSE file for the text of the license) *)
+(************************************************************************)
+
+(* $Id$ *)
+
+open Pp
+open Util
+
+include Minisys
+
+(** Returns the list of all recursive subdirectories of [root] in
+ depth-first search, with sons ordered as on the file system;
+ warns if [root] does not exist *)
+
+let warn_cannot_open_dir =
+ CWarnings.create ~name:"cannot-open-dir" ~category:"filesystem"
+ (fun dir -> str ("Cannot open directory " ^ dir))
+
+let all_subdirs ~unix_path:root =
+ let l = ref [] in
+ let add f rel = l := (f, rel) :: !l in
+ let rec traverse path rel =
+ let f = function
+ | FileDir (path,f) ->
+ let newrel = rel @ [f] in
+ add path newrel;
+ traverse path newrel
+ | _ -> ()
+ in process_directory f path
+ in
+ if exists_dir root then traverse root []
+ else warn_cannot_open_dir root;
+ List.rev !l
+
+(* Caching directory contents for efficient syntactic equality of file
+ names even on case-preserving but case-insensitive file systems *)
+
+module StrMod = struct
+ type t = string
+ let compare = compare
+end
+
+module StrMap = Map.Make(StrMod)
+module StrSet = Set.Make(StrMod)
+
+let dirmap = ref StrMap.empty
+
+let make_dir_table dir =
+ let filter_dotfiles s f = if f.[0] = '.' then s else StrSet.add f s in
+ Array.fold_left filter_dotfiles StrSet.empty (Sys.readdir dir)
+
+(** Don't trust in interactive mode (the default) *)
+let trust_file_cache = ref false
+
+let exists_in_dir_respecting_case dir bf =
+ let cache_dir dir =
+ let contents = make_dir_table dir in
+ dirmap := StrMap.add dir contents !dirmap;
+ contents in
+ let contents, fresh =
+ try
+ (* in batch mode, assume the directory content is still fresh *)
+ StrMap.find dir !dirmap, !trust_file_cache
+ with Not_found ->
+ (* in batch mode, we are not yet sure the directory exists *)
+ if !trust_file_cache && not (exists_dir dir) then StrSet.empty, true
+ else cache_dir dir, true in
+ StrSet.mem bf contents ||
+ not fresh &&
+ (* rescan, there is a new file we don't know about *)
+ StrSet.mem bf (cache_dir dir)
+
+let file_exists_respecting_case path f =
+ (* This function ensures that a file with expected lowercase/uppercase
+ is the correct one, even on case-insensitive file systems *)
+ let rec aux f =
+ let bf = Filename.basename f in
+ let df = Filename.dirname f in
+ (* When [df] is the same as [f], it means that the root of the file system
+ has been reached. There is no point in looking further up. *)
+ (String.equal df "." || String.equal f df || aux df)
+ && exists_in_dir_respecting_case (Filename.concat path df) bf
+ in (!trust_file_cache || Sys.file_exists (Filename.concat path f)) && aux f
+
+let rec search paths test =
+ match paths with
+ | [] -> []
+ | lpe :: rem -> test lpe @ search rem test
+
+let warn_ambiguous_file_name =
+ CWarnings.create ~name:"ambiguous-file-name" ~category:"filesystem"
+ (fun (filename,l,f) -> str filename ++ str " has been found in" ++ spc () ++
+ hov 0 (str "[ " ++
+ hv 0 (prlist_with_sep (fun () -> str " " ++ pr_semicolon())
+ (fun (lpe,_) -> str lpe) l)
+ ++ str " ];") ++ fnl () ++
+ str "loading " ++ str f)
+
+
+let where_in_path ?(warn=true) path filename =
+ let check_and_warn l = match l with
+ | [] -> raise Not_found
+ | (lpe, f) :: l' ->
+ let () = match l' with
+ | _ :: _ when warn -> warn_ambiguous_file_name (filename,l,f)
+ | _ -> ()
+ in
+ (lpe, f)
+ in
+ check_and_warn (search path (fun lpe ->
+ let f = Filename.concat lpe filename in
+ if file_exists_respecting_case lpe filename then [lpe,f] else []))
+
+let find_file_in_path ?(warn=true) paths filename =
+ if not (Filename.is_implicit filename) then
+ (* the name is considered to be a physical name and we use the file
+ system rules (e.g. possible case-insensitivity) to find it *)
+ if Sys.file_exists filename then
+ let root = Filename.dirname filename in
+ root, filename
+ else
+ CErrors.user_err ~hdr:"System.find_file_in_path"
+ (hov 0 (str "Can't find file" ++ spc () ++ str filename))
+ else
+ (* the name is considered to be the transcription as a relative
+ physical name of a logical name, so we deal with it as a name
+ to be locate respecting case *)
+ try where_in_path ~warn paths filename
+ with Not_found ->
+ CErrors.user_err ~hdr:"System.find_file_in_path"
+ (hov 0 (str "Can't find file" ++ spc () ++ str filename ++ spc () ++
+ str "on loadpath"))
+
+let is_in_path lpath filename =
+ try ignore (where_in_path ~warn:false lpath filename); true
+ with Not_found -> false
+
+let warn_path_not_found =
+ CWarnings.create ~name:"path-not-found" ~category:"filesystem"
+ (fun () -> str "system variable PATH not found")
+
+let is_in_system_path filename =
+ try
+ let lpath = CUnix.path_to_list (Sys.getenv "PATH") in
+ is_in_path lpath filename
+ with Not_found ->
+ warn_path_not_found ();
+ false
+
+let open_trapping_failure name =
+ try open_out_bin name
+ with e when CErrors.noncritical e ->
+ CErrors.user_err ~hdr:"System.open" (str "Can't open " ++ str name)
+
+let warn_cannot_remove_file =
+ CWarnings.create ~name:"cannot-remove-file" ~category:"filesystem"
+ (fun filename -> str"Could not remove file " ++ str filename ++ str" which is corrupted!")
+
+let try_remove filename =
+ try Sys.remove filename
+ with e when CErrors.noncritical e ->
+ warn_cannot_remove_file filename
+
+let error_corrupted file s =
+ CErrors.user_err ~hdr:"System" (str file ++ str ": " ++ str s ++ str ". Try to rebuild it.")
+
+let input_binary_int f ch =
+ try input_binary_int ch
+ with
+ | End_of_file -> error_corrupted f "premature end of file"
+ | Failure s -> error_corrupted f s
+let output_binary_int ch x = output_binary_int ch x; flush ch
+
+let marshal_out ch v = Marshal.to_channel ch v []; flush ch
+let marshal_in filename ch =
+ try Marshal.from_channel ch
+ with
+ | End_of_file -> error_corrupted filename "premature end of file"
+ | Failure s -> error_corrupted filename s
+
+let digest_out = Digest.output
+let digest_in filename ch =
+ try Digest.input ch
+ with
+ | End_of_file -> error_corrupted filename "premature end of file"
+ | Failure s -> error_corrupted filename s
+
+let marshal_out_segment f ch v =
+ let start = pos_out ch in
+ output_binary_int ch 0; (* dummy value for stop *)
+ marshal_out ch v;
+ let stop = pos_out ch in
+ seek_out ch start;
+ output_binary_int ch stop;
+ seek_out ch stop;
+ digest_out ch (Digest.file f)
+
+let marshal_in_segment f ch =
+ let stop = (input_binary_int f ch : int) in
+ let v = marshal_in f ch in
+ let digest = digest_in f ch in
+ v, stop, digest
+
+let skip_in_segment f ch =
+ let stop = (input_binary_int f ch : int) in
+ seek_in ch stop;
+ stop, digest_in f ch
+
+type magic_number_error = {filename: string; actual: int; expected: int}
+exception Bad_magic_number of magic_number_error
+
+let raw_extern_state magic filename =
+ let channel = open_trapping_failure filename in
+ output_binary_int channel magic;
+ channel
+
+let raw_intern_state magic filename =
+ try
+ let channel = open_in_bin filename in
+ let actual_magic = input_binary_int filename channel in
+ if not (Int.equal actual_magic magic) then
+ raise (Bad_magic_number {
+ filename=filename;
+ actual=actual_magic;
+ expected=magic});
+ channel
+ with
+ | End_of_file -> error_corrupted filename "premature end of file"
+ | Failure s | Sys_error s -> error_corrupted filename s
+
+let extern_state magic filename val_0 =
+ try
+ let channel = raw_extern_state magic filename in
+ try
+ marshal_out channel val_0;
+ close_out channel
+ with reraise ->
+ let reraise = CErrors.push reraise in
+ let () = try_remove filename in
+ iraise reraise
+ with Sys_error s ->
+ CErrors.user_err ~hdr:"System.extern_state" (str "System error: " ++ str s)
+
+let intern_state magic filename =
+ try
+ let channel = raw_intern_state magic filename in
+ let v = marshal_in filename channel in
+ close_in channel;
+ v
+ with Sys_error s ->
+ CErrors.user_err ~hdr:"System.intern_state" (str "System error: " ++ str s)
+
+let with_magic_number_check f a =
+ try f a
+ with Bad_magic_number {filename=fname;actual=actual;expected=expected} ->
+ CErrors.user_err ~hdr:"with_magic_number_check"
+ (str"File " ++ str fname ++ strbrk" has bad magic number " ++
+ int actual ++ str" (expected " ++ int expected ++ str")." ++
+ spc () ++
+ strbrk "It is corrupted or was compiled with another version of Coq.")
+
+(* Time stamps. *)
+
+type time = float * float * float
+
+let get_time () =
+ let t = Unix.times () in
+ (Unix.gettimeofday(), t.Unix.tms_utime, t.Unix.tms_stime)
+
+(* Keep only 3 significant digits *)
+let round f = (floor (f *. 1e3)) *. 1e-3
+
+let time_difference (t1,_,_) (t2,_,_) = round (t2 -. t1)
+
+let fmt_time_difference (startreal,ustart,sstart) (stopreal,ustop,sstop) =
+ real (round (stopreal -. startreal)) ++ str " secs " ++
+ str "(" ++
+ real (round (ustop -. ustart)) ++ str "u" ++
+ str "," ++
+ real (round (sstop -. sstart)) ++ str "s" ++
+ str ")"
+
+let with_time ~batch ~header f x =
+ let tstart = get_time() in
+ let msg = if batch then "" else "Finished transaction in " in
+ try
+ let y = f x in
+ let tend = get_time() in
+ let msg2 = if batch then "" else " (successful)" in
+ Feedback.msg_info (header ++ str msg ++ fmt_time_difference tstart tend ++ str msg2);
+ y
+ with e ->
+ let tend = get_time() in
+ let msg = if batch then "" else "Finished failing transaction in " in
+ let msg2 = if batch then "" else " (failure)" in
+ Feedback.msg_info (header ++ str msg ++ fmt_time_difference tstart tend ++ str msg2);
+ raise e
+
+(* We use argv.[0] as we don't want to resolve symlinks *)
+let get_toplevel_path ?(byte=Sys.(backend_type = Bytecode)) top =
+ let open Filename in
+ let dir = if String.equal (basename Sys.argv.(0)) Sys.argv.(0)
+ then "" else dirname Sys.argv.(0) ^ dir_sep in
+ let exe = if Sys.(os_type = "Win32" || os_type = "Cygwin") then ".exe" else "" in
+ let eff = if byte then ".byte" else ".opt" in
+ dir ^ top ^ eff ^ exe