File: child.ml

package info (click to toggle)
xen-api-libs 0.5.2-3
  • links: PTS, VCS
  • area: main
  • in suites: wheezy
  • size: 1,940 kB
  • sloc: ml: 13,925; sh: 2,930; ansic: 1,699; makefile: 1,240; python: 83
file content (228 lines) | stat: -rw-r--r-- 7,582 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
open Stringext
open Pervasiveext

let debug (fmt : ('a, unit, string, unit) format4) = (Printf.kprintf (fun s -> Printf.fprintf stderr "%s\n" s) fmt)

exception Cancelled

type syslog_stdout_t = {
  enabled : bool;
  key : string option;
}

type state_t = {
  cmdargs : string list;
  env : string list;
  id_to_fd_map : (string * int option) list;
  syslog_stdout : syslog_stdout_t;
  ids_received : (string * Unix.file_descr) list;
  fd_sock2 : Unix.file_descr option;
  finished : bool;
}

open Fe_debug

let handle_fd_sock fd_sock state =
  try
    let (newfd,buffer) = Fecomms.receive_named_fd fd_sock in
    if Unixext.int_of_file_descr newfd = -1 then begin
      debug "Failed to receive an fd associated with the message '%s'" buffer;
      failwith "Didn't get an fd"
    end;
    let dest_fd = List.assoc buffer state.id_to_fd_map in
    let fd = begin 
      match dest_fd with 
	| Some d -> 
	    debug "Received fd named: %s - duping to %d (from %d)" buffer d (Unixext.int_of_file_descr newfd);
	    let d = Unixext.file_descr_of_int d in
	    begin
	      if d = newfd
	      then ()
	      else begin
		Unix.dup2 newfd d;
		Unix.close newfd;
	      end
	    end;
	    d
	| None -> 
	    debug "Received fd named: %s (%d)" buffer (Unixext.int_of_file_descr newfd);
	    newfd
    end in
    {state with ids_received = (buffer,fd) :: state.ids_received}
  with Fecomms.Connection_closed -> 
    {state with fd_sock2 = None}

let handle_comms_sock comms_sock state =
  let call = Fecomms.read_raw_rpc comms_sock in
  match call with 
    | Fe.Cancel -> debug "Cancel"; raise Cancelled
    | Fe.Exec -> debug "Exec"; {state with finished=true;}
    | _ -> 
	debug "Ignoring unknown command";
	state

let handle_comms_no_fd_sock2 comms_sock fd_sock state =
  debug "Selecting in handle_comms_no_fd_sock2";
  let (ready,_,_) = Unix.select [comms_sock; fd_sock] [] [] (-1.0) in
  debug "Done";
  if List.mem fd_sock ready then begin
    debug "fd sock";
    let fd_sock2,_ = Unix.accept fd_sock in
    {state with fd_sock2=Some fd_sock2}
  end else begin
    debug "comms sock";
    handle_comms_sock comms_sock state    
  end
  
let handle_comms_with_fd_sock2 comms_sock fd_sock fd_sock2 state =
  debug "Selecting in handle_comms_with_fd_sock2";
  let (ready,_,_) = Unix.select [comms_sock; fd_sock2] [] [] (-1.0) in
  debug "Done";
  if List.mem fd_sock2 ready then begin
    debug "fd sock2";
    handle_fd_sock fd_sock2 state 
  end else begin
    debug "comms sock";
    handle_comms_sock comms_sock state    
  end

let handle_comms comms_sock fd_sock state =
  match state.fd_sock2 with 
    | None -> handle_comms_no_fd_sock2 comms_sock fd_sock state
    | Some x -> handle_comms_with_fd_sock2 comms_sock fd_sock x state

let run state comms_sock fd_sock fd_sock_path =
  let rec inner state =
    let state = handle_comms comms_sock fd_sock state in
    if state.finished then state else inner state
  in

  try
    debug "Started: state.cmdargs = [%s]" (String.concat ";" (state.cmdargs));
    debug "Started: state.env = [%s]" (String.concat ";" (state.env));

    let fd = Unix.openfile "/dev/null" [ Unix.O_WRONLY ] 0o0 in
    Unix.dup2 fd Unix.stdin;
    Unix.dup2 fd Unix.stdout;
    Unix.dup2 fd Unix.stderr;

    if fd<>Unix.stdin && fd<>Unix.stdout && fd<>Unix.stderr then Unix.close fd;

    let state = inner state in

    debug "Finished...";
    Unix.close fd_sock;
    (match state.fd_sock2 with Some x -> Unix.close x | None -> ());

    Unixext.unlink_safe fd_sock_path;
    
    (* Finally, replace placeholder uuids in the commandline arguments
       to be the string representation of the fd (where we don't care what
       fd it ends up being) *)
    let args = List.map (fun arg ->
      try 
	let (id_received,fd) = List.find (fun (id_received,fd) -> String.endswith id_received arg) state.ids_received in
	let stem = String.sub arg 0 (String.length arg - String.length id_received) in
	stem ^ (string_of_int (Unixext.int_of_file_descr fd));
      with _ -> arg) state.cmdargs in

    debug "Args after replacement = [%s]" (String.concat ";" args);    
	let name = List.hd args in

    let fds = List.map snd state.ids_received in
    
    debug "I've received the following fds: [%s]\n" 
      (String.concat ";" (List.map (fun fd -> string_of_int (Unixext.int_of_file_descr fd)) fds));

    let in_childlogging = ref None in
    let out_childlogging = ref None in
    if state.syslog_stdout.enabled then begin
      (* Create a pipe used to listen to the child process's stdout *)
      let (in_fd, out_fd) = Unix.pipe () in
      in_childlogging := Some in_fd;
      out_childlogging := Some out_fd
    end;

    let result = Unix.fork () in

    if result=0 then begin
		(* child *)

        (* Make the child's stdout go into the pipe *)
		Opt.iter (fun out_fd -> Unix.dup2 out_fd Unix.stdout) !out_childlogging;

      (* Now let's close everything except those fds mentioned in the ids_received list *)
      Unixext.close_all_fds_except ([Unix.stdin; Unix.stdout; Unix.stderr] @ fds);
      
      (* Distance ourselves from our parent process: *)
      if Unix.setsid () == -1 then failwith "Unix.setsid failed";	  

      (* And exec *)
      Unix.execve name (Array.of_list args) (Array.of_list state.env)
    end else begin
      Fecomms.write_raw_rpc comms_sock (Fe.Execed result);

      List.iter (fun fd -> Unix.close fd) fds;

      (* Close the end of the pipe that's only supposed to be written to by the child process. *)
	  Opt.iter Unix.close !out_childlogging;

      let log_failure reason code = 
		(* The commandline might be too long to clip it *)
		let cmdline = String.concat " " args in
		let limit = 80 - 3 in
		let cmdline' = if String.length cmdline > limit then String.sub cmdline 0 limit ^ "..." else cmdline in
		if code=0 && name = "/usr/lib/xcp/sm/ISOSR" && (String.has_substr cmdline' "sr_scan") then () else
			Syslog.log Syslog.Syslog Syslog.Err (Printf.sprintf "%d (%s) %s %d" result cmdline' reason code) in

	  let status = ref (Unix.WEXITED (-1)) in
	  finally
		  (fun () ->
			  Opt.iter
				  (fun in_fd ->
					  let key = (match state.syslog_stdout.key with None -> Filename.basename name | Some key -> key) in
					  (* Read from the child's stdout and write each one to syslog *)
					  Unixext.lines_iter
						  (fun line ->
							  Syslog.log Syslog.Daemon Syslog.Info (Printf.sprintf "%s[%d]: %s" key result line)
						  ) (Unix.in_channel_of_descr in_fd)
				  ) !in_childlogging
		  ) (fun () -> status := snd (Unix.waitpid [] result));

      let pr = match !status with
		| Unix.WEXITED n -> 
			(* Unfortunately logging this was causing too much spam *)
			if n <> 0 then log_failure "exitted with code" n;
			  Fe.WEXITED n
		| Unix.WSIGNALED n -> 
			  log_failure "exitted with signal" n;
			  Fe.WSIGNALED n 
		| Unix.WSTOPPED n -> 
			  log_failure "stopped with signal" n;
			  Fe.WSTOPPED n
      in
      let result = Fe.Finished (pr) in
	  (* If the controlling process has called Forkhelpers.dontwaitpid
		 then the comms_sock will be closed. We don't need to write our full debug
		 logs in syslog if this happens: *)
	  begin
		  try
			  Fecomms.write_raw_rpc comms_sock result
		  with Unix.Unix_error(Unix.EPIPE, _, _) -> ()
	  end;
      Unix.close comms_sock;
      exit 0;
    end
  with 
    | Cancelled ->
	debug "Cancelling";
	Unix.close comms_sock;
	Unix.close fd_sock;
	Unixext.unlink_safe fd_sock_path;
	exit 0;
    | e -> 
	debug "Caught unexpected exception: %s" (Printexc.to_string e);
	write_log ();
	Unixext.unlink_safe fd_sock_path;
	exit 1