Change proc_mgmt/pending_clients.h to use reply queues instead of hashtables.
[barrelfish] / usr / proc_mgmt / service.c
1 /**
2  * \file
3  * \brief Process management service.
4  */
5
6 /*
7  * Copyright (c) 2017, ETH Zurich.
8  * All rights reserved.
9  *
10  * This file is distributed under the terms in the attached LICENSE file.
11  * If you do not find this file, copies can be found by writing to:
12  * ETH Zurich D-INFK, Haldeneggsteig 4, CH-8092 Zurich. Attn: Systems Group.
13  */
14
15 #include <barrelfish/barrelfish.h>
16 #include <barrelfish/nameservice_client.h>
17 #include <barrelfish/proc_mgmt_client.h>
18 #include <barrelfish/spawn_client.h>
19 #include <if/monitor_defs.h>
20 #include <if/proc_mgmt_defs.h>
21 #include <if/spawn_defs.h>
22
23 #include "domain.h"
24 #include "internal.h"
25 #include "pending_clients.h"
26 #include "spawnd_state.h"
27
28 static void add_spawnd_handler(struct proc_mgmt_binding *b, coreid_t core_id,
29                                iref_t iref)
30 {
31     if (spawnd_state_exists(core_id)) {
32         DEBUG_ERR(PROC_MGMT_ERR_SPAWND_EXISTS, "spawnd_state_exists");
33         return;
34     }
35
36     // Bind with the spawnd.
37     struct spawn_binding *spawnb;
38     errval_t err = spawn_bind_iref(iref, &spawnb);
39     if (err_is_fail(err)) {
40         DEBUG_ERR(err, "spawn_bind_iref");
41         return;
42     }
43
44     err = spawnd_state_alloc(core_id, spawnb);
45     if (err_is_fail(err)) {
46         DEBUG_ERR(err, "spawnd_state_alloc");
47     }
48
49     debug_printf("Process manager bound with spawnd.%u on iref %u\n", core_id,
50             iref);
51 }
52
53 static void add_spawnd_handler_non_monitor(struct proc_mgmt_binding *b,
54                                            coreid_t core_id, iref_t iref)
55 {
56     // debug_printf("Ignoring add_spawnd call: %s\n",
57     //              err_getstring(PROC_MGMT_ERR_NOT_MONITOR));
58 }
59
60 static bool cleanup_request_sender(struct msg_queue_elem *m);
61
62 static void spawn_reply_handler(struct spawn_binding *b, errval_t spawn_err)
63 {
64     struct pending_client *cl =
65             (struct pending_client*) spawnd_state_dequeue_recv(b->st);
66
67     struct pending_spawn *spawn = NULL;
68     struct pending_span *span = NULL;
69     struct pending_kill_cleanup *kc = NULL;
70
71     struct domain_entry *entry;
72     
73     errval_t err, resp_err;
74
75     switch (cl->type) {
76         case ClientType_Spawn:
77         case ClientType_SpawnWithCaps:
78             spawn = (struct pending_spawn*) cl->st;
79             err = spawn_err;
80             if (err_is_ok(spawn_err)) {
81                 err = domain_spawn(spawn->domain_cap, spawn->core_id);
82                 if (cl->type == ClientType_Spawn) {
83                     resp_err = cl->b->tx_vtbl.spawn_response(cl->b, NOP_CONT,
84                                                              err,
85                                                              spawn->domain_cap);
86                 } else {
87                     resp_err = cl->b->tx_vtbl.spawn_with_caps_response(cl->b,
88                                                                        NOP_CONT,
89                                                                        err,
90                                                                        spawn->domain_cap);
91                 }
92             }
93
94             free(spawn);
95             break;
96
97         case ClientType_Span:
98             span = (struct pending_span*) cl->st;
99             entry = span->entry;
100             if (entry->status == DOMAIN_STATUS_RUNNING) {
101                 resp_err = cl->b->tx_vtbl.span_response(cl->b, NOP_CONT,
102                                                         spawn_err);
103             }
104
105             free(span);
106             break;
107
108         case ClientType_Cleanup:
109             kc = (struct pending_kill_cleanup*) cl->st;
110             entry = kc->entry;
111
112             assert(entry->num_spawnds_resources > 0);
113             assert(entry->status != DOMAIN_STATUS_CLEANED);
114
115             --entry->num_spawnds_resources;
116             if (entry->num_spawnds_resources == 0) {
117                 entry->status = DOMAIN_STATUS_CLEANED;
118
119                 // At this point, the domain exists in state CLEANED for history
120                 // reasons. For instance, if some other domain issues a wait
121                 // call for this one, the process manager can return the exit
122                 // status directly. At some point, however, we might want to
123                 // just clean up the domain entry and recycle the domain cap.
124             }
125
126             free(kc);
127             break;
128
129         case ClientType_Kill:
130         case ClientType_Exit:
131             kc = (struct pending_kill_cleanup*) cl->st;
132             entry = kc->entry;
133
134             assert(entry->num_spawnds_running > 0);
135             assert(entry->status != DOMAIN_STATUS_STOPPED);
136
137             --entry->num_spawnds_running;
138
139             if (entry->num_spawnds_running == 0) {
140                 entry->status = DOMAIN_STATUS_STOPPED;
141
142                 if (cl->type == ClientType_Kill) {
143                     entry->exit_status = EXIT_STATUS_KILLED;
144                     resp_err = cl->b->tx_vtbl.kill_response(cl->b, NOP_CONT,
145                                                             spawn_err);
146                 }
147
148                 struct domain_waiter *waiter = entry->waiters;
149                 while (waiter != NULL) {
150                     waiter->b->tx_vtbl.wait_response(waiter->b, NOP_CONT,
151                                                      SYS_ERR_OK,
152                                                      entry->exit_status);
153                     struct domain_waiter *tmp = waiter;
154                     waiter = waiter->next;
155                     free(tmp);
156                 }
157
158                 for (coreid_t i = 0; i < MAX_COREID; ++i) {
159                     if (entry->spawnds[i] == NULL) {
160                         continue;
161                     }
162
163                     struct spawn_binding *spb = entry->spawnds[i]->b;
164
165                     struct pending_kill_cleanup *cleanup =
166                             (struct pending_kill_cleanup*) malloc(
167                                     sizeof(struct pending_kill_cleanup));
168                     cleanup->b = spb;
169                     cleanup->domain_cap = kc->domain_cap;
170                     cleanup->entry = entry;
171
172                     struct pending_client *cleanup_cl =
173                             (struct pending_client*) malloc(
174                                     sizeof(struct pending_client));
175                     cleanup_cl->b = cl->b;
176                     cleanup_cl->type = ClientType_Cleanup;
177                     cleanup_cl->st = cleanup;
178
179                     struct msg_queue_elem *msg = (struct msg_queue_elem*) malloc(
180                             sizeof(struct msg_queue_elem));
181                     msg->st = cleanup_cl;
182                     msg->cont = cleanup_request_sender;
183
184                     err = spawnd_state_enqueue_send(entry->spawnds[i], msg);
185
186                     if (err_is_fail(err)) {
187                         DEBUG_ERR(err, "enqueuing cleanup request");
188                         free(cleanup);
189                         free(cleanup_cl);
190                         free(msg);
191                     }
192                 }
193             }
194
195             free(kc);
196             break;
197
198         default:
199             USER_PANIC("Unknown client type in spawn_reply_handler: %u\n",
200                        cl->type);
201     }
202
203     free(cl);
204 }
205
206 static bool spawn_request_sender(struct msg_queue_elem *m)
207 {
208     struct pending_client *cl = (struct pending_client*) m->st;
209     struct pending_spawn *spawn = (struct pending_spawn*) cl->st;
210     spawn->b->rx_vtbl.spawn_reply = spawn_reply_handler;
211
212     errval_t err;
213     bool with_caps = !(capref_is_null(spawn->inheritcn_cap) &&
214                        capref_is_null(spawn->argcn_cap));
215     if (with_caps) {
216         err = spawn->b->tx_vtbl.spawn_with_caps_request(spawn->b, NOP_CONT,
217                                                         cap_procmng,
218                                                         spawn->domain_cap,
219                                                         spawn->path,
220                                                         spawn->argvbuf,
221                                                         spawn->argvbytes,
222                                                         spawn->envbuf,
223                                                         spawn->envbytes,
224                                                         spawn->inheritcn_cap,
225                                                         spawn->argcn_cap,
226                                                         spawn->flags);
227     } else {
228         err = spawn->b->tx_vtbl.spawn_request(spawn->b, NOP_CONT, cap_procmng,
229                                               spawn->domain_cap, spawn->path,
230                                               spawn->argvbuf, spawn->argvbytes,
231                                               spawn->envbuf, spawn->envbytes,
232                                               spawn->flags);
233     }
234
235     if (err_is_fail(err)) {
236         if (err_no(err) == FLOUNDER_ERR_TX_BUSY) {
237             return false;
238         } else {
239             USER_PANIC_ERR(err, "sending spawn request");
240         }
241     }
242
243     free(m);
244
245     return true;
246 }
247
248 static bool span_request_sender(struct msg_queue_elem *m)
249 {
250     struct pending_client *cl = (struct pending_client*) m->st;
251     struct pending_span *span = (struct pending_span*) cl->st;
252
253     errval_t err;
254     span->b->rx_vtbl.spawn_reply = spawn_reply_handler;
255     err = span->b->tx_vtbl.span_request(span->b, NOP_CONT, cap_procmng,
256                                         span->domain_cap, span->vroot,
257                                         span->dispframe);
258
259     if (err_is_fail(err)) {
260         if (err_no(err) == FLOUNDER_ERR_TX_BUSY) {
261             return false;
262         } else {
263             USER_PANIC_ERR(err, "sending span request");
264         }
265     }
266
267     free(m);
268
269     return true;
270 }
271
272 static bool kill_request_sender(struct msg_queue_elem *m)
273 {
274     struct pending_client *cl = (struct pending_client*) m->st;
275     struct pending_kill_cleanup *kill = (struct pending_kill_cleanup*) cl->st;
276
277     errval_t err;
278     kill->b->rx_vtbl.spawn_reply = spawn_reply_handler;
279     err = kill->b->tx_vtbl.kill_request(kill->b, NOP_CONT, cap_procmng,
280                                         kill->domain_cap);
281
282     if (err_is_fail(err)) {
283         if (err_no(err) == FLOUNDER_ERR_TX_BUSY) {
284             return false;
285         } else {
286             USER_PANIC_ERR(err, "sending kill request");
287         }
288     }
289
290     free(m);
291
292     return true;
293 }
294
295 static bool cleanup_request_sender(struct msg_queue_elem *m)
296 {
297     struct pending_client *cl = (struct pending_client*) m->st;
298     struct pending_kill_cleanup *cleanup = (struct pending_kill_cleanup*) cl->st;
299
300     errval_t err;
301     cleanup->b->rx_vtbl.spawn_reply = spawn_reply_handler;
302     err = cleanup->b->tx_vtbl.cleanup_request(cleanup->b, NOP_CONT,
303                                               cap_procmng,
304                                               cleanup->domain_cap);
305
306     if (err_is_fail(err)) {
307         if (err_no(err) == FLOUNDER_ERR_TX_BUSY) {
308             return false;
309         } else {
310             USER_PANIC_ERR(err, "sending cleanup request");
311         }
312     }
313
314     free(m);
315
316     return true;
317 }
318
319 static errval_t spawn_handler_common(struct proc_mgmt_binding *b,
320                                      enum ClientType type,
321                                      coreid_t core_id, const char *path,
322                                      const char *argvbuf, size_t argvbytes,
323                                      const char *envbuf, size_t envbytes,
324                                      struct capref inheritcn_cap,
325                                      struct capref argcn_cap, uint8_t flags)
326 {
327     if (!spawnd_state_exists(core_id)) {
328         return PROC_MGMT_ERR_INVALID_SPAWND;
329     }
330
331     struct spawnd_state *spawnd = spawnd_state_get(core_id);
332     assert(spawnd != NULL);
333     struct spawn_binding *cl = spawnd->b;
334     assert(cl != NULL);
335
336     struct capref domain_cap;
337     errval_t err = slot_alloc(&domain_cap);
338     if (err_is_fail(err)) {
339         DEBUG_ERR(err, "slot_alloc domain_cap");
340         return err_push(err, PROC_MGMT_ERR_CREATE_DOMAIN_CAP);
341     }
342     err = cap_retype(domain_cap, cap_procmng, 0, ObjType_Domain, 0, 1);
343     if (err_is_fail(err)) {
344         DEBUG_ERR(err, "cap_retype domain_cap");
345         return err_push(err, PROC_MGMT_ERR_CREATE_DOMAIN_CAP);
346     }
347
348     struct pending_spawn *spawn = (struct pending_spawn*) malloc(
349             sizeof(struct pending_spawn));
350     spawn->domain_cap = domain_cap;
351     spawn->b = cl;
352     spawn->core_id = core_id;
353     spawn->path = path;
354     spawn->argvbuf = argvbuf;
355     spawn->argvbytes = argvbytes;
356     spawn->envbuf = envbuf;
357     spawn->envbytes = envbytes;
358     spawn->inheritcn_cap = inheritcn_cap;
359     spawn->argcn_cap = argcn_cap;
360     spawn->flags = flags;
361
362     struct pending_client *spawn_cl = (struct pending_client*) malloc(
363             sizeof(struct pending_client));
364     spawn_cl->b = b;
365     spawn_cl->type = type;
366     spawn_cl->st = spawn;
367
368     struct msg_queue_elem *msg = (struct msg_queue_elem*) malloc(
369             sizeof(struct msg_queue_elem));
370     msg->st = spawn_cl;
371     msg->cont = spawn_request_sender;
372
373     err = spawnd_state_enqueue_send(spawnd, msg);
374
375     if (err_is_fail(err)) {
376         DEBUG_ERR(err, "enqueuing spawn request");
377         free(spawn);
378         free(spawn_cl);
379         free(msg);
380     }
381
382     return SYS_ERR_OK;
383 }
384
385 static void spawn_handler(struct proc_mgmt_binding *b, coreid_t core_id,
386                           const char *path, const char *argvbuf,
387                           size_t argvbytes, const char *envbuf, size_t envbytes,
388                           uint8_t flags)
389 {
390     errval_t err, resp_err;
391     err = spawn_handler_common(b, ClientType_Spawn, core_id, path, argvbuf,
392                                argvbytes, envbuf, envbytes, NULL_CAP, NULL_CAP,
393                                flags);
394     if (err_is_ok(err)) {
395         // Will respond to client when we get the reply from spawnd.
396         return;
397     }
398
399     resp_err = b->tx_vtbl.spawn_response(b, NOP_CONT, err, NULL_CAP);
400     if (err_is_fail(resp_err)) {
401         DEBUG_ERR(resp_err, "failed to send spawn_response");
402     }
403 }
404
405 static void spawn_with_caps_handler(struct proc_mgmt_binding *b,
406                                     coreid_t core_id, const char *path,
407                                     const char *argvbuf, size_t argvbytes,
408                                     const char *envbuf, size_t envbytes,
409                                     struct capref inheritcn_cap,
410                                     struct capref argcn_cap, uint8_t flags)
411 {
412     errval_t err, resp_err;
413     err = spawn_handler_common(b, ClientType_SpawnWithCaps, core_id, path,
414                                argvbuf, argvbytes, envbuf, envbytes,
415                                inheritcn_cap, argcn_cap, flags);
416     if (err_is_ok(err)) {
417         // Will respond to client when we get the reply from spawnd.
418         return;
419     }
420
421     resp_err = b->tx_vtbl.spawn_with_caps_response(b, NOP_CONT, err,
422                                                             NULL_CAP);
423     if (err_is_fail(resp_err)) {
424         DEBUG_ERR(resp_err, "failed to send spawn_with_caps_response");
425     }
426 }
427
428 static void span_handler(struct proc_mgmt_binding *b, struct capref domain_cap,
429                          coreid_t core_id, struct capref vroot,
430                          struct capref dispframe)
431 {
432     errval_t err, resp_err;
433     struct domain_entry *entry = NULL;
434     err = domain_get_by_cap(domain_cap, &entry);
435     if (err_is_fail(err)) {
436         goto respond_with_err;
437     }
438
439     assert(entry != NULL);
440     if (entry->status != DOMAIN_STATUS_RUNNING) {
441         err = PROC_MGMT_ERR_DOMAIN_NOT_RUNNING;
442         goto respond_with_err;
443     }
444
445     if (entry->spawnds[core_id] != NULL) {
446         // TODO(razvan): Maybe we want to allow the same domain to span multiple
447         // dispatchers onto the same core?
448         err = PROC_MGMT_ERR_ALREADY_SPANNED;
449         goto respond_with_err;
450     }
451
452     if (!spawnd_state_exists(core_id)) {
453         err = PROC_MGMT_ERR_INVALID_SPAWND;
454         goto respond_with_err;
455     }
456
457     struct spawnd_state *spawnd = spawnd_state_get(core_id);
458     assert(spawnd != NULL);
459     struct spawn_binding *cl = spawnd->b;
460     assert(cl != NULL);
461
462     struct pending_span *span = (struct pending_span*) malloc(
463             sizeof(struct pending_span));
464     span->domain_cap = domain_cap;
465     span->entry = entry;
466     span->b = cl;
467     span->core_id = core_id;
468     span->vroot = vroot;
469     span->dispframe = dispframe;
470
471     struct pending_client *span_cl = (struct pending_client*) malloc(
472             sizeof(struct pending_client));
473     span_cl->b = b;
474     span_cl->type = ClientType_Span;
475     span_cl->st = span;
476
477     struct msg_queue_elem *msg = (struct msg_queue_elem*) malloc(
478             sizeof(struct msg_queue_elem));
479     msg->st = span_cl;
480     msg->cont = span_request_sender;
481
482     err = spawnd_state_enqueue_send(spawnd, msg);
483
484     if (err_is_fail(err)) {
485         DEBUG_ERR(err, "enqueuing span request");
486         free(span);
487         free(span_cl);
488         free(msg);
489     }
490
491 respond_with_err:
492     resp_err = b->tx_vtbl.span_response(b, NOP_CONT, err);
493     if (err_is_fail(resp_err)) {
494         DEBUG_ERR(resp_err, "failed to send span_response");
495     }
496 }
497
498 static errval_t kill_handler_common(struct proc_mgmt_binding *b,
499                                     struct capref domain_cap,
500                                     enum ClientType type,
501                                     uint8_t exit_status)
502 {
503     struct domain_entry *entry;
504     errval_t err = domain_get_by_cap(domain_cap, &entry);
505     if (err_is_fail(err)) {
506         return err;
507     }
508
509     entry->exit_status = exit_status;
510     domain_stop_pending(entry);
511
512     for (coreid_t i = 0; i < MAX_COREID; ++i) {
513         if (entry->spawnds[i] == NULL) {
514             continue;
515         }
516
517         struct spawn_binding *spb = entry->spawnds[i]->b;
518
519         struct pending_kill_cleanup *cmd = (struct pending_kill_cleanup*) malloc(
520                 sizeof(struct pending_kill_cleanup));
521         cmd->domain_cap = domain_cap;
522         cmd->entry = entry;
523         cmd->b = spb;
524
525         struct pending_client *cl = (struct pending_client*) malloc(
526                 sizeof(struct pending_client));
527         cl->b = b;
528         cl->type = type;
529         cl->st = cmd;
530
531         struct msg_queue_elem *msg = (struct msg_queue_elem*) malloc(
532                 sizeof(struct msg_queue_elem));
533         msg->st = cl;
534         msg->cont = kill_request_sender;
535
536         err = spawnd_state_enqueue_send(entry->spawnds[i], msg);
537         if (err_is_fail(err)) {
538             DEBUG_ERR(err, "enqueuing kill request");
539             free(cmd);
540             free(cl);
541             free(msg);
542         }
543     }
544
545     return SYS_ERR_OK;
546 }
547
548 static void kill_handler(struct proc_mgmt_binding *b,
549                          struct capref victim_domain_cap)
550 {
551     errval_t err = kill_handler_common(b, victim_domain_cap, ClientType_Kill,
552                                        EXIT_STATUS_KILLED);
553     if (err_is_fail(err)) {
554         errval_t resp_err = b->tx_vtbl.kill_response(b, NOP_CONT, err);
555         if (err_is_fail(resp_err)) {
556             DEBUG_ERR(resp_err, "failed to send kill_response");
557         }
558     }
559 }
560
561 static void exit_handler(struct proc_mgmt_binding *b, struct capref domain_cap,
562                          uint8_t exit_status)
563 {
564     errval_t err = kill_handler_common(b, domain_cap, ClientType_Exit,
565                                        exit_status);
566     if (err_is_fail(err)) {
567         DEBUG_ERR(err, "processing exit_handler for requesting domain, exit "
568                   "code %u", exit_status);
569     }
570     // Error or not, there's no client to respond to anymore.
571 }
572
573 static void wait_handler(struct proc_mgmt_binding *b, struct capref domain_cap)
574 {
575     errval_t err, resp_err;
576     struct domain_entry *entry;
577     err = domain_get_by_cap(domain_cap, &entry);
578     if (err_is_fail(err)) {
579         goto respond;
580     }
581
582     if (entry->status == DOMAIN_STATUS_STOPPED) {
583         // Domain has already been stopped, so just reply with exit status.
584         goto respond;
585     }
586
587     struct domain_waiter *waiter = (struct domain_waiter*) malloc(
588             sizeof(struct domain_waiter));
589     waiter->b = b;
590     waiter->next = entry->waiters;
591     entry->waiters = waiter;
592     // Will respond when domain is stopped.
593     return;
594
595 respond:
596     resp_err = b->tx_vtbl.wait_response(b, NOP_CONT, err, entry->exit_status);
597     if (err_is_fail(resp_err)) {
598         DEBUG_ERR(resp_err, "failed to send wait_response");
599     }
600 }
601
602 static struct proc_mgmt_rx_vtbl monitor_vtbl = {
603     .add_spawnd           = add_spawnd_handler,
604     .spawn_call           = spawn_handler,
605     .spawn_with_caps_call = spawn_with_caps_handler,
606     .span_call            = span_handler,
607     .kill_call            = kill_handler,
608     .exit_call            = exit_handler,
609     .wait_call            = wait_handler
610 };
611
612 static struct proc_mgmt_rx_vtbl non_monitor_vtbl = {
613     .add_spawnd           = add_spawnd_handler_non_monitor,
614     .spawn_call           = spawn_handler,
615     .spawn_with_caps_call = spawn_with_caps_handler,
616     .span_call            = span_handler,
617     .kill_call            = kill_handler,
618     .exit_call            = exit_handler,
619     .wait_call            = wait_handler
620 };
621
622 static errval_t alloc_ep_for_monitor(struct capref *ep)
623 {
624     struct proc_mgmt_lmp_binding *lmpb =
625         malloc(sizeof(struct proc_mgmt_lmp_binding));
626     assert(lmpb != NULL);
627
628     // setup our end of the binding
629     errval_t err = proc_mgmt_client_lmp_accept(lmpb, get_default_waitset(),
630                                                DEFAULT_LMP_BUF_WORDS);
631     if (err_is_fail(err)) {
632         free(lmpb);
633         return err_push(err, LIB_ERR_PROC_MGMT_CLIENT_ACCEPT);
634     }
635
636     *ep = lmpb->chan.local_cap;
637     lmpb->b.rx_vtbl = monitor_vtbl;
638
639     return SYS_ERR_OK;
640 }
641
642 static void export_cb(void *st, errval_t err, iref_t iref)
643 {
644     if (err_is_fail(err)) {
645         USER_PANIC_ERR(err, "export failed");
646     }
647
648     // Allocate an endpoint for the local monitor, who will use it to inform
649     // us about new spawnd irefs on behalf of other monitors.
650     struct capref ep;
651     err = alloc_ep_for_monitor(&ep);
652     if (err_is_fail(err)) {
653         USER_PANIC_ERR(err, "failed to allocate LMP EP for local monitor");
654     }
655
656     // Send the endpoint to the monitor, so it can finish the handshake.
657     struct monitor_binding *mb = get_monitor_binding();
658     err = mb->tx_vtbl.set_proc_mgmt_ep_request(mb, NOP_CONT, ep);
659     if (err_is_fail(err)) {
660         USER_PANIC_ERR(err, "failed to send set_proc_mgmt_ep_request to "
661                        "monitor");
662     }
663
664     // Also register this iref with the name service, for arbitrary client
665     // domains to use for spawn-related ops.
666     err = nameservice_register(SERVICE_BASENAME, iref);
667     if (err_is_fail(err)) {
668         USER_PANIC_ERR(err, "nameservice_register failed");
669     }
670 }
671
672 static errval_t connect_cb(void *st, struct proc_mgmt_binding *b)
673 {
674     b->rx_vtbl = non_monitor_vtbl;
675     return SYS_ERR_OK;
676 }
677
678 errval_t start_service(void)
679 {
680     return proc_mgmt_export(NULL, export_cb, connect_cb, get_default_waitset(),
681             IDC_EXPORT_FLAGS_DEFAULT);
682 }