In 2014, I think we can stop trying to outsmart the compiler. Remove
[vde.git] / vde-2 / src / lib / libvdehist.c
blob1f69d9a1dbab178f8fe568512d84d256d2a7d562
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 if (commandlist == NULL && st->mgmtfd >= 0)
327 vdehist_create_commandlist(st->mgmtfd);
328 /* redraw the input line */
329 redraw_line(st,1);
332 static int hist_sendcmd(struct vdehiststat *st)
334 char *cmd=st->linebuf;
335 if (st->status != HIST_COMMAND) {
336 cmd=vdehist_logincmd(cmd,st->bufindex,st);
337 if (commandlist == NULL && st->mgmtfd >= 0)
338 vdehist_create_commandlist(st->mgmtfd);
339 if (cmd==NULL)
340 return 0;
342 while (*cmd == ' ' || *cmd == '\t')
343 cmd++;
344 if (strncmp(cmd,"logout",6)==0)
345 return 1;
346 else {
347 if (*cmd != 0) {
348 write(st->mgmtfd,st->linebuf,st->bufindex);
349 if (strncmp(cmd,"shutdown",8)==0)
350 return 2;
352 vdehist_termwrite(st->termfd,"\r\n",2);
353 vdehist_termwrite(st->termfd,prompt,strlen(prompt));
355 if (commandlist != NULL &&
356 (strncmp(cmd,"plugin/add",10) == 0 ||
357 strncmp(cmd,"plugin/del",10) == 0)) {
358 free(commandlist);
359 commandlist=NULL;
361 return 0;
364 static void put_history(struct vdehiststat *st)
366 if(st->history[st->histindex])
367 free(st->history[st->histindex]);
368 st->history[st->histindex]=strdup(st->linebuf);
371 static void get_history(int change,struct vdehiststat *st)
373 st->histindex += change;
374 if(st->histindex < 0) st->histindex=0;
375 if(st->histindex >= HISTORYSIZE) st->histindex=HISTORYSIZE-1;
376 if(st->history[st->histindex] == NULL) (st->histindex)--;
377 strcpy(st->linebuf,st->history[st->histindex]);
378 st->bufindex=strlen(st->linebuf);
381 static void shift_history(struct vdehiststat *st)
383 if (st->history[HISTORYSIZE-1] != NULL)
384 free(st->history[HISTORYSIZE-1]);
385 memmove(st->history+1,st->history,(HISTORYSIZE-1)*sizeof(char *));
386 st->history[0]=NULL;
389 static void telnet_option_send3(int fd,int action,int object)
391 char opt[3];
392 opt[0]=0xff;
393 opt[1]=action;
394 opt[2]=object;
395 vdehist_termwrite(fd,opt,3);
398 static int telnet_options(struct vdehiststat *st,unsigned char *s)
400 int action_n_object;
401 if (st->telnetprotocol == 0) {
402 st->telnetprotocol=1;
403 st->echo=0;
404 telnet_option_send3(st->termfd,WILL,TELOPT_ECHO);
406 int skip=2;
407 s++;
408 action_n_object=((*s)<<8) + (*(s+1));
409 switch (action_n_object) {
410 case (DO<<8) + TELOPT_ECHO:
411 //printf("okay echo\n");
412 st->echo=1;
413 break;
414 case (WILL<<8) + TELOPT_ECHO:
415 telnet_option_send3(st->termfd,DONT,TELOPT_ECHO);
416 telnet_option_send3(st->termfd,WILL,TELOPT_ECHO);
417 break;
418 case (DO<<8) + TELOPT_SGA:
419 //printf("do sga -> okay will sga\n");
420 telnet_option_send3(st->termfd,WILL,TELOPT_SGA);
421 break;
422 case (WILL<<8) + TELOPT_TTYPE:
423 //printf("will tty -> dont tty\n");
424 telnet_option_send3(st->termfd,DONT,TELOPT_TTYPE);
425 break;
426 default:
427 //printf("not managed yet %x %x\n",*s,*(s+1));
428 if (*s == WILL)
429 telnet_option_send3(st->termfd,DONT,*(s+1));
430 else if (*s == DO)
431 telnet_option_send3(st->termfd,WONT,*(s+1));
433 return skip;
436 int vdehist_term_to_mgmt(struct vdehiststat *st)
438 unsigned char buf[BUFSIZE];
439 int n,i,rv=0;
440 n=vdehist_termread(st->termfd,buf,BUFSIZE);
441 //printf("termto mgmt N%d %x %x %x %x\n",n,buf[0],buf[1],buf[2],buf[3]);
442 if (n==0)
443 return 1;
444 else if (n<0)
445 return n;
446 else {
447 for (i=0;i<n && strlen(st->linebuf)<BUFSIZE;i++) {
448 if (buf[i] == 0xff && buf[i+1] == 0xff)
449 i++;
450 if(buf[i]==0) buf[i]='\n'; /*telnet encode \n as a 0 when in raw mode*/
451 if (buf[i] == 0xff && buf[i+1] != 0xff) {
452 i+=telnet_options(st,buf+i);
453 } else
455 if(buf[i] == 0x1b) {
456 /* ESCAPE! */
457 if (buf[i+1]=='[' && st->status == HIST_COMMAND) {
458 st->edited=1;
459 switch (buf[i+2]) {
460 case 'A': //fprintf(stderr,"UP\n");
461 erase_line(st,0);
462 put_history(st);
463 get_history(1,st);
464 redraw_line(st,0);
465 st->bufindex=strlen(st->linebuf);
466 break;
467 case 'B': //fprintf(stderr,"DOWN\n");
468 erase_line(st,0);
469 put_history(st);
470 get_history(-1,st);
471 redraw_line(st,0);
472 break;
473 case 'C': //fprintf(stderr,"RIGHT\n");
474 if (st->linebuf[st->bufindex] != '\0') {
475 vdehist_termwrite(st->termfd,"\033[C",3);
476 (st->bufindex)++;
478 break;
479 case 'D': //fprintf(stderr,"LEFT\n");
480 if (st->bufindex > 0) {
481 vdehist_termwrite(st->termfd,"\033[D",3);
482 (st->bufindex)--;
484 break;
486 i+=3;
488 else
489 i+=2;/* ignored */
490 } else if(buf[i] < 0x20 && !(buf[i] == '\n' || buf[i] == '\r')) {
491 /*ctrl*/
492 if (buf[i] == 4) /*ctrl D is a shortcut for UNIX people! */ {
493 rv=1;
494 break;
496 switch (buf[i]) {
497 case 3: /*ctrl C cleans the current buffer */
498 erase_line(st,0);
499 st->bufindex=0;
500 st->linebuf[(st->bufindex)]=0;
501 break;
502 case 12: /* ctrl L redraw */
503 erase_line(st,1);
504 redraw_line(st,1);
505 break;
506 case 1: /* ctrl A begin of line */
507 erase_line(st,0);
508 st->bufindex=0;
509 redraw_line(st,0);
510 break;
511 case 5: /* ctrl E endofline */
512 erase_line(st,0);
513 st->bufindex=strlen(st->linebuf);
514 redraw_line(st,0);
515 case '\t': /* tab */
516 if (st->lastchar== '\t') {
517 erase_line(st,1);
518 showexpand(st->linebuf,st->bufindex,st->termfd);
519 redraw_line(st,1);
520 } else {
521 erase_line(st,0);
522 st->bufindex=tabexpand(st->linebuf,st->bufindex,BUFSIZE);
523 redraw_line(st,0);
525 break;
527 } else if(buf[i] == 0x7f) {
528 if(st->bufindex > 0) {
529 char *x;
530 (st->bufindex)--;
531 x=st->linebuf+st->bufindex;
532 memmove(x,x+1,strlen(x));
533 if (st->echo && !(st->status & HIST_PASSWDFLAG)) {
534 if (st->edited)
535 vdehist_termwrite(st->termfd,"\010\033[P",4);
536 else
537 vdehist_termwrite(st->termfd,"\010 \010",3);
540 } else {
541 if (st->echo && !(st->status & HIST_PASSWDFLAG)) {
542 if (st->edited && buf[i] >= ' ')
543 vdehist_termwrite(st->termfd,"\033[@",3);
544 vdehist_termwrite(st->termfd,&(buf[i]),1);
546 if (buf[i] != '\r') {
547 if (buf[i]=='\n') {
548 if (st->status == HIST_COMMAND) {
549 st->histindex=0;
550 put_history(st);
551 if (strlen(st->linebuf) > 0)
552 shift_history(st);
554 st->bufindex=strlen(st->linebuf);
555 if ((rv=hist_sendcmd(st)) != 0)
556 break;
557 st->bufindex=st->edited=st->histindex=0;
558 st->linebuf[(st->bufindex)]=0;
559 } else {
560 char *x;
561 x=st->linebuf+st->bufindex;
562 memmove(x+1,x,strlen(x)+1);
563 st->linebuf[(st->bufindex)++]=buf[i];
567 st->lastchar=buf[i];
570 return rv;
573 struct vdehiststat *vdehist_new(int termfd,int mgmtfd) {
574 struct vdehiststat *st;
575 if (commandlist == NULL && mgmtfd >= 0)
576 vdehist_create_commandlist(mgmtfd);
577 st=malloc(sizeof(struct vdehiststat));
578 if (st) {
579 int i;
580 if (mgmtfd < 0)
581 st->status=HIST_NOCMD;
582 else
583 st->status=HIST_COMMAND;
584 st->echo=1;
585 st->telnetprotocol=0;
586 st->bufindex=st->edited=st->histindex=st->vbufindex=st->vindata=st->lastchar=0;
587 st->linebuf[(st->bufindex)]=0;
588 st->vlinebuf[(st->vbufindex)]=0;
589 st->termfd=termfd;
590 st->mgmtfd=mgmtfd;
591 for (i=0;i<HISTORYSIZE;i++)
592 st->history[i]=0;
594 return st;
597 void vdehist_free(struct vdehiststat *st)
599 if (st) {
600 int i;
601 for (i=0;i<HISTORYSIZE;i++)
602 if(st->history[i])
603 free(st->history[i]);
604 free(st);
608 int vdehist_getstatus(struct vdehiststat *st)
610 return st->status;
613 void vdehist_setstatus(struct vdehiststat *st,int status)
615 st->status=status;
619 int vdehist_gettermfd(struct vdehiststat *st)
621 return st->termfd;
625 int vdehist_getmgmtfd(struct vdehiststat *st)
627 return st->mgmtfd;
630 void vdehist_setmgmtfd(struct vdehiststat *st,int mgmtfd)
632 st->mgmtfd=mgmtfd;