159243Sobrien/* 259243Sobrien * tw.color.c: builtin color ls-F 359243Sobrien */ 459243Sobrien/*- 559243Sobrien * Copyright (c) 1998 The Regents of the University of California. 659243Sobrien * All rights reserved. 759243Sobrien * 859243Sobrien * Redistribution and use in source and binary forms, with or without 959243Sobrien * modification, are permitted provided that the following conditions 1059243Sobrien * are met: 1159243Sobrien * 1. Redistributions of source code must retain the above copyright 1259243Sobrien * notice, this list of conditions and the following disclaimer. 1359243Sobrien * 2. Redistributions in binary form must reproduce the above copyright 1459243Sobrien * notice, this list of conditions and the following disclaimer in the 1559243Sobrien * documentation and/or other materials provided with the distribution. 16100616Smp * 3. Neither the name of the University nor the names of its contributors 1759243Sobrien * may be used to endorse or promote products derived from this software 1859243Sobrien * without specific prior written permission. 1959243Sobrien * 2059243Sobrien * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 2159243Sobrien * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 2259243Sobrien * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 2359243Sobrien * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 2459243Sobrien * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 2559243Sobrien * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 2659243Sobrien * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 2759243Sobrien * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 2859243Sobrien * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 2959243Sobrien * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 3059243Sobrien * SUCH DAMAGE. 3159243Sobrien */ 3259243Sobrien#include "sh.h" 3359243Sobrien#include "tw.h" 3459243Sobrien#include "ed.h" 3559243Sobrien#include "tc.h" 3659243Sobrien 3759243Sobrien#ifdef COLOR_LS_F 3859243Sobrien 3959243Sobrientypedef struct { 40167465Smp const char *s; 41167465Smp size_t len; 4259243Sobrien} Str; 4359243Sobrien 4459243Sobrien 4559243Sobrien#define VAR(suffix,variable,defaultcolor) \ 4659243Sobrien{ \ 4759243Sobrien suffix, variable, { defaultcolor, sizeof(defaultcolor) - 1 }, \ 4859243Sobrien { defaultcolor, sizeof(defaultcolor) - 1 } \ 4959243Sobrien} 5059243Sobrien#define NOS '\0' /* no suffix */ 5159243Sobrien 5259243Sobrientypedef struct { 5359243Sobrien const char suffix; 5459243Sobrien const char *variable; 55167465Smp Str color; 56167465Smp Str defaultcolor; 5759243Sobrien} Variable; 5859243Sobrien 5959243Sobrienstatic Variable variables[] = { 6059243Sobrien VAR('/', "di", "01;34"), /* Directory */ 6159243Sobrien VAR('@', "ln", "01;36"), /* Symbolic link */ 6259243Sobrien VAR('&', "or", ""), /* Orphanned symbolic link (defaults to ln) */ 6359243Sobrien VAR('|', "pi", "33"), /* Named pipe (FIFO) */ 6459243Sobrien VAR('=', "so", "01;35"), /* Socket */ 6583098Smp VAR('>', "do", "01;35"), /* Door (solaris fast ipc mechanism) */ 6659243Sobrien VAR('#', "bd", "01;33"), /* Block device */ 6759243Sobrien VAR('%', "cd", "01;33"), /* Character device */ 6859243Sobrien VAR('*', "ex", "01;32"), /* Executable file */ 6959243Sobrien VAR(NOS, "fi", "0"), /* Regular file */ 7059243Sobrien VAR(NOS, "no", "0"), /* Normal (non-filename) text */ 7159243Sobrien VAR(NOS, "mi", ""), /* Missing file (defaults to fi) */ 7269408Sache#ifdef IS_ASCII 7369408Sache VAR(NOS, "lc", "\033["), /* Left code (ASCII) */ 7469408Sache#else 7559243Sobrien VAR(NOS, "lc", "\x27["), /* Left code (EBCDIC)*/ 7669408Sache#endif 7759243Sobrien VAR(NOS, "rc", "m"), /* Right code */ 7859243Sobrien VAR(NOS, "ec", ""), /* End code (replaces lc+no+rc) */ 79167465Smp VAR(NOS, "su", ""), /* Setuid file (u+s) */ 80167465Smp VAR(NOS, "sg", ""), /* Setgid file (g+s) */ 81167465Smp VAR(NOS, "tw", ""), /* Sticky and other writable dir (+t,o+w) */ 82167465Smp VAR(NOS, "ow", ""), /* Other writable dir (o+w) but not sticky */ 83167465Smp VAR(NOS, "st", ""), /* Sticky dir (+t) but not other writable */ 84195609Smp VAR(NOS, "rs", "0"), /* Reset to normal color */ 85231990Smp VAR(NOS, "hl", "44;37"), /* Reg file extra hard links, obsolete? */ 86231990Smp VAR(NOS, "mh", "44;37"), /* Reg file extra hard links */ 87231990Smp VAR(NOS, "ca", "30;41"), /* File with capability */ 8859243Sobrien}; 8959243Sobrien 90316957Sdchagin#define nvariables (sizeof(variables)/sizeof(variables[0])) 91316957Sdchagin 9259243Sobrienenum FileType { 9383098Smp VDir, VSym, VOrph, VPipe, VSock, VDoor, VBlock, VChr, VExe, 94316957Sdchagin VFile, VNormal, VMiss, VLeft, VRight, VEnd, VSuid, VSgid, VSticky, 95316957Sdchagin VOther, Vstird, VReset, Vhard, Vhard2, VCap 9659243Sobrien}; 9759243Sobrien 98316957Sdchagin/* 99316957Sdchagin * Map from LSCOLORS entry index to Variable array index 100316957Sdchagin */ 101316957Sdchaginstatic const uint8_t map[] = { 102316957Sdchagin VDir, /* Directory */ 103316957Sdchagin VSym, /* Symbolic Link */ 104316957Sdchagin VSock, /* Socket */ 105316957Sdchagin VPipe, /* Named Pipe */ 106316957Sdchagin VExe, /* Executable */ 107316957Sdchagin VBlock, /* Block Special */ 108316957Sdchagin VChr, /* Character Special */ 109316957Sdchagin VSuid, /* Setuid Executable */ 110316957Sdchagin VSgid, /* Setgid Executable */ 111316957Sdchagin VSticky, /* Directory writable to others and sticky */ 112316957Sdchagin VOther, /* Directory writable to others but not sticky */ 113316957Sdchagin}; 11459243Sobrien 115316957Sdchagin 116316957Sdchagin 117316957Sdchaginenum ansi { 118316957Sdchagin ANSI_RESET_ON = 0, /* reset colors/styles (white on black) */ 119316957Sdchagin ANSI_BOLD_ON = 1, /* bold on */ 120316957Sdchagin ANSI_ITALICS_ON = 3, /* italics on */ 121316957Sdchagin ANSI_UNDERLINE_ON = 4, /* underline on */ 122316957Sdchagin ANSI_INVERSE_ON = 7, /* inverse on */ 123316957Sdchagin ANSI_STRIKETHROUGH_ON = 9, /* strikethrough on */ 124316957Sdchagin ANSI_BOLD_OFF = 21, /* bold off */ 125316957Sdchagin ANSI_ITALICS_OFF = 23, /* italics off */ 126316957Sdchagin ANSI_UNDERLINE_OFF = 24, /* underline off */ 127316957Sdchagin ANSI_INVERSE_OFF = 27, /* inverse off */ 128316957Sdchagin ANSI_STRIKETHROUGH_OFF = 29,/* strikethrough off */ 129316957Sdchagin ANSI_FG_BLACK = 30, /* fg black */ 130316957Sdchagin ANSI_FG_RED = 31, /* fg red */ 131316957Sdchagin ANSI_FG_GREEN = 32, /* fg green */ 132316957Sdchagin ANSI_FG_YELLOW = 33, /* fg yellow */ 133316957Sdchagin ANSI_FG_BLUE = 34, /* fg blue */ 134316957Sdchagin ANSI_FG_MAGENTA = 35, /* fg magenta */ 135316957Sdchagin ANSI_FG_CYAN = 36, /* fg cyan */ 136316957Sdchagin ANSI_FG_WHITE = 37, /* fg white */ 137316957Sdchagin ANSI_FG_DEFAULT = 39, /* fg default (white) */ 138316957Sdchagin ANSI_BG_BLACK = 40, /* bg black */ 139316957Sdchagin ANSI_BG_RED = 41, /* bg red */ 140316957Sdchagin ANSI_BG_GREEN = 42, /* bg green */ 141316957Sdchagin ANSI_BG_YELLOW = 43, /* bg yellow */ 142316957Sdchagin ANSI_BG_BLUE = 44, /* bg blue */ 143316957Sdchagin ANSI_BG_MAGENTA = 45, /* bg magenta */ 144316957Sdchagin ANSI_BG_CYAN = 46, /* bg cyan */ 145316957Sdchagin ANSI_BG_WHITE = 47, /* bg white */ 146316957Sdchagin ANSI_BG_DEFAULT = 49, /* bg default (black) */ 147316957Sdchagin}; 148316957Sdchagin#define TCSH_BOLD 0x80 149316957Sdchagin 15059243Sobrientypedef struct { 151167465Smp Str extension; /* file extension */ 152167465Smp Str color; /* color string */ 15359243Sobrien} Extension; 15459243Sobrien 15559243Sobrienstatic Extension *extensions = NULL; 156145479Smpstatic size_t nextensions = 0; 15759243Sobrien 15859243Sobrienstatic char *colors = NULL; 159145479Smpint color_context_ls = FALSE; /* do colored ls */ 160167465Smpstatic int color_context_lsmF = FALSE; /* do colored ls-F */ 16159243Sobrien 162167465Smpstatic int getstring (char **, const Char **, Str *, int); 163167465Smpstatic void put_color (const Str *); 164167465Smpstatic void print_color (const Char *, size_t, Char); 16559243Sobrien 16659243Sobrien/* set_color_context(): 16759243Sobrien */ 16859243Sobrienvoid 169167465Smpset_color_context(void) 17059243Sobrien{ 17159243Sobrien struct varent *vp = adrof(STRcolor); 17259243Sobrien 173100616Smp if (vp == NULL || vp->vec == NULL) { 17459243Sobrien color_context_ls = FALSE; 17559243Sobrien color_context_lsmF = FALSE; 176100616Smp } else if (!vp->vec[0] || vp->vec[0][0] == '\0') { 17759243Sobrien color_context_ls = TRUE; 17859243Sobrien color_context_lsmF = TRUE; 179100616Smp } else { 18059243Sobrien size_t i; 18159243Sobrien 18259243Sobrien color_context_ls = FALSE; 18359243Sobrien color_context_lsmF = FALSE; 18459243Sobrien for (i = 0; vp->vec[i]; i++) 18559243Sobrien if (Strcmp(vp->vec[i], STRls) == 0) 18659243Sobrien color_context_ls = TRUE; 18759243Sobrien else if (Strcmp(vp->vec[i], STRlsmF) == 0) 18859243Sobrien color_context_lsmF = TRUE; 18959243Sobrien } 19059243Sobrien} 19159243Sobrien 19259243Sobrien 19359243Sobrien/* getstring(): 19459243Sobrien */ 195167465Smpstatic int 196167465Smpgetstring(char **dp, const Char **sp, Str *pd, int f) 19759243Sobrien{ 19859243Sobrien const Char *s = *sp; 19959243Sobrien char *d = *dp; 200145479Smp eChar sc; 20159243Sobrien 202145479Smp while (*s && (*s & CHAR) != (Char)f && (*s & CHAR) != ':') { 20359243Sobrien if ((*s & CHAR) == '\\' || (*s & CHAR) == '^') { 204145479Smp if ((sc = parseescape(&s)) == CHAR_ERR) 20559243Sobrien return 0; 20659243Sobrien } 20759243Sobrien else 208145479Smp sc = *s++ & CHAR; 209145479Smp d += one_wctomb(d, sc); 21059243Sobrien } 21159243Sobrien 21259243Sobrien pd->s = *dp; 213167465Smp pd->len = d - *dp; 21459243Sobrien *sp = s; 21559243Sobrien *dp = d; 216145479Smp return *s == (Char)f; 21759243Sobrien} 21859243Sobrien 219316957Sdchaginstatic void 220316957Sdchagininit(size_t colorlen, size_t extnum) 221316957Sdchagin{ 222316957Sdchagin size_t i; 22359243Sobrien 224316957Sdchagin xfree(extensions); 225316957Sdchagin for (i = 0; i < nvariables; i++) 226316957Sdchagin variables[i].color = variables[i].defaultcolor; 227316957Sdchagin if (colorlen == 0 && extnum == 0) { 228316957Sdchagin extensions = NULL; 229316957Sdchagin colors = NULL; 230316957Sdchagin } else { 231316957Sdchagin extensions = xmalloc(colorlen + extnum * sizeof(*extensions)); 232316957Sdchagin colors = extnum * sizeof(*extensions) + (char *)extensions; 233316957Sdchagin } 234316957Sdchagin nextensions = 0; 235316957Sdchagin} 236316957Sdchagin 237316957Sdchaginstatic int 238316957Sdchagincolor(Char x) 239316957Sdchagin{ 240316957Sdchagin static const char ccolors[] = "abcdefghx"; 241316957Sdchagin char *p; 242316957Sdchagin if (Isupper(x)) { 243316957Sdchagin x = Tolower(x); 244316957Sdchagin } 245316957Sdchagin 246316957Sdchagin if (x == '\0' || (p = strchr(ccolors, x)) == NULL) 247316957Sdchagin return -1; 248316957Sdchagin return 30 + (p - ccolors); 249316957Sdchagin} 250316957Sdchagin 251316957Sdchaginstatic void 252316957Sdchaginmakecolor(char **c, int fg, int bg, Str *v) 253316957Sdchagin{ 254316957Sdchagin int l; 255316957Sdchagin if (fg & 0x80) 256316957Sdchagin l = xsnprintf(*c, 12, "%.2d;%.2d;%.2d;%.2d", ANSI_BOLD_ON, 257316957Sdchagin fg & ~TCSH_BOLD, (10 + bg) & ~TCSH_BOLD, ANSI_BOLD_OFF); 258316957Sdchagin l = xsnprintf(*c, 6, "%.2d;%.2d", 259316957Sdchagin fg & ~TCSH_BOLD, (10 + bg) & ~TCSH_BOLD); 260316957Sdchagin 261316957Sdchagin v->s = *c; 262316957Sdchagin v->len = l; 263316957Sdchagin *c += l + 1; 264316957Sdchagin} 265316957Sdchagin 266316957Sdchagin/* parseLSCOLORS(): 267316957Sdchagin * Parse the LSCOLORS environment variable 268316957Sdchagin */ 269316957Sdchaginstatic const Char *xv; /* setjmp clobbering */ 270316957Sdchaginvoid 271316957SdchaginparseLSCOLORS(const Char *value) 272316957Sdchagin{ 273316957Sdchagin size_t i, len, clen; 274316957Sdchagin jmp_buf_t osetexit; 275316957Sdchagin size_t omark; 276316957Sdchagin xv = value; 277316957Sdchagin 278316957Sdchagin if (xv == NULL) { 279316957Sdchagin init(0, 0); 280316957Sdchagin return; 281316957Sdchagin } 282316957Sdchagin 283316957Sdchagin len = Strlen(xv); 284316957Sdchagin len >>= 1; 285316957Sdchagin clen = len * 12; /* "??;??;??;??\0" */ 286316957Sdchagin init(clen, 0); 287316957Sdchagin 288316957Sdchagin /* Prevent from crashing if unknown parameters are given. */ 289316957Sdchagin omark = cleanup_push_mark(); 290316957Sdchagin getexit(osetexit); 291316957Sdchagin 292316957Sdchagin /* init pointers */ 293316957Sdchagin 294316957Sdchagin if (setexit() == 0) { 295316957Sdchagin const Char *v = xv; 296316957Sdchagin char *c = colors; 297316957Sdchagin 298316957Sdchagin int fg, bg; 299316957Sdchagin for (i = 0; i < len; i++) { 300316957Sdchagin fg = color(*v++); 301316957Sdchagin if (fg == -1) 302316957Sdchagin stderror(ERR_BADCOLORVAR, v[-1], '?'); 303316957Sdchagin 304316957Sdchagin bg = color(*v++); 305316957Sdchagin if (bg == -1) 306316957Sdchagin stderror(ERR_BADCOLORVAR, '?', v[-1]); 307316957Sdchagin makecolor(&c, fg, bg, &variables[map[i]].color); 308316957Sdchagin } 309316957Sdchagin 310316957Sdchagin } 311316957Sdchagin cleanup_pop_mark(omark); 312316957Sdchagin resexit(osetexit); 313316957Sdchagin} 314316957Sdchagin 31559243Sobrien/* parseLS_COLORS(): 31659243Sobrien * Parse the LS_COLORS environment variable 31759243Sobrien */ 31859243Sobrienvoid 319167465SmpparseLS_COLORS(const Char *value) 32059243Sobrien{ 321145479Smp size_t i, len; 322167465Smp const Char *v; /* pointer in value */ 32359243Sobrien char *c; /* pointer in colors */ 324145479Smp Extension *volatile e; /* pointer in extensions */ 32559415Sobrien jmp_buf_t osetexit; 326167465Smp size_t omark; 32759243Sobrien 328100616Smp (void) &e; 329100616Smp 33059243Sobrien 331316957Sdchagin if (value == NULL) { 332316957Sdchagin init(0, 0); 33359243Sobrien return; 334316957Sdchagin } 33559243Sobrien 33659243Sobrien len = Strlen(value); 33759243Sobrien /* allocate memory */ 33859243Sobrien i = 1; 33959243Sobrien for (v = value; *v; v++) 34059243Sobrien if ((*v & CHAR) == ':') 34159243Sobrien i++; 34259243Sobrien 343316957Sdchagin init(len, i); 344316957Sdchagin 34559243Sobrien /* init pointers */ 34659243Sobrien v = value; 34759243Sobrien c = colors; 348316957Sdchagin e = extensions; 34959243Sobrien 35059415Sobrien /* Prevent from crashing if unknown parameters are given. */ 35159415Sobrien 352167465Smp omark = cleanup_push_mark(); 35359415Sobrien getexit(osetexit); 35459415Sobrien 35559415Sobrien if (setexit() == 0) { 356167465Smp 357316957Sdchagin /* parse */ 358316957Sdchagin while (*v) { 359316957Sdchagin switch (*v & CHAR) { 360316957Sdchagin case ':': 36159243Sobrien v++; 36259243Sobrien continue; 36359243Sobrien 364316957Sdchagin case '*': /* :*ext=color: */ 365316957Sdchagin v++; 366316957Sdchagin if (getstring(&c, &v, &e->extension, '=') && 367316957Sdchagin 0 < e->extension.len) { 368316957Sdchagin v++; 369316957Sdchagin getstring(&c, &v, &e->color, ':'); 370316957Sdchagin e++; 37159243Sobrien continue; 37259243Sobrien } 373316957Sdchagin break; 374316957Sdchagin 375316957Sdchagin default: /* :vl=color: */ 376316957Sdchagin if (v[0] && v[1] && (v[2] & CHAR) == '=') { 377316957Sdchagin for (i = 0; i < nvariables; i++) 378316957Sdchagin if ((Char)variables[i].variable[0] == (v[0] & CHAR) && 379316957Sdchagin (Char)variables[i].variable[1] == (v[1] & CHAR)) 380316957Sdchagin break; 381316957Sdchagin if (i < nvariables) { 382316957Sdchagin v += 3; 383316957Sdchagin getstring(&c, &v, &variables[i].color, ':'); 384316957Sdchagin continue; 385316957Sdchagin } 386316957Sdchagin else 387316957Sdchagin stderror(ERR_BADCOLORVAR, v[0], v[1]); 388316957Sdchagin } 389316957Sdchagin break; 39059243Sobrien } 391316957Sdchagin while (*v && (*v & CHAR) != ':') 392316957Sdchagin v++; 39359243Sobrien } 39459243Sobrien } 39559243Sobrien 396167465Smp cleanup_pop_mark(omark); 39759415Sobrien resexit(osetexit); 39859415Sobrien 399167465Smp nextensions = e - extensions; 40059243Sobrien} 40159243Sobrien 40259243Sobrien/* put_color(): 40359243Sobrien */ 40459243Sobrienstatic void 405316957Sdchaginput_color(const Str *colorp) 40659243Sobrien{ 40759243Sobrien size_t i; 408316957Sdchagin const char *c = colorp->s; 409167465Smp int original_output_raw = output_raw; 41059243Sobrien 41159243Sobrien output_raw = TRUE; 412167465Smp cleanup_push(&original_output_raw, output_raw_restore); 413316957Sdchagin for (i = colorp->len; 0 < i; i--) 41459243Sobrien xputchar(*c++); 415167465Smp cleanup_until(&original_output_raw); 41659243Sobrien} 41759243Sobrien 41859243Sobrien 41959243Sobrien/* print_color(): 42059243Sobrien */ 42159243Sobrienstatic void 422167465Smpprint_color(const Char *fname, size_t len, Char suffix) 42359243Sobrien{ 424145479Smp size_t i; 42559243Sobrien char *filename = short2str(fname); 42659243Sobrien char *last = filename + len; 427316957Sdchagin Str *colorp = &variables[VFile].color; 42859243Sobrien 42959243Sobrien switch (suffix) { 43059243Sobrien case '>': /* File is a symbolic link pointing to 43159243Sobrien * a directory */ 432316957Sdchagin colorp = &variables[VDir].color; 433167465Smp break; 43459243Sobrien case '+': /* File is a hidden directory [aix] or 43559243Sobrien * context dependent [hpux] */ 43659243Sobrien case ':': /* File is network special [hpux] */ 437167465Smp break; 43859243Sobrien default: 43959243Sobrien for (i = 0; i < nvariables; i++) 44059243Sobrien if (variables[i].suffix != NOS && 441145479Smp (Char)variables[i].suffix == suffix) { 442316957Sdchagin colorp = &variables[i].color; 44359243Sobrien break; 44459243Sobrien } 44559243Sobrien if (i == nvariables) { 44659243Sobrien for (i = 0; i < nextensions; i++) 447167465Smp if (len >= extensions[i].extension.len 448167465Smp && strncmp(last - extensions[i].extension.len, 449167465Smp extensions[i].extension.s, 450167465Smp extensions[i].extension.len) == 0) { 451316957Sdchagin colorp = &extensions[i].color; 45259243Sobrien break; 45359243Sobrien } 45459243Sobrien } 45559243Sobrien break; 45659243Sobrien } 45759243Sobrien 45859243Sobrien put_color(&variables[VLeft].color); 459316957Sdchagin put_color(colorp); 46059243Sobrien put_color(&variables[VRight].color); 46159243Sobrien} 46259243Sobrien 46359243Sobrien 46459243Sobrien/* print_with_color(): 46559243Sobrien */ 46659243Sobrienvoid 467167465Smpprint_with_color(const Char *filename, size_t len, Char suffix) 46859243Sobrien{ 46959243Sobrien if (color_context_lsmF && 47059243Sobrien (haderr ? (didfds ? is2atty : isdiagatty) : 47159243Sobrien (didfds ? is1atty : isoutatty))) { 47259243Sobrien print_color(filename, len, suffix); 47359243Sobrien xprintf("%S", filename); 47459243Sobrien if (0 < variables[VEnd].color.len) 47559243Sobrien put_color(&variables[VEnd].color); 47659243Sobrien else { 47759243Sobrien put_color(&variables[VLeft].color); 47859243Sobrien put_color(&variables[VNormal].color); 47959243Sobrien put_color(&variables[VRight].color); 48059243Sobrien } 48159243Sobrien } 48259243Sobrien else 483145479Smp xprintf("%S", filename); 484145479Smp xputwchar(suffix); 48559243Sobrien} 48659243Sobrien 48759243Sobrien 48859243Sobrien#endif /* COLOR_LS_F */ 489