Retired Document
Important: This sample code may not represent best practices for current development. The project may use deprecated symbols and illustrate technologies and techniques that are no longer recommended.
LogEngine/LogEngine.c
/* |
File: LogEngine.c |
Contains: The core code to talk to the STREAMS log module. |
Written by: Quinn "The Eskimo!" |
Copyright: © 1998 by Apple Computer, Inc., all rights reserved. |
Change History (most recent first): |
You may incorporate this sample code into your applications without |
restriction, though the sample code has been provided "AS IS" and the |
responsibility for its operation is 100% yours. However, what you are |
not permitted to do is to redistribute the source as "DSC Sample Code" |
after having made changes. If you're going to re-distribute the source, |
we require that you make it clear in the source that the code was |
descended from Apple Sample Code, but that you've made changes. |
*/ |
#define qDebug 1 |
///////////////////////////////////////////////////////////////////// |
// Pick up the standard C stuff. |
#include <stdio.h> |
#include <string.h> |
// Mac OS interfaces |
#include <OpenTransportProtocol.h> |
#include <Processes.h> |
// Stuff that eventually needs to get into the OT headers |
#include "OTClassicContext.h" |
// Pick up our memory management module. |
#include "OTMemoryReserve.h" |
// Pick up our types and prototypes. |
#include "LogEngine.h" |
///////////////////////////////////////////////////////////////////// |
// OTDebugStr is defined in a wacky OT library. Let's just use |
// lowercase debugstr instead. |
#define OTDebugStr debugstr |
///////////////////////////////////////////////////////////////////// |
// If we can't log an entry (because of lack of memory), we |
// increment gDroppedLogEntries. The client can access this to |
// tell the user that we're dropped logs. |
static UInt32 gDroppedLogEntries = 0; |
// gNewLogEntries is a FIFO containing log entries that have |
// been read but not yet delivered to the client. It is populated |
// at interrupt time by our notifier (LogNotifier) and consumed |
// at system task time by ForEachNewLogEntry. |
static OTLIFO gNewLogEntries = { nil }; |
static SInt32 gNumberInList = 0; |
// We record the PSN of the current process when we start up |
// so that we can wake ourselves up when log entries arrive. |
// We also have a flag to say whether we've already called |
// WakeUpProcess, so we don't call it zillions of times. |
static ProcessSerialNumber gOurProcess; |
static OTLock gProcessWoken; |
///////////////////////////////////////////////////////////////////// |
static void CreateNewLogEntry(struct log_ctl *logHeader, OTBuffer *logTextBuffer) |
// Create a new LogEntry, fill out the field fields based on |
// logHeader and the variable length data based on the text in |
// logTextBuffer, and add it to the gNewEntries list of recent |
// additions. |
// |
// Context: OT deferred task |
{ |
OSStatus junk; |
OTBuffer *thisTextBuffer; |
UInt32 textLength; |
LogEntryPtr newEntry; |
UInt8 *destCursor; |
// First calculate the number of bytes of text that are in |
// this entry (ie in the logTextBuffer and any buffers chained |
// off it). |
textLength = 0; |
thisTextBuffer = logTextBuffer; |
while ( thisTextBuffer != nil ) { |
OTAssert("CreateNewLogEntry: Non-data message in buffer chain", thisTextBuffer->fType == M_DATA); |
textLength += thisTextBuffer->fLen; |
thisTextBuffer = thisTextBuffer->fNext; |
} |
// Then allocate a new log entry of the correct size, |
// ie the length of the fixed part + the length of the text. |
newEntry = (LogEntryPtr) OTAllocMemFromReserveInContext( sizeof(LogEntry) + textLength, nil ); |
if (newEntry == nil) { |
if (gDroppedLogEntries == 0) { |
// OTDebugStr("CreateNewLogEntry: Started dropping log entries"); |
} |
OTAtomicAdd32(1, (SInt32 *) &gDroppedLogEntries); |
} else { |
// Fill out the fixed fields at the beginning of the |
// new log entry. |
newEntry->fNext = nil; |
newEntry->fMagic = kMagicValue; |
newEntry->fRefCount = 1; |
newEntry->fTextLength = textLength; |
newEntry->fLogHeader = *logHeader; |
// Now copy bytes out of the text messages into the |
// variable part of the log entry. destCursor is the |
// byte we're going to blat next, ie it starts just |
// after the fixed fields of the new log entry and marches |
// onwards through the variable text data. |
destCursor = ((UInt8 *) newEntry) + sizeof(LogEntry); |
thisTextBuffer = logTextBuffer; |
while ( thisTextBuffer != nil ) { |
BlockMoveData(thisTextBuffer->fData, destCursor, thisTextBuffer->fLen); |
destCursor += thisTextBuffer->fLen; |
thisTextBuffer = thisTextBuffer->fNext; |
} |
OTAssert("CreateNewLogEntry: Did not copy the number of bytes that we calculated were in the message", |
destCursor = ((UInt8 *) newEntry) + sizeof(LogEntry) + textLength); |
// Finally add the new entry to the global list |
// of new entries to be processed at SystemTask time. |
OTLIFOEnqueue(&gNewLogEntries, (OTLink *) &newEntry->fNext); |
OTAtomicAdd32(1, &gNumberInList); |
// If we haven't already woken up our process, do so now. |
if ( OTAcquireLock(&gProcessWoken) ) { |
// OTDebugStr("CreateNewLogEntry: Waking up process"); |
junk = WakeUpProcess(&gOurProcess); |
OTAssert("CreateNewLogEntry: Error waking up our process", junk == noErr); |
} |
} |
} |
extern pascal void RetainLogEntry(LogEntryPtr thisEntry) |
// See comment in interface part. |
{ |
OTAssert("RetainLogEntry: Bad magic", thisEntry->fMagic == kMagicValue); |
thisEntry->fRefCount += 1; |
} |
extern pascal void ReleaseLogEntry(LogEntryPtr thisEntry) |
// See comment in interface part. |
{ |
OTAssert("ReleaseLogEntry: Bad magic", thisEntry->fMagic == kMagicValue); |
thisEntry->fRefCount -= 1; |
if ( thisEntry->fRefCount == 0 ) { |
thisEntry->fMagic = 'bad!'; |
OTFreeMem(thisEntry); |
} |
} |
///////////////////////////////////////////////////////////////////// |
static pascal void LogNotifier(void *contextPtr, OTEventCode code, OTResult err, void *cookie) |
// This is the notifier that OT calls when an event happens on |
// the raw stream. In this case the only event we're interested in |
// is SIGPOLL, which says that something interesting has happened with |
// data, typically that data has arrived. We respond to a SIGPOLL |
// by calling OTReadMessage to extract the message from the stream. |
// We then process the message and loop looking for more. |
// |
// OTReadMessage is like getmsg() but it a) operates immediately, |
// so we don't need to wait for a completion event like we do with |
// getmsg, and b) returns the data without copying it. |
// |
// Context: OT deferred task |
{ |
#pragma unused(err) |
#pragma unused(cookie) |
OTReadInfo readInfo; |
OTBuffer *readBuffer; |
StreamRef strm; |
strm = (StreamRef) contextPtr; |
// Debugger(); |
switch (code) { |
case kSIGNALEVENT + SIGPOLL: |
do { |
readInfo.fType = kOTAnyMsgType; |
readInfo.fCommand = 0; // Not strictly necessary because kOTAnyMsgType implies this field is ignored. |
readBuffer = OTReadMessage(strm, &readInfo); |
if ( readBuffer != nil ) { |
if ( (readBuffer->fType == M_PROTO || readBuffer->fType == M_PCPROTO) && |
readBuffer->fLen == sizeof(struct log_ctl) ) { |
CreateNewLogEntry( (struct log_ctl *) readBuffer->fData , readBuffer->fNext); |
} else { |
OTDebugStr("LogNotifier: OTReadMessage returned a weird buffer"); |
} |
OTReleaseBuffer(readBuffer); |
} |
} while ( readBuffer != nil ); |
break; |
default: |
OTDebugStr("LogNotifier: Unexpected event code"); |
break; |
} |
} |
///////////////////////////////////////////////////////////////////// |
static OSStatus SetupLogging(StreamRef strm, SInt32 command, |
trace_ids traceInfo[], UInt32 traceInfoCount) |
// Sends a log configuration ioctl to the raw stream. |
// command is the ioctl, traceInfoCount is the number of |
// trace_ids in the buffer pointed to be traceInfo. Note |
// that traceInfoCount can be zero, a common case for setting |
// up an error log stream. Note that strm is assumed to be |
// in sync/blocking mode. |
// |
// Context: SystemTask /only/ |
{ |
OSStatus err; |
struct strioctl logIoctl; |
logIoctl.ic_cmd = command; |
logIoctl.ic_timout = -1; |
logIoctl.ic_dp = (char *) traceInfo; |
logIoctl.ic_len = traceInfoCount * sizeof(struct trace_ids); |
err = OTStreamIoctl(strm, I_STR, &logIoctl); |
return err; |
} |
///////////////////////////////////////////////////////////////////// |
// Public Entry Points |
// This is the raw stream which we use for logging. It is |
// set up by StartLogging and torn down by StopLogging. |
static StreamRef gLogStream = kOTInvalidStreamRef; |
extern pascal OSStatus StartLogging(Boolean logErrors, |
UInt32 traceInfoCount, struct trace_ids traceInfo[]) |
// See comment in interface part. |
{ |
OSStatus err; |
OSStatus junk; |
OTAssert("StartLogging: paramErr", traceInfoCount == 0 || traceInfo != nil); |
OTAssert("StartLogging: We're already logging!", gLogStream == kOTInvalidStreamRef); |
OTClearLock(&gProcessWoken); |
err = GetCurrentProcess(&gOurProcess); |
// Create and configure a stream to the log device. Note that |
// we do this stuff with the stream in sync/blocking mode, and |
// switch to async/blocking at the end. |
if (err == noErr) { |
gLogStream = OTStreamOpenInContext(MI_LOG_DEVICE, 0, &err, nil); |
} |
if (err == noErr) { |
OTStreamSetBlocking(gLogStream); |
} |
if (err == noErr && traceInfoCount > 0) { |
// If we've been asked to log traces, tell the log device that |
// we're the trace stream. |
err = SetupLogging(gLogStream, I_TRCLOG, traceInfo, traceInfoCount); |
} |
if (err == noErr && logErrors) { |
// If we've been asked to log error, tell the log device that |
// we're the error stream. |
err = SetupLogging(gLogStream, I_ERRLOG, nil, 0); |
} |
if (err == noErr) { |
// We want all signals to arrive in our notifier. The only one we're |
// specifically interested in is SIGPOLL (ie S_RDNORM etc) but if any |
// other signals are generated we /really/ want to know about it. |
err = OTStreamIoctl(gLogStream, I_SETSIG, |
(void *) (S_INPUT | S_RDNORM | S_RDBAND | S_HIPRI | S_OUTPUT | S_WRNORM | |
S_WRBAND | S_MSG | S_ERROR | S_HANGUP | S_BANDURG)); |
} |
if (err == noErr) { |
OTStreamSetAsynchronous(gLogStream); |
err = OTStreamInstallNotifier(gLogStream, LogNotifier, gLogStream); |
} |
// Clean up. |
if (err != noErr) { |
if (gLogStream != kOTInvalidStreamRef) { |
OTStreamRemoveNotifier(gLogStream); |
junk = OTStreamClose(gLogStream); |
OTAssert("StartLogging: OTStreamClose failed", junk == noErr); |
} |
gLogStream = kOTInvalidStreamRef; |
} |
return err; |
} |
extern pascal void StopLogging(void) |
// See comment in interface part. |
{ |
OSStatus junk; |
OTAssert("StopLogging: We're not logging!", gLogStream != kOTInvalidStreamRef); |
OTStreamRemoveNotifier(gLogStream); |
junk = OTStreamClose(gLogStream); |
OTAssert("StopLogging: OTStreamClose failed", junk == noErr); |
gLogStream = kOTInvalidStreamRef; |
} |
extern pascal Boolean LoggingActive(void) |
// See comment in interface part. |
{ |
return gLogStream != kOTInvalidStreamRef; |
} |
extern pascal UInt32 NumberOfDroppedLogEntries(void) |
// See comment in interface part. |
{ |
return gDroppedLogEntries; |
} |
extern pascal void ForEachNewLogEntry(ProcessLogEntryProcPtr doThis, void *refCon) |
// See comment in interface part. |
{ |
OTLink *thisLink; |
LogEntryPtr thisEntry; |
SInt32 junkLong; |
if ( gNumberInList != 0 ) { |
// OTDebugStr("ForEachNewLogEntry: Process woken with something to do"); |
} |
OTClearLock(&gProcessWoken); |
// If you're having troubles understanding this, check out |
// the sample code in the section on lists in "Inside Macintosh: |
// Networking with Open Transport". |
do { |
thisLink = OTReverseList(OTLIFOStealList(&gNewLogEntries)); |
while ( thisLink != nil ) { |
thisEntry = OTGetLinkObject(thisLink, LogEntry, fNext); |
doThis(thisEntry, refCon); |
thisLink = thisLink->fNext; |
ReleaseLogEntry(thisEntry); |
junkLong = OTAtomicAdd32(-1, (SInt32 *) &gNumberInList); |
OTAssert("ForEachNewLogEntry: ", junkLong >= 0); |
} |
} while ( gNewLogEntries.fHead != nil ); |
} |
Copyright © 2003 Apple Computer, Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2003-07-22