Skip to content

Commit 354740a

Browse files
committed
various bugfixes
1 parent 7115310 commit 354740a

2 files changed

Lines changed: 129 additions & 32 deletions

File tree

yacli.c

Lines changed: 126 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// $Id: yacli.c,v 4.12 2026/04/29 23:03:05 bbonev Exp $
1+
// $Id: yacli.c,v 4.13 2026/04/30 06:59:53 bbonev Exp $
22
//
33
// Copyright © 2015-2020 Boian Bonev (bbonev@ipacct.com) {{{
44
//
@@ -140,6 +140,8 @@ struct _yacli {
140140
int moresiz; // morebuf alloc size
141141
int parsedcnt; // parsed command word count
142142
int parsedsiz; // parsed command array size
143+
int hstmax; // soft cap on history entries; 0 = unlimited
144+
int hstcnt; // current history entry count
143145
uint8_t more:1; // enable paged output
144146
uint8_t redraw:1; // prompt needs redraw
145147
uint8_t wastab:1; // control for double tab
@@ -214,32 +216,39 @@ inline void yacli_set_showtermsize(yacli *cli,int v) { // {{{
214216
cli->showtsize=!!v;
215217
} // }}}
216218

217-
static char myver[]="\0Yet another command line interface library (https://github.com/bbonev/yacli) $Revision: 4.12 $\n\n"; // {{{
219+
static char myver[]="\0Yet another command line interface library (https://github.com/bbonev/yacli) $Revision: 4.13 $\n\n"; // {{{
218220
// }}}
219221

220222
inline const char *yacli_ver(void) { // {{{
221223
return myver;
222224
} // }}}
223225

226+
static inline void yacli_buf_zeroterm(yacli *cli);
227+
224228
static inline int yacli_buf_inc(char **buf,int *siz,int *len,int add) { // {{{
225-
// increase buffer by len, if needed
229+
// ensure *siz can hold *len+add bytes (strictly above, so a trailing null fits); growth rounded to BUFFER_STEP
226230
// return 0 on success, non-zero on error
231+
int needed,alloclen;
232+
char *n;
233+
227234
if (add<=0)
228235
return 0; // ok
229-
if (*siz<=*len+add) { // need to realloc
230-
int alloclen=*siz+(*len/BUFFER_STEP+1)*BUFFER_STEP;
231-
char *n=calloc(1,alloclen);
232-
233-
if (!n) // no free mem, nothing more to do
234-
return -1;
236+
needed=*len+add;
237+
if (*siz>needed) // already enough
238+
return 0;
239+
alloclen=((needed+BUFFER_STEP)/BUFFER_STEP)*BUFFER_STEP; // next BUFFER_STEP-aligned size strictly above needed
240+
if (alloclen<=*siz) // assure progress when needed lands exactly at a BUFFER_STEP boundary
241+
alloclen=*siz+BUFFER_STEP;
242+
n=calloc(1,alloclen);
243+
if (!n) // no free mem, nothing more to do
244+
return -1;
235245

236-
if (*siz&&*buf)
237-
memcpy(n,*buf,*siz);
238-
if (*buf)
239-
free(*buf);
240-
*buf=n;
241-
*siz=alloclen;
242-
}
246+
if (*len&&*buf) // copy only valid contents, not slack
247+
memcpy(n,*buf,*len);
248+
if (*buf)
249+
free(*buf);
250+
*buf=n;
251+
*siz=alloclen;
243252
return 0;
244253
} // }}}
245254

@@ -459,7 +468,7 @@ static inline int yacli_filter_feed_include(filter_inst *fltr,const char *line,i
459468
if (!fltr->next->fltr->feed)
460469
return -1;
461470

462-
if (yacli_buf_inc(&fltr->buf,&fltr->bufsiz,&fltr->buflen,fltr->buflen+len+1)) // +1 for zero term
471+
if (yacli_buf_inc(&fltr->buf,&fltr->bufsiz,&fltr->buflen,len+1)) // additional bytes beyond current contents (+1 for zero term)
463472
return -1; // no memory
464473

465474
memcpy(fltr->buf+fltr->buflen,line,len);
@@ -519,7 +528,7 @@ static inline int yacli_filter_feed_exclude(filter_inst *fltr,const char *line,i
519528
if (!fltr->next->fltr->feed)
520529
return -1;
521530

522-
if (yacli_buf_inc(&fltr->buf,&fltr->bufsiz,&fltr->buflen,fltr->buflen+len+1)) // +1 for zero term
531+
if (yacli_buf_inc(&fltr->buf,&fltr->bufsiz,&fltr->buflen,len+1)) // additional bytes beyond current contents (+1 for zero term)
523532
return -1; // no memory
524533

525534
memcpy(fltr->buf+fltr->buflen,line,len);
@@ -598,7 +607,10 @@ static inline void yacli_filter_done_count(filter_inst *fltr) { // {{{
598607
} // }}}
599608

600609
inline yacli *yacli_init(yascreen *s) { // {{{
610+
static volatile int verfmt_state=0; // 0=untouched, 1=formatting in progress, 2=done; serializes the one-time reformat of myver
601611
yacli *cli=calloc(1,sizeof *cli);
612+
char *rev;
613+
int vermaj,vermin;
602614

603615
if (!cli)
604616
return NULL;
@@ -612,18 +624,20 @@ inline yacli *yacli_init(yascreen *s) { // {{{
612624
// we use explicit flush, tell yascreen not to do it
613625
yascreen_line_flush(cli->s,0);
614626

615-
if (myver[0]==0) { // reformat the static version string
616-
char *rev=strstr(myver+1,"$Revision: ");
617-
int vermaj,vermin;
618-
627+
if (__sync_bool_compare_and_swap(&verfmt_state,0,1)) { // first caller wins, reformats the static version string
628+
rev=strstr(myver+1,"$Revision: ");
619629
if (rev) {
620630
sscanf(rev+strlen("$Revision: "),"%d.%d",&vermaj,&vermin);
621631
vermaj+=vermin/100;
622632
vermin=vermin%100;
623633
memmove(myver,myver+1,strlen(myver+1)+1);
624634
snprintf(rev-1,sizeof myver-(rev-1-myver),"%d.%02d\n\n",vermaj,vermin);
625635
}
626-
}
636+
__sync_synchronize();
637+
verfmt_state=2;
638+
} else
639+
while (verfmt_state!=2) // another thread is formatting; wait for it to finish before reading myver
640+
__sync_synchronize();
627641

628642
cli->hostname=strdup("none");
629643
if (!cli->hostname)
@@ -759,8 +773,10 @@ static inline void yacli_clear_parsed(yacli *cli) { // {{{
759773
return;
760774
}
761775

762-
for (i=0;i<cli->parsedcnt;i++)
776+
for (i=0;i<cli->parsedcnt;i++) {
763777
free(cli->parsedcmd[i]);
778+
cli->parsedcmd[i]=NULL;
779+
}
764780
cli->parsedcnt=0;
765781
} // }}}
766782

@@ -834,6 +850,8 @@ inline void yacli_set_banner(yacli *cli,const char *banner) { // {{{
834850

835851
if (!cli)
836852
return;
853+
if (!banner)
854+
return;
837855

838856
t=cli->banner;
839857
cli->banner=strdup(banner);
@@ -848,6 +866,8 @@ inline void yacli_set_level(yacli *cli,const char *level) { // {{{
848866

849867
if (!cli)
850868
return;
869+
if (!level)
870+
return;
851871

852872
t=cli->level;
853873
cli->level=strdup(level);
@@ -863,6 +883,8 @@ inline void yacli_set_hostname(yacli *cli,const char *hostname) { // {{{
863883

864884
if (!cli)
865885
return;
886+
if (!hostname)
887+
return;
866888

867889
t=cli->hostname;
868890
cli->hostname=strdup(hostname);
@@ -1205,10 +1227,12 @@ inline int yacli_add_hist(yacli *cli,const char *buf) { // {{{
12051227

12061228
if (!cli)
12071229
return 0;
1230+
if (!buf)
1231+
return 0;
12081232

12091233
cli->hst_p=NULL;
12101234

1211-
if (!strlen(buf)) // skip empty command
1235+
if (!*buf) // skip empty command
12121236
return 0;
12131237
if (cli->hst&&!strcmp(buf,cli->hst->prev->command)) // skip repeated command
12141238
return 0;
@@ -1233,9 +1257,32 @@ inline int yacli_add_hist(yacli *cli,const char *buf) { // {{{
12331257
cli->hst->prev=h;
12341258
h->prev->next=h;
12351259
}
1260+
cli->hstcnt++;
1261+
while (cli->hstmax>0&&cli->hstcnt>cli->hstmax&&cli->hst) { // evict oldest entries until under cap
1262+
history *t=cli->hst; // hst points to the OLDEST item; hst->prev is the newest
1263+
1264+
if (t->next==t) { // single item — should not happen if we just added one
1265+
free(t->command);
1266+
free(t);
1267+
cli->hst=NULL;
1268+
} else {
1269+
cli->hst=t->next;
1270+
cli->hst->prev=t->prev;
1271+
t->prev->next=cli->hst;
1272+
free(t->command);
1273+
free(t);
1274+
}
1275+
cli->hstcnt--;
1276+
}
12361277
return 0;
12371278
} // }}}
12381279

1280+
inline void yacli_set_hist_max(yacli *cli,int n) { // {{{
1281+
if (!cli)
1282+
return;
1283+
cli->hstmax=n<0?0:n;
1284+
} // }}}
1285+
12391286
static inline void yacli_moveleft(yacli *cli) { // {{{
12401287
if (!cli)
12411288
return;
@@ -1266,6 +1313,8 @@ static inline void yacli_moveleftw(yacli *cli) { // {{{
12661313
if (!cli)
12671314
return;
12681315

1316+
yacli_buf_zeroterm(cli); // assure buffer[buflen]=='\0' so walks past valid data terminate cleanly
1317+
12691318
if (cli->cursor) {
12701319
if (cli->buffer[cli->cursor]!=' '&&cli->buffer[cli->cursor-1]==' ') // if we are at word begin, step left
12711320
cli->cursor--;
@@ -1287,6 +1336,8 @@ static inline void yacli_moverightw(yacli *cli) { // {{{
12871336
if (!cli)
12881337
return;
12891338

1339+
yacli_buf_zeroterm(cli); // assure buffer[buflen]=='\0'
1340+
12901341
if (cli->cursor<cli->buflen) {
12911342
int shiftr=yacli_bufpos_shiftr(cli);
12921343

@@ -1369,6 +1420,8 @@ static inline void yacli_delword(yacli *cli) { // {{{
13691420
if (cli->cursor>=cli->buflen) // nothing to delete
13701421
return;
13711422

1423+
yacli_buf_zeroterm(cli); // assure buffer[buflen]=='\0'
1424+
13721425
if (cli->buffer[cli->cursor]==' ')
13731426
while (cli->buffer[cli->cursor]==' '&&cli->cursor<cli->buflen)
13741427
yacli_del(cli);
@@ -1383,6 +1436,8 @@ static inline void yacli_delprevword(yacli *cli) { // {{{
13831436
if (!cli->cursor) // nothing to delete
13841437
return;
13851438

1439+
yacli_buf_zeroterm(cli); // assure buffer[buflen]=='\0'
1440+
13861441
if (cli->cursor>0&&cli->buffer[cli->cursor-1]==' ')
13871442
while (cli->buffer[cli->cursor-1]==' '&&cli->cursor)
13881443
yacli_bsp(cli);
@@ -1406,14 +1461,17 @@ static inline void yacli_setbuf(yacli *cli,const char *buf) { // {{{
14061461

14071462
if (!cli)
14081463
return;
1464+
if (!buf)
1465+
return;
14091466

14101467
len=strlen(buf);
1411-
add=len+1-cli->bufsiz; // add 1 for zero term by strcpy
1468+
add=len+1-cli->buflen; // additional bytes beyond current valid contents (+1 for trailing null)
14121469

14131470
if (yacli_buf_inc(&cli->buffer,&cli->bufsiz,&cli->buflen,add)) // error in malloc
14141471
return;
14151472

1416-
strncpy(cli->buffer,buf,cli->bufsiz);
1473+
memcpy(cli->buffer,buf,len);
1474+
cli->buffer[len]=0;
14171475
cli->buflen=len;
14181476
cli->cursor=len;
14191477
cli->redraw=1;
@@ -1858,11 +1916,12 @@ static inline void yacli_cmd_dump(yacli *cli) { // {{{
18581916
static inline void yacli_replace(yacli *cli,int pos,int len,const char *word) { // {{{
18591917
int wlen=strlen(word);
18601918
int add=wlen-len;
1919+
int grow=add>0?add+1:1; // need add extra bytes for the new content + 1 for trailing null safety
18611920

18621921
if (!cli)
18631922
return;
18641923

1865-
if (yacli_buf_inc(&cli->buffer,&cli->bufsiz,&cli->buflen,1)) // assure buffer can hold what is needed
1924+
if (yacli_buf_inc(&cli->buffer,&cli->bufsiz,&cli->buflen,grow))
18661925
return;
18671926
memmove(cli->buffer+pos+wlen,cli->buffer+pos+len,cli->buflen-pos-len);
18681927
memcpy(cli->buffer+pos,word,wlen);
@@ -1988,15 +2047,25 @@ static inline int yacli_trycomplete(yacli *cli,int docomplete) { // {{{
19882047
}
19892048

19902049
if (strlen(word)) { // ignore trailing ws yielding empty word
2050+
cmnode *dyn_resume=NULL; // sibling after a @-node we just stepped into; revisit if nothing in the dyn list matches
2051+
19912052
for (;;) {
19922053
int cmp;
19932054

19942055
if (cn->cmd[0]=='@') { // dynamic command
19952056
yacli_dyn_upd(cli,cn,word);
19962057
dyn=1;
2058+
dyn_resume=cn->next; // any literal/^ sibling sitting after the @ — try it if dyn list yields nothing
19972059
cn=cn->dyn;
1998-
if (!cn)
2060+
if (!cn) { // empty dyn list; resume from the saved sibling, if any
2061+
if (dyn_resume) {
2062+
cn=dyn_resume;
2063+
dyn_resume=NULL;
2064+
lastcn=cn;
2065+
continue;
2066+
}
19992067
break;
2068+
}
20002069
cmp=strcmp(word,cn->cmd);
20012070
} else if (cn->cmd[0]=='^') { // regex
20022071
cmp=yacli_regx(cn->cmd,word);
@@ -2051,6 +2120,12 @@ static inline int yacli_trycomplete(yacli *cli,int docomplete) { // {{{
20512120
lastcn=cn;
20522121
continue;
20532122
}
2123+
if (dyn_resume) { // exhausted dyn list without a match; retry the saved sibling chain
2124+
cn=dyn_resume;
2125+
dyn_resume=NULL;
2126+
lastcn=cn;
2127+
continue;
2128+
}
20542129
yacli_print_nof(cli,"\nNo matched command (2)\n");
20552130
cli->redraw=1;
20562131
free(fb);
@@ -2161,6 +2236,10 @@ static inline int yacli_trycomplete(yacli *cli,int docomplete) { // {{{
21612236
break;
21622237
} else { // regex match always returns 1 on no match
21632238
cn=cn->next;
2239+
if (!cn&&dyn_resume) { // exhausted dyn list (or chain past @) without matching; retry from saved sibling
2240+
cn=dyn_resume;
2241+
dyn_resume=NULL;
2242+
}
21642243
if (cn)
21652244
lastcn=cn;
21662245
if (!cn) {
@@ -2231,7 +2310,7 @@ static inline int yacli_trycomplete(yacli *cli,int docomplete) { // {{{
22312310
havenextfltr=1;
22322311
*worde=0;
22332312
}
2234-
while (worde[-1]==' ') { // sooner or later we will hit \0 or |
2313+
while (worde>word&&worde[-1]==' ') { // strip trailing ws of filter args; bounded by start of args
22352314
worde[-1]=0;
22362315
worde--;
22372316
}
@@ -2957,6 +3036,14 @@ inline void *yacli_add_cmd(yacli *cli,void *parent,const char *cmd,const char *h
29573036
if (par&&par->cli!=cli) // parent node must match cli
29583037
return NULL;
29593038

3039+
if (cmd[0]=='^') { // validate the regex up-front; reject malformed patterns now rather than silently failing every match later
3040+
regex_t re;
3041+
3042+
if (regcomp(&re,cmd,REG_EXTENDED)!=0)
3043+
return NULL;
3044+
regfree(&re);
3045+
}
3046+
29603047
if (par) // find proper place in the tree
29613048
place=&par->child;
29623049
else
@@ -3035,7 +3122,7 @@ inline void yacli_list(yacli *cli,void *ctx,const char *item) { // {{{
30353122

30363123
static inline void yacli_gen_modes(yacli *cli) { // {{{
30373124
int modelen=0;
3038-
cmstack *s,*l;
3125+
cmstack *s,*l=NULL;
30393126
int first=1;
30403127

30413128
if (!cli)
@@ -3078,6 +3165,8 @@ inline void yacli_enter_mode(yacli *cli,const char *mode,void *hint) { // {{{
30783165

30793166
if (!cli)
30803167
return;
3168+
if (!mode)
3169+
return;
30813170

30823171
s=calloc(1,sizeof *s);
30833172
if (!s) // crit err - no memory
@@ -3087,6 +3176,12 @@ inline void yacli_enter_mode(yacli *cli,const char *mode,void *hint) { // {{{
30873176
if (s->next)
30883177
s->next->prev=s;
30893178
s->mode=strdup(mode);
3179+
if (!s->mode) { // alloc failure: undo the partial push
3180+
if (s->next)
3181+
s->next->prev=NULL;
3182+
free(s);
3183+
return;
3184+
}
30903185
s->cmdt=cli->cmdt;
30913186
s->hint=hint;
30923187
cli->cmdt=NULL;

0 commit comments

Comments
 (0)