/* * Copyright (c) 2001-2002 Packet Design, LLC. * All rights reserved. * * Subject to the following obligations and disclaimer of warranty, * use and redistribution of this software, in source or object code * forms, with or without modifications are expressly permitted by * Packet Design; provided, however, that: * * (i) Any and all reproductions of the source or object code * must include the copyright notice above and the following * disclaimer of warranties; and * (ii) No rights are granted, in any manner or form, to use * Packet Design trademarks, including the mark "PACKET DESIGN" * on advertising, endorsements, or otherwise except as such * appears in the above copyright notice or in the software. * * THIS SOFTWARE IS BEING PROVIDED BY PACKET DESIGN "AS IS", AND * TO THE MAXIMUM EXTENT PERMITTED BY LAW, PACKET DESIGN MAKES NO * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING * THIS SOFTWARE, INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, * OR NON-INFRINGEMENT. PACKET DESIGN DOES NOT WARRANT, GUARANTEE, * OR MAKE ANY REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS * OF THE USE OF THIS SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, * RELIABILITY OR OTHERWISE. IN NO EVENT SHALL PACKET DESIGN BE * LIABLE FOR ANY DAMAGES RESULTING FROM OR ARISING OUT OF ANY USE * OF THIS SOFTWARE, INCLUDING WITHOUT LIMITATION, ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, PUNITIVE, OR CONSEQUENTIAL * DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, LOSS OF * USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF * THE USE OF THIS SOFTWARE, EVEN IF PACKET DESIGN IS ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. * * Author: Archie Cobbs */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "structs/structs.h" #include "structs/type/array.h" #include "sys/logfile.h" #include "sys/alog.h" #include "util/typed_mem.h" #define LOGFILE_MEM_TYPE "logfile" #define LOGFILE_MAGIC 0x476ea198 #define MAX_ENTRIES (1 << 20) #define MAX_DATA (1 << 24) /* Structure passed back to client */ struct logfile { struct loghead *head; /* mmap'd file region */ pthread_mutex_t mutex; /* mutex lock */ u_int32_t headlen; /* length of head + entries */ int fd; /* file descriptor for file */ }; struct logent { u_int32_t offset; u_int32_t length; }; /* Initial part of a log file; all fields in host order */ struct loghead { u_int32_t magic; /* magic number */ u_int32_t maxent; /* max # entries */ u_int32_t maxdata; /* max data area length */ u_int32_t num; /* number of valid entries */ u_int32_t next; /* next entry index */ struct logent ents[0]; /* maxent entries */ }; /* * Open/create a new logfile. */ struct logfile * logfile_open(const char *path, int flags, u_int32_t maxent, u_int32_t maxdata) { struct logfile *lf; struct loghead head; int initialize; int esave; /* Get and sanity check flags */ switch (flags) { case 0: #ifdef O_SHLOCK case O_SHLOCK: case O_EXLOCK: #endif break; default: errno = EINVAL; return (NULL); } #ifdef O_SHLOCK if ((flags & (O_SHLOCK|O_EXLOCK)) != 0) flags |= O_NONBLOCK; #endif /* Create object and open file */ if ((lf = MALLOC(LOGFILE_MEM_TYPE, sizeof(*lf))) == NULL) return (NULL); memset(lf, 0, sizeof(*lf)); if (path != NULL) { if ((lf->fd = open(path, O_CREAT|O_RDWR|flags, 0644)) == -1) goto fail; (void)fcntl(lf->fd, F_SETFD, 1); } else lf->fd = -1; /* See if file already existed */ if (lf->fd != -1) { struct stat sb; if (fstat(lf->fd, &sb) == -1) goto fail; if (!(initialize = (sb.st_size == 0))) { int r; if ((r = read(lf->fd, &head, sizeof(head))) != sizeof(head)) { if (r != -1) errno = EINVAL; goto fail; } if (head.magic != LOGFILE_MAGIC) { errno = EINVAL; goto fail; } maxdata = head.maxdata; maxent = head.maxent; } } else initialize = 1; /* Sanity check parameters */ if (maxent == 0 || maxdata == 0 || maxent > MAX_ENTRIES || maxdata > MAX_DATA) { errno = EINVAL; goto fail; } /* Compute size of header */ lf->headlen = sizeof(*lf->head) + (maxent * sizeof(*lf->head->ents)); /* Set file length */ if (lf->fd != -1 && ftruncate(lf->fd, lf->headlen + maxdata) == -1) goto fail; /* Memory map file */ if ((lf->head = mmap(NULL, lf->headlen + maxdata, PROT_READ|PROT_WRITE, path == NULL ? MAP_ANON : MAP_SHARED, lf->fd, 0)) == MAP_FAILED) goto fail; /* For new file, write header and initialize entries */ if (initialize) { lf->head->magic = LOGFILE_MAGIC; lf->head->maxdata = maxdata; lf->head->maxent = maxent; lf->head->num = 0; lf->head->next = 0; memset(lf->head->ents, 0, maxent * sizeof(*lf->head->ents)); (void)msync(lf->head, 0, MS_ASYNC); } /* Sanitize header fields */ if (lf->head->num > lf->head->maxent) lf->head->num = lf->head->maxent; lf->head->next %= lf->head->maxent; /* Initialize mutex */ if ((errno = pthread_mutex_init(&lf->mutex, NULL)) != 0) goto fail; /* Done */ return (lf); fail: esave = errno; if (lf->fd != -1) (void)close(lf->fd); if (lf->head != NULL) (void)munmap(lf->head, lf->headlen + maxdata); FREE(LOGFILE_MEM_TYPE, lf); errno = esave; return (NULL); } /* * Close a logfile. */ void logfile_close(struct logfile **lfp) { struct logfile *const lf = *lfp; /* Check for NULL */ if (lf == NULL) return; *lfp = NULL; /* Close up shop */ (void)msync(lf->head, 0, MS_SYNC); (void)munmap(lf->head, lf->headlen + lf->head->maxdata); if (lf->fd != -1) (void)close(lf->fd); pthread_mutex_destroy(&lf->mutex); FREE(LOGFILE_MEM_TYPE, lf); } /* * Get the number of valid entries in a logfile. */ u_int32_t logfile_num_entries(struct logfile *lf) { u_int32_t num; int r; r = pthread_mutex_lock(&lf->mutex); assert(r == 0); num = lf->head->num; r = pthread_mutex_unlock(&lf->mutex); assert(r == 0); return (num); } /* * Trim the number of stored entries. */ void logfile_trim(struct logfile *lf, int num) { int r; r = pthread_mutex_lock(&lf->mutex); assert(r == 0); if (lf->head->num > num) lf->head->num = num; r = pthread_mutex_unlock(&lf->mutex); assert(r == 0); } /* * Retrieve an entry. */ const void * logfile_get(struct logfile *lf, int which, int *lenp) { struct loghead *const head = lf->head; struct logent *ent; const void *rtn; int r; /* Lock logfile */ r = pthread_mutex_lock(&lf->mutex); assert(r == 0); /* Find entry */ if (which >= 0 || which < -head->num) { r = pthread_mutex_unlock(&lf->mutex); assert(r == 0); errno = ENOENT; return (NULL); } ent = &head->ents[(head->next + head->maxent + which) % head->maxent]; /* Sanity check it */ if (ent->offset > head->maxdata || ent->length > head->maxdata || ent->offset + ent->length > head->maxdata) { r = pthread_mutex_unlock(&lf->mutex); assert(r == 0); errno = EINVAL; return (NULL); } /* Get data and length */ if (lenp != NULL) *lenp = ent->length; rtn = (u_char *)lf->head + lf->headlen + ent->offset; /* Unlock logfile */ r = pthread_mutex_unlock(&lf->mutex); assert(r == 0); /* Done */ return (rtn); } /* * Put an entry. */ int logfile_put(struct logfile *lf, const void *data, int len) { struct loghead *const head = lf->head; struct logent *ent; u_int32_t start; int wrap = 0; int r; if (len < 0) { errno = EINVAL; return (-1); } if (len > head->maxdata) { errno = EMSGSIZE; return (-1); } /* Lock logfile */ r = pthread_mutex_lock(&lf->mutex); assert(r == 0); /* Figure out where this entry's data will go */ if (head->num > 0) { ent = &head->ents[(head->next + head->maxent - 1) % head->maxent]; start = ALIGN(ent->offset + ent->length); if (start + len > head->maxdata) { /* won't fit, wrap it */ wrap = start; /* point where we were forced to wrap */ start = 0; } } else { head->next = 0; start = 0; } /* Remove all entries whose data overlaps the new guy's data */ for ( ; head->num > 0; head->num--) { ent = &head->ents[(head->next + head->maxent - head->num) % head->maxent]; if (wrap != 0) { /* clear out end region we skipped */ if (ent->offset >= wrap) continue; wrap = 0; } if (ent->offset + ent->length <= start || ent->offset >= start + len) break; } /* Save entry */ ent = &head->ents[head->next]; ent->offset = start; ent->length = len; memcpy((u_char *)lf->head + lf->headlen + ent->offset, data, len); if (head->num < head->maxent) head->num++; head->next = (head->next + 1) % head->maxent; /* Unlock logfile */ r = pthread_mutex_unlock(&lf->mutex); assert(r == 0); /* Done */ return (0); } /* * Sync logfile to disk. */ void logfile_sync(struct logfile *lf) { int r; r = pthread_mutex_lock(&lf->mutex); assert(r == 0); (void)msync(lf->head, 0, MS_SYNC); r = pthread_mutex_unlock(&lf->mutex); assert(r == 0); } #ifdef LOGFILE_TEST #include int main(int ac, char **av) { const time_t now = time(NULL); struct logfile *lf; int maxent = 0; int maxdata = 0; int readonly = 0; int alog_ents = 0; long seed = 0; int num = -1; char *path; int i; int ch; srandomdev(); while ((ch = getopt(ac, av, "s:n:ra")) != -1) { switch (ch) { case 's': seed = atol(optarg); break; case 'n': num = atol(optarg); break; case 'a': alog_ents = 1; break; case 'r': readonly = 1; break; default: goto usage; } } ac -= optind; av += optind; /* Sanity */ if (!readonly) alog_ents = 0; if (!readonly && seed == 0) { seed = random(); printf("Seed is %ld.\n", seed); } srandom(seed); switch (ac) { case 3: maxent = atoi(av[1]); maxdata = atoi(av[2]); /* fall through */ case 1: path = av[0]; break; default: usage: fprintf(stderr, "usage: logfile [-r] [-s seed] [-n num]" " path [maxent maxdata]\n"); exit(1); } /* Open log */ if ((lf = logfile_open(strcmp(path, "-") == 0 ? NULL : path, maxent, maxdata)) == NULL) err(1, "%s", path); /* Read only? */ if (readonly) goto readit; /* Write some entries into it */ printf("Logfile \"%s\" has %d entries.\n", path, logfile_num_entries(lf)); if (num == -1) num = random() % 64; printf("Writing %d entries...\n", num); for (i = 0; i < num; i++) { char buf[128]; int nex; int j; snprintf(buf, sizeof(buf), "%03ld:%03d ", now % 1000, i); nex = random() % 32; for (j = 0; j < nex; j++) { snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), "%02lx", random() % 0x100); } strlcat(buf, "\n", sizeof(buf)); if (logfile_put(lf, buf, strlen(buf) + 1) == -1) warn("logfile_put: \"%s\"", buf); } readit: num = logfile_num_entries(lf); printf("Logfile \"%s\" now has %d entries\n", path, num); printf("\t maxent=%u\n", lf->head->maxent); printf("\tmaxdata=%u\n", lf->head->maxdata); printf("\t next=%u\n", lf->head->next); for (i = -num; i < 0; i++) { const void *e; int len; printf("%4d: ", i + num); if ((e = logfile_get(lf, i, &len)) == NULL) { warn("logfile_get(%d)", i); continue; } if (alog_ents) { const struct alog_entry *const ent = e; struct tm tm; char tbuf[64]; strftime(tbuf, sizeof(tbuf), "%b %e %T", localtime_r(&ent->when, &tm)); printf("%s [%d] %s\n", tbuf, ent->sev, ent->msg); } else printf("(%2d) %s", len, (const char *)e); } printf("Closing logfile...\n"); logfile_close(&lf); return (0); } #endif /* LOGFILE_TEST */