From 5dd30d768c7cd795c2a3b974a6a1809dd8a3becf Mon Sep 17 00:00:00 2001 From: Apple Date: Tue, 30 Oct 2007 06:41:11 +0000 Subject: [PATCH] syslog-64.tar.gz --- Makefile | 2 + Makefile.postamble | 3 +- aslcommon/Makefile | 56 + aslcommon/Makefile.postamble | 1 + aslcommon/Makefile.preamble | 6 + aslcommon/PB.project | 41 + aslcommon/asl_ipc.defs | 45 + aslcommon/asl_store.c | 3892 +++++++++++++++++++++++++ aslcommon/asl_store.h | 140 + syslogd.tproj/Makefile | 6 +- syslogd.tproj/Makefile.postamble | 6 + syslogd.tproj/Makefile.preamble | 3 +- syslogd.tproj/asl.conf.5 | 120 + syslogd.tproj/asl_action.c | 192 +- syslogd.tproj/asl_in.c | 367 +-- syslogd.tproj/bsd_in.c | 77 +- syslogd.tproj/bsd_out.c | 340 ++- syslogd.tproj/com.apple.syslogd.plist | 54 +- syslogd.tproj/daemon.c | 604 +++- syslogd.tproj/daemon.h | 62 +- syslogd.tproj/dbserver.c | 654 +++++ syslogd.tproj/klog_in.c | 23 +- syslogd.tproj/syslogd.8 | 184 +- syslogd.tproj/syslogd.c | 523 +++- syslogd.tproj/syslogd.sb | 38 + syslogd.tproj/udp_in.c | 145 +- util.tproj/Makefile | 2 +- util.tproj/Makefile.preamble | 1 + util.tproj/syslog.1 | 237 +- util.tproj/syslog.c | 985 ++++--- 30 files changed, 7672 insertions(+), 1137 deletions(-) create mode 100644 aslcommon/Makefile create mode 100644 aslcommon/Makefile.postamble create mode 100644 aslcommon/Makefile.preamble create mode 100644 aslcommon/PB.project create mode 100644 aslcommon/asl_ipc.defs create mode 100644 aslcommon/asl_store.c create mode 100644 aslcommon/asl_store.h create mode 100644 syslogd.tproj/asl.conf.5 create mode 100644 syslogd.tproj/dbserver.c create mode 100644 syslogd.tproj/syslogd.sb diff --git a/Makefile b/Makefile index 75883c0..de8fc1f 100644 --- a/Makefile +++ b/Makefile @@ -14,6 +14,8 @@ PROJECT_TYPE = Aggregate TOOLS = syslogd.tproj util.tproj +LIBRARIES = aslcommon + OTHERSRCS = Makefile Makefile.preamble Makefile.postamble MAKEFILEDIR = $(MAKEFILEPATH)/pb_makefiles diff --git a/Makefile.postamble b/Makefile.postamble index fd69f0b..0a6f8b0 100644 --- a/Makefile.postamble +++ b/Makefile.postamble @@ -1 +1,2 @@ -# Empty for now +after_install: + $(RM) -rf $(DSTROOT)/usr/lib diff --git a/aslcommon/Makefile b/aslcommon/Makefile new file mode 100644 index 0000000..98cd622 --- /dev/null +++ b/aslcommon/Makefile @@ -0,0 +1,56 @@ +# +# Generated by the Apple Project Builder. +# +# NOTE: Do NOT change this file -- Project Builder maintains it. +# +# Put all of your customizations in files called Makefile.preamble +# and Makefile.postamble (both optional), and Makefile will include them. +# + +NAME = aslcommon + +PROJECTVERSION = 2.8 +PROJECT_TYPE = Library + +HFILES = asl_store.h + +CFILES = asl_store.c + +OTHERSRCS = Makefile Makefile.postamble Makefile.preamble asl_ipc.defs + + +MAKEFILEDIR = $(MAKEFILEPATH)/pb_makefiles +CURRENTLY_ACTIVE_VERSION = YES +DEPLOY_WITH_VERSION_NAME = A +CODE_GEN_STYLE = DYNAMIC +MAKEFILE = library.make +NEXTSTEP_INSTALLDIR = /usr/lib +LIBS = +DEBUG_LIBS = $(LIBS) +PROF_LIBS = $(LIBS) + + +PUBLIC_HEADERS = + +PROJECT_HEADERS = asl_ipc.defs asl_store.h asl_ipc.h + + + +WINDOWS_PUBLIC_HEADERS_DIR = LOCAL_DEVELOPER_DIR/Headers/$(NAME) + +NEXTSTEP_OBJCPLUS_COMPILER = /usr/bin/cc +WINDOWS_OBJCPLUS_COMPILER = $(DEVDIR)/gcc +PDO_UNIX_OBJCPLUS_COMPILER = $(NEXTDEV_BIN)/gcc +NEXTSTEP_JAVA_COMPILER = /usr/bin/javac +WINDOWS_JAVA_COMPILER = $(JDKBINDIR)/javac.exe +PDO_UNIX_JAVA_COMPILER = $(JDKBINDIR)/javac + +include $(MAKEFILEDIR)/platform.make + +-include Makefile.preamble + +include $(MAKEFILEDIR)/$(MAKEFILE) + +-include Makefile.postamble + +-include Makefile.dependencies diff --git a/aslcommon/Makefile.postamble b/aslcommon/Makefile.postamble new file mode 100644 index 0000000..fd69f0b --- /dev/null +++ b/aslcommon/Makefile.postamble @@ -0,0 +1 @@ +# Empty for now diff --git a/aslcommon/Makefile.preamble b/aslcommon/Makefile.preamble new file mode 100644 index 0000000..688b428 --- /dev/null +++ b/aslcommon/Makefile.preamble @@ -0,0 +1,6 @@ +LIBRARY_STYLE = STATIC +USE_AR = YES + +DEFSFILES = asl_ipc.defs +OTHER_OFILES += asl_ipcServer.o +OTHER_CFLAGS += -D__MigTypeCheck=1 diff --git a/aslcommon/PB.project b/aslcommon/PB.project new file mode 100644 index 0000000..da7a519 --- /dev/null +++ b/aslcommon/PB.project @@ -0,0 +1,41 @@ +{ + DOCICONFILES = (); + FILESTABLE = { + C_FILES = (); + H_FILES = (asl_store.h); + OTHER_LIBS = (); + OTHER_LINKED = (asl_store.c); + OTHER_SOURCES = (Makefile.preamble, Makefile, Makefile.postamble, asl_ipc.defs); + PRECOMPILED_HEADERS = (); + PROJECT_HEADERS = (); + PUBLIC_HEADERS = (); + SUBPROJECTS = (); + }; + GENERATEMAIN = YES; + LANGUAGE = English; + LOCALIZABLE_FILES = {}; + NEXTSTEP_BUILDDIR = ""; + NEXTSTEP_BUILDTOOL = /bin/make; + NEXTSTEP_COMPILEROPTIONS = ""; + NEXTSTEP_INSTALLDIR = /usr/sbin; + NEXTSTEP_JAVA_COMPILER = /usr/bin/javac; + NEXTSTEP_LINKEROPTIONS = ""; + NEXTSTEP_OBJCPLUS_COMPILER = /usr/bin/cc; + PDO_UNIX_BUILDDIR = ""; + PDO_UNIX_BUILDTOOL = /bin/make; + PDO_UNIX_COMPILEROPTIONS = ""; + PDO_UNIX_INSTALLDIR = /usr/sbin; + PDO_UNIX_JAVA_COMPILER = "$(NEXTDEV_BIN)/javac"; + PDO_UNIX_LINKEROPTIONS = ""; + PDO_UNIX_OBJCPLUS_COMPILER = "$(NEXTDEV_BIN)/gcc"; + PROJECTNAME = aslcommon; + PROJECTTYPE = Library; + PROJECTVERSION = 2.8; + WINDOWS_BUILDDIR = ""; + WINDOWS_BUILDTOOL = /bin/make; + WINDOWS_COMPILEROPTIONS = ""; + WINDOWS_INSTALLDIR = /usr/sbin; + WINDOWS_JAVA_COMPILER = "$(JDKBINDIR)/javac.exe"; + WINDOWS_LINKEROPTIONS = ""; + WINDOWS_OBJCPLUS_COMPILER = "$(DEVDIR)/gcc"; +} diff --git a/aslcommon/asl_ipc.defs b/aslcommon/asl_ipc.defs new file mode 100644 index 0000000..7d8cce9 --- /dev/null +++ b/aslcommon/asl_ipc.defs @@ -0,0 +1,45 @@ +#include +#include + +subsystem asl_ipc 1; +serverprefix _; + +import ; + +type ooline_data = ^ array [] of MACH_MSG_TYPE_BYTE + ctype : caddr_t; + +routine _asl_server_query +( + server : mach_port_t; + request : ooline_data, dealloc; + startid : uint64_t; + count : int; + flags : int; + out reply : ooline_data, dealloc; + out lastid : uint64_t; + out status : int; + SecToken token : security_token_t +); + +routine _asl_server_query_timeout +( + server : mach_port_t; + request : ooline_data, dealloc; + startid : uint64_t; + count : int; + flags : int; + WaitTime timeout: natural_t; + out reply : ooline_data, dealloc; + out lastid : uint64_t; + out status : int; + SecToken token : security_token_t +); + +routine _asl_server_prune +( + server : mach_port_t; + request : ooline_data, dealloc; + out status : int; + SecToken token : security_token_t +); diff --git a/aslcommon/asl_store.c b/aslcommon/asl_store.c new file mode 100644 index 0000000..3e0ab84 --- /dev/null +++ b/aslcommon/asl_store.c @@ -0,0 +1,3892 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define forever for(;;) + +#define FILE_MODE 0600 + +/* + * Magic Cookie for database files. + * MAXIMUM 12 CHARS! (DB_HEADER_VERS_OFFSET) + */ +#define ASL_DB_COOKIE "ASL DB" +#define ASL_DB_COOKIE_LEN 6 + +#define ASL_INDEX_NULL 0xffffffff + +#define DB_HLEN_EMPTY 0 +#define DB_HLEN_HEADER 13 +#define DB_HLEN_MESSAGE 13 +#define DB_HLEN_KVLIST 9 +#define DB_HLEN_STRING 25 +#define DB_HLEN_STRCONT 5 + +#define MSG_OFF_KEY_TYPE 0 +#define MSG_OFF_KEY_NEXT 1 +#define MSG_OFF_KEY_ID 5 +#define MSG_OFF_KEY_RUID 13 +#define MSG_OFF_KEY_RGID 17 +#define MSG_OFF_KEY_TIME 21 +#define MSG_OFF_KEY_HOST 29 +#define MSG_OFF_KEY_SENDER 37 +#define MSG_OFF_KEY_FACILITY 45 +#define MSG_OFF_KEY_LEVEL 53 +#define MSG_OFF_KEY_PID 57 +#define MSG_OFF_KEY_UID 61 +#define MSG_OFF_KEY_GID 65 +#define MSG_OFF_KEY_MSG 69 +#define MSG_OFF_KEY_FLAGS 77 + +#define mix(a, b, c) \ +{ \ + a -= b; a -= c; a ^= (c>>13); \ + b -= c; b -= a; b ^= (a<< 8); \ + c -= a; c -= b; c ^= (b>>13); \ + a -= b; a -= c; a ^= (c>>12); \ + b -= c; b -= a; b ^= (a<<16); \ + c -= a; c -= b; c ^= (b>> 5); \ + a -= b; a -= c; a ^= (c>> 3); \ + b -= c; b -= a; b ^= (a<<10); \ + c -= a; c -= b; c ^= (b>>15); \ +} + +extern time_t asl_parse_time(const char *str); +extern int asl_msg_cmp(asl_msg_t *a, asl_msg_t *b); + +#define asl_msg_list_t asl_search_result_t + +#define PMSG_SEL_TIME 0x0001 +#define PMSG_SEL_HOST 0x0002 +#define PMSG_SEL_SENDER 0x0004 +#define PMSG_SEL_FACILITY 0x0008 +#define PMSG_SEL_MESSAGE 0x0010 +#define PMSG_SEL_LEVEL 0x0020 +#define PMSG_SEL_PID 0x0040 +#define PMSG_SEL_UID 0x0080 +#define PMSG_SEL_GID 0x0100 +#define PMSG_SEL_RUID 0x0200 +#define PMSG_SEL_RGID 0x0400 + +#define PMSG_FETCH_ALL 0 +#define PMSG_FETCH_STD 1 +#define PMSG_FETCH_KV 2 + +#define Q_NULL 100001 +#define Q_FAST 100002 +#define Q_SLOW 100003 +#define Q_FAIL 100004 + +#define ARCHIVE_DELETE_VS_COPY_PERCENT 5 + +typedef struct +{ + uint16_t kselect; + uint16_t vselect; + uint64_t msgid; + uint64_t time; + uint64_t host; + uint64_t sender; + uint64_t facility; + uint64_t message; + uint32_t level; + uint32_t pid; + int32_t uid; + int32_t gid; + int32_t ruid; + int32_t rgid; + uint32_t next; + uint32_t kvcount; + uint64_t *kvlist; +} pmsg_t; + +static uint64_t +_asl_htonq(uint64_t n) +{ +#ifdef __BIG_ENDIAN__ + return n; +#else + u_int32_t t; + union + { + u_int64_t q; + u_int32_t l[2]; + } x; + + x.q = n; + t = x.l[0]; + x.l[0] = htonl(x.l[1]); + x.l[1] = htonl(t); + + return x.q; +#endif +} + +static uint64_t +_asl_ntohq(uint64_t n) +{ +#ifdef __BIG_ENDIAN__ + return n; +#else + u_int32_t t; + union + { + u_int64_t q; + u_int32_t l[2]; + } x; + + x.q = n; + t = x.l[0]; + x.l[0] = ntohl(x.l[1]); + x.l[1] = ntohl(t); + + return x.q; +#endif +} + +static uint16_t +_asl_get_16(char *h) +{ + uint16_t x; + + memcpy(&x, h, 2); + return ntohs(x); +} + +static void +_asl_put_16(uint16_t i, char *h) +{ + uint16_t x; + + x = htons(i); + memcpy(h, &x, 2); +} + +static uint32_t +_asl_get_32(char *h) +{ + uint32_t x; + + memcpy(&x, h, 4); + return ntohl(x); +} + +static void +_asl_put_32(uint32_t i, char *h) +{ + uint32_t x; + + x = htonl(i); + memcpy(h, &x, 4); +} + +static uint64_t +_asl_get_64(char *h) +{ + uint64_t x; + + memcpy(&x, h, 8); + return _asl_ntohq(x); +} + +static void +_asl_put_64(uint64_t i, char *h) +{ + uint64_t x; + + x = _asl_htonq(i); + memcpy(h, &x, 8); +} + +#define header_get_next(h) _asl_get_32(h + 1) +#define header_get_id(h) _asl_get_64(h + 5) +#define header_get_refcount(h) _asl_get_32(h + 13) +#define header_get_hash(h) _asl_get_32(h + 17) + +#define header_put_next(i, h) _asl_put_32(i, h + 1) +#define header_put_id(i, h) _asl_put_64(i, h + 5) +#define header_put_refcount(i, h) _asl_put_32(i, h + 13) +#define header_put_hash(i, h) _asl_put_32(i, h + 17) + +/* + * callback for sorting slotlist + * primary sort is by xid + * secondary sort is by slot, which happens when xid is 0 + * this allows us to quickly find xids (using binary search on the xid key) + * it's also used to find slots quickly from record_chain_free() + */ +static int +slot_comp(const void *a, const void *b) +{ + slot_info_t *ai, *bi; + + if (a == NULL) + { + if (b == NULL) return 0; + return -1; + } + + if (b == NULL) return 1; + + ai = (slot_info_t *)a; + bi = (slot_info_t *)b; + + if (ai->xid < bi->xid) return -1; + + if (ai->xid == bi->xid) + { + if (ai->slot < bi->slot) return -1; + if (ai->slot == bi->slot) return 0; + return 1; + } + + return 1; +} + +/* find a slot (with xid 0) in the slot list */ +static uint32_t +slotlist_find_xid0_slot(asl_store_t *s, uint32_t slot) +{ + uint32_t top, bot, mid, range; + + if (s == NULL) return ASL_INDEX_NULL; + if (s->slot_zero_count == 0) return ASL_INDEX_NULL; + + top = s->slot_zero_count - 1; + bot = 0; + mid = top / 2; + + range = top - bot; + while (range > 1) + { + if (slot == s->slotlist[mid].slot) return mid; + else if (slot < s->slotlist[mid].slot) top = mid; + else bot = mid; + + range = top - bot; + mid = bot + (range / 2); + } + + if (slot == s->slotlist[top].slot) return top; + if (slot == s->slotlist[bot].slot) return bot; + + return ASL_INDEX_NULL; +} + +/* find an xid in the slot list */ +static uint32_t +slotlist_find(asl_store_t *s, uint64_t xid, uint32_t slot, int32_t direction) +{ + uint32_t top, bot, mid, range; + + if (s == NULL) return ASL_INDEX_NULL; + if (s->slotlist_count == 0) return ASL_INDEX_NULL; + + /* special case for xid 0: binary search for slot */ + if (xid == 0) return slotlist_find_xid0_slot(s, slot); + + top = s->slotlist_count - 1; + bot = 0; + mid = top / 2; + + range = top - bot; + while (range > 1) + { + if (xid == s->slotlist[mid].xid) return mid; + else if (xid < s->slotlist[mid].xid) top = mid; + else bot = mid; + + range = top - bot; + mid = bot + (range / 2); + } + + if (xid == s->slotlist[top].xid) return top; + if (xid == s->slotlist[bot].xid) return bot; + + if (direction == 0) return ASL_INDEX_NULL; + if (direction < 0) return bot; + return top; +} + +static uint32_t +slotlist_insert(asl_store_t *s, uint8_t type, uint32_t slot, uint64_t xid, uint32_t hash) +{ + int i, j, k; + + if (s == NULL) return ASL_STATUS_INVALID_STORE; + if (s->slotlist_count == 0) + { + s->slotlist = (slot_info_t *)calloc(1, sizeof(slot_info_t)); + if (s->slotlist == NULL) return ASL_STATUS_NO_MEMORY; + + s->slotlist[0].type = type; + s->slotlist[0].slot = slot; + s->slotlist[0].xid = xid; + s->slotlist[0].hash = hash; + s->slotlist_count = 1; + if (xid == 0) s->slot_zero_count = 1; + return ASL_STATUS_OK; + } + + s->slotlist = (slot_info_t *)reallocf(s->slotlist, (s->slotlist_count + 1) * sizeof(slot_info_t)); + if (s->slotlist == NULL) + { + s->slotlist_count = 0; + s->slot_zero_count = 0; + return ASL_STATUS_NO_MEMORY; + } + + /* + * slotlist is sorted in increasing order by xid + * there may be multiple xid 0 entries (empty slots) which are further sorted by slot + */ + if (xid == 0) + { + /* update empty count */ + s->slot_zero_count++; + + for (i = 0; (i < s->slotlist_count) && (s->slotlist[i].xid == 0); i++) + { + if (slot <= s->slotlist[i].slot) break; + } + } + else + { + i = s->slotlist_count - 1; + if (xid > s->slotlist[i].xid) + { + /* append XID at end of slotlist */ + i++; + } + else + { + /* usually we are adding records, so it's likely that the new xid will be large */ + for (i = s->slotlist_count; i > 0; i--) + { + if (xid > s->slotlist[i - 1].xid) break; + } + } + } + + for (j = s->slotlist_count; j > i; j--) + { + k = j - 1; + s->slotlist[j].type = s->slotlist[k].type; + s->slotlist[j].slot = s->slotlist[k].slot; + s->slotlist[j].xid = s->slotlist[k].xid; + s->slotlist[j].hash = s->slotlist[k].hash; + } + + s->slotlist[i].type = type; + s->slotlist[i].slot = slot; + s->slotlist[i].xid = xid; + s->slotlist[i].hash = hash; + s->slotlist_count++; + + return ASL_STATUS_OK; +} + +static uint32_t +slotlist_delete(asl_store_t *s, uint32_t where) +{ + uint32_t i, j, n; + + if (s->slotlist_count == 0) return ASL_STATUS_OK; + + n = s->slotlist_count - 1; + if (n == 0) + { + free(s->slotlist); + s->slotlist = NULL; + s->slotlist_count = 0; + s->slot_zero_count = 0; + return ASL_STATUS_OK; + } + + if (s->slotlist[where].xid == 0) s->slot_zero_count--; + + for (i = where, j = i + 1; i < n; i++, j++) + { + s->slotlist[i].type = s->slotlist[j].type; + s->slotlist[i].slot = s->slotlist[j].slot; + s->slotlist[i].xid = s->slotlist[j].xid; + s->slotlist[i].hash = s->slotlist[j].hash; + } + + s->slotlist_count = n; + s->slotlist = (slot_info_t *)reallocf(s->slotlist, s->slotlist_count * sizeof(slot_info_t)); + + if (s->slotlist == NULL) + { + s->slotlist_count = 0; + s->slot_zero_count = 0; + return ASL_STATUS_NO_MEMORY; + } + + return ASL_STATUS_OK; +} + +static uint32_t +slotlist_make_empty(asl_store_t *s, uint32_t where) +{ + uint32_t i, j, slot; + + if (s->slotlist_count == 0) return ASL_STATUS_OK; + if (where > s->slotlist_count) return ASL_STATUS_OK; + + /* + * Special case for asl_store_archive. + * Since we expect to be doing lots of deletions during an archive call, + * this routine only marks the type as empty. + * asl_store_archive cleans up the slotlist when it is finished. + */ + if (s->flags & ASL_STORE_FLAG_DEFER_SORT) + { + s->slotlist[where].type = DB_TYPE_EMPTY; + s->slotlist[where].hash = 0; + + s->empty_count++; + + return ASL_STATUS_OK; + } + + slot = s->slotlist[where].slot; + + /* primary sort by xid */ + for (i = where, j = where - 1; (i > 0) && (s->slotlist[j].xid != 0); i--, j--) + { + s->slotlist[i].type = s->slotlist[j].type; + s->slotlist[i].slot = s->slotlist[j].slot; + s->slotlist[i].xid = s->slotlist[j].xid; + s->slotlist[i].hash = s->slotlist[j].hash; + } + + /* xid 0 entries sorted by slot */ + for (j = i - 1; (i > 0) && (s->slotlist[j].slot > slot); i--, j--) + { + s->slotlist[i].type = s->slotlist[j].type; + s->slotlist[i].slot = s->slotlist[j].slot; + s->slotlist[i].xid = s->slotlist[j].xid; + s->slotlist[i].hash = s->slotlist[j].hash; + } + + s->slotlist[i].type = DB_TYPE_EMPTY; + s->slotlist[i].slot = slot; + s->slotlist[i].xid = 0; + s->slotlist[i].hash = 0; + + s->empty_count++; + + /* new xid=0 count */ + for (s->slot_zero_count = 0; (s->slot_zero_count < s->slotlist_count) && (s->slotlist[s->slot_zero_count].xid == 0); s->slot_zero_count++); + + return ASL_STATUS_OK; +} + +static uint32_t +slotlist_init(asl_store_t *s) +{ + uint32_t i, si, status, hash, addslot; + uint64_t xid, tick; + uint8_t t; + char tmp[DB_RECORD_LEN]; + + s->empty_count = 0; + + /* Start at first slot after the header */ + status = fseek(s->db, DB_RECORD_LEN, SEEK_SET); + if (status != 0) return ASL_STATUS_READ_FAILED; + + s->slotlist = (slot_info_t *)calloc(s->record_count, sizeof(slot_info_t)); + if (s->slotlist == NULL) return ASL_STATUS_NO_MEMORY; + + si = 0; + + for (i = 1; i < s->record_count; i++) + { + status = fread(tmp, DB_RECORD_LEN, 1, s->db); + if (status != 1) return ASL_STATUS_READ_FAILED; + + t = tmp[0]; + addslot = 0; + xid = 0; + hash = 0; + + if (t == DB_TYPE_EMPTY) + { + addslot = 1; + s->empty_count++; + } + + if (t == DB_TYPE_STRING) + { + addslot = 1; + s->string_count++; + xid = header_get_id(tmp); + hash = header_get_hash(tmp); + } + + if (t == DB_TYPE_MESSAGE) + { + addslot = 1; + s->message_count++; + xid = header_get_id(tmp); + tick = _asl_get_64(tmp + MSG_OFF_KEY_TIME); + if (tick > s->max_time) s->max_time = tick; + } + + if (addslot == 1) + { + s->slotlist[si].type = t; + s->slotlist[si].slot = i; + s->slotlist[si].xid = xid; + s->slotlist[si].hash = hash; + si++; + } + } + + s->slotlist = (slot_info_t *)reallocf(s->slotlist, si * sizeof(slot_info_t)); + if (s->slotlist == NULL) return ASL_STATUS_NO_MEMORY; + s->slotlist_count = si; + + /* slotlist is sorted by xid */ + qsort((void *)s->slotlist, s->slotlist_count, sizeof(slot_info_t), slot_comp); + + /* new xid=0 count */ + for (s->slot_zero_count = 0; (s->slot_zero_count < s->slotlist_count) && (s->slotlist[s->slot_zero_count].xid == 0); s->slot_zero_count++); + + return ASL_STATUS_OK; +} + +uint32_t +asl_store_open(const char *path, uint32_t flags, asl_store_t **out) +{ + asl_store_t *s; + struct stat sb; + int status, i, j, fd; + char cbuf[DB_RECORD_LEN]; + off_t fsize; + uint64_t next; + + memset(&sb, 0, sizeof(struct stat)); + status = stat(path, &sb); + + fsize = 0; + + if (status < 0) + { + if (errno != ENOENT) return ASL_STATUS_FAILED; + + fd = open(path, O_RDWR | O_CREAT | O_EXCL, FILE_MODE); + if (fd < 0) return ASL_STATUS_FAILED; + + memset(cbuf, 0, DB_RECORD_LEN); + memcpy(cbuf, ASL_DB_COOKIE, ASL_DB_COOKIE_LEN); + + _asl_put_32(DB_VERSION, cbuf + DB_HEADER_VERS_OFFSET); + + /* record IDs start at 1 */ + _asl_put_64(1, cbuf + DB_HEADER_MAXID_OFFSET); + + status = write(fd, cbuf, DB_RECORD_LEN); + close(fd); + if (status != DB_RECORD_LEN) return ASL_STATUS_FAILED; + + fsize = DB_RECORD_LEN; + } + else + { + fsize = sb.st_size; + } + + s = (asl_store_t *)calloc(1, sizeof(asl_store_t)); + if (s == NULL) return ASL_STATUS_NO_MEMORY; + + s->flags = flags; + + for (i = 0; i < RECORD_CACHE_SIZE; i++) + { + s->rcache[i] = malloc(DB_RECORD_LEN); + if (s->rcache[i] == NULL) + { + for (j = 0; j < i; j++) free(s->rcache[j]); + free(s); + return ASL_STATUS_NO_MEMORY; + } + } + + s->db = NULL; + if (flags & ASL_STORE_FLAG_READ_ONLY) s->db = fopen(path, "r"); + else s->db = fopen(path, "r+"); + if (s->db == NULL) + { + free(s); + return ASL_STATUS_INVALID_STORE; + } + + memset(cbuf, 0, DB_RECORD_LEN); + status = fread(cbuf, DB_RECORD_LEN, 1, s->db); + if (status != 1) + { + fclose(s->db); + free(s); + return ASL_STATUS_READ_FAILED; + } + + /* Check the database Magic Cookie */ + if (strncmp(cbuf, ASL_DB_COOKIE, ASL_DB_COOKIE_LEN)) + { + fclose(s->db); + free(s); + return ASL_STATUS_INVALID_STORE; + } + + next = 0; + memcpy(&next, cbuf + DB_HEADER_MAXID_OFFSET, 8); + s->next_id = _asl_ntohq(next); + + s->record_count = fsize / DB_RECORD_LEN; + + status = slotlist_init(s); + + for (i = 0; i < STRING_CACHE_SIZE; i++) + { + s->string_cache[i].index = ASL_INDEX_NULL; + s->string_cache[i].refcount = 0; + s->string_cache[i].str = NULL; + } + + s->db_path = strdup(path); + + *out = s; + return ASL_STATUS_OK; +} + +uint32_t +asl_store_close(asl_store_t *s) +{ + uint32_t i; + + if (s == NULL) return ASL_STATUS_INVALID_STORE; + + if (s->slotlist != NULL) free(s->slotlist); + for (i = 0; i < RECORD_CACHE_SIZE; i++) free(s->rcache[i]); + for (i = 0; i < STRING_CACHE_SIZE; i++) + { + if (s->string_cache[i].str != NULL) free(s->string_cache[i].str); + } + + if (s->db_path != NULL) free(s->db_path); + if (s->db != NULL) fclose(s->db); + free(s); + + return ASL_STATUS_OK; +} + +static char * +record_buffer_alloc(asl_store_t *s) +{ + uint32_t i; + + if (s == NULL) return calloc(1, DB_RECORD_LEN); + + for (i = 0; i < RECORD_CACHE_SIZE; i++) + { + if (s->rcache_state[i] == 0) + { + s->rcache_state[i] = 1; + memset(s->rcache[i], 0, DB_RECORD_LEN); + return s->rcache[i]; + } + } + + return calloc(1, DB_RECORD_LEN); +} + +static void +record_buffer_free(asl_store_t *s, char *p) +{ + uint32_t i; + + if (s == NULL) return free(p); + + for (i = 0; i < RECORD_CACHE_SIZE; i++) + { + if (s->rcache[i] == p) + { + s->rcache_state[i] = 0; + return; + } + } + + free(p); +} + +/* + * Finds a free (DB_TYPE_EMPTY) record slot. + * Returns the index of the free entry in the slotlist. + * Returns ASL_INDEX_NULL if no free slots are available (next write should be at end of file). + */ +static uint32_t +get_free_slot(asl_store_t *s) +{ + uint32_t i; + + if (s == NULL) return ASL_INDEX_NULL; + + if (s->empty_count == 0) return ASL_INDEX_NULL; + + for (i = 0; i < s->slotlist_count; i++) + { + if (s->slotlist[i].type == DB_TYPE_EMPTY) + { + s->empty_count--; + return i; + } + } + + /* impossible */ + s->empty_count = 0; + return ASL_INDEX_NULL; +} + +static void +record_list_free(asl_store_t *s, char **list) +{ + uint32_t i; + + if (list == NULL) return; + + for (i = 0; list[i] != NULL; i++) record_buffer_free(s, list[i]); + free(list); +} + +static uint64_t +new_id(asl_store_t *s) +{ + int status; + uint64_t n, out; + + if (s == NULL) return ASL_REF_NULL; + + status = fseek(s->db, DB_HEADER_MAXID_OFFSET, SEEK_SET); + if (status < 0) return ASL_REF_NULL; + + out = s->next_id; + s->next_id++; + + n = _asl_htonq(s->next_id); + status = fwrite(&n, 8, 1, s->db); + if (status != 1) return ASL_REF_NULL; + return out; +} + +/* + * Write each record in the list to a slot in the database. + * Fills in "next" index. + */ +static uint32_t +save_record_list(asl_store_t *s, char **list, uint32_t *start) +{ + uint32_t i, n, status, si, slot, next, rcount, hash; + uint8_t type; + off_t offset; + + if (s == NULL) return ASL_STATUS_INVALID_STORE; + + if (list == NULL) return ASL_STATUS_OK; + + for (n = 0; list[n] != NULL; n++); + + if (n == 0) return ASL_STATUS_OK; + + rcount = s->record_count; + si = get_free_slot(s); + + /* update slotlist */ + type = list[0][0]; + + if (type == DB_TYPE_STRING) s->string_count++; + if (type == DB_TYPE_MESSAGE) s->message_count++; + + next = ASL_INDEX_NULL; + if (si == ASL_INDEX_NULL) + { + next = s->record_count; + } + else if (si > s->slotlist_count) + { + return ASL_STATUS_FAILED; + } + else + { + next = s->slotlist[si].slot; + slotlist_delete(s, si); + } + + hash = 0; + if (type == DB_TYPE_STRING) hash = header_get_hash(list[0]); + + status = slotlist_insert(s, type, next, header_get_id(list[0]), hash); + if (status != ASL_STATUS_OK) return status; + + *start = next; + + for (i = 0; i < n; i++) + { + slot = next; + + next = 0; + if ((i + 1) < n) + { + si = get_free_slot(s); + if (si == ASL_INDEX_NULL) + { + next = s->record_count + 1; + } + else if (next > s->slotlist_count) + { + return ASL_STATUS_FAILED; + } + else + { + type = list[i + 1][0]; + next = s->slotlist[si].slot; + slotlist_delete(s, si); + } + } + + offset = slot * DB_RECORD_LEN; + status = fseek(s->db, offset, SEEK_SET); + if (status < 0) return ASL_STATUS_WRITE_FAILED; + + header_put_next(next, list[i]); + + status = fwrite(list[i], DB_RECORD_LEN, 1, s->db); + if (status != 1) return ASL_STATUS_WRITE_FAILED; + + if (si == ASL_INDEX_NULL) s->record_count++; + } + + fflush(s->db); + + return ASL_STATUS_OK; +} + +/* + * Converts a string into a NULL-terminated list of records. + * Sets sid to new string ID. + */ +static uint32_t +string_encode(asl_store_t *s, uint32_t hash, const char *str, uint64_t *sid, char ***list) +{ + char **outlist, *p; + const char *t; + uint32_t length, remaining, i, n, x; + + if (s == NULL) return ASL_STATUS_INVALID_STORE; + if (str == NULL) return ASL_STATUS_INVALID_STRING; + + *sid = new_id(s); + if (*sid == ASL_REF_NULL) return ASL_STATUS_FAILED; + + length = strlen(str) + 1; + remaining = length; + + x = DB_RECORD_LEN - DB_HLEN_STRING; + if (remaining < x) x = remaining; + remaining -= x; + n = 1; + + x = DB_RECORD_LEN - DB_HLEN_STRCONT; + n += (remaining + x - 1) / x; + + outlist = (char **)calloc(n + 1, sizeof(char *)); + if (outlist == NULL) return ASL_STATUS_NO_MEMORY; + + for (i = 0; i < n; i++) + { + outlist[i] = record_buffer_alloc(s); + if (outlist[i] == NULL) + { + n = i; + for (i = 0; i < n; i++) record_buffer_free(s, outlist[i]); + free(outlist); + return ASL_STATUS_NO_MEMORY; + } + } + + *list = outlist; + + outlist[0][0] = (char)DB_TYPE_STRING; + p = outlist[0] + 5; + + /* sid */ + _asl_put_64(*sid, p); + p += 8; + + /* refcount */ + _asl_put_32(1, p); + p += 4; + + /* hash */ + _asl_put_32(hash, p); + p += 4; + + /* string length (includes trailing nul) */ + _asl_put_32(length, p); + p += 4; + + t = str; + remaining = length; + + x = DB_RECORD_LEN - DB_HLEN_STRING; + if (remaining < x) x = remaining; + memcpy(p, t, x); + + t += x; + remaining -= x; + + x = DB_RECORD_LEN - DB_HLEN_STRCONT; + for (i = 1; i < n; i++) + { + outlist[i][0] = (char)DB_TYPE_STRCONT; + p = outlist[i] + 5; + + if (remaining < x) x = remaining; + memcpy(p, t, x); + + t += x; + remaining -= x; + } + + return ASL_STATUS_OK; +} + +/* + * Hash is used to improve string search. + */ +uint32_t +string_hash(const char *s, uint32_t inlen) +{ + uint32_t a, b, c, l, len; + + if (s == NULL) return 0; + + l = inlen; + + len = l; + a = b = 0x9e3779b9; + c = 0; + + while (len >= 12) + { + a += (s[0] + ((uint32_t)s[1]<<8) + ((uint32_t)s[ 2]<<16) + ((uint32_t)s[ 3]<<24)); + b += (s[4] + ((uint32_t)s[5]<<8) + ((uint32_t)s[ 6]<<16) + ((uint32_t)s[ 7]<<24)); + c += (s[8] + ((uint32_t)s[9]<<8) + ((uint32_t)s[10]<<16) + ((uint32_t)s[11]<<24)); + + mix(a, b, c); + + s += 12; + len -= 12; + } + + c += l; + switch(len) + { + case 11: c += ((uint32_t)s[10]<<24); + case 10: c += ((uint32_t)s[9]<<16); + case 9 : c += ((uint32_t)s[8]<<8); + + case 8 : b += ((uint32_t)s[7]<<24); + case 7 : b += ((uint32_t)s[6]<<16); + case 6 : b += ((uint32_t)s[5]<<8); + case 5 : b += s[4]; + + case 4 : a += ((uint32_t)s[3]<<24); + case 3 : a += ((uint32_t)s[2]<<16); + case 2 : a += ((uint32_t)s[1]<<8); + case 1 : a += s[0]; + } + + mix(a, b, c); + + return c; +} + +/* + * Write refcount to database and update string cache. + */ +static void +string_set_refcount(asl_store_t *s, uint32_t index, const char *str, uint32_t refcount) +{ + uint32_t slot, i, min, status, v32; + off_t offset; + + if (s == NULL) return; + + /* update the database */ + slot = s->slotlist[index].slot; + + offset = (slot * DB_RECORD_LEN) + 13; + status = fseek(s->db, offset, SEEK_SET); + + if (status < 0) return; + + v32 = htonl(refcount); + status = fwrite(&v32, 4, 1, s->db); + + min = 0; + + /* if the string is in the string cache, update the refcount there */ + for (i = 0; i < STRING_CACHE_SIZE; i++) + { + if (s->string_cache[i].index == index) + { + s->string_cache[i].refcount = refcount; + return; + } + + /* locate the minimum refcount while we're looping */ + if (s->string_cache[i].refcount < s->string_cache[min].refcount) min = i; + } + + /* bail out if the refcount is too low */ + if (s->string_cache[min].refcount > refcount) return; + + /* replace the current minimum */ + if (s->string_cache[min].str != NULL) free(s->string_cache[min].str); + + s->string_cache[min].index = index; + s->string_cache[min].refcount = refcount; + s->string_cache[min].str = strdup(str); + + if (s->string_cache[min].str == NULL) + { + s->string_cache[min].index = ASL_INDEX_NULL; + s->string_cache[min].refcount = 0; + return; + } +} + +static uint32_t +string_fetch_slot(asl_store_t *s, uint32_t slot, char **out, uint32_t *refcount) +{ + off_t offset; + uint8_t type; + uint32_t status, next, len, x, remaining; + char *outstr, *p, tmp[DB_RECORD_LEN]; + + if (s == NULL) return ASL_STATUS_INVALID_STORE; + if (out == NULL) return ASL_STATUS_INVALID_ARG; + + *out = NULL; + offset = slot * DB_RECORD_LEN; + status = fseek(s->db, offset, SEEK_SET); + + if (status < 0) return ASL_STATUS_READ_FAILED; + + status = fread(tmp, DB_RECORD_LEN, 1, s->db); + if (status != 1) return ASL_STATUS_READ_FAILED; + + type = tmp[0]; + if (type != DB_TYPE_STRING) return ASL_STATUS_INVALID_STRING; + + len = _asl_get_32(tmp + 21); + if (len == 0) return ASL_STATUS_OK; + + *refcount = _asl_get_32(tmp + 13); + + next = header_get_next(tmp); + + outstr = calloc(1, len); + if (outstr == NULL) return ASL_STATUS_NO_MEMORY; + + p = outstr; + remaining = len; + + x = DB_RECORD_LEN - DB_HLEN_STRING; + if (x > remaining) x = remaining; + + memcpy(p, tmp + DB_HLEN_STRING, x); + p += x; + remaining -= x; + + while ((next != 0) && (remaining > 0)) + { + offset = next * DB_RECORD_LEN; + status = fseek(s->db, offset, SEEK_SET); + + if (status < 0) + { + free(outstr); + return ASL_STATUS_READ_FAILED; + } + + status = fread(tmp, DB_RECORD_LEN, 1, s->db); + if (status != 1) + { + free(outstr); + return ASL_STATUS_READ_FAILED; + } + + next = header_get_next(tmp); + + x = DB_RECORD_LEN - DB_HLEN_STRCONT; + if (x > remaining) x = remaining; + + memcpy(p, tmp + DB_HLEN_STRCONT, x); + p += x; + remaining -= x; + } + + if ((next != 0) || (remaining != 0)) + { + free(outstr); + return ASL_STATUS_READ_FAILED; + } + + *out = outstr; + return ASL_STATUS_OK; +} + +static uint32_t +string_fetch_sid(asl_store_t *s, uint64_t sid, char **out) +{ + uint32_t i, len, ref; + uint64_t nsid; + uint8_t inls; + char *p; + + if (s == NULL) return ASL_STATUS_INVALID_STORE; + if (out == NULL) return ASL_STATUS_INVALID_ARG; + + *out = NULL; + if (sid == ASL_REF_NULL) return ASL_STATUS_OK; + + ref = 0; + + inls = 0; + nsid = _asl_htonq(sid); + memcpy(&inls, &nsid, 1); + if (inls & 0x80) + { + /* inline string */ + inls &= 0x0f; + len = inls; + *out = calloc(1, len); + if (*out == NULL) return ASL_STATUS_NO_MEMORY; + p = 1 + (char *)&nsid; + memcpy(*out, p, len); + return ASL_STATUS_OK; + } + + /* Find the string in the database */ + i = slotlist_find(s, sid, 0, 0); + if (i == ASL_INDEX_NULL) return ASL_STATUS_NOT_FOUND; + + return string_fetch_slot(s, s->slotlist[i].slot, out, &ref); +} + +static uint32_t +check_user_access(int32_t msgu, int32_t u) +{ + /* -1 means anyone may read */ + if (msgu == -1) return ASL_STATUS_OK; + + /* Check for exact match */ + if (msgu == u) return ASL_STATUS_OK; + + return ASL_STATUS_ACCESS_DENIED; +} + +static uint32_t +check_group_access(int32_t msgg, int32_t u, int32_t g) +{ + int check; + uuid_t uu, gu; + + /* -1 means anyone may read */ + if (msgg == -1) return ASL_STATUS_OK; + + /* Check for exact match */ + if (msgg == g) return ASL_STATUS_OK; + + /* Check if user (u) is in read group (msgg) */ + mbr_uid_to_uuid(u, uu); + mbr_gid_to_uuid(msgg, gu); + + check = 0; + mbr_check_membership(uu, gu, &check); + if (check != 0) return ASL_STATUS_OK; + + return ASL_STATUS_ACCESS_DENIED; +} + +static uint32_t +check_access(int32_t msgu, int32_t msgg, int32_t u, int32_t g, uint16_t flags) +{ + uint16_t uset, gset; + + /* root (uid 0) may always read */ + if (u == 0) return ASL_STATUS_OK; + + uset = flags & ASL_MSG_FLAG_READ_UID_SET; + gset = flags & ASL_MSG_FLAG_READ_GID_SET; + + /* if no access controls are set, anyone may read */ + if ((uset | gset) == 0) return ASL_STATUS_OK; + + /* if only uid is set, then access is only by uid match */ + if ((uset != 0) && (gset == 0)) return check_user_access(msgu, u); + + /* if only gid is set, then access is only by gid match */ + if ((uset == 0) && (gset != 0)) return check_group_access(msgg, u, g); + + /* both uid and gid are set - check user, then group */ + if ((check_user_access(msgu, u)) == ASL_STATUS_OK) return ASL_STATUS_OK; + return check_group_access(msgg, u, g); +} + +static uint32_t +pmsg_fetch(asl_store_t *s, uint32_t slot, int32_t ruid, int32_t rgid, uint32_t action, pmsg_t **pmsg) +{ + off_t offset; + uint32_t status, i, n, v32, next; + int32_t msgu, msgg; + uint64_t msgid; + uint16_t flags; + pmsg_t *out; + char *p, tmp[DB_RECORD_LEN]; + + if (s == NULL) return ASL_STATUS_INVALID_STORE; + if (pmsg == NULL) return ASL_STATUS_INVALID_ARG; + + out = NULL; + + if ((action == PMSG_FETCH_ALL) || (action == PMSG_FETCH_STD)) + { + *pmsg = NULL; + + offset = slot * DB_RECORD_LEN; + status = fseek(s->db, offset, SEEK_SET); + + if (status < 0) return ASL_STATUS_READ_FAILED; + + status = fread(tmp, DB_RECORD_LEN, 1, s->db); + if (status != 1) return ASL_STATUS_READ_FAILED; + + msgid = _asl_get_64(tmp + MSG_OFF_KEY_ID); + msgu = _asl_get_32(tmp + MSG_OFF_KEY_RUID); + msgg = _asl_get_32(tmp + MSG_OFF_KEY_RGID); + flags = _asl_get_16(tmp + MSG_OFF_KEY_FLAGS); + + status = check_access(msgu, msgg, ruid, rgid, flags); + if (status != ASL_STATUS_OK) return status; + + out = (pmsg_t *)calloc(1, sizeof(pmsg_t)); + if (out == NULL) return ASL_STATUS_NO_MEMORY; + + + p = tmp + 21; + + /* ID */ + out->msgid = msgid; + + /* ReadUID */ + out->ruid = msgu; + + /* ReadGID */ + out->rgid = msgg; + + /* Time */ + out->time = _asl_get_64(p); + p += 8; + + /* Host */ + out->host = _asl_get_64(p); + p += 8; + + /* Sender */ + out->sender = _asl_get_64(p); + p += 8; + + /* Facility */ + out->facility = _asl_get_64(p); + p += 8; + + /* Level */ + out->level = _asl_get_32(p); + p += 4; + + /* PID */ + out->pid = _asl_get_32(p); + p += 4; + + /* UID */ + out->uid = _asl_get_32(p); + p += 4; + + /* GID */ + out->gid = _asl_get_32(p); + p += 4; + + /* Message */ + out->message = _asl_get_64(p); + p += 8; + + next = header_get_next(tmp); + out->next = next; + + if (action == PMSG_FETCH_STD) + { + /* caller only wants "standard" keys */ + *pmsg = out; + return ASL_STATUS_OK; + } + + *pmsg = out; + } + else + { + out = *pmsg; + } + + n = 0; + next = out->next; + + while (next != 0) + { + offset = next * DB_RECORD_LEN; + status = fseek(s->db, offset, SEEK_SET); + if (status < 0) + { + *pmsg = NULL; + free(out); + return ASL_STATUS_READ_FAILED; + } + + status = fread(tmp, DB_RECORD_LEN, 1, s->db); + if (status != 1) + { + *pmsg = NULL; + free(out); + return ASL_STATUS_READ_FAILED; + } + + if (out->kvcount == 0) + { + v32 = _asl_get_32(tmp + 5); + out->kvcount = v32 * 2; + out->kvlist = (uint64_t *)calloc(out->kvcount, sizeof(uint64_t)); + if (out->kvlist == NULL) + { + *pmsg = NULL; + free(out); + return ASL_STATUS_NO_MEMORY; + } + } + + p = tmp + 9; + + for (i = 0; (i < 4) && (n < out->kvcount); i++) + { + out->kvlist[n++] = _asl_get_64(p); + p += 8; + + out->kvlist[n++] = _asl_get_64(p); + p += 8; + } + + next = header_get_next(tmp); + } + + return ASL_STATUS_OK; +} + +static uint32_t +pmsg_match(asl_store_t *s, pmsg_t *q, pmsg_t *m) +{ + uint32_t i, j; + + if (s == NULL) return 0; + if (q == NULL) return 1; + if (m == NULL) return 0; + + if (q->kselect & PMSG_SEL_TIME) + { + if (q->time == ASL_REF_NULL) return 0; + if ((q->vselect & PMSG_SEL_TIME) && (q->time != m->time)) return 0; + } + + if (q->kselect & PMSG_SEL_HOST) + { + if (q->host == ASL_REF_NULL) return 0; + if ((q->vselect & PMSG_SEL_HOST) && (q->host != m->host)) return 0; + } + + if (q->kselect & PMSG_SEL_SENDER) + { + if (q->sender == ASL_REF_NULL) return 0; + if ((q->vselect & PMSG_SEL_SENDER) && (q->sender != m->sender)) return 0; + } + + if (q->kselect & PMSG_SEL_FACILITY) + { + if (q->facility == ASL_REF_NULL) return 0; + if ((q->vselect & PMSG_SEL_FACILITY) && (q->facility != m->facility)) return 0; + } + + if (q->kselect & PMSG_SEL_MESSAGE) + { + if (q->message == ASL_REF_NULL) return 0; + if ((q->vselect & PMSG_SEL_MESSAGE) && (q->message != m->message)) return 0; + } + + if (q->kselect & PMSG_SEL_LEVEL) + { + if (q->level == ASL_INDEX_NULL) return 0; + if ((q->vselect & PMSG_SEL_LEVEL) && (q->level != m->level)) return 0; + } + + if (q->kselect & PMSG_SEL_PID) + { + if (q->pid == -1) return 0; + if ((q->vselect & PMSG_SEL_PID) && (q->pid != m->pid)) return 0; + } + + if (q->kselect & PMSG_SEL_UID) + { + if (q->uid == -2) return 0; + if ((q->vselect & PMSG_SEL_UID) && (q->uid != m->uid)) return 0; + } + + if (q->kselect & PMSG_SEL_GID) + { + if (q->gid == -2) return 0; + if ((q->vselect & PMSG_SEL_GID) && (q->gid != m->gid)) return 0; + } + + if (q->kselect & PMSG_SEL_RUID) + { + if (q->ruid == -1) return 0; + if ((q->vselect & PMSG_SEL_RUID) && (q->ruid != m->ruid)) return 0; + } + + if (q->kselect & PMSG_SEL_RGID) + { + if (q->rgid == -1) return 0; + if ((q->vselect & PMSG_SEL_RGID) && (q->rgid != m->rgid)) return 0; + } + + for (i = 0; i < q->kvcount; i += 2) + { + for (j = 0; j < m->kvcount; j += 2) + { + if (q->kvlist[i] == m->kvlist[j]) + { + if (q->kvlist[i + 1] == m->kvlist[j + 1]) break; + return 0; + } + } + + if (j >= m->kvcount) return 0; + } + + return 1; +} + +static void +free_pmsg(pmsg_t *p) +{ + if (p == NULL) return; + if (p->kvlist != NULL) free(p->kvlist); + free(p); +} + +static uint32_t +pmsg_fetch_by_id(asl_store_t *s, uint64_t msgid, int32_t ruid, int32_t rgid, pmsg_t **pmsg, uint32_t *slot) +{ + uint32_t i, status; + + if (s == NULL) return ASL_STATUS_INVALID_STORE; + if (msgid == ASL_REF_NULL) return ASL_STATUS_INVALID_ARG; + if (slot == NULL) return ASL_STATUS_INVALID_ARG; + + *slot = ASL_INDEX_NULL; + + i = slotlist_find(s, msgid, 0, 0); + if (i == ASL_INDEX_NULL) return ASL_STATUS_INVALID_ID; + + *slot = s->slotlist[i].slot; + + /* read the message */ + *pmsg = NULL; + status = pmsg_fetch(s, s->slotlist[i].slot, ruid, rgid, PMSG_FETCH_ALL, pmsg); + if (status != ASL_STATUS_OK) return status; + if (pmsg == NULL) return ASL_STATUS_FAILED; + + return status; +} + +static uint32_t +msg_decode(asl_store_t *s, pmsg_t *pmsg, asl_msg_t **out) +{ + uint32_t status, i, n; + char *key, *val; + asl_msg_t *msg; + + if (s == NULL) return ASL_STATUS_INVALID_STORE; + if (out == NULL) return ASL_STATUS_INVALID_ARG; + if (pmsg == NULL) return ASL_STATUS_INVALID_ARG; + + *out = NULL; + + msg = (asl_msg_t *)calloc(1, sizeof(asl_msg_t)); + if (msg == NULL) return ASL_STATUS_NO_MEMORY; + + msg->type = ASL_TYPE_MSG; + msg->count = 0; + if (pmsg->time != ASL_REF_NULL) msg->count++; + if (pmsg->host != ASL_REF_NULL) msg->count++; + if (pmsg->sender != ASL_REF_NULL) msg->count++; + if (pmsg->facility != ASL_REF_NULL) msg->count++; + if (pmsg->message != ASL_REF_NULL) msg->count++; + if (pmsg->level != ASL_INDEX_NULL) msg->count++; + if (pmsg->pid != -1) msg->count++; + if (pmsg->uid != -2) msg->count++; + if (pmsg->gid != -2) msg->count++; + if (pmsg->ruid != -1) msg->count++; + if (pmsg->rgid != -1) msg->count++; + + msg->count += pmsg->kvcount / 2; + + if (msg->count == 0) + { + free(msg); + return ASL_STATUS_INVALID_MESSAGE; + } + + /* Message ID */ + msg->count += 1; + + msg->key = (char **)calloc(msg->count, sizeof(char *)); + if (msg->key == NULL) + { + free(msg); + return ASL_STATUS_NO_MEMORY; + } + + msg->val = (char **)calloc(msg->count, sizeof(char *)); + if (msg->val == NULL) + { + free(msg->key); + free(msg); + return ASL_STATUS_NO_MEMORY; + } + + n = 0; + + /* Time */ + if (pmsg->time != ASL_REF_NULL) + { + msg->key[n] = strdup(ASL_KEY_TIME); + if (msg->key[n] == NULL) + { + asl_free(msg); + return ASL_STATUS_NO_MEMORY; + } + + asprintf(&(msg->val[n]), "%llu", pmsg->time); + if (msg->val[n] == NULL) + { + asl_free(msg); + return ASL_STATUS_NO_MEMORY; + } + n++; + } + + /* Host */ + if (pmsg->host != ASL_REF_NULL) + { + msg->key[n] = strdup(ASL_KEY_HOST); + if (msg->key[n] == NULL) + { + asl_free(msg); + return ASL_STATUS_NO_MEMORY; + } + + status = string_fetch_sid(s, pmsg->host, &(msg->val[n])); + n++; + } + + /* Sender */ + if (pmsg->sender != ASL_REF_NULL) + { + msg->key[n] = strdup(ASL_KEY_SENDER); + if (msg->key[n] == NULL) + { + asl_free(msg); + return ASL_STATUS_NO_MEMORY; + } + + status = string_fetch_sid(s, pmsg->sender, &(msg->val[n])); + n++; + } + + /* Facility */ + if (pmsg->facility != ASL_REF_NULL) + { + msg->key[n] = strdup(ASL_KEY_FACILITY); + if (msg->key[n] == NULL) + { + asl_free(msg); + return ASL_STATUS_NO_MEMORY; + } + + status = string_fetch_sid(s, pmsg->facility, &(msg->val[n])); + n++; + } + + /* Level */ + if (pmsg->level != ASL_INDEX_NULL) + { + msg->key[n] = strdup(ASL_KEY_LEVEL); + if (msg->key[n] == NULL) + { + asl_free(msg); + return ASL_STATUS_NO_MEMORY; + } + + asprintf(&(msg->val[n]), "%u", pmsg->level); + if (msg->val[n] == NULL) + { + asl_free(msg); + return ASL_STATUS_NO_MEMORY; + } + n++; + } + + /* PID */ + if (pmsg->pid != -1) + { + msg->key[n] = strdup(ASL_KEY_PID); + if (msg->key[n] == NULL) + { + asl_free(msg); + return ASL_STATUS_NO_MEMORY; + } + + asprintf(&(msg->val[n]), "%d", pmsg->pid); + if (msg->val[n] == NULL) + { + asl_free(msg); + return ASL_STATUS_NO_MEMORY; + } + n++; + } + + /* UID */ + if (pmsg->uid != -2) + { + msg->key[n] = strdup(ASL_KEY_UID); + if (msg->key[n] == NULL) + { + asl_free(msg); + return ASL_STATUS_NO_MEMORY; + } + + asprintf(&(msg->val[n]), "%d", pmsg->uid); + if (msg->val[n] == NULL) + { + asl_free(msg); + return ASL_STATUS_NO_MEMORY; + } + n++; + } + + /* GID */ + if (pmsg->gid != -2) + { + msg->key[n] = strdup(ASL_KEY_GID); + if (msg->key[n] == NULL) + { + asl_free(msg); + return ASL_STATUS_NO_MEMORY; + } + + asprintf(&(msg->val[n]), "%d", pmsg->gid); + if (msg->val[n] == NULL) + { + asl_free(msg); + return ASL_STATUS_NO_MEMORY; + } + n++; + } + + /* Message */ + if (pmsg->message != ASL_REF_NULL) + { + msg->key[n] = strdup(ASL_KEY_MSG); + if (msg->key[n] == NULL) + { + asl_free(msg); + return ASL_STATUS_NO_MEMORY; + } + + status = string_fetch_sid(s, pmsg->message, &(msg->val[n])); + n++; + } + + /* ReadUID */ + if (pmsg->ruid != -1) + { + msg->key[n] = strdup(ASL_KEY_READ_UID); + if (msg->key[n] == NULL) + { + asl_free(msg); + return ASL_STATUS_NO_MEMORY; + } + + asprintf(&(msg->val[n]), "%d", pmsg->ruid); + if (msg->val[n] == NULL) + { + asl_free(msg); + return ASL_STATUS_NO_MEMORY; + } + n++; + } + + /* ReadGID */ + if (pmsg->rgid != -1) + { + msg->key[n] = strdup(ASL_KEY_READ_GID); + if (msg->key[n] == NULL) + { + asl_free(msg); + return ASL_STATUS_NO_MEMORY; + } + + asprintf(&(msg->val[n]), "%d", pmsg->rgid); + if (msg->val[n] == NULL) + { + asl_free(msg); + return ASL_STATUS_NO_MEMORY; + } + n++; + } + + /* Message ID */ + msg->key[n] = strdup(ASL_KEY_MSG_ID); + if (msg->key[n] == NULL) + { + asl_free(msg); + return ASL_STATUS_NO_MEMORY; + } + + asprintf(&(msg->val[n]), "%llu", pmsg->msgid); + if (msg->val[n] == NULL) + { + asl_free(msg); + return ASL_STATUS_NO_MEMORY; + } + n++; + + /* Key - Value List */ + for (i = 0; i < pmsg->kvcount; i++) + { + key = NULL; + status = string_fetch_sid(s, pmsg->kvlist[i++], &key); + if (status != ASL_STATUS_OK) + { + if (key != NULL) free(key); + continue; + } + + val = NULL; + status = string_fetch_sid(s, pmsg->kvlist[i], &val); + if (status != ASL_STATUS_OK) + { + if (key != NULL) free(key); + if (val != NULL) free(val); + continue; + } + + status = asl_set((aslmsg)msg, key, val); + if (key != NULL) free(key); + if (val != NULL) free(val); + if (status != 0) + { + asl_free(msg); + return ASL_STATUS_FAILED; + } + } + + *out = msg; + return ASL_STATUS_OK; +} + +/* + * Finds string either in the string cache or in the database + */ +static uint32_t +store_string_find(asl_store_t *s, uint32_t hash, const char *str, uint32_t *index, uint32_t *refcount) +{ + uint32_t i, status, test; + char *tmp; + + if (s == NULL) return ASL_STATUS_INVALID_STORE; + if (str == NULL) return ASL_STATUS_INVALID_ARG; + if (index == NULL) return ASL_STATUS_INVALID_ARG; + if (refcount == NULL) return ASL_STATUS_INVALID_ARG; + if (s->slotlist == NULL) return ASL_STATUS_FAILED; + + /* check the cache */ + for (i = 0; i < STRING_CACHE_SIZE; i++) + { + if (s->string_cache[i].index == ASL_INDEX_NULL) continue; + + test = s->slotlist[s->string_cache[i].index].hash; + if (test != hash) continue; + + if (s->string_cache[i].str == NULL) + { + /* can't happen, but clean up anyway */ + s->string_cache[i].index = ASL_INDEX_NULL; + s->string_cache[i].refcount = 0; + continue; + } + + if (strcmp(s->string_cache[i].str, str)) continue; + + /* Bingo */ + *index = s->string_cache[i].index; + *refcount = s->string_cache[i].refcount; + return ASL_STATUS_OK; + } + + /* check the database */ + for (i = 0; i < s->slotlist_count; i++) + { + if ((s->slotlist[i].type != DB_TYPE_STRING) || (s->slotlist[i].hash != hash)) continue; + + /* read the whole string */ + tmp = NULL; + *refcount = 0; + status = string_fetch_slot(s, s->slotlist[i].slot, &tmp, refcount); + if (status != ASL_STATUS_OK) return status; + if (tmp == NULL) return ASL_STATUS_FAILED; + + status = strcmp(tmp, str); + free(tmp); + if (status != 0) continue; + + /* Bingo! */ + *index = i; + return ASL_STATUS_OK; + } + + *refcount = 0; + return ASL_STATUS_FAILED; +} + +/* + * Looks up a string ID number. + * Creates the string if necessary. + * Increments the string refcount. + */ +static uint64_t +string_retain(asl_store_t *s, const char *str, uint32_t create) +{ + uint32_t status, hash, index, slot, refcount, len; + uint64_t nsid, sid; + char **recordlist, *p; + uint8_t inls; + + if (s == NULL) return ASL_REF_NULL; + if (str == NULL) return ASL_REF_NULL; + + sid = ASL_REF_NULL; + index = ASL_INDEX_NULL; + slot = ASL_INDEX_NULL; + refcount = 0; + + len = strlen(str); + if (len < 8) + { + /* inline string */ + inls = len; + inls |= 0x80; + + nsid = 0; + p = (char *)&nsid; + memcpy(p, &inls, 1); + memcpy(p + 1, str, len); + sid = _asl_ntohq(nsid); + return sid; + } + + hash = string_hash(str, len); + + /* check the database */ + status = store_string_find(s, hash, str, &index, &refcount); + if (status == ASL_STATUS_OK) + { + if (index == ASL_INDEX_NULL) return ASL_REF_NULL; + if (create == 0) return s->slotlist[index].xid; + + refcount++; + string_set_refcount(s, index, str, refcount); + return s->slotlist[index].xid; + } + + if (create == 0) return ASL_REF_NULL; + + /* create the string */ + recordlist = NULL; + status = string_encode(s, hash, str, &sid, &recordlist); + if (status != ASL_STATUS_OK) return ASL_REF_NULL; + if (recordlist == NULL) return ASL_REF_NULL; + + status = save_record_list(s, recordlist, &slot); + record_list_free(s, recordlist); + if (status != ASL_STATUS_OK) return status; + + return sid; +} + +static uint32_t +record_chain_free(asl_store_t *s, uint64_t xid, uint32_t slot, uint8_t type) +{ + uint32_t status, next, where; + off_t offset; + char zdb[DB_RECORD_LEN]; + + if (s == NULL) return ASL_STATUS_INVALID_STORE; + + if ((type == DB_TYPE_STRING) && (s->string_count > 0)) s->string_count--; + if ((type == DB_TYPE_MESSAGE) && (s->message_count > 0)) s->message_count--; + + memset(zdb, 0, DB_RECORD_LEN); + + /* + * Walk the chain and mark each slot as free. + * + * This is tricky: + * We need to know the index in the slot list for each slot used in the record. + * We are given the xid for the record, which we pass to slotlist_find + * to get the index of the first slot. The slotlist entries for all subsequent + * slots will have xid 0, since they are of type DB_TYPE_KVLIST or DB_TYPE_STRCONT. + * Since slotlist has a secondary sort by slot, we can do a binary search within the + * xid 0 entries with slotlist_find to get the slotlist index of those slots. + */ + where = slotlist_find(s, xid, 0, 0); + + next = slot; + while (next != 0) + { + /* update slotlist */ + slotlist_make_empty(s, where); + + offset = next * DB_RECORD_LEN; + + status = fseek(s->db, offset + 1, SEEK_SET); + if (status < 0) return ASL_STATUS_WRITE_FAILED; + + status = fread(&next, 4, 1, s->db); + if (status != 1) return ASL_STATUS_WRITE_FAILED; + next = ntohl(next); + + status = fseek(s->db, offset, SEEK_SET); + if (status < 0) return ASL_STATUS_WRITE_FAILED; + + status = fwrite(zdb, DB_RECORD_LEN, 1, s->db); + if (status != 1) return ASL_STATUS_WRITE_FAILED; + + if (next != 0) where = slotlist_find_xid0_slot(s, next); + } + + return ASL_STATUS_OK; +} + +/* + * Removes records. + * + * Decrements string refcount. + * Removes string if refcount becomes zero. + */ +static uint32_t +id_release(asl_store_t *s, uint64_t xid, uint32_t slot, uint8_t type) +{ + uint32_t status, refcount, v32; + uint64_t x; + char head[17]; + off_t offset; + + if (s == NULL) return ASL_STATUS_INVALID_STORE; + if (xid == ASL_REF_NULL) return ASL_STATUS_INVALID_ID; + if (slot == ASL_INDEX_NULL) return ASL_STATUS_INVALID_ARG; + + /* Note that record_chain_free() updates slotlist */ + + offset = slot * DB_RECORD_LEN; + status = fseek(s->db, offset, SEEK_SET); + if (status < 0) return ASL_STATUS_WRITE_FAILED; + + status = fread(head, 17, 1, s->db); + if (status != 1) return ASL_STATUS_WRITE_FAILED; + + if (head[0] != type) return ASL_STATUS_FAILED; + + x = header_get_id(head); + + if (x != xid) return ASL_STATUS_FAILED; + + /* small kludge: only strings are refcounted */ + refcount = 1; + if (type == DB_TYPE_STRING) refcount = header_get_refcount(head); + + refcount--; + if (refcount == 0) + { + return record_chain_free(s, xid, slot, type); + } + else + { + offset += 13; + status = fseek(s->db, offset, SEEK_SET); + if (status < 0) return ASL_STATUS_WRITE_FAILED; + + v32 = htonl(refcount); + status = fwrite(&v32, 4, 1, s->db); + if (status != 1) return ASL_STATUS_WRITE_FAILED; + + return ASL_STATUS_OK; + } +} + +static uint32_t +string_release(asl_store_t *s, uint64_t sid) +{ + uint32_t i; + + i = slotlist_find(s, sid, 0, 0); + if (i == ASL_INDEX_NULL) return ASL_STATUS_INVALID_ID; + + return id_release(s, sid, s->slotlist[i].slot, DB_TYPE_STRING); +} + +static uint32_t +message_release(asl_store_t *s, uint64_t xid) +{ + uint32_t i, slot, status; + pmsg_t *pmsg; + + pmsg = NULL; + slot = ASL_INDEX_NULL; + + /* read message and release strings */ + status = pmsg_fetch_by_id(s, xid, 0, 0, &pmsg, &slot); + if (status != ASL_STATUS_OK) return status; + if (pmsg == NULL) return ASL_STATUS_READ_FAILED; + + string_release(s, pmsg->host); + string_release(s, pmsg->sender); + string_release(s, pmsg->facility); + string_release(s, pmsg->message); + for (i = 0; i < pmsg->kvcount; i++) string_release(s, pmsg->kvlist[i]); + free_pmsg(pmsg); + + return id_release(s, xid, slot, DB_TYPE_MESSAGE); +} + +/* + * Convert msg into a database record (or list of records if additional key/value pairs are present). + * Returns a NULL-terminated list of records. + * + * Sets the message access control flags as follows: + * If ruid is specified (anthing other than -1), then we use that as the ReadUID and set the uid access flag + * Else if msg contains a ReadUID, we use that as the ReadUID and set the uid access flag + * Else ReadUID is -1 and we don't set the uid access flag. + * Same logic for ReadGID. + */ +static uint32_t +message_encode(asl_store_t *s, asl_msg_t *msg, int32_t ruid, int32_t rgid, uint64_t *msgid, char ***list) +{ + char **outlist, *std, *kvl, *p; + uint32_t i, kvcount, kvn, len; + uint32_t level; + uint16_t flags; + int32_t pid, uid, gid, muid, mgid; + uint64_t time_val, host_sid, sender_sid, facility_sid, message_sid, k, v; + + if (s == NULL) return ASL_STATUS_INVALID_STORE; + if (msg == NULL) return ASL_STATUS_INVALID_MESSAGE; + if (list == NULL) return ASL_STATUS_INVALID_ARG; + + len = 2; + + outlist = (char **)calloc(len, sizeof(char *)); + if (outlist == NULL) return ASL_STATUS_NO_MEMORY; + + std = record_buffer_alloc(s); + if (std == NULL) + { + free(outlist); + return ASL_STATUS_NO_MEMORY; + } + + *msgid = new_id(s); + if (*msgid == ASL_REF_NULL) + { + free(outlist); + free(std); + return ASL_STATUS_FAILED; + } + + flags = 0; + + muid = -1; + if (ruid != -1) + { + muid = ruid; + flags |= ASL_MSG_FLAG_READ_UID_SET; + } + + mgid = -1; + if (rgid != -1) + { + mgid = rgid; + flags |= ASL_MSG_FLAG_READ_GID_SET; + } + + outlist[0] = std; + + flags = 0; + level = ASL_INDEX_NULL; + pid = -1; + uid = -2; + gid = -2; + host_sid = ASL_REF_NULL; + sender_sid = ASL_REF_NULL; + facility_sid = ASL_REF_NULL; + message_sid = ASL_REF_NULL; + time_val = ASL_REF_NULL; + + kvcount = 0; + kvn = 0; + kvl = NULL; + p = NULL; + + for (i = 0; i < msg->count; i++) + { + if (msg->key[i] == NULL) continue; + + else if (!strcmp(msg->key[i], ASL_KEY_TIME)) + { + if (msg->val[i] != NULL) time_val = asl_parse_time(msg->val[i]); + } + else if (!strcmp(msg->key[i], ASL_KEY_HOST)) + { + if (msg->val[i] != NULL) host_sid = string_retain(s, msg->val[i], 1); + } + else if (!strcmp(msg->key[i], ASL_KEY_SENDER)) + { + if (msg->val[i] != NULL) sender_sid = string_retain(s, msg->val[i], 1); + } + else if (!strcmp(msg->key[i], ASL_KEY_PID)) + { + if (msg->val[i] != NULL) pid = atoi(msg->val[i]); + } + else if (!strcmp(msg->key[i], ASL_KEY_UID)) + { + if (msg->val[i] != NULL) uid = atoi(msg->val[i]); + } + else if (!strcmp(msg->key[i], ASL_KEY_GID)) + { + if (msg->val[i] != NULL) gid = atoi(msg->val[i]); + } + else if (!strcmp(msg->key[i], ASL_KEY_LEVEL)) + { + if (msg->val[i] != NULL) level = atoi(msg->val[i]); + } + else if (!strcmp(msg->key[i], ASL_KEY_MSG)) + { + if (msg->val[i] != NULL) message_sid = string_retain(s, msg->val[i], 1); + } + else if (!strcmp(msg->key[i], ASL_KEY_FACILITY)) + { + if (msg->val[i] != NULL) facility_sid = string_retain(s, msg->val[i], 1); + } + else if (!strcmp(msg->key[i], ASL_KEY_READ_UID)) + { + if (((flags & ASL_MSG_FLAG_READ_UID_SET) == 0) && (msg->val[i] != NULL)) + { + muid = atoi(msg->val[i]); + flags |= ASL_MSG_FLAG_READ_UID_SET; + } + } + else if (!strcmp(msg->key[i], ASL_KEY_READ_GID)) + { + if (((flags & ASL_MSG_FLAG_READ_GID_SET) == 0) && (msg->val[i] != NULL)) + { + mgid = atoi(msg->val[i]); + flags |= ASL_MSG_FLAG_READ_GID_SET; + } + } + else if (!strcmp(msg->key[i], ASL_KEY_MSG_ID)) + { + /* Ignore */ + continue; + } + else + { + k = string_retain(s, msg->key[i], 1); + if (k == ASL_REF_NULL) continue; + + v = ASL_REF_NULL; + if (msg->val[i] != NULL) v = string_retain(s, msg->val[i], 1); + + if (kvl == NULL) + { + outlist = (char **)reallocf(outlist, (len + 1) * sizeof(char *)); + if (outlist == NULL) + { + free(std); + return ASL_STATUS_NO_MEMORY; + } + + kvl = record_buffer_alloc(s); + if (kvl == NULL) + { + record_list_free(s, outlist); + return ASL_STATUS_NO_MEMORY; + } + + kvl[0] = DB_TYPE_KVLIST; + p = kvl + 9; + + outlist[len - 1] = kvl; + outlist[len] = NULL; + len++; + } + + kvcount++; + + _asl_put_64(k, p); + kvn++; + p += 8; + + _asl_put_64(v, p); + kvn++; + p += 8; + + if (kvn >= 8) + { + kvl = NULL; + kvn = 0; + } + } + } + + /* encode kvcount in first kvlist record */ + if (kvcount > 0) + { + kvl = outlist[1]; + _asl_put_32(kvcount, kvl + 5); + } + + /* encode std */ + std[0] = DB_TYPE_MESSAGE; + p = std + 5; + + _asl_put_64(*msgid, p); + p += 8; + + _asl_put_32(muid, p); + p += 4; + + _asl_put_32(mgid, p); + p += 4; + + _asl_put_64(time_val, p); + p += 8; + + _asl_put_64(host_sid, p); + p += 8; + + _asl_put_64(sender_sid, p); + p += 8; + + _asl_put_64(facility_sid, p); + p += 8; + + _asl_put_32(level, p); + p += 4; + + _asl_put_32(pid, p); + p += 4; + + _asl_put_32(uid, p); + p += 4; + + _asl_put_32(gid, p); + p += 4; + + _asl_put_64(message_sid, p); + p += 8; + + _asl_put_16(flags, p); + p += 4; + + *list = outlist; + + return ASL_STATUS_OK; +} + +uint32_t +asl_store_save(asl_store_t *s, asl_msg_t *msg, int32_t ruid, int32_t rgid, uint64_t *msgid) +{ + char **list; + uint32_t status, slot; + uint64_t tick; + + if (s == NULL) return ASL_STATUS_INVALID_STORE; + if (s->flags & ASL_STORE_FLAG_READ_ONLY) return ASL_STATUS_READ_ONLY; + + list = NULL; + status = message_encode(s, msg, ruid, rgid, msgid, &list); + if ((status != ASL_STATUS_OK) || (list == NULL)) return status; + + slot = 0; + status = save_record_list(s, list, &slot); + tick = _asl_get_64(list[0] + MSG_OFF_KEY_TIME); + + /* if time has gone backwards, unset the flag */ + if (tick < s->max_time) s->flags &= ~ASL_STORE_FLAG_TIME_SORTED; + else s->max_time = tick; + + record_list_free(s, list); + + return status; +} + +uint32_t +asl_store_remove(asl_store_t *s, uint64_t msgid) +{ + uint32_t status; + + if (s == NULL) return ASL_STATUS_INVALID_STORE; + if (s->flags & ASL_STORE_FLAG_READ_ONLY) return ASL_STATUS_READ_ONLY; + + status = message_release(s, msgid); + return status; +} + +uint32_t +asl_store_fetch(asl_store_t *s, uint64_t msgid, int32_t ruid, int32_t rgid, asl_msg_t **msg) +{ + uint32_t status, slot; + pmsg_t *pmsg; + + if (s == NULL) return ASL_STATUS_INVALID_STORE; + if (msgid == ASL_REF_NULL) return ASL_STATUS_INVALID_ARG; + + pmsg = NULL; + slot = ASL_INDEX_NULL; + + status = pmsg_fetch_by_id(s, msgid, ruid, rgid, &pmsg, &slot); + if (status != ASL_STATUS_OK) return status; + if (pmsg == NULL) return ASL_STATUS_FAILED; + + status = msg_decode(s, pmsg, msg); + free_pmsg(pmsg); + + return status; +} + +static uint32_t +query_to_pmsg(asl_store_t *s, asl_msg_t *q, pmsg_t **p) +{ + pmsg_t *out; + uint32_t i, j; + uint64_t ksid, vsid; + + if (s == NULL) return ASL_STATUS_INVALID_STORE; + if (p == NULL) return ASL_STATUS_INVALID_ARG; + + if (q == NULL) return Q_NULL; + if (q->count == 0) return Q_NULL; + + *p = NULL; + + if (q->op != NULL) + { + for (i = 0; i < q->count; i++) if (q->op[i] != ASL_QUERY_OP_EQUAL) return Q_SLOW; + } + + out = (pmsg_t *)calloc(1, sizeof(pmsg_t)); + if (out == NULL) return ASL_STATUS_NO_MEMORY; + + for (i = 0; i < q->count; i++) + { + if (q->key[i] == NULL) continue; + + else if (!strcmp(q->key[i], ASL_KEY_TIME)) + { + if (out->kselect & PMSG_SEL_TIME) + { + free_pmsg(out); + return Q_SLOW; + } + + out->kselect |= PMSG_SEL_TIME; + if (q->val[i] != NULL) + { + out->vselect |= PMSG_SEL_TIME; + out->time = asl_parse_time(q->val[i]); + } + } + else if (!strcmp(q->key[i], ASL_KEY_HOST)) + { + if (out->kselect & PMSG_SEL_HOST) + { + free_pmsg(out); + return Q_SLOW; + } + + out->kselect |= PMSG_SEL_HOST; + if (q->val[i] != NULL) + { + out->vselect |= PMSG_SEL_HOST; + out->host = string_retain(s, q->val[i], 0); + if (out->host == ASL_REF_NULL) + { + free_pmsg(out); + return Q_FAIL; + } + } + } + else if (!strcmp(q->key[i], ASL_KEY_SENDER)) + { + if (out->kselect & PMSG_SEL_SENDER) + { + free_pmsg(out); + return Q_SLOW; + } + + out->kselect |= PMSG_SEL_SENDER; + if (q->val[i] != NULL) + { + out->vselect |= PMSG_SEL_SENDER; + out->sender = string_retain(s, q->val[i], 0); + if (out->sender == ASL_REF_NULL) + { + free_pmsg(out); + return Q_FAIL; + } + } + } + else if (!strcmp(q->key[i], ASL_KEY_PID)) + { + if (out->kselect & PMSG_SEL_PID) + { + free_pmsg(out); + return Q_SLOW; + } + + out->kselect |= PMSG_SEL_PID; + if (q->val[i] != NULL) + { + out->vselect |= PMSG_SEL_PID; + out->pid = atoi(q->val[i]); + } + } + else if (!strcmp(q->key[i], ASL_KEY_UID)) + { + if (out->kselect & PMSG_SEL_UID) + { + free_pmsg(out); + return Q_SLOW; + } + + out->kselect |= PMSG_SEL_UID; + if (q->val[i] != NULL) + { + out->vselect |= PMSG_SEL_UID; + out->uid = atoi(q->val[i]); + } + } + else if (!strcmp(q->key[i], ASL_KEY_GID)) + { + if (out->kselect & PMSG_SEL_GID) + { + free_pmsg(out); + return Q_SLOW; + } + + out->kselect |= PMSG_SEL_GID; + if (q->val[i] != NULL) + { + out->vselect |= PMSG_SEL_GID; + out->gid = atoi(q->val[i]); + } + } + else if (!strcmp(q->key[i], ASL_KEY_LEVEL)) + { + if (out->kselect & PMSG_SEL_LEVEL) + { + free_pmsg(out); + return Q_SLOW; + } + + out->kselect |= PMSG_SEL_LEVEL; + if (q->val[i] != NULL) + { + out->vselect |= PMSG_SEL_LEVEL; + out->level = atoi(q->val[i]); + } + } + else if (!strcmp(q->key[i], ASL_KEY_MSG)) + { + if (out->kselect & PMSG_SEL_MESSAGE) + { + free_pmsg(out); + return Q_SLOW; + } + + out->kselect |= PMSG_SEL_MESSAGE; + if (q->val[i] != NULL) + { + out->vselect |= PMSG_SEL_MESSAGE; + out->message = string_retain(s, q->val[i], 0); + if (out->message == ASL_REF_NULL) + { + free_pmsg(out); + return Q_FAIL; + } + } + } + else if (!strcmp(q->key[i], ASL_KEY_FACILITY)) + { + if (out->kselect & PMSG_SEL_FACILITY) + { + free_pmsg(out); + return Q_SLOW; + } + + out->kselect |= PMSG_SEL_FACILITY; + if (q->val[i] != NULL) + { + out->vselect |= PMSG_SEL_FACILITY; + out->facility = string_retain(s, q->val[i], 0); + if (out->facility == ASL_REF_NULL) + { + free_pmsg(out); + return Q_FAIL; + } + } + } + else if (!strcmp(q->key[i], ASL_KEY_READ_UID)) + { + if (out->kselect & PMSG_SEL_RUID) + { + free_pmsg(out); + return Q_SLOW; + } + + out->kselect |= PMSG_SEL_RUID; + if (q->val[i] != NULL) + { + out->vselect |= PMSG_SEL_RUID; + out->ruid = atoi(q->val[i]); + } + } + else if (!strcmp(q->key[i], ASL_KEY_READ_GID)) + { + if (out->kselect & PMSG_SEL_RGID) + { + free_pmsg(out); + return Q_SLOW; + } + + out->kselect |= PMSG_SEL_RGID; + if (q->val[i] != NULL) + { + out->vselect |= PMSG_SEL_RGID; + out->rgid = atoi(q->val[i]); + } + } + else + { + ksid = string_retain(s, q->key[i], 0); + if (ksid == ASL_REF_NULL) + { + free_pmsg(out); + return Q_FAIL; + } + + for (j = 0; j < out->kvcount; j += 2) + { + if (out->kvlist[j] == ksid) + { + free_pmsg(out); + return Q_SLOW; + } + } + + vsid = ASL_REF_NULL; + if (q->val[i] != NULL) + { + vsid = string_retain(s, q->val[i], 0); + if (ksid == ASL_REF_NULL) + { + free_pmsg(out); + return Q_FAIL; + } + } + + if (out->kvcount == 0) + { + out->kvlist = (uint64_t *)calloc(2, sizeof(uint64_t)); + } + else + { + out->kvlist = (uint64_t *)reallocf(out->kvlist, (out->kvcount + 2) * sizeof(uint64_t)); + } + + if (out->kvlist == NULL) + { + free_pmsg(out); + return ASL_STATUS_NO_MEMORY; + } + + out->kvlist[out->kvcount++] = ksid; + out->kvlist[out->kvcount++] = vsid; + } + } + + *p = out; + return Q_FAST; +} + +uint32_t +msg_match(asl_store_t *s, uint32_t qtype, pmsg_t *qp, asl_msg_t *q, int32_t ruid, int32_t rgid, uint32_t slot, pmsg_t **iopm, asl_msg_t **iomsg, asl_msg_list_t **res, uint32_t *didmatch) +{ + uint32_t status, what; + + *didmatch = 0; + + if (qtype == Q_FAIL) return ASL_STATUS_OK; + + if (qtype == Q_NULL) + { + if (*iopm == NULL) + { + status = pmsg_fetch(s, slot, ruid, rgid, PMSG_FETCH_ALL, iopm); + if (status != ASL_STATUS_OK) return status; + if (*iopm == NULL) return ASL_STATUS_FAILED; + } + } + else if (qtype == Q_FAST) + { + if (qp == NULL) return ASL_STATUS_INVALID_ARG; + + what = PMSG_FETCH_STD; + if (qp->kvcount > 0) what = PMSG_FETCH_ALL; + + if (*iopm == NULL) + { + status = pmsg_fetch(s, slot, ruid, rgid, what, iopm); + if (status != ASL_STATUS_OK) return status; + if (*iopm == NULL) return ASL_STATUS_FAILED; + } + + status = pmsg_match(s, qp, *iopm); + if (status == 1) + { + if ((what == PMSG_FETCH_STD) && ((*iopm)->next != 0) && ((*iopm)->kvcount == 0)) + { + status = pmsg_fetch(s, slot, ruid, rgid, PMSG_FETCH_KV, iopm); + if (status != ASL_STATUS_OK) return status; + if (*iopm == NULL) return ASL_STATUS_FAILED; + } + } + else return ASL_STATUS_OK; + } + else if (qtype == Q_SLOW) + { + if (*iomsg == NULL) + { + if (*iopm == NULL) + { + status = pmsg_fetch(s, slot, ruid, rgid, PMSG_FETCH_ALL, iopm); + if (status != ASL_STATUS_OK) return status; + if (*iopm == NULL) return ASL_STATUS_FAILED; + } + + status = msg_decode(s, *iopm, iomsg); + if (status == ASL_STATUS_INVALID_MESSAGE) return ASL_STATUS_OK; + if (status != ASL_STATUS_OK) return status; + if (*iomsg == NULL) return ASL_STATUS_FAILED; + } + + status = 0; + if (asl_msg_cmp(q, *iomsg) != 0) status = 1; + if (status == 0) return ASL_STATUS_OK; + } + + *didmatch = 1; + + if (res == NULL) return ASL_STATUS_OK; + + if (*iomsg == NULL) + { + status = msg_decode(s, *iopm, iomsg); + if (status == ASL_STATUS_INVALID_MESSAGE) + { + *didmatch = 0; + return ASL_STATUS_OK; + } + + if (status != ASL_STATUS_OK) return status; + } + + if ((*res)->count == 0) (*res)->msg = (asl_msg_t **)calloc(1, sizeof(asl_msg_t *)); + else (*res)->msg = (asl_msg_t **)reallocf((*res)->msg, (1 + (*res)->count) * sizeof(asl_msg_t *)); + if ((*res)->msg == NULL) return ASL_STATUS_NO_MEMORY; + + (*res)->msg[(*res)->count++] = *iomsg; + + return ASL_STATUS_OK; +} + +static uint32_t +next_search_slot(asl_store_t *s, uint32_t last_si, int32_t direction) +{ + uint32_t i; + + if (direction >= 0) + { + for (i = last_si + 1; i < s->slotlist_count; i++) + { + if (s->slotlist[i].type == DB_TYPE_MESSAGE) return i; + } + + return ASL_INDEX_NULL; + } + + if (last_si == 0) return ASL_INDEX_NULL; + if (last_si > s->slotlist_count) return ASL_INDEX_NULL; + + for (i = last_si - 1; i > 0; i--) + { + if (s->slotlist[i].type == DB_TYPE_MESSAGE) return i; + } + + if (s->slotlist[0].type == DB_TYPE_MESSAGE) return 0; + + return ASL_INDEX_NULL; +} + +static uint32_t +query_list_to_pmsg_list(asl_store_t *s, asl_msg_list_t *query, uint32_t *match, pmsg_t ***qp, uint32_t **qtype, uint32_t *count) +{ + pmsg_t **outp, *pm; + uint32_t i, j, *outt; + *match = 0; + *qp = NULL; + *qtype = 0; + *count = 0; + + if (query == NULL) return ASL_STATUS_OK; + if (match == NULL) return ASL_STATUS_INVALID_ARG; + if (qp == NULL) return ASL_STATUS_INVALID_ARG; + if (qtype == NULL) return ASL_STATUS_INVALID_ARG; + if (query->msg == NULL) return ASL_STATUS_INVALID_ARG; + if (query->count == 0) return ASL_STATUS_OK; + + outp = (pmsg_t **)calloc(query->count, sizeof(pmsg_t *)); + if (outp == NULL) return ASL_STATUS_NO_MEMORY; + + outt = (uint32_t *)calloc(query->count, sizeof(uint32_t)); + if (outt == NULL) + { + free(outp); + return ASL_STATUS_NO_MEMORY; + } + + *match = 1; + + for (i = 0; i < query->count; i++) + { + pm = NULL; + outt[i] = query_to_pmsg(s, query->msg[i], &pm); + if (outt[i] <= ASL_STATUS_FAILED) + { + if (pm != NULL) free_pmsg(pm); + for (j = 0; j < i; j++) free_pmsg(outp[j]); + free(outp); + free(outt); + return ASL_STATUS_NO_MEMORY; + } + + outp[i] = pm; + } + + *count = query->count; + *qp = outp; + *qtype = outt; + return ASL_STATUS_OK; +} + +static void +match_worker_cleanup(pmsg_t **ql, uint32_t *qt, uint32_t n, asl_msg_list_t **res) +{ + uint32_t i; + + if (ql != NULL) + { + for (i = 0; i < n; i++) free_pmsg(ql[i]); + free(ql); + } + + if (qt != NULL) free(qt); + + if (res != NULL) + { + for (i = 0; i < (*res)->count; i++) asl_free((*res)->msg[i]); + free(*res); + } +} + + /* + * Input to asl_store_match is a list of queries. + * A record in the store matches if it matches any query (i.e. query list is "OR"ed) + * + * If counting up (direction is positive) find first record with ID > start_id. + * Else if counting down (direction is negative) find first record with ID < start_id. + * + * Set match flag on. + * If any query is NULL, set match flog off (skips matching below). + * Else if all queries only check "standard" keys, set std flag to on. + * + * If a query only tests equality, convert it to a pmsg_t. The conversion routine + * checks for string values that are NOT in the database. If a string is not found, + * the conversion fails and the query is markes as "never matches". Otherwise, + * the query is marked "fast". + * + * If all queries are marked as "never matches", return NULL. + * + * match loop: + * fetch record (with std flag) + * if match flag is off, decode record and add it to result. + * else for each query: + * if query is NULL (shouldn't happen) decode record and add it to result. Return to match loop. + * else if query never matches, ignore it. + * else if query is fast, use pmsg_match. If it succeeds, decode record and add it to result. Return to match loop. + * else decode record and use asl_cmp. If it succeeds, add record to result. Return to match loop. + * + * return results. + */ +static uint32_t +match_worker(asl_store_t *s, asl_msg_list_t *query, asl_msg_list_t **res, uint64_t *last_id, uint64_t **idlist, uint32_t *idcount, uint64_t start_id, int32_t count, int32_t direction, int32_t ruid, int32_t rgid) +{ + uint32_t mx, si, slot, i, qcount, match, didmatch, status, *qtype; + uint64_t xid; + pmsg_t **qp, *iopmsg; + asl_msg_t *iomsg; + + if (s == NULL) return ASL_STATUS_INVALID_STORE; + if ((res == NULL) && (idlist == NULL)) return ASL_STATUS_INVALID_ARG; + if (last_id == NULL) return ASL_STATUS_INVALID_ARG; + if (idcount == NULL) return ASL_STATUS_INVALID_ARG; + + if (res != NULL) *res = NULL; + if (idlist != NULL) *idlist = NULL; + + mx = 0; + + if (direction < 0) direction = -1; + else direction = 1; + + si = ASL_INDEX_NULL; + if ((direction == -1) && (start_id == ASL_REF_NULL)) si = s->slotlist_count; + else si = slotlist_find(s, start_id, 0, direction); + + si = next_search_slot(s, si, direction); + if (si == ASL_INDEX_NULL) return ASL_STATUS_OK; + if (si >= s->slotlist_count) return ASL_STATUS_FAILED; + + slot = s->slotlist[si].slot; + + status = query_list_to_pmsg_list(s, query, &match, &qp, &qtype, &qcount); + if (status != ASL_STATUS_OK) return status; + + /* + * initialize result list if we've been asked to return messages + */ + if (res != NULL) + { + *res = (asl_msg_list_t *)calloc(1, sizeof(asl_msg_list_t)); + if (*res == NULL) + { + match_worker_cleanup(qp, qtype, qcount, NULL); + return ASL_STATUS_NO_MEMORY; + } + } + + /* + * loop through records + */ + *idcount = 0; + while ((count == 0) || (*idcount < count)) + { + if (si == ASL_INDEX_NULL) break; + if (si >= s->slotlist_count) break; + + slot = s->slotlist[si].slot; + xid = s->slotlist[si].xid; + + *last_id = xid; + + iopmsg = NULL; + iomsg = NULL; + + didmatch = 0; + if (match == 0) + { + status = msg_match(s, Q_NULL, NULL, NULL, ruid, rgid, slot, &iopmsg, &iomsg, res, &didmatch); + free_pmsg(iopmsg); + if (didmatch == 0) + { + asl_free(iomsg); + iomsg = NULL; + } + else + { + if (idlist != NULL) + { + if (*idlist == NULL) *idlist = (uint64_t *)calloc(1, sizeof(uint64_t)); + else *idlist = (uint64_t *)reallocf(*idlist, (*idcount + 1) * sizeof(uint64_t)); + if (*idlist == NULL) status = ASL_STATUS_NO_MEMORY; + else (*idlist)[*idcount] = xid; + } + + (*idcount)++; + } + + if (status == ASL_STATUS_ACCESS_DENIED) + { + si = next_search_slot(s, si, direction); + continue; + } + else if (status != ASL_STATUS_OK) + { + match_worker_cleanup(qp, qtype, qcount, res); + return status; + } + } + else + { + for (i = 0; i < qcount; i++) + { + status = msg_match(s, qtype[i], qp[i], query->msg[i], ruid, rgid, slot, &iopmsg, &iomsg, res, &didmatch); + if (status == ASL_STATUS_ACCESS_DENIED) break; + else if (status != ASL_STATUS_OK) + { + free_pmsg(iopmsg); + asl_free(iomsg); + match_worker_cleanup(qp, qtype, qcount, res); + return status; + } + + if (didmatch == 1) + { + if (idlist != NULL) + { + if (*idlist == NULL) *idlist = (uint64_t *)calloc(1, sizeof(uint64_t)); + else *idlist = (uint64_t *)reallocf(*idlist, (*idcount + 1) * sizeof(uint64_t)); + if (*idlist == NULL) + { + match_worker_cleanup(qp, qtype, qcount, res); + return ASL_STATUS_NO_MEMORY; + } + + (*idlist)[*idcount] = xid; + } + + (*idcount)++; + break; + } + } + + free_pmsg(iopmsg); + if ((didmatch == 0) || (res == NULL)) asl_free(iomsg); + } + + si = next_search_slot(s, si, direction); + } + + match_worker_cleanup(qp, qtype, qcount, NULL); + return status; +} + +uint32_t +asl_store_match(asl_store_t *s, asl_msg_list_t *query, asl_msg_list_t **res, uint64_t *last_id, uint64_t start_id, uint32_t count, int32_t direction, int32_t ruid, int32_t rgid) +{ + uint32_t idcount; + + idcount = 0; + return match_worker(s, query, res, last_id, NULL, &idcount, start_id, count, direction, ruid, rgid); +} + +uint32_t +asl_store_prune(asl_store_t *s, asl_msg_list_t *prune) +{ + uint64_t *idlist, max_id; + uint32_t status, i, idcount; + + if (s == NULL) return ASL_STATUS_INVALID_STORE; + if (s->flags & ASL_STORE_FLAG_READ_ONLY) return ASL_STATUS_READ_ONLY; + + if (prune == NULL) return ASL_STATUS_OK; + + idlist = NULL; + idcount = 0; + max_id = 0; + + status = match_worker(s, prune, NULL, &max_id, &idlist, &idcount, 0, 0, 1, 0, 0); + if (status != ASL_STATUS_OK) + { + if (idlist != NULL) free(idlist); + return status; + } + + for (i = 0; i < idcount; i++) message_release(s, idlist[i]); + if (idlist != NULL) free(idlist); + + return ASL_STATUS_OK; +} + +/* + * Compact the database. + * Removes NULL and EMPTY records by copying records to the front of the file. + */ +uint32_t +asl_store_compact(asl_store_t *s) +{ + char tmp[DB_RECORD_LEN]; + int status; + uint8_t t; + uint32_t i, j, nrecords, next, slcount, old_slcount, *record_map; + off_t offset; + slot_info_t *old_slot_list; + size_t vmrecord_map_len, vmslot_list_len; + void *vmrecord_map, *vmslot_list; + + if (s == NULL) return ASL_STATUS_INVALID_STORE; + if (s->flags & ASL_STORE_FLAG_READ_ONLY) return ASL_STATUS_READ_ONLY; + + status = fseek(s->db, DB_RECORD_LEN, SEEK_SET); + if (status < 0) return ASL_STATUS_READ_FAILED; + + /* + * record map is a mapping from pre-compaction record number to post-compaction record number. + * We allocate it in VM rather than on the malloc heap to keep from creating a lot of + * empty pages. + */ + nrecords = s->record_count; + + vmrecord_map_len = nrecords * sizeof(uint32_t); + vmrecord_map = mmap(0, vmrecord_map_len, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0); + + record_map = (uint32_t *)vmrecord_map; + if (record_map == NULL) return ASL_STATUS_NO_MEMORY; + + record_map[0] = 0; + + /* size of post-compaction slotlist */ + slcount = 0; + + /* first pass: create the record map (N.B. starting at 1 skips the header) */ + for (i = 1, j = 1; i < nrecords; i++) + { + record_map[i] = 0; + + status = fread(tmp, DB_RECORD_LEN, 1, s->db); + if (status != 1) + { + munmap(vmrecord_map, vmrecord_map_len); + return ASL_STATUS_READ_FAILED; + } + + t = tmp[0]; + + if (t == DB_TYPE_HEADER) + { + munmap(vmrecord_map, vmrecord_map_len); + return ASL_STATUS_INVALID_STORE; + } + + /* + * Only messages, kvlists, strings, and string continuations get copied. + * Empty, null, and unrecognized record types (i.e. corruption in the database) + * are skipped. This compresses out gaps and deletes bad records. + */ + if ((t != DB_TYPE_MESSAGE) && (t != DB_TYPE_KVLIST) && (t != DB_TYPE_STRING) && (t != DB_TYPE_STRCONT)) continue; + + /* count to get size of new slotlist */ + if ((t == DB_TYPE_STRING) || (t == DB_TYPE_MESSAGE)) slcount++; + + record_map[i] = j++; + } + + /* second pass: copy records and fix "next" indexes */ + for (i = 1; i < nrecords; i++) + { + offset = i * DB_RECORD_LEN; + status = fseek(s->db, offset, SEEK_SET); + if (status < 0) + { + munmap(vmrecord_map, vmrecord_map_len); + return ASL_STATUS_READ_FAILED; + } + + status = fread(tmp, DB_RECORD_LEN, 1, s->db); + if (status != 1) + { + munmap(vmrecord_map, vmrecord_map_len); + return ASL_STATUS_READ_FAILED; + } + + t = tmp[0]; + + /* only copy messages, kvlists, strings, and string continuations */ + if ((t != DB_TYPE_MESSAGE) && (t != DB_TYPE_KVLIST) && (t != DB_TYPE_STRING) && (t != DB_TYPE_STRCONT)) continue; + + next = _asl_get_32(tmp + 1); + + if (next > nrecords) next = 0; + else next = record_map[next]; + + _asl_put_32(next, tmp + 1); + + offset = record_map[i] * DB_RECORD_LEN; + status = fseek(s->db, offset, SEEK_SET); + if (status < 0) + { + munmap(vmrecord_map, vmrecord_map_len); + return ASL_STATUS_READ_FAILED; + } + + status = fwrite(tmp, DB_RECORD_LEN, 1, s->db); + if (status != 1) + { + munmap(vmrecord_map, vmrecord_map_len); + return ASL_STATUS_WRITE_FAILED; + } + } + + /* truncate file */ + s->record_count = j; + offset = s->record_count * DB_RECORD_LEN; + + status = fseek(s->db, 0, SEEK_SET); + if (status < 0) + { + munmap(vmrecord_map, vmrecord_map_len); + return ASL_STATUS_READ_FAILED; + } + + status = ftruncate(fileno(s->db), offset); + if (status != 0) + { + munmap(vmrecord_map, vmrecord_map_len); + return ASL_STATUS_WRITE_FAILED; + } + + /* + * build new slotlist + * + * We start by allocating and copying the old slotlist into VM. + * Then we realloc the old slotlist to become the new slotlist. + * Then we build the new slotlist from the values in VM. + * Finally we deallocate the VM. + * This is done so that we don't create a large malloc heap. + */ + + vmslot_list_len = s->slotlist_count * sizeof(slot_info_t); + + vmslot_list = mmap(0, vmslot_list_len, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0); + old_slot_list = (slot_info_t *)vmslot_list; + if (old_slot_list == NULL) + { + munmap(vmrecord_map, vmrecord_map_len); + return ASL_STATUS_NO_MEMORY; + } + + old_slcount = s->slotlist_count; + + /* copy old values to VM */ + for (i = 0; i < s->slotlist_count; i++) + { + old_slot_list[i].type = s->slotlist[i].type; + old_slot_list[i].slot = s->slotlist[i].slot; + old_slot_list[i].xid = s->slotlist[i].xid; + old_slot_list[i].hash = s->slotlist[i].hash; + } + + s->slotlist = (slot_info_t *)reallocf(s->slotlist, slcount * sizeof(slot_info_t)); + if (s->slotlist == NULL) + { + munmap(vmrecord_map, vmrecord_map_len); + munmap(vmslot_list, vmslot_list_len); + return ASL_STATUS_NO_MEMORY; + } + + s->slotlist_count = slcount; + + /* create the new compacted slotlist */ + for (i = 0, j = 0; i < old_slcount; i++) + { + t = old_slot_list[i].type; + if ((t == DB_TYPE_STRING) || (t == DB_TYPE_MESSAGE)) + { + s->slotlist[j].type = t; + s->slotlist[j].slot = record_map[old_slot_list[i].slot]; + s->slotlist[j].xid = old_slot_list[i].xid; + s->slotlist[j].hash = old_slot_list[i].hash; + j++; + } + } + + munmap(vmslot_list, vmslot_list_len); + + s->empty_count = 0; + + /* fix string cache index (which indexes into slotlist) */ + for (i = 0; i < STRING_CACHE_SIZE; i++) + { + if (s->string_cache[i].index == ASL_INDEX_NULL) continue; + s->string_cache[i].index = record_map[s->string_cache[i].index]; + } + + /* new xid=0 count */ + for (s->slot_zero_count = 0; (s->slot_zero_count < s->slotlist_count) && (s->slotlist[s->slot_zero_count].xid == 0); s->slot_zero_count++); + + munmap(vmrecord_map, vmrecord_map_len); + + return ASL_STATUS_OK; +} + +static uint32_t +write_to_archive(asl_store_t *s, asl_store_t *a, uint64_t msgid) +{ + uint32_t status; + uint64_t xid; + aslmsg msg; + + if (s == NULL) return ASL_STATUS_INVALID_STORE; + if (a == NULL) return ASL_STATUS_OK; + + status = asl_store_fetch(s, msgid, 0, 0, &msg); + if (status != ASL_STATUS_OK) return status; + + status = asl_store_save(a, msg, -1, -1, &xid); + asl_free(msg); + return status; +} + +static uint64_t +oldest_id(asl_store_t *s) +{ + uint32_t si; + + if (s == NULL) return ASL_STATUS_INVALID_STORE; + + si = next_search_slot(s, ASL_INDEX_NULL, 1); + if (si == ASL_INDEX_NULL) return ASL_REF_NULL; + + return s->slotlist[si].xid; +} + +/* + * Archive/remove oldest messages to make the database <= max_size + * This is slow - messages are removed one at a time. + */ +uint32_t +asl_store_truncate(asl_store_t *s, uint64_t max_size, const char *archive) +{ + uint32_t max_slots, curr_used; + uint32_t status; + uint64_t old; + asl_store_t *a; + + if (s == NULL) return ASL_STATUS_INVALID_STORE; + if (s->flags & ASL_STORE_FLAG_READ_ONLY) return ASL_STATUS_READ_ONLY; + + if (max_size == 0) return ASL_STATUS_OK; + + a = NULL; + + max_slots = (max_size + DB_RECORD_LEN - 1) / DB_RECORD_LEN; + curr_used = s->record_count - s->empty_count; + + if ((curr_used > max_slots) && (archive != NULL)) + { + status = asl_store_open(archive, 0, &a); + if (status != ASL_STATUS_OK) return status; + } + + while (curr_used > max_slots) + { + old = oldest_id(s); + if (old == ASL_REF_NULL) return ASL_STATUS_FAILED; + + if (archive != NULL) + { + status = write_to_archive(s, a, old); + if (status != ASL_STATUS_OK) return status; + } + + status = message_release(s, old); + if (status != ASL_STATUS_OK) return status; + + curr_used = s->record_count - s->empty_count; + } + + if (archive != NULL) asl_store_close(a); + + status = asl_store_compact(s); + return status; +} + +static uint32_t +archive_time_worker(asl_store_t *s, uint64_t cut_time, uint64_t **idlist, uint16_t **flags, uint32_t *idcount) +{ + uint32_t si, slot, status, check_sort; + uint64_t xid, t, lastt; + uint16_t rflags; + char tmp[DB_RECORD_LEN]; + off_t offset; + + if (s == NULL) return ASL_STATUS_INVALID_STORE; + if (idlist == NULL) return ASL_STATUS_INVALID_ARG; + if (flags == NULL) return ASL_STATUS_INVALID_ARG; + if (idcount == NULL) return ASL_STATUS_INVALID_ARG; + + *idlist = NULL; + *flags = NULL; + *idcount = 0; + si = ASL_INDEX_NULL; + + lastt = 0; + check_sort = 1; + + /* + * loop through records + * + * Note that next_search_slot() traverses slotlist, which is sorted by id numder. + * If the ASL_STORE_FLAG_TIME_SORTED flag is not set, we must search the whole database. + * If the flag is set, timestamps will increase with xid, so we stop when we get + * a message with time > cut_time. + */ + forever + { + si = next_search_slot(s, si, 1); + if (si == ASL_INDEX_NULL) break; + + slot = s->slotlist[si].slot; + xid = s->slotlist[si].xid; + + offset = slot * DB_RECORD_LEN; + status = fseek(s->db, offset, SEEK_SET); + + if (status < 0) return ASL_STATUS_READ_FAILED; + + status = fread(tmp, DB_RECORD_LEN, 1, s->db); + if (status != 1) return ASL_STATUS_READ_FAILED; + + t = _asl_get_64(tmp + MSG_OFF_KEY_TIME); + + if (lastt > t) check_sort = 0; + + if (t > cut_time) + { + if (s->flags & ASL_STORE_FLAG_TIME_SORTED) return ASL_STATUS_OK; + continue; + } + + rflags = _asl_get_16(tmp + MSG_OFF_KEY_FLAGS); + + if (*idlist == NULL) + { + *idlist = (uint64_t *)calloc(1, sizeof(uint64_t)); + *flags = (uint16_t *)calloc(1, sizeof(uint16_t)); + } + else + { + *idlist = (uint64_t *)reallocf(*idlist, (*idcount + 1) * sizeof(uint64_t)); + *flags = (uint16_t *)reallocf(*flags, (*idcount + 1) * sizeof(uint16_t)); + } + + if (*idlist == NULL) + { + if (*flags != NULL) free(*flags); + *flags = NULL; + return ASL_STATUS_NO_MEMORY; + } + + if (*flags == NULL) + { + if (*idlist != NULL) free(*idlist); + *idlist = NULL; + return ASL_STATUS_NO_MEMORY; + } + + (*idlist)[*idcount] = xid; + (*flags)[*idcount] = rflags; + (*idcount)++; + } + + /* if timestamps increase with xid, set the flag to improve subsequent search performance */ + if (check_sort == 1) s->flags |= ASL_STORE_FLAG_TIME_SORTED; + + return ASL_STATUS_OK; +} + +static uint32_t +archive_time_inverse(asl_store_t *s, uint64_t cut_time, uint64_t **idlist, uint32_t *idcount) +{ + uint32_t si, slot, status; + uint64_t xid, t, lastt; + char tmp[DB_RECORD_LEN]; + off_t offset; + + if (s == NULL) return ASL_STATUS_INVALID_STORE; + if (idlist == NULL) return ASL_STATUS_INVALID_ARG; + if (idcount == NULL) return ASL_STATUS_INVALID_ARG; + + *idlist = NULL; + *idcount = 0; + si = ASL_INDEX_NULL; + + lastt = 0; + + /* + * loop through records + */ + forever + { + si = next_search_slot(s, si, 1); + if (si == ASL_INDEX_NULL) break; + + slot = s->slotlist[si].slot; + xid = s->slotlist[si].xid; + + offset = slot * DB_RECORD_LEN; + status = fseek(s->db, offset, SEEK_SET); + + if (status < 0) return ASL_STATUS_READ_FAILED; + + status = fread(tmp, DB_RECORD_LEN, 1, s->db); + if (status != 1) return ASL_STATUS_READ_FAILED; + + t = _asl_get_64(tmp + MSG_OFF_KEY_TIME); + + if (t <= cut_time) continue; + + if (*idlist == NULL) + { + *idlist = (uint64_t *)calloc(1, sizeof(uint64_t)); + } + else + { + *idlist = (uint64_t *)reallocf(*idlist, (*idcount + 1) * sizeof(uint64_t)); + } + + if (*idlist == NULL) return ASL_STATUS_NO_MEMORY; + + + (*idlist)[*idcount] = xid; + (*idcount)++; + } + + return ASL_STATUS_OK; +} + +static uint32_t +archive_release(asl_store_t *s, uint64_t xid, uint64_t cut_time, uint64_t expire_ref) +{ + uint32_t i, slot, status; + uint16_t rflags; + pmsg_t *pmsg; + uint64_t expire_time; + char *str, tmp[DB_RECORD_LEN]; + off_t offset; + + pmsg = NULL; + slot = ASL_INDEX_NULL; + + /* read message and release strings */ + status = pmsg_fetch_by_id(s, xid, 0, 0, &pmsg, &slot); + if (status != ASL_STATUS_OK) return status; + if (pmsg == NULL) return ASL_STATUS_READ_FAILED; + + if (expire_ref != ASL_REF_NULL) + { + for (i = 0; i < pmsg->kvcount; i += 2) + { + if (pmsg->kvlist[i] == expire_ref) + { + str = NULL; + status = string_fetch_sid(s, pmsg->kvlist[i + 1], &str); + if (status != ASL_STATUS_OK) return status; + if (str != NULL) + { + expire_time = 0; + /* relative time not allowed - that would be cheating! */ + if (str[0] != '+') expire_time = asl_parse_time(str); + free(str); + + if (expire_time > cut_time) + { + /* expires in the future - mark as "do not archive" and don't release */ + free_pmsg(pmsg); + + offset = slot * DB_RECORD_LEN; + status = fseek(s->db, offset, SEEK_SET); + if (status < 0) return ASL_STATUS_READ_FAILED; + + status = fread(tmp, DB_RECORD_LEN, 1, s->db); + if (status != 1) return ASL_STATUS_READ_FAILED; + + rflags = _asl_get_16(tmp + MSG_OFF_KEY_FLAGS); + if ((rflags & ASL_MSG_FLAG_DO_NOT_ARCHIVE) == 0) + { + rflags |= ASL_MSG_FLAG_DO_NOT_ARCHIVE; + _asl_put_16(rflags, tmp + MSG_OFF_KEY_FLAGS); + + status = fseek(s->db, offset, SEEK_SET); + if (status < 0) return ASL_STATUS_WRITE_FAILED; + + status = fwrite(tmp, DB_RECORD_LEN, 1, s->db); + if (status != 1) return ASL_STATUS_WRITE_FAILED; + } + + return ASL_STATUS_OK; + } + } + } + } + } + + string_release(s, pmsg->host); + string_release(s, pmsg->sender); + string_release(s, pmsg->facility); + string_release(s, pmsg->message); + for (i = 0; i < pmsg->kvcount; i++) string_release(s, pmsg->kvlist[i]); + free_pmsg(pmsg); + + return id_release(s, xid, slot, DB_TYPE_MESSAGE); +} + +static char * +asl_store_mk_tmp_path() +{ + char tmp[PATH_MAX], *path; + + if (confstr(_CS_DARWIN_USER_TEMP_DIR, tmp, sizeof(tmp)) <= 0) return NULL; + + path = NULL; + asprintf(&path, "%sasl.%d.tmp", tmp, getpid()); + return path; +} + +/* + * Moves messages added at or before cut_time to an archive, + * or delete them if archive_name is NULL. + */ +uint32_t +asl_store_archive(asl_store_t *s, uint64_t cut_time, const char *archive_name) +{ + asl_store_t *archive, *newstore; + char *path, *newmapped; + uint16_t *flags; + uint32_t status, i, archive_count, save_count; + uint64_t expire_ref, *archive_list, *save_list; + size_t dbsize; + + if (s == NULL) return ASL_STATUS_INVALID_STORE; + if (s->flags & ASL_STORE_FLAG_READ_ONLY) return ASL_STATUS_READ_ONLY; + + if (cut_time == 0) return ASL_STATUS_OK; + + archive_count = 0; + archive_list = NULL; + save_count = 0; + save_list = NULL; + flags = NULL; + + s->flags = 0; + + status = archive_time_worker(s, cut_time, &archive_list, &flags, &archive_count); + if (status != ASL_STATUS_OK) return status; + + if ((archive_list == NULL) || (archive_count == 0)) + { + if (archive_list != NULL) free(archive_list); + return ASL_STATUS_OK; + } + + archive = NULL; + if (archive_name != NULL) + { + status = asl_store_open(archive_name, 0, &archive); + if (status != ASL_STATUS_OK) return status; + } + + if (archive != NULL) + { + for (i = 0; i < archive_count; i++) + { + if (flags[i] & ASL_MSG_FLAG_DO_NOT_ARCHIVE) continue; + + status = write_to_archive(s, archive, archive_list[i]); + if (status != ASL_STATUS_OK) + { + free(archive_list); + asl_store_close(archive); + return status; + } + } + + asl_store_close(archive); + } + + /* + * Deleting large numbers of records is slow. + * If the number of records to be deleted is at least 1000, and + * is ARCHIVE_DELETE_VS_COPY_PERCENT or above, we copy the records + * that should remain to a temporary archive, then replace the + * database with the temporary one. + * Note that we need to know the name of the current DB file. + */ + path = NULL; + if ((archive_count >= 1000) && (((archive_count * 100) / s->message_count) >= ARCHIVE_DELETE_VS_COPY_PERCENT) && (s->db_path != NULL)) path = asl_store_mk_tmp_path(); + if (path != NULL) + { + status = unlink(path); + if ((status != 0) && (errno != ENOENT)) + { + free(path); + path = NULL; + } + } + + if (path != NULL) + { + if (archive_list != NULL) free(archive_list); + archive_list = NULL; + archive_count = 0; + + newstore = NULL; + status = asl_store_open(path, 0, &newstore); + free(path); + path = NULL; + if (status != ASL_STATUS_OK) return status; + + /* Set the next_id so that the archive will have ids bigger than the current archive. */ + newstore->next_id = s->next_id; + + /* get a list of records that we want to keep */ + status = archive_time_inverse(s, cut_time, &save_list, &save_count); + if (status != ASL_STATUS_OK) + { + asl_store_close(newstore); + return status; + } + + if ((save_list == NULL) || (save_count == 0)) + { + if (save_list != NULL) free(save_list); + asl_store_close(newstore); + return ASL_STATUS_OK; + } + + /* save to the temp archive */ + for (i = 0; i < save_count; i++) + { + status = write_to_archive(s, newstore, save_list[i]); + if (status != ASL_STATUS_OK) + { + if (save_list != NULL) free(save_list); + asl_store_close(newstore); + return status; + } + } + + free(save_list); + save_list = NULL; + + /* try rename since it's fast, but may fail (e.g. files are on different filesystems) */ + fclose(s->db); + status = rename(newstore->db_path, s->db_path); + if (status == 0) + { + /* success */ + s->db = fopen(s->db_path, "r+"); + if (s->db == NULL) + { + /* Disaster! Can't open the database! */ + asl_store_close(newstore); + return ASL_STATUS_FAILED; + } + } + else + { + /* rename failed, copy the data */ + s->db = fopen(s->db_path, "r+"); + if (s->db == NULL) + { + /* Disaster! Can't open the database! */ + asl_store_close(newstore); + return ASL_STATUS_FAILED; + } + + dbsize = newstore->record_count * DB_RECORD_LEN; + newmapped = mmap(0, dbsize, PROT_READ, MAP_PRIVATE, fileno(newstore->db), 0); + if (newmapped == (void *)-1) + { + asl_store_close(newstore); + return ASL_STATUS_FAILED; + } + + fseek(s->db, 0, SEEK_SET); + status = ftruncate(fileno(s->db), 0); + if (status != ASL_STATUS_OK) + { + asl_store_close(newstore); + return status; + } + + status = fwrite(newmapped, dbsize, 1, s->db); + munmap(newmapped, dbsize); + if (status == 0) + { + asl_store_close(newstore); + return ASL_STATUS_FAILED; + } + } + + /* swap data in the store handles */ + if (s->slotlist != NULL) free(s->slotlist); + s->slotlist = newstore->slotlist; + newstore->slotlist = NULL; + + for (i = 0; i < RECORD_CACHE_SIZE; i++) + { + free(s->rcache[i]); + s->rcache[i] = newstore->rcache[i]; + newstore->rcache[i] = NULL; + s->rcache_state[i] = newstore->rcache_state[i]; + } + + for (i = 0; i < STRING_CACHE_SIZE; i++) + { + s->string_cache[i].index = newstore->string_cache[i].index; + s->string_cache[i].refcount = newstore->string_cache[i].refcount; + if (s->string_cache[i].str != NULL) free(s->string_cache[i].str); + s->string_cache[i].str = newstore->string_cache[i].str; + newstore->string_cache[i].str = NULL; + } + + s->flags = newstore->flags; + s->record_count = newstore->record_count; + s->message_count = newstore->message_count; + s->string_count = newstore->string_count; + s->empty_count = newstore->empty_count; + s->next_id = newstore->next_id; + s->max_time = newstore->max_time; + s->slotlist_count = newstore->slotlist_count; + s->slot_zero_count = newstore->slot_zero_count; + + fclose(newstore->db); + unlink(newstore->db_path); + free(newstore->db_path); + free(newstore); + + return ASL_STATUS_OK; + } + + expire_ref = string_retain(s, ASL_KEY_EXPIRE_TIME, 0); + + /* + * This flag turns off most of the code in slotlist_make_empty. + * We get much better performace while we delete records, + * but the slotlist has to be repaired and re-sorted afterwards. + */ + s->flags |= ASL_STORE_FLAG_DEFER_SORT; + + for (i = 0; i < archive_count; i++) + { + status = archive_release(s, archive_list[i], cut_time, expire_ref); + if (status != ASL_STATUS_OK) return status; + } + + s->flags &= ~ASL_STORE_FLAG_DEFER_SORT; + + free(archive_list); + archive_list = NULL; + archive_count = 0; + + free(flags); + flags = NULL; + + /* zero xids for slots that became empty during archive release */ + for (i = 0; i < s->slotlist_count; i++) + { + if (s->slotlist[i].type == DB_TYPE_EMPTY) s->slotlist[i].xid = 0; + } + + /* re-sort and determine the zero count */ + qsort((void *)s->slotlist, s->slotlist_count, sizeof(slot_info_t), slot_comp); + + /* new xid=0 count */ + for (s->slot_zero_count = 0; (s->slot_zero_count < s->slotlist_count) && (s->slotlist[s->slot_zero_count].xid == 0); s->slot_zero_count++); + + return ASL_STATUS_OK; +} + +const char * +asl_store_error(uint32_t code) +{ + switch (code) + { + case ASL_STATUS_OK: return "Operation Succeeded"; + case ASL_STATUS_INVALID_ARG: return "Invalid Argument"; + case ASL_STATUS_INVALID_STORE: return "Invalid Data Store"; + case ASL_STATUS_INVALID_STRING: return "Invalid String"; + case ASL_STATUS_INVALID_ID: return "Invalid ID Number"; + case ASL_STATUS_INVALID_MESSAGE: return "Invalid Message"; + case ASL_STATUS_NOT_FOUND: return "Not Found"; + case ASL_STATUS_READ_FAILED: return "Read Operation Failed"; + case ASL_STATUS_WRITE_FAILED: return "Write Operation Failed"; + case ASL_STATUS_NO_MEMORY: return "System Memory Allocation Failed"; + case ASL_STATUS_ACCESS_DENIED: return "Access Denied"; + case ASL_STATUS_READ_ONLY: return "Read Only Access"; + } + + return "Operation Failed"; +} diff --git a/aslcommon/asl_store.h b/aslcommon/asl_store.h new file mode 100644 index 0000000..e29dee5 --- /dev/null +++ b/aslcommon/asl_store.h @@ -0,0 +1,140 @@ +#ifndef __ASL_STORE_H__ +#define __ASL_STORE_H__ + +/* + * ASL Database + * + * Log messages are stored in 80 byte records of the form: + * + * | 1 | 4 | 8 | 4 | 4 | 8 | 8 | 8 | 8 | 4 | 4 | 4 | 4 | 8 | 2 | 1 | (80 bytes) + * | Type | Next | ID | RUID | RGID | Time | Host | Sender | Facility | LEVEL | PID | UID | GID | Message | Flags | Zero | + * + * If there are no additional key/value pairs in the message, Next will be zero. If there are additional + * key/value pairs in the database, Next is a record number for a record with the format: + * + * | 1 | 4 | 4 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 7 | (80 bytes) + * | Type | Next | Count | Key1 | Val1 | Key2 | Val2 | Key3 | Val3 | Key4 | Val4 | Zero | + * + * Additional records will be chained using the Next field, with the count field left zero. + * + * Strings stored in records of the form: + * + * | 1 | 4 | 8 | 4 | 4 | 4 | 55 | (80 bytes) + * | Type | Next | ID | Refcount | Hash | Length | String | + * + * If the string is longer than 55 bytes, Next is a record number for a record with the format: + * + * | 1 | 4 | 75 | (80 bytes) + * | Type | Next | String | + * + * The first record (header) in the database has the format: + * + * | 12 | 4 | 8 | 56 | (80 bytes) + * | Cookie | Vers | Max ID | Zero | + * + */ + +#include +#include +#include +#include + +#define ASL_STORE_FLAG_READ_ONLY 0x00000001 +#define ASL_STORE_FLAG_DEFER_SORT 0x00000002 +#define ASL_STORE_FLAG_TIME_SORTED 0x00000004 + +#define ASL_MSG_FLAG_DO_NOT_ARCHIVE 0x0001 +#define ASL_MSG_FLAG_READ_UID_SET 0x0002 +#define ASL_MSG_FLAG_READ_GID_SET 0x0004 + +#define DB_VERSION 1 +#define DB_RECORD_LEN 80 + +#define DB_HEADER_COOKIE_OFFSET 0 +#define DB_HEADER_VERS_OFFSET 12 +#define DB_HEADER_MAXID_OFFSET 16 + +#define DB_TYPE_NULL 255 +#define DB_TYPE_EMPTY 0 +#define DB_TYPE_HEADER 1 +#define DB_TYPE_MESSAGE 2 +#define DB_TYPE_KVLIST 3 +#define DB_TYPE_STRING 4 +#define DB_TYPE_STRCONT 5 + +#define ASL_REF_NULL 0xffffffffffffffffLL + +#define ASL_STATUS_OK 0 +#define ASL_STATUS_INVALID_ARG 1 +#define ASL_STATUS_INVALID_STORE 2 +#define ASL_STATUS_INVALID_STRING 3 +#define ASL_STATUS_INVALID_ID 4 +#define ASL_STATUS_INVALID_MESSAGE 5 +#define ASL_STATUS_NOT_FOUND 6 +#define ASL_STATUS_READ_FAILED 7 +#define ASL_STATUS_WRITE_FAILED 8 +#define ASL_STATUS_NO_MEMORY 9 +#define ASL_STATUS_ACCESS_DENIED 10 +#define ASL_STATUS_READ_ONLY 11 +#define ASL_STATUS_FAILED 9999 + +#define ASL_KEY_FACILITY "Facility" +#define ASL_KEY_READ_UID "ReadUID" +#define ASL_KEY_READ_GID "ReadGID" +#define ASL_KEY_EXPIRE_TIME "ASLExpireTime" +#define ASL_KEY_MSG_ID "ASLMessageID" + +#define STRING_CACHE_SIZE 100 +#define RECORD_CACHE_SIZE 8 + +typedef struct +{ + uint8_t type; + uint32_t slot; + uint64_t xid; + uint32_t hash; +} slot_info_t; + +typedef struct +{ + uint32_t index; + uint32_t refcount; + char *str; +} string_cache_entry_t; + +typedef struct +{ + uint32_t flags; + uint32_t record_count; + uint32_t message_count; + uint32_t string_count; + uint32_t empty_count; + uint64_t next_id; + uint64_t max_time; + slot_info_t *slotlist; + uint32_t slotlist_count; + uint32_t slot_zero_count; + string_cache_entry_t string_cache[STRING_CACHE_SIZE]; + char *rcache[RECORD_CACHE_SIZE]; + uint8_t rcache_state[RECORD_CACHE_SIZE]; + char *db_path; + FILE *db; +} asl_store_t; + +uint32_t asl_store_open(const char *path, uint32_t flags, asl_store_t **s); +uint32_t asl_store_close(asl_store_t *s); + +uint32_t asl_store_save(asl_store_t *s, aslmsg msg, int32_t ruid, int32_t rgid, uint64_t *msgid); +uint32_t asl_store_fetch(asl_store_t *s, uint64_t msgid, int32_t ruid, int32_t rgid, aslmsg *msg); +uint32_t asl_store_remove(asl_store_t *s, uint64_t msgid); + +uint32_t asl_store_match(asl_store_t *s, aslresponse query, aslresponse *res, uint64_t *last_id, uint64_t start_id, uint32_t count, int32_t direction, int32_t ruid, int32_t rgid); + +uint32_t asl_store_prune(asl_store_t *s, aslresponse prune); +uint32_t asl_store_archive(asl_store_t *s, uint64_t cut_time, const char *archive); +uint32_t asl_store_truncate(asl_store_t *s, uint64_t max_size, const char *archive); +uint32_t asl_store_compact(asl_store_t *s); + +const char *asl_store_error(uint32_t code); + +#endif /*__ASL_STORE_H__*/ diff --git a/syslogd.tproj/Makefile b/syslogd.tproj/Makefile index c93682c..3a1fee2 100644 --- a/syslogd.tproj/Makefile +++ b/syslogd.tproj/Makefile @@ -14,9 +14,9 @@ PROJECT_TYPE = Tool HFILES = daemon.h -CFILES = asl_action.c asl_in.c bsd_in.c bsd_out.c daemon.c klog_in.c syslogd.c udp_in.c +CFILES = asl_action.c asl_in.c bsd_in.c bsd_out.c daemon.c dbserver.c klog_in.c syslogd.c udp_in.c -OTHERSRCS = Makefile.preamble Makefile Makefile.postamble syslogd.8 syslog.conf.5 com.apple.syslogd.plist +OTHERSRCS = Makefile.preamble Makefile Makefile.postamble asl.conf.5 syslogd.8 syslog.conf.5 syslogd.sb com.apple.syslogd.plist MAKEFILEDIR = $(MAKEFILEPATH)/pb_makefiles CODE_GEN_STYLE = DYNAMIC @@ -24,7 +24,7 @@ MAKEFILE = tool.make NEXTSTEP_INSTALLDIR = /usr/sbin WINDOWS_INSTALLDIR = /usr/sbin PDO_UNIX_INSTALLDIR = /usr/sbin -LIBS = +LIBS = -laslcommon DEBUG_LIBS = $(LIBS) PROF_LIBS = $(LIBS) diff --git a/syslogd.tproj/Makefile.postamble b/syslogd.tproj/Makefile.postamble index 015aa9d..08ebe43 100644 --- a/syslogd.tproj/Makefile.postamble +++ b/syslogd.tproj/Makefile.postamble @@ -113,7 +113,13 @@ STRIPFLAGS = -x install-man-page: install -d $(DSTROOT)/usr/share/man/man5 install -d $(DSTROOT)/usr/share/man/man8 + install -c -m 444 asl.conf.5 $(DSTROOT)/usr/share/man/man5/asl.conf.5 install -c -m 444 syslog.conf.5 $(DSTROOT)/usr/share/man/man5/syslog.conf.5 install -c -m 444 syslogd.8 $(DSTROOT)/usr/share/man/man8/syslogd.8 + +after_install: + mkdir -p $(DSTROOT)/System/Library/LaunchDaemons install -d $(DSTROOT)/System/Library/LaunchDaemons install -c -m 444 com.apple.syslogd.plist $(DSTROOT)/System/Library/LaunchDaemons + mkdir -p $(DSTROOT)/usr/share/sandbox + install -m 644 syslogd.sb $(DSTROOT)/usr/share/sandbox diff --git a/syslogd.tproj/Makefile.preamble b/syslogd.tproj/Makefile.preamble index 1dca380..890cc27 100644 --- a/syslogd.tproj/Makefile.preamble +++ b/syslogd.tproj/Makefile.preamble @@ -16,7 +16,7 @@ ## (e.g. change -O to -O2), see Makefile.postamble. # Flags passed to compiler (in addition to -g, -O, etc) -OTHER_CFLAGS = -DINET6 +OTHER_CFLAGS = -DINET6 -I/System/Library/Frameworks/System.framework/PrivateHeaders # Flags passed to ld (in addition to -ObjC, etc.) OTHER_LDFLAGS = @@ -92,6 +92,7 @@ CLEAN_ALL_SUBPROJECTS = MSGFILES = # .defs files that should have mig run on them DEFSFILES = + # .mig files (no .defs files) that should have mig run on them MIGFILES = diff --git a/syslogd.tproj/asl.conf.5 b/syslogd.tproj/asl.conf.5 new file mode 100644 index 0000000..5d506b9 --- /dev/null +++ b/syslogd.tproj/asl.conf.5 @@ -0,0 +1,120 @@ +.Dd December 22, 2005 +.Dt asl.conf 5 +.Os "Mac OS X" +.Sh NAME +.Nm asl.conf +.Nd configuration file for +.Xr syslogd 8 +asl_action module. +.Sh DESCRIPTION +The +.Xr syslogd 8 +server in Mac OS X includes a module that compares messages with a set of query patterns, +and which performs various actions when messages match the query patterns. +.Pp +Each line in the file contains three components. +The first is a query, the second is an action, and the third contains parameters specific to that action. +For example: +.Pp +.Dl Q [= Sender foobar] [N< Level 3] notify com.apple.foobar +.Pp +.Ss Queries +Queries start with the letter "Q" followed by whitespace. +Following that are any number of message matching components, each of which has the form: +.Pp +.Dl [OP KEY VAL] +.Pp +OP is a comparison operator. +It can have the following values: +.Pp +.Bl -tag -width "<= " -compact -offset indent +.It T +true (always matches) +.It = +equal +.It ! +not equal +.It > +greater than +.It >= +greater than or equal to +.It < +less than +.It <= +less than or equal to +.El +.Pp +It can also be preceded by one or more modifiers: +.Bl -tag -width "C " -compact -offset indent +.Pp +.It C +casefold +.It N +numeric comparison +.It S +substring +.It A +prefix +.It Z +suffix +.El +.Pp +KEY and VAL are message keys and values. +For example +.Pp +.Dl Q [= Sender foobar] +.Pp +matches any message with key="Sender" and val="foobar". +The query +.Pp +.Dl Q [CA= Color gr] +.Pp +matches any message with key=Color and val beginning with the letters GR, Gr, gr, or gR +(C meaning casefold, A meaning prefix). +The example query above, +.Pp +.Dl Q [= Sender foobar] [N< Level 3] +.Pp +matches any message from "foobar" with a level numerically less than 3 +(string values are converted to integers, and the comparison is done on the integer values). +.Pp +The "T" operator is useful to test for the presence of a particular key. +.Pp +.Dl Q [T Flavor whatever] +.Pp +Will match any message that has a "Flavor" key, regardless of its value. +.Pp +.Ss Actions +The "notify" action causes +.Nm syslogd +to post a notification with +.Fn notify_post . +The notification key must appear as a single parameter following the "notify" action. +.Pp +The "access" action sets read access controls for messages that match the associated query pattern. +.Nm syslogd +will restrict read access to matching messages to a specific user and group. +The user ID number and group ID number must follow the "access" keyword as parameters. +.Pp +The "store" action saves matching messages in a separate log message database. +The database may be accessed using the +.Nm syslog +command line utility. +A database pathname must follow the "store" keyword. +A new database will be created if one does not exist. +Two optional parameters, "stayopen" and "exclude_asldb" may follow the database pathname. +.Pp +By default, +.Nm syslogd +will open the database, save a matching message, and then close the database. +If a high volume of messages is expected, specifying "stayopen" will improve performance. +.Pp +Specifying "exclude_asldb" will cause syslogd to save matching messages in the database, +but exclude them from the main +.Nm syslogd +database (/var/log/asl.db). +.Sh SEE ALSO +.Xr asl 3 , +.Xr notify 3 , +.Xr syslog 1 , +.Xr syslogd 8 . diff --git a/syslogd.tproj/asl_action.c b/syslogd.tproj/asl_action.c index 92b7da1..699413f 100644 --- a/syslogd.tproj/asl_action.c +++ b/syslogd.tproj/asl_action.c @@ -3,21 +3,20 @@ * * @APPLE_LICENSE_HEADER_START@ * - * "Portions Copyright (c) 2004 Apple Computer, Inc. All Rights - * Reserved. This file contains Original Code and/or Modifications of - * Original Code as defined in and that are subject to the Apple Public - * Source License Version 1.0 (the 'License'). You may not use this file - * except in compliance with the License. Please obtain a copy of the - * License at http://www.apple.com/publicsource and read it before using - * this file. + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT. Please see the - * License for the specific language governing rights and limitations - * under the License." + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ @@ -35,6 +34,7 @@ #include #include #include +#include #include "daemon.h" #define _PATH_ASL_CONF "/etc/asl.conf" @@ -44,6 +44,9 @@ #define IndexNull ((uint32_t)-1) #define forever for(;;) +#define ACT_STORE_FLAG_STAY_OPEN 0x00000001 +#define ACT_STORE_FLAG_EXCLUDE_ASLDB 0x00000002 + static asl_msg_t *query = NULL; static int reset = 0; @@ -52,9 +55,17 @@ struct action_rule asl_msg_t *query; char *action; char *options; + void *data; TAILQ_ENTRY(action_rule) entries; }; +struct store_data +{ + asl_store_t *store; + char *path; + uint32_t flags; +}; + static TAILQ_HEAD(cr, action_rule) asl_action_rule; int asl_action_close(); @@ -165,6 +176,68 @@ _parse_line(char *s) return 0; } +static char * +_next_word(char **s) +{ + char *a, *p, *e, *out; + int quote, len; + + if (s == NULL) return NULL; + if (*s == NULL) return NULL; + + quote = 0; + + p = *s; + a = p; + e = p; + + while (*p != '\0') + { + if (*p == '\\') + { + p++; + e = p; + + if (*p == '\0') + { + p--; + break; + } + + p++; + e = p; + continue; + } + + if (*p == '"') + { + if (quote == 0) quote = 1; + else quote = 0; + } + + if (((*p == ' ') || (*p == '\t')) && (quote == 0)) + { + e = p + 1; + break; + } + + p++; + e = p; + } + + *s = e; + + len = p - a; + if (len == 0) return NULL; + + out = malloc(len + 1); + if (out == NULL) return NULL; + + memcpy(out, a, len); + out[len] = '\0'; + return out; +} + static void _act_notify(struct action_rule *r) { @@ -173,12 +246,96 @@ _act_notify(struct action_rule *r) notify_post(r->options); } +static void +_act_access_control(struct action_rule *r, asl_msg_t *msg) +{ + int32_t ruid, rgid; + char *p; + + ruid = atoi(r->options); + rgid = -1; + p = strchr(r->options, ' '); + if (p == NULL) p = strchr(r->options, '\t'); + if (p != NULL) + { + *p = '\0'; + p++; + rgid = atoi(p); + } + + if (ruid != -1) asl_set((aslmsg)msg, ASL_KEY_READ_UID, r->options); + if (p != NULL) + { + if (rgid != -1) asl_set((aslmsg)msg, ASL_KEY_READ_GID, p); + p--; + *p = ' '; + } +} + +static void +_act_store(struct action_rule *r, asl_msg_t *msg) +{ + struct store_data *sd; + asl_store_t *s; + char *p, *opts; + uint32_t status; + uint64_t msgid; + + if (r == NULL) return; + if (r->options == NULL) return; + if (r->data == NULL) + { + /* Set up store data */ + sd = (struct store_data *)calloc(1, sizeof(struct store_data)); + if (sd == NULL) return; + + opts = r->options; + sd->store = NULL; + sd->path = _next_word(&opts); + if (sd->path == NULL) + { + free(sd); + return; + } + + sd->flags = 0; + while (NULL != (p = _next_word(&opts))) + { + if (!strcmp(p, "stayopen")) sd->flags |= ACT_STORE_FLAG_STAY_OPEN; + else if (!strcmp(p, "exclude_asldb")) sd->flags |= ACT_STORE_FLAG_EXCLUDE_ASLDB; + free(p); + p = NULL; + } + } + else + { + sd = (struct store_data *)r->data; + } + + if (sd->store == NULL) + { + s = NULL; + status = asl_store_open(sd->path, 0, &s); + if (status != ASL_STATUS_OK) return; + if (s == NULL) return; + sd->store = s; + } + + asl_store_save(sd->store, msg, -1, -1, &msgid); + if (!(sd->flags & ACT_STORE_FLAG_STAY_OPEN)) + { + asl_store_close(sd->store); + sd->store = NULL; + } + + if (sd->flags & ACT_STORE_FLAG_EXCLUDE_ASLDB) asl_set(msg, ASL_KEY_IGNORE, "Yes"); +} + int asl_action_sendmsg(asl_msg_t *msg, const char *outid) { struct action_rule *r; - if (reset != 0) { _do_reset(); @@ -192,7 +349,9 @@ asl_action_sendmsg(asl_msg_t *msg, const char *outid) if (asl_msg_cmp(r->query, msg) == 1) { if (r->action == NULL) continue; - if (!strcmp(r->action, "notify")) _act_notify(r); + else if (!strcmp(r->action, "access")) _act_access_control(r, msg); + else if (!strcmp(r->action, "notify")) _act_notify(r); + else if (!strcmp(r->action, "store")) _act_store(r, msg); } } @@ -245,12 +404,21 @@ int asl_action_close(void) { struct action_rule *r, *n; + struct store_data *sd; n = NULL; for (r = asl_action_rule.tqh_first; r != NULL; r = n) { n = r->entries.tqe_next; + if ((!strcmp(r->action, "store")) && (r->data != NULL)) + { + sd = (struct store_data *)r->data; + if (sd->store != NULL) asl_store_close(sd->store); + if (sd->path != NULL) free(sd->path); + free(r->data); + } + if (r->query != NULL) asl_free(r->query); if (r->action != NULL) free(r->action); if (r->options != NULL) free(r->options); diff --git a/syslogd.tproj/asl_in.c b/syslogd.tproj/asl_in.c index 7a1723e..bc5b2b8 100644 --- a/syslogd.tproj/asl_in.c +++ b/syslogd.tproj/asl_in.c @@ -3,21 +3,20 @@ * * @APPLE_LICENSE_HEADER_START@ * - * "Portions Copyright (c) 2004 Apple Computer, Inc. All Rights - * Reserved. This file contains Original Code and/or Modifications of - * Original Code as defined in and that are subject to the Apple Public - * Source License Version 1.0 (the 'License'). You may not use this file - * except in compliance with the License. Please obtain a copy of the - * License at http://www.apple.com/publicsource and read it before using - * this file. + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT. Please see the - * License for the specific language governing rights and limitations - * under the License." + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ @@ -37,10 +36,10 @@ #define forever for(;;) +#define ASL_SOCKET_NAME "AppleSystemLogger" #define MY_ID "asl" static int sock = -1; -static FILE *aslfile = NULL; static asl_msg_t *query = NULL; extern int asl_log_filter; @@ -52,188 +51,9 @@ extern int asl_log_filter; extern int prune; -static int filter_token = -1; - -struct prune_query_entry -{ - asl_msg_t *query; - TAILQ_ENTRY(prune_query_entry) entries; -}; - -static TAILQ_HEAD(pql, prune_query_entry) pquery; - -static int -_search_next(FILE *log, char **outstr) -{ - char *str; - aslmsg m; - int match, i; - struct prune_query_entry *p; - - *outstr = NULL; - - if (log == NULL) return MATCH_EOF; - - str = get_line_from_file(log); - if (str == NULL) return MATCH_EOF; - - m = asl_msg_from_string(str); - if (m == NULL) - { - free(str); - return MATCH_NULL; - } - - *outstr = str; - - for (i = 0, p = pquery.tqh_first; p != NULL; p = p->entries.tqe_next, i++) - { - match = asl_msg_cmp(p->query, m); - if (match == 1) - { - asl_free(m); - return MATCH_TRUE; - } - } - - asl_free(m); - return MATCH_FALSE; -} - -/* - * Pruning the main output file (asl.log) - * - * The prune file (_PATH_ASL_PRUNE) is set up by the syslog command-line utiliy. - * It contains a set of queries. The main output file is read, and each - * message is matched against these queries. If any query matches, we save - * that message. Anything that doesn't match is discarded. - * - */ -int -asl_prune(asl_msg_t *inq) -{ - char *pname, *str; - FILE *pfile, *qfile; - struct prune_query_entry *p, *n; - asl_msg_t *q; - int status, incount, outcount; - - asldebug("syslogd: pruning %s\n", _PATH_ASL_OUT); - - if (inq != NULL) - { - TAILQ_INIT(&pquery); - p = (struct prune_query_entry *)calloc(1, sizeof(struct prune_query_entry)); - if (p == NULL) return -1; +extern void db_enqueue(asl_msg_t *m); - p->query = inq; - TAILQ_INSERT_TAIL(&pquery, p, entries); - } - else - { - qfile = fopen(_PATH_ASL_PRUNE, "r"); - if (qfile == NULL) - { - asldebug("syslogd: can't read %s: %s\n", _PATH_ASL_PRUNE, strerror(errno)); - return 0; - } - - TAILQ_INIT(&pquery); - - forever - { - str = get_line_from_file(qfile); - if (str == NULL) break; - - q = asl_msg_from_string(str); - asldebug("syslogd: prune line %s\n", str); - - free(str); - if (q == NULL) continue; - - if (q->type != ASL_TYPE_QUERY) - { - asl_free(q); - continue; - } - - p = (struct prune_query_entry *)calloc(1, sizeof(struct prune_query_entry)); - if (p == NULL) return -1; - - p->query = q; - TAILQ_INSERT_TAIL(&pquery, p, entries); - } - } - - pname = NULL; - asprintf(&pname, "%s.%d", _PATH_ASL_OUT, getpid()); - if (pname == NULL) return -1; - - pfile = fopen(pname, "w"); - if (pfile == NULL) - { - asldebug("syslogd: can't write %s: %s\n", pname, strerror(errno)); - free(pname); - return -1; - } - - fclose(aslfile); - aslfile = fopen(_PATH_ASL_OUT, "r"); - if (aslfile == NULL) - { - asldebug("syslogd: can't read %s: %s\n", _PATH_ASL_OUT, strerror(errno)); - free(pname); - aslfile = fopen(_PATH_ASL_OUT, "a"); - return -1; - } - - incount = 0; - outcount = 0; - - do - { - str = NULL; - incount++; - status = _search_next(aslfile, &str); - - /* - * Pruning deletes records that match the search. - * If the match fails, we keep the record. - */ - if (status == MATCH_FALSE) - { - outcount++; - fprintf(pfile, "%s\n", str); - } - - if (str != NULL) free(str); - } - while (status != MATCH_EOF); - - fclose(pfile); - fclose(aslfile); - - unlink(_PATH_ASL_OUT); - rename(pname, _PATH_ASL_OUT); - free(pname); - unlink(_PATH_ASL_PRUNE); - aslfile = fopen(_PATH_ASL_OUT, "a"); - - n = NULL; - for (p = pquery.tqh_first; p != NULL; p = n) - { - n = p->entries.tqe_next; - - if (p->query != NULL) asl_free(p->query); - - TAILQ_REMOVE(&pquery, p, entries); - free(p); - } - - asldebug("syslogd: prune %d records in, %d records out\n", incount, outcount); - - return 0; -} +static int filter_token = -1; asl_msg_t * asl_in_getmsg(int fd) @@ -241,14 +61,24 @@ asl_in_getmsg(int fd) char *out; asl_msg_t *m; uint32_t len, n; - char ls[16]; + char tmp[16]; + int status; + uid_t uid; + gid_t gid; - n = read(fd, ls, 11); + n = read(fd, tmp, 11); if (n < 11) { - if (n <= 0) + if (n == 0) + { + close(fd); + aslevent_removefd(fd); + return NULL; + } + + if (n < 0) { - asldebug("%s: read error (len): %s\n", MY_ID, strerror(errno)); + asldebug("%s: read error (len=%d): %s\n", MY_ID, n, strerror(errno)); if (errno != EINTR) { close(fd); @@ -260,8 +90,9 @@ asl_in_getmsg(int fd) return NULL; } - len = atoi(ls); - asldebug("%s: expecting message length %d bytes\n", MY_ID, len); + len = atoi(tmp); + if (len == 0) return NULL; + out = malloc(len); if (out == NULL) return NULL; @@ -281,7 +112,25 @@ asl_in_getmsg(int fd) } } + asldebug("asl_in_getmsg: %s\n", (out == NULL) ? "NULL" : out); + + uid = -2; + gid = -2; + + status = getpeereid(fd, &uid, &gid); m = asl_msg_from_string(out); + if (m == NULL) + { + free(out); + return NULL; + } + + snprintf(tmp, sizeof(tmp), "%d", uid); + asl_set(m, ASL_KEY_UID, tmp); + + snprintf(tmp, sizeof(tmp), "%d", gid); + asl_set(m, ASL_KEY_GID, tmp); + free(out); return m; } @@ -307,19 +156,17 @@ asl_in_acceptmsg(int fd) return NULL; } - aslevent_addfd(clientfd, asl_in_getmsg, NULL, NULL); + aslevent_addfd(clientfd, ADDFD_FLAGS_LOCAL, asl_in_getmsg, NULL, NULL); return NULL; } int aslmod_sendmsg(asl_msg_t *msg, const char *outid) { - const char *vlevel; - char *mstr; - uint32_t n, lmask; - int status, x, level; - - if (aslfile == NULL) return -1; + const char *vlevel, *facility, *ignore; + uint32_t lmask; + uint64_t v64; + int status, x, level, log_me; /* set up com.apple.syslog.asl_filter */ if (filter_token == -1) @@ -332,7 +179,11 @@ aslmod_sendmsg(asl_msg_t *msg, const char *outid) else { status = notify_check(filter_token, &x); - if (status == NOTIFY_STATUS_OK) status = notify_set_state(filter_token, asl_log_filter); + if (status == NOTIFY_STATUS_OK) + { + v64 = asl_log_filter; + status = notify_set_state(filter_token, v64); + } if (status != NOTIFY_STATUS_OK) { notify_cancel(filter_token); @@ -343,66 +194,89 @@ aslmod_sendmsg(asl_msg_t *msg, const char *outid) if (filter_token >= 0) { - x = 1; + x = 0; status = notify_check(filter_token, &x); if ((status == NOTIFY_STATUS_OK) && (x == 1)) { - x = asl_log_filter; - status = notify_get_state(filter_token, &x); - if ((status == NOTIFY_STATUS_OK) && (x != 0)) asl_log_filter = x; + v64 = 0; + status = notify_get_state(filter_token, &v64); + if ((status == NOTIFY_STATUS_OK) && (v64 != 0)) asl_log_filter = v64; } } - vlevel = asl_get(msg, ASL_KEY_LEVEL); - level = 7; - if (vlevel != NULL) level = atoi(vlevel); - lmask = ASL_FILTER_MASK(level); - if ((lmask & asl_log_filter) == 0) return 0; + log_me = 0; + facility = asl_get(msg, ASL_KEY_FACILITY); + if ((facility != NULL) && (!strcmp(facility, "kern"))) log_me = 1; + else + { + vlevel = asl_get(msg, ASL_KEY_LEVEL); + level = 7; + if (vlevel != NULL) level = atoi(vlevel); + lmask = ASL_FILTER_MASK(level); + if ((lmask & asl_log_filter) != 0) log_me = 1; + } - mstr = asl_msg_to_string(msg, &n); - if (mstr != NULL) + if (log_me == 1) { - fprintf(aslfile, "%s\n", mstr); - fflush(aslfile); - free(mstr); + ignore = asl_get(msg, ASL_KEY_IGNORE); + if ((ignore != NULL) && (!strcasecmp(ignore, "yes"))) log_me = 0; } + if (log_me == 1) db_enqueue(msg); + return 0; } int asl_in_init(void) { - struct sockaddr_un sun; int rbufsize; int len; + launch_data_t sockets_dict, fd_array, fd_dict; asldebug("%s: init\n", MY_ID); if (sock >= 0) return sock; + if (launch_dict == NULL) + { + asldebug("%s: laucnchd dict is NULL\n", MY_ID); + return -1; + } - unlink(_PATH_ASL_IN); - sock = socket(AF_UNIX, SOCK_STREAM, 0); - if (sock < 0) + sockets_dict = launch_data_dict_lookup(launch_dict, LAUNCH_JOBKEY_SOCKETS); + if (sockets_dict == NULL) { - asldebug("%s: couldn't create socket for %s: %s\n", MY_ID, _PATH_ASL_IN, strerror(errno)); + asldebug("%s: laucnchd lookup of LAUNCH_JOBKEY_SOCKETS failed\n", MY_ID); return -1; } - asldebug("%s: creating %s for fd %d\n", MY_ID, _PATH_ASL_IN, sock); + fd_array = launch_data_dict_lookup(sockets_dict, ASL_SOCKET_NAME); + if (fd_array == NULL) + { + asldebug("%s: laucnchd lookup of ASL_SOCKET_NAME failed\n", MY_ID); + return -1; + } + + len = launch_data_array_get_count(fd_array); + if (len <= 0) + { + asldebug("%s: laucnchd fd array is empty\n", MY_ID); + return -1; + } - memset(&sun, 0, sizeof(sun)); - sun.sun_family = AF_UNIX; - strcpy(sun.sun_path, _PATH_ASL_IN); + if (len > 1) + { + asldebug("%s: warning! laucnchd fd array has %d sockets\n", MY_ID, len); + } - len = sizeof(struct sockaddr_un); - if (bind(sock, (struct sockaddr *)&sun, len) < 0) + fd_dict = launch_data_array_get_index(fd_array, 0); + if (fd_dict == NULL) { - asldebug("%s: couldn't bind socket %d for %s: %s\n", MY_ID, sock, _PATH_ASL_IN, strerror(errno)); - close(sock); - sock = -1; + asldebug("%s: laucnchd file discriptor array element 0 is NULL\n", MY_ID); return -1; } + sock = launch_data_get_fd(fd_dict); + rbufsize = 128 * 1024; len = sizeof(rbufsize); @@ -413,15 +287,6 @@ asl_in_init(void) sock = -1; return -1; } - - if (listen(sock, SOMAXCONN) < 0) - { - asldebug("%s: couldn't listen on socket %d for %s: %s\n", MY_ID, sock, _PATH_ASL_IN, strerror(errno)); - close(sock); - sock = -1; - unlink(_PATH_ASL_IN); - return -1; - } if (fcntl(sock, F_SETFL, O_NONBLOCK) < 0) { @@ -431,18 +296,11 @@ asl_in_init(void) return -1; } - chmod(_PATH_ASL_IN, 0666); - - /* Add logger routine for main output file */ - aslfile = fopen(_PATH_ASL_OUT, "a"); - if (aslfile != NULL) - { - query = asl_new(ASL_TYPE_QUERY); - aslevent_addmatch(query, MY_ID); - aslevent_addoutput(aslmod_sendmsg, MY_ID); - } + query = asl_new(ASL_TYPE_QUERY); + aslevent_addmatch(query, MY_ID); + aslevent_addoutput(aslmod_sendmsg, MY_ID); - return aslevent_addfd(sock, asl_in_acceptmsg, NULL, NULL); + return aslevent_addfd(sock, ADDFD_FLAGS_LOCAL, asl_in_acceptmsg, NULL, NULL); } int @@ -462,7 +320,6 @@ asl_in_close(void) asl_free(query); close(sock); - if (aslfile != NULL) fclose(aslfile); unlink(_PATH_ASL_IN); return 0; diff --git a/syslogd.tproj/bsd_in.c b/syslogd.tproj/bsd_in.c index d1ccd50..75727fb 100644 --- a/syslogd.tproj/bsd_in.c +++ b/syslogd.tproj/bsd_in.c @@ -3,21 +3,20 @@ * * @APPLE_LICENSE_HEADER_START@ * - * "Portions Copyright (c) 2004 Apple Computer, Inc. All Rights - * Reserved. This file contains Original Code and/or Modifications of - * Original Code as defined in and that are subject to the Apple Public - * Source License Version 1.0 (the 'License'). You may not use this file - * except in compliance with the License. Please obtain a copy of the - * License at http://www.apple.com/publicsource and read it before using - * this file. + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT. Please see the - * License for the specific language governing rights and limitations - * under the License." + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ @@ -35,8 +34,9 @@ #include #include "daemon.h" +#define BSD_SOCKET_NAME "BSDSystemLogger" #define MY_ID "bsd_in" -#define MAXLINE 1024 +#define MAXLINE 4096 static int sock = -1; @@ -54,45 +54,64 @@ bsd_in_acceptmsg(int fd) if (n <= 0) return NULL; line[n] = '\0'; - return asl_syslog_input_convert(line, n, NULL, 0); + + return asl_input_parse(line, n, NULL, 0); } int bsd_in_init(void) { - struct sockaddr_un sun; int rbufsize; int len; + launch_data_t sockets_dict, fd_array, fd_dict; asldebug("%s: init\n", MY_ID); if (sock >= 0) return sock; - unlink(_PATH_SYSLOG_IN); - sock = socket(AF_UNIX, SOCK_DGRAM, 0); - if (sock < 0) + if (launch_dict == NULL) { - asldebug("%s: couldn't create socket for %s: %s\n", MY_ID, _PATH_SYSLOG_IN, strerror(errno)); + asldebug("%s: laucnchd dict is NULL\n", MY_ID); return -1; } - asldebug("%s: creating %s for fd %d\n", MY_ID, _PATH_SYSLOG_IN, sock); + sockets_dict = launch_data_dict_lookup(launch_dict, LAUNCH_JOBKEY_SOCKETS); + if (sockets_dict == NULL) + { + asldebug("%s: laucnchd lookup of LAUNCH_JOBKEY_SOCKETS failed\n", MY_ID); + return -1; + } - memset(&sun, 0, sizeof(sun)); - sun.sun_family = AF_UNIX; - strcpy(sun.sun_path, _PATH_SYSLOG_IN); + fd_array = launch_data_dict_lookup(sockets_dict, BSD_SOCKET_NAME); + if (fd_array == NULL) + { + asldebug("%s: laucnchd lookup of BSD_SOCKET_NAME failed\n", MY_ID); + return -1; + } - len = sizeof(struct sockaddr_un); - if (bind(sock, (struct sockaddr *)&sun, len) < 0) + len = launch_data_array_get_count(fd_array); + if (len <= 0) { - asldebug("%s: couldn't bind socket %d for %s: %s\n", MY_ID, sock, _PATH_SYSLOG_IN, strerror(errno)); - close(sock); - sock = -1; + asldebug("%s: laucnchd fd array is empty\n", MY_ID); return -1; } + if (len > 1) + { + asldebug("%s: warning! laucnchd fd array has %d sockets\n", MY_ID, len); + } + + fd_dict = launch_data_array_get_index(fd_array, 0); + if (fd_dict == NULL) + { + asldebug("%s: laucnchd file discriptor array element 0 is NULL\n", MY_ID); + return -1; + } + + sock = launch_data_get_fd(fd_dict); + rbufsize = 128 * 1024; len = sizeof(rbufsize); - + if (setsockopt(sock, SOL_SOCKET, SO_RCVBUF, &rbufsize, len) < 0) { asldebug("%s: couldn't set receive buffer size for socket %d (%s): %s\n", MY_ID, sock, _PATH_SYSLOG_IN, strerror(errno)); @@ -109,9 +128,7 @@ bsd_in_init(void) return -1; } - chmod(_PATH_SYSLOG_IN, 0666); - - return aslevent_addfd(sock, bsd_in_acceptmsg, NULL, NULL); + return aslevent_addfd(sock, ADDFD_FLAGS_LOCAL, bsd_in_acceptmsg, NULL, NULL); } int diff --git a/syslogd.tproj/bsd_out.c b/syslogd.tproj/bsd_out.c index 4b16bc9..7da14d8 100644 --- a/syslogd.tproj/bsd_out.c +++ b/syslogd.tproj/bsd_out.c @@ -3,21 +3,20 @@ * * @APPLE_LICENSE_HEADER_START@ * - * "Portions Copyright (c) 2004 Apple Computer, Inc. All Rights - * Reserved. This file contains Original Code and/or Modifications of - * Original Code as defined in and that are subject to the Apple Public - * Source License Version 1.0 (the 'License'). You may not use this file - * except in compliance with the License. Please obtain a copy of the - * License at http://www.apple.com/publicsource and read it before using - * this file. + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT. Please see the - * License for the specific language governing rights and limitations - * under the License." + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ @@ -56,6 +55,9 @@ static asl_msg_t *query = NULL; static int reset = 0; +extern uint64_t bsd_flush_time; +extern uint64_t bsd_max_dup_time; + struct config_rule { uint32_t count; @@ -65,11 +67,17 @@ struct config_rule struct sockaddr *addr; char **facility; int *pri; + uint32_t last_hash; + uint32_t last_count; + time_t last_time; + char *last_msg; TAILQ_ENTRY(config_rule) entries; }; static TAILQ_HEAD(cr, config_rule) bsd_out_rule; +extern uint32_t string_hash(const char *s, uint32_t inlen); + int bsd_out_close(); static int _parse_config_file(const char *); @@ -200,12 +208,11 @@ _syslog_dst_open(struct config_rule *r) struct addrinfo hints, *gai, *ai; if (r == NULL) return -1; - - r->fd = -1; + if (r->fd != -1) return 0; if (r->dst[0] == '/') { - r->fd = open(r->dst, O_WRONLY | O_APPEND | O_CREAT, 0644); + r->fd = open(r->dst, O_WRONLY | O_APPEND | O_CREAT | O_NOCTTY, 0644); if (r->fd < 0) { asldebug("%s: open failed for file: %s (%s)\n", MY_ID, r->dst, strerror(errno)); @@ -221,6 +228,7 @@ _syslog_dst_open(struct config_rule *r) if (r->dst[0] == '!') { r->type = DST_TYPE_NOTE; + r->fd = 0; return 0; } @@ -282,6 +290,7 @@ _syslog_dst_open(struct config_rule *r) if (strcmp(r->dst, "*") == 0) { r->type = DST_TYPE_WALL; + r->fd = 0; return 0; } @@ -290,6 +299,41 @@ _syslog_dst_open(struct config_rule *r) return -1; } +static void +_syslog_dst_close(struct config_rule *r) +{ + if (r == NULL) return; + + switch (r->type) + { + case DST_TYPE_FILE: + case DST_TYPE_CONS: + { + if (r->fd >= 0) close(r->fd); + r->fd = -1; + break; + } + + case DST_TYPE_SOCK: + { + if (r->fd >= 0) close(r->fd); + r->fd = -1; + if (r->addr != NULL) free(r->addr); + r->addr = NULL; + break; + } + + case DST_TYPE_NONE: + case DST_TYPE_WALL: + case DST_TYPE_NOTE: + default: + { + /* do nothing */ + return; + } + } +} + static int _parse_line(char *s) { @@ -306,6 +350,7 @@ _parse_line(char *s) if (semi == NULL) return -1; out = (struct config_rule *)calloc(1, sizeof(struct config_rule)); if (out == NULL) return -1; + out->fd = -1; n = 0; lasts = -1; @@ -319,8 +364,6 @@ _parse_line(char *s) out->dst = strdup(semi[lasts]); if (out->dst == NULL) return -1; - _syslog_dst_open(out); - for (i = 0; i < lasts; i++) { if (semi[i][0] == '\0') continue; @@ -351,7 +394,7 @@ _parse_line(char *s) if (out->facility == NULL) return -1; if (out->pri == NULL) return -1; - + out->facility[out->count] = strdup(comma[j]); if (out->facility[out->count] == NULL) return -1; @@ -375,27 +418,30 @@ bsd_log_string(const char *msg) uint32_t i, len, outlen; char *out, *q; uint8_t c; - + if (msg == NULL) return NULL; - + len = strlen(msg); - + while ((len > 0) && (msg[len - 1] == '\n')) len--; + + if (len == 0) return NULL; + outlen = len + 1; for (i = 0; i < len; i++) { c = msg[i]; if (isascii(c) && iscntrl(c) && (c != '\t')) outlen++; } - + out = malloc(outlen); if (out == NULL) return NULL; q = out; - + for (i = 0; i < len; i++) { c = msg[i]; - + if (isascii(c) && iscntrl(c)) { if (c == '\n') @@ -418,24 +464,77 @@ bsd_log_string(const char *msg) *q++ = c; } } - + *q = '\0'; - + return out; } static int -_syslog_send(asl_msg_t *msg, struct config_rule *r, char **out, char **fwd) +_syslog_send_repeat_msg(struct config_rule *r) +{ + char vt[16], *p, *msg; + time_t tick; + int len, status; + + if (r == NULL) return -1; + if (r->type != DST_TYPE_FILE) return 0; + if (r->last_count == 0) return 0; + + tick = time(NULL); + p = ctime(&tick); + if (p == NULL) return -1; + + memcpy(vt, p+4, 15); + vt[15] = '\0'; + + msg = NULL; + asprintf(&msg, "%s: --- last message repeated %u time%s ---\n", vt, r->last_count, (r->last_count == 1) ? "" : "s"); + if (msg == NULL) return -1; + + len = strlen(msg); + status = write(r->fd, msg, len); + if ((status < 0) || (status < len)) + { + asldebug("%s: error writing repeat message (%s): %s\n", MY_ID, r->dst, strerror(errno)); + + /* Try re-opening the file (once) and write again */ + close(r->fd); + r->fd = open(r->dst, O_WRONLY | O_APPEND | O_CREAT | O_NOCTTY, 0644); + if (r->fd < 0) + { + asldebug("%s: re-open failed for file: %s (%s)\n", MY_ID, r->dst, strerror(errno)); + free(msg); + return -1; + } + + status = write(r->fd, msg, len); + if ((status < 0) || (status < len)) + { + asldebug("%s: error re-writing message (%s): %s\n", MY_ID, r->dst, strerror(errno)); + free(msg); + return -1; + } + } + + free(msg); + return 0; +} + +static int +_syslog_send(asl_msg_t *msg, struct config_rule *r, char **out, char **fwd, time_t now) { char *so, *sf, *vt, *p, *outmsg; - const char *vtime, *vhost, *vident, *vpid, *vmsg, *vlevel, *vfacility; + const char *vtime, *vhost, *vident, *vpid, *vmsg, *vlevel, *vfacility, *vrefproc, *vrefpid; size_t outlen, n; - int pf, fc, status; - FILE *pw; time_t tick; + int pf, fc, status, is_dup, do_write; + FILE *pw; + uint32_t msg_hash; if (out == NULL) return -1; if (fwd == NULL) return -1; + if (r == NULL) return -1; if (r->type == DST_TYPE_NOTE) { @@ -443,6 +542,7 @@ _syslog_send(asl_msg_t *msg, struct config_rule *r, char **out, char **fwd) return 0; } + msg_hash = 0; vt = NULL; outmsg = NULL; @@ -462,24 +562,24 @@ _syslog_send(asl_msg_t *msg, struct config_rule *r, char **out, char **fwd) memcpy(vt, p+4, 15); vt[15] = '\0'; } - } - else if (strlen(vtime) < 24) - { - vt = strdup(vtime); - if (vt == NULL) return -1; - } - else - { - vt = malloc(16); - if (vt == NULL) return -1; + else if (strlen(vtime) < 24) + { + vt = strdup(vtime); + if (vt == NULL) return -1; + } + else + { + vt = malloc(16); + if (vt == NULL) return -1; - memcpy(vt, vtime+4, 15); - vt[15] = '\0'; + memcpy(vt, vtime+4, 15); + vt[15] = '\0'; + } } if (vt == NULL) { - tick = time(NULL); + tick = now; p = ctime(&tick); vt = malloc(16); if (vt == NULL) return -1; @@ -499,19 +599,46 @@ _syslog_send(asl_msg_t *msg, struct config_rule *r, char **out, char **fwd) if ((vpid != NULL) && (vident == NULL)) vident = "Unknown"; + vrefproc = asl_get(msg, ASL_KEY_REF_PROC); + vrefpid = asl_get(msg, ASL_KEY_REF_PID); + vmsg = asl_get(msg, ASL_KEY_MSG); if (vmsg != NULL) outmsg = bsd_log_string(vmsg); - + n = 0; + /* Time + " " */ if (vt != NULL) n += (strlen(vt) + 1); + + /* Host + " " */ if (vhost != NULL) n += (strlen(vhost) + 1); + + /* Sender */ if (vident != NULL) n += strlen(vident); - n += 2; + + /* "[" PID "]" */ if (vpid != NULL) n += (strlen(vpid) + 2); - + + /* " (" */ + if ((vrefproc != NULL) || (vrefpid != NULL)) n += 2; + + /* RefProc */ + if (vrefproc != NULL) n += strlen(vrefproc); + + /* "[" RefPID "]" */ + if (vrefpid != NULL) n += (strlen(vrefpid) + 2); + + /* ")" */ + if ((vrefproc != NULL) || (vrefpid != NULL)) n += 1; + + /* ": " */ + n += 2; + + /* Message */ if (outmsg != NULL) n += strlen(outmsg); if (n == 0) return -1; + + /* "\n" + nul */ n += 2; so = calloc(1, n); @@ -540,19 +667,47 @@ _syslog_send(asl_msg_t *msg, struct config_rule *r, char **out, char **fwd) } } + if ((vrefproc != NULL) || (vrefpid != NULL)) + { + strcat(so, " ("); + + if (vrefproc != NULL) strcat(so, vrefproc); + + if (vrefpid != NULL) + { + strcat(so, "["); + strcat(so, vrefpid); + strcat(so, "]"); + } + + strcat(so, ")"); + } + strcat(so, ": "); if (outmsg != NULL) { strcat(so, outmsg); free(outmsg); - strcat(so, "\n"); } + strcat(so, "\n"); + free(vt); *out = so; } + /* check if message is a duplicate of the last message, and inside the dup time window */ + is_dup = 0; + if ((bsd_max_dup_time > 0) && (*out != NULL) && (r->last_msg != NULL)) + { + msg_hash = string_hash(*out + 16, strlen(*out + 16)); + if ((r->last_hash == msg_hash) && (!strcmp(r->last_msg, *out + 16))) + { + if ((now - r->last_time) < bsd_max_dup_time) is_dup = 1; + } + } + if ((*fwd == NULL) && (r->type == DST_TYPE_SOCK)) { pf = 7; @@ -565,6 +720,7 @@ _syslog_send(asl_msg_t *msg, struct config_rule *r, char **out, char **fwd) sf = NULL; asprintf(&sf, "<%d>%s", pf, *out); if (sf == NULL) return -1; + *fwd = sf; } @@ -572,8 +728,18 @@ _syslog_send(asl_msg_t *msg, struct config_rule *r, char **out, char **fwd) if (r->type == DST_TYPE_SOCK) outlen = strlen(*fwd); else outlen = strlen(*out); + _syslog_dst_open(r); + if ((r->type == DST_TYPE_FILE) || (r->type == DST_TYPE_CONS)) { + /* + * If current message is NOT a duplicate and r->last_count > 0 + * we need to write a "last message was repeated N times" log entry + */ + if ((r->type == DST_TYPE_FILE) && (is_dup == 0) && (r->last_count > 0)) _syslog_send_repeat_msg(r); + + do_write = 1; + /* * Special case for kernel messages. * Don't write kernel messages to /dev/console. @@ -581,16 +747,19 @@ _syslog_send(asl_msg_t *msg, struct config_rule *r, char **out, char **fwd) * so writing them here would cause duplicates. */ vfacility = asl_get(msg, ASL_KEY_FACILITY); - if ((vfacility != NULL) && (!strcmp(vfacility, FACILITY_KERNEL)) && (r->type == DST_TYPE_CONS)) return 0; + if ((vfacility != NULL) && (!strcmp(vfacility, FACILITY_KERNEL)) && (r->type == DST_TYPE_CONS)) do_write = 0; + if ((do_write == 1) && (r->type == DST_TYPE_FILE) && (is_dup == 1)) do_write = 0; + + if (do_write == 0) status = outlen; + else status = write(r->fd, *out, outlen); - status = write(r->fd, *out, outlen); if ((status < 0) || (status < outlen)) { asldebug("%s: error writing message (%s): %s\n", MY_ID, r->dst, strerror(errno)); /* Try re-opening the file (once) and write again */ close(r->fd); - r->fd = open(r->dst, O_WRONLY | O_APPEND | O_CREAT, 0644); + r->fd = open(r->dst, O_WRONLY | O_APPEND | O_CREAT | O_NOCTTY, 0644); if (r->fd < 0) { asldebug("%s: re-open failed for file: %s (%s)\n", MY_ID, r->dst, strerror(errno)); @@ -604,7 +773,7 @@ _syslog_send(asl_msg_t *msg, struct config_rule *r, char **out, char **fwd) } } } - else if (r->type == DST_TYPE_SOCK) + else if ((r->type == DST_TYPE_SOCK) && (r->addr != NULL)) { status = sendto(r->fd, *fwd, outlen, 0, r->addr, r->addr->sa_len); if (status < 0) asldebug("%s: error sending message (%s): %s\n", MY_ID, r->dst, strerror(errno)); @@ -622,6 +791,24 @@ _syslog_send(asl_msg_t *msg, struct config_rule *r, char **out, char **fwd) pclose(pw); } + _syslog_dst_close(r); + + if (is_dup == 1) + { + r->last_count++; + } + else + { + if (r->last_msg != NULL) free(r->last_msg); + r->last_msg = NULL; + + if (*out != NULL) r->last_msg = strdup(*out + 16); + + r->last_hash = msg_hash; + r->last_count = 0; + r->last_time = now; + } + return 0; } @@ -678,6 +865,8 @@ bsd_out_sendmsg(asl_msg_t *msg, const char *outid) { struct config_rule *r; char *out, *fwd; + time_t tick; + uint64_t delta; if (reset != 0) { @@ -690,9 +879,22 @@ bsd_out_sendmsg(asl_msg_t *msg, const char *outid) out = NULL; fwd = NULL; + tick = time(NULL); + bsd_flush_time = 0; + for (r = bsd_out_rule.tqh_first; r != NULL; r = r->entries.tqe_next) { - if (_syslog_rule_match(msg, r) == 1) _syslog_send(msg, r, &out, &fwd); + if (_syslog_rule_match(msg, r) == 1) _syslog_send(msg, r, &out, &fwd, tick); + if ((r->type == DST_TYPE_FILE) && (r->last_count > 0)) + { + delta = tick - r->last_time; + if (delta < bsd_max_dup_time) + { + delta = bsd_max_dup_time - delta; + if (bsd_flush_time == 0) bsd_flush_time = delta; + else if (delta < bsd_flush_time) bsd_flush_time = delta; + } + } } if (out != NULL) free(out); @@ -701,6 +903,41 @@ bsd_out_sendmsg(asl_msg_t *msg, const char *outid) return 0; } +void +bsd_flush_duplicates() +{ + struct config_rule *r; + time_t tick; + uint64_t delta; + + tick = time(NULL); + bsd_flush_time = 0; + + for (r = bsd_out_rule.tqh_first; r != NULL; r = r->entries.tqe_next) + { + if (r->type != DST_TYPE_FILE) continue; + + if (r->last_count > 0) + { + delta = tick - r->last_time; + if (delta < bsd_max_dup_time) + { + delta = bsd_max_dup_time - delta; + if (bsd_flush_time == 0) bsd_flush_time = delta; + else if (delta < bsd_flush_time) bsd_flush_time = delta; + } + else + { + _syslog_dst_open(r); + _syslog_send_repeat_msg(r); + _syslog_dst_close(r); + + r->last_count = 0; + } + } + } +} + static int _parse_config_file(const char *confname) { @@ -753,10 +990,11 @@ bsd_out_close(void) for (r = bsd_out_rule.tqh_first; r != NULL; r = n) { n = r->entries.tqe_next; - + if (r->dst != NULL) free(r->dst); if (r->fd > 0) close(r->fd); if (r->addr != NULL) free(r->addr); + if (r->last_msg != NULL) free(r->last_msg); if (r->facility != NULL) { for (i = 0; i < r->count; i++) diff --git a/syslogd.tproj/com.apple.syslogd.plist b/syslogd.tproj/com.apple.syslogd.plist index 8d7574f..4b1854d 100644 --- a/syslogd.tproj/com.apple.syslogd.plist +++ b/syslogd.tproj/com.apple.syslogd.plist @@ -4,15 +4,59 @@ Label com.apple.syslogd - ServiceDescription - Apple System Log Daemon OnDemand ProgramArguments - /usr/sbin/syslogd + + + /usr/sbin/syslogd - ServiceIPC - + MachServices + + com.apple.system.logger + + + Sockets + + AppleSystemLogger + + SockPathName + /var/run/asl_input + SockPathMode + 438 + + BSDSystemLogger + + SockPathName + /var/run/syslog + SockType + dgram + SockPathMode + 438 + + + + diff --git a/syslogd.tproj/daemon.c b/syslogd.tproj/daemon.c index 775a0d7..bfa7e45 100644 --- a/syslogd.tproj/daemon.c +++ b/syslogd.tproj/daemon.c @@ -3,21 +3,20 @@ * * @APPLE_LICENSE_HEADER_START@ * - * "Portions Copyright (c) 2004 Apple Computer, Inc. All Rights - * Reserved. This file contains Original Code and/or Modifications of - * Original Code as defined in and that are subject to the Apple Public - * Source License Version 1.0 (the 'License'). You may not use this file - * except in compliance with the License. Please obtain a copy of the - * License at http://www.apple.com/publicsource and read it before using - * this file. + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT. Please see the - * License for the specific language governing rights and limitations - * under the License." + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ @@ -35,12 +34,48 @@ #include #define SYSLOG_NAMES #include +#include +#include +#include +#include +#include #include "daemon.h" #define streq(A,B) (strcmp(A,B)==0) +#define forever for(;;) + +#define ASL_MSG_TYPE_MASK 0x0000000f +#define ASL_TYPE_ERROR 2 + +#define ASL_KEY_FACILITY "Facility" + +#define FACILITY_USER "user" +#define FACILITY_CONSOLE "com.apple.console" +#define SYSTEM_RESERVED "com.apple.system" +#define SYSTEM_RESERVED_LEN 16 + +extern void disaster_message(asl_msg_t *m); static char myname[MAXHOSTNAMELEN + 1] = {0}; -static int gotname = 0; +extern const char *debug_file; +extern int debug; +extern time_t utmp_ttl; +extern time_t fs_ttl; +extern int kfd; + +static pthread_mutex_t event_lock = PTHREAD_MUTEX_INITIALIZER; + +static const char *kern_notify_key[] = +{ + "com.apple.system.log.kernel.emergency", + "com.apple.system.log.kernel.alert", + "com.apple.system.log.kernel.critical", + "com.apple.system.log.kernel.error", + "com.apple.system.log.kernel.warning", + "com.apple.system.log.kernel.notice", + "com.apple.system.log.kernel.info", + "com.apple.system.log.kernel.debug" +}; struct aslevent { @@ -75,6 +110,8 @@ TAILQ_HEAD(ae, aslevent) Eventq; TAILQ_HEAD(ao, asloutput) Outq; TAILQ_HEAD(am, aslmatch) Matchq; +static struct aslevent *launchd_event; + int aslevent_init(void) { @@ -112,6 +149,7 @@ aslevent_addmatch(asl_msg_t *query, char *outid) tmp = calloc(1, sizeof(struct aslmatch)); if (tmp == NULL) return -1; + tmp->query = query; tmp->outid = outid; TAILQ_INSERT_TAIL(&Matchq, tmp, entries); @@ -138,21 +176,20 @@ aslevent_match(asl_msg_t *msg) int aslevent_removefd(int fd) { - struct aslevent *i; - int found = -1; + struct aslevent *e, *next; + + e = Eventq.tqh_first; - for (i = Eventq.tqh_first; i != NULL; i = i->entries.tqe_next) + while (e != NULL) { - if (fd == i->fd) + next = e->entries.tqe_next; + if (fd == e->fd) { - asldebug("removing %d\n", i->fd); - TAILQ_REMOVE(&Eventq, i, entries); - found = 0; - if (i->sender != NULL) free(i->sender); - free(i); - i = NULL; + e->fd = -1; return 0; } + + e = next; } return -1; @@ -163,16 +200,12 @@ whatsmyhostname() { char *dot; - if (gotname != 0) return (const char *)myname; - if (gethostname(myname, MAXHOSTNAMELEN) < 0) { memset(myname, 0, sizeof(myname)); return "localhost"; } - if (strcmp(myname, "localhost")) gotname = 1; - dot = strchr(myname, '.'); if (dot != NULL) *dot = '\0'; @@ -180,14 +213,14 @@ whatsmyhostname() } int -aslevent_addfd(int fd, aslreadfn readfn, aslwritefn writefn, aslexceptfn exceptfn) +aslevent_addfd(int fd, uint32_t flags, aslreadfn readfn, aslwritefn writefn, aslexceptfn exceptfn) { struct aslevent *e; - int found = 0; + int found = 0, status; #ifdef LOCAL_PEERCRED struct xucred cr; #endif - int len; + socklen_t len; uid_t u; gid_t g; struct sockaddr_storage ss; @@ -198,34 +231,45 @@ aslevent_addfd(int fd, aslreadfn readfn, aslwritefn writefn, aslexceptfn exceptf sender = NULL; memset(&ss, 0, sizeof(struct sockaddr_storage)); + memset(str, 0, sizeof(str)); len = sizeof(struct sockaddr_storage); - if (getpeername(fd, (struct sockaddr *)&ss, &len) == 0) + if (flags & ADDFD_FLAGS_LOCAL) { - if (len == 0) - { - /* UNIX Domain socket */ - snprintf(str, sizeof(str), whatsmyhostname()); - sender = str; - } - else - { - if (inet_ntop(ss.ss_family, (struct sockaddr *)&ss, str, 256) == 0) sender = str; - } - } + snprintf(str, sizeof(str), "localhost"); + sender = str; #ifdef LOCAL_PEERCRED - len = sizeof(cr); + len = sizeof(cr); - if (getsockopt(fd, LOCAL_PEERCRED, 1, &cr, &len) == 0) + status = getsockopt(fd, LOCAL_PEERCRED, 1, &cr, &len); + if (status == 0) + { + u = cr.cr_uid; + g = cr.cr_gid; + } +#endif + } + else { - u = cr.cr_uid; - g = cr.cr_gid; + status = getpeername(fd, (struct sockaddr *)&ss, &len); + if (status == 0) + { + if (len == 0) + { + /* UNIX Domain socket */ + snprintf(str, sizeof(str), "localhost"); + sender = str; + } + else + { + if (inet_ntop(ss.ss_family, (struct sockaddr *)&ss, str, 256) == NULL) sender = str; + } + } } -#endif - asldebug("fd %d UID %d GID %d Sender %s\n", fd, u, g, (sender == NULL) ? "NULL" : sender ); + asldebug("fd %d flags 0x%08x UID %d GID %d Sender %s\n", fd, flags, u, g, (sender == NULL) ? "NULL" : sender ); for (e = Eventq.tqh_first; e != NULL; e = e->entries.tqe_next) { @@ -236,7 +280,12 @@ aslevent_addfd(int fd, aslreadfn readfn, aslwritefn writefn, aslexceptfn exceptf e->exceptfn = exceptfn; if (e->sender != NULL) free(e->sender); e->sender = NULL; - if (sender != NULL) e->sender = strdup(sender); + if (sender != NULL) + { + e->sender = strdup(sender); + if (e->sender == NULL) return -1; + } + e->uid = u; e->gid = g; found = 1; @@ -253,7 +302,12 @@ aslevent_addfd(int fd, aslreadfn readfn, aslwritefn writefn, aslexceptfn exceptf e->writefn = writefn; e->exceptfn = exceptfn; e->sender = NULL; - if (sender != NULL) e->sender = strdup(sender); + if (sender != NULL) + { + e->sender = strdup(sender); + if (e->sender == NULL) return -1; + } + e->uid = u; e->gid = g; @@ -262,22 +316,34 @@ aslevent_addfd(int fd, aslreadfn readfn, aslwritefn writefn, aslexceptfn exceptf return 0; } -int -aslmsg_verify(struct aslevent *e, asl_msg_t *msg) +static int +aslmsg_verify(struct aslevent *e, asl_msg_t *msg, int32_t *kern_post_level) { - const char *val; + const char *val, *fac; char buf[32], *timestr; - time_t tick; + time_t tick, now; struct tm gtime; + uid_t uid; + uint32_t level, fnum, kern; + int isreserved; if (msg == NULL) return -1; + *kern_post_level = -1; + + kern = 0; + if ((e != NULL) && (e->fd == kfd)) kern = 1; + /* Time */ + now = time(NULL); + tick = 0; val = asl_get(msg, ASL_KEY_TIME); if (val != NULL) tick = asl_parse_time(val); - if (tick == 0) tick = time(NULL); + /* Set time to now if it is unset or from the future (not allowed!) */ + if ((tick == 0) || (tick > now)) tick = now; + memset(>ime, 0, sizeof(struct tm)); gmtime_r(&tick, >ime); @@ -291,37 +357,49 @@ aslmsg_verify(struct aslevent *e, asl_msg_t *msg) /* Host */ if (e == NULL) asl_set(msg, ASL_KEY_HOST, whatsmyhostname()); - else if (e->sender != NULL) asl_set(msg, ASL_KEY_HOST, e->sender); - - /* Sender */ - val = asl_get(msg, ASL_KEY_SENDER); - if (val == NULL) asl_set(msg, ASL_KEY_SENDER, "Unknown"); + else if (e->sender != NULL) + { + if (!strcmp(e->sender, "localhost")) asl_set(msg, ASL_KEY_HOST, whatsmyhostname()); + else asl_set(msg, ASL_KEY_HOST, e->sender); + } /* PID */ val = asl_get(msg, ASL_KEY_PID); if (val == NULL) asl_set(msg, ASL_KEY_PID, "0"); /* UID */ + uid = -2; val = asl_get(msg, ASL_KEY_UID); - if (val == NULL) + if (kern == 1) + { + uid = 0; + asl_set(msg, ASL_KEY_UID, "0"); + } + else if (val == NULL) { if (e == NULL) asl_set(msg, ASL_KEY_UID, "-2"); else if (e->uid == 99) asl_set(msg, ASL_KEY_UID, "-2"); else { + uid = e->uid; snprintf(buf, sizeof(buf), "%d", e->uid); asl_set(msg, ASL_KEY_UID, buf); } } else if ((e != NULL) && (e->uid != 99)) { + uid = e->uid; snprintf(buf, sizeof(buf), "%d", e->uid); asl_set(msg, ASL_KEY_UID, buf); } /* GID */ val = asl_get(msg, ASL_KEY_GID); - if (val == NULL) + if (kern == 1) + { + asl_set(msg, ASL_KEY_GID, "0"); + } + else if (val == NULL) { if (e == NULL) asl_set(msg, ASL_KEY_GID, "-2"); else if (e->gid == 99) asl_set(msg, ASL_KEY_GID, "-2"); @@ -337,9 +415,83 @@ aslmsg_verify(struct aslevent *e, asl_msg_t *msg) asl_set(msg, ASL_KEY_GID, buf); } + /* Sender */ + val = asl_get(msg, ASL_KEY_SENDER); + if (val == NULL) + { + if (kern == 0) asl_set(msg, ASL_KEY_SENDER, "Unknown"); + else asl_set(msg, ASL_KEY_SENDER, "kernel"); + } + else if ((kern == 0) && (uid != 0) && (!strcmp(val, "kernel"))) + { + asl_set(msg, ASL_KEY_SENDER, "Unknown"); + } + /* Level */ val = asl_get(msg, ASL_KEY_LEVEL); - if (val == NULL) asl_set(msg, ASL_KEY_LEVEL, "7"); + level = ASL_LEVEL_DEBUG; + if ((val != NULL) && (val[1] == '\0') && (val[0] >= '0') && (val[0] <= '7')) level = val[0] - '0'; + snprintf(buf, sizeof(buf), "%d", level); + asl_set(msg, ASL_KEY_LEVEL, buf); + + /* Facility */ + fac = asl_get(msg, ASL_KEY_FACILITY); + if (fac == NULL) + { + if (kern == 1) fac = "kern"; + else fac = "user"; + asl_set(msg, ASL_KEY_FACILITY, fac); + } + else if (fac[0] == '#') + { + fnum = LOG_USER; + if ((fac[1] >= '0') && (fac[1] <= '9')) + { + fnum = atoi(fac + 1) << 3; + if ((fnum == 0) && (strcmp(fac + 1, "0"))) fnum = LOG_USER; + } + + fac = asl_syslog_faciliy_num_to_name(fnum); + asl_set(msg, ASL_KEY_FACILITY, fac); + } + + /* + * Access Control: only UID 0 may use facility com.apple.system (or anything with that prefix). + * N.B. kernel can use any facility name. + */ + + /* Set DB Expire Time for com.apple.system.utmpx and lastlog */ + if ((!strcmp(fac, "com.apple.system.utmpx")) || (!strcmp(fac, "com.apple.system.lastlog"))) + { + snprintf(buf, sizeof(buf), "%lu", tick + utmp_ttl); + asl_set(msg, ASL_KEY_EXPIRE_TIME, buf); + } + + /* Set DB Expire Time for Filestsrem errors */ + if (!strcmp(fac, FSLOG_VAL_FACILITY)) + { + snprintf(buf, sizeof(buf), "%lu", tick + fs_ttl); + asl_set(msg, ASL_KEY_EXPIRE_TIME, buf); + } + + if (e != NULL) + { + isreserved = 0; + if (!strncmp(fac, SYSTEM_RESERVED, SYSTEM_RESERVED_LEN)) + { + if (uid != 0) asl_set(msg, ASL_KEY_FACILITY, FACILITY_USER); + else isreserved = 1; + } + } + + /* + * special case handling of kernel disaster messages + */ + if ((kern == 1) && (level <= KERN_DISASTER_LEVEL)) + { + *kern_post_level = level; + disaster_message(msg); + } return 0; } @@ -366,14 +518,16 @@ aslevent_fdsets(fd_set *rd, fd_set *wr, fd_set *ex) struct aslevent *e; int status = 0; - asldebug("--> aslevent_fdsets\n"); +// asldebug("--> aslevent_fdsets\n"); FD_ZERO(rd); FD_ZERO(wr); FD_ZERO(ex); for (e = Eventq.tqh_first; e != NULL; e = e->entries.tqe_next) { - asldebug("adding fd %d\n", e->fd); + if (e->fd < 0) continue; + +// asldebug("adding fd %d\n", e->fd); if (e->readfn) { FD_SET(e->fd, rd); @@ -393,76 +547,145 @@ aslevent_fdsets(fd_set *rd, fd_set *wr, fd_set *ex) } } - asldebug("<--aslevent_fdsets\n"); +// asldebug("<--aslevent_fdsets\n"); return status; } void -aslevent_handleevent(fd_set rd, fd_set wr, fd_set ex, char *errstr) +aslevent_cleanup() +{ + struct aslevent *e, *next; + + e = Eventq.tqh_first; + + while (e != NULL) + { + next = e->entries.tqe_next; + if (e->fd < 0) + { + TAILQ_REMOVE(&Eventq, e, entries); + if (e->sender != NULL) free(e->sender); + free(e); + } + + e = next; + } +} + +void +aslevent_handleevent(fd_set *rd, fd_set *wr, fd_set *ex) { struct aslevent *e; char *out = NULL; asl_msg_t *msg; + int32_t kplevel, cleanup; - asldebug("--> aslevent_handleevent\n"); - if (errstr) errstr = NULL; +// asldebug("--> aslevent_handleevent\n"); + + cleanup = 0; for (e = Eventq.tqh_first; e != NULL; e = e->entries.tqe_next) { - if (FD_ISSET(e->fd, &rd) && (e->readfn != NULL)) + if (e->fd < 0) { - asldebug("handling read event on %d\n", e->fd); + cleanup = 1; + continue; + } + + if (FD_ISSET(e->fd, rd) && (e->readfn != NULL)) + { +// asldebug("handling read event on %d\n", e->fd); msg = e->readfn(e->fd); - if (msg == NULL) - { - asldebug("error reading message\n"); - continue; - } + if (msg == NULL) continue; - if (aslmsg_verify(e, msg) < 0) + /* type field is overloaded to provide retain/release inside syslogd */ + msg->type |= 0x10; + + pthread_mutex_lock(&event_lock); + + kplevel = -1; + if (aslmsg_verify(e, msg, &kplevel) < 0) { - asl_free(msg); - asldebug("recieved invalid message\n"); + asl_msg_release(msg); + asldebug("recieved invalid message\n\n"); } else { aslevent_match(msg); - asl_free(msg); + asl_msg_release(msg); + if (kplevel >= 0) notify_post(kern_notify_key[kplevel]); } + + pthread_mutex_unlock(&event_lock); } - if (FD_ISSET(e->fd, &ex) && e->exceptfn) + if (FD_ISSET(e->fd, ex) && e->exceptfn) { asldebug("handling except event on %d\n", e->fd); out = e->exceptfn(e->fd); - if (out == NULL) asldebug("error writing message\n"); + if (out == NULL) asldebug("error writing message\n\n"); } } - asldebug("<-- aslevent_handleevent\n"); + if (cleanup != 0) aslevent_cleanup(); + +// asldebug("<-- aslevent_handleevent\n"); } int asl_log_string(const char *str) { asl_msg_t *msg; + int32_t unused; if (str == NULL) return 1; msg = asl_msg_from_string(str); - if (aslmsg_verify(NULL, msg) < 0) + + /* set retain count */ + msg->type |= 0x10; + + pthread_mutex_lock(&event_lock); + + unused = -1; + if (aslmsg_verify(NULL, msg, &unused) < 0) { - asl_free(msg); + pthread_mutex_unlock(&event_lock); + asl_msg_release(msg); return -1; } aslevent_match(msg); - asl_free(msg); + asl_msg_release(msg); + + pthread_mutex_unlock(&event_lock); + return 0; } +int +asldebug(const char *str, ...) +{ + va_list v; + int status; + FILE *dfp; + + if (debug == 0) return 0; + + dfp = stderr; + if (debug_file != NULL) dfp = fopen(debug_file, "a"); + if (dfp == NULL) return 0; + + va_start(v, str); + status = vfprintf(dfp, str, v); + va_end(v); + + if (debug_file != NULL) fclose(dfp); + return status; +} + void -aslmark(void) +asl_mark(void) { char *str; @@ -491,8 +714,6 @@ asl_syslog_input_convert(const char *in, int len, char *rhost, int kern) if (in == NULL) return NULL; if (len <= 0) return NULL; - asldebug("asl_syslog_input_convert: %s\n", in); - pri = LOG_DEBUG; tval = NULL; sval = NULL; @@ -541,6 +762,8 @@ asl_syslog_input_convert(const char *in, int len, char *rhost, int kern) if (((len - index) > 15) && (p[9] == ':') && (p[12] == ':') && (p[15] == ' ')) { tmp = malloc(16); + if (tmp == NULL) return NULL; + memcpy(tmp, p, 15); tmp[15] = '\0'; @@ -563,16 +786,8 @@ asl_syslog_input_convert(const char *in, int len, char *rhost, int kern) if (kern != 0) { msg = (asl_msg_t *)calloc(1, sizeof(asl_msg_t)); - msg->type = ASL_TYPE_MSG; + if (msg == NULL) return NULL; - asl_set(msg, ASL_KEY_SENDER, "kernel"); - - asl_set(msg, "Facility", "kern"); - if (tval != NULL) - { - asl_set(msg, ASL_KEY_TIME, tval); - free(tval); - } asl_set(msg, ASL_KEY_MSG, p); @@ -580,10 +795,6 @@ asl_syslog_input_convert(const char *in, int len, char *rhost, int kern) asl_set(msg, ASL_KEY_PID, "0"); - asl_set(msg, ASL_KEY_UID, "0"); - - asl_set(msg, ASL_KEY_GID, "0"); - asl_set(msg, ASL_KEY_HOST, whatsmyhostname()); return msg; @@ -598,11 +809,15 @@ asl_syslog_input_convert(const char *in, int len, char *rhost, int kern) { n = brace - p; sval = malloc(n + 1); + if (sval == NULL) return NULL; + memcpy(sval, p, n); sval[n] = '\0'; n = colon - (brace + 1) - 1; pval = malloc(n + 1); + if (pval == NULL) return NULL; + memcpy(pval, (brace + 1), n); pval[n] = '\0'; } @@ -610,6 +825,8 @@ asl_syslog_input_convert(const char *in, int len, char *rhost, int kern) { n = colon - p; sval = malloc(n + 1); + if (sval == NULL) return NULL; + memcpy(sval, p, n); sval[n] = '\0'; } @@ -629,6 +846,8 @@ asl_syslog_input_convert(const char *in, int len, char *rhost, int kern) if (n > 0) { mval = malloc(n + 1); + if (mval == NULL) return NULL; + memcpy(mval, p, n); mval[n] = '\0'; } @@ -636,7 +855,7 @@ asl_syslog_input_convert(const char *in, int len, char *rhost, int kern) if (fval == NULL) fval = asl_syslog_faciliy_num_to_name(LOG_USER); msg = (asl_msg_t *)calloc(1, sizeof(asl_msg_t)); - msg->type = ASL_TYPE_MSG; + if (msg == NULL) return NULL; if (tval != NULL) { @@ -675,13 +894,50 @@ asl_syslog_input_convert(const char *in, int len, char *rhost, int kern) if (msg->count == 0) { - asl_free(msg); + asl_msg_release(msg); return NULL; } return msg; } +asl_msg_t * +asl_input_parse(const char *in, int len, char *rhost, int kern) +{ + asl_msg_t *m; + int status, x, legacy; + + asldebug("asl_input_parse: %s\n", (in == NULL) ? "NULL" : in); + + if (in == NULL) return NULL; + + legacy = 1; + m = NULL; + + /* calculate length if not provided */ + if (len == 0) len = strlen(in); + + /* + * Determine if the input is "old" syslog format or new ASL format. + * Old format lines should start with "<", but they can just be straight text. + * ASL input starts with a length (10 bytes) followed by a space and a '['. + */ + if ((in[0] != '<') && (len > 11)) + { + status = sscanf(in, "%d ", &x); + if ((status == 1) && (in[10] == ' ') && (in[11] == '[')) legacy = 0; + } + + if (legacy == 1) return asl_syslog_input_convert(in, len, rhost, kern); + + m = asl_msg_from_string(in + 11); + if (m == NULL) return NULL; + + if (rhost != NULL) asl_set(m, ASL_KEY_HOST, rhost); + + return m; +} + char * get_line_from_file(FILE *f) { @@ -693,8 +949,152 @@ get_line_from_file(FILE *f) if (len == 0) return NULL; s = malloc(len + 1); + if (s == NULL) return NULL; + memcpy(s, out, len); s[len - 1] = '\0'; return s; } + +uint32_t +asl_msg_type(asl_msg_t *m) +{ + if (m == NULL) return ASL_TYPE_ERROR; + return (m->type & ASL_MSG_TYPE_MASK); +} + +void +asl_msg_release(asl_msg_t *m) +{ + uint32_t refcount; + + if (m == NULL) return; + + refcount = m->type >> 4; + if (refcount > 0) + { + refcount--; + m->type -= 0x10; + } + + if (refcount > 0) return; + asl_free(m); +} + +asl_msg_t * +asl_msg_retain(asl_msg_t *m) +{ + if (m == NULL) return NULL; + + m->type += 0x10; + return m; +} + +void +launchd_callback(struct timeval *when, pid_t from_pid, pid_t about_pid, uid_t sender_uid, gid_t sender_gid, int priority, const char *from_name, const char *about_name, const char *session_name, const char *msg) +{ + asl_msg_t *m; + char str[256]; + int32_t unused; + int status; + time_t now; + +/* + asldebug("launchd_callback Time %lu %lu PID %u RefPID %u UID %d GID %d PRI %d Sender %s Ref %s Session %s Message %s\n", + when->tv_sec, when->tv_usec, from_pid, about_pid, sender_uid, sender_gid, priority, from_name, about_name, session_name, msg); +*/ + + if (launchd_event != NULL) + { + launchd_event->uid = sender_uid; + launchd_event->gid = sender_gid; + } + + m = asl_new(ASL_TYPE_MSG); + if (m == NULL) return; + + /* Level */ + if (priority < ASL_LEVEL_EMERG) priority = ASL_LEVEL_EMERG; + if (priority > ASL_LEVEL_DEBUG) priority = ASL_LEVEL_DEBUG; + snprintf(str, sizeof(str), "%d", priority); + asl_set(m, ASL_KEY_LEVEL, str); + + /* Time */ + if (when != NULL) + { + snprintf(str, sizeof(str), "%lu", when->tv_sec); + asl_set(m, ASL_KEY_TIME, str); + + snprintf(str, sizeof(str), "%lu", 1000 * (unsigned long int)when->tv_usec); + asl_set(m, ASL_KEY_TIME_NSEC, str); + } + else + { + now = time(NULL); + snprintf(str, sizeof(str), "%lu", now); + asl_set(m, ASL_KEY_TIME, str); + } + + /* Host */ + asl_set(m, ASL_KEY_HOST, whatsmyhostname()); + + /* Facility */ + asl_set(m, ASL_KEY_FACILITY, FACILITY_CONSOLE); + + /* PID */ + if (from_pid != 0) + { + snprintf(str, sizeof(str), "%u", (unsigned int)from_pid); + asl_set(m, ASL_KEY_PID, str); + } + + /* Reference PID */ + if ((about_pid > 0) && (about_pid != from_pid)) + { + snprintf(str, sizeof(str), "%u", (unsigned int)about_pid); + asl_set(m, ASL_KEY_REF_PID, str); + } + + /* Sender */ + if (from_name != NULL) asl_set(m, ASL_KEY_SENDER, from_name); + + /* ReadUID */ + if (sender_uid != 0) + { + snprintf(str, sizeof(str), "%d", (int)sender_uid); + asl_set(m, ASL_KEY_READ_UID, str); + } + + /* Reference Process */ + if (about_name != NULL) + { + if ((from_name != NULL) && (strcmp(from_name, about_name) != 0)) asl_set(m, ASL_KEY_REF_PROC, about_name); + } + + /* Session */ + if (session_name != NULL) asl_set(m, ASL_KEY_SESSION, session_name); + + /* Message */ + if (msg != NULL) asl_set(m, ASL_KEY_MSG, msg); + + /* set retain count */ + m->type |= 0x10; + + /* verify and push to receivers */ + status = aslmsg_verify(launchd_event, m, &unused); + if (status >= 0) aslevent_match(m); + + asl_msg_release(m); +} + +void +launchd_drain() +{ + launchd_event = (struct aslevent *)calloc(1, sizeof(struct aslevent)); + + forever + { + _vprocmgr_log_drain(NULL, &event_lock, launchd_callback); + } +} diff --git a/syslogd.tproj/daemon.h b/syslogd.tproj/daemon.h index c99e628..5aa0198 100644 --- a/syslogd.tproj/daemon.h +++ b/syslogd.tproj/daemon.h @@ -3,21 +3,20 @@ * * @APPLE_LICENSE_HEADER_START@ * - * "Portions Copyright (c) 2004 Apple Computer, Inc. All Rights - * Reserved. This file contains Original Code and/or Modifications of - * Original Code as defined in and that are subject to the Apple Public - * Source License Version 1.0 (the 'License'). You may not use this file - * except in compliance with the License. Please obtain a copy of the - * License at http://www.apple.com/publicsource and read it before using - * this file. + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT. Please see the - * License for the specific language governing rights and limitations - * under the License." + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ @@ -32,16 +31,34 @@ #include #include #include +#include + +#define ADDFD_FLAGS_LOCAL 0x00000001 + +#define ASL_DB_NOTIFICATION "com.apple.system.logger.message" + +#define ASL_KEY_READ_UID "ReadUID" +#define ASL_KEY_READ_GID "ReadGID" +#define ASL_KEY_EXPIRE_TIME "ASLExpireTime" +#define ASL_KEY_IGNORE "ASLIgnore" +#define ASL_KEY_TIME_NSEC "TimeNanoSec" +#define ASL_KEY_REF_PID "RefPID" +#define ASL_KEY_REF_PROC "RefProc" +#define ASL_KEY_SESSION "Session" #define _PATH_PIDFILE "/var/run/syslog.pid" #define _PATH_ASL_IN "/var/run/asl_input" -#define _PATH_ASL_PRUNE "/var/run/asl_prune" -#define _PATH_ASL_OUT "/var/log/asl.log" +#define _PATH_ASL_DIR "/var/log" +#define _PATH_ASL_DB "/var/log/asl.db" #define _PATH_SYSLOG_CONF "/etc/syslog.conf" #define _PATH_SYSLOG_IN "/var/run/syslog" #define _PATH_KLOG "/dev/klog" #define _PATH_MODULE_LIB "/usr/lib/asl" +#define KERN_DISASTER_LEVEL 3 + +extern launch_data_t launch_dict; + struct module_list { char *name; @@ -54,8 +71,9 @@ struct module_list int aslevent_init(void); int aslevent_fdsets(fd_set *, fd_set *, fd_set *); -void aslevent_handleevent(fd_set, fd_set, fd_set, char *); -void aslmark(void); +void aslevent_handleevent(fd_set *, fd_set *, fd_set *); +void asl_mark(void); +void asl_archive(void); char *get_line_from_file(FILE *f); @@ -72,7 +90,7 @@ typedef char *(*aslwritefn)(const char *, int); typedef char *(*aslexceptfn)(int); typedef int (*aslsendmsgfn)(asl_msg_t *msg, const char *outid); -int aslevent_addfd(int fd, aslreadfn, aslwritefn, aslexceptfn); +int aslevent_addfd(int fd, uint32_t flags, aslreadfn, aslwritefn, aslexceptfn); int aslevent_removefd(int fd); int aslevent_addmatch(asl_msg_t *query, char *outid); @@ -80,12 +98,18 @@ int aslevent_addoutput(aslsendmsgfn, const char *outid); int asl_syslog_faciliy_name_to_num(const char *fac); const char *asl_syslog_faciliy_num_to_name(int num); -asl_msg_t *asl_syslog_input_convert(const char *in, int len, char *rhost, int flag); -int asl_prune(asl_msg_t *pq); +asl_msg_t *asl_input_parse(const char *in, int len, char *rhost, int flag); + +uint32_t db_prune(aslresponse query); +uint32_t db_archive(time_t cut, uint64_t max); +uint32_t db_compact(void); + +/* message refcount utilities */ +uint32_t asl_msg_type(asl_msg_t *m); +asl_msg_t *asl_msg_retain(asl_msg_t *m); +void asl_msg_release(asl_msg_t *m); /* notify SPI */ -uint32_t notify_get_state(int token, int *state); -uint32_t notify_set_state(int token, int state); uint32_t notify_register_plain(const char *name, int *out_token); #endif /* __DAEMON_H__ */ diff --git a/syslogd.tproj/dbserver.c b/syslogd.tproj/dbserver.c new file mode 100644 index 0000000..a900355 --- /dev/null +++ b/syslogd.tproj/dbserver.c @@ -0,0 +1,654 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "daemon.h" + +#define forever for(;;) + +#define LIST_SIZE_DELTA 256 +#define MAX_PRE_DISASTER_COUNT 64 +#define MAX_DISASTER_COUNT LIST_SIZE_DELTA + +#define SEND_NOTIFICATION 0xfadefade + +#define QUERY_FLAG_SEARCH_REVERSE 0x00000001 +#define SEARCH_FORWARD 1 +#define SEARCH_BACKWARD -1 + +static asl_store_t *store = NULL; +static int disaster_occurred = 0; + +extern mach_port_t server_port; +extern int archive_enable; +extern uint64_t db_curr_size; +extern uint64_t db_curr_empty; + +static pthread_mutex_t db_lock = PTHREAD_MUTEX_INITIALIZER; +static pthread_mutex_t queue_lock = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t queue_cond = PTHREAD_COND_INITIALIZER; + +extern char *asl_list_to_string(asl_search_result_t *list, uint32_t *outlen); +extern asl_search_result_t *asl_list_from_string(const char *buf); + +static time_t last_archive_sod = 0; + +static asl_search_result_t work_queue = {0, 0, NULL}; +static asl_search_result_t disaster_log = {0, 0, NULL}; + +extern boolean_t asl_ipc_server +( + mach_msg_header_t *InHeadP, + mach_msg_header_t *OutHeadP +); + +typedef union +{ + mach_msg_header_t head; + union __RequestUnion__asl_ipc_subsystem request; +} asl_request_msg; + +typedef union +{ + mach_msg_header_t head; + union __ReplyUnion__asl_ipc_subsystem reply; +} asl_reply_msg; + +void +list_append_msg(asl_search_result_t *list, asl_msg_t *msg, uint32_t retain) +{ + if (list == NULL) return; + if (msg == NULL) return; + + /* + * NB: curr is the list size + * grow list if necessary + */ + if (list->count == list->curr) + { + if (list->curr == 0) + { + list->msg = (asl_msg_t **)calloc(LIST_SIZE_DELTA, sizeof(asl_msg_t *)); + } + else + { + list->msg = (asl_msg_t **)reallocf(list->msg, (list->curr + LIST_SIZE_DELTA) * sizeof(asl_msg_t *)); + } + + if (list->msg == NULL) + { + list->curr = 0; + list->count = 0; + return; + } + + list->curr += LIST_SIZE_DELTA; + } + + if (retain != 0) asl_msg_retain(msg); + list->msg[list->count] = msg; + list->count++; +} + +void +disaster_message(asl_msg_t *m) +{ + uint32_t i; + + if (disaster_occurred == 0) + { + /* retain last MAX_PRE_DISASTER_COUNT messages */ + while (disaster_log.count >= MAX_PRE_DISASTER_COUNT) + { + asl_msg_release(disaster_log.msg[0]); + for (i = 1; i < disaster_log.count; i++) disaster_log.msg[i - 1] = disaster_log.msg[i]; + disaster_log.count--; + } + } + + if (disaster_log.count < MAX_DISASTER_COUNT) list_append_msg(&disaster_log, m, 1); +} + +void +db_enqueue(asl_msg_t *m) +{ + if (m == NULL) return; + + pthread_mutex_lock(&queue_lock); + list_append_msg(&work_queue, m, 1); + pthread_mutex_unlock(&queue_lock); + pthread_cond_signal(&queue_cond); +} + +asl_msg_t ** +db_dequeue(uint32_t *count) +{ + asl_msg_t **work; + + pthread_mutex_lock(&queue_lock); + pthread_cond_wait(&queue_cond, &queue_lock); + + work = NULL; + *count = 0; + + if (work_queue.count == 0) + { + pthread_mutex_unlock(&queue_lock); + return NULL; + } + + work = work_queue.msg; + *count = work_queue.count; + + work_queue.count = 0; + work_queue.curr = 0; + work_queue.msg = NULL; + + pthread_mutex_unlock(&queue_lock); + return work; +} + +/* + * Takes messages off the work queue and saves them in the database. + * Runs in it's own thread. + */ +void +db_worker() +{ + asl_msg_t **work; + uint64_t msgid; + uint32_t i, count, status; + mach_msg_empty_send_t *msg; + kern_return_t kstatus; + + msg = (mach_msg_empty_send_t *)calloc(1, sizeof(mach_msg_empty_send_t)); + if (msg == NULL) return; + + msg->header.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, MACH_MSGH_BITS_ZERO); + msg->header.msgh_remote_port = server_port; + msg->header.msgh_local_port = MACH_PORT_NULL; + msg->header.msgh_size = sizeof(mach_msg_empty_send_t); + msg->header.msgh_id = SEND_NOTIFICATION; + + forever + { + count = 0; + work = db_dequeue(&count); + + if (work == NULL) continue; + + pthread_mutex_lock(&db_lock); + + if (store == NULL) + { + status = asl_store_open(_PATH_ASL_DB, 0, &store); + if (status != ASL_STATUS_OK) + { + disaster_occurred = 1; + store = NULL; + } + } + + for (i = 0; (i < count) && (store != NULL); i++) + { + msgid = 0; + status = ASL_STATUS_OK; + + status = asl_store_save(store, work[i], -1, -1, &msgid); + if (status != ASL_STATUS_OK) + { + /* write failed - reopen store */ + asl_store_close(store); + store = NULL; + + status = asl_store_open(_PATH_ASL_DB, 0, &store); + if (status != ASL_STATUS_OK) + { + disaster_occurred = 1; + store = NULL; + } + + /* if the store re-opened, retry the save */ + if (store != NULL) + { + status = asl_store_save(store, work[i], -1, -1, &msgid); + if (status != ASL_STATUS_OK) + { + disaster_occurred = 1; + store = NULL; + } + } + } + + if ((i % 500) == 499) + { + pthread_mutex_unlock(&db_lock); + pthread_mutex_lock(&db_lock); + } + } + + db_curr_size = 0; + db_curr_empty = 0; + + if (store != NULL) + { + db_curr_size = (store->record_count + 1) * DB_RECORD_LEN; + db_curr_empty = store->empty_count * DB_RECORD_LEN; + } + + pthread_mutex_unlock(&db_lock); + + for (i = 0; i < count; i++) asl_msg_release(work[i]); + free(work); + + kstatus = mach_msg(&(msg->header), MACH_SEND_MSG, msg->header.msgh_size, 0, MACH_PORT_NULL, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); + } +} + +static char * +disaster_query(aslresponse query, uint32_t *outlen) +{ + asl_search_result_t res; + uint32_t i, j, match; + char *out; + + if (outlen == NULL) return NULL; + *outlen = 0; + + if ((query == NULL) || ((query != NULL) && (query->count == 0))) return asl_list_to_string(&disaster_log, outlen); + + memset(&res, 0, sizeof(asl_search_result_t)); + + for (i = 0; i < disaster_log.count; i++) + { + match = 1; + + for (j = 0; (j < query->count) && (match == 1); j++) + { + match = asl_msg_cmp(query->msg[j], disaster_log.msg[i]); + } + + if (match == 1) list_append_msg(&res, disaster_log.msg[i], 0); + } + + if (res.count == 0) return NULL; + + out = asl_list_to_string((aslresponse)&res, outlen); + free(res.msg); + return out; +} + +/* + * Do a database search. + */ +uint32_t +db_query(aslresponse query, aslresponse *res, uint64_t startid, int count, int flags, uint64_t *lastid, int32_t ruid, int32_t rgid) +{ + uint32_t status, ucount; + int32_t dir; + + ucount = count; + dir = SEARCH_FORWARD; + if (flags & QUERY_FLAG_SEARCH_REVERSE) dir = SEARCH_BACKWARD; + + pthread_mutex_lock(&db_lock); + + if (store == NULL) + { + status = asl_store_open(_PATH_ASL_DB, 0, &store); + if (status != ASL_STATUS_OK) store = NULL; + } + + status = asl_store_match(store, query, res, lastid, startid, ucount, dir, ruid, rgid); + + pthread_mutex_unlock(&db_lock); + + return status; +} + +/* + * Prune the database. + */ +uint32_t +db_prune(aslresponse query) +{ + uint32_t status; + + if (disaster_occurred == 1) return ASL_STATUS_FAILED; + + pthread_mutex_lock(&db_lock); + + if (store == NULL) + { + status = asl_store_open(_PATH_ASL_DB, 0, &store); + if (status != ASL_STATUS_OK) store = NULL; + } + + status = asl_store_prune(store, query); + + pthread_mutex_unlock(&db_lock); + + return status; +} + +/* + * Database archiver + */ +uint32_t +db_archive(time_t cut, uint64_t max_size) +{ + time_t sod; + struct tm ctm; + char *archive_file_name; + uint32_t status; + + archive_file_name = NULL; + memset(&ctm, 0, sizeof(struct tm)); + + if (localtime_r((const time_t *)&cut, &ctm) == NULL) return ASL_STATUS_FAILED; + + if (archive_enable != 0) + { + asprintf(&archive_file_name, "%s/asl.%d.%02d.%02d.archive", _PATH_ASL_DIR, ctm.tm_year + 1900, ctm.tm_mon + 1, ctm.tm_mday); + if (archive_file_name == NULL) return ASL_STATUS_NO_MEMORY; + } + + ctm.tm_sec = 0; + ctm.tm_min = 0; + ctm.tm_hour = 0; + + sod = mktime(&ctm); + + /* if the day changed, archive message received before the start of the day */ + if (sod > last_archive_sod) + { + last_archive_sod = sod; + status = db_archive(sod - 1, 0); + /* NB return status not checked */ + } + + pthread_mutex_lock(&db_lock); + + if (store == NULL) + { + status = asl_store_open(_PATH_ASL_DB, 0, &store); + if (status != ASL_STATUS_OK) store = NULL; + } + + status = asl_store_archive(store, cut, archive_file_name); + if (status == ASL_STATUS_OK) status = asl_store_compact(store); + if ((status == ASL_STATUS_OK) && (max_size > 0)) status = asl_store_truncate(store, max_size, archive_file_name); + + db_curr_size = 0; + db_curr_empty = 0; + + if (store != NULL) + { + db_curr_size = (store->record_count + 1) * DB_RECORD_LEN; + db_curr_empty = store->empty_count * DB_RECORD_LEN; + } + + pthread_mutex_unlock(&db_lock); + + if (archive_file_name != NULL) free(archive_file_name); + + return status; +} + +uint32_t +db_compact(void) +{ + uint32_t status; + + pthread_mutex_lock(&db_lock); + + if (store == NULL) + { + status = asl_store_open(_PATH_ASL_DB, 0, &store); + if (status != ASL_STATUS_OK) + { + pthread_mutex_unlock(&db_lock); + return status; + } + } + + status = asl_store_compact(store); + + db_curr_size = (store->record_count + 1) * DB_RECORD_LEN; + db_curr_empty = store->empty_count * DB_RECORD_LEN; + + pthread_mutex_unlock(&db_lock); + + return status; +} + +/* + * Receives messages on the "com.apple.system.logger" mach port. + * Services database search and pruning requests. + * Runs in it's own thread. + */ +void +database_server() +{ + kern_return_t kstatus; + asl_request_msg *request; + asl_reply_msg *reply; + uint32_t rqs, rps; + uint32_t rbits, sbits; + uint32_t flags, snooze; + struct timeval now, send_time; + + send_time.tv_sec = 0; + send_time.tv_usec = 0; + + rqs = sizeof(asl_request_msg) + MAX_TRAILER_SIZE; + rps = sizeof(asl_reply_msg) + MAX_TRAILER_SIZE; + reply = (asl_reply_msg *)calloc(1, rps); + if (reply == NULL) return; + + rbits = MACH_RCV_MSG | MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_SENDER) | MACH_RCV_TRAILER_TYPE(MACH_MSG_TRAILER_FORMAT_0); + sbits = MACH_SEND_MSG | MACH_SEND_TIMEOUT; + + forever + { + snooze = 0; + now.tv_sec = 0; + now.tv_usec = 0; + + /* Check if it's time to post a database change notification */ + if (send_time.tv_sec != 0) + { + gettimeofday(&now, NULL); + if ((now.tv_sec > send_time.tv_sec) || ((now.tv_sec == send_time.tv_sec) && (now.tv_usec > send_time.tv_usec))) + { + notify_post(ASL_DB_NOTIFICATION); + send_time.tv_sec = 0; + send_time.tv_usec = 0; + snooze = 0; + } + else + { + /* mach_msg timeout is in milliseconds */ + snooze = ((send_time.tv_sec - now.tv_sec) * 1000) + ((send_time.tv_usec - now.tv_usec) / 1000); + } + } + + request = (asl_request_msg *)calloc(1, rqs); + if (request == NULL) continue; + + request->head.msgh_local_port = server_port; + request->head.msgh_size = rqs; + + memset(reply, 0, rps); + + flags = rbits; + if (snooze != 0) flags |= MACH_RCV_TIMEOUT; + + kstatus = mach_msg(&(request->head), flags, 0, rqs, server_port, snooze, MACH_PORT_NULL); + if (request->head.msgh_id == SEND_NOTIFICATION) + { + if (send_time.tv_sec == 0) + { + gettimeofday(&send_time, NULL); + send_time.tv_sec += 1; + } + + free(request); + continue; + } + + kstatus = asl_ipc_server(&(request->head), &(reply->head)); + kstatus = mach_msg(&(reply->head), sbits, reply->head.msgh_size, 0, MACH_PORT_NULL, 10, MACH_PORT_NULL); + if (kstatus == MACH_SEND_INVALID_DEST) + { + mach_port_destroy(mach_task_self(), request->head.msgh_remote_port); + } + + free(request); + } +} + +kern_return_t +__asl_server_query +( + mach_port_t server, + caddr_t request, + mach_msg_type_number_t requestCnt, + uint64_t startid, + int count, + int flags, + caddr_t *reply, + mach_msg_type_number_t *replyCnt, + uint64_t *lastid, + int *status, + security_token_t *token +) +{ + aslresponse query; + aslresponse res; + char *out, *vmbuffer; + uint32_t outlen; + kern_return_t kstatus; + + *status = ASL_STATUS_OK; + query = asl_list_from_string(request); + vm_deallocate(mach_task_self(), (vm_address_t)request, requestCnt); + res = NULL; + + /* + * If the database went offline (i.e. the filesystem died), + * just search the disaster_log messages. + */ + if (disaster_occurred == 1) + { + out = NULL; + outlen = 0; + out = disaster_query(query, &outlen); + aslresponse_free(query); + } + else + { + *status = db_query(query, &res, startid, count, flags, lastid, token->val[0], token->val[1]); + + aslresponse_free(query); + if (*status != ASL_STATUS_OK) + { + if (res != NULL) aslresponse_free(res); + return KERN_SUCCESS; + } + + out = NULL; + outlen = 0; + out = asl_list_to_string((asl_search_result_t *)res, &outlen); + aslresponse_free(res); + } + + if ((out == NULL) || (outlen == 0)) return KERN_SUCCESS; + + kstatus = vm_allocate(mach_task_self(), (vm_address_t *)&vmbuffer, outlen, TRUE); + if (kstatus != KERN_SUCCESS) + { + free(out); + return kstatus; + } + + memmove(vmbuffer, out, outlen); + free(out); + + *reply = vmbuffer; + *replyCnt = outlen; + + return KERN_SUCCESS; +} + + +kern_return_t +__asl_server_query_timeout +( + mach_port_t server, + caddr_t request, + mach_msg_type_number_t requestCnt, + uint64_t startid, + int count, + int flags, + caddr_t *reply, + mach_msg_type_number_t *replyCnt, + uint64_t *lastid, + int *status, + security_token_t *token + ) +{ + return __asl_server_query(server, request, requestCnt, startid, count, flags, reply, replyCnt, lastid, status, token); +} + +kern_return_t +__asl_server_prune +( + mach_port_t server, + caddr_t request, + mach_msg_type_number_t requestCnt, + int *status, + security_token_t *token +) +{ + aslresponse query; + + *status = ASL_STATUS_OK; + + if (request == NULL) return KERN_SUCCESS; + + query = asl_list_from_string(request); + + vm_deallocate(mach_task_self(), (vm_address_t)request, requestCnt); + + /* only root may prune the database */ + if (token->val[0] != 0) + { + *status = ASL_STATUS_FAILED; + return KERN_SUCCESS; + } + + *status = db_prune(query); + aslresponse_free(query); + + return KERN_SUCCESS; +} diff --git a/syslogd.tproj/klog_in.c b/syslogd.tproj/klog_in.c index f9a5809..7a93c2a 100644 --- a/syslogd.tproj/klog_in.c +++ b/syslogd.tproj/klog_in.c @@ -3,21 +3,20 @@ * * @APPLE_LICENSE_HEADER_START@ * - * "Portions Copyright (c) 2004 Apple Computer, Inc. All Rights - * Reserved. This file contains Original Code and/or Modifications of - * Original Code as defined in and that are subject to the Apple Public - * Source License Version 1.0 (the 'License'). You may not use this file - * except in compliance with the License. Please obtain a copy of the - * License at http://www.apple.com/publicsource and read it before using - * this file. + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT. Please see the - * License for the specific language governing rights and limitations - * under the License." + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ @@ -65,7 +64,7 @@ klog_in_acceptmsg(int fd) kline[kx] = '\0'; kx = 0; - return asl_syslog_input_convert(kline, n, NULL, 1); + return asl_input_parse(kline, n, NULL, 1); } int @@ -89,7 +88,7 @@ klog_in_init(void) return -1; } - return aslevent_addfd(kfd, klog_in_acceptmsg, NULL, NULL); + return aslevent_addfd(kfd, ADDFD_FLAGS_LOCAL, klog_in_acceptmsg, NULL, NULL); } int diff --git a/syslogd.tproj/syslogd.8 b/syslogd.tproj/syslogd.8 index 2dd6da1..2b355fd 100644 --- a/syslogd.tproj/syslogd.8 +++ b/syslogd.tproj/syslogd.8 @@ -37,10 +37,15 @@ .Op Fl d .Op Fl D .Op Fl m Ar mark_interval -.Op Fl p Ar prune_days .Op Fl c Ar log_cutoff .Op Fl l Ar lib_path -.Op Fl u +.Op Fl a +.Op Fl ttl Ar time +.Op Fl sweep Ar time +.Op Fl db_max Ar size +.Op Fl utmp_ttl Ar time +.Op Fl fs_ttl Ar time +.Op Fl dup_delay Ar time .Op Fl module_name Li {0|1} .Sh DESCRIPTION The @@ -51,9 +56,7 @@ including UNIX domain sockets associated with the .Xr syslog 3 , .Xr asl 3 , and kernel printf APIs, -and optionally from a UDP socket if the -.Dq udp_in -module is enabled. +and optionally on a UDP socket from network clients. .Pp The Apple System Log facility comprises the .Xr asl 3 @@ -67,14 +70,14 @@ permitting advanced message browsing and management through search APIs and other components of the Apple system log facility. .Pp Log messages are retained in a data store, -subject to pruning and input filtering as described below, +subject to pruning, automatic archival, and input filtering as described below, to simplify the task of locating log messages and to facilitate browsing and searching. The data store is intended to become a replacement for the numerous log files that are currently found in various locations on the system. Those files will be phased out in future versions of Mac OS. .Pp The following options are recognized: -.Bl -tag -width indent +.Bl -tag -width "-utmp_ttl" .It Fl d Run .Nm @@ -98,25 +101,6 @@ The default is 20 minutes. The .Dq mark facility is disabled if the setting is zero minutes. -.It Fl p -.Nm -saves log messages in a data store that may be searched using the -.Xr syslog 1 -utility or with the -.Xr asl 3 -API. -The data store is pruned daily by the /etc/daily cron job to keep it from growing without bound. -Since many systems are shut down overnight (when the daily cron job runs), -the data store is also pruned shortly after -.Nm -starts up as the system boots. -By default, log messages in the data store that are more than 7 days old are removed. -The setting of the -.Fl p Ar prune_days -overrides the default. -A setting of zero days disables pruning of the data store when -.Nm -starts up. .It Fl c Sets a cutoff filter for log priorities for messages to be retained in the log message data store. The value of @@ -150,22 +134,64 @@ Specifies an alternate path for loading plug-in modules. By default, .Nm checks for plug-in modules in the directory /usr/lib/asl. -.It Fl u -Enables the -.Dq udp_in -module, configuring +.It Fl a +Enables message archival. +Messages older than 24 hours (or as otherwise set using +.Fl ttl ) +will be copied to an archive database when they expire from the active database. +Archive databases are named /var/log/asl.yyyy.mm.dd.archive, and may be read or +searched using the +.Xr syslog 1 +command. +.It Fl ttl +Sets the time-to-live in seconds for messages in the active database. +Expired messages are removed or copied to an archive database if archival is enabled. +.It Fl sweep +Sets the interval (in seconds) for a periodic database operation that removes and +(optionally) archives expired messages. +.It Fl db_max +Sets a size limit in bytes for the active database. +The size of the database is reduced by deleting oldest messages. +Deleted messages will be archived if archival is enabled. +When the database reaches its size limit, it is reduced to approximately 90% of the allowed maximum size. +This allows the database to grow for some time before the next size-reduction. +The default value for +.Fl db_max +is 25600000 bytes. +.It Fl utmp_ttl +Sets the time-to-live in seconds for messages used by the +.Xr utmp , +.Xr wtmp , +and +.Xr lastlog +subsystems. +The default is 31622400 seconds (approximately 1 year). +Note that if archival is enabled, these messages will be copied to an archive file +after the regular time-to-live interval (24 hours, or as set using +.Fl ttl ) +but will persist in the active database until their own expiry time. +.It Fl fs_ttl +Sets the time-to-live in seconds for filesystem error messages generated by the kernel. +The default is 31622400 seconds (approximately 1 year). +As in the case of +.Fl utmp_tt , +if archival is enabled, these messages will be copied to an archive file +after the regular time-to-live interval (24 hours, or as set using +.Fl ttl ) +but will persist in the active database until their own expiry time. +.It Fl dup_delay +Sets the time to delay for coalescing duplicate message in log files. +If a process logs multiple messages with the same text, .Nm -to act as a network log message receiver. -The server will receive messages on the standard -.Dq syslog -UDP port. -Note that this opens the server to potential denial-of-service attacks, -as a malicious remote sender can flood the server with messages. -The -.Fl u -option is equivalent to using the -.Fl udp_in Li 1 -option. +will wait for the specified period of time to coalesce duplicates. +If identical messages arrive during this interval, +.Nm +will print a message of the form: +.Pp +.Li May 7 12:34:56: --- last message repeated 17 times --- +.Pp +The default delay time is 30 seconds. +Setting the value to 0 disables the coalescing mechanism. .El .Pp The remaining options of the form @@ -232,11 +258,24 @@ module. The .Dq udp_in module receives log messages on the UDP socket associated with the Internet syslog message protocol. -The module may be enabled using -.Fl udp_in Li 1 . -The module is normally disabled. -This module may also be enabled using the -.Fl u +.Pp +This module is normally enabled, but is inactive. +The actual UDP sockets are managed by +.Nm launched , +and configured in the +.Nm syslogd +configuration file /System/Library/LaunchDaemons/com.apple.syslogd.plist. +In the default configuration, +.Nm launchd +does not open any sockets for the +.Dq syslog +UDP service, so no sockets are provided to the +.Dq udp_in +module. +If no sockets are provided, the module remains inactive. +.Pp +The module may be specifically disabled using the +.Fl udp_in Li 0 option. .El .Pp @@ -246,6 +285,59 @@ The data store is pruned approximately 5 minutes after startup. .Pp .Nm reinitializes in response to a HUP signal. +.Sh MESSAGE EXPIRY AND ARCHIVAL +.Nm +periodically removes messages from the active database, optionally copying them to an archival database. +Archival is enabled if the +.Fl a +flag is supplied. +By default, messages are removed or archived after they are 24 hours old. +The maximum age of messages in the active database may be set as the value for the +.Fl ttl +flag. +The message expiry operation runs once an hour by default, but the interval may be changed as the value for the +.Fl sweep +flag. +.Pp +After the database sweep operation, +.Nm +optionally can check the size of the database, and may be configured to remove additional messages +to limit the size of the database. +The maximum size of the database (in bytes) may be specified using the +.Fl db_max +option. +If messages must be removed to limit the database size, oldest messages are removed first. +By default there is no database size limit. +.Pp +Log messages from the +.Xr utmp , +.Xr wtmp , +and +.Xr lastlog +subsystems record login, logout, shutdowns, and reboots. +These log messages are given a longer time-to-live in the active database. +The default time-to-live for these messages is 31622400 seconds (approximately one year). +This value may be changed using the +.Fl utmp_ttl +flag. +If archival is enabled, a copy of these messages will be archived at the end of the +regular time-to-live interval (24 hours, or as specified using +.Fl ttl ). +The messages will persist in the active database until their own time-to-live has expired. +.Sh DATABASE SECURITY +The data store file /var/log/asl.db is only readable by processes with UID 0. +Messages in the data store may have a read UID and GID, +so that only processes with the specified UID or GID can fetch those messages when using +.Nm asl_search . +Read access UID and GID settings may be attached to messages using the +.Xr asl 3 +library by setting a value for the "ReadUID" and/or "ReadGID" message keys. +.Pp +Although clients are generally free to use any value for the "Facility" message key, +only processes running with UID 0 may log messages with a facility value of "com.apple.system", +or with a value that has "com.apple.system" as a prefix. +Messages logged by non UID 0 processes that use "com.apple.system" as a facility value or prefix +will be saved with the facility value "user". .Sh FILES .Bl -tag -width /var/run/syslog.pid -compact .It Pa /etc/syslog.conf diff --git a/syslogd.tproj/syslogd.c b/syslogd.tproj/syslogd.c index 0b5e2ca..c348e68 100644 --- a/syslogd.tproj/syslogd.c +++ b/syslogd.tproj/syslogd.c @@ -3,21 +3,20 @@ * * @APPLE_LICENSE_HEADER_START@ * - * "Portions Copyright (c) 2004 Apple Computer, Inc. All Rights - * Reserved. This file contains Original Code and/or Modifications of - * Original Code as defined in and that are subject to the Apple Public - * Source License Version 1.0 (the 'License'). You may not use this file - * except in compliance with the License. Please obtain a copy of the - * License at http://www.apple.com/publicsource and read it before using - * this file. + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT. Please see the - * License for the specific language governing rights and limitations - * under the License." + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ @@ -27,40 +26,86 @@ #include #include #include +#include +#include +#include +#include #include #include #include #include #include +#include #include #include #include #include #include "daemon.h" +#define SERVICE_NAME "com.apple.system.logger" +#define SERVER_STATUS_ERROR -1 +#define SERVER_STATUS_INACTIVE 0 +#define SERVER_STATUS_ACTIVE 1 +#define SERVER_STATUS_ON_DEMAND 2 + #define DEFAULT_MARK_SEC 0 -#define DEFAULT_PRUNE_DAYS 7 -#define PRUNE_AFTER_START_DELAY 300 +#define DEFAULT_SWEEP_SEC 3600 +#define DEFAULT_FIRST_SWEEP_SEC 300 +#define DEFAULT_UTMP_TTL_SEC 31622400 +#define DEFAULT_FS_TTL_SEC 31622400 +#define DEFAULT_TTL_SEC 86400 +#define DEFAULT_BSD_MAX_DUP_SEC 30 +#define BILLION 1000000000 #define NOTIFY_DELAY 1 +#define NETWORK_CHANGE_NOTIFICATION "com.apple.system.config.network_change" + #define streq(A,B) (strcmp(A,B)==0) #define forever for(;;) -static int debug = 0; static int reset = 0; +static double mach_time_factor = 1.0; static TAILQ_HEAD(ml, module_list) Moduleq; /* global */ int asl_log_filter = ASL_FILTER_MASK_UPTO(ASL_LEVEL_NOTICE); -int prune = 0; +int archive_enable = 0; +int restart = 0; +int debug = 0; +mach_port_t server_port = MACH_PORT_NULL; +launch_data_t launch_dict; +const char *debug_file = NULL; extern int __notify_78945668_info__; +extern int _malloc_no_asl_log; + +/* monitor the database */ +uint64_t db_max_size = 25600000; +uint64_t db_curr_size = 0; +uint64_t db_shrink_size; +uint64_t db_curr_empty = 0; +uint64_t db_max_empty; /* kernel log fd */ extern int kfd; +/* timers */ +uint64_t time_last = 0; +uint64_t time_start = 0; +uint64_t mark_last = 0; +uint64_t mark_time = 0; +uint64_t sweep_last = 0; +uint64_t sweep_time = DEFAULT_SWEEP_SEC; +uint64_t first_sweep_delay = DEFAULT_FIRST_SWEEP_SEC; +uint64_t bsd_flush_time = 0; +uint64_t bsd_max_dup_time = DEFAULT_BSD_MAX_DUP_SEC; + +time_t utmp_ttl = DEFAULT_UTMP_TTL_SEC; +time_t fs_ttl = DEFAULT_FS_TTL_SEC; +time_t ttl = DEFAULT_TTL_SEC; + /* Static Modules */ int asl_in_init(); int asl_in_reset(); @@ -90,7 +135,12 @@ static int activate_bsd_out = 1; int udp_in_init(); int udp_in_reset(); int udp_in_close(); -static int activate_udp_in = 0; +static int activate_udp_in = 1; + +extern void database_server(); +extern void db_worker(); +extern void launchd_drain(); +extern void bsd_flush_duplicates(); /* * Module approach: only one type of module. This module may implement @@ -104,27 +154,48 @@ static_modules() { struct module_list *tmp; - if (activate_asl_in != 0) + /* + * The order of these initializations is important. + * When messages are sent to output modules, they are + * sent in the same order as these initializations. + * asl_action may add modify messages, for example to + * add access controls, so it must come first. + */ + if (activate_asl_action != 0) { tmp = calloc(1, sizeof(struct module_list)); if (tmp == NULL) return 1; - tmp->name = strdup("asl_in"); + + tmp->name = strdup("asl_action"); + if (tmp->name == NULL) + { + free(tmp); + return 1; + } + tmp->module = NULL; - tmp->init = asl_in_init; - tmp->reset = asl_in_reset; - tmp->close = asl_in_close; + tmp->init = asl_action_init; + tmp->reset = asl_action_reset; + tmp->close = asl_action_close; TAILQ_INSERT_TAIL(&Moduleq, tmp, entries); } - if (activate_asl_action != 0) + if (activate_asl_in != 0) { tmp = calloc(1, sizeof(struct module_list)); if (tmp == NULL) return 1; - tmp->name = strdup("asl_action"); + + tmp->name = strdup("asl_in"); + if (tmp->name == NULL) + { + free(tmp); + return 1; + } + tmp->module = NULL; - tmp->init = asl_action_init; - tmp->reset = asl_action_reset; - tmp->close = asl_action_close; + tmp->init = asl_in_init; + tmp->reset = asl_in_reset; + tmp->close = asl_in_close; TAILQ_INSERT_TAIL(&Moduleq, tmp, entries); } @@ -132,7 +203,14 @@ static_modules() { tmp = calloc(1, sizeof(struct module_list)); if (tmp == NULL) return 1; + tmp->name = strdup("klog_in"); + if (tmp->name == NULL) + { + free(tmp); + return 1; + } + tmp->module = NULL; tmp->init = klog_in_init; tmp->reset = klog_in_reset; @@ -144,7 +222,14 @@ static_modules() { tmp = calloc(1, sizeof(struct module_list)); if (tmp == NULL) return 1; + tmp->name = strdup("bsd_in"); + if (tmp->name == NULL) + { + free(tmp); + return 1; + } + tmp->module = NULL; tmp->init = bsd_in_init; tmp->reset = bsd_in_reset; @@ -156,7 +241,14 @@ static_modules() { tmp = calloc(1, sizeof(struct module_list)); if (tmp == NULL) return 1; + tmp->name = strdup("bsd_out"); + if (tmp->name == NULL) + { + free(tmp); + return 1; + } + tmp->module = NULL; tmp->init = bsd_out_init; tmp->reset = bsd_out_reset; @@ -168,7 +260,14 @@ static_modules() { tmp = calloc(1, sizeof(struct module_list)); if (tmp == NULL) return 1; + tmp->name = strdup("udp_in"); + if (tmp->name == NULL) + { + free(tmp); + return 1; + } + tmp->module = NULL; tmp->init = udp_in_init; tmp->reset = udp_in_reset; @@ -185,7 +284,7 @@ static_modules() * and loads them. This does not mean the module will be used. */ static int -load_modules(char *mp) +load_modules(const char *mp) { DIR *d; struct dirent *de; @@ -223,6 +322,12 @@ load_modules(char *mp) bn = basename(modulepath); tmp->name = strdup(bn); + if (tmp->name == NULL) + { + free(tmp); + return 1; + } + tmp->module = c; TAILQ_INSERT_TAIL(&Moduleq, tmp, entries); @@ -277,12 +382,6 @@ catch_sighup(int x) reset = 1; } -static void -catch_sigwinch(int x) -{ - prune = 1; -} - static void send_reset(void) { @@ -294,48 +393,235 @@ send_reset(void) } } +/* + * perform timed activities and set next run-loop timeout + */ +static void +timed_events(struct timeval **run) +{ + uint64_t nanonow, now, delta, t; + static struct timeval next; + double d; + + nanonow = mach_absolute_time(); + d = nanonow; + d = d * mach_time_factor; + nanonow = d; + now = nanonow / 1000000000; + + *run = NULL; + next.tv_sec = 0; + next.tv_usec = 0; + + if (time_start == 0) + { + /* startup */ + time_start = now; + time_last = now; + mark_last = now; + + /* fake sweep_last so that we plan to run first_sweep_delay seconds after startup */ + sweep_last = (now - sweep_time) + first_sweep_delay; + } + + /* + * At startup, we try sending a notification once a second. + * Once it succeeds, we set the Libc global __notify_78945668_info__ to 0 + * which lets Libc's localtime calculations use notifyd to invalidate + * cached timezones. This prevents a deadlock in localtime. + */ + if (__notify_78945668_info__ < 0) + { + if (notify_post("com.apple.system.syslogd") == NOTIFY_STATUS_OK) __notify_78945668_info__ = 0; + else next.tv_sec = 1; + } + + if (time_last > now) + { + /* + * Despite Albert Einstein's assurance, time has gone backward. + * Reset "last" times to current time. + */ + time_last = now; + sweep_last = now; + mark_last = now; + } + + /* + * Run database archiver. + */ + if (sweep_time > 0) + { + delta = now - sweep_last; + if (delta >= sweep_time) + { + db_archive(time(NULL) - ttl, db_shrink_size); + sweep_last = now; + t = sweep_time; + } + else + { + t = sweep_time - delta; + } + + if (next.tv_sec == 0) next.tv_sec = t; + else if (t < next.tv_sec) next.tv_sec = t; + } + + /* + * Shrink the database if it's too large. + */ + if (db_curr_size > db_max_size) + { + db_archive(time(NULL) - ttl, db_shrink_size); + } + + /* + * Compact the database if there are too many empty slots. + */ + if (db_curr_empty > db_max_empty) + { + db_compact(); + } + + /* + * Tickle bsd_out module to flush duplicates. + */ + if (bsd_flush_time > 0) + { + bsd_flush_duplicates(); + if (bsd_flush_time > 0) + { + if (next.tv_sec == 0) next.tv_sec = bsd_flush_time; + else if (bsd_flush_time < next.tv_sec) next.tv_sec = bsd_flush_time; + } + } + + /* + * Send MARK + */ + if (mark_time > 0) + { + delta = now - mark_last; + if (delta >= mark_time) + { + asl_mark(); + mark_last = now; + t = mark_time; + } + else + { + t = mark_time - delta; + } + + if (next.tv_sec == 0) next.tv_sec = t; + else if (t < next.tv_sec) next.tv_sec = t; + } + + /* + * set output timeout parameter if runloop needs to have a timer + */ + if (next.tv_sec > 0) *run = &next; + + time_last = now; +} + +void +init_config() +{ + launch_data_t tmp, pdict; + kern_return_t status; + + tmp = launch_data_new_string(LAUNCH_KEY_CHECKIN); + launch_dict = launch_msg(tmp); + launch_data_free(tmp); + + if (launch_dict == NULL) + { + fprintf(stderr, "%d launchd checkin failed\n", getpid()); + exit(1); + } + + tmp = launch_data_dict_lookup(launch_dict, LAUNCH_JOBKEY_MACHSERVICES); + if (tmp == NULL) + { + fprintf(stderr, "%d launchd lookup of LAUNCH_JOBKEY_MACHSERVICES failed\n", getpid()); + exit(1); + } + + pdict = launch_data_dict_lookup(tmp, SERVICE_NAME); + if (pdict == NULL) + { + fprintf(stderr, "%d launchd lookup of SERVICE_NAME failed\n", getpid()); + exit(1); + } + + server_port = launch_data_get_machport(pdict); + + status = mach_port_insert_right(mach_task_self(), server_port, server_port, MACH_MSG_TYPE_MAKE_SEND); + if (status != KERN_SUCCESS) fprintf(stderr, "Warning! Can't make send right for server_port: %x\n", status); +} + int -main(int argc, char *argv[]) +main(int argc, const char *argv[]) { struct module_list *mod; fd_set rd, wr, ex, kern; - int fd, i, max, status, pdays, daemonize; - time_t lastmark, msec, ssec, tick, delta, ptime, notify_time; - char *mp, *str; - struct timeval timeout, *pto, zto; - asl_msg_t *pq; + int32_t fd, i, max, status, daemonize; + const char *mp; + struct timeval *runloop_timer, zto; + pthread_attr_t attr; + pthread_t t; + int nctoken; + mach_timebase_info_data_t info; + double mtn, mtd; + char tstr[32]; + time_t now; mp = _PATH_MODULE_LIB; - msec = DEFAULT_MARK_SEC; - pdays = DEFAULT_PRUNE_DAYS; daemonize = 0; __notify_78945668_info__ = -1; + _malloc_no_asl_log = 1; /* prevent malloc from calling ASL on error */ kfd = -1; zto.tv_sec = 0; zto.tv_usec = 0; FD_ZERO(&kern); + debug_file = NULL; + + memset(&info, 0, sizeof(mach_timebase_info_data_t)); + mach_timebase_info(&info); + + mtn = info.numer; + mtd = info.denom; + mach_time_factor = mtn / mtd; for (i = 1; i < argc; i++) { if (streq(argv[i], "-d")) { debug = 1; + if (((i+1) < argc) && (argv[i+1][0] != '-')) debug_file = argv[++i]; + memset(tstr, 0, sizeof(tstr)); + now = time(NULL); + ctime_r(&now, tstr); + tstr[19] = '\0'; + asldebug("%s syslogd[%d]: Start\n", tstr, getpid()); } - if (streq(argv[i], "-D")) + else if (streq(argv[i], "-D")) { daemonize = 1; } - if (streq(argv[i], "-u")) + else if (streq(argv[i], "-m")) { - activate_udp_in = 1; + if ((i + 1) < argc) mark_time = 60 * atoll(argv[++i]); } - else if (streq(argv[i], "-m")) + else if (streq(argv[i], "-utmp_ttl")) { - if ((i + 1) < argc) msec = 60 * atoi(argv[++i]); + if ((i + 1) < argc) utmp_ttl = atol(argv[++i]); } - else if (streq(argv[i], "-p")) + else if (streq(argv[i], "-fs_ttl")) { - if ((i + 1) < argc) pdays = atoi(argv[++i]); + if ((i + 1) < argc) fs_ttl = atol(argv[++i]); } else if (streq(argv[i], "-l")) { @@ -349,6 +635,26 @@ main(int argc, char *argv[]) if ((argv[i][0] >= '0') && (argv[i][0] <= '7') && (argv[i][1] == '\0')) asl_log_filter = ASL_FILTER_MASK_UPTO(atoi(argv[i])); } } + else if (streq(argv[i], "-a")) + { + archive_enable = 1; + } + else if (streq(argv[i], "-sweep")) + { + if ((i + 1) < argc) sweep_time = atoll(argv[++i]); + } + else if (streq(argv[i], "-dup_delay")) + { + if ((i + 1) < argc) bsd_max_dup_time = atoll(argv[++i]); + } + else if (streq(argv[i], "-ttl")) + { + if ((i + 1) < argc) ttl = atol(argv[++i]); + } + else if (streq(argv[i], "-db_max")) + { + if ((i + 1) < argc) db_max_size = atoll(argv[++i]); + } else if (streq(argv[i], "-asl_in")) { if ((i + 1) < argc) activate_asl_in = atoi(argv[++i]); @@ -375,6 +681,9 @@ main(int argc, char *argv[]) } } + db_max_empty = db_max_size / 8; + db_shrink_size = db_max_size - (db_max_size / 8); + TAILQ_INIT(&Moduleq); static_modules(); load_modules(mp); @@ -393,12 +702,12 @@ main(int argc, char *argv[]) writepid(); } + init_config(); + signal(SIGHUP, catch_sighup); - signal(SIGWINCH, catch_sigwinch); - FD_ZERO(&rd); - FD_ZERO(&wr); - FD_ZERO(&ex); + nctoken = -1; + notify_register_signal(NETWORK_CHANGE_NOTIFICATION, SIGHUP, &nctoken); for (mod = Moduleq.tqh_first; mod != NULL; mod = mod->entries.tqe_next) { @@ -406,22 +715,25 @@ main(int argc, char *argv[]) if (fd < 0) continue; } - lastmark = time(NULL); - notify_time = lastmark; - memset(&timeout, 0, sizeof(struct timeval)); - pto = NULL; - - ssec = msec; - if (ssec > 0) - { - timeout.tv_sec = ssec; - pto = &timeout; - } + /* + * Start database server thread + */ + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + pthread_create(&t, &attr, (void *(*)(void *))database_server, NULL); + pthread_attr_destroy(&attr); - ptime = 0; - if (pdays > 0) ptime = lastmark + PRUNE_AFTER_START_DELAY; + /* + * Start database worker thread + */ + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + pthread_create(&t, &attr, (void *(*)(void *))db_worker, NULL); + pthread_attr_destroy(&attr); - if (kfd >= 0) FD_SET(kfd, &kern); + FD_ZERO(&rd); + FD_ZERO(&wr); + FD_ZERO(&ex); /* * drain /dev/klog first @@ -431,26 +743,34 @@ main(int argc, char *argv[]) max = kfd + 1; while (select(max, &kern, NULL, NULL, &zto) > 0) { - aslevent_handleevent(kern, wr, ex, NULL); + aslevent_handleevent(&kern, &wr, &ex); } } - + + /* + * Start launchd drain thread + */ + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + pthread_create(&t, &attr, (void *(*)(void *))launchd_drain, NULL); + pthread_attr_destroy(&attr); + + runloop_timer = NULL; + timed_events(&runloop_timer); + forever { - if (pto != NULL) pto->tv_sec = ssec; max = aslevent_fdsets(&rd, &wr, &ex) + 1; - status = select(max, &rd, &wr, &ex, pto); + status = select(max, &rd, &wr, &ex, runloop_timer); if ((kfd >= 0) && FD_ISSET(kfd, &rd)) { - /* - * drain /dev/klog - */ + /* drain /dev/klog */ max = kfd + 1; while (select(max, &kern, NULL, NULL, &zto) > 0) { - aslevent_handleevent(kern, wr, ex, NULL); + aslevent_handleevent(&kern, &wr, &ex); } } @@ -460,61 +780,8 @@ main(int argc, char *argv[]) reset = 0; } - if (pto != NULL) - { - tick = time(NULL); - delta = tick - lastmark; - if (delta >= msec) - { - lastmark = tick; - aslmark(); - } - } - - if (prune != 0) - { - asl_prune(NULL); - prune = 0; - ptime = 0; - } - else if (ptime != 0) - { - tick = time(NULL); - if (tick >= ptime) - { - pq = asl_new(ASL_TYPE_QUERY); - str = NULL; - asprintf(&str, "-%dd", pdays); - asl_set_query(pq, ASL_KEY_TIME, str, ASL_QUERY_OP_LESS); - if (str != NULL) free(str); - - /* asl_prune frees the query */ - asl_prune(pq); - ptime = 0; - } - } + if (status != 0) aslevent_handleevent(&rd, &wr, &ex); - if (__notify_78945668_info__ < 0) - { - tick = time(NULL); - if (tick >= notify_time) - { - if (notify_post("com.apple.system.syslogd") == NOTIFY_STATUS_OK) __notify_78945668_info__ = 0; - else notify_time = tick + NOTIFY_DELAY; - } - } - - if (status != 0) aslevent_handleevent(rd, wr, ex, NULL); + timed_events(&runloop_timer); } } - -int -asldebug(const char *str, ...) -{ - va_list v; - - if (debug == 0) return 0; - - va_start(v, str); - return vprintf(str, v); -} diff --git a/syslogd.tproj/syslogd.sb b/syslogd.tproj/syslogd.sb new file mode 100644 index 0000000..1a9d65a --- /dev/null +++ b/syslogd.tproj/syslogd.sb @@ -0,0 +1,38 @@ +;; +;; syslogd - sandbox profile +;; Copyright (c) 2007 Apple Inc. All Rights reserved. +;; +;; WARNING: The sandbox rules in this file currently constitute +;; Apple System Private Interface and are subject to change at any time and +;; without notice. The contents of this file are also auto-generated and not +;; user editable; it may be overwritten at any time. +;; +(version 1) +(debug deny) + +(import "bsd.sb") + +(deny default) +(allow process*) +(deny signal) +(allow sysctl-read) +(allow network*) + +;;; Allow syslogd specific files + +(allow file-write* file-read-data file-read-metadata + (regex #"^(/private)?/var/run/syslog$" + #"^(/private)?/var/run/syslog\.pid$" + #"^(/private)?/var/run/asl_input$")) + +(allow file-write* file-read-data file-read-metadata + (regex #"^(/private)?/dev/console$" + #"^(/private)?/var/log/.*\.log$" + #"^(/private)?/var/log/asl\.db$")) + +(allow file-read-data file-read-metadata + (regex #"^(/private)?/dev/klog$" + #"^(/private)?/etc/asl\.conf$" + #"^(/private)?/etc/syslog\.conf$" + #"^/usr/lib/asl/.*\.so$")) +(allow mach-lookup (global-name "com.apple.system.notification_center")) diff --git a/syslogd.tproj/udp_in.c b/syslogd.tproj/udp_in.c index fa91a69..e398eab 100644 --- a/syslogd.tproj/udp_in.c +++ b/syslogd.tproj/udp_in.c @@ -3,21 +3,20 @@ * * @APPLE_LICENSE_HEADER_START@ * - * "Portions Copyright (c) 2004 Apple Computer, Inc. All Rights - * Reserved. This file contains Original Code and/or Modifications of - * Original Code as defined in and that are subject to the Apple Public - * Source License Version 1.0 (the 'License'). You may not use this file - * except in compliance with the License. Please obtain a copy of the - * License at http://www.apple.com/publicsource and read it before using - * this file. + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT. Please see the - * License for the specific language governing rights and limitations - * under the License." + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ @@ -40,6 +39,7 @@ #define forever for(;;) +#define UDP_SOCKET_NAME "NetworkListener" #define MY_ID "udp_in" #define MAXLINE 4096 @@ -52,30 +52,10 @@ static char uline[MAXLINE + 1]; #define FMT_LEGACY 0 #define FMT_ASL 1 -asl_msg_t * -udp_convert(int fmt, char *s, int len, char *from) -{ - char *out; - asl_msg_t *m; - - out = NULL; - m = NULL; - - if (fmt == FMT_ASL) - { - m = asl_msg_from_string(s); - if (from != NULL) asl_set(m, ASL_KEY_HOST, from); - return m; - } - - return asl_syslog_input_convert(uline, len, from, 0); -} - asl_msg_t * udp_in_acceptmsg(int fd) { - int format, status, x, fromlen; - size_t off; + socklen_t fromlen; ssize_t len; struct sockaddr_storage from; char fromstr[64], *r, *p; @@ -111,77 +91,76 @@ udp_in_acceptmsg(int fd) p = strrchr(uline, '\n'); if (p != NULL) *p = '\0'; - - /* - * Determine if the input is "old" syslog format or new ASL format. - * Old format lines should start with "<", but they can just be - * straight text. ASL input starts with a length (10 bytes) - * followed by a space and a '['. - */ - format = FMT_LEGACY; - off = 0; - - if ((uline[0] != '<') && (len > 11)) - { - status = sscanf(uline, "%d ", &x); - if (status == 1) - { - if ((uline[10] == ' ') && (uline[11] == '[')) - { - format = FMT_ASL; - off = 11; - } - } - } - - return udp_convert(format, uline+off, len-off, r); + return asl_input_parse(uline, len, r, 0); } int udp_in_init(void) { - struct addrinfo hints, *gai, *ai; - int status, i; + int i, rbufsize, len; + launch_data_t sockets_dict, fd_array, fd_dict; asldebug("%s: init\n", MY_ID); if (nsock > 0) return 0; + if (launch_dict == NULL) + { + asldebug("%s: laucnchd dict is NULL\n", MY_ID); + return -1; + } - memset(&hints, 0, sizeof(hints)); - hints.ai_flags = AI_PASSIVE; - hints.ai_family = PF_UNSPEC; - hints.ai_socktype = SOCK_DGRAM; + sockets_dict = launch_data_dict_lookup(launch_dict, LAUNCH_JOBKEY_SOCKETS); + if (sockets_dict == NULL) + { + asldebug("%s: laucnchd lookup of LAUNCH_JOBKEY_SOCKETS failed\n", MY_ID); + return -1; + } + + fd_array = launch_data_dict_lookup(sockets_dict, UDP_SOCKET_NAME); + if (fd_array == NULL) + { + asldebug("%s: laucnchd lookup of UDP_SOCKET_NAME failed\n", MY_ID); + return -1; + } + + nsock = launch_data_array_get_count(fd_array); + if (nsock <= 0) + { + asldebug("%s: laucnchd fd array is empty\n", MY_ID); + return -1; + } + + for (i = 0; i < nsock; i++) + { + fd_dict = launch_data_array_get_index(fd_array, i); + if (fd_dict == NULL) + { + asldebug("%s: laucnchd file discriptor array element 0 is NULL\n", MY_ID); + return -1; + } + + ufd[i] = launch_data_get_fd(fd_dict); - status = getaddrinfo(NULL, "syslog", &hints, &gai); - if (status != 0) return -1; + rbufsize = 128 * 1024; + len = sizeof(rbufsize); - for (ai = gai; (ai != NULL) && (nsock < MAXSOCK); ai = ai->ai_next) - { - ufd[nsock] = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); - if (ufd[nsock] < 0) + if (setsockopt(ufd[i], SOL_SOCKET, SO_RCVBUF, &rbufsize, len) < 0) { - asldebug("%s: socket: %s\n", MY_ID, strerror(errno)); + asldebug("%s: couldn't set receive buffer size for socket %d: %s\n", MY_ID, ufd[i], strerror(errno)); + close(ufd[i]); + ufd[i] = -1; continue; } - if (bind(ufd[nsock], ai->ai_addr, ai->ai_addrlen) < 0) + if (fcntl(ufd[i], F_SETFL, O_NONBLOCK) < 0) { - asldebug("%s: bind: %s\n", MY_ID, strerror(errno)); - close(ufd[nsock]); + asldebug("%s: couldn't set O_NONBLOCK for socket %d: %s\n", MY_ID, ufd[i], strerror(errno)); + close(ufd[i]); + ufd[i] = -1; continue; } - - nsock++; - } - - freeaddrinfo(gai); - - if (nsock == 0) - { - asldebug("%s: no input sockets\n", MY_ID); - return -1; } - for (i = 0; i < nsock; i++) aslevent_addfd(ufd[i], udp_in_acceptmsg, NULL, NULL); + for (i = 0; i < nsock; i++) if (ufd[i] != -1) aslevent_addfd(ufd[i], 0, udp_in_acceptmsg, NULL, NULL); return 0; } @@ -200,7 +179,7 @@ udp_in_close(void) for (i = 0; i < nsock; i++) { - close(ufd[i]); + if (ufd[i] != -1) close(ufd[i]); ufd[i] = -1; } diff --git a/util.tproj/Makefile b/util.tproj/Makefile index 6049f02..8274d83 100644 --- a/util.tproj/Makefile +++ b/util.tproj/Makefile @@ -24,7 +24,7 @@ MAKEFILE = tool.make NEXTSTEP_INSTALLDIR = /usr/bin WINDOWS_INSTALLDIR = /usr/bin PDO_UNIX_INSTALLDIR = /usr/bin -LIBS = +LIBS = -laslcommon DEBUG_LIBS = $(LIBS) PROF_LIBS = $(LIBS) diff --git a/util.tproj/Makefile.preamble b/util.tproj/Makefile.preamble index 1dca380..823c969 100644 --- a/util.tproj/Makefile.preamble +++ b/util.tproj/Makefile.preamble @@ -92,6 +92,7 @@ CLEAN_ALL_SUBPROJECTS = MSGFILES = # .defs files that should have mig run on them DEFSFILES = + # .mig files (no .defs files) that should have mig run on them MIGFILES = diff --git a/util.tproj/syslog.1 b/util.tproj/syslog.1 index e0aaf3c..6c7924f 100644 --- a/util.tproj/syslog.1 +++ b/util.tproj/syslog.1 @@ -51,11 +51,21 @@ key val .Li ... .D1 "" .Nm -.Op Fl w +.Fl C +.D1 "" +.Nm +.Op Fl db Op Ar file ... +.Op Fl w Op Ar n .Op Fl F Ar format +.Op Fl T Ar format .Ar expression .D1 "" .Nm +.Op Fl db Op Ar file ... +.Fl x Ar expression +.D1 "" +.Nm +.Op Fl db Op Ar file ... .Fl p Ar expression .D1 "" .Nm @@ -64,7 +74,7 @@ key val .Nm is a command-line utility for a variety of tasks relating to the Apple System Log facility. It provides mechanisms for sending and viewing log messages, -pruning the contents of the system's log message data store, +pruning the contents of the log message databases, and for controlling the flow of log messages from client processes. .Pp When invoked with the @@ -125,6 +135,11 @@ The string .Dq Panic is an alias for .Dq Emergency . +.Pp +If the +.Fl l +option is omitted, the log level defaults to 7 (Debug). +.Pp .Nm only requires one or two leading characters for a level specification. A single character suffices in most cases. @@ -140,47 +155,104 @@ for Error). .Ss READING MESSAGES The .Nm syslogd -daemon receives messages from a variety of input sources. -Received messages are processed by a set of output modules, -each of which may act on messages in different ways. -.Pp -Two of the standard modules filter messages using criteria like the sender and the priority level of the message, -and write copies of these messages to different output streams. -One module does this filtering and filing task using the configuration specified in the +daemon filters and saves log messages to different output streams. +One module saves messages to files specified in the .Xr syslog.conf 5 file. -The output files specified in that configuration may be examined by any file printing or editing utility, +Those log files may be examined with any file printing or editing utility, e.g. .Pp .Dl cat /var/log/system.log .Pp -Another module saves messages in a data store, which may be searched using the -.Nm -command. +Another module saves messages in an active database, +and may also be configured to keep archive databases. .Pp If invoked with no arguments, .Nm -simply prints all of the messages saved in the data store. If the +sends a query to +.Nm syslogd +to fetch all messages from the active database. +The messages are then printed to standard output, subject to formatting options and character encoding as described below. +.Pp +If invoked with the +.Fl C +option, +.Nm +fetches and prints console messages. +The +.Fl C +option is actually an alias for the expression: +.Pp +.Dl -k Facility com.apple.console +.Pp +See the EXPRESSIONS section below for more details. +.Pp +If the system is running in single-user mode, +.Nm +will open the ASL active database file (/var/log/asl.db) directly, rather than queying +.Nm syslogd +for messages. +The database file is opened in read-only mode, and read access permissions are required. +Specifying the +.Fl db +option with no argument also forces raw database access. +.Pp +Archive log databases may be read by providing one or more database names as arguments to the +.Fl db +option. +Each database is read in sequence. +As a special case, the name +.Dq - +may be used in place of a database file name. +In this case, +.Nm +will connect to +.Nm syslogd +and query for the contents of the active database. +.Pp +The .Fl w -option is used, +option causes +.Nm +to wait for new messages. +By default, .Nm -waits for new messages to be added to the data store. -Messages already in the store are ignored. +prints the last 10 messages, +then waits for new messages to be added to the database. +A number following the +.Fl w +option specifies the number of messages to print and overrides the default value of 10. +For example: +.Pp +.Dl syslog -w 20 +.Pp This usage is similar to watching a log file using, e.g. .Pp .Dl tail -f /var/log/system.log .Pp -Messages are printed in a format similar to that used in the system.log file, -except that the message priority level is printed between angle-brackets. +The +.Fl w +option can only be used for a single database, and when printing messages to standard output. .Pp +If the +.Fl x Ar database +option is specified, messages are copied to the named +.Ar database +file rather than being printed. The -.Fl u -option forces all time stamps to be printed using UTC. -This overrides printing of time stamps using the local time zone. +.Ar database +file will be created if it does not exist. +.Pp +When called without the +.Fl x +option, messages are printed to standard output. +Messages are printed in a format similar to that used in the system.log file, +except that the message priority level is printed between angle-brackets. .Pp The output format may by changed by specifying the .Fl F Ar format option. +Non-printable and control characters are encoded in the output, as described below. The value of .Ar format may be one of the following: @@ -199,7 +271,15 @@ but includes the message priority level. Prints the complete message structure. Each key/value pair is enclosed in square brackets. Embedded closing brackets and white space are escaped. -Time stamps are printed using UTC rather than being converted to the local time zone. +Time stamps are printed as seconds since the epoch by default, but may also be +printed in local time or UTC if the +.Fl T +option is specified (see below). +.It xml +The list of messages is printed as an XML property list. +Each message is represented as a dictionary in a array. +Dictionary keys represent message keys. +Dictionary values are strings. .El .Pp The value of the @@ -222,13 +302,68 @@ produces output similar to the .Dq bsd format. .Pp +Timestamps may be printed in three formats. +Times are generally converted to local time, except when the +.Fl F Ar sec +option is used, in which case times are printed as the number of seconds since the epoch. +The +.Fl T Ar format +option may be used to explicity control the format used for timestamps. +The value of +.Ar format +may be one of the following: +.Pp +.Bl -tag -width "local" +.It sec +Times are printed as the number of seconds since the epoch. +.It local +Times are converted to the local time zone, and printed with the format +.Dl MMM DD HH:MM:SS +.It utc +Times are converted to UTC, and printed with the format +.Dl YYYY.MM.DD HH:MM:SS UTC +.El +.Pp +The +.Fl u +option is a short form for +.Fl T Ar utc . +.Pp +Control characters and non-printable characters are encoded in the output stream. +In some cases this may make messages slightly less natural in appearance. +However, the encoding is designed to preserve all the information in the log message, +and to prevent malicious users from spoofing or obsucring information in log messages. +.Pp +Output in the +.Dq std , +.Dq bsd , +and +.Dq raw +formats is encoded as it is by the +.Xr vis +utility with the +.Fl c +option. +Newlines and tabs are also encoded as "\\n" and "\\t" respectively. +In +.Dq raw +format, space chanacters embedded in log message keys are encoded as "\\s" +and embedded brackets are escaped to print as "\\[" and "\\]". +.Pp +XML format output requires that keys are valid UTF8 strings. +Keys which are not valid UTF8 are ignored, and the associated value is not printed. +.Pp +Values that contain legal UTF8 are printed as strings. +Ampersand, less than, greater than, quotation mark, and apostrophe characters are encoded according to XML conventions. +Embedded control characters are encoded as +.Dq &#xNN; +where NN is the character's hexidecimal value. +.Pp +Values that do not contain legal UTF8 are encoded in base-64 and printed as data objects. +.Pp If no further command line options are specified, .Nm -displays all messages, -either all those saved in the data store, -or all new messages if -.Fl w -is used. +displays all messages, or copies all messages to a database file. However, an expression may be specified using the .Fl k and @@ -236,17 +371,25 @@ and options. .Ss EXPRESSIONS Expressions specify matching criteria. -They may be used when reading messages to filter for messages of interest. +They may be used to search for messages of interest. Expressions are also required when pruning the system log file with the .Fl p option. .Pp A simple expression is a list of one or more key/value pairs. A match is made when a message has the given value for the specified key. -For example, to find all messages send by the portmap process: +For example, to find all messages sent by the portmap process: .Pp .Dl syslog -k Sender portmap .Pp +Note that the +.Fl C +option is treated as an alias for the expression: +.Pp +.Dl -k Facility com.apple.console +.Pp +This provides a quick way to search for console messages. +.Pp The .Fl k option may be followed by one, two, or three arguments. @@ -327,30 +470,40 @@ A week is taken to be 7 complete days (i.e. 604800 seconds). .Ss PRUNING The Apple System Log facility saves received messages, subject to filtering criteria described in the FILTERING CONTROLS section below. -Pruning is required to prevent unlimited growth of the data store. -.Pp The .Nm syslogd -daemon itself will prune the data store shortly after it starts up. +daemon deletes messages after given time-to-live values to prevent the database from growing too large. +When messages expire, they are either removed entirely, or copied to an archive database. See the .Xr syslogd 8 -manual for more details on startup pruning. -During extended operation of -.Nm syslogd , -pruning is accomplished by using the +manual for more details on archiving messages. +.Pp +Messages may be removed from either the active database or from an archive database by using the .Fl p option of .Nm syslog . The .Fl p option must be followed by an expression (see above). -The contents of the data store are filtered using the given expression. Messages that match the expression are deleted. .Pp -A daily pruning operation is performed by the -.Nm cron -utility. -The command is specified in the /etc/periodic/daily/500.daily file. +If the +.Fl db +option is not specified +.Nm +sends a request to +.Nm syslogd +to perform the requested pruning operation. +If +.Fl db +is given without a database file name, +.Nm +prunes the active database file. +This may only be done if the +.Nm syslogd +server is not running. +If one or more database file names are given, those databases are pruned subject to the specified expression. +Read and write access to the database files are required. .Ss FILTERING CONTROLS Clients of the Apple System Log facility using either the .Xr asl 3 diff --git a/util.tproj/syslog.c b/util.tproj/syslog.c index 74e69e7..e03833e 100644 --- a/util.tproj/syslog.c +++ b/util.tproj/syslog.c @@ -3,21 +3,20 @@ * * @APPLE_LICENSE_HEADER_START@ * - * "Portions Copyright (c) 2004 Apple Computer, Inc. All Rights - * Reserved. This file contains Original Code and/or Modifications of - * Original Code as defined in and that are subject to the Apple Public - * Source License Version 1.0 (the 'License'). You may not use this file - * except in compliance with the License. Please obtain a copy of the - * License at http://www.apple.com/publicsource and read it before using - * this file. + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT. Please see the - * License for the specific language governing rights and limitations - * under the License." + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ @@ -29,14 +28,19 @@ #include #include #include +#include #include #include #include #include +#include +#include #include #include #include #include +#include +#include #define MOD_CASE_FOLD 'C' #define MOD_REGEX 'R' @@ -54,6 +58,8 @@ #define ASL_QUERY_OP_NOT 0x1000 +#define FACILITY_CONSOLE "com.apple.console" + #define SEARCH_EOF -1 #define SEARCH_NULL 0 #define SEARCH_MATCH 1 @@ -70,9 +76,16 @@ #define SEND_FORMAT_LEGACY 0 #define SEND_FORMAT_ASL 1 -#define PRINT_LOCALTIME 0x00000001 -#define PRINT_LEGACY_FMT 0x00000002 -#define PRINT_STD_FMT 0x00000004 +#define TIME_SEC 0x00000001 +#define TIME_UTC 0x00000002 +#define TIME_LCL 0x00000004 + +#define FORMAT_RAW 0x00000010 +#define FORMAT_LEGACY 0x00000020 +#define FORMAT_STD 0x00000040 +#define FORMAT_XML 0x00000080 + +#define EXPORT 0x00000100 #define ASL_FILTER_MASK_PACEWNID 0xff #define ASL_FILTER_MASK_PACEWNI 0x7f @@ -81,24 +94,31 @@ #define ASL_FILTER_MASK_PACE 0x0f #define ASL_FILTER_MASK_PAC 0x07 +#define FETCH_BATCH 256 -/* BEGIN PRIVATE API */ -#define _PATH_ASL_PRUNE "/var/run/asl_prune" -#define _PATH_SYSLOGD_PID "/var/run/syslog.pid" +#define _PATH_ASL_STORE "/var/log/asl.db" +static asl_store_t **dbstore = NULL; +static uint32_t store_count = 0; +static uint32_t store_raw = 1; + +static asl_store_t *export = NULL; /* notify SPI */ -uint32_t notify_get_state(int token, int *state); -uint32_t notify_set_state(int token, int state); uint32_t notify_register_plain(const char *name, int *out_token); extern char *asl_msg_to_string(aslmsg msg, uint32_t *len); extern asl_msg_t *asl_msg_from_string(const char *buf); +extern char *asl_list_to_string(asl_search_result_t *list, uint32_t *outlen); +extern asl_search_result_t *asl_list_from_string(const char *buf); extern int asl_msg_cmp(asl_msg_t *a, asl_msg_t *b); extern time_t asl_parse_time(const char *in); /* END PRIVATE API */ static const char *myname = "syslog"; +#define ASL_SERVICE_NAME "com.apple.system.logger" +static mach_port_t asl_server_port = MACH_PORT_NULL; + void usage() { @@ -122,18 +142,26 @@ usage() fprintf(stderr, " d = Debug\n"); fprintf(stderr, " a minus sign preceeding a single letter means \"up to\" that level\n"); fprintf(stderr, "\n"); - fprintf(stderr, "%s -p [-k key [[op] val]]... [-o -k key [[op] val]] ...]...\n", myname); + fprintf(stderr, "%s -p [-db [file]...] [-k key [[op] val]]... [-o -k key [[op] val]] ...]...\n", myname); + fprintf(stderr, " -db prune /var/log/asl.db or named file, rather than sending a prune command to syslogd.\n"); fprintf(stderr, " -p prune datastore according to input expression (see below)\n"); fprintf(stderr, "\n"); - fprintf(stderr, "%s [-w] [-F format] [-u] [-k key [[op] val]]... [-o -k key [[op] val]] ...]...\n", myname); - fprintf(stderr, " -w watch file (^C to quit)\n"); - fprintf(stderr, " -F output format may be \"std\", \"raw\", or \"bsd\"\n"); + fprintf(stderr, "%s [-db [file]...] [-x file] [-w [N]] [-F format] [-u] [-k key [[op] val]]... [-o -k key [[op] val]] ...]...\n", myname); + fprintf(stderr, " -db read /var/log/asl.db or named file, rather than querying syslogd.\n"); + fprintf(stderr, " use \"-\" to explicitly include a connection to syslogd.\n"); + fprintf(stderr, " -x export to named database, rather than printing\n"); + fprintf(stderr, " -w watch database (^C to quit)\n"); + fprintf(stderr, " prints the last N matching lines (default 10) before waiting\n"); + fprintf(stderr, " \"-w 0\" prints all matching lines before waiting\n"); + fprintf(stderr, " -F output format may be \"std\", \"raw\", \"bsd\", or \"xml\"\n"); fprintf(stderr, " format may also be a string containing variables of the form\n"); fprintf(stderr, " $Key or $(Key) - use the latter for non-whitespace delimited variables\n"); - fprintf(stderr, " -u force printing of all timestamps using UTC\n"); + fprintf(stderr, " -T timestamp format may be \"sec\" (seconds), \"utc\" (UTC), or \"local\" (local timezone)\n"); + fprintf(stderr, " -u print timestamps using UTC (equivalent to \"-T utc\")\n"); fprintf(stderr, " -k key/value match\n"); fprintf(stderr, " if no operator or value is given, checks for the existance of the key\n"); fprintf(stderr, " if no operator is given, default is \"%s\"\n", OP_EQ); + fprintf(stderr, " -C alias for \"-k Facility com.apple.console\"\n"); fprintf(stderr, " -o begins a new query\n"); fprintf(stderr, " queries are \'OR\'ed together\n"); fprintf(stderr, "operators are zero or more modifiers followed by a comparison\n"); @@ -196,8 +224,8 @@ procinfo(char *pname, int *pid, int *uid) do { size += size / 10; - newprocs = realloc(procs, size); - if (newprocs == 0) + newprocs = reallocf(procs, size); + if (newprocs == NULL) { if (procs != NULL) free(procs); return PROC_NOT_FOUND; @@ -264,8 +292,9 @@ procinfo(char *pname, int *pid, int *uid) int rcontrol_get_string(const char *prefix, int pid, int *val) { - int t, x, status; + int t, status; char *name; + uint64_t x; status = NOTIFY_STATUS_OK; @@ -303,7 +332,8 @@ rcontrol_set_string(const char *prefix, int pid, int filter) { int t, status; char *name; - + uint64_t x; + status = NOTIFY_STATUS_OK; if (pid == RC_SYSLOGD) @@ -319,13 +349,15 @@ rcontrol_set_string(const char *prefix, int pid, int filter) name = NULL; asprintf(&name, "%s.%d", prefix, pid); if (name == NULL) return NOTIFY_STATUS_FAILED; - + status = notify_register_plain(name, &t); free(name); } if (status != NOTIFY_STATUS_OK) return status; - status = notify_set_state(t, filter); + + x = filter; + status = notify_set_state(t, x); if ((pid == RC_SYSLOGD) && (status == NOTIFY_STATUS_OK)) status = notify_post(NOTIFY_SYSTEM_ASL_FILTER); notify_cancel(t); return status; @@ -388,37 +420,37 @@ asl_filter_string(int f) strcat(str, "Emergency - Debug"); return str; } - + if ((f == ASL_FILTER_MASK_PACEWNI) != 0) { strcat(str, "Emergency - Info"); return str; } - + if ((f == ASL_FILTER_MASK_PACEWN) != 0) { strcat(str, "Emergency - Notice"); return str; } - + if ((f == ASL_FILTER_MASK_PACEW) != 0) { strcat(str, "Emergency - Warning"); return str; } - + if ((f == ASL_FILTER_MASK_PACE) != 0) { strcat(str, "Emergency - Error"); return str; } - + if ((f == ASL_FILTER_MASK_PAC) != 0) { strcat(str, "Emergency - Critical"); return str; } - + if ((f & ASL_FILTER_MASK_EMERG) != 0) { strcat(str, "Emergency"); @@ -475,7 +507,7 @@ asl_filter_string(int f) } if (i == 0) sprintf(str, "Off"); - + return str; } @@ -509,7 +541,7 @@ rcontrol_get(const char *prefix, int pid) printf("Process %d syslog filter mask: %s\n", pid, asl_filter_string(filter)); return 0; } - + printf("Unable to determine syslog filter mask for pid %d\n", pid); return -1; } @@ -657,19 +689,19 @@ static int _isanumber(char *s) { int i; - + if (s == NULL) return 0; - + i = 0; if ((s[0] == '-') || (s[0] == '+')) i = 1; - + if (s[i] == '\0') return 0; - + for (; s[i] != '\0'; i++) { if (!isdigit(s[i])) return 0; } - + return 1; } @@ -679,7 +711,7 @@ asl_string_to_level(const char *s) if (s == NULL) return -1; if ((s[0] >= '0') && (s[0] <= '7') && (s[1] == '\0')) return atoi(s); - + if (!strncasecmp(s, "em", 2)) return ASL_LEVEL_EMERG; else if (!strncasecmp(s, "p", 1)) return ASL_LEVEL_EMERG; else if (!strncasecmp(s, "a", 1)) return ASL_LEVEL_ALERT; @@ -693,7 +725,7 @@ asl_string_to_level(const char *s) return -1; } - + int syslog_remote_control(int argc, char *argv[]) { @@ -774,7 +806,7 @@ syslog_remote_control(int argc, char *argv[]) return 0; } - + int syslog_send(int argc, char *argv[]) { @@ -785,21 +817,19 @@ syslog_send(int argc, char *argv[]) kv = 0; rhost = NULL; - rfmt = SEND_FORMAT_ASL; + rfmt = SEND_FORMAT_LEGACY; start = 1; rlevel = 7; for (i = 1; i < argc; i++) { if (!strcmp(argv[i], "-s")) start = i+1; - else if (!strcmp(argv[i], "-k")) kv = 1; - else if (!strcmp(argv[i], "-r")) + else if (!strcmp(argv[i], "-k")) { - rhost = argv[++i]; - start = i+1; - rfmt = SEND_FORMAT_LEGACY; + kv = 1; + rfmt = SEND_FORMAT_ASL; } - else if (!strcmp(argv[i], "-R")) + else if (!strcmp(argv[i], "-r")) { rhost = argv[++i]; start = i+1; @@ -832,6 +862,8 @@ syslog_send(int argc, char *argv[]) len = 0; for (i = start; i < argc; i++) len += (strlen(argv[i]) + 1); str = calloc(len + 1, 1); + if (str == NULL) return -1; + for (i = start; i < argc; i++) { strcat(str, argv[i]); @@ -867,257 +899,246 @@ syslog_send(int argc, char *argv[]) } static void -printmsg(FILE *f, asl_msg_t *msg, char *mstr, char *fmt, int pflags) +print_xml_header(FILE *f) { - char *k, *t, c; - const char *v; - int i, j, l, paren, oval; - time_t tick; - - if ((pflags & PRINT_STD_FMT) || (pflags & PRINT_LEGACY_FMT)) - { - /* LEGACY: Mth dd hh:mm:ss host sender[pid]: message */ - /* STD: Mth dd hh:mm:ss host sender[pid] : message */ - - /* Time */ - v = asl_get(msg, ASL_KEY_TIME); - tick = 0; - if (v == NULL) - { - fprintf(f, "***Time unknown "); - } - else - { - tick = asl_parse_time(v); - t = ctime(&tick); - if (t == NULL) fprintf(f, "***Time unknown "); - else - { - t[19] = '\0'; - fprintf(f, "%s ", t + 4); - } - } + if (f == NULL) return; - /* Host */ - v = asl_get(msg, ASL_KEY_HOST); - if (v != NULL) fprintf(f, "%s ", v); + fprintf(f, "\n"); + fprintf(f, "\n"); + fprintf(f, "\n"); + fprintf(f, "\n"); +} - /* Sender */ - v = asl_get(msg, ASL_KEY_SENDER); - if (v != NULL) fprintf(f, "%s", v); +static void +print_xml_trailer(FILE *f) +{ + if (f == NULL) return; - /* PID */ - v = asl_get(msg, ASL_KEY_PID); - if ((v != NULL) && (v[0] != '-')) fprintf(f, "[%s]", v); + fprintf(f, "\n"); + fprintf(f, "\n"); +} - v = asl_get(msg, ASL_KEY_LEVEL); - i = -1; - if (_isanumber((char *)v)) i = atoi(v); - if (pflags & PRINT_STD_FMT) fprintf(f, " <%s>", asl_level_string(i)); +static void +print_xml_str(FILE *f, const char *str) +{ + uint32_t i; - fprintf(f, ": "); - - /* Message */ - v = asl_get(msg, ASL_KEY_MSG); - if (v != NULL) fprintf(f, "%s", v); + if (f == NULL) return; + if (str == NULL) return; - fprintf(f, "\n"); - return; + for (i = 0; str[i] != '\0'; i++) + { + if (str[i] == '&') fprintf(f, "&"); + else if (str[i] == '<') fprintf(f, "<"); + else if (str[i] == '>') fprintf(f, ">"); + else if (str[i] == '"') fprintf(f, """); + else if (str[i] == '\'') fprintf(f, "'"); + else fprintf(f, "%c", str[i]); } +} - if (fmt == NULL) +static void +printsafe(FILE *f, const char *str) +{ + uint8_t c; + uint32_t i; + + if (f == NULL) return; + if (str == NULL) return; + + for (i = 0; str[i] != '\0'; i++) { - fprintf(f, "%s\n", mstr); - return; + c = str[i]; + + if (isascii(c) && iscntrl(c)) + { + if (c == '\n') printf("\\n"); + else if (c == '\t') printf("\t"); + else printf("^%c", c ^ 0100); + } + else printf("%c", c); } +} - for (i = 0; fmt[i] != '\0'; i++) +static void +printmsg(FILE *f, asl_msg_t *msg, char *fmt, int pflags) +{ + char *str; + const char *mf, *tf; + uint32_t len, status; + uint64_t msgid; + + if (f == NULL) { - if (fmt[i] == '$') + if (export != NULL) { - i++; - paren = 0; - - if (fmt[i] == '(') + status = asl_store_save(export, msg, -1, -1, &msgid); + if (status != ASL_STATUS_OK) { - paren = 1; - i++; + fprintf(stderr, "export database write failed: %s\n", asl_store_error(status)); + asl_store_close(export); + export = NULL; } + } - k = calloc(1, 1); - l = 0; + return; + } - for (j = i; fmt[j] != '\0'; j++) - { - c = '\0'; - if (fmt[j] == '\\') c = fmt[++j]; - else if ((paren == 1) && (fmt[j] ==')')) break; - else if (fmt[j] != ' ') c = fmt[j]; + mf = ASL_MSG_FMT_RAW; + if (fmt != NULL) mf = (const char *)fmt; + else if (pflags & FORMAT_STD) mf = ASL_MSG_FMT_STD; + else if (pflags & FORMAT_LEGACY) mf = ASL_MSG_FMT_BSD; + else if (pflags & FORMAT_XML) mf = ASL_MSG_FMT_XML; - if (c == '\0') break; + tf = ASL_TIME_FMT_SEC; + if (pflags & TIME_UTC) tf = ASL_TIME_FMT_UTC; + if (pflags & TIME_LCL) tf = ASL_TIME_FMT_LCL; - k = realloc(k, l + 1); - k[l] = c; - k[l + 1] = '\0'; - l++; - } + len = 0; + str = asl_format_message(msg, mf, tf, &len); + if (str != NULL) + { + fprintf(f, "%s", str); + free(str); + } +} - if (paren == 1) j++; - i = j; - if (l > 0) - { - v = asl_get(msg, k); - if (v != NULL) - { - if ((pflags & PRINT_LOCALTIME) && (!strcmp(k, ASL_KEY_TIME))) - { - /* convert UTC time to localtime */ - tick = asl_parse_time(v); - t = ctime(&tick); - if (t == NULL) fprintf(f, "%s", v); - else - { - t[19] = '\0'; - fprintf(f, "%s", t + 4); - } - } - else - { - fprintf(f, "%s", v); - } - } - } - free(k); - } +uint32_t +send_prune(asl_search_result_t *pl) +{ + char *str; + caddr_t vmstr; + uint32_t len, status; + kern_return_t kstatus; + security_token_t sec; - if (fmt[i] == '\\') - { - i++; - if (fmt[i] == '$') fprintf(f, "$"); - else if (fmt[i] == 'e') fprintf(f, "\e"); - else if (fmt[i] == 'a') fprintf(f, "\a"); - else if (fmt[i] == 'b') fprintf(f, "\b"); - else if (fmt[i] == 'f') fprintf(f, "\f"); - else if (fmt[i] == 'n') fprintf(f, "\n"); - else if (fmt[i] == 'r') fprintf(f, "\r"); - else if (fmt[i] == 't') fprintf(f, "\t"); - else if (fmt[i] == 'v') fprintf(f, "\v"); - else if (fmt[i] == '\'') fprintf(f, "\'"); - else if (fmt[i] == '\\') fprintf(f, "\\"); - else if (isdigit(fmt[i])) - { - oval = fmt[i] - '0'; - if (isdigit(fmt[i+1])) - { - i++; - oval = (oval * 8) + (fmt[i] - '0'); - if (isdigit(fmt[i+1])) - { - i++; - oval = (oval * 8) + (fmt[i] - '0'); - } - } - c = oval; - fputc(c, stdout); - } - continue; - } + if (asl_server_port == MACH_PORT_NULL) return 1; - if (fmt[i] == '\0') break; - fputc(fmt[i], stdout); + len = 0; + str = asl_list_to_string(pl, &len); + + kstatus = vm_allocate(mach_task_self(), (vm_address_t *)&vmstr, len, TRUE); + if (kstatus != KERN_SUCCESS) + { + free(str); + return 1; } - fprintf(f, "\n"); + memmove(vmstr, str, len); + free(str); + + sec.val[0] = -1; + sec.val[1] = -1; + status = 0; + + kstatus = _asl_server_prune(asl_server_port, (caddr_t)vmstr, len, (int *)&status, &sec); + if (kstatus != KERN_SUCCESS) status = 1; + + return status; } -static char * -getnextline(FILE *fp, int watch) +asl_search_result_t * +send_query(asl_search_result_t *q, uint64_t start, int count, int dir, uint64_t *last) { - char *out, c; - int len, count; + char *str, *res; + caddr_t vmstr; + uint32_t len, reslen, status; + kern_return_t kstatus; + security_token_t sec; + asl_search_result_t *l; + + if (asl_server_port == MACH_PORT_NULL) return NULL; - len = CHUNK; - count = 0; - out = calloc(len + 1, 1); + len = 0; + str = asl_list_to_string(q, &len); - forever + kstatus = vm_allocate(mach_task_self(), (vm_address_t *)&vmstr, len, TRUE); + if (kstatus != KERN_SUCCESS) { - c = getc(fp); - if (c == EOF) - { - if (watch == 0) - { - if (count == 0) - { - free(out); - return NULL; - } - return out; - } - clearerr(fp); - usleep(250000); - continue; - } + free(str); + return NULL; + } - if (c == '\n') return out; - if (c == '\0') return out; + memmove(vmstr, str, len); + free(str); - if (count == len) - { - len += CHUNK; - out = realloc(out, len + 1); - } + res = NULL; + reslen = 0; + sec.val[0] = -1; + sec.val[1] = -1; + status = 0; - out[count++] = c; - out[count] = '\0'; - } + kstatus = _asl_server_query(asl_server_port, (caddr_t)vmstr, len, start, count, dir, (caddr_t *)&res, &reslen, last, (int *)&status, &sec); - return NULL; + if (res == NULL) return NULL; + l = asl_list_from_string(res); + vm_deallocate(mach_task_self(), (vm_address_t)res, reslen); + return l; } -int -search_next(asl_msg_t **q, int nq, FILE *log, int watch, aslmsg *outmsg, char **outstr) +asl_search_result_t * +db_query(asl_store_t *s, asl_search_result_t *q, uint64_t qmin, uint64_t *cmax) { - char *str; - aslmsg m; - int i, match; + uint32_t status; + asl_search_result_t *res; - *outmsg = NULL; - *outstr = NULL; + res = NULL; + status = asl_store_match(s, q, &res, cmax, qmin, 0, 1, 0, 0); + if (status != 0) return NULL; - if (log == NULL) return SEARCH_EOF; + return res; +} - str = getnextline(log, watch); - if (str == NULL) return SEARCH_EOF; +void +search_once(FILE *f, char *pfmt, int pflags, asl_search_result_t *ql, uint64_t qmin, uint64_t *cmax, uint32_t count, uint32_t tail) +{ + asl_search_result_t *res; + int i, j; - m = asl_msg_from_string(str); - if (m == NULL) - { - free(str); - return SEARCH_NULL; - } + if (pflags & FORMAT_XML) print_xml_header(f); - match = 0; - if (q == NULL) match = 1; - for (i = 0; (i < nq) && (match == 0); i++) + i = 0; + while (i < store_count) { - match = asl_msg_cmp(q[i], m); - if ((q[i]->count > 0) && (q[i]->op[0] & ASL_QUERY_OP_NOT)) + res = NULL; + if ((dbstore[i] == NULL) && (store_raw == 0)) { - match = !match; + if (count == 0) + { + res = send_query(ql, qmin, 0, 0, cmax); + i++; + } + else + { + res = send_query(ql, qmin, count, 0, cmax); + if (*cmax > qmin) qmin = *cmax; + if (res == NULL) i++; + else if (res->count < count) i++; + } + } + else + { + res = db_query(dbstore[i], ql, qmin, cmax); + i++; } - } - if (match == 0) - { - free(str); - asl_free(m); - return SEARCH_NULL; + if (res != NULL) + { + j = 0; + if (tail != 0) + { + j = res->count - tail; + tail = 0; + if (j < 0) j = 0; + } + + for (; j < res->count; j++) printmsg(f, res->msg[j], pfmt, pflags); + + aslresponse_free((aslresponse)res); + } } - *outmsg = m; - *outstr = str; - return SEARCH_MATCH; + if (pflags & FORMAT_XML) print_xml_trailer(f); } uint32_t @@ -1252,7 +1273,7 @@ add_op(asl_msg_t *q, char *key, char *op, char *val, uint32_t flags) return -1; } - if ((o & ASL_QUERY_OP_NUMERIC) && (_isanumber(val) == 0)) + if ((o & ASL_QUERY_OP_NUMERIC) && (strcmp(key, ASL_KEY_TIME) != 0) && (_isanumber(val) == 0)) { fprintf(stderr, "non-numeric value supplied for numeric operator %s %s %s\n", key, op, val); return -1; @@ -1266,29 +1287,87 @@ add_op(asl_msg_t *q, char *key, char *op, char *val, uint32_t flags) return 0; } +static void +add_store(const char *name, uint32_t flags) +{ + asl_store_t *s; + uint32_t status; + + s = NULL; + + if (name != NULL) + { + status = asl_store_open(name, flags, &s); + if (status != ASL_STATUS_OK) + { + fprintf(stderr, "database %s open failed: %s \n", name, asl_store_error(status)); + exit(1); + } + + if (s == NULL) + { + fprintf(stderr, "database %s open failed\n", name); + exit(1); + } + } + else + { + store_raw = 0; + } + + if (store_count == 0) dbstore = (asl_store_t **)calloc(1, sizeof(asl_store_t *)); + else dbstore = (asl_store_t **)reallocf(dbstore, (store_count + 1) * sizeof(asl_store_t *)); + + if (dbstore == NULL) + { + fprintf(stderr, "Can't allocate memory!\n"); + exit(1); + } + + dbstore[store_count] = s; + store_count++; +} + int main(int argc, char *argv[]) { - FILE *log, *pf, *outfile; - int i, j, n, qcount, qn, watch, prune, status, pflags, tflag; - asl_msg_t **qlist, *outmsg; - char *logname, *outname, *pfmt, *outstr; - pid_t syslogd_pid; - uint32_t flags; - - qn = 0; + FILE *outfile; + int i, j, n, watch, prune, status, pflags, tflags, sflags, iamroot, user_tflag; + int notify_file, notify_token; + asl_search_result_t *qlist, *lx, *res; + asl_msg_t *cq; + char *logname, *pfmt; + const char *dbname, *exportname; + uint32_t flags, tail_count, batch; + uint64_t qmin, cmax; + kern_return_t kstatus; + watch = 0; prune = 0; - logname = _PATH_ASL_OUT; - qlist = NULL; - qcount = 0; + iamroot = 0; + user_tflag = 0; + logname = NULL; pfmt = NULL; flags = 0; - pflags = PRINT_STD_FMT; - tflag = PRINT_LOCALTIME; + tail_count = 0; + batch = FETCH_BATCH; + sflags = ASL_STORE_FLAG_READ_ONLY; + pflags = FORMAT_STD; + tflags = TIME_LCL; + cq = NULL; + dbname = _PATH_ASL_STORE; + exportname = NULL; + + if (getuid() == 0) iamroot = 1; for (i = 1; i < argc; i++) { + if ((!strcmp(argv[i], "-help")) || (!strcmp(argv[i], "--help"))) + { + usage(); + exit(0); + } + if (!strcmp(argv[i], "-s")) { syslog_send(argc, argv); @@ -1300,31 +1379,84 @@ main(int argc, char *argv[]) syslog_remote_control(argc, argv); exit(0); } + + if (!strcmp(argv[i], "-p")) + { + prune = 1; + sflags = 0; + } } + qlist = (asl_search_result_t *)calloc(1, sizeof(asl_search_result_t)); + if (qlist == NULL) exit(1); + for (i = 1; i < argc; i++) { - if ((!strcmp(argv[i], "-help")) || (!strcmp(argv[i], "--help"))) + if (!strcmp(argv[i], "-db")) { - usage(); - exit(0); + if ((i + 1) < argc) + { + for (j = i + 1; j < argc; j++) + { + if (!strcmp(argv[j], "-")) + { + /* -db - means add syslogd search (dbstore is NULL) */ + add_store(NULL, sflags); + } + else if (argv[j][0] == '-') + { + if (j == (i + 1)) + { + /* No databases: add /var/log/asl.db */ + add_store(_PATH_ASL_STORE, sflags); + i = j - 1; + } + + break; + } + else + { + add_store(argv[j], sflags); + } + } + } + else + { + /* No databases: add /var/log/asl.db */ + add_store(_PATH_ASL_STORE, sflags); + } } else if (!strcmp(argv[i], "-w")) { watch = 1; + tail_count = 10; + if (((i + 1) < argc) && (argv[i + 1][0] != '-')) + { + i++; + tail_count = atoi(argv[i]); + } } else if (!strcmp(argv[i], "-u")) { - tflag = 0; + tflags = TIME_UTC; + user_tflag = 1; } - else if (!strcmp(argv[i], "-p")) + else if (!strcmp(argv[i], "-x")) { - prune = 1; + if ((i + 1) >= argc) + { + aslresponse_free(qlist); + usage(); + exit(1); + } + + exportname = argv[++i]; } else if (!strcmp(argv[i], "-f")) { if ((i + 1) >= argc) { + aslresponse_free(qlist); usage(); exit(1); } @@ -1335,23 +1467,29 @@ main(int argc, char *argv[]) { if ((i + 1) >= argc) { + aslresponse_free(qlist); usage(); exit(1); } i++; + if (!strcmp(argv[i], "raw")) { - pflags = 0; - tflag = 0; + pflags = FORMAT_RAW; + if (user_tflag == 0) tflags = TIME_SEC; } else if (!strcmp(argv[i], "std")) { - pflags = PRINT_STD_FMT; + pflags = FORMAT_STD; } else if (!strcmp(argv[i], "bsd")) { - pflags = PRINT_LEGACY_FMT; + pflags = FORMAT_LEGACY; + } + else if (!strcmp(argv[i], "xml")) + { + pflags = FORMAT_XML; } else { @@ -1359,27 +1497,68 @@ main(int argc, char *argv[]) pfmt = argv[i]; } } + else if (!strcmp(argv[i], "-T")) + { + if ((i + 1) >= argc) + { + aslresponse_free(qlist); + usage(); + exit(1); + } + + i++; + user_tflag = 1; + + if (!strcmp(argv[i], "sec")) tflags = TIME_SEC; + else if (!strcmp(argv[i], "utc")) tflags = TIME_UTC; + else if (!strcmp(argv[i], "local")) tflags = TIME_LCL; + else if (!strcmp(argv[i], "lcl")) tflags = TIME_LCL; + else tflags = TIME_LCL; + } else if (!strcmp(argv[i], "-o")) { flags = 0; - if (qlist == NULL) + if (qlist->count == 0) { - qlist = (asl_msg_t **)calloc(1, sizeof(asl_msg_t *)); + qlist->msg = (asl_msg_t **)calloc(1, sizeof(asl_msg_t *)); } else { - qlist = (asl_msg_t **)realloc(qlist, (qcount + 1) * sizeof(asl_msg_t *)); + qlist->msg = (asl_msg_t **)reallocf(qlist->msg, (qlist->count + 1) * sizeof(asl_msg_t *)); } - - qcount++; - qn = qcount - 1; - qlist[qn] = asl_new(ASL_TYPE_QUERY); + + if (qlist->msg == NULL) exit(1); + + cq = asl_new(ASL_TYPE_QUERY); + qlist->msg[qlist->count] = cq; + qlist->count++; } else if (!strcmp(argv[i], "-n")) { flags = ASL_QUERY_OP_NOT; } + else if (!strcmp(argv[i], "-C")) + { + if (qlist->count == 0) + { + qlist->msg = (asl_msg_t **)calloc(1, sizeof(asl_msg_t *)); + if (qlist->msg == NULL) exit(1); + + cq = asl_new(ASL_TYPE_QUERY); + qlist->msg[qlist->count] = cq; + qlist->count++; + } + + status = add_op(cq, ASL_KEY_FACILITY, OP_EQ, FACILITY_CONSOLE, flags); + + flags = 0; + if (status != 0) + { + aslresponse_free(qlist); + exit(1); + } + } else if (!strcmp(argv[i], "-k")) { i++; @@ -1405,117 +1584,231 @@ main(int argc, char *argv[]) continue; } - if (qlist == NULL) + if (qlist->count == 0) { - qlist = (asl_msg_t **)calloc(1, sizeof(asl_msg_t *)); - qcount = 1; - qn = 0; - qlist[qn] = asl_new(ASL_TYPE_QUERY); + qlist->msg = (asl_msg_t **)calloc(1, sizeof(asl_msg_t *)); + if (qlist->msg == NULL) exit(1); + + cq = asl_new(ASL_TYPE_QUERY); + qlist->msg[qlist->count] = cq; + qlist->count++; } status = 0; - if (n == 1) status = add_op(qlist[qn], argv[i], NULL, NULL, flags); - else if (n == 2) status = add_op(qlist[qn], argv[i], OP_EQ, argv[i+1], flags); - else status = add_op(qlist[qn], argv[i], argv[i+1], argv[i+2], flags); + if (n == 1) status = add_op(cq, argv[i], NULL, NULL, flags); + else if (n == 2) status = add_op(cq, argv[i], OP_EQ, argv[i+1], flags); + else status = add_op(cq, argv[i], argv[i+1], argv[i+2], flags); flags = 0; - if (status != 0) exit(1); + if (status != 0) + { + aslresponse_free(qlist); + exit(1); + } } } - pflags |= tflag; + pflags |= tflags; - if (prune == 1) + if (store_count == 0) add_store(NULL, sflags); + + kstatus = bootstrap_look_up(bootstrap_port, ASL_SERVICE_NAME, &asl_server_port); + if (kstatus != KERN_SUCCESS) { - if (watch == 1) + if (prune == 1) { - fprintf(stderr, "-w flag has no effect when pruning log file\n"); + fprintf(stderr, "prune operation failed: can't contact syslogd server\n"); + exit(1); } - if (getuid() != 0) + if (iamroot == 0) { - fprintf(stderr, "you must be root to prune the log file\n"); + fprintf(stderr, "operation failed: can't contact syslogd server\n"); exit(1); } - if (qlist == NULL) + /* force raw access (for single-user mode when syslogd is not running) */ + if (store_raw == 0) + { + fprintf(stderr, "*** can't contact syslogd server - using read-only database access ***\n"); + add_store(_PATH_ASL_STORE, ASL_STORE_FLAG_READ_ONLY); + store_raw = 1; + } + } + + if (prune == 1) + { + if (watch == 1) + { + fprintf(stderr, "Warning: -w flag has no effect when pruning\n"); + watch = 0; + } + + if (qlist->count == 0) { fprintf(stderr, "no queries for pruning\n"); + + aslresponse_free(qlist); + for (j = 0; j < store_count; j++) asl_store_close(dbstore[j]); + if (dbstore != NULL) free(dbstore); + exit(0); } - pf = fopen(_PATH_SYSLOGD_PID, "r"); - if (pf == NULL) + for (i = 0; i < store_count; i++) { - perror(_PATH_SYSLOGD_PID); - exit(1); + status = ASL_STATUS_OK; + + if ((dbstore[i] == NULL) && (store_raw == 0)) + { + if (iamroot == 0) + { + fprintf(stderr, "you must be root to prune the log database\n"); + + aslresponse_free(qlist); + for (j = 0; j < store_count; j++) asl_store_close(dbstore[j]); + if (dbstore != NULL) free(dbstore); + + exit(1); + } + + status = send_prune(qlist); + } + else + { + status = asl_store_prune(dbstore[i], qlist); + } + + if (status != ASL_STATUS_OK) + { + fprintf(stderr, "database prune failed: %s\n", asl_store_error(status)); + + aslresponse_free(qlist); + for (j = 0; j < store_count; j++) asl_store_close(dbstore[j]); + if (dbstore != NULL) free(dbstore); + + exit(1); + } } - status = fscanf(pf, "%d", &syslogd_pid); - fclose(pf); - if (status != 1) + aslresponse_free(qlist); + + exit(0); + } + + outfile = stdout; + + if (exportname != NULL) + { + if (watch == 1) { - fprintf(stderr, "can't read syslogd pid from %s\n", _PATH_SYSLOGD_PID); - exit(1); + fprintf(stderr, "Warning: -w flag has no effect with -x export flag\n"); + watch = 0; } - unlink(_PATH_ASL_PRUNE); - pf = fopen(_PATH_ASL_PRUNE, "w"); - if (pf == NULL) + status = asl_store_open(exportname, 0, &export); + if (status != ASL_STATUS_OK) { - perror(_PATH_ASL_PRUNE); + aslresponse_free(qlist); + fprintf(stderr, "export database open failed: %s\n", asl_store_error(status)); exit(1); } - for (i = 0; i < qcount; i++) + outfile = NULL; + pflags = EXPORT; + } + + qmin = 0; + cmax = 0; + notify_file = -1; + notify_token = -1; + + if (watch == 1) + { + if (store_raw == 1) { - outstr = asl_msg_to_string(qlist[i], &j); - fprintf(pf, "%s\n", outstr); - free(outstr); + fprintf(stderr, "Warning: -w flag can only be used to watch syslogd's active database\n"); + watch = 0; } + else if (store_count > 1) + { + fprintf(stderr, "Warning: -w flag has no effect with multiple databases\n"); + watch = 0; + } + else + { + status = notify_register_file_descriptor("com.apple.system.logger.message", ¬ify_file, 0, ¬ify_token); + if (status != NOTIFY_STATUS_OK) notify_token = -1; + } + } - fclose(pf); - - kill(syslogd_pid, SIGWINCH); + if ((qlist->count == 0) && (watch == 1) && (store_raw == 0)) + { + lx = (asl_search_result_t *)calloc(1, sizeof(asl_search_result_t)); + if (lx == NULL) exit(1); - exit(0); - } + lx->count = 1; + lx->msg = (asl_msg_t **)calloc(1, sizeof(asl_msg_t *)); + if (lx->msg == NULL) + { + aslresponse_free(lx); + exit(1); + } - log = NULL; + lx->msg[0] = asl_new(ASL_TYPE_QUERY); + if (lx->msg[0] == NULL) + { + aslresponse_free(lx); + exit(1); + } - if (!strcmp(logname, "-")) log = stdin; - else log = fopen(logname, "r"); + asl_set_query(lx->msg[0], "Level", "0", ASL_QUERY_OP_NUMERIC | ASL_QUERY_OP_GREATER_EQUAL); + qmin = -1; + res = send_query(lx, qmin, 1, 1, &cmax); + aslresponse_free(lx); + aslresponse_free(res); + qmin = cmax - tail_count; + tail_count = 0; + } - if (log == NULL) + if (qlist->count == 0) { - perror(logname); - exit(1); + qlist->msg = (asl_msg_t **)calloc(1, sizeof(asl_msg_t *)); + if (qlist->msg == NULL) exit(1); + + cq = asl_new(ASL_TYPE_QUERY); + qlist->msg[qlist->count] = cq; + qlist->count++; } - if (watch == 1) fseek(log, 0, SEEK_END); - outname = NULL; - outfile = stdout; + search_once(outfile, pfmt, pflags, qlist, qmin, &cmax, batch, tail_count); - do + if (watch == 1) { - outmsg = NULL; - outstr = NULL; - status = search_next(qlist, qcount, log, watch, &outmsg, &outstr); - - if (status == SEARCH_MATCH) + if (notify_token == -1) { - printmsg(outfile, outmsg, outstr, pfmt, pflags); + forever + { + usleep(500000); + if (cmax > qmin) qmin = cmax; + search_once(outfile, pfmt, pflags, qlist, qmin, &cmax, 0, 0); + } + } + else + { + while (read(notify_file, &i, 4) == 4) + { + if (cmax > qmin) qmin = cmax; + search_once(outfile, pfmt, pflags, qlist, qmin, &cmax, 0, 0); + } } - - if (outstr != NULL) free(outstr); - if (outmsg != NULL) asl_free(outmsg); } - while (status != SEARCH_EOF); - fclose(log); + for (i = 0; i < store_count; i++) asl_store_close(dbstore[i]); + if (dbstore != NULL) free(dbstore); + if (export != NULL) asl_store_close(export); - for (i = 0; i < qcount; i++) asl_free(qlist[i]); - if (qlist != NULL) free(qlist); + aslresponse_free(qlist); exit(0); } -- 2.45.2