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