1 /*
2 * This file and its contents are supplied under the terms of the
3 * Common Development and Distribution License ("CDDL"), version 1.0.
4 * You may only use this file in accordance with the terms of version
5 * 1.0 of the CDDL.
6 *
7 * A full copy of the text of the CDDL should have accompanied this
8 * source. A copy of the CDDL is also available via the Internet at
9 * http://www.illumos.org/license/CDDL.
10 */
11
12 /*
13 * Copyright 2017 Nexenta Systems, Inc. All rights reserved.
14 */
15
16 #include <sys/types.h>
17 #include <stdio.h>
18 #include <stdlib.h>
19 #include <string.h>
20 #include <stdarg.h>
21 #include <unistd.h>
22 #include <stropts.h>
23 #include <fcntl.h>
24 #include <errno.h>
25 #include <sys/debug.h>
26 #include <inttypes.h>
27 #include <ctype.h>
28 #include <umem.h>
29 #include <libnvpair.h>
30 #include <uuid/uuid.h>
31
32 #include <libsysevent.h>
33 #include <sys/sysevent/krrp.h>
34
35 #include <assert.h>
36
37 #include <sys/krrp.h>
38 #include <libkrrp.h>
39
40
41 #define KRRP_CMD_MAP(X) \
42 X(read-event, krrp_do_read_event, \
43 krrp_usage_event, READ_EVENT) \
44 X(sess-list, krrp_do_sess_list, \
45 krrp_usage_sess, SESS_LIST) \
46 X(sess-create, krrp_do_sess_create, \
47 krrp_usage_sess, SESS_CREATE) \
48 X(sess-destroy, krrp_do_sess_action, \
49 krrp_usage_sess, SESS_DESTROY) \
50 X(sess-run, krrp_do_sess_action, \
51 krrp_usage_sess, SESS_RUN) \
52 X(sess-send-stop, krrp_do_sess_action, \
53 krrp_usage_sess, SESS_SEND_STOP) \
54 X(sess-status, krrp_do_sess_status, \
55 krrp_usage_sess, SESS_STATUS) \
56 X(sess-create-conn, krrp_do_sess_create_conn, \
57 krrp_usage_sess, SESS_CREATE_CONN) \
58 X(sess-create-read-stream, krrp_do_sess_create_read_stream, \
59 krrp_usage_sess, SESS_CREATE_READ_STREAM) \
60 X(sess-create-write-stream, krrp_do_sess_create_write_stream, \
61 krrp_usage_sess, SESS_CREATE_WRITE_STREAM) \
62 X(sess-create-pdu-engine, krrp_do_sess_create_pdu_engine, \
63 krrp_usage_sess, SESS_CREATE_PDU_ENGINE) \
64 X(sess-conn-throttle, krrp_do_sess_action, \
65 krrp_usage_sess, SESS_CONN_THROTTLE) \
66 X(sess-get-conn-info, krrp_do_sess_get_conn_info, \
67 krrp_usage_sess, SESS_GET_CONN_INFO) \
68 X(ksvc-enable, krrp_do_ksvc_action, \
69 krrp_usage_ksvc, KSVC_ENABLE) \
70 X(ksvc-disable, krrp_do_ksvc_action, \
71 krrp_usage_ksvc, KSVC_DISABLE) \
72 X(ksvc-state, krrp_do_svc_get_state, \
73 krrp_usage_ksvc, KSVC_STATE) \
74 X(ksvc-configure, krrp_do_ksvc_configure, \
75 krrp_usage_ksvc, KSVC_CONFIGURE) \
76
77 typedef enum {
78 #define KRRP_CMD_EXPAND(cmd_name, action_func, usage_func, enum_name) \
79 KRRP_CMD_##enum_name,
80 KRRP_CMD_MAP(KRRP_CMD_EXPAND)
81 #undef KRRP_CMD_EXPAND
82 KRRP_CMD_LAST
83 } krrp_cmd_item_t;
84
85 typedef struct krrp_cmd_s krrp_cmd_t;
86
87 typedef void (krrp_usage_func)(int, krrp_cmd_t *, boolean_t);
88 typedef int (krrp_handler_func)(int argc, char **argv, krrp_cmd_t *);
89
90 struct krrp_cmd_s {
91 const char *name;
92 krrp_handler_func *handler_func;
93 krrp_usage_func *usage_func;
94 krrp_cmd_item_t item;
95 };
96
97 static void common_usage(int);
98 static int krrp_lookup_cmd(const char *, krrp_cmd_t **);
99
100 static krrp_usage_func krrp_usage_ksvc, krrp_usage_sess, krrp_usage_event;
101
102 static krrp_handler_func krrp_do_ksvc_action, krrp_do_ksvc_configure,
103 krrp_do_sess_list, krrp_do_sess_action, krrp_do_sess_create_conn,
104 krrp_do_sess_create_read_stream, krrp_do_sess_create_write_stream,
105 krrp_do_sess_create_pdu_engine, krrp_do_sess_status, krrp_do_read_event,
106 krrp_do_svc_get_state, krrp_do_sess_create, krrp_do_sess_get_conn_info;
107
108 static int krrp_parse_and_check_sess_id(char *sess_id_str,
109 uuid_t sess_id);
110
111 static int krrp_sysevent_cb(libkrrp_event_t *ev, void *cookie);
112 static void krrp_print_err_already_defined(const char *param);
113 static void krrp_print_err_unknown_param(const char *param);
114 static void krrp_print_err_no_sess_id(void);
115 static void krrp_print_libkrrp_error(void);
116
117 static void fprintf_err(const char *fmt, ...);
118 static void fprintf_msg(const char *fmt, ...);
119
120 static krrp_cmd_t cmds[] = {
121 #define KRRP_CMD_EXPAND(cmd_name, action_func, usage_func, enum_name) \
122 {#cmd_name, action_func, usage_func, KRRP_CMD_##enum_name},
123 KRRP_CMD_MAP(KRRP_CMD_EXPAND)
124 #undef KRRP_CMD_EXPAND
125 };
126 static size_t cmds_sz = sizeof (cmds) / sizeof (cmds[0]);
127
128 const char *tool_name;
129 libkrrp_handle_t *libkrrp_hdl = NULL;
130
131 int
132 main(int argc, char **argv)
133 {
134 int rc = 0;
135 krrp_cmd_t *cmd;
136
137 opterr = 0;
138
139 tool_name = argv[0];
140
141 if (argc < 2) {
142 fprintf_err("missing command\n\n\n");
143 common_usage(1);
144 }
145
146 rc = krrp_lookup_cmd(argv[1], &cmd);
147 if (rc != 0) {
148 fprintf_err("unknown command\n");
149 common_usage(1);
150 }
151
152 libkrrp_hdl = libkrrp_init();
153 if (libkrrp_hdl == NULL) {
154 fprintf_err("Failed to init libkrrp\n");
155 exit(1);
156 }
157
158 rc = cmd->handler_func(argc - 1, argv + 1, cmd);
159
160 return (rc);
161 }
162
163 static void
164 common_usage(int rc)
165 {
166 size_t i;
167
168 for (i = 0; i < cmds_sz; i++)
169 cmds[i].usage_func(0, &cmds[i], B_TRUE);
170
171 exit(rc);
172 }
173
174 static void
175 krrp_usage_event(int rc, krrp_cmd_t *cmd, boolean_t use_return)
176 {
177 assert(cmd->item == KRRP_CMD_READ_EVENT);
178
179 fprintf_msg("Usage: %s read-event\n\n\n", tool_name);
180
181 if (use_return)
182 return;
183
184 exit(rc);
185 }
186
187 static void
188 krrp_usage_ksvc(int rc, krrp_cmd_t *cmd, boolean_t use_return)
189 {
190 switch (cmd->item) {
191 case KRRP_CMD_KSVC_ENABLE:
192 fprintf_msg("Usage: %s ksvc-enable\n", tool_name);
193 break;
194 case KRRP_CMD_KSVC_DISABLE:
195 fprintf_msg("Usage: %s ksvc-disable\n", tool_name);
196 break;
197 case KRRP_CMD_KSVC_STATE:
198 fprintf_msg("Usage: %s ksvc-state\n", tool_name);
199 break;
200 case KRRP_CMD_KSVC_CONFIGURE:
201 fprintf_msg("Usage: %s ksvc-configure "
202 "<-p listning port>\n", tool_name);
203 break;
204 default:
205 assert(0);
206 }
207
208 fprintf_msg("\n\n");
209
210 if (use_return)
211 return;
212
213 exit(rc);
214 }
215
216 static void
217 krrp_usage_sess(int rc, krrp_cmd_t *cmd, boolean_t use_return)
218 {
219 switch (cmd->item) {
220 case KRRP_CMD_SESS_LIST:
221 fprintf_msg("Usage: %s sess-list\n", tool_name);
222 break;
223 case KRRP_CMD_SESS_CREATE:
224 fprintf_msg("Usage: %s sess-create -s <sess_id> "
225 "<-k kstat_id (16 symbols)> "
226 "[-a <auth digest (max 255 symbols)>] "
227 "[-z] [-f] [-c]\n", tool_name);
228 break;
229 case KRRP_CMD_SESS_DESTROY:
230 fprintf_msg("Usage: %s sess-destroy "
231 "-s <sess_id>\n", tool_name);
232 break;
233 case KRRP_CMD_SESS_STATUS:
234 fprintf_msg("Usage: %s sess-status "
235 "-s <sess_id>\n", tool_name);
236 break;
237 case KRRP_CMD_SESS_RUN:
238 fprintf_msg("Usage: %s sess-run "
239 "-s <sess_id>\n", tool_name);
240 break;
241 case KRRP_CMD_SESS_SEND_STOP:
242 fprintf_msg("Usage: %s sess-send-stop "
243 "-s <sess_id>\n", tool_name);
244 break;
245 case KRRP_CMD_SESS_CREATE_CONN:
246 fprintf_msg("Usage: %s sess-create-conn "
247 "-s <sess_id> -a <remote IP> -p <remote port> "
248 "-t <timeout>\n", tool_name);
249 break;
250 case KRRP_CMD_SESS_CONN_THROTTLE:
251 fprintf_msg("Usage: %s sess-conn-throttle "
252 "-s <sess_id> -l <limit>\n",
253 tool_name);
254 break;
255 case KRRP_CMD_SESS_GET_CONN_INFO:
256 fprintf_msg("Usage: %s sess-get-conn-info "
257 "-s <sess_id>\n", tool_name);
258 break;
259 case KRRP_CMD_SESS_CREATE_READ_STREAM:
260 fprintf_msg("Usage: %s sess-create-read-stream "
261 "-s <sess_id> -d <src dataset> [-z <src snapshot>] "
262 "[-c <common snapshot>] [-I] [-r] [-p] [-e] [-k] "
263 "[-j] [-b] [-f <fake_data_sz>] [-t <resume_token>] "
264 "[-n <keep snaps>] [-m <user_prop_name>=<val>]\n",
265 tool_name);
266 break;
267 case KRRP_CMD_SESS_CREATE_WRITE_STREAM:
268 fprintf_msg("Usage: %s sess-create-write-stream "
269 "-s <sess_id> -d <dst dataset> [-c <common snapshot>] "
270 "[-F] [-k] [-l | -x] [-i <prop_name>] "
271 "[-o <prop_name=value>] [-t <resume_token>] "
272 "[-n <keep snaps>]\n", tool_name);
273 break;
274 case KRRP_CMD_SESS_CREATE_PDU_ENGINE:
275 fprintf_msg("Usage: %s sess-create-pdu-engine "
276 "-s <sess_id> -b <data block size> [-a] "
277 "-m <max memory in MB>\n", tool_name);
278 break;
279 default:
280 assert(0);
281 }
282
283 fprintf_msg("\n\n");
284
285 if (use_return)
286 return;
287
288 exit(rc);
289 }
290
291 static int
292 krrp_lookup_cmd(const char *cmd_name, krrp_cmd_t **cmd)
293 {
294 size_t i;
295 int rc = -1;
296
297 for (i = 0; i < cmds_sz; i++) {
298 if (strcmp(cmds[i].name, cmd_name) == 0) {
299 *cmd = &cmds[i];
300 rc = 0;
301 break;
302 }
303 }
304
305 return (rc);
306 }
307
308 /* ARGSUSED */
309 static int
310 krrp_do_read_event(int argc, char **argv, krrp_cmd_t *cmd)
311 {
312 int rc;
313 libkrrp_evc_handle_t *libkrrp_evc_hdl = NULL;
314
315
316 rc = libkrrp_evc_subscribe(&libkrrp_evc_hdl, krrp_sysevent_cb, NULL);
317 if (rc != 0) {
318 fprintf_err("Failed to subscribe to KRRP events\n");
319 exit(1);
320 }
321
322 for (;;)
323 (void) sleep(1);
324
325 /* NOTREACHED */
326 return (0);
327 }
328
329 /* ARGSUSED */
330 static int
331 krrp_sysevent_cb(libkrrp_event_t *ev, void *cookie)
332 {
333 libkrrp_ev_type_t ev_type;
334 libkrrp_ev_data_t *ev_data;
335 char sess_id_str[UUID_PRINTABLE_STRING_LENGTH];
336 libkrrp_error_descr_t err_desc;
337
338 ev_type = libkrrp_ev_type(ev);
339 ev_data = libkrrp_ev_data(ev);
340
341 switch (ev_type) {
342 case LIBKRRP_EV_TYPE_SESS_SEND_DONE:
343 uuid_unparse(ev_data->sess_send_done.sess_id, sess_id_str);
344 fprintf_msg("Session '%s' has done send\n", sess_id_str);
345 break;
346 case LIBKRRP_EV_TYPE_SESS_ERROR:
347 uuid_unparse(ev_data->sess_error.sess_id, sess_id_str);
348 libkrrp_ev_data_error_description(ev_type,
349 &ev_data->sess_error.libkrrp_error, err_desc);
350 fprintf_msg("Session '%s' has interrupted by error:\n"
351 " %s\n", sess_id_str, err_desc);
352 break;
353 case LIBKRRP_EV_TYPE_SERVER_ERROR:
354 libkrrp_ev_data_error_description(ev_type,
355 &ev_data->sess_error.libkrrp_error, err_desc);
356 fprintf_msg("An error occured in kernel-server:\n"
357 " %s\n", err_desc);
358 break;
359 default:
360 fprintf_err("Unknow event type\n");
361 assert(0);
362 }
363
364 return (0);
365 }
366
367 static int
368 krrp_do_sess_create(int argc, char **argv, krrp_cmd_t *cmd)
369 {
370 int c, rc = 0;
371 uuid_t sess_id;
372 boolean_t sender = B_FALSE, compound = B_FALSE,
373 fake_mode = B_FALSE;
374 char *auth_digest = NULL, *kstat_id = NULL;
375
376 assert(cmd->item == KRRP_CMD_SESS_CREATE);
377
378 uuid_clear(sess_id);
379
380 while ((c = getopt(argc, argv, "hs:zfck:a:")) != -1) {
381 switch (c) {
382 case 's':
383 if (krrp_parse_and_check_sess_id(optarg, sess_id) != 0)
384 exit(1);
385
386 break;
387 case 'a':
388 if (auth_digest != NULL) {
389 krrp_print_err_already_defined("a");
390 exit(1);
391 }
392
393 auth_digest = optarg;
394 break;
395 case 'k':
396 if (kstat_id != NULL) {
397 krrp_print_err_already_defined("k");
398 exit(1);
399 }
400
401 kstat_id = optarg;
402 break;
403 case 'z':
404 if (sender) {
405 krrp_print_err_already_defined("z");
406 exit(1);
407 }
408
409 sender = B_TRUE;
410 break;
411 case 'f':
412 if (fake_mode) {
413 krrp_print_err_already_defined("f");
414 exit(1);
415 }
416
417 fake_mode = B_TRUE;
418 break;
419 case 'c':
420 if (compound) {
421 krrp_print_err_already_defined("c");
422 exit(1);
423 }
424
425 compound = B_TRUE;
426 break;
427 case '?':
428 krrp_print_err_unknown_param(argv[optind - 1]);
429 cmd->usage_func(1, cmd, B_FALSE);
430 break;
431 case 'h':
432 cmd->usage_func(0, cmd, B_FALSE);
433 break;
434 }
435 }
436
437 if (uuid_is_null(sess_id) == 1) {
438 krrp_print_err_no_sess_id();
439 cmd->usage_func(1, cmd, B_FALSE);
440 }
441
442 if (kstat_id == NULL) {
443 fprintf_err("Session Kstat ID is not defined\n");
444 cmd->usage_func(1, cmd, B_FALSE);
445 }
446
447 if (sender && compound) {
448 fprintf_err("'c' and 'z' parameters cannot "
449 "be used together\n");
450 exit(1);
451 }
452
453 if (compound) {
454 rc = krrp_sess_create_compound(libkrrp_hdl, sess_id,
455 kstat_id, fake_mode);
456 } else if (sender) {
457 rc = krrp_sess_create_sender(libkrrp_hdl, sess_id,
458 kstat_id, auth_digest, fake_mode);
459 } else {
460 rc = krrp_sess_create_receiver(libkrrp_hdl, sess_id,
461 kstat_id, auth_digest, fake_mode);
462 }
463
464 if (rc != 0) {
465 fprintf_err("Failed to create session\n");
466 krrp_print_libkrrp_error();
467 exit(1);
468 }
469
470 return (0);
471 }
472
473 static int
474 krrp_do_sess_action(int argc, char **argv, krrp_cmd_t *cmd)
475 {
476 int c, rc = 0;
477 uuid_t sess_id;
478 uint32_t limit = UINT32_MAX;
479 boolean_t run_once = B_FALSE;
480 const char *opts = "hs:";
481
482 uuid_clear(sess_id);
483
484 switch (cmd->item) {
485 case KRRP_CMD_SESS_RUN:
486 opts = "hs:o";
487 break;
488 case KRRP_CMD_SESS_CONN_THROTTLE:
489 opts = "hs:l:";
490 break;
491 case KRRP_CMD_SESS_SEND_STOP:
492 break;
493 case KRRP_CMD_SESS_DESTROY:
494 break;
495 default:
496 fprintf_err("Unknown cmd_item: [%d]\n", cmd->item);
497 assert(0);
498 }
499
500 while ((c = getopt(argc, argv, opts)) != -1) {
501 switch (c) {
502 case 's':
503 if (krrp_parse_and_check_sess_id(optarg, sess_id) != 0)
504 exit(1);
505
506 break;
507 case 'l':
508 if (limit != UINT32_MAX) {
509 krrp_print_err_already_defined("l");
510 exit(1);
511 }
512
513 limit = strtoul(optarg, NULL, 0);
514 if (limit < KRRP_MIN_CONN_THROTTLE && limit != 0) {
515 fprintf_err("Limit must be 0 or > %d\n",
516 KRRP_MIN_CONN_THROTTLE);
517 exit(1);
518 }
519
520 break;
521 case 'o':
522 if (run_once) {
523 krrp_print_err_already_defined("o");
524 exit(1);
525 }
526
527 run_once = B_TRUE;
528 break;
529 case '?':
530 krrp_print_err_unknown_param(argv[optind - 1]);
531 cmd->usage_func(1, cmd, B_FALSE);
532 break;
533 case 'h':
534 cmd->usage_func(0, cmd, B_FALSE);
535 break;
536 }
537 }
538
539 if (uuid_is_null(sess_id) == 1) {
540 krrp_print_err_no_sess_id();
541 cmd->usage_func(1, cmd, B_FALSE);
542 }
543
544 switch (cmd->item) {
545 case KRRP_CMD_SESS_RUN:
546 rc = krrp_sess_run(libkrrp_hdl, sess_id, run_once);
547 if (rc != 0)
548 fprintf_err("Failed to run session\n");
549
550 break;
551 case KRRP_CMD_SESS_CONN_THROTTLE:
552 if (limit == UINT32_MAX) {
553 fprintf_err("The throughput limit is not defined\n");
554 cmd->usage_func(1, cmd, B_FALSE);
555 }
556
557 rc = krrp_sess_conn_throttle(libkrrp_hdl, sess_id, limit);
558 if (rc != 0) {
559 fprintf_err("Failed to throttle "
560 "session's connection\n");
561 }
562
563 break;
564 case KRRP_CMD_SESS_SEND_STOP:
565 rc = krrp_sess_send_stop(libkrrp_hdl, sess_id);
566 if (rc != 0)
567 fprintf_err("Failed to stop sending\n");
568
569 break;
570 case KRRP_CMD_SESS_DESTROY:
571 rc = krrp_sess_destroy(libkrrp_hdl, sess_id);
572 if (rc != 0)
573 fprintf_err("Failed to destroy session\n");
574
575 break;
576 default:
577 break;
578 }
579
580 if (rc != 0) {
581 krrp_print_libkrrp_error();
582 exit(1);
583 }
584
585 return (0);
586 }
587
588 static int
589 krrp_do_sess_get_conn_info(int argc, char **argv, krrp_cmd_t *cmd)
590 {
591 int c, rc = 0;
592 uuid_t sess_id;
593 libkrrp_sess_conn_info_t sess_conn_info;
594 char *sess_id_str = NULL;
595
596 assert(cmd->item == KRRP_CMD_SESS_GET_CONN_INFO);
597
598 uuid_clear(sess_id);
599
600 while ((c = getopt(argc, argv, "hs:")) != -1) {
601 switch (c) {
602 case 's':
603 if (krrp_parse_and_check_sess_id(optarg, sess_id) != 0)
604 exit(1);
605
606 sess_id_str = optarg;
607 break;
608 case '?':
609 krrp_print_err_unknown_param(argv[optind - 1]);
610 cmd->usage_func(1, cmd, B_FALSE);
611 break;
612 case 'h':
613 cmd->usage_func(0, cmd, B_FALSE);
614 break;
615 }
616 }
617
618 if (uuid_is_null(sess_id) == 1) {
619 krrp_print_err_no_sess_id();
620 cmd->usage_func(1, cmd, B_FALSE);
621 }
622
623 rc = krrp_sess_get_conn_info(libkrrp_hdl, sess_id, &sess_conn_info);
624 if (rc != 0) {
625 fprintf_err("Failed to get information about connection\n");
626 krrp_print_libkrrp_error();
627 exit(1);
628 }
629
630 fprintf_msg("Session: [%s]\n"
631 " block size: %u\n",
632 sess_id_str,
633 sess_conn_info.blk_sz);
634
635 fprintf_msg("\n");
636
637 return (0);
638 }
639
640 static int
641 krrp_do_sess_status(int argc, char **argv, krrp_cmd_t *cmd)
642 {
643 int c, rc = 0;
644 uuid_t sess_id;
645 libkrrp_sess_status_t sess_status;
646 libkrrp_error_descr_t err_desc;
647 char *sess_id_str = NULL;
648
649 assert(cmd->item == KRRP_CMD_SESS_STATUS);
650
651 uuid_clear(sess_id);
652
653 while ((c = getopt(argc, argv, "hs:")) != -1) {
654 switch (c) {
655 case 's':
656 if (krrp_parse_and_check_sess_id(optarg, sess_id) != 0)
657 exit(1);
658
659 sess_id_str = optarg;
660 break;
661 case '?':
662 krrp_print_err_unknown_param(argv[optind - 1]);
663 cmd->usage_func(1, cmd, B_FALSE);
664 break;
665 case 'h':
666 cmd->usage_func(0, cmd, B_FALSE);
667 break;
668 }
669 }
670
671 if (uuid_is_null(sess_id) == 1) {
672 krrp_print_err_no_sess_id();
673 cmd->usage_func(1, cmd, B_FALSE);
674 }
675
676 rc = krrp_sess_status(libkrrp_hdl, sess_id, &sess_status);
677 if (rc != 0) {
678 fprintf_err("Failed to get session status\n");
679 krrp_print_libkrrp_error();
680 exit(1);
681 }
682
683 fprintf_msg("Session: [%s]\n"
684 " kstat ID: %s\n"
685 " type: %s\n"
686 " started: %s\n"
687 " running: %s\n",
688 sess_id_str,
689 sess_status.sess_kstat_id,
690 sess_status.sess_type == LIBKRRP_SESS_TYPE_SENDER ? "sender" :
691 sess_status.sess_type == LIBKRRP_SESS_TYPE_RECEIVER ? "receiver" :
692 "compound",
693 sess_status.sess_started ? "YES" : "NO",
694 sess_status.sess_running ? "YES" : "NO",
695 err_desc);
696
697 if (sess_status.libkrrp_error.libkrrp_errno != LIBKRRP_ERRNO_OK) {
698 libkrrp_sess_error_description(&sess_status.libkrrp_error,
699 err_desc);
700
701 fprintf_msg(" error: %s\n", err_desc);
702 }
703
704 fprintf_msg("\n");
705
706 return (0);
707 }
708
709 static int
710 krrp_do_sess_list(int argc, char **argv, krrp_cmd_t *cmd)
711 {
712 int c, rc = 0;
713 libkrrp_sess_list_t *sess_list = NULL, *sess_list_head = NULL;
714
715 while ((c = getopt(argc, argv, "h")) != -1) {
716 switch (c) {
717 case '?':
718 krrp_print_err_unknown_param(argv[optind - 1]);
719 cmd->usage_func(1, cmd, B_FALSE);
720 break;
721 case 'h':
722 cmd->usage_func(0, cmd, B_FALSE);
723 break;
724 }
725 }
726
727 rc = krrp_sess_list(libkrrp_hdl, &sess_list_head);
728 if (rc != 0) {
729 fprintf_err("Failed to get list of sessions\n");
730 krrp_print_libkrrp_error();
731 exit(1);
732 }
733
734 sess_list = sess_list_head;
735 while (sess_list != NULL) {
736 char sess_id_str[UUID_PRINTABLE_STRING_LENGTH];
737
738 uuid_unparse(sess_list->sess_id, sess_id_str);
739
740 fprintf_msg("Session: [%s]\n"
741 " kstat ID: %s\n"
742 " started: %s\n"
743 " running: %s\n\n",
744 sess_id_str,
745 sess_list->sess_kstat_id,
746 sess_list->sess_started ? "YES" : "NO",
747 sess_list->sess_running ? "YES" : "NO");
748
749 sess_list = sess_list->sl_next;
750 }
751
752 krrp_sess_list_free(sess_list_head);
753 return (0);
754 }
755
756 static int
757 krrp_do_sess_create_conn(int argc, char **argv, krrp_cmd_t *cmd)
758 {
759 int c, i, rc = 0;
760 uuid_t sess_id;
761 const char *remote_addr = NULL;
762 uint16_t remote_port = 0;
763 uint32_t timeout = 0;
764
765 uuid_clear(sess_id);
766
767 while ((c = getopt(argc, argv, "hs:a:p:t:")) != -1) {
768 switch (c) {
769 case 's':
770 if (krrp_parse_and_check_sess_id(optarg, sess_id) != 0)
771 exit(1);
772
773 break;
774 case 'a':
775 if (remote_addr != NULL) {
776 krrp_print_err_already_defined("a");
777 exit(1);
778 }
779
780 remote_addr = optarg;
781 break;
782 case 'p':
783 if (remote_port != 0) {
784 krrp_print_err_already_defined("p");
785 exit(1);
786 }
787
788 i = strtol(optarg, NULL, 0);
789 if (i < KRRP_MIN_PORT || i > KRRP_MAX_PORT) {
790 fprintf_err("Port number must be an "
791 "integer in range from %d to %d\n",
792 KRRP_MIN_PORT, KRRP_MAX_PORT);
793 exit(1);
794 }
795
796 remote_port = i;
797 break;
798 case 't':
799 if (timeout != 0) {
800 krrp_print_err_already_defined("t");
801 exit(1);
802 }
803
804 i = strtol(optarg, NULL, 0);
805 if (i < KRRP_MIN_CONN_TIMEOUT ||
806 i > KRRP_MAX_CONN_TIMEOUT) {
807 fprintf_err("Connection timeout "
808 "must be an integer in range from "
809 "%d to %d\n", KRRP_MIN_CONN_TIMEOUT,
810 KRRP_MAX_CONN_TIMEOUT);
811 exit(1);
812 }
813
814 timeout = i;
815 break;
816 case '?':
817 krrp_print_err_unknown_param(argv[optind - 1]);
818 cmd->usage_func(1, cmd, B_FALSE);
819 break;
820 case 'h':
821 cmd->usage_func(0, cmd, B_FALSE);
822 break;
823 }
824 }
825
826 if (uuid_is_null(sess_id) == 1) {
827 krrp_print_err_no_sess_id();
828 cmd->usage_func(1, cmd, B_FALSE);
829 }
830
831 if (remote_addr == NULL) {
832 fprintf_err("Remote host is not defined\n");
833 cmd->usage_func(1, cmd, B_FALSE);
834 }
835
836 if (remote_port == 0) {
837 fprintf_err("Remote port is not defined\n");
838 cmd->usage_func(1, cmd, B_FALSE);
839 }
840
841 rc = krrp_sess_create_conn(libkrrp_hdl, sess_id,
842 remote_addr, remote_port, timeout);
843 if (rc != 0) {
844 fprintf_err("Failed to create connection\n");
845 krrp_print_libkrrp_error();
846 exit(1);
847 }
848
849 return (0);
850 }
851
852 static int
853 krrp_do_sess_create_read_stream(int argc, char **argv, krrp_cmd_t *cmd)
854 {
855 int c, i, rc = 0;
856 uuid_t sess_id;
857 uint64_t fake_data_sz = 0;
858 char *dataset = NULL, *common_snap = NULL, *src_snap = NULL,
859 *resume_token = NULL, *skip_snaps_mask = NULL;
860 krrp_sess_stream_flags_t flags = 0;
861 uint32_t keep_snaps = 0;
862
863 uuid_clear(sess_id);
864
865 while ((c = getopt(argc, argv, "hs:d:c:z:Irpejbkf:t:n:m:")) != -1) {
866 switch (c) {
867 case 's':
868 if (krrp_parse_and_check_sess_id(optarg, sess_id) != 0)
869 exit(1);
870
871 break;
872 case 'd':
873 if (dataset != NULL) {
874 krrp_print_err_already_defined("d");
875 exit(1);
876 }
877
878 dataset = optarg;
879 break;
880 case 'z':
881 if (src_snap != NULL) {
882 krrp_print_err_already_defined("z");
883 exit(1);
884 }
885
886 src_snap = optarg;
887 break;
888 case 'c':
889 if (common_snap != NULL) {
890 krrp_print_err_already_defined("c");
891 exit(1);
892 }
893
894 common_snap = optarg;
895 break;
896 case 'I':
897 if ((flags & KRRP_STREAM_INCLUDE_ALL_SNAPS) != 0) {
898 krrp_print_err_already_defined("I");
899 exit(1);
900 }
901
902 flags |= KRRP_STREAM_INCLUDE_ALL_SNAPS;
903 break;
904 case 'r':
905 if ((flags & KRRP_STREAM_SEND_RECURSIVE) != 0) {
906 krrp_print_err_already_defined("r");
907 exit(1);
908 }
909
910 flags |= KRRP_STREAM_SEND_RECURSIVE;
911 break;
912 case 'p':
913 if ((flags & KRRP_STREAM_SEND_PROPERTIES) != 0) {
914 krrp_print_err_already_defined("p");
915 exit(1);
916 }
917
918 flags |= KRRP_STREAM_SEND_PROPERTIES;
919 break;
920 case 'e':
921 if ((flags & KRRP_STREAM_ZFS_EMBEDDED) != 0) {
922 krrp_print_err_already_defined("e");
923 exit(1);
924 }
925
926 flags |= KRRP_STREAM_ZFS_EMBEDDED;
927 break;
928 case 'j':
929 if ((flags & KRRP_STREAM_ZFS_COMPRESSED) != 0) {
930 krrp_print_err_already_defined("j");
931 exit(1);
932 }
933
934 flags |= KRRP_STREAM_ZFS_COMPRESSED;
935 break;
936 case 'b':
937 if ((flags & KRRP_STREAM_ZFS_LARGE_BLOCKS) != 0) {
938 krrp_print_err_already_defined("b");
939 exit(1);
940 }
941
942 flags |= KRRP_STREAM_ZFS_LARGE_BLOCKS;
943 break;
944 case 'k':
945 if ((flags & KRRP_STREAM_ZFS_CHKSUM) != 0) {
946 krrp_print_err_already_defined("k");
947 exit(1);
948 }
949
950 flags |= KRRP_STREAM_ZFS_CHKSUM;
951 break;
952 case 'f':
953 if (fake_data_sz != 0) {
954 krrp_print_err_already_defined("f");
955 exit(1);
956 }
957
958 fake_data_sz = strtoull(optarg, NULL, 0);
959 if (fake_data_sz == 0) {
960 fprintf_err("Fake data size must >0\n");
961 exit(1);
962 }
963
964 break;
965 case 't':
966 if (resume_token != NULL) {
967 krrp_print_err_already_defined("t");
968 exit(1);
969 }
970
971 resume_token = optarg;
972 break;
973 case 'n':
974 if (keep_snaps != 0) {
975 krrp_print_err_already_defined("n");
976 exit(1);
977 }
978
979 i = strtol(optarg, NULL, 0);
980 if (i < KRRP_MIN_KEEP_SNAPS ||
981 i > KRRP_MAX_KEEP_SNAPS) {
982 fprintf_err("Maximum number of snapshots that "
983 "will be kept must be an integer in range "
984 "from %d to %d\n", KRRP_MIN_KEEP_SNAPS,
985 KRRP_MAX_KEEP_SNAPS);
986 exit(1);
987 }
988
989 keep_snaps = i;
990 break;
991 case 'm':
992 if (skip_snaps_mask != NULL) {
993 krrp_print_err_already_defined("m");
994 exit(1);
995 }
996
997 skip_snaps_mask = optarg;
998 break;
999 case '?':
1000 krrp_print_err_unknown_param(argv[optind - 1]);
1001 cmd->usage_func(1, cmd, B_FALSE);
1002 break;
1003 case 'h':
1004 cmd->usage_func(0, cmd, B_FALSE);
1005 break;
1006 }
1007 }
1008
1009 if (uuid_is_null(sess_id) == 1) {
1010 krrp_print_err_no_sess_id();
1011 cmd->usage_func(1, cmd, B_FALSE);
1012 }
1013
1014 if (dataset == NULL) {
1015 fprintf_err("Source dataset must be specified\n");
1016 cmd->usage_func(1, cmd, B_FALSE);
1017 }
1018
1019 if (keep_snaps == 0)
1020 keep_snaps = UINT32_MAX;
1021
1022 rc = krrp_sess_create_read_stream(libkrrp_hdl, sess_id,
1023 dataset, common_snap, src_snap, fake_data_sz, flags,
1024 resume_token, keep_snaps, skip_snaps_mask);
1025 if (rc != 0) {
1026 fprintf_err("Failed to create read-stream\n");
1027 krrp_print_libkrrp_error();
1028 exit(1);
1029 }
1030
1031 return (0);
1032 }
1033
1034 static int
1035 krrp_do_sess_create_write_stream(int argc, char **argv, krrp_cmd_t *cmd)
1036 {
1037 int c, i, rc = 0;
1038 uuid_t sess_id;
1039 nvlist_t *ignore_props_list, *replace_props_list;
1040 char *dataset = NULL, *common_snap = NULL, *resume_token = NULL;
1041 krrp_sess_stream_flags_t flags = 0;
1042 uint32_t keep_snaps = 0;
1043
1044 ignore_props_list = fnvlist_alloc();
1045 replace_props_list = fnvlist_alloc();
1046
1047 uuid_clear(sess_id);
1048
1049 while ((c = getopt(argc, argv, "hs:d:c:Fki:o:t:n:lx")) != -1) {
1050 switch (c) {
1051 case 's':
1052 if (krrp_parse_and_check_sess_id(optarg, sess_id) != 0)
1053 exit(1);
1054
1055 break;
1056 case 'd':
1057 if (dataset != NULL) {
1058 krrp_print_err_already_defined("d");
1059 exit(1);
1060 }
1061
1062 dataset = optarg;
1063 break;
1064 case 'c':
1065 if (common_snap != NULL) {
1066 krrp_print_err_already_defined("c");
1067 exit(1);
1068 }
1069
1070 common_snap = optarg;
1071 break;
1072 case 'i':
1073 if (nvlist_exists(ignore_props_list, optarg)) {
1074 (void) fprintf(stderr, "The property '%s' "
1075 "already defined\n", optarg);
1076 exit(1);
1077 }
1078
1079 fnvlist_add_boolean_value(ignore_props_list,
1080 optarg, B_TRUE);
1081 break;
1082 case 'o':
1083 {
1084 char *p;
1085
1086 p = strchr(optarg, '=');
1087 if (p == NULL || *(p + 1) == '\0') {
1088 (void) fprintf(stderr, "Incorrect "
1089 "argument of '-o' parameter\n");
1090 exit(1);
1091 }
1092
1093 *p = '\0'; p++;
1094
1095 if (nvlist_exists(replace_props_list, optarg)) {
1096 (void) fprintf(stderr, "The property "
1097 "'%s' already defined\n", optarg);
1098 exit(1);
1099 }
1100
1101 fnvlist_add_string(replace_props_list,
1102 optarg, p);
1103 p--; *p = '=';
1104 }
1105 break;
1106 case 'F':
1107 if ((flags & KRRP_STREAM_FORCE_RECEIVE) != 0) {
1108 krrp_print_err_already_defined("F");
1109 exit(1);
1110 }
1111
1112 flags |= KRRP_STREAM_FORCE_RECEIVE;
1113 break;
1114 case 'k':
1115 if ((flags & KRRP_STREAM_ZFS_CHKSUM) != 0) {
1116 krrp_print_err_already_defined("k");
1117 exit(1);
1118 }
1119
1120 flags |= KRRP_STREAM_ZFS_CHKSUM;
1121 break;
1122 case 't':
1123 if (resume_token != NULL) {
1124 krrp_print_err_already_defined("t");
1125 exit(1);
1126 }
1127
1128 resume_token = optarg;
1129 break;
1130 case 'n':
1131 if (keep_snaps != 0) {
1132 krrp_print_err_already_defined("n");
1133 exit(1);
1134 }
1135
1136 i = strtol(optarg, NULL, 0);
1137 if (i < KRRP_MIN_KEEP_SNAPS ||
1138 i > KRRP_MAX_KEEP_SNAPS) {
1139 fprintf_err("Maximum number of snapshots that "
1140 "will be kept must be an integer in range "
1141 "from %d to %d\n", KRRP_MIN_KEEP_SNAPS,
1142 KRRP_MAX_KEEP_SNAPS);
1143 exit(1);
1144 }
1145
1146 keep_snaps = i;
1147 break;
1148 case 'l':
1149 if ((flags & KRRP_STREAM_LEAVE_TAIL) != 0) {
1150 krrp_print_err_already_defined("l");
1151 exit(1);
1152 }
1153
1154 flags |= KRRP_STREAM_LEAVE_TAIL;
1155 break;
1156 case 'x':
1157 if ((flags & KRRP_STREAM_DISCARD_HEAD) != 0) {
1158 krrp_print_err_already_defined("x");
1159 exit(1);
1160 }
1161
1162 flags |= KRRP_STREAM_DISCARD_HEAD;
1163 break;
1164 case '?':
1165 krrp_print_err_unknown_param(argv[optind - 1]);
1166 cmd->usage_func(1, cmd, B_FALSE);
1167 break;
1168 case 'h':
1169 cmd->usage_func(0, cmd, B_FALSE);
1170 break;
1171 }
1172 }
1173
1174 if (((flags & KRRP_STREAM_DISCARD_HEAD) != 0) &&
1175 ((flags & KRRP_STREAM_LEAVE_TAIL) != 0)) {
1176 fprintf_err("Parameters 'x' and 'l' cannot "
1177 "be used together\n");
1178 exit(1);
1179 }
1180
1181
1182 if (uuid_is_null(sess_id) == 1) {
1183 krrp_print_err_no_sess_id();
1184 cmd->usage_func(1, cmd, B_FALSE);
1185 }
1186
1187 if (dataset == NULL) {
1188 fprintf_err("Destination dataset must be specified\n");
1189 cmd->usage_func(1, cmd, B_FALSE);
1190 }
1191
1192 if (nvlist_empty(ignore_props_list)) {
1193 fnvlist_free(ignore_props_list);
1194 ignore_props_list = NULL;
1195 }
1196
1197 if (nvlist_empty(replace_props_list)) {
1198 fnvlist_free(replace_props_list);
1199 replace_props_list = NULL;
1200 }
1201
1202 if (keep_snaps == 0)
1203 keep_snaps = UINT32_MAX;
1204
1205 rc = krrp_sess_create_write_stream(libkrrp_hdl, sess_id,
1206 dataset, common_snap, flags, ignore_props_list,
1207 replace_props_list, resume_token, keep_snaps);
1208 if (rc != 0) {
1209 fprintf_err("Failed to create write stream\n");
1210 krrp_print_libkrrp_error();
1211 exit(1);
1212 }
1213
1214 return (0);
1215 }
1216
1217 static int
1218 krrp_do_sess_create_pdu_engine(int argc, char **argv, krrp_cmd_t *cmd)
1219 {
1220 int c, i, rc = 0;
1221 uuid_t sess_id;
1222 int mem_limit = 0, dblk_sz = 0;
1223 boolean_t use_prealloc = B_FALSE;
1224
1225 uuid_clear(sess_id);
1226
1227 while ((c = getopt(argc, argv, "hs:m:b:a")) != -1) {
1228 switch (c) {
1229 case 's':
1230 if (krrp_parse_and_check_sess_id(optarg, sess_id) != 0)
1231 exit(1);
1232
1233 break;
1234 case 'm':
1235 if (mem_limit != 0) {
1236 krrp_print_err_already_defined("m");
1237 exit(1);
1238 }
1239
1240 i = strtol(optarg, NULL, 0);
1241 if (i < KRRP_MIN_MAXMEM || i > 16384) {
1242 fprintf_err("Maximum memory size "
1243 "must be an integer in range from "
1244 "%d to 16384\n", KRRP_MIN_MAXMEM);
1245 exit(1);
1246 }
1247
1248 mem_limit = i;
1249 break;
1250 case 'b':
1251 if (dblk_sz != 0) {
1252 krrp_print_err_already_defined("b");
1253 exit(1);
1254 }
1255
1256 i = strtol(optarg, NULL, 0);
1257 if (i < KRRP_MIN_SESS_PDU_DBLK_DATA_SZ ||
1258 i > KRRP_MAX_SESS_PDU_DBLK_DATA_SZ) {
1259 fprintf_err("DBLK data size must "
1260 "be an integer in range from "
1261 "%d to %d\n",
1262 KRRP_MIN_SESS_PDU_DBLK_DATA_SZ,
1263 KRRP_MAX_SESS_PDU_DBLK_DATA_SZ);
1264 exit(1);
1265 }
1266
1267 dblk_sz = i;
1268 break;
1269 case 'a':
1270 if (use_prealloc) {
1271 krrp_print_err_already_defined("a");
1272 exit(1);
1273 }
1274
1275 use_prealloc = B_TRUE;
1276 break;
1277 case '?':
1278 krrp_print_err_unknown_param(argv[optind - 1]);
1279 cmd->usage_func(1, cmd, B_FALSE);
1280 break;
1281 case 'h':
1282 cmd->usage_func(0, cmd, B_FALSE);
1283 break;
1284 }
1285 }
1286
1287 if (uuid_is_null(sess_id) == 1) {
1288 krrp_print_err_no_sess_id();
1289 cmd->usage_func(1, cmd, B_FALSE);
1290 }
1291
1292 if (dblk_sz == 0) {
1293 fprintf_err("DBLK Data size is not defined\n");
1294 cmd->usage_func(1, cmd, B_FALSE);
1295 }
1296
1297 if (mem_limit == 0) {
1298 fprintf_err("Maximum memory is not defined\n");
1299 cmd->usage_func(1, cmd, B_FALSE);
1300 }
1301
1302 rc = krrp_sess_create_pdu_engine(libkrrp_hdl, sess_id,
1303 mem_limit, dblk_sz, use_prealloc);
1304 if (rc != 0) {
1305 fprintf_err("Failed to create pdu engine\n");
1306 krrp_print_libkrrp_error();
1307 exit(1);
1308 }
1309
1310 return (0);
1311 }
1312
1313 static int
1314 krrp_do_ksvc_action(int argc, char **argv, krrp_cmd_t *cmd)
1315 {
1316 int rc = 0;
1317 const char *action;
1318
1319 assert(cmd->item == KRRP_CMD_KSVC_ENABLE ||
1320 cmd->item == KRRP_CMD_KSVC_DISABLE);
1321
1322 if (argc > 1) {
1323 if (strcmp(argv[1], "-h") == 0) {
1324 cmd->usage_func(0, cmd, B_FALSE);
1325 } else {
1326 fprintf_err("Unknown params\n");
1327 cmd->usage_func(1, cmd, B_FALSE);
1328 }
1329 }
1330
1331 if (cmd->item == KRRP_CMD_KSVC_ENABLE) {
1332 rc = krrp_svc_enable(libkrrp_hdl);
1333 action = "enable";
1334 } else {
1335 rc = krrp_svc_disable(libkrrp_hdl);
1336 action = "disable";
1337 }
1338
1339 if (rc != 0) {
1340 fprintf_err("Failed to %s in kernel service\n", action);
1341 krrp_print_libkrrp_error();
1342 exit(1);
1343 }
1344
1345 return (0);
1346 }
1347
1348 /* ARGSUSED */
1349 static int
1350 krrp_do_svc_get_state(int argc, char **argv, krrp_cmd_t *cmd)
1351 {
1352 int rc;
1353 libkrrp_svc_state_t svc_state;
1354
1355 assert(cmd->item == KRRP_CMD_KSVC_STATE);
1356
1357 rc = krrp_svc_state(libkrrp_hdl, &svc_state);
1358 if (rc != 0) {
1359 fprintf_err("Failed to get state of in-kernel service\n");
1360 krrp_print_libkrrp_error();
1361 exit(1);
1362 }
1363
1364 fprintf_msg("KSVC-ENABLED: %s, KSRV-RUNNING: %s\n",
1365 (svc_state.enabled ? "YES" : "NO"),
1366 (svc_state.running ? "YES" : "NO"));
1367
1368 return (0);
1369 }
1370
1371 static int
1372 krrp_do_ksvc_configure(int argc, char **argv, krrp_cmd_t *cmd)
1373 {
1374 int c, rc, port = -1;
1375 const char *addr = NULL;
1376
1377 assert(cmd->item == KRRP_CMD_KSVC_CONFIGURE);
1378
1379 while ((c = getopt(argc, argv, "hp:a:")) != -1) {
1380 switch (c) {
1381 case 'p':
1382 if (port != -1) {
1383 krrp_print_err_already_defined("p");
1384 exit(1);
1385 }
1386
1387 port = strtol(optarg, NULL, 0);
1388 if (port < KRRP_MIN_PORT || port > KRRP_MAX_PORT) {
1389 fprintf_err("Port number must be "
1390 "an integer in range from %d to %d\n",
1391 KRRP_MIN_PORT, KRRP_MAX_PORT);
1392 exit(1);
1393 }
1394
1395 break;
1396 case 'a':
1397 if (addr != NULL) {
1398 krrp_print_err_already_defined("a");
1399 exit(1);
1400 }
1401
1402 addr = optarg;
1403 break;
1404 case '?':
1405 krrp_print_err_unknown_param(argv[optind - 1]);
1406 cmd->usage_func(1, cmd, B_FALSE);
1407 break;
1408 case 'h':
1409 cmd->usage_func(0, cmd, B_FALSE);
1410 break;
1411 }
1412 }
1413
1414 if (port == -1) {
1415 fprintf_err("Listening port number is not defined\n");
1416 exit(1);
1417 }
1418
1419 rc = krrp_set_srv_config(libkrrp_hdl, addr, port);
1420 if (rc != 0) {
1421 fprintf_err("Failed to configure in-kernel service\n");
1422 krrp_print_libkrrp_error();
1423 exit(1);
1424 }
1425
1426 return (0);
1427 }
1428
1429 static int
1430 krrp_parse_and_check_sess_id(char *sess_id_str, uuid_t sess_id)
1431 {
1432 if (uuid_is_null(sess_id) != 1) {
1433 fprintf_err("Session ID already defined\n");
1434 return (-1);
1435 }
1436
1437 if (uuid_parse(sess_id_str, sess_id) != 0) {
1438 fprintf_err("Failed to parse Session ID\n");
1439 return (-1);
1440 }
1441
1442 return (0);
1443 }
1444
1445 static void
1446 krrp_print_err_already_defined(const char *param)
1447 {
1448 fprintf_err("The parameter '%s' already defined\n", param);
1449 }
1450
1451 static void
1452 krrp_print_err_unknown_param(const char *param)
1453 {
1454 fprintf_err("Unknown parameter '%s'\n", param);
1455 }
1456
1457 static void
1458 krrp_print_err_no_sess_id(void)
1459 {
1460 fprintf_err("Session ID is not defined\n");
1461 }
1462
1463 static void
1464 krrp_print_libkrrp_error(void)
1465 {
1466 fprintf_err("%s\n", libkrrp_error_description(libkrrp_hdl));
1467 }
1468
1469 static void
1470 fprintf_err(const char *fmt, ...)
1471 {
1472 va_list ap;
1473
1474 va_start(ap, fmt);
1475 (void) vfprintf(stderr, fmt, ap);
1476 va_end(ap);
1477 }
1478
1479 static void
1480 fprintf_msg(const char *fmt, ...)
1481 {
1482 va_list ap;
1483
1484 va_start(ap, fmt);
1485 (void) vfprintf(stdout, fmt, ap);
1486 va_end(ap);
1487 }