1 /*
2 Copyright (C) 1996-1997 Id Software, Inc.
3
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
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.
12
13 See the GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18
19 */
20 // cmd.c -- Quake script command processing module
21
22 #include <string.h>
23
24 #include "client.h"
25 #include "cmd.h"
26 #include "common.h"
27 #include "console.h"
28 #include "cvar.h"
29 #include "quakedef.h"
30 #include "shell.h"
31 #include "sys.h"
32 #include "zone.h"
33
34 #ifdef NQ_HACK
35 #include "host.h"
36 #include "protocol.h"
37 #endif
38
39 #if defined(QW_HACK) && !defined(SERVERONLY)
40 static void Cmd_ForwardToServer_f(void);
41 #endif
42
43 #define MAX_ALIAS_NAME 32
44
45 typedef struct cmdalias_s {
46 char name[MAX_ALIAS_NAME];
47 const char *value;
48 struct stree_node stree;
49 } cmdalias_t;
50
51 #define cmdalias_entry(ptr) container_of(ptr, struct cmdalias_s, stree)
52 static DECLARE_STREE_ROOT(cmdalias_tree);
53
54 static qboolean cmd_wait;
55
56 cvar_t cl_warncmd = { "cl_warncmd", "0" };
57
58 //=============================================================================
59
60 /*
61 ============
62 Cmd_Wait_f
63
64 Causes execution of the remainder of the command buffer to be delayed until
65 next frame. This allows commands like:
66 bind g "impulse 5 ; +attack ; wait ; -attack ; impulse 2"
67 ============
68 */
69 void
Cmd_Wait_f(void)70 Cmd_Wait_f(void)
71 {
72 cmd_wait = true;
73 }
74
75 /*
76 =============================================================================
77
78 COMMAND BUFFER
79
80 =============================================================================
81 */
82
83 static sizebuf_t cmd_text;
84 #ifdef QW_HACK
85 static byte cmd_text_buf[8192];
86 #endif
87
88 /*
89 ============
90 Cbuf_Init
91 ============
92 */
93 void
Cbuf_Init(void)94 Cbuf_Init(void)
95 {
96 #ifdef NQ_HACK
97 SZ_Alloc(&cmd_text, 8192);
98 #endif
99 #ifdef QW_HACK
100 cmd_text.data = cmd_text_buf;
101 cmd_text.maxsize = sizeof(cmd_text_buf);
102 cmd_text.cursize = 0;
103 #endif
104 }
105
106
107 /*
108 ============
109 Cbuf_AddText
110
111 Adds command text at the end of the buffer
112 ============
113 */
114 void
Cbuf_AddText(const char * fmt,...)115 Cbuf_AddText(const char *fmt, ...)
116 {
117 va_list ap;
118 int len, maxlen;
119 char *buf;
120
121 buf = (char *)cmd_text.data + cmd_text.cursize;
122 maxlen = cmd_text.maxsize - cmd_text.cursize;
123 va_start(ap, fmt);
124 len = vsnprintf(buf, maxlen, fmt, ap);
125 va_end(ap);
126
127 if (cmd_text.cursize + len < cmd_text.maxsize)
128 cmd_text.cursize += len;
129 else
130 Con_Printf("%s: overflow\n", __func__);
131 }
132
133
134 /*
135 ============
136 Cbuf_InsertText
137
138 Adds command text immediately after the current command (i.e. at the start
139 of the command buffer). Adds a \n to the text.
140 ============
141 */
142 void
Cbuf_InsertText(const char * text)143 Cbuf_InsertText(const char *text)
144 {
145 int len;
146
147 len = strlen(text);
148 if (cmd_text.cursize) {
149 if (cmd_text.cursize + len + 1 > cmd_text.maxsize)
150 Sys_Error("%s: overflow", __func__);
151
152 /* move any commands still remaining in the exec buffer */
153 memmove(cmd_text.data + len + 1, cmd_text.data, cmd_text.cursize);
154 memcpy(cmd_text.data, text, len);
155 cmd_text.data[len] = '\n';
156 cmd_text.cursize += len + 1;
157 } else {
158 Cbuf_AddText("%s\n", text);
159 }
160 }
161
162 /*
163 ============
164 Cbuf_Execute
165 ============
166 */
Cbuf_Execute(void)167 void Cbuf_Execute(void)
168 {
169 int len;
170 char line[1024];
171
172 while (cmd_text.cursize)
173 {
174 /* find a \n or ; line break */
175 char *text = (char *)cmd_text.data;
176 int quotes = 0;
177 int maxlen = qmin(cmd_text.cursize, (int)sizeof(line));
178
179 for (len = 0; len < maxlen; len++)
180 {
181 if (text[len] == '"')
182 quotes++;
183 if (!(quotes & 1) && text[len] == ';')
184 break; /* don't break if inside a quoted string */
185 if (text[len] == '\n')
186 break;
187 }
188 if (len == sizeof(line)) {
189 Con_Printf("%s: command truncated\n", __func__);
190 len--;
191 }
192 memcpy(line, text, len);
193 line[len] = 0;
194
195 /*
196 * delete the text from the command buffer and move remaining commands
197 * down this is necessary because commands (exec, alias) can insert
198 * data at the beginning of the text buffer
199 */
200 if (len == cmd_text.cursize)
201 cmd_text.cursize = 0;
202 else {
203 len++; /* skip the terminating character */
204 cmd_text.cursize -= len;
205 memmove(text, text + len, cmd_text.cursize);
206 }
207
208 /* execute the command line */
209 #ifdef NQ_HACK
210 Cmd_ExecuteString(line, src_command);
211 #endif
212 #ifdef QW_HACK
213 Cmd_ExecuteString(line);
214 #endif
215
216 if (cmd_wait)
217 {
218 /*
219 * skip out while text still remains in buffer, leaving it for
220 * next frame
221 */
222 cmd_wait = false;
223 break;
224 }
225 }
226 }
227
228 /*
229 ==============================================================================
230
231 SCRIPT COMMANDS
232
233 ==============================================================================
234 */
235
236 /*
237 ===============
238 Cmd_StuffCmds_f
239
240 Adds command line parameters as script statements
241 Commands lead with a +, and continue until a - or another +
242 quake +prog jctest.qp +cmd amlev1
243 quake -nosound +cmd amlev1
244 ===============
245 */
246 void
Cmd_StuffCmds_f(void)247 Cmd_StuffCmds_f(void)
248 {
249 int i, j;
250 int s;
251 char *text, *build, c;
252
253 if (Cmd_Argc() != 1) {
254 Con_Printf("stuffcmds : execute command line parameters\n");
255 return;
256 }
257 // build the combined string to parse from
258 s = 0;
259 for (i = 1; i < com_argc; i++) {
260 if (!com_argv[i])
261 continue; // NEXTSTEP nulls out -NXHost
262 s += strlen(com_argv[i]) + 1;
263 }
264 if (!s)
265 return;
266
267 text = (char*)Z_Malloc(s + 1);
268 text[0] = 0;
269 for (i = 1; i < com_argc; i++) {
270 if (!com_argv[i])
271 continue; // NEXTSTEP nulls out -NXHost
272 strcat(text, com_argv[i]);
273 if (i != com_argc - 1)
274 strcat(text, " ");
275 }
276
277 // pull out the commands
278 build = (char*)Z_Malloc(s + 1);
279 build[0] = 0;
280
281 for (i = 0; i < s - 1; i++) {
282 if (text[i] == '+') {
283 i++;
284
285 for (j = i;
286 (text[j] != '+') && (text[j] != '-') && (text[j] != 0); j++);
287
288 c = text[j];
289 text[j] = 0;
290
291 strcat(build, text + i);
292 strcat(build, "\n");
293 text[j] = c;
294 i = j - 1;
295 }
296 }
297
298 if (build[0])
299 Cbuf_InsertText(build);
300
301 Z_Free(text);
302 Z_Free(build);
303 }
304
305
306 /*
307 ===============
308 Cmd_Exec_f
309 ===============
310 */
311 void
Cmd_Exec_f(void)312 Cmd_Exec_f(void)
313 {
314 char *f;
315 int mark;
316
317 if (Cmd_Argc() != 2) {
318 Con_Printf("exec <filename> : execute a script file\n");
319 return;
320 }
321 // FIXME: is this safe freeing the hunk here???
322 mark = Hunk_LowMark();
323 f = (char*)COM_LoadHunkFile(Cmd_Argv(1));
324 if (!f) {
325 Con_Printf("couldn't exec %s\n", Cmd_Argv(1));
326 return;
327 }
328 if (cl_warncmd.value || developer.value)
329 Con_Printf("execing %s\n", Cmd_Argv(1));
330
331 Cbuf_InsertText(f);
332 Hunk_FreeToLowMark(mark);
333 }
334
335
336 /*
337 ===============
338 Cmd_Echo_f
339
340 Just prints the rest of the line to the console
341 ===============
342 */
343 void
Cmd_Echo_f(void)344 Cmd_Echo_f(void)
345 {
346 int i;
347
348 for (i = 1; i < Cmd_Argc(); i++)
349 Con_Printf("%s ", Cmd_Argv(i));
350 Con_Printf("\n");
351 }
352
353 /*
354 ===============
355 Cmd_Alias_f
356
357 Creates a new command that executes a command string (possibly ; seperated)
358 ===============
359 */
360
361 static struct cmdalias_s *
Cmd_Alias_Find(const char * name)362 Cmd_Alias_Find(const char *name)
363 {
364 struct cmdalias_s *ret = NULL;
365 struct stree_node *n;
366
367 n = STree_Find(&cmdalias_tree, name);
368 if (n)
369 ret = cmdalias_entry(n);
370
371 return ret;
372 }
373
374 void
Cmd_Alias_f(void)375 Cmd_Alias_f(void)
376 {
377 cmdalias_t *a;
378 char cmd[1024];
379 int i, c;
380 const char *s;
381 char *newval;
382 size_t cmd_len;
383 struct stree_node *node;
384
385 if (Cmd_Argc() == 1) {
386 Con_Printf("Current alias commands:\n");
387 STree_ForEach_After_NullStr(&cmdalias_tree, node) {
388 a = cmdalias_entry(node);
389 Con_Printf("%s : %s\n", a->name, a->value);
390 }
391 return;
392 }
393
394 s = Cmd_Argv(1);
395 if (strlen(s) >= MAX_ALIAS_NAME) {
396 Con_Printf("Alias name is too long\n");
397 return;
398 }
399
400 // if the alias already exists, reuse it
401 a = Cmd_Alias_Find(s);
402 if (a)
403 Z_Free(a->value);
404
405 if (!a) {
406 a = (cmdalias_t*)Z_Malloc(sizeof(cmdalias_t));
407 strcpy(a->name, s);
408 a->stree.string = a->name;
409 STree_Insert(&cmdalias_tree, &a->stree);
410 }
411
412 // copy the rest of the command line
413 cmd[0] = 0; // start out with a null string
414 c = Cmd_Argc();
415 cmd_len = 1;
416 for (i = 2; i < c; i++) {
417 cmd_len += strlen(Cmd_Argv(i));
418 if (i != c - 1)
419 cmd_len++;
420 if (cmd_len >= sizeof(cmd)) {
421 Con_Printf("Alias value is too long\n");
422 cmd[0] = 0; // nullify the string
423 break;
424 }
425 strcat(cmd, Cmd_Argv(i));
426 if (i != c - 1)
427 strcat(cmd, " ");
428 }
429 strcat(cmd, "\n");
430
431 newval = (char*)Z_Malloc(strlen(cmd) + 1);
432 strcpy(newval, cmd);
433 a->value = newval;
434 }
435
436 /*
437 =============================================================================
438
439 COMMAND EXECUTION
440
441 =============================================================================
442 */
443
444 typedef struct cmd_function_s {
445 const char *name;
446 xcommand_t function;
447 cmd_arg_f completion;
448 struct stree_node stree;
449 } cmd_function_t;
450
451 #define cmd_entry(ptr) container_of(ptr, struct cmd_function_s, stree)
452 static DECLARE_STREE_ROOT(cmd_tree);
453
454 #define MAX_ARGS 80
455 static int cmd_argc;
456 static const char *cmd_argv[MAX_ARGS];
457 static const char *cmd_null_string = "";
458 static const char *cmd_args = NULL;
459
460 #ifdef NQ_HACK
461 cmd_source_t cmd_source;
462 #endif
463
464 /*
465 ============
466 Cmd_Init
467 ============
468 */
469 void
Cmd_Init(void)470 Cmd_Init(void)
471 {
472 //
473 // register our commands
474 //
475 Cmd_AddCommand("stuffcmds", Cmd_StuffCmds_f);
476 Cmd_AddCommand("exec", Cmd_Exec_f);
477 Cmd_AddCommand("echo", Cmd_Echo_f);
478 Cmd_AddCommand("alias", Cmd_Alias_f);
479 Cmd_AddCommand("wait", Cmd_Wait_f);
480 #ifdef NQ_HACK
481 Cmd_AddCommand("cmd", Cmd_ForwardToServer);
482 #elif defined(QW_HACK) && !defined(SERVERONLY)
483 Cmd_AddCommand("cmd", Cmd_ForwardToServer_f);
484 #endif
485 }
486
487 /*
488 ============
489 Cmd_Argc
490 ============
491 */
492 int
Cmd_Argc(void)493 Cmd_Argc(void)
494 {
495 return cmd_argc;
496 }
497
498 /*
499 ============
500 Cmd_Argv
501 ============
502 */
503 const char *
Cmd_Argv(int arg)504 Cmd_Argv(int arg)
505 {
506 if (arg >= cmd_argc)
507 return cmd_null_string;
508 return cmd_argv[arg];
509 }
510
511 /*
512 ============
513 Cmd_Args
514
515 Returns a single string containing argv(1) to argv(argc()-1)
516 ============
517 */
518 const char *
Cmd_Args(void)519 Cmd_Args(void)
520 {
521 // FIXME - check necessary?
522 if (!cmd_args)
523 return "";
524 return cmd_args;
525 }
526
527
528 /*
529 ============
530 Cmd_TokenizeString
531
532 Parses the given string into command line tokens.
533 ============
534 */
535 void
Cmd_TokenizeString(const char * text)536 Cmd_TokenizeString(const char *text)
537 {
538 int i;
539 char *arg;
540
541 // clear the args from the last string
542 for (i = 0; i < cmd_argc; i++)
543 Z_Free(cmd_argv[i]);
544
545 cmd_argc = 0;
546 cmd_args = NULL;
547
548 while (1) {
549 // skip whitespace up to a /n
550 while (*text && *text <= ' ' && *text != '\n') {
551 text++;
552 }
553
554 if (*text == '\n') { // a newline seperates commands in the buffer
555 text++;
556 break;
557 }
558
559 if (!*text)
560 return;
561
562 if (cmd_argc == 1)
563 cmd_args = text;
564
565 text = COM_Parse(text);
566 if (!text)
567 return;
568
569 if (cmd_argc < MAX_ARGS) {
570 arg = (char*)Z_Malloc(strlen(com_token) + 1);
571 strcpy(arg, com_token);
572 cmd_argv[cmd_argc] = arg;
573 cmd_argc++;
574 }
575 }
576 }
577
578 static struct cmd_function_s *
Cmd_FindCommand(const char * name)579 Cmd_FindCommand(const char *name)
580 {
581 struct cmd_function_s *ret = NULL;
582 struct stree_node *n;
583
584 n = STree_Find(&cmd_tree, name);
585 if (n)
586 ret = cmd_entry(n);
587
588 return ret;
589 }
590
591 /*
592 ============
593 Cmd_AddCommand
594 ============
595 */
596 void
Cmd_AddCommand(const char * cmd_name,xcommand_t function)597 Cmd_AddCommand(const char *cmd_name, xcommand_t function)
598 {
599 cmd_function_t *cmd;
600
601 if (host_initialized) // because hunk allocation would get stomped
602 Sys_Error("%s: called after host_initialized", __func__);
603
604 // fail if the command is a variable name
605 if (Cvar_VariableString(cmd_name)[0]) {
606 Con_Printf("%s: %s already defined as a var\n", __func__, cmd_name);
607 return;
608 }
609 // fail if the command already exists
610 cmd = Cmd_FindCommand(cmd_name);
611 if (cmd) {
612 Con_Printf("%s: %s already defined\n", __func__, cmd_name);
613 return;
614 }
615
616 cmd = (cmd_function_t*)Hunk_Alloc(sizeof(cmd_function_t));
617 cmd->name = cmd_name;
618 cmd->function = function;
619 cmd->completion = NULL;
620 cmd->stree.string = cmd->name;
621 STree_Insert(&cmd_tree, &cmd->stree);
622 }
623
624 void
Cmd_SetCompletion(const char * cmd_name,cmd_arg_f completion)625 Cmd_SetCompletion(const char *cmd_name, cmd_arg_f completion)
626 {
627 cmd_function_t *cmd;
628
629 cmd = Cmd_FindCommand(cmd_name);
630 if (cmd)
631 cmd->completion = completion;
632 else
633 Sys_Error("%s: no such command - %s", __func__, cmd_name);
634 }
635
636 /*
637 ============
638 Cmd_Exists
639 ============
640 */
641 qboolean
Cmd_Exists(const char * cmd_name)642 Cmd_Exists(const char *cmd_name)
643 {
644 return Cmd_FindCommand(cmd_name) != NULL;
645 }
646
647 qboolean
Cmd_Alias_Exists(const char * cmd_name)648 Cmd_Alias_Exists(const char *cmd_name)
649 {
650 return Cmd_Alias_Find(cmd_name) != NULL;
651 }
652
653
654 #ifdef NQ_HACK
655 /*
656 ===================
657 Cmd_ForwardToServer
658
659 Sends the entire command line over to the server
660 ===================
661 */
662 void
Cmd_ForwardToServer(void)663 Cmd_ForwardToServer(void)
664 {
665 if (cls.state < ca_connected) {
666 Con_Printf("Can't \"%s\", not connected\n", Cmd_Argv(0));
667 return;
668 }
669
670 if (cls.demoplayback)
671 return; // not really connected
672
673 MSG_WriteByte(&cls.message, clc_stringcmd);
674 if (strcasecmp(Cmd_Argv(0), "cmd") != 0) {
675 SZ_Print(&cls.message, Cmd_Argv(0));
676 SZ_Print(&cls.message, " ");
677 }
678 if (Cmd_Argc() > 1)
679 SZ_Print(&cls.message, Cmd_Args());
680 else
681 SZ_Print(&cls.message, "\n");
682 }
683 #endif
684 #ifdef QW_HACK
685 #ifndef SERVERONLY
686 /*
687 ===================
688 Cmd_ForwardToServer
689
690 adds the current command line as a clc_stringcmd to the client message.
691 things like godmode, noclip, etc, are commands directed to the server,
692 so when they are typed in at the console, they will need to be forwarded.
693 ===================
694 */
695 void
Cmd_ForwardToServer(void)696 Cmd_ForwardToServer(void)
697 {
698 if (cls.state == ca_disconnected) {
699 Con_Printf("Can't \"%s\", not connected\n", Cmd_Argv(0));
700 return;
701 }
702
703 if (cls.demoplayback)
704 return; // not really connected
705
706 MSG_WriteByte(&cls.netchan.message, clc_stringcmd);
707 SZ_Print(&cls.netchan.message, Cmd_Argv(0));
708 if (Cmd_Argc() > 1) {
709 SZ_Print(&cls.netchan.message, " ");
710 SZ_Print(&cls.netchan.message, Cmd_Args());
711 }
712 }
713
714 // don't forward the first argument
715 static void
Cmd_ForwardToServer_f(void)716 Cmd_ForwardToServer_f(void)
717 {
718 if (cls.state == ca_disconnected) {
719 Con_Printf("Can't \"%s\", not connected\n", Cmd_Argv(0));
720 return;
721 }
722
723 if (strcasecmp(Cmd_Argv(1), "snap") == 0) {
724 Cbuf_InsertText("snap\n");
725 return;
726 }
727
728 if (cls.demoplayback)
729 return; // not really connected
730
731 if (Cmd_Argc() > 1) {
732 MSG_WriteByte(&cls.netchan.message, clc_stringcmd);
733 SZ_Print(&cls.netchan.message, Cmd_Args());
734 }
735 }
736 #else
737 void
Cmd_ForwardToServer(void)738 Cmd_ForwardToServer(void)
739 {
740 }
741 #endif /* SERVERONLY */
742 #endif /* QW_HACK */
743
744 /*
745 ============
746 Cmd_ExecuteString
747
748 A complete command line has been parsed, so try to execute it
749 FIXME: lookupnoadd the token to speed search?
750 ============
751 */
752 void
753 #ifdef NQ_HACK
Cmd_ExecuteString(const char * text,cmd_source_t src)754 Cmd_ExecuteString(const char *text, cmd_source_t src)
755 #endif
756 #ifdef QW_HACK
757 Cmd_ExecuteString(const char *text)
758 #endif
759 {
760 cmd_function_t *cmd;
761 cmdalias_t *a;
762
763 #ifdef NQ_HACK
764 cmd_source = src;
765 #endif
766 Cmd_TokenizeString(text);
767
768 // execute the command line
769 if (!Cmd_Argc())
770 return; // no tokens
771
772 // check functions
773 cmd = Cmd_FindCommand(cmd_argv[0]);
774 if (cmd) {
775 if (cmd->function)
776 cmd->function();
777 #ifdef QW_HACK
778 else
779 Cmd_ForwardToServer();
780 #endif
781 return;
782 }
783
784 // check alias
785 a = Cmd_Alias_Find(cmd_argv[0]);
786 if (a) {
787 Cbuf_InsertText(a->value);
788 return;
789 }
790
791 // check cvars
792 if (!Cvar_Command() && (cl_warncmd.value || developer.value))
793 Con_Printf("Unknown command \"%s\"\n", Cmd_Argv(0));
794 }
795
796 /*
797 * Return a string tree with all possible argument completions of the given
798 * buffer for the given command.
799 */
800 struct stree_root *
Cmd_ArgCompletions(const char * name,const char * buf)801 Cmd_ArgCompletions(const char *name, const char *buf)
802 {
803 cmd_function_t *cmd;
804 struct stree_root *root = NULL;
805
806 cmd = Cmd_FindCommand(name);
807 if (cmd && cmd->completion)
808 root = cmd->completion(buf);
809
810 return root;
811 }
812
813 /*
814 * Call the argument completion function for cmd "name".
815 * Returned result should be Z_Free'd after use.
816 */
817 const char *
Cmd_ArgComplete(const char * name,const char * buf)818 Cmd_ArgComplete(const char *name, const char *buf)
819 {
820 char *result = NULL;
821 struct stree_root *root;
822
823 root = Cmd_ArgCompletions(name, buf);
824 if (root) {
825 result = STree_MaxMatch(root, buf);
826 Z_Free(root);
827 }
828
829 return result;
830 }
831
832
833 /*
834 ================
835 Cmd_CheckParm
836
837 Returns the position (1 to argc-1) in the command's argument list
838 where the given parameter apears, or 0 if not present
839 ================
840 */
841 int
Cmd_CheckParm(const char * parm)842 Cmd_CheckParm(const char *parm)
843 {
844 int i;
845
846 if (!parm)
847 Sys_Error("Cmd_CheckParm: NULL");
848
849 for (i = 1; i < Cmd_Argc(); i++)
850 if (!strcasecmp(parm, Cmd_Argv(i)))
851 return i;
852
853 return 0;
854 }
855
Cmd_CommandCompletions(const char * buf)856 struct stree_root *Cmd_CommandCompletions(const char *buf)
857 {
858 struct stree_root *root_tree;
859
860 root_tree = (struct stree_root*)Z_Malloc(sizeof(struct stree_root));
861
862 root_tree->entries = 0;
863 root_tree->maxlen = 0;
864 root_tree->minlen = -1;
865 //root_tree->root = {NULL};
866 root_tree->stack = NULL;
867
868 STree_AllocInit();
869
870 STree_Completions(root_tree, &cmd_tree, buf);
871 STree_Completions(root_tree, &cmdalias_tree, buf);
872 STree_Completions(root_tree, &cvar_tree, buf);
873
874 return root_tree;
875 }
876
Cmd_CommandComplete(const char * buf)877 const char *Cmd_CommandComplete(const char *buf)
878 {
879 struct stree_root *root;
880 char *ret;
881
882 root = Cmd_CommandCompletions(buf);
883 ret = STree_MaxMatch(root, buf);
884 Z_Free(root);
885
886 return ret;
887 }
888