Audited code. Vsys should now be robust to system exceptions.
[vsys.git] / fifowatcher.ml
1 (** fifowatcher.ml: Routines for creating and managing fifos *)
2
3 open Inotify
4 open Unix
5 open Globals
6 open Dirwatcher
7 open Printf
8
9 (** A connected process, FIFO *)
10 type channel_pipe = Process of out_channel | Fifo of out_channel | BrokenPipe
11 (** Signed file descriptors. Usually, we'll make sure that they're not
12   mistreated *)
13 type signed_fd = Infd of Unix.file_descr | Outfd of Unix.file_descr | Eventfd of Unix.file_descr
14
15 let fdmap: (Unix.file_descr,string*string) Hashtbl.t = Hashtbl.create 1024
16 (** Maps pids to slice connections. Needed to clean up fds when a script dies
17   with EPIPE *)
18 let pidmap: (int,signed_fd list) Hashtbl.t = Hashtbl.create 1024
19 let backend_prefix = ref ""
20 let open_fds: (Unix.file_descr,channel_pipe) Hashtbl.t = Hashtbl.create 1024
21
22
23 (** Receive an event from a running script. This event must be relayed to the
24   slice that invoked it.
25
26   @param idesc fd/fname identifier for process
27   *)
28 let receive_process_event (idesc: fname_and_fd) (_: fname_and_fd) =
29   let (_,ifd) = idesc in
30   let cp = try Hashtbl.find open_fds ifd with
31       Not_found->
32         printf "Fifo fd disappeared\n";flush Pervasives.stdout;raise Bug
33   in
34     match (cp) with 
35       | Fifo(fifo_outchan) ->
36           let process_inchan = in_channel_of_descr ifd in
37           let cont = ref true in
38             while (!cont) do
39               try 
40                 let curline = input_line process_inchan in
41                   fprintf fifo_outchan "%s\n" curline;flush fifo_outchan
42               with 
43                 | End_of_file|Sys_blocked_io|Unix_error(EPIPE,_,_)|Unix_error(EBADF,_,_) ->
44                     begin
45                       cont:=false
46                     end
47                 | Unix_error(_,s1,s2) -> printf "Unix error %s - %s\n" s1 s2;flush Pervasives.stdout;cont:=false
48                 | Sys_error(s) -> (* We get this error if the EPIPE comes before the EOF marker*) cont:=false
49                 | e -> printf "Error - received unexpected event from file system !!!\n";raise e
50             done
51       | _ -> printf "Bug! Process fd received in the channel handler\n";flush Pervasives.stdout;raise Bug
52
53
54 let rec openentry_int fifoin fifoout (abspath:string*string) =
55   let fdin =
56     try openfile fifoin [O_RDONLY;O_NONBLOCK] 0o777 with 
57         e->printf "Error opening and connecting FIFO: %s,%o\n" fifoin 0o777;flush Pervasives.stdout;raise e
58   in
59     Hashtbl.replace fdmap fdin abspath;
60     Fdwatcher.add_fd (Some(fifoin),fdin) (Some(fifoout),stdout) receive_fifo_event
61 and reopenentry_int fdin fifoin fifoout =
62   close fdin;
63     Fdwatcher.del_fd fdin;
64     let abspath = try 
65       Hashtbl.find fdmap fdin with _ -> printf "Bug: Phantom pipe\n";flush Pervasives.stdout;raise Bug
66     in
67       openentry_int fifoin fifoout abspath
68 (** receive an event from a fifo and connect to the corresponding service, or to
69   create it if it doesn't exit 
70   @param eventdescriptor Name of input pipe,in descriptor
71   @param outdescriptor Name of output pipe, out descriptor
72   *)
73 and receive_fifo_event eventdescriptor outdescriptor =
74   let (evfname,evfd) = eventdescriptor in
75   let (fname_other,fd_other) = outdescriptor in
76   (* Open the output pipe, or use stdout instead *)
77   let outfd =
78     match (fname_other) with
79       | Some(str)->
80           (
81             try openfile str [O_WRONLY;O_NONBLOCK] 0o777 with
82                 _->printf "Output pipe not open, using stdout in place of %s\n" str;flush Pervasives.stdout;stdout
83           )
84       | None-> printf "Bug, nameless pipe\n";flush Pervasives.stdout;raise Bug
85   in
86   (* Check if the input descriptor is already registered (=> a session is open).
87    If not, register it and start a new session.*)
88   let pipe = try Hashtbl.find open_fds evfd with
89     | Not_found ->
90         (* Ok, need to launch script *)
91         let execpath,slice_name = Hashtbl.find fdmap evfd in
92         let (script_infd,pout) = Unix.pipe () in
93         let (pin,script_outfd) = Unix.pipe () in
94           set_nonblock script_infd;
95           let rpid = try Some(create_process execpath [|execpath;slice_name|] pin pout pout) with e -> printf "Error executing service: %s\n" execpath;flush Pervasives.stdout;None
96           in
97             match rpid with
98               | None-> BrokenPipe
99               | Some(pid)->
100                   (* Register fds associated with pid so that they can be cleaned up
101                    * when it dies *)
102                   Hashtbl.add pidmap pid [Infd(script_infd);Outfd(script_outfd);Eventfd(evfd)];
103
104                   (* Connect pipe to running script *)
105                   Hashtbl.add open_fds evfd (Process(out_channel_of_descr script_outfd));
106
107                   (* Connect the running script to the pipe *)
108                   Hashtbl.add open_fds script_infd (Fifo(out_channel_of_descr outfd));
109
110                   (* Activate running script *)
111                   Fdwatcher.add_fd (None,script_infd) (None,script_infd) receive_process_event;
112
113                   (Process(out_channel_of_descr script_outfd))
114   in
115   (* We have the connection to the process - because it was open, or because it
116    just got established *)
117   let inchan_fd = in_channel_of_descr evfd in
118     match (pipe) with
119       | Process(out_channel) -> 
120           let cont = ref true in
121             while (!cont) do
122               try 
123                 printf "Reading...\n";flush Pervasives.stdout;
124                 let curline = input_line inchan_fd in
125                   fprintf out_channel "%s\n" curline;flush out_channel 
126               with 
127                 |End_of_file->
128                     (
129                       match (evfname,fname_other) with
130                         | Some(str1),Some(str2)->
131                             printf "Reopening entry\n";flush Pervasives.stdout;
132                             reopenentry_int evfd str1 str2
133                         | Some(str1),None ->
134                             printf "Bug, nameless pipe\n";flush Pervasives.stdout;raise Bug
135                         | None,_ ->
136                             printf "Race condition -> user deleted file before closing it. Clever ploy, but won't work.\n";
137                             flush Pervasives.stdout
138                     );
139                     cont:=false
140                 |Sys_blocked_io ->printf "Sysblockedio\n";flush Pervasives.stdout;
141                                   cont:=false
142                 | _ ->printf "Bug: unhandled exception\n";flush Pervasives.stdout;raise Bug
143             done
144       | BrokenPipe -> ()
145       | Fifo(_) -> printf "BUG! received process event from fifo\n";raise Bug
146
147
148 (** Make a pair of fifo entries *)
149 let mkentry fqp abspath perm = 
150   printf "Making entry %s->%s\n" fqp abspath;flush Pervasives.stdout;
151   let fifoin=sprintf "%s.in" fqp in
152   let fifoout=sprintf "%s.out" fqp in
153     (try Unix.unlink fifoin with _ -> ());
154     (try Unix.unlink fifoout with _ -> ());
155     (try 
156        Unix.mkfifo (sprintf "%s.in" fqp) 0o666;
157        Unix.mkfifo (sprintf "%s.out" fqp) 0o666;
158        Success
159      with 
160          e->printf "Error creating FIFO: %s->%s. May be something wrong at the frontend.\n" fqp fifoout;flush Pervasives.stdout;Failed)
161
162 (** Open fifos for a session. Will shutdown vsys if the fifos don't exist *)
163 let openentry fqp abspath perm =
164   let fifoin = String.concat "." [fqp;"in"] in
165   let fifoout = String.concat "." [fqp;"out"] in
166     openentry_int fifoin fifoout abspath
167
168 let sigchld_handle s =
169   let pid,_=Unix.waitpid [Unix.WNOHANG] 0 in
170     try
171       let sfd_list = Hashtbl.find pidmap pid in
172       let handle_sfd sfd =
173         match sfd with
174           | Infd(fd) ->
175               close fd;
176               Fdwatcher.del_fd fd
177           | Outfd(fd)->
178               close fd
179           | Eventfd(fd)->
180               Hashtbl.remove open_fds fd (* Disconnect pipe *)
181       in
182         List.iter handle_sfd sfd_list;
183         Hashtbl.remove pidmap pid
184     with 
185         Not_found-> (* Do nothing, probably a grandchild *)
186           ()
187
188 let initialize () = 
189   Sys.set_signal Sys.sigchld (Sys.Signal_handle sigchld_handle)