(**************************************************************************)
(*                                                                        *)
(*                                 OCaml                                  *)
(*                                                                        *)
(*           Jerome Vouillon, projet Cristal, INRIA Rocquencourt          *)
(*           OCaml port by John Malecki and Xavier Leroy                  *)
(*                                                                        *)
(*   Copyright 1996 Institut National de Recherche en Informatique et     *)
(*     en Automatique.                                                    *)
(*                                                                        *)
(*   All rights reserved.  This file is distributed under the terms of    *)
(*   the GNU Lesser General Public License version 2.1, with the          *)
(*   special exception on linking described in the file LICENSE.          *)
(*                                                                        *)
(**************************************************************************)

(* Manage the loading of the program *)

open Int64ops
open Unix
open Unix_tools
open Debugger_config
open Primitives
open Parameters
open Input_handling
open Question
open Program_loading
open Time_travel

(*** Connection opening and control. ***)

(* Name of the file if the socket is in the unix domain.*)
let file_name = ref (None : string option)

(* Default connection handler. *)
let buffer = Bytes.create 1024
let control_connection pid fd =
  if (read fd.io_fd buffer 0 1024) = 0 then
    forget_process fd pid
  else begin
    prerr_string "Garbage data from process ";
    prerr_int pid;
    prerr_endline ""
    end

(* Accept a connection from another process. *)
let accept_connection continue fd =
  let (sock, _) = accept ~cloexec:true fd.io_fd in
  let io_chan = io_channel_of_descr sock in
  let pid = input_binary_int io_chan.io_in in
  if pid = -1 then begin
    let pid' = input_binary_int io_chan.io_in in
    new_checkpoint pid' io_chan;
    Input_handling.add_file io_chan (control_connection pid');
    continue ()
    end
  else begin
    if set_file_descriptor pid io_chan then
      Input_handling.add_file io_chan (control_connection pid)
    end

(* Initialize the socket. *)
let open_connection address continue =
  try
    let addr_info = convert_address address in
      file_name :=
        (match addr_info with
         | { ai_addr = ADDR_UNIX file; _} -> Some file
         | _ -> None);
      let sock = socket ~cloexec:true addr_info.ai_family addr_info.ai_socktype
                   addr_info.ai_protocol in
        (try
           bind sock addr_info.ai_addr;
           setsockopt sock SO_REUSEADDR true;
           listen sock 3;
           connection := io_channel_of_descr sock;
           Input_handling.add_file !connection (accept_connection continue);
           connection_opened := true
         with x -> cleanup x @@ fun () -> close sock)
  with
    Failure e -> prerr_endline e; raise Toplevel
  | (Unix_error _) as err -> report_error err; raise Toplevel

(* Close the socket. *)
let close_connection () =
  if !connection_opened then begin
    connection_opened := false;
    Input_handling.remove_file !connection;
    close_io !connection;
    match !file_name with
      Some file ->
        unlink file
    | None ->
        ()
    end

(*** Kill program. ***)
let loaded = ref false

let kill_program () =
  Breakpoints.remove_all_breakpoints ();
  History.empty_history ();
  kill_all_checkpoints ();
  loaded := false;
  close_connection ()

let ask_kill_program () =
  if not !loaded then
    true
  else
    let answer = yes_or_no "A program is being debugged already. Kill it" in
      if answer then
        kill_program ();
      answer

(*** Program loading and initializations. ***)

let initialize_loading () =
  if !debug_loading then begin
    prerr_endline "Loading debugging information...";
    Printf.fprintf Stdlib.stderr "\tProgram: [%s]\n%!" !program_name;
  end;
  begin try access !program_name [F_OK]
  with Unix_error _ ->
    prerr_endline "Program not found.";
    raise Toplevel;
  end;
  Symbols.clear_symbols ();
  Symbols.read_symbols Debugcom.main_frag !program_name;
  let Load_path.{visible; hidden} = Load_path.get_paths () in
  let visible = visible @ !Symbols.program_source_dirs in
  Load_path.init ~auto_include:Compmisc.auto_include ~visible ~hidden;
  Envaux.reset_cache ();
  if !debug_loading then
    prerr_endline "Opening a socket...";
  open_connection !socket_name
    (function () ->
      go_to _0;
      Symbols.set_all_events Debugcom.main_frag;
      exit_main_loop ())

(* Ensure the program is already loaded. *)
let ensure_loaded () =
  if not !loaded then begin
    print_string "Loading program... ";
    flush Stdlib.stdout;
    if !program_name = "" then begin
      prerr_endline "No program specified.";
      raise Toplevel
    end;
    try
      initialize_loading();
      !launching_func ();
      if !debug_loading then
        prerr_endline "Waiting for connection...";
      main_loop ();
      loaded := true;
      prerr_endline "done."
    with
      x ->
        cleanup x kill_program
  end
