DNA.
wyrm-dayfile
Version.
1.0.9
Namespace.
::wyrm
Namespace.
::wyrm::wif::dayfile
Command.
::wyrm::dayfile
Command.
::wyrm::wif::dayfile::statusUpdate
Command.
::wyrm::wif::dayfile::statusThrob
Command.
::wyrm::wif::dayfile::statusCreate
Command.
::wyrm::wif::dayfile::logUpdate
Command.
::wyrm::wif::dayfile::logCreate
Language.
c
Manpage.
dayfile (1WY)
Manpage.
wyrm_dayfile (3WY)
Manpage.
wyrm_dayfileDump (3WY)
Manpage.
wyrm_dayfileAttach (3WY)
Manpage.
wyrm_dayfileStatusWidget (3WY)
Manpage.
wyrm_dayfileEval (3WY)
Testbase.
Test Script
Test Report
Import.
Interface.
wyrm-io
wyrmwif
Package.
wyrmwif
Export.
Implementation.
wyrm-dayfile.c
Interface.
wyrm-dayfile.h
Package.
wyrmdayfile.dylib
System.
wyrm-dayfile.sys

Job Log and Tracing

Sections.
Files
Man Pages
Threads
Formatting Messages into the Dayfile
Dayfile Residency
Reading a Dayfile
Tk Widget Interface
Console/Stdout Interface
Trace and Dump Utilities
Dayfile Command
Make.
Package.
compile -cc -ld -o [
  export package
] [
  export implementation
] -- -list [
  import interface
] [
  export interface
] $include/unix/$base.sys
Script.
rule clean :: {} "
  -rm $test/wyrm-dayfile.TESTING
"
rule clobber :: {} "
  -rm $include/wyrm-dayfile.h
  -rm $so/wyrmdayfile[info sharedlibextension]
"
   
top

1 :: Provide a dayfile, which is basically, basically, a job log and notification.

Copyright (C) 2004 SM Ryan

Wyrmwif Tcl extensions. For non-profit uses only, provided this copyright is preserved on all copies, this work may be freely copied, modified, redistributed, compiled, and incorporated in other works. This work is distributed with no warranty of any kind; no author or distributor accepts any responsibility for the consequences of using it, or for whether it serves any particular purpose or works at all, unless he or she says so in writing.

   
   

Files

   
top

#ifndef WYRM_DAYFILE_H
#define WYRM_DAYFILE_H

	//	wyrm-dayfile.dna - Copyright (C) 2004 wyrmwif@bigfoot.com.  All rights reserved.

	#include "wyrmwif.h"

	<Export dayfile declaration>
	<Export dayfile control declarations>
	<Export dayfile read declarations>
	<Export display declarations>
	<Export tracing declarations>
	int Wyrmdayfile_Init(Intr intr);
	int Wyrmdayfile_SafeInit(Intr intr);

#endif

		
 
section    top

static const char COPYRIGHT[] =
	"wyrm-dayfile.dna - Copyright (C) 2004 SM Ryan. All rights reserved.";

#include <time.h>
#include "wyrm-dayfile.h"
#include "wyrm-io.h"


<Declare mutex>
<Typedefs>
<Dayfile residency>
<Structures and static data>
<Hierarchy>
<Shell status state>
<Forward declarations>

#include "wyrm-dayfile.sys"

<Add a bufferable message to the dayfile>
<Allocate and initialise m buffers>
<Dayfile attach and open>
<Catalog of existing dayfiles>
<Dayfile detach>
<Dayfile dump>
<Get line 1 or line 2 status and whether this is an alert>
<Create a Tk dayfile status widget>
<Create a Tk dayfile log widget>
<Control display of status to the shell output>
<Intercept and report background errors>
<Intercept and report unknown commands>
<Dump variables according to a pattern>
<Trace variables according to a pattern>
<Trace commands according to a pattern>
<Evaluate a command and report its result>

<Message format>
<Flush dayfile buffers>
<Free a message from buffer i>
<Add a message to buffer i>
<Delete the captured interpretter if that has not yet been done>
<Variable patterns>
<Package and dayfile command>

		
 
section    top

#include <sys/types.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <dirent.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>

<Unix dayfile attach and open>
<Unix write dayfile line>
<Unix read dayfile lines>
<Unix process id>

		
   
   

Man Pages

   
top
 
section    top
 
section    top
 
section    top
 
section    top
 
section    top
   
   

Threads

   
top
TCL_DECLARE_MUTEX(dayfileMutex)
		
 
section    top
Tcl_MutexLock(&dayfileMutex);
		
 
section    top
Tcl_MutexUnlock(&dayfileMutex)
		
   
   

Formatting Messages into the Dayfile

   
top

