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