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 warn fmt = Logging.warn "process" fmt 19let info fmt = Logging.info "process" fmt 20let debug fmt = Logging.debug "process" fmt 21 22open Printf 23open Stdext 24 25exception Transaction_again 26exception Transaction_nested 27exception Domain_not_match 28exception Invalid_Cmd_Args 29 30(* This controls the do_debug fn in this module, not the debug logging-function. *) 31let allow_debug = ref false 32 33let c_int_of_string s = 34 let v = ref 0 in 35 let is_digit c = c >= '0' && c <= '9' in 36 let len = String.length s in 37 let i = ref 0 in 38 while !i < len && not (is_digit s.[!i]) do incr i done; 39 while !i < len && is_digit s.[!i] 40 do 41 let x = (Char.code s.[!i]) - (Char.code '0') in 42 v := !v * 10 + x; 43 incr i 44 done; 45 !v 46 47(* when we don't want a limit, apply a max limit of 8 arguments. 48 no arguments take more than 3 currently, which is pointless to split 49 more than needed. *) 50let split limit c s = 51 let limit = match limit with None -> 8 | Some x -> x in 52 String.split ~limit c s 53 54let split_one_path data con = 55 let args = split (Some 2) '\000' data in 56 match args with 57 | path :: "" :: [] -> Store.Path.create path (Connection.get_path con) 58 | _ -> raise Invalid_Cmd_Args 59 60let process_watch source t cons = 61 let oldroot = t.Transaction.oldroot in 62 let newroot = Store.get_root t.Transaction.store in 63 let ops = Transaction.get_paths t |> List.rev in 64 let do_op_watch op cons = 65 let recurse, oldroot, root = match (fst op) with 66 | Xenbus.Xb.Op.Write|Xenbus.Xb.Op.Mkdir -> false, None, newroot 67 | Xenbus.Xb.Op.Rm -> true, None, oldroot 68 | Xenbus.Xb.Op.Setperms -> false, Some oldroot, newroot 69 | _ -> raise (Failure "huh ?") in 70 Connections.fire_watches ?oldroot source root cons (snd op) recurse in 71 List.iter (fun op -> do_op_watch op cons) ops; 72 Connections.send_watchevents cons source 73 74let create_implicit_path t perm path = 75 let dirname = Store.Path.get_parent path in 76 if not (Transaction.path_exists t dirname) then ( 77 let rec check_path p = 78 match p with 79 | [] -> [] 80 | h :: l -> 81 if Transaction.path_exists t h then 82 check_path l 83 else 84 p in 85 let ret = check_path (List.tl (Store.Path.get_hierarchy dirname)) in 86 List.iter (fun s -> Transaction.mkdir ~with_watch:false t perm s) ret 87 ) 88 89module LiveUpdate = struct 90 type t = 91 { binary: string 92 ; cmdline: string list 93 ; deadline: float 94 ; force: bool 95 ; result: string list 96 ; pending: bool } 97 98 let state = ref 99 { binary= Sys.executable_name 100 ; cmdline= (Sys.argv |> Array.to_list |> List.tl) 101 ; deadline= 0. 102 ; force= false 103 ; result = [] 104 ; pending= false } 105 106 let debug = Printf.eprintf 107 108 let forced_args = ["--live"; "--restart"] 109 let args_of_t t = 110 let filtered = List.filter (fun x -> not @@ List.mem x forced_args) t.cmdline in 111 (t.binary, forced_args @ filtered) 112 113 let string_of_t t = 114 let executable, rest = args_of_t t in 115 Filename.quote_command executable rest 116 117 let launch_exn t = 118 let executable, rest = args_of_t t in 119 let args = Array.of_list (executable :: rest) in 120 info "Launching %s, args: %s" executable (String.concat " " rest); 121 Unix.execv args.(0) args 122 123 let validate_exn t = 124 (* --help must be last to check validity of earlier arguments *) 125 let t' = {t with cmdline= t.cmdline @ ["--help"]} in 126 let cmd = string_of_t t' in 127 debug "Executing %s" cmd ; 128 match Unix.fork () with 129 | 0 -> ( try launch_exn t' with _ -> exit 2 ) 130 | pid -> ( 131 match Unix.waitpid [] pid with 132 | _, Unix.WEXITED 0 -> 133 debug "Live update validated cmdline %s" cmd; 134 t 135 | _, Unix.WEXITED n -> 136 invalid_arg (Printf.sprintf "Command %s exited with code %d" cmd n) 137 | _, Unix.WSIGNALED n -> 138 invalid_arg (Printf.sprintf "Command %s killed by ocaml signal number %d" cmd n) 139 | _, Unix.WSTOPPED n -> 140 invalid_arg (Printf.sprintf "Command %s stopped by ocaml signal number %d" cmd n) 141 ) 142 143 let parse_live_update args = 144 try 145 (state := 146 match args with 147 | ["-f"; file] -> 148 validate_exn {!state with binary= file} 149 | ["-a"] -> 150 debug "Live update aborted" ; 151 {!state with pending= false; result = []} 152 | "-c" :: cmdline -> 153 validate_exn {!state with cmdline = !state.cmdline @ cmdline} 154 | "-s" :: _ -> 155 (match !state.pending, !state.result with 156 | true, _ -> !state (* no change to state, avoid resetting timeout *) 157 | false, _ :: _ -> !state (* we got a pending result to deliver *) 158 | false, [] -> 159 let timeout = ref 60 in 160 let force = ref false in 161 Arg.parse_argv ~current:(ref 0) (Array.of_list args) 162 [ ( "-t" 163 , Arg.Set_int timeout 164 , "timeout in seconds to wait for active transactions to finish" 165 ) 166 ; ( "-F" 167 , Arg.Set force 168 , "force live update to happen even with running transactions after timeout elapsed" 169 ) 170 ] 171 (fun x -> raise (Arg.Bad x)) 172 "live-update -s" ; 173 debug "Live update process queued" ; 174 {!state with deadline = Unix.gettimeofday () +. float !timeout 175 ; force= !force; pending= true}) 176 | _ -> 177 invalid_arg ("Unknown arguments: " ^ String.concat "," args)) ; 178 match !state.pending, !state.result with 179 | true, _ -> Some "BUSY" 180 | false, (_ :: _ as result) -> 181 (* xenstore-control has read the result, clear it *) 182 state := { !state with result = [] }; 183 Some (String.concat "\n" result) 184 | false, [] -> None 185 with 186 | Arg.Bad s | Arg.Help s | Invalid_argument s -> 187 Some s 188 | Unix.Unix_error (e, fn, args) -> 189 Some (Printf.sprintf "%s(%s): %s" fn args (Unix.error_message e)) 190 191 let should_run cons = 192 let t = !state in 193 if t.pending then begin 194 match Connections.prevents_quit cons with 195 | [] -> true 196 | _ when Unix.gettimeofday () < t.deadline -> false 197 | l -> 198 warn "timeout reached: have to wait, migrate or shutdown %d domains:" (List.length l); 199 let msgs = List.rev_map (fun con -> Printf.sprintf "%s: %d tx, out: %b, perm: %s" 200 (Connection.get_domstr con) 201 (Connection.number_of_transactions con) 202 (Connection.has_output con) 203 (Connection.get_perm con |> Perms.Connection.to_string) 204 ) l in 205 List.iter (warn "Live-update: %s") msgs; 206 if t.force then begin 207 warn "Live update forced, some domain connections may break!"; 208 true 209 end else begin 210 warn "Live update aborted (see above for domains preventing it)"; 211 state := { t with pending = false; result = msgs}; 212 false 213 end 214 end else false 215 216 let completed () = 217 state := { !state with result = ["OK"] } 218end 219 220(* packets *) 221let do_debug con t _domains cons data = 222 if not (Connection.is_dom0 con) && not !allow_debug 223 then None 224 else try match split None '\000' data with 225 | "live-update" :: params -> 226 let dropped_trailing_nul = params |> List.rev |> List.tl |> List.rev in 227 LiveUpdate.parse_live_update dropped_trailing_nul 228 | "print" :: msg :: _ -> 229 Logging.xb_op ~tid:0 ~ty:Xenbus.Xb.Op.Debug ~con:"=======>" msg; 230 None 231 | "quota" :: domid :: _ -> 232 let domid = Utils.int_of_string_exn domid in 233 let quota = (Store.get_quota t.Transaction.store) in 234 Some (Quota.to_string quota domid ^ "\000") 235 | "watches" :: _ -> 236 let watches = Connections.debug cons in 237 Some (watches ^ "\000") 238 | "compact" :: _ -> 239 Gc.compact (); 240 Some "Compacted" 241 | "trim" :: _ -> 242 History.trim (); 243 Some "trimmed" 244 | "txn" :: domid :: _ -> 245 let domid = Utils.int_of_string_exn domid in 246 let con = Connections.find_domain cons domid in 247 let b = Buffer.create 128 in 248 let () = con.transactions |> Hashtbl.iter @@ fun id tx -> 249 Printf.bprintf b "paths: %d, operations: %d, quota_reached: %b\n" 250 (List.length tx.Transaction.paths) 251 (List.length tx.Transaction.operations) 252 tx.Transaction.quota_reached 253 in 254 Some (Buffer.contents b) 255 | "xenbus" :: domid :: _ -> 256 let domid = Utils.int_of_string_exn domid in 257 let con = Connections.find_domain cons domid in 258 let s = Printf.sprintf "xenbus: %s; overflow queue length: %d, can_input: %b, has_more_input: %b, has_old_output: %b, has_new_output: %b, has_more_work: %b. pending: %s" 259 (Xenbus.Xb.debug con.xb) 260 (Connection.source_pending_watchevents con) 261 (Connection.can_input con) 262 (Connection.has_more_input con) 263 (Connection.has_old_output con) 264 (Connection.has_new_output con) 265 (Connection.has_more_work con) 266 (Connections.debug_watchevents cons con) 267 in 268 Some s 269 | "mfn" :: domid :: _ -> 270 let domid = Utils.int_of_string_exn domid in 271 let con = Connections.find_domain cons domid in 272 may (fun dom -> Printf.sprintf "%nd\000" (Domain.get_mfn dom)) (Connection.get_domain con) 273 | _ -> None 274 with _ -> None 275 276let do_directory con t _domains _cons data = 277 let path = split_one_path data con in 278 let entries = Transaction.ls t (Connection.get_perm con) path in 279 if List.length entries > 0 then 280 (Utils.join_by_null entries) ^ "\000" 281 else 282 "" 283 284let do_read con t _domains _cons data = 285 let path = split_one_path data con in 286 Transaction.read t (Connection.get_perm con) path 287 288let do_getperms con t _domains _cons data = 289 let path = split_one_path data con in 290 let perms = Transaction.getperms t (Connection.get_perm con) path in 291 Perms.Node.to_string perms ^ "\000" 292 293let do_getdomainpath _con _t _domains _cons data = 294 let domid = 295 match (split None '\000' data) with 296 | domid :: "" :: [] -> c_int_of_string domid 297 | _ -> raise Invalid_Cmd_Args 298 in 299 sprintf "/local/domain/%u\000" domid 300 301let do_write con t _domains _cons data = 302 let path, value = 303 match (split (Some 2) '\000' data) with 304 | path :: value :: [] -> Store.Path.create path (Connection.get_path con), value 305 | _ -> raise Invalid_Cmd_Args 306 in 307 create_implicit_path t (Connection.get_perm con) path; 308 Transaction.write t (Connection.get_perm con) path value 309 310let do_mkdir con t _domains _cons data = 311 let path = split_one_path data con in 312 create_implicit_path t (Connection.get_perm con) path; 313 try 314 Transaction.mkdir t (Connection.get_perm con) path 315 with 316 Define.Already_exist -> () 317 318let do_rm con t _domains _cons data = 319 let path = split_one_path data con in 320 try 321 Transaction.rm t (Connection.get_perm con) path 322 with 323 Define.Doesnt_exist -> () 324 325let do_setperms con t _domains _cons data = 326 let path, perms = 327 match (split (Some 2) '\000' data) with 328 | path :: perms :: _ -> 329 Store.Path.create path (Connection.get_path con), 330 (Perms.Node.of_string perms) 331 | _ -> raise Invalid_Cmd_Args 332 in 333 Transaction.setperms t (Connection.get_perm con) path perms 334 335let do_error _con _t _domains _cons _data = 336 raise Define.Unknown_operation 337 338let do_isintroduced con _t domains _cons data = 339 if not (Connection.is_dom0 con) 340 then raise Define.Permission_denied; 341 let domid = 342 match (split None '\000' data) with 343 | domid :: _ -> Utils.int_of_string_exn domid 344 | _ -> raise Invalid_Cmd_Args 345 in 346 if domid = Define.domid_self || Domains.exist domains domid then "T\000" else "F\000" 347 348(* only in xen >= 4.2 *) 349let do_reset_watches con _t _domains cons _data = 350 Connections.del_watches cons con; 351 Connection.del_transactions con 352 353(* only in >= xen3.3 *) 354let do_set_target con _t _domains cons data = 355 if not (Connection.is_dom0 con) 356 then raise Define.Permission_denied; 357 match split None '\000' data with 358 | [ domid; target_domid; "" ] -> Connections.set_target cons (c_int_of_string domid) (c_int_of_string target_domid) 359 | _ -> raise Invalid_Cmd_Args 360 361(*------------- Generic handling of ty ------------------*) 362let send_response ty con t rid response = 363 match response with 364 | Packet.Ack f -> 365 Connection.send_ack con (Transaction.get_id t) rid ty; 366 (* Now do any necessary follow-up actions *) 367 f () 368 | Packet.Reply ret -> 369 Connection.send_reply con (Transaction.get_id t) rid ty ret 370 | Packet.Error e -> 371 Connection.send_error con (Transaction.get_id t) rid e 372 373let reply_ack fct con t doms cons data = 374 fct con t doms cons data; 375 Packet.Ack (fun () -> 376 if Transaction.get_id t = Transaction.none then 377 process_watch con t cons 378 ) 379 380let reply_data fct con t doms cons data = 381 let ret = fct con t doms cons data in 382 Packet.Reply ret 383 384let reply_data_or_ack fct con t doms cons data = 385 match fct con t doms cons data with 386 | Some ret -> Packet.Reply ret 387 | None -> Packet.Ack (fun () -> ()) 388 389let reply_none fct con t doms cons data = 390 (* let the function reply *) 391 fct con t doms cons data 392 393(* Functions for 'simple' operations that cannot be part of a transaction *) 394let function_of_type_simple_op ty = 395 match ty with 396 | Xenbus.Xb.Op.Debug 397 | Xenbus.Xb.Op.Watch 398 | Xenbus.Xb.Op.Unwatch 399 | Xenbus.Xb.Op.Transaction_start 400 | Xenbus.Xb.Op.Transaction_end 401 | Xenbus.Xb.Op.Introduce 402 | Xenbus.Xb.Op.Release 403 | Xenbus.Xb.Op.Isintroduced 404 | Xenbus.Xb.Op.Resume 405 | Xenbus.Xb.Op.Set_target 406 | Xenbus.Xb.Op.Reset_watches 407 | Xenbus.Xb.Op.Invalid -> error "called function_of_type_simple_op on operation %s" (Xenbus.Xb.Op.to_string ty); 408 raise (Invalid_argument (Xenbus.Xb.Op.to_string ty)) 409 | Xenbus.Xb.Op.Directory -> reply_data do_directory 410 | Xenbus.Xb.Op.Read -> reply_data do_read 411 | Xenbus.Xb.Op.Getperms -> reply_data do_getperms 412 | Xenbus.Xb.Op.Getdomainpath -> reply_data do_getdomainpath 413 | Xenbus.Xb.Op.Write -> reply_ack do_write 414 | Xenbus.Xb.Op.Mkdir -> reply_ack do_mkdir 415 | Xenbus.Xb.Op.Rm -> reply_ack do_rm 416 | Xenbus.Xb.Op.Setperms -> reply_ack do_setperms 417 | _ -> reply_ack do_error 418 419let input_handle_error ~cons ~doms ~fct ~con ~t ~req = 420 let reply_error e = 421 Packet.Error e in 422 try 423 Transaction.check_quota_exn ~perm:(Connection.get_perm con) t; 424 fct con t doms cons req.Packet.data 425 with 426 | Define.Invalid_path -> reply_error "EINVAL" 427 | Define.Already_exist -> reply_error "EEXIST" 428 | Define.Doesnt_exist -> reply_error "ENOENT" 429 | Define.Lookup_Doesnt_exist _ -> reply_error "ENOENT" 430 | Define.Permission_denied -> reply_error "EACCES" 431 | Not_found -> reply_error "ENOENT" 432 | Invalid_Cmd_Args -> reply_error "EINVAL" 433 | Invalid_argument _ -> reply_error "EINVAL" 434 | Transaction_again -> reply_error "EAGAIN" 435 | Transaction_nested -> reply_error "EBUSY" 436 | Domain_not_match -> reply_error "EINVAL" 437 | Quota.Limit_reached -> reply_error "EQUOTA" 438 | Quota.Data_too_big -> reply_error "E2BIG" 439 | Quota.Transaction_opened -> reply_error "EQUOTA" 440 | Utils.ConversionFailed s -> reply_error "EINVAL" 441 | Define.Unknown_operation -> reply_error "ENOSYS" 442 443let write_access_log ~ty ~tid ~con ~data = 444 Logging.xb_op ~ty ~tid ~con data 445 446let write_answer_log ~ty ~tid ~con ~data = 447 Logging.xb_answer ~ty ~tid ~con data 448 449let write_response_log ~ty ~tid ~con ~response = 450 match response with 451 | Packet.Ack _ -> write_answer_log ~ty ~tid ~con ~data:"" 452 | Packet.Reply x -> write_answer_log ~ty ~tid ~con ~data:x 453 | Packet.Error e -> write_answer_log ~ty:(Xenbus.Xb.Op.Error) ~tid ~con ~data:e 454 455let record_commit ~con ~tid ~before ~after = 456 let inc r = r := Int64.add 1L !r in 457 let finish_count = inc Transaction.counter; !Transaction.counter in 458 History.push {History.con=con; tid=tid; before=before; after=after; finish_count=finish_count} 459 460(* Replay a stored transaction against a fresh store, check the responses are 461 all equivalent: if so, commit the transaction. Otherwise send the abort to 462 the client. *) 463let transaction_replay c t doms cons = 464 match t.Transaction.ty with 465 | Transaction.No -> 466 error "attempted to replay a non-full transaction"; 467 false 468 | Transaction.Full(id, _oldstore, cstore) -> 469 let tid = Connection.start_transaction c cstore in 470 let replay_t = Transaction.make ~internal:true tid cstore in 471 let con = sprintf "r(%d):%s" id (Connection.get_domstr c) in 472 473 let perform_exn ~wlog txn (request, response) = 474 if wlog then write_access_log ~ty:request.Packet.ty ~tid ~con ~data:request.Packet.data; 475 let fct = function_of_type_simple_op request.Packet.ty in 476 let response' = input_handle_error ~cons ~doms ~fct ~con:c ~t:txn ~req:request in 477 if wlog then write_response_log ~ty:request.Packet.ty ~tid ~con ~response:response'; 478 if not(Packet.response_equal response response') then raise Transaction_again 479 in 480 finally 481 (fun () -> 482 try 483 Logging.start_transaction ~con ~tid; 484 List.iter (perform_exn ~wlog:true replay_t) (Transaction.get_operations t); (* May throw EAGAIN *) 485 486 Logging.end_transaction ~con ~tid; 487 Transaction.commit ~con replay_t 488 with 489 | Transaction_again -> ( 490 Transaction.failed_commits := Int64.add !Transaction.failed_commits 1L; 491 let victim_domstr = Connection.get_domstr c in 492 debug "Apportioning blame for EAGAIN in txn %d, domain=%s" id victim_domstr; 493 let punish guilty_con = 494 debug "Blaming domain %s for conflict with domain %s txn %d" 495 (Connection.get_domstr guilty_con) victim_domstr id; 496 Connection.decr_conflict_credit doms guilty_con 497 in 498 let judge_and_sentence hist_rec = ( 499 let can_apply_on store = ( 500 let store = Store.copy store in 501 let trial_t = Transaction.make ~internal:true Transaction.none store in 502 try List.iter (perform_exn ~wlog:false trial_t) (Transaction.get_operations t); 503 true 504 with Transaction_again -> false 505 ) in 506 if can_apply_on hist_rec.History.before 507 && not (can_apply_on hist_rec.History.after) 508 then (punish hist_rec.History.con; true) 509 else false 510 ) in 511 let guilty_cons = History.filter_connections ~ignore:c ~since:t.Transaction.start_count ~f:judge_and_sentence in 512 if Hashtbl.length guilty_cons = 0 then ( 513 debug "Found no culprit for conflict in %s: must be self or not in history." con; 514 Transaction.failed_commits_no_culprit := Int64.add !Transaction.failed_commits_no_culprit 1L 515 ); 516 false 517 ) 518 | e -> 519 info "transaction_replay %d caught: %s" tid (Printexc.to_string e); 520 false 521 ) 522 (fun () -> 523 ignore @@ Connection.end_transaction c tid None 524 ) 525 526let do_watch con _t _domains cons data = 527 let (node, token) = 528 match (split None '\000' data) with 529 | [node; token; ""] -> node, token 530 | _ -> raise Invalid_Cmd_Args 531 in 532 let watch = Connections.add_watch cons con node token in 533 Packet.Ack (fun () -> 534 (* xenstore.txt says this watch is fired immediately, 535 implying even if path doesn't exist or is unreadable *) 536 Connection.fire_single_watch_unchecked con watch) 537 538let do_unwatch con _t _domains cons data = 539 let (node, token) = 540 match (split None '\000' data) with 541 | [node; token; ""] -> node, token 542 | _ -> raise Invalid_Cmd_Args 543 in 544 ignore @@ Connections.del_watch cons con node token 545 546let do_transaction_start con t _domains _cons _data = 547 if Transaction.get_id t <> Transaction.none then 548 raise Transaction_nested; 549 let store = Transaction.get_store t in 550 string_of_int (Connection.start_transaction con store) ^ "\000" 551 552let do_transaction_end con t domains cons data = 553 let commit = 554 match (split None '\000' data) with 555 | "T" :: _ -> true 556 | "F" :: _ -> false 557 | x :: _ -> raise (Invalid_argument x) 558 | _ -> raise Invalid_Cmd_Args 559 in 560 let commit = commit && not (Transaction.is_read_only t) in 561 let success = 562 let commit = if commit then Some (fun con trans -> transaction_replay con trans domains cons) else None in 563 History.end_transaction t con (Transaction.get_id t) commit in 564 if not success then 565 raise Transaction_again; 566 if commit then begin 567 process_watch con t cons; 568 match t.Transaction.ty with 569 | Transaction.No -> 570 () (* no need to record anything *) 571 | Transaction.Full(id, oldstore, cstore) -> 572 record_commit ~con ~tid:id ~before:oldstore ~after:cstore 573 end 574 575let do_introduce con t domains cons data = 576 if not (Connection.is_dom0 con) 577 then raise Define.Permission_denied; 578 let (domid, mfn, remote_port) = 579 match (split None '\000' data) with 580 | domid :: mfn :: remote_port :: _ -> 581 Utils.int_of_string_exn domid, Nativeint.of_string mfn, Utils.int_of_string_exn remote_port 582 | _ -> raise Invalid_Cmd_Args; 583 in 584 let dom = 585 if Domains.exist domains domid then 586 let edom = Domains.find domains domid in 587 if (Domain.get_mfn edom) = mfn && (Connections.find_domain cons domid) != con then begin 588 (* Use XS_INTRODUCE for recreating the xenbus event-channel. *) 589 Domain.rebind_evtchn edom remote_port; 590 end; 591 edom 592 else try 593 let ndom = Domains.create ~remote_port domains domid mfn in 594 Connections.add_domain cons ndom; 595 Connections.fire_spec_watches (Transaction.get_root t) cons Store.Path.introduce_domain; 596 ndom 597 with _ -> raise Invalid_Cmd_Args 598 in 599 if (Domain.get_remote_port dom) <> remote_port || (Domain.get_mfn dom) <> mfn then 600 raise Domain_not_match 601 602let do_release con t domains cons data = 603 if not (Connection.is_dom0 con) 604 then raise Define.Permission_denied; 605 let domid = 606 match (split None '\000' data) with 607 | [domid;""] -> Utils.int_of_string_exn domid 608 | _ -> raise Invalid_Cmd_Args 609 in 610 let fire_spec_watches = Domains.exist domains domid in 611 Domains.del domains domid; 612 Connections.del_domain cons domid; 613 Store.reset_permissions (Transaction.get_store t) domid; 614 if fire_spec_watches 615 then Connections.fire_spec_watches (Transaction.get_root t) cons Store.Path.release_domain 616 else raise Invalid_Cmd_Args 617 618let do_resume con _t domains _cons data = 619 if not (Connection.is_dom0 con) 620 then raise Define.Permission_denied; 621 let domid = 622 match (split None '\000' data) with 623 | domid :: _ -> Utils.int_of_string_exn domid 624 | _ -> raise Invalid_Cmd_Args 625 in 626 if Domains.exist domains domid 627 then Domains.resume domains domid 628 else raise Invalid_Cmd_Args 629 630let function_of_type ty = 631 match ty with 632 | Xenbus.Xb.Op.Debug -> reply_data_or_ack do_debug 633 | Xenbus.Xb.Op.Watch -> reply_none do_watch 634 | Xenbus.Xb.Op.Unwatch -> reply_ack do_unwatch 635 | Xenbus.Xb.Op.Transaction_start -> reply_data do_transaction_start 636 | Xenbus.Xb.Op.Transaction_end -> reply_ack do_transaction_end 637 | Xenbus.Xb.Op.Introduce -> reply_ack do_introduce 638 | Xenbus.Xb.Op.Release -> reply_ack do_release 639 | Xenbus.Xb.Op.Isintroduced -> reply_data do_isintroduced 640 | Xenbus.Xb.Op.Resume -> reply_ack do_resume 641 | Xenbus.Xb.Op.Set_target -> reply_ack do_set_target 642 | Xenbus.Xb.Op.Reset_watches -> reply_ack do_reset_watches 643 | Xenbus.Xb.Op.Invalid -> reply_ack do_error 644 | _ -> function_of_type_simple_op ty 645 646(** 647 * Determines which individual (non-transactional) operations we want to retain. 648 * We only want to retain operations that have side-effects in the store since 649 * these can be the cause of transactions failing. 650*) 651let retain_op_in_history ty = 652 match ty with 653 | Xenbus.Xb.Op.Write 654 | Xenbus.Xb.Op.Mkdir 655 | Xenbus.Xb.Op.Rm 656 | Xenbus.Xb.Op.Setperms -> true 657 | Xenbus.Xb.Op.Debug 658 | Xenbus.Xb.Op.Directory 659 | Xenbus.Xb.Op.Read 660 | Xenbus.Xb.Op.Getperms 661 | Xenbus.Xb.Op.Watch 662 | Xenbus.Xb.Op.Unwatch 663 | Xenbus.Xb.Op.Transaction_start 664 | Xenbus.Xb.Op.Transaction_end 665 | Xenbus.Xb.Op.Introduce 666 | Xenbus.Xb.Op.Release 667 | Xenbus.Xb.Op.Getdomainpath 668 | Xenbus.Xb.Op.Watchevent 669 | Xenbus.Xb.Op.Error 670 | Xenbus.Xb.Op.Isintroduced 671 | Xenbus.Xb.Op.Resume 672 | Xenbus.Xb.Op.Set_target 673 | Xenbus.Xb.Op.Reset_watches 674 | Xenbus.Xb.Op.Invalid -> false 675 676let maybe_ignore_transaction = function 677 | Xenbus.Xb.Op.Watch | Xenbus.Xb.Op.Unwatch -> fun tid -> 678 if tid <> Transaction.none then 679 debug "Ignoring transaction ID %d for watch/unwatch" tid; 680 Transaction.none 681 | _ -> fun x -> x 682 683 684let () = Printexc.record_backtrace true 685 686(** 687 * Nothrow guarantee. 688*) 689let process_packet ~store ~cons ~doms ~con ~req = 690 let ty = req.Packet.ty in 691 let tid = maybe_ignore_transaction ty req.Packet.tid in 692 let rid = req.Packet.rid in 693 try 694 let fct = function_of_type ty in 695 let t = 696 if tid = Transaction.none then 697 Transaction.make tid store 698 else 699 Connection.get_transaction con tid 700 in 701 702 let execute () = input_handle_error ~cons ~doms ~fct ~con ~t ~req in 703 704 let response = 705 (* Note that transactions are recorded in history separately. *) 706 if tid = Transaction.none && retain_op_in_history ty then begin 707 let before = Store.copy store in 708 let response = execute () in 709 let after = Store.copy store in 710 record_commit ~con ~tid ~before ~after; 711 response 712 end else execute () 713 in 714 715 let response = try 716 Transaction.check_quota_exn ~perm:(Connection.get_perm con) t; 717 if tid <> Transaction.none then 718 (* Remember the request and response for this operation in case we need to replay the transaction *) 719 Transaction.add_operation t req response; 720 response 721 with Quota.Limit_reached -> 722 Packet.Error "EQUOTA" 723 in 724 725 (* Put the response on the wire *) 726 send_response ty con t rid response 727 with exn -> 728 let bt = Printexc.get_backtrace () in 729 error "process packet: %s. %s" (Printexc.to_string exn) bt; 730 Connection.send_error con tid rid "EIO" 731 732let do_input store cons doms con = 733 let newpacket = 734 try 735 if Connection.can_input con then Connection.do_input con 736 else None 737 with Xenbus.Xb.Reconnect -> 738 info "%s requests a reconnect" (Connection.get_domstr con); 739 History.reconnect con; 740 info "%s reconnection complete" (Connection.get_domstr con); 741 None 742 | Invalid_argument exp | Failure exp -> 743 error "caught exception %s" exp; 744 error "got a bad client %s" (sprintf "%-8s" (Connection.get_domstr con)); 745 Connection.mark_as_bad con; 746 None 747 in 748 749 match newpacket with 750 | None -> () 751 | Some packet -> 752 let tid, rid, ty, data = Xenbus.Xb.Packet.unpack packet in 753 let req = {Packet.tid=tid; Packet.rid=rid; Packet.ty=ty; Packet.data=data} in 754 755 (* As we don't log IO, do not call an unnecessary sanitize_data 756 info "[%s] -> [%d] %s \"%s\"" 757 (Connection.get_domstr con) tid 758 (Xenbus.Xb.Op.to_string ty) (sanitize_data data); *) 759 process_packet ~store ~cons ~doms ~con ~req; 760 write_access_log ~ty ~tid ~con:(Connection.get_domstr con) ~data; 761 Connection.incr_ops con 762 763let do_output _store _cons _doms con = 764 Connection.source_flush_watchevents con; 765 if Connection.has_output con then ( 766 if Connection.has_new_output con then ( 767 let packet = Connection.peek_output con in 768 let tid, _rid, ty, data = Xenbus.Xb.Packet.unpack packet in 769 (* As we don't log IO, do not call an unnecessary sanitize_data 770 info "[%s] <- %s \"%s\"" 771 (Connection.get_domstr con) 772 (Xenbus.Xb.Op.to_string ty) (sanitize_data data);*) 773 write_answer_log ~ty ~tid ~con:(Connection.get_domstr con) ~data; 774 ); 775 try 776 ignore (Connection.do_output con) 777 with Xenbus.Xb.Reconnect -> 778 info "%s requests a reconnect" (Connection.get_domstr con); 779 History.reconnect con; 780 info "%s reconnection complete" (Connection.get_domstr con) 781 ) 782 783