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