File: tapctl.ml

package info (click to toggle)
xen-api-libs 0.5.2-3
  • links: PTS, VCS
  • area: main
  • in suites: wheezy
  • size: 1,940 kB
  • sloc: ml: 13,925; sh: 2,930; ansic: 1,699; makefile: 1,240; python: 83
file content (361 lines) | stat: -rw-r--r-- 11,200 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
open Stringext
open Listext
open Threadext
open Forkhelpers

type tapdev = {
	minor : int;
	tapdisk_pid : int;
} with rpc

type t = tapdev * string * (string * string) option 


type context = {
	host_local_dir: string;
	dummy: bool;
}

let create () = { host_local_dir = ""; dummy = false }
let create_dummy dir = 
	{host_local_dir=dir; dummy=true }

let get_devnode_dir ctx =
	let d = Printf.sprintf "%s/dev/xen/blktap-2" ctx.host_local_dir in
	Unixext.mkdir_rec d 0o755;
	d
let get_blktapstem ctx = Printf.sprintf "%s/dev/xen/blktap-2/blktap" ctx.host_local_dir
let get_tapdevstem ctx = Printf.sprintf "%s/dev/xen/blktap-2/tapdev" ctx.host_local_dir

type driver = | Vhd | Aio

let string_of_driver = function
| Vhd -> "vhd"
| Aio -> "aio"

(* DUMMY MODE FUNCTIONS *)

let get_minor tapdev = tapdev.minor
let get_tapdisk_pid tapdev = tapdev.tapdisk_pid

module Dummy = struct 
	type dummy_tap = {
		d_minor : int option;
		d_pid : int option;
		d_state : string option;
		d_args : string option;
	} and dummy_tap_list = dummy_tap list with rpc
			
	let d_lock = Mutex.create ()
		
	let get_dummy_tapdisk_list_filename ctx =
		let file = Printf.sprintf "%s/dev/tapdisks" ctx.host_local_dir in
		Unixext.mkdir_rec (Filename.dirname file) 0o777;
		file
			
	let get_dummy_tapdisk_list ctx =
		let filename = get_dummy_tapdisk_list_filename ctx in
		try
			dummy_tap_list_of_rpc (Jsonrpc.of_string (Unixext.string_of_file filename))
		with _ -> []
			
	let write_dummy_tapdisk_list ctx list =
		let filename = get_dummy_tapdisk_list_filename ctx in
		let str = Jsonrpc.to_string (rpc_of_dummy_tap_list list) in
		Unixext.write_string_to_file filename str
			
	let find_next_unused_number list =
		if List.length list = 0 then 0 else
			let list_plus_one = List.map ((+) 1) list in
			let diff = List.set_difference list_plus_one list in
			List.hd diff
				
	let find_next_unused_minor list =
		let minors = List.filter_map (fun t -> t.d_minor) list in
		find_next_unused_number minors
			
	let find_next_unused_pid list =
		let pids = List.filter_map (fun t -> t.d_pid) list in
		find_next_unused_number pids
			
	let get_entry_from_pid pid list =
		try Some (List.find (fun entry -> entry.d_pid = Some pid) list) with _ -> None
			
	let get_entry_from_minor minor list =
		try Some (List.find (fun entry -> entry.d_minor = Some minor) list) with _ -> None
			
	let allocate ctx =
		Mutex.execute d_lock (fun () -> 
			let list = get_dummy_tapdisk_list ctx in
			let minor = find_next_unused_minor list in
			let entry = {
				d_minor = Some minor;
				d_pid = None;
				d_state = None;
				d_args = None;
			} in
			let stem = get_tapdevstem ctx in
			let dummy_device = Printf.sprintf "%s%d" stem minor in
			Unixext.unlink_safe dummy_device;
			Unixext.touch_file dummy_device;
			write_dummy_tapdisk_list ctx (entry::list);
			minor
		)
			
	let spawn ctx =
		Mutex.execute d_lock (fun () -> 
			let list = get_dummy_tapdisk_list ctx in
			let pid = find_next_unused_pid list in
			let entry = {
				d_minor = None;
				d_pid = Some pid;
				d_state = None;
				d_args = None;
			} in
			write_dummy_tapdisk_list ctx (entry::list);
			pid
		)
			
	let attach ctx pid minor =
		Mutex.execute d_lock (fun () -> 
			let list = get_dummy_tapdisk_list ctx in
			begin (* sanity check *)
				match (get_entry_from_pid pid list, get_entry_from_minor minor list) with
					| Some e1, Some e2 ->
						if e1.d_minor <> None then failwith "pid already attached!";
						if e2.d_pid <> None then failwith "minor already in use!";
					| None, Some _ -> 
						failwith "pid nonexistant"
					| Some _, None ->
						failwith "minor nonexistant"
					| None, None -> 
						failwith "neither pid nor minor exist!"
			end;
			let new_entry = {
				d_minor = Some minor;
				d_pid = Some pid;
				d_state = Some "0";
				d_args = None;
			} in
			let list = List.filter (fun e -> e.d_pid <> Some pid && e.d_minor <> Some minor) list in
			write_dummy_tapdisk_list ctx (new_entry::list);
			{tapdisk_pid=pid; minor=minor})
		
	let _open ctx t leaf_path driver =
		let args = Printf.sprintf "%s:%s" (string_of_driver driver) leaf_path in
		Mutex.execute d_lock (fun () -> 
			let list = get_dummy_tapdisk_list ctx in
			let list = List.map (fun e -> 
				if e.d_pid = Some t.tapdisk_pid && e.d_minor = Some t.minor 
				then { e with 
					d_state = Some "0";
					d_args = Some args }
				else e) list in
			write_dummy_tapdisk_list ctx list)

	let close ctx t =
		Mutex.execute d_lock (fun () -> 
			let list = get_dummy_tapdisk_list ctx in
			let list = List.map (fun e -> 
				if e.d_pid = Some t.tapdisk_pid && e.d_minor = Some t.minor 
				then { e with 
					d_state = Some "0x2";
					d_args = None }
				else e) list in
			write_dummy_tapdisk_list ctx list)

	let pause ctx t =
		Mutex.execute d_lock (fun () -> 
			let list = get_dummy_tapdisk_list ctx in
			let list = List.map (fun e -> 
				if e.d_pid = Some t.tapdisk_pid && e.d_minor = Some t.minor 
				then { e with d_state = Some "0x2a" }
				else e) list in
			write_dummy_tapdisk_list ctx list)

	let unpause ctx t leaf_path driver =
		let args = Printf.sprintf "%s:%s" (string_of_driver driver) leaf_path in
		Mutex.execute d_lock (fun () -> 
			let list = get_dummy_tapdisk_list ctx in
			let list = List.map (fun e -> 
				if e.d_pid = Some t.tapdisk_pid && e.d_minor = Some t.minor 
				then { e with 
					d_state = Some "0";
					d_args = Some args }
				else e) list in
			write_dummy_tapdisk_list ctx list)

	let detach ctx t = 
		Mutex.execute d_lock (fun () -> 
			let list = get_dummy_tapdisk_list ctx in
			let (a,b) = get_entry_from_pid t.tapdisk_pid list, get_entry_from_minor t.minor list in
			if a<>None && a <> b then failwith "Not attached";
			let list = List.filter (fun entry -> entry.d_pid <> Some t.tapdisk_pid) list in
			let list = { d_minor = Some t.minor;
			             d_pid = None;
						 d_state = None;
						 d_args = None; }::list in
			write_dummy_tapdisk_list ctx list)

	let free ctx minor =
		Mutex.execute d_lock (fun () ->
			let list = get_dummy_tapdisk_list ctx in
			let entry = get_entry_from_minor minor list in
			begin (* sanity check *)
				match entry with
					| Some e -> if e.d_pid <> None then failwith "Can't free an attached minor"
					| None -> failwith "Unknown minor"
			end;
			let list = List.filter (fun e -> e.d_minor <> Some minor) list in
			write_dummy_tapdisk_list ctx list)

	let list ?t ctx =
		Mutex.execute d_lock (fun () -> 
			let list = get_dummy_tapdisk_list ctx in
			List.filter_map (fun e -> 
				let args = 
					match Opt.map (String.split ':') e.d_args with
						| Some (ty::arguments) ->
							Some (ty,String.concat ":" arguments)
						| _ -> None
				in
				match (e.d_minor, e.d_pid, e.d_state, t) with
					| Some m, Some p, Some s, None ->
						Some ({tapdisk_pid=p; minor=m},s,args) 
					| Some m, Some p, Some s, Some t ->
						if t.tapdisk_pid = p && t.minor=m then 
							Some ({tapdisk_pid=p; minor=m},s,args) 
						else 
							None
					| _ -> None) list)
end
 		

(* END OF DUMMY STUFF *)

let invoke_tap_ctl ctx cmd args =
	let stdout, stderr = execute_command_get_output ~env:[|"PATH=" ^ (Sys.getenv "PATH") |] "/usr/sbin/tap-ctl" (cmd::args) in
	stdout

let allocate ctx =
	if ctx.dummy then Dummy.allocate ctx else begin
		let result = invoke_tap_ctl ctx "allocate" [] in
		let stem = get_tapdevstem ctx in
		let stemlen = String.length stem in
		assert(String.startswith stem result);
		let minor_str = (String.sub result stemlen (String.length result - stemlen)) in
		let minor = Scanf.sscanf minor_str "%d" (fun d -> d) in
		minor
	end

let devnode ctx minor =
	Printf.sprintf "%s%d" (get_tapdevstem ctx) minor

let spawn ctx =
	if ctx.dummy then Dummy.spawn ctx else begin
		let result = invoke_tap_ctl ctx "spawn" [] in
		let pid = Scanf.sscanf result "%d" (fun d -> d) in
		pid
	end

let attach ctx pid minor =
	if ctx.dummy then Dummy.attach ctx pid minor else begin
		let _ = invoke_tap_ctl ctx "attach" ["-p"; string_of_int pid; "-m"; string_of_int minor] in
		{minor=minor; tapdisk_pid=pid}
	end

let args tapdev =
	["-p"; string_of_int tapdev.tapdisk_pid; "-m"; string_of_int tapdev.minor]

let _open ctx t leaf_path driver =
	if ctx.dummy then Dummy._open ctx t leaf_path driver else begin
		ignore(invoke_tap_ctl ctx "open" (args t @ ["-a"; Printf.sprintf "%s:%s" (string_of_driver driver) leaf_path]))
	end

let close ctx t =
	if ctx.dummy then Dummy.close ctx t else begin
		ignore(invoke_tap_ctl ctx "close" (args t))
	end
let pause ctx t =
	if ctx.dummy then Dummy.pause ctx t else begin
		ignore(invoke_tap_ctl ctx "pause" (args t))
	end

let unpause ctx t leaf_path driver =
	if ctx.dummy then Dummy.unpause ctx t leaf_path driver else begin
		ignore(invoke_tap_ctl ctx "unpause" (args t @ [ "-a"; Printf.sprintf "%s:%s" (string_of_driver driver) leaf_path ]))
	end

let detach ctx t =
	if ctx.dummy then Dummy.detach ctx t else begin
		ignore(invoke_tap_ctl ctx "detach" (args t))
	end

let free ctx minor =
	if ctx.dummy then Dummy.free ctx minor else begin
		ignore(invoke_tap_ctl ctx "free" ["-m"; string_of_int minor])
	end

let list ?t ctx =
	if ctx.dummy then Dummy.list ?t ctx else begin
		let args = match t with
			| Some tapdev -> args tapdev
			| None -> []
		in
		let result = invoke_tap_ctl ctx "list" args in
		let lines = String.split '\n' result in
		List.filter_map (fun line ->
			try 
				let fields = String.split_f String.isspace line in
				let assoc = List.filter_map (fun field -> 
					match String.split '=' field with
						| x::ys -> 
							Some (x,String.concat "=" ys)
						| _ -> 
							None) fields
				in
				let args = 
					try 
						match String.split ':' (List.assoc "args" assoc) with
							| ty::arguments ->
								Some (ty,String.concat ":" arguments)
							| _ -> None
					with _ -> None
				in
				Some ({tapdisk_pid=int_of_string (List.assoc "pid" assoc); minor=int_of_string (List.assoc "minor" assoc)},(List.assoc "state" assoc),args)
			with _ -> None) lines
	end

let is_paused ctx t =
	let result = list ~t ctx in
	match result with
		| [(tapdev,state,args)] -> state="0x2a"
		| _ -> failwith "Unknown device"

let is_active ctx t =
	let result = list ~t ctx in
	match result with
		| [(tapdev,state,Some _ )] -> true
		| _ -> false

(* We need to be able to check that a given device's major number corresponds to the right driver *)
let read_proc_devices () : (int * string) list = 
	let parse_line x = match List.filter (fun x -> x <> "") (String.split ' ' x) with
	| [x; y] -> (try Some (int_of_string x, y) with _ -> None)
	| _ -> None in
	List.concat (List.map Opt.to_list ( Unixext.file_lines_fold (fun acc x -> parse_line x :: acc) [] "/proc/devices") )

let driver_of_major major = List.assoc major (read_proc_devices ())

exception Not_blktap
exception Not_a_device

let of_device ctx path =
	let stat = Unix.stat path in
	if stat.Unix.st_kind <> Unix.S_BLK then raise Not_a_device;
	let major = stat.Unix.st_rdev / 256 in
	let minor = stat.Unix.st_rdev mod 256 in
	if driver_of_major major <> "tapdev" then raise Not_blktap;
	match List.filter (fun (tapdev, _, _) -> tapdev.minor = minor) (list ctx) with
		| [ t ] -> t
		| _ -> raise Not_found