14. Dayfile format string :: A message are given to the dayfile with printf-like strings, but with fewer options and some extra/changed descriptors.

"%q" message-class
Give the message class of the current message. If not specified, the class is ' '. Message class TCL_OK or TCL_RETURN is 'R', TCL_BREAK is 'B', TCL_CONTINUE is 'C', and TCL_ERROR is 'E'. The %q format must be the first if it is present.
"%o" Obj-pointer
Format the object's string value.
"%s" string
Format the string. Strings are truncated to 255 bytes, if longer than that.
"%ns" string
Format the string. Strings are truncated to n<256 bytes, if longer than that.
"%c" character
Format one character.
"%r" interpretter
Format the current interpretter result.
"%d" long
Format the long integer in decimal.
"%D" long-long
Format the long long integer in decimal.
"%b" int
Format the int in 2 hexidecimal digits.
"%h" int
Format the int in 4 hexidecimal digits.
"%w" long
Format the long 8 hexidecimal digits.
"%x" long-long
Format the long long in 16 hexidecimal digits.

 
section    top

Obj r = incr(Tcl_NewObj()),p;
int n; chars s; char c;

int class;
if (strbegins("%q",format)) {
	class = va_arg(L,int); format += 2;
}else
	class = ' ';
switch (class) {
	case TCL_OK: case TCL_RETURN: class = 'R'; break;
	case TCL_ERROR: class = 'E'; break;
	case TCL_BREAK: class = 'B'; break;
	case TCL_CONTINUE: class = 'C'; break;
}
int max;
if (perm>=0) {
	if (perm>26) perm = 26;
	<Timestamp current clock>
	Tcl_AppendObjToObj(r,(p=oprintf("%c%s.",perm+'A',hhmmss))); decr(p);
	addPidToObj;
	Tcl_AppendObjToObj(r,(p=oprintf("%c.",class))); decr(p);
	max = 865;
}else {
	max = 80;
}
int e = Tcl_GetCharLength(r);

while (*format && Tcl_GetCharLength(r)<max) {
	int n;
	if (*format!='%') {
		Tcl_AppendToObj(r,format,1); format++;
	}else if (format++, isdigit(*format) ? ((n=strtol(format+1,&format,0)),*format=='s') : false) {
		s = va_arg(L,chars);
		if (strlen(s)<n) n = strlen(s);
		goto string;
	}else switch (*format++) {
		case 's':
			s = va_arg(L,chars); n = 255;
			if (strlen(s)<n) n = strlen(s);
		string:
			while (n-->0) {
				c = *s++;
				if (!isascii(c)) c = '.';
				else if (isspace(c)) c = ' ';
				else if (iscntrl(c)) c = '.';
				Tcl_AppendToObj(r,&c,1);
			}
			break;
		case 'o': {
			Obj arg = va_arg(L,Obj);
			s = Tcl_GetStringFromObj(arg,&n);
			if (n>255) n = 255;
		}   goto string;
		case 'r': {
			Intr arg = va_arg(L,Intr);
			Obj obj = incr(Tcl_GetObjResult(arg));
			s = Tcl_GetStringFromObj(obj,&n);
			if (n>255) n = 255;
		}   goto string;
		case 'c':
			c = va_arg(L,int); s = &c; n = 1;
			goto string;
		case 'b':
			Tcl_AppendObjToObj(r,(p=oprintf("%02X",va_arg(L,unsigned)))); decr(p);
			break;
		case 'h':
			Tcl_AppendObjToObj(r,(p=oprintf("%04X",va_arg(L,unsigned)))); decr(p);
			break;
		case 'w':
			Tcl_AppendObjToObj(r,(p=oprintf("%08lX",va_arg(L,unsigned long)))); decr(p);
			break;
		case 'x':
			Tcl_AppendObjToObj(r,(p=oprintf("%016llX",va_arg(L,unsigned long long)))); decr(p);
			break;
		case 'd':
			Tcl_AppendObjToObj(r,(p=oprintf("%ld",va_arg(L,long)))); decr(p);
			break;
		case 'D':
			Tcl_AppendObjToObj(r,(p=oprintf("%lld",va_arg(L,long long)))); decr(p);
			break;
		default:
			s = format-1; n = 1;
			goto string;
	}
}
if (Tcl_GetCharLength(r)>max) Tcl_SetObjLength(r,max);

		
 
section    top

#define addPidToObj (Tcl_AppendObjToObj(r,(p=oprintf("%04X.",getpid()))), decr(p))

		
 
section    top

static chars argv0 = "?";
static char line[2][81] = {{0},{0}};

		
 
section    top

