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 2007 Sun Microsystems, Inc. All rights reserved. 23 * Use is subject to license terms. 24 * 25 * logadm/fn.c -- "filename" string module 26 * 27 * this file contains routines for the manipulation of filenames. 28 * they aren't particularly fast (at least they weren't designed 29 * for performance), but they are simple and put all the malloc/free 30 * stuff for these strings in a central place. most routines in 31 * logadm that return filenames return a struct fn, and most routines 32 * that return lists of strings return a struct fn_list. 33 */ 34 35 #pragma ident "%Z%%M% %I% %E% SMI" 36 37 #include <stdio.h> 38 #include <libintl.h> 39 #include <strings.h> 40 #include <sys/types.h> 41 #include <sys/stat.h> 42 #include "err.h" 43 #include "fn.h" 44 45 #define roundup(x, y) ((((x)+((y)-1))/(y))*(y)) 46 47 /* 48 * constants controlling how we malloc space. bigger means fewer 49 * calls to malloc. smaller means less wasted space. 50 */ 51 #define FN_MIN 1024 /* initial size of string buffers */ 52 #define FN_MAX 10240 /* maximum size allowed before fatal "overflow" error */ 53 #define FN_INC 1024 /* increments in buffer size as strings grow */ 54 55 /* info created by fn_new(), private to this module */ 56 struct fn { 57 char *fn_buf; /* first location in buf */ 58 char *fn_buflast; /* last location in buf */ 59 char *fn_rptr; /* read pointer (next unread character) */ 60 char *fn_wptr; /* write pointer (points at null terminator) */ 61 struct fn *fn_next; /* next in list */ 62 struct stat fn_stbuf; 63 int fn_n; 64 }; 65 66 /* info created by fn_list_new(), private to this module */ 67 struct fn_list { 68 struct fn *fnl_first; /* first element of list */ 69 struct fn *fnl_last; /* last element of list */ 70 struct fn *fnl_rptr; /* read pointer for iterating through list */ 71 }; 72 73 /* 74 * fn_new -- create a new filename buffer, possibly with initial contents 75 * 76 * use like this: 77 * struct fn *fnp = fn_new("this is a string"); 78 */ 79 struct fn * 80 fn_new(const char *s) 81 { 82 struct fn *fnp = MALLOC(sizeof (struct fn)); 83 84 fnp->fn_n = -1; 85 bzero(&fnp->fn_stbuf, sizeof (fnp->fn_stbuf)); 86 fnp->fn_next = NULL; 87 88 /* if passed-in string contains at least 1 non-null character... */ 89 if (s != NULL && *s) { 90 int len = strlen(s); 91 int buflen = roundup(len + 1, FN_INC); 92 93 /* start with buffer filled with passed-in string */ 94 fnp->fn_buf = MALLOC(buflen); 95 fnp->fn_buflast = &fnp->fn_buf[buflen - 1]; 96 (void) strlcpy(fnp->fn_buf, s, buflen); 97 fnp->fn_rptr = fnp->fn_buf; 98 fnp->fn_wptr = &fnp->fn_buf[len]; 99 } else { 100 /* start with empty buffer */ 101 fnp->fn_buf = MALLOC(FN_MIN); 102 fnp->fn_buflast = &fnp->fn_buf[FN_MIN - 1]; 103 *fnp->fn_buf = '\0'; 104 fnp->fn_rptr = fnp->fn_buf; 105 fnp->fn_wptr = fnp->fn_buf; 106 } 107 108 return (fnp); 109 } 110 111 /* 112 * fn_dup -- duplicate a filename buffer 113 */ 114 struct fn * 115 fn_dup(struct fn *fnp) 116 { 117 struct fn *ret = fn_new(fn_s(fnp)); 118 119 ret->fn_n = fnp->fn_n; 120 ret->fn_stbuf = fnp->fn_stbuf; 121 122 return (ret); 123 } 124 125 /* 126 * fn_dirname -- return the dirname part of a filename 127 */ 128 struct fn * 129 fn_dirname(struct fn *fnp) 130 { 131 char *ptr = NULL; 132 struct fn *ret; 133 char *buf; 134 135 buf = fn_s(fnp); 136 137 if (buf != NULL) 138 ptr = strrchr(buf, '/'); 139 if (ptr == NULL || buf == NULL) 140 return (fn_new(".")); 141 else { 142 *ptr = '\0'; 143 ret = fn_new(buf); 144 *ptr = '/'; 145 return (ret); 146 } 147 } 148 149 /* 150 * fn_setn -- set the "n" value for a filename 151 * 152 * the "n" value is initially -1, and is used by logadm to store 153 * the suffix for rotated log files. the function fn_list_popoldest() 154 * looks at these "n" values when sorting filenames to determine which 155 * old log file is the oldest and should be expired first. 156 */ 157 void 158 fn_setn(struct fn *fnp, int n) 159 { 160 fnp->fn_n = n; 161 } 162 163 /* 164 * fn_setstat -- store a struct stat with a filename 165 * 166 * the glob functions typically fill in these struct stats since they 167 * have to stat while globbing anyway. just turned out to be a common 168 * piece of information that was conveniently stored with the associated 169 * filename. 170 */ 171 void 172 fn_setstat(struct fn *fnp, struct stat *stp) 173 { 174 fnp->fn_stbuf = *stp; 175 } 176 177 /* 178 * fn_getstat -- return a pointer to the stat info stored by fn_setstat() 179 */ 180 struct stat * 181 fn_getstat(struct fn *fnp) 182 { 183 return (&fnp->fn_stbuf); 184 } 185 186 /* 187 * fn_free -- free a filename buffer 188 */ 189 void 190 fn_free(struct fn *fnp) 191 { 192 if (fnp) { 193 if (fnp->fn_buf) 194 FREE(fnp->fn_buf); 195 FREE(fnp); 196 } 197 } 198 199 /* 200 * fn_renew -- reset a filename buffer 201 * 202 * calling fn_renew(fnp, s) is the same as calling: 203 * fn_free(fnp); 204 * fn_new(s); 205 */ 206 void 207 fn_renew(struct fn *fnp, const char *s) 208 { 209 fnp->fn_rptr = fnp->fn_wptr = fnp->fn_buf; 210 fn_puts(fnp, s); 211 } 212 213 /* 214 * fn_putc -- append a character to a filename 215 * 216 * this is the function that handles growing the filename buffer 217 * automatically and calling err() if it overflows. 218 */ 219 void 220 fn_putc(struct fn *fnp, int c) 221 { 222 if (fnp->fn_wptr >= fnp->fn_buflast) { 223 int buflen = fnp->fn_buflast + 1 - fnp->fn_buf; 224 char *newbuf; 225 char *src; 226 char *dst; 227 228 /* overflow, allocate more space or die if at FN_MAX */ 229 if (buflen >= FN_MAX) 230 err(0, "fn buffer overflow"); 231 buflen += FN_INC; 232 newbuf = MALLOC(buflen); 233 234 /* copy string into new buffer */ 235 src = fnp->fn_buf; 236 dst = newbuf; 237 238 /* just copy up to wptr, rest is history anyway */ 239 while (src < fnp->fn_wptr) 240 *dst++ = *src++; 241 fnp->fn_rptr = &newbuf[fnp->fn_rptr - fnp->fn_buf]; 242 FREE(fnp->fn_buf); 243 fnp->fn_buf = newbuf; 244 fnp->fn_buflast = &fnp->fn_buf[buflen - 1]; 245 fnp->fn_wptr = dst; 246 } 247 *fnp->fn_wptr++ = c; 248 *fnp->fn_wptr = '\0'; 249 } 250 251 /* 252 * fn_puts -- append a string to a filename 253 */ 254 void 255 fn_puts(struct fn *fnp, const char *s) 256 { 257 /* non-optimal, but simple! */ 258 while (s != NULL && *s) 259 fn_putc(fnp, *s++); 260 } 261 262 /* 263 * fn_putfn -- append a filename buffer to a filename 264 */ 265 void 266 fn_putfn(struct fn *fnp, struct fn *srcfnp) 267 { 268 int c; 269 270 fn_rewind(srcfnp); 271 while (c = fn_getc(srcfnp)) 272 fn_putc(fnp, c); 273 } 274 275 /* 276 * fn_rewind -- reset the "read pointer" to the beginning of a filename 277 */ 278 void 279 fn_rewind(struct fn *fnp) 280 { 281 fnp->fn_rptr = fnp->fn_buf; 282 } 283 284 /* 285 * fn_getc -- "read" the next character of a filename 286 */ 287 int 288 fn_getc(struct fn *fnp) 289 { 290 if (fnp->fn_rptr > fnp->fn_buflast || *fnp->fn_rptr == '\0') 291 return (0); 292 293 return (*fnp->fn_rptr++); 294 } 295 296 /* 297 * fn_peekc -- "peek" at the next character of a filename 298 */ 299 int 300 fn_peekc(struct fn *fnp) 301 { 302 if (fnp->fn_rptr > fnp->fn_buflast || *fnp->fn_rptr == '\0') 303 return (0); 304 305 return (*fnp->fn_rptr); 306 } 307 308 /* 309 * fn_s -- return a pointer to a null-terminated string containing the filename 310 */ 311 char * 312 fn_s(struct fn *fnp) 313 { 314 return (fnp->fn_buf); 315 } 316 317 /* 318 * fn_isgz -- return true if filename is *.gz 319 */ 320 boolean_t 321 fn_isgz(struct fn *fnp) 322 { 323 size_t len; 324 char *name; 325 326 name = fnp->fn_buf; 327 len = strlen(name); 328 if (len > 3 && strcmp(name + len - 3, ".gz") == 0) 329 return (B_TRUE); 330 else 331 return (B_FALSE); 332 } 333 334 /* 335 * fn_list_new -- create a new list of filenames 336 * 337 * by convention, an empty list is represented by an allocated 338 * struct fn_list which contains a NULL linked list, rather than 339 * by a NULL fn_list pointer. in other words: 340 * 341 * struct fn_list *fnlp = some_func_returning_a_list(); 342 * if (fn_list_empty(fnlp)) 343 * ... 344 * 345 * is preferable to checking if the fnlp returned is NULL. 346 */ 347 struct fn_list * 348 fn_list_new(const char * const *slist) 349 { 350 struct fn_list *fnlp = MALLOC(sizeof (struct fn_list)); 351 352 fnlp->fnl_first = fnlp->fnl_last = fnlp->fnl_rptr = NULL; 353 354 while (slist && *slist) 355 fn_list_adds(fnlp, *slist++); 356 357 return (fnlp); 358 } 359 360 /* 361 * fn_list_dup -- duplicate a list of filenames 362 */ 363 struct fn_list * 364 fn_list_dup(struct fn_list *fnlp) 365 { 366 struct fn_list *ret = fn_list_new(NULL); 367 struct fn *fnp; 368 369 fn_list_rewind(fnlp); 370 while ((fnp = fn_list_next(fnlp)) != NULL) 371 fn_list_addfn(ret, fn_dup(fnp)); 372 373 return (ret); 374 } 375 376 /* 377 * fn_list_free -- free a list of filenames 378 */ 379 void 380 fn_list_free(struct fn_list *fnlp) 381 { 382 struct fn *fnp; 383 384 fn_list_rewind(fnlp); 385 while ((fnp = fn_list_next(fnlp)) != NULL) 386 fn_free(fnp); 387 FREE(fnlp); 388 } 389 390 /* 391 * fn_list_adds -- add a string to a list of filenames 392 */ 393 void 394 fn_list_adds(struct fn_list *fnlp, const char *s) 395 { 396 fn_list_addfn(fnlp, fn_new(s)); 397 } 398 399 /* 400 * fn_list_addfn -- add a filename (i.e. struct fn *) to a list of filenames 401 */ 402 void 403 fn_list_addfn(struct fn_list *fnlp, struct fn *fnp) 404 { 405 fnp->fn_next = NULL; 406 if (fnlp->fnl_first == NULL) 407 fnlp->fnl_first = fnlp->fnl_last = fnlp->fnl_rptr = fnp; 408 else { 409 fnlp->fnl_last->fn_next = fnp; 410 fnlp->fnl_last = fnp; 411 } 412 } 413 414 /* 415 * fn_list_rewind -- reset the "read pointer" to the beginning of the list 416 */ 417 void 418 fn_list_rewind(struct fn_list *fnlp) 419 { 420 fnlp->fnl_rptr = fnlp->fnl_first; 421 } 422 423 /* 424 * fn_list_next -- return the filename at the read pointer and advance it 425 */ 426 struct fn * 427 fn_list_next(struct fn_list *fnlp) 428 { 429 struct fn *ret = fnlp->fnl_rptr; 430 431 if (fnlp->fnl_rptr == fnlp->fnl_last) 432 fnlp->fnl_rptr = NULL; 433 else if (fnlp->fnl_rptr != NULL) 434 fnlp->fnl_rptr = fnlp->fnl_rptr->fn_next; 435 436 return (ret); 437 } 438 439 /* 440 * fn_list_addfn_list -- move filenames from fnlp2 to end of fnlp 441 * 442 * frees fnlp2 after moving all the filenames off of it. 443 */ 444 void 445 fn_list_addfn_list(struct fn_list *fnlp, struct fn_list *fnlp2) 446 { 447 struct fn *fnp2 = fnlp2->fnl_first; 448 struct fn *nextfnp2; 449 450 /* for each fn in the second list... */ 451 while (fnp2) { 452 if (fnp2 == fnlp2->fnl_last) 453 nextfnp2 = NULL; 454 else 455 nextfnp2 = fnp2->fn_next; 456 457 /* append it to the first list */ 458 fn_list_addfn(fnlp, fnp2); 459 460 fnp2 = nextfnp2; 461 } 462 /* all the fn's were moved off the second list */ 463 fnlp2->fnl_first = fnlp2->fnl_last = fnlp2->fnl_rptr = NULL; 464 465 /* done with the second list */ 466 fn_list_free(fnlp2); 467 } 468 469 /* 470 * fn_list_appendrange -- append a range of characters to each filename in list 471 * 472 * range of characters appended is the character at *s up to but not including 473 * the character at *limit. NULL termination is not required. 474 */ 475 void 476 fn_list_appendrange(struct fn_list *fnlp, const char *s, const char *limit) 477 { 478 struct fn *fnp = fnlp->fnl_first; 479 struct fn *nextfnp; 480 const char *ptr; 481 482 /* for each fn in the list... */ 483 while (fnp != NULL) { 484 if (fnp == fnlp->fnl_last) 485 nextfnp = NULL; 486 else 487 nextfnp = fnp->fn_next; 488 489 /* append the range */ 490 for (ptr = s; ptr < limit; ptr++) 491 fn_putc(fnp, *ptr); 492 493 fnp = nextfnp; 494 } 495 } 496 497 /* 498 * fn_list_totalsize -- sum up all the st_size fields in the stat structs 499 */ 500 off_t 501 fn_list_totalsize(struct fn_list *fnlp) 502 { 503 struct fn *fnp; 504 off_t ret = 0; 505 506 fn_list_rewind(fnlp); 507 while ((fnp = fn_list_next(fnlp)) != NULL) 508 ret += fnp->fn_stbuf.st_size; 509 510 return (ret); 511 } 512 513 /* 514 * fn_list_popoldest -- remove oldest file from list and return it 515 * 516 * this function uses the "n" values (set by fn_setn()) to determine 517 * which file is oldest, or when there's a tie it turns to the modification 518 * times in the stat structs, or when there's still a tie lexical sorting. 519 */ 520 struct fn * 521 fn_list_popoldest(struct fn_list *fnlp) 522 { 523 struct fn *fnp; 524 struct fn *ret = NULL; 525 526 fn_list_rewind(fnlp); 527 while ((fnp = fn_list_next(fnlp)) != NULL) 528 if (ret == NULL) 529 ret = fnp; 530 else if (fnp->fn_n > ret->fn_n || 531 (fnp->fn_n == ret->fn_n && 532 (fnp->fn_stbuf.st_mtime < ret->fn_stbuf.st_mtime || 533 ((fnp->fn_stbuf.st_mtime == ret->fn_stbuf.st_mtime && 534 strcmp(fnp->fn_buf, ret->fn_buf) > 0))))) 535 ret = fnp; 536 537 if (ret == NULL) 538 return (NULL); 539 540 /* oldest file is ret, remove it from list */ 541 if (fnlp->fnl_first == ret) { 542 fnlp->fnl_first = ret->fn_next; 543 } else { 544 fn_list_rewind(fnlp); 545 while ((fnp = fn_list_next(fnlp)) != NULL) { 546 if (fnp->fn_next == ret) { 547 fnp->fn_next = ret->fn_next; 548 if (fnlp->fnl_last == ret) 549 fnlp->fnl_last = fnp; 550 break; 551 } 552 } 553 } 554 555 ret->fn_next = NULL; 556 return (ret); 557 } 558 559 /* 560 * fn_list_empty -- true if the list is empty 561 */ 562 boolean_t 563 fn_list_empty(struct fn_list *fnlp) 564 { 565 return (fnlp->fnl_first == NULL); 566 } 567 568 /* 569 * fn_list_count -- return number of filenames in list 570 */ 571 int 572 fn_list_count(struct fn_list *fnlp) 573 { 574 int ret = 0; 575 576 /* 577 * if this operation were more common, we'd cache the count 578 * in the struct fn_list, but it isn't very common so we just 579 * count 'em up here 580 */ 581 fn_list_rewind(fnlp); 582 while (fn_list_next(fnlp) != NULL) 583 ret++; 584 585 return (ret); 586 } 587