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