case o_dayfilep:
	if (N==3) {
		class = ' ';
		goto o_dayfile;
	}else if (N==4) {
		s = Tcl_GetStringFromObj(P[2],&n);
		if (n!=1) return rprintf(intr,"%!dayfile message class must be one character",TCL_ERROR);
		class = *s;
		goto o_dayfile;
	}else {
		Tcl_WrongNumArgs(intr,1,P0,"[permanency] [class] message");
		return TCL_ERROR;
	}
case o_dayfilec:
	class = *s;
	if (N==3) {
		perm = 0;
		goto o_dayfile;
	}else if (N==4) {
		if (Tcl_GetIntFromObj(intr,P[2],&perm)!=TCL_OK) return TCL_ERROR;
		goto o_dayfile;
	}else {
		Tcl_WrongNumArgs(intr,1,P0,"[class] [permanency] message");
		return TCL_ERROR;
	}
o_dayfile:
	wyrm_dayfile(perm,"%q%o",class,P[N-1]);
	Tcl_ResetResult(intr);
	return TCL_OK;

		
 
section    top

static void vdayfile(int perm,chars format0,va_list L) {
	chars format = format0;
	<Encode a message>
	va_end(L);
	if (perm<=0) {
		int l = perm?-perm-1:0;
		int n; chars s = Tcl_GetStringFromObj(r,&n);
		n -= e; s += e;
		if (n>80) n = 80;
		memcpy(line[l],s,n); line[l][n] = 0;
		<Update shell status>
		if (anyStatusWidget) {
			<Update line 1/line2 status widgets>
		}
	}
	Tcl_AppendToObj(r,"\n",1);
	s = Tcl_GetStringFromObj(r,&n);
	if (perm>=0) {
		addBufferMessage(m,s,0,n,0);
	}
	if (perm==0 && anyLogWidget) {
		<Update log widgets>
	}
	decr(r);
}

static void dayfile(int perm,chars format,...) {
	va_list L;
	if (m==0 && perm>0) return;
	va_start(L,format);
	vdayfile(perm,format,L);
}

void wyrm_dayfile(int perm,chars format,...) {
	va_list L;
	if (m==0 && perm>0) return;
	va_start(L,format);
	<Lock access to dayfile data>
	vdayfile(perm,format,L);
	<Unlock access to dayfile data>
}

		
 
section    top
static void vdayfile(int perm,chars format0,va_list L);
static void dayfile(int perm,chars format,...);
		
 
section    top

void wyrm_dayfile(int perm,chars format,...);

		
 
section    top

time_t t = time(0); struct tm *tm = localtime(&t); char hhmmss[20];
strftime(hhmmss,sizeof(hhmmss),"%H.%M.%S",tm);

		
 
section    top

time_t t = time(0); struct tm *tm = localtime(&t); char yyyymmdd[20];
strftime(yyyymmdd,sizeof(yyyymmdd),"%Y/%m/%d",tm);

		
 
section    top

static int displayMessage(Tcl_Channel ch,chars buffer,int o,int n,int m,int c) {
	while (n>0) {
		if (m>0 && o==m) o = 0;
		else if (buffer[o]=='\n') {cputc(ch,'\n'); o++; n--; c = 0; break;}
		else if (c==90) {cputs(ch,"\n..  "); cputc(ch,buffer[o++]); n--; c = 5;}
		else {cputc(ch,buffer[o++]); n--; c++;}
	}
	return c;
}

		
 
section    top

static int displayMessage(Tcl_Channel ch,chars buffer,int o,int n,int m,int c);

		
 
section    top

26. Hierarchy :: Each message is assigned a permanency p, the lower the value of p the more permanent the message. All p=0 messages are written out to disk. If p>0, the message might not appear. Dayfile messages are cascaded through a series of m circular buffers. If p>m, it is treated as if equal to m. All messages are added to the mth buffer first. If the jth buffer is full, so that the new message would overwrite an old message, if the overwritten message permanency p<i, the overwritten message is first copied to the i-1 buffer, possibly cascading all the way to buffer 1. If instead the overwritten message is already at its maximum permanency, it is discarded. A message that is copied out of buffer 1 (its p=0) is written to disk.

In the normal course of events, permanent (p=0) messages percolate through the buffers and are written to disk, and all other messages are ignored. Seems rather pointless. However the concatenation of the file with buffer 1 through buffer m gives a kind of logarithmic back off based on message permanency. Old permanent message are followed by slightly younger messages less permanent, through younger and younger and lesser and lesser permanent to the youngest and most temporary messages. By scaling the importance of debugging and trace information, this allows a dayfile to capture the most critical information at the time of dayfile dump without permanently recording far too many irrelevant messages.

Note also that buffer i will retain messages with p<=i, so that permanent message can be found in buffer m after a dump.

If m=0, only permanent messages are retained.

