v01i001: My afio for even better floppy backups, Part01/01

Jeffery A Buhrt sawmill!buhrt at cs.purdue.edu
Mon Feb 11 13:19:23 AEST 1991


Submitted-by: sawmill!buhrt at cs.purdue.edu (Jeffery A Buhrt)
Posting-number: Volume 1, Issue 1
Archive-name: afio/part01


[You might post this to alt.sources as well. -Jeff]

Afio is a cpio clone that is much more intelligent about backups
than most cpios. If I am using floppies for a backup I will not
use anything else. It verifies the floppies, allows a restart if
one fails, and can compress each file as it is backed up.

I have been unable to contact  Mark Brukhartz at Lachman Associates,
and I am posting this verses patches. Please see the top of afio.c
for distribution rights.

I make no warranties on this software other than it seems to work
great for me.

I have added a few neat new features to afio.
1) Compress files as they are passing though.
2) If we are a floppy (on output)
	a) store the entire floppy into core before writing
	b) write the floppy from the in-core copy
		(1) if the write fails, just rewrite that disk!
		(2) verify the data written, BYTE-for-BYTE
	c) a cute little menu pops up when there are problems

3) If the machine has shared memory (and does a copy on fork vs exec)
	the 1.2m or 720k (etc) malloced buffer is shmemalloc()'d
	-The data then is not copied when afio forks to compress a file

This version is running on 40+ 3b1's of ours, a few '386's, and
100+ other 3b1's at another company.

Bugs (features to add):
a) There is no support for malloc(795k,1.2m) on a '286 w/o a huge
	model	-sorry
b) On a UNIXPC you CAN not read(from_floppy, to_shmem, #) or
	write(to_floppy, from_shmem, #) without hanging the process
	in kernal mode (note the #ifdef UNIXPC that uses a malloced
	buffer). -This was fixed in UNIX3.51m+ (dK+)
c) If you are compressing and the temp directory you pick is
	on a NSF file system, watch out for "stale handle" caused
	by opening the compress output file then unlinking before
	returning an open file descriptor.
	- not sure what to do, ideas welcome
d) What about listing files to extract like cpio has?
	ex: afio -iv /dev/rfp021 /etc/passwd /etc/group

I have sent this version to a few people who have not reported any problems
and have had a few requests to post.

Please send any comments or new code that you have.

dodsk is an example script to write a simple backup.
	ex: find dir -print | dodsk

						-Jeff Buhrt
						317-477-6000
			{sequent, tippy.cs.purdue.edu}!sawmill!buhrt
#!/bin/sh
# This is a shell archive (shar 3.20)
# made 01/28/1991 14:29 UTC by buhrt at sawmill
# Source directory /users/buhrt/src/afio
#
# existing files WILL be overwritten
#
# This shar contains:
# length  mode       name
# ------ ---------- ------------------------------------------
#    591 -rwxr-xr-x dodsk
#   1777 -rw-r--r-- Makefile
#   1777 -rw-rw-r-- Makefile.3b1
#   7476 -r--r--r-- afio.1
#  72522 -r--r--r-- afio.c
#
if touch 2>&1 | fgrep '[-amc]' > /dev/null
 then TOUCH=touch
 else TOUCH=true
fi
# ============= dodsk ==============
echo "x - extracting dodsk (Text)"
sed 's/^X//' << 'SHAR_EOF' > dodsk &&
X#
X# for a 3b1 (UNIXPC)
X#	1) Put a formatted floppy in the drive
X#	2) /etc/iv -tv /dev/rfp020
X#	3) Partition 1 (/dev/rfp021) insert that number into the -s flag
X#		valid numbers are formatted as ###[bmk]
X#		b=512, k=1024, m=1048576 (ex: 1m5k105)
X#
X# 3b1 (UNIXPC) 3.5" 720k floppy, high density format (part 0 is 5k)
X#DISKSIZE=795k
X#FORMAT='iv -iv /dev/rfp020 /usr/lib/iv/FDnl'
X#DEVICE=/dev/rfp021
X
X# 1.2M (whole disk) drive under Unix SysV 3.2.2
X#DISKSIZE=1440k
XDISKSIZE=1200k
XFORMAT=format\ -v\ /dev/rdsk/f05ht
XDEVICE=/dev/rdsk/f05ht
Xafio -ovzFKZ -L./BackupLog -s$DISKSIZE -R$FORMAT $DEVICE
SHAR_EOF
$TOUCH -am 0524182390 dodsk &&
chmod 0755 dodsk ||
echo "restore of dodsk failed"
set `wc -c dodsk`;Wc_c=$1
if test "$Wc_c" != "591"; then
	echo original size 591, current size $Wc_c
fi
# ============= Makefile ==============
echo "x - extracting Makefile (Text)"
sed 's/^X//' << 'SHAR_EOF' > Makefile &&
XSHELL=/bin/sh
X##
X##  I wrote this Makefile, based on comments in the source.  -rich $alz.
X##  Define INDEX to use index() in place of strchr() (v7, BSD).
X1	= -DINDEX
X#1	= -UINDEX
X##  Define MEMCPY when an efficient memcpy() exists (SysV).
X#2	= -DMEMCPY
X2	= -UMEMCPY
X##  Define MKDIR when a mkdir() system call is present (4.2BSD, SysVr3).
X3	= -DMKDIR
X#3	= -UMKDIR
X##  Define NOVOID if your compiler doesn't like void casts.
X#4	= -DNOVOID
X4	= -UNOVOID
X##  Define SYSTIME to use <sys/time.h> rather than <time.h> (4.2BSD).
X5	= -DSYSTIME
X#5	= -USYSTIME
X##  Define VOIDFIX to allow pointers to functions returning void (non-PCC).
X#6	= -DVOIDFIX
X6	= -UVOIDFIX
X##  Define CTC3B2 to support AT&T 3B2 streaming cartridge tape.
X#7	= -DCTC3B2
X7	= -UCTC3B2
X##  Define HAVEFCNTL if you have <fcntl.h>
X8	= -DHAVEFCNTL
X#8	= -UHAVEFCNTL
X##  Define USESHMEM if you have shared memory
X#9	= -DUSESHMEM
X9	= -UUSESHMEM
X##  Define MYTEMPNAM if you don't have tempnam()
Xa	= -DMYTEMPNAM
X#a	= -UMYTEMPNAM
X##  Define UNIXPC if you are on a 3b1, 7300, or other stupid AT&T no-work-alike
X##	(problem is you can't write to a floppy from shared memory)
X#b	= -DUNIXPC
Xb	= -UUNIXPC
X##  Define HAVEMEMCMP if you have memcmp otherwise assumes bcmp
X#c	= -DHAVEMEMCMP
Xc	= -UHAVEMEMCMP
X##  Define DEFFMTCMD to being how to format the media you use the most
X##		This is the DEFault FoRMat CoManD. 
Xd	= -DDEFFMTCMD='"/bin/false"'
X#d	= -DDEFFMTCMD='"format.sh"'
X#d	= -DDEFFMTCMD='"iv -i /dev/rfp020 /usr/lib/iv/FDnl"'
X
XCC=atscc
XCFLAGS	= $1 $2 $3 $4 $5 $6 $7 $8 $9 $a $b $c $d -O
XLDFLAGS =
X
Xall:		afio afio.1
X
Xafio:		afio.c
X	$(CC) $(CFLAGS) -o afio afio.c
X
Xshar:	README Makefile afio.1 afio.c
X	shar README Makefile afio.1 afio.c > afio.shar
X
Xinstall:	all
X	@echo copy afio and afio.1 into the appropriate directories.
SHAR_EOF
$TOUCH -am 0128092791 Makefile &&
chmod 0644 Makefile ||
echo "restore of Makefile failed"
set `wc -c Makefile`;Wc_c=$1
if test "$Wc_c" != "1777"; then
	echo original size 1777, current size $Wc_c
fi
# ============= Makefile.3b1 ==============
echo "x - extracting Makefile.3b1 (Text)"
sed 's/^X//' << 'SHAR_EOF' > Makefile.3b1 &&
XCC=ccc
XSHELL=/bin/sh
X##
X##  I wrote this Makefile, based on comments in the source.  -rich $alz.
X##  Define INDEX to use index() in place of strchr() (v7, BSD).
X#1	= -DINDEX
X1	= -UINDEX
X##  Define MEMCPY when an efficient memcpy() exists (SysV).
X2	= -DMEMCPY
X#2	= -UMEMCPY
X##  Define MKDIR when a mkdir() system call is present (4.2BSD, SysVr3).
X#3	= -DMKDIR
X3	= -UMKDIR
X##  Define NOVOID if your compiler doesn't like void casts.
X#4	= -DNOVOID
X4	= -UNOVOID
X##  Define SYSTIME to use <sys/time.h> rather than <time.h> (4.2BSD).
X#5	= -DSYSTIME
X5	= -USYSTIME
X##  Define VOIDFIX to allow pointers to functions returning void (non-PCC).
X#6	= -DVOIDFIX
X6	= -UVOIDFIX
X##  Define CTC3B2 to support AT&T 3B2 streaming cartridge tape.
X#7	= -DCTC3B2
X7	= -UCTC3B2
X##  Define HAVEFCNTL if you have <fcntl.h>
X8	= -DHAVEFCNTL
X#8	= -UHAVEFCNTL
X##  Define USESHMEM if you have shared memory
X9	= -DUSESHMEM
X#9	= -UUSESHMEM
X##  Define MYTEMPNAM if you don't have tempnam()
X#a	= -DMYTEMPNAM
Xa	= -UMYTEMPNAM
X##  Define UNIXPC if you are on a 3b1, 7300, or other stupid AT&T no-work-alike
X##	(problem is you can't write to a floppy from shared memory)
Xb	= -DUNIXPC
X#b	= -UUNIXPC
X##  Define HAVEMEMCMP if you have memcmp otherwise assumes bcmp
Xc	= -DHAVEMEMCMP
X#c	= -UHAVEMEMCMP
X##  Define DEFFMTCMD to being how to format the media you use the most
X##		This is the DEFault FoRMat CoManD. 
X#d	= -DDEFFMTCMD='"/bin/false"'
X#d	= -DDEFFMTCMD='"format.sh"'
Xd	= -DDEFFMTCMD=\""iv -i /dev/rfp020 /usr/lib/iv/FDnl"\"
X
XCFLAGS	= -O $1 $2 $3 $4 $5 $6 $7 $8 $9 $a $b $c $d
XLDFLAGS =
X
Xall:		afio afio.1
X
Xafio:		afio.c
X	$(CC) $(CFLAGS) -o afio afio.c
X
Xshar:	README Makefile afio.1 afio.c
X	shar README Makefile afio.1 afio.c > afio.shar
X
Xinstall:	all
X	@echo copy afio and afio.1 into the appropriate directories.
SHAR_EOF
$TOUCH -am 0128092891 Makefile.3b1 &&
chmod 0664 Makefile.3b1 ||
echo "restore of Makefile.3b1 failed"
set `wc -c Makefile.3b1`;Wc_c=$1
if test "$Wc_c" != "1777"; then
	echo original size 1777, current size $Wc_c
fi
# ============= afio.1 ==============
echo "x - extracting afio.1 (Text)"
sed 's/^X//' << 'SHAR_EOF' > afio.1 &&
X'br $Header: /users/buhrt/src/afio/afio.1,v 1.2 90/10/31 10:55:24 buhrt Exp $
X.TH AFIO 1
X.SH NAME
Xafio \- manipulate archives and files
X.SH SYNOPSIS
X.B "afio \-o"
X[
X.I options
X] archive
X.br
X.B "afio \-t"
X[
X.I options
X] archive
X.br
X.B "afio \-i"
X[
X.I options
X] archive
X.br
X.B "afio \-p"
X[
X.I options
X] directory [ ... ]
X.SH DESCRIPTION
X.I Afio
Xmanipulates groups of files, copying them within the (collective)
Xfilesystem or between the filesystem and an
X.I afio
Xarchive. Note that
X.I afio
Xarchives are portable, as they contain only ASCII-formatted
Xheader information. They are also compatible with ASCII
X.IR cpio (1)
Xarchives (ala
X.IR "cpio \-c" ).
X.PP
XWith
X.BR \-o ,
Xreads pathnames from the standard input
Xand writes an
X.IR archive .
X.PP
XWith
X.BR \-t ,
Xreads an
X.I archive
Xand writes a table-of-contents to the standard output.
X.PP
XWith
X.BR \-i ,
Xinstalls the contents of an
X.I archive
Xrelative to the working directory.
X.PP
XWith
X.BR \-p ,
Xreads pathnames from the standard input
Xand copies the files to each
X.IR directory .
X.PP
XCreates missing directories as necessary, with permissions
Xto match their parents.
X.PP
XGenerates sparse filesystem blocks (with
X.IR lseek (2))
Xwhen possible.
X.PP
XSupports multi-volume archives during interactive operation
X(i.e., when
X.I /dev/tty
Xis accessible and
X.I SIGINT
Xis not being ignored).
X.PP
XOptions:
X.TP 13
X.BI \-b "\ size"
XRead or write
X.IR size -character
Xarchive blocks.
XSuffices of
X.BR b ,
X.B k
Xand
X.B m
Xdenote multiples of
X.IR 512 ,
X.I 1024
Xand
X.IR 1048576 ,
Xrespectively.
XDefaults to
X.I 5120
Xfor compatibility with
X.IR cpio (1).
X.TP
X.BI \-c "\ count"
XBuffer
X.I count
Xarchive blocks between I/O operations. A large
X.I count
Xis recommended with streaming magnetic tape drives.
X.TP
X.B \-d
XDon't create missing directories.
X.TP
X.BI \-e "\ bound"
XPad the archive to a multiple of
X.I bound
Xcharacters.
XRecognizes the same suffices as
X.BR \-s .
XDefaults to
X.I 1x\^
X(the
X.B \-b
Xblock size)
Xfor compatibility with
X.IR cpio (1).
X.TP
X.B \-f
XSpawn a child process to actually write to the archive; provides
Xa clumsy form of double-buffering.
XRequires
X.B \-s
Xfor multi-volume archive support.
X.TP
X.B \-g
XChange to input file directories. Avoids quadratic filesystem
Xbehavior with long similar pathnames. Requires all absolute
Xpathnames, including those for the
X.B \-o
X.I archive
Xand the
X.B \-p
X.IR directories .
X.TP
X.B \-h
XFollow symbolic links, treating them as ordinary files and directories.
X.TP
X.B \-j
XDon't generate sparse filesystem blocks.
X.TP
X.B \-k
XSkip corrupt data at the
X.I beginning
Xof an archive (rather
Xthan complaining about unrecognizable input).
X.TP
X.B \-l
XWith
X.BR \-o ,
Xwrite file contents with each hard link.
X.sp
XWith
X.BR \-t ,
Xreport hard links.
X.sp
XWith
X.BR \-p ,
Xattempt to link files rather than copying them.
X.TP
X.B \-m
XMark output files with a common current timestamp
X(rather than with input file modification times).
X.TP
X.B \-n
XProtect newer existing files (comparing file modification times).
X.TP
X.BI \-s "\ limit"
XRestrict each portion of a multi-volume archive to
X.I limit
Xcharacters.
XRecognizes the same suffices as
X.BR \-b .
XAlso, the suffix
X.B x
Xdenotes a multiple of the
X.B \-b
Xblock size (and must follow any
X.B \-b
Xspecification).
XUseful with finite-length devices which do not return short
Xcounts at end of media (sigh); output to magnetic tape typically
Xfalls into this category.
X.TP
X.B \-u
XReport files with unseen links.
X.TP
X.B \-v
XVerbose. Report pathnames as they are processed. With
X.BR \-t ,
Xgives an
X.I "ls \-l"
Xstyle report (including link information).
X.TP
X.B \-x
XRetain file ownership and setuid/setgid permissions.
XThis is the default for the super-user; he may use
X.B \-X
Xto override it.
X.TP
X.BI \-y "\ prefix"
XRestrict archive processing to names beginning with
X.IR prefix .
XSpecify once for each prefix to be recognized.
XUse
X.B \-Y
Xto supply prefixes which are
X.I not
Xto be processed.
X.TP
X.B \-z
XPrint execution statistics. This is meant for human consumption;
Xuse by other programs is officially discouraged.
X.PP
XSpecial-case archive names:
X.RS 3
X.TP 3
X.B o
XSpecify
X.I \-
Xto read or write the standard input or output, respectively.
XThis disables multi-volume archive handling.
X.TP
X.B o
XPrefix a command string to be executed with an exclamation mark
X.RI ( ! ).
XThe command is executed once for each archive volume,
Xwith its standard input or output piped to
X.IR afio .
XIt is expected to produce a zero exit code when all is well.
X.TP
X.B o
XUse
X.I system:file
Xto access an archive in
X.I file
Xon
X.IR system .
XThis is really just a special case of pipelining.
XIt requires a 4.2BSD-style remote shell
X.RI ( rsh (1C))
Xand a remote copy of
X.IR afio .
X.TP
X.B o
XAnything else specifies a local file or device.
XAn output file will be created if it does not already exist.
X.RE
X.PP
XSawmill localizisms (Floppy and compression support):
X.TP
X.B -F
XThis is a floppy disk, -s is required.
XUses shared memory if compiled in otherwise mallocs as needed (a 3b1
Xwill not be able to malloc the needed memory w/o shared memory),
Xafio assumes either way you can malloc/shmalloc a chunck of memory
Xthe size of one disk. Examples: 795k: 3.5" (720k drive), 316k (360k drive)
X.nf
XAt the end of each disk this message occurs:
X Ready for disk [#] on [output] (remove the disk when the light goes out)
X Type "go" (or "GO") when ready to proceed (or "quit" to abort):
X.fi
X.TP
X.B -K
XVerify the output against what is in the memory copy of the disk (-F required).
XIf the writing or verifying fails the following menu pops up
X(the hidden option "quit" will also exit from the backup at this point).
X.nf
X    [Writing/Verify] of disk [disk #] has FAILED (try option #3 first ])!
X	Enter 1 to RETRY this disk
X	Enter 2 to REFORMAT this disk
X	Enter 3 to REFORMAT AND THEN RETRY this disk if the format works
X.fi
X.TP
X.B -L Log_file_path
XSpecify the name of the file to log errors and the finial totals to.
X.TP
X.B -R "Disk format command string"
XThis is the command that is run when you enter 2 to reformat the disk.
XThe default (char *formatcmd = "sh /u/store/format.sh") can be changed
Xto a given system's default.
X.TP
X.B -Z
XCompress the files on the way out (valid w/ or w/o -F or -K).
X.PP
XRecognizes obsolete binary
X.IR cpio (1)
Xarchives (including those from machines with reversed byte order),
Xbut cannot write them.
X.PP
XRecovers from archive corruption by searching for a valid magic
Xnumber. This is rather simplistic, but, much like a disassembler,
Xalmost always works.
X.PP
XOptimizes pathnames with respect to the current and parent
Xdirectories. For example, 
X.I ./src/sh/../misc/afio.c
Xbecomes
X.IR src/misc/afio.c .
X.SH EXAMPLE
X	AT&T 3b1 (all one line)
X	find /u/bstore -print | \\
X	  afio -ovzFZK -L/u/store/BackupLog \\
X	  -R'/etc/iv -i /dev/rfp020 /usr/lib/iv/FDnl' -s$DISKSIZE /dev/rfp021
X.SH BUGS
XThere are too many options.
X.PP
XRestricts pathnames to 1023 characters and 255 meaningful elements.
X.PP
XThere is no sequence information within multi-volume archives.
XInput sequence errors generally masquerade as data corruption.
XA solution would probably be mutually exclusive with
X.IR cpio (1)
Xcompatibility.
X.PP
XDegenerate uses of symbolic links are mangled by pathname optimization.
XFor example, assuming that "usr.src" is a symbolic link to "/usr/src",
Xthe pathname "usr.src/../bin/cu" is mis-optimized into "bin/cu" (rather
Xthan "/usr/bin/cu").
X.SH "SEE ALSO"
Xcpio(1), find(1), tar(1), tp(1).
X.SH AUTHOR
XMark Brukhartz
X.br
X.I "..!ihnp4!laidbak!mdb"
XJeff Buhrt (floppy extensions)
X.br
X.I "sequent!sawmill!buhrt"
SHAR_EOF
$TOUCH -am 1031105590 afio.1 &&
chmod 0444 afio.1 ||
echo "restore of afio.1 failed"
set `wc -c afio.1`;Wc_c=$1
if test "$Wc_c" != "7476"; then
	echo original size 7476, current size $Wc_c
fi
# ============= afio.c ==============
echo "x - extracting afio.c (Text)"
sed 's/^X//' << 'SHAR_EOF' > afio.c &&
X/*
X * afio.c
X *
X * Manipulate archives and files.
X *
X * Copyright (c) 1985 Lachman Associates, Inc..
X *
X * This software was written by Mark Brukhartz at Lachman Associates,
X * Inc.. It may be distributed within the following restrictions:
X *	(1) It may not be sold at a profit.
X *	(2) This credit and notice must remain intact.
X * This software may be distributed with other software by a commercial
X * vendor, provided that it is included at no additional charge.
X *
X * Please report bugs to "..!ihnp4!laidbak!mdb".
X *
X * Options:
X *  o Define INDEX to use index() in place of strchr() (v7, BSD).
X *  o Define MEMCPY when an efficient memcpy() exists (SysV).
X *  o Define MKDIR when a mkdir() system call is present (4.2BSD, SysVr3).
X *  o Define NOVOID if your compiler doesn't like void casts.
X *  o Define SYSTIME to use <sys/time.h> rather than <time.h> (4.2BSD).
X *  o Define VOIDFIX to allow pointers to functions returning void (non-PCC).
X *  o Define CTC3B2 to support AT&T 3B2 streaming cartridge tape.
X *
X * BUGS:
X *	Still needs '286 floppy support.
X *
X * Added by Jeff Buhrt:
X *	Floppy Verify/format/restart output in the middle of a set,
X *	compress files on output, extended error messages and logging
X *
X * NOTE: If you are compressing and the temp directory you pick is
X *	on a NSF file system, watch out for "stale handle" caused
X *	by opening the compress output file then unlinking before
X *	returning an open file descriptor.
X */
X
Xstatic char *ident = "$Header: /users/buhrt/src/afio/myafio.c,v 1.23 1991/01/23 19:50:13 buhrt Exp $";
X
X#include <stdio.h>
X#include <errno.h>
X#include <sys/signal.h>
X#include <sys/types.h>
X#include <sys/ioctl.h>
X#include <sys/stat.h>
X#include <pwd.h>
X#include <grp.h>
X
X#ifndef	major
X#	include <sys/sysmacros.h>
X#endif	/* major */
X
X#ifdef	SYSTIME
X#	include <sys/time.h>
X#else	/* SYSTIME */
X#	include <time.h>
X#endif	/* SYSTIME */
X
X#ifdef	CTC3B2
X#	include <sys/vtoc.h>
X#	include <sys/ct.h>
X#endif	/* CTC3B2 */
X
X#ifdef MYTEMPNAM
X#include <sys/file.h>
X#endif
X
X#ifdef USESHMEM
X#include <sys/ipc.h>
X#include <sys/shm.h>
X
X#define	NUMSHKEYS	20
X#define	SHMEMSIZE	262144	/* 2^18 (dev3b1) */
X#endif
X
X/* done writing to the archive */
X#define	FALSE	0
X#define	TRUE	1
X#define	NOTDONE	0
X#define	DONE	1
X#define	NODIE	0
X#define	DIE	1
X
X/*
X * Address link information base.
X */
X#define	linkhash(ino)	\
X	(linkbase + (ino) % nel(linkbase))
X
X/*
X * Mininum value.
X */
X#define	min(one, two)	\
X	(one < two ? one : two)
X
X/*
X * Number of array elements.
X */
X#define	nel(a)		\
X	(sizeof(a) / sizeof(*(a)))
X
X/*
X * Remove a file or directory.
X */
X#define	remove(name, asb) \
X	(((asb)->sb_mode & S_IFMT) == S_IFDIR ? rmdir(name) : unlink(name))
X
X/*
X * Swap bytes.
X */
X#define	swab(n)		\
X	((((ushort)(n) >> 8) & 0xff) | (((ushort)(n) << 8) & 0xff00))
X
X/*
X * Cast and reduce to unsigned short.
X */
X#define	ush(n)		\
X	(((ushort) (n)) & 0177777)
X
X/*
X * Definitions.
X */
X#define	reg	register	/* Convenience */
X#define	uint	unsigned int	/* Not always in types.h */
X#define	ushort	unsigned short	/* Not always in types.h */
X#define	BLOCK	5120		/* Default archive block size */
X#define	FSBUF	(8*1024)	/* Filesystem buffer size */
X#define	H_COUNT	10		/* Number of items in ASCII header */
X#define	H_PRINT	"%06o%06o%06o%06o%06o%06o%06o%011lo%06o%011lo"
X#define	H_SCAN	"%6ho%6ho%6ho%6ho%6ho%6ho%6ho%11lo%6o%11lo"
X#define	H_STRLEN 70		/* ASCII header string length */
X#define	M_ASCII "070707"	/* ASCII magic number */
X#define	M_BINARY 070707		/* Binary magic number */
X#define	M_STRLEN 6		/* ASCII magic number length */
X#define	NULLDEV	-1		/* Null device code */
X#define	NULLINO	0		/* Null inode number */
X#define	PATHELEM 256		/* Pathname element count limit */
X#define	PATHSIZE 1024		/* Pathname length limit */
X#define	S_IFSHF	12		/* File type shift (shb in stat.h) */
X#define	S_IPERM	07777		/* File permission bits (shb in stat.h) */
X#define	S_IPEXE	07000		/* Special execution bits (shb in stat.h) */
X#define	S_IPOPN	0777		/* Open access bits (shb in stat.h) */
X#define	STDIN	0		/* Standard input file descriptor */
X#define	STDOUT	1		/* Standard output file descriptor */
X#define	TTY	"/dev/tty"	/* For volume-change queries */
X
X/*
X * Some versions of the portable "C" compiler (PCC) can't handle
X * pointers to functions returning void.
X */
X#ifdef	VOIDFIX
X#	define	VOIDFN	void	/* Expect "void (*fnptr)()" to work */
X#else	/* VOIDFIX */
X#	define	VOIDFN	int	/* Avoid PCC "void (*fnptr)()" bug */
X#endif	/* VOIDFIX */
X
X/*
X * Trailer pathnames. All must be of the same length.
X */
X#define	TRAILER	"TRAILER!!!"	/* Archive trailer (cpio compatible) */
X#define	TRAILZ	11		/* Trailer pathname length (including null) */
X
X/*
X * Open modes; there is no <fcntl.h> with v7 UNIX.
X */
X#ifdef HAVEFCNTL
X#include <fcntl.h>
X#else
X#define	O_RDONLY 0		/* Read-only */
X#define	O_WRONLY 1		/* Write-only */
X#define	O_RDWR	2		/* Read/write */
X#endif
X/*
X * V7 and BSD UNIX use old-fashioned names for a couple of
X * string functions.
X */
X#ifdef	INDEX
X#	define	strchr	index	/* Forward character search */
X#	define	strrchr	rindex	/* Reverse character search */
X#endif	/* INDEX */
X
X/*
X * Some compilers can't handle void casts.
X */
X#ifdef	NOVOID
X#	define	VOID		/* Omit void casts */
X#else	/* NOVOID */
X#	define	VOID	(void)	/* Quiet lint about ignored return values */
X#endif	/* NOVOID */
X
X/*
X * Adb is more palatable when static functions and variables are
X * declared as globals. Lint gives more useful information when
X * statics are truly static.
X */
X#ifdef	lint
X#	define	STATIC	static	/* Declare static variables for lint */
X#else	/* lint */
X#	define	STATIC		/* Make static variables global for adb */
X#endif	/* lint */
X
X/*
X * Simple types.
X */
Xtypedef struct group	Group;	/* Structure for getgrgid(3) */
Xtypedef struct passwd	Passwd;	/* Structure for getpwuid(3) */
Xtypedef struct tm	Time;	/* Structure for localtime(3) */
X
X#ifdef	S_IFLNK
X	/*
X	 * File status with symbolic links. Kludged to hold symbolic
X	 * link pathname within structure.
X	 */
X	typedef struct {
X		struct stat	sb_stat;
X		char		sb_link[PATHSIZE];
X	} Stat;
X#	define	STAT(name, asb)		stat(name, &(asb)->sb_stat)
X#	define	FSTAT(fd, asb)		fstat(fd, &(asb)->sb_stat)
X#	define	LSTAT(name, asb)	lstat(name, &(asb)->sb_stat)
X#	define	sb_dev		sb_stat.st_dev
X#	define	sb_ino		sb_stat.st_ino
X#	define	sb_mode		sb_stat.st_mode
X#	define	sb_nlink	sb_stat.st_nlink
X#	define	sb_uid		sb_stat.st_uid
X#	define	sb_gid		sb_stat.st_gid
X#	define	sb_rdev		sb_stat.st_rdev
X#	define	sb_size		sb_stat.st_size
X#	define	sb_atime	sb_stat.st_atime
X#	define	sb_mtime	sb_stat.st_mtime
X#	define	sb_ctime	sb_stat.st_ctime
X#	define	sb_blksize	sb_stat.st_blksize
X#	define	sb_blocks	sb_stat.st_blocks
X#else	/* S_IFLNK */
X	/*
X	 * File status without symbolic links.
X	 */
X	typedef	struct stat	Stat;
X#	define	STAT(name, asb)		stat(name, asb)
X#	define	FSTAT(fd, asb)		fstat(fd, asb)
X#	define	LSTAT(name, asb)	stat(name, asb)
X#	define	sb_dev		st_dev
X#	define	sb_ino		st_ino
X#	define	sb_mode		st_mode
X#	define	sb_nlink	st_nlink
X#	define	sb_uid		st_uid
X#	define	sb_gid		st_gid
X#	define	sb_rdev		st_rdev
X#	define	sb_size		st_size
X#	define	sb_atime	st_atime
X#	define	sb_mtime	st_mtime
X#	define	sb_ctime	st_ctime
X#endif	/* S_IFLNK */
X
X/*
X * Binary archive header (obsolete).
X */
Xtypedef struct {
X	short	b_dev;			/* Device code */
X	ushort	b_ino;			/* Inode number */
X	ushort	b_mode;			/* Type and permissions */
X	ushort	b_uid;			/* Owner */
X	ushort	b_gid;			/* Group */
X	short	b_nlink;		/* Number of links */
X	short	b_rdev;			/* Real device */
X	ushort	b_mtime[2];		/* Modification time (hi/lo) */
X	ushort	b_name;			/* Length of pathname (with null) */
X	ushort	b_size[2];		/* Length of data */
X} Binary;
X
X/*
X * Child process structure.
X */
Xtypedef struct child {
X	struct child	*c_forw;	/* Forward link */
X	int		c_pid;		/* Process ID */
X	int		c_flags;	/* Flags (CF_) */
X	int		c_status;	/* Exit status */
X} Child;
X
X/*
X * Child process flags (c_flags).
X */
X#define	CF_EXIT	(1<<0)			/* Exited */
X
X/*
X * Hard link sources. One or more are chained from each link
X * structure.
X */
Xtypedef struct name {
X	struct name	*p_forw;	/* Forward chain (terminated) */
X	struct name	*p_back;	/* Backward chain (circular) */
X	char		*p_name;	/* Pathname to link from */
X} Path;
X
X/*
X * File linking information. One entry exists for each unique
X * file with with outstanding hard links.
X */
Xtypedef struct link {
X	struct link	*l_forw;	/* Forward chain (terminated) */
X	struct link	*l_back;	/* Backward chain (terminated) */
X	dev_t		l_dev;		/* Device */
X	ino_t		l_ino;		/* Inode */
X	ushort		l_nlink;	/* Unresolved link count */
X	off_t		l_size;		/* Length */
X	Path		*l_path;	/* Pathname(s) to link from */
X} Link;
X
X/*
X * Pathnames to (or to not) be processed.
X */
Xtypedef struct pattern {
X	struct pattern	*p_forw;	/* Forward chain */
X	char		*p_str;		/* String */
X	int		p_len;		/* Length of string */
X	int		p_not;		/* Reverse logic */
X} Pattern;
X
X/*
X * External functions.
X */
Xvoid	_exit();
Xvoid	exit();
Xvoid	free();
Xchar	*getenv();
Xushort	getgid();
XGroup	*getgrgid();
XPasswd	*getpwuid();
Xushort	getuid();
XTime	*localtime();
Xoff_t	lseek();
Xchar	*malloc();
Xuint	sleep();
Xchar	*strcat();
Xchar	*strchr();
Xchar	*strcpy();
Xchar	*strncpy();
Xchar	*strrchr();
Xtime_t	time();
Xushort	umask();
X
X
X/*
X * Internal functions.
X */
XVOIDFN	copyin();
XVOIDFN	copyout();
Xint	dirchg();
Xint	dirmake();
Xint	dirneed();
Xvoid	fatal();
XVOIDFN	in();
Xvoid	inalloc();
Xint	inascii();
Xint	inavail();
Xint	inbinary();
Xint	indata();
Xint	inentry();
Xint	infill();
Xint	inhead();
Xint	inread();
Xint	inskip();
Xint	inswab();
Xint	lineget();
Xvoid	linkalso();
XLink	*linkfrom();
Xvoid	linkleft();
XLink	*linkto();
Xvoid	memcpy();
Xchar	*memget();
Xchar	*memstr();
Xint	mkdir();
Xvoid	nameadd();
Xint	namecmp();
Xint	nameopt();
Xvoid	next();
Xvoid	nextask();
Xvoid	nextclos();
Xint	nextopen();
Xint	openin();
Xint	openo();
XVOIDFN	openq();
Xint	options();
Xoff_t	optsize();
XVOIDFN	out();
Xvoid	outalloc();
Xuint	outavail();
Xint	outdata();
Xvoid	outeof();
Xvoid	outflush();
Xvoid	outhead();
Xvoid	outpad();
Xvoid	outwait();
Xvoid	outwrite();
XVOIDFN	pass();
Xvoid	passdata();
Xint	passitem();
Xint	pipechld();
Xint	pipeopen();
Xvoid	pipewait();
Xvoid	prsize();
Xint	rmdir();
XVOIDFN	(*signal())();
Xint	swrite();
X#ifdef USESHMEM
Xchar	*shmemalloc();
Xvoid	shmemfree();
X#endif
Xchar	*syserr();
X#ifdef MYTEMPNAM
Xchar *tempnam();
X#endif
XVOIDFN	toc();
Xvoid	tocentry();
Xvoid	tocmode();
Xvoid	usage();
Xint	warn();
Xint	warnarch();
Xint	xfork();
Xvoid	xpause();
Xint	xwait();
X
X/*
X * External variables.
X */
Xextern int	errno;		/* System error code */
Xextern char	*sys_errlist[];	/* System error messages */
Xextern int	sys_nerr;	/* Number of sys_errlist entries */
X
X/*
X * Static variables.
X */
XSTATIC short	Fflag;		/*
X				 * floppy flag (write when buf full)
X				 * set -sdisk_size as well
X				 */
XSTATIC short	Zflag;		/* compress the files that we can */
XSTATIC short	verifyflag;	/* Verify (floppy) flag */
XSTATIC short	verifycnt;
X#ifdef	CTC3B2
XSTATIC short	Cflag;		/* Enable 3B2 CTC streaming (kludge) */
X#endif	/* CTC3B2 */
XSTATIC short	dflag;		/* Don't create missing directories */
XSTATIC short	fflag;		/* Fork before writing to archive */
XSTATIC short	gflag;		/* Change to input file directories */
XSTATIC short	hflag;		/* Follow symbolic links */
XSTATIC short	jflag;		/* Don't generate sparse filesystem blocks */
XSTATIC short	kflag;		/* Skip initial junk to find a header */
XSTATIC short	lflag;		/* Link rather than copying (when possible) */
XSTATIC short	mflag;		/* Ignore archived timestamps */
XSTATIC short	nflag;		/* Keep newer existing files */
XSTATIC short	uflag;		/* Report files with unseen links */
XSTATIC short	vflag;		/* Verbose */
XSTATIC short	xflag;		/* Retain file ownership */
XSTATIC short	zflag;		/* Print final statistics */
XSTATIC short	hidequit;	/* show the quit option? */
XSTATIC uint	arbsize = BLOCK;/* Archive block size */
XSTATIC short	areof;		/* End of input volume reached */
XSTATIC int	arfd = -1;	/* Archive file descriptor */
XSTATIC off_t	arleft;		/* Space remaining within current volume */
XSTATIC char	*arname;	/* Expanded archive name */
XSTATIC uint	arpad;		/* Final archive block padding boundary */
XSTATIC char	arspec[PATHSIZE];/* Specified archive name */
XSTATIC off_t	aruntil;	/* Volume size limit */
XSTATIC uint	arvolume = 1;	/* Volume number */
XSTATIC uint	buflen;		/* Archive buffer length */
XSTATIC char	*buffer;	/* Archive buffer */
XSTATIC char	*bufidx;	/* Archive buffer index */
XSTATIC char	*bufend;	/* End of data within archive buffer */
XSTATIC Child	*children;	/* Child processes */
XSTATIC char	*formatcmd = DEFFMTCMD; /* how to format */
XSTATIC ushort	gid;		/* Group ID */
XSTATIC Link	*linkbase[256];	/* Unresolved link information */
XSTATIC FILE	*logfile = NULL;/* log same errors as stderr would */
XSTATIC ushort	mask;		/* File creation mask */
XSTATIC char	*myname;	/* Arg0 */
XSTATIC char	*optarg;	/* Option argument */
XSTATIC int	optind;		/* Command line index */
XSTATIC int	outpid;		/* Process ID of outstanding outflush() */
XSTATIC Pattern	*pattern;	/* Pathname matching patterns */
XSTATIC char	pwd[PATHSIZE];	/* Working directory (with "-g") */
XSTATIC int	pipepid;	/* Pipeline process ID */
XSTATIC time_t	timenow;	/* Current time */
XSTATIC time_t	timewait;	/* Time spent awaiting new media */
XSTATIC off_t	total;		/* Total number of bytes transferred */
XSTATIC int	ttyf;		/* For interactive queries (yuk) */
XSTATIC ushort	uid;		/* User ID */
X
Xmain(ac, av)
Xint		ac;
Xreg char	**av;
X{
X	reg int		c;
X	reg uint	group = 1;
X	reg VOIDFN	(*fn)() = NULL;
X	time_t	timedone;
X	auto char	remote[PATHSIZE];
X
X	if (myname = strrchr(*av, '/'))
X		++myname;
X	else
X		myname = *av;
X	mask = umask(0);
X	uid = getuid();
X	gid = getgid();
X	if (uid == 0)
X		++xflag;
X	VOID signal(SIGPIPE, SIG_IGN);
X	while (c = options(ac, av, "ioptIOVCb:c:de:fghjklmns:uvxXy:Y:zFKZL:R:q")) {
X		switch (c) {
X		case 'i':
X			if (fn)
X				usage();
X			fn = in;
X			break;
X		case 'o':
X			if (fn)
X				usage();
X			fn = out;
X			break;
X		case 'p':
X			if (fn)
X				usage();
X			fn = pass;
X			break;
X		case 't':
X			if (fn)
X				usage();
X			fn = toc;
X			break;
X		case 'I':
X			if (fn)
X				usage();
X			fn = copyin;
X			break;
X		case 'O':
X			if (fn)
X				usage();
X			fn = copyout;
X			break;
X		case 'V':
X			VOID printf("%s\n", ident);
X			exit(0);
X#ifdef	CTC3B2
X		case 'C':
X			++Cflag;
X			arbsize = 31 * 512;
X			group = 10;
X			aruntil = 1469 * 31 * 512;
X			break;
X#endif	/* CTC3B2 */
X		case 'b':
X			if ((arbsize = (uint) optsize(optarg)) == 0)
X				fatal(optarg, "Bad block size");
X			break;
X		case 'c':
X			if ((group = (uint) optsize(optarg)) == 0)
X				fatal(optarg, "Bad buffer count");
X			break;
X		case 'd':
X			++dflag;
X			break;
X		case 'e':
X			arpad = (uint) optsize(optarg);
X			break;
X		case 'f':
X			++fflag;
X			break;
X		case 'g':
X			++gflag;
X			break;
X		case 'h':
X			++hflag;
X			break;
X		case 'j':
X			++jflag;
X			break;
X		case 'k':
X			++kflag;
X			break;
X		case 'l':
X			++lflag;
X			break;
X		case 'm':
X			++mflag;
X			break;
X		case 'n':
X			++nflag;
X			break;
X		case 's':
X			aruntil = optsize(optarg);
X			if (aruntil == 0)
X				usage();
X			break;
X		case 'F':
X			++Fflag;
X			break;
X		case 'Z':
X			++Zflag;
X			break;
X		case 'K':
X			++verifyflag;
X			break;
X		case 'u':
X			++uflag;
X			break;
X		case 'v':
X			++vflag;
X			break;
X		case 'x':
X			++xflag;
X			break;
X		case 'X':
X			xflag = 0;
X			break;
X		case 'y':
X			nameadd(optarg, 0);
X			break;
X		case 'Y':
X			nameadd(optarg, 1);
X			break;
X		case 'z':
X			++zflag;
X			break;
X		case 'L':
X			if ((logfile = fopen(optarg, "a")) == (FILE *)0)
X			{	fprintf(stderr,
X					"Can't open %s to append, get help\n",
X					optarg);
X				exit(1);
X			}
X			break;
X		case 'R':
X			formatcmd = optarg;
X			break;
X		case 'q':
X			hidequit = TRUE;
X			break;
X		default:
X			usage();
X		}
X	}
X	if (fn == NULL || av[optind] == NULL)
X		usage();
X	ttyf = openq();
X	if (Fflag)
X	{	if ((buflen = aruntil) == 0)
X			usage();
X	}
X	else
X		buflen = arbsize * group;
X	if (aruntil && (aruntil < arbsize))
X	{	fprintf(stderr, "Media size %d is less than buffer size %d\n",
X				aruntil, arbsize);
X		usage();
X	}
X	if (arpad == 0)
X		arpad = arbsize;
X	if (fn != pass) {
X		reg char	*colon;
X		reg char	*equal;
X		reg int		isoutput = (fn == out || fn == copyout);
X		int		goodbye();
X
X		arname = strcpy(arspec, av[optind++]);
X		if (colon = strchr(arspec, ':')) {
X			*colon++ = '\0';
X			if (equal = strchr(arspec, '='))
X				*equal++ = '\0';
X			VOID sprintf(arname = remote,
X			    "!rsh %s %s -%c -b %u -c %u %s",
X			    arspec, equal ? equal : myname,
X			    isoutput ? 'O' : 'I', arbsize,
X			    group, colon);
X			if (equal)
X				*--equal = '=';
X			*--colon = ':';
X		}
X		if (gflag && *arname != '/' && *arname != '!')
X			fatal(arspec, "Relative pathname");
X		VOID signal(SIGINT, goodbye);
X#ifdef USESHMEM
X		/* alloc the space as shared if we will be forking a lot */
X		if ((*arspec != '!') && (Fflag || fflag)
X#if ((defined (USESHMEM)) && (defined (UNIXPC)))
X			/*
X			 * UNIXPC and shared memory read/write bug
X			 * -only a write() is handled correctly below
X			 * in writedisk()
X			 */
X			&& ((fn == out) || (fn == copyout)))
X#else
X			)
X#endif
X		{
X			if ((buffer = bufidx = bufend = shmemalloc(buflen, arname)) == NULL)
X				fatal(arspec, "Cannot allocate I/O buffer (shmem)");
X		}
X		else
X#endif
X		if ((buffer = bufidx = bufend = malloc(buflen)) == NULL)
X			fatal(arspec, "Cannot allocate I/O buffer");
X
X		/*
X		 * open a floppy at the last moment (if output), otherwise now
X		 * note we set arleft prematurely so we don't have to open the
X		 * disk now
X		 */
X		if (!Fflag || !isoutput)
X		{	if (nextopen(isoutput ? O_WRONLY : O_RDONLY) < 0)
X				goodbye(1);
X		}
X		else
X			arleft = aruntil;
X	}
X	timenow = time((time_t *) NULL);
X	(*fn)(av + optind);
X	timedone = time((time_t *) NULL);
X	if (uflag)
X		linkleft();
X	if (zflag) {
X		reg FILE	*stream;
X
X		stream = fn == toc || arfd == STDOUT ? stderr : stdout;
X		VOID fprintf(stream, "%s: ", myname);
X		prsize(stream, total);
X		VOID fprintf(stream, " bytes %s in %lu seconds. The backup was successfull!\n",
X		  fn == pass
X		    ? "transferred"
X		    : fn == out || fn == copyout
X		      ? "written"
X		      : "read",
X		  timedone - timenow - timewait);
X	}
X	if (logfile != (FILE *)0)
X	{	VOID fprintf(logfile, "%s: Successfully backed up ", myname);
X		prsize(logfile, total);
X		VOID fprintf(logfile,
X	" bytes %s in %lu seconds (+waited %d seconds for disk swapping (%u disks)) finished at %s",
X			fn == pass
X			? "transferred"
X			: fn == out || fn == copyout
X			? "written"
X			: "read",
X			timedone - timenow - timewait,
X			timewait,
X			arvolume,
X			ctime(&timedone));
X	}
X	nextclos();
X	goodbye(0);
X	/* NOTREACHED */
X}
X
X/*
X * copyin()
X *
X * Copy directly from the archive to the standard output.
X */
XSTATIC VOIDFN
Xcopyin(av)
Xreg char	**av;
X{
X	reg int		got;
X	reg uint	have;
X
X	if (*av)
X		fatal(*av, "Extraneous argument");
X	while (!areof) {
X		VOID infill();
X		while (have = bufend - bufidx)
X			if ((got = write(STDOUT, bufidx, have)) < 0)
X				fatal("<stdout>", syserr());
X			else if (got > 0)
X				bufidx += got;
X			else
X				return;
X	}
X}
X
X/*
X * copyout()
X *
X * Copy directly from the standard input to the archive.
X */
XSTATIC VOIDFN
Xcopyout(av)
Xreg char	**av;
X{
X	reg int		got;
X	reg uint	want;
X
X	if (*av)
X		fatal(*av, "Extraneous argument");
X	for (;;) {
X		while ((want = bufend - bufidx) == 0)
X			outflush(NOTDONE);
X		if ((got = read(STDIN, bufidx, want)) < 0)
X			fatal("<stdin>", syserr());
X		else if (got == 0)
X			break;
X		else
X			bufidx += got;
X	}
X	outflush(DONE);
X	if (fflag)
X		outwait();
X}
X
X/*
X * dirchg()
X *
X * Change to the directory containing a given file.
X */
XSTATIC int
Xdirchg(name, local)
Xreg char	*name;
Xreg char	*local;
X{
X	reg char	*last;
X	reg int		len;
X	auto char	dir[PATHSIZE];
X
X	if (*name != '/')
X		return (warn(name, "Relative pathname"));
X	for (last = name + strlen(name); last[-1] != '/'; --last)
X		;
X	len = last - name;
X	strncpy(dir, name, len)[len] = '\0';
X	VOID strcpy(local, *last ? last : ".");
X	if (strcmp(dir, pwd) == 0)
X		return (0);
X	if (chdir(dir) < 0)
X		return (warn(name, syserr()));
X	VOID strcpy(pwd, dir);
X	return (0);
X}
X
X/*
X * dirmake()
X *
X * Make a directory. Returns zero if successful, -1 otherwise.
X */
XSTATIC int
Xdirmake(name, asb)
Xreg char	*name;
Xreg Stat	*asb;
X{
X	if (mkdir(name, asb->sb_mode & S_IPOPN) < 0)
X		return (-1);
X	if (asb->sb_mode & S_IPEXE)
X		VOID chmod(name, asb->sb_mode & S_IPERM);
X	if (xflag)
X		VOID chown(name,
X		    uid == 0 ? ush(asb->sb_uid) : uid,
X		    ush(asb->sb_gid));
X	return (0);
X}
X
X/*
X * dirneed()
X *
X * Recursively create missing directories (with the same permissions
X * as their first existing parent). Temporarily modifies the 'name'
X * argument string. Returns zero if successful, -1 otherwise.
X */
XSTATIC int
Xdirneed(name)
Xchar		*name;
X{
X	reg char	*cp;
X	reg char	*last;
X	reg int		ok;
X	static Stat	sb;
X
X	last = NULL;
X	for (cp = name; *cp; )
X		if (*cp++ == '/')
X			last = cp;
X	if (last == NULL)
X		return (STAT(".", &sb));
X	*--last = '\0';
X	ok = STAT(*name ? name : "/", &sb) == 0
X	    ? ((sb.sb_mode & S_IFMT) == S_IFDIR)
X	    : (!dflag && dirneed(name) == 0 && dirmake(name, &sb) == 0);
X	*last = '/';
X	return (ok ? 0 : -1);
X}
X
X/*
X * fatal()
X *
X * Print fatal message and exit.
X */
XSTATIC void
Xfatal(what, why)
Xchar		*what;
Xchar		*why;
X{
X	VOID warn(what, why);
X	goodbye(1);
X}
X
X/*
X * in()
X *
X * Read an archive.
X */
XSTATIC VOIDFN
Xin(av)
Xreg char	**av;
X{
X	auto Stat	sb;
X	auto char	name[PATHSIZE];
X
X	if (*av)
X		fatal(*av, "Extraneous argument");
X	name[0] = '\0';
X	while (inhead(name, &sb) == 0) {
X		if (namecmp(name) < 0 || inentry(name, &sb) < 0)
X			if (inskip(sb.sb_size) < 0)
X				VOID warn(name, "Skipped file data is corrupt");
X		if (vflag)
X			VOID fprintf(stderr, "%s\n", name);
X	}
X}
X
X/*
X * inalloc()
X *
X * Allocate input buffer space (which was previously indexed
X * by inavail()).
X */
XSTATIC void
Xinalloc(len)
Xreg uint	len;
X{
X	bufidx += len;
X	total += len;
X}
X
X/*
X * inascii()
X *
X * Read an ASCII header. Returns zero if successful;
X * -1 otherwise. Assumes that the entire magic number
X * has been read.
X */
XSTATIC int
Xinascii(magic, name, asb)
Xreg char	*magic;
Xreg char	*name;
Xreg Stat	*asb;
X{
X	auto uint	namelen;
X	auto char	header[H_STRLEN + 1];
X
X	if (strncmp(magic, M_ASCII, M_STRLEN) != 0)
X		return (-1);
X	if (inread(header, H_STRLEN) < 0)
X		return (warnarch("Corrupt ASCII header", (off_t) H_STRLEN));
X	header[H_STRLEN] = '\0';
X	if (sscanf(header, H_SCAN, &asb->sb_dev,
X	    &asb->sb_ino, &asb->sb_mode, &asb->sb_uid,
X	    &asb->sb_gid, &asb->sb_nlink, &asb->sb_rdev,
X	    &asb->sb_mtime, &namelen, &asb->sb_size) != H_COUNT)
X		return (warnarch("Bad ASCII header", (off_t) H_STRLEN));
X	if (namelen == 0 || namelen >= PATHSIZE)
X		return (warnarch("Bad ASCII pathname length", (off_t) H_STRLEN));
X	if (inread(name, namelen) < 0)
X		return (warnarch("Corrupt ASCII pathname", (off_t) namelen));
X	if (name[namelen - 1] != '\0')
X		return (warnarch("Bad ASCII pathname", (off_t) namelen));
X	return (0);
X}
X
X/*
X * inavail()
X *
X * Index availible input data within the buffer. Stores a pointer
X * to the data and its length in given locations. Returns zero with
X * valid data, -1 if unreadable portions were replaced with nulls.
X */
XSTATIC int
Xinavail(bufp, lenp)
Xreg char	**bufp;
Xuint		*lenp;
X{
X	reg uint	have;
X	reg int		corrupt = 0;
X
X	while ((have = bufend - bufidx) == 0)
X		corrupt |= infill();
X	*bufp = bufidx;
X	*lenp = have;
X	return (corrupt);
X}
X
X/*
X * inbinary()
X *
X * Read a binary header. Returns the number of trailing alignment
X * bytes to skip; -1 if unsuccessful.
X */
XSTATIC int
Xinbinary(magic, name, asb)
Xreg char	*magic;
Xreg char	*name;
Xreg Stat	*asb;
X{
X	reg uint	namefull;
X	auto Binary	binary;
X
X	if (*((ushort *) magic) != M_BINARY)
X		return (-1);
X	memcpy((char *) &binary,
X	    magic + sizeof(ushort),
X	    M_STRLEN - sizeof(ushort));
X	if (inread((char *) &binary + M_STRLEN - sizeof(ushort),
X	    sizeof(binary) - (M_STRLEN - sizeof(ushort))) < 0)
X		return (warnarch("Corrupt binary header",
X		    (off_t) sizeof(binary) - (M_STRLEN - sizeof(ushort))));
X	asb->sb_dev = binary.b_dev;
X	asb->sb_ino = binary.b_ino;
X	asb->sb_mode = binary.b_mode;
X	asb->sb_uid = binary.b_uid;
X	asb->sb_gid = binary.b_gid;
X	asb->sb_nlink = binary.b_nlink;
X	asb->sb_rdev = binary.b_rdev;
X	asb->sb_mtime = binary.b_mtime[0] << 16 | binary.b_mtime[1];
X	asb->sb_size = binary.b_size[0] << 16 | binary.b_size[1];
X	if (binary.b_name == 0 || binary.b_name >= PATHSIZE)
X		return (warnarch("Bad binary pathname length",
X		    (off_t) sizeof(binary) - (M_STRLEN - sizeof(ushort))));
X	if (inread(name, namefull = binary.b_name + binary.b_name % 2) < 0)
X		return (warnarch("Corrupt binary pathname", (off_t) namefull));
X	if (name[binary.b_name - 1] != '\0')
X		return (warnarch("Bad binary pathname", (off_t) namefull));
X	return (asb->sb_size % 2);
X}
X
X/*
X * indata()
X *
X * Install data from an archive. Returns given file descriptor.
X */
XSTATIC int
Xindata(fd, size, name)
Xint		fd;
Xreg off_t	size;
Xchar		*name;
X{
X	reg uint	chunk;
X	reg char	*oops;
X	reg int		sparse;
X	reg int		corrupt;
X	auto char	*buf;
X	auto uint	avail;
X
X	corrupt = sparse = 0;
X	oops = NULL;
X	while (size) {
X		corrupt |= inavail(&buf, &avail);
X		size -= (chunk = size < avail ? (uint) size : avail);
X		if (oops == NULL && (sparse = swrite(fd, buf, chunk)) < 0)
X			oops = syserr();
X		inalloc(chunk);
X	}
X	if (corrupt)
X		VOID warn(name, "Corrupt archive data");
X	if (oops)
X		VOID warn(name, oops);
X	else if (sparse > 0
X	  && (lseek(fd, (off_t) -1, 1) < 0
X	    || write(fd, "", 1) != 1))
X		VOID warn(name, syserr());
X	return (fd);
X}
X
X/*
X * inentry()
X *
X * Install a single archive entry. Returns zero if successful, -1 otherwise.
X */
XSTATIC int
Xinentry(name, asb)
Xchar		*name;
Xreg Stat	*asb;
X{
X	reg Link	*linkp;
X	reg int		ifd;
X	reg int		ofd;
X	auto time_t	tstamp[2];
X
X	if ((ofd = openo(name, asb, linkp = linkfrom(asb), 0)) > 0)
X		if (asb->sb_size || linkp == NULL || linkp->l_size == 0)
X			VOID close(indata(ofd, asb->sb_size, name));
X		else if ((ifd = open(linkp->l_path->p_name, O_RDONLY)) < 0)
X			VOID warn(linkp->l_path->p_name, syserr());
X		else {
X			passdata(linkp->l_path->p_name, ifd, name, ofd);
X			VOID close(ifd);
X			VOID close(ofd);
X		}
X	else if (ofd < 0)
X		return (-1);
X	else if (inskip(asb->sb_size) < 0)
X		VOID warn(name, "Redundant file data is corrupt");
X	tstamp[0] = tstamp[1] = mflag ? timenow : asb->sb_mtime;
X	VOID utime(name, tstamp);
X	return (0);
X}
X
X/*
X * infill()
X *
X * Fill the archive buffer. Remembers mid-buffer read failures and
X * reports them the next time through. Replaces unreadable data with
X * null characters. Returns zero with valid data, -1 otherwise.
X */
XSTATIC int
Xinfill()
X{
X	reg int		got;
X	static	int	failed;
X
X	bufend = bufidx = buffer;
X	if (!failed) {
X		if (areof)
X			if (total == 0)
X				fatal(arspec, "No input");
X			else
X				next(O_RDONLY, "Input EOF");
X		if (aruntil && arleft < arbsize)
X			next(O_RDONLY, "Input limit reached");
X		while (!failed
X		    && !areof
X		    && (aruntil == 0 || arleft >= arbsize)
X		    && buffer + buflen - bufend >= arbsize) {
X			if ((got = read(arfd, bufend, arbsize)) > 0) {
X				bufend += got;
X				arleft -= got;
X			} else if (got < 0)
X				failed = warnarch(syserr(),
X				    (off_t) 0 - (bufend - bufidx));
X			else
X				++areof;
X		}
X	}
X	if (failed && bufend == buffer) {
X		failed = 0;
X		for (got = 0; got < arbsize; ++got)
X			*bufend++ = '\0';
X		return (-1);
X	}
X	return (0);
X}
X
X/*
X * inhead()
X *
X * Read a header. Quietly translates old-fashioned binary cpio headers
X * (and arranges to skip the possible alignment byte). Returns zero if
X * successful, -1 upon archive trailer.
X */
XSTATIC int
Xinhead(name, asb)
Xreg char	*name;
Xreg Stat	*asb;
X{
X	reg off_t	skipped;
X	auto char	magic[M_STRLEN];
X	static int	align;
X
X	if (align > 0)
X		VOID inskip((off_t) align);
X	align = 0;
X	for (;;) {
X		VOID inread(magic, M_STRLEN);
X		skipped = 0;
X		while ((align = inascii(magic, name, asb)) < 0
X		    && (align = inbinary(magic, name, asb)) < 0
X		    && (align = inswab(magic, name, asb)) < 0) {
X			if (++skipped == 1) {
X				if (!kflag && total - sizeof(magic) == 0)
X					fatal(arspec, "Unrecognizable archive");
X				VOID warnarch("Bad magic number",
X				    (off_t) sizeof(magic));
X				if (name[0])
X					VOID warn(name, "May be corrupt");
X			}
X			memcpy(magic, magic + 1, sizeof(magic) - 1);
X			VOID inread(magic + sizeof(magic) - 1, 1);
X		}
X		if (skipped) {
X			VOID warnarch("Apparently resynchronized",
X			    (off_t) sizeof(magic));
X			VOID warn(name, "Continuing");
X		}
X		if (strcmp(name, TRAILER) == 0)
X			return (-1);
X		if (nameopt(name) >= 0)
X			break;
X		VOID inskip(asb->sb_size + align);
X	}
X#ifdef	S_IFLNK
X	if ((asb->sb_mode & S_IFMT) == S_IFLNK) {
X		if (inread(asb->sb_link, (uint) asb->sb_size) < 0) {
X			VOID warn(name, "Corrupt symbolic link");
X			return (inhead(name, asb));
X		}
X		asb->sb_link[asb->sb_size] = '\0';
X		asb->sb_size = 0;
X	}
X#endif	/* S_IFLNK */
X	if (name[0] == '/')
X		if (name[1])
X			while (name[0] = name[1])
X				++name;
X		else
X			name[0] = '.';
X	asb->sb_atime = asb->sb_ctime = asb->sb_mtime;
X	return (0);
X}
X
X/*
X * inread()
X *
X * Read a given number of characters from the input archive. Returns
X * zero with valid data, -1 if unreadable portions were replaced by
X * null characters.
X */
XSTATIC int
Xinread(dst, len)
Xreg char	*dst;
Xuint		len;
X{
X	reg uint	have;
X	reg uint	want;
X	reg int		corrupt = 0;
X	char		*endx = dst + len;
X
X	while (want = endx - dst) {
X		while ((have = bufend - bufidx) == 0)
X			corrupt |= infill();
X		if (have > want)
X			have = want;
X		memcpy(dst, bufidx, have);
X		bufidx += have;
X		dst += have;
X		total += have;
X	}
X	return (corrupt);
X}
X
X/*
X * inskip()
X *
X * Skip input archive data. Returns zero under normal circumstances,
X * -1 if unreadable data is encountered.
X */
XSTATIC int
Xinskip(len)
Xreg off_t	len;
X{
X	reg uint	chunk;
X	reg int		corrupt = 0;
X
X	while (len) {
X		while ((chunk = bufend - bufidx) == 0)
X			corrupt |= infill();
X		if (chunk > len)
X			chunk = len;
X		bufidx += chunk;
X		len -= chunk;
X		total += chunk;
X	}
X	return (corrupt);
X}
X
X/*
X * inswab()
X *
X * Read a reversed byte order binary header. Returns the number
X * of trailing alignment bytes to skip; -1 if unsuccessful.
X */
XSTATIC int
Xinswab(magic, name, asb)
Xreg char	*magic;
Xreg char	*name;
Xreg Stat	*asb;
X{
X	reg ushort	namesize;
X	reg uint	namefull;
X	auto Binary	binary;
X
X	if (*((ushort *) magic) != swab(M_BINARY))
X		return (-1);
X	memcpy((char *) &binary,
X	    magic + sizeof(ushort),
X	    M_STRLEN - sizeof(ushort));
X	if (inread((char *) &binary + M_STRLEN - sizeof(ushort),
X	    sizeof(binary) - (M_STRLEN - sizeof(ushort))) < 0)
X		return (warnarch("Corrupt swapped header",
X		    (off_t) sizeof(binary) - (M_STRLEN - sizeof(ushort))));
X	asb->sb_dev = (dev_t) swab(binary.b_dev);
X	asb->sb_ino = (ino_t) swab(binary.b_ino);
X	asb->sb_mode = swab(binary.b_mode);
X	asb->sb_uid = swab(binary.b_uid);
X	asb->sb_gid = swab(binary.b_gid);
X	asb->sb_nlink = swab(binary.b_nlink);
X	asb->sb_rdev = (dev_t) swab(binary.b_rdev);
X	asb->sb_mtime = swab(binary.b_mtime[0]) << 16 | swab(binary.b_mtime[1]);
X	asb->sb_size = swab(binary.b_size[0]) << 16 | swab(binary.b_size[1]);
X	if ((namesize = swab(binary.b_name)) == 0 || namesize >= PATHSIZE)
X		return (warnarch("Bad swapped pathname length",
X		    (off_t) sizeof(binary) - (M_STRLEN - sizeof(ushort))));
X	if (inread(name, namefull = namesize + namesize % 2) < 0)
X		return (warnarch("Corrupt swapped pathname", (off_t) namefull));
X	if (name[namesize - 1] != '\0')
X		return (warnarch("Bad swapped pathname", (off_t) namefull));
X	return (asb->sb_size % 2);
X}
X
X/*
X * lineget()
X *
X * Get a line from a given stream. Returns 0 if successful, -1 at EOF.
X */
XSTATIC int
Xlineget(stream, buf)
Xreg FILE	*stream;
Xreg char	*buf;
X{
X	reg int		c;
X
X	for (;;) {
X		if ((c = getc(stream)) == EOF)
X			return (-1);
X		if (c == '\n')
X			break;
X		*buf++ = c;
X	}
X	*buf = '\0';
X	return (0);
X}
X
X/*
X * linkalso()
X *
X * Add a destination pathname to an existing chain. Assumes that
X * at least one element is present.
X */
XSTATIC void
Xlinkalso(linkp, name)
Xreg Link	*linkp;
Xchar		*name;
X{
X	reg Path	*path;
X
X	if ((path = (Path *) memget(sizeof(Path))) == NULL
X	    || (path->p_name = memstr(name)) == NULL)
X		return;
X	path->p_forw = NULL;
X	path->p_back = linkp->l_path->p_back;
X	path->p_back->p_forw = path;
X	linkp->l_path->p_back = path;
X}
X
X/*
X * linkfrom()
X *
X * Find a file to link from. Returns a pointer to a link
X * structure, or NULL if unsuccessful.
X */
XSTATIC Link *
Xlinkfrom(asb)
Xreg Stat	*asb;
X{
X	reg Link	*linkp;
X	reg Link	*linknext;
X	reg Path	*path;
X	reg Path	*pathnext;
X	reg Link	**abase;
X
X	for (linkp = *(abase = linkhash(asb->sb_ino)); linkp; linkp = linknext)
X		if (linkp->l_nlink == 0) {
X			for (path = linkp->l_path; path; path = pathnext) {
X				pathnext = path->p_forw;
X				free(path->p_name);
X			}
X			free((char *) linkp->l_path);
X			if (linknext = linkp->l_forw)
X				linknext->l_back = linkp->l_back;
X			if (linkp->l_back)
X				linkp->l_back->l_forw = linkp->l_forw;
X			else
X				*abase = linkp->l_forw;
X			free((char *) linkp);
X		} else if (linkp->l_ino == asb->sb_ino
X		    && linkp->l_dev == asb->sb_dev) {
X			--linkp->l_nlink;
X			return (linkp);
X		} else
X			linknext = linkp->l_forw;
X	return (NULL);
X}
X
X
X/*
X * linkleft()
X *
X * Complain about files with unseen links.
X */
XSTATIC void
Xlinkleft()
X{
X	reg Link	*lp;
X	reg Link	**base;
X
X	for (base = linkbase; base < linkbase + nel(linkbase); ++base)
X		for (lp = *base; lp; lp = lp->l_forw)
X			if (lp->l_nlink)
X				VOID warn(lp->l_path->p_name, "Unseen link(s)");
X}
X
X/*
X * linkto()
X *
X * Remember a file with outstanding links. Returns a
X * pointer to the associated link structure, or NULL
X * when linking is not possible.
X */
XSTATIC Link *
Xlinkto(name, asb)
Xchar		*name;
Xreg Stat	*asb;
X{
X	reg Link	*linkp;
X	reg Path	*path;
X	reg Link	**abase;
X
X	if ((asb->sb_mode & S_IFMT) == S_IFDIR
X	    || (linkp = (Link *) memget(sizeof(Link))) == NULL
X	    || (path = (Path *) memget(sizeof(Path))) == NULL
X	    || (path->p_name = memstr(name)) == NULL)
X		return (NULL);
X	linkp->l_dev = asb->sb_dev;
X	linkp->l_ino = asb->sb_ino;
X	linkp->l_nlink = asb->sb_nlink - 1;
X	linkp->l_size = asb->sb_size;
X	linkp->l_path = path;
X	path->p_forw = NULL;
X	path->p_back = path;
X	if (linkp->l_forw = *(abase = linkhash(asb->sb_ino)))
X		linkp->l_forw->l_back = linkp;
X	linkp->l_back = NULL;
X	return (*abase = linkp);
X}
X
X#ifndef	MEMCPY
X
X/*
X * memcpy()
X *
X * A simple block move.
X */
XSTATIC void
Xmemcpy(to, from, len)
Xreg char	*to;
Xreg char	*from;
Xuint		len;
X{
X	reg char	*toend;
X
X	for (toend = to + len; to < toend; *to++ = *from++)
X		;
X}
X
X#endif	/* MEMCPY */
X
X/*
X * memget()
X *
X * Allocate memory.
X */
XSTATIC char *
Xmemget(len)
Xuint		len;
X{
X	reg char	*mem;
X	static short	outofmem;
X
X	if ((mem = malloc(len)) == NULL && !outofmem)
X		outofmem = warn("memget()", "Out of memory");
X	return (mem);
X}
X
X/*
X * memstr()
X *
X * Duplicate a string into dynamic memory.
X */
XSTATIC char *
Xmemstr(str)
Xreg char	*str;
X{
X	reg char	*mem;
X
X	if (mem = memget((uint) strlen(str) + 1))
X		VOID strcpy(mem, str);
X	return (mem);
X}
X
X#ifndef	MKDIR
X
X/*
X * mkdir()
X *
X * Make a directory via "/bin/mkdir". Sets errno to a
X * questionably sane value upon failure.
X */
XSTATIC int
Xmkdir(name, mode)
Xreg char	*name;
Xreg ushort	mode;
X{
X	reg int		pid;
X
X	if ((pid = xfork("mkdir()", DIE)) == 0) {
X		VOID close(fileno(stdin));
X		VOID close(fileno(stdout));
X		VOID close(fileno(stderr));
X		VOID open("/dev/null", O_RDWR);
X		VOID dup(fileno(stdin));
X		VOID dup(fileno(stdin));
X		VOID umask(~mode);
X		VOID execl("/bin/mkdir", "mkdir", name, (char *) NULL);
X		exit(1);
X	}
X	if (xwait(pid, "mkdir()", TRUE) == 0)
X		return (0);
X	errno = EACCES;
X	return (-1);
X}
X
X#endif	/* MKDIR */
X
X/*
X * nameadd()
X *
X * Add a name to the pattern list.
X */
XSTATIC void
Xnameadd(name, not)
Xreg char	*name;
Xint		not;
X{
X	reg Pattern	*px;
X
X	px = (Pattern *) memget(sizeof(Pattern));
X	px->p_forw = pattern;
X	px->p_str = name;
X	px->p_len = strlen(name);
X	px->p_not = not;
X	pattern = px;
X}
X
X/*
X * namecmp()
X *
X * Compare a pathname with the pattern list. Returns 0 for
X * a match, -1 otherwise.
X */
XSTATIC int
Xnamecmp(name)
Xreg char	*name;
X{
X	reg Pattern	*px;
X	reg int		positive;
X	reg int		match;
X	int	x;
X	int	wild = 0;
X	int	comp;
X
X	positive = match = 0;
X	if (pattern == NULL) return(0);	/* Ok if nothing spec'd */
X	for (px = pattern; px; px = px->p_forw) {
X		if (!px->p_not)
X			++positive;
X		x = px->p_len;
X		if (px->p_str[px->p_len - 1] == '*') {
X			x--;		/* If wild, back up one */
X			wild++;
X		}
X		comp = strncmp(name, px->p_str, x);
X		if (px->p_not) {
X			if (wild == 0)	{	/* If not wild */
X				if ((name[x] != '\0') || (comp != 0))
X					++match;
X				else 
X					return(-1);	/* OUT NOW */
X			} else
X				if (comp != 0)
X					++match;
X				else
X					return(-1);	/* OUT NOW */
X		} else {
X			if (wild == 0) {	/* If not wild */
X				if ((name[x] == '\0') && (comp == 0))
X					++match;
X			} else
X				if (comp == 0)
X					++match;
X		}
X	}
X	if (match > 0) 
X		return(0);
X	return(-1);
X}
X
X/*
X * nameopt()
X *
X * Optimize a pathname. Confused by "<symlink>/.." twistiness.
X * Returns the number of final pathname elements (zero for "/"
X * or ".") or -1 if unsuccessful.
X */
XSTATIC int
Xnameopt(begin)
Xchar	*begin;
X{
X	reg char	*name;
X	reg char	*item;
X	reg int		idx;
X	int		absolute;
X	auto char	*element[PATHELEM];
X
X	absolute = (*(name = begin) == '/');
X	idx = 0;
X	for (;;) {
X		if (idx == PATHELEM)
X			return (warn(begin, "Too many elements"));
X		while (*name == '/')
X			++name;
X		if (*name == '\0')
X			break;
X		element[idx] = item = name;
X		while (*name && *name != '/')
X			++name;
X		if (*name)
X			*name++ = '\0';
X		if (strcmp(item, "..") == 0)
X			if (idx == 0)
X				if (absolute)
X					;
X				else
X					++idx;
X			else if (strcmp(element[idx - 1], "..") == 0)
X				++idx;
X			else
X				--idx;
X		else if (strcmp(item, ".") != 0)
X			++idx;
X	}
X	if (idx == 0)
X		element[idx++] = absolute ? "" : ".";
X	element[idx] = NULL;
X	name = begin;
X	if (absolute)
X		*name++ = '/';
X	for (idx = 0; item = element[idx]; ++idx, *name++ = '/')
X		while (*item)
X			*name++ = *item++;
X	*--name = '\0';
X	return (idx);
X}
X
X/*
X * next()
X *
X * Advance to the next archive volume.
X */
XSTATIC void
Xnext(mode, why)
Xreg int		mode;
Xreg char	*why;
X{
X	reg time_t	began;
X	auto char	msg[200];
X	auto char	answer[20];
X
X	began = time((time_t *) NULL);
X	nextclos();
X	VOID warnarch(why, (off_t) 0);
X	if (arfd == STDIN || arfd == STDOUT)
X		goodbye(1);
X	++arvolume;	/* change disk # here */
X	VOID sprintf(msg, "\
X%s: Ready for disk %u on \"%s\" (remove the disk when the light goes out)\n\
X%s: Type \"go\" (or \"GO\") when ready to proceed %s: \07",
X	    myname, arvolume, arspec, myname,
X	    hidequit ? "" : "(or \"quit\" to abort )");
X	for (;;) {
X		nextask(msg, answer, sizeof(answer));
X		if (strcmp(answer, "quit") == 0)
X			fatal(arspec, "Aborted");
X		if ((strcmp(answer, "go") == 0) || 
X		    (strcmp(answer, "GO") == 0))
X		{
X			/* only open the disk now if we it it right now */
X			if (!Fflag || (mode == O_RDONLY))
X			{	if (nextopen(mode) == 0)
X					break;
X			}
X			else
X				break;
X		}
X	}
X	VOID warnarch("Continuing", (off_t) 0);
X	timewait += time((time_t *) NULL) - began;
X}
X
X/*
X * nextask()
X *
X * Ask a question and get a response. Ignores spaces and tabs.
X */
XSTATIC void
Xnextask(msg, answer, limit)
Xreg char	*msg;
Xreg char	*answer;
Xreg int		limit;
X{
X	reg int		idx;
X	reg int		got;
X	auto char	c;
X
X	if (ttyf < 0)
X		fatal(TTY, "Unavailable");
X	VOID write(ttyf, msg, (uint) strlen(msg));
X	idx = 0;
X	while ((got = read(ttyf, &c, 1)) == 1)
X		if (c == '\04' || c == '\n')
X			break;
X		else if (c == ' ' || c == '\t')
X			continue;
X		else if (idx < limit - 1)
X			answer[idx++] = c;
X	if (got < 0)
X		fatal(TTY, syserr());
X	answer[idx] = '\0';
X}
X
X/*
X * nextclos()
X *
X * Close an archive.
X */
XSTATIC void
Xnextclos()
X{
X	if (arfd != STDIN && arfd != STDOUT)
X		VOID close(arfd);
X	areof = 0;
X	if (arname && *arname == '!')
X		pipewait();
X}
X
X/*
X * nextopen()
X *
X * Open an archive. Returns 0 if successful, -1 otherwise.
X */
XSTATIC int
Xnextopen(mode)
Xint		mode;
X{
X	if (*arname == '!')
X		arfd = pipeopen(mode);
X	else if (strcmp(arname, "-") == 0)
X		arfd = mode ? STDOUT : STDIN;
X	else {
X#ifdef	CTC3B2
X		if (Cflag) {
X			reg int		oops;
X			reg int		fd;
X
X			oops = ((fd = open(arname, O_RDWR | O_CTSPECIAL)) < 0
X			    || ioctl(fd, STREAMON) < 0);
X			VOID close(fd);
X			if (oops)
X				return (warnarch(syserr(), (off_t) 0));
X		}
X#endif	/* CTC3B2 */
X		arfd = mode ? creat(arname, 0666 & ~mask) : open(arname, mode);
X	}
X	if (arfd < 0)
X		return (warnarch(syserr(), (off_t) 0));
X	arleft = aruntil;
X	return (0);
X}
X
X/*
X * openin()
X *
X * Open the next input file. Returns a file descriptor, 0 if no data
X * exists, or -1 at EOF. This kludge works because standard input is
X * in use, preventing open() from returning zero.
X */
XSTATIC int
Xopenin(name, asb)
Xchar		*name;
Xreg Stat	*asb;
X{
X	int		fd;
X	auto char	local[PATHSIZE];
X
X	for (;;) {
X		if (lineget(stdin, name) < 0)
X			return (-1);
X		if (nameopt(name) < 0)
X			continue;
X		if (!gflag)
X			VOID strcpy(local, name);
X		else if (dirchg(name, local) < 0)
X			continue;
X		if ((hflag ? STAT(local, asb) : LSTAT(local, asb)) < 0) {
X			VOID warn(name, syserr());
X			continue;
X		}
X		switch (asb->sb_mode & S_IFMT) {
X		case S_IFDIR:
X			asb->sb_nlink = 1;
X			asb->sb_size = 0;
X			return (0);
X#ifdef	S_IFLNK
X		case S_IFLNK:
X			if ((asb->sb_size = readlink(local,
X			    asb->sb_link, sizeof(asb->sb_link) - 1)) < 0) {
X				VOID warn(name, syserr());
X				continue;
X			}
X			asb->sb_link[asb->sb_size] = '\0';
X			return (0);
X#endif	/* S_IFLNK */
X		case S_IFREG:
X			if (asb->sb_size == 0)
X				return (0);
X			if ((fd = open(local, O_RDONLY)) >= 0)
X			{	if (Zflag)
X					compressfile(&fd, name, asb);
X
X				return (fd);
X			}
X			VOID warn(name, syserr());
X			break;
X		default:
X			asb->sb_size = 0;
X			return (0);
X		}
X	}
X}
X
X/*
X * openo()
X *
X * Open an output file. Returns the output file descriptor,
X * 0 if no data is required or -1 if unsuccessful. Note that
X * UNIX open() will never return 0 because the standard input
X * is in use.
X */
XSTATIC int
Xopeno(name, asb, linkp, ispass)
Xchar		*name;
Xreg Stat	*asb;
XLink		*linkp;
Xreg int		ispass;
X{
X	reg int		exists;
X	reg int		fd;
X	reg ushort	perm;
X	ushort		operm;
X	ushort		ouid;
X	ushort		ogid;
X	Path		*path;
X	auto Stat	osb;
X#ifdef	S_IFLNK
X	reg int		ssize;
X	auto char	sname[PATHSIZE];
X#endif	/* S_IFLNK */
X
X	if (exists = (LSTAT(name, &osb) == 0))
X		if (ispass
X		    && osb.sb_ino == asb->sb_ino
X		    && osb.sb_dev == asb->sb_dev)
X			return (warn(name, "Same file"));
X		else if ((osb.sb_mode & S_IFMT) == (asb->sb_mode & S_IFMT))
X			operm = osb.sb_mode & (xflag ? S_IPERM : S_IPOPN);
X		else if (remove(name, &osb) < 0)
X			return (warn(name, syserr()));
X		else
X			exists = 0;
X	if (linkp) {
X		if (exists)
X			if (asb->sb_ino == osb.sb_ino
X			    && asb->sb_dev == osb.sb_dev)
X				return (0);
X			else if (unlink(name) < 0)
X				return (warn(name, syserr()));
X			else
X				exists = 0;
X		for (path = linkp->l_path; path; path = path->p_forw)
X			if (link(path->p_name, name) == 0
X			  || (errno == ENOENT
X			    && dirneed(name) == 0
X			    && link(path->p_name, name) == 0))
X				return (0);
X			else if (errno != EXDEV)
X				return (warn(name, syserr()));
X		VOID warn(name, "Link broken");
X		linkalso(linkp, name);
X	}
X	perm = asb->sb_mode & (xflag ? S_IPERM : S_IPOPN);
X	if (exists){
X		ouid = osb.sb_uid;
X		ogid = osb.sb_gid;
X	}
X	switch (asb->sb_mode & S_IFMT) {
X	case S_IFBLK:
X	case S_IFCHR:
X		fd = 0;
X		if (exists)
X			if (asb->sb_rdev == osb.sb_rdev)
X				if (perm != operm && chmod(name, perm) < 0)
X					return (warn(name, syserr()));
X				else
X					break;
X			else if (remove(name, &osb) < 0)
X				return (warn(name, syserr()));
X			else
X				exists = 0;
X		if (mknod(name, asb->sb_mode, asb->sb_rdev) < 0
X		  && (errno != ENOENT
X		    || dirneed(name) < 0
X		    || mknod(name, asb->sb_mode, asb->sb_rdev) < 0))
X			return (warn(name, syserr()));
X		break;
X	case S_IFDIR:
X		if (exists)
X			if (perm != operm && chmod(name, perm) < 0)
X				return (warn(name, syserr()));
X			else if (xflag && (asb->sb_uid != ouid || asb->sb_gid != ogid) &&
X				chown(name, asb->sb_uid, asb->sb_gid) < 0)
X				return (warn(name, syserr()));
X			else
X				;
X		else if (dirneed(name) < 0 || dirmake(name, asb) < 0)
X			return (warn(name, syserr()));
X		return (0);
X#ifdef	S_IFIFO
X	case S_IFIFO:
X		fd = 0;
X		if (exists)
X			if (perm != operm && chmod(name, perm) < 0)
X				return (warn(name, syserr()));
X			else
X				;
X		else if (mknod(name, asb->sb_mode, (dev_t) 0) < 0
X		  && (errno != ENOENT
X		    || dirneed(name) < 0
X		    || mknod(name, asb->sb_mode, (dev_t) 0) < 0))
X			return (warn(name, syserr()));
X		break;
X#endif	/* S_IFIFO */
X#ifdef	S_IFLNK
X	case S_IFLNK:
X		if (exists)
X			if ((ssize = readlink(name, sname, sizeof(sname))) < 0)
X				return (warn(name, syserr()));
X			else if (strncmp(sname, asb->sb_link, ssize) == 0)
X				return (0);
X			else if (remove(name, &osb) < 0)
X				return (warn(name, syserr()));
X			else
X				exists = 0;
X		if (symlink(asb->sb_link, name) < 0
X		  && (errno != ENOENT
X		    || dirneed(name) < 0
X		    || symlink(asb->sb_link, name) < 0))
X			return (warn(name, syserr()));
X		return (0);	/* Can't chown()/chmod() a symbolic link */
X#endif	/* S_IFLNK */
X	case S_IFREG:
X		if (exists)
X			if (nflag && osb.sb_mtime > asb->sb_mtime)
X				return (warn(name, "Newer file exists"));
X			else if (unlink(name) < 0)
X				return (warn(name, syserr()));
X			else
X				exists = 0;
X		if ((fd = creat(name, perm)) < 0
X		  && (errno != ENOENT
X		    || dirneed(name) < 0
X		    || (fd = creat(name, perm)) < 0))
X			return (warn(name, syserr()));
X		break;
X	default:
X		return (warn(name, "Unknown filetype"));
X	}
X	if (xflag
X	  && (!exists
X	    || asb->sb_uid != osb.sb_uid
X	    || asb->sb_gid != osb.sb_gid))
X		VOID chown(name,
X		    uid == 0 ? ush(asb->sb_uid) : uid,
X		    ush(asb->sb_gid));
X	if (linkp == NULL && asb->sb_nlink > 1)
X		VOID linkto(name, asb);
X	return (fd);
X}
X
X/*
X * openq()
X *
X * Open the terminal for interactive queries (sigh). Assumes that
X * background processes ignore interrupts and that the open() or
X * the isatty() will fail for processes which are not attached to
X * terminals. Returns a file descriptor (-1 if unsuccessful).
X */
Xint
Xopenq()
X{
X	reg VOIDFN	(*intr)();
X	int	fd;
X
X	fd = -1;
X	if (!Fflag)
X	{
X		if ((intr = signal(SIGINT, SIG_IGN)) == SIG_IGN)
X			return;
X		VOID signal(SIGINT, intr);
X	}
X
X	if ((fd = open(TTY, O_RDWR)) < 0)
X	{	warn(TTY, syserr());
X	}
X	else
X	if (!isatty(fd))
X		warn(TTY, "Is not a tty");
X}
X
X/*
X * options()
X *
X * Decode most reasonable forms of UNIX option syntax. Takes main()-
X * style argument indices (argc/argv) and a string of valid option
X * letters. Letters denoting options with arguments must be followed
X * by colons. With valid options, returns the option letter and points
X * "optarg" at the associated argument (if any). Returns '?' for bad
X * options and missing arguments. Returns zero when no options remain,
X * leaving "optind" indexing the first remaining argument.
X */
XSTATIC int
Xoptions(ac, av, proto)
Xint		ac;
Xregister char	**av;
Xchar		*proto;
X{
X	register int	c;
X	register char	*idx;
X	static int	optsub;
X
X	if (optind == 0) {
X		optind = 1;
X		optsub = 0;
X	}
X	optarg = NULL;
X	if (optind >= ac)
X		return (0);
X	if (optsub == 0 && (av[optind][0] != '-' || av[optind][1] == '\0'))
X		return (0);
X	switch (c = av[optind][++optsub]) {
X	case '\0':
X		++optind;
X		optsub = 0;
X		return (options(ac, av, proto));
X	case '-':
X		++optind;
X		optsub = 0;
X		return (0);
X	case ':':
X		return ('?');
X	}
X	if ((idx = strchr(proto, c)) == NULL)
X		return ('?');
X	if (idx[1] != ':')
X		return (c);
X	optarg = &av[optind][++optsub];
X	++optind;
X	optsub = 0;
X	if (*optarg)
X		return (c);
X	if (optind >= ac)
X		return ('?');
X	optarg = av[optind++];
X	return (c);
X}
X
X/*
X * optsize()
X *
X * Interpret a "size" argument. Recognizes suffices for blocks
X * (512-byte), kilobytes and megabytes and blocksize. Returns
X * the size in bytes.
X */
XSTATIC off_t
Xoptsize(str)
Xchar		*str;
X{
X	reg char	*idx;
X	reg off_t	number;
X	reg off_t	result;
X
X	result = 0;
X	idx = str;
X	for (;;) {
X		number = 0;
X		while (*idx >= '0' && *idx <= '9')
X			number = number * 10 + *idx++ - '0';
X		switch (*idx++) {
X		case 'b':
X			result += number * 512;
X			continue;
X		case 'k':
X			result += number * 1024;
X			continue;
X		case 'm':
X			result += number * 1024L * 1024L;
X			continue;
X		case 'x':
X			result += number * arbsize;
X			continue;
X		case '+':
X			result += number;
X			continue;
X		case '\0':
X			result += number;
X			break;
X		default:
X			break;
X		}
X		break;
X	}
X	if (*--idx)
X		fatal(str, "Unrecognizable value");
X	return (result);
X}
X
X/*
X * out()
X *
X * Write an archive.
X */
XSTATIC VOIDFN
Xout(av)
Xchar		**av;
X{
X	reg int		fd;
X	auto Stat	sb;
X	auto char	name[PATHSIZE];
X
X	if (*av)
X		fatal(*av, "Extraneous argument");
X	while ((fd = openin(name, &sb)) >= 0) {
X		if (!lflag && sb.sb_nlink > 1)
X		{	if (linkfrom(&sb))
X				sb.sb_size = 0;
X			else
X				VOID linkto(name, &sb);
X		}
X
X		outhead(name, &sb);
X		if (fd)
X			VOID close(outdata(fd, name, sb.sb_size));
X		if (vflag)
X			VOID fprintf(stderr, "%s\n", name);
X	}
X	outeof(TRAILER, TRAILZ);
X}
X
X/*
X * outalloc()
X *
X * Allocate buffer space previously referenced by outavail().
X */
XSTATIC void
Xoutalloc(len)
Xreg uint	len;
X{
X	bufidx += len;
X	total += len;
X}
X
X/*
X * outavail()
X *
X * Index buffer space for archive output. Stores a buffer pointer
X * at a given location. Returns the number of bytes available.
X */
XSTATIC uint
Xoutavail(bufp)
Xreg char	**bufp;
X{
X	reg uint	have;
X
X	while ((have = bufend - bufidx) == 0)
X		outflush(NOTDONE);
X	*bufp = bufidx;
X	return (have);
X}
X
X/*
X * outdata()
X *
X * Write archive data. Continues after file read errors, padding with
X * null characters if neccessary. Always returns the given input file
X * descriptor.
X */
XSTATIC int
Xoutdata(fd, name, size)
Xint		fd;
Xchar		*name;
Xreg off_t	size;
X{
X	reg uint	chunk;
X	reg int		got;
X	reg int		oops;
X	reg uint	avail;
X	auto char	*buf;
X
X	oops = got = 0;
X	while (size) {
X		avail = outavail(&buf);
X		size -= (chunk = size < avail ? (uint) size : avail);
X		if (oops == 0 && (got = read(fd, buf, chunk)) < 0) {
X			oops = warn(name, syserr());
X			got = 0;
X		}
X		if (got < chunk) {
X			if (oops == 0)
X				oops = warn(name, "Early EOF");
X			while (got < chunk)
X				buf[got++] = '\0';
X		}
X		outalloc(chunk);
X	}
X	return (fd);
X}
X
X/*
X * outeof()
X *
X * Write an archive trailer.
X */
XSTATIC void
Xouteof(name, namelen)
Xchar		*name;
Xreg uint	namelen;
X{
X	reg off_t	pad;
X	auto char	header[M_STRLEN + H_STRLEN + 1];
X
X	if (pad = (total + M_STRLEN + H_STRLEN + namelen) % arpad)
X		pad = arpad - pad;
X	VOID strcpy(header, M_ASCII);
X	VOID sprintf(header + M_STRLEN, H_PRINT, 0, 0,
X	    0, 0, 0, 1, 0, (time_t) 0, namelen, pad);
X	outwrite(header, M_STRLEN + H_STRLEN);
X	outwrite(name, namelen);
X	outpad(pad);
X	outflush(DONE);
X	if (fflag)
X		outwait();
X}
X
X/*
X * outflush()
X *
X * Flush the output buffer. Optionally fork()s to allow the
X * parent to refill the buffer while the child waits for the
X * write() to complete.
X */
XSTATIC void
Xoutflush(done)
Xint	done;
X{
X	int	wrstat;
X
X	/*
X	 * in this case we are a floppy and want to write the floppy from one
X	 * buffer (to have a copy of the data to verify against)
X	 */
X	bufend = buffer + (aruntil ? min(buflen, arleft) : buflen);
X	if (Fflag && (done == NOTDONE) && ((bufend - bufidx) > 0))
X	{	return;
X	}
X
X	if (aruntil && arleft == 0)
X		next(O_WRONLY, "Output limit reached");
X
X	/*
X	 * if we are a floppy open the disk at the last moment
X	 * call verify w/ an error if the disk can't be opened
X	 */
X	if (Fflag)
X	{
X		if (nextopen(O_WRONLY) < 0)
X		{
X			verifycnt = 0;
X			verify(1);
X			if (done == NOTDONE)
X				next(O_WRONLY, "Disk's output limit reached (disk one)");
X			bufend = (bufidx = buffer) + (aruntil ? min(buflen, arleft) : buflen);
X			return;
X		}
X	}
X	if (fflag) {
X		outwait();
X		if ((outpid = xfork("outflush()", DIE)) == 0)
X			VOID nice(-40);
X	}
X
X	if (!fflag || outpid == 0) {
X		wrstat = writedisk(1);
X		if (Fflag && verifyflag)
X		{	verifycnt = 0;
X			verify(wrstat);
X			if (done == NOTDONE)
X				next(O_WRONLY, "Disk's output limit reached");
X		}
X
X	}
X	if (fflag) {
X		if (outpid == 0)
X			_exit(0);
X		else
X			arleft -= bufidx - buffer;
X	}
X	bufend = (bufidx = buffer) + (aruntil ? min(buflen, arleft) : buflen);
X}
X
X/*
X * outhead()
X *
X * Write an archive header.
X */
XSTATIC void
Xouthead(name, asb)
Xreg char	*name;
Xreg Stat	*asb;
X{
X	reg uint	namelen;
X	auto char	header[M_STRLEN + H_STRLEN + 1];
X
X	if (name[0] == '/')
X		if (name[1])
X			++name;
X		else
X			name = ".";
X	namelen = (uint) strlen(name) + 1;
X	VOID strcpy(header, M_ASCII);
X	VOID sprintf(header + M_STRLEN, H_PRINT, ush(asb->sb_dev),
X	    ush(asb->sb_ino), ush(asb->sb_mode), ush(asb->sb_uid),
X	    ush(asb->sb_gid), ush(asb->sb_nlink), ush(asb->sb_rdev),
X	    mflag ? timenow : asb->sb_mtime, namelen, asb->sb_size);
X	outwrite(header, M_STRLEN + H_STRLEN);
X	outwrite(name, namelen);
X#ifdef	S_IFLNK
X	if ((asb->sb_mode & S_IFMT) == S_IFLNK)
X		outwrite(asb->sb_link, (uint) asb->sb_size);
X#endif	/* S_IFLNK */
X}
X
X/*
X * outpad()
X *
X * Pad the archive.
X */
XSTATIC void
Xoutpad(pad)
Xreg off_t	pad;
X{
X	reg int		idx;
X	reg int		len;
X
X	while (pad) {
X		if ((len = bufend - bufidx) > pad)
X			len = pad;
X		for (idx = 0; idx < len; ++idx)
X			*bufidx++ = '\0';
X		total += len;
X		outflush(NOTDONE);
X		pad -= len;
X	}
X}
X
X/*
X * outwait()
X *
X * Wait for the last background outflush() process (if any). The child
X * exit value is zero if successful, 255 if a write() returned zero or
X * the value of errno if a write() was unsuccessful.
X */
XSTATIC void
Xoutwait()
X{
X	auto int	status;
X
X	if (outpid == 0)
X		return;
X	status = xwait(outpid, "outwait()", TRUE);
X	outpid = 0;
X	if (status)
X		fatal(arspec, "Child error");
X}
X
X/*
X * outwrite()
X *
X * Write archive data.
X */
XSTATIC void
Xoutwrite(idx, len)
Xreg char	*idx;
Xuint		len;
X{
X	reg uint	have;
X	reg uint	want;
X	reg char	*endx = idx + len;
X
X	while (want = endx - idx) {
X		while ((have = bufend - bufidx) == 0)
X			outflush(NOTDONE);
X		if (have > want)
X			have = want;
X		memcpy(bufidx, idx, have);
X		bufidx += have;
X		idx += have;
X		total += have;
X	}
X}
X
X/*
X * pass()
X *
X * Copy within the filesystem.
X */
XSTATIC VOIDFN
Xpass(av)
Xreg char	**av;
X{
X	reg int		fd;
X	reg char	**avx;
X	auto Stat	sb;
X	auto char	name[PATHSIZE];
X
X	for (avx = av; *avx; ++avx) {
X		if (gflag && **avx != '/')
X			fatal(*avx, "Relative pathname");
X		if (STAT(*avx, &sb) < 0)
X			fatal(*avx, syserr());
X		if ((sb.sb_mode & S_IFMT) != S_IFDIR)
X			fatal(*avx, "Not a directory");
X	}
X	while ((fd = openin(name, &sb)) >= 0) {
X		if (passitem(name, &sb, fd, av))
X			VOID close(fd);
X		if (vflag)
X			VOID fprintf(stderr, "%s\n", name);
X	}
X}
X
X/*
X * passdata()
X *
X * Copy data to one file. Doesn't believe in input file
X * descriptor zero (see description of kludge in openin()
X * comments). Closes the provided output file descriptor.
X */
XSTATIC void
Xpassdata(from, ifd, to, ofd)
Xchar		*from;
Xreg int		ifd;
Xchar		*to;
Xreg int		ofd;
X{
X	reg int		got;
X	reg int		sparse;
X	auto char	block[FSBUF];
X
X	if (ifd) {
X		VOID lseek(ifd, (off_t) 0, 0);
X		sparse = 0;
X		while ((got = read(ifd, block, sizeof(block))) > 0
X		    && (sparse = swrite(ofd, block, (uint) got)) >= 0)
X			total += got;
X		if (got)
X			VOID warn(got < 0 ? from : to, syserr());
X		else if (sparse > 0
X		  && (lseek(ofd, (off_t) -sparse, 1) < 0
X		    || write(ofd, block, (uint) sparse) != sparse))
X			VOID warn(to, syserr());
X	}
X	VOID close(ofd);
X}
X
X/*
X * passitem()
X *
X * Copy one file. Returns given input file descriptor.
X */
XSTATIC int
Xpassitem(from, asb, ifd, dir)
Xchar		*from;
XStat		*asb;
Xreg int		ifd;
Xreg char	**dir;
X{
X	reg int		ofd;
X	auto time_t	tstamp[2];
X	auto char	to[PATHSIZE];
X
X	while (*dir) {
X		if (nameopt(strcat(strcat(strcpy(to, *dir++), "/"), from)) < 0)
X			continue;
X		if ((ofd = openo(to, asb,
X		    lflag ? linkto(from, asb) : linkfrom(asb), 1)) < 0)
X			continue;
X		if (ofd > 0)
X			passdata(from, ifd, to, ofd);
X		tstamp[0] = tstamp[1] = mflag ? timenow : asb->sb_mtime;
X		VOID utime(to, tstamp);
X	}
X	return (ifd);
X}
X
X/*
X * pipechld()
X *
X * Child portion of pipeline fork.
X */
XSTATIC int
Xpipechld(mode, pfd)
Xint		mode;
Xreg int		*pfd;
X{
X	reg char	**av;
X	auto char	*arg[32];
X
X	av = arg;
X	if ((*av = getenv("SHELL")) && **av)
X		++av;
X	else
X		*av++ = "/bin/sh";
X	*av++ = "-c";
X	*av++ = arname + 1;
X	*av = NULL;
X	if (mode) {
X		VOID close(pfd[1]);
X		VOID close(STDIN);
X		VOID dup(pfd[0]);
X		VOID close(pfd[0]);
X		VOID close(STDOUT);
X		VOID open("/dev/null", O_WRONLY);
X	} else {
X		VOID close(STDIN);
X		VOID open("/dev/null", O_RDONLY);
X		VOID close(pfd[0]);
X		VOID close(STDOUT);
X		VOID dup(pfd[1]);
X		VOID close(pfd[1]);
X	}
X	if (ttyf >= 0)
X		VOID close(ttyf);
X	VOID execvp(arg[0], arg);
X	VOID warn(arg[0], syserr());
X	_exit(1);
X}
X
X/*
X * pipeopen()
X *
X * Open an archive via a pipeline. Returns a file
X * descriptor, or -1 if unsuccessful.
X */
XSTATIC int
Xpipeopen(mode)
Xreg int		mode;
X{
X	auto int	pfd[2];
X
X	if (pipe(pfd) < 0)
X		return (-1);
X	if ((pipepid = xfork("pipeopen()", DIE)) == 0)
X		pipechld(mode, pfd);
X	if (mode) {
X		VOID close(pfd[0]);
X		return (pfd[1]);
X	} else {
X		VOID close(pfd[1]);
X		return (pfd[0]);
X	}
X}
X
X/*
X * pipewait()
X *
X * Await a pipeline.
X */
XSTATIC void
Xpipewait()
X{
X	reg int		status;
X
X	if (pipepid == 0)
X		return;
X	status = xwait(pipepid, "pipewait()", TRUE);
X	pipepid = 0;
X	if (status)
X		fatal(arspec, "Pipeline error");
X}
X
X/*
X * prsize()
X *
X * Print a file offset.
X */
XSTATIC void
Xprsize(stream, size)
XFILE		*stream;
Xreg off_t	size;
X{
X	reg off_t	n;
X
X	if (n = (size / (1024L * 1024L))) {
X		VOID fprintf(stream, "%ldm+", n);
X		size -= n * 1024 * 1024;
X	}
X	if (n = (size / 1024)) {
X		VOID fprintf(stream, "%ldk+", n);
X		size -= n * 1024;
X	}
X	VOID fprintf(stream, "%ld", size);
X}
X
X#ifndef	MKDIR
X
X/*
X * rmdir()
X *
X * Remove a directory via "/bin/rmdir". Sets errno to a
X * questionably sane value upon failure.
X */
XSTATIC int
Xrmdir(name)
Xreg char	*name;
X{
X	reg int		pid;
X
X	if ((pid = xfork("rmdir()", DIE)) == 0) {
X		VOID close(fileno(stdin));
X		VOID close(fileno(stdout));
X		VOID close(fileno(stderr));
X		VOID open("/dev/null", O_RDWR);
X		VOID dup(fileno(stdin));
X		VOID dup(fileno(stdin));
X		VOID execl("/bin/rmdir", "rmdir", name, (char *) NULL);
X		exit(1);
X	}
X	if (xwait(pid, "rmdir()", TRUE) == 0)
X		return (0);
X	errno = EACCES;
X	return (-1);
X}
X
X#endif	/* MKDIR */
X
X/*
X * swrite()
X *
X * Write a filesystem block. Seeks past sparse blocks. Returns
X * 0 if the block was written, the given length for a sparse
X * block or -1 if unsuccessful.
X */
XSTATIC int
Xswrite(fd, buf, len)
Xint		fd;
Xchar		*buf;
Xuint		len;
X{
X	reg char	*bidx;
X	reg char	*bend;
X
X	if (jflag)
X		return (write(fd, buf, len) == len ? 0 : -1);
X	bend = (bidx = buf) + len;
X	while (bidx < bend)
X		if (*bidx++)
X			return (write(fd, buf, len) == len ? 0 : -1);
X	return (lseek(fd, (off_t) len, 1) < 0 ? -1 : len);
X}
X
X/*
X * syserr()
X *
X * Return pointer to appropriate system error message.
X */
XSTATIC char *
Xsyserr()
X{
X	static char	msg[40];
X
X	if (errno > 0 && errno < sys_nerr)
X		return (sys_errlist[errno]);
X	VOID sprintf(msg, "Unknown error (errno %d)", errno);
X	return (msg);
X}
X
X/*
X * toc()
X *
X * Print archive table of contents.
X */
XSTATIC VOIDFN
Xtoc(av)
Xreg char	**av;
X{
X	auto Stat	sb;
X	auto char	name[PATHSIZE];
X
X	if (*av)
X		fatal(*av, "Extraneous argument");
X	name[0] = '\0';
X	while (inhead(name, &sb) == 0) {
X		if (namecmp(name) == 0)
X			tocentry(name, &sb);
X		if (inskip(sb.sb_size) < 0)
X			VOID warn(name, "File data is corrupt");
X	}
X}
X
X/*
X * tocentry()
X *
X * Print a single table-of-contents entry.
X */
XSTATIC void
Xtocentry(name, asb)
Xchar		*name;
Xreg Stat	*asb;
X{
X	reg Time	*atm;
X	reg Link	*from;
X	reg Passwd	*pwp;
X	reg Group	*grp;
X	static char	*month[] = {
X		"Jan", "Feb", "Mar", "Apr", "May", "Jun",
X		"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
X	};
X
X	if (vflag) {
X		tocmode(asb->sb_mode);
X		VOID printf(" %2d", asb->sb_nlink);
X		atm = localtime(&asb->sb_mtime);
X		if (pwp = getpwuid(ush(asb->sb_uid)))
X			VOID printf(" %-8s", pwp->pw_name);
X		else
X			VOID printf(" %-8u", ush(asb->sb_uid));
X		if (grp = getgrgid(ush(asb->sb_gid)))
X			VOID printf(" %-8s", grp->gr_name);
X		else
X			VOID printf(" %-8u", ush(asb->sb_gid));
X		switch (asb->sb_mode & S_IFMT) {
X		case S_IFBLK:
X		case S_IFCHR:
X			VOID printf(" %3d, %3d",
X			    major(asb->sb_rdev), minor(asb->sb_rdev));
X			break;
X		case S_IFREG:
X			VOID printf(" %8ld", asb->sb_size);
X			break;
X		default:
X			VOID printf("         ");
X		}
X		VOID printf(" %3s %2d %02d:%02d:%02d %4d ",
X		    month[atm->tm_mon], atm->tm_mday, atm->tm_hour,
X		    atm->tm_min, atm->tm_sec, atm->tm_year + 1900);
X	}
X	VOID printf("%s", name);
X	if (vflag || lflag) {
X		if (asb->sb_nlink > 1)
X			if (from = linkfrom(asb))
X				VOID printf(" -> %s",
X				    from->l_path->p_name);
X			else
X				VOID linkto(name, asb);
X#ifdef	S_IFLNK
X		if ((asb->sb_mode & S_IFMT) == S_IFLNK)
X			VOID printf(" S-> %s", asb->sb_link);
X#endif	/* S_IFLNK */
X	}
X	putchar('\n');
X}
X
X/*
X * tocmode()
X *
X * Fancy file mode display.
X */
XSTATIC void
Xtocmode(mode)
Xreg ushort	mode;
X{
X	switch (mode & S_IFMT) {
X		case S_IFREG: putchar('-'); break;
X		case S_IFDIR: putchar('d'); break;
X#ifdef	S_IFLNK
X		case S_IFLNK: putchar('l'); break;
X#endif	/* S_IFLNK */
X		case S_IFBLK: putchar('b'); break;
X		case S_IFCHR: putchar('c'); break;
X#ifdef	S_IFIFO
X		case S_IFIFO: putchar('p'); break;
X#endif	/* S_IFIFO */
X		default:
X			VOID printf("[%o]", mode >> S_IFSHF);
X	}
X	putchar(mode & 0400 ? 'r' : '-');
X	putchar(mode & 0200 ? 'w' : '-');
X	putchar(mode & 0100
X	    ? mode & 04000 ? 's' : 'x'
X	    : mode & 04000 ? 'S' : '-');
X	putchar(mode & 0040 ? 'r' : '-');
X	putchar(mode & 0020 ? 'w' : '-');
X	putchar(mode & 0010
X	    ? mode & 02000 ? 's' : 'x'
X	    : mode & 02000 ? 'S' : '-');
X	putchar(mode & 0004 ? 'r' : '-');
X	putchar(mode & 0002 ? 'w' : '-');
X	putchar(mode & 0001
X	    ? mode & 01000 ? 't' : 'x'
X	    : mode & 01000 ? 'T' : '-');
X}
X
X/*
X * usage()
X *
X * Print a helpful message and exit.
X */
XSTATIC void
Xusage()
X{
X	VOID fprintf(stderr, "\
XUsage:	%s -o [ -fghlmuvzq ] [ -(bces) n ] [-F[KZL status_file][R format_command]s n]  archive\n\
X	%s -i [ -djkmnuvxz ] [ -(bcs) n ] [ -y prefix ] archive\n\
X	%s -t [ -kuvz ] [ -(bcs) n ] [ -y prefix ] archive\n\
X	%s -p [ -dghjlmnuvxz ] dir [ ... ]\n",
X	    myname, myname, myname, myname);
X	exit(1);
X}
X
X/*
X * warn()
X *
X * Print a warning message. Always returns -1.
X */
XSTATIC int
Xwarn(what, why)
Xchar	*what;
Xchar	*why;
X{
X	long	dietime;
X	dietime = time((time_t *) NULL);
X
X	VOID fprintf(stderr,
X	    "%s: \"%s\": %s\n",
X	    myname, what, why);
X	if (logfile != (FILE *)0)
X		VOID fprintf(logfile, "%s: \"%s\": %s (disk %u) at %s\n",
X			myname, what, why, arvolume, ctime(&dietime));
X	return (-1);
X}
X
X/*
X * warnarch()
X *
X * Print an archive-related warning message, including
X * an adjusted file offset. Always returns -1.
X */
XSTATIC int
Xwarnarch(msg, adjust)
Xchar	*msg;
Xoff_t	adjust;
X{
X	VOID fprintf(stderr, "%s: \"%s\" [offset ", myname, arspec);
X	prsize(stderr, total - adjust);
X	VOID fprintf(stderr, "]: %s\n", msg);
X	return (-1);
X}
X
X/*
X * xfork()
X *
X * Create a child.
X */
XSTATIC int
Xxfork(what, die)
Xreg char	*what;
Xint	die;
X{
X	reg int		pid;
X	reg Child	*cp;
X	reg int		idx;
X	static uint	delay[] = { 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144 };
X
X	VOID signal(SIGCHLD, SIG_DFL);	/* SysV mostly... */
X	for (idx = 0; (pid = fork()) < 0; ++idx) {
X		if (idx == sizeof(delay))
X			if (die)
X				fatal(arspec, syserr());
X			else
X				return(-1);
X		VOID warn(what, "Trouble forking...");
X		if (Fflag && !die)	/* give up and go on... */
X			return(-1);
X		sleep(delay[idx]);
X	}
X	if (idx)
X		VOID warn(what, "...successful fork");
X	cp = (Child *) memget(sizeof(*cp));
X	cp->c_pid = pid;
X	cp->c_flags = 0;
X	cp->c_status = 0;
X	cp->c_forw = children;
X	children = cp;
X	return (pid);
X}
X
X/*
X * xpause()
X *
X * Await a child.
X */
XSTATIC void
Xxpause()
X{
X	reg Child	*cp;
X	reg int		pid;
X	auto int	status;
X
X	do {
X		while ((pid = wait(&status)) < 0)
X			;
X		for (cp = children; cp && cp->c_pid != pid; cp = cp->c_forw)
X			;
X	} while (cp == NULL);
X	cp->c_flags |= CF_EXIT;
X	cp->c_status = status;
X}
X
X/*
X * xwait()
X *
X * Find the status of a child.
X */
XSTATIC int
Xxwait(pid, what, compstat2)
Xreg int		pid;
Xchar		*what;
Xint		compstat2;
X{
X	reg int		status;
X	reg Child	*cp;
X	reg Child	**acp;
X	auto char	why[100];
X
X	for (acp = &children; cp = *acp; acp = &cp->c_forw)
X		if (cp->c_pid == pid)
X			break;
X	if (cp == NULL)
X		fatal(what, "Lost child");
X	while ((cp->c_flags & CF_EXIT) == 0)
X		xpause();
X	status = cp->c_status;
X	*acp = cp->c_forw;
X	free((char *) cp);
X	if (status == 0)
X		return (0);
X	if (status & 0377)
X		VOID sprintf(why, "Killed by signal %d%s",
X		    status & 0177, status & 0200 ? " -- core dumped" : "");
X	else
X		VOID sprintf(why, "Exit %d", (status >> 8) & 0377);
X
X	if ((!compstat2 && ((status >> 8) & 0377 != 2)) || compstat2)
X		return (warn(what, why));
X	else
X		return ((status >> 8) & 0377);
X}
X
X/* right now we verify the whole disk */
Xverify(error)
Xint	error;
X{	char	*verbuf;
X	char	*buf;
X	reg time_t	began;
X	int	got, readamt, len;
X	int	wrstat;
X	auto char	msg[200];
X	auto char	answer[20];
X
X	if (*arname == '!')
X	{	warn("Can't verify a piped command", "");
X		return;
X	}
X	if (!error)
X	{	if ((verbuf = malloc(arbsize)) == NULL)
X			fatal(arspec, "Cannot allocate Verify I/O buffer");
X
X		/*
X		 * close as O_WRONLY and reopen as O_RDONLY to verify (on a
X		 * disk this is a good thing)
X		 */
X		nextclos();
X		nextopen(O_RDONLY);
X		fprintf(stderr, "Verifying...\n");
X		verifycnt++;
X		for (buf = buffer; len = bufidx - buf; )
X		{
X			readamt = min(len, arbsize);
X			if ((got = read(arfd, verbuf, readamt)) == readamt)
X			{
X#ifdef HAVEMEMCMP
X				if (memcmp(verbuf, buf, got) != 0)
X#else
X				if (bcmp(verbuf, buf, got) != 0)
X#endif
X				{	warn("Verify failed", "");
X					error = 1;
X					break;
X				}
X				else
X					buf += got;
X			}
X			else
X			{	warn("Read returned short", "");
X			fprintf(stderr, "Read %d wanted %d bytes\n", got,
X					readamt);
X				error = 1;
X				break;
X			}
X		}
X		free(verbuf);
X	}
X	if (error)
X	{	int	answernum = 0;
X
X		for (;;)
X		{
X		    /*
X		     * answernum == 3 here means the format worked and we want
X		     * to try to write the disk (answernum == 1)
X		     */
X		    if (answernum == 3)
X			answernum = 1;
X		    else
X		    {
X			/* don't count time waiting for the user */
X			began = time((time_t *) NULL);
X			VOID sprintf(msg, "\
X%s: %s of disk %u has FAILED (try option #3 first)!\07\n\
X\tEnter 1 to RETRY this disk\n\
X\tEnter 2 to REFORMAT this disk\n\07\
X\tEnter 3 to REFORMAT AND THEN RETRY this disk if the format works\n%s",
X				myname,
X				((error && (verifycnt == 0)) ? "Writing" :
X					"Verify"),
X				arvolume, hidequit ? "" :
X				"\tEnter \"quit\" to ABORT the backup\n\07");
X			nextask(msg, answer, sizeof(answer));
X			timewait += time((time_t *) NULL) - began;
X			answernum = atoi(answer);
X		    }
X
X		    if (answernum == 1)	/* note: recursive here... */
X		    {	nextclos();
X			/* if we can't open, try again */
X			if (nextopen(O_WRONLY) < 0)
X				continue;
X			wrstat = writedisk(0);
X				/* if we failed or we are verifing */
X			if (wrstat || (Fflag && verifyflag))
X				verify(wrstat);
X			break;
X		    }
X		    else
X		    if ((answernum == 2) || (answernum == 3))
X		    {	if (system(formatcmd) != 0)
X			{	fprintf(stderr, "Format failed!\n");
X				answernum = 0; /* error, don't autowrite */
X			}
X			else
X				fprintf(stderr, "Format successful!\n");
X		    }
X		    else
X		    if (strcmp(answer, "quit") == 0)
X			fatal(arspec, "Quiting during a verify");
X		}
X	}
X}
X
Xwritedisk(realwrite)
Xint	realwrite;
X{
X	reg char	*buf;
X	reg int		got;
X	reg uint	len;
X	char	*tbuf;
X
X	if (Fflag)
X		fprintf(stderr, "Writing the disk...\n");
X#if ((defined (USESHMEM)) && (defined (UNIXPC)))
X	/*
X	 * stupid 3b1 will block in a write to a floppy from shared memory
X	 * (tested on Unix3.51[ ac])
X	 */
X	if ((tbuf = malloc(arbsize)) == (char *)0)
X	{	fatal("writedisk malloc failed", syserr());
X	}
X#endif
X	for (buf = buffer; len = bufidx - buf; ) {
X#if ((defined (USESHMEM)) && (defined (UNIXPC)))
X		memcpy(tbuf, buf, min(len, arbsize));
X		if ((got = write(arfd, tbuf,
X#else
X		if ((got = write(arfd, buf,
X#endif
X		    *arname == '!' ? len : min(len, arbsize))) > 0) {
X			buf += got;
X			if (realwrite)
X				arleft -= got;
X		} else if (fflag) {
X			VOID warn(arspec, got < 0
X			    ? syserr()
X			    : "Apparently full");
X			_exit(1);
X		} else if (got < 0) {
X			/*fatal(arspec, syserr());*/
X			warn(arspec, syserr());
X			return(1);
X		}
X		else
X		{
X/* check here if verifying... */
X			next(O_WRONLY, "Apparently full");
X			return(1);
X		}
X	}
X#if ((defined (USESHMEM)) && (defined (UNIXPC)))
X	free(tbuf);
X#endif
X	return(0);
X}
X
X/*
X * compress "name" if we can
X * If we compress we change the statbuf size, the file name, close
X * the old pointer to the file and return the pointer to the compressed
X * version;
X */
X#include <sys/dir.h>
Xcompressfile(fdp, name, asb)
Xint	*fdp;
Xchar	*name;
Xreg Stat	*asb;
X{
X	int		compout;
X	char		*tmpcomp;
X	Stat		asb2;
X	int		comppid;
X
X	/* compress only if no links */
X	if (!lflag && (asb->sb_nlink == 1) &&
X				(strncmp(name+strlen(name)-2, ".Z", 2) != 0))
X	{
X		/* make sure compress could put on the .Z */
X		if ((tmpcomp = strrchr(name, '/')) != NULL)
X			tmpcomp++;
X		else
X			tmpcomp = name;
X#ifdef MAXNAMLEN	/* BSD otherwise should be sysV (FFS on sysV?) */
X		if (strlen(tmpcomp)+2 > MAXNAMLEN)
X#else
X		if (strlen(tmpcomp)+2 > DIRSIZ)
X#endif
X		{	warn(name, " is too long to tack on .Z");
X			return;
X		}
X
X		tmpcomp = tempnam(NULL, "afiot");
X		VOID unlink(tmpcomp);
X
X		if ((comppid = xfork("out(), compressing", NODIE)) == 0)
X		{	VOID close(fileno(stdout));
X			if (open(tmpcomp, O_WRONLY|O_CREAT|O_TRUNC, 0600) >= 0)
X			{	execlp("compress", "compress", "-c",
X				       name, 0);
X				close(tmpcomp);
X			}
X			else
X				warn("open compress output", syserr());
X			exit(1);
X		}
X		if ((comppid > 0) &&
X		    (xwait(comppid, "out(), wait for child", FALSE) == 0))
X		{
X			if ((hflag ? STAT(tmpcomp, &asb2) :
X				LSTAT(tmpcomp, &asb2)) >= 0)
X			{
X				if ((compout = open(tmpcomp, O_RDONLY, 0400)) > 0)
X				{
X					if (asb2.sb_size < asb->sb_size)
X					{	close(*fdp);
X						strcat(name, ".Z");
X						asb->sb_size = asb2.sb_size;
X						*fdp = compout;
X					}
X				}
X			}
X		}
X
X		/*
X		 * assume the open descriptor above will still access the file
X		 */
X		if (tmpcomp)
X			unlink(tmpcomp);
X	}
X}
X
X#ifdef USESHMEM
Xextern char	*shmat();
Xint	memids[NUMSHKEYS];
Xchar	*memlocs[NUMSHKEYS];
Xint	indx = 0;
X
X/* in this malloc we assume the memory segments will be allocated in order */
Xchar *
Xshmemalloc(buflen, arname)
Xuint	buflen;		/* Archive buffer length */
Xchar	*arname;
X{
X	int	size;
X	key_t	shkey;
X
X	do {
X		if (indx == NUMSHKEYS)
X			fatal("Too many shared memory keys", "");
X		shkey = ftok(arname, (char)indx);
X		size = min(SHMEMSIZE, buflen);
X		if ((memids[indx] = shmget(shkey, size, IPC_CREAT|0666)) < 0) {
X			fatal("shmget failed", syserr());
X		}
X		if ((memlocs[indx] = shmat(memids[indx], 0, 0)) == (char *)-1) {
X			fatal("shmat failed", syserr());
X		}
X		indx++;
X		buflen -= size;
X	} while (buflen > 0);
X	return(memlocs[0]);
X}
X
Xvoid
Xshmemfree()
X{	for(--indx; indx >= 0; indx--)
X		if (shmctl(memids[indx], IPC_RMID, NULL) < 0) {
X			warn("shmctl failed (shmemfree)\n", syserr());
X	}
X}
X#endif /* USESHMEM */
X
Xgoodbye(stat)
Xint	stat;
X{
X#ifdef USESHMEM
X	shmemfree();
X#endif
X	/* log that we died */
X	if (stat && (logfile != (FILE *)0))
X	{	long	dietime;
X		dietime = time((time_t *) NULL);
X
X		VOID fprintf(logfile, "%s: the backup has failed (stat %d), died at %s",
X			myname, stat, ctime(&dietime));
X
X	}
X		
X	exit(stat);
X}
X
X#ifdef MYTEMPNAM
X/* author:	Monty Walls
X * written:	4/17/89
X * Copyright:	Copyright (c) 1989 by Monty Walls.
X *		Not derived from licensed software.
X *
X *		Permission to copy and/or distribute granted under the
X *		following conditions:
X *	
X *		1). This notice must remain intact.
X *		2). The author is not responsible for the consequences of use
X *			this software, no matter how awful, even if they
X *			arise from defects in it.
X *		3). Altered version must not be represented as being the 
X *			original software.
X */
X
X#define MAXPREFIX	5
X#define TMPNAME		"tmp"
X#ifndef P_tmpdir
X#define P_tmpdir	"/tmp"
X#define L_tmpnam	14
X#endif
X
Xextern char *mktemp();
Xextern char *strcat();
Xextern char *strcpy();
Xextern char *getenv();
X
Xchar *
Xtempnam(dir, name)
Xchar *dir;
Xchar *name;
X{
X	char *buf, *tmpdir;
X	
X	/* 
X	 * This is kind of like the chicken & the egg.
X	 * Do we use the users preference or the programmers?
X	 */
X#ifdef USE_ENV_VAR	 
X	if ((tmpdir = getenv("TMPDIR")) == (char *)NULL) {
X		if ((tmpdir = dir) == (char *)NULL)
X			tmpdir = P_tmpdir;
X	}
X#else
X	if ((tmpdir = dir) == (char *)NULL) {
X		if ((tmpdir = getenv("TMPDIR")) == (char *)NULL)
X			tmpdir = P_tmpdir;
X	}
X#endif
X	/* now lets check and see if we can work there */
X	if (access(tmpdir, R_OK+W_OK+X_OK) < 0)
X		return ((char *)NULL);
X			
X	if (name == (char *)NULL) 
X		name = TMPNAME;
X	else if (strlen(name) > MAXPREFIX)
X		name[5] = '\0';	/* this is according to SYS5 */
X	
X	/* the magic value 2 is for '\0' & '/' */
X	if ((buf = (char *)malloc(L_tmpnam + strlen(tmpdir) + 2)) == (char *)NULL)
X		return ((char *)NULL);
X		
X	strcpy(buf, tmpdir);
X	strcat(buf, "/");
X	strcat(buf, name);
X	strcat(buf, ".XXXXXX");
X	
X	/* pass our completed pattern to mktemp */
X	return (mktemp(buf));
X}
X#endif /* MYTEMPNAM */
SHAR_EOF
$TOUCH -am 0123145091 afio.c &&
chmod 0444 afio.c ||
echo "restore of afio.c failed"
set `wc -c afio.c`;Wc_c=$1
if test "$Wc_c" != "72522"; then
	echo original size 72522, current size $Wc_c
fi
exit 0

-- 
David H. Brierley
Home: dave at galaxia.newport.ri.us; Work: dhb at quahog.ssd.ray.com
Send comp.sources.3b1 submissions to comp-sources-3b1 at galaxia.newport.ri.us
%% Can I be excused, my brain is full. **



More information about the Comp.sources.3b1 mailing list