1(*
2 * Copyright (C) 2006-2007 XenSource Ltd.
3 * Copyright (C) 2008      Citrix Ltd.
4 * Author Vincent Hanquez <vincent.hanquez@eu.citrix.com>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU Lesser General Public License as published
8 * by the Free Software Foundation; version 2.1 only. with the special
9 * exception on linking described in file LICENSE.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 * GNU Lesser General Public License for more details.
15 *)
16
17let error fmt = Logging.error "process" fmt
18let info fmt = Logging.info "process" fmt
19let debug fmt = Logging.debug "process" fmt
20
21open Printf
22open Stdext
23
24exception Transaction_again
25exception Transaction_nested
26exception Domain_not_match
27exception Invalid_Cmd_Args
28
29(* This controls the do_debug fn in this module, not the debug logging-function. *)
30let allow_debug = ref false
31
32let c_int_of_string s =
33	let v = ref 0 in
34	let is_digit c = c >= '0' && c <= '9' in
35	let len = String.length s in
36	let i = ref 0 in
37	while !i < len && not (is_digit s.[!i]) do incr i done;
38	while !i < len && is_digit s.[!i]
39	do
40		let x = (Char.code s.[!i]) - (Char.code '0') in
41		v := !v * 10 + x;
42		incr i
43	done;
44	!v
45
46(* when we don't want a limit, apply a max limit of 8 arguments.
47   no arguments take more than 3 currently, which is pointless to split
48   more than needed. *)
49let split limit c s =
50	let limit = match limit with None -> 8 | Some x -> x in
51	String.split ~limit c s
52
53let split_one_path data con =
54	let args = split (Some 2) '\000' data in
55	match args with
56	| path :: "" :: [] -> Store.Path.create path (Connection.get_path con)
57	| _                -> raise Invalid_Cmd_Args
58
59let process_watch t cons =
60	let oldroot = t.Transaction.oldroot in
61	let newroot = Store.get_root t.store in
62	let ops = Transaction.get_paths t |> List.rev in
63	let do_op_watch op cons =
64		let recurse, oldroot, root = match (fst op) with
65		| Xenbus.Xb.Op.Write|Xenbus.Xb.Op.Mkdir -> false, None, newroot
66		| Xenbus.Xb.Op.Rm       -> true, None, oldroot
67		| Xenbus.Xb.Op.Setperms -> false, Some oldroot, newroot
68		| _              -> raise (Failure "huh ?") in
69		Connections.fire_watches ?oldroot root cons (snd op) recurse in
70	List.iter (fun op -> do_op_watch op cons) ops
71
72let create_implicit_path t perm path =
73	let dirname = Store.Path.get_parent path in
74	if not (Transaction.path_exists t dirname) then (
75		let rec check_path p =
76			match p with
77			| []      -> []
78			| h :: l  ->
79				if Transaction.path_exists t h then
80					check_path l
81				else
82					p in
83		let ret = check_path (List.tl (Store.Path.get_hierarchy dirname)) in
84		List.iter (fun s -> Transaction.mkdir ~with_watch:false t perm s) ret
85	)
86
87(* packets *)
88let do_debug con t _domains cons data =
89	if not (Connection.is_dom0 con) && not !allow_debug
90	then None
91	else try match split None '\000' data with
92	| "print" :: msg :: _ ->
93		Logging.xb_op ~tid:0 ~ty:Xenbus.Xb.Op.Debug ~con:"=======>" msg;
94		None
95	| "quota" :: domid :: _ ->
96		let domid = int_of_string domid in
97		let quota = (Store.get_quota t.Transaction.store) in
98		Some (Quota.to_string quota domid ^ "\000")
99	| "watches" :: _ ->
100		let watches = Connections.debug cons in
101		Some (watches ^ "\000")
102	| "mfn" :: domid :: _ ->
103		let domid = int_of_string domid in
104		let con = Connections.find_domain cons domid in
105		may (fun dom -> Printf.sprintf "%nd\000" (Domain.get_mfn dom)) (Connection.get_domain con)
106	| _ -> None
107	with _ -> None
108
109let do_directory con t _domains _cons data =
110	let path = split_one_path data con in
111	let entries = Transaction.ls t (Connection.get_perm con) path in
112	if List.length entries > 0 then
113		(Utils.join_by_null entries) ^ "\000"
114	else
115		""
116
117let do_read con t _domains _cons data =
118	let path = split_one_path data con in
119	Transaction.read t (Connection.get_perm con) path
120
121let do_getperms con t _domains _cons data =
122	let path = split_one_path data con in
123	let perms = Transaction.getperms t (Connection.get_perm con) path in
124	Perms.Node.to_string perms ^ "\000"
125
126let do_getdomainpath _con _t _domains _cons data =
127	let domid =
128		match (split None '\000' data) with
129		| domid :: "" :: [] -> c_int_of_string domid
130		| _                 -> raise Invalid_Cmd_Args
131		in
132	sprintf "/local/domain/%u\000" domid
133
134let do_write con t _domains _cons data =
135	let path, value =
136		match (split (Some 2) '\000' data) with
137		| path :: value :: [] -> Store.Path.create path (Connection.get_path con), value
138		| _                   -> raise Invalid_Cmd_Args
139		in
140	create_implicit_path t (Connection.get_perm con) path;
141	Transaction.write t (Connection.get_perm con) path value
142
143let do_mkdir con t _domains _cons data =
144	let path = split_one_path data con in
145	create_implicit_path t (Connection.get_perm con) path;
146	try
147		Transaction.mkdir t (Connection.get_perm con) path
148	with
149		Define.Already_exist -> ()
150
151let do_rm con t _domains _cons data =
152	let path = split_one_path data con in
153	try
154		Transaction.rm t (Connection.get_perm con) path
155	with
156		Define.Doesnt_exist -> ()
157
158let do_setperms con t _domains _cons data =
159	let path, perms =
160		match (split (Some 2) '\000' data) with
161		| path :: perms :: _ ->
162			Store.Path.create path (Connection.get_path con),
163			(Perms.Node.of_string perms)
164		| _                   -> raise Invalid_Cmd_Args
165		in
166	Transaction.setperms t (Connection.get_perm con) path perms
167
168let do_error _con _t _domains _cons _data =
169	raise Define.Unknown_operation
170
171let do_isintroduced con _t domains _cons data =
172	if not (Connection.is_dom0 con)
173	then raise Define.Permission_denied;
174	let domid =
175		match (split None '\000' data) with
176		| domid :: _ -> int_of_string domid
177		| _          -> raise Invalid_Cmd_Args
178		in
179	if domid = Define.domid_self || Domains.exist domains domid then "T\000" else "F\000"
180
181(* only in xen >= 4.2 *)
182let do_reset_watches con _t _domains cons _data =
183  Connections.del_watches cons con;
184  Connection.del_transactions con
185
186(* only in >= xen3.3                                                                                    *)
187let do_set_target con _t _domains cons data =
188	if not (Connection.is_dom0 con)
189	then raise Define.Permission_denied;
190	match split None '\000' data with
191		| [ domid; target_domid; "" ] -> Connections.set_target cons (c_int_of_string domid) (c_int_of_string target_domid)
192		| _                           -> raise Invalid_Cmd_Args
193
194(*------------- Generic handling of ty ------------------*)
195let send_response ty con t rid response =
196	match response with
197	| Packet.Ack f ->
198		Connection.send_ack con (Transaction.get_id t) rid ty;
199		(* Now do any necessary follow-up actions *)
200		f ()
201	| Packet.Reply ret ->
202		Connection.send_reply con (Transaction.get_id t) rid ty ret
203	| Packet.Error e ->
204		Connection.send_error con (Transaction.get_id t) rid e
205
206let reply_ack fct con t doms cons data =
207	fct con t doms cons data;
208	Packet.Ack (fun () ->
209		if Transaction.get_id t = Transaction.none then
210			process_watch t cons
211	)
212
213let reply_data fct con t doms cons data =
214	let ret = fct con t doms cons data in
215	Packet.Reply ret
216
217let reply_data_or_ack fct con t doms cons data =
218	match fct con t doms cons data with
219		| Some ret -> Packet.Reply ret
220		| None -> Packet.Ack (fun () -> ())
221
222let reply_none fct con t doms cons data =
223	(* let the function reply *)
224	fct con t doms cons data
225
226(* Functions for 'simple' operations that cannot be part of a transaction *)
227let function_of_type_simple_op ty =
228	match ty with
229	| Xenbus.Xb.Op.Debug
230	| Xenbus.Xb.Op.Watch
231	| Xenbus.Xb.Op.Unwatch
232	| Xenbus.Xb.Op.Transaction_start
233	| Xenbus.Xb.Op.Transaction_end
234	| Xenbus.Xb.Op.Introduce
235	| Xenbus.Xb.Op.Release
236	| Xenbus.Xb.Op.Isintroduced
237	| Xenbus.Xb.Op.Resume
238	| Xenbus.Xb.Op.Set_target
239	| Xenbus.Xb.Op.Reset_watches
240	| Xenbus.Xb.Op.Invalid           -> error "called function_of_type_simple_op on operation %s" (Xenbus.Xb.Op.to_string ty);
241	                                    raise (Invalid_argument (Xenbus.Xb.Op.to_string ty))
242	| Xenbus.Xb.Op.Directory         -> reply_data do_directory
243	| Xenbus.Xb.Op.Read              -> reply_data do_read
244	| Xenbus.Xb.Op.Getperms          -> reply_data do_getperms
245	| Xenbus.Xb.Op.Getdomainpath     -> reply_data do_getdomainpath
246	| Xenbus.Xb.Op.Write             -> reply_ack do_write
247	| Xenbus.Xb.Op.Mkdir             -> reply_ack do_mkdir
248	| Xenbus.Xb.Op.Rm                -> reply_ack do_rm
249	| Xenbus.Xb.Op.Setperms          -> reply_ack do_setperms
250	| _                              -> reply_ack do_error
251
252let input_handle_error ~cons ~doms ~fct ~con ~t ~req =
253	let reply_error e =
254		Packet.Error e in
255	try
256		fct con t doms cons req.Packet.data
257	with
258	| Define.Invalid_path          -> reply_error "EINVAL"
259	| Define.Already_exist         -> reply_error "EEXIST"
260	| Define.Doesnt_exist          -> reply_error "ENOENT"
261	| Define.Lookup_Doesnt_exist _ -> reply_error "ENOENT"
262	| Define.Permission_denied     -> reply_error "EACCES"
263	| Not_found                    -> reply_error "ENOENT"
264	| Invalid_Cmd_Args             -> reply_error "EINVAL"
265	| Invalid_argument _           -> reply_error "EINVAL"
266	| Transaction_again            -> reply_error "EAGAIN"
267	| Transaction_nested           -> reply_error "EBUSY"
268	| Domain_not_match             -> reply_error "EINVAL"
269	| Quota.Limit_reached          -> reply_error "EQUOTA"
270	| Quota.Data_too_big           -> reply_error "E2BIG"
271	| Quota.Transaction_opened     -> reply_error "EQUOTA"
272	| (Failure "int_of_string")    -> reply_error "EINVAL"
273	| Define.Unknown_operation     -> reply_error "ENOSYS"
274
275let write_access_log ~ty ~tid ~con ~data =
276	Logging.xb_op ~ty ~tid ~con data
277
278let write_answer_log ~ty ~tid ~con ~data =
279	Logging.xb_answer ~ty ~tid ~con data
280
281let write_response_log ~ty ~tid ~con ~response =
282	match response with
283	| Packet.Ack _   -> write_answer_log ~ty ~tid ~con ~data:""
284	| Packet.Reply x -> write_answer_log ~ty ~tid ~con ~data:x
285	| Packet.Error e -> write_answer_log ~ty:(Xenbus.Xb.Op.Error) ~tid ~con ~data:e
286
287let record_commit ~con ~tid ~before ~after =
288	let inc r = r := Int64.add 1L !r in
289	let finish_count = inc Transaction.counter; !Transaction.counter in
290	History.push {History.con=con; tid=tid; before=before; after=after; finish_count=finish_count}
291
292(* Replay a stored transaction against a fresh store, check the responses are
293   all equivalent: if so, commit the transaction. Otherwise send the abort to
294   the client. *)
295let transaction_replay c t doms cons =
296	match t.Transaction.ty with
297	| Transaction.No ->
298		error "attempted to replay a non-full transaction";
299		false
300	| Transaction.Full(id, _oldstore, cstore) ->
301		let tid = Connection.start_transaction c cstore in
302		let replay_t = Transaction.make ~internal:true tid cstore in
303		let con = sprintf "r(%d):%s" id (Connection.get_domstr c) in
304
305		let perform_exn ~wlog txn (request, response) =
306			if wlog then write_access_log ~ty:request.Packet.ty ~tid ~con ~data:request.Packet.data;
307			let fct = function_of_type_simple_op request.Packet.ty in
308			let response' = input_handle_error ~cons ~doms ~fct ~con:c ~t:txn ~req:request in
309			if wlog then write_response_log ~ty:request.Packet.ty ~tid ~con ~response:response';
310			if not(Packet.response_equal response response') then raise Transaction_again
311		in
312		finally
313		(fun () ->
314			try
315				Logging.start_transaction ~con ~tid;
316				List.iter (perform_exn ~wlog:true replay_t) (Transaction.get_operations t); (* May throw EAGAIN *)
317
318				Logging.end_transaction ~con ~tid;
319				Transaction.commit ~con replay_t
320			with
321			| Transaction_again -> (
322				Transaction.failed_commits := Int64.add !Transaction.failed_commits 1L;
323				let victim_domstr = Connection.get_domstr c in
324				debug "Apportioning blame for EAGAIN in txn %d, domain=%s" id victim_domstr;
325				let punish guilty_con =
326					debug "Blaming domain %s for conflict with domain %s txn %d"
327						(Connection.get_domstr guilty_con) victim_domstr id;
328					Connection.decr_conflict_credit doms guilty_con
329				in
330				let judge_and_sentence hist_rec = (
331					let can_apply_on store = (
332						let store = Store.copy store in
333						let trial_t = Transaction.make ~internal:true Transaction.none store in
334						try List.iter (perform_exn ~wlog:false trial_t) (Transaction.get_operations t);
335							true
336						with Transaction_again -> false
337					) in
338					if can_apply_on hist_rec.History.before
339					&& not (can_apply_on hist_rec.History.after)
340					then (punish hist_rec.History.con; true)
341					else false
342				) in
343				let guilty_cons = History.filter_connections ~ignore:c ~since:t.Transaction.start_count ~f:judge_and_sentence in
344				if Hashtbl.length guilty_cons = 0 then (
345					debug "Found no culprit for conflict in %s: must be self or not in history." con;
346					Transaction.failed_commits_no_culprit := Int64.add !Transaction.failed_commits_no_culprit 1L
347				);
348				false
349			)
350			| e ->
351				info "transaction_replay %d caught: %s" tid (Printexc.to_string e);
352				false
353			)
354		(fun () ->
355			ignore @@ Connection.end_transaction c tid None
356		)
357
358let do_watch con t _domains cons data =
359	let (node, token) =
360		match (split None '\000' data) with
361		| [node; token; ""]   -> node, token
362		| _                   -> raise Invalid_Cmd_Args
363		in
364	let watch = Connections.add_watch cons con node token in
365	Packet.Ack (fun () ->
366		(* xenstore.txt says this watch is fired immediately,
367		   implying even if path doesn't exist or is unreadable *)
368		Connection.fire_single_watch_unchecked watch)
369
370let do_unwatch con _t _domains cons data =
371	let (node, token) =
372		match (split None '\000' data) with
373		| [node; token; ""]   -> node, token
374		| _                   -> raise Invalid_Cmd_Args
375		in
376	ignore @@ Connections.del_watch cons con node token
377
378let do_transaction_start con t _domains _cons _data =
379	if Transaction.get_id t <> Transaction.none then
380		raise Transaction_nested;
381	let store = Transaction.get_store t in
382	string_of_int (Connection.start_transaction con store) ^ "\000"
383
384let do_transaction_end con t domains cons data =
385	let commit =
386		match (split None '\000' data) with
387		| "T" :: _ -> true
388		| "F" :: _ -> false
389		| x :: _   -> raise (Invalid_argument x)
390		| _        -> raise Invalid_Cmd_Args
391		in
392	let commit = commit && not (Transaction.is_read_only t) in
393	let success =
394		let commit = if commit then Some (fun con trans -> transaction_replay con trans domains cons) else None in
395		History.end_transaction t con (Transaction.get_id t) commit in
396	if not success then
397		raise Transaction_again;
398	if commit then begin
399		process_watch t cons;
400		match t.Transaction.ty with
401		| Transaction.No ->
402			() (* no need to record anything *)
403		| Transaction.Full(id, oldstore, cstore) ->
404			record_commit ~con ~tid:id ~before:oldstore ~after:cstore
405	end
406
407let do_introduce con t domains cons data =
408	if not (Connection.is_dom0 con)
409	then raise Define.Permission_denied;
410	let (domid, mfn, port) =
411		match (split None '\000' data) with
412		| domid :: mfn :: port :: _ ->
413			int_of_string domid, Nativeint.of_string mfn, int_of_string port
414		| _                         -> raise Invalid_Cmd_Args;
415		in
416	let dom =
417		if Domains.exist domains domid then
418			let edom = Domains.find domains domid in
419			if (Domain.get_mfn edom) = mfn && (Connections.find_domain cons domid) != con then begin
420				(* Use XS_INTRODUCE for recreating the xenbus event-channel. *)
421				edom.remote_port <- port;
422				Domain.bind_interdomain edom;
423			end;
424			edom
425		else try
426			let ndom = Domains.create domains domid mfn port in
427			Connections.add_domain cons ndom;
428			Connections.fire_spec_watches (Transaction.get_root t) cons Store.Path.introduce_domain;
429			ndom
430		with _ -> raise Invalid_Cmd_Args
431	in
432	if (Domain.get_remote_port dom) <> port || (Domain.get_mfn dom) <> mfn then
433		raise Domain_not_match
434
435let do_release con t domains cons data =
436	if not (Connection.is_dom0 con)
437	then raise Define.Permission_denied;
438	let domid =
439		match (split None '\000' data) with
440		| [domid;""] -> int_of_string domid
441		| _          -> raise Invalid_Cmd_Args
442		in
443	let fire_spec_watches = Domains.exist domains domid in
444	Domains.del domains domid;
445	Connections.del_domain cons domid;
446	Store.reset_permissions (Transaction.get_store t) domid;
447	if fire_spec_watches
448	then Connections.fire_spec_watches (Transaction.get_root t) cons Store.Path.release_domain
449	else raise Invalid_Cmd_Args
450
451let do_resume con _t domains _cons data =
452	if not (Connection.is_dom0 con)
453	then raise Define.Permission_denied;
454	let domid =
455		match (split None '\000' data) with
456		| domid :: _ -> int_of_string domid
457		| _          -> raise Invalid_Cmd_Args
458		in
459	if Domains.exist domains domid
460	then Domains.resume domains domid
461	else raise Invalid_Cmd_Args
462
463let function_of_type ty =
464	match ty with
465	| Xenbus.Xb.Op.Debug             -> reply_data_or_ack do_debug
466	| Xenbus.Xb.Op.Watch             -> reply_none do_watch
467	| Xenbus.Xb.Op.Unwatch           -> reply_ack do_unwatch
468	| Xenbus.Xb.Op.Transaction_start -> reply_data do_transaction_start
469	| Xenbus.Xb.Op.Transaction_end   -> reply_ack do_transaction_end
470	| Xenbus.Xb.Op.Introduce         -> reply_ack do_introduce
471	| Xenbus.Xb.Op.Release           -> reply_ack do_release
472	| Xenbus.Xb.Op.Isintroduced      -> reply_data do_isintroduced
473	| Xenbus.Xb.Op.Resume            -> reply_ack do_resume
474	| Xenbus.Xb.Op.Set_target        -> reply_ack do_set_target
475	| Xenbus.Xb.Op.Reset_watches     -> reply_ack do_reset_watches
476	| Xenbus.Xb.Op.Invalid           -> reply_ack do_error
477	| _                              -> function_of_type_simple_op ty
478
479(**
480 * Determines which individual (non-transactional) operations we want to retain.
481 * We only want to retain operations that have side-effects in the store since
482 * these can be the cause of transactions failing.
483 *)
484let retain_op_in_history ty =
485	match ty with
486	| Xenbus.Xb.Op.Write
487	| Xenbus.Xb.Op.Mkdir
488	| Xenbus.Xb.Op.Rm
489	| Xenbus.Xb.Op.Setperms          -> true
490	| Xenbus.Xb.Op.Debug
491	| Xenbus.Xb.Op.Directory
492	| Xenbus.Xb.Op.Read
493	| Xenbus.Xb.Op.Getperms
494	| Xenbus.Xb.Op.Watch
495	| Xenbus.Xb.Op.Unwatch
496	| Xenbus.Xb.Op.Transaction_start
497	| Xenbus.Xb.Op.Transaction_end
498	| Xenbus.Xb.Op.Introduce
499	| Xenbus.Xb.Op.Release
500	| Xenbus.Xb.Op.Getdomainpath
501	| Xenbus.Xb.Op.Watchevent
502	| Xenbus.Xb.Op.Error
503	| Xenbus.Xb.Op.Isintroduced
504	| Xenbus.Xb.Op.Resume
505	| Xenbus.Xb.Op.Set_target
506	| Xenbus.Xb.Op.Reset_watches
507	| Xenbus.Xb.Op.Invalid           -> false
508
509let maybe_ignore_transaction = function
510	| Xenbus.Xb.Op.Watch | Xenbus.Xb.Op.Unwatch -> fun tid ->
511		if tid <> Transaction.none then
512			debug "Ignoring transaction ID %d for watch/unwatch" tid;
513		Transaction.none
514	| _ -> fun x -> x
515
516
517let () = Printexc.record_backtrace true
518(**
519 * Nothrow guarantee.
520 *)
521let process_packet ~store ~cons ~doms ~con ~req =
522	let ty = req.Packet.ty in
523	let tid = maybe_ignore_transaction ty req.Packet.tid in
524	let rid = req.Packet.rid in
525	try
526		let fct = function_of_type ty in
527		let t =
528			if tid = Transaction.none then
529				Transaction.make tid store
530			else
531				Connection.get_transaction con tid
532			in
533
534		let execute () = input_handle_error ~cons ~doms ~fct ~con ~t ~req in
535
536		let response =
537			(* Note that transactions are recorded in history separately. *)
538			if tid = Transaction.none && retain_op_in_history ty then begin
539				let before = Store.copy store in
540				let response = execute () in
541				let after = Store.copy store in
542				record_commit ~con ~tid ~before ~after;
543				response
544			end else execute ()
545		in
546
547		let response = try
548			if tid <> Transaction.none then
549				(* Remember the request and response for this operation in case we need to replay the transaction *)
550				Transaction.add_operation ~perm:(Connection.get_perm con) t req response;
551			response
552		with Quota.Limit_reached ->
553			Packet.Error "EQUOTA"
554		in
555
556		(* Put the response on the wire *)
557		send_response ty con t rid response
558	with exn ->
559		let bt = Printexc.get_backtrace () in
560		error "process packet: %s. %s" (Printexc.to_string exn) bt;
561		Connection.send_error con tid rid "EIO"
562
563let do_input store cons doms con =
564	let newpacket =
565		try
566			Connection.do_input con
567		with Xenbus.Xb.Reconnect ->
568			info "%s requests a reconnect" (Connection.get_domstr con);
569			Connection.reconnect con;
570			info "%s reconnection complete" (Connection.get_domstr con);
571			false
572		| Failure exp ->
573			error "caught exception %s" exp;
574			error "got a bad client %s" (sprintf "%-8s" (Connection.get_domstr con));
575			Connection.mark_as_bad con;
576			false
577	in
578
579	if newpacket then (
580		let packet = Connection.pop_in con in
581		let tid, rid, ty, data = Xenbus.Xb.Packet.unpack packet in
582		let req = {Packet.tid=tid; Packet.rid=rid; Packet.ty=ty; Packet.data=data} in
583
584		(* As we don't log IO, do not call an unnecessary sanitize_data
585		   info "[%s] -> [%d] %s \"%s\""
586		         (Connection.get_domstr con) tid
587		         (Xenbus.Xb.Op.to_string ty) (sanitize_data data); *)
588		process_packet ~store ~cons ~doms ~con ~req;
589		write_access_log ~ty ~tid ~con:(Connection.get_domstr con) ~data;
590		Connection.incr_ops con;
591	)
592
593let do_output _store _cons _doms con =
594	if Connection.has_output con then (
595		if Connection.has_new_output con then (
596			let packet = Connection.peek_output con in
597			let tid, _rid, ty, data = Xenbus.Xb.Packet.unpack packet in
598			(* As we don't log IO, do not call an unnecessary sanitize_data
599			   info "[%s] <- %s \"%s\""
600			         (Connection.get_domstr con)
601			         (Xenbus.Xb.Op.to_string ty) (sanitize_data data);*)
602			write_answer_log ~ty ~tid ~con:(Connection.get_domstr con) ~data;
603		);
604		try
605			ignore (Connection.do_output con)
606		with Xenbus.Xb.Reconnect ->
607			info "%s requests a reconnect" (Connection.get_domstr con);
608			Connection.reconnect con;
609			info "%s reconnection complete" (Connection.get_domstr con)
610	)
611
612