Two special permanencies, -1 and -2, are also called line 1 and line 2. Line 1 and 2 messages are never permanent (though the last permanent message does overwrite line 1), and only buffer a single message. Line 1 is intended for ongoing status information. Line 2 is intended for alert messages. When the dayfile status is returned, it returns line 2 if it is not empty, otherwise line 2. The dayfile status widget display line 1 or line 2; when displaying line 2, it flashes the message to get the user's attention.


struct Buffer {
	chars p;
	int   in,out,avail;
};
static Buffer *buffer = 0;
static int m = 0;

		
 
section    top

case o_buffer:
	if (N==3) {
		int nb;
		if (Tcl_GetIntFromObj(intr,P[2],&nb)!=TCL_OK) return TCL_ERROR;
		wyrm_dayfileBufferCount(nb);
		Tcl_ResetResult(intr);
		return TCL_OK;
	}else {
		Tcl_WrongNumArgs(intr,2,P0,"number-of-buffers");
		return TCL_ERROR;
	}

		
 
section    top

void wyrm_dayfileBufferCount(int m1) {
	<Lock access to dayfile data>
	int i;
	if (m1>100) m1 = 100;
	if (m1<m) {
		flushBuffer(m1+1,m);
		for (i=m; i>m1; i--) {
			dispose(buffer[i].p);
		}
	}
	buffer = reheap(m1+1,Buffer,buffer);
	buffer[0].p = 0;
	buffer[0].in = 0;
	buffer[0].out = 0;
	buffer[0].avail = 0;
	for (i=m+1; i<=m1; i++) {
		buffer[i].p = nheap(BufferSize,char);
		buffer[i].in = 0;
		buffer[i].out = 0;
		buffer[i].avail = BufferSize;
	}
	m = m1;
	<Unlock access to dayfile data>
}

		
 
section    top

void wyrm_dayfileBufferCount(int m);

		
 
section    top

static void flushBuffer(int l,int u) {
	int i;
	for (i=u; i>=l && i>0; i--) {
		if (attachedSys) {
			while (buffer[i].avail<BufferSize) {
				freeBufferMessage(i);
			}
		}else {
			buffer[i].avail = BufferSize;
		}
	}
}

		
 
section    top

static void freeBufferMessage(int i) {
	<Extract a message from buffer i>
	if (buffer[i].p[buffer[i].out]-'A'<i) {
		addBufferMessage(i-1,buffer[i].p,buffer[i].out,n,BufferSize);
	}
	buffer[i].out = o; buffer[i].avail += n;
}

		
 
section    top

static void addBufferMessage(int i,chars b,int o,int n,int m) {
	announceSys();
	if (i==0) {
		outputMessage(b,o+1,n-1,m);
	}else {
		while (buffer[i].avail<n) freeBufferMessage(i);
		while (n-->0) {
			buffer[i].p[buffer[i].in] = b[o++];
			buffer[i].in = (buffer[i].in+1)&(BufferSize-1);
			if (m>0) o = o&(m-1);
			buffer[i].avail -= 1;
		}
	}
}

		
 
section    top

int n,o;
for (o=buffer[i].out,n=0; buffer[i].p[o]!='\n'; o=(o+1)&(BufferSize-1)) n++;
o = (o+1)&(BufferSize-1); n++;

		
 
section    top

typedef struct Buffer Buffer;
enum {BufferSize = 4096};

		
 
section    top

static void flushBuffer(int l,int u);
static void freeBufferMessage(int i);
static void addBufferMessage(int i,chars buffer,int o,int n,int m);

		
   
   

Dayfile Residency

   
top

36. Dayfile residency :: Dayfiles are stored at a fixed location, $HOME/.wyrm-dayfile on Unices. Within that folder, only a small number of (default 10) are kept. The dayfiles are numberred within the set, and each is identified by its number. When a dayfile is created, the oldest is automatically purged. An old dayfile can also be reattached and new message appended. When a dayfile is attached, the first messages describe the process calling the dayfile. These initial messages can be searched to determine which old dayfile, if any, to reattach.

If the dayfile is not open on the first attempt to write it, a new dayfile is openned without permanency buffers. The explicit openning gives more control and options.

On Unix, if the dayfile is openned in the parent before a fork, the child will write to the same dayfile. The first message of the child will be proceeded by its process description. Dayfile messages include process ids to distingush their messages. A child can also close its dayfile and open a new one or reattach an old one.


typedef struct {int number; time_t mtime;} DayfileCatalog;

		
 
section    top

