From: bm@shadow.columbia.edu (Blair MacIntyre)
Subject: Re: .plan pipe to backfinger.
Reply-To: Blair MacIntyre <bm@cs.columbia.edu>
Organization: Columbia University
Date: Tue, 26 Jan 1993 03:12:01 GMT

>>>>> On 25 Jan 1993 22:12:36 GMT, gezelter@neon.cchem.berkeley.edu (Dan
>>>>> Gezelter) said:

dan> Here's the latest version of the .plan pipe program that I've
dan> inherited from numerous other people.  

I have a similar .plan pipe program (based on a simple version by Rich
Salz which was modified and given to me by my old officemate Hamish
MacDonald, which I then hacked to death) which uses TCL to parse a
.planinit file in the users home dir and execute a certain function
within it to create the output for the .plan

There is no documentation, but it seems fairly clean.  Like Dan, I
haven't used this in a while in the FIFO version, since we are NFS'd
like crazy here and pipes don't dig NFS access.  However, a compile time
switch allows it to become a program which rewrites your .plan every N
seconds (which is what I currently use.)  

If anyone wants to play with it, I make no guarantees.   There are two
sample .planinit files in here, which you should be able to get the
right idea from.  And, of course, you need to grab a copy of TCL from
somewhere.  Nothing that's a problem for a hacker ...

It currently compiles to the non-FIFO version, so the trivial change to
get it to compile the FIFO version is left as an exercise to the reader.

Have fun.   If you do anything cool with it, let me know!  (like
figuring out how to do a complete ID of people fingering you)

