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 );
}