386BSD 0.1 development
authorWilliam F. Jolitz <wjolitz@soda.berkeley.edu>
Mon, 1 Jun 1992 23:24:47 +0000 (15:24 -0800)
committerWilliam F. Jolitz <wjolitz@soda.berkeley.edu>
Mon, 1 Jun 1992 23:24:47 +0000 (15:24 -0800)
Work on file usr/src/libexec/uucp/sys3.unx

Co-Authored-By: Lynne Greer Jolitz <ljolitz@cardio.ucsf.edu>
Synthesized-from: 386BSD-0.1

usr/src/libexec/uucp/sys3.unx [new file with mode: 0644]

diff --git a/usr/src/libexec/uucp/sys3.unx b/usr/src/libexec/uucp/sys3.unx
new file mode 100644 (file)
index 0000000..0e8d0ee
--- /dev/null
@@ -0,0 +1,2647 @@
+/* sys3.unx
+   The system dependent spool directory subroutines for Unix.
+
+   Copyright (C) 1991, 1992 Ian Lance Taylor
+
+   This file is part of the Taylor UUCP package.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+   The author of the program may be contacted at ian@airs.com or
+   c/o AIRS, P.O. Box 520, Waltham, MA 02254.
+
+   $Log:       sys3.unx,v $
+# Revision 1.2  92/05/13  05:42:07  rich
+# ported to 386bsd
+# 
+# Revision 1.1  1992/05/10  18:00:16  rich
+# Initial revision
+#
+   Revision 1.48  1992/04/01  22:36:48  ian
+   David J. MacKenzie: some USG_STATFS systems use 512 despite f_bsize
+
+   Revision 1.47  1992/03/30  15:03:07  ian
+   Niels Baggesen: USG statfs has an f_bsize field
+
+   Revision 1.46  1992/03/28  22:06:38  ian
+   Michael I Bushnell: renamed enum tstatus to avoid header file conflict
+
+   Revision 1.45  1992/03/26  20:20:28  ian
+   Reduce race condition in fsdo_lock
+
+   Revision 1.44  1992/03/15  01:54:46  ian
+   All execs are now done in isspawn, all waits are done in iswait
+
+   Revision 1.43  1992/03/12  19:54:43  ian
+   Debugging based on types rather than number
+
+   Revision 1.42  1992/03/10  20:45:58  ian
+   Check size of destination file system as well as temporary system
+
+   Revision 1.41  1992/03/09  19:42:43  ian
+   Ted Lindgreen: don't send mail for nonexistent file
+
+   Revision 1.40  1992/03/04  01:40:51  ian
+   Thomas Fischer: tweaked a bit for the NeXT
+
+   Revision 1.39  1992/02/29  04:07:08  ian
+   Added -j option to uucp and uux
+
+   Revision 1.38  1992/02/29  01:06:59  ian
+   Chip Salzenberg: recheck file permissions before sending
+
+   Revision 1.37  1992/02/28  15:57:58  ian
+   Give error if esysdep_open_send is given a directory
+
+   Revision 1.36  1992/02/24  22:05:30  ian
+   Roberto Biancardi: support F_CHSIZE and F_FREESP in esysdep_truncate
+
+   Revision 1.35  1992/02/24  20:07:43  ian
+   John Theus: some systems don't have <fcntl.h>
+
+   Revision 1.34  1992/02/23  03:26:51  ian
+   Overhaul to use automatic configure shell script
+
+   Revision 1.33  1992/02/20  04:18:59  ian
+   Added uustat
+
+   Revision 1.32  1992/02/08  03:54:18  ian
+   Include <string.h> only in <uucp.h>, added 1992 copyright
+
+   Revision 1.31  1992/02/02  20:42:40  ian
+   Niels Baggesen: case enum to int before comparison
+
+   Revision 1.30  1992/02/01  00:54:31  ian
+   Michael Nolan: cast alloca return value
+
+   Revision 1.29  1992/01/29  04:27:11  ian
+   Jay Vassos-Libove: removed some conflicting declarations
+
+   Revision 1.28  1992/01/28  04:34:10  ian
+   Marty Shannon: handle trailing '/' to indicate directory
+
+   Revision 1.27  1992/01/14  04:51:48  ian
+   David Nugent: don't declare chmod
+
+   Revision 1.26  1992/01/14  04:25:20  ian
+   Chip Salzenberg: avoid use before set warning
+
+   Revision 1.25  1992/01/14  03:46:55  ian
+   Chip Salzenberg: handle invalid status values in status files
+
+   Revision 1.24  1992/01/13  06:11:39  ian
+   David Nugent: can't declare open or fcntl
+
+   Revision 1.23  1992/01/05  03:18:54  ian
+   Avoid redefining SEEK_SET
+
+   Revision 1.22  1992/01/04  22:56:22  ian
+   Added extern definition
+
+   Revision 1.21  1992/01/03  05:44:35  ian
+   Remove temporary file if link fails in fsdo_lock
+
+   Revision 1.20  1991/12/29  04:04:18  ian
+   Added a bunch of extern definitions
+
+   Revision 1.19  1991/12/22  22:14:19  ian
+   Monty Solomon: added HAVE_UNISTD_H configuration parameter
+
+   Revision 1.18  1991/12/22  20:50:47  ian
+   Franc,ois Pinard: fixed bug in fsysdep_get_status
+
+   Revision 1.17  1991/12/12  18:35:47  ian
+   Do locking with link to avoid races and to permit running as root
+
+   Revision 1.16  1991/12/12  17:45:34  ian
+   fcopy_file now creates the file with IPRIVATE_MODE
+
+   Revision 1.15  1991/12/11  03:59:19  ian
+   Create directories when necessary; don't just assume they exist
+
+   Revision 1.14  1991/12/09  19:07:07  ian
+   Richard Todd: add HAVE_V2_LOCKFILES--binary number in lock file
+
+   Revision 1.13  1991/12/03  02:59:46  ian
+   Using LOCKDIR clobbered a byte on the stack
+
+   Revision 1.12  1991/12/01  02:23:12  ian
+   Niels Baggesen: don't multiply include <unistd.h>
+
+   Revision 1.11  1991/12/01  01:12:40  ian
+   Marty Shannon: accept truncated status file; also eliminated scanf calls
+
+   Revision 1.10  1991/11/30  23:28:26  ian
+   Marty Shannon: some systems need a fake version of the rename system call
+
+   Revision 1.9  1991/11/21  21:43:42  ian
+   Eliminate unused MIN_FREE_BYTES
+
+   Revision 1.8  1991/11/21  21:07:46  ian
+   Brian Campbell: offer ltrunc as an alternative to ftruncate
+
+   Revision 1.7  1991/11/10  21:32:16  ian
+   Fixed ftruncate call
+
+   Revision 1.6  1991/11/10  19:24:22  ian
+   Added pffile protocol entry point for file level control
+
+   Revision 1.5  1991/11/07  19:32:28  ian
+   Chip Salzenberg: allow LOCKDIR, and check that locking process exists
+
+   Revision 1.4  1991/09/19  17:28:01  ian
+   Chip Salzenberg: make sure spool directory files are not world readable
+
+   Revision 1.3  1991/09/19  03:23:34  ian
+   Chip Salzenberg: append to private debugging file, don't overwrite it
+
+   Revision 1.2  1991/09/19  03:06:04  ian
+   Chip Salzenberg: put BNU temporary files in system's directory
+
+   Revision 1.1  1991/09/10  19:45:50  ian
+   Initial revision
+
+   */
+
+#include "uucp.h"
+
+#if USE_RCS_ID
+char sys3_unx_rcsid[] = "$Id: sys3.unx,v 1.2 92/05/13 05:42:07 rich Exp Locker: root $";
+#endif
+
+#include <ctype.h>
+#include <errno.h>
+
+#if USE_STDIO && HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#include "system.h"
+#include "sysdep.h"
+
+#include <pwd.h>
+
+#if HAVE_FCNTL_H
+#include <fcntl.h>
+#else
+#if HAVE_SYS_FILE_H
+#include <sys/file.h>
+#endif
+#endif
+
+#ifndef O_RDONLY
+#define O_RDONLY 0
+#define O_WRONLY 1
+#define O_RDWR 2
+#endif
+
+/* Get the right header files for statfs and friends.  This stuff is
+   from David MacKenzie's df program.  */
+
+#ifdef FS_STATVFS
+#include <sys/statvfs.h>
+extern int statvfs ();
+#endif
+
+#ifdef FS_USG_STATFS
+#include <sys/statfs.h>
+extern int statfs ();
+#endif
+
+#ifdef FS_MNTENT
+#include <sys/vfs.h>
+extern int statfs ();
+#endif
+
+#ifdef FS_GETMNT
+#include <sys/param.h>
+#include <sys/mount.h>
+extern int statfs ();
+#endif
+
+#ifdef FS_STATFS
+#include <sys/mount.h>
+extern int statfs ();
+#endif
+
+#ifdef FS_USTAT
+#include <ustat.h>
+extern int ustat ();
+#endif
+
+/* We need a definition for SEEK_SET.  */
+
+#ifndef SEEK_SET
+#define SEEK_SET 0
+#endif
+
+/* External functions.  */
+extern int close (), link (), read (), write ();
+#ifndef __386BSD__
+extern int kill ();
+#endif __386BSD__
+extern int fstat (), stat ();
+extern int fclose (), fseek (), pclose ();
+extern pid_t getpid ();
+extern off_t lseek ();
+extern char *strrchr ();
+
+#if HAVE_FTRUNCATE
+extern int ftruncate ();
+#endif
+
+#if HAVE_RENAME
+extern int rename ();
+#else
+static int rename P((const char *zfrom, const char *zto));
+#endif
+\f
+/* There are several types of files that go in the spool directory,
+   and they go into various different subdirectories.  When using
+   SPOOLDIR_TAYLOR, there is a subdirectory for each system for which
+   communication occurs; these system names have been made canonical
+   via fread_system_info or ztranslate_name, so they will fit any
+   name length restrictions (namely 14 characters on System V).
+   Whenever the system name LOCAL appears below, it means whatever
+   the local system name is.
+
+   Command files
+   These contain instructions for uucico indicating what files to transfer
+   to and from what systems.  Each line of a work file is a command
+   beginning with S, R or X.
+   #if ! SPOOLDIR_TAYLOR
+   They are named C.ssssssgqqqq, where ssssss is the system name to
+   transfer to or from, g is the grade and qqqq is the sequence number.
+   #if SPOOLDIR_V2
+   They are put in the spool directory.
+   #elif SPOOLDIR_BSD42 | SPOOLDIR_BSD43
+   They are put in the directory C.
+   #elif SPOOLDIR_BNU
+   They are put in a directory named for the system for which they were
+   created.
+   #elif SPOOLDIR_ULTRIX
+   If the directory sys/ssssss exists, they are put in the directory
+   sys/ssssss/C; otherwise, they are put in the directory sys/DEFAULT/C.
+   #endif
+   #else SPOOLDIR_TAYLOR
+   They are named C.gqqqq, where g is the grade and qqqq is the sequence
+   number, and are placed in the directory ssssss/C. where ssssss is
+   the system name to transfer to or from.
+   #endif
+
+   Data files
+   There are files to be transferred to other systems.  Some files to
+   be transferred may not be in the spool directory, depending on how
+   uucp was invoked.  Data files are named in work files, so it is
+   never necessary to look at them directly (except to remove old ones);
+   it is only necessary to create them.  These means that the many
+   variations in naming are inconsequential.
+   #if ! SPOOLDIR_TAYLOR
+   They are named D.ssssssgqqqq where ssssss is a system name (which
+   may be LOCAL for locally initiated transfers or a remote system for
+   remotely initiated transfers, except that BNU appears to use the
+   system the file is being transferred to), g is the grade and qqqq
+   is the sequence number.  Some systems use a trailing subjob ID
+   number, but we currently do not.  The grade is not important, and
+   some systems do not use it.  If the data file is to become an
+   execution file on another system the grade (if present) will be
+   'X'.  Otherwise Ultrix appears to use 'b'; the uux included with
+   gnuucp 1.0 appears to use 'S'; SCO does not appear to use a grade,
+   although it does use a subjob ID number.
+   #if SPOOLDIR_V2
+   They are put in the spool directory.
+   #elif SPOOLDIR_BSD42
+   If the name begins with D.LOCAL, the file is put in the directory
+   D.LOCAL.  Otherwise the file is put in the directory D..
+   #elif SPOOLDIR_BSD43
+   If the name begins with D.LOCALX, the file is put in the directory
+   D.LOCALX.  Otherwise if the name begins with D.LOCAL, the file is
+   put in the directory D.LOCAL Otherwise the file is put in the
+   directory D..
+   #elif SPOOLDIR_BNU
+   They are put in a directory named for the system for which they
+   were created.
+   #elif SPOOLDIR_ULTRIX
+   Say the file is being transferred to system REMOTE.  If the
+   directory sys/REMOTE exists, then if the file begins with D.LOCALX
+   it is put in sys/REMOTE/D.LOCALX, if the file begins with D.LOCAL
+   it is put in sys/REMOTE/D.LOCAL, and otherwise it is put in
+   sys/REMOTE/D..  If the directory sys/REMOTE does not exist, the
+   same applies except that DEFAULT is used instead of REMOTE.
+   #endif
+   #else SPOOLDIR_TAYLOR
+   If the file is to become an executable file on another system it is
+   named D.Xqqqq, otherwise it is named D.qqqq where in both cases
+   qqqq is a sequence number.  If the corresponding C. file is in
+   directory ssssss/C., a D.X file is placed in ssssss/D.X and a D.
+   file is placed in ssssss/D..
+   #endif
+
+   Execute files
+   These are files that specify programs to be executed.  They are
+   created by uux, perhaps as run on another system.  These names are
+   important, because a file transfer done to an execute file name
+   causes an execution to occur.  The name is X.ssssssgqqqq, where
+   ssssss is the requesting system, g is the grade, and qqqq is a
+   sequence number.
+   #if SPOOLDIR_V2 | SPOOLDIR_BSD42
+   These files are placed in the spool directory.
+   #elif SPOOLDIR_BSD43
+   These files are placed in the directory X..
+   #elif SPOOLDIR_BNU
+   These files are put in a directory named for the system for which
+   the files were created.
+   #elif SPOOLDIR_ULTRIX
+   If there is a spool directory (sys/ssssss) for the requesting
+   system, the files are placed in sys/ssssss/X.; otherwise, the files
+   are placed in sys/DEFAULT/X..
+   #elif SPOOLDIR_TAYLOR
+   The system name is automatically truncated to seven characters when
+   a file is created.  The files are placed in the subdirectory X. of
+   a directory named for the system for which the files were created.
+   #endif
+
+   Temporary receive files
+   These are used when receiving files from another system.  They are
+   later renamed to the final name.  The actual name is unimportant,
+   although it generally begins with TM..
+   #if SPOOLDIR_V2 | SPOOLDIR_BSD42
+   These files are placed in the spool directory.
+   #elif SPOOLDIR_BSD43 | SPOOLDIR_ULTRIX | SPOOLDIR_TAYLOR
+   These files are placed in the directory .Temp.
+   #elif SPOOLDIR_BNU
+   These files are placed in a directory named for the system for
+   which they were created.
+   #endif
+
+   Lock files
+   These files are used to lock systems, devices and special files.
+   The file LCK..ssssss is used to lock a system, where ssssss is the
+   system name.  The file LCK..dev is used to lock a device, where dev
+   is the device name.  The file LCK.file is used to lock a file,
+   where file is LOG for the log file, SQ for the system sequence
+   number file, or SEQF for the work queue sequence number file.  At
+   least under Ultrix, the file LCK.XQT is used to lock uuxqt
+   execution.  Some systems supposedly use LCK.SEQL for something.  On
+   some systems, the contents of the lock file is the ASCII process
+   id; on others, it is the process id as four data bytes.  As far as
+   I can tell, the only lock file I really have to get right is the
+   one locking a device, so that cu won't use it; if somebody tries to
+   run old UUCP and this at the same time, they will probably have
+   trouble unless they make sure the locking is correct for their
+   system.  Not that there is any easy way to make that check,
+   unfortunately.  Supposedly all normal systems put the LCK files in
+   the spool directory, and this package will do that also.
+
+   System status files
+   These are used to record when the last call was made to the system
+   and what the status is.  They are used to prevent frequent recalls
+   to a system which is not responding.  I will not attempt to
+   recreate the format of these exactly, since they are not all that
+   important.  They will be put in the directory .Status, as in BNU,
+   and they use the system name as the name of the file.
+
+   Log files
+   These are used to record what UUCP has done.  I will not attempt to
+   recreate the format of these at all.  They will be stored in the
+   directory .Log/uucico (or .Log/uucp, .Log/uux, .Log/uuxqt) and
+   named for the relevant system.  This is the format used by BNU.
+
+   Statistics files
+   I don't really know the format of these.  They are apparently used
+   to keep track of what jobs have been run by UUCP, but at least on
+   Ultrix they don't seem to be used very consistently.
+
+   Sequence file
+   This is used to generate a unique sequence number.  It contains an
+   ASCII number.
+   #if SPOOLDIR_V2 | SPOOLDIR_BSD42 | SPOOLDIR_BSD43
+   The file is named SEQF and is kept in the spool directory.
+   #elif SPOOLDIR_BNU
+   A separate sequence file is kept for each system in the directory
+   .Sequence with the name of the system.
+   #elif SPOOLDIR_ULTRIX
+   Each system with a file sys/ssssss has a sequence file in
+   sys/ssssss/.SEQF.  Other systems use sys/DEFAULT/.SEQF.
+   #else SPOOLDIR_TAYLOR
+   A sequence file named SEQF is kept in the directory ssssss for each
+   system.
+   #endif
+
+   Audit files
+   Debugging messages are stored in these when running as a slave.  We
+   use the file AUDIT in the spool directory.  */
+\f
+/* Local functions.  */
+
+static char *zsstatic_size P((int c));
+#if SPOOLDIR_TAYLOR
+static const char *zsappend3 P((const char *zdir1, const char *zdir2,
+                               const char *zfile));
+#endif
+#if SPOOLDIR_ULTRIX
+static const char *zsappend4 P((const char *zdir1, const char *zdir2,
+                               const char *zdir3, const char *zfile));
+#endif
+static const char *zsfind_file P((const char *zsimple,
+                                 const char *zsystem));
+static boolean fscmd_seq P((const char *zsystem, char *zseq));
+static const char *zsfile_name P((int btype, const char *zsystem,
+                                 int bgrade, char *ztname,
+                                 char *zdname, char *zxname));
+\f
+/* A few routines to manipulate strings with directories.  */
+
+#define CSTATICLEN (50)
+static char abSstatic[CSTATICLEN];
+static char *zSstatic_alloc;
+static int cSstatic_alloc;
+
+/* Return a pointer to a static buffer of a certain size.  */
+
+static char *
+zsstatic_size (c)
+     int c;
+{
+  if (c <= CSTATICLEN)
+    return abSstatic;
+  if (cSstatic_alloc < c)
+    {
+      xfree ((pointer) zSstatic_alloc);
+      zSstatic_alloc = (char *) xmalloc (c);
+      cSstatic_alloc = c;
+   }
+  return zSstatic_alloc;
+}
+
+/* Copy a string into a static buffer.  */
+
+const char *
+zscopy (z)
+     const char *z;
+{
+  char *zret;
+
+  zret = zsstatic_size (strlen (z) + 1);
+  strcpy (zret, z);
+  return (const char *) zret;
+}
+
+/* Stick a directory and file name together.  Return a static buffer
+   holding the combined name.  This is called by Unix system dependent
+   routines outside this file.  */
+
+const char *
+zsappend (zdir, zfile)
+     const char *zdir;
+     const char *zfile;
+{
+  char *zret;
+
+  zret = zsstatic_size (strlen (zdir) + strlen (zfile) + sizeof "/");
+  sprintf (zret, "%s/%s", zdir, zfile);
+  return (const char *) zret;
+}
+
+#if SPOOLDIR_TAYLOR
+
+/* Stick two directories and a file name together.  Return a static
+   buffer holding the combined name.  */
+
+static const char *
+zsappend3 (zdir1, zdir2, zfile)
+     const char *zdir1;
+     const char *zdir2;
+     const char *zfile;
+{
+  char *zret;
+
+  zret = zsstatic_size (strlen (zdir1) + strlen (zdir2)
+                       + strlen (zfile) + sizeof "//");
+  sprintf (zret, "%s/%s/%s", zdir1, zdir2, zfile);
+  return (const char *) zret;
+}
+    
+#endif /* SPOOLDIR_TAYLOR */
+
+#if SPOOLDIR_ULTRIX
+
+/* Stick three directories and a file name together.  Return a static
+   buffer holding the combined name.  */
+
+static const char *
+zsappend4 (zdir1, zdir2, zdir3, zfile)
+     const char *zdir1;
+     const char *zdir2;
+     const char *zdir3;
+     const char *zfile;
+{
+  char *zret;
+
+  zret = zsstatic_size (strlen (zdir1) + strlen (zdir2) + strlen (zdir3)
+                       + strlen (zfile) + sizeof "///");
+  sprintf (zret, "%s/%s/%s/%s", zdir1, zdir2, zdir3, zfile);
+  return (const char *) zret;
+}
+
+/* See whether an ULTRIX spool directory exists for a system.  For system
+   ssssss, the spool directory is called sys/ssssss.  */
+
+boolean
+fsultrix_has_spool (zsystem)
+     const char *zsystem;
+{
+  char *z;
+
+  z = (char *) alloca (sizeof "sys/" + strlen (zsystem));
+  sprintf (z, "sys/%s", zsystem);
+  return fsdirectory_exists (z);
+}
+
+#endif /* SPOOLDIR_ULTRIX */
+\f
+/* Create a spool directory for a system.  This is only relevant for
+   SPOOLDIR_BNU or SPOOLDIR_TAYLOR.  The system specific directories
+   for Ultrix are meant to be created by hand.  */
+
+#if SPOOLDIR_TAYLOR | SPOOLDIR_BNU
+
+static boolean fsmkdir P((const char *zdir));
+
+static boolean
+fsmkdir (zdir)
+     const char *zdir;
+{
+  if (mkdir ((char *) zdir, IDIRECTORY_MODE) < 0)
+    {
+      ulog (LOG_ERROR, "mkdir (%s): %s", zdir, strerror (errno));
+      return FALSE;
+    }
+  return TRUE;
+}
+
+#endif /* SPOOLDIR_TAYLOR | SPOOLDIR_BNU */
+
+boolean
+fsysdep_make_spool_dir (qsys)
+     const struct ssysteminfo *qsys;
+{
+  const char *zsystem;
+
+  zsystem = qsys->zname;
+
+#if SPOOLDIR_BNU
+  if (fsdirectory_exists (zsystem))
+    return TRUE;
+  if (! fsmkdir (zsystem))
+    return FALSE;
+#endif /* SPOOLDIR_BNU */
+
+#if SPOOLDIR_TAYLOR
+  if (fsdirectory_exists (zsystem))
+    return TRUE;
+  if (! fsmkdir (zsystem)
+      || ! fsmkdir (zsappend (zsystem, "C."))
+      || ! fsmkdir (zsappend (zsystem, "D."))
+      || ! fsmkdir (zsappend (zsystem, "D.X"))
+      || ! fsmkdir (zsappend (zsystem, "X.")))
+    return FALSE;
+#endif /* SPOOLDIR_TAYLOR */
+
+  return TRUE;
+}
+\f
+/* Given the name of a file as specified in a UUCP command, and the
+   system for which this file has been created, return where to find
+   it in the spool directory.  The file will begin with C. (a command
+   file), D. (a data file) or X. (an execution file).  The return
+   value of this function will point to a static buffer.  */
+
+static const char *
+zsfind_file (zsimple, zsystem)
+     const char *zsimple;
+     const char *zsystem;
+{
+  if (zsimple[1] != '.'
+      || (*zsimple != 'C'
+         && *zsimple != 'D'
+         && *zsimple != 'X'))
+    {
+      ulog (LOG_ERROR, "Unrecognized file name %s", zsimple);
+      return NULL;
+    }
+
+  switch (*zsimple)
+    {
+    case 'C':
+#if SPOOLDIR_V2
+      return zscopy (zsimple);
+#endif /* SPOOLDIR_V2 */
+#if SPOOLDIR_BSD42 | SPOOLDIR_BSD43
+      return zsappend ("C.", zsimple);
+#endif /* SPOOLDIR_BSD42 | SPOOLDIR_BSD43 */
+#if SPOOLDIR_BNU
+      return zsappend (zsystem, zsimple);
+#endif /* SPOOLDIR_BNU */
+#if SPOOLDIR_ULTRIX
+      if (fsultrix_has_spool (zsystem))
+       return zsappend4 ("sys", zsystem, "C.", zsimple);
+      else
+       return zsappend4 ("sys", "DEFAULT", "C.", zsimple);
+#endif
+#if SPOOLDIR_TAYLOR
+      return zsappend3 (zsystem, "C.", zsimple);
+#endif
+
+    case 'D':
+#if SPOOLDIR_V2
+      return zscopy (zsimple);
+#endif /* SPOOLDIR_V2 */
+#if SPOOLDIR_BSD42 | SPOOLDIR_BSD43
+      {
+       int c;
+       boolean ftruncated;
+       char *zalloc;
+      
+       /* D.LOCAL in D.LOCAL/, others in D./.  If BSD43, D.LOCALX in
+          D.LOCALX/.  */
+       ftruncated = TRUE;
+       if (strncmp (zsimple + 2, zLocalname, strlen (zLocalname)) == 0)
+         {
+           c = strlen (zLocalname);
+           ftruncated = FALSE;
+         }
+       else if (strncmp (zsimple + 2, zLocalname, 7) == 0)
+         c = 7;
+       else if (strncmp (zsimple + 2, zLocalname, 6) == 0)
+         c = 6;
+       else
+         c = 0;
+#if SPOOLDIR_BSD43
+       if (c > 0 && zsimple[c + 2] == 'X')
+         c++;
+#endif /* SPOOLDIR_BSD43 */
+       if (c > 0)
+         {
+           zalloc = (char *) alloca (c + 3);
+           strncpy (zalloc, zsimple, c + 2);
+           zalloc[c + 2] = '\0';
+
+           /* If we truncated the system name, and there is no existing
+              directory with the truncated name, then just use D..  */
+           if (ftruncated && ! fsdirectory_exists (zalloc))
+             return zsappend ("D.", zsimple);
+
+           return zsappend (zalloc, zsimple);
+         }
+       else
+         return zsappend ("D.", zsimple);
+      }
+#endif /* SPOOLDIR_BSD42 | SPOOLDIR_BSD43 */
+#if SPOOLDIR_BNU
+      return zsappend (zsystem, zsimple);
+#endif /* SPOOLDIR_BNU */
+#if SPOOLDIR_ULTRIX
+      {
+       int c;
+       boolean ftruncated;
+       char *zalloc;
+       const char *zdir;
+      
+       /* D.LOCALX in D.LOCALX/, D.LOCAL in D.LOCAL/, others in D./.  */
+
+       ftruncated = TRUE;
+       if (strncmp (zsimple + 2, zLocalname, strlen (zLocalname)) == 0)
+         {
+           c = strlen (zLocalname);
+           ftruncated = FALSE;
+         }
+       else if (strncmp (zsimple + 2, zLocalname, 7) == 0)
+         c = 7;
+       else if (strncmp (zsimple + 2, zLocalname, 6) == 0)
+         c = 6;
+       else
+         c = 0;
+       if (c > 0 && zsimple[c + 2] == 'X')
+         c++;
+       if (c > 0)
+         {
+           zalloc = (char *) alloca (c + 3);
+           strncpy (zalloc, zsimple, c + 2);
+           zalloc[c + 2] = '\0';
+           zdir = zalloc;
+
+           /* If we truncated the name, and there is no directory for
+              the truncated name, then don't use it.  */
+           if (ftruncated)
+             {
+               char *zlook;
+
+               zlook = (char *) alloca (c + 20 + strlen (zsystem));
+               if (fsultrix_has_spool (zsystem))
+                 sprintf (zlook, "sys/%s/%s", zsystem, zdir);
+               else
+                 sprintf (zlook, "sys/DEFAULT/%s", zdir);
+               if (! fsdirectory_exists (zlook))
+                 zdir = "D.";
+             }
+         }
+       else
+         zdir = "D.";
+      
+       if (fsultrix_has_spool (zsystem))
+         return zsappend4 ("sys", zsystem, zdir, zsimple);
+       else
+         return zsappend4 ("sys", "DEFAULT", zdir, zsimple);
+      }
+#endif /* SPOOLDIR_ULTRIX */
+#if SPOOLDIR_TAYLOR
+      if (zsimple[2] == 'X')
+       return zsappend3 (zsystem, "D.X", zsimple);
+      else
+       return zsappend3 (zsystem, "D.", zsimple);
+#endif /* SPOOLDIR_TAYLOR */
+
+      /* Files beginning with X. are execute files.  It is important
+        for security reasons that we know the system which created
+        the X. file.  This is easy under SPOOLDIR_BNU or
+        SPOOLDIR_TAYLOR, because the file will be in a directory
+        named for the system.  Under other schemes, we must get the
+        system name from the X. file name.  To prevent security
+        violations, we set the system name directly here; this will
+        cause problems if the maximum file name length is too short,
+        but hopefully no problem will occur since any System V
+        systems will be using either BNU or TAYLOR.  */
+
+    case 'X':
+#if ! SPOOLDIR_BNU && ! SPOOLDIR_TAYLOR
+      if (strncmp (zsimple + 2, zsystem, strlen (zsimple) - 7) != 0)
+       {
+         char *zcopy;
+
+         zcopy = (char *) alloca (strlen (zsystem) + 8);
+         sprintf (zcopy, "X.%s%s", zsystem,
+                  zsimple + strlen (zsimple) - 5);
+         zsimple = zcopy;
+       }
+#endif /* ! SPOOLDIR_BNU && ! SPOOLDIR_TAYLOR */
+
+#if SPOOLDIR_V2 | SPOOLDIR_BSD42
+      return zscopy (zsimple);
+#endif
+#if SPOOLDIR_BSD43
+      return zsappend ("X.", zsimple);
+#endif
+#if SPOOLDIR_BNU
+      return zsappend (zsystem, zsimple);
+#endif
+#if SPOOLDIR_ULTRIX
+      if (fsultrix_has_spool (zsystem))
+       return zsappend4 ("sys", zsystem, "X.", zsimple);
+      else
+       return zsappend4 ("sys", "DEFAULT", "X.", zsimple);
+#endif
+#if SPOOLDIR_TAYLOR
+      return zsappend3 (zsystem, "X.", zsimple);
+#endif
+
+    default:
+#if DEBUG > 0
+      ulog (LOG_FATAL, "zsfind_file: Can't happen");
+#endif /* DEBUG */
+      return NULL;
+    }
+  /*NOTREACHED*/
+}
+\f
+/* Get the status of a system.  */
+
+/*ARGSUSED*/
+boolean
+fsysdep_get_status (qsys, qret)
+     const struct ssysteminfo *qsys;
+     struct sstatus *qret;
+{
+  const char *zname;
+  FILE *e;
+  char *zline;
+  char *zend, *znext;
+  boolean fbad;
+  int istat;
+
+  zname = zsappend (".Status", qsys->zname);
+  e = fopen (zname, "r");
+  if (e == NULL)
+    {
+      if (errno != ENOENT)
+       {
+         ulog (LOG_ERROR, "fopen (%s): %s", zname, strerror (errno));
+         return FALSE;
+       }
+      zline = NULL;
+    }
+  else
+    {
+      zline = zfgets (e, FALSE);
+      (void) fclose (e);
+    }
+
+  if (zline == NULL)
+    {
+      /* There is either no status file for this system, or it's been
+        truncated, so fake a good status.  */
+      qret->ttype = STATUS_COMPLETE;
+      qret->cretries = 0;
+      qret->ilast = 0;
+      qret->cwait = 0;
+      return TRUE;
+    }
+
+  /* It turns out that scanf is not used much in this program, so for
+     the benefit of small computers we avoid linking it in.  This is
+     basically
+
+     sscanf (zline, "%d %d %ld %d", &qret->ttype, &qret->cretries,
+             &qret->ilast, &qret->cwait);
+
+     except that it's done with strtol.  */
+
+  fbad = FALSE;
+  istat = (int) strtol (zline, &zend, 10);
+  if (zend == zline)
+    fbad = TRUE;
+
+  /* On some systems it may be appropriate to map system dependent status
+     values on to our status values.  Perhaps someday.  */
+
+  if (istat < 0 || istat >= (int) STATUS_VALUES)
+    istat = (int) STATUS_COMPLETE;
+  qret->ttype = (enum tstatus_type) istat;
+  znext = zend;
+  qret->cretries = (int) strtol (znext, &zend, 10);
+  if (zend == znext)
+    fbad = TRUE;
+  znext = zend;
+  qret->ilast = strtol (znext, &zend, 10);
+  if (zend == znext)
+    fbad = TRUE;
+  znext = zend;
+  qret->cwait = (int) strtol (znext, &zend, 10);
+  if (zend == znext)
+    fbad = TRUE;
+
+  xfree ((pointer) zline);
+
+  if (fbad)
+    {
+      ulog (LOG_ERROR, "Bad format of status file for %s", qsys->zname);
+      return FALSE;
+    }
+
+  return TRUE;
+}
+
+/* Set the status of a remote system.  We assume the system is locked
+   when this is called.  */
+
+/*ARGSUSED*/
+boolean
+fsysdep_set_status (qsys, qset)
+     const struct ssysteminfo *qsys;
+     const struct sstatus *qset;
+{
+  const char *zname;
+  FILE *e;
+  int istat;
+
+  zname = zsappend (".Status", qsys->zname);
+
+  e = esysdep_fopen (zname, TRUE, FALSE, TRUE);
+  if (e == NULL)
+    return FALSE;
+  istat = (int) qset->ttype;
+
+  /* On some systems it may be appropriate to map istat onto a system
+     dependent number.  Perhaps someday.  */
+
+  fprintf (e, "%d %d %ld %d %s %s\n", istat, qset->cretries,
+          qset->ilast, qset->cwait, azStatus[(int) qset->ttype],
+          qsys->zname);
+  if (fclose (e) != 0)
+    {
+      ulog (LOG_ERROR, "fclose: %s", strerror (errno));
+      return FALSE;
+    }
+
+  return TRUE;
+}
+\f
+/* Get the real name of a spool file.  */
+
+const char *
+zsysdep_spool_file_name (qsys, zfile)
+     const struct ssysteminfo *qsys;
+     const char *zfile;
+{
+  return zsfind_file (zfile, qsys->zname);
+}
+
+/* Expand a file name on the local system.  The qsys argument is only
+   used to determine which public directory to use.  */
+
+const char *
+zsysdep_real_file_name (qsys, zfile, zname)
+     const struct ssysteminfo *qsys;
+     const char *zfile;
+     const char *zname;
+{
+  const char *ztry;
+  char *zlook;
+
+  if (zfile[0] == '/')
+    ztry = zfile;
+  else if (zfile[0] == '~')
+    {
+      const char *z;
+      char *zcopy;
+
+      z = zstilde_expand (qsys, zfile);
+      zcopy = (char *) alloca (strlen (z) + 1);
+      strcpy (zcopy, z);
+      ztry = zcopy;
+    }
+  else
+    {
+      const char *zpub, *z;
+      char *zcopy;
+
+      /* Put the file in the public directory.  */
+      if (qsys == NULL || qsys->zpubdir == NULL)
+       zpub = zPubdir;
+      else
+       zpub = qsys->zpubdir;
+      z = zsappend (zpub, zfile);
+      zcopy = (char *) alloca (strlen (z) + 1);
+      strcpy (zcopy, z);
+      ztry = zcopy;
+    }
+
+  /* If we don't have a file name to use within a directory, or we
+     haven't named a directory, we use what we've got so far.  If the
+     name ends in a '/', it is assumed to name a directory.  */
+
+  if (zname == NULL)
+    return zscopy (ztry);
+
+  if (ztry[strlen (ztry) - 1] != '/')
+    {
+      if (! fsdirectory_exists (ztry))
+       return zscopy (ztry);
+    }
+  else
+    {
+      char *zcopy;
+      int clen;
+
+      clen = strlen (ztry);
+      zcopy = (char *) alloca (clen + 1);
+      strcpy (zcopy, ztry);
+      zcopy[clen - 1] = '\0';
+      ztry = zcopy;
+    }
+
+  /* Get a name out of zname and tag it on.  */
+
+  zlook = strrchr (zname, '/');
+  if (zlook != NULL)
+    zname = zlook + 1;
+
+  return zsappend (ztry, zname);
+}
+
+/* Return a file name within a directory.  */
+
+const char *
+zsysdep_in_dir (zdir, zfile)
+     const char *zdir;
+     const char *zfile;
+{
+  if (fsdirectory_exists (zdir))
+    return zsappend (zdir, zfile);
+  else
+    return zdir;
+}
+\f
+/* Open a file to send to another system, and return the mode and
+   the size.  */
+
+/*ARGSUSED*/
+openfile_t
+esysdep_open_send (qsys, zfile, fcheck, zuser, pimode, pcbytes, pfgone)
+     const struct ssysteminfo *qsys;
+     const char *zfile;
+     boolean fcheck;
+     const char *zuser;
+     unsigned int *pimode;
+     long *pcbytes;
+     boolean *pfgone;
+{
+  struct stat s;
+  openfile_t e;
+  int o;
+  
+  if (pfgone != NULL)
+    *pfgone = FALSE;
+
+  if (fsdirectory_exists (zfile))
+    {
+      ulog (LOG_ERROR, "%s: is a directory", zfile);
+      return EFILECLOSED;
+    }
+#if USE_STDIO
+  e = fopen (zfile, BINREAD);
+  if (e == NULL)
+    {
+      ulog (LOG_ERROR, "fopen (%s): %s", zfile, strerror (errno));
+      if (pfgone != NULL && errno == ENOENT)
+       *pfgone = TRUE;
+      return NULL;
+    }
+  o = fileno (e);
+#else
+  e = open (zfile, O_RDONLY, 0);
+  if (e == -1)
+    {
+      ulog (LOG_ERROR, "open (%s): %s", zfile, strerror (errno));
+      if (pfgone != NULL && errno == ENOENT)
+       *pfgone = TRUE;
+      return -1;
+    }
+  o = e;
+#endif
+
+  if (fstat (o, &s) == -1)
+    {
+      ulog (LOG_ERROR, "fstat: %s", strerror (errno));
+      s.st_mode = 0666;
+    }
+
+  /* We have to recheck the file permission, although we probably
+     checked it already, because otherwise there would be a window in
+     which somebody could change the contents of a symbolic link to
+     point to some file which was only readable by uucp.  */
+  if (fcheck)
+    {
+      if (! fsuser_access (&s, R_OK, zuser))
+       {
+         ulog (LOG_ERROR, "%s: %s", zfile, strerror (EACCES));
+         (void) ffileclose (e);
+         return EFILECLOSED;
+       }
+    }
+
+  *pimode = s.st_mode & 0777;
+  *pcbytes = s.st_size;
+  return e;
+}
+\f
+/* Get a temporary file name.  */
+
+/*ARGSUSED*/
+const char *
+zstemp_file (qsys)
+     const struct ssysteminfo *qsys;
+{
+  static int icount;
+  char *zret;
+
+#if SPOOLDIR_V2 | SPOOLDIR_BSD42
+  {
+    static char ab[sizeof "TM.12345.123"];
+
+    sprintf (ab, "TM.%05d.%03d", getpid (), icount);
+    zret = ab;
+  }
+#endif
+#if SPOOLDIR_BSD43 | SPOOLDIR_ULTRIX | SPOOLDIR_TAYLOR
+  {
+    static char ab[sizeof ".Temp/TM.12345.123"];
+
+    sprintf (ab, ".Temp/TM.%05d.%03d", getpid (), icount);
+    zret = ab;
+  }
+#endif
+#if SPOOLDIR_BNU
+  {
+    static char *z;
+    static int calc;
+    int cneed;
+
+    cneed = strlen (qsys->zname) + sizeof "/TM.12345.123";
+    if (cneed > calc)
+      {
+       xfree ((pointer) z);
+       z = (char *) xmalloc (cneed);
+       calc = cneed;
+      }
+    sprintf (z, "%s/TM.%05d.%03d", qsys->zname, getpid (), icount);
+    zret = z;
+  }
+#endif
+
+  ++icount;
+
+  return zret;
+}
+
+/* Open a temporary file to receive into.  This should, perhaps, check
+   that we have write permission on the receiving directory, but it
+   doesn't.  It is supposed to set *pcbytes to the size of the largest
+   file that can be accepted.  */
+
+/*ARGSUSED*/
+openfile_t
+esysdep_open_receive (qsys, zto, pztemp, pcbytes)
+     const struct ssysteminfo *qsys;
+     const char *zto;
+     const char **pztemp;
+     long *pcbytes;
+{
+  const char *z;
+  int o;
+  openfile_t e;
+  long c1, c2;
+  char *zcopy, *zslash;
+
+  z = zstemp_file (qsys);
+
+  o = creat (z, IPRIVATE_FILE_MODE);
+
+  if (o == -1)
+    {
+      if (errno == ENOENT)
+       {
+         if (! fsysdep_make_dirs (z, FALSE))
+           return EFILECLOSED;
+         o = creat (z, IPRIVATE_FILE_MODE);
+       }
+      if (o == -1)
+       {
+         ulog (LOG_ERROR, "creat (%s): %s", z, strerror (errno));
+         return EFILECLOSED;
+       }
+    }
+
+#if USE_STDIO
+  e = fdopen (o, (char *) BINWRITE);
+
+  if (e == NULL)
+    {
+      ulog (LOG_ERROR, "fdopen (%s): %s", z, strerror (errno));
+      (void) close (o);
+      (void) remove (z);
+      return NULL;
+    }
+#else
+  e = o;
+#endif
+
+  *pztemp = z;
+
+  /* Try to determine the amount of free space available for the
+     temporary file and for the final destination.  This code is
+     mostly from David MacKenzie's df program.  */
+
+  c1 = (long) -1;
+  c2 = (long) -1;
+
+  zcopy = (char *) alloca (strlen (zto) + 1);
+  strcpy (zcopy, zto);
+  zslash = strrchr (zcopy, '/');
+  if (zslash != NULL)
+    *zslash = '\0';
+  else
+    {
+      zcopy[0] = '.';
+      zcopy[1] = '\0';
+    }
+
+  {
+#ifdef FS_STATVFS
+    struct statvfs s;
+
+    if (statvfs (z, &s) >= 0)
+      c1 = (long) s.f_bavail * (long) s.f_frsize;
+    if (statvfs (zcopy, &s) >= 0)
+      c2 = (long) s.f_bavail * (long) s.f_frsize;
+#endif
+#ifdef FS_USG_STATFS
+    struct statfs s;
+
+    /* This structure has an f_bsize field, but on many systems
+       f_bfree is measured in 512 byte blocks.  On some systems,
+       f_bfree is measured in f_bsize byte blocks.  Rather than
+       overestimate the amount of free space, this code assumes that
+       f_bfree is measuring 512 byte blocks.  */
+
+    if (statfs (z, &s, sizeof s, 0) >= 0)
+      c1 = (long) s.f_bfree * (long) 512;
+    if (statfs (zcopy, &s, sizeof s, 0) >= 0)
+      c2 = (long) s.f_bfree * (long) 512;
+#endif
+#ifdef FS_MNTENT
+    struct statfs s;
+
+    if (statfs (z, &s) == 0)
+      c1 = (long) s.f_bavail * (long) s.f_bsize;
+    if (statfs (zcopy, &s) == 0)
+      c2 = (long) s.f_bavail * (long) s.f_bsize;
+#endif
+#ifdef FS_GETMNT
+    struct fs_data s;
+
+    if (statfs (z, &s) == 1)
+      c1 = (long) s.fd_req.bfreen * (long) 1024;
+    if (statfs (zcopy, &s) == 1)
+      c2 = (long) s.fd_req.bfreen * (long) 1024;
+#endif
+#ifdef FS_STATFS
+    struct statfs s;
+
+    if (statfs (z, &s) >= 0)
+      c1 = (long) s.f_bavail * (long) s.f_fsize;
+    if (statfs (zcopy, &s) >= 0)
+      c2 = (long) s.f_bavail * (long) s.f_fsize;
+#endif
+#ifdef FS_USTAT
+    struct stat sstat;
+    struct ustat s;
+
+    if (fstat (o, &sstat) == 0
+       && ustat (sstat.st_dev, &s) == 0)
+      c1 = (long) s.f_tfree * (long) 512;
+    if (stat (zcopy, &sstat) == 0
+       && ustat (sstat.st_dev, &s) == 0)
+      c2 = (long) s.f_tfree * (long) 512;
+#endif
+  }
+
+  if (c1 == (long) -1)
+    *pcbytes = c2;
+  else if (c2 == (long) -1)
+    *pcbytes = c1;
+  else if (c1 < c2)
+    *pcbytes = c1;
+  else
+    *pcbytes = c2;
+
+  return e;
+}
+
+/* After the temporary file has been completely written out, the file
+   is closed and this routine is called to move it into its final
+   location.  If we fail, we must remove the temporary file.  */
+
+boolean
+fsysdep_move_file (zorig, zto, imode, fcheck, zuser)
+     const char *zorig;
+     const char *zto;
+     unsigned int imode;
+     boolean fcheck;
+     const char *zuser;
+{
+  DEBUG_MESSAGE2 (DEBUG_SPOOLDIR,
+                 "fsysdep_move_file: Moving %s to %s", zorig, zto);
+
+  /* Unless and until we add an option to change the ownership of the
+     file, the only information we want from the mode is whether the
+     file is executable or not.  It would be dumb to create a file
+     with mode 0600, for example, since the owner will be uucp and the
+     recipient will not be able to read it.  If we do not have an
+     absolute path to the file, which means that it is being moved
+     somewhere in the spool directory, we don't change the mode; in
+     general, the files in the spool directory should not be
+     publically readable.  */
+
+  if (*zto != '/')
+    imode = 0;
+
+  if (imode != 0)
+    {
+      if ((imode & 0111) != 0)
+       imode = S_IRWXU | S_IRWXG | S_IRWXO;
+      else
+       imode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
+    }
+
+  /* Optionally make sure that zuser has write access on the
+     directory.  We only check files that are not in the spool
+     directory.  */
+  if (fcheck && *zto == '/')
+    {
+      char *zcopy;
+      char *zslash;
+      struct stat s;
+
+      zcopy = (char *) alloca (strlen (zto) + 1);
+      strcpy (zcopy, zto);
+      zslash = strrchr (zcopy, '/');
+      if (zslash == zcopy)
+       zslash[1] = '\0';
+      else
+       *zslash = '\0';
+
+      if (stat (zcopy, &s) != 0)
+       {
+         ulog (LOG_ERROR, "stat (%s): %s", zcopy, strerror (errno));
+         (void) remove (zorig);
+         return FALSE;
+       }
+      if (! fsuser_access (&s, W_OK, zuser))
+       {
+         ulog (LOG_ERROR, "%s: %s", zcopy, strerror (EACCES));
+         (void) remove (zorig);
+         return FALSE;
+       }
+
+      /* A malicious user now has a few milliseconds to change a
+        symbolic link to a directory uucp has write permission on but
+        the user does not (the obvious choice being /usr/lib/uucp).
+        The only certain method I can come up with to close this race
+        is to fork an suid process which takes on the users identity
+        and does the actual copy.  This is sufficiently high overhead
+        that I'm not going to do it.  */
+    }
+
+  /* We try to use rename to move the file.  */
+
+  if (rename (zorig, zto) == 0)
+    {
+      /* We must set the correct file mode, but don't worry if it doesn't
+        work.  There should be an option for setting the owner, as
+        well.  */
+      if (imode != 0)
+       (void) chmod (zto, imode);
+      return TRUE;
+    }
+
+  /* If this file is in the spool directory, make sure all directories
+     exist.  */
+  if (*zto != '/' && errno == ENOENT)
+    {
+      if (! fsysdep_make_dirs (zto, FALSE))
+       {
+         (void) remove (zorig);
+         return FALSE;
+       }
+      if (rename (zorig, zto) == 0)
+       {
+         if (imode != 0)
+           (void) chmod (zto, imode);
+         return TRUE;
+       }
+    }
+
+  /* If we can't link across devices, we must copy the file by hand.  */
+  if (errno != EXDEV)
+    {
+      ulog (LOG_ERROR, "rename (%s, %s): %s", zorig, zto,
+           strerror (errno));
+      (void) remove (zorig);
+      return FALSE;
+    }
+
+  /* If the destination file is not in the spool directory, any
+     necessary directories should already have been made.  */
+  if (! fcopy_file (zorig, zto, FALSE, *zto != '/'))
+    {
+      (void) remove (zorig);
+      return FALSE;
+    }
+
+  if (remove (zorig) < 0)
+    ulog (LOG_ERROR, "remove (%s): %s", zorig, strerror (errno));
+
+  if (imode != 0)
+    (void) chmod (zto, imode);
+
+  return TRUE;
+}
+\f
+/* Truncate a file to zero length.  If this fails, it closes and
+   removes the file.  We support a number of different means of
+   truncation, which is probably a waste of time since this function
+   is currently only called when the 'f' protocol resends a file.  */
+
+#if HAVE_FTRUNCATE
+#undef HAVE_LTRUNC
+#define HAVE_LTRUNC 0
+#endif
+
+#if ! HAVE_FTRUNCATE && ! HAVE_LTRUNC
+#ifdef F_CHSIZE
+#define HAVE_F_CHSIZE 1
+#else /* ! defined (F_CHSIZE) */
+#ifdef F_FREESP
+#define HAVE_F_FREESP 1
+#endif /* defined (F_FREESP) */
+#endif /* ! defined (F_CHSIZE) */
+#endif /* ! HAVE_FTRUNCATE && ! HAVE_LTRUNC */
+
+openfile_t
+esysdep_truncate (e, zname)
+     openfile_t e;
+     const char *zname;
+{
+  int o;
+
+#if HAVE_FTRUNCATE || HAVE_LTRUNC || HAVE_F_CHSIZE || HAVE_F_FREESP
+  int itrunc;
+
+  if (! ffilerewind (e))
+    {
+      ulog (LOG_ERROR, "rewind: %s", strerror (errno));
+      (void) ffileclose (e);
+      (void) remove (zname);
+      return EFILECLOSED;
+    }
+
+#if USE_STDIO
+  o = fileno (e);
+#else
+  o = e;
+#endif
+
+#if HAVE_FTRUNCATE
+  itrunc = ftruncate (o, 0);
+#endif
+#if HAVE_LTRUNC
+  itrunc = ltrunc (o, (long) 0, SEEK_SET);
+#endif
+#if HAVE_F_CHSIZE
+  itrunc = fcntl (o, F_CHSIZE, (off_t) 0);
+#endif
+#if HAVE_F_FREESP
+  /* This selection is based on an implementation of ftruncate by
+     kucharsk@Solbourne.com (William Kucharski).  */
+  {
+    struct flock fl;
+
+    fl.l_whence = 0;
+    fl.l_len = 0;
+    fl.l_start = 0;
+    fl.l_type = F_WRLCK;
+
+    itrunc = fcntl (o, F_FREESP, &fl);
+  }
+#endif
+
+  if (itrunc != 0)
+    {
+#if HAVE_FTRUNCATE
+      ulog (LOG_ERROR, "ftruncate: %s", strerror (errno));
+#endif
+#ifdef HAVE_LTRUNC
+      ulog (LOG_ERROR, "ltrunc: %s", strerror (errno));
+#endif
+#ifdef HAVE_F_CHSIZE
+      ulog (LOG_ERROR, "fcntl (F_CHSIZE): %s", strerror (errno));
+#endif
+#ifdef HAVE_F_FREESP
+      ulog (LOG_ERROR, "fcntl (F_FREESP): %s", strerror (errno));
+#endif
+
+      (void) ffileclose (e);
+      (void) remove (zname);
+      return EFILECLOSED;
+    }
+
+  return e;
+#else /* ! (HAVE_FTRUNCATE || HAVE_LTRUNC) */
+  (void) ffileclose (e);
+  (void) remove (zname);
+
+  o = creat (zname, IPRIVATE_FILE_MODE);
+
+  if (o == -1)
+    {
+      ulog (LOG_ERROR, "open (%s): %s", zname, strerror (errno));
+      return EFILECLOSED;
+    }
+
+#if USE_STDIO
+  e = fdopen (o, (char *) BINWRITE);
+
+  if (e == NULL)
+    {
+      ulog (LOG_ERROR, "fdopen (%s): %s", zname, strerror (errno));
+      (void) close (o);
+      (void) remove (zname);
+      return NULL;
+    }
+#else /* ! USE_STDIO */
+  e = o;
+#endif /* ! USE_STDIO */
+
+  return e;
+#endif /* ! HAVE_FTRUNCATE */
+}
+\f
+/* Lock something.  If the fspooldir argument is TRUE, the argument is
+   a file name relative to the spool directory; otherwise the argument
+   is a simple file name which should be created in the system lock
+   directory (under BNU this is /etc/locks).  */
+
+/*ARGSUSED*/
+boolean
+fsdo_lock (zlock, fspooldir)
+     const char *zlock;
+     boolean fspooldir;
+{
+  const char *zpath, *zslash;
+  int cslash;
+  char *ztempfile;
+  char abtempfile[20];
+  int o;
+  pid_t ime;
+#if HAVE_V2_LOCKFILES
+  int i;
+#else
+  char ab[12];
+#endif
+  int cwrote;
+  const char *zerr;
+  boolean fret;
+
+#ifdef LOCKDIR
+  if (fspooldir)
+    zpath = zlock;
+  else
+    {
+      char *zalc;
+
+      zalc = (char *) alloca (sizeof LOCKDIR + strlen (zlock) + 1);
+      sprintf (zalc, "%s/%s", LOCKDIR, zlock);
+      zpath = zalc;
+    }
+#else /* ! defined (LOCKDIR) */
+  zpath = zlock;
+#endif
+
+  ime = getpid ();
+
+  /* We do the actual lock by creating a file and then linking it to
+     the final file name we want.  This avoids race conditions due to
+     one process checking the file before we have finished writing it,
+     and also works even if we are somehow running as root.
+
+     First, create the file in the right directory (we must create the
+     file in the same directory since otherwise we might attempt a
+     cross-device link).  */
+  zslash = strrchr (zpath, '/');
+  if (zslash == NULL)
+    cslash = 0;
+  else
+    cslash = zslash - zpath + 1;
+
+  ztempfile = (char *) alloca (cslash + sizeof "TMP1234567890");
+  strncpy (ztempfile, zpath, cslash);
+  sprintf (abtempfile, "TMP%010d", (int) ime);
+  ztempfile[cslash] = '\0';
+  strcat (ztempfile, abtempfile);
+
+  o = creat (ztempfile, IPUBLIC_FILE_MODE);
+  if (o < 0)
+    {
+      if (errno == ENOENT)
+       {
+         if (! fsysdep_make_dirs (ztempfile, FALSE))
+           return FALSE;
+         o = creat (ztempfile, IPUBLIC_FILE_MODE);
+       }
+      if (o < 0)
+       {
+         ulog (LOG_ERROR, "open (%s): %s", ztempfile, strerror (errno));
+         return FALSE;
+       }
+    }
+
+#if HAVE_V2_LOCKFILES
+  i = ime;
+  cwrote = write (o, &i, sizeof i);
+#else
+  sprintf (ab, "%10d\n", (int) ime);
+  cwrote = write (o, ab, strlen (ab));
+#endif
+
+  zerr = NULL;
+  if (cwrote < 0)
+    zerr = "write";
+  if (close (o) < 0)
+    zerr = "close";
+  if (zerr != NULL)
+    {
+      ulog (LOG_ERROR, "%s (%s): %s", zerr, ztempfile, strerror (errno));
+      (void) remove (ztempfile);
+      return FALSE;
+    }
+
+  /* Now try to link the file we just created to the lock file that we
+     want.  If it fails, try reading the existing file to make sure
+     the process that created it still exists.  We do this in a loop
+     to make it easy to retry if the old locking process no longer
+     exists.  */
+
+  fret = TRUE;
+  o = -1;
+  zerr = NULL;
+
+  while (link (ztempfile, zpath) != 0)
+    {
+      int cgot;
+      int ipid;
+
+      fret = FALSE;
+
+      if (errno != EEXIST)
+       {
+         ulog (LOG_ERROR, "link (%s, %s): %s", ztempfile, zpath,
+               strerror (errno));
+         break;
+       }
+
+      o = open (zpath, O_RDWR, 0);
+      if (o < 0)
+       {
+         zerr = "open";
+         break;
+       }
+
+      /* The race starts here.  See below for a discussion.  */
+
+#if HAVE_V2_LOCKFILES
+      cgot = read (o, &i, sizeof i);
+#else
+      cgot = read (o, ab, sizeof ab - 1);
+#endif
+
+      if (cgot < 0)
+       {
+         zerr = "read";
+         break;
+       }
+
+#if HAVE_V2_LOCKFILES
+      ipid = i;
+#else
+      ab[cgot] = '\0';
+      ipid = atoi (ab);
+#endif
+
+      /* If the process still exists, we will get EPERM rather than
+        ESRCH.  We then return FALSE to indicate that we cannot make
+        the lock.  */
+      if (kill (ipid, 0) == 0 || errno == EPERM)
+       break;
+
+      /* On NFS, the link might have actually succeeded even though we
+        got a failure return.  This can happen if the original
+        acknowledgement was lost or delayed and the operation was
+        retried.  In this case the pid will be our own.  This
+        introduces a rather improbable race condition: if a stale
+        lock was left with our process ID in it, and another process
+        just did the above kill but has not yet changed the lock file
+        to hold its own process ID, we could start up and make it all
+        the way to here and think we have the lock.  I'm not going to
+        worry about this possibility.  */
+      if (ipid == ime)
+       {
+         fret = TRUE;
+         break;
+       }
+
+      ulog (LOG_ERROR, "Found stale lock %s held by process %d",
+           zpath, ipid);
+
+      /* This is a stale lock, created by a process that no longer
+        exists.
+
+        Now we could remove the file, but that would be a race
+        condition.  If we were interrupted any time after we did the
+        read until we did the remove, another process could get in,
+        open the file, find that it was a stale lock, remove the file
+        and create a new one.  When we woke up we would remove the
+        file the other process just created.
+
+        These files are being generated partially for the benefit of
+        cu, and it would be nice to avoid the race however cu avoids
+        it, so that the programs remain compatible.  Unfortunately,
+        nobody seems to know how cu avoids the race, or even if it
+        tries to avoid it at all.
+
+        There are a few ways to avoid the race.  We could use kernel
+        locking primitives, but they may not be available.  We could
+        link to a special file name, but if that file were left lying
+        around then no stale lock could ever be broken (Henry Spencer
+        would think this was a good thing).
+
+        Instead I've implemented the following procedure: seek to the
+        start of the file, write our pid into it, sleep for five
+        seconds, and then make sure our pid is still there.  Anybody
+        who checks the file while we're asleep will find our pid
+        there and fail the lock.  The only race will come from
+        another process which has done the read by the time we do our
+        write.  That process will then have five seconds to do its
+        own write.  When we wake up, we'll notice that our pid is no
+        longer in the file, and retry the lock from the beginning.
+
+        This relies on the atomicity of write(2).  If it possible for
+        the writes of two processes to be interleaved, the two
+        processes could livelock.  POSIX unfortunately leaves this
+        case explicitly undefined; however, given that the write is
+        of less than a disk block, it's difficult to imagine an
+        interleave occurring.
+
+        Note that this is still a race.  If it takes the second
+        process more than five seconds to do the kill, the lseek, and
+        the write, both processes will think they have the lock.
+        Perhaps the length of time to sleep should be configurable.
+        Even better, perhaps I should add a configuration option to
+        use a permanent lock file, which eliminates any race and
+        forces the installer to be aware of the existence of the
+        permanent lock file.
+
+        For the benefit of cu, we stat the file after the sleep, to
+        make sure some cu program hasn't deleted it for us.  */
+
+      if (lseek (o, (off_t) 0, SEEK_SET) != 0)
+       {
+         zerr = "lseek";
+         break;
+       }
+
+#if HAVE_V2_LOCKFILES
+      i = ime;
+      cwrote = write (o, &i, sizeof i);
+#else
+      sprintf (ab, "%10d\n", (int) ime);
+      cwrote = write (o, ab, strlen (ab));
+#endif
+
+      if (cwrote < 0)
+       {
+         zerr = "write";
+         break;
+       }
+
+      (void) sleep (5);
+
+      if (lseek (o, (off_t) 0, SEEK_SET) != 0)
+       {
+         zerr = "lseek";
+         break;
+       }
+
+#if HAVE_V2_LOCKFILES
+      cgot = read (o, &i, sizeof i);
+#else
+      cgot = read (o, ab, sizeof ab - 1);
+#endif
+
+      if (cgot < 0)
+       {
+         zerr = "read";
+         break;
+       }
+
+#if HAVE_V2_LOCKFILES
+      ipid = i;
+#else
+      ab[cgot] = '\0';
+      ipid = atoi (ab);
+#endif
+
+      if (ipid == ime)
+       {
+         struct stat sfile, sdescriptor;
+
+         /* It looks like we have the lock.  Do the final stat
+            check.  */
+
+         if (stat (zpath, &sfile) != 0)
+           {
+             if (errno != ENOENT)
+               {
+                 zerr = "stat";
+                 break;
+               }
+             /* Loop around and try again.  */
+           }
+         else
+           {
+             if (fstat (o, &sdescriptor) < 0)
+               {
+                 zerr = "fstat";
+                 break;
+               }
+
+             if (sfile.st_ino == sdescriptor.st_ino
+                 && sfile.st_dev == sdescriptor.st_dev)
+               {
+                 /* Close the file before assuming we've succeeded to
+                    pick up any trailing errors.  */
+                 if (close (o) < 0)
+                   {
+                     zerr = "close";
+                     break;
+                   }
+
+                 o = -1;
+
+                 /* We have the lock.  */
+                 fret = TRUE;
+                 break;
+               }
+           }
+       }
+
+      /* Loop around and try the lock again.  We keep doing this until
+        the lock file holds a pid that exists.  */
+
+      (void) close (o);
+      o = -1;
+      fret = TRUE;
+    }
+
+  if (zerr != NULL)
+    ulog (LOG_ERROR, "%s (%s): %s", zerr, zpath, strerror (errno));
+
+  if (o >= 0)
+    (void) close (o);
+
+  /* It would be nice if we could leave the temporary file around for
+     future calls, but considering that we create lock files in
+     various different directories it's probably more trouble than
+     it's worth.  */
+  if (remove (ztempfile) != 0)
+    ulog (LOG_ERROR, "remove (%s): %s", ztempfile, strerror (errno));
+
+  return fret;
+}
+
+/* Unlock something.  The fspooldir argument is as in fsdo_lock.  */
+
+boolean
+fsdo_unlock (zlock, fspooldir)
+     const char *zlock;
+     boolean fspooldir;
+{
+  const char *zpath;
+
+#ifdef LOCKDIR
+  if (fspooldir)
+    zpath = zlock;
+  else
+    {
+      char *zalc;
+
+      zalc = (char *) alloca (sizeof LOCKDIR + strlen (zlock) + 1);
+      sprintf (zalc, "%s/%s", LOCKDIR, zlock);
+      zpath = zalc;
+    }
+#else /* ! defined (LOCKDIR) */
+  zpath = zlock;
+#endif
+
+  if (remove (zpath) == 0
+      || errno == ENOENT)
+    return TRUE;
+  else
+    {
+      ulog (LOG_ERROR, "remove (%s): %s", zpath, strerror (errno));
+      return FALSE;
+    }
+}
+
+/* Lock a remote system.  */
+
+/*ARGSUSED*/
+boolean
+fsysdep_lock_system (qsys)
+     const struct ssysteminfo *qsys;
+{
+  char *z;
+
+  z = (char *) alloca (strlen (qsys->zname) + sizeof "LCK..");
+  sprintf (z, "LCK..%.8s", qsys->zname);
+  return fsdo_lock (z, FALSE);
+}
+
+/* Unlock a remote system.  */
+
+/*ARGSUSED*/
+boolean
+fsysdep_unlock_system (qsys)
+     const struct ssysteminfo *qsys;
+{
+  char *z;
+
+  z = (char *) alloca (strlen (qsys->zname) + sizeof "LCK..");
+  sprintf (z, "LCK..%.8s", qsys->zname);
+  return fsdo_unlock (z, FALSE);
+}
+\f
+/* Get a new command sequence number (this is not a sequence number to
+   be used for communicating with another system, but a sequence
+   number to be used when generating the name of a command file).
+   The sequence number is placed into zseq, which should be five
+   characters long.  */
+
+#define CSEQLEN (4)
+
+static boolean
+fscmd_seq (zsystem, zseq)
+     const char *zsystem;
+     char *zseq;
+{
+  int ctries;
+  const char *zfile;
+  int o;
+  int i;
+
+  /* Lock the sequence file.  This may not be correct for all systems,
+     but it only matters if the system UUCP and this UUCP are running
+     at the same time.  */
+
+  ctries = 0;
+  while (! fsdo_lock ("LCK..SEQ", TRUE))
+    {
+      ++ctries;
+      if (ctries > 10)
+       {
+         ulog (LOG_ERROR, "Can't lock sequence file");
+         return FALSE;
+       }
+      sleep (1);
+    }
+
+#if SPOOLDIR_V2 | SPOOLDIR_BSD42 | SPOOLDIR_BSD43
+  zfile = "SEQF";
+#endif
+#if SPOOLDIR_BNU
+  {
+    char *zalc;
+
+    zalc = (char *) alloca (strlen (zsystem) + sizeof ".Sequence/");
+    sprintf (zalc, ".Sequence/%s", zsystem);
+    zfile = zalc;
+  }
+#endif
+#if SPOOLDIR_ULTRIX
+  if (fsultrix_has_spool (zsystem))
+    {
+      char *zalc;
+
+      zalc = (char *) alloca (strlen (zsystem) + sizeof "sys//.SEQF");
+      sprintf (zalc, "sys/%s/.SEQF", zsystem);
+      zfile = zalc;
+    }
+  else
+    zfile = "sys/DEFAULT/.SEQF";
+#endif /* SPOOLDIR_ULTRIX */
+#if SPOOLDIR_TAYLOR
+  {
+    char *zalc;
+
+    zalc = (char *) alloca (strlen (zsystem) + sizeof "/SEQF");
+    sprintf (zalc, "%s/SEQF", zsystem);
+    zfile = zalc;
+  }
+#endif /* SPOOLDIR_TAYLOR */
+
+#ifdef O_CREAT
+  o = open (zfile, O_RDWR | O_CREAT, IPUBLIC_FILE_MODE);
+#else
+  o = open (zfile, O_RDWR);
+  if (o < 0 && errno == ENOENT)
+    {
+      o = creat (zfile, IPUBLIC_FILE_MODE);
+      if (o >= 0)
+       {
+         (void) close (o);
+         o = open (zfile, O_RDWR);
+       }
+    }
+#endif
+
+  if (o < 0)
+    {
+      if (errno == ENOENT)
+       {
+         if (! fsysdep_make_dirs (zfile, FALSE))
+           {
+             (void) fsdo_unlock ("LCK..SEQ", TRUE);
+             return FALSE;
+           }
+#ifdef O_CREAT
+         o = open (zfile, O_RDWR | O_CREAT, IPUBLIC_FILE_MODE);
+#else
+         o = creat (zfile, IPUBLIC_FILE_MODE);
+         if (o >= 0)
+           {
+             (void) close (o);
+             o = open (zfile, O_RDWR);
+           }
+#endif
+       }
+      if (o < 0)
+       {
+         ulog (LOG_ERROR, "open (%s): %s", zfile, strerror (errno));
+         (void) fsdo_unlock ("LCK..SEQ", TRUE);
+         return FALSE;
+       }
+    }
+
+  if (read (o, zseq, CSEQLEN) != CSEQLEN)
+    strcpy (zseq, "0000");
+  zseq[CSEQLEN] = '\0';
+
+  /* We must add one to the sequence number and return the new value.
+     On Ultrix, arbitrary characters are allowed in the sequence number.
+     On other systems, the sequence number apparently must be in hex.  */
+
+#if SPOOLDIR_V2 | SPOOLDIR_BSD42 | SPOOLDIR_BSD43 | SPOOLDIR_BNU
+  i = strtol (zseq, (char **) NULL, 16);
+  ++i;
+  if (i > 0xffff)
+    i = 0;
+  /* The sprintf argument has CSEQLEN built into it.  */
+  sprintf (zseq, "%04x", (unsigned int) i);
+#endif /* SPOOLDIR_V2 | SPOOLDIR_BSD42 | SPOOLDIR_BSD43 | SPOOLDIR_BNU */
+#if SPOOLDIR_ULTRIX | SPOOLDIR_TAYLOR
+  for (i = CSEQLEN - 1; i >= 0; i--)
+    {
+      if (zseq[i] == 'z')
+       {
+         zseq[i] = '0';
+         continue;
+       }
+
+      if (zseq[i] == '9')
+       zseq[i] = 'A';
+      else if (zseq[i] == 'Z')
+       zseq[i] = 'a';
+      else if ((zseq[i] >= '0' && zseq[i] < '9')
+              || (zseq[i] >= 'A' && zseq[i] < 'Z')
+              || (zseq[i] >= 'a' && zseq[i] < 'z'))
+       ++zseq[i];
+      else
+       {
+         /* A bad character was found; reset the entire sequence
+            number.  */
+         ulog (LOG_ERROR,
+               "Bad sequence number %s for system %s",
+               zseq, zsystem);
+         strcpy (zseq, "0000");
+       }
+
+      break;
+    }
+#endif /* SPOOLDIR_ULTRIX | SPOOLDIR_TAYLOR */
+
+  if (lseek (o, (off_t) 0, SEEK_SET) < 0
+      || write (o, zseq, CSEQLEN) != CSEQLEN
+      || close (o) < 0)
+    {
+      ulog (LOG_ERROR, "lseek or write or close: %s", strerror (errno));
+      (void) close (o);
+      (void) fsdo_unlock ("LCK..SEQ", TRUE);
+      return FALSE;
+    }
+
+  (void) fsdo_unlock ("LCK..SEQ", TRUE);
+
+  return TRUE;
+}
+\f
+/* Get the name of a command or data file for a remote system.  The
+   btype argument should be C for a command file or D for a data file.
+   If the grade of a data file is X, it is assumed that this is going
+   to become an execute file on some other system.  The zsystem
+   argument is the system that the file will be transferred to.  The
+   ztname argument will be set to a file name that could be passed to
+   zsysdep_spool_file_name.  The zdname argument, if not NULL, will be
+   set to a data file name appropriate for the remote system.  The
+   zxname argument, if not NULL, will be set to the name of an execute
+   file on the remote system.  None of the names will be more than 14
+   characters long.  */
+
+static const char *
+zsfile_name (btype, zsystem, bgrade, ztname, zdname, zxname)
+     int btype;
+     const char *zsystem;
+     int bgrade;
+     char *ztname;
+     char *zdname;
+     char *zxname;
+{
+  char abseq[CSEQLEN + 1];
+  char absimple[11 + CSEQLEN];
+  const char *zname;
+  struct stat s;
+
+  do
+    {
+      if (! fscmd_seq (zsystem, abseq))
+       return NULL;
+
+      if (btype == 'C')
+       {
+#if ! SPOOLDIR_TAYLOR
+         sprintf (absimple, "C.%.7s%c%s", zsystem, bgrade, abseq);
+#else
+         sprintf (absimple, "C.%c%s", bgrade, abseq);
+#endif
+       }
+      else if (btype == 'D')
+       {
+#if ! SPOOLDIR_TAYLOR
+         /* Note that a data file uses the local system's name.  */
+         sprintf (absimple, "D.%.7s%c%s", zLocalname, bgrade, abseq);
+#else
+         if (bgrade == 'X')
+           sprintf (absimple, "D.X%s", abseq);
+         else
+           sprintf (absimple, "D.%s", abseq);
+#endif
+       }
+#if DEBUG > 0
+      else
+       ulog (LOG_FATAL, "zsfile_name: Can't happen (%d)", btype);
+#endif
+
+      zname = zsfind_file (absimple, zsystem);
+      if (zname == NULL)
+       return NULL;
+    }
+  while (stat (zname, &s) == 0);
+
+  if (ztname != NULL)
+    strcpy (ztname, absimple);
+
+  if (zdname != NULL)
+    sprintf (zdname, "D.%.7s%c%s", zLocalname, bgrade, abseq);
+
+  if (zxname != NULL)
+    sprintf (zxname, "X.%.7s%c%s", zLocalname, bgrade, abseq);
+
+  return zname;
+}
+
+/* Given a set of commands to execute for a remote system, create a
+   command file holding them.  This creates a single command file
+   holding all the commands passed in.  It returns a jobid.  */
+
+const char *
+zsysdep_spool_commands (qsys, bgrade, ccmds, pascmds)
+     const struct ssysteminfo *qsys;
+     int bgrade;
+     int ccmds;
+     const struct scmd *pascmds;
+{
+  const char *z;
+  FILE *e;
+  int i;
+  const struct scmd *q;
+  const char *zjobid;
+
+#if DEBUG > 0
+  if (! FGRADE_LEGAL (bgrade))
+    ulog (LOG_FATAL, "Bad grade %d", bgrade);
+#endif
+
+  z = zsfile_name ('C', qsys->zname, bgrade, (char *) NULL, (char *) NULL,
+                  (char *) NULL);
+  if (z == NULL)
+    return NULL;
+
+  e = esysdep_fopen (z, FALSE, FALSE, TRUE);
+  if (e == NULL)
+    return NULL;
+
+  for (i = 0, q = pascmds; i < ccmds; i++, q++)
+    {
+      switch (q->bcmd)
+       {
+       case 'S':
+         fprintf (e, "S %s %s %s -%s %s 0%o %s\n", q->zfrom, q->zto,
+                  q->zuser, q->zoptions, q->ztemp, q->imode,
+                  q->znotify);
+         break;
+       case 'R':
+         fprintf (e, "R %s %s %s -%s\n", q->zfrom, q->zto, q->zuser,
+                  q->zoptions);
+         break;
+       case 'X':
+         fprintf (e, "X %s %s %s -%s\n", q->zfrom, q->zto, q->zuser,
+                  q->zoptions);
+         break;
+       default:
+         ulog (LOG_ERROR,
+               "zsysdep_spool_commands: Unrecognized type %d",
+               q->bcmd);
+         (void) fclose (e);
+         (void) remove (z);
+         return NULL;
+       }
+    }
+
+  if (fclose (e) != 0)
+    {
+      ulog (LOG_ERROR, "fclose: %s", strerror (errno));
+      (void) remove (z);
+      return NULL;
+    }
+
+  zjobid = zsfile_to_jobid (qsys, z);
+  if (zjobid == NULL)
+    (void) remove (z);
+  return zjobid;
+}
+
+/* Return a name to use for a data file to be copied to another
+   system.  The name returned will be for a real file.  The ztname
+   argument, if not NULL, will be set to a name that could be passed
+   to zsysdep_spool_file_name to get back the return value of this
+   function.  The zdname argument, if not NULL, will be set to a name
+   that the file could be given on another system.  The zxname
+   argument, if not NULL, will be set to a name for an execute file on
+   another system.  */
+
+const char *
+zsysdep_data_file_name (qsys, bgrade, ztname, zdname, zxname)
+     const struct ssysteminfo *qsys;
+     int bgrade;
+     char *ztname;
+     char *zdname;
+     char *zxname;
+{
+  return zsfile_name ('D', qsys->zname, bgrade, ztname, zdname, zxname);
+}
+
+/* Return a name for an execute file to be created locally.  This is
+   used by uux to execute a command locally with remote files.  */
+
+const char *
+zsysdep_xqt_file_name ()
+{
+  char abseq[CSEQLEN + 1];
+  char absx[11 + CSEQLEN];
+  const char *zname;
+  struct stat s;
+
+  while (TRUE)
+    {
+      if (! fscmd_seq (zLocalname, abseq))
+       return NULL;
+
+      sprintf (absx, "X.%.7sX%s", zLocalname, abseq);
+
+      zname = zsfind_file (absx, zLocalname);
+      if (zname == NULL)
+       return NULL;
+      if (stat (zname, &s) != 0)
+       break;
+    }
+
+  return zname;
+}
+\f
+/* Start getting a wildcarded file spec.  We use the shell to expand
+   the wildcard.  */
+
+static char *zSwildcard_alloc;
+static char *zSwildcard;
+
+boolean
+fsysdep_wildcard_start (qsys, zfile)
+     const struct ssysteminfo *qsys;
+     const char *zfile;
+{
+  char *zcmd;
+  const char *azargs[4];
+  FILE *e;
+  pid_t ipid;
+
+  zSwildcard_alloc = NULL;
+  zSwildcard = NULL;
+
+  if (*zfile == '~')
+    {
+      zfile = zstilde_expand (qsys, zfile);
+      if (zfile == NULL)
+       return FALSE;
+    }
+
+  if (*zfile != '/')
+    {
+      ulog (LOG_ERROR, "Relative path not permitted in wildcard");
+      return FALSE;
+    }
+
+  zcmd = (char *) alloca (sizeof ECHO_PROGRAM + sizeof " " + strlen (zfile));
+  sprintf (zcmd, "%s %s", ECHO_PROGRAM, zfile);
+
+  azargs[0] = "/bin/sh";
+  azargs[1] = "-c";
+  azargs[2] = zcmd;
+  azargs[3] = NULL;
+
+  e = espopen (azargs, TRUE, &ipid);
+  if (e == NULL)
+    {
+      ulog (LOG_ERROR, "espopen: %s", strerror (errno));
+      return FALSE;
+    }
+
+  zSwildcard_alloc = zfgets (e, FALSE);
+
+  if (iswait ((unsigned long) ipid, ECHO_PROGRAM) != 0)
+    {
+      xfree ((pointer) zSwildcard_alloc);
+      return FALSE;
+    }
+
+  if (zSwildcard_alloc == NULL)
+    return FALSE;
+
+  DEBUG_MESSAGE1 (DEBUG_EXECUTE,
+                 "fsysdep_wildcard_start: got \"%s\"",
+                 zSwildcard_alloc);
+
+  zSwildcard = zSwildcard_alloc;
+
+  return TRUE;
+}
+
+/* Get the next wildcard spec.  */
+
+/*ARGSUSED*/
+const char *
+zsysdep_wildcard (qsys, zfile)
+     const struct ssysteminfo *qsys;
+     const char *zfile;
+{
+  char *zret;
+
+  if (zSwildcard_alloc == NULL || zSwildcard == NULL)
+    return NULL;
+
+  zret = zSwildcard;
+
+  while (*zSwildcard != '\0' && ! isspace (BUCHAR (*zSwildcard)))
+    ++zSwildcard;
+
+  if (*zSwildcard == '\0')
+    zSwildcard = NULL;
+  else
+    {
+      *zSwildcard = '\0';
+      ++zSwildcard;
+      while (*zSwildcard != '\0' && isspace (BUCHAR (*zSwildcard)))
+       ++zSwildcard;
+      if (*zSwildcard == '\0')
+       zSwildcard = NULL;
+    }
+
+  return zret;
+}
+
+/* Finish up getting wildcard specs.  */
+
+boolean
+fsysdep_wildcard_end ()
+{
+  xfree ((pointer) zSwildcard_alloc);
+  zSwildcard_alloc = NULL;
+  zSwildcard = NULL;
+  return TRUE;
+}
+\f
+/* Get the current conversation sequence number for a remote system,
+   and increment it for next time.  The conversation sequence number
+   is kept in a file named .SQ in the spool directory for that system.
+   This is not compatible with other versions of UUCP, but it makes
+   more sense to me.  The sequence file is only used if specified in
+   the information for that system.  In V2, the file
+   /usr/lib/uucp/SQFILE is searched for each system to get a
+   conversation sequence number.  */
+
+long
+isysdep_get_sequence (qsys)
+     const struct ssysteminfo *qsys;
+{
+  FILE *e;
+  const char *zname;
+  struct stat s;
+  long iseq;
+
+  /* This will only be called when the system is locked anyhow, so there
+     is no need to use a separate lock for the conversation sequence
+     file.  */
+
+  zname = zsappend (".Sequence", qsys->zname);
+
+  iseq = 0;
+  if (stat (zname, &s) == 0)
+    {
+      boolean fok;
+      char *zline;
+
+      /* The file should only be readable and writable by uucp.  */
+
+      if ((s.st_mode & (S_IRWXG | S_IRWXO)) != 0)
+       {
+         ulog (LOG_ERROR,
+               "Bad file protection for conversation sequence file");
+         return -1;
+       }
+    
+      /* The file already exists, so we don't have to worry about
+        its protection.  */
+      e = fopen (zname, "r+");
+      if (e == NULL)
+       {
+         ulog (LOG_ERROR, "fopen (%s): %s", zname, strerror (errno));
+         return -1;
+       }
+
+      zline = zfgets (e, FALSE);
+
+      fok = TRUE;
+      if (zline == NULL)
+       fok = FALSE;
+      else
+       {
+         char *zend;
+
+         iseq = strtol (zline, &zend, 10);
+         if (zend == zline)
+           fok = FALSE;
+         xfree ((pointer) zline);
+       }
+
+      if (! fok)
+       {
+         ulog (LOG_ERROR, "Bad format for conversation sequence file");
+         return -1;
+       }
+
+      rewind (e);
+    }
+  else
+    {
+      e = esysdep_fopen (zname, FALSE, FALSE, TRUE);
+      if (e == NULL)
+       return -1;
+    }
+
+  ++iseq;
+
+  fprintf (e, "%ld", iseq);
+
+  if (fclose (e) != 0)
+    {
+      ulog (LOG_ERROR, "fclose: %s", strerror (errno));
+      return -1;
+    }
+
+  return iseq;
+}
+\f
+/* Get the Unix file mode of a file.  */
+
+unsigned int
+isysdep_file_mode (zfile)
+     const char *zfile;
+{
+  struct stat s;
+
+  if (stat (zfile, &s) != 0)
+    {
+      ulog (LOG_ERROR, "stat (%s): %s", zfile, strerror (errno));
+      return 0;
+    }
+
+#if S_IRWXU != 0700
+ #error Files modes need to be translated
+#endif
+
+  /* We can't return 0, since that indicate an error.  */
+  if ((s.st_mode & 0777) == 0)
+    return 0400;
+
+  return s.st_mode & 0777;
+}
+\f
+/* Translate a file name and an associated system into a job id.
+   These job ids are used by uustat.  We use the system name attached
+   to the grade and sequence number.  */
+
+const char *
+zsfile_to_jobid (qsys, zfile)
+     const struct ssysteminfo *qsys;
+     const char *zfile;
+{
+  char *zid;
+
+  zid = (char *) alloca (strlen (qsys->zname) + CSEQLEN + 2);
+  sprintf (zid, "%s%s", qsys->zname,
+          zfile + strlen (zfile) - CSEQLEN - 1);
+
+  return zscopy (zid);
+}
+
+/* Turn a job id back into a file name.  */
+
+const char *
+zsjobid_to_file (zid, pzsystem)
+     const char *zid;
+     const char **pzsystem;
+{
+  int clen;
+  char abend[CSEQLEN + 2];
+  static char *zsys;
+  static int csyslen;
+  char abname[CSEQLEN + 11];
+
+  clen = strlen (zid);
+  strcpy (abend, zid + clen - CSEQLEN - 1);
+
+  if (clen - CSEQLEN > csyslen)
+    {
+      zsys = (char *) xrealloc ((pointer) zsys, clen - CSEQLEN);
+      csyslen = clen - CSEQLEN;
+    }
+
+  strncpy (zsys, zid, clen - CSEQLEN - 1);
+  zsys[clen - CSEQLEN - 1] = '\0';
+  if (pzsystem != NULL)
+    *pzsystem = zsys;
+
+  /* This must correspond to zsfile_name.  */
+#if ! SPOOLDIR_TAYLOR
+  sprintf (abname, "C.%.7s%s", zsys, abend);
+#else
+  sprintf (abname, "C.%s", abend);
+#endif
+
+  return zsfind_file (abname, zsys);
+}
+\f
+#if ! HAVE_RENAME
+
+/* This is currently the only file which calls rename, so I've put my
+   fake rename function in here.  */
+
+static int
+rename (zfrom, zto)
+     const char *zfrom;
+     const char *zto;
+{
+  /* Try to make the link without removing the old file first.  */
+  if (link (zfrom, zto) == -1)
+    {
+      if (errno != EEXIST)
+       return -1;
+
+      /* Assume that this will never be called with zfrom the same as
+        zto.  If it could be, this is wrong.  */
+      (void) remove (zto);
+
+      if (link (zfrom, zto) == -1)
+       return -1;
+    }
+
+  return remove (zfrom);
+}
+
+#endif /* ! HAVE_RENAME */
+\f
+/*
+  Local variables:
+  mode:c
+  End:
+  */