+
+static oslog_stream_buf_entry_t
+oslog_stream_find_free_buf_entry_locked(void)
+{
+ struct msgbuf *mbp;
+ oslog_stream_buf_entry_t buf_entry = NULL;
+
+ lck_spin_assert(&oslog_stream_lock, LCK_ASSERT_OWNED);
+
+ mbp = oslog_streambufp;
+
+ buf_entry = STAILQ_FIRST(&oslog_stream_free_head);
+ if (buf_entry) {
+ STAILQ_REMOVE_HEAD(&oslog_stream_free_head, buf_entries);
+ }
+ else {
+ // If no list elements are available in the free-list,
+ // consume the next log line so we can free up its list element
+ oslog_stream_buf_entry_t prev_entry = NULL;
+
+ buf_entry = STAILQ_FIRST(&oslog_stream_buf_head);
+ while (buf_entry->type == oslog_stream_link_type_metadata) {
+ prev_entry = buf_entry;
+ buf_entry = STAILQ_NEXT(buf_entry, buf_entries);
+ }
+
+ if (prev_entry == NULL) {
+ STAILQ_REMOVE_HEAD(&oslog_stream_buf_head, buf_entries);
+ }
+ else {
+ STAILQ_REMOVE_AFTER(&oslog_stream_buf_head, prev_entry, buf_entries);
+ }
+
+ mbp->msg_bufr += buf_entry->size;
+ oslog_s_dropped_msgcount++;
+ if (mbp->msg_bufr >= mbp->msg_size) {
+ mbp->msg_bufr = (mbp->msg_bufr % mbp->msg_size);
+ }
+ }
+
+ return buf_entry;
+}
+
+void
+oslog_streamwrite_metadata_locked(oslog_stream_buf_entry_t m_entry)
+{
+ lck_spin_assert(&oslog_stream_lock, LCK_ASSERT_OWNED);
+ STAILQ_INSERT_TAIL(&oslog_stream_buf_head, m_entry, buf_entries);
+
+ return;
+}
+
+static void oslog_streamwrite_append_bytes(const char *buffer, int buflen)
+{
+ struct msgbuf *mbp;
+
+ lck_spin_assert(&oslog_stream_lock, LCK_ASSERT_OWNED);
+
+ mbp = oslog_streambufp;
+ // Check if we have enough space in the stream buffer to write the data
+ if (mbp->msg_bufx + buflen <= mbp->msg_size) {
+ memcpy((void *)(mbp->msg_bufc + mbp->msg_bufx), buffer, buflen);
+
+ mbp->msg_bufx += buflen;
+ if (mbp->msg_bufx == mbp->msg_size) {
+ mbp->msg_bufx = 0;
+ }
+ } else {
+ // Copy part of the data until the end of the stream
+ int bytes_left = mbp->msg_size - mbp->msg_bufx;
+ memcpy((void *)(mbp->msg_bufc + mbp->msg_bufx), buffer, bytes_left);
+
+ buflen -= bytes_left;
+ buffer += bytes_left;
+
+ // Copy the remainder of the data from the beginning of stream
+ memcpy((void *)mbp->msg_bufc, buffer, buflen);
+ mbp->msg_bufx = buflen;
+ }
+ return;
+}
+
+
+void
+oslog_streamwrite_locked(firehose_tracepoint_id_u ftid,
+ uint64_t stamp, const void *pubdata, size_t publen)
+{
+ struct msgbuf *mbp;
+ int available_space = 0;
+ oslog_stream_buf_entry_t buf_entry = NULL;
+ oslog_stream_buf_entry_t next_entry = NULL;
+
+ uint16_t ft_size = offsetof(struct firehose_tracepoint_s, ft_data);
+ int ft_length = ft_size + publen;
+
+ lck_spin_assert(&oslog_stream_lock, LCK_ASSERT_OWNED);
+
+ mbp = oslog_streambufp;
+ if (ft_length > mbp->msg_size) {
+ (void)hw_atomic_add(&oslog_s_error_count, 1);
+ return;
+ }
+
+ // Ensure that we have a list element for this record
+ buf_entry = oslog_stream_find_free_buf_entry_locked();
+
+ assert(buf_entry != NULL);
+
+ // Ensure that we have space in the ring buffer for the current logline
+ if (mbp->msg_bufr > mbp->msg_bufx) {
+ available_space = mbp->msg_bufr - mbp->msg_bufx;
+ } else {
+ available_space = mbp->msg_size - mbp->msg_bufx + mbp->msg_bufr;
+ }
+ while(ft_length > available_space) {
+ oslog_stream_buf_entry_t prev_entry = NULL;
+
+ next_entry = STAILQ_FIRST(&oslog_stream_buf_head);
+ assert(next_entry != NULL);
+ while (next_entry->type == oslog_stream_link_type_metadata) {
+ prev_entry = next_entry;
+ next_entry = STAILQ_NEXT(next_entry, buf_entries);
+ }
+
+ if (prev_entry == NULL) {
+ STAILQ_REMOVE_HEAD(&oslog_stream_buf_head, buf_entries);
+ }
+ else {
+ STAILQ_REMOVE_AFTER(&oslog_stream_buf_head, prev_entry, buf_entries);
+ }
+
+ mbp->msg_bufr += next_entry->size;
+ if (mbp->msg_bufr >= mbp->msg_size) {
+ mbp->msg_bufr = (mbp->msg_bufr % mbp->msg_size);
+ }
+
+ oslog_s_dropped_msgcount++;
+ available_space += next_entry->size;
+
+ STAILQ_INSERT_TAIL(&oslog_stream_free_head, next_entry, buf_entries);
+ }
+
+ assert(ft_length <= available_space);
+
+ // Write the log line and update the list entry for this record
+ buf_entry->offset = mbp->msg_bufx;
+ buf_entry->size = ft_length;
+ buf_entry->timestamp = stamp;
+ buf_entry->type = oslog_stream_link_type_log;
+
+ // Construct a tracepoint
+ struct firehose_tracepoint_s fs = {
+ .ft_thread = thread_tid(current_thread()),
+ .ft_id.ftid_value = ftid.ftid_value,
+ .ft_length = publen
+ };
+
+ oslog_streamwrite_append_bytes((char *)&fs, sizeof(fs));
+ oslog_streamwrite_append_bytes(pubdata, publen);
+
+ assert(mbp->msg_bufr < mbp->msg_size);
+ // Insert the element to the buffer data list
+ STAILQ_INSERT_TAIL(&oslog_stream_buf_head, buf_entry, buf_entries);
+
+ return;
+}
+
+
+
+/*
+ * log_putc
+ *
+ * Decription: Output a character to the log; assumes the LOG_LOCK() is NOT
+ * held by the caller.
+ *
+ * Parameters: c Character to output
+ *
+ * Returns: (void)
+ *
+ * Notes: This function is used for syingle byte output to the log. It
+ * primarily exists to maintain binary backward compatibility.
+ */
+void
+log_putc(char c)
+{
+ int unread_count = 0;
+ LOG_LOCK();
+ log_putc_locked(c);
+ unread_count = msgbufp->msg_bufx - msgbufp->msg_bufr;
+ LOG_UNLOCK();
+
+ if (unread_count < 0)
+ unread_count = 0 - unread_count;
+ if (c == '\n' || unread_count >= MAX_UNREAD_CHARS)
+ logwakeup();
+}
+
+
+/*
+ * it is possible to increase the kernel log buffer size by adding
+ * msgbuf=n
+ * to the kernel command line, and to read the current size using
+ * sysctl kern.msgbuf
+ * If there is no parameter on the kernel command line, the buffer is
+ * allocated statically and is CONFIG_MSG_BSIZE characters in size, otherwise
+ * memory is dynamically allocated. Memory management must already be up.
+ */
+int
+log_setsize(int size) {
+ char *new_logdata;
+ int new_logsize, new_bufr, new_bufx;
+ char *old_logdata;
+ int old_logsize, old_bufr, old_bufx;
+ int i, count;
+ char *p, ch;
+
+ if (size > MAX_MSG_BSIZE)
+ return (EINVAL);
+
+ if (size <= 0)
+ return (EINVAL);
+
+ new_logsize = size;
+ if (!(new_logdata = (char*)kalloc(size))) {
+ printf("log_setsize: unable to allocate memory\n");
+ return (ENOMEM);
+ }
+ bzero(new_logdata, new_logsize);
+
+ LOG_LOCK();
+
+ old_logsize = msgbufp->msg_size;
+ old_logdata = msgbufp->msg_bufc;
+ old_bufr = msgbufp->msg_bufr;
+ old_bufx = msgbufp->msg_bufx;
+
+ LOG_SETSIZE_DEBUG("log_setsize(%d): old_logdata %p old_logsize %d old_bufr %d old_bufx %d\n",
+ size, old_logdata, old_logsize, old_bufr, old_bufx);
+
+ /* start "new_logsize" bytes before the write pointer */
+ if (new_logsize <= old_bufx) {
+ count = new_logsize;
+ p = old_logdata + old_bufx - count;
+ } else {
+ /*
+ * if new buffer is bigger, copy what we have and let the
+ * bzero above handle the difference
+ */
+ count = MIN(new_logsize, old_logsize);
+ p = old_logdata + old_logsize - (count - old_bufx);
+ }
+ for (i = 0; i < count; i++) {
+ if (p >= old_logdata + old_logsize)
+ p = old_logdata;
+
+ ch = *p++;
+ new_logdata[i] = ch;
+ }
+
+ new_bufx = i;
+ if (new_bufx >= new_logsize)
+ new_bufx = 0;
+ msgbufp->msg_bufx = new_bufx;
+
+ new_bufr = old_bufx - old_bufr; /* how much were we trailing bufx by? */
+ if (new_bufr < 0)
+ new_bufr += old_logsize;
+ new_bufr = new_bufx - new_bufr; /* now relative to oldest data in new buffer */
+ if (new_bufr < 0)
+ new_bufr += new_logsize;
+ msgbufp->msg_bufr = new_bufr;
+
+ msgbufp->msg_size = new_logsize;
+ msgbufp->msg_bufc = new_logdata;
+
+ LOG_SETSIZE_DEBUG("log_setsize(%d): new_logdata %p new_logsize %d new_bufr %d new_bufx %d\n",
+ size, new_logdata, new_logsize, new_bufr, new_bufx);
+
+ LOG_UNLOCK();
+
+ /* this memory is now dead - clear it so that it compresses better
+ in case of suspend to disk etc. */
+ bzero(old_logdata, old_logsize);
+ if (old_logdata != smsg_bufc) {
+ /* dynamic memory that must be freed */
+ kfree(old_logdata, old_logsize);
+ }
+
+ printf("set system log size to %d bytes\n", new_logsize);
+
+ return 0;
+}
+
+void oslog_setsize(int size)
+{
+ uint16_t scale = 0;
+ // If the size is less than the default stream buffer
+ // do nothing
+ if (size <= OSLOG_STREAM_BUF_SIZE) {
+ return;
+ }
+
+ scale = (uint16_t) (size / OSLOG_STREAM_BUF_SIZE);
+
+ oslog_stream_buf_size = size;
+ oslog_stream_num_entries = scale * OSLOG_NUM_STREAM_ENTRIES;
+ printf("oslog_setsize: new buffer size = %d, new num entries= %d\n", oslog_stream_buf_size, oslog_stream_num_entries);
+}
+
+SYSCTL_PROC(_kern, OID_AUTO, msgbuf, CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_LOCKED, 0, 0, sysctl_kern_msgbuf, "I", "");
+
+static int sysctl_kern_msgbuf(struct sysctl_oid *oidp __unused,
+ void *arg1 __unused,
+ int arg2 __unused,
+ struct sysctl_req *req)
+{
+ int old_bufsize, bufsize;
+ int error;
+
+ LOG_LOCK();
+ old_bufsize = bufsize = msgbufp->msg_size;
+ LOG_UNLOCK();
+
+ error = sysctl_io_number(req, bufsize, sizeof(bufsize), &bufsize, NULL);
+ if (error)
+ return (error);
+
+ if (bufsize != old_bufsize) {
+ error = log_setsize(bufsize);
+ }
+
+ return (error);
+}
+
+
+/*
+ * This should be called by /sbin/dmesg only via libproc.
+ * It returns as much data still in the buffer as possible.
+ */
+int
+log_dmesg(user_addr_t buffer, uint32_t buffersize, int32_t * retval) {
+ uint32_t i;
+ uint32_t localbuff_size;
+ int error = 0, newl, skip;
+ char *localbuff, *p, *copystart, ch;
+ size_t copysize;
+
+ LOG_LOCK();
+ localbuff_size = (msgbufp->msg_size + 2); /* + '\n' + '\0' */
+ LOG_UNLOCK();
+
+ /* Allocate a temporary non-circular buffer for copyout */
+ if (!(localbuff = (char *)kalloc(localbuff_size))) {
+ printf("log_dmesg: unable to allocate memory\n");
+ return (ENOMEM);
+ }
+
+ /* in between here, the log could become bigger, but that's fine */
+ LOG_LOCK();
+
+ /*
+ * The message buffer is circular; start at the write pointer, and
+ * make one loop up to write pointer - 1.
+ */
+ p = msgbufp->msg_bufc + msgbufp->msg_bufx;
+ for (i = newl = skip = 0; p != msgbufp->msg_bufc + msgbufp->msg_bufx - 1; ++p) {
+ if (p >= msgbufp->msg_bufc + msgbufp->msg_size)
+ p = msgbufp->msg_bufc;
+ ch = *p;
+ /* Skip "\n<.*>" syslog sequences. */
+ if (skip) {
+ if (ch == '>')
+ newl = skip = 0;
+ continue;
+ }
+ if (newl && ch == '<') {
+ skip = 1;
+ continue;
+ }
+ if (ch == '\0')
+ continue;
+ newl = (ch == '\n');
+ localbuff[i++] = ch;
+ /* The original version of this routine contained a buffer
+ * overflow. At the time, a "small" targeted fix was desired
+ * so the change below to check the buffer bounds was made.
+ * TODO: rewrite this needlessly convoluted routine.
+ */
+ if (i == (localbuff_size - 2))
+ break;
+ }
+ if (!newl)
+ localbuff[i++] = '\n';
+ localbuff[i++] = 0;
+
+ if (buffersize >= i) {
+ copystart = localbuff;
+ copysize = i;
+ } else {
+ copystart = localbuff + i - buffersize;
+ copysize = buffersize;
+ }
+
+ LOG_UNLOCK();
+
+ error = copyout(copystart, buffer, copysize);
+ if (!error)
+ *retval = copysize;
+
+ kfree(localbuff, localbuff_size);
+ return (error);
+}
+