1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9 * or http://www.opensolaris.org/os/licensing. 10 * See the License for the specific language governing permissions 11 * and limitations under the License. 12 * 13 * When distributing Covered Code, include this CDDL HEADER in each 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15 * If applicable, add the following below this CDDL HEADER, with the 16 * fields enclosed by brackets "[]" replaced with your own identifying 17 * information: Portions Copyright [yyyy] [name of copyright owner] 18 * 19 * CDDL HEADER END 20 */ 21 /* 22 * Copyright 2006 Sun Microsystems, Inc. All rights reserved. 23 * Use is subject to license terms. 24 * 25 * logadm/kw.c -- manage keywords table 26 * 27 * this module expands things like $file.$n in "templates". 28 * calling kw_init() sets the current "filename" used for 29 * $file, $dirname, and $basename. 30 * 31 * any time-based expansions, like $secs, or all the strftime() 32 * percent sequences, are based on the exact same point in time. 33 * so calling kw_expand() on something like "file-%T" will return 34 * the same thing when called multiple times during the same logadm run. 35 */ 36 37 #pragma ident "%Z%%M% %I% %E% SMI" 38 39 #include <stdio.h> 40 #include <libintl.h> 41 #include <stdlib.h> 42 #include <sys/types.h> 43 #include <sys/stat.h> 44 #include <sys/utsname.h> 45 #include <sys/systeminfo.h> 46 #include <strings.h> 47 #include <time.h> 48 #include <ctype.h> 49 #include <zone.h> 50 #include "err.h" 51 #include "lut.h" 52 #include "fn.h" 53 #include "kw.h" 54 55 /* forward declarations for functions used internally by this module */ 56 static void kw_printer(const char *lhs, void *rhs, void *arg); 57 58 /* 59 * absurdly long length to hold sprintf of a %d, 60 * or strftime() expansion of a *single* percent sequence 61 */ 62 #define MAXDIGITS 100 63 64 static struct lut *Keywords; /* lookup table for keywords */ 65 66 extern time_t Now; /* time used for keyword expansions */ 67 68 /* 69 * kw_init -- initialize keywords based on given filename 70 */ 71 void 72 kw_init(struct fn *fnp, struct fn *nfnp) 73 { 74 static char *fullpath; 75 static char *nfullpath; 76 static char *splitpath; 77 static char secs[MAXDIGITS]; 78 static struct utsname un; 79 static char platform[SYS_NMLN]; 80 static char isa[SYS_NMLN]; 81 static char domain[256]; 82 static char *home; 83 static char *user; 84 static char *logname; 85 static char zonename[ZONENAME_MAX]; 86 static zoneid_t zoneid; 87 static int initialized; 88 char *ptr; 89 90 /* make a copy of the string for $file */ 91 if (fullpath) 92 FREE(fullpath); 93 fullpath = STRDUP(fn_s(fnp)); 94 Keywords = lut_add(Keywords, "file", fullpath); 95 96 /* make a copy of the string for $nfile */ 97 if (nfullpath) 98 FREE(nfullpath); 99 if (nfnp == NULL) { 100 nfullpath = NULL; 101 Keywords = lut_add(Keywords, "nfile", ""); 102 } else { 103 nfullpath = STRDUP(fn_s(nfnp)); 104 Keywords = lut_add(Keywords, "nfile", nfullpath); 105 } 106 107 /* make a copy of the string for $dirname/$basename */ 108 if (splitpath) 109 FREE(splitpath); 110 splitpath = STRDUP(fn_s(fnp)); 111 112 if ((ptr = strrchr(splitpath, '/')) == NULL) { 113 Keywords = lut_add(Keywords, "basename", splitpath); 114 Keywords = lut_add(Keywords, "dirname", "."); 115 } else { 116 *ptr++ = '\0'; 117 Keywords = lut_add(Keywords, "basename", ptr); 118 Keywords = lut_add(Keywords, "dirname", splitpath); 119 } 120 121 if (initialized) 122 return; /* rest of the keywords don't change */ 123 124 (void) snprintf(secs, MAXDIGITS, "%d", (int)Now); 125 Keywords = lut_add(Keywords, "secs", secs); 126 127 if (uname(&un) < 0) 128 err(EF_SYS, "uname"); 129 130 Keywords = lut_add(Keywords, "nodename", un.nodename); 131 Keywords = lut_add(Keywords, "release", un.release); 132 Keywords = lut_add(Keywords, "machine", un.machine); 133 134 if (sysinfo(SI_ARCHITECTURE, isa, sizeof (isa)) == -1) 135 err(EF_WARN|EF_SYS, "sysinfo(SI_ARCHITECTURE) failed."); 136 else 137 Keywords = lut_add(Keywords, "isa", isa); 138 139 if (sysinfo(SI_PLATFORM, platform, sizeof (platform)) == -1) 140 err(EF_WARN|EF_SYS, "sysinfo(SI_PLATFORM) failed."); 141 else 142 Keywords = lut_add(Keywords, "platform", platform); 143 144 if (sysinfo(SI_SRPC_DOMAIN, domain, sizeof (domain)) == -1) 145 err(EF_WARN|EF_SYS, "sysinfo(SI_SRPC_DOMAIN) failed."); 146 else 147 Keywords = lut_add(Keywords, "domain", domain); 148 149 if ((home = getenv("HOME")) != NULL) 150 Keywords = lut_add(Keywords, "home", STRDUP(home)); 151 152 if ((user = getenv("USER")) != NULL) 153 Keywords = lut_add(Keywords, "user", STRDUP(user)); 154 155 if ((logname = getenv("LOGNAME")) != NULL) 156 Keywords = lut_add(Keywords, "logname", STRDUP(logname)); 157 158 zoneid = getzoneid(); 159 if ((getzonenamebyid(zoneid, zonename, sizeof (zonename))) == -1) 160 err(EF_WARN|EF_SYS, "getzonenamebyid() failed."); 161 else 162 Keywords = lut_add(Keywords, "zonename", STRDUP(zonename)); 163 164 initialized = 1; 165 } 166 167 /* helper function for kw_print() */ 168 static void 169 kw_printer(const char *lhs, void *rhs, void *arg) 170 { 171 FILE *stream = (FILE *)arg; 172 173 (void) fprintf(stream, "%20.20s %s\n", lhs, (char *)rhs); 174 } 175 176 /* 177 * kw_print -- spew the entire keywords table to stream 178 * 179 * this routine is used to dump the keywords table for debugging. 180 */ 181 void 182 kw_print(FILE *stream) 183 { 184 lut_walk(Keywords, kw_printer, stream); 185 } 186 187 /* 188 * kw_expand -- expand src into dst with given n value for $n (or $N) 189 * 190 * n == -1 means expand src into a reglob 191 * if gz is true, include ".gz" extension 192 * 193 * returns true if template contains $n or $N (implying rotation of files) 194 */ 195 boolean_t 196 kw_expand(struct fn *src, struct fn *dst, int n, boolean_t gz) 197 { 198 int c; 199 char buf[MAXDIGITS]; 200 boolean_t hasn = B_FALSE; 201 struct fn *kw = fn_new(NULL); 202 char *ptr; 203 struct tm *gmt_tm = localtime(&Now); 204 205 while ((c = fn_getc(src)) != '\0') 206 switch (c) { 207 case '.': 208 case '(': 209 case ')': 210 case '^': 211 case '+': 212 case '{': 213 case '}': 214 /* when building an re, escape with a backslash */ 215 if (n < 0) 216 fn_putc(dst, '\\'); 217 fn_putc(dst, c); 218 break; 219 case '?': 220 /* when building an re, change '?' to a single dot */ 221 if (n < 0) 222 fn_putc(dst, '.'); 223 break; 224 case '*': 225 /* when building an re, change '*' to ".*" */ 226 if (n < 0) 227 fn_putc(dst, '.'); 228 fn_putc(dst, '*'); 229 break; 230 case '$': 231 /* '$' marks the start of a keyword */ 232 switch (c = fn_getc(src)) { 233 case '$': 234 /* double '$' stands for a single '$' */ 235 if (n < 0) 236 fn_putc(dst, '\\'); 237 fn_putc(dst, '$'); 238 break; 239 case '#': 240 /* 241 * $# expands to nothing, but forces an end 242 * of keyword, allow juxtaposition of a 243 * keyword with lower-case characters 244 */ 245 break; 246 case 'n': 247 case 'N': 248 if (c == 'N' || !islower(fn_peekc(src))) { 249 /* 250 * we've found $n or $N, if we're 251 * building an re, build one that 252 * matches a number, otherwise 253 * expand the keyword to the n 254 * passed in to this function 255 */ 256 hasn = B_TRUE; 257 if (n < 0) 258 fn_puts(dst, "([0-9]+)$0"); 259 else { 260 (void) snprintf(buf, 261 MAXDIGITS, "%d", 262 (c == 'n') ? n : n + 1); 263 fn_puts(dst, buf); 264 } 265 break; 266 } 267 /*FALLTHROUGH*/ 268 default: 269 /* gather up the keyword name */ 270 fn_renew(kw, NULL); 271 fn_putc(kw, c); 272 while (islower(fn_peekc(src))) 273 fn_putc(kw, fn_getc(src)); 274 275 /* lookup keyword */ 276 if ((ptr = (char *)lut_lookup(Keywords, 277 fn_s(kw))) == NULL) { 278 /* nope, copy it unexpanded */ 279 if (n < 0) 280 fn_putc(dst, '\\'); 281 fn_putc(dst, '$'); 282 fn_putfn(dst, kw); 283 } else 284 fn_puts(dst, ptr); 285 } 286 break; 287 case '%': 288 /* 289 * % sequence for strftime(), if we're building 290 * an re, we take our best guess at the re for 291 * this sequence, otherwise we pass it to strftime() 292 */ 293 if (n < 0) { 294 /* 295 * the regex for a percent sequence is 296 * usually just ".*" unless it is one 297 * of the common cases we know about 298 * that are numeric. in those cases, we 299 * tighten up the regex to just match digits. 300 * 301 * while it is gross that we embed knowledge 302 * of strftime() sequences here, they are 303 * specified in a standard so aren't 304 * expected to change often, and it *really* 305 * cuts down on the possibility that we'll 306 * expire a file that isn't an old log file. 307 */ 308 if ((c = fn_getc(src)) == 'E' || c == 'O') { 309 c = fn_getc(src); 310 fn_puts(dst, ".*"); 311 } else 312 switch (c) { 313 case 'd': 314 case 'g': 315 case 'G': 316 case 'H': 317 case 'I': 318 case 'j': 319 case 'm': 320 case 'M': 321 case 'S': 322 case 'u': 323 case 'U': 324 case 'V': 325 case 'w': 326 case 'W': 327 case 'y': 328 case 'Y': 329 /* pure numeric cases */ 330 fn_puts(dst, "[0-9]+"); 331 break; 332 case 'e': 333 case 'k': 334 case 'l': 335 /* possible space then num */ 336 fn_puts(dst, " *[0-9]+"); 337 break; 338 case 'D': /* %m/%d/%y */ 339 /* adds slashes! */ 340 fn_puts(dst, 341 "[0-9]+/[0-9]+/[0-9]+"); 342 break; 343 case 'R': /* %H:%M */ 344 fn_puts(dst, "[0-9]+:[0-9]+"); 345 break; 346 case 'T': /* %H:%M:%S */ 347 fn_puts(dst, 348 "[0-9]+:[0-9]+:[0-9]+"); 349 break; 350 default: 351 fn_puts(dst, ".*"); 352 } 353 } else { 354 char tbuf[4]; 355 356 /* copy % sequence to tbuf */ 357 tbuf[0] = '%'; 358 tbuf[1] = fn_getc(src); 359 if (tbuf[1] == 'E' || tbuf[1] == 'O') { 360 /* "extended" sequence */ 361 tbuf[2] = fn_getc(src); 362 tbuf[3] = '\0'; 363 } else 364 tbuf[2] = '\0'; 365 366 if (strftime(buf, MAXDIGITS, tbuf, gmt_tm) == 0) 367 /* just copy %x */ 368 fn_puts(dst, tbuf); 369 else 370 fn_puts(dst, buf); 371 } 372 break; 373 default: 374 /* nothing special, just copy it */ 375 fn_putc(dst, c); 376 } 377 378 if (gz) { 379 if (n < 0) 380 fn_puts(dst, "(\\.gz){0,1}"); 381 else 382 fn_puts(dst, ".gz"); 383 } 384 385 fn_free(kw); 386 return (hasn); 387 } 388 389 #ifdef TESTMODULE 390 391 time_t Now; 392 393 /* 394 * test main for kw module, usage: a.out fname [template...] 395 */ 396 int 397 main(int argc, char *argv[]) 398 { 399 int i; 400 struct fn *src = fn_new(NULL); 401 struct fn *dst = fn_new(NULL); 402 403 err_init(argv[0]); 404 setbuf(stdout, NULL); 405 406 Now = time(0); 407 408 if (argc < 2) 409 err(0, "first arg must be fname"); 410 411 kw_init(fn_new(argv[1]), NULL); 412 413 kw_print(stdout); 414 415 for (i = 2; i < argc; i++) { 416 int n; 417 418 for (n = -1; n < 2; n++) { 419 fn_renew(src, argv[i]); 420 fn_renew(dst, NULL); 421 printf("expand<%s> n %d hasn %d ", 422 argv[i], n, kw_expand(src, dst, n, B_FALSE)); 423 printf("result <%s>\n", fn_s(dst)); 424 } 425 } 426 427 err_done(0); 428 /* NOTREACHED */ 429 return (0); 430 } 431 432 #endif /* TESTMODULE */ 433