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
17module Op = struct include Op end
18module Packet = struct include Packet end
19
20exception End_of_file
21exception Eagain
22exception Noent
23exception Invalid
24exception Reconnect
25
26let _ =
27  Callback.register_exception "Xb.Reconnect" Reconnect
28
29type backend_mmap =
30{
31	mmap: Xenmmap.mmap_interface;     (* mmaped interface = xs_ring *)
32	eventchn_notify: unit -> unit; (* function to notify through eventchn *)
33	mutable work_again: bool;
34}
35
36type backend_fd =
37{
38	fd: Unix.file_descr;
39}
40
41type backend = Fd of backend_fd | Xenmmap of backend_mmap
42
43type partial_buf = HaveHdr of Partial.pkt | NoHdr of int * bytes
44
45type t =
46{
47	backend: backend;
48	pkt_in: Packet.t Queue.t;
49	pkt_out: Packet.t Queue.t;
50	mutable partial_in: partial_buf;
51	mutable partial_out: string;
52}
53
54let init_partial_in () = NoHdr
55	(Partial.header_size (), Bytes.make (Partial.header_size()) '\000')
56
57let reconnect t = match t.backend with
58	| Fd _ ->
59		(* should never happen, so close the connection *)
60		raise End_of_file
61	| Xenmmap backend ->
62		Xs_ring.close backend.mmap;
63		backend.eventchn_notify ();
64		(* Clear our old connection state *)
65		Queue.clear t.pkt_in;
66		Queue.clear t.pkt_out;
67		t.partial_in <- init_partial_in ();
68		t.partial_out <- ""
69
70let queue con pkt = Queue.push pkt con.pkt_out
71
72let read_fd back _con b len =
73	let rd = Unix.read back.fd b 0 len in
74	if rd = 0 then
75		raise End_of_file;
76	rd
77
78let read_mmap back _con b len =
79	let s = Bytes.make len '\000' in
80	let rd = Xs_ring.read back.mmap s len in
81	Bytes.blit s 0 b 0 rd;
82	back.work_again <- (rd > 0);
83	if rd > 0 then
84		back.eventchn_notify ();
85	rd
86
87let read con b len =
88	match con.backend with
89	| Fd backfd     -> read_fd backfd con b len
90	| Xenmmap backmmap -> read_mmap backmmap con b len
91
92let write_fd back _con b len =
93	Unix.write_substring back.fd b 0 len
94
95let write_mmap back _con s len =
96	let ws = Xs_ring.write_substring back.mmap s len in
97	if ws > 0 then
98		back.eventchn_notify ();
99	ws
100
101let write con s len =
102	match con.backend with
103	| Fd backfd     -> write_fd backfd con s len
104	| Xenmmap backmmap -> write_mmap backmmap con s len
105
106(* NB: can throw Reconnect *)
107let output con =
108	(* get the output string from a string_of(packet) or partial_out *)
109	let s = if String.length con.partial_out > 0 then
110			con.partial_out
111		else if Queue.length con.pkt_out > 0 then
112			Packet.to_string (Queue.pop con.pkt_out)
113		else
114			"" in
115	(* send data from s, and save the unsent data to partial_out *)
116	if s <> "" then (
117		let len = String.length s in
118		let sz = write con s len in
119		let left = String.sub s sz (len - sz) in
120		con.partial_out <- left
121	);
122	(* after sending one packet, partial is empty *)
123	con.partial_out = ""
124
125(* NB: can throw Reconnect *)
126let input con =
127	let newpacket = ref false in
128	let to_read =
129		match con.partial_in with
130		| HaveHdr partial_pkt -> Partial.to_complete partial_pkt
131		| NoHdr   (i, _)    -> i in
132
133	(* try to get more data from input stream *)
134	let b = Bytes.make to_read '\000' in
135	let sz = if to_read > 0 then read con b to_read else 0 in
136
137	(
138	match con.partial_in with
139	| HaveHdr partial_pkt ->
140		(* we complete the data *)
141		if sz > 0 then
142			Partial.append partial_pkt (Bytes.to_string b) sz;
143		if Partial.to_complete partial_pkt = 0 then (
144			let pkt = Packet.of_partialpkt partial_pkt in
145			con.partial_in <- init_partial_in ();
146			Queue.push pkt con.pkt_in;
147			newpacket := true
148		)
149	| NoHdr (i, buf)      ->
150		(* we complete the partial header *)
151		if sz > 0 then
152			Bytes.blit b 0 buf (Partial.header_size () - i) sz;
153		con.partial_in <- if sz = i then
154			HaveHdr (Partial.of_string (Bytes.to_string buf)) else NoHdr (i - sz, buf)
155	);
156	!newpacket
157
158let newcon backend = {
159	backend = backend;
160	pkt_in = Queue.create ();
161	pkt_out = Queue.create ();
162	partial_in = init_partial_in ();
163	partial_out = "";
164	}
165
166let open_fd fd = newcon (Fd { fd = fd; })
167
168let open_mmap mmap notifyfct =
169	(* Advertise XENSTORE_SERVER_FEATURE_RECONNECTION *)
170	Xs_ring.set_server_features mmap (Xs_ring.Server_features.singleton Xs_ring.Server_feature.Reconnection);
171	newcon (Xenmmap {
172		mmap = mmap;
173		eventchn_notify = notifyfct;
174		work_again = false; })
175
176let close con =
177	match con.backend with
178	| Fd backend   -> Unix.close backend.fd
179	| Xenmmap backend -> Xenmmap.unmap backend.mmap
180
181let is_fd con =
182	match con.backend with
183	| Fd _   -> true
184	| Xenmmap _ -> false
185
186let is_mmap con = not (is_fd con)
187
188let output_len con = Queue.length con.pkt_out
189let has_new_output con = Queue.length con.pkt_out > 0
190let has_old_output con = String.length con.partial_out > 0
191
192let has_output con = has_new_output con || has_old_output con
193
194let peek_output con = Queue.peek con.pkt_out
195
196let input_len con = Queue.length con.pkt_in
197let has_in_packet con = Queue.length con.pkt_in > 0
198let get_in_packet con = Queue.pop con.pkt_in
199let has_more_input con =
200	match con.backend with
201	| Fd _         -> false
202	| Xenmmap backend -> backend.work_again
203
204let is_selectable con =
205	match con.backend with
206	| Fd _   -> true
207	| Xenmmap _ -> false
208
209let get_fd con =
210	match con.backend with
211	| Fd backend -> backend.fd
212	| Xenmmap _     -> raise (Failure "get_fd")
213