This is spool.c in view mode; [Download] [Up]
/* @(#)src/spool.c 1.2 24 Oct 1990 05:25:19 */ /* * 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 /* * 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); } /* * 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; } /* * 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 */ } /* * 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; } /* * 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(); } } /* * 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; } /* * 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; } /* * 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; } /* * 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); } /* * 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; } /* * 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; } #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 */
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.