home *** CD-ROM | disk | FTP | other *** search
- /*
- * BUFFER.C vi:ts=4
- *
- * Copyright (c) Eddy Carroll, September 1994.
- *
- * This module maintains the review buffer used by SnoopDos to record
- * the past events that have occurred.
- */
-
- #define TESTING 0
-
- #include "system.h"
- #include "snoopdos.h"
- #include "patches.h"
-
- #define INVALID_HUNK ((USHORT)(-1))
-
- #if 0
- #define DB(s) KPrintF(s)
- #else
- #define DB(s)
- #endif
-
- static UBYTE *BufferStart; /* Ptr to start of our buffer */
- static UBYTE *BufferEnd; /* Ptr to end of our buffer */
- static UBYTE *BufferCur; /* Ptr to next free loc in buffer */
- static ULONG BufferSize; /* Size of current buffer */
-
- /*
- * This is the mapping that assigns format ID's to the various
- * fields we can output.
- */
- FieldInit FieldTypes[] = {
- EF_ACTION, 'a', 10, MSG_ACTION_COL,
- EF_CALLADDR, 'c', 8, MSG_CALL_COL,
- EF_DATE, 'd', 9, MSG_DATE_COL,
- EF_HUNKOFFSET, 'h', 11, MSG_HUNK_COL,
- EF_PROCID, 'i', 8, MSG_PROCESSID_COL,
- EF_FILENAME, 'n', 27, MSG_FILENAME_COL,
- EF_OPTIONS, 'o', 7, MSG_OPTIONS_COL,
- EF_PROCNAME, 'p', 18, MSG_PROCNAME_COL,
- EF_RESULT, 'r', 4, MSG_RESULT_COL,
- EF_SEGNAME, 's', 20, MSG_SEGNAME_COL,
- EF_TIME, 't', 8, MSG_TIME_COL,
- EF_COUNT, 'u', 5, MSG_COUNT_COL,
- EF_END, 0, 0, 0
- };
-
- /*
- * InitBuffers()
- *
- * Initialises certain variables associated with the buffer routines.
- * Must be called before any of the buffer functions are called.
- */
- void InitBuffers(void)
- {
- InitSemaphore(&BufSem);
- NewList(&EventList);
- }
-
- /*
- * SetTotalBufferSize(size, alloctype)
- *
- * This function is used to change the size of the buffer. If no buffer
- * has yet been created, one is allocated. If there is not enough room
- * to create a new buffer without freeing the existing buffer first,
- * then FALSE is returned, _unless_ the alloctype parameter is
- * SETBUF_FORCENEW, in which case it simply frees the existing buffer
- * first, losing any existing history info.
- *
- * In general, the old buffer's contents are copied across to the
- * new buffer so that the user's history isn't destroyed.
- *
- * The intention is that the caller tries to use this call with
- * the tryalways flag set to SETBUF_KEEPOLD first; if that fails,
- * then it can display a requester to the user saying that this will
- * cause the existing buffer info to be lost permanently if you go
- * ahead--Yes/No? and then do a retry with alloctype set to
- * SETBUF_FORCENEW if the user so chooses.
- *
- * In any case, when allocating a new buffer, the routine will always
- * allocate a buffer if possible, even if it can't allocate the full
- * size requested.
- *
- * Returns FALSE if there wasn't enough room to allocate the new buffer
- * (the minimum buffer size is something like 4K.)
- *
- * Note that if the size of the buffer is being reduced, the caller
- * is responsible for updating the display to reflect the new status.
- *
- * Note also that the whole idea of only freeing the buffer if there's
- * no other option is great in principle; in practise, it turned out to
- * be too much work to support copying buffers for such an infrequent
- * operation, so we don't do it after all.
- */
- int SetTotalBufferSize(ULONG newsize, int alloctype)
- {
- UBYTE *oldbuf = BufferStart;
- ULONG oldsize = BufferSize;
- UBYTE *newbuf;
-
- ObtainSemaphore(&BufSem);
- ClearBuffer(); /* Clear the existing buffer first */
-
- /*
- * First, work out if we have enough memory to satisfy the request
- * without freeing our old buffer first. If we don't, and if
- * alloctype is SETBUF_KEEPOLD, then we fail immediately.
- */
- if (newsize < MIN_BUF_SIZE)
- newsize = MIN_BUF_SIZE;
-
- newbuf = AllocMem(newsize, MEMF_ANY);
- if (!newbuf) {
- /*
- * No memory available -- we _may_ need to bomb out at this
- * point. If we didn't have a buffer already, then we
- * always go ahead and try and allocate some, regardless
- * of alloctype.
- */
- if (oldbuf && alloctype == SETBUF_KEEPOLD) {
- ReleaseSemaphore(&BufSem);
- return (FALSE);
- }
-
- /*
- * Okay, try and allocate the memory even though it means
- * freeing up the old buffer first.
- */
- if (oldbuf) {
- FreeMem(oldbuf, oldsize);
- oldbuf = NULL;
- }
- newbuf = AllocMem(newsize, MEMF_ANY);
- if (!newbuf) {
- /*
- * Couldn't allocate memory on second attempt. Finally,
- * we just grab the largest chunk we can and use that
- * instead.
- */
- ULONG freesize;
-
- Forbid();
- freesize = AvailMem(MEMF_ANY | MEMF_LARGEST);
- if (freesize < MIN_BUF_SIZE) {
- Permit();
- ReleaseSemaphore(&BufSem);
- return (FALSE);
- }
- if (freesize < newsize) /* freesize > newsize _is_ possible! */
- newsize = freesize;
- newbuf = AllocMem(newsize, MEMF_ANY);
- Permit();
- if (!newbuf) {
- /*
- * Should never happen, but better safe than sorry
- */
- ReleaseSemaphore(&BufSem);
- return (FALSE);
- }
- }
- }
- /*
- * Okay, we finally have some memory so initialise the global
- * buffer variables for use by everyone else.
- */
- BufferStart = newbuf;
- BufferSize = newsize;
- BufferEnd = newbuf + newsize;
- BufferCur = BufferStart;
-
- if (oldbuf != NULL) {
- // CopyEvents(newbuf, newsize, oldbuf, oldsize);
- FreeMem(oldbuf, oldsize);
- }
- NewList(&EventList); /* Probably need to delete this eventually */
- ReleaseSemaphore(&BufSem);
- return (TRUE);
- }
-
- // void CopyEvents(UBYTE *newbuf, ULONG newsize,
- // UBYTE *oldbuf, ULONG oldsize) {}
-
- /*
- * ClearBuffer()
- *
- * Clears the current buffer by a very simple process -- it simply
- * erases the current list of linked items, and resets RealFirstSeq etc.
- * to be the next available event number. This means that any events
- * that were partially in progress will spot, on their completition,
- * that their associated event number is now < RealFirstSeq, and so
- * won't try and write into the now-cleared buffer, thus avoiding
- * corruption.
- *
- * BaseSeq is used to allow the new buffer count to commence from
- * 1 again, rather than keeping the old number (which might be
- * into the hundreds by this stage!)
- *
- * Note: we are a little bit naughty in some of the patches in that
- * after allocating a brand new event, we sometimes write into it
- * immediately _without_ semaphore protection (to fill in the
- * initial details etc.) This is not terribly wise, but since there
- * is never anything to deliberately cause a task switch, it's
- * reasonably safe. Adding semaphore protection to all these cases
- * would complicate things quite a bit.
- */
- void ClearBuffer(void)
- {
- ObtainSemaphore(&BufSem);
- Delay(5); /* Just to be safe, let everyone else run for 0.1 seconds */
-
- NewList(&EventList);
- BufferCur = BufferStart;
-
- BaseSeq =
- FirstSeq =
- RealFirstSeq =
- TopSeq =
- LastDrawnTopSeq =
- BottomSeq =
- MaxScannedSeq =
- EndSeq =
- EndCompleteSeq = NextSeq;
-
- ReleaseSemaphore(&BufSem);
- }
-
- /*
- * CleanupBuffers()
- *
- * Frees up the buffer on exit, along with any other pertinent
- * resources that were allocated by this module.
- */
- void CleanupBuffers(void)
- {
- if (BufferStart) {
- ObtainSemaphore(&BufSem);
- FreeMem(BufferStart, BufferSize);
- if (BufferSize == 0)
- ShowError("Warning! BufferSize is zero for some reason!");
- BufferStart = NULL;
- BufferSize = 0;
- NewList(&EventList);
- ReleaseSemaphore(&BufSem);
- }
- }
-
- /*
- * GetNewEvent(stringsize)
- *
- * Allocates a new event structure from the list from the buffer
- * and returns a pointer to it. If necessary, one of the existing
- * buffers will be flushed to make room for the new event.
- *
- * The stringsize is the maximum amount of extra space to allocate
- * after the end of the event to accomodate string storage. A pointer
- * to this data area can be obtained by accessing the first location
- * after the event itself. For example, GetNewEvent(20) would return
- * an event with 20 additional data bytes after it. Note that this
- * _includes_ any terminating zeros that may be present in the
- * strings.
- */
- Event *GetNewEvent(int stringsize)
- {
- UBYTE *startev;
- ULONG totalsize;
- Event *ev;
-
- /* Calculate total size and ensure it's a longword multiple */
- totalsize = (sizeof(Event) + stringsize + 3) & ~3;
-
- if (BufferStart == NULL || totalsize > (MIN_BUF_SIZE/2))
- return (NULL); /* Should never happen but doesn't hurt to check */
-
- DB(" [GNE-Go] ");
- ObtainSemaphore(&BufSem);
- #if 0
- ev = (Event *)BufferStart;
- ReleaseSemaphore(&BufSem);
- return (ev);
- #endif
-
- startev = HeadNode(&EventList);
- if (IsListEmpty(&EventList)) {
- /*
- * First time being called, so simply begin at the
- * start of the buffer.
- */
- BufferCur = BufferStart;
- } else {
- /*
- * There are already some nodes. Try and allocate a new node,
- * flushing nodes from the start of the list as necessary.
- */
- for (;;) {
- if (startev < BufferCur) {
- /*
- * From BufferCur to the end of the buffer is free space,
- * so we can check for a quick fit. If we get one, then
- * we go for it, otherwise we move BufferCur back to the
- * start of the buffer and start flushing events from
- * there.
- */
- if (totalsize <= (BufferEnd - BufferCur))
- break; /* Got it */
- BufferCur = BufferStart; /* Else wrap to start of list */
- }
- /*
- * Now delete events from here to the end of the buffer until
- * we finally have enough room in the buffer. If we run off
- * the end of the buffer, let the main loop go around again
- * to reset everything.
- */
- while (startev >= BufferCur && (startev - BufferCur) < totalsize) {
- RemHead(&EventList);
- if (IsListEmpty(&EventList)) {
- KPrintF("Uhoh --- list is empty and shouldn't be!\n");
- Delay(50);
- startev = BufferCur = BufferStart;
- break;
- }
- startev = HeadNode(&EventList);
- }
- if (startev > BufferCur)
- break; /* Got it */
- }
- }
- ev = (Event *)BufferCur;
- BufferCur += totalsize;
-
- /*
- * Allocate our new event from the current buffer block (which
- * we are now assured will have enough space in it)
- */
- ev->seqnum = ++NextSeq; /* Give it a unique sequence number */
- ev->status = ES_CREATING; /* Mark event as not-to-be-touched */
- ev->flags = EFLG_NONE; /* No date/segment lookup as yet */
- AddTail(&EventList, (Node *)ev);
- /*
- * Update current first seq num so patch code knows when an
- * event currently being created has become invalid
- */
- RealFirstSeq = ((Event *)HeadNode(&EventList))->seqnum;
- DateStamp(&ev->datestamp); /* Store current date */
- DB(" [GNEEnd] ");
- ReleaseSemaphore(&BufSem);
- return (ev);
- }
-
- /*
- * ParseFormatString(formatstr, evformat, maxfields)
- *
- * Parses a text format string containing a list of format identifiers
- * (%d, %s, etc.) with optional field widths (%20s, %10d) into an
- * internal list of formatting codes that can be quickly processed
- * when displaying output.
- *
- * formatstr points to the format string. evformat points to the
- * EventFormat[] array to hold the output.
- *
- * maxfields is the maximum number of format fields that will
- * be recognised -- any more than this will be ignored. evformat[]
- * must be able to hold at least maxfields elements. If a field is
- * duplicated, only the first occurrance is recognised.
- *
- * Returns the length any output produced using this format array will
- * have (including separating spaces)
- */
- int ParseFormatString(char *formatstr, EventFormat *evformat, int maxfields)
- {
- int evindex = 0;
- int totlen = 0;
- char *p = formatstr;
-
- while (*p) {
- int width = 0;
- int i;
- char type;
- char ch;
-
- if (*p++ != '%')
- continue;
-
- /*
- * Got a format specifier, so now parse it.
- *
- * We ignore any negative prefix -- in the real printf,
- * it means left-align this field, and all our fields are
- * left-aligned by default anyway.
- */
- if (*p == '-')
- p++;
- while (*p >= '0' && *p <= '9') {
- width = (width * 10) + *p - '0';
- p++;
- }
- if (!*p)
- break;
-
- ch = *p++;
- if (ch >= 'A' && ch <= 'Z')
- ch |= 0x20;
-
- for (i = 0; FieldTypes[i].type != EF_END; i++) {
- if (FieldTypes[i].idchar == ch)
- break;
- }
- type = FieldTypes[i].type;
- if (type != EF_END) {
- /*
- * Matched a field. Now try and insert it. But first,
- * make sure we don't have a duplicate.
- */
- int j;
-
- for (j = 0; j < evindex; j++)
- if (evformat[j].type == type) /* Got a duplicate */
- break;
-
- if (j < evindex) /* Skip over duplicates */
- continue;
-
- if (width == 0)
- width = FieldTypes[i].defwidth;
-
- if (width < 1) width = 1;
- if (width > MAX_FIELD_LEN) width = MAX_FIELD_LEN;
-
- evformat[evindex].type = type;
- evformat[evindex].width = width;
- evformat[evindex].titlemsgid = FieldTypes[i].titlemsgid;
- totlen += width + 1;
- evindex++;
- if (evindex >= (maxfields-1))
- break;
- }
- }
- evformat[evindex].type = EF_END;
- if (totlen) /* Compensate for the extra 'space' we counted */
- totlen--;
-
- return (totlen);
- }
-
- /*
- * BuildFormatString(evformat, formatstr, maxlen)
- *
- * Converts an existing event format array into an ascii string
- * corresponding to the format that can be parsed by ParseFormatString.
- * The string is guaranteed to be at most maxlen chars in length
- * (including terminating \0);
- */
- void BuildFormatString(EventFormat *evformat, char *formatstr, int maxlen)
- {
- char *p = formatstr;
- char *end = formatstr + maxlen - 6; /* Max length of format element is 5 */
-
- while (evformat->type != EF_END) {
- int j;
-
- if (p >= end) /* Never exceed string length */
- break;
- /*
- * Find the event type in our mapping array so that we can
- * determine the default width. This lets us choose not
- * to include the width in the ascii field, for neatness.
- */
- for (j = 0; FieldTypes[j].type != evformat->type; j++)
- ;
- *p++ = '%';
- if (FieldTypes[j].defwidth != evformat->width) {
- mysprintf(p, "%ld", evformat->width);
- p += strlen(p);
- }
- *p++ = FieldTypes[j].idchar;
- *p++ = ' '; /* Space out entries for neatness */
- evformat++;
- }
- p[-1] = '\0'; /* Replace final space with a terminator instead. */
- }
-
- /*
- * ltoa(buffer, val, numdigits)
- * ltoap(buffer, val, numdigits)
- *
- * Converts the given value to an ascii hex string of up to numdigits
- * (with leading zeros). No zero termination is performed. The output
- * is stored in the first numdigits characters of buffer.
- *
- * ltoap is similar, except that it pads leading zeros with spaces.
- *
- * Returns a pointer to the buffer.
- */
- char *ltoa(char *buffer, unsigned long val, int numdigits)
- {
- static char HexDigits[] = "0123456789ABCDEF";
- char *p = buffer + numdigits - 1;
-
- while (p >= buffer) {
- *p-- = HexDigits[val & 0x0F];
- val >>= 4;
- }
- return (buffer);
- }
-
- char *ltoap(char *buffer, unsigned long val, int numdigits)
- {
- static char HexDigits[] = "0123456789ABCDEF";
- char *p = buffer + numdigits - 1;
-
- do {
- *p-- = HexDigits[val & 0x0F];
- val >>= 4;
- } while (val && p >= buffer);
-
- while (p >= buffer)
- *p-- = ' ';
-
- return (buffer);
- }
-
- /*
- * SetEventDateStr(event)
- *
- * This function converts the datestamp in the passed event into a date
- * and time string, also stored in the event.
- */
- void SetEventDateStr(Event *ev)
- {
- struct DateTime datetime;
-
- datetime.dat_Stamp = ev->datestamp;
- datetime.dat_Format = FORMAT_DOS;
- datetime.dat_Flags = 0;
- datetime.dat_StrDay = NULL;
- datetime.dat_StrDate = ev->date;
- datetime.dat_StrTime = ev->time;
- DateToStr(&datetime);
- }
-
- /*
- * CheckSegTracker()
- *
- * Checks to see if SegTracker is loaded and sets the SegTracker
- * global variable accordingly. Should be called every now and
- * again to keep us up to date (e.g. whenever a new window is
- * opened).
- */
- void CheckSegTracker(void)
- {
- SegTrackerActive = (FindSemaphore("SegTracker") != NULL);
- }
-
- /*
- * SetEventSegmentStr(event)
- *
- * This function converts the calladdr in the passed event into a segment
- * name and hunk/offset pair if SegTracker is loaded, or into an invalid
- * string if SegTracker is not loaded.
- *
- * See the Enforcer/Segtracker documentation (from Michael Sinz) for
- * more info on how this works.
- *
- * Note that if the passed-in event->segname is NULL, then it will
- * be reset to an "unknown module" string and no attempt to locate
- * the module will be made. It is thus imperative that the _caller_
- * only calls this function once for each event. The easiest way
- * to ensure this is to use the EFLG_DONESEG flag in the ev->flags
- * register to track whether the call has been made or not.
- *
- * New: we now detect ROM addresses and search the resident module
- * list ourselves looking for the module that contains it.
- */
- void SetEventSegmentStr(Event *ev)
- {
- typedef char (*__asm SegTrack(reg_a0 ULONG,reg_a1 ULONG *,reg_a2 ULONG *));
-
- static struct {
- Semaphore seg_Semaphore;
- SegTrack *seg_Find;
- } *segsem;
-
- ULONG hunk;
- ULONG offset;
- char *segname = NULL;
-
- ev->hunk = INVALID_HUNK;
- if (ev->segname == NULL) {
- ev->segname = "Unknown module";
- return;
- }
-
- Forbid();
- if (!segsem)
- segsem = (void *)FindSemaphore("SegTracker");
-
- if (segsem)
- segname = segsem->seg_Find(ev->calladdr, &hunk, &offset);
-
- if (segname) {
- strncpy(ev->segname, segname, MAX_SEGTRACKER_LEN);
- ev->segname[MAX_SEGTRACKER_LEN-1] = 0;
- ev->hunk = hunk;
- ev->offset = offset;
- } else {
- if (ev->calladdr >= RomStart && ev->calladdr <= RomEnd) {
- /*
- * Let's search the resident list for it ourselves.
- * and see if we can't do any better.
- */
- struct Resident *res;
- ULONG *pres = (ULONG *)SysBase->ResModules;
- UBYTE *p;
-
- while (*pres) {
- if (*pres & 0x80000000) {
- pres = (ULONG *)(*pres & 0x7FFFFFFF);
- continue;
- }
- res = (struct Resident *)*pres++;
- if (ev->calladdr >= (ULONG)res
- && ev->calladdr <= (ULONG)res->rt_EndSkip) {
- ev->hunk = 0;
- ev->offset = ev->calladdr - (ULONG)res;
- strcpy(ev->segname, "ROM: ");
- strncat(ev->segname + 5, res->rt_IdString,
- MAX_SEGTRACKER_LEN - 6);
- ev->segname[MAX_SEGTRACKER_LEN-1] = 0;
- /*
- * Now filter out any remaining carriage returns
- * and linefeeds, since they look ugly when printed
- * We also convert any open brackets into '\0's
- * since these are just dates which we don't care
- * about -- it looks neater to just display the
- * module name and version.
- */
- for (p = ev->segname; *p; p++) {
- if (*p < ' ')
- *p = ' ';
- if (*p == '(')
- *p = '\0';
- }
- break;
- }
- }
- }
- if (ev->hunk == INVALID_HUNK)
- ev->segname = MSG(MSG_SEG_MODULE_NOT_FOUND);
- }
- Permit();
- }
-
- /*
- * FormatEvent(eventformat, event, outputstr, start, end)
- *
- * Formats the specified event according to the passed in format
- * array. Only that portion of the fully formatted event which
- * lies from the 'start' to 'end' characters is produced, and
- * output always starts at the beginning of the outputstr.
- *
- * For example, a start of 4 and end of 8 will produce a formatted
- * string of exactly 5 characters.
- *
- * Each entry in the eventformat array is decoded, and text which
- * corresponds to the entry type is copied to the output string. If
- * the text is longer than the entry width setting, it is truncated;
- * if shorter, it is padded with spaces.
- *
- * Each event is separated by one space character.
- *
- * If the event pointer is NULL, then the title headings are returned
- * instead of the event contents.
- */
- void FormatEvent(EventFormat *eventformat, Event *event,
- char *outstr, int start, int end)
- {
- static char hexbuf8[] = "00000000";
- static char hexbuf13[] = "0000:00000000";
- static char countbuf[] = " ";
-
- EventFormat *ef = eventformat;
- int savechar = 0;
- char *savep;
- char *p;
- int col = 0;
-
- end++; /* Make exclusive rather than inclusive */
-
- while (ef->type != EF_END && col < end) {
- int width = ef->width;
-
- /*
- * First skip over any entries that fall before desired section
- */
- if ((col + width) < start) {
- col += width + 1;
- if (col > start)
- *outstr++ = ' '; /* Add leading space */
- ef++;
- continue;
- }
-
- /*
- * Now parse our current entry
- */
- if (event) {
- switch (ef->type) {
-
- case EF_PROCNAME: p = event->procname; break;
- case EF_ACTION: p = event->action; break;
- case EF_OPTIONS: p = event->options; break;
- case EF_RESULT: p = event->result; break;
- case EF_CALLADDR:
- p = ltoap(hexbuf8, event->calladdr, 8);
- if (*p == ' ')
- p++;
- break;
-
- case EF_PROCID:
- p = ltoap(hexbuf8, event->processid,8);
- if (*p == ' ')
- p++;
- break;
-
- case EF_FILENAME:
- {
- /*
- * This is a little trickier since we allow the user
- * to view it left-aligned or right-aligned. For
- * right-aligned, we temporarily replace the first char
- * with a « to indicate there are additional characters
- * to the left, then restore it after we've copied the
- * string data later on. This is much quicker (and
- * safer) than copying the entire string to a temporary
- * just so we can patch a single character of it.
- */
- int len;
-
- p = event->filename;
- if (RightAligned) {
- len = strlen(p);
- if (len > width) {
- p += len - width;
- if (width > 1) {
- savep = p;
- savechar = *p;
- *p = '«';
- }
- }
- }
- break;
- }
-
- case EF_DATE:
- if ((event->flags & EFLG_DONEDATE) == 0) {
- SetEventDateStr(event);
- event->flags |= EFLG_DONEDATE;
- }
- p = event->date;
- break;
-
- case EF_TIME:
- if ((event->flags & EFLG_DONEDATE) == 0) {
- SetEventDateStr(event);
- event->flags |= EFLG_DONEDATE;
- }
- p = event->time;
- break;
-
- case EF_SEGNAME:
- if ((event->flags & EFLG_DONESEG) == 0) {
- SetEventSegmentStr(event);
- event->flags |= EFLG_DONESEG;
- }
- p = event->segname;
- break;
-
- case EF_COUNT:
- /*
- * We subtract BaseSeq from the event number when
- * displaying it so that we have a convenient way of
- * making the event number appear to return to 1,
- * while internally still ensuring that the event
- * number is ALWAYS monotonically increasing, even
- * across buffer clears and the like.
- */
- mysprintf(countbuf, "%ld", event->seqnum - BaseSeq);
- p = countbuf;
- break;
-
- case EF_HUNKOFFSET:
- if ((event->flags & EFLG_DONESEG) == 0) {
- SetEventSegmentStr(event);
- event->flags |= EFLG_DONESEG;
- }
- if (event->hunk == INVALID_HUNK) {
- /*
- * If we couldn't locate the module address,
- * then don't bother printing the hunk/offset
- * info since it will only be zero anyway.
- */
- p = ""; /* Couldn't find module so don't print hunk addr */
- } else {
- /*
- * Only output 6 digits of offset and 2 digits
- * of hunk, but arrange that we can handle a
- * full 8 / 4 combination if necessary
- */
- p = ltoa(hexbuf13+7, event->offset, 6);
- *--p = ':';
- p = ltoap(p-2, event->hunk, 2);
- }
- break;
-
- default:
- p = "<Invalid>";
- break;
- }
- } else {
- /*
- * Format column headings instead
- */
- p = MSG(ef->titlemsgid);
- }
-
- /*
- * Okay, so now we have p pointing to our string. We now need to
- * copy a sufficient number of characters into the string to
- * fulfill our need.
- */
- if (col < start) {
- /*
- * Our first special case is where we have to skip over some of
- * the early characters, i.e. chars to left are clipped.
- */
- while (col < start && *p) {
- col++;
- width--;
- p++;
- }
- if (col < start) {
- /*
- * Ran out of characters in our string, so pretend we
- * have a null string and let the following code pad
- * the remaining width with spaces.
- */
- width -= (start - col);
- col = start;
- }
- }
-
- /*
- * Now copy characters intact into string
- */
- if ((col + width) > end) {
- /*
- * This field needs to be clipped. We do this quite simply
- * by adjusting the width to be the remaining number of
- * chars in the string.
- */
- width = end - col;
- }
-
- /*
- * Now copy width chars from our string to the output buffer,
- * padding with spaces as necessary.
- */
- while (width && *p) {
- *outstr++ = *p++;
- width--;
- col++;
- }
- if (width) {
- memset(outstr, ' ', width);
- outstr += width;
- col += width;
- }
- *outstr++ = ' '; /* Space in between output fields */
- col++; /* onto next column */
- ef++; /* Onto the next format field */
- /*
- * Finally, restore the character we replaced with « earlier
- * (if any)
- */
- if (savechar) {
- *savep = savechar;
- savechar = 0;
- }
- }
-
- /*
- * Okay, we've generated the string as requested. Now check to see if
- * we need to pad the remainder of the string with spaces (i.e. the
- * number of fields listed wasn't as wide as the whole window).
- *
- * Ideally, we will never be passed in a start/end pair wider than
- * the width of the formatted output, but you never know...
- */
- if (col <= end) {
- memset(outstr, ' ', end-col+1);
- outstr += end-col+1;
- }
- outstr[-1] = '\0'; /* Remove final terminating space */
- }
-
- /*
- * UnderlineTitles(eventformat, outstr, underchar)
- *
- * This function creates a string of underlines which matches the
- * event headings in the passed event format, such that if the
- * headings were printed out in full, the underline string would
- * line up properly. underchar is the char used for underlining.
- *
- * The reason we can't just take the obvious approach and generate
- * a title string, then convert all non-space chars to minus, is
- * because we can have titles like "Process Name" and we want the
- * underline to be continuous, not break between process and name.
- *
- * Returns a pointer to the newly formatted string (outstr). Outstr
- * must have room to store the entire width, as defined by the
- * event format.
- */
- char *UnderlineTitles(EventFormat *ef, char *outstr, char underchar)
- {
- char *p = outstr;
-
- while (ef->type != EF_END) {
- int width = ef->width;
- int titleid = ef->titlemsgid;
- int titlelen = strlen(MSG(titleid));
-
- if (titlelen > width)
- titlelen = width;
-
- memset(p, underchar, titlelen);
- p += titlelen;
- width -= titlelen-1; /* Include an extra space between cols */
- if (width) {
- memset(p, ' ', width);
- p += width;
- }
- ef++;
- }
- *--p = 0; /* Replace final space with a null */
- return (outstr);
- }
-
- #if TESTING
- /*
- * TestEventFields()
- *
- * Simple test function to check out our format conversion routines
- */
- void TestEventFields(void)
- {
- static char inline[200];
- static char formatstr[200];
- static EventFormat evformat[50];
-
- while (gets(inline) != NULL) {
- int len;
-
- if (*inline == 'q')
- break;
-
- len = ParseFormatString(inline, evformat, 50);
- BuildFormatString(evformat, formatstr, 200);
-
- printf("Converted string: >>%s<<, length = %d\n", formatstr, len);
- }
- }
-
- /*
- * TestFormat
- *
- * Test our text formatting routines
- */
- void TestFormat()
- {
- static char inline[200];
- static char formatstr[200];
- static EventFormat evformat[50];
- static char outstr[500];
- Event *ev;
- int showtitles = 0;
-
- for (;;) {
- int len;
-
- printf("Enter a format string (q to quit): ");
- gets(inline);
- if (*inline == 'q')
- return;
-
- len = ParseFormatString(inline, evformat, 50);
- BuildFormatString(evformat, formatstr, 200);
- printf("Actual string is >> %s << (%d chars)\n", formatstr, len);
-
- /*
- * Get ourselves a new event
- */
- ev = GetNewEvent(0);
- if (!ev) {
- printf("Uhoh! Couldn't allocate event!\n");
- continue;
- }
- ev->procname = "[ 1] SAS/C compiler";
- ev->filename = "s:startup-sequence";
- ev->action = "Open";
- ev->result = "Okay";
- ev->calladdr = 0x12345678;
- ev->processid = 0xDEADBEEF;
- DateStamp(&ev->datestamp);
-
- for (;;) {
- int start, end;
-
- printf("Now enter start,end values (-1 to quit, s to toggle): ");
- gets(inline);
- if (*inline == 'q')
- return;
- if (*inline == 's') {
- showtitles = !showtitles;
- printf("Now printing %s\n", showtitles ? "titles" : "contents");
- continue;
- }
- sscanf(inline, "%d,%d", &start, &end);
- if (start == -1)
- break;
-
- if (showtitles)
- FormatEvent(evformat, NULL, outstr, start, end);
- else
- FormatEvent(evformat, ev, outstr, start, end);
- printf("|%s|\n", outstr);
- }
- }
- }
-
- /*
- * TestEvents
- *
- * Creates some random events, then prints them out
- */
- void TestEvents(void)
- {
- int howmany = 0;
- int i;
-
- printf("Generate how many events? ");
- scanf("%d", &howmany);
-
- for (i = 0; i < howmany; i++) {
- Event *ev = GetNewEvent((rand() & 63));
-
- printf("Event head: %8x Event new: %8x\n",
- (UBYTE *)HeadNode(&EventList) - BufferStart,
- (UBYTE *)ev - BufferStart);
- }
- }
- #endif
-