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/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_list_new -- create a new list of filenames 319 * 320 * by convention, an empty list is represented by an allocated 321 * struct fn_list which contains a NULL linked list, rather than 322 * by a NULL fn_list pointer. in other words: 323 * 324 * struct fn_list *fnlp = some_func_returning_a_list(); 325 * if (fn_list_empty(fnlp)) 326 * ... 327 * 328 * is preferable to checking if the fnlp returned is NULL. 329 */ 330 struct fn_list * 331 fn_list_new(const char * const *slist) 332 { 333 struct fn_list *fnlp = MALLOC(sizeof (struct fn_list)); 334 335 fnlp->fnl_first = fnlp->fnl_last = fnlp->fnl_rptr = NULL; 336 337 while (slist && *slist) 338 fn_list_adds(fnlp, *slist++); 339 340 return (fnlp); 341 } 342 343 /* 344 * fn_list_dup -- duplicate a list of filenames 345 */ 346 struct fn_list * 347 fn_list_dup(struct fn_list *fnlp) 348 { 349 struct fn_list *ret = fn_list_new(NULL); 350 struct fn *fnp; 351 352 fn_list_rewind(fnlp); 353 while ((fnp = fn_list_next(fnlp)) != NULL) 354 fn_list_addfn(ret, fn_dup(fnp)); 355 356 return (ret); 357 } 358 359 /* 360 * fn_list_free -- free a list of filenames 361 */ 362 void 363 fn_list_free(struct fn_list *fnlp) 364 { 365 struct fn *fnp; 366 367 fn_list_rewind(fnlp); 368 while ((fnp = fn_list_next(fnlp)) != NULL) 369 fn_free(fnp); 370 FREE(fnlp); 371 } 372 373 /* 374 * fn_list_adds -- add a string to a list of filenames 375 */ 376 void 377 fn_list_adds(struct fn_list *fnlp, const char *s) 378 { 379 fn_list_addfn(fnlp, fn_new(s)); 380 } 381 382 /* 383 * fn_list_addfn -- add a filename (i.e. struct fn *) to a list of filenames 384 */ 385 void 386 fn_list_addfn(struct fn_list *fnlp, struct fn *fnp) 387 { 388 fnp->fn_next = NULL; 389 if (fnlp->fnl_first == NULL) 390 fnlp->fnl_first = fnlp->fnl_last = fnlp->fnl_rptr = fnp; 391 else { 392 fnlp->fnl_last->fn_next = fnp; 393 fnlp->fnl_last = fnp; 394 } 395 } 396 397 /* 398 * fn_list_rewind -- reset the "read pointer" to the beginning of the list 399 */ 400 void 401 fn_list_rewind(struct fn_list *fnlp) 402 { 403 fnlp->fnl_rptr = fnlp->fnl_first; 404 } 405 406 /* 407 * fn_list_next -- return the filename at the read pointer and advance it 408 */ 409 struct fn * 410 fn_list_next(struct fn_list *fnlp) 411 { 412 struct fn *ret = fnlp->fnl_rptr; 413 414 if (fnlp->fnl_rptr == fnlp->fnl_last) 415 fnlp->fnl_rptr = NULL; 416 else if (fnlp->fnl_rptr != NULL) 417 fnlp->fnl_rptr = fnlp->fnl_rptr->fn_next; 418 419 return (ret); 420 } 421 422 /* 423 * fn_list_addfn_list -- move filenames from fnlp2 to end of fnlp 424 * 425 * frees fnlp2 after moving all the filenames off of it. 426 */ 427 void 428 fn_list_addfn_list(struct fn_list *fnlp, struct fn_list *fnlp2) 429 { 430 struct fn *fnp2 = fnlp2->fnl_first; 431 struct fn *nextfnp2; 432 433 /* for each fn in the second list... */ 434 while (fnp2) { 435 if (fnp2 == fnlp2->fnl_last) 436 nextfnp2 = NULL; 437 else 438 nextfnp2 = fnp2->fn_next; 439 440 /* append it to the first list */ 441 fn_list_addfn(fnlp, fnp2); 442 443 fnp2 = nextfnp2; 444 } 445 /* all the fn's were moved off the second list */ 446 fnlp2->fnl_first = fnlp2->fnl_last = fnlp2->fnl_rptr = NULL; 447 448 /* done with the second list */ 449 fn_list_free(fnlp2); 450 } 451 452 /* 453 * fn_list_appendrange -- append a range of characters to each filename in list 454 * 455 * range of characters appended is the character at *s up to but not including 456 * the character at *limit. NULL termination is not required. 457 */ 458 void 459 fn_list_appendrange(struct fn_list *fnlp, const char *s, const char *limit) 460 { 461 struct fn *fnp = fnlp->fnl_first; 462 struct fn *nextfnp; 463 const char *ptr; 464 465 /* for each fn in the list... */ 466 while (fnp != NULL) { 467 if (fnp == fnlp->fnl_last) 468 nextfnp = NULL; 469 else 470 nextfnp = fnp->fn_next; 471 472 /* append the range */ 473 for (ptr = s; ptr < limit; ptr++) 474 fn_putc(fnp, *ptr); 475 476 fnp = nextfnp; 477 } 478 } 479 480 /* 481 * fn_list_totalsize -- sum up all the st_size fields in the stat structs 482 */ 483 off_t 484 fn_list_totalsize(struct fn_list *fnlp) 485 { 486 struct fn *fnp; 487 off_t ret = 0; 488 489 fn_list_rewind(fnlp); 490 while ((fnp = fn_list_next(fnlp)) != NULL) 491 ret += fnp->fn_stbuf.st_size; 492 493 return (ret); 494 } 495 496 /* 497 * fn_list_popoldest -- remove oldest file from list and return it 498 * 499 * this function uses the "n" values (set by fn_setn()) to determine 500 * which file is oldest, or when there's a tie it turns to the modification 501 * times in the stat structs, or when there's still a tie lexical sorting. 502 */ 503 struct fn * 504 fn_list_popoldest(struct fn_list *fnlp) 505 { 506 struct fn *fnp; 507 struct fn *ret = NULL; 508 509 fn_list_rewind(fnlp); 510 while ((fnp = fn_list_next(fnlp)) != NULL) 511 if (ret == NULL) 512 ret = fnp; 513 else if (fnp->fn_n > ret->fn_n || 514 (fnp->fn_n == ret->fn_n && 515 (fnp->fn_stbuf.st_mtime < ret->fn_stbuf.st_mtime || 516 ((fnp->fn_stbuf.st_mtime == ret->fn_stbuf.st_mtime && 517 strcmp(fnp->fn_buf, ret->fn_buf) > 0))))) 518 ret = fnp; 519 520 if (ret == NULL) 521 return (NULL); 522 523 /* oldest file is ret, remove it from list */ 524 if (fnlp->fnl_first == ret) 525 fnlp->fnl_first = ret->fn_next; 526 else { 527 fn_list_rewind(fnlp); 528 while ((fnp = fn_list_next(fnlp)) != NULL) 529 if (fnp->fn_next == ret) { 530 fnp->fn_next = ret->fn_next; 531 break; 532 } 533 } 534 535 ret->fn_next = NULL; 536 return (ret); 537 } 538 539 /* 540 * fn_list_empty -- true if the list is empty 541 */ 542 boolean_t 543 fn_list_empty(struct fn_list *fnlp) 544 { 545 return (fnlp->fnl_first == NULL); 546 } 547 548 /* 549 * fn_list_count -- return number of filenames in list 550 */ 551 int 552 fn_list_count(struct fn_list *fnlp) 553 { 554 int ret = 0; 555 556 /* 557 * if this operation were more common, we'd cache the count 558 * in the struct fn_list, but it isn't very common so we just 559 * count 'em up here 560 */ 561 fn_list_rewind(fnlp); 562 while (fn_list_next(fnlp) != NULL) 563 ret++; 564 565 return (ret); 566 } 567