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