386BSD 0.1 development
[unix-history] / usr / src / libexec / uucp / sys3.unx
/* 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:
*/