Annotation of embedaddon/mtr/ui/cmdpipe.c, revision 1.1.1.1
1.1 misho 1: /*
2: mtr -- a network diagnostic tool
3: Copyright (C) 2016 Matt Kimball
4:
5: This program is free software; you can redistribute it and/or modify
6: it under the terms of the GNU General Public License version 2 as
7: published by the Free Software Foundation.
8:
9: This program is distributed in the hope that it will be useful,
10: but WITHOUT ANY WARRANTY; without even the implied warranty of
11: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12: GNU General Public License for more details.
13:
14: You should have received a copy of the GNU General Public License
15: along with this program; if not, write to the Free Software
16: Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17: */
18:
19: #include "cmdpipe.h"
20:
21: #include <assert.h>
22: #include <errno.h>
23: #include <fcntl.h>
24: #include <signal.h>
25: #include <stdbool.h>
26: #include <stdio.h>
27: #include <stdlib.h>
28: #include <string.h>
29: #include <strings.h>
30: #include <sys/wait.h>
31: #include <unistd.h>
32:
33: #ifdef HAVE_ERROR_H
34: #include <error.h>
35: #else
36: #include "portability/error.h"
37: #endif
38:
39: #include "packet/cmdparse.h"
40: #include "display.h"
41:
42:
43: /* Set a file descriptor to non-blocking */
44: static
45: void set_fd_nonblock(
46: int fd)
47: {
48: int flags;
49:
50: /* Get the current flags of the file descriptor */
51: flags = fcntl(fd, F_GETFL, 0);
52: if (flags == -1) {
53: error(EXIT_FAILURE, errno, "F_GETFL failure");
54: exit(1);
55: }
56:
57: /* Add the O_NONBLOCK bit to the current flags */
58: if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) {
59: error(EXIT_FAILURE, errno, "Failure to set O_NONBLOCK");
60: exit(1);
61: }
62: }
63:
64:
65: /*
66: Send a command synchronously to mtr-packet, blocking until a result
67: is available. This is intended to be used at start-up to check the
68: capabilities of mtr-packet, but probes should be sent asynchronously
69: to avoid blocking other probes and the user interface.
70: */
71: static
72: int send_synchronous_command(
73: struct mtr_ctl *ctl,
74: struct packet_command_pipe_t *cmdpipe,
75: const char *cmd,
76: struct command_t *result)
77: {
78: char reply[PACKET_REPLY_BUFFER_SIZE];
79: int command_length;
80: int write_length;
81: int read_length;
82:
83: /* Query send-probe support */
84: command_length = strlen(cmd);
85: write_length = write(cmdpipe->write_fd, cmd, command_length);
86:
87: if (write_length == -1) {
88: return -1;
89: }
90:
91: if (write_length != command_length) {
92: errno = EIO;
93: return -1;
94: }
95:
96: /* Read the reply to our query */
97: read_length =
98: read(cmdpipe->read_fd, reply, PACKET_REPLY_BUFFER_SIZE - 1);
99:
100: if (read_length < 0) {
101: return -1;
102: }
103:
104: /* Parse the query reply */
105: reply[read_length] = 0;
106: if (parse_command(result, reply)) {
107: return -1;
108: }
109:
110: return 0;
111: }
112:
113:
114: /* Check support for a particular feature with the mtr-packet we invoked */
115: static
116: int check_feature(
117: struct mtr_ctl *ctl,
118: struct packet_command_pipe_t *cmdpipe,
119: const char *feature)
120: {
121: char check_command[COMMAND_BUFFER_SIZE];
122: struct command_t reply;
123:
124: snprintf(check_command, COMMAND_BUFFER_SIZE,
125: "1 check-support feature %s\n", feature);
126:
127: if (send_synchronous_command(ctl, cmdpipe, check_command, &reply) ==
128: -1) {
129: return -1;
130: }
131:
132: /* Check that the feature is supported */
133: if (!strcmp(reply.command_name, "feature-support")
134: && reply.argument_count >= 1
135: && !strcmp(reply.argument_name[0], "support")
136: && !strcmp(reply.argument_value[0], "ok")) {
137:
138: /* Looks good */
139: return 0;
140: }
141:
142: errno = ENOTSUP;
143: return -1;
144: }
145:
146:
147: /*
148: Check the protocol selected against the mtr-packet we are using.
149: Returns zero if everything is fine, or -1 with errno for either
150: a failure during the check, or for an unsupported feature.
151: */
152: static
153: int check_packet_features(
154: struct mtr_ctl *ctl,
155: struct packet_command_pipe_t *cmdpipe)
156: {
157: /* Check the IP protocol version */
158: if (ctl->af == AF_INET6) {
159: if (check_feature(ctl, cmdpipe, "ip-6")) {
160: return -1;
161: }
162: } else if (ctl->af == AF_INET) {
163: if (check_feature(ctl, cmdpipe, "ip-4")) {
164: return -1;
165: }
166: } else {
167: errno = EINVAL;
168: return -1;
169: }
170:
171: /* Check the transport protocol */
172: if (ctl->mtrtype == IPPROTO_ICMP) {
173: if (check_feature(ctl, cmdpipe, "icmp")) {
174: return -1;
175: }
176: } else if (ctl->mtrtype == IPPROTO_UDP) {
177: if (check_feature(ctl, cmdpipe, "udp")) {
178: return -1;
179: }
180: } else if (ctl->mtrtype == IPPROTO_TCP) {
181: if (check_feature(ctl, cmdpipe, "tcp")) {
182: return -1;
183: }
184: #ifdef HAS_SCTP
185: } else if (ctl->mtrtype == IPPROTO_SCTP) {
186: if (check_feature(ctl, cmdpipe, "sctp")) {
187: return -1;
188: }
189: #endif
190: } else {
191: errno = EINVAL;
192: return -1;
193: }
194:
195: #ifdef SO_MARK
196: if (ctl->mark) {
197: if (check_feature(ctl, cmdpipe, "mark")) {
198: return -1;
199: }
200: }
201: #endif
202:
203: return 0;
204: }
205:
206:
207: /*
208: Execute mtr-packet, allowing the MTR_PACKET evironment to override
209: the PATH when locating the executable.
210: */
211: static
212: void execute_packet_child(
213: void)
214: {
215: /*
216: Allow the MTR_PACKET environment variable to override
217: the path to the mtr-packet executable. This is necessary
218: for debugging changes for mtr-packet.
219: */
220: char *mtr_packet_path = getenv("MTR_PACKET");
221: if (mtr_packet_path == NULL) {
222: mtr_packet_path = "mtr-packet";
223: }
224:
225: /*
226: First, try to execute mtr-packet from PATH
227: or MTR_PACKET environment variable.
228: */
229: execlp(mtr_packet_path, "mtr-packet", (char *) NULL);
230:
231: /*
232: If mtr-packet is not found, try to use mtr-packet from current directory
233: */
234: execl("./mtr-packet", "./mtr-packet", (char *) NULL);
235:
236: /* Both exec attempts failed, so nothing to do but exit */
237: exit(1);
238: }
239:
240:
241: /* Create the command pipe to a new mtr-packet subprocess */
242: int open_command_pipe(
243: struct mtr_ctl *ctl,
244: struct packet_command_pipe_t *cmdpipe)
245: {
246: int stdin_pipe[2];
247: int stdout_pipe[2];
248: pid_t child_pid;
249: int i;
250:
251: /*
252: We actually need two Unix pipes. One for stdin and one for
253: stdout on the new process.
254: */
255: if (pipe(stdin_pipe) || pipe(stdout_pipe)) {
256: return errno;
257: }
258:
259: child_pid = fork();
260: if (child_pid == -1) {
261: return errno;
262: }
263:
264: if (child_pid == 0) {
265: /*
266: In the child process, attach our created pipes to stdin
267: and stdout
268: */
269: dup2(stdin_pipe[0], STDIN_FILENO);
270: dup2(stdout_pipe[1], STDOUT_FILENO);
271:
272: /* Close all unnecessary fds */
273: for (i = STDERR_FILENO + 1; i <= stdout_pipe[1]; i++) {
274: close(i);
275: }
276:
277: execute_packet_child();
278: } else {
279: memset(cmdpipe, 0, sizeof(struct packet_command_pipe_t));
280:
281: /*
282: In the parent process, save the opposite ends of the pipes
283: attached as stdin and stdout in the child.
284: */
285: cmdpipe->pid = child_pid;
286: cmdpipe->read_fd = stdout_pipe[0];
287: cmdpipe->write_fd = stdin_pipe[1];
288:
289: /* We don't need the child ends of the pipe open in the parent. */
290: close(stdout_pipe[1]);
291: close(stdin_pipe[0]);
292:
293: /*
294: Check that we can communicate with the client. If we failed to
295: execute the mtr-packet binary, we will discover that here.
296: */
297: if (check_feature(ctl, cmdpipe, "send-probe")) {
298: error(EXIT_FAILURE, errno, "Failure to start mtr-packet");
299: }
300:
301: if (check_packet_features(ctl, cmdpipe)) {
302: error(EXIT_FAILURE, errno, "Packet type unsupported");
303: }
304:
305: /* We will need non-blocking reads from the child */
306: set_fd_nonblock(cmdpipe->read_fd);
307: }
308:
309: return 0;
310: }
311:
312:
313: /* Kill the mtr-packet child process and close the command pipe */
314: void close_command_pipe(
315: struct packet_command_pipe_t *cmdpipe)
316: {
317: int child_exit_value;
318:
319: if (cmdpipe->pid) {
320: close(cmdpipe->read_fd);
321: close(cmdpipe->write_fd);
322:
323: kill(cmdpipe->pid, SIGTERM);
324: waitpid(cmdpipe->pid, &child_exit_value, 0);
325: }
326:
327: memset(cmdpipe, 0, sizeof(struct packet_command_pipe_t));
328: }
329:
330:
331: /* Start building the command string for the "send-probe" command */
332: static
333: void construct_base_command(
334: struct mtr_ctl *ctl,
335: char *command,
336: int buffer_size,
337: int command_token,
338: ip_t * address,
339: ip_t * localaddress)
340: {
341: char ip_string[INET6_ADDRSTRLEN];
342: char local_ip_string[INET6_ADDRSTRLEN];
343: const char *ip_type;
344: const char *local_ip_type;
345: const char *protocol = NULL;
346:
347: /* Conver the remote IP address to a string */
348: if (inet_ntop(ctl->af, address, ip_string, INET6_ADDRSTRLEN) == NULL) {
349:
350: display_close(ctl);
351: error(EXIT_FAILURE, errno, "invalid remote IP address");
352: }
353:
354: if (inet_ntop(ctl->af, localaddress,
355: local_ip_string, INET6_ADDRSTRLEN) == NULL) {
356:
357: display_close(ctl);
358: error(EXIT_FAILURE, errno, "invalid local IP address");
359: }
360:
361: if (ctl->af == AF_INET6) {
362: ip_type = "ip-6";
363: local_ip_type = "local-ip-6";
364: } else {
365: ip_type = "ip-4";
366: local_ip_type = "local-ip-4";
367: }
368:
369: if (ctl->mtrtype == IPPROTO_ICMP) {
370: protocol = "icmp";
371: } else if (ctl->mtrtype == IPPROTO_UDP) {
372: protocol = "udp";
373: } else if (ctl->mtrtype == IPPROTO_TCP) {
374: protocol = "tcp";
375: #ifdef HAS_SCTP
376: } else if (ctl->mtrtype == IPPROTO_SCTP) {
377: protocol = "sctp";
378: #endif
379: } else {
380: display_close(ctl);
381: error(EXIT_FAILURE, 0,
382: "protocol unsupported by mtr-packet interface");
383: }
384:
385: snprintf(command, buffer_size,
386: "%d send-probe %s %s %s %s protocol %s",
387: command_token,
388: ip_type, ip_string, local_ip_type, local_ip_string, protocol);
389: }
390:
391:
392: /* Append an argument to the "send-probe" command string */
393: static
394: void append_command_argument(
395: char *command,
396: int buffer_size,
397: char *name,
398: int value)
399: {
400: char argument[COMMAND_BUFFER_SIZE];
401: int remaining_size;
402:
403: remaining_size = buffer_size - strlen(command) - 1;
404:
405: snprintf(argument, buffer_size, " %s %d", name, value);
406: strncat(command, argument, remaining_size);
407: }
408:
409:
410: /* Request a new probe from the "mtr-packet" child process */
411: void send_probe_command(
412: struct mtr_ctl *ctl,
413: struct packet_command_pipe_t *cmdpipe,
414: ip_t * address,
415: ip_t * localaddress,
416: int packet_size,
417: int sequence,
418: int time_to_live)
419: {
420: char command[COMMAND_BUFFER_SIZE];
421: int remaining_size;
422: int timeout;
423:
424: construct_base_command(ctl, command, COMMAND_BUFFER_SIZE, sequence,
425: address, localaddress);
426:
427: append_command_argument(command, COMMAND_BUFFER_SIZE, "size",
428: packet_size);
429:
430: append_command_argument(command, COMMAND_BUFFER_SIZE, "bit-pattern",
431: ctl->bitpattern);
432:
433: append_command_argument(command, COMMAND_BUFFER_SIZE, "tos", ctl->tos);
434:
435: append_command_argument(command, COMMAND_BUFFER_SIZE, "ttl",
436: time_to_live);
437:
438: timeout = ctl->probe_timeout / 1000000;
439: append_command_argument(command, COMMAND_BUFFER_SIZE, "timeout",
440: timeout);
441:
442: if (ctl->remoteport) {
443: append_command_argument(command, COMMAND_BUFFER_SIZE, "port",
444: ctl->remoteport);
445: }
446:
447: if (ctl->localport) {
448: append_command_argument(command, COMMAND_BUFFER_SIZE, "local-port",
449: ctl->localport);
450: }
451: #ifdef SO_MARK
452: if (ctl->mark) {
453: append_command_argument(command, COMMAND_BUFFER_SIZE, "mark",
454: ctl->mark);
455: }
456: #endif
457:
458: remaining_size = COMMAND_BUFFER_SIZE - strlen(command) - 1;
459: strncat(command, "\n", remaining_size);
460:
461: /* Send a probe using the mtr-packet subprocess */
462: if (write(cmdpipe->write_fd, command, strlen(command)) == -1) {
463: display_close(ctl);
464: error(EXIT_FAILURE, errno,
465: "mtr-packet command pipe write failure");
466: }
467: }
468:
469:
470: /*
471: Parse a comma separated field of mpls values, filling out the mplslen
472: structure with mpls labels.
473: */
474: static
475: void parse_mpls_values(
476: struct mplslen *mpls,
477: char *value_str)
478: {
479: char *next_value = value_str;
480: char *end_of_value;
481: int value;
482: int label_count = 0;
483: int label_field = 0;
484:
485: while (*next_value) {
486: value = strtol(next_value, &end_of_value, 10);
487:
488: /* Failure to advance means an invalid numeric value */
489: if (end_of_value == next_value) {
490: return;
491: }
492:
493: /* If the next character is not a comma or a NUL, we have
494: an invalid string */
495: if (*end_of_value == ',') {
496: next_value = end_of_value + 1;
497: } else if (*end_of_value == 0) {
498: next_value = end_of_value;
499: } else {
500: return;
501: }
502:
503: /*
504: Store the converted value in the next field of the MPLS
505: structure.
506: */
507: if (label_field == 0) {
508: mpls->label[label_count] = value;
509: } else if (label_field == 1) {
510: mpls->exp[label_count] = value;
511: } else if (label_field == 2) {
512: mpls->s[label_count] = value;
513: } else if (label_field == 3) {
514: mpls->ttl[label_count] = value;
515: }
516:
517: label_field++;
518: if (label_field > 3) {
519: label_field = 0;
520: label_count++;
521: }
522:
523: /*
524: If we've used up all MPLS labels in the structure, return with
525: what we've got
526: */
527: if (label_count >= MAXLABELS) {
528: break;
529: }
530: }
531:
532: mpls->labels = label_count;
533: }
534:
535:
536: /*
537: Extract the IP address and round trip time from a reply to a probe.
538: Returns true if both arguments are found in the reply, false otherwise.
539: */
540: static
541: bool parse_reply_arguments(
542: struct mtr_ctl *ctl,
543: struct command_t *reply,
544: ip_t * fromaddress,
545: int *round_trip_time,
546: struct mplslen *mpls)
547: {
548: bool found_round_trip;
549: bool found_ip;
550: char *arg_name;
551: char *arg_value;
552: int i;
553:
554: *round_trip_time = 0;
555: memset(fromaddress, 0, sizeof(ip_t));
556: memset(mpls, 0, sizeof(struct mplslen));
557:
558: found_ip = false;
559: found_round_trip = false;
560:
561: /* Examine the reply arguments for known values */
562: for (i = 0; i < reply->argument_count; i++) {
563: arg_name = reply->argument_name[i];
564: arg_value = reply->argument_value[i];
565:
566: if (ctl->af == AF_INET6) {
567: /* IPv6 address of the responding host */
568: if (!strcmp(arg_name, "ip-6")) {
569: if (inet_pton(AF_INET6, arg_value, fromaddress)) {
570: found_ip = true;
571: }
572: }
573: } else {
574: /* IPv4 address of the responding host */
575: if (!strcmp(arg_name, "ip-4")) {
576: if (inet_pton(AF_INET, arg_value, fromaddress)) {
577: found_ip = true;
578: }
579: }
580: }
581:
582: /* The round trip time in microseconds */
583: if (!strcmp(arg_name, "round-trip-time")) {
584: errno = 0;
585: *round_trip_time = strtol(arg_value, NULL, 10);
586: if (!errno) {
587: found_round_trip = true;
588: }
589: }
590:
591: /* MPLS labels */
592: if (!strcmp(arg_name, "mpls")) {
593: parse_mpls_values(mpls, arg_value);
594: }
595: }
596:
597: return found_ip && found_round_trip;
598: }
599:
600:
601: /*
602: If an mtr-packet command has returned an error result,
603: report the error and exit.
604: */
605: static
606: void handle_reply_errors(
607: struct mtr_ctl *ctl,
608: struct command_t *reply)
609: {
610: char *reply_name;
611:
612: reply_name = reply->command_name;
613:
614: if (!strcmp(reply_name, "no-route")) {
615: display_close(ctl);
616: error(EXIT_FAILURE, 0, "No route to host");
617: }
618:
619: if (!strcmp(reply_name, "network-down")) {
620: display_close(ctl);
621: error(EXIT_FAILURE, 0, "Network down");
622: }
623:
624: if (!strcmp(reply_name, "probes-exhausted")) {
625: display_close(ctl);
626: error(EXIT_FAILURE, 0, "Probes exhausted");
627: }
628:
629: if (!strcmp(reply_name, "invalid-argument")) {
630: display_close(ctl);
631: error(EXIT_FAILURE, 0, "mtr-packet reported invalid argument");
632: }
633:
634: if (!strcmp(reply_name, "permission-denied")) {
635: display_close(ctl);
636: error(EXIT_FAILURE, 0, "Permission denied");
637: }
638:
639: if (!strcmp(reply_name, "address-in-use")) {
640: display_close(ctl);
641: error(EXIT_FAILURE, 0, "Address in use");
642: }
643:
644: if (!strcmp(reply_name, "address-not-available")) {
645: display_close(ctl);
646: error(EXIT_FAILURE, 0, "Address not available");
647: }
648:
649: if (!strcmp(reply_name, "unexpected-error")) {
650: display_close(ctl);
651: error(EXIT_FAILURE, 0, "Unexpected mtr-packet error");
652: }
653: }
654:
655:
656: /*
657: A complete mtr-packet reply line has arrived. Parse it and record
658: the responding IP and round trip time, if it is a reply that we
659: understand.
660: */
661: static
662: void handle_command_reply(
663: struct mtr_ctl *ctl,
664: char *reply_str,
665: probe_reply_func_t reply_func)
666: {
667: struct command_t reply;
668: ip_t fromaddress;
669: int seq_num;
670: int round_trip_time;
671: char *reply_name;
672: struct mplslen mpls;
673:
674: /* Parse the reply string */
675: if (parse_command(&reply, reply_str)) {
676: /*
677: If the reply isn't well structured, something is fundamentally
678: wrong, as we might as well exit. Even if the reply is of an
679: unknown type, it should still parse.
680: */
681: display_close(ctl);
682: error(EXIT_FAILURE, errno, "reply parse failure");
683: return;
684: }
685:
686: handle_reply_errors(ctl, &reply);
687:
688: seq_num = reply.token;
689: reply_name = reply.command_name;
690:
691: /* If the reply type is unknown, ignore it for future compatibility */
692: if (strcmp(reply_name, "reply") && strcmp(reply_name, "ttl-expired")) {
693: return;
694: }
695:
696: /*
697: If the reply had an IP address and a round trip time, we can
698: record the result.
699: */
700: if (parse_reply_arguments
701: (ctl, &reply, &fromaddress, &round_trip_time, &mpls)) {
702:
703: reply_func(ctl, seq_num, &mpls, (void *) &fromaddress,
704: round_trip_time);
705: }
706: }
707:
708:
709: /*
710: Check the command pipe for completed replies to commands
711: we have previously sent. Record the results of those replies.
712: */
713: static
714: void consume_reply_buffer(
715: struct mtr_ctl *ctl,
716: struct packet_command_pipe_t *cmdpipe,
717: probe_reply_func_t reply_func)
718: {
719: char *reply_buffer;
720: char *reply_start;
721: char *end_of_reply;
722: int used_size;
723: int move_size;
724:
725: reply_buffer = cmdpipe->reply_buffer;
726:
727: /* Terminate the string storing the replies */
728: assert(cmdpipe->reply_buffer_used < PACKET_REPLY_BUFFER_SIZE);
729: reply_buffer[cmdpipe->reply_buffer_used] = 0;
730:
731: reply_start = reply_buffer;
732:
733: /*
734: We may have multiple completed replies. Loop until we don't
735: have any more newlines termininating replies.
736: */
737: while (true) {
738: /* If no newline is found, our reply isn't yet complete */
739: end_of_reply = index(reply_start, '\n');
740: if (end_of_reply == NULL) {
741: /* No complete replies remaining */
742: break;
743: }
744:
745: /*
746: Terminate the reply string at the newline, which
747: is necessary in the case where we are able to read
748: mulitple replies arriving simultaneously.
749: */
750: *end_of_reply = 0;
751:
752: /* Parse and record the reply results */
753: handle_command_reply(ctl, reply_start, reply_func);
754:
755: reply_start = end_of_reply + 1;
756: }
757:
758: /*
759: After replies have been processed, free the space used
760: by the replies, and move any remaining partial reply text
761: to the start of the reply buffer.
762: */
763: used_size = reply_start - reply_buffer;
764: move_size = cmdpipe->reply_buffer_used - used_size;
765: memmove(reply_buffer, reply_start, move_size);
766: cmdpipe->reply_buffer_used -= used_size;
767:
768: if (cmdpipe->reply_buffer_used >= PACKET_REPLY_BUFFER_SIZE - 1) {
769: /*
770: We've overflowed the reply buffer without a complete reply.
771: There's not much we can do about it but discard the data
772: we've got and hope new data coming in fits.
773: */
774: cmdpipe->reply_buffer_used = 0;
775: }
776: }
777:
778:
779: /*
780: Read as much as we can from the reply pipe from the child process, and
781: process as many replies as are available.
782: */
783: void handle_command_replies(
784: struct mtr_ctl *ctl,
785: struct packet_command_pipe_t *cmdpipe,
786: probe_reply_func_t reply_func)
787: {
788: int read_count;
789: int buffer_remaining;
790: char *reply_buffer;
791: char *read_buffer;
792:
793: reply_buffer = cmdpipe->reply_buffer;
794:
795: /*
796: Read the available reply text, up to the the remaining
797: buffer space. (Minus one for the terminating NUL.)
798: */
799: read_buffer = &reply_buffer[cmdpipe->reply_buffer_used];
800: buffer_remaining =
801: PACKET_REPLY_BUFFER_SIZE - cmdpipe->reply_buffer_used;
802: read_count = read(cmdpipe->read_fd, read_buffer, buffer_remaining - 1);
803:
804: if (read_count < 0) {
805: /*
806: EAGAIN simply indicates that there is no data currently
807: available on our non-blocking pipe.
808: */
809: if (errno == EAGAIN) {
810: return;
811: }
812:
813: display_close(ctl);
814: error(EXIT_FAILURE, errno, "command reply read failure");
815: return;
816: }
817:
818: if (read_count == 0) {
819: display_close(ctl);
820:
821: errno = EPIPE;
822: error(EXIT_FAILURE, EPIPE, "unexpected packet generator exit");
823: }
824:
825: cmdpipe->reply_buffer_used += read_count;
826:
827: /* Handle any replies completed by this read */
828: consume_reply_buffer(ctl, cmdpipe, reply_func);
829: }
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>