1 /* $OpenBSD: tran.c,v 1.40 2024/06/03 00:55:05 millert Exp $ */ 2 /**************************************************************** 3 Copyright (C) Lucent Technologies 1997 4 All Rights Reserved 5 6 Permission to use, copy, modify, and distribute this software and 7 its documentation for any purpose and without fee is hereby 8 granted, provided that the above copyright notice appear in all 9 copies and that both that the copyright notice and this 10 permission notice and warranty disclaimer appear in supporting 11 documentation, and that the name Lucent Technologies or any of 12 its entities not be used in advertising or publicity pertaining 13 to distribution of the software without specific, written prior 14 permission. 15 16 LUCENT DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, 17 INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. 18 IN NO EVENT SHALL LUCENT OR ANY OF ITS ENTITIES BE LIABLE FOR ANY 19 SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 20 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER 21 IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, 22 ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF 23 THIS SOFTWARE. 24 ****************************************************************/ 25 26 #define DEBUG 27 #include <stdio.h> 28 #include <math.h> 29 #include <ctype.h> 30 #include <string.h> 31 #include <stdlib.h> 32 #include "awk.h" 33 34 #define FULLTAB 2 /* rehash when table gets this x full */ 35 #define GROWTAB 4 /* grow table by this factor */ 36 37 Array *symtab; /* main symbol table */ 38 39 char **FS; /* initial field sep */ 40 char **RS; /* initial record sep */ 41 char **OFS; /* output field sep */ 42 char **ORS; /* output record sep */ 43 char **OFMT; /* output format for numbers */ 44 char **CONVFMT; /* format for conversions in getsval */ 45 Awkfloat *NF; /* number of fields in current record */ 46 Awkfloat *NR; /* number of current record */ 47 Awkfloat *FNR; /* number of current record in current file */ 48 char **FILENAME; /* current filename argument */ 49 Awkfloat *ARGC; /* number of arguments from command line */ 50 char **SUBSEP; /* subscript separator for a[i,j,k]; default \034 */ 51 Awkfloat *RSTART; /* start of re matched with ~; origin 1 (!) */ 52 Awkfloat *RLENGTH; /* length of same */ 53 54 Cell *fsloc; /* FS */ 55 Cell *nrloc; /* NR */ 56 Cell *nfloc; /* NF */ 57 Cell *fnrloc; /* FNR */ 58 Cell *ofsloc; /* OFS */ 59 Cell *orsloc; /* ORS */ 60 Cell *rsloc; /* RS */ 61 Cell *ARGVcell; /* cell with symbol table containing ARGV[...] */ 62 Cell *rstartloc; /* RSTART */ 63 Cell *rlengthloc; /* RLENGTH */ 64 Cell *subseploc; /* SUBSEP */ 65 Cell *symtabloc; /* SYMTAB */ 66 67 Cell *nullloc; /* a guaranteed empty cell */ 68 Node *nullnode; /* zero&null, converted into a node for comparisons */ 69 Cell *literal0; 70 71 extern Cell **fldtab; 72 73 void syminit(void) /* initialize symbol table with builtin vars */ 74 { 75 literal0 = setsymtab("0", "0", 0.0, NUM|STR|CON|DONTFREE, symtab); 76 /* this is used for if(x)... tests: */ 77 nullloc = setsymtab("$zero&null", "", 0.0, NUM|STR|CON|DONTFREE, symtab); 78 nullnode = celltonode(nullloc, CCON); 79 80 fsloc = setsymtab("FS", " ", 0.0, STR|DONTFREE, symtab); 81 FS = &fsloc->sval; 82 rsloc = setsymtab("RS", "\n", 0.0, STR|DONTFREE, symtab); 83 RS = &rsloc->sval; 84 ofsloc = setsymtab("OFS", " ", 0.0, STR|DONTFREE, symtab); 85 OFS = &ofsloc->sval; 86 orsloc = setsymtab("ORS", "\n", 0.0, STR|DONTFREE, symtab); 87 ORS = &orsloc->sval; 88 OFMT = &setsymtab("OFMT", "%.6g", 0.0, STR|DONTFREE, symtab)->sval; 89 CONVFMT = &setsymtab("CONVFMT", "%.6g", 0.0, STR|DONTFREE, symtab)->sval; 90 FILENAME = &setsymtab("FILENAME", "", 0.0, STR|DONTFREE, symtab)->sval; 91 nfloc = setsymtab("NF", "", 0.0, NUM, symtab); 92 NF = &nfloc->fval; 93 nrloc = setsymtab("NR", "", 0.0, NUM, symtab); 94 NR = &nrloc->fval; 95 fnrloc = setsymtab("FNR", "", 0.0, NUM, symtab); 96 FNR = &fnrloc->fval; 97 subseploc = setsymtab("SUBSEP", "\034", 0.0, STR|DONTFREE, symtab); 98 SUBSEP = &subseploc->sval; 99 rstartloc = setsymtab("RSTART", "", 0.0, NUM, symtab); 100 RSTART = &rstartloc->fval; 101 rlengthloc = setsymtab("RLENGTH", "", 0.0, NUM, symtab); 102 RLENGTH = &rlengthloc->fval; 103 symtabloc = setsymtab("SYMTAB", "", 0.0, ARR, symtab); 104 free(symtabloc->sval); 105 symtabloc->sval = (char *) symtab; 106 } 107 108 void arginit(int ac, char **av) /* set up ARGV and ARGC */ 109 { 110 Array *ap; 111 Cell *cp; 112 int i; 113 char temp[50]; 114 115 ARGC = &setsymtab("ARGC", "", (Awkfloat) ac, NUM, symtab)->fval; 116 cp = setsymtab("ARGV", "", 0.0, ARR, symtab); 117 ap = makesymtab(NSYMTAB); /* could be (int) ARGC as well */ 118 free(cp->sval); 119 cp->sval = (char *) ap; 120 for (i = 0; i < ac; i++) { 121 double result; 122 123 snprintf(temp, sizeof(temp), "%d", i); 124 if (is_number(*av, & result)) 125 setsymtab(temp, *av, result, STR|NUM, ap); 126 else 127 setsymtab(temp, *av, 0.0, STR, ap); 128 av++; 129 } 130 ARGVcell = cp; 131 } 132 133 void envinit(char **envp) /* set up ENVIRON variable */ 134 { 135 Array *ap; 136 Cell *cp; 137 char *p; 138 139 cp = setsymtab("ENVIRON", "", 0.0, ARR, symtab); 140 ap = makesymtab(NSYMTAB); 141 free(cp->sval); 142 cp->sval = (char *) ap; 143 for ( ; *envp; envp++) { 144 double result; 145 146 if ((p = strchr(*envp, '=')) == NULL) 147 continue; 148 if( p == *envp ) /* no left hand side name in env string */ 149 continue; 150 *p++ = 0; /* split into two strings at = */ 151 if (is_number(p, & result)) 152 setsymtab(*envp, p, result, STR|NUM, ap); 153 else 154 setsymtab(*envp, p, 0.0, STR, ap); 155 p[-1] = '='; /* restore in case env is passed down to a shell */ 156 } 157 } 158 159 Array *makesymtab(int n) /* make a new symbol table */ 160 { 161 Array *ap; 162 Cell **tp; 163 164 ap = (Array *) malloc(sizeof(*ap)); 165 tp = (Cell **) calloc(n, sizeof(*tp)); 166 if (ap == NULL || tp == NULL) 167 FATAL("out of space in makesymtab"); 168 ap->nelem = 0; 169 ap->size = n; 170 ap->tab = tp; 171 return(ap); 172 } 173 174 void freesymtab(Cell *ap) /* free a symbol table */ 175 { 176 Cell *cp, *temp; 177 Array *tp; 178 int i; 179 180 if (!isarr(ap)) 181 return; 182 tp = (Array *) ap->sval; 183 if (tp == NULL) 184 return; 185 for (i = 0; i < tp->size; i++) { 186 for (cp = tp->tab[i]; cp != NULL; cp = temp) { 187 xfree(cp->nval); 188 if (freeable(cp)) 189 xfree(cp->sval); 190 temp = cp->cnext; /* avoids freeing then using */ 191 free(cp); 192 tp->nelem--; 193 } 194 tp->tab[i] = NULL; 195 } 196 if (tp->nelem != 0) 197 WARNING("can't happen: inconsistent element count freeing %s", ap->nval); 198 free(tp->tab); 199 free(tp); 200 } 201 202 void freeelem(Cell *ap, const char *s) /* free elem s from ap (i.e., ap["s"] */ 203 { 204 Array *tp; 205 Cell *p, *prev = NULL; 206 int h; 207 208 tp = (Array *) ap->sval; 209 h = hash(s, tp->size); 210 for (p = tp->tab[h]; p != NULL; prev = p, p = p->cnext) 211 if (strcmp(s, p->nval) == 0) { 212 if (prev == NULL) /* 1st one */ 213 tp->tab[h] = p->cnext; 214 else /* middle somewhere */ 215 prev->cnext = p->cnext; 216 if (freeable(p)) 217 xfree(p->sval); 218 free(p->nval); 219 free(p); 220 tp->nelem--; 221 return; 222 } 223 } 224 225 Cell *setsymtab(const char *n, const char *s, Awkfloat f, unsigned t, Array *tp) 226 { 227 int h; 228 Cell *p; 229 230 if (n != NULL && (p = lookup(n, tp)) != NULL) { 231 DPRINTF("setsymtab found %p: n=%s s=\"%s\" f=%g t=%o\n", 232 (void*)p, NN(p->nval), NN(p->sval), p->fval, p->tval); 233 return(p); 234 } 235 p = (Cell *) malloc(sizeof(*p)); 236 if (p == NULL) 237 FATAL("out of space for symbol table at %s", n); 238 p->nval = tostring(n); 239 p->sval = s ? tostring(s) : tostring(""); 240 p->fval = f; 241 p->tval = t; 242 p->csub = CUNK; 243 p->ctype = OCELL; 244 tp->nelem++; 245 if (tp->nelem > FULLTAB * tp->size) 246 rehash(tp); 247 h = hash(n, tp->size); 248 p->cnext = tp->tab[h]; 249 tp->tab[h] = p; 250 DPRINTF("setsymtab set %p: n=%s s=\"%s\" f=%g t=%o\n", 251 (void*)p, p->nval, p->sval, p->fval, p->tval); 252 return(p); 253 } 254 255 int hash(const char *s, int n) /* form hash value for string s */ 256 { 257 unsigned hashval; 258 259 for (hashval = 0; *s != '\0'; s++) 260 hashval = (*s + 31 * hashval); 261 return hashval % n; 262 } 263 264 void rehash(Array *tp) /* rehash items in small table into big one */ 265 { 266 int i, nh, nsz; 267 Cell *cp, *op, **np; 268 269 nsz = GROWTAB * tp->size; 270 np = (Cell **) calloc(nsz, sizeof(*np)); 271 if (np == NULL) /* can't do it, but can keep running. */ 272 return; /* someone else will run out later. */ 273 for (i = 0; i < tp->size; i++) { 274 for (cp = tp->tab[i]; cp; cp = op) { 275 op = cp->cnext; 276 nh = hash(cp->nval, nsz); 277 cp->cnext = np[nh]; 278 np[nh] = cp; 279 } 280 } 281 free(tp->tab); 282 tp->tab = np; 283 tp->size = nsz; 284 } 285 286 Cell *lookup(const char *s, Array *tp) /* look for s in tp */ 287 { 288 Cell *p; 289 int h; 290 291 h = hash(s, tp->size); 292 for (p = tp->tab[h]; p != NULL; p = p->cnext) 293 if (strcmp(s, p->nval) == 0) 294 return(p); /* found it */ 295 return(NULL); /* not found */ 296 } 297 298 Awkfloat setfval(Cell *vp, Awkfloat f) /* set float val of a Cell */ 299 { 300 int fldno; 301 302 f += 0.0; /* normalise negative zero to positive zero */ 303 if ((vp->tval & (NUM | STR)) == 0) 304 funnyvar(vp, "assign to"); 305 if (isfld(vp)) { 306 donerec = false; /* mark $0 invalid */ 307 fldno = atoi(vp->nval); 308 if (fldno > *NF) 309 newfld(fldno); 310 DPRINTF("setting field %d to %g\n", fldno, f); 311 } else if (&vp->fval == NF) { 312 donerec = false; /* mark $0 invalid */ 313 setlastfld(f); 314 DPRINTF("setfval: setting NF to %g\n", f); 315 } else if (isrec(vp)) { 316 donefld = false; /* mark $1... invalid */ 317 donerec = true; 318 savefs(); 319 } else if (vp == ofsloc) { 320 if (!donerec) 321 recbld(); 322 } 323 if (freeable(vp)) 324 xfree(vp->sval); /* free any previous string */ 325 vp->tval &= ~(STR|CONVC|CONVO); /* mark string invalid */ 326 vp->fmt = NULL; 327 vp->tval |= NUM; /* mark number ok */ 328 if (f == -0) /* who would have thought this possible? */ 329 f = 0; 330 DPRINTF("setfval %p: %s = %g, t=%o\n", (void*)vp, NN(vp->nval), f, vp->tval); 331 return vp->fval = f; 332 } 333 334 void funnyvar(Cell *vp, const char *rw) 335 { 336 if (isarr(vp)) 337 FATAL("can't %s %s; it's an array name.", rw, vp->nval); 338 if (vp->tval & FCN) 339 FATAL("can't %s %s; it's a function.", rw, vp->nval); 340 WARNING("funny variable %p: n=%s s=\"%s\" f=%g t=%o", 341 (void *)vp, vp->nval, vp->sval, vp->fval, vp->tval); 342 } 343 344 char *setsval(Cell *vp, const char *s) /* set string val of a Cell */ 345 { 346 char *t; 347 int fldno; 348 Awkfloat f; 349 350 DPRINTF("starting setsval %p: %s = \"%s\", t=%o, r,f=%d,%d\n", 351 (void*)vp, NN(vp->nval), s, vp->tval, donerec, donefld); 352 if ((vp->tval & (NUM | STR)) == 0) 353 funnyvar(vp, "assign to"); 354 if (CSV && (vp == rsloc)) 355 WARNING("danger: don't set RS when --csv is in effect"); 356 if (CSV && (vp == fsloc)) 357 WARNING("danger: don't set FS when --csv is in effect"); 358 if (isfld(vp)) { 359 donerec = false; /* mark $0 invalid */ 360 fldno = atoi(vp->nval); 361 if (fldno > *NF) 362 newfld(fldno); 363 DPRINTF("setting field %d to %s (%p)\n", fldno, s, (const void*)s); 364 } else if (isrec(vp)) { 365 donefld = false; /* mark $1... invalid */ 366 donerec = true; 367 savefs(); 368 } else if (vp == ofsloc) { 369 if (!donerec) 370 recbld(); 371 } 372 t = s ? tostring(s) : tostring(""); /* in case it's self-assign */ 373 if (freeable(vp)) 374 xfree(vp->sval); 375 vp->tval &= ~(NUM|DONTFREE|CONVC|CONVO); 376 vp->tval |= STR; 377 vp->fmt = NULL; 378 DPRINTF("setsval %p: %s = \"%s (%p) \", t=%o r,f=%d,%d\n", 379 (void*)vp, NN(vp->nval), t, (void*)t, vp->tval, donerec, donefld); 380 vp->sval = t; 381 if (&vp->fval == NF) { 382 donerec = false; /* mark $0 invalid */ 383 f = getfval(vp); 384 setlastfld(f); 385 DPRINTF("setsval: setting NF to %g\n", f); 386 } 387 388 return(vp->sval); 389 } 390 391 Awkfloat getfval(Cell *vp) /* get float val of a Cell */ 392 { 393 if ((vp->tval & (NUM | STR)) == 0) 394 funnyvar(vp, "read value of"); 395 if (isfld(vp) && !donefld) 396 fldbld(); 397 else if (isrec(vp) && !donerec) 398 recbld(); 399 if (!isnum(vp)) { /* not a number */ 400 double fval; 401 bool no_trailing; 402 403 if (is_valid_number(vp->sval, true, & no_trailing, & fval)) { 404 vp->fval = fval; 405 if (no_trailing && !(vp->tval&CON)) 406 vp->tval |= NUM; /* make NUM only sparingly */ 407 } else 408 vp->fval = 0.0; 409 } 410 DPRINTF("getfval %p: %s = %g, t=%o\n", 411 (void*)vp, NN(vp->nval), vp->fval, vp->tval); 412 return(vp->fval); 413 } 414 415 static const char *get_inf_nan(double d) 416 { 417 if (isinf(d)) { 418 return (d < 0 ? "-inf" : "+inf"); 419 } else if (isnan(d)) { 420 return (signbit(d) != 0 ? "-nan" : "+nan"); 421 } else 422 return NULL; 423 } 424 425 static char *get_str_val(Cell *vp, char **fmt) /* get string val of a Cell */ 426 { 427 int n; 428 double dtemp; 429 const char *p; 430 431 if ((vp->tval & (NUM | STR)) == 0) 432 funnyvar(vp, "read value of"); 433 if (isfld(vp) && ! donefld) 434 fldbld(); 435 else if (isrec(vp) && ! donerec) 436 recbld(); 437 438 /* 439 * ADR: This is complicated and more fragile than is desirable. 440 * Retrieving a string value for a number associates the string 441 * value with the scalar. Previously, the string value was 442 * sticky, meaning if converted via OFMT that became the value 443 * (even though POSIX wants it to be via CONVFMT). Or if CONVFMT 444 * changed after a string value was retrieved, the original value 445 * was maintained and used. Also not per POSIX. 446 * 447 * We work around this design by adding two additional flags, 448 * CONVC and CONVO, indicating how the string value was 449 * obtained (via CONVFMT or OFMT) and _also_ maintaining a copy 450 * of the pointer to the xFMT format string used for the 451 * conversion. This pointer is only read, **never** dereferenced. 452 * The next time we do a conversion, if it's coming from the same 453 * xFMT as last time, and the pointer value is different, we 454 * know that the xFMT format string changed, and we need to 455 * redo the conversion. If it's the same, we don't have to. 456 * 457 * There are also several cases where we don't do a conversion, 458 * such as for a field (see the checks below). 459 */ 460 461 /* Don't duplicate the code for actually updating the value */ 462 #define update_str_val(vp) \ 463 { \ 464 if (freeable(vp)) \ 465 xfree(vp->sval); \ 466 if ((p = get_inf_nan(vp->fval)) != NULL) \ 467 n = (vp->sval = strdup(p)) ? 0 : -1; \ 468 else if (modf(vp->fval, &dtemp) == 0) /* it's integral */ \ 469 n = asprintf(&vp->sval, "%.30g", vp->fval); \ 470 else \ 471 n = asprintf(&vp->sval, *fmt, vp->fval); \ 472 if (n == -1) \ 473 FATAL("out of space in get_str_val"); \ 474 vp->tval &= ~DONTFREE; \ 475 vp->tval |= STR; \ 476 } 477 478 if (isstr(vp) == 0) { 479 update_str_val(vp); 480 if (fmt == OFMT) { 481 vp->tval &= ~CONVC; 482 vp->tval |= CONVO; 483 } else { 484 /* CONVFMT */ 485 vp->tval &= ~CONVO; 486 vp->tval |= CONVC; 487 } 488 vp->fmt = *fmt; 489 } else if ((vp->tval & DONTFREE) != 0 || ! isnum(vp) || isfld(vp)) { 490 goto done; 491 } else if (isstr(vp)) { 492 if (fmt == OFMT) { 493 if ((vp->tval & CONVC) != 0 494 || ((vp->tval & CONVO) != 0 && vp->fmt != *fmt)) { 495 update_str_val(vp); 496 vp->tval &= ~CONVC; 497 vp->tval |= CONVO; 498 vp->fmt = *fmt; 499 } 500 } else { 501 /* CONVFMT */ 502 if ((vp->tval & CONVO) != 0 503 || ((vp->tval & CONVC) != 0 && vp->fmt != *fmt)) { 504 update_str_val(vp); 505 vp->tval &= ~CONVO; 506 vp->tval |= CONVC; 507 vp->fmt = *fmt; 508 } 509 } 510 } 511 done: 512 DPRINTF("getsval %p: %s = \"%s (%p)\", t=%o\n", 513 (void*)vp, NN(vp->nval), vp->sval, (void*)vp->sval, vp->tval); 514 return(vp->sval); 515 } 516 517 char *getsval(Cell *vp) /* get string val of a Cell */ 518 { 519 return get_str_val(vp, CONVFMT); 520 } 521 522 char *getpssval(Cell *vp) /* get string val of a Cell for print */ 523 { 524 return get_str_val(vp, OFMT); 525 } 526 527 528 char *tostring(const char *s) /* make a copy of string s */ 529 { 530 char *p = strdup(s); 531 if (p == NULL) 532 FATAL("out of space in tostring on %s", s); 533 return(p); 534 } 535 536 char *tostringN(const char *s, size_t n) /* make a copy of string s */ 537 { 538 char *p; 539 540 p = (char *) malloc(n); 541 if (p == NULL) 542 FATAL("out of space in tostringN %zu", n); 543 if (strlcpy(p, s, n) >= n) 544 FATAL("out of space in tostringN on %s", s); 545 return(p); 546 } 547 548 Cell *catstr(Cell *a, Cell *b) /* concatenate a and b */ 549 { 550 Cell *c; 551 char *p; 552 char *sa = getsval(a); 553 char *sb = getsval(b); 554 size_t l = strlen(sa) + strlen(sb) + 1; 555 p = (char *) malloc(l); 556 if (p == NULL) 557 FATAL("out of space concatenating %s and %s", sa, sb); 558 snprintf(p, l, "%s%s", sa, sb); 559 560 l++; // add room for ' ' 561 char *newbuf = (char *) malloc(l); 562 if (newbuf == NULL) 563 FATAL("out of space concatenating %s and %s", sa, sb); 564 // See string() in lex.c; a string "xx" is stored in the symbol 565 // table as "xx ". 566 snprintf(newbuf, l, "%s ", p); 567 c = setsymtab(newbuf, p, 0.0, CON|STR|DONTFREE, symtab); 568 free(p); 569 free(newbuf); 570 return c; 571 } 572 573 char *qstring(const char *is, int delim) /* collect string up to next delim */ 574 { 575 int c, n; 576 const uschar *s = (const uschar *) is; 577 uschar *buf, *bp; 578 579 if ((buf = (uschar *) malloc(strlen(is)+3)) == NULL) 580 FATAL( "out of space in qstring(%s)", s); 581 for (bp = buf; (c = *s) != delim; s++) { 582 if (c == '\n') 583 SYNTAX( "newline in string %.20s...", is ); 584 else if (c != '\\') 585 *bp++ = c; 586 else { /* \something */ 587 c = *++s; 588 if (c == 0) { /* \ at end */ 589 *bp++ = '\\'; 590 break; /* for loop */ 591 } 592 switch (c) { 593 case '\\': *bp++ = '\\'; break; 594 case 'n': *bp++ = '\n'; break; 595 case 't': *bp++ = '\t'; break; 596 case 'b': *bp++ = '\b'; break; 597 case 'f': *bp++ = '\f'; break; 598 case 'r': *bp++ = '\r'; break; 599 case 'v': *bp++ = '\v'; break; 600 case 'a': *bp++ = '\a'; break; 601 default: 602 if (!isdigit(c)) { 603 *bp++ = c; 604 break; 605 } 606 n = c - '0'; 607 if (isdigit(s[1])) { 608 n = 8 * n + *++s - '0'; 609 if (isdigit(s[1])) 610 n = 8 * n + *++s - '0'; 611 } 612 *bp++ = n; 613 break; 614 } 615 } 616 } 617 *bp++ = 0; 618 return (char *) buf; 619 } 620 621 const char *flags2str(int flags) 622 { 623 static const struct ftab { 624 const char *name; 625 int value; 626 } flagtab[] = { 627 { "NUM", NUM }, 628 { "STR", STR }, 629 { "DONTFREE", DONTFREE }, 630 { "CON", CON }, 631 { "ARR", ARR }, 632 { "FCN", FCN }, 633 { "FLD", FLD }, 634 { "REC", REC }, 635 { "CONVC", CONVC }, 636 { "CONVO", CONVO }, 637 { NULL, 0 } 638 }; 639 static char buf[100]; 640 int i, len; 641 char *cp = buf; 642 643 for (i = 0; flagtab[i].name != NULL; i++) { 644 if ((flags & flagtab[i].value) != 0) { 645 len = snprintf(cp, sizeof(buf) - (cp - buf), 646 "%s%s", cp > buf ? "|" : "", flagtab[i].name); 647 if (len < 0 || (size_t)len >= sizeof(buf) - (cp - buf)) 648 FATAL("out of space in flags2str"); 649 cp += len; 650 } 651 } 652 653 return buf; 654 } 655