case o_attach: {
	int dn = -1,max = -1;
	switch (N) {
		case 2: /*dayfile attach*/
			goto doIt;
		case 3: /*dayfile attach number*/
			if (Tcl_GetIntFromObj(intr,P[2],&dn)!=TCL_OK) return TCL_ERROR;
			goto doIt;
		case 4: /*dayfile attach -max m*/
			if (!streq(Tcl_GetString(P[2]),"-max"))
				return rprintf(intr,"%!expected -max: %{y}s",TCL_ERROR,P[2]);
			if (Tcl_GetIntFromObj(intr,P[3],&max)!=TCL_OK) return TCL_ERROR;
			goto doIt;
		case 5: /*dayfile attach number -max m  or  dayfile attach number -max m*/
			if (streq(Tcl_GetString(P[2]),"-max")) {
				if (Tcl_GetIntFromObj(intr,P[3],&max)!=TCL_OK) return TCL_ERROR;
				if (Tcl_GetIntFromObj(intr,P[4],&dn)!=TCL_OK) return TCL_ERROR;
			}else  if (streq(Tcl_GetString(P[3]),"-max")) {
				if (Tcl_GetIntFromObj(intr,P[4],&max)!=TCL_OK) return TCL_ERROR;
				if (Tcl_GetIntFromObj(intr,P[2],&dn)!=TCL_OK) return TCL_ERROR;
			}else {
				return rprintf(intr,"%!expected -max",TCL_ERROR,P[2]);
			}
			goto doIt;
		doIt:
			Tcl_SetObjResult(intr,Tcl_NewIntObj(wyrm_dayfileAttach(dn,max)));
			return TCL_OK;
		default:
			Tcl_WrongNumArgs(intr,2,P0,"[dayfile-number] [-max number]");
			return TCL_ERROR;
	}
}

		
 
section    top

static int dayfileAttach(int dayfilenumber,int maxdayfiles) {
	bool reopen = dayfilenumber>=0;
	flushBuffer(1,m);
	detachSys;
	if (dayfilenumber<0) {
		int nd; DayfileCatalog *c = dayfileCatalog(&nd);
		if (maxdayfiles<0) maxdayfiles = 10;
		if (nd==0) {
			dayfilenumber = 0;
		}else if (nd<maxdayfiles) {
			int i,max=-1; for (i=0; i<nd; i++) if (c[i].number>max) max = c[i].number;
			dayfilenumber = max+1;
		}else {
			int i; time_t min = c[0].mtime; dayfilenumber = c[0].number;
			for (i=1; i<nd; i++) if (c[i].mtime<min) {
				min = c[i].mtime; dayfilenumber = c[i].number;
			}
		}
		dispose(c);
	}
	attachSys(reopen);
	return dayfilenumber;
}

int wyrm_dayfileAttach(int dayfilenumber,int maxdayfiles) {
	<Lock access to dayfile data>
	int num = dayfileAttach(dayfilenumber,maxdayfiles);
	<Unlock access to dayfile data>
	return num;
}

		
 
section    top

case o_catalog:
	if (N==2) {
		Obj r = wyrm_dayfileCatalog(0,0);
		Tcl_SetObjResult(intr,r); decr(r);
		return TCL_OK;
	}else {
		Tcl_WrongNumArgs(intr,2,P0,0);
		return TCL_ERROR;
	}

		
 
section    top

Obj wyrm_dayfileCatalog(int *N,Obj **P) {
	<Lock access to dayfile data>
	int i,n; DayfileCatalog *c = dayfileCatalog(&n);
	Obj r = incr(Tcl_NewObj());
	for (i=0; i<n; i++) {
		Obj E[3];
		E[0] = Tcl_NewLongObj(c[i].number);
		E[1] = Tcl_NewLongObj(c[i].mtime);
		Obj path = dayfilePath(c[i].number);
		Tcl_Channel channel = Tcl_OpenFileChannel(0,Tcl_GetString(path),"r",0);
		decr(path);
		if (channel) {
			E[2] = cgeto(channel);
			Tcl_Close(0,channel);
		}else {
			E[2] = incr(Tcl_NewStringObj("open failed",-1));
		}
		Tcl_ListObjAppendElement(0,r,Tcl_NewListObj(3,E));
		decr(E[2]);
	}
	dispose(c);
	if (N || P) {
		int N1; Obj *P1;
		if (N) N = &N1;
		if (P) P = &P1;
		Tcl_ListObjGetElements(0,r,N,P);
	}
	<Unlock access to dayfile data>
	return r;
}

		
 
section    top

case o_detach:
	if (N==2) {
		wyrm_dayfileDetach();
		Tcl_ResetResult(intr);
		return TCL_OK;
	}else {
		Tcl_WrongNumArgs(intr,2,P0,0);
		return TCL_ERROR;
	}

		
 
section    top

void wyrm_dayfileDetach(void) {
	<Lock access to dayfile data>
	flushBuffer(1,m);
	detachSys;
	<Unlock access to dayfile data>
}

		
 
section    top
static int dayfileAttach(int dayfilenumber,int maxdayfiles);
		
 
section    top

