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