mgmtops.c revision 2db4f654cb730adffe7b55044327f68808cd1c9d
1/*
2 *
3 *  BlueZ - Bluetooth protocol stack for Linux
4 *
5 *  Copyright (C) 2010  Nokia Corporation
6 *  Copyright (C) 2010  Marcel Holtmann <marcel@holtmann.org>
7 *
8 *  This program is free software; you can redistribute it and/or modify
9 *  it under the terms of the GNU General Public License as published by
10 *  the Free Software Foundation; either version 2 of the License, or
11 *  (at your option) any later version.
12 *
13 *  This program is distributed in the hope that it will be useful,
14 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
15 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 *  GNU General Public License for more details.
17 *
18 *  You should have received a copy of the GNU General Public License
19 *  along with this program; if not, write to the Free Software
20 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
21 *
22 */
23
24#ifdef HAVE_CONFIG_H
25#include <config.h>
26#endif
27
28#include <stdio.h>
29#include <errno.h>
30#include <unistd.h>
31#include <stdlib.h>
32#include <sys/types.h>
33#include <sys/ioctl.h>
34#include <sys/wait.h>
35
36#include <glib.h>
37
38#include <bluetooth/bluetooth.h>
39#include <bluetooth/hci.h>
40#include <bluetooth/mgmt.h>
41
42#include "plugin.h"
43#include "log.h"
44#include "manager.h"
45#include "adapter.h"
46#include "device.h"
47#include "event.h"
48
49#define MGMT_BUF_SIZE 1024
50
51static int max_index = -1;
52static struct controller_info {
53	gboolean valid;
54	gboolean notified;
55	uint8_t type;
56	bdaddr_t bdaddr;
57	uint8_t features[8];
58	uint8_t dev_class[3];
59	uint16_t manufacturer;
60	uint8_t hci_ver;
61	uint16_t hci_rev;
62	gboolean enabled;
63	gboolean discoverable;
64	gboolean pairable;
65	uint8_t sec_mode;
66} *controllers = NULL;
67
68static int mgmt_sock = -1;
69static guint mgmt_watch = 0;
70
71static uint8_t mgmt_version = 0;
72static uint16_t mgmt_revision = 0;
73
74static void read_version_complete(int sk, void *buf, size_t len)
75{
76	struct mgmt_hdr hdr;
77	struct mgmt_rp_read_version *rp = buf;
78
79	if (len < sizeof(*rp)) {
80		error("Too small read version complete event");
81		return;
82	}
83
84	mgmt_revision = btohs(bt_get_unaligned(&rp->revision));
85	mgmt_version = rp->version;
86
87	DBG("version %u revision %u", mgmt_version, mgmt_revision);
88
89	memset(&hdr, 0, sizeof(hdr));
90	hdr.opcode = MGMT_OP_READ_INDEX_LIST;
91	if (write(sk, &hdr, sizeof(hdr)) < 0)
92		error("Unable to read controller index list: %s (%d)",
93						strerror(errno), errno);
94}
95
96static void add_controller(uint16_t index)
97{
98	if (index > max_index) {
99		size_t size = sizeof(struct controller_info) * (index + 1);
100		max_index = index;
101		controllers = g_realloc(controllers, size);
102	}
103
104	memset(&controllers[index], 0, sizeof(struct controller_info));
105
106	controllers[index].valid = TRUE;
107
108	DBG("Added controller %u", index);
109}
110
111static void read_info(int sk, uint16_t index)
112{
113	char buf[MGMT_HDR_SIZE + sizeof(struct mgmt_cp_read_info)];
114	struct mgmt_hdr *hdr = (void *) buf;
115	struct mgmt_cp_read_info *cp = (void *) &buf[sizeof(*hdr)];
116
117	memset(buf, 0, sizeof(buf));
118	hdr->opcode = MGMT_OP_READ_INFO;
119	hdr->len = htobs(sizeof(*cp));
120
121	cp->index = htobs(index);
122
123	if (write(sk, buf, sizeof(buf)) < 0)
124		error("Unable to send read_info command: %s (%d)",
125						strerror(errno), errno);
126}
127
128static void mgmt_index_added(int sk, void *buf, size_t len)
129{
130	struct mgmt_ev_index_added *ev = buf;
131	uint16_t index;
132
133	if (len < sizeof(*ev)) {
134		error("Too small index added event");
135		return;
136	}
137
138	index = btohs(bt_get_unaligned(&ev->index));
139
140	add_controller(index);
141	read_info(sk, index);
142}
143
144static void remove_controller(uint16_t index)
145{
146	if (index > max_index)
147		return;
148
149	if (!controllers[index].valid)
150		return;
151
152	btd_manager_unregister_adapter(index);
153
154	memset(&controllers[index], 0, sizeof(struct controller_info));
155
156	DBG("Removed controller %u", index);
157}
158
159static void mgmt_index_removed(int sk, void *buf, size_t len)
160{
161	struct mgmt_ev_index_removed *ev = buf;
162	uint16_t index;
163
164	if (len < sizeof(*ev)) {
165		error("Too small index removed event");
166		return;
167	}
168
169	index = btohs(bt_get_unaligned(&ev->index));
170
171	remove_controller(index);
172}
173
174static void mgmt_powered(int sk, void *buf, size_t len)
175{
176	struct mgmt_ev_powered *ev = buf;
177	uint16_t index;
178
179	if (len < sizeof(*ev)) {
180		error("Too small powered event");
181		return;
182	}
183
184	index = btohs(bt_get_unaligned(&ev->index));
185
186	if (index > max_index) {
187		DBG("Ignoring powered event for unknown controller %u", index);
188		return;
189	}
190
191	controllers[index].enabled = ev->powered;
192
193	DBG("Controller %u powered %s", index, ev->powered ? "on" : "off");
194}
195
196static void read_index_list_complete(int sk, void *buf, size_t len)
197{
198	struct mgmt_rp_read_index_list *rp = buf;
199	uint16_t num;
200	int i;
201
202	if (len < sizeof(*rp)) {
203		error("Too small read index list complete event");
204		return;
205	}
206
207	num = btohs(bt_get_unaligned(&rp->num_controllers));
208
209	if (num * sizeof(uint16_t) + sizeof(*rp) != len) {
210		error("Incorrect packet size for index list event");
211		return;
212	}
213
214	for (i = 0; i < num; i++) {
215		uint16_t index;
216
217		index = btohs(bt_get_unaligned(&rp->index[i]));
218
219		add_controller(index);
220		read_info(sk, index);
221	}
222}
223
224static int mgmt_stop(int index)
225{
226	DBG("index %d", index);
227	return -ENOSYS;
228}
229
230static int mgmt_set_discoverable(int index, gboolean discoverable)
231{
232	DBG("index %d discoverable %d", index, discoverable);
233	return -ENOSYS;
234}
235
236static int mgmt_set_pairable(int index, gboolean pairable)
237{
238	DBG("index %d pairable %d", index, pairable);
239	return -ENOSYS;
240}
241
242static void read_info_complete(int sk, void *buf, size_t len)
243{
244	struct mgmt_rp_read_info *rp = buf;
245	struct controller_info *info;
246	struct btd_adapter *adapter;
247	uint8_t mode;
248	gboolean pairable, discoverable;
249	uint16_t index;
250	char addr[18];
251
252	if (len < sizeof(*rp)) {
253		error("Too small read info complete event");
254		return;
255	}
256
257	index = btohs(bt_get_unaligned(&rp->index));
258	if (index > max_index) {
259		error("Unexpected index %u in read info complete", index);
260		return;
261	}
262
263	info = &controllers[index];
264	info->type = rp->type;
265	info->enabled = rp->powered;
266	info->discoverable = rp->discoverable;
267	info->pairable = rp->pairable;
268	info->sec_mode = rp->sec_mode;
269	bacpy(&info->bdaddr, &rp->bdaddr);
270	memcpy(info->dev_class, rp->dev_class, 3);
271	memcpy(info->features, rp->features, 8);
272	info->manufacturer = btohs(bt_get_unaligned(&rp->manufacturer));
273	info->hci_ver = rp->hci_ver;
274	info->hci_rev = btohs(bt_get_unaligned(&rp->hci_rev));
275
276	ba2str(&info->bdaddr, addr);
277	DBG("hci%u type %u addr %s", index, info->type, addr);
278	DBG("hci%u class 0x%02x%02x%02x", index,
279		info->dev_class[2], info->dev_class[1], info->dev_class[0]);
280	DBG("hci%u manufacturer %d HCI ver %d:%d", index, info->manufacturer,
281						info->hci_ver, info->hci_rev);
282	DBG("hci%u enabled %u discoverable %u pairable %u sec_mode %u", index,
283					info->enabled, info->discoverable,
284					info->pairable, info->sec_mode);
285
286	adapter = btd_manager_register_adapter(index);
287	if (adapter == NULL) {
288		error("mgmtops: unable to register adapter");
289		return;
290	}
291
292	btd_adapter_get_state(adapter, &mode, NULL, &pairable);
293	if (mode == MODE_OFF) {
294		mgmt_stop(index);
295		return;
296	}
297
298	discoverable = (mode == MODE_DISCOVERABLE);
299
300	if (info->discoverable != discoverable)
301		mgmt_set_discoverable(index, discoverable);
302
303	if (info->pairable != pairable)
304		mgmt_set_pairable(index, pairable);
305
306	if (info->enabled)
307		btd_adapter_start(adapter);
308
309	btd_adapter_unref(adapter);
310}
311
312static void mgmt_cmd_complete(int sk, void *buf, size_t len)
313{
314	struct mgmt_ev_cmd_complete *ev = buf;
315	uint16_t opcode;
316
317	DBG("");
318
319	if (len < sizeof(*ev)) {
320		error("Too small management command complete event packet");
321		return;
322	}
323
324	opcode = btohs(bt_get_unaligned(&ev->opcode));
325
326	switch (opcode) {
327	case MGMT_OP_READ_VERSION:
328		read_version_complete(sk, ev->data, len - sizeof(*ev));
329		break;
330	case MGMT_OP_READ_INDEX_LIST:
331		read_index_list_complete(sk, ev->data, len - sizeof(*ev));
332		break;
333	case MGMT_OP_READ_INFO:
334		read_info_complete(sk, ev->data, len - sizeof(*ev));
335		break;
336	default:
337		error("Unknown command complete for opcode %u", opcode);
338		break;
339	}
340}
341
342static void mgmt_cmd_status(int sk, void *buf, size_t len)
343{
344	struct mgmt_ev_cmd_status *ev = buf;
345	uint16_t opcode;
346
347	if (len < sizeof(*ev)) {
348		error("Too small management command status event packet");
349		return;
350	}
351
352	opcode = btohs(bt_get_unaligned(&ev->opcode));
353
354	DBG("status %u opcode %u", ev->status, opcode);
355}
356
357static void mgmt_controller_error(int sk, void *buf, size_t len)
358{
359	struct mgmt_ev_controller_error *ev = buf;
360	uint16_t index;
361
362	if (len < sizeof(*ev)) {
363		error("Too small management controller error event packet");
364		return;
365	}
366
367	index = btohs(bt_get_unaligned(&ev->index));
368
369	DBG("index %u error_code %u", index, ev->error_code);
370}
371
372static gboolean mgmt_event(GIOChannel *io, GIOCondition cond, gpointer user_data)
373{
374	char buf[MGMT_BUF_SIZE];
375	struct mgmt_hdr *hdr = (void *) buf;
376	int sk;
377	ssize_t ret;
378	uint16_t len, opcode;
379
380	DBG("cond %d", cond);
381
382	if (cond & G_IO_NVAL)
383		return FALSE;
384
385	sk = g_io_channel_unix_get_fd(io);
386
387	if (cond & (G_IO_ERR | G_IO_HUP)) {
388		error("Error on management socket");
389		return FALSE;
390	}
391
392	ret = read(sk, buf, sizeof(buf));
393	if (ret < 0) {
394		error("Unable to read from management socket: %s (%d)",
395						strerror(errno), errno);
396		return TRUE;
397	}
398
399	DBG("Received %zd bytes from management socket", ret);
400
401	if (ret < MGMT_HDR_SIZE) {
402		error("Too small Management packet");
403		return TRUE;
404	}
405
406	opcode = btohs(bt_get_unaligned(&hdr->opcode));
407	len = btohs(bt_get_unaligned(&hdr->len));
408
409	if (ret != MGMT_HDR_SIZE + len) {
410		error("Packet length mismatch. ret %zd len %u", ret, len);
411		return TRUE;
412	}
413
414	switch (opcode) {
415	case MGMT_EV_CMD_COMPLETE:
416		mgmt_cmd_complete(sk, buf + MGMT_HDR_SIZE, len);
417		break;
418	case MGMT_EV_CMD_STATUS:
419		mgmt_cmd_status(sk, buf + MGMT_HDR_SIZE, len);
420		break;
421	case MGMT_EV_CONTROLLER_ERROR:
422		mgmt_controller_error(sk, buf + MGMT_HDR_SIZE, len);
423		break;
424	case MGMT_EV_INDEX_ADDED:
425		mgmt_index_added(sk, buf + MGMT_HDR_SIZE, len);
426		break;
427	case MGMT_EV_INDEX_REMOVED:
428		mgmt_index_removed(sk, buf + MGMT_HDR_SIZE, len);
429		break;
430	case MGMT_EV_POWERED:
431		mgmt_powered(sk, buf + MGMT_HDR_SIZE, len);
432		break;
433	default:
434		error("Unknown Management opcode %u", opcode);
435		break;
436	}
437
438	return TRUE;
439}
440
441static int mgmt_setup(void)
442{
443	struct mgmt_hdr hdr;
444	struct sockaddr_hci addr;
445	GIOChannel *io;
446	GIOCondition condition;
447	int dd, err;
448
449	dd = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI);
450	if (dd < 0)
451		return -errno;
452
453	memset(&addr, 0, sizeof(addr));
454	addr.hci_family = AF_BLUETOOTH;
455	addr.hci_dev = HCI_DEV_NONE;
456	addr.hci_channel = HCI_CHANNEL_CONTROL;
457
458	if (bind(dd, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
459		err = -errno;
460		goto fail;
461	}
462
463	memset(&hdr, 0, sizeof(hdr));
464	hdr.opcode = MGMT_OP_READ_VERSION;
465	if (write(dd, &hdr, sizeof(hdr)) < 0) {
466		err = -errno;
467		goto fail;
468	}
469
470	io = g_io_channel_unix_new(dd);
471	condition = G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL;
472	mgmt_watch = g_io_add_watch(io, condition, mgmt_event, NULL);
473	g_io_channel_unref(io);
474
475	mgmt_sock = dd;
476
477	info("Bluetooth Management interface initialized");
478
479	return 0;
480
481fail:
482	close(dd);
483	return err;
484}
485
486static void mgmt_cleanup(void)
487{
488	g_free(controllers);
489	controllers = NULL;
490	max_index = -1;
491
492	if (mgmt_sock >= 0) {
493		close(mgmt_sock);
494		mgmt_sock = -1;
495	}
496
497	if (mgmt_watch > 0) {
498		g_source_remove(mgmt_watch);
499		mgmt_watch = 0;
500	}
501}
502
503static int mgmt_start(int index)
504{
505	DBG("index %d", index);
506	return -ENOSYS;
507}
508
509static int mgmt_set_powered(int index, gboolean powered)
510{
511	DBG("index %d powered %d", index, powered);
512	return -ENOSYS;
513}
514
515static int mgmt_set_connectable(int index, gboolean connectable)
516{
517	DBG("index %d connectable %d", index, connectable);
518	return -ENOSYS;
519}
520
521static int mgmt_set_dev_class(int index, uint8_t major, uint8_t minor)
522{
523	DBG("index %d major %u minor %u", index, major, minor);
524	return -ENOSYS;
525}
526
527static int mgmt_set_limited_discoverable(int index, gboolean limited)
528{
529	DBG("index %d limited %d", index, limited);
530	return -ENOSYS;
531}
532
533static int mgmt_start_inquiry(int index, uint8_t length, gboolean periodic)
534{
535	DBG("index %d length %u periodic %d", index, length, periodic);
536	return -ENOSYS;
537}
538
539static int mgmt_stop_inquiry(int index)
540{
541	DBG("index %d", index);
542	return -ENOSYS;
543}
544
545static int mgmt_start_scanning(int index)
546{
547	DBG("index %d", index);
548	return -ENOSYS;
549}
550
551static int mgmt_stop_scanning(int index)
552{
553	DBG("index %d", index);
554	return -ENOSYS;
555}
556
557static int mgmt_resolve_name(int index, bdaddr_t *bdaddr)
558{
559	char addr[18];
560
561	ba2str(bdaddr, addr);
562	DBG("index %d addr %s", index, addr);
563
564	return -ENOSYS;
565}
566
567static int mgmt_set_name(int index, const char *name)
568{
569	DBG("index %d, name %s", index, name);
570	return -ENOSYS;
571}
572
573static int mgmt_cancel_resolve_name(int index, bdaddr_t *bdaddr)
574{
575	char addr[18];
576
577	ba2str(bdaddr, addr);
578	DBG("index %d addr %s", index, addr);
579
580	return -ENOSYS;
581}
582
583static int mgmt_fast_connectable(int index, gboolean enable)
584{
585	DBG("index %d enable %d", index, enable);
586	return -ENOSYS;
587}
588
589static int mgmt_read_clock(int index, int handle, int which, int timeout,
590					uint32_t *clock, uint16_t *accuracy)
591{
592	DBG("index %d handle %d which %d timeout %d", index, handle,
593							which, timeout);
594	return -ENOSYS;
595}
596
597static int mgmt_conn_handle(int index, const bdaddr_t *bdaddr, int *handle)
598{
599	char addr[18];
600
601	ba2str(bdaddr, addr);
602	DBG("index %d addr %s", index, addr);
603
604	return -ENOSYS;
605}
606
607static int mgmt_read_bdaddr(int index, bdaddr_t *bdaddr)
608{
609	char addr[18];
610	struct controller_info *info = &controllers[index];
611
612	ba2str(&info->bdaddr, addr);
613	DBG("index %d addr %s", index, addr);
614
615	if (!info->valid)
616		return -ENODEV;
617
618	bacpy(bdaddr, &info->bdaddr);
619
620	return 0;
621}
622
623static int mgmt_block_device(int index, bdaddr_t *bdaddr)
624{
625	char addr[18];
626
627	ba2str(bdaddr, addr);
628	DBG("index %d addr %s", index, addr);
629
630	return -ENOSYS;
631}
632
633static int mgmt_unblock_device(int index, bdaddr_t *bdaddr)
634{
635	char addr[18];
636
637	ba2str(bdaddr, addr);
638	DBG("index %d addr %s", index, addr);
639
640	return -ENOSYS;
641}
642
643static int mgmt_get_conn_list(int index, GSList **conns)
644{
645	DBG("index %d", index);
646	return -ENOSYS;
647}
648
649static int mgmt_read_local_version(int index, struct hci_version *ver)
650{
651	struct controller_info *info = &controllers[index];
652
653	DBG("index %d", index);
654
655	if (!info->valid)
656		return -ENODEV;
657
658	memset(ver, 0, sizeof(*ver));
659	ver->manufacturer = info->manufacturer;
660	ver->hci_ver = info->hci_ver;
661	ver->hci_rev = info->hci_rev;
662
663	return 0;
664}
665
666static int mgmt_read_local_features(int index, uint8_t *features)
667{
668	struct controller_info *info = &controllers[index];
669
670	DBG("index %d", index);
671
672	if (!info->valid)
673		return -ENODEV;
674
675	memcpy(features, info->features, 8);
676
677	return 0;
678}
679
680static int mgmt_disconnect(int index, uint16_t handle)
681{
682	DBG("index %d handle %u", index, handle);
683	return -ENOSYS;
684}
685
686static int mgmt_remove_bonding(int index, bdaddr_t *bdaddr)
687{
688	char addr[18];
689
690	ba2str(bdaddr, addr);
691	DBG("index %d addr %s", index, addr);
692
693	return -ENOSYS;
694}
695
696static int mgmt_request_authentication(int index, uint16_t handle)
697{
698	DBG("index %d handle %u", index, handle);
699	return -ENOSYS;
700}
701
702static int mgmt_pincode_reply(int index, bdaddr_t *bdaddr, const char *pin)
703{
704	char addr[18];
705
706	ba2str(bdaddr, addr);
707	DBG("index %d addr %s pin %s", index, addr, pin);
708
709	return -ENOSYS;
710}
711
712static int mgmt_confirm_reply(int index, bdaddr_t *bdaddr, gboolean success)
713{
714	char addr[18];
715
716	ba2str(bdaddr, addr);
717	DBG("index %d addr %s success %d", index, addr, success);
718
719	return -ENOSYS;
720}
721
722static int mgmt_passkey_reply(int index, bdaddr_t *bdaddr, uint32_t passkey)
723{
724	char addr[18];
725
726	ba2str(bdaddr, addr);
727	DBG("index %d addr %s passkey %06u", index, addr, passkey);
728
729	return -ENOSYS;
730}
731
732static int mgmt_get_auth_info(int index, bdaddr_t *bdaddr, uint8_t *auth)
733{
734	char addr[18];
735
736	ba2str(bdaddr, addr);
737	DBG("index %d addr %s", index, addr);
738
739	return -ENOSYS;
740}
741
742static int mgmt_read_scan_enable(int index)
743{
744	DBG("index %d", index);
745	return -ENOSYS;
746}
747
748static int mgmt_enable_le(int index)
749{
750	DBG("index %d", index);
751	return -ENOSYS;
752}
753
754static int mgmt_get_remote_version(int index, uint16_t handle,
755							gboolean delayed)
756{
757	DBG("index %d handle %u delayed %d", index, handle, delayed);
758	return -ENOSYS;
759}
760
761static int mgmt_encrypt_link(int index, bdaddr_t *dst, bt_hci_result_t cb,
762							gpointer user_data)
763{
764	char addr[18];
765
766	ba2str(dst, addr);
767	DBG("index %d addr %s", index, addr);
768
769	return -ENOSYS;
770}
771
772static struct btd_adapter_ops mgmt_ops = {
773	.setup = mgmt_setup,
774	.cleanup = mgmt_cleanup,
775	.start = mgmt_start,
776	.stop = mgmt_stop,
777	.set_powered = mgmt_set_powered,
778	.set_connectable = mgmt_set_connectable,
779	.set_discoverable = mgmt_set_discoverable,
780	.set_pairable = mgmt_set_pairable,
781	.set_limited_discoverable = mgmt_set_limited_discoverable,
782	.start_inquiry = mgmt_start_inquiry,
783	.stop_inquiry = mgmt_stop_inquiry,
784	.start_scanning = mgmt_start_scanning,
785	.stop_scanning = mgmt_stop_scanning,
786	.resolve_name = mgmt_resolve_name,
787	.cancel_resolve_name = mgmt_cancel_resolve_name,
788	.set_name = mgmt_set_name,
789	.set_dev_class = mgmt_set_dev_class,
790	.set_fast_connectable = mgmt_fast_connectable,
791	.read_clock = mgmt_read_clock,
792	.get_conn_handle = mgmt_conn_handle,
793	.read_bdaddr = mgmt_read_bdaddr,
794	.block_device = mgmt_block_device,
795	.unblock_device = mgmt_unblock_device,
796	.get_conn_list = mgmt_get_conn_list,
797	.read_local_version = mgmt_read_local_version,
798	.read_local_features = mgmt_read_local_features,
799	.disconnect = mgmt_disconnect,
800	.remove_bonding = mgmt_remove_bonding,
801	.request_authentication = mgmt_request_authentication,
802	.pincode_reply = mgmt_pincode_reply,
803	.confirm_reply = mgmt_confirm_reply,
804	.passkey_reply = mgmt_passkey_reply,
805	.get_auth_info = mgmt_get_auth_info,
806	.read_scan_enable = mgmt_read_scan_enable,
807	.enable_le = mgmt_enable_le,
808	.get_remote_version = mgmt_get_remote_version,
809	.encrypt_link = mgmt_encrypt_link,
810};
811
812static int mgmt_init(void)
813{
814	return btd_register_adapter_ops(&mgmt_ops, TRUE);
815}
816
817static void mgmt_exit(void)
818{
819	btd_adapter_cleanup_ops(&mgmt_ops);
820}
821
822BLUETOOTH_PLUGIN_DEFINE(mgmtops, VERSION,
823		BLUETOOTH_PLUGIN_PRIORITY_LOW, mgmt_init, mgmt_exit)
824