|
|
DataMuseum.dkPresents historical artifacts from the history of: DKUUG/EUUG Conference tapes |
This is an automatic "excavation" of a thematic subset of
See our Wiki for more about DKUUG/EUUG Conference tapes Excavated with: AutoArchaeologist - Free & Open Source Software. |
top - metrics - downloadIndex: T s
Length: 48309 (0xbcb5)
Types: TextFile
Names: »spool.c«
└─⟦9ae75bfbd⟧ Bits:30007242 EUUGD3: Starter Kit
└─⟦2fafebccf⟧ »EurOpenD3/mail/smail3.1.19.tar.Z«
└─⟦bcd2bc73f⟧
└─⟦this⟧ »src/spool.c«
/* @(#)spool.c 3.38 12/8/88 19:49:50 */
/*
* Copyright (C) 1987, 1988 Ronald S. Karr and Landon Curt Noll
*
* See the file COPYING, distributed with smail, for restriction
* and warranty information.
*/
/*
* spool.c:
* message spooling and retrieval. This source file implements a
* reliable spooling system which is resiliant against inaccessible
* directories, create errors and write errors. The algorithms here
* are set up such that alternate directories can be used in the case
* that smail is not able to complete spooling to a primary spool
* directory.
*
* NOTE: This section will probably require substantial
* modifications to work with a non-UN*X operating
* system.
*
* external functions: creat_spool, write_spool, open_spool,
* close_spool, unlink_spool, seek_spool,
* tell_spool, send_spool, read_spool,
* log_spool_errors, new_grade, defer_message,
* message_date
*/
#include <stdio.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include "defs.h"
#include "smail.h"
#include "spool.h"
#include "transport.h"
#include "log.h"
#include "dys.h"
#include "exitcodes.h"
#ifndef DEPEND
# include "debug.h"
# include "extern.h"
#endif
#ifdef UNIX_BSD
# include <sys/time.h>
#else /* not UNIX_BSD */
# include <time.h>
#endif /* not UNIX_BSD */
#ifdef UNIX_SYS5
# include <fcntl.h>
#endif
#ifdef UNIX_BSD
# include <sys/file.h>
#endif
#ifdef STANDALONE
# define xmalloc malloc
# define xfree free
extern char *malloc();
int force_write_error = FALSE;
#endif /* STANDALONE */
/* variables exported from this file */
char *message_id = NULL; /* unique ID for this message */
char *spool_dir; /* directory used to spool message */
char *spool_fn = NULL; /* basename of open spool file */
char *input_spool_fn = NULL; /* name in input/ directory */
int spoolfile = -1; /* open spool file */
char *lock_fn; /* name of lock file for spool_fn */
char *msg_buf; /* i/o buffer for spool file */
char *msg_ptr; /* read placeholder in msg_buf */
char *msg_max; /* last valid char in msg_buf */
char *end_msg_buf; /* end of msg_buf */
long msg_foffset; /* file offset for msg_buf contents */
long msg_size; /* size of spool file */
/* variables local to this file */
static char *temp_fn = NULL; /* temp spool file name */
static char *funct_name; /* name of current function */
static char *old_spool_dir; /* saved value of spool_dir */
static char *old_spool_fn; /* saved value of spool_fn */
static char *old_input_spool_fn; /* saved value of input_spool_fn */
static int old_spoolfile; /* saved value of spoolfile */
static struct log_msgs {
struct log_msgs *succ;
} *log_msgs; /* message lines to send to log */
static struct log_msgs **next_log_msg; /* where to put next log message */
static enum locker { l_creat, l_open, l_lock }; /* who is trying to lock */
/* functions local to this file */
static int int_creat_spool();
static void build_spool_fn();
static void build_message_id();
static int set_alt_spool();
static int lock_spoolfile();
static void copy_old_names();
static void failed_write();
static int copy_old_spool();
static void log_message();
/* functions imported from libc */
long lseek();
struct tm *localtime();
struct tm *gmtime();
char *ctime();
/* trap undefined open flags */
#ifndef O_RDONLY
# define O_RDONLY 0
#endif
#ifndef O_WRONLY
# define O_WRONLY 1
#endif
#ifndef O_RDWR
# define O_RDWR 2
#endif
\f
/*
* creat_spool - create a spool file
*
* This is external entrypoint for spool file creation. It initializes some
* state and then calls int_creat_spool() to do the real work.
*
* If spoolfile creation succeeded, return SUCCEED. Also, the following
* external variables will be set:
*
* spool_dir - the directory in which the spoolfile was created
* spool_fn - the basename of the generated file
* input_spool_fn - the name of the spool file in the input directory
* spoolfile - the file descriptor (used by the PUTSPOOL macro)
* message_id - a unique message ID computed from the filename
* msg_buf,msg_ptr - points to a region in which the message can be
* written into memory
*
* In addition, the current directory will be set to spool_dir, and the file
* will be locked against premature attempts at delivery by background
* processes.
*
* If spoolfile creation failed, return FAIL. In this case, spool_fn,
* input_spool_fn, spool_dir and message_id will all be set to NULL.
*/
int
creat_spool()
{
DEBUG(DBG_SPOOL_HI, "create_spool called\n");
funct_name = "creat_spool";
temp_fn = NULL;
spool_fn = NULL;
input_spool_fn = NULL;
message_id = NULL;
lock_fn = NULL;
spoolfile = -1;
log_msgs = NULL;
next_log_msg = &log_msgs;
msg_foffset = 0;
msg_size = 0;
if (msg_buf == NULL) {
/* allocate the spool i/o buffer if it does not yet exist */
msg_buf = xmalloc(message_bufsiz);
}
msg_max = msg_buf; /* start at beginning of buffer */
msg_ptr = msg_buf;
end_msg_buf = msg_buf + message_bufsiz;
if (int_creat_spool() == FAIL) {
DEBUG(DBG_SPOOL_LO, "creat_spool failed!\n");
return FAIL;
}
DEBUG2(DBG_SPOOL_LO, "new spool file is %s/%s\n",
spool_dir, input_spool_fn);
return SUCCEED;
}
/*
* int_creat_spool - create a spool file (internal form)
*
* try to create a spool file in either the primary or alternate spool
* directory. The file will be locked and empty at the end of creat_spool.
*
* return SUCCEED or FAIL. If FAIL, then the spool file and lock file
* will not exist unless they could not be unlinked.
* log_spool_errors should be called once the log files are opened, as
* well.
*/
static int
int_creat_spool()
{
int attempts = 0; /* # open attempts in a directory */
char build_temp_fn[sizeof("input/msg.dddddddddd")];
/* first try the primary spool directory */
spool_dir = NULL;
/* get the first "alternate" directory, which is the primary directory */
if (set_alt_spool((char *)NULL) == FAIL) {
return FAIL;
}
/*
* create the temporary spool file in the first spool directory
* that the open succeeds in.
*/
(void) sprintf((temp_fn = build_temp_fn), "input/msg.%d", getpid());
for (;;) {
/*
* unfortunately, the errno returned by creat(2) does not
* distinguish between directory and file access errors, so
* we use the open call, which does, for systems that
* support it
*/
#ifdef O_EXCL
spoolfile = open(temp_fn, O_RDWR|O_CREAT|O_EXCL, spool_mode);
#else /* O_EXCL */
/*
* if we must use creat, then the mode should not allow writing.
* As well, we will have to reopen for reading later on in
* read_spool, if we try to read on the file without closing
* and then opening again.
*/
spoolfile = creat(temp_fn, spool_mode&(~0222));
if (spoolfile < 0 && errno == EACCES) {
/* we assume that EACCES does not represent a directory error */
errno = EEXIST;
}
#endif /* O_EXCL */
if (spoolfile < 0 && errno == ENOENT) {
/* NOENT means the directory did not exist, try to make it */
DEBUG1(DBG_SPOOL_LO, "make directory %s/input\n", spool_dir);
(void) mkdir("input", auto_mkdir_mode);
#ifdef O_EXCL
spoolfile = open(temp_fn, O_RDWR|O_CREAT|O_EXCL, spool_mode);
#else
spoolfile = creat(temp_fn, spool_mode&(~0222));
if (spoolfile < 0 && errno == EACCES) {
errno = EEXIST;
}
#endif
}
if (spoolfile < 0) {
if (errno == EEXIST && attempts == 0) {
/* if the spool file existed, then try to unlink it */
DEBUG2(DBG_SPOOL_MID,
"int_creat_spool: %s/input/%s exists, unlinking\n",
spool_dir, temp_fn);
attempts++; /* only attempt unlink once */
continue;
}
/* otherwise we need to try an alternate spool directory */
if (set_alt_spool("cannot create spool file") == FAIL) {
return FAIL;
}
attempts = 0; /* new directory, reset try count */
continue;
}
/*
* we have an open temp file. Now create a lock file
* and move the temp file to its more permanent name.
*/
build_spool_fn(spool_grade);
#ifndef lock_fd
if (lock_spoolfile(l_creat) == FAIL) {
/* we need to try an alternate directory */
(void) close(spoolfile);
(void) unlink(temp_fn);
if (set_alt_spool("cannot lock message") == FAIL) {
return FAIL;
}
continue;
}
#else /* lock_fd */
if ((lock_by_name && lock_spoolfile(l_creat) == FAIL) ||
(! lock_by_name && lock_fd(spoolfile) == FAIL))
{
(void) close(spoolfile);
(void) unlink(temp_fn);
if (set_alt_spool("cannot lock message") == FAIL) {
return FAIL;
}
continue;
}
#endif /* lock_fd */
if (rename(temp_fn, input_spool_fn) < 0) {
/* why would rename fail? */
if (lock_by_name) {
(void) unlink(lock_fn);
}
(void) close(spoolfile);
(void) unlink(temp_fn);
if (set_alt_spool("failed to rename temp file") == FAIL) {
return FAIL;
}
continue;
}
/* we have a valid locked spool file */
return SUCCEED;
}
}
/*
* build_spool_fn - form the spool filename
*
* as a side effect, generate the lock filename, if needed, and
* generate the message_id.
*
* the grade_char represents a priority which is appended to the
* filename.
*/
static void
build_spool_fn(grade_char)
int grade_char;
{
/* store the actual spool file basename here */
static char fn[SPOOL_FN_LEN + 1];
/* name of spool file in input directory */
static char ifn[sizeof("input/") + SPOOL_FN_LEN];
/* store the lock file name here */
static char lfn[sizeof("lock/") + SPOOL_FN_LEN];
extern long time();
long clock = time((long *)0);
struct stat statbuf;
char a_inode[8]; /* ASCII base 62 inode number */
/* get the inode number in base 62 information */
(void) fstat(spoolfile, &statbuf);
(void) strcpy(a_inode, base62((long)statbuf.st_ino));
/* this is 14 chars which should work on all UN*X systems */
spool_fn = fn;
(void) sprintf(spool_fn, "%s-%s%c", base62(clock), a_inode, grade_char);
/* use this to build the path in the input directory */
input_spool_fn = ifn;
(void) sprintf(input_spool_fn, "input/%s", spool_fn);
/* have the message-ID computed from the computed spool file name */
build_message_id();
if (lock_by_name) {
lock_fn = lfn;
(void) sprintf(lock_fn, "lock/%s", spool_fn);
}
DEBUG2(DBG_SPOOL_HI, "build_spool_fn: try spool file %s/%s\n",
spool_dir, input_spool_fn);
}
/*
* build_message_id - build message_id from the value of spool_fn
*
* the only difference is that the message-Id begins with the letter `m', to
* make it a valid local-addr vis-a-vis RFC822.
*/
static void
build_message_id()
{
static char m_id[sizeof("m") + SPOOL_FN_LEN];
message_id = m_id;
(void) sprintf(message_id, "m%s", spool_fn);
}
\f
/*
* lock_message - lock the current message
*
* When forking a process to deliver mail, locks are released by the
* parent process and then regained in the child process by calling
* lock_message(). If the lock cannot be regained then the child
* process should exit, assuming that some other process has decided
* to attempt delivery. This method is necessary when using lock
* files, as pids are used to determine whether the locking process
* still exists. It is also necessary when using the System V lockf
* call as this does not preserve locks in a child of the process that
* made the lock.
*
* Returns SUCCEED if the lock was obtained, FAIL otherwise.
*/
int
lock_message()
{
struct stat statbuf;
int success;
long offset;
#ifdef lock_fd
if (lock_by_name) {
success = lock_spoolfile(l_lock);
} else {
offset = lseek(spoolfile, 0L, 1);
(void) lseek(spoolfile, 0L, 0);
success = lock_fd(spoolfile);
(void) lseek(spoolfile, offset, 0);
}
#else
success = lock_spoolfile(l_lock);
#endif
if (success == FAIL) {
return FAIL;
}
(void) fstat(spoolfile, &statbuf);
/*
* If the file has been removed, then don't process it.
*/
if (statbuf.st_nlink == 0) {
DEBUG2(DBG_SPOOL_MID,
"open_spool: %s/%s: spool file was removed\n",
spool_dir, input_spool_fn);
close_spool();
return FAIL;
}
return SUCCEED;
}
/*
* unlock_message - unlock the current message
*
* The parent process calls this before forking a process to perform
* delivery. See lock_message() for an explanation.
*/
void
unlock_message()
{
long offset;
#ifdef lock_fd
if (lock_by_name) {
#endif
if (lock_fn) {
DEBUG2(DBG_SPOOL_HI, "close_spool: unlinking %s/lock/%s\n",
spool_dir, lock_fn);
(void) unlink(lock_fn);
}
#ifdef lock_fd
} else {
offset = lseek(spoolfile, 0L, 1);
(void) lseek(spoolfile, 0L, 0);
unlock_fd(spoolfile);
(void) lseek(spoolfile, offset, 0);
}
#endif
}
/*
* lock_spoolfile - create a lock file for the spool file
*
* algorithm:
* 1. create lock file exclusively. If this fails, goto step 3.
* 2. write ASCII process id, followed by newline. If this succeeds,
* then the lock succeeded, close the file and return.
* 3. stat the lock file
* 4. if st_size != 0, the proceed to step 10
* 5. The lock file is empty because the locking process has
* not yet written out its process id, or because the
* process or system crashed prior to the write.
* If the current time < Oct 28, 1986, then proceed to step 7.
* If st_ctime > the current time then proceed to step 8.
* If st_ctime < `now' - 2 hours proceed to step 9.
* 6. The empty lock file is too new to touch (the system could
* be very heavely loaded) and so we simply conclude that
* some process has it locked and go on to do something else.
* 7. Since this version of smail did not exist on Oct 28, 1986,
* the current time must be set wrong. At this point
* we give up all hope of stale lock file detection,
* consider the file locked and go on to do something else.
* 8. The empty lock file was created in the future so we don't
* know if the empty lock file is stale or not. We first
* bring the st_ctime of the file back into reality by
* doing a chmod (to the same permission). This forces
* the st_ctime to be set to `now'. Next we send the
* following error message to the system log:
*
* time warp on zero length lock file: lock/0571338010a72y
*
* We consider the lock file active for now.
* 9. The empty lock file is very likely stale. We will
* remove the lock file but we will still consider it
* locked, allowing some future process to operate
* 10. The lock file contains the process id of the locking
* process. We open the lock file and read it to find
* the associated pid.
* If pid == 0, then proceed to step 13.
* If pid is not a process, then proceed to step 12.
* 11. The process pid is a valid and currently
* running process, and thus the lock file is really
* valid. At this point we go on to do something else.
* 12. The lock file is very likely stale. We will remove the
* lock file but we will still consider it locked, allowing
* some future process to operate on the spool file. We
* now go on to do something else.
* 13. If pid == 0, then the lock file is a
* permanent lock file. (perhaps set by the system
* administrator to freeze a mail message) At this
* point we go on to do something else.
*
* return either SUCCEED or FAIL, and set errno to EEXIST if
* the spoolfile is already locked.
*/
static int lock_creat();
static int check_empty_lock();
static int verify_lock();
static int
lock_spoolfile(who)
enum locker who; /* who is locking, open or creat */
{
struct stat statbuf; /* temp buf for stats */
int attempts = 0;
int mkdir_tried = FALSE; /* TRUE if mkdir("lock") tried */
DEBUG1(DBG_SPOOL_HI, "lock_spoolfile called, lock_fn = %s\n", lock_fn);
/*
* loop until we have a lock or we fail to get a lock
*/
for (;;) {
/*
* try to create one
*/
if (lock_creat() == FAIL) {
if (errno == ENOENT && !mkdir_tried && auto_mkdir) {
mkdir_tried = TRUE;
DEBUG1(DBG_SPOOL_LO, "make directory %s/lock\n",
spool_dir);
(void) mkdir("lock", auto_mkdir_mode);
continue;
}
if (errno != EEXIST) {
/* it failed, but not because it exists, can't handle this */
DEBUG1(DBG_SPOOL_HI, "create failed: %s\n", strerrno());
return FAIL;
}
if (who == l_creat) {
/*
* for creat_spool, no lock file should exist because
* of inode uniqueness on a filesystem. Just unlink
* the lock file and try again (once).
*/
if (attempts == 0) {
(void) unlink(lock_fn);
attempts++;
continue;
}
DEBUG3(DBG_SPOOL_MID,
"lock_spoolfile: %s/lock/%s: lock failed: %s\n",
spool_dir, lock_fn, strerrno());
return FAIL;
}
} else {
/* the create worked, great! */
return SUCCEED;
}
if (stat(lock_fn, &statbuf) < 0) {
/* failed to stat the file, hmmm */
DEBUG3(DBG_SPOOL_LO,
"lock_spoolfile: %s/lock/%s: stat failed: %s\n",
spool_dir, lock_fn, strerrno());
return FAIL;
}
if (statbuf.st_size == 0) {
if (check_empty_lock(&statbuf) == SUCCEED) {
errno = EEXIST; /* lock was upheld, for now */
return FAIL;
}
} else {
if (verify_lock() == SUCCEED) {
errno = EEXIST; /* lock was upheld */
return FAIL;
}
}
/*
* we consider the lock to be stale, so unlink it,
* and let a future process try again.
*/
(void) unlink(lock_fn);
DEBUG2(DBG_SPOOL_LO,
"lock_spoolfile: %s/lock/%s: stale lock file removed\n",
spool_dir, lock_fn);
errno = EEXIST;
return FAIL;
}
}
/*
* lock_creat - try to create a lock and write into it the current pid
*
* this is only called from lock_spoolfile and returns SUCCEED or FAIL.
*/
static int
lock_creat()
{
int lfd;
char apid[BITS_PER_INT/3 + 2]; /* should hold the process id */
int ct;
#ifdef O_EXCL
lfd = open(lock_fn, O_RDWR|O_CREAT|O_EXCL, lock_mode);
#else /* O_EXCL */
/*
* it is time for the silly creat trick again
*/
lfd = creat(lock_fn, lock_mode&(~0222));
if (lfd < 0 && errno == EACCES) {
errno = EEXIST;
}
#endif /* O_EXCL */
if (lfd < 0) {
/* we failed to creat the lock the file */
return FAIL;
}
(void) sprintf(apid, "%u\n", getpid());
ct = strlen(apid);
if (write(lfd, apid, ct) < ct) {
/* we failed to write the pid into the lock file, quit */
(void) unlink(lock_fn);
(void) close(lfd);
return FAIL;
}
(void) close(lfd);
return SUCCEED;
}
/*
* check_empty_lock - decide if zero-length lock file is stale
*
* return SUCCEED if not stale and should be kept, FAIL otherwise.
*/
static int
check_empty_lock(stp)
struct stat *stp; /* statbuf from lock_spoolfile */
{
struct tm *ctm;
extern long time();
long now = time((long *)0); /* verify based on age */
ctm = gmtime(&now);
if (ctm->tm_year <= 86 && ctm->tm_mon <= 10 && ctm->tm_mday < 28) {
/*
* it is before chongo's birthday, but on a year that must be
* in the past. Since this version of smail has not been
* written yet, punt.
*/
write_log(LOG_SYS|LOG_CONS, "machine is in a time warp, check date");
return SUCCEED;
}
if (stp->st_ctime > now) {
/*
* file created in the future, bring it back to the present, but
* otherwise assume it is a valid lock.
*/
(void) chmod(lock_fn, stp->st_mode);
write_log(LOG_SYS, "time warp on zero length lock file: %s", lock_fn);
return SUCCEED;
}
if (stp->st_ctime < now - 2*3600/*two hours in seconds*/) {
/* lock not upheld, file can be removed */
return FAIL;
}
/* a new lock file, leave it alone for now */
DEBUG(DBG_SPOOL_HI, "new zero-length lock file, let it stand\n");
return SUCCEED;
}
/*
* verify_lock - decide if non-zero lockfile is stale
*
* return SUCCEED if not stale and should be kept, FAIL otherwise.
*/
static int
verify_lock()
{
int lfd;
char rpid[BITS_PER_INT/3 + 2];
int pid = 0;
int ct;
lfd = open(lock_fn, O_RDONLY);
if (lfd < 0) {
/* failed to open the lock file, don't consider it stale yet */
DEBUG3(DBG_SPOOL_HI, "verify_lock: %s/lock/%s: %s\n",
spool_dir, lock_fn, strerrno());
return SUCCEED;
}
/* read in the pid */
ct = read(lfd, rpid, sizeof(rpid)-1);
if (ct <= 0) {
/* failed to read anything from the lock file, ignore it for now */
(void) close(lfd);
DEBUG3(DBG_SPOOL_HI, "verify_lock: %s/lock/%s: read faile: %s\n",
spool_dir, lock_fn, strerrno());
return SUCCEED;
}
(void) close(lfd);
rpid[ct] = '\0'; /* firewall */
(void) sscanf(rpid, "%d", &pid);
if (pid == 0) {
/* pid is 0 or not a valid number, consider the lock valid */
DEBUG(DBG_SPOOL_HI, "verify_lock: pid==0 lock considered valid\n");
return SUCCEED;
}
/*
* does the process exist? before you look at the man page,
* kill(pid,0) detects process existence on all versions of
* UN*X I know of including v6 and v7
*/
if (kill(pid, 0) < 0 && errno == ESRCH) {
/* process does not exist, lock is stale */
return FAIL;
}
/* the lock is valid */
DEBUG2(DBG_SPOOL_HI, "verify_lock: %s/lock/%s: valid lock\n",
spool_dir, lock_fn);
return SUCCEED;
}
\f
/*
* write_spool - write completed block to the spool file
*
* write the contents of the spool i/o buffer out to the spool file,
* at the current i/o position. But otherwise don't change anything.
*
* If the write fails then try to create an alternate spool file and
* copy into it the contents previously written to the old spool file.
*
* NOTE: writing to the spool file should be done through the macro
* PUTSPOOL(c) defined in spool.h, except that the final write
* should be done with a call to write_spool. PUTSPOOL will
* manage the various associated pointers.
*/
int
write_spool()
{
old_spool_fn = NULL; /* set to old name if new created */
old_input_spool_fn = NULL;
old_spool_dir = NULL;
old_spoolfile = spoolfile;
funct_name = "write_spool";
DEBUG(DBG_SPOOL_HI, "write_spool called\n");
/*
* loop until we have successfully written the block to something,
* or until we cannot recover from past errors
*/
for (;;) {
if (write(spoolfile, msg_buf, msg_max - msg_buf) == msg_max - msg_buf
#ifdef STANDALONE
/* it is a bit tough to get random write errors while testing */
&& (!force_write_error || (force_write_error = FALSE))
#endif /* STANDALONE */
)
{
break;
} else {
/*
* write failed, unlink the old spool file and copy contents
* to a new spool file.
*/
copy_old_names();
failed_write();
for (;;) {
/* get an alternate spool directory */
if (set_alt_spool("write to spool failed") == FAIL) {
(void) close(old_spoolfile);
DEBUG(DBG_SPOOL_LO,
"write_spool: no more spool dirs, write failed\n");
return FAIL;
}
if (int_creat_spool() == FAIL) {
(void) close(old_spoolfile);
DEBUG(DBG_SPOOL_LO,
"write_spool: can't create new spool file, write failed\n");
return FAIL;
}
if (msg_size == 0) {
break;
} else {
int code = copy_old_spool();
if (code == READ_FAIL) {
DEBUG(DBG_SPOOL_LO,
"write_spool: failed to copy old spool file: read failed\n");
return FAIL;
} else if (code == SUCCEED) {
break;
}
DEBUG(DBG_SPOOL_LO,
"write_spool: failed to copy old spool file: write failed\n");
}
}
}
}
if (old_spool_fn) {
/* we succeeded in copying to a new spool file, don't need these */
xfree(old_spool_fn);
xfree(old_input_spool_fn);
xfree(old_spool_dir);
}
msg_size += msg_max - msg_buf;
return SUCCEED;
}
/*
* copy_old_names - make a copy of the old spool file names
*/
static void
copy_old_names()
{
if (old_spool_fn == NULL) {
/* save a copy of the old names if needed later */
old_spool_fn = COPY_STRING(spool_fn);
old_input_spool_fn = COPY_STRING(input_spool_fn);
old_spool_dir = COPY_STRING(spool_dir);
}
}
/*
* failed_write - remove old files after a write error
*
* NOTE: we aren't closing them, so the data in them can still be read.
*/
static void
failed_write()
{
if (spool_fn) {
(void) unlink(input_spool_fn);
spool_fn = NULL;
input_spool_fn = NULL;
}
if (lock_fn) {
(void) unlink(lock_fn);
}
}
/*
* copy_old_spool - copy old spool file to the new one
*
* return READ_FAIL on read error, SUCCEED on success, and
* WRITE_FAIL on write error
*/
static int
copy_old_spool()
{
char *tempbuf = xmalloc(message_bufsiz);
int copied = 0; /* count of chars copied to new file */
/*
* previous contents exist, we will need to copy them
*/
(void) lseek(old_spoolfile, 0L, 0);
while (copied < msg_size) {
register int ct;
/* fill up as much of the temp buffer as reasaonble */
if (msg_size - copied < message_bufsiz) {
ct = msg_size - copied;
} else {
ct = message_bufsiz;
}
ct = read(old_spoolfile, tempbuf, ct);
if (ct <= 0) {
failed_write();
log_message("read failed in copying to new file");
(void) close(spoolfile);
(void) close(old_spoolfile);
return READ_FAIL; /* read error - can't recover */
} else {
if (write(spoolfile, tempbuf, ct) < ct) {
failed_write();
return WRITE_FAIL; /* write error - maybe can recover */
}
}
copied += ct;
}
return SUCCEED; /* we have recovered */
}
\f
/*
* open_spool - open and possibly lock the given spool file
*
* the filename is assumed to be absolute, so a chdir is done to
* the directory name, and spool_fn is set to the basename. This
* process is destructive of the spool file string passed.
*
* As a side effect, msg_buf will be loaded with the first part of the
* spool file.
*
* return SUCCEED or FAIL. If fail, load error with error message.
* errno will be 0 if no system error was involved.
*/
int
open_spool(fn, lock, error)
char *fn; /* basename of spool file */
int lock; /* if TRUE, lock the file */
char **error; /* return error message here */
{
static char lfn[sizeof("lock/") + SPOOL_FN_LEN];
struct stat statbuf;
DEBUG1(DBG_SPOOL_HI, "open_spool(%s) called\n", fn);
funct_name = "open_spool";
message_id = NULL;
lock_fn = NULL;
spoolfile = -1;
log_msgs = NULL;
msg_foffset = 0;
msg_size = 0;
errno = 0;
if (msg_buf == NULL) {
/* allocate the spool i/o buffer if it does not yet exist */
msg_buf = xmalloc(message_bufsiz);
}
msg_max = msg_buf; /* start at beginning of buffer */
end_msg_buf = msg_buf + message_bufsiz;
if (fn[0] != '/') {
DEBUG1(DBG_SPOOL_LO, "open_spool: %s: relative pathname!\n", fn);
*error = "filename not absolute";
return FAIL;
}
spool_dir = fn; /* get directory from fn */
spool_fn = rindex(fn, '/'); /* get basename from fn */
input_spool_fn = spool_fn - sizeof("/input") + 1;
spool_fn++;
if (input_spool_fn < spool_dir ||
strncmp(input_spool_fn, "/input", sizeof("/input") - 1))
{
DEBUG1(DBG_SPOOL_LO, "open_spool failed: %s: invalid spool filename",
spool_dir);
*error = "invalid spool filename";
return FAIL;
}
*input_spool_fn++ = '\0';
if (chdir(spool_dir) < 0) {
DEBUG2(DBG_SPOOL_LO, "open_spool failed: %s: chdir failed: %s",
spool_dir, strerrno());
*error = "chdir failed";
return FAIL;
}
/*
* attempt to open the file
*
* some of the OS locking algorithms require that the file be
* opened for writing. For such systems, the spool_mode must have
* at least ownership write permission.
*/
#ifdef LOCK_REQUIRES_WRITE
if (lock && ! lock_by_name) {
spoolfile = open(input_spool_fn, O_RDWR);
} else
#endif
{
spoolfile = open(input_spool_fn, O_RDONLY);
}
if (spoolfile < 0) {
DEBUG3(DBG_SPOOL_LO, "open_spool: %s/%s: open failed: %s\n",
spool_dir, input_spool_fn, strerrno());
*error = "open failed";
return FAIL;
}
if (lock) {
#ifndef lock_fd
/* only lock_by_name is possible */
(void) sprintf(lfn, "lock/%s", spool_fn);
lock_fn = lfn;
if (lock_spoolfile(l_open) == FAIL) {
DEBUG2(DBG_SPOOL_MID, "open_spool: %s/%s: lock failed\n",
spool_dir, input_spool_fn);
(void) close(spoolfile);
*error = "lock failed";
return FAIL;
}
#else /* lock_fd */
if (lock_by_name) {
(void) sprintf(lfn, "lock/%s", spool_fn);
lock_fn = lfn;
if (lock_spoolfile(l_open) == FAIL) {
DEBUG2(DBG_SPOOL_MID, "open_spool: %s/%s: lock failed\n",
spool_dir, input_spool_fn);
(void) close(spoolfile);
*error = "lock failed";
return FAIL;
}
} else {
if (lock_fd(spoolfile) == FAIL) {
DEBUG2(DBG_SPOOL_MID, "open_spool: %s/%s: lock failed\n",
spool_dir, input_spool_fn);
(void) close(spoolfile);
*error = "lock failed";
return FAIL;
}
}
#endif /* lock_fd */
}
/*
* Get statistics on spool file.
*/
(void) fstat(spoolfile, &statbuf);
/*
* If the file has been removed, then don't process it.
*/
if (statbuf.st_nlink == 0) {
DEBUG2(DBG_SPOOL_MID,
"open_spool: %s/%s: spool file was removed\n",
spool_dir, input_spool_fn);
close_spool();
return FAIL;
}
/*
* now that the file itself is locked, we need to setup the
* message_id and initialize the msg_buf pointers so that
* read_spool will be called the first time the GETSPOOL
* macro is called.
*/
build_message_id();
if (msg_buf == NULL) {
msg_buf = xmalloc(message_bufsiz);
end_msg_buf = msg_buf + message_bufsiz;
}
msg_max = msg_buf;
msg_ptr = msg_buf;
(void) fstat(spoolfile, &statbuf);
msg_size = statbuf.st_size;
if (lock) {
DEBUG2(DBG_SPOOL_LO, "opened and locked spool file %s/%s\n",
spool_dir, input_spool_fn);
} else {
DEBUG2(DBG_SPOOL_LO, "opened spool file %s/%s\n",
spool_dir, input_spool_fn);
}
return SUCCEED;
}
\f
/*
* close_spool - unlock and close the spool file
*/
void
close_spool()
{
DEBUG(DBG_SPOOL_HI, "close_spool called\n");
if (lock_fn) {
DEBUG2(DBG_SPOOL_HI, "close_spool: unlinking %s/lock/%s\n",
spool_dir, lock_fn);
(void) unlink(lock_fn);
}
(void) close(spoolfile);
spoolfile = -1;
spool_fn = NULL;
input_spool_fn = NULL;
}
/*
* unlink_spool - unlock, close and unlink the spool file
*/
void
unlink_spool()
{
char *save_input_spool_fn = input_spool_fn;
DEBUG(DBG_SPOOL_HI, "unlink_spool called\n");
if (save_input_spool_fn) {
DEBUG2(DBG_SPOOL_HI, "unlink_spool: unlinking: %s/%s\n",
spool_dir, save_input_spool_fn);
(void) unlink(save_input_spool_fn);
}
close_spool();
}
/*
* defer_message - put the spool file in a defer directory
*
* some processing errors may need attention from the postmaster,
* and may be correctable through a change in configuration, or other
* such things. Rather than mail such problems to the sender or to
* the postmaster, put them in a special (error/) directory. When
* the problem that caused the error is resolved, the postmaster can
* then simply move the spool file back into the spool directory and
* delivery will be reattempted.
*/
void
defer_message()
{
char error_fn[sizeof("error/") + SPOOL_FN_LEN];
int success;
char *save_input_spool_fn = input_spool_fn;
char *save_spool_fn = spool_fn;
(void) sprintf(error_fn, "error/%s", spool_fn);
spool_fn = NULL;
success = link(save_input_spool_fn, error_fn);
if (success < 0 && auto_mkdir && errno == ENOENT) {
DEBUG1(DBG_SPOOL_LO, "make directory %s/error\n", spool_dir);
(void) mkdir("error", auto_mkdir_mode);
success = link(save_input_spool_fn, error_fn);
}
if (success < 0) {
write_log(LOG_PANIC|LOG_SYS,
"defer_message: error linking %s/%s to error/%s: %s",
spool_dir, save_input_spool_fn, spool_fn, strerrno());
} else {
if (errfile) {
write_log(LOG_TTY|LOG_SYS, "mail moved to %s/error/%s",
spool_dir, save_spool_fn);
}
input_spool_fn = save_input_spool_fn;
spool_fn = save_spool_fn;
unlink_spool();
}
}
\f
/*
* seek_spool - seek to the specified absolute spool file offset
*
* causes the aligned block around that point to be read into
* msg_buf.
*/
int
seek_spool(offset)
register long offset;
{
DEBUG1(DBG_SPOOL_HI, "seek_spool(%ld) called\n", offset);
if (offset > msg_size) {
/* attempt to seek past the end of message */
msg_ptr = msg_max + 1; /* will cause EOF from GETSPOOL */
DEBUG(DBG_SPOOL_LO, "seek_spool failed, seek past end of file\n");
return FAIL;
}
/* is the offset in the current block? */
if (msg_foffset <= offset && offset < msg_foffset + (msg_max - msg_buf)) {
/* yes, just adjust the current loc pointer */
msg_ptr = msg_buf + (offset - msg_foffset);
return SUCCEED;
}
/* compute the beginning of the block containing the offset */
msg_foffset = offset - offset%message_bufsiz;
/* actually read in the message */
(void) lseek(spoolfile, msg_foffset, 0);
/* set the buffer to zero-length so read_spool won't advance foffset */
msg_max = msg_buf;
if (read_spool() == FAIL) {
return FAIL;
}
/* set up the ptr to point to the byte for the offset */
msg_ptr = msg_buf + (offset - msg_foffset);
if (msg_ptr > msg_max) {
DEBUG(DBG_SPOOL_LO, "seek_spool failed, read came up short\n");
return FAIL;
}
return SUCCEED;
}
/*
* tell_spool - return the current offset in the spool file.
*/
int
tell_spool()
{
return msg_foffset + msg_ptr - msg_buf;
}
\f
/*
* send_spool - write the spool file to a stdio FILE pointer
*
* send the spool file to an open file, starting at the current
* offset and ending at the end of the spool file. If PUT_DOTS is set
* then use the hidden dot algorithm, prepending a dot to each
* line that begins with a dot. If PUT_CRLF is set than put a carriage
* return before each newline. If UNIX_FROM_HACK then put > in front of
* any line beginning with From.
*
* return SUCCEED, READ_FAIL or WRITE_FAIL.
*/
int
send_spool(f, flags)
register FILE *f; /* file to write on */
long flags; /* transport flags */
{
register char *p;
int eof = FALSE; /* TRUE if found end of file */
register int last_c = '\n';
int dot = flags & PUT_DOTS;
int crlf = flags & PUT_CRLF;
int uucp_hack = flags & UNIX_FROM_HACK;
DEBUG(DBG_SPOOL_HI, "send_spool called\n");
while (!eof) {
for (p = msg_ptr; p < msg_max; p++) {
if (last_c == '\n') {
if (dot && *p == '.') {
putc('.', f); /* hidden dot algorithm */
}
if (uucp_hack && *p == 'F') {
int ct = msg_max - p;
/* look ahead to see if this line begins with From */
if (ct > sizeof("From ") - 1) {
if (strncmp("From ", p, sizeof("From ") - 1) == 0) {
putc('>', f);
}
} else if (strncmp("From ", p, ct) == 0) {
/*
* we have to look ahead to the next block to
* determine if this is a From, but make sure
* there is another block, first.
*/
if (msg_foffset + (msg_max - msg_buf) < msg_size) {
int i;
msg_ptr = p;
if (read_spool() == FAIL) {
return READ_FAIL;
}
p = msg_ptr;
if (strncmp("From " + ct, p,
sizeof("From ") - ct - 1) == 0)
{
putc('>', f);
}
for (i = 0; i < ct; i++) {
putc("From "[i], f);
}
}
}
}
}
if (*p == '\n' && crlf) {
putc('\r', f); /* ARPAnet CR/LF */
}
if (putc(last_c = *p, f) == EOF) {
DEBUG(DBG_SPOOL_LO, "send_spool: write failed\n");
return WRITE_FAIL;
}
}
msg_ptr = p;
if (msg_foffset + (msg_max - msg_buf) < msg_size) {
if (read_spool() == FAIL) {
return READ_FAIL;
}
} else {
eof = TRUE;
}
}
if (last_c != '\n') {
if (crlf) {
putc('\r', f);
}
if (putc('\n', f) == EOF) {
DEBUG(DBG_SPOOL_LO, "send_spool: write failed\n");
return WRITE_FAIL;
}
}
return SUCCEED;
}
\f
/*
* read_spool - read in a block from the spool file
*
* read a block starting at the current file position into msg_buf
* and update the various associated variables.
*
* return SUCCEED or FAIL.
*/
int
read_spool()
{
register int ct;
/* advance the current file offset by the amount previously read */
msg_foffset += msg_max - msg_buf;
if (message_bufsiz > msg_size - msg_foffset) {
/* the remaining size is less then a whole buffer */
ct = msg_size - msg_foffset;
if (ct < 0) {
errno = ENXIO; /* reading beyond end of the file */
DEBUG(DBG_SPOOL_LO, "read_spool: read failed, past end of file");
return FAIL;
}
if (ct == 0) {
/* end of file */
msg_max = msg_ptr = msg_buf;
DEBUG(DBG_SPOOL_MID, "end of file on spool file\n");
return FAIL;
}
} else {
/* there is enough data to fit in a complete buffer */
ct = message_bufsiz;
}
/* now, where were we? */
(void) lseek(spoolfile, msg_foffset, 0);
/* read in a buffer */
ct = read(spoolfile, msg_buf, ct);
if (ct < 0) {
DEBUG1(DBG_SPOOL_LO, "read_spool: read of spool file failed: %s\n",
strerrno());
return FAIL;
}
/* point to the end and beginning of the used region */
msg_max = msg_buf + ct;
msg_ptr = msg_buf;
return SUCCEED;
}
\f
/*
* log_spool_errors - write spooling errors to system log
*
* while spooling, errors are not written out to the spool files. This
* avoid having to deal with the fact that those writes may fail. Thus,
* when finished spooling the message, we must write out any errors to the
* panic file.
*/
void
log_spool_errors()
{
DEBUG(DBG_SPOOL_HI, "log_spool_errors called, saving errors from spool\n");
while (log_msgs) {
write_log(LOG_PANIC, "%s", (char *)(log_msgs + 1));
xfree((char *)log_msgs);
log_msgs = log_msgs->succ;
}
}
/*
* log_message - save a message to be written to the log file
*
* the message is not in printf format, but filename and directory
* information will be added to it.
*/
static void
log_message(m)
char *m; /* message */
{
char *p, *q; /* temp */
register unsigned a; /* how much to alloc */
char *save;
a = sizeof(sizeof(*log_msgs)) + strlen(m) + 1;
if (spool_dir) {
a += sizeof(", dir=") + strlen(spool_dir);
}
if (input_spool_fn) {
a += sizeof(", spoolfile=") + strlen(input_spool_fn);
}
if (lock_fn) {
a += sizeof(", lockfile=") + strlen(lock_fn);
}
*next_log_msg = (struct log_msgs*)xmalloc(a);
(*next_log_msg)->succ = NULL;
save = p = (char *)(*next_log_msg + 1);
next_log_msg = &(*next_log_msg)->succ;
/* copy message */
for (q = m; *q; *p++ = *q++) ;
if (spool_dir) {
/* put spool directory in message */
for (q = ", dir="; *q; *p++ = *q++) ;
for (q = spool_dir; *q; *p++ = *q++) ;
}
if (input_spool_fn) {
/* put spoolfile name in message */
for (q = ", spoolfile="; *q; *p++ = *q++) ;
for (q = input_spool_fn; *q; *p++ = *q++) ;
}
if (lock_fn) {
/* put lockfile name in message */
for (q = ", lockfile="; *q; *p++ = *q++) ;
for (q = lock_fn; *q; *p++ = *q++) ;
}
*p++ = '\0';
DEBUG2(DBG_SPOOL_MID, "%s: %s\n", funct_name, save);
}
\f
/*
* set_alt_spool - set a primary or alternate spool directory
*
* step through the list of spool directories, advancing once per
* call to set_alt_spool and resetting when the current spool directory
* was not set by set_alt_spool. Do a chdir(2) to the directory
* set spool_dir and return SUCCEED on the first one for chdir
* succeeds. If none remain, return FAIL.
*/
static int
set_alt_spool(m)
char *m; /* message to put in log */
{
static char altname[SIZE_FILNAM];
static char *altlist = NULL;
char *p;
/* log the message using the old spool dir then set the new one */
if (m) {
log_message(m);
}
if (spool_dir != altname) {
/* previous spool dir not one of ours, reset to start */
altlist = spool_dirs;
}
spool_dir = altname;
/* loop until we have a valid alternate spool directory, or none left */
while (altlist && *altlist) {
/* copy a directory name into altname, may end in ':' or nul */
for (p = altname; *altlist && *altlist != ':'; *p++ = *altlist++) ;
if (*altlist == ':') {
/* next try gets the next directory */
altlist++;
}
*p++ = '\0'; /* terminate directory name */
if (altname[0] != '/') {
/* directory must begin with '/', ignore it */
log_message("config error: alt directory doesn't begin with /");
continue;
}
if (chdir(altname) < 0) {
/* Okay, try to make the directory and then chdir again */
if (auto_mkdir && errno == ENOENT) {
DEBUG1(DBG_SPOOL_LO, "make directory %s\n", altname);
(void) mkdir(altname, auto_mkdir_mode);
if (chdir(altname) < 0) {
log_message("chdir failed");
continue;
}
} else {
log_message("chdir failed");
continue;
}
}
/* we found a valid alternate directory */
return SUCCEED;
}
/* we failed, *sigh* */
return FAIL;
}
\f
/*
* new_grade - assign a new grade to the spool file
*
* assign a new grade, or precedence, code to the spool file. This
* involves linking to a new name with a different precedence character.
* We go through build_spool_fn to compute new names and keep trying
* new names until we have one that doesn't already exist. If links
* fail for some reason other than a name not existing then don't bother
* trying to change the grade.
*
* return SUCCEED or FAIL, though a FAIL should be ignored as there is
* no good way to recover and it is not generally that important.
*/
int
new_grade(grade)
int grade; /* grade character */
{
/*
* NOTE: the above must be the same size as the names
* built by build_spool_fn
*/
char save_spool_fn[SPOOL_FN_LEN + 1];
char save_input_spool_fn[sizeof("input/") + SPOOL_FN_LEN + 1];
char save_message_id[SPOOL_FN_LEN + 2];
char save_lock_fn[sizeof("lock/") + SPOOL_FN_LEN + 1];
DEBUG1(DBG_SPOOL_HI, "new_grade(%c) called\n", grade);
/*
* make a copy of the old names to use as args to link, and so that
* the old names can be restored
*/
(void) memcpy(save_spool_fn, spool_fn, sizeof(save_spool_fn));
(void) memcpy(save_input_spool_fn, input_spool_fn,
sizeof(save_input_spool_fn));
(void) memcpy(save_message_id, message_id, sizeof(save_message_id));
if (lock_by_name) {
(void) memcpy(save_lock_fn, lock_fn, sizeof(save_lock_fn));
}
/*
* loop until we link both lock file and spool file to new names
* and unlink the old ones.
*/
for (;;) {
build_spool_fn(grade);
/* try to link the lock file first so that it is initially locked */
if (lock_by_name && link(save_lock_fn, lock_fn) < 0) {
if (errno != EEXIST) {
DEBUG1(DBG_SPOOL_LO, "new_grade: failed to link lock: %s\n",
strerrno());
break;
}
DEBUG1(DBG_SPOOL_MID,
"new_grade: failed to link lock:%, try another\n",
strerrno());
continue;
}
/* now try to link the actual spool file */
if (link(save_input_spool_fn, input_spool_fn) < 0) {
if (errno != EEXIST) {
DEBUG1(DBG_SPOOL_LO, "new_grade: failed to link spool: %s\n",
strerrno());
if (lock_by_name) {
(void) unlink(lock_fn);
}
break;
}
DEBUG1(DBG_SPOOL_MID,
"new_grade: failed to link spool file: %s, try another\n",
strerrno());
continue;
}
/* success, now unlink the old names */
(void) unlink(save_input_spool_fn);
if (lock_by_name) {
(void) unlink(save_lock_fn);
}
return SUCCEED;
}
/* we failed, copy the old names back */
(void) memcpy(spool_fn, save_spool_fn, sizeof(save_spool_fn));
(void) memcpy(input_spool_fn, save_input_spool_fn,
sizeof(save_input_spool_fn));
(void) memcpy(message_id, save_message_id, sizeof(save_message_id));
if (lock_by_name) {
(void) memcpy(lock_fn, save_lock_fn, sizeof(save_lock_fn));
}
return FAIL;
}
/*
* message_date - return the date that the message was spooled
*
* The message spool date is encoded in the actual Message Id as a
* base 62 number. Return the date as the number of seconds since
* the epoch.
*/
long
message_date()
{
long clock;
register int digit;
register char *p;
clock = 0;
for (p = message_id + 1; *p; p++) {
if (isdigit(*p)) {
digit = *p - '0';
} else if (isupper(*p)) {
digit = *p - ('A' - 10);
} else if (islower(*p)) {
digit = *p - ('a' - 36);
} else {
break;
}
clock = (clock * 62) + digit;
}
return clock;
}
\f
#ifdef STANDALONE
#include "varargs.h"
char *program = "spool";
char *spool_dirs = "/usr/spool/smail:/wd1h/spool/smail:/usr/tmp/spool/smail";
int debug = 0;
int message_bufsiz = MESSAGE_BUF_SIZE;
int spool_mode = 0444;
int spool_grade = 'C';
int lock_mode = 0444;
/*
* standalone main for spooling subsystem.
* usage:
* spool [-ddebug] [-msize] o|c[w][u] [filename]
*
* -d sets the debug level, -m sets message_bufsiz. An arg of `o'
* causes a spool file in the given filename to be locked, written to
* standard out and closed. An arg of `c' causes a spool file to be
* read from standard input and put in a new spool file. If `w'
* follows `o' or `c' then write the spool file to standard out before
* closing. If `u' then unspool the message.
*/
void
main(argc, argv)
int argc;
char *argv[];
{
char *p;
int mode = 0;
int dowrite = FALSE;
int dounspool = FALSE;
program = *argv++;
--argc;
while ((*argv)[0] == '-') {
switch ((*argv)[1]) {
case 'd':
debug = atoi(*argv + 2);
break;
case 'm':
message_bufsiz = atoi(*argv + 2);
break;
default:
usage();
/*NOTREACHED*/
}
argv++;
--argc;
}
if (argc <= 0) {
usage();
/*NOTREACHED*/
}
for (p = *argv++, --argc; *p; p++) {
switch (*p) {
case 'o':
if (mode) {
usage();
/*NOTREACHED*/
}
mode = 'o';
break;
case 'c':
if (mode) {
usage();
/*NOTREACHED*/
}
mode = 'c';
break;
case 'w':
dowrite = TRUE;
break;
case 'u':
dounspool = TRUE;
break;
default:
usage();
/*NOTREACHED*/
}
}
if (!mode) {
usage();
/*NOTREACHED*/
}
if (mode == 'o' && argc <= 0) {
(void) fprintf(stderr, "%s: open mode requires filename\n", program);
exit(EX_USAGE);
}
if (mode == 'o') {
if (open_spool(*argv) < 0) {
(void) fprintf(stderr, "%s: open failed\n", program);
exit(EX_OSFILE);
}
} else {
register int c;
if (creat_spool(*argv) < 0) {
(void) fprintf(stderr, "%s: creat failed\n", program);
exit(EX_OSFILE);
}
while ((c = getchar()) != EOF) {
if (c == '@') {
force_write_error = TRUE;
}
if (PUTSPOOL(c) == FAIL) {
(void) fprintf(stderr, "%s: write failed\n", program);
exit(EX_IOERR);
}
}
if (write_spool() == FAIL) {
(void) fprintf(stderr, "%s: write failed\n", program);
}
log_spool_errors();
}
if (dowrite) {
if (seek_spool(0L) != SUCCEED) {
(void) fprintf(stderr, "seek_spool failed\n");
}
if (send_spool(stdout, TRUE, FALSE) != SUCCEED) {
(void) fprintf(stderr, "seek_spool failed\n");
}
}
if (dounspool) {
unlink_spool();
} else {
close_spool();
}
exit(EX_OK);
}
usage()
{
(void) fprintf(stderr, "usage: %s [-ddebug] [-msize] o|c[w][u] [file]\n",
program);
exit(EX_USAGE);
}
/*VARARGS2*/
void
write_log(log, fmt, va_alist)
int log; /* TRUE if to write global log file */
char *fmt; /* printf(3) format */
va_dcl /* arguments for printf */
{
va_list ap;
va_start(ap);
(void)fprintf(stderr, log? "PUBLIC: ": "PRIVATE: ");
(void)vfprintf(stderr, fmt, ap);
putc('\n', stderr);
va_end(ap);
}
char *
strerrno()
{
char errmsg[100];
(void) sprintf(errmsg, "Error_%d", errno);
return errmsg;
}
#endif /* STANDALONE */