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 17exception End_of_file 18 19open Stdext 20 21let xenstore_payload_max = 4096 (* xen/include/public/io/xs_wire.h *) 22 23type watch = { 24 con: t; 25 token: string; 26 path: string; 27 base: string; 28 is_relative: bool; 29} 30 31and t = { 32 xb: Xenbus.Xb.t; 33 dom: Domain.t option; 34 transactions: (int, Transaction.t) Hashtbl.t; 35 mutable next_tid: int; 36 watches: (string, watch list) Hashtbl.t; 37 mutable nb_watches: int; 38 anonid: int; 39 mutable stat_nb_ops: int; 40 mutable perm: Perms.Connection.t; 41} 42 43let mark_as_bad con = 44 match con.dom with 45 |None -> () 46 | Some domain -> Domain.mark_as_bad domain 47 48let initial_next_tid = 1 49 50let reconnect con = 51 Xenbus.Xb.reconnect con.xb; 52 (* dom is the same *) 53 Hashtbl.clear con.transactions; 54 con.next_tid <- initial_next_tid; 55 Hashtbl.clear con.watches; 56 (* anonid is the same *) 57 con.nb_watches <- 0; 58 con.stat_nb_ops <- 0; 59 (* perm is the same *) 60 () 61 62let get_path con = 63Printf.sprintf "/local/domain/%i/" (match con.dom with None -> 0 | Some d -> Domain.get_id d) 64 65let watch_create ~con ~path ~token = { 66 con = con; 67 token = token; 68 path = path; 69 base = get_path con; 70 is_relative = path.[0] <> '/' && path.[0] <> '@' 71} 72 73let get_con w = w.con 74 75let number_of_transactions con = 76 Hashtbl.length con.transactions 77 78let get_domain con = con.dom 79 80let anon_id_next = ref 1 81 82let get_domstr con = 83 match con.dom with 84 | None -> "A" ^ (string_of_int con.anonid) 85 | Some dom -> "D" ^ (string_of_int (Domain.get_id dom)) 86 87let make_perm dom = 88 let domid = 89 match dom with 90 | None -> 0 91 | Some d -> Domain.get_id d 92 in 93 Perms.Connection.create ~perms:[Perms.READ; Perms.WRITE] domid 94 95let create xbcon dom = 96 let id = 97 match dom with 98 | None -> let old = !anon_id_next in incr anon_id_next; old 99 | Some _ -> 0 100 in 101 let con = 102 { 103 xb = xbcon; 104 dom = dom; 105 transactions = Hashtbl.create 5; 106 next_tid = initial_next_tid; 107 watches = Hashtbl.create 8; 108 nb_watches = 0; 109 anonid = id; 110 stat_nb_ops = 0; 111 perm = make_perm dom; 112 } 113 in 114 Logging.new_connection ~tid:Transaction.none ~con:(get_domstr con); 115 con 116 117let get_fd con = Xenbus.Xb.get_fd con.xb 118let close con = 119 Logging.end_connection ~tid:Transaction.none ~con:(get_domstr con); 120 Xenbus.Xb.close con.xb 121 122let get_perm con = 123 con.perm 124 125let set_target con target_domid = 126 con.perm <- Perms.Connection.set_target (get_perm con) ~perms:[Perms.READ; Perms.WRITE] target_domid 127 128let is_backend_mmap con = match con.xb.Xenbus.Xb.backend with 129 | Xenbus.Xb.Xenmmap _ -> true 130 | _ -> false 131 132let send_reply con tid rid ty data = 133 if (String.length data) > xenstore_payload_max && (is_backend_mmap con) then 134 Xenbus.Xb.queue con.xb (Xenbus.Xb.Packet.create tid rid Xenbus.Xb.Op.Error "E2BIG\000") 135 else 136 Xenbus.Xb.queue con.xb (Xenbus.Xb.Packet.create tid rid ty data) 137 138let send_error con tid rid err = send_reply con tid rid Xenbus.Xb.Op.Error (err ^ "\000") 139let send_ack con tid rid ty = send_reply con tid rid ty "OK\000" 140 141let get_watch_path con path = 142 if path.[0] = '@' || path.[0] = '/' then 143 path 144 else 145 let rpath = get_path con in 146 rpath ^ path 147 148let get_watches (con: t) path = 149 if Hashtbl.mem con.watches path 150 then Hashtbl.find con.watches path 151 else [] 152 153let get_children_watches con path = 154 let path = path ^ "/" in 155 List.concat (Hashtbl.fold (fun p w l -> 156 if String.startswith path p then w :: l else l) con.watches []) 157 158let is_dom0 con = 159 Perms.Connection.is_dom0 (get_perm con) 160 161let add_watch con path token = 162 if !Quota.activate && !Define.maxwatch > 0 && 163 not (is_dom0 con) && con.nb_watches > !Define.maxwatch then 164 raise Quota.Limit_reached; 165 let apath = get_watch_path con path in 166 let l = get_watches con apath in 167 if List.exists (fun w -> w.token = token) l then 168 raise Define.Already_exist; 169 let watch = watch_create ~con ~token ~path in 170 Hashtbl.replace con.watches apath (watch :: l); 171 con.nb_watches <- con.nb_watches + 1; 172 apath, watch 173 174let del_watch con path token = 175 let apath = get_watch_path con path in 176 let ws = Hashtbl.find con.watches apath in 177 let w = List.find (fun w -> w.token = token) ws in 178 let filtered = Utils.list_remove w ws in 179 if List.length filtered > 0 then 180 Hashtbl.replace con.watches apath filtered 181 else 182 Hashtbl.remove con.watches apath; 183 con.nb_watches <- con.nb_watches - 1; 184 apath, w 185 186let del_watches con = 187 Hashtbl.clear con.watches; 188 con.nb_watches <- 0 189 190let del_transactions con = 191 Hashtbl.clear con.transactions 192 193let list_watches con = 194 let ll = Hashtbl.fold 195 (fun _ watches acc -> List.map (fun watch -> watch.path, watch.token) watches :: acc) 196 con.watches [] in 197 List.concat ll 198 199let fire_single_watch watch = 200 let data = Utils.join_by_null [watch.path; watch.token; ""] in 201 send_reply watch.con Transaction.none 0 Xenbus.Xb.Op.Watchevent data 202 203let fire_watch watch path = 204 let new_path = 205 if watch.is_relative && path.[0] = '/' 206 then begin 207 let n = String.length watch.base 208 and m = String.length path in 209 String.sub path n (m - n) 210 end else 211 path 212 in 213 let data = Utils.join_by_null [ new_path; watch.token; "" ] in 214 send_reply watch.con Transaction.none 0 Xenbus.Xb.Op.Watchevent data 215 216(* Search for a valid unused transaction id. *) 217let rec valid_transaction_id con proposed_id = 218 (* 219 * Clip proposed_id to the range [1, 0x3ffffffe] 220 * 221 * The chosen id must not trucate when written into the uint32_t tx_id 222 * field, and needs to fit within the positive range of a 31 bit ocaml 223 * integer to function when compiled as 32bit. 224 * 225 * Oxenstored therefore supports only 1 billion open transactions. 226 *) 227 let id = if proposed_id <= 0 || proposed_id >= 0x3fffffff then 1 else proposed_id in 228 229 if Hashtbl.mem con.transactions id then ( 230 (* Outstanding transaction with this id. Try the next. *) 231 valid_transaction_id con (id + 1) 232 ) else 233 id 234 235let start_transaction con store = 236 if !Define.maxtransaction > 0 && not (is_dom0 con) 237 && Hashtbl.length con.transactions > !Define.maxtransaction then 238 raise Quota.Transaction_opened; 239 let id = valid_transaction_id con con.next_tid in 240 con.next_tid <- id + 1; 241 let ntrans = Transaction.make id store in 242 Hashtbl.add con.transactions id ntrans; 243 Logging.start_transaction ~tid:id ~con:(get_domstr con); 244 id 245 246let end_transaction con tid commit = 247 let trans = Hashtbl.find con.transactions tid in 248 Hashtbl.remove con.transactions tid; 249 Logging.end_transaction ~tid ~con:(get_domstr con); 250 match commit with 251 | None -> true 252 | Some transaction_replay_f -> 253 Transaction.commit ~con:(get_domstr con) trans || transaction_replay_f con trans 254 255let get_transaction con tid = 256 Hashtbl.find con.transactions tid 257 258let do_input con = Xenbus.Xb.input con.xb 259let has_input con = Xenbus.Xb.has_in_packet con.xb 260let pop_in con = Xenbus.Xb.get_in_packet con.xb 261let has_more_input con = Xenbus.Xb.has_more_input con.xb 262 263let has_output con = Xenbus.Xb.has_output con.xb 264let has_old_output con = Xenbus.Xb.has_old_output con.xb 265let has_new_output con = Xenbus.Xb.has_new_output con.xb 266let peek_output con = Xenbus.Xb.peek_output con.xb 267let do_output con = Xenbus.Xb.output con.xb 268 269let has_more_work con = 270 has_more_input con || not (has_old_output con) && has_new_output con 271 272let incr_ops con = con.stat_nb_ops <- con.stat_nb_ops + 1 273 274let mark_symbols con = 275 Hashtbl.iter (fun _ t -> Store.mark_symbols (Transaction.get_store t)) con.transactions 276 277let stats con = 278 Hashtbl.length con.watches, con.stat_nb_ops 279 280let dump con chan = 281 match con.dom with 282 | Some dom -> 283 let domid = Domain.get_id dom in 284 (* dump domain *) 285 Domain.dump dom chan; 286 (* dump watches *) 287 List.iter (fun (path, token) -> 288 Printf.fprintf chan "watch,%d,%s,%s\n" domid (Utils.hexify path) (Utils.hexify token) 289 ) (list_watches con); 290 | None -> () 291 292let debug con = 293 let domid = get_domstr con in 294 let watches = List.map (fun (path, token) -> Printf.sprintf "watch %s: %s %s\n" domid path token) (list_watches con) in 295 String.concat "" watches 296 297let decr_conflict_credit doms con = 298 match con.dom with 299 | None -> () (* It's a socket connection. We don't know which domain we're in, so treat it as if it's free to conflict *) 300 | Some dom -> Domains.decr_conflict_credit doms dom 301