bugfix on libvdehist: memory leak. vdehist_create_commandlist rewritten.
[vde.git] / vde-2 / src / lib / libvdehist.c
blobff3c4886a66645697e790f8a2c3b5975e120e14e
1 /*
2 * libvdehist - A library to manage history and command completion for vde mgmt protocol
3 * Copyright (C) 2006 Renzo Davoli, University of Bologna
5 * This library is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU Lesser General Public License as published by
7 * the Free Software Foundation version 2.1 of the License, or (at
8 * your option) any later version.
10 * This library is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
13 * General Public License for more details.
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
20 #define _GNU_SOURCE
21 #include <unistd.h>
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <string.h>
25 #include <ctype.h>
26 #include <poll.h>
27 #include <arpa/telnet.h>
29 #include <vdecommon.h>
31 #define BUFSIZE 1024
32 #define HISTORYSIZE 32
34 extern char *prompt;
36 static char **commandlist;
38 typedef ssize_t (* ssize_fun)();
39 ssize_fun vdehist_vderead=read;
40 ssize_fun vdehist_vdewrite=write;
41 ssize_fun vdehist_termread=read;
42 ssize_fun vdehist_termwrite=write;
44 #define HIST_COMMAND 0x0
45 #define HIST_NOCMD 0x1
46 #define HIST_PASSWDFLAG 0x80
48 struct vdehiststat {
49 unsigned char status;
50 unsigned char echo;
51 unsigned char telnetprotocol;
52 unsigned char edited; /* the linebuf has been modified (left/right arrow)*/
53 unsigned char vindata; /* 1 when in_data... (0000 end with .)*/
54 char lastchar; /* for double tag*/
55 char linebuf[BUFSIZE]; /*line buffer from the user*/
56 int bufindex; /*current editing position on the buf */
57 char vlinebuf[BUFSIZE+1]; /*line buffer from vde*/
58 int vbufindex; /*current editing position on the buf */
59 char *history[HISTORYSIZE]; /*history of the previous commands*/
60 int histindex; /* index on the history (changed with up/down arrows) */
61 int termfd; /* fd to the terminal */
62 int mgmtfd; /* mgmt fd to vde_switch */
65 char * nologin(char *cmd,int len,struct vdehiststat *st) {
66 return NULL;
68 char * (* vdehist_logincmd)(char *cmd,int len,struct vdehiststat *s)
69 =nologin;
71 static int commonprefix(char *x, char *y,int maxlen)
73 int len=0;
74 while (*(x++)==*(y++) && len<maxlen)
75 len++;
76 return len;
79 static void showexpand(char *linebuf,int bufindex, int termfd)
81 char *buf;
82 size_t bufsize;
83 FILE *ms=open_memstream(&buf,&bufsize);
84 int nmatches=0;
85 if (ms) {
86 if (commandlist && bufindex>0) {
87 char **s=commandlist;
88 while (*s) {
89 if (strncmp(linebuf,*s,bufindex)==0) {
90 nmatches++;
91 fprintf(ms,"%s ",*s);
93 s++;
95 fprintf(ms,"\r\n");
97 fclose(ms);
98 if (nmatches > 1)
99 vdehist_termwrite(termfd,buf,strlen(buf));
100 free(buf);
104 static int tabexpand(char *linebuf,int bufindex,int maxlength)
106 if (commandlist && bufindex>0) {
107 char **s=commandlist;
108 int nmatches=0;
109 int len=0;
110 char *match=NULL;
111 while (*s) {
112 if (strncmp(linebuf,*s,bufindex)==0) {
113 nmatches++;
114 if (nmatches == 1) {
115 match=*s;
116 len=strlen(match);
117 } else
118 len=commonprefix(match,*s,len);
120 s++;
122 if (len > 0) {
123 int alreadymatch=commonprefix(linebuf,match,len);
124 //fprintf(stderr,"TAB %s %d -> %s %d already %d\n",linebuf,bufindex,match,len,alreadymatch);
125 if ((len-alreadymatch)+strlen(linebuf) < maxlength) {
126 memmove(linebuf+len,linebuf+alreadymatch,
127 strlen(linebuf+alreadymatch)+1);
128 memcpy(linebuf+alreadymatch,match+alreadymatch,len-alreadymatch);
129 if (nmatches == 1 && linebuf[len] != ' ' && strlen(linebuf)+1 < maxlength) {
130 memmove(linebuf+len+1,linebuf+len,
131 strlen(linebuf+len)+1);
132 linebuf[len]=' ';
133 len++;
135 bufindex=len;
139 return bufindex;
142 #define CC_HEADER 0
143 #define CC_BODY 1
144 #define CC_TERM 2
146 static int qstrcmp(const void *a,const void *b)
148 return strcmp(*(char * const *)a,*(char * const *)b);
151 struct vh_readln {
152 int readbufsize;
153 int readbufindex;
154 char readbuf[BUFSIZE];
157 static char *vdehist_readln(int vdefd,char *linebuf,int size,struct vh_readln *vlb)
159 int i;
160 char lastch=' ';
161 struct pollfd wfd={vdefd,POLLIN|POLLHUP,0};
162 i=0;
163 do {
164 if (vlb->readbufindex==vlb->readbufsize) {
165 poll(&wfd,1,-1);
166 if ((vlb->readbufsize=read(vdefd,vlb->readbuf,BUFSIZE)) <= 0)
167 return NULL;
168 vlb->readbufindex=0;
170 if (vlb->readbuf[vlb->readbufindex]==' ' && lastch=='$' && vlb->readbufindex==vlb->readbufsize-1)
171 return NULL;
172 lastch=linebuf[i]=vlb->readbuf[vlb->readbufindex];
173 i++;vlb->readbufindex++;
174 } while (lastch!='\n' && i<size-1);
175 linebuf[i]=0;
176 return linebuf;
179 /* create the commandlist (from the output of the help command) */
180 static void vdehist_create_commandlist(int vdefd)
182 char linebuf[BUFSIZE];
183 struct vh_readln readlnbuf={0,0};
184 char *buf;
185 size_t bufsize;
186 char *lastcommand=NULL;
187 /* use a memstream to create the array.
188 add (char *) elements by fwrite */
189 FILE *ms=open_memstream(&buf,&bufsize);
190 if (ms && vdefd >= 0) {
191 int status=CC_HEADER;
192 vdehist_vdewrite(vdefd,"help\n",5);
193 while (status != CC_TERM && vdehist_readln(vdefd,linebuf,BUFSIZE,&readlnbuf) != NULL) {
194 if (status == CC_HEADER) {
195 if (strncmp(linebuf,"------------",12) == 0)
196 status=CC_BODY;
197 } else {
198 if (strncmp(linebuf,".\n",2) == 0)
199 status=CC_TERM;
200 else {
201 char *s=linebuf;
202 while (*s!=' ' && *s != 0)
203 s++;
204 *s=0; /* take the first token */
205 /* test for menu header */
206 if (lastcommand) {
207 if (strncmp(lastcommand,linebuf,strlen(lastcommand)) == 0 &&
208 linebuf[strlen(lastcommand)] == '/')
209 free(lastcommand);
210 else
211 fwrite(&lastcommand, sizeof(char *), 1, ms);
213 lastcommand=strdup(linebuf);
217 if (lastcommand)
218 fwrite(&lastcommand, sizeof(char *), 1, ms);
219 lastcommand = NULL;
220 fwrite(&lastcommand, sizeof(char *), 1, ms);
221 fclose(ms);
222 commandlist=(char **)buf;
223 qsort(commandlist,(bufsize / sizeof(char *))-1,sizeof(char *),qstrcmp);
227 static void erase_line(struct vdehiststat *st,int prompt_too)
229 int j;
230 int size=st->bufindex+(prompt_too != 0)*strlen(prompt);
231 char *buf;
232 size_t bufsize;
233 FILE *ms=open_memstream(&buf,&bufsize);
234 if (ms) {
235 for (j=0;j<size;j++)
236 fputc('\010',ms);
237 size=strlen(st->linebuf)+(prompt_too != 0)*strlen(prompt);
238 for (j=0;j<size;j++)
239 fputc(' ',ms);
240 for (j=0;j<size;j++)
241 fputc('\010',ms);
242 fclose(ms);
243 if (buf)
244 vdehist_termwrite(st->termfd,buf,bufsize);
245 free(buf);
249 static void redraw_line(struct vdehiststat *st,int prompt_too)
251 int j;
252 int tail=strlen(st->linebuf)-st->bufindex;
253 char *buf;
254 size_t bufsize;
255 FILE *ms=open_memstream(&buf,&bufsize);
256 if (ms) {
257 if (prompt_too)
258 fprintf(ms,"%s%s",prompt,st->linebuf);
259 else
260 fprintf(ms,"%s",st->linebuf);
261 for (j=0;j<tail;j++)
262 fputc('\010',ms);
263 fclose(ms);
264 if (buf)
265 vdehist_termwrite(st->termfd,buf,bufsize);
266 free(buf);
270 void vdehist_mgmt_to_term(struct vdehiststat *st)
272 char buf[BUFSIZE+1];
273 int n=0,ib=0;
274 /* erase the input line */
275 erase_line(st,1);
276 /* if the communication with the manager object holds, print the output*/
277 //fprintf(stderr,"mgmt2term\n");
278 if (st->mgmtfd) {
279 n=vdehist_vderead(st->mgmtfd,buf,BUFSIZE);
280 //fprintf(stderr,"mgmt2term n=%d\n",n);
281 buf[n]=0;
282 while (n>0) {
283 for(ib=0;ib<n;ib++)
285 st->vlinebuf[(st->vbufindex)++]=buf[ib];
286 if (buf[ib] == '\n') {
287 st->vlinebuf[(st->vbufindex)-1]='\r';
288 st->vlinebuf[(st->vbufindex)]='\n';
289 st->vlinebuf[(st->vbufindex)+1]='\0';
290 (st->vbufindex)++;
291 if (st->vindata) {
292 if (st->vlinebuf[0]=='.' && st->vlinebuf[1]=='\r')
293 st->vindata=0;
294 else
295 vdehist_termwrite(st->termfd,st->vlinebuf,(st->vbufindex));
296 } else {
297 char *message=st->vlinebuf;
298 //fprintf(stderr,"MSG1 \"%s\"\n",message);
299 while (*message != '\0' &&
300 !(isdigit(message[0]) &&
301 isdigit(message[1]) &&
302 isdigit(message[2]) &&
303 isdigit(message[3])))
304 message++;
305 if (strncmp(message,"0000",4)==0)
306 st->vindata=1;
307 else if (isdigit(message[1]) &&
308 isdigit(message[2]) &&
309 isdigit(message[3])) {
310 if(message[0]=='1') {
311 message+=5;
312 vdehist_termwrite(st->termfd,message,strlen(message));
313 } else if (message[0]=='3') {
314 message+=5;
315 vdehist_termwrite(st->termfd,"** DBG MSG: ",12);
316 vdehist_termwrite(st->termfd,(message),strlen(message));
320 (st->vbufindex)=0;
323 n=vdehist_vderead(st->mgmtfd,buf,BUFSIZE);
326 /* redraw the input line */
327 redraw_line(st,1);
330 static int hist_sendcmd(struct vdehiststat *st)
332 char *cmd=st->linebuf;
333 if (st->status != HIST_COMMAND) {
334 cmd=vdehist_logincmd(cmd,st->bufindex,st);
335 if (commandlist == NULL && st->mgmtfd >= 0)
336 vdehist_create_commandlist(st->mgmtfd);
337 if (cmd==NULL)
338 return 0;
340 while (*cmd == ' ' || *cmd == '\t')
341 cmd++;
342 if (strncmp(cmd,"logout",6)==0)
343 return 1;
344 else {
345 if (*cmd != 0) {
346 write(st->mgmtfd,st->linebuf,st->bufindex);
347 if (strncmp(cmd,"shutdown",8)==0)
348 return 2;
350 vdehist_termwrite(st->termfd,"\r\n",2);
351 vdehist_termwrite(st->termfd,prompt,strlen(prompt));
353 return 0;
356 static void put_history(struct vdehiststat *st)
358 if(st->history[st->histindex])
359 free(st->history[st->histindex]);
360 st->history[st->histindex]=strdup(st->linebuf);
363 static void get_history(int change,struct vdehiststat *st)
365 st->histindex += change;
366 if(st->histindex < 0) st->histindex=0;
367 if(st->histindex >= HISTORYSIZE) st->histindex=HISTORYSIZE-1;
368 if(st->history[st->histindex] == NULL) (st->histindex)--;
369 strcpy(st->linebuf,st->history[st->histindex]);
370 st->bufindex=strlen(st->linebuf);
373 static void shift_history(struct vdehiststat *st)
375 if (st->history[HISTORYSIZE-1] != NULL)
376 free(st->history[HISTORYSIZE-1]);
377 memmove(st->history+1,st->history,(HISTORYSIZE-1)*sizeof(char *));
378 st->history[0]=NULL;
381 static void telnet_option_send3(int fd,int action,int object)
383 char opt[3];
384 opt[0]=0xff;
385 opt[1]=action;
386 opt[2]=object;
387 vdehist_termwrite(fd,opt,3);
390 static int telnet_options(struct vdehiststat *st,unsigned char *s)
392 register int action_n_object;
393 if (st->telnetprotocol == 0) {
394 st->telnetprotocol=1;
395 st->echo=0;
396 telnet_option_send3(st->termfd,WILL,TELOPT_ECHO);
398 int skip=2;
399 s++;
400 action_n_object=((*s)<<8) + (*(s+1));
401 switch (action_n_object) {
402 case (DO<<8) + TELOPT_ECHO:
403 //printf("okay echo\n");
404 st->echo=1;
405 break;
406 case (WILL<<8) + TELOPT_ECHO:
407 telnet_option_send3(st->termfd,DONT,TELOPT_ECHO);
408 telnet_option_send3(st->termfd,WILL,TELOPT_ECHO);
409 break;
410 case (DO<<8) + TELOPT_SGA:
411 //printf("do sga -> okay will sga\n");
412 telnet_option_send3(st->termfd,WILL,TELOPT_SGA);
413 break;
414 case (WILL<<8) + TELOPT_TTYPE:
415 //printf("will tty -> dont tty\n");
416 telnet_option_send3(st->termfd,DONT,TELOPT_TTYPE);
417 break;
418 default:
419 //printf("not managed yet %x %x\n",*s,*(s+1));
420 if (*s == WILL)
421 telnet_option_send3(st->termfd,DONT,*(s+1));
422 else if (*s == DO)
423 telnet_option_send3(st->termfd,WONT,*(s+1));
425 return skip;
428 int vdehist_term_to_mgmt(struct vdehiststat *st)
430 unsigned char buf[BUFSIZE];
431 int n,i,rv=0;
432 n=vdehist_termread(st->termfd,buf,BUFSIZE);
433 //printf("termto mgmt N%d %x %x %x %x\n",n,buf[0],buf[1],buf[2],buf[3]);
434 if (n==0)
435 return 1;
436 else if (n<0)
437 return n;
438 else {
439 for (i=0;i<n && strlen(st->linebuf)<BUFSIZE;i++) {
440 if (buf[i] == 0xff && buf[i+1] == 0xff)
441 i++;
442 if(buf[i]==0) buf[i]='\n'; /*telnet encode \n as a 0 when in raw mode*/
443 if (buf[i] == 0xff && buf[i+1] != 0xff) {
444 i+=telnet_options(st,buf+i);
445 } else
447 if(buf[i] == 0x1b) {
448 /* ESCAPE! */
449 if (buf[i+1]=='[' && st->status == HIST_COMMAND) {
450 st->edited=1;
451 switch (buf[i+2]) {
452 case 'A': //fprintf(stderr,"UP\n");
453 erase_line(st,0);
454 put_history(st);
455 get_history(1,st);
456 redraw_line(st,0);
457 st->bufindex=strlen(st->linebuf);
458 break;
459 case 'B': //fprintf(stderr,"DOWN\n");
460 erase_line(st,0);
461 put_history(st);
462 get_history(-1,st);
463 redraw_line(st,0);
464 break;
465 case 'C': //fprintf(stderr,"RIGHT\n");
466 if (st->linebuf[st->bufindex] != '\0') {
467 vdehist_termwrite(st->termfd,"\033[C",3);
468 (st->bufindex)++;
470 break;
471 case 'D': //fprintf(stderr,"LEFT\n");
472 if (st->bufindex > 0) {
473 vdehist_termwrite(st->termfd,"\033[D",3);
474 (st->bufindex)--;
476 break;
478 i+=3;
480 else
481 i+=2;/* ignored */
482 } else if(buf[i] < 0x20 && !(buf[i] == '\n' || buf[i] == '\r')) {
483 /*ctrl*/
484 if (buf[i] == 4) /*ctrl D is a shortcut for UNIX people! */ {
485 rv=1;
486 break;
488 switch (buf[i]) {
489 case 3: /*ctrl C cleans the current buffer */
490 erase_line(st,0);
491 st->bufindex=0;
492 st->linebuf[(st->bufindex)]=0;
493 break;
494 case 12: /* ctrl L redraw */
495 erase_line(st,1);
496 redraw_line(st,1);
497 break;
498 case 1: /* ctrl A begin of line */
499 erase_line(st,0);
500 st->bufindex=0;
501 redraw_line(st,0);
502 break;
503 case 5: /* ctrl E endofline */
504 erase_line(st,0);
505 st->bufindex=strlen(st->linebuf);
506 redraw_line(st,0);
507 case '\t': /* tab */
508 if (st->lastchar== '\t') {
509 erase_line(st,1);
510 showexpand(st->linebuf,st->bufindex,st->termfd);
511 redraw_line(st,1);
512 } else {
513 erase_line(st,0);
514 st->bufindex=tabexpand(st->linebuf,st->bufindex,BUFSIZE);
515 redraw_line(st,0);
517 break;
519 } else if(buf[i] == 0x7f) {
520 if(st->bufindex > 0) {
521 char *x;
522 (st->bufindex)--;
523 x=st->linebuf+st->bufindex;
524 memmove(x,x+1,strlen(x));
525 if (st->echo && !(st->status & HIST_PASSWDFLAG)) {
526 if (st->edited)
527 vdehist_termwrite(st->termfd,"\010\033[P",4);
528 else
529 vdehist_termwrite(st->termfd,"\010 \010",3);
532 } else {
533 if (st->echo && !(st->status & HIST_PASSWDFLAG)) {
534 if (st->edited && buf[i] >= ' ')
535 vdehist_termwrite(st->termfd,"\033[@",3);
536 vdehist_termwrite(st->termfd,&(buf[i]),1);
538 if (buf[i] != '\r') {
539 if (buf[i]=='\n') {
540 if (st->status == HIST_COMMAND) {
541 st->histindex=0;
542 put_history(st);
543 if (strlen(st->linebuf) > 0)
544 shift_history(st);
546 st->bufindex=strlen(st->linebuf);
547 if ((rv=hist_sendcmd(st)) != 0)
548 break;
549 st->bufindex=st->edited=st->histindex=0;
550 st->linebuf[(st->bufindex)]=0;
551 } else {
552 char *x;
553 x=st->linebuf+st->bufindex;
554 memmove(x+1,x,strlen(x)+1);
555 st->linebuf[(st->bufindex)++]=buf[i];
559 st->lastchar=buf[i];
562 return rv;
565 struct vdehiststat *vdehist_new(int termfd,int mgmtfd) {
566 struct vdehiststat *st;
567 if (commandlist == NULL && mgmtfd >= 0)
568 vdehist_create_commandlist(mgmtfd);
569 st=malloc(sizeof(struct vdehiststat));
570 if (st) {
571 int i;
572 if (mgmtfd < 0)
573 st->status=HIST_NOCMD;
574 else
575 st->status=HIST_COMMAND;
576 st->echo=1;
577 st->telnetprotocol=0;
578 st->bufindex=st->edited=st->histindex=st->vbufindex=st->vindata=st->lastchar=0;
579 st->linebuf[(st->bufindex)]=0;
580 st->vlinebuf[(st->vbufindex)]=0;
581 st->termfd=termfd;
582 st->mgmtfd=mgmtfd;
583 for (i=0;i<HISTORYSIZE;i++)
584 st->history[i]=0;
586 return st;
589 void vdehist_free(struct vdehiststat *st)
591 if (st) {
592 int i;
593 for (i=0;i<HISTORYSIZE;i++)
594 if(st->history[i])
595 free(st->history[i]);
596 free(st);
600 int vdehist_getstatus(struct vdehiststat *st)
602 return st->status;
605 void vdehist_setstatus(struct vdehiststat *st,int status)
607 st->status=status;
611 int vdehist_gettermfd(struct vdehiststat *st)
613 return st->termfd;
617 int vdehist_getmgmtfd(struct vdehiststat *st)
619 return st->mgmtfd;
622 void vdehist_setmgmtfd(struct vdehiststat *st,int mgmtfd)
624 st->mgmtfd=mgmtfd;