aboutsummaryrefslogtreecommitdiffstats
path: root/tools/ocaml/xenstored/xenstored.ml
blob: 2742e836b50e6799e0adcdba85d263e13147a296 (plain)
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
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
(*
 * Copyright (C) 2006-2007 XenSource Ltd.
 * Copyright (C) 2008      Citrix Ltd.
 * Author Vincent Hanquez <vincent.hanquez@eu.citrix.com>
 * Author Thomas Gazagnaire <thomas.gazagnaire@eu.citrix.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published
 * by the Free Software Foundation; version 2.1 only. with the special
 * exception on linking described in file LICENSE.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *)

open Printf
open Parse_arg
open Stdext

let error fmt = Logging.error "xenstored" fmt
let debug fmt = Logging.debug "xenstored" fmt
let info fmt = Logging.info "xenstored" fmt

(*------------ event klass processors --------------*)
let process_connection_fds store cons domains rset wset =
	let try_fct fct c =
		try
			fct store cons domains c
		with
		| Unix.Unix_error(err, "write", _) ->
			Connections.del_anonymous cons c;
			error "closing socket connection: write error: %s"
			      (Unix.error_message err)
		| Unix.Unix_error(err, "read", _) ->
			Connections.del_anonymous cons c;
			if err <> Unix.ECONNRESET then
			error "closing socket connection: read error: %s"
			      (Unix.error_message err)
		| Xenbus.Xb.End_of_file ->
			Connections.del_anonymous cons c;
			debug "closing socket connection"
		in
	let process_fdset_with fds fct =
		List.iter (fun fd -> try_fct fct (Connections.find cons fd)) fds
	in
	process_fdset_with rset Process.do_input;
	process_fdset_with wset Process.do_output

let process_domains store cons domains =
	let do_io_domain domain =
		if not (Domain.is_bad_domain domain) then
			let con = Connections.find_domain cons (Domain.get_id domain) in
				Process.do_input store cons domains con;
				Process.do_output store cons domains con in
	Domains.iter domains do_io_domain

let sigusr1_handler store =
	try
		let channel = open_out_gen [ Open_wronly; Open_creat; Open_trunc; ]
		                           0o600 "/var/run/xenstored/db.debug" in
		finally (fun () -> Store.dump store channel)
			(fun () -> close_out channel)
	with _ ->
		()

let sighup_handler _ =
	maybe (fun logger -> logger.Logging.restart()) !Logging.xenstored_logger;
	maybe (fun logger -> logger.Logging.restart()) !Logging.access_logger

let config_filename cf =
	match cf.config_file with
	| Some name -> name
	| None      -> Define.default_config_dir ^ "/oxenstored.conf"

let default_pidfile = "/var/run/xenstored.pid"

let parse_config filename =
	let pidfile = ref default_pidfile in
	let options = [
		("merge-activate", Config.Set_bool Transaction.do_coalesce);
		("perms-activate", Config.Set_bool Perms.activate);
		("quota-activate", Config.Set_bool Quota.activate);
		("quota-maxwatch", Config.Set_int Define.maxwatch);
		("quota-transaction", Config.Set_int Define.maxtransaction);
		("quota-maxentity", Config.Set_int Quota.maxent);
		("quota-maxsize", Config.Set_int Quota.maxsize);
		("test-eagain", Config.Set_bool Transaction.test_eagain);
		("persistent", Config.Set_bool Disk.enable);
		("xenstored-log-file", Config.Set_string Logging.xenstored_log_file);
		("xenstored-log-level", Config.String
			(fun s -> Logging.xenstored_log_level := Logging.level_of_string s));
		("xenstored-log-nb-files", Config.Set_int Logging.xenstored_log_nb_files);
		("xenstored-log-nb-lines", Config.Set_int Logging.xenstored_log_nb_lines);
		("xenstored-log-nb-chars", Config.Set_int Logging.xenstored_log_nb_chars);
		("access-log-file", Config.Set_string Logging.access_log_file);
		("access-log-nb-files", Config.Set_int Logging.access_log_nb_files);
		("access-log-nb-lines", Config.Set_int Logging.access_log_nb_lines);
		("access-log-nb-chars", Config.Set_int Logging.access_log_nb_chars);
		("access-log-read-ops", Config.Set_bool Logging.access_log_read_ops);
		("access-log-transactions-ops", Config.Set_bool Logging.access_log_transaction_ops);
		("access-log-special-ops", Config.Set_bool Logging.access_log_special_ops);
		("allow-debug", Config.Set_bool Process.allow_debug);
		("pid-file", Config.Set_string pidfile); ] in
	begin try Config.read filename options (fun _ _ -> raise Not_found)
	with
	| Config.Error err -> List.iter (fun (k, e) ->
		match e with
		| "unknown key" -> eprintf "config: unknown key %s\n" k
		| _             -> eprintf "config: %s: %s\n" k e
		) err;
	| Sys_error m -> eprintf "error: config: %s\n" m;
	end;
	!pidfile

