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