int wyrm_dayfileAttach(int dayfilenumber,int maxdayfiles);
Obj wyrm_dayfileCatalog(int *N,Obj **P);
void wyrm_dayfileDetach(void);

		
 
section    top

static int activedayfile = -1;
static pid_t announcedpid = -1;

static Obj dayfilePath(int num) {
	chars HOME = getenv("HOME");
	#ifdef TESTING
		if (testing) return oprintf(num<0 ? "./wyrm-dayfile" : "./wyrm-dayfile/%d",num);
	#endif
	return oprintf(num<0 ? "%s/.wyrm-dayfile" : "%s/.wyrm-dayfile/%d",HOME?HOME:".",num);
}

static DayfileCatalog *dayfileCatalog(int *n) {
	Obj path = dayfilePath(-1);
	DIR *dir = opendir(Tcl_GetString(path));
	DayfileCatalog *c = nheap(10,DayfileCatalog); int nd=0,md=10;
	if (dir) {
		struct dirent *ent;
		while ((ent=readdir(dir))) {
			chars p = ent->d_name,q; Obj epath = oprintf("%{y}s/%s",path,ent->d_name);
			struct stat s; int num = strtol(p,&q,10);
			if (p!=q && *q==0 && stat(Tcl_GetString(epath),&s)==0) {
				if (nd+1>=md) {md = 2*(nd+1); c = reheap(md,DayfileCatalog,c);}
				c[nd].number = num;
				c[nd].mtime = s.st_mtime;
				nd++;
			}
			decr(epath);
		}
		closedir(dir);
	}
	decr(path);
	*n = nd;
	c = reheap(nd==0?1:nd,DayfileCatalog,c);
	return c;
}

#define attachSys(reopen) \
	Obj path; \
	path = dayfilePath(-1); mkdir(Tcl_GetString(path),0700); decr(path); \
	path = dayfilePath(dayfilenumber); if (!(reopen)) unlink(Tcl_GetString(path)); \
	activedayfile = open(Tcl_GetString(path),O_RDWR|O_CREAT,0600); \
	if (activedayfile<0) { \
		Tcl_Panic("dayfile attach failed: %s: %s",Tcl_GetString(path),strerror(errno)); \
	} \
	decr(path);
#define attachedSys (activedayfile>=0)
#define detachSys \
	if (activedayfile>=0) { \
		close(activedayfile); \
		activedayfile = -1; \
		announcedpid = -1; \
	}

		
 
section    top

static void announceSys(void) {
	if (activedayfile<0) dayfileAttach(-1,-1);
	if (announcedpid!=getpid()) {
		int i;
		<Timestamp current date>
		announcedpid = getpid();
		for (i=1; i<=m; i++) buffer[i].avail = BufferSize;
		dayfile(0,"Dayfile start: %s %s parent=%h.",yyyymmdd,argv0,getppid());
	}
}

static void outputMessage(chars b,int o,int n,int m) {
	if (flock(activedayfile,LOCK_EX)<0) {
		Tcl_Panic("dayfile write lock failed: %s",strerror(errno));
	}
	lseek(activedayfile,0,SEEK_END);
	if (m==0 || o+n<=m) {
		write(activedayfile,b+o,n);
	}else {
		write(activedayfile,b+o,m-o);
		write(activedayfile,b,n-(m-o));
	}
	flock(activedayfile,LOCK_UN);
}

		
   
   

Reading a Dayfile

   
top

case o_dump:
	if (N==2) {
		wyrm_dayfileDump(chstdout);
		Tcl_ResetResult(intr);
		return TCL_OK;
	}else if (N==3) {
		int mood; Tcl_Channel channel = Tcl_GetChannel(intr,Tcl_GetString(P[2]),&mood);
		if (!channel) return rprintf(intr,"%!not a channel: %{y}s",TCL_ERROR,P[2]);
		if (!(mood&TCL_WRITABLE)) return rprintf(intr,"%!not a writable channel: %{y}s",TCL_ERROR,P[2]);
		wyrm_dayfileDump(channel);
		Tcl_ResetResult(intr);
		return TCL_OK;
	}else {
		Tcl_WrongNumArgs(intr,2,P0,"[channel]");
		return TCL_ERROR;
	}

		
 
section    top

void wyrm_dayfileDump(Tcl_Channel channel) {
	<Lock access to dayfile data>
	char diskBuffer[BufferSize]; int size; int c = 0; int i;
	for (i=1; i<=m; i++) {
		while (buffer[i].avail<BufferSize) {
			<Extract a message from buffer i>
			outputMessage(buffer[i].p,buffer[i].out+1,n-1,BufferSize);
			buffer[i].out = o; buffer[i].avail += n;
		}
	}
	if (!beginReading()) return;
	while ((size=blockRead(diskBuffer))>0) {
		int o = 0,n;
		while (size>0) {
			chars nl = memchr(diskBuffer+o,'\n',size);
			if (nl) n = nl-(diskBuffer+o)+1; else n = size;
			c = displayMessage(channel,diskBuffer,o,n,0,c);
			o += n; size -= n;
		}
	}
	endReading();
	<Unlock access to dayfile data>
}

		
 