----snippity snip----
: This is a shar archive.  Extract with sh, not csh.
: The rest of this file will extract: 
:
:	.planinit
:	.planinit2
:	Makefile
:	plan_proto.h
:	plantcl.c
:	tcl_cmds.c
:	tcl_cmds.h
:	uniqueproc.c
:
echo x - .planinit
sed 's/^X//' > .planinit << '//go.sysin dd *'
Xproc time12 hour24 {
X	if {($hour24 % 12) == 0} \
X	    {return [format 12]} \
X	else \
X	    {return [expr {$hour24 % 12}]}
X}
X
Xproc timeampm hour24 {
X	if {($hour24 < 12) || ($hour24 == 24)} \
X	    {return am} \
X	else \
X	    {return pm}
X}
X
Xproc timestr hour24 {
X	set ampm [timeampm $hour24]
X	case $hour24 in \
X	    {0 24} {return [format midnight]} \
X	    {12} {return [format noon]} \
X	    default {return [format "%d o'clock %s" [time12 $hour24] $ampm]} 
X}
X
Xproc daysuffix monthday {
X	case $monthday in \
X	    {1 21 31} {return st} \
X	    {2 22 32} {return nd} \
X	    {3 23 33} {return rd} \
X	    default {return th}
X}
X
Xproc getload {} {
X	global _load _load1 _load5 _load15
X    
X	scan [exec loadaverage] "%*d %*d %*d %f %f %f" _load1 _load5 _load15
X
X	set _load [format "%.2f (%.2f, %.2f)" $_load1 $_load5 $_load15]
X
X	set _load1  [format "%.2f" $_load1]
X	set _load5  [format "%.2f" $_load5]
X	set _load15 [format "%.2f" $_load15]
X}
X
Xproc timecomment {hour24 minute} { 
X	set hour12 [time12 $hour24]
X	set ampm [timeampm $hour24]
X
X	proc ecomment {epsilon} {
X		case $epsilon in \
X		    {0 1} {return "almost "} \
X		    {2 4} {return "around "} \
X		    {3}   {return ""}
X	}
X
X	set fiveminute [expr {(($minute + 3) / 5) * 5}]
X	set e [expr {($minute + 3) % 5}]
X
X	# first do the 15 minute intervals, then do the rest
X
X	if {$fiveminute == 0} \
X	    {return [format "%s%s" [ecomment $e] [timestr $hour24]]} \
X	else { if {$fiveminute == 15} \
X	    {return [format "%squarter after %s (%s)" [ecomment $e] $hour12 $ampm]} \
X	else { if {$fiveminute == 30} \
X 	    {return [format "%shalf past %s (%s)" [ecomment $e] $hour12 $ampm]} \
X	else { if {$fiveminute == 45} \
X	    {return [format "%squarter to %s (%s)" [ecomment $e] \
X		[expr {($hour24 % 12) + 1}] $ampm]} \
X	else { if {$fiveminute == 60} \
X	    {return [format "%s%s" [ecomment $e] \
X		[timestr [expr {$hour24 + 1}]]]} \
X	else {if {$minute < 30} \
X	    {return [format "%s%d after %s (%s)" [ecomment $e] \
X		$fiveminute $hour12 $ampm]} \
X	    {return [format "%s%d to %s (%s)" [ecomment $e] \
X		[expr {60 - $fiveminute}] [expr {($hour24 % 12) + 1}] $ampm]} \
X	}}}}}
X}
X
Xset fingercount 0
X
Xproc main {} {
X    global _year _month _day _hour12 _hour24 _minute _second fingercount\
X	   _weekdaystring _monthstring
X
X    set fingercount [expr {$fingercount + 1}]
X
X    done
X
X#    echo -n Thesis: 
X#    echo [fill 8 75 -h Computer Aided Colour Selection for People Who Have 
X#	Better Things to Do With Their Time Than Play With Colour.]
X
X     # **** OLD Code, kept around for the heck of it, in case I reuse it ***
X
X    #set host [toupper [getenv SHORTHOST]]
X    #getload
X
X    #echo -n Commentary:\ 
X    #fill 12 75 -h It's $comment here in Waterloo. \  etc etc etc
X
X    #set netstat [exec grep "128.84.237" < [exec netstat -n]]
X    #set netstat [exec netstat -n]
X    #echo $netstat
X    #set cornell [string match "* 128.84.237.*" $netstat]
X    #echo $cornell
X    #if {$cornell == 1} {echo "(Katy, you deadbeat, get to work!  Like me!)"}
X    #if {$cornell >= 0} {echo $cornell [index $netstat $cornell]}
X    
X    #echo \nFortune #$fingercount:
X    #echo [exec fortune -s]
X
X    set comment [format "Local Time: %s" [timecomment $_hour24 $_minute]]
X    echo -n $comment
X    set tabchars ""
X    
X    set commentlength [length $comment chars]
X    set tabs [expr {(38 - $commentlength) / 8 + 1}]
X    #echo $commentlength $tabs
X    for {} {$tabs > 0} {set tabs [expr {$tabs - 1}]} \
X	    {set tabchars [format "%s\t" $tabchars]}
X    
X    echo [format "%s%s" $tabchars Date:] $_weekdaystring, the \
X	    [format "%d%s" $_day [daysuffix $_day]] of $_monthstring
X    
X#    set _sked [exec month -B1]
X#    if {[length $_sked] > 0} \
X#	    { set temp1 [range $_sked 2 end] 
X#	      set length1 [expr {[length $temp1 chars] - 3}]
X#	      echo Schedule for today: \n "  " [range $temp1 0 $length1 chars] 
X#	    } \
X#	    { 
X#	      echo Nothing scheduled for today. 
X#	    }
X    
X#    echo \nAnswer this skill testing question and win valuable prizes!
X#    echo \t\t\t ( $fingercount * \
X#        [expr {(($_day * ($_second + 1)) * ($fingercount)) % 137 + 1}] + \
X#	[expr {$_year % ($_second + 10) + 1}] ) / \
X#	[expr {($_hour24 * $fingercount * ($_second + 1)) % 23 + 1}] 
X
X#     echo "\nCurrent State of the Thesis:"
X#     echo [exec cat /u/bmacinty/.thesistoc]
X#	echo " "
X#	echo [fill 4 74 As my above finger info shows, I am not longer
X#		gracing the halls of the DC.  I am now in the Big Apple,
X#		doing a PhD at Columbia University.  My address is]
X#	echo "\t435 W 119th St. Apt. 4B\n\tNew York, NY, 10027"
X#	echo "\n\tPhone: 212-932-1911"
X#	echo [fill 4 74 If you are in new York, phone me and we can get
X#		together.]
X#	echo " "
X#
X#	echo
X#	echo [fill 0 65 I'll be at Cornell for the weekend.  If you 
X#	need me, I'll be at (607) 277-1788.  Of course, I'll log on
X#	as much as possible.]
X
X	echo \nRandom Space Filling Quotation:
X	echo [exec fortune -s]
X
X	set cc [exec randterm dummyarg]
X	# this shit to get around the ld.so: warning!
X        if {([string match "ld.so:" [index $cc 0]]) == 1} {
X	    set cc [range $cc 9 end]
X	}
X	set colour1 [index $cc 3]
X	set colour1v [format "(%d,%d,%d)"
X		[index $cc 0] [index $cc 1] [index $cc 2]]
X	set colour1f [index $cc 4]
X
X	set colour2 [index $cc 8]
X	set colour2v [format "(%d,%d,%d)"
X		[index $cc 5] [index $cc 6] [index $cc 7]]
X	set colour2f [index $cc 9]
X
X	echo "Scientifically Selected Colour Scheme for the Aesthetically Challenged:"
X	echo [fill 4 74 $colour1 $colour1v text on a field of 
X		$colour2 $colour2v. This gives a luminance contrast of about 
X		[index $cc 10]% on a standard NTSC display.]
X	echo Try it out like this:
X	echo [format "    xterm -fg %s -bg %s" $colour1f $colour2f ]
X}
X
Xproc done {} {
X    echo [ fill 2 75 I'm a Phd student in graphics, advised by Steven \
X	Feiner.  This term's fun includes TAing CS4160, Computer Graphics. \
X	Office hours are Tuesday and Wednesday, 11 till 12.  I'm also \
X	thinking of taking the software qual.\n]
X
X    echo [ fill 2 75 I live in 1122D Fairchild right now, phone x43614.  \
X	The phone number in the graphics lab x48699.\n]
X
X    echo [ fill 2 75 My home phone number is 222-5315, but don't bother \
X	trying to reach me there, since I'm usually not at home.  \
X	Email is your best bet.\n]
X} 
//go.sysin dd *
echo x - .planinit2
sed 's/^X//' > .planinit2 << '//go.sysin dd *'
Xproc time12 hour24 {
X	if {($hour24 % 12) == 0} \
X	    {return [format 12]} \
X	else \
X	    {return [expr {$hour24 % 12}]}
X}
X
Xproc timestr hour24 {
X	case $hour24 in \
X	    {0 24} {return [format midnight]} \
X	    {12} {return [format noon]} \
X	    default {return [format "%d o'clock" [time12 $hour24]]} 
X}
X
Xproc daysuffix monthday {
X	case {$monthday} in \
X	    {1 21 31} {return st} \
X	    {2 22 32} {return nd} \
X	    {3 23 33} {return rd} \
X	    default {return th}
X}
X
Xproc getload {} {
X	global _load _load1 _load5 _load15
X    
X	scan [exec loadaverage] "%*d %*d %*d %f %f %f" _load1 _load5 _load15
X
X	set _load [format "%.2f (%.2f, %.2f)" $_load1 $_load5 $_load15]
X
X	set _load1  [format "%.2f" $_load1]
X	set _load5  [format "%.2f" $_load5]
X	set _load15 [format "%.2f" $_load15]
X}
X
Xproc timecomment {hour24 minute} { 
X	set hour12 [time12 $hour24]
X
X	proc ecomment {epsilon} {
X		case $epsilon in \
X		    {0 1} {return "almost "} \
X		    {2 4} {return "around "} \
X		    {3}   {return ""}
X	}
X
X	set fiveminute [expr {(($minute + 3) / 5) * 5}]
X	set e [expr {($minute + 3) % 5}]
X
X	# first do the 15 minute intervals, then do the rest
X
X	if {$fiveminute == 0} \
X	    {return [format "%s%s" [ecomment $e] [timestr $hour24]]} \
X	else { if {$fiveminute == 15} \
X	    {return [format "%squarter after %s" [ecomment $e] $hour12]} \
X	else { if {$fiveminute == 30} \
X 	    {return [format "%shalf past %s" [ecomment $e] $hour12]} \
X	else { if {$fiveminute == 45} \
X	    {return [format "%squarter to %s" [ecomment $e] \
X		[expr {($hour24 % 12) + 1}]]} \
X	else { if {$fiveminute == 60} \
X	    {return [format "%s%s" [ecomment $e] \
X		[timestr [expr {$hour24 + 1}]]]} \
X	else {if {$minute < 30} \
X	    {return [format "%s%d after %s" [ecomment $e] \
X		$fiveminute $hour12]} \
X	    {return [format "%s%d to %s" [ecomment $e] \
X		[expr {60 - $fiveminute}] [expr {($hour24 % 12) + 1}]]} \
X	}}}}}
X}
X
Xproc main {} {
X    global _year _month _day _hour12 _hour24 _minute _second _fingercount \
X	   _weekdaystring _monthstring
X    echo To actually do some work this term ... and keep my plan up to date!
X    echo Skill Testing Question: \
X    [expr {$_year % $_fingercount + 1}] * \
X    [expr {($_day * ($_second + 1) * ($_month + 10) * ($_minute + 1)) % $_fingercount + 1}] + \
X    [expr {$_year % ($_second + 1) + 1}] / \
X    [expr {(($_hour24 * $_fingercount) % $_second) + 1}]
X    
X    #for {set i 0} {$i < 60} {set i [expr {$i+1}]} {echo [timecomment 4 $i]}
X    #set comment [timecomment $_hour24 $_minute]
X    #set host [toupper [getenv SHORTHOST]]
X    #getload
X    
X    #echo -n Commentary:\ 
X    #fill 12 75 -h It's $comment here in Waterloo. \  etc etc etc
X    
X    set comment [format "Local Time: %s" [timecomment $_hour24 $_minute]]
X    echo -n $comment
X    set tabchars ""
X    
X    set fred [length $comment chars]
X    set tabs [expr {(38 - $fred) / 8 + 1}]
X    #echo $fred $tabs
X    for {} {$tabs > 0} {set tabs [expr {$tabs - 1}]} \
X    {set tabchars [format "%s\t" $tabchars]}
X    
X    echo [format "%s%s" $tabchars Date:] $_weekdaystring, the \
X    [format "%d%s" $_day [daysuffix $_day]] of $_monthstring
X    
X    set _sked [exec month -B1]
X    if {[length $_sked] > 0} \
X    { set temp1 [range $_sked 2 end] 
X    set length1 [expr {[length $temp1 chars] - 3}]
X    echo Schedule for today: \n "  " [range $temp1 0 $length1 chars] 
X    } \
X    { echo Nothing scheduled for today. }
X    
X    #set netstat [exec grep "128.84.237" < [exec netstat -n]]
X    set netstat [exec netstat -n]
X    #echo $netstat
X    set cornell [string match "* 128.84.237.*" $netstat]
X    #echo $cornell
X    if {$cornell == 1} {echo "(Katy, you deadbeat, get to work!  Like me!)"}
X    #if {$cornell >= 0} {echo $cornell [index $netstat $cornell]}
X}
//go.sysin dd *
echo x - Makefile
sed 's/^X//' > Makefile << '//go.sysin dd *'
XTCLOBJS = plantcl.o tcl_cmds.o 
X#uniqueproc.o
XCC = gcc
XCFLAGS = -O -g -I${TCLINCLUDE} -Wall
XTCLLIBSDIR = /u/foria/bm/lib
XTCLINCLUDE = /u/foria/bm/include
X
Xtclplan : ${TCLOBJS}
X	gcc -o tclplan ${CFLAGS} ${TCLOBJS} -L${TCLLIBSDIR} -ltcl
X
Xlexplan : ${OLDOBJS}
X	gcc -o lexplan ${CFLAGS} ${OLDOBJS}
X
Xplanmain.o : plan.h
X
Xplantcl.o : plantcl.c tcl_cmds.h plan_proto.h ${TCLINCLUDE}/tcl.h
X
Xuniqueproc.o : uniqueproc.c
X
Xtcl_cmds.o : tcl_cmds.c ${TCLINCLUDE}/tcl.h
X
Xlex.yy.o : lex.yy.c plan.h 
X
Xlex.yy.c : plan.l
X	flex -i plan.l
X
Xclean:
X	rm -f ${TCLOBJS} ${OLDOBJS} tclplan lexplan
//go.sysin dd *
echo x - plan_proto.h
sed 's/^X//' > plan_proto.h << '//go.sysin dd *'
X#include <sys/types.h>
X#include <sys/stat.h>
X#include <sys/time.h>
X#include <sys/file.h>
X#include <sgtty.h>
X#include <signal.h>
X#include <errno.h>
X#include <string.h>
X#include <ctype.h>
X#include "tcl.h"
X
Xextern int puts();
Xextern int stat(char*, struct stat*);
X
Xextern char     getopt(int, char**, const char*);
Xextern char	*getenv(const char*);
Xextern int      chdir(const char*);
Xextern int      unlink(const char*);
Xextern int      mknod(const char*, int, int);
Xextern int      access(const char*, int);
Xextern int      select(int, fd_set*,fd_set*,fd_set*,struct timeval*);
Xextern int      fprintf(FILE*, char*,...);
X#if !defined(sun)
Xextern int      umask(int);
X#endif SUN4_1
Xextern int      fclose(FILE*);
Xextern int      getppid(void), getpid(void);
Xextern int      getpgrp(int);
Xextern FILE     *popen(const char*, const char*);
Xextern int      pclose(FILE*), fscanf(FILE*,const char*,...);
Xextern int      fork(void);
Xextern int      setpgrp(int,int);
Xextern int      ioctl(int,int,void*);
Xextern int      close(int);
Xextern void     perror(const char*);
Xextern void     fputs(const char*, FILE*);
Xextern void     sleep(unsigned int);
Xextern void     bzero(void*,int); 
Xextern int      fread(void*,unsigned,unsigned,FILE*);
Xextern int      dup2(int,int);
Xextern int      open(char*, int,int);
X
Xextern int      unique_proc();
//go.sysin dd *
echo x - plantcl.c
sed 's/^X//' > plantcl.c << '//go.sysin dd *'
X/*                               -*- Mode: c -*- 
X * plantcl.c -- the mainline for my tcl-based dynamic plan program
X * Author          : Blair MacIntyre
X * Last Modified By: Blair MacIntyre
X * Last Modified On: Wed Apr 15 13:49:59 1992
X * Update Count    : 446
X * Status          : Version 3.
X * 
X * $Source: /u/foria/bm/src/plan/RCS/plantcl.c,v $
X * $Date: 1992/04/15 17:50:44 $
X * $Author: bm $
X * $Revision: 1.12 $
X */
X
X/*
X**  A process to create dynamic .plan files.  Two programs in one:
X**  1) Creates a fifo, waits for someone to connect to it.  Redo .plan info
X**     each time.
X**  2) Runs in the background and rewrites the plan file evey N seconds.
X**     Needed for NFS environments, since fifo's can't be read over NFS.
X**
X** Original version:  Rich Salz
X** Prints out the time, and a guess-timate of the temperature,
X** based on the day of the year and the time.
X**
X** Major Revision:  (Blair MacIntyre, Oct 1, 1990)
X** Accepts a configuration file
X**
X** Major Revision:  (Blair MacIntyre, April 11, 1992)
X** Doesn't have to use a fifo.  Seems like a step backwards toward the dark
X** ages, but they use NFS for everything here at Columbia.
X*/
X
X#include <stdio.h>
X#include <stdlib.h>
X#include <sys/file.h>
X#include "plan_proto.h"
X#include "tcl_cmds.h"
X
X/* beats the heck out of me why these aren't prototyped */
Xint rename(char*, char*);
Xint flock(int, int);
X
X/* default FIFO name and initialization files */
X#define PLAN ".plan"
X#define PLANINIT ".planinit"
X
X/* various defines for variable contents */
X#define TIME_DEFAULT "%2d:%02d:%02d"
X#define DATE_DEFAULT "%d %.3s %d"
X#define INFO_DEFAULT "proc main {} {\n\
X	global _time _date\n\
X	echo \"I've got the time ....\"\n\
X        echo \"Time:       $_time \t\t\tDate: $_date\"\n\
X    }\n"
X#define INFO_ERROR "proc main {} {\n\
X	global _time _date errorInfo\n\
X	echo \"      Plan init file contained an error near:\"\n\
X        echo $errorInfo\n\
X	echo \"Time:       $_time \t\t\tDate: $_date\"\n\
X    }\n"
X#define INFO_READ_ERROR "proc main {} {\n\
X	global _time _date\n\
X	echo \"      Error Reading plan init file.\"\n\
X	echo \"Time:       $_time \t\t\tDate: $_date\"\n\
X    }\n"
X#define INFO_NOMAIN "proc main {} {\n\
X	global _time _date\n\
X	echo \"      Plan init file did not define a 'main' procedure.\"\n\
X	echo \"Time:       $_time \t\t\tDate: $_date\"\n\
X    }\n"
X
X#define INFO_NO_DONE "proc done {} {\n\
X        echo \"Plan?  Plan?  We don't need no stinking plan!\"\
X    }\n"
X
X#ifndef FIFO
X/* the default amount of time to sleep between plan file recreates (2 minues)*/
X#define DEFAULT_SLEEP_TIME (120)
X#endif
X
X/* global variables */
Xstatic char *fifo = NULL;
Xstatic char  lockname[255];
Xstatic char* tempfile;
Xstatic char  lockfd;
X
XFILE *FingerOutput;		/* needed in tcl_cmds.c */
X
Xstatic Tcl_Interp *my_interp;
Xstatic void Setup();
Xstatic Tcl_Interp *GetConfig(char *, int);
Xstatic void SetState(Tcl_Interp *interp);
X
X/***********************************************************************
X * general Exit routine 
X ***********************************************************************/
Xint Exit(void)
X{
X    int fd;
X    int         result;
X
X    /* Close and remove the current FingerOutput tempfile */
X    (void)fclose(FingerOutput);
X    if (unlink(tempfile) < 0 && errno != ENOENT) {
X        perror("Can't unlink tempfile");
X    }
X
X    /* Remove the lockfile */
X    close(lockfd);
X    if (unlink(lockname) < 0 && errno != ENOENT) {
X        perror("Can't unlink lockfile");
X    }
X
X#ifdef FIFO
X    /* Remove the FIFO. */
X    if (unlink(fifo) < 0 && errno != ENOENT) {
X        perror("Can't unlink");
X	exit(1);
X    }
X#endif
X
X    /* Open the fifo for writing. Attempt to create a simple file, so there
X       is something reasonable left lying around when we are gone */
X    if ((FingerOutput = fopen(fifo, "w")) == NULL) {
X	perror("Can't fopen");
X	exit(1);
X    }
X    fd = fileno(FingerOutput);
X
X    /* execute the TCL "done" procedure to (presumably) dump a final message */
X    SetState(my_interp);
X    result = Tcl_Eval(my_interp, "done", 0, NULL);
X    if (result == TCL_OK) {
X	if (*my_interp->result != 0) {
X	    fprintf(FingerOutput, "TCL Result: %s\n", my_interp->result);
X	}
X    } else {
X	if (result == TCL_ERROR) {
X	    fprintf(FingerOutput, "TCL Error");
X	} else {
X	    fprintf(FingerOutput, "TCL Error %d", result);
X	}
X	if (*my_interp->result != 0) {
X	    fprintf(FingerOutput, ": %s\n", my_interp->result);
X	} else {
X	    fprintf(FingerOutput, "\n");
X	}
X    }
X
X    /* Close it */
X    (void)fclose(FingerOutput);
X
X    exit(0);
X    /*NOTREACHED*/
X    return 0;
X}
X
X/***********************************************************************
X * read the config file into Config_buffer and Tcl_Eval it.
X ***********************************************************************/
XTcl_Interp *GetConfig(char *initfile, int filesize)
X{
X    int count, result;
X    FILE *ConfigFile = fopen(initfile, "r"); 
X    Tcl_Interp *interp = NULL;
X    char *Config_buffer = NULL;
X    
X    /* 
X     * initialize the TCL interpretter and add our custom functions 
X     */
X    interp = Tcl_CreateInterp();
X    Tcl_CreateCommand(interp, "getenv", cmdGetEnv, (ClientData) 0, 0);
X    Tcl_CreateCommand(interp, "echo", cmdEcho, (ClientData) 0, 0);
X    Tcl_CreateCommand(interp, "toupper", cmdToUpper, (ClientData) 0, 0);
X    Tcl_CreateCommand(interp, "tolower", cmdToLower, (ClientData) 0, 0);
X    Tcl_CreateCommand(interp, "fill", cmdFill, (ClientData) 0, 0);
X    
X    /* 
X     * we must do this so that our variables are always defined, even the
X     * first time the init file is executed
X     */
X    SetState(interp);
X
X    /* use default plan */
X    if (!ConfigFile) {
X	(void)Tcl_Eval(interp, INFO_DEFAULT, 0, NULL);
X	(void)Tcl_Eval(interp, INFO_NO_DONE, 0, NULL);
X    }
X    else
X    {
X	Config_buffer = malloc(filesize+1);
X	count = fread(Config_buffer, sizeof(char), filesize, ConfigFile);
X	if (count == 0) 
X	{
X	    (void)Tcl_Eval(interp, INFO_READ_ERROR, 0, NULL);
X	    (void)Tcl_Eval(interp, INFO_NO_DONE, 0, NULL);
X	}
X	else
X	{	
X	    Config_buffer[count] = '\0';
X	    result = Tcl_Eval(interp, Config_buffer, 0, NULL);
X	    if (result == TCL_OK) {
X		/* check for main */
X		result = Tcl_Eval(interp, "info procs main", 0, NULL);
X		
X		if (result == TCL_OK && strcmp(interp->result,"main") != 0) {
X		    /* no main procedure defined */
X		    (void)Tcl_Eval(interp, INFO_NOMAIN, 0, NULL);
X		}
X
X		/* check for done */
X		result = Tcl_Eval(interp, "info procs done", 0, NULL);
X		
X		if (result == TCL_OK && strcmp(interp->result,"done") != 0) {
X		    /* no done procedure defined */
X		    (void)Tcl_Eval(interp, INFO_NO_DONE, 0, NULL);
X		}
X	    } else {
X		(void)Tcl_Eval(interp, INFO_ERROR, 0, NULL);
X		(void)Tcl_Eval(interp, INFO_NO_DONE, 0, NULL);
X	    }
X	}
X
X	free(Config_buffer);
X	fclose(ConfigFile);
X	
X    } /* if */
X
X    return interp;
X}
X
X/***********************************************************************
X * This function sets up signal handlers for the HUP and TERM signals.
X * The signal handlers remove the named pipe and exit, rewriting a simple
X * .plan on exit.
X * This function sets up ignoring of INT, PIPE and QUIT signals.
X * It also ensure that the forked process is in the process group of 
X * the parent.
X ***********************************************************************/
Xvoid Setup()
X{
X    int pid,fd;
X
X    /*
X     * Arrange for handling or ignoring of signals
X     */
X    signal( SIGTERM, Exit );
X    signal( SIGPIPE, SIG_IGN );
X    signal( SIGHUP, SIG_IGN );
X    signal( SIGINT, SIG_IGN );
X    signal( SIGQUIT, SIG_IGN );
X
X    /* fork.. the parent dies, the child lives on */
X#ifndef DEBUG
X    switch( fork() )
X    {
X      case -1:			/* fail */
X	/* fork failed */
X	perror( "fork" );
X	Exit();
X	break;
X
X      case 0:			/* child */
X#ifdef FIFO
X	/*
X	 * If the FIFO exists, unlink it.
X	 */
X	if (access(fifo,F_OK) == 0)
X	{
X	    (void)unlink(fifo);
X	}
X
X	(void)umask(0);
X	if (mkfifo(fifo, (mode_t)0644) < 0)
X	{
X	    perror("Can't mknod");
X	    Exit();
X	}
X#else
X	/* see if we can write to the .plan file */
X	if (access(fifo,W_OK))
X	{
X	    if (errno != ENOENT)
X	    {
X		perror("can't write to file");
X		Exit();
X	    }
X	}
X#endif
X	pid = getpid();
X
X	if( setpgrp(0,pid) < 0 ) 
X	{
X	    perror( "setpgrp" );
X	    Exit();
X	}
X
X	/* disassociate controlling terminal */
X	if( (fd = open( "/dev/tty", O_RDONLY, 0 ) ) == -1 ) {
X	    perror( "open /dev/tty" );
X	    Exit();
X	}
X
X	if( ioctl( fd, TIOCNOTTY, 0 ) == -1 ) {
X	    perror( "ioctl" );
X	}
X
X	close( fd );
X
X	/* 
X	 * close the I/O files opened by C and redirect them to /dev/null 
X	 * for TCL.
X	 */
X	close(0); 
X	close(1);
X	close(2);
X
X	if (fd = open("/dev/null", O_RDWR, 0) == -1)
X	{
X	    perror( "open /dev/null");
X	    Exit();
X	}
X
X	if (dup2(fd,0) == -1)
X	{
X	    perror( "dup2 stdin");
X	    Exit();
X	}
X
X	if (dup2(fd,1) == -1)
X	{
X	    perror( "dup2 stdout");
X	    Exit();
X	}
X
X	if (dup2(fd,2) == -1)
X	{
X	    perror( "dup2 stderr");
X	    Exit();
X	}
X
X	return;
X      default:			/* parent */
X	exit(0);
X    }
X#endif
X}
X
X/**********************************************************************
X * month and day variables for setting the TCL variables
X **********************************************************************/
Xchar *months[12] = { "January", "Febuary", "March", "April", "May", "June",
X			 "July", "August", "September", "October", 
X			 "November", "December" };
Xchar *dayofweek[7] = { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday",
X		     "Friday", "Saturday" };
X
X/**********************************************************************
X * the good stuff:  set the TCL variables!
X **********************************************************************/
Xvoid SetState(Tcl_Interp *interp)
X{
X    time_t time(time_t*), curr; 
X    struct tm parts;
X
X    char F[BUFSIZ];		/* temporary buffer.  the name is historical */
X
X    curr = time(0);
X    parts = *localtime(&curr);
X
X#ifdef DEBUG
X#ifdef FIFO
X    fprintf(stderr, "Someone is fingering me!\n");
X#endif
X#endif
X    
X    sprintf( F, TIME_DEFAULT, parts.tm_hour, parts.tm_min, parts.tm_sec);
X    Tcl_SetVar(interp, "_time", F, 1);
X
X    sprintf( F, "%d", parts.tm_hour);
X    Tcl_SetVar(interp, "_hour24", F, 1);
X
X    if (parts.tm_hour == 0)
X	sprintf( F, "12");
X    else if (parts.tm_hour < 13)
X	sprintf( F, "%d", parts.tm_hour);
X    else
X	sprintf( F, "%d", parts.tm_hour - 12);
X    Tcl_SetVar(interp, "_hour12", F, 1);
X
X    sprintf( F, "%d", parts.tm_min);
X    Tcl_SetVar(interp, "_minute", F, 1);
X
X    sprintf( F, "%d", parts.tm_sec);
X    Tcl_SetVar(interp, "_second", F, 1);
X
X    sprintf( F, DATE_DEFAULT, parts.tm_mday, months[parts.tm_mon], 
X	    parts.tm_year + 1900 );
X    Tcl_SetVar(interp, "_date", F, 1);
X
X    sprintf( F, "%d", parts.tm_mday);
X    Tcl_SetVar(interp, "_day", F, 1);
X
X    sprintf( F, "%d", parts.tm_mon + 1); 
X    Tcl_SetVar(interp, "_month", F, 1);
X
X    sprintf( F, "%s", months[parts.tm_mon]);
X    Tcl_SetVar(interp, "_monthstring", F, 1);
X
X    sprintf( F, "%d", parts.tm_year + 1900 );
X    Tcl_SetVar(interp, "_year", F, 1);
X
X    sprintf( F, "%d", parts.tm_wday );
X    Tcl_SetVar(interp, "_weekday", F, 1);
X
X    sprintf( F, "%d", parts.tm_yday );
X    Tcl_SetVar(interp, "_yearday", F, 1);
X
X    sprintf( F, "%s", dayofweek[parts.tm_wday]);
X    Tcl_SetVar(interp, "_weekdaystring", F, 1);
X}
X
X/***********************************************************************
X * the mainline, where the work is done
X ***********************************************************************/
Xvoid 
Xmain(int argc, char *argv[])
X{
X    int		fd;
X#ifdef FIFO
X    fd_set	writers;
X#endif
X    struct stat stat_buf;
X    time_t      mod_time;
X    int         result;
X    int         c;
X    extern char *optarg;
X    char        *initfile = NULL, *directory = NULL;
X#ifndef FIFO
X    int         sleeptime = 0;
X#endif
X    char        tempheader[6];
X
X#ifdef FIFO
X    while ((c = getopt (argc, argv, "f:i:d:")) != EOF)
X#else
X    while ((c = getopt (argc, argv, "f:i:d:s:")) != EOF)
X#endif
X	switch (c) 
X	{
X	  case 'f':
X	    fifo = optarg;
X	    break;
X	  case 'i':
X	    initfile = optarg;
X	    break;
X	  case 'd':
X	    directory = optarg;
X	    break;
X#ifndef FIFO
X	  case 's':
X	    sleeptime = atoi(optarg);
X	    break;
X
X	  case '?':
X	    fprintf(stderr,"usage: %s [-f outputfile] [-i initfile] [-d dir] [-s sleeptime]\n",
X		     argv[0]);
X#else
X	  case '?':
X	    fprintf (stderr,"usage: %s [-f fifofile] [-i initfile] [-d dir]\n",
X		     argv[0]);
X#endif
X	    exit(1);
X	}
X
X    /* use defaults if the options aren't specified */
X    if (!fifo)
X	fifo = PLAN;
X    if (!initfile)
X	initfile = PLANINIT;
X    if (sleeptime == 0)
X	sleeptime = DEFAULT_SLEEP_TIME;
X
X    /* Go to the right directory. */
X    if (!directory && ((directory = getenv("HOME")) == NULL))
X    {
X	fprintf(stderr, "No $HOME.\n");
X	exit(1);
X    }
X
X    if (chdir(directory) < 0) {
X	perror("Can't cd home directory");
X	exit(1);
X    }
X
X    /*
X     * Check for existence of the FIFO, and
X     * that the current "plan" program isn't running.
X     * Setup signal handlers for HUP and TERM signals, 
X     *    to ignore INT and QUIT, and fork
X     */
X    stat(initfile, &stat_buf);
X    mod_time = stat_buf.st_mtime;
X    FingerOutput = stdout;
X
X    /* lock out lockfile */
X    sprintf(lockname, "%s.lock", fifo);
X    if ((lockfd = open(lockname, O_RDONLY | O_CREAT, 0600)) < 0)
X    {
X	perror("Can't open lockfile");
X	exit(1);
X    }
X    if (flock(lockfd, LOCK_EX | LOCK_NB))
X    {
X	fprintf(stderr, "%s already running for %s\n", argv[0], fifo);
X	exit(1);
X    }
X
X#ifndef FIFO
X    /* get a temporary name */
X    strncpy(tempheader, fifo, 5);
X    tempheader[5] = '\0';
X    tempfile = tempnam (".", tempheader);
X#endif
X
X    /* do the initial setup */
X    Setup();
X
X    /*
X     * read in the config file
X     * This initializes the TCL interpreter as well.
X     * If the config file cannot be read, or there are
X     * TCL errors when eval'ing it, a default "main" procedure
X     * is set up.
X     */
X    my_interp = GetConfig(initfile, stat_buf.st_size);
X
X    /* Enter the server loop. */
X    for (;;) {
X
X	/* Open the fifo for writing. */
X#ifdef FIFO
X	if ((FingerOutput = fopen(fifo, "w")) == NULL) {
X#else
X	if ((FingerOutput = fopen(tempfile, "w")) == NULL) {
X#endif
X	    perror("Can't fopen");
X	    Exit();
X	}
X	fd = fileno(FingerOutput);
X
X#ifdef FIFO
X	/* Wait until someone else opens it for reading, so that we can
X	 * write on it. */
X	FD_ZERO(&writers);
X	FD_SET(fd, &writers);
X	if (select(fd + 1, (fd_set *)NULL, &writers, (fd_set *)NULL,
X		(struct timeval *)NULL) < 0) {
X	    if (errno != EINTR)
X		perror("Can't select");
X	    continue;
X	}
X	if (!FD_ISSET(fd, &writers))
X	    /* "Can't happen" */
X	    continue;
X#endif
X	/* if the config file has changed, reread it */
X	stat(initfile, &stat_buf);
X	if (mod_time != stat_buf.st_mtime)
X	{
X	    mod_time = stat_buf.st_mtime;
X	    /* 
X	     * re-initialize the TCL interpreter from the config file.
X	     */
X	    Tcl_DeleteInterp(my_interp);
X	    my_interp = GetConfig(initfile, stat_buf.st_size);
X	}
X
X	/* execute the TCL "main" procedure to (presumably) dump the message */
X	SetState(my_interp);
X	result = Tcl_Eval(my_interp, "main", 0, NULL);
X	if (result == TCL_OK) {
X	    if (*my_interp->result != 0) {
X		fprintf(FingerOutput, "TCL Result: %s\n", my_interp->result);
X	    }
X	} else {
X	    if (result == TCL_ERROR) {
X		fprintf(FingerOutput, "TCL Error");
X	    } else {
X		fprintf(FingerOutput, "TCL Error %d", result);
X	    }
X	    if (*my_interp->result != 0) {
X		fprintf(FingerOutput, ": %s\n", my_interp->result);
X	    } else {
X		fprintf(FingerOutput, "\n");
X	    }
X	}
X
X	/* Close it so they stop reading, and pause to make sure of it. */
X	(void)fclose(FingerOutput);
X
X#ifndef FIFO
X	/* move the temp file to the fifo */
X	if (rename(tempfile, fifo))
X	{
X	    perror("error renaming tempfile to output file");
X	    Exit();
X	}
X#endif
X#ifdef FIFO
X	(void)sleep(5);
X#else
X	(void)sleep(sleeptime);
X#endif
X    }
X
X    /* NOTREACHED */
X}
X
X/* Local Variables: */
X/* compile-command: "make" */
X/* End: */
//go.sysin dd *
echo x - tcl_cmds.c
sed 's/^X//' > tcl_cmds.c << '//go.sysin dd *'
X/*
X**  A process to create dynamic .plan files.  Creates a fifo, waits for
X**  someone to connect to it.  Optional first argument is the directory
X**  to chdir(2) to, as in "plan ~rsalz &"; default is value of $HOME.
X**
X** Original version:
X** Prints out the time, and a guess-timate of the temperature,
X** based on the day of the year and the time.
X**
X** Major Revision:  (Blair MacIntyre, Oct 1, 1990)
X** Accepts a configuration file
X**
X** Major Revision 2:  (Blair MacIntyre, Nov 1, 1990)
X** Uses TCL to interpret the configuration file
X*/
X
X#include <stdio.h>
X#include <stdlib.h>
X#include <string.h> 
X#include <ctype.h>
X#include "tcl.h"
X
Xextern char	*getenv(const char*);
Xextern int      fputc(int, FILE*);
X#if defined(ultrix) | defined(sun)
Xextern int      toupper(int);
Xextern int      tolower(int);
X#endif
Xextern int      fprintf(FILE*, char*,...);
X
Xextern FILE *FingerOutput;
X
Xint
XcmdToUpper( clientData, interp, argc, argv )
X    char *clientData;
X    Tcl_Interp *interp;
X    int argc;
X    char **argv;
X{
X    int j;
X    char *buffer;
X
X    if (argc != 2) {
X	sprintf(interp->result,
X		"incorrect number of parameters to \"%s\" command", argv[0]);
X	return (TCL_ERROR);
X    }
X
X    if ((buffer = malloc(strlen(argv[1]) + 1)) == NULL) {
X	sprintf(interp->result,
X		"out of memory while executing \"%s\" command", argv[0]);
X	return (TCL_ERROR);
X    }
X    
X    for (j = 0; argv[1][j] != NULL; j++ )
X#if defined(ultrix)
X	buffer[j] = toupper(argv[1][j]);
X#else
X        buffer[j] = islower( argv[1][j] ) ? toupper( argv[1][j] ) : argv[1][j];
X#endif
X    buffer[j] = 0;
X
X    Tcl_Return(interp, buffer, TCL_DYNAMIC);
X
X    return (TCL_OK);
X}
X
Xint
XcmdToLower( clientData, interp, argc, argv )
X    char *clientData;
X    Tcl_Interp *interp;
X    int argc;
X    char **argv;
X{
X    int j;
X    char *buffer;
X
X    if (argc != 2) {
X	sprintf(interp->result,
X		"incorrect number of parameters to \"%s\" command", argv[0]);
X	return (TCL_ERROR);
X    }
X
X    if ((buffer = malloc(strlen(argv[1]) + 1)) == NULL) {
X	sprintf(interp->result,
X		"out of memory while executing \"%s\" command", argv[0]);
X	return (TCL_ERROR);
X    }
X    
X    for (j = 0; argv[1][j] != NULL; j++ )
X#if defined(ultrix)
X	buffer[j] = tolower(argv[1][j]);
X#else
X        buffer[j] = isupper( argv[1][j] ) ? tolower( argv[1][j] ) : argv[1][j];
X#endif
X    buffer[j] = 0;
X
X    Tcl_Return(interp, buffer, TCL_DYNAMIC);
X
X    return (TCL_OK);
X}
X
Xint
XcmdGetEnv(clientData, interp, argc, argv)
X    char *clientData;
X    Tcl_Interp *interp;
X    int argc;
X    char **argv;
X{
X    if (argc != 2) {
X	sprintf(interp->result, 
X		"incorrect number of parameters to \"%s\" command", argv[0]);
X	return (TCL_ERROR);
X    }
X
X    Tcl_Return(interp, getenv(argv[1]), TCL_STATIC);
X
X    return TCL_OK;
X}
X
Xint
XcmdEcho(clientData, interp, argc, argv)
X    char *clientData;
X    Tcl_Interp *interp;
X    int argc;
X    char **argv;
X{
X    int i = 1, newline = 1;
X
X    if (argc > 1 && (strcmp(argv[1], "-n") == 0)) {
X	newline = 0;
X	i++;
X    }
X
X    for (; i < argc ; i++) 
X	fprintf(FingerOutput, "%s ", argv[i]);
X    
X    if (newline)
X	fprintf(FingerOutput, "\n");
X
X    return TCL_OK;
X}
X
Xint
XcmdFill(clientData, interp, argc, argv)
X    char *clientData;
X    Tcl_Interp *interp;
X    int argc;
X    char **argv;
X{
X    int i, linelen, indent, j, length, hanging = 0;
X
X    if (argc < 4) {
X	sprintf(interp->result,
X		"incorrect number of parameters to \"%s\" command", argv[0]);
X	return (TCL_ERROR);
X    }
X	
X    indent = atoi(argv[1]);
X    linelen = atoi(argv[2]);
X
X    i = 3;
X    if (argc > 4 && (strcmp(argv[3], "-h") == 0)) {
X	hanging = 1;
X	i++;
X    }
X
X    for (; i < argc ; ) {
X	/* if there's a hanging indent, don't pad first time */
X	if (i != 4 || !hanging)
X	    for (j = 1; j <= indent; j++)
X		fputc(' ', FingerOutput);
X
X	/* length is the length after the second word */
X	length = strlen(argv[i]);
X	fprintf(FingerOutput, "%s", argv[i++]);
X	if (i == argc)
X	    break;
X
X	length += strlen(argv[i]) + indent + 1;
X
X	/* while the _next_ word fits, do this ... */
X	while (length <= linelen && i < argc) {
X	    /* print the next word, add length for following word */
X	    fprintf(FingerOutput, " %s", argv[i]);
X	    if (++i == argc)
X		break;
X	    length += strlen(argv[i]) + 1;
X	}
X
X	if (i == argc)
X	    break;
X
X	fputc('\n', FingerOutput);
X    }
X
X    return TCL_OK;
X}
X
X/* Local Variables: */
X/* compile-command: "make" */
X/* End: */
//go.sysin dd *
echo x - tcl_cmds.h
sed 's/^X//' > tcl_cmds.h << '//go.sysin dd *'
Xextern int cmdToUpper( char *, Tcl_Interp *, int, char ** );
Xextern int cmdToLower( char *, Tcl_Interp *, int, char ** );
Xextern int cmdGetEnv( char *, Tcl_Interp *, int, char ** );
Xextern int cmdEcho( char *, Tcl_Interp *, int, char ** );
Xextern int cmdFill( char *, Tcl_Interp *, int, char ** );
//go.sysin dd *
echo x - uniqueproc.c
sed 's/^X//' > uniqueproc.c << '//go.sysin dd *'
X/*
X * This file contains a function "unique_proc" which ensures that the
X * process calling it is the only instance of this program running
X * under this userid.
X *
X * It returns TRUE if this process is the only one, and FALSE otherwise.
X *
X * It uses a PID file and the "ps" program to do its job.  It is not
X * bulletproof.
X */
X
X#include <stdlib.h>
X#include <stdio.h>
X#include <errno.h>
X#include <string.h>
X#include "plan_proto.h"
X
Xvoid writepid(const char*, int);
X
X#define BUFFER_SIZE (512)
X#define FALSE (0)
X#define TRUE (!FALSE)
X
Xint unique_proc() {
X    int mypid, procpid, n;
X    FILE *ps_out, *pid_file;
X    static char buf[BUFFER_SIZE], cmd_name[BUFFER_SIZE];
X    static char pid_cmd_name[BUFFER_SIZE], pid_file_name[BUFFER_SIZE];
X    char *home;
X
X    /* create the command to get the ps output for this process */
X    mypid = getpid();
X    sprintf( buf, "ps -c%d", mypid );
X
X    /* open the command for read */
X    if( (ps_out = popen( buf, "r" )) == NULL ) {
X	fprintf( stderr, "unique_proc: popen #1 failed. errno=%d\n", errno );
X	return FALSE;
X    } /* if */
X
X    /* suck up the header line */
X    fgets( buf, BUFFER_SIZE, ps_out );
X
X    /* scan the command name */
X    n = fscanf( ps_out, "%*20c%s", cmd_name );
X    if( pclose(ps_out) == -1 ) {
X	fprintf( stderr, "unique_proc: pclose #1 failed, errno =%d\n", errno );
X	return FALSE;
X    } /* if */
X
X    if( n != 1 ) {
X	fprintf( stderr, "unique_proc: cannot scan command name (n=%d)\n", n );
X	return FALSE;
X    } /* if */
X
X    /* get the home directory for this user */
X    if( (home = getenv("HOME")) == NULL ) {
X	fprintf( stderr, "unique_proc: getenv(\"HOME\") failed\n" );
X	return FALSE;
X    } /* if */
X
X    /* create the pidfile name for this command */
X    sprintf( pid_file_name, "%s/.%spid", home, cmd_name );
X
X    /* open the pid file */
X    if( (pid_file = fopen( pid_file_name, "r" )) == NULL ) {
X	/* assume this is the only instance, and write pid to file */
X	writepid( pid_file_name, mypid );
X	return TRUE;
X    } /* if */
X
X    /* read the pid */
X    if( (n = fscanf( pid_file, "%d", &procpid )) != 1 ) {
X	/* can't scan pid, assume this is only process, write pid to file */
X	fclose( pid_file );
X	writepid( pid_file_name, mypid );
X	return TRUE;
X    } /* if */
X
X    /* check out the pid read */
X    sprintf( buf, "ps -c%d", procpid );
X
X    /* open the command for read */
X    if( (ps_out = popen( buf, "r" )) == NULL ) {
X	fprintf( stderr, "unique_proc: popen #2 failed. errno=%d\n", errno );
X	return FALSE;
X    } /* if */
X
X    /* suck up the header line */
X    fgets( buf, BUFFER_SIZE, ps_out );
X
X    /* scan the command name */
X    n = fscanf( ps_out, "%*20c%s", pid_cmd_name );
X    if( pclose(ps_out) == -1 ) {
X	fprintf( stderr, "unique_proc: pclose #2 failed, errno =%d\n", errno );
X	return FALSE;
X    } /* if */
X
X    if( n == EOF ) {
X	/*
X	 * no such process...
X	 * therefore old process is dead
X	 * write new pid
X	 */
X	writepid( pid_file_name, mypid );
X	return TRUE;
X    } else if( strcmp( cmd_name, pid_cmd_name ) != 0 ) {
X	/*
X	 * names do not match, so pid match is a coincidence.
X	 * write new pid
X	 */
X	writepid( pid_file_name, mypid );
X	return TRUE;
X    } else {
X	/*
X	 * found an old instance of this program
X	 */
X	return FALSE;
X    } /* if */
X} /* unique_proc */
X
Xvoid writepid( const char* pidfilename, int pid ) {
X    FILE *pid_file;
X
X    if( (pid_file = fopen( pidfilename, "w" )) == NULL ) {
X	fprintf( stderr, "unique_proc: cannot open pid file ('%s') for write\n", pidfilename );
X    } else {
X	fprintf( pid_file, "%d\n", pid );
X	fclose( pid_file );
X    } /* if */
X} /* writepid */
//go.sysin dd *
exit

--
Blair MacIntyre --- bm@cs.columbia.edu --- CS Department, Columbia University
Newsgroups: alt.hackers