open Unix;;
open Sys;;

(** Création d'une chaîne de caractères à partir d'un adresse *)
let string_of_sockaddr s = match s with
  | ADDR_UNIX s -> s
  | ADDR_INET (inet,p) -> (string_of_inet_addr inet)^":"^(string_of_int p)

(** Création et attachement d'une socket serveur *)
let open_server port =
  let socket = socket PF_INET SOCK_STREAM 0 in
  setsockopt socket SO_REUSEADDR true ;
  bind socket (ADDR_INET (inet_addr_anyport));
  listen socket 20;
  socket;;

(** Installation d'un service. *)
let install_service (portservice) =
  Printf.eprintf "Service \"%s\" on port %d\n" service.(0) port;
  Pervasives.flush Pervasives.stderr;
  let socket = open_server port in
  (socketservice);;

(** Exécution d'un service. *)
let exec_service command socket =
  try
    match fork () with
      0 ->
        dup2 socket stdin;
        dup2 socket stdout;
        dup2 socket stderr;
        close socket;
        execvp command.(0) command;
        assert false
    | _ ->
        close socket
  with Unix_error ((EAGAIN|ENOMEM),"fork",_) ->
    close socket;
    prerr_endline "No more process available";;

(** Attente de tous les fils décédés. *)
let rec wait_for_children signal =
  try
    let pid,_waitpid [WNOHANG] (-1) in
    if pid <> 0 then wait_for_children signal
  with Unix_error(ECHILD,_,_) -> ();;

(** Démarrage du démon *)
let inetd services =
  (* Gestion du signal SICHLD pour récupérer les zombis *)
  set_signal sigchld (Signal_handle wait_for_children);
  (* installer les services *)
  let installed_services = List.map install_service services in
  (* construire la liste des sockets à écouter *)
  let sockets = List.map fst installed_services in
  (* acceptation d'une connexion et démarrage de la command associée *)
  let rec accept_connection server_socket =
    try
      let connection_socket,remote_addr = accept server_socket in
      Printf.eprintf "Connexion depuis %s\n" (string_of_sockaddr remote_addr);
      Pervasives.flush Pervasives.stderr;
      try
        (* chercher le service associé à la socket *)
        let command = List.assoc server_socket installed_services in
        (* exécuter le service avec la connexion fd *)
        exec_service command connection_socket
      with Not_found -> close connection_socket
    with Unix_error(EINTR,_,_) -> accept_connection server_socket in
  (* traitement du serveur *)
  let rec treatment () =
    begin
      try
        (* attente d'une connexion sur l'une des sockets *)
        let connections,_,_ = select sockets [] [] (-1.) in
        (* acceptation de toutes les connexions en attente sur socket_serveur *)
        List.iter accept_connection connections
      with Unix_error (EINTR,_,_) -> ()
    end;
    treatment () in
  treatment();;

(** Définition des services : liste de (port, command). *)
let services = [ (7000, [| "cat" |]); (7001, [| "date""-I" |]); (7002, [| "ocaml" |]) ];;

handle_unix_error inetd services;;