section    top

static bool beginReading(void) {
	if (activedayfile<0) return false;
	if (flock(activedayfile,LOCK_SH)<0) {
		Tcl_Panic("dayfile read lock failed: %s",strerror(errno));
	}
	lseek(activedayfile,0,SEEK_SET);
	return true;
}

static int blockRead(chars buffer) {
	return read(activedayfile,buffer,BufferSize);
}

static void endReading(void) {
	flock(activedayfile,LOCK_UN);
}

		
 
section    top

case o_alert:
	if (N==2) {
		bool alert; chars line = wyrm_dayfileStatus(&alert);
		Obj E[2];
		E[0] = Tcl_NewStringObj(line,-1);
		E[1] = Tcl_NewBooleanObj(alert);
		Tcl_SetObjResult(intr,Tcl_NewListObj(2,E));
		dispose(line);
		return TCL_OK;
	}else {
		Tcl_WrongNumArgs(intr,2,P0,0);
		return TCL_ERROR;
	}

		
 
section    top

chars wyrm_dayfileStatus(bool *alert) {
	<Lock access to dayfile data>
	chars r;
	if (line[1][0]) {
		if (alert) *alert = true;
		r = line[1];
	}else {
		if (alert) *alert = false;
		r = line[0];
	}
	r = strcpy(nheap(strlen(r)+1,char),r);
	<Unlock access to dayfile data>
	return r;
}

		
 
section    top

void wyrm_dayfileDump(Tcl_Channel channel);
chars wyrm_dayfileStatus(bool *alert);

		
   
   

Tk Widget Interface

   
top
 
section    top

static Intr wishIntr = 0;
static bool anyStatusWidget = false;
static bool anyLogWidget = false;

		
 
section    top

if (!wishIntr) {
	wishIntr = intr;
	Tcl_Preserve(wishIntr);
}

		
 
section    top

if (wishIntr && Tcl_InterpDeleted(wishIntr)) {
	Tcl_Release(wishIntr);
	wishIntr = 0;
}
if (!wishIntr) {
	anyStatusWidget = false;
	anyLogWidget = false;
}

		
 
section    top

static void releaseCapturedInterpretter(ClientData clientData) {
	if (wishIntr) {
		Tcl_Release(wishIntr);
		wishIntr = 0;
	}
	anyStatusWidget = false;
	anyLogWidget = false;
}

		
 
section    top

static void releaseCapturedInterpretter(ClientData clientData);

		
 
section    top

<Verify the interpretter is still available>
else if (l==1 || line[1][0]==0) {
	Obj E[3],cmd;
	E[0] = Tcl_NewStringObj("::wyrm::wif::dayfile::statusUpdate",-1);
	E[1] = Tcl_NewStringObj(line[0],-1);
	E[2] = Tcl_NewStringObj(line[1],-1);
	cmd = incr(Tcl_NewListObj(3,E));
	Tcl_EvalObjEx(wishIntr,cmd,TCL_EVAL_GLOBAL|TCL_EVAL_DIRECT);
	decr(cmd);
}

		
 
section    top

"proc statusUpdate {message1 message2} {\n",
	"variable statuswidgets\n",
	"variable heartthrob\n",
	"catch {\n",
		"after cancel $heartthrob\n",
	"}\n",
	"set heartthrob 0\n",
	"set W {}\n",
	"foreach w  $statuswidgets {\n",
		"if {[winfo exists $w]} {\n",
			"lappend W $w\n",
			"if {[string length $message2]} {\n",
				"$w configure -text $message2\n",
			"} else {\n",
				"$w configure -text $message1 -foreground black\n",
			"}\n",
		"}\n",
	"}\n",
	"if {[string length $message2]} {statusThrob 0}\n",
	"set statuswidgets $W\n",
"}\n",

"proc statusThrob n {\n",
	"variable statuswidgets\n",
	"variable heartthrob\n",
	"set W {}\n",
	"foreach w  $statuswidgets {\n",
		"if {[winfo exists $w]} {\n",
			"lappend W $w\n",
			"$w configure -foreground [lindex {\n",
					"#FF8000\n",
					"#FF7000\n",
					"#FF4000\n",
					"#FF1000\n",
					"#FF0000\n",
					"#FF1000\n",
					"#FF4000\n",
					"#FF7000\n",
			"} $n]\n",
		"}\n",
	"}\n",
	"set statuswidgets $W\n",
	"set n [expr {($n+1)&7}]\n",
	"set heartthrob [after 100 ::wyrm::wif::dayfile::statusThrob $n]\n",
