/* 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 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 only in , 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 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 #include #if USE_STDIO && HAVE_UNISTD_H #include #endif #include "system.h" #include "sysdep.h" #include #if HAVE_FCNTL_H #include #else #if HAVE_SYS_FILE_H #include #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 extern int statvfs (); #endif #ifdef FS_USG_STATFS #include extern int statfs (); #endif #ifdef FS_MNTENT #include extern int statfs (); #endif #ifdef FS_GETMNT #include #include extern int statfs (); #endif #ifdef FS_STATFS #include extern int statfs (); #endif #ifdef FS_USTAT #include 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 /* 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: */