/* 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.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.48 1992/04/01 22:36:48 ian Rel $";
#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 <sys/stat.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 ();
extern int fstat (), kill (), stat ();
extern int fclose (), 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

/* 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.  */

/* 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));

/* 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 */

/* 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;
}

/* 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*/
}

/* 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;
}

/* 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;
}

/* 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;
}

/* 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;
}

/* 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 */
}

/* 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);
}

/* 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;
}

/* 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;
}

/* 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;
}

/* 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;
}

/* 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;
}

/* 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);
}

#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 */

/*
  Local variables:
  mode:c
  End:
  */
