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