1: /*
2: * Guitar-ZyX(tm)::MasterControlProgram - portable guitar F/X controller
3: * Copyright (C) 2009 Douglas McClendon
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 as published by
7: * the Free Software Foundation, version 3 of the License.
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, see <http://www.gnu.org/licenses/>.
16: */
17: /*
18: #
19: #############################################################################
20: #############################################################################
21: ##
22: ## gzmcp: debug client (x86)
23: ##
24: #############################################################################
25: ##
26: ## Copyright 2007-2009 Douglas McClendon <dmc AT filteredperception DOT org>
27: ##
28: #############################################################################
29: #############################################################################
30: #
31: #
32: #
33: # usage: gzmcp-client-debug.x86
34: #
35: # The Guitar-ZyX Master Control Program Debug Client listens on the localhost
36: # at the udp port specified by GZMCP_DEF_UDP_PORT or 24642 for a gzmcp
37: # server to broadcast its presence. Upon identification of gzmcp server
38: # a command connection will be initiated with it, and a special command
39: # syntax entered at the client's stdin, will cause gzmcp commands to be
40: # sent to the server, just as the real NDS client does.
41: #
42: # The acceptable commands are >>>
43: #
44: # preset <preset int>
45: # parameter <parameter int> <value int>
46: # exit(or quit)
47: #
48: # note: for these effectively midi commands, channel 1 is used.
49: #
50: */
51:
52:
53:
54: #include <unistd.h>
55:
56: #include <sys/types.h>
57:
58: #include <sys/wait.h>
59:
60: #include <stdio.h>
61:
62: #include <stdlib.h>
63:
64: #include <stdarg.h>
65:
66: #include <errno.h>
67: #include <limits.h>
68:
69: #include <string.h>
70:
71: #include <sys/socket.h>
72:
73: #include <netinet/in.h>
74:
75: #include <arpa/inet.h>
76:
77: #include <netdb.h>
78:
79: #include <math.h>
80:
81: #include <alsa/asoundlib.h>
82:
83: #include <signal.h>
84:
85:
86: #include "client.h"
87:
88:
89:
90:
91: char prog_name[PATH_MAXLEN + 1];
92: char prog_dir[PATH_MAXLEN + 1];
93:
94: int cmd_socket_fd;
95:
96: long magic_cookie;
97:
98:
99:
100: int
101: main(int argc, char *argv[]) {
102:
103: // port number specified by the user on the commandline
104: long bcast_portnum;
105: long cmd_portnum;
106:
107: // server net address
108: // for standard socket address structure
109: struct sockaddr_in server_address;
110:
111:
112: //
113: // derived global 'constants'
114: //
115: dirname(prog_dir, argv[0]);
116: basename(prog_name, argv[0]);
117:
118: //
119: // Hello User
120: //
121: fprintf(stdout, "\n\n\n");
122: fprintf(stdout,
123: "==================================================\n");
124: fprintf(stdout,
125: "Guitar-ZyX Master Control Program X86 Debug Client\n");
126: fprintf(stdout,
127: " - v2k9.dev - (c)2009 Douglas McClendon\n");
128: fprintf(stdout,
129: "==================================================\n");
130: fprintf(stdout, "\n\n\n");
131: fprintf(stdout,
132: "==================================================\n");
133: fprintf(stdout,
134: "\nvalid gzmcp commands\n--------------------\n\n");
135: fprintf(stdout,
136: ">>> set-preset <new_preset(int)>\n\n");
137: fprintf(stdout,
138: ">>> set-parameter <parameter_id(int)> <value(int)>\n\n");
139: fprintf(stdout,
140: "==================================================\n");
141: fprintf(stdout, "\n\n\n");
142:
143: //
144: // set defaults
145: //
146: cmd_portnum = GZMCP_DEF_TCP_PORT;
147: bcast_portnum = GZMCP_DEF_UDP_PORT;
148:
149: //
150: // parse commandline
151: //
152: // TODO: add better getopt-ish, with --verbose...
153: if (argc > 2) {
154: // too many args: tell the user how to use
155: usage();
156: } else if (argc == 2) {
157: // get the user specified port number from the command line arg
158: cmd_portnum = ec_strtol(argv[1]);
159: bcast_portnum = ec_strtol(argv[1]);
160: } else {
161: // defaults are fine
162: } // end parsing commandline
163:
164: // put the global handshake cookie where it can be sizeof()d
165: magic_cookie = (long)GZMCP_DEF_MAGIC_COOKIE;
166:
167: //
168: // wait_for_bcast: wait for a server broadcast, retrieving the address
169: //
170: wait_for_bcast(bcast_portnum,
171: &server_address);
172:
173: //
174: // connect to the server, exchange handshake
175: //
176: connect_to_server(cmd_portnum,
177: server_address);
178:
179: //
180: // get_and_handle_user_commands: prompt user for input and handle
181: //
182: get_and_handle_user_commands();
183:
184: //
185: // network_shutdown: clean up network resources
186: //
187: network_shutdown();
188:
189: //
190: // done
191: //
192: return(0);
193:
194: } // end main
195:
196:
197:
198:
199:
200: void usage(void) {
201: fprintf(stderr, "\n\nusage: gzmcpd [<port number> default=24642]\n\n\n");
202: exit(1);
203: }
204:
205:
206:
207: long ec_strtol(char *str) {
208:
209: char *endptr;
210: long val;
211:
212: // error check strtol
213: // note: code verbatim from man strtol
214: // To distinguish success/failure after call
215: errno = 0;
216: val = strtol(str, &endptr, 10);
217:
218: // Check for various possible errors
219: if ((errno == ERANGE && (val == LONG_MAX || val == LONG_MIN))
220: || (errno != 0 && val == 0)) {
221: perror("strtol");
222: exit(EXIT_FAILURE);
223: }
224:
225: if (endptr == str) {
226: fprintf(stderr, "No digits were found\n");
227: exit(EXIT_FAILURE);
228: }
229:
230: // If we got here, strtol() successfully parsed a number
231: return val;
232: }
233:
234:
235:
236: void basename(char *dest, char *path) {
237:
238: char *new_result;
239: char *last_result;
240:
241:
242: // look for the last '/'
243: last_result = path;
244: while ((new_result = strstr(last_result, "/")) != NULL) {
245: last_result = new_result + 1;
246: }
247:
248: // copy to dest, everything after the last '/'
249: strncpy(dest, last_result, PATH_MAXLEN);
250:
251: // guarantee null terminated destination string
252: dest[PATH_MAXLEN] = 0;
253:
254: return;
255: }
256:
257:
258:
259: void dirname(char *dest, char *path) {
260:
261: char *new_result;
262: char *last_result;
263: int dirname_length;
264:
265:
266: // look for the last '/'
267: last_result = path;
268: while ((new_result = strstr(last_result, "/")) != NULL) {
269: last_result = new_result + 1;
270: }
271:
272: // calculdate the length of the result we want
273: dirname_length = strlen(path) - strlen(last_result) - 1;
274:
275: if (dirname_length > PATH_MAXLEN) {
276: die("dirname() result greater than PATH_MAXLEN - %d\n",
277: PATH_MAXLEN);
278: }
279:
280: // copy to dest, everything up to last '/'
281: strncpy(dest,
282: path,
283: dirname_length);
284:
285: // guarantee null terminated destination string
286: dest[PATH_MAXLEN] = 0;
287:
288: return;
289: }
290:
291:
292:
293: void status(const char *msg_fmt, ...) {
294:
295: char print_msg[STATUS_MSG_MAXLEN + 1];
296:
297: va_list msg_arg;
298:
299:
300: // standard printf style variable argument processing...
301: va_start(msg_arg, msg_fmt);
302: vsnprintf(print_msg,
303: STATUS_MSG_MAXLEN,
304: msg_fmt,
305: msg_arg);
306: va_end(msg_arg);
307:
308: fprintf(stdout,
309: "%s: %s\n",
310: prog_name,
311: print_msg);
312: }
313:
314:
315:
316: void die(const char *msg_fmt, ...) {
317:
318: char print_msg[STATUS_MSG_MAXLEN + 1];
319:
320: va_list msg_arg;
321:
322:
323: // standard printf style variable argument processing...
324: va_start(msg_arg, msg_fmt);
325: vsnprintf(print_msg,
326: STATUS_MSG_MAXLEN,
327: msg_fmt,
328: msg_arg);
329: va_end(msg_arg);
330:
331: fprintf(stdout,
332: "%s: FATAL ERROR: %s\n",
333: prog_name,
334: print_msg);
335: exit(1);
336:
337: }
338:
339:
340:
341: void wait_for_bcast(int portnum,
342: struct sockaddr_in* address) {
343:
344: // file descriptor for the server brodcast udp socket
345: int bcast_socket_fd;
346:
347: int found_server = 0;
348:
349: int bytes_received;
350: unsigned int from_length;
351:
352: // for setsockopt
353: int optval;
354:
355: // buffer to receive broadcast messages
356: char beacon_msg[GZMCP_BEACON_MSG_MAXLEN + 1];
357:
358:
359: status("listening for server broadcasts on udp port %d\n",
360: portnum);
361:
362: status("listening for magic cookie - 0x%08x\n",
363: magic_cookie);
364:
365: // create the broadcast reception udp socket
366: // - AF_INET means ipv4, vs AF_INET6 vs AF_UNIX/AF_LOCAL
367: // - SOCK_DGRAM means udp, vs SOCK_STREAM for tcp
368: // - 0 means default protocol type for family
369: bcast_socket_fd = socket(AF_INET,
370: SOCK_DGRAM,
371: 0);
372: if (bcast_socket_fd < 0) {
373: perror("socket()");
374: die("couldn't create broadcast socket");
375: }
376:
377: // enable broadcast
378: // note: perhaps this is unnecessary, since we only receive
379: optval = 1;
380: if (setsockopt(bcast_socket_fd,
381: SOL_SOCKET,
382: SO_BROADCAST,
383: &optval, sizeof(optval)) < 0) {
384: perror("setsockopt()");
385: die("failed to set broadcast socket options\n");
386: }
387:
388:
389: // create a destination address structure, initialize...
390: memset(address, 0, sizeof(*address));
391:
392: // configure the address structure for our purposes
393: // INET, vs e.g. UNIX for local non network sockets
394: address->sin_family = AF_INET;
395: address->sin_port = htons(portnum);
396: // ???
397: address->sin_addr.s_addr = htonl(INADDR_ANY);
398:
399: if (bind(bcast_socket_fd, (struct sockaddr *) address,
400: sizeof(*address)) < 0) {
401: perror("bind()");
402: die("failed to bind to broadcast socket");
403: }
404:
405: //
406: // listen for broadcasts until a gzmcp server is discovered
407: //
408: while(!found_server) {
409:
410: // initialize the length of the address result
411: from_length = sizeof(struct sockaddr_in);
412:
413: bytes_received = recvfrom(bcast_socket_fd,
414: beacon_msg,
415: GZMCP_BEACON_MSG_MAXLEN,
416: 0,
417: (struct sockaddr*)address,
418: &from_length);
419: // status("debug: bytes_received for beacon was %d\n",
420: // bytes_received);
421: if (bytes_received < 0) {
422: // udp makes no guarantees
423: // perror("sendto()");
424: // status("could not send message\n");
425: } else {
426: // check if message is a gzmcp server broadcast message
427: if (bcmp((void *)beacon_msg,
428: (void *)&magic_cookie,
429: 4) == 0) {
430: // a correct magic cookie
431: status("server at %s had the right magic cookie!\n",
432: inet_ntoa(address->sin_addr));
433: found_server = 1;
434:
435: } else {
436: // not a correct magic cookie
437: status("got an incorrect magic cookie! ignoring...\n");
438: }
439:
440: } // end handling received possible cookie data
441:
442: } // end while(1)
443:
444: } // end wait_for_bcast()
445:
446:
447:
448: void do_handshake(int cmd_socket_fd) {
449:
450: // for read/write return values
451: int numbytes;
452:
453: // for outgoing handshake
454: gzmcp_cmd handshake_cmd;
455: // for incoming handshake
456: gzmcp_cmd reflected_handshake_cmd;
457:
458:
459: // create handshake command/packet
460: handshake_cmd.type = GZMCP_CMD_HANDSHAKE;
461: memcpy((void *)handshake_cmd.data.handshake.string,
462: (void *)&magic_cookie,
463: sizeof(magic_cookie));
464:
465: // send the handshake
466: numbytes = send(cmd_socket_fd,
467: (void *)&handshake_cmd,
468: sizeof(gzmcp_cmd),
469: 0);
470: // check for errors
471: if (numbytes < 0) {
472: perror("send()");
473: die("handshake send");
474: }
475:
476: // status("debug: handshake sent, numbytes is %d, sizeof handshake cmd is %d\n",
477: // numbytes, sizeof(gzmcp_cmd));
478: if (numbytes == sizeof(gzmcp_cmd)) {
479: status("handshake sent\n");
480: } else {
481: die("failed to send handshake");
482: }
483:
484: // recieve the reciprocal handshake
485: numbytes = recv(cmd_socket_fd,
486: (void *)&reflected_handshake_cmd,
487: sizeof(gzmcp_cmd),
488: 0);
489: // check for errors
490: if (numbytes < 0) {
491: perror("recv()");
492: die("reflected handshake reception");
493: }
494:
495: // status("debug: reflected handshake received, numbytes is %d, sizeof handshake is %d\n",
496: // numbytes, sizeof(gzmcp_cmd));
497:
498: if (numbytes == sizeof(gzmcp_cmd)) {
499:
500: // check reflected handshake against the original
501: if (memcmp((void*)&reflected_handshake_cmd,
502: (void*)&handshake_cmd,
503: sizeof(gzmcp_cmd)) == 0 ) {
504: status("correct handshake received\n");
505: } else {
506: die("incorrect handshake received");
507: }
508:
509: } else {
510: // TODO(?) receive rest of partial handshake
511: die("received partial handshake back");
512: }
513:
514: }
515:
516:
517:
518: void set_preset(int cmd_socket_fd,
519: char *preset_string) {
520:
521: // packet structure for sending commands to the server
522: gzmcp_cmd newcmd;
523:
524: // for send() return values
525: int numbytes;
526:
527:
528: // create new command/packet
529: newcmd.type = GZMCP_CMD_PRESET;
530: newcmd.data.preset.channel = 1;
531: newcmd.data.preset.preset = ec_strtol(preset_string);
532:
533: status("setting preset to %d ...\n",
534: newcmd.data.preset.preset);
535:
536: // send the new command
537: numbytes = send(cmd_socket_fd,
538: (void *)&newcmd,
539: sizeof(gzmcp_cmd),
540: 0);
541: // check for errors
542: if (numbytes < 0) {
543: perror("send()");
544: die("set-preset command sending failed");
545: }
546:
547: // debug
548: status("debug: set-preset sent, numbytes is %d\n",
549: numbytes);
550:
551: if (numbytes == sizeof(gzmcp_cmd)) {
552: status("set-preset command sent\n");
553: } else {
554: die("failed to send set-preset command");
555: }
556:
557: }
558:
559:
560:
561: void set_parameter(int cmd_socket_fd,
562: char *parameter_string,
563: char *value_string) {
564:
565: // packet structure for sending commands to the server
566: gzmcp_cmd newcmd;
567:
568: // for send() return values
569: int numbytes;
570:
571:
572: // create new command/packet
573: newcmd.type = GZMCP_CMD_PARAMETER;
574: newcmd.data.parameter.channel = 1;
575: newcmd.data.parameter.parameter = ec_strtol(parameter_string);
576: newcmd.data.parameter.value = ec_strtol(value_string);
577:
578: status("setting parameter %d to value %d ...\n",
579: newcmd.data.parameter.parameter,
580: newcmd.data.parameter.value);
581:
582: // send the new command
583: numbytes = send(cmd_socket_fd,
584: (void *)&newcmd,
585: sizeof(gzmcp_cmd),
586: 0);
587: // check for errors
588: if (numbytes < 0) {
589: perror("send()");
590: die("set-parameter command sending failed");
591: }
592:
593: // debug
594: status("debug: set-parameter sent, numbytes is %d\n",
595: numbytes);
596:
597: if (numbytes == sizeof(gzmcp_cmd)) {
598: status("set-parameter command sent\n");
599: } else {
600: die("failed to send set-parameter command");
601: }
602:
603: }
604:
605:
606:
607: void get_update(int cmd_socket_fd,
608: char *destination_filename) {
609:
610: // to store the retrieved update
611: FILE *update_file;
612:
613: // packet structure for sending commands to the server
614: gzmcp_cmd newcmd;
615:
616: // for send() return values
617: int numbytes;
618:
619: // for reading the update file
620: int bytes_to_read;
621: unsigned char read_buf[UPD_RD_BUF_SZ];
622:
623: // for magic return value checking
624: unsigned char magic_found;
625: unsigned char last_byte;
626: unsigned char second_to_last_byte;
627:
628: // for returned signature
629: uint32_t update_signature[3];
630:
631:
632: // create new command/packet
633: newcmd.type = GZMCP_CMD_GETUPDATE;
634: // TODO: check for existing cached copy, and set these appropriately
635: newcmd.data.getupdate.size = 0;
636: newcmd.data.getupdate.hash = 0;
637: newcmd.data.getupdate.sample_data = 0;
638:
639: status("getting update as file %s...\n",
640: destination_filename);
641:
642: // send the new command
643: numbytes = send(cmd_socket_fd,
644: (void *)&newcmd,
645: sizeof(gzmcp_cmd),
646: 0);
647: // check for errors
648: if (numbytes < 0) {
649: perror("send()");
650: die("get-update command sending failed");
651: }
652:
653: // debug
654: status("debug: get-update sent, numbytes is %d\n",
655: numbytes);
656:
657: if (numbytes == sizeof(gzmcp_cmd)) {
658: status("get-update command sent\n");
659: } else {
660: die("failed to send set-parameter command");
661: }
662:
663: //
664: // receive update file from server
665: //
666:
667: // discard incoming data until magic is found
668: // todo: this could be a generic function with magic and socket_fd as arguments
669: status("waiting for get-update return magic...\n");
670: magic_found = 0;
671: last_byte = 0;
672: second_to_last_byte = 0;
673: while (!magic_found) {
674: second_to_last_byte = last_byte;
675: // recieve a byte
676: numbytes = recv(cmd_socket_fd,
677: (void *)&last_byte,
678: 1,
679: 0);
680: // check for errors
681: if (numbytes < 0) {
682: perror("recv()");
683: die("get_update: waiting for return magic");
684: }
685:
686: if ((last_byte == 24) &&
687: (second_to_last_byte == 42)) {
688: status("get_update: got correct return magic\n");
689: magic_found = 1;
690: } else {
691: status("get_update: haven't yet gotten correct return magic\n");
692: }
693:
694: } // end while (!magic_found)
695:
696: // read file size, signature, and sample data
697: numbytes = recv(cmd_socket_fd,
698: (void *)&update_signature,
699: 4 * 3,
700: 0);
701: // check for errors
702: if (numbytes < 0) {
703: perror("recv()");
704: die("get_update: waiting for return signature");
705: } else if (numbytes != (4 * 3)) {
706: die("get_update: return signature incomplete %d/12 bytes received",
707: numbytes);
708: }
709:
710: bytes_to_read = update_signature[0];
711:
712: status("get_update: return signature received: update is %d bytes\n",
713: bytes_to_read);
714:
715: // open output file
716: update_file = fopen(destination_filename, "w");
717:
718: if (update_file == NULL) {
719: status("get-update failed: cannot open update destination - %s - file for writing\n");
720: return;
721: }
722:
723: // read up to 1kb at a time until done
724: bytes_to_read = update_signature[0];
725: while (bytes_to_read) {
726: numbytes = recv(cmd_socket_fd,
727: (void *)read_buf,
728: UPD_RD_BUF_SZ,
729: 0);
730: // check for errors
731: if (numbytes < 0) {
732: perror("recv()");
733: die("get_update: while getting update file data");
734: }
735:
736: // write data to file
737: fwrite(read_buf,
738: 1,
739: numbytes,
740: update_file);
741:
742: // update amount of work left to do
743: bytes_to_read -= numbytes;
744:
745: }
746:
747:
748: // close output file
749: fclose(update_file);
750:
751: }
752:
753:
754:
755: void connect_to_server(int portnum, struct sockaddr_in server_address) {
756:
757: // return values
758: int rv;
759:
760:
761: // create the command dispatch tcp socket
762: // open an internet stream socket to the server
763: cmd_socket_fd = socket(AF_INET, SOCK_STREAM, 0);
764: if (cmd_socket_fd < 0) {
765: perror("socket()");
766: die("failed to open server socket");
767: }
768:
769: status("connecting to server at port %d...\n",
770: portnum);
771:
772: server_address.sin_port = htons(portnum);
773:
774: rv = connect(cmd_socket_fd,
775: (struct sockaddr *)&server_address,
776: sizeof(server_address));
777: if (rv < 0) {
778: perror("connect()");
779: die("failed to connect to server\n");
780: }
781:
782: // handshake with server
783: do_handshake(cmd_socket_fd);
784:
785: } // end connect_to_server()
786:
787:
788:
789: void get_and_handle_user_commands(void) {
790:
791: // for user program termination request handling
792: int user_exit;
793:
794: // string buffers for processing user input
795: char user_cmd_string[USER_CMD_MAXLEN + 1];
796: char user_cmd[USER_CMD_MAXLEN + 1];
797: char user_cmd_arga[USER_CMD_MAXLEN + 1];
798: char user_cmd_argb[USER_CMD_MAXLEN + 1];
799: char user_cmd_argc[USER_CMD_MAXLEN + 1];
800: // number of fields in user input
801: int num_items;
802:
803:
804: // TODO: no supported exit method exists yet
805: user_exit = 0;
806:
807: // handle commands given by user at stdin
808: while (!user_exit) {
809:
810: // prompt for a user command
811: status("enter a command to send to the server\n");
812: fprintf(stdout, "\ngzmcp command >>> ");
813:
814: // read a user command
815: if(fgets(user_cmd_string, USER_CMD_MAXLEN, stdin) == NULL) {
816: // input from terminal ceased
817: user_exit = 1;
818: status("end of user input reached, goodbye...\n");
819: exit(0);
820: }
821:
822: // output an end of line
823: fprintf(stdout, "\n\n");
824:
825: // extract command type and arg fields
826: num_items = sscanf(user_cmd_string,
827: "%s %s %s %s",
828: user_cmd,
829: user_cmd_arga,
830: user_cmd_argb,
831: user_cmd_argc);
832:
833: // handle possible lack of command
834: if (num_items == 0) { // no command input
835: status("user input empty? try again...\n");
836: continue;
837: }
838:
839: // check for and handle each known command
840: if ((strncmp(user_cmd, "exit", strlen(user_cmd)) == 0 ) ||
841: (strncmp(user_cmd, "quit", strlen(user_cmd)) == 0 )) {
842:
843: if (num_items != 1) {
844: status("command ignored: %s takes no arguments\n",
845: user_cmd);
846: continue;
847: } else {
848: user_exit = 1;
849: }
850:
851: } else if (strncmp(user_cmd, "send-handshake", strlen(user_cmd)) == 0 ) {
852:
853: if (num_items != 1) {
854: status("command ignored: %s takes no arguments\n",
855: user_cmd);
856: continue;
857: } else {
858: do_handshake(cmd_socket_fd);
859: }
860:
861: } else if ((strncmp(user_cmd, "set-preset", strlen(user_cmd)) == 0 )) {
862:
863: if (num_items != 2) {
864: status("command ignored: %s takes exactly one argument\n",
865: user_cmd);
866: continue;
867: } else {
868: set_preset(cmd_socket_fd, user_cmd_arga);
869: }
870:
871: } else if ((strncmp(user_cmd, "set-parameter", strlen(user_cmd)) == 0 )) {
872:
873: if (num_items != 3) {
874: status("command ignored: %s takes exactly two arguments\n",
875: user_cmd);
876: continue;
877: } else {
878: set_parameter(cmd_socket_fd, user_cmd_arga, user_cmd_argb);
879: }
880:
881: } else if ((strncmp(user_cmd, "get-update", strlen(user_cmd)) == 0 )) {
882:
883: if (num_items != 2) {
884: status("command ignored: %s takes exactly one argument\n",
885: user_cmd);
886: continue;
887: } else {
888: get_update(cmd_socket_fd, user_cmd_arga);
889: }
890:
891: } else {
892:
893: status("unknown command - %s - try again...\n", user_cmd);
894:
895: } // end if/else handling of known user commands
896:
897: } // end while (!user_exit)
898:
899: // later...
900: status("user requested exit, goodbye...\n");
901: exit(0);
902:
903: }
904:
905:
906:
907: void network_shutdown(void) {
908:
909: shutdown(cmd_socket_fd, SHUT_RDWR);
910:
911: } // end network_shutdown()
912:
913:
914: