archie/tcl-dp/unix/dpEmail.c
2024-05-27 16:13:40 +02:00

2052 lines
56 KiB
C
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* dpEmail.c --
*
* This file contains the implementation of the email channel.
*
*/
/*
* Important facts about email channels:
*
* 1. Email channels are always bidirectional.
* 2. The subject line of the messages is retrieved and stored in the seek
* file, but not used.
* 3. An email channel is always writable; no exceptions are signalled on an
* email channel.
* 4. The application will create and/or modify the user's ~/.forward file. If
* several copies of this program are run in the same account, and if the user
* did not have a ~/.forward file before the first copy was started, it might
* happen (depending on the order in which the applications were started/ended),
* that the user will end all the applications, s/he will have a ~/.forward file
* containing "\user-name".
* 5. If an application that opened email channels crashes, the user is advised
* to check and possibly edit the ~/.forward file, and delete files
* ~/.emailSpool*, ~/.emailSeek*, ~/.emailChannel*, and ~/.emailLock*.
* 6. The identifier parameter is required in dp_connect whenever the number of
* opened email channels is zero. In all other cases it is ignored, but it is
* for correctness (i.e. if present, it has to be a positive integer).
*/
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/param.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <ctype.h>
#include <tcl.h>
#include <generic/dpInt.h>
#ifndef MAXHOSTNAMELEN
#define MAXHOSTNAMELEN 256
#endif
/*
* It is likely that these definitions are only temporary.
* They should be reviewed before the final release.
*/
#define DP_ARBITRARY_LIMIT 500
#define DP_GARBAGE_LIMIT 100000
#define DP_MAX_FILE_NAME_LENGTH 20 /* OS dependent */
#define DP_LIB_DIRECTORY LLIB
#define DP_HOME_DIRECTORY "/tmp"
#define min(x, y) (((x) < (y)) ? (x) : (y))
typedef struct {
/*
* This stores the complete address of the sender/receiver. The address can
* consist of any non-white characters, except for the first character which
* can not be '*'.
*/
char address [DP_ARBITRARY_LIMIT + 1];
/*
* If border is zero, the program will not read across message boundaries.
* If border is non-zero, then message boundaries will be ignored: whenever
* a read is requested, the amount of data returned will be min(requested,
* available). In the current implementation this value is always zero, it
* can not be set or retrieved with fconfigure.
*/
int border;
/*
* If this value is non-zero, then the data will be read, but not consumed.
*/
int peek;
/*
* This variable stores a lower bound on the number of bytes that are
* available for reading in the email channel. It is decreased by read's,
* and if it reaches zero, it is reset to its real value, based on the data
* stored in the seek file. It's main use is to speed up the event
* notification mechanism: if it is positive, then data is waiting to be
* read, if it is zero, one has to process the seek file to find the answer.
*/
long available;
/*
* This variable stores the value of the "-sequence" parameter. It
* it is zero, no sequence numbers will be associated with the
* written messages.
*/
int sequence;
/*
* This variable is the source for the sequence numbers that can
* be associated with the messages sent out by the email channel.
*/
long sequenceNo;
} EmailInfo;
/*
* Prototypes for functions referenced only in this file.
*/
static int CloseEmailChannel _ANSI_ARGS_((ClientData instanceData,
Tcl_Interp *interp));
static int InputEmailChannel _ANSI_ARGS_((ClientData instanceData,
char *buf, int bufsize,
int *errorCodePtr));
static int OutputEmailChannel _ANSI_ARGS_((ClientData instanceData,
char *buf, int toWrite,
int *errorCodePtr));
static int SOPEmailChannel _ANSI_ARGS_((ClientData instanceData,
Tcl_Interp *interp,
char *optionName,
char *optionValue));
#ifndef _TCL76
static int GOPEmailChannel _ANSI_ARGS_((ClientData instanceData,
Tcl_Interp *interp,
char *optionName,
Tcl_DString *dsPtr));
#else
static int GOPEmailChannel _ANSI_ARGS_((ClientData instanceData,
char *optionName,
Tcl_DString *dsPtr));
#endif
#ifdef _TCL76
static Tcl_File GFPEmailChannel _ANSI_ARGS_((ClientData instanceData,
int direction));
#else
static int GFPEmailChannel _ANSI_ARGS_((ClientData instanceData,
int direction,
FileHandle *fd));
#endif
static int CRPEmailChannel _ANSI_ARGS_((ClientData instanceData,
int mask));
static void WCPEmailChannel _ANSI_ARGS_((ClientData instanceData,
int mask));
static long OverwriteRestOfFile _ANSI_ARGS_((FILE *fp, int delta));
static int LogicalEraseFromSeek _ANSI_ARGS_((char *sender));
static int CleanUpFiles _ANSI_ARGS_((void));
static long ReadFromSpool _ANSI_ARGS_((char *sender, char *where,
long howMany, int border,
int peek));
static int SetAvailable _ANSI_ARGS_((ClientData instanceData));
/*
* This structure stores the names of the functions that Tcl calls when certain
* actions have to be performed on an email channel. To understand this entry,
* please refer to the documentation of the Tcl_CreateChannel and its associated
* functions in the Tcl 7.6 documentation.
*
* Notice that an email channel will always be non-blocking. Seek will not be
* allowed either.
*/
static Tcl_ChannelType emailChannelType = {
"email",
NULL, /* blockModeProc */
CloseEmailChannel, /* closeProc */
InputEmailChannel, /* inputProc */
OutputEmailChannel, /* outputProc */
NULL, /* seekProc */
SOPEmailChannel, /* setOptionProc */
GOPEmailChannel, /* getOptionProc */
WCPEmailChannel, /* watchChannelProc */
#ifdef _TCL76
CRPEmailChannel, /* channelReadyProc */
#endif
GFPEmailChannel /* getFileProc */
};
/*
* Variables that are local to this file:
*/
/*
* This variable stores the unique identifier that is used to call
* CreateEmailChannel, and which is used to name the channel, seek, and spool
* file (see below).
*
* NOTE: currently this variable has no real use as a global variable. If the
* application would want to shut down without intrerupting the incoming
* communication on the email channels, this information could be used later
* to retrieve the channel, seek, and spool files, and to revert the
* modifications of the ~/.forward file when the last email channel will be
* closed.
*/
static int gIdentifier = 0;
/*
* This stores the path of the spool file. Each application has a unique spool
* file in ~/.emailSpoolXXX, where XXX is the unique identifier asscociated
* with the running application (see gIdentifier above - it is set in
* CreateEmailChannel). The spool file contains all the data transmitted
* in the body (e.g. no address, subject is included) of the email
* messages. There is no explicit representation of the message boundaries,
* these are identified based on the information stored in the seek file
* (see below).
*/
static char spoolFile [MAXHOSTNAMELEN + 1];
/*
* This stores the path of the channel file. Each application has a unique
* channel file in ~/.emailChannelXXX, where XXX is the unique identifier
* associated with the running application (see gIdentifier above and in
* CreateEmailChannel). The channel file contains the names of all the
* email addresses that are authorized to send/receive mail. Each address is
* stored on one separate line.
*/
static char channelFile [MAXHOSTNAMELEN + 1];
/*
* This stores the name of the seek file. Each application has a unique seek
* file in ~/.emailSeekXXX, where XXX is the unique identifier asscociated
* with the running application (see gIdentifier above and in
* CreateEmailChannel). The seek file contains one line for each message
* whose body was appended to the spool file. The format of the entry is the
* following:
*
* <address><space><message lenght><space><used-lenght><space><subject><RET>
*
* Fields <message-lenght> and <used-lenght> have exactly 8 digits, while
* <address> and <subject> are not constraind in lenght. <address> can not
* contain spaces, nor have '*' as its first character.
*
* If a message was completely read, or the channel associated with it was
* closed, then the first character of the entry is overwritten with "*". These
* entries will be removed when the first garbage collection is done.
*/
static char seekFile [MAXHOSTNAMELEN + 1];
/*
* This stores the path of the ~/.forward file. When creating the first email
* channel, this file is modified by adding a filter program to it. Only one
* filter is added per application. The filter program uses the information
* in the channel file to select the messages that are relevant; if they are,
* it adds one entry to the seek file and appends the content of the message
* body to the channel file. When the last email channel is closed, the filter
* program is removed from the ~/.forward file.
*
* Important: Please refer to the note on the .forward file at the top of this
* file.
*/
static char fwdFile [MAXHOSTNAMELEN + 1];
/*
* This stores the path of the lock file. Each application uses a single lock
* file in ~/.emailLockXXX, where XXX is the unique identifier
* associated with the running application (see gIdentifier above and in
* CreateEmailChannel). The lock file is used to assure that simultaneous
* access of data files by the application and the filter program is impossible.
*/
static char lockFile [MAXHOSTNAMELEN + 1] = { '\0' };
/*
* This is a pointer to a string containing the path to the directory in which
* the executable filter program is stored. The name of the executable should be
* dpfilter.
*/
static char *libDirectory;
/*
* This is a pointer to a string containing the path to the home directory. All
* temporary files associated with the email channel will be stored here.
*/
static char *homeDirectory;
/*
* In order to increase efficiency, the spool and seek file are garbage collected
* only when the amount of garbage is above a given limit. This variable stores
* the amount of garbage (already read data) in the spool file.
*/
static long garbageInSpoolFile = 0;
/*
* This is a counter that stores the number of opened email channels. It is
* used to detect the first email channel that is opened in order to modify the
* ~/.forward file, and to create the channel, spool and seek files. It is also
* used to detect the closing of the last email channel when the content of the
* ~/.forward file is restored, and the channel, spool and seek files are
* deleted.
*/
static int openedChannels = 0;
/*
* If, by the time the first channel was opened, there existed a ~/.forward file
* the value of this variable will be set to 1. This value is tested when the
* last channel is closed. If it is zero, the ~/.forward file will be deleted,
* otherwise the program will only remove the filter program from it.
*/
static int wasThereAForwardFile = 0;
/*
*-----------------------------------------------------------------------------
*
* CreateEmailChannel --
*
* Creates an email channel to exchange information with "address". The
* "identifier" is used to uniquely distinguish between different
* applications that might run concurrently, and use email channels. In
* most of the cases, "identifier" can be the pid of the running program.
*
* Results:
*
* Returns a channel data structure. If an error happens, NULL
* is returned.
*
* Side effects:
*
* When the first email channel is created, the value of identifier is
* stored in the global variable gIdentifier. Also, the channel, seek and
* spool files are creates. The ~/.forward file is modified by adding to
* it a call to the filter program (emailFilter). For each email channel
* that is opened a new entry is added to the channel file.
*
*-----------------------------------------------------------------------------
*/
Tcl_Channel
DpCreateEmailChannel (interp, argc, argv)
Tcl_Interp *interp; /* (in) Pointer to tcl interpreter. */
int argc; /* (in) Number of arguments. */
char **argv; /* (in) Argument strings. */
{
static long channelSerialNumber = 0;
char fwdFileBuffer [DP_ARBITRARY_LIMIT + 1];
char channelFileBuffer [DP_ARBITRARY_LIMIT + 1];
char chanName [30];
char *address;
int i, identifier;
FILE *chanFilePtr, *fwdFilePtr;
EmailInfo *infoPtr;
Tcl_Channel chan;
address = NULL;
identifier = 0;
for (i = 0; i < argc; i += 2) {
int v = i+1;
size_t len = strlen(argv[i]);
if (strncmp(argv[i], "-address", len)==0) {
if (v == argc) {
goto arg_missing;
}
address = argv[v];
if((address[0] == '\0') || (address[0] == '*')) {
Tcl_AppendResult(interp, "the address for an email channel ",
"can not be empty or start with a '*'", NULL);
return NULL;
}
} else if (strncmp(argv[i], "-identifier", len)==0) {
if (v == argc) {
goto arg_missing;
}
if(Tcl_GetInt(interp, argv[v], &identifier) != TCL_OK) {
return NULL;
}
if(identifier <= 0) {
Tcl_AppendResult(interp, "the identifier for an email channel ",
"can not be zero, negative, or a string", NULL);
return NULL;
}
} else {
Tcl_AppendResult(interp, "unknown option \"",
argv[i], "\", must be -address or -identifier", NULL);
return NULL;
}
}
if((address == NULL) || ((openedChannels == 0) & (identifier <= 0))) {
Tcl_AppendResult(interp, "address and/or identifier not defined for ",
"email channel", NULL);
return NULL;
}
if(lockFile[0] == '\0') {
/* Set the paths of the files used by the email channels. */
if((homeDirectory = getenv("HOME")) == NULL) {
homeDirectory = DP_HOME_DIRECTORY;
}
if((libDirectory = getenv("DP_LIBRARY")) == NULL) {
libDirectory = DP_LIB_DIRECTORY;
}
/* Protect against overflow due to very long paths. */
if(strlen(homeDirectory) > MAXPATHLEN - DP_MAX_FILE_NAME_LENGTH) {
Tcl_AppendResult(interp, "too long path for home directory ",
homeDirectory, NULL);
return NULL;
}
sprintf(spoolFile, "%s/.emailSpool%d", homeDirectory, identifier);
sprintf(channelFile, "%s/.emailChannel%d", homeDirectory, identifier);
sprintf(seekFile, "%s/.emailSeek%d", homeDirectory, identifier);
sprintf(lockFile, "%s/.emailLock%d", homeDirectory, identifier);
sprintf(fwdFile, "%s/.forward", homeDirectory);
}
if(PutLock(lockFile) != 0) {
return NULL;
}
if(openedChannels > 0) {
if((chanFilePtr = fopen(channelFile, "r")) == NULL) {
goto error0;
}
/* Check if this channel was already opened. */
if(fgets(channelFileBuffer,sizeof(channelFileBuffer),chanFilePtr)==NULL){
goto error1;
}
while(!feof(chanFilePtr)) {
strtok(channelFileBuffer, " ");
if(!strcasecmp(channelFileBuffer, address)) {
/* Oops, this channel has been opened earlier! */
goto error1;
};
if(fgets(channelFileBuffer, sizeof(channelFileBuffer),
chanFilePtr) == NULL) {
if(!feof(chanFilePtr)) {
goto error1;
}
}
}
}
/*
* OK, either the channel file does not exist, either this channel
* has not been opened yet.
*/
sprintf(chanName, "email%ld", channelSerialNumber++);
if(openedChannels == 0) {
garbageInSpoolFile = 0;
/* Assume there was no .forward file. */
wasThereAForwardFile = 0;
if(creat(spoolFile, 0x180) == -1) {
goto error0;
}
if(creat(seekFile, 0x180) == -1) {
unlink(spoolFile);
goto error0;
}
}
if((chanFilePtr = fopen(channelFile, "a")) == NULL) {
goto error0;
}
if(fprintf(chanFilePtr, "%s\n", address) == EOF) {
goto error1;
}
fclose(chanFilePtr);
if(openedChannels == 0) {
if(access(fwdFile, R_OK | W_OK) == -1) {
/* .forward file does not exist or inaccessible */
/* Try to create the file... */
if(creat(fwdFile, 0x180) == -1) {
/* File could not be created, nothing to do. */
goto error2;
}
} else {
wasThereAForwardFile = 1;
}
if((fwdFilePtr = fopen(fwdFile, "r+")) == NULL) {
goto error3;
}
if(fgets(fwdFileBuffer, DP_ARBITRARY_LIMIT, fwdFilePtr) == NULL) {
if(!feof(fwdFilePtr)) {
goto error4;
}
}
if(fseek(fwdFilePtr, 0, 0) == -1) {
goto error4;
}
if(!wasThereAForwardFile) {
if(fprintf(fwdFilePtr, "\"|%s/dpfilter %s/ .emailLock%d \
.emailChannel%d .emailSeek%d .emailSpool%d\", \\%s", libDirectory, homeDirectory,
identifier, identifier, identifier, identifier,
getenv("USER")) == EOF) {
goto error4;
}
} else {
if(fprintf(fwdFilePtr, "\"|%s/dpfilter %s/ .emailLock%d \
.emailChannel%d .emailSeek%d .emailSpool%d\", %s", libDirectory, homeDirectory,
identifier, identifier, identifier, identifier,
fwdFileBuffer) == EOF) {
goto error4;
}
}
fclose(fwdFilePtr);
}
/*
* Create the instance data associated with this channel
* only if everything went ok.
*/
if((infoPtr = (EmailInfo *)ckalloc(sizeof(EmailInfo))) == NULL) {
goto error0;
}
strcpy(infoPtr->address, address);
infoPtr->border = 0; /* Do not read across message boundaries. */
infoPtr->peek = 0; /* Read the data, don't peek. */
infoPtr->available = 0; /* No data available in the email channel. */
infoPtr->sequence = 0; /* No sequence numbers in messages. */
infoPtr->sequenceNo = 0;/* Start sequences from 0. */
if(openedChannels == 0) {
gIdentifier = identifier;
}
openedChannels++;
RemoveLock(lockFile);
chan = Tcl_CreateChannel(&emailChannelType, chanName, infoPtr,
TCL_READABLE | TCL_WRITABLE);
Tcl_RegisterChannel(interp, chan);
return chan;
error1:
fclose(chanFilePtr);
/* Continues with error0. */
error0:
RemoveLock(lockFile);
return NULL;
error4:
fclose(fwdFilePtr);
/* Continues with error3. */
error3:
if(!wasThereAForwardFile)
unlink(fwdFile);
/* Continues with error2. */
error2:
unlink(spoolFile);
unlink(seekFile);
RemoveLock(lockFile);
return NULL;
arg_missing:
Tcl_AppendResult(interp, "value for \"", argv[argc-1], "\" missing", NULL);
return NULL;
}
/*
*-----------------------------------------------------------------------------
*
* CloseEmailChannel --
*
* Closes the given email channel.
*
* Results:
*
* If everything goes well, it returns 0. If any error happens,
* it returns a POSIX error code.
*
* Side effects:
*
* If the last email channel is closed, the channel, seek, and spool
* files are removed. Also, the filter program is removed from the
* ~/.forward file.
* If the email channel that is closed is not the last one, the
* corresponding entry is removed from the channel file. All the entries
* associated with this channel in the seek file will be modified to have
* a "*" character in the first position. These entries, together with
* their corresponding data in the spool file will be removed when
* the garbage collection procedure is run.
*
*-----------------------------------------------------------------------------
*/
static int
CloseEmailChannel (instanceData, interp)
ClientData instanceData; /* (in) Pointer to EmailInfo struct. */
Tcl_Interp *interp; /* (in) Pointer to tcl interpreter. */
{
FILE *fwdFilePtr, *chanFilePtr;
int delta;
long logicalEOF;
char buffer [DP_ARBITRARY_LIMIT + 1];
if(PutLock(lockFile) != 0) {
return ENOLCK;
}
if(openedChannels == 1) {
/* We are closing the last open channel. */
char tmpBuffer [20 * DP_ARBITRARY_LIMIT + 1];
char *tmp;
char *found;
int error = 0;
error += unlink(spoolFile);
error += unlink(channelFile);
error += unlink(seekFile);
if(error) {
Tcl_AppendResult(interp, "unable to delete spool file ", spoolFile,
" and/or channel file ", channelFile,
"and/or seekfile ", seekFile, NULL);
/*
* Errno is not reset if a successful call follows an erroneous one.
*/
goto error4;
}
/* Remove the entry from the ~/.forward file. */
if((fwdFilePtr = fopen(fwdFile, "r")) == NULL) {
Tcl_AppendResult(interp,
"unable to open ~/.forward file for reading",
NULL);
goto error3;
}
if(fgets(buffer, DP_ARBITRARY_LIMIT, fwdFilePtr) == NULL) {
goto error1;
}
/* Find the name of the channel file in the .forward file */
sprintf(tmpBuffer, "\"|%s/dpfilter %s/ .emailLock%d .emailChannel%d \
.emailSeek%d .emailSpool%d\", ", libDirectory, homeDirectory, gIdentifier,
gIdentifier, gIdentifier, gIdentifier);
if((found = strstr(buffer, tmpBuffer)) == NULL) {
/* Oops, it's not there! */
Tcl_AppendResult(interp, "filter program not found in ~/.forward ",
"file, did you remove it?", NULL);
goto error1;
}
for(tmp = found + strlen(tmpBuffer); *tmp != '\0'; *found++ = *tmp++) {
/* empty body */
}
*found = '\0';
/* Was there a ~/.forward file when we opened the first channel? */
if(!wasThereAForwardFile) {
/* If no, did the somebody modify it since we created it? */
char *format = "\\%.*s";
char tmp [DP_ARBITRARY_LIMIT + 1];
sprintf(tmp, format, sizeof(tmp) - strlen(format), getenv("USER"));
if(!strcmp(buffer, tmp)) {
/* No, the file was (probably) not modified; delete it. */
openedChannels--;
if(unlink(fwdFile) == -1) {
/* An error message would not be very relevant here. */
goto error4;
}
goto error0;
}
/*
* Yes, the file was modified, rewrite it to disk as if it had been
* there before this program strated to run.
*/
}
/* Write the modified content back to the ~/.forward file */
if((fwdFilePtr = freopen(fwdFile, "w", fwdFilePtr)) == NULL) {
Tcl_AppendResult(interp, "unable to open ~/.forward file for \
writing", NULL);
goto error3;
}
if(fputs(buffer, fwdFilePtr) == EOF) {
Tcl_AppendResult(interp, "can not write ~/.forward file", NULL);
goto error1;
}
fclose(fwdFilePtr);
openedChannels--;
goto error0;
}
/*
* Logically eliminate unread messages associated with this
* channel from the seek file by overwriting the first character of their
* corresponding entries in the seek file with "*".
*/
if(LogicalEraseFromSeek(((EmailInfo *)instanceData)->address) == -1) {
Tcl_AppendResult(interp, "unable to eliminate garbage entries from ",
"the spoolfile", NULL);
goto error3;
}
/*
* Eliminate the entry in the channel file that is associated with
* the channel we are closing. First, find the address in the channel
* file.
*/
if((chanFilePtr = fopen(channelFile, "r+")) == NULL) {
Tcl_AppendResult(interp, "unable to open channel file ",
channelFile, NULL);
goto error3;
}
if(fgets(buffer, DP_ARBITRARY_LIMIT, chanFilePtr) == NULL) {
if(!feof(chanFilePtr)) {
goto error2;
}
}
while(!feof(chanFilePtr)) {
strtok(buffer, " \n");
if(!strcasecmp(buffer, ((EmailInfo *)instanceData) -> address)) {
/* We found the entry we are looking for. */
break;
}
if(fgets(buffer, DP_ARBITRARY_LIMIT, chanFilePtr) == NULL) {
if(!feof(chanFilePtr)) {
goto error2;
}
}
}
if(feof(chanFilePtr)) {
/* Too bad, there is no channel to close. */
Tcl_AppendResult(interp, "unable to close email channel ",
((EmailInfo *)instanceData) -> address,
". Not opened?", NULL);
goto error2;
}
/* Eliminate the entry by overwriting it. */
buffer[strlen(buffer)] = ' '; /* Undo the effect of strtok(). */
delta = strlen(buffer);
if((logicalEOF = OverwriteRestOfFile(chanFilePtr, delta)) == -1) {
Tcl_AppendResult(interp, "unable to rewrite the channel file ",
channelFile, NULL);
goto error2;
}
fclose(chanFilePtr);
if(truncate(channelFile, logicalEOF) == -1) {
Tcl_AppendResult(interp, "unable to rewrite the channel file ",
channelFile, NULL);
goto error3;
}
openedChannels--;
/* Continues with error0. */
error0:
RemoveLock(lockFile);
return 0;
error1:
fclose(fwdFilePtr);
RemoveLock(lockFile);
return EINVAL;
error2:
fclose(chanFilePtr);
/* Continues with error3. */
error3:
RemoveLock(lockFile);
return EINVAL;
error4:
RemoveLock(lockFile);
return Tcl_GetErrno();
}
/*
*-----------------------------------------------------------------------------
*
* OverwriteRestOfFile --
*
* Starting from the current position in a file, it will eliminate chunk
* of size delta characters, by overwriting it with the data (if any) that
* follows in the file after the block to be deleted.
*
* Results:
*
* A positive long integer specifying the logical end of the file.
* Normally, the file will be truncated to this size by the caller
* function. If an error happens, -1 will be returned. If this occurs, it
* is likely that the file content will be corrupted.
*
* Side effects:
*
* None.
*
*-----------------------------------------------------------------------------
*/
static long
OverwriteRestOfFile (filePtr, delta)
FILE *filePtr; /* (in) File to work on. */
int delta; /* (in) Number of bytes to eliminate. */
{
char bigBuffer [ 20 * DP_ARBITRARY_LIMIT];
long temp, whereToWriteTo, whereToReadFrom;
/* O is a valid return value for fread's in this function */
temp = fread(bigBuffer, sizeof(char), sizeof(bigBuffer), filePtr);
whereToReadFrom = ftell(filePtr);
whereToWriteTo = whereToReadFrom - temp - delta;
while(temp > 0) {
if(fseek(filePtr, whereToWriteTo, 0) == -1) {
return -1;
}
if(fwrite(bigBuffer, sizeof(char), temp, filePtr) != temp) {
return -1;
}
if(fseek(filePtr, whereToReadFrom, 0) == -1) {
return -1;
}
whereToWriteTo += temp;
temp = fread(bigBuffer, sizeof(char), sizeof(bigBuffer), filePtr);
whereToReadFrom += temp;
}
return whereToWriteTo;
}
/*
*-----------------------------------------------------------------------------
*
* LogicalEraseFromSeek --
*
* Eliminates the entries associated with "sender" in the seek file.
* The elimination is done by overwriting the first character of each
* entry referring to a message received from "sender" with "*". The
* physical elimination of these entries, together with the corresponding
* data in the spool file, will be done when the garbage collection
* algorithm is run.
*
* Results:
*
* 0 if no error is detected, -1 in the opposite case. If an error
* occured, the content of the seek file might be damaged.
*
* Side effects:
*
* None.
*
*-----------------------------------------------------------------------------
*/
static int
LogicalEraseFromSeek (sender)
char *sender; /* (in) Email address of sender. */
{
FILE *seekFilePtr;
char buffer [DP_ARBITRARY_LIMIT];
long whereToReadFrom, whereToWriteTo;
if((seekFilePtr = fopen(seekFile, "r+")) == NULL) {
return -1;
}
whereToWriteTo = 0;
if(fgets(buffer, DP_ARBITRARY_LIMIT, seekFilePtr) == NULL) {
if(!feof(seekFilePtr)) {
goto error1;
}
}
whereToReadFrom = ftell(seekFilePtr);
strtok(buffer, " ");
while(!feof(seekFilePtr)) {
if(!strcasecmp(sender, buffer)) {
garbageInSpoolFile += atol(strtok(NULL, " "));
/*
* The repetition is not error: it undoes the effect of
* the two calls to strtok() that were made above.
*/
buffer[strlen(buffer)] = ' ';
buffer[strlen(buffer)] = ' ';
buffer[0] = '*';
if(fseek(seekFilePtr, whereToWriteTo, 0) == -1) {
goto error1;
}
if(fputs(buffer, seekFilePtr) == EOF) {
goto error1;
}
if(fseek(seekFilePtr, whereToReadFrom, 0) == -1) {
goto error1;
}
}
if(fgets(buffer, DP_ARBITRARY_LIMIT, seekFilePtr) == NULL) {
if(!feof(seekFilePtr)) {
goto error1;
}
}
whereToWriteTo = whereToReadFrom;
whereToReadFrom += strlen(buffer);
strtok(buffer, " ");
}
fclose(seekFilePtr);
return 0;
error1:
fclose(seekFilePtr);
return -1;
}
/*
*-----------------------------------------------------------------------------
*
* CleanUpFiles --
*
* Clean up the garbage accumulated in the seek and spool files by
* removing the entries marked with a "*" from the seek file, as well as
* the corresponding data from the spool file. Also, it there exists a
* partially read entry in the seek file (one for which the content of the
* <used-length> field is not zero, but the number it contains is smaller
* than the one in <message-lenght>), its content is adjusted to show a
* message of lenght <message-lenght> - <used-length>, and <used-length> =
* 0. The spool file is adjusted accordingly.
*
* Results:
*
* 0 if no error is detected, -1 in the opoosite case. If an error
* occured, the content of the seek and spool files might be damaged.
*
* Side effects:
*
* Sets variable garbageInSpoolFile (local to this file) to 0.
*
*-----------------------------------------------------------------------------
*/
static int
CleanUpFiles (/*void*/)
{
FILE *fpSeek, *fpSpool;
char buffer [DP_ARBITRARY_LIMIT];
/* wtrf = where to read from */
/* wtwt = where to write to */
long wtrfSeek, wtwtSeek;
long wtrfSpool, wtwtSpool;
if((fpSeek = fopen(seekFile, "r+")) == NULL) {
return -1;
}
if((fpSpool = fopen(spoolFile, "r+")) == NULL) {
fclose(fpSeek);
return -1;
}
wtrfSeek = 0;
wtwtSeek = 0;
wtrfSpool = 0;
wtwtSpool = 0;
if(fgets(buffer, DP_ARBITRARY_LIMIT, fpSeek) == NULL) {
if(!feof(fpSeek)) {
goto error1;
}
}
wtrfSeek = strlen(buffer);
/*
* Treat each entry in the seek file according to the three possible cases:
*
* (a) garbage entry (first character is "*"): eliminate it from the seek
* file and delete the corresponding data from the spool file.
*
* (b) partially used entry (<used-length> non-zero and less than
* <message-lenght>): rewrite the adjusted entry to disk and eliminate the
* used part of the message from the spool file.
*
* (c) normal entries: rewrite the unmodified entry to the disk (possible to
* a different position because of the entries eliminated before this one),
* and copy the corresponding data in the spool file (this woould also go in
* general to a different position because of the already deleted entries).
*/
while(!feof(fpSeek)) {
char bigBuffer [20 * DP_ARBITRARY_LIMIT];
long total, used, toBeTransferred, transferred;
long temp, temp2;
char *ctemp, *ctemp2;
/*
* Ignored garbage entries,ant the messages originating from other
* senders; don't move the "write to" pointers in the seek and spool
* file.
*/
while((!feof(fpSeek)) && (buffer[0] == '*')) {
strtok(buffer, " ");
wtrfSpool += atol(strtok(NULL, " "));
if(fseek(fpSeek, wtrfSeek, 0) == -1) {
goto error1;
}
if(fgets(buffer, DP_ARBITRARY_LIMIT, fpSeek) == NULL) {
if(!feof(fpSeek)) {
goto error1;
}
}
wtrfSeek += strlen(buffer);
}
if(feof(fpSeek))
continue;
/*
* The entry is of type (b) or (c). Treat them in the same way, by
* transferring <message lenght> - <used lenght> (for entries of type
* (c) <used lenght> = 0) bytes in the spool file.
*/
strtok(buffer, " ");
ctemp = strtok(NULL, " ");
total = atol(ctemp);
ctemp2 = strtok(NULL, " ");
used = atol(ctemp2);
/*
* Skip the already used part from the spool file.
*/
wtrfSpool += used;
toBeTransferred = total - used;
transferred = 0;
while(toBeTransferred > 0) {
if(fseek(fpSpool, wtrfSpool, 0) == -1) {
goto error1;
}
temp = fread(bigBuffer, sizeof(char),
min(toBeTransferred, sizeof(bigBuffer)), fpSpool);
wtrfSpool += temp;
if(fseek(fpSpool, wtwtSpool, 0) == -1) {
goto error1;
}
temp2 = fwrite(bigBuffer, sizeof(char), temp, fpSpool);
wtwtSpool += temp2;
if((temp == 0) || (temp2 == 0) || (temp != temp2)) {
goto error1;
}
toBeTransferred -= temp;
}
/*
* Create the entry that will be written to the seek file. If the old
* entry is of type (c), we just recreate it. If it is of type (b), we
* create an equivalent entry of type (b) for it.
*
* Notice how we deal with the problem of '\0' characters introduced by
* succesive calls of strtok().
*/
sprintf(ctemp - 1, " %.8ld", total - used);
sprintf(ctemp2 - 1, " 00000000");
ctemp2[strlen(ctemp2)] = ' ';
if(fseek(fpSeek, wtwtSeek, 0) == -1) {
goto error1;
}
if(fputs(buffer, fpSeek) == EOF) {
goto error1;
}
wtwtSeek += strlen(buffer);
if(fseek(fpSeek, wtrfSeek, 0) == -1) {
goto error1;
}
if(fgets(buffer, DP_ARBITRARY_LIMIT, fpSeek) == NULL) {
if(!feof(fpSeek)) {
goto error1;
}
}
wtrfSeek += strlen(buffer);
}
fclose(fpSeek);
fclose(fpSpool);
truncate(seekFile, wtwtSeek);
truncate(spoolFile, wtwtSpool);
if(truncate(seekFile, wtwtSeek) + truncate(spoolFile, wtwtSpool) != 0) {
return -1;
}
garbageInSpoolFile = 0;
return 0;
error1:
fclose(fpSeek);
fclose(fpSpool);
return -1;
}
/*
*-----------------------------------------------------------------------------
*
* ReadFromSpool --
*
* Reads at most howMany bytes from the first available message sent by
* sender. If border is 0, the reading will stop message boundaries,
* otherwise it will ignore them.
*
* Results:
*
* Return the number of bytes read or -1 if an error is detected.
*
* Side effects:
*
* None.
*
*-----------------------------------------------------------------------------
*/
static long
ReadFromSpool (sender, where, howMany, border, peek)
char *sender; /* where do we want the read message to be from */
char *where; /* address of buffer to store the result */
long howMany; /* maximal number of bytes to be read */
int border; /* if 0, keep message boundaries, otherwise not */
int peek; /* if 0, really read the data, otherwise not */
{
FILE *fpSeek, *fpSpool;
char buffer [DP_ARBITRARY_LIMIT];
/* wtrf = where to read from */
/* wtwt = where to write to */
long wtrfSeek, wtwtSeek, wtrfSpool, wtwtSpool;
long transferred, toBeTransferred;
if((fpSeek = fopen(seekFile, "r+")) == NULL) {
return -1;
}
if((fpSpool = fopen(spoolFile, "r")) == NULL) {
fclose(fpSeek);
return -1;
}
wtrfSeek = 0;
wtwtSeek = 0;
wtrfSpool = 0;
transferred = 0;
toBeTransferred = howMany;
if(fgets(buffer, DP_ARBITRARY_LIMIT, fpSeek) == NULL) {
if(!feof(fpSeek)) {
goto error1;
}
}
wtrfSeek = strlen(buffer);
strtok(buffer, " ");
/*
* Treat each entry in the seek file according to the three possible cases:
*
* (a) garbage entry (first character is "*"): ignore it.
*
* (b) partially used entry (<used-length> non-zero and less than
* <message-lenght>): read min(howMany, <message-lenght> - <used-lenght>)
* bytes from the spool file, adjust the entry and rewrite the adjusted
* entry to disk.
*
*
* (c) normal entries: same as for (b).
*
* If the amount of bytes that must be read is greater than that available
* in the first acceptable seek file entry, and border != 0, process the next
* acceptable seek file entry, if any.
*/
while((!feof(fpSeek)) && (toBeTransferred > 0)) {
char *ctemp, *ctemp2;
long total, used, temp, transferredNow;
/*
* Ignored garbage entries,ant the messages originating from other
* senders; don't move the "write to" pointers in the seek and spool
* file.
*/
while((!feof(fpSeek)) && (strcasecmp(sender, buffer))) {
wtrfSpool += atol(strtok(NULL, " "));
if(fgets(buffer, DP_ARBITRARY_LIMIT, fpSeek) == NULL) {
if(!feof(fpSeek)) {
goto error1;
}
}
wtrfSeek += strlen(buffer);
wtwtSeek += strlen(buffer);
strtok(buffer, " ");
}
if(feof(fpSeek)) {
continue;
}
ctemp = strtok(NULL, " ");
total = atol(ctemp);
ctemp2 = strtok(NULL, " ");
used = atol(ctemp2);
wtrfSpool += used;
transferredNow = 0;
while(transferredNow < min(howMany, min(total-used, toBeTransferred))) {
if(fseek(fpSpool, wtrfSpool, 0) == -1) {
goto error1;
}
temp = fread(where, sizeof(char),
min(howMany, min(total - used, toBeTransferred)), fpSpool);
where += temp;
wtrfSpool += temp;
if(temp < min(howMany, min(total - used, toBeTransferred))) {
goto error1;
}
transferredNow += temp;
}
/*
* Restore buffer. i.e. undo the effects of successive calls to strtok().
*/
*(ctemp - 1) = ' ';
*(ctemp + 8) = ' ';
*(ctemp + 17) = ' ';
transferred += transferredNow;
/*
* Create the entry of type (a) or (d) that will be rewritten to the
* seek file.
*/
if(peek == 0) {
if(transferredNow == total - used) {
buffer[0] = '*';
}
else {
sprintf(ctemp + 9, "%.8ld", used + transferredNow);
*(ctemp + 17) = ' ';
}
}
if(fseek(fpSeek, wtwtSeek, 0) == -1) {
goto error1;
}
if(fputs(buffer, fpSeek) == EOF) {
goto error1;
}
wtwtSeek += strlen(buffer);
if((transferred < howMany) && (border == 0)) {
/* Do not cross message boundaries. */
garbageInSpoolFile += transferred;
fclose(fpSeek);
fclose(fpSpool);
return transferred;
}
/* Ignore message boundaries. */
toBeTransferred -= transferredNow;
if(fseek(fpSeek, wtrfSeek, 0) == -1) {
goto error1;
}
if(fgets(buffer, DP_ARBITRARY_LIMIT, fpSeek) == NULL) {
if(!feof(fpSeek)) {
goto error1;
}
}
wtrfSeek += strlen(buffer);
strtok(buffer, " ");
}
if(peek == 0) {
garbageInSpoolFile += transferred;
}
fclose(fpSeek);
fclose(fpSpool);
return transferred;
error1:
fclose(fpSeek);
fclose(fpSpool);
return -1;
}
/*
*-----------------------------------------------------------------------------
*
* GFPEmailChannel --
*
* Returns the tcl file descriptor that is associated with the given
* channel. In the case of email channels, this descriptor will always be
* the NULL pointer, signalling that there is no such file.
* For a more complete description of this function please refer
* to the description of Tcl_CreateChannel and its associated functions
* in the Tcl 7.6 documentation. This function is described under the
* entry GETFILEPROC.
*
* Results:
*
* Always return NULL.
*
* Side effects:
*
* None.
*
*-----------------------------------------------------------------------------
*/
/* ARGSUSED */
#ifdef _TCL76
static Tcl_File
GFPEmailChannel (instanceData, direction)
ClientData instanceData;
int direction;
{
return (Tcl_File)NULL;
}
#else
static int
GFPEmailChannel (instanceData, direction, fd)
ClientData instanceData;
int direction;
FileHandle *fd;
{
*fd = NULL;
return 0;
}
#endif
/*
*-----------------------------------------------------------------------------
*
* CRPEmailChannel --
*
* Mask is an ORed combination of TCL_READABLE, TCL_WRITABLE and
* TCL_EXCEPTION. If any of the events in the mask happened, the
* corresponding bit is maintained, otherwise it is cleared. TCL_EXCEPTION
* is not treated for the moment. An email channel is always writable. If
* more than one message was received since the last call to
* WCPEmailChannel, they will be collectively signaled as a single
* event.
* For a more complete description of this function please refer
* to the description of Tcl_CreateChannel and its associated functions
* in the Tcl 7.6 documentation. This function is described under the entry
* CHANNELREADYPROC.
*
* Results:
*
* Return the number of bytes read or -1 if an error is detected.
*
* Side effects:
*
* None.
*
*-----------------------------------------------------------------------------
*/
static int
CRPEmailChannel (instanceData, mask)
ClientData instanceData; /* (in) Pointer to EmailInfo struct. */
int mask; /* (in) ORed combination of TCL_READABLE,
* TCL_WRITABLE and TCL_EXCEPTION. It designates
* the event categories whose occurence has to
* be signalled.
*/
{
if(mask & TCL_EXCEPTION) {
mask &= ~TCL_EXCEPTION; /* ignored */
}
if(mask & TCL_READABLE) {
if(((EmailInfo *)instanceData)->available <= 0) {
int tmp;
if(PutLock(lockFile) != 0) {
return -1;
}
tmp = SetAvailable(instanceData);
RemoveLock(lockFile);
if(tmp == -1) {
/* set errno */
return -1;
}
if(((EmailInfo *)instanceData)->available <= 0) {
mask &= ~TCL_READABLE;
}
}
}
return mask;
}
/*
*-----------------------------------------------------------------------------
*
* WCPEmailChannel --
*
* This is the "watch channel" procedure for email channels.
*
* Results:
*
* None.
*
* Side effects:
*
* Sets the maximum blocking time.
*
*-----------------------------------------------------------------------------
*/
/* ARGSUSED */
static void
WCPEmailChannel (instanceData, mask)
ClientData instanceData; /* (in) Pointer to EmailInfo struct. */
int mask; /* (in) ORed combination of TCL_READABLE,
* TCL_WRITABLE and TCL_EXCEPTION. It designates
* the event categories that have to be watched.
*/
{
Tcl_Time x;
x.sec = 0;
x.usec = 0;
Tcl_SetMaxBlockTime(&x);
return;
}
/*
*-----------------------------------------------------------------------------
*
* SetAvailable --
*
* Computes the number of bytes available for a given email channel. It is
* used to give a faster answer to calls to the 'channel ready' procedure.
*
* Results:
*
* Number of bytes available for reading for a given email channel.
*
* Side effects:
*
* Updates the 'available' filed of instance data.
*
*-----------------------------------------------------------------------------
*/
static int
SetAvailable (instanceData)
ClientData instanceData; /* (in) Pointer to EmailInfo struct. */
{
FILE *fpSeek;
char *ctemp, *ctemp2;
char buffer [DP_ARBITRARY_LIMIT];
EmailInfo *data = ((EmailInfo *)instanceData);
if((fpSeek = fopen(seekFile, "r")) == NULL) {
return -1;
}
data->available = 0;
if(fgets(buffer, DP_ARBITRARY_LIMIT, fpSeek) == NULL) {
if(!feof(fpSeek)) {
goto error1;
}
}
while(!feof(fpSeek)) {
strtok(buffer, " ");
if(!strcasecmp(buffer, data->address)) {
char *ctemp, *ctemp2;
ctemp = strtok(NULL, " ");
ctemp2 = strtok(NULL, " ");
data->available += (atol(ctemp) - atol(ctemp2));
}
if(fgets(buffer, DP_ARBITRARY_LIMIT, fpSeek) == NULL) {
if(!feof(fpSeek)) {
goto error1;
}
}
}
fclose(fpSeek);
return 0;
error1:
fclose(fpSeek);
return -1;
}
/*
* OutputEmailChannel --
*
* Sends an email message on the email channel represented by instanceData.
* For a more complete description of this function please refer
* to the description of Tcl_CreateChannel and its associated functions
* in the Tcl 7.6 documentation. This function is described under the entry
* OUTPUTPROC.
*
* Result:
*
* 0 if everything went OK, -1 if an error was detected.
*
* Side effects:
*
* None.
*/
static int
OutputEmailChannel (instanceData, buf, toWrite, errorCodePtr)
ClientData instanceData; /* (in) Pointer to EmailInfo struct. */
char *buf; /* (in) Buffer to write. */
int toWrite; /* (in) Number of bytes to write. */
int *errorCodePtr; /* (out) POSIX error code (if any). */
{
char *format = "/usr/lib/sendmail %.*s >/dev/null";
char *subject = "Subject: email channel\n";
char *sequence = "Sequence: %ld\n";
char *finish = "\n.\n";
FILE *pipeFilePtr;
int error = 0;
char buffer [DP_ARBITRARY_LIMIT + 1];
char seqBuffer [DP_ARBITRARY_LIMIT + 1];
EmailInfo *data = ((EmailInfo *)instanceData);
sprintf(buffer, format, sizeof(buffer) - strlen(format), data->address);
if((pipeFilePtr = popen(buffer, "w")) == NULL) {
*errorCodePtr = ECHILD;
return -1;
}
error += (fwrite(subject, sizeof(char), strlen(subject), pipeFilePtr)
!= strlen(subject));
if(data->sequence != 0) {
sprintf(seqBuffer, sequence, (data->sequenceNo)++);
error += (fwrite(sequence, sizeof(char), strlen(sequence), pipeFilePtr)
!= strlen(sequence));
}
error += (fwrite(buf, sizeof(char), toWrite, pipeFilePtr) != toWrite);
error += (fwrite(finish, sizeof(char), strlen(finish), pipeFilePtr)
!= strlen(finish));
error += (pclose(pipeFilePtr) != 0);
if(error) {
*errorCodePtr = ECHILD;
return -1;
}
return toWrite;
}
/*
*-----------------------------------------------------------------------------
*
* InputEmailChannel --
*
* This is a wrap function around ReadFromSpool. It provides the interface
* required by tcl for reading from the email channel.
*
* Results:
*
* Number of bytes of data read from the spool file. If an error
* happened, it returns -1.
*
* Side effects:
*
* Modifies the files associated with the email channels.
*
*-----------------------------------------------------------------------------
*/
static int
InputEmailChannel (instanceData, buf, bufsize, errorCodePtr)
ClientData instanceData; /* (in) Pointer to EmailInfo struct. */
char *buf; /* (in/out) Buffer to fill. */
int bufsize; /* (in) Size of buffer. */
int *errorCodePtr; /* (out) POSIX error code (if any). */
{
int tmp;
EmailInfo *data = ((EmailInfo *)instanceData);
if(PutLock(lockFile) != 0) {
return EAGAIN;
}
if(data->available == 0) {
SetAvailable(instanceData);
}
if(data->available == 0) {
*errorCodePtr = EAGAIN;
RemoveLock(lockFile);
return 0;
}
tmp = ReadFromSpool(data->address, buf, bufsize, data->border, data->peek);
if(tmp > 0) {
data->available -= tmp;
}
if(tmp == -1) {
*errorCodePtr = EINVAL;
} else if(tmp == 0) {
*errorCodePtr = EAGAIN;
}
if(garbageInSpoolFile > DP_GARBAGE_LIMIT) {
if(CleanUpFiles() == -1) {
/*
* An error happened while trying to eliminate garbage the
* garbage from the seek and spool files.
*/
*errorCodePtr = EIO;
tmp = -1;
}
}
RemoveLock(lockFile);
return tmp;
}
/*
*-----------------------------------------------------------------------------
*
* SOPEmailChannel --
*
* This function is called by the Tcl channel driver
* whenever Tcl evaluates an fconfigure call to set
* some property of the email channel. Currently, only
* "-peek" is accepted.
*
* Results:
* Standard Tcl return value.
*
* Side effects:
*
* Depends on the option.
*
*-----------------------------------------------------------------------------
*/
static int
SOPEmailChannel (instanceData, interp, optionName, optionValue)
ClientData instanceData; /* (in) Pointer to EmailInfo struct. */
Tcl_Interp *interp; /* (in) Pointer to tcl interpreter. */
char *optionName;
char *optionValue;
{
int option;
int value;
/*
* Set the option specified by optionName.
*/
if (optionName[0] == '-') {
option = DpTranslateOption(optionName+1);
} else {
option = -1;
}
switch(option) {
case DP_PEEK:
if (Tcl_GetBoolean(interp, optionValue, &value) != TCL_OK) {
return TCL_ERROR;
}
((EmailInfo *)instanceData)->peek = value;
break;
case DP_SEQUENCE:
if (Tcl_GetBoolean(interp, optionValue, &value) != TCL_OK) {
return TCL_ERROR;
}
((EmailInfo *)instanceData)->sequence = value;
break;
case DP_ADDRESS:
Tcl_AppendResult(interp, "can't set address after email channel ",
"is opened", NULL);
return TCL_ERROR;
case DP_IDENTIFIER:
Tcl_AppendResult(interp, "can't set identifier after email channel ",
"is opened", NULL);
return TCL_ERROR;
default:
Tcl_AppendResult (interp, "illegal option \"", optionName,
"\" -- must be peek, or a standard fconfigure option", NULL);
return TCL_ERROR;
}
return TCL_OK;
}
/*
*-----------------------------------------------------------------------------
*
* GOPEmailChannel --
*
* This function is called by the Tcl channel driver to
* retrieve parameters that are associated with the email channel.
* The only accepted option is "-peek".
*
* Results:
*
* A standard Tcl result.
*
* Side effects:
*
* None.
*
*-----------------------------------------------------------------------------
*/
static int
GOPEmailChannel (instanceData,
#ifndef _TCL76
interp,
#endif
optionName, dsPtr)
ClientData instanceData; /* (in) Pointer to EmailInfo struct. */
#ifndef _TCL76
Tcl_Interp *interp;
#endif
char *optionName;
Tcl_DString *dsPtr; /* (out) String to store the result in. */
{
int option;
int size;
char str[256];
/*
* If optionName is NULL, then store an alternating list of
* all supported options and their current values in dsPtr.
*/
#ifdef _TCL80
#define IGO(a, b, c) GOPEmailChannel(a, interp, b, c)
#else
#define IGO(a, b, c) GOPEmailChannel(a, b, c)
#endif
if (optionName == NULL) {
Tcl_DStringAppend (dsPtr, " -address ", -1);
IGO (instanceData, "-address", dsPtr);
Tcl_DStringAppend (dsPtr, " -identifier ", -1);
IGO (instanceData, "-identifier", dsPtr);
Tcl_DStringAppend (dsPtr, " -peek ", -1);
IGO (instanceData, "-peek", dsPtr);
Tcl_DStringAppend (dsPtr, " -sequence ", -1);
IGO (instanceData, "-sequence", dsPtr);
return TCL_OK;
}
#undef IGO
/*
* Retrieve the value of the option specified by optionName
*/
if (optionName[0] == '-') {
option = DpTranslateOption(optionName+1);
} else {
option = -1;
}
switch (option) {
case DP_PEEK:
if (((EmailInfo *)instanceData)->peek) {
Tcl_DStringAppend (dsPtr, "1", -1);
} else {
Tcl_DStringAppend (dsPtr, "0", -1);
}
break;
case DP_SEQUENCE:
if (((EmailInfo *)instanceData)->sequence) {
Tcl_DStringAppend (dsPtr, "1", -1);
} else {
Tcl_DStringAppend (dsPtr, "0", -1);
}
break;
case DP_ADDRESS:
Tcl_DStringAppend (dsPtr, ((EmailInfo *)instanceData)->address, -1);
break;
case DP_IDENTIFIER:
{
char tmp [DP_ARBITRARY_LIMIT + 1];
sprintf(tmp, "%d", gIdentifier);
Tcl_DStringAppend (dsPtr, tmp, -1);
}
break;
default:
Tcl_SetErrno (EINVAL);
return TCL_ERROR;
}
return TCL_OK;
}