]> granicus.if.org Git - uw-imap/commitdiff
add files for 2007-10-11T22:16:37Z
authorUnknown <>
Thu, 11 Oct 2007 22:16:37 +0000 (22:16 +0000)
committerNathan Wagner <nw@hydaspes.if.org>
Fri, 7 Sep 2018 00:02:38 +0000 (00:02 +0000)
src/osdep/unix/flocksim.c [new file with mode: 0644]

diff --git a/src/osdep/unix/flocksim.c b/src/osdep/unix/flocksim.c
new file mode 100644 (file)
index 0000000..82f0783
--- /dev/null
@@ -0,0 +1,920 @@
+/* ========================================================================
+ * Copyright 1988-2007 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 
+ * ========================================================================
+ */
+
+/*
+ * Program:    flock emulation via fcntl() locking
+ *
+ * Author:     Mark Crispin
+ *             Networks and Distributed Computing
+ *             Computing & Communications
+ *             University of Washington
+ *             Administration Building, AG-44
+ *             Seattle, WA  98195
+ *             Internet: MRC@CAC.Washington.EDU
+ *
+ * Date:       10 April 2001
+ * Last Edited:        11 October 2007
+ */
+#undef flock                   /* name is used as a struct for fcntl */
+
+#ifndef NOFSTATVFS             /* thank you, SUN.  NOT! */
+# ifndef NOFSTATVFS64
+#  ifndef _LARGEFILE64_SOURCE
+#   define _LARGEFILE64_SOURCE
+#  endif       /* _LARGEFILE64_SOURCE */
+# endif                /* NOFSTATVFFS64 */
+#include <sys/statvfs.h>
+#endif         /* NOFSTATVFS */
+
+#ifndef NSIG                   /* don't know if this can happen */
+#define NSIG 32                        /* a common maximum */
+#endif
+\f
+/* Emulator for flock() call
+ * Accepts: file descriptor
+ *         operation bitmask
+ * Returns: 0 if successful, -1 if failure under BSD conditions
+ */
+
+int flocksim (int fd,int op)
+{
+  char tmp[MAILTMPLEN];
+  int logged = 0;
+  struct stat sbuf;
+  struct ustat usbuf;
+  struct flock fl;
+                               /* lock zero bytes at byte 0 */
+  fl.l_whence = SEEK_SET; fl.l_start = fl.l_len = 0;
+  fl.l_pid = getpid ();                /* shouldn't be necessary */
+  switch (op & ~LOCK_NB) {     /* translate to fcntl() operation */
+  case LOCK_EX:                        /* exclusive */
+    fl.l_type = F_WRLCK;
+    break;
+  case LOCK_SH:                        /* shared */
+    fl.l_type = F_RDLCK;
+    break;
+  case LOCK_UN:                        /* unlock */
+    fl.l_type = F_UNLCK;
+    break;
+  default:                     /* default */
+    errno = EINVAL;
+    return -1;
+  }
+                               /* always return success if disabled */
+  if (mail_parameters (NIL,GET_DISABLEFCNTLLOCK,NIL)) return 0;
+\f
+  /*  Make fcntl() locking of NFS files be a no-op the way it is with flock()
+   * on BSD.  This is because the rpc.statd/rpc.lockd daemons don't work very
+   * well and cause cluster-wide hangs if you exercise them at all.  The
+   * result of this is that you lose the ability to detect shared mail_open()
+   * on NFS-mounted files.  If you are wise, you'll use IMAP instead of NFS
+   * for mail files.
+   *
+   *  Sun alleges that it doesn't matter, and that they have fixed all the
+   * rpc.statd/rpc.lockd bugs.  As of October 2006, that is still false.
+   *
+   *  We need three tests for three major historical variants in SVR4:
+   *  1) In NFSv2, ustat() would return -1 in f_tinode for NFS.
+   *  2) When fstatvfs() was introduced with NFSv3, ustat() was "fixed".
+   *  3) When 64-bit filesystems were introduced, fstatvfs() would return
+   *    EOVERFLOW; you have to use fstatvfs64() even though you don't care
+   *    about any of the affected values.
+   *
+   * We can't use fstatfs() because fstatfs():
+   * . is documented as being deprecated in SVR4.
+   * . has inconsistent calling conventions (there are two additional int
+   *   arguments on Solaris and I don't know what they do).
+   * . returns inconsistent statfs structs.  On Solaris, the file system type
+   *   is a short called f_fstyp.  On AIX, it's an int called f_type that is
+   *   documented as always being 0!
+   *
+   * For what it's worth, here's the scoop on fstatfs() elsewhere:
+   *
+   *  On Linux, the file system type is a long called f_type that has a file
+   * system type code.  A different module (flocklnx.c) uses this because
+   * some knothead "improved" flock() to return ENOLCK on NFS files instead
+   * of being a successful no-op.  This "improvement" apparently has been
+   * reverted, but not before it got to many systems in the field.
+   *
+   *  On BSD, it's a short called either f_otype or f_type that is documented
+   * as always being zero.  Fortunately, BSD has flock() the way it's supposed
+   * to be, and none of this nonsense is necessary.
+   */
+  if (!fstat (fd,&sbuf))       { /* no hope of working if can't fstat()! */
+    /* Any base type that begins with "nfs" or "afs" is considered to be a
+     * network filesystem.
+     */
+#ifndef NOFSTATVFS
+    struct statvfs vsbuf;
+#ifndef NOFSTATVFS64
+    struct statvfs64 vsbuf64;
+    if (!fstatvfs64 (fd,&vsbuf64) && (vsbuf64.f_basetype[1] == 'f') &&
+       (vsbuf64.f_basetype[2] == 's') &&
+       ((vsbuf64.f_basetype[0] == 'n') || (vsbuf64.f_basetype[0] == 'a')))
+      return 0;
+#endif         /* NOFSTATVFS64 */
+    if (!fstatvfs (fd,&vsbuf) && (vsbuf.f_basetype[1] == 'f') &&
+       (vsbuf.f_basetype[2] == 's') &&
+       ((vsbuf.f_basetype[0] == 'n') || (vsbuf.f_basetype[0] == 'a')))
+      return 0;
+#endif         /* NOFSTATVFS */
+    if (!ustat (sbuf.st_dev,&usbuf) && !++usbuf.f_tinode) return 0;
+  }
+\f
+                               /* do the lock */
+  while (fcntl (fd,(op & LOCK_NB) ? F_SETLK : F_SETLKW,&fl))
+    if (errno != EINTR) {
+      /* Can't use switch here because these error codes may resolve to the
+       * same value on some systems.
+       */
+      if ((errno != EWOULDBLOCK) && (errno != EAGAIN) && (errno != EACCES)) {
+       sprintf (tmp,"Unexpected file locking failure: %.100s",
+                strerror (errno));
+                               /* give the user a warning of what happened */
+       MM_NOTIFY (NIL,tmp,WARN);
+       if (!logged++) syslog (LOG_ERR,"%s",tmp);
+       if (op & LOCK_NB) return -1;
+       sleep (5);              /* slow things down for loops */
+      }
+                               /* return failure for non-blocking lock */
+      else if (op & LOCK_NB) return -1;
+    }
+  return 0;                    /* success */
+}
+\f
+/* Master/slave procedures for safe fcntl() locking.
+ *
+ *  The purpose of this nonsense is to work around a bad bug in fcntl()
+ * locking.  The cretins who designed it decided that a close() should
+ * release any locks made by that process on the file opened on that
+ * file descriptor.  Never mind that the lock wasn't made on that file
+ * descriptor, but rather on some other file descriptor.
+ *
+ *  This bug is on every implementation of fcntl() locking that I have
+ * tested.  Fortunately, on BSD systems, OSF/1, and Linux, we can use the
+ * flock() system call which doesn't have this bug.
+ *
+ *  Note that OSF/1, Linux, and some BSD systems have both broken fcntl()
+ * locking and the working flock() locking.
+ *
+ *  The program below can be used to demonstrate this problem.  Be sure to
+ * let it run long enough for all the sleep() calls to finish.
+ */
+
+#if 0
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <string.h>
+#include <sys/file.h>
+
+main ()
+{
+  struct flock fl;
+  int fd,fd2;
+  char *file = "a.a";
+  if ((fd = creat (file,0666)) < 0)
+    perror ("TEST FAILED: can't create test file"),_exit (errno);
+  close (fd);
+  if (fork ()) {               /* parent */
+    if ((fd = open (file,O_RDWR,0)) < 0) abort();
+                               /* lock applies to entire file */
+    fl.l_whence = fl.l_start = fl.l_len = 0;
+    fl.l_pid = getpid ();      /* shouldn't be necessary */
+    fl.l_type = F_RDLCK;
+    if (fcntl (fd,F_SETLKW,&fl) == -1) abort ();
+    sleep (5);
+    if ((fd2 = open (file,O_RDWR,0)) < 0) abort ();
+    sleep (1);
+    puts ("parent test ready -- will hang here if locking works correctly");
+    close (fd2);
+    wait (0);
+    puts ("OS BUG: child terminated");
+    _exit (0);
+  }
+  else {                       /* child */
+    sleep (2);
+    if ((fd = open (file,O_RDWR,0666)) < 0) abort ();
+    puts ("child test ready -- child will hang if no bug");
+                               /* lock applies to entire file */
+    fl.l_whence = fl.l_start = fl.l_len = 0;
+    fl.l_pid = getpid ();      /* shouldn't be necessary */
+    fl.l_type = F_WRLCK;
+    if (fcntl (fd,F_SETLKW,&fl) == -1) abort ();
+    puts ("OS BUG: child got lock");
+  }
+}
+#endif
+\f
+/*  Beware of systems such as AIX which offer flock() as a compatibility
+ * function that is just a jacket into fcntl() locking.  The program below
+ * is a variant of the program above, only using flock().  It can be used
+ * to test to see if your system has real flock() or just a jacket into
+ * fcntl().
+ *
+ *  Be sure to let it run long enough for all the sleep() calls to finish.
+ * If the program hangs, then flock() works and you can dispense with the
+ * use of this module (you lucky person!).
+ */
+
+#if 0
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <sys/file.h>
+
+main ()
+{
+  int fd,fd2;
+  char *file = "a.a";
+  if ((fd = creat (file,0666)) < 0)
+    perror ("TEST FAILED: can't create test file"),_exit (errno);
+  close (fd);
+  if (fork ()) {               /* parent */
+    if ((fd = open (file,O_RDWR,0)) < 0) abort();
+    if (flock (fd,LOCK_SH) == -1) abort ();
+    sleep (5);
+    if ((fd2 = open (file,O_RDWR,0)) < 0) abort ();
+    sleep (1);
+    puts ("parent test ready -- will hang here if flock() works correctly");
+    close (fd2);
+    wait (0);
+    puts ("OS BUG: child terminated");
+    _exit (0);
+  }
+  else {                       /* child */
+    sleep (2);
+    if ((fd = open (file,O_RDWR,0666)) < 0) abort ();
+    puts ("child test ready -- child will hang if no bug");
+    if (flock (fd,LOCK_EX) == -1) abort ();
+    puts ("OS BUG: child got lock");
+  }
+}
+#endif
+\f
+/* Master/slave details
+ *
+ *  On broken systems, we invoke an inferior fork to execute any driver
+ * dispatches which are likely to tickle this bug; specifically, any
+ * dispatch which may fiddle with a mailbox that is already selected.  As
+ * of this writing, these are: delete, rename, status, scan, copy, and append.
+ *
+ *  Delete and rename are pretty marginal, yet there are certain clients
+ * (e.g. Outlook Express) that really want to delete or rename the selected
+ * mailbox.  The same is true of status, but there are people (such as the
+ * authors of Entourage) who don't understand why status of the selected
+ * mailbox is bad news.
+ *
+ *  However, in copy and append it is reasonable to do this to a selected
+ * mailbox.  Although scanning the selected mailbox isn't particularly
+ * sensible, it's hard to avoid due to wildcards.
+ *
+ *  It is still possible for an application to trigger the bug by doing
+ * mail_open() on the same mailbox twice.  Don't do it.
+ *
+ *  Once the slave is invoked, the master only has to read events from the
+ * slave's output (see below for these events) and translate these events
+ * to the appropriate c-client callback.  When end of file occurs on the pipe,
+ * the master reads the slave's exit status and uses that as the function
+ * return.  The append master is slightly more complicated because it has to
+ * send data back to the slave (see below).
+ *
+ *  The slave takes callback events from the driver which otherwise would
+ * pass to the main program.  Only those events which a slave can actually
+ * encounter are covered here; for example mm_searched() and mm_list() are
+ * not covered since a slave never does the operations that trigger these.
+ * Certain other events (mm_exists(), mm_expunged(), mm_flags()) are discarded
+ * by the slave since the master will generate these events for itself.
+ *
+ *  The other events cause the slave to write a newline-terminated string to
+ * its output.  The first character of string indicates the event: S for
+ * mm_status(), N for mm_notify(), L for mm_log(), C for mm_critical(), X for
+ * mm_nocritical(), D for mm_diskerror(), F for mm_fatal(), and "A" for append
+ * argument callback.  Most of these events also carry data, which carried as
+ * text space-delimited in the string.
+ *
+ *  Append argument callback requires the master to provide the slave with
+ * data in the slave's input.  The first thing that the master provides is
+ * either a "+" (master has data for the slave) or a "-" (master has no data).
+ * If the master has data, it will then send the flags, internal date, and
+ * message text, each as <text octet count><SPACE><text>.
+ */
+\f
+/*  It should be alright for lockslavep to be a global, since it will always
+ * be zero in the master (which is where threads would be).  The slave won't
+ * ever thread, since any driver which threads in its methods probably can't
+ * use fcntl() locking so won't have DR_LOCKING in its driver flags 
+ *
+ *  lockslavep can not be a static, since it's used by the dispatch macros.
+ */
+
+int lockslavep = 0;            /* non-zero means slave process for locking */
+static int lockproxycopy = 0;  /* non-zero means redo copy as proxy */
+FILE *slavein = NIL;           /* slave input */
+FILE *slaveout = NIL;          /* slave output */
+
+
+/* Common master
+ * Accepts: permitted stream
+ *         append callback (append calls only, else NIL)
+ *         data for callback (append calls only, else NIL)
+ * Returns: (master) T if slave succeeded, NIL if slave failed
+ *         (slave) NIL always, with lockslavep non-NIL
+ */
+
+static long master (MAILSTREAM *stream,append_t af,void *data)
+{
+  MAILSTREAM *st;
+  MAILSTATUS status;
+  STRING *message;
+  FILE *pi,*po;
+  blocknotify_t bn = (blocknotify_t) mail_parameters (NIL,GET_BLOCKNOTIFY,NIL);
+  long ret = NIL;
+  unsigned long i,j;
+  int c,pid,pipei[2],pipeo[2];
+  char *s,*t,event[MAILTMPLEN],tmp[MAILTMPLEN];
+  lockproxycopy = NIL;         /* not doing a lock proxycopy */
+                               /* make pipe from slave */
+  if (pipe (pipei) < 0) mm_log ("Can't create input pipe",ERROR);
+  else if (pipe (pipeo) < 0) {
+    mm_log ("Can't create output pipe",ERROR);
+    close (pipei[0]); close (pipei[1]);
+  }
+  else if ((pid = fork ()) < 0) {/* make slave */
+    mm_log ("Can't create execution process",ERROR);
+    close (pipei[0]); close (pipei[1]);
+    close (pipeo[0]); close (pipeo[1]);
+  }
+  else if (lockslavep = !pid) {        /* are we slave or master? */
+    alarm (0);                 /* slave doesn't have alarms or signals */
+    for (c = 0; c < NSIG; c++) signal (c,SIG_DFL);
+    if (!(slavein = fdopen (pipeo[0],"r")) ||
+       !(slaveout = fdopen (pipei[1],"w")))
+      fatal ("Can't do slave pipe buffered I/O");
+    close (pipei[0]);          /* close parent's side of the pipes */
+    close (pipeo[1]);
+  }
+\f
+  else {                       /* master process */
+    void *blockdata = (*bn) (BLOCK_SENSITIVE,NIL);
+    close (pipei[1]);          /* close slave's side of the pipes */
+    close (pipeo[0]);
+    if (!(pi = fdopen (pipei[0],"r")) || !(po = fdopen (pipeo[1],"w")))
+      fatal ("Can't do master pipe buffered I/O");
+                               /* do slave events until EOF */
+                               /* read event */
+    while (fgets (event,MAILTMPLEN-1,pi)) {
+      if (!(s = strchr (event,'\n'))) {
+       sprintf (tmp,"Execution process event string too long: %.500s",event);
+       fatal (tmp);
+      }
+      *s = '\0';               /* tie off event at end of line */
+      switch (event[0]) {      /* analyze event */
+      case 'A':                        /* append callback */
+       if ((*af) (NIL,data,&s,&t,&message)) {
+         if (i = message ? SIZE (message) : 0) {
+           if (!s) s = "";     /* default values */
+           if (!t) t = "";
+         }
+         else s = t = "";      /* no flags or date if no message */
+         errno = NIL;          /* reset last error */
+                               /* build response */
+         if (fprintf (po,"+%lu %s%lu %s%lu ",strlen (s),s,strlen (t),t,i) < 0)
+           fatal ("Failed to pipe append command");
+                               /* write message text */
+         if (i) do if (putc (c = 0xff & SNX (message),po) == EOF) {
+           sprintf (tmp,"Failed to pipe %lu bytes (of %lu), last=%u: %.100s",
+                    i,message->size,c,strerror (errno));
+           fatal (tmp);
+         } while (--i);
+       }
+       else putc ('-',po);     /* append error */
+       fflush (po);
+       break;
+      case '&':                        /* slave wants a proxycopy? */
+       lockproxycopy = T;
+       break;
+\f
+      case 'L':                        /* mm_log() */
+       i = strtoul (event+1,&s,10);
+       if (!s || (*s++ != ' ')) {
+         sprintf (tmp,"Invalid log event arguments: %.500s",event);
+         fatal (tmp);
+       }
+       mm_log (s,i);
+       break;
+      case 'N':                        /* mm_notify() */
+       st = (MAILSTREAM *) strtoul (event+1,&s,16);
+       if (s && (*s++ == ' ')) {
+         i = strtoul (s,&s,10);/* get severity */
+         if (s && (*s++ == ' ')) {
+           mm_notify ((st == stream) ? stream : NIL,s,i);
+           break;
+         }
+       }
+       sprintf (tmp,"Invalid notify event arguments: %.500s",event);
+       fatal (tmp);
+\f
+      case 'S':                        /* mm_status() */
+       st = (MAILSTREAM *) strtoul (event+1,&s,16);
+       if (s && (*s++ == ' ')) {
+         status.flags = strtoul (s,&s,10);
+         if (s && (*s++ == ' ')) {
+           status.messages = strtoul (s,&s,10);
+           if (s && (*s++ == ' ')) {
+             status.recent = strtoul (s,&s,10);
+             if (s && (*s++ == ' ')) {
+               status.unseen = strtoul (s,&s,10);
+               if (s && (*s++ == ' ')) {
+                 status.uidnext = strtoul (s,&s,10);
+                 if (s && (*s++ == ' ')) {
+                   status.uidvalidity = strtoul (s,&s,10);
+                   if (s && (*s++ == ' ')) {
+                     mm_status ((st == stream) ? stream : NIL,s,&status);
+                     break;
+                   }
+                 }
+               }
+             }
+           }
+         }
+       }
+       sprintf (tmp,"Invalid status event arguments: %.500s",event);
+       fatal (tmp);
+      case 'C':                        /* mm_critical() */
+       st = (MAILSTREAM *) strtoul (event+1,&s,16);
+       mm_critical ((st == stream) ? stream : NIL);
+       break;
+      case 'X':                        /* mm_nocritical() */
+       st = (MAILSTREAM *) strtoul (event+1,&s,16);
+       mm_nocritical ((st == stream) ? stream : NIL);
+       break;
+\f
+      case 'D':                        /* mm_diskerror() */
+       st = (MAILSTREAM *) strtoul (event+1,&s,16);
+       if (s && (*s++ == ' ')) {
+         i = strtoul (s,&s,10);
+         if (s && (*s++ == ' ')) {
+           j = (long) strtoul (s,NIL,10);
+           if (st == stream)   /* let's hope it's on usable stream */
+             putc (mm_diskerror (stream,(long) i,j) ? '+' : '-',po);
+           else if (j) {       /* serious diskerror on slave-created stream */
+             mm_log ("Retrying disk write to avoid mailbox corruption!",WARN);
+             sleep (5);        /* give some time for it to clear up */
+             putc ('-',po);    /* don't abort */
+           }
+           else {              /* recoverable on slave-created stream */
+             mm_log ("Error on disk write",ERROR);
+             putc ('+',po);    /* so abort it */
+           }
+           fflush (po);        /* force it out either way */
+           break;
+         }
+       }
+       sprintf (tmp,"Invalid diskerror event arguments: %.500s",event);
+       fatal (tmp);
+      case 'F':                        /* mm_fatal() */
+       mm_fatal (event+1);
+       break;
+      default:                 /* random lossage */
+       sprintf (tmp,"Unknown event from execution process: %.500s",event);
+       fatal (tmp);
+      }
+    }
+    fclose (pi); fclose (po);  /* done with the pipes */
+                               /* get slave status */
+    grim_pid_reap_status (pid,NIL,&ret);
+    if (ret & 0177) {          /* signal or stopped */
+      sprintf (tmp,"Execution process terminated abnormally (%lx)",ret);
+      mm_log (tmp,ERROR);
+      ret = NIL;
+    }
+    else ret >>= 8;            /* return exit code */
+    (*bn) (BLOCK_NONSENSITIVE,blockdata);
+  }
+  return ret;                  /* return status */
+}
+\f
+/* Safe driver calls */
+
+
+/* Safely delete mailbox
+ * Accepts: driver to call under slave
+ *         MAIL stream
+ *         mailbox name to delete
+ * Returns: T on success, NIL on failure
+ */
+
+long safe_delete (DRIVER *dtb,MAILSTREAM *stream,char *mbx)
+{
+  long ret = master (stream,NIL,NIL);
+  if (lockslavep) exit ((*dtb->mbxdel) (stream,mbx));
+  return ret;
+}
+
+
+/* Safely rename mailbox
+ * Accepts: driver to call under slave
+ *         MAIL stream
+ *         old mailbox name
+ *         new mailbox name (or NIL for delete)
+ * Returns: T on success, NIL on failure
+ */
+
+long safe_rename (DRIVER *dtb,MAILSTREAM *stream,char *old,char *newname)
+{
+  long ret = master (stream,NIL,NIL);
+  if (lockslavep) exit ((*dtb->mbxren) (stream,old,newname));
+  return ret;
+}
+
+
+/* Safely get status of mailbox
+ * Accepts: driver to call under slave
+ *         MAIL stream
+ *         mailbox name
+ *         status flags
+ * Returns: T on success, NIL on failure
+ */
+
+long safe_status (DRIVER *dtb,MAILSTREAM *stream,char *mbx,long flags)
+{
+  long ret = master (stream,NIL,NIL);
+  if (lockslavep) exit ((*dtb->status) (stream,mbx,flags));
+  return ret;
+}
+
+
+/* Scan file for contents
+ * Accepts: driver to call under slave
+ *         file name
+ *         desired contents
+ *         length of contents
+ *         length of file
+ * Returns: NIL if contents not found, T if found
+ */
+
+long safe_scan_contents (DRIVER *dtb,char *name,char *contents,
+                        unsigned long csiz,unsigned long fsiz)
+{
+  long ret = master (NIL,NIL,NIL);
+  if (lockslavep) exit (scan_contents (dtb,name,contents,csiz,fsiz));
+  return ret;
+}
+\f
+/* Safely copy message to mailbox
+ * Accepts: driver to call under slave
+ *         MAIL stream
+ *         sequence
+ *         destination mailbox
+ *         copy options
+ * Returns: T if success, NIL if failed
+ */
+
+long safe_copy (DRIVER *dtb,MAILSTREAM *stream,char *seq,char *mbx,long flags)
+{
+  mailproxycopy_t pc =
+    (mailproxycopy_t) mail_parameters (stream,GET_MAILPROXYCOPY,NIL);
+  long ret = master (stream,NIL,NIL);
+  if (lockslavep) {
+                               /* don't do proxycopy in slave */
+    if (pc) mail_parameters (stream,SET_MAILPROXYCOPY,(void *) slaveproxycopy);
+    exit ((*dtb->copy) (stream,seq,mbx,flags));
+  }
+                               /* do any proxycopy in master */
+  if (lockproxycopy && pc) return (*pc) (stream,seq,mbx,flags);
+  return ret;
+}
+
+
+/* Append package for slave */
+
+typedef struct append_data {
+  int first;                   /* flag indicating first message */
+  char *flags;                 /* message flags */
+  char *date;                  /* message date */
+  char *msg;                   /* message text */
+  STRING message;              /* message stringstruct */
+} APPENDDATA;
+
+
+/* Safely append message to mailbox
+ * Accepts: driver to call under slave
+ *         MAIL stream
+ *         destination mailbox
+ *         append callback
+ *         data for callback
+ * Returns: T if append successful, else NIL
+ */
+
+long safe_append (DRIVER *dtb,MAILSTREAM *stream,char *mbx,append_t af,
+                 void *data)
+{
+  long ret = master (stream,af,data);
+  if (lockslavep) {
+    APPENDDATA ad;
+    ad.first = T;              /* initialize initial append package */
+    ad.flags = ad.date = ad.msg = NIL;
+    exit ((*dtb->append) (stream,mbx,slave_append,&ad));
+  }
+  return ret;
+}
+\f
+/* Slave callbacks */
+
+
+/* Message exists (i.e. there are that many messages in the mailbox)
+ * Accepts: MAIL stream
+ *         message number
+ */
+
+void slave_exists (MAILSTREAM *stream,unsigned long number)
+{
+  /* this event never passed by slaves */
+}
+
+
+/* Message expunged
+ * Accepts: MAIL stream
+ *         message number
+ */
+
+void slave_expunged (MAILSTREAM *stream,unsigned long number)
+{
+  /* this event never passed by slaves */
+}
+
+
+/* Message status changed
+ * Accepts: MAIL stream
+ *         message number
+ */
+
+void slave_flags (MAILSTREAM *stream,unsigned long number)
+{
+  /* this event never passed by slaves */
+}
+\f
+/* Mailbox status
+ * Accepts: MAIL stream
+ *         mailbox name
+ *         mailbox status
+ */
+
+void slave_status (MAILSTREAM *stream,char *mailbox,MAILSTATUS *status)
+{
+  int i,c;
+  fprintf (slaveout,"S%lx %lu %lu %lu %lu %lu %lu ",
+         (unsigned long) stream,status->flags,status->messages,status->recent,
+         status->unseen,status->uidnext,status->uidvalidity,mailbox);
+                               /* yow!  are we paranoid enough yet? */
+  for (i = 0; (i < 500) && (c = *mailbox++); ++i) switch (c) {
+  case '\r': case '\n':                /* newline in a mailbox name? */
+    c = ' ';
+  default:
+    putc (c,slaveout);
+  }
+  putc ('\n',slaveout);
+  fflush (slaveout);
+}
+\f
+/* Notification event
+ * Accepts: MAIL stream
+ *         string to log
+ *         error flag
+ */
+
+void slave_notify (MAILSTREAM *stream,char *string,long errflg)
+{
+  int i,c;
+  fprintf (slaveout,"N%lx %lu ",(unsigned long) stream,errflg);
+                               /* prevent more than 500 bytes */
+  for (i = 0; (i < 500) && (c = *string++); ++i) switch (c) {
+  case '\r': case '\n':                /* or embedded newline */
+    c = ' ';
+  default:
+    putc (c,slaveout);
+  }
+  putc ('\n',slaveout);
+  fflush (slaveout);
+}
+
+
+/* Log an event for the user to see
+ * Accepts: string to log
+ *         error flag
+ */
+
+void slave_log (char *string,long errflg)
+{
+  int i,c;
+  fprintf (slaveout,"L%lu ",errflg);
+                               /* prevent more than 500 bytes */
+  for (i = 0; (i < 500) && (c = *string++); ++i) switch (c) {
+  case '\r': case '\n':                /* or embedded newline */
+    c = ' ';
+  default:
+    putc (c,slaveout);
+  }
+  putc ('\n',slaveout);
+  fflush (slaveout);
+}
+\f
+/* About to enter critical code
+ * Accepts: stream
+ */
+
+void slave_critical (MAILSTREAM *stream)
+{
+  fprintf (slaveout,"C%lx\n",(unsigned long) stream);
+  fflush (slaveout);
+}
+
+
+/* About to exit critical code
+ * Accepts: stream
+ */
+
+void slave_nocritical (MAILSTREAM *stream)
+{
+  fprintf (slaveout,"X%lx\n",(unsigned long) stream);
+  fflush (slaveout);
+}
+\f
+/* Disk error found
+ * Accepts: stream
+ *         system error code
+ *         flag indicating that mailbox may be clobbered
+ * Returns: abort flag
+ */
+
+long slave_diskerror (MAILSTREAM *stream,long errcode,long serious)
+{
+  char tmp[MAILTMPLEN];
+  int c;
+  long ret = NIL;
+  fprintf (slaveout,"D%lx %lu %lu\n",(unsigned long) stream,errcode,serious);
+  fflush (slaveout);
+  switch (c = getc (slavein)) {
+  case EOF:                    /* pipe broken */
+    slave_fatal ("Pipe broken reading diskerror response");
+  case '+':                    /* user wants to abort */
+    ret = LONGT;
+  case '-':                    /* no abort */
+    break;
+  default:
+    sprintf (tmp,"Unknown master response for diskerror: %c",c);
+    slave_fatal (tmp);
+  }
+  return ret;
+}
+
+
+/* Log a fatal error event
+ * Accepts: string to log
+ * Does not return
+ */
+
+void slave_fatal (char *string)
+{
+  int i,c;
+  syslog (LOG_ALERT,"IMAP toolkit slave process crash: %.500s",string);
+  putc ('F',slaveout);
+                               /* prevent more than 500 bytes */
+  for (i = 0; (i < 500) && (c = *string++); ++i) switch (c) {
+  case '\r': case '\n':                /* newline in a mailbox name? */
+    c = ' ';
+  default:
+    putc (c,slaveout);
+  }
+  putc ('\n',slaveout);
+  fflush (slaveout);
+  abort ();                    /* die */
+}
+\f
+/* Append read buffer
+ * Accepts: number of bytes to read
+ *         error message if fails
+ * Returns: read-in string
+ */
+
+static char *slave_append_read (unsigned long n,char *error)
+{
+#if 0
+  unsigned long i;
+#endif
+  int c;
+  char *t,tmp[MAILTMPLEN];
+  char *s = (char *) fs_get (n + 1);
+  s[n] = '\0';
+#if 0
+  /* This doesn't work on Solaris with GCC.  I think that it's a C library
+   * bug, since the problem only shows up if the application does fread()
+   * on some other file
+   */
+  for (t = s; n && ((i = fread (t,1,n,slavein)); t += i,n -= i);
+#else
+  for (t = s; n && ((c = getc (slavein)) != EOF); *t++ = c,--n);
+#endif
+  if (n) {
+    sprintf(tmp,"Pipe broken reading %.100s with %lu bytes remaining",error,n);
+    slave_fatal (tmp);
+  }
+  return s;
+}
+\f
+/* Append message callback
+ * Accepts: MAIL stream
+ *         append data package
+ *         pointer to return initial flags
+ *         pointer to return message internal date
+ *         pointer to return stringstruct of message or NIL to stop
+ * Returns: T if success (have message or stop), NIL if error
+ */
+
+long slave_append (MAILSTREAM *stream,void *data,char **flags,char **date,
+                  STRING **message)
+{
+  char tmp[MAILTMPLEN];
+  unsigned long n;
+  int c;
+  APPENDDATA *ad = (APPENDDATA *) data;
+                               /* flush text of previous message */
+  if (ad->flags) fs_give ((void **) &ad->flags);
+  if (ad->date) fs_give ((void **) &ad->date);
+  if (ad->msg) fs_give ((void **) &ad->msg);
+  *flags = *date = NIL;                /* assume no flags or date */
+  fputs ("A\n",slaveout);      /* tell master we're doing append callback */
+  fflush (slaveout);
+  switch (c = getc (slavein)) {        /* what did master say? */
+  case '+':                    /* have message, get size of flags */
+    for (n = 0; isdigit (c = getc (slavein)); n *= 10, n += (c - '0'));
+    if (c != ' ') {
+      if (c == EOF) sprintf (tmp,"Pipe broken after flag size %lu",n);
+      sprintf (tmp,"Missing delimiter after flag size %lu: %c",n,c);
+      slave_fatal (tmp);
+    }
+    if (n) *flags = ad->flags = slave_append_read (n,"flags");
+                               /* get size of date */
+    for (n = 0; isdigit (c = getc (slavein)); n *= 10, n += (c - '0'));
+    if (c != ' ') {
+      if (c == EOF) sprintf (tmp,"Pipe broken after date size %lu",n);
+      else sprintf (tmp,"Missing delimiter after date size %lu: %c",n,c);
+      slave_fatal (tmp);
+    }
+    if (n) *date = ad->date = slave_append_read (n,"date");
+                               /* get size of message */
+    for (n = 0; isdigit (c = getc (slavein)); n *= 10, n += (c - '0'));
+    if (c != ' ') {
+      if (c == EOF) sprintf (tmp,"Pipe broken after message size %lu",n);
+      sprintf (tmp,"Missing delimiter after message size %lu: %c",n,c);
+      slave_fatal (tmp);
+    }
+    if (n) {                   /* make buffer for message */
+      ad->msg = slave_append_read (n,"message");
+                               /* initialize stringstruct */
+      INIT (&ad->message,mail_string,(void *) ad->msg,n);
+      ad->first = NIL;         /* no longer first message */
+      *message = &ad->message; /* return message */
+    }
+    else *message = NIL;       /* empty message */
+    return LONGT;
+  case '-':                    /* error */
+    *message = NIL;            /* set stop */
+    break;
+  case EOF:                    /* end of file */
+    slave_fatal ("Pipe broken reading append response");
+  default:                     /* unknown event */
+    sprintf (tmp,"Unknown master response for append: %c",c);
+    slave_fatal (tmp);
+  }
+  return NIL;                  /* return failure */
+}
+\f
+/* Proxy copy across mailbox formats
+ * Accepts: mail stream
+ *         sequence to copy on this stream
+ *         destination mailbox
+ *         option flags
+ * Returns: T if success, else NIL
+ */
+
+long slaveproxycopy (MAILSTREAM *stream,char *sequence,char *mailbox,
+                    long options)
+{
+  fputs ("&\n",slaveout);      /* redo copy as append */
+  fflush (slaveout);
+  return NIL;                  /* failure for now */
+}