/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * logadm/fn.c -- "filename" string module * * this file contains routines for the manipulation of filenames. * they aren't particularly fast (at least they weren't designed * for performance), but they are simple and put all the malloc/free * stuff for these strings in a central place. most routines in * logadm that return filenames return a struct fn, and most routines * that return lists of strings return a struct fn_list. */ #pragma ident "%Z%%M% %I% %E% SMI" #include #include #include #include #include #include "err.h" #include "fn.h" #define roundup(x, y) ((((x)+((y)-1))/(y))*(y)) /* * constants controlling how we malloc space. bigger means fewer * calls to malloc. smaller means less wasted space. */ #define FN_MIN 1024 /* initial size of string buffers */ #define FN_MAX 10240 /* maximum size allowed before fatal "overflow" error */ #define FN_INC 1024 /* increments in buffer size as strings grow */ /* info created by fn_new(), private to this module */ struct fn { char *fn_buf; /* first location in buf */ char *fn_buflast; /* last location in buf */ char *fn_rptr; /* read pointer (next unread character) */ char *fn_wptr; /* write pointer (points at null terminator) */ struct fn *fn_next; /* next in list */ struct stat fn_stbuf; int fn_n; }; /* info created by fn_list_new(), private to this module */ struct fn_list { struct fn *fnl_first; /* first element of list */ struct fn *fnl_last; /* last element of list */ struct fn *fnl_rptr; /* read pointer for iterating through list */ }; /* * fn_new -- create a new filename buffer, possibly with initial contents * * use like this: * struct fn *fnp = fn_new("this is a string"); */ struct fn * fn_new(const char *s) { struct fn *fnp = MALLOC(sizeof (struct fn)); fnp->fn_n = -1; bzero(&fnp->fn_stbuf, sizeof (fnp->fn_stbuf)); fnp->fn_next = NULL; /* if passed-in string contains at least 1 non-null character... */ if (s != NULL && *s) { int len = strlen(s); int buflen = roundup(len + 1, FN_INC); /* start with buffer filled with passed-in string */ fnp->fn_buf = MALLOC(buflen); fnp->fn_buflast = &fnp->fn_buf[buflen - 1]; (void) strlcpy(fnp->fn_buf, s, buflen); fnp->fn_rptr = fnp->fn_buf; fnp->fn_wptr = &fnp->fn_buf[len]; } else { /* start with empty buffer */ fnp->fn_buf = MALLOC(FN_MIN); fnp->fn_buflast = &fnp->fn_buf[FN_MIN - 1]; *fnp->fn_buf = '\0'; fnp->fn_rptr = fnp->fn_buf; fnp->fn_wptr = fnp->fn_buf; } return (fnp); } /* * fn_dup -- duplicate a filename buffer */ struct fn * fn_dup(struct fn *fnp) { struct fn *ret = fn_new(fn_s(fnp)); ret->fn_n = fnp->fn_n; ret->fn_stbuf = fnp->fn_stbuf; return (ret); } /* * fn_dirname -- return the dirname part of a filename */ struct fn * fn_dirname(struct fn *fnp) { char *ptr = NULL; struct fn *ret; char *buf; buf = fn_s(fnp); if (buf != NULL) ptr = strrchr(buf, '/'); if (ptr == NULL || buf == NULL) return (fn_new(".")); else { *ptr = '\0'; ret = fn_new(buf); *ptr = '/'; return (ret); } } /* * fn_setn -- set the "n" value for a filename * * the "n" value is initially -1, and is used by logadm to store * the suffix for rotated log files. the function fn_list_popoldest() * looks at these "n" values when sorting filenames to determine which * old log file is the oldest and should be expired first. */ void fn_setn(struct fn *fnp, int n) { fnp->fn_n = n; } /* * fn_setstat -- store a struct stat with a filename * * the glob functions typically fill in these struct stats since they * have to stat while globbing anyway. just turned out to be a common * piece of information that was conveniently stored with the associated * filename. */ void fn_setstat(struct fn *fnp, struct stat *stp) { fnp->fn_stbuf = *stp; } /* * fn_getstat -- return a pointer to the stat info stored by fn_setstat() */ struct stat * fn_getstat(struct fn *fnp) { return (&fnp->fn_stbuf); } /* * fn_free -- free a filename buffer */ void fn_free(struct fn *fnp) { if (fnp) { if (fnp->fn_buf) FREE(fnp->fn_buf); FREE(fnp); } } /* * fn_renew -- reset a filename buffer * * calling fn_renew(fnp, s) is the same as calling: * fn_free(fnp); * fn_new(s); */ void fn_renew(struct fn *fnp, const char *s) { fnp->fn_rptr = fnp->fn_wptr = fnp->fn_buf; fn_puts(fnp, s); } /* * fn_putc -- append a character to a filename * * this is the function that handles growing the filename buffer * automatically and calling err() if it overflows. */ void fn_putc(struct fn *fnp, int c) { if (fnp->fn_wptr >= fnp->fn_buflast) { int buflen = fnp->fn_buflast + 1 - fnp->fn_buf; char *newbuf; char *src; char *dst; /* overflow, allocate more space or die if at FN_MAX */ if (buflen >= FN_MAX) err(0, "fn buffer overflow"); buflen += FN_INC; newbuf = MALLOC(buflen); /* copy string into new buffer */ src = fnp->fn_buf; dst = newbuf; /* just copy up to wptr, rest is history anyway */ while (src < fnp->fn_wptr) *dst++ = *src++; fnp->fn_rptr = &newbuf[fnp->fn_rptr - fnp->fn_buf]; FREE(fnp->fn_buf); fnp->fn_buf = newbuf; fnp->fn_buflast = &fnp->fn_buf[buflen - 1]; fnp->fn_wptr = dst; } *fnp->fn_wptr++ = c; *fnp->fn_wptr = '\0'; } /* * fn_puts -- append a string to a filename */ void fn_puts(struct fn *fnp, const char *s) { /* non-optimal, but simple! */ while (s != NULL && *s) fn_putc(fnp, *s++); } /* * fn_putfn -- append a filename buffer to a filename */ void fn_putfn(struct fn *fnp, struct fn *srcfnp) { int c; fn_rewind(srcfnp); while (c = fn_getc(srcfnp)) fn_putc(fnp, c); } /* * fn_rewind -- reset the "read pointer" to the beginning of a filename */ void fn_rewind(struct fn *fnp) { fnp->fn_rptr = fnp->fn_buf; } /* * fn_getc -- "read" the next character of a filename */ int fn_getc(struct fn *fnp) { if (fnp->fn_rptr > fnp->fn_buflast || *fnp->fn_rptr == '\0') return (0); return (*fnp->fn_rptr++); } /* * fn_peekc -- "peek" at the next character of a filename */ int fn_peekc(struct fn *fnp) { if (fnp->fn_rptr > fnp->fn_buflast || *fnp->fn_rptr == '\0') return (0); return (*fnp->fn_rptr); } /* * fn_s -- return a pointer to a null-terminated string containing the filename */ char * fn_s(struct fn *fnp) { return (fnp->fn_buf); } /* * fn_isgz -- return true if filename is *.gz */ boolean_t fn_isgz(struct fn *fnp) { size_t len; char *name; name = fnp->fn_buf; len = strlen(name); if (len > 3 && strcmp(name + len - 3, ".gz") == 0) return (B_TRUE); else return (B_FALSE); } /* * fn_list_new -- create a new list of filenames * * by convention, an empty list is represented by an allocated * struct fn_list which contains a NULL linked list, rather than * by a NULL fn_list pointer. in other words: * * struct fn_list *fnlp = some_func_returning_a_list(); * if (fn_list_empty(fnlp)) * ... * * is preferable to checking if the fnlp returned is NULL. */ struct fn_list * fn_list_new(const char * const *slist) { struct fn_list *fnlp = MALLOC(sizeof (struct fn_list)); fnlp->fnl_first = fnlp->fnl_last = fnlp->fnl_rptr = NULL; while (slist && *slist) fn_list_adds(fnlp, *slist++); return (fnlp); } /* * fn_list_dup -- duplicate a list of filenames */ struct fn_list * fn_list_dup(struct fn_list *fnlp) { struct fn_list *ret = fn_list_new(NULL); struct fn *fnp; fn_list_rewind(fnlp); while ((fnp = fn_list_next(fnlp)) != NULL) fn_list_addfn(ret, fn_dup(fnp)); return (ret); } /* * fn_list_free -- free a list of filenames */ void fn_list_free(struct fn_list *fnlp) { struct fn *fnp; fn_list_rewind(fnlp); while ((fnp = fn_list_next(fnlp)) != NULL) fn_free(fnp); FREE(fnlp); } /* * fn_list_adds -- add a string to a list of filenames */ void fn_list_adds(struct fn_list *fnlp, const char *s) { fn_list_addfn(fnlp, fn_new(s)); } /* * fn_list_addfn -- add a filename (i.e. struct fn *) to a list of filenames */ void fn_list_addfn(struct fn_list *fnlp, struct fn *fnp) { fnp->fn_next = NULL; if (fnlp->fnl_first == NULL) fnlp->fnl_first = fnlp->fnl_last = fnlp->fnl_rptr = fnp; else { fnlp->fnl_last->fn_next = fnp; fnlp->fnl_last = fnp; } } /* * fn_list_rewind -- reset the "read pointer" to the beginning of the list */ void fn_list_rewind(struct fn_list *fnlp) { fnlp->fnl_rptr = fnlp->fnl_first; } /* * fn_list_next -- return the filename at the read pointer and advance it */ struct fn * fn_list_next(struct fn_list *fnlp) { struct fn *ret = fnlp->fnl_rptr; if (fnlp->fnl_rptr == fnlp->fnl_last) fnlp->fnl_rptr = NULL; else if (fnlp->fnl_rptr != NULL) fnlp->fnl_rptr = fnlp->fnl_rptr->fn_next; return (ret); } /* * fn_list_addfn_list -- move filenames from fnlp2 to end of fnlp * * frees fnlp2 after moving all the filenames off of it. */ void fn_list_addfn_list(struct fn_list *fnlp, struct fn_list *fnlp2) { struct fn *fnp2 = fnlp2->fnl_first; struct fn *nextfnp2; /* for each fn in the second list... */ while (fnp2) { if (fnp2 == fnlp2->fnl_last) nextfnp2 = NULL; else nextfnp2 = fnp2->fn_next; /* append it to the first list */ fn_list_addfn(fnlp, fnp2); fnp2 = nextfnp2; } /* all the fn's were moved off the second list */ fnlp2->fnl_first = fnlp2->fnl_last = fnlp2->fnl_rptr = NULL; /* done with the second list */ fn_list_free(fnlp2); } /* * fn_list_appendrange -- append a range of characters to each filename in list * * range of characters appended is the character at *s up to but not including * the character at *limit. NULL termination is not required. */ void fn_list_appendrange(struct fn_list *fnlp, const char *s, const char *limit) { struct fn *fnp = fnlp->fnl_first; struct fn *nextfnp; const char *ptr; /* for each fn in the list... */ while (fnp != NULL) { if (fnp == fnlp->fnl_last) nextfnp = NULL; else nextfnp = fnp->fn_next; /* append the range */ for (ptr = s; ptr < limit; ptr++) fn_putc(fnp, *ptr); fnp = nextfnp; } } /* * fn_list_totalsize -- sum up all the st_size fields in the stat structs */ off_t fn_list_totalsize(struct fn_list *fnlp) { struct fn *fnp; off_t ret = 0; fn_list_rewind(fnlp); while ((fnp = fn_list_next(fnlp)) != NULL) ret += fnp->fn_stbuf.st_size; return (ret); } /* * fn_list_popoldest -- remove oldest file from list and return it * * this function uses the "n" values (set by fn_setn()) to determine * which file is oldest, or when there's a tie it turns to the modification * times in the stat structs, or when there's still a tie lexical sorting. */ struct fn * fn_list_popoldest(struct fn_list *fnlp) { struct fn *fnp; struct fn *ret = NULL; fn_list_rewind(fnlp); while ((fnp = fn_list_next(fnlp)) != NULL) if (ret == NULL) ret = fnp; else if (fnp->fn_n > ret->fn_n || (fnp->fn_n == ret->fn_n && (fnp->fn_stbuf.st_mtime < ret->fn_stbuf.st_mtime || ((fnp->fn_stbuf.st_mtime == ret->fn_stbuf.st_mtime && strcmp(fnp->fn_buf, ret->fn_buf) > 0))))) ret = fnp; if (ret == NULL) return (NULL); /* oldest file is ret, remove it from list */ if (fnlp->fnl_first == ret) { fnlp->fnl_first = ret->fn_next; } else { fn_list_rewind(fnlp); while ((fnp = fn_list_next(fnlp)) != NULL) { if (fnp->fn_next == ret) { fnp->fn_next = ret->fn_next; if (fnlp->fnl_last == ret) fnlp->fnl_last = fnp; break; } } } ret->fn_next = NULL; return (ret); } /* * fn_list_empty -- true if the list is empty */ boolean_t fn_list_empty(struct fn_list *fnlp) { return (fnlp->fnl_first == NULL); } /* * fn_list_count -- return number of filenames in list */ int fn_list_count(struct fn_list *fnlp) { int ret = 0; /* * if this operation were more common, we'd cache the count * in the struct fn_list, but it isn't very common so we just * count 'em up here */ fn_list_rewind(fnlp); while (fn_list_next(fnlp) != NULL) ret++; return (ret); }