summaryrefslogtreecommitdiff
path: root/imap/src/osdep/unix/flocksim.c
diff options
context:
space:
mode:
Diffstat (limited to 'imap/src/osdep/unix/flocksim.c')
-rw-r--r--imap/src/osdep/unix/flocksim.c920
1 files changed, 920 insertions, 0 deletions
diff --git a/imap/src/osdep/unix/flocksim.c b/imap/src/osdep/unix/flocksim.c
new file mode 100644
index 00000000..82f07837
--- /dev/null
+++ b/imap/src/osdep/unix/flocksim.c
@@ -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
+
+/* 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;
+
+ /* 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;
+ }
+
+ /* 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 */
+}
+
+/* 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
+
+/* 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
+
+/* 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>.
+ */
+
+/* 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]);
+ }
+
+ 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;
+
+ 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);
+
+ 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;
+
+ 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 */
+}
+
+/* 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;
+}
+
+/* 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;
+}
+
+/* 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 */
+}
+
+/* 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);
+}
+
+/* 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);
+}
+
+/* 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);
+}
+
+/* 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 */
+}
+
+/* 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;
+}
+
+/* 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 */
+}
+
+/* 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 */
+}