open Unix;;
open Sys;;
open Inet;;

(** 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 ->
        let exec () =
          dup2 socket stdin;
          dup2 socket stdout;
          dup2 socket stderr;
          close socket;
          execvp command.(0) command;
          in
        handle_unix_error exec ();
        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 *)
  ignore (signal sigchld (Signal_handle wait_for_children));
  (* On ignore SIGPIPE et on recevra une erreur EPIPE lorsqu'on parle à
     un client qui à couper brutalement la connection. *)

  ignore (signal sigpipe Signal_ignore);
  (* 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;;