"}\n",

		
 
section    top
 
section    top

case o_status:
	if (N>=3) {
		wyrm_dayfileStatusWidget(P[2],N-3,P+3);
		return TCL_OK;
	}else {
		Tcl_WrongNumArgs(intr,2,P0,"widget ...");
		return TCL_ERROR;
	}

		
 
section    top

void wyrm_dayfileStatusWidget(Obj widget,int N,Obj *P) {
	<Lock access to dayfile data>
	<Verify the interpretter is still available>
	else {
		Obj E[5],cmd;
		E[0] = Tcl_NewStringObj("::wyrm::wif::dayfile::statusCreate",-1);
		E[1] = widget;
		E[2] = Tcl_NewStringObj(line[0],-1);
		E[3] = Tcl_NewStringObj(line[1],-1);
		E[4] = Tcl_NewListObj(N,P);
		cmd = incr(Tcl_NewListObj(5,E));
		Tcl_EvalObjEx(wishIntr,cmd,TCL_EVAL_GLOBAL|TCL_EVAL_DIRECT);
		decr(cmd);
	}
	<Unlock access to dayfile data>
}

		
 
section    top

"variable statuswidgets {}\n",
"variable heartthrob 0\n",

"proc statusCreate {w message1 message2 argv} {\n",
	"variable statuswidgets\n",
	"if {[string length $message2]} {\n",
		"set message $message2\n",
	"} else {\n",
		"set message $message1\n",
	"}\n",
	"label $w -text $message -foreground black -anchor w -width 80\n",
	"if {[llength $argv]} {eval [list $w] $argv}\n",
	"lappend statuswidgets $w\n",
	"return $w\n",
"}\n",

		
 
section    top

<Verify the interpretter is still available>
else {
	Obj E[2],cmd;
	if (Tcl_GetCharLength(r)>91) {
		Tcl_SetObjLength(r,90);
		Tcl_AppendToObj(r,"\n",1);
	}
	E[0] = Tcl_NewStringObj("::wyrm::wif::dayfile::logUpdatee",-1);
	E[1] = r;
	cmd = incr(Tcl_NewListObj(3,E));
	Tcl_EvalObjEx(wishIntr,cmd,TCL_EVAL_GLOBAL|TCL_EVAL_DIRECT);
	decr(cmd);
}

		
 
section    top

"proc logUpdate {message} {\n",
	"variable logwidgets\n",
	"set W {}\n",
	"foreach w  $logwidgets {\n",
		"if {[winfo exists $w]} {\n",
			"lappend W $w\n",
			"set length [lindex [split [$w index end] .] 0]\n",
			"set height [$w cget -height]\n",
			"$w configure -state normal\n",
			"if {$length>=$height} {\n",
				"$w delete 1.0 [expr {$length-$height}].0\n",
			"}\n",
			"$w insert end $message\n",
			"$w configure -state disabled\n",
			"$w see 1.0\n",
		"}\n",
	"}\n",
	"set logwidgets $W\n",
"}\n",

		
 
section    top
 
section    top

case o_log:
	if (N>=3) {
		wyrm_dayfileLogWidget(P[2],N-3,P+3);
		return TCL_OK;
	}else {
		Tcl_WrongNumArgs(intr,2,P0,"widget ...");
		return TCL_ERROR;
	}

		
 
section    top

void wyrm_dayfileLogWidget(Obj widget,int N,Obj *P) {
	<Lock access to dayfile data>
	<Verify the interpretter is still available>
	else {
		Obj E[3],cmd;
		E[0] = Tcl_NewStringObj("::wyrm::wif::dayfile::logCreate",-1);
		E[1] = widget;
		E[2] = Tcl_NewListObj(N,P);
		cmd = incr(Tcl_NewListObj(3,E));
		Tcl_EvalObjEx(wishIntr,cmd,TCL_EVAL_GLOBAL|TCL_EVAL_DIRECT);
		decr(cmd);
	}
	<Unlock access to dayfile data>
}

		
 
section    top

"variable logwidgets {}\n",

"proc logCreate {w argv} {\n",
	"variable logwidgets\n",
	"text $w -width 90 -state disabled -wrap none\n",
	"if {[llength $argv]} {eval [list $w] $argv}\n",
	"lappend logwidgets $w\n",
	"return $w\n",
"}\n",

		
 
section    top

void wyrm_dayfileStatusWidget(Obj widget,int N,Obj *P);
void wyrm_dayfileLogWidget(Obj widget,int N,Obj *P);

		
   
   

Console/Stdout Interface