module DB = struct

exception Bad_format of string

let dump_format_header = "$xenstored-dump-format"

let from_channel_f chan domain_f watch_f store_f =
	let unhexify s = Utils.unhexify s in
	let getpath s = Store.Path.of_string (Utils.unhexify s) in
	let header = input_line chan in
	if header <> dump_format_header then
		raise (Bad_format "header");
	let quit = ref false in
	while not !quit
	do
		try
			let line = input_line chan in
			let l = String.split ',' line in
			try
				match l with
				| "dom" :: domid :: mfn :: port :: []->
					domain_f (int_of_string domid)
					         (Nativeint.of_string mfn)
					         (int_of_string port)
				| "watch" :: domid :: path :: token :: [] ->
					watch_f (int_of_string domid)
					        (unhexify path) (unhexify token)
				| "store" :: path :: perms :: value :: [] ->
					store_f (getpath path)
					        (Perms.Node.of_string (unhexify perms ^ "\000"))
					        (unhexify value)
				| _ ->
					info "restoring: ignoring unknown line: %s" line
			with exn ->
				info "restoring: ignoring unknown line: %s (exception: %s)"
				     line (Printexc.to_string exn);
				()
		with End_of_file ->
			quit := true
	done;
	()

let from_channel store cons doms chan =
	(* don't let the permission get on our way, full perm ! *)
	let op = Store.get_ops store Perms.Connection.full_rights in
	let xc = Xenctrl.interface_open () in

	let domain_f domid mfn port =
		let ndom =
			if domid > 0 then
				Domains.create xc doms domid mfn port
			else
				Domains.create0 false doms
			in
		Connections.add_domain cons ndom;
		in
	let watch_f domid path token = 
		let con = Connections.find_domain cons domid in
		ignore (Connections.add_watch cons con path token)
		in
	let store_f path perms value =
		op.Store.write path value;
		op.Store.setperms path perms
		in
	finally (fun () -> from_channel_f chan domain_f watch_f store_f)
	        (fun () -> Xenctrl.interface_close xc)

let from_file store cons doms file =
	let channel = open_in file in
	finally (fun () -> from_channel store doms cons channel)
	        (fun () -> close_in channel)

let to_channel store cons chan =
	let hexify s = Utils.hexify s in

	fprintf chan "%s\n" dump_format_header;

	(* dump connections related to domains; domid, mfn, eventchn port, watches *)
	Connections.iter_domains cons (fun con -> Connection.dump con chan);

	(* dump the store *)
	Store.dump_fct store (fun path node ->
		let name, perms, value = Store.Node.unpack node in
		let fullpath = (Store.Path.to_string path) ^ "/" ^ name in
		let permstr = Perms.Node.to_string perms in
		fprintf chan "store,%s,%s,%s\n" (hexify fullpath) (hexify permstr) (hexify value)
	);
	flush chan;
	()


let to_file store cons file =
	let channel = open_out_gen [ Open_wronly; Open_creat; Open_trunc; ] 0o600 file in
	finally (fun () -> to_channel store cons channel)
	        (fun () -> close_out channel)
end

let _ =
	let cf = do_argv in
	let pidfile =
		if Sys.file_exists (config_filename cf) then
			parse_config (config_filename cf)
		else
			default_pidfile
		in

	(try 
		Unixext.mkdir_rec (Filename.dirname pidfile) 0o755
	with _ ->
		()
	);

	let rw_sock, ro_sock =
		if cf.disable_socket then
			None, None
		else
			Some (Unix.handle_unix_error Utils.create_unix_socket Define.xs_daemon_socket),
			Some (Unix.handle_unix_error Utils.create_unix_socket Define.xs_daemon_socket_ro)
		in
	
	if cf.daemonize then
		Unixext.daemonize ()
	else
		printf "Xen Storage Daemon, version %d.%d\n%!" 
			Define.xenstored_major Define.xenstored_minor;

	(try Unixext.pidfile_write pidfile with _ -> ());

	(* for compatilibity with old xenstored *)
	begin match cf.pidfile with
	| Some pidfile -> Unixext.pidfile_write pidfile
	| None         -> () end;

	let store = Store.create () in
	let eventchn = Event.init () in
	let domains = Domains.init eventchn in
	let cons = Connections.create () in

	let quit = ref false in

	if cf.restart then (
		DB.from_file store domains cons "/var/run/xenstored/db";
		Event.bind_dom_exc_virq eventchn
	) else (
		if !Disk.enable then (
			info "reading store from disk";
			Disk.read store
		);

		let localpath = Store.Path.of_string "/local" in
		if not (Store.path_exists store localpath) then
			Store.mkdir store (Perms.Connection.create 0) localpath;

		if cf.domain_init then (
			let usingxiu = Xenctrl.is_fake () in
			Connections.add_domain cons (Domains.create0 usingxiu domains);
			Event.bind_dom_exc_virq eventchn
		);
	);

	Sys.set_signal Sys.sighup (Sys.Signal_handle sighup_handler);
	Sys.set_signal Sys.sigterm (Sys.Signal_handle (fun i -> quit := true));
	Sys.set_signal Sys.sigusr1 (Sys.Signal_handle (fun i -> sigusr1_handler store));
	Sys.set_signal Sys.sigpipe Sys.Signal_ignore;

	Logging.init_xenstored_log();
	if cf.activate_access_log then begin
		let post_rotate () = DB.to_file store cons "/var/run/xenstored/db" in
		Logging.init_access_log post_rotate
	end;

	let spec_fds =
		(match rw_sock with None -> [] | Some x -> [ x ]) @
		(match ro_sock with None -> [] | Some x -> [ x ]) @
		(if cf.domain_init then [ Event.fd eventchn ] else [])
		in

	let xc = Xenctrl.interface_open () in

	let process_special_fds rset =
		let accept_connection can_write fd =
			let (cfd, addr) = Unix.accept fd in
			debug "new connection through socket";
			Connections.add_anonymous cons cfd can_write
		and handle_eventchn fd =
			let port = Event.pending eventchn in
			finally (fun () ->
				if port = eventchn.Event.virq_port then (
					let (notify, deaddom) = Domains.cleanup xc domains in
					List.iter (Connections.del_domain cons) deaddom;
					if deaddom <> [] || notify then
						Connections.fire_spec_watches cons "@releaseDomain"
				)
			) (fun () -> Event.unmask eventchn port);
		and do_if_set fd set fct =
			if List.mem fd set then
				fct fd in

		maybe (fun fd -> do_if_set fd rset (accept_connection true)) rw_sock;
		maybe (fun fd -> do_if_set fd rset (accept_connection false)) ro_sock;
		do_if_set (Event.fd eventchn) rset (handle_eventchn)
		in

	let last_stat_time = ref 0. in
	let periodic_ops_counter = ref 0 in
	let periodic_ops () =
		(* we garbage collect the string->int dictionary after a sizeable amount of operations,
		 * there's no need to be really fast even if we got loose
		 * objects since names are often reuse.
		 *)
		if Symbol.created () > 1000 || Symbol.used () > 20000
		then begin
			Symbol.mark_all_as_unused ();
			Store.mark_symbols store;
			Connections.iter cons Connection.mark_symbols;
			Symbol.garbage ()
		end;

		(* make sure we don't print general stats faster than 2 min *)
		let ntime = Unix.gettimeofday () in
		if ntime > (!last_stat_time +. 120.) then (
			last_stat_time := ntime;

			let gc = Gc.stat () in
			let (lanon, lanon_ops, lanon_watchs,
			     ldom, ldom_ops, ldom_watchs) = Connections.stats cons in
			let store_nodes, store_abort, store_coalesce = Store.stats store in
			let symtbl_len = Symbol.stats () in

			info "store stat: nodes(%d) t-abort(%d) t-coalesce(%d)"
			     store_nodes store_abort store_coalesce;
			info "sytbl stat: %d" symtbl_len;
			info "  con stat: anonymous(%d, %d o, %d w) domains(%d, %d o, %d w)"
			     lanon lanon_ops lanon_watchs ldom ldom_ops ldom_watchs;
			info "  mem stat: minor(%.0f) promoted(%.0f) major(%.0f) heap(%d w, %d c) live(%d w, %d b) free(%d w, %d b)"
			     gc.Gc.minor_words gc.Gc.promoted_words gc.Gc.major_words
			     gc.Gc.heap_words gc.Gc.heap_chunks
			     gc.Gc.live_words gc.Gc.live_blocks
			     gc.Gc.free_words gc.Gc.free_blocks
		)
		in

	let main_loop () =
		incr periodic_ops_counter;
		if !periodic_ops_counter > 20 then (
			periodic_ops_counter := 0;
			periodic_ops ();
		);

		let mw = Connections.has_more_work cons in
		let inset, outset = Connections.select cons in
		let timeout = if List.length mw > 0 then 0. else -1. in
		let rset, wset, _ =
		try
			Unix.select (spec_fds @ inset) outset [] timeout
		with Unix.Unix_error(Unix.EINTR, _, _) ->
			[], [], [] in
		let sfds, cfds =
			List.partition (fun fd -> List.mem fd spec_fds) rset in
		if List.length sfds > 0 then
			process_special_fds sfds;
		if List.length cfds > 0 || List.length wset > 0 then
			process_connection_fds store cons domains cfds wset;
		process_domains store cons domains
		in

	while not !quit
	do
		try
			main_loop ()
		with exc ->
			error "caught exception %s" (Printexc.to_string exc);
			if cf.reraise_top_level then
				raise exc
	done;
	info "stopping xenstored";
	DB.to_file store cons "/var/run/xenstored/db";
	()