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