Imported Upstream version 1.01b upstream upstream/1.01b
authorAxel Beckert <abe@deuxchevaux.org>
Fri, 7 Sep 2012 10:59:49 +0000 (12:59 +0200)
committerAxel Beckert <abe@deuxchevaux.org>
Fri, 7 Sep 2012 10:59:49 +0000 (12:59 +0200)
ANNOUNCE [new file with mode: 0644]
ARTICLE [new file with mode: 0644]
Makefile [new file with mode: 0644]
READ.ME [new file with mode: 0644]
mmv.1 [new file with mode: 0644]
mmv.c [new file with mode: 0644]

diff --git a/ANNOUNCE b/ANNOUNCE
new file mode 100644 (file)
index 0000000..122d50f
--- /dev/null
+++ b/ANNOUNCE
@@ -0,0 +1,45 @@
+Copyright (c) 1989 Vladimir Lanin
+
+This is mmv, a program to move/copy/append/link multiple files
+according to a set of wildcard patterns. This multiple action is
+performed safely, i.e. without any unexpected deletion of files due to
+collisions of target names with existing filenames or with other
+target names. Furthermore, before doing anything, mmv attempts to
+detect any errors that would result from the entire set of actions
+specified and gives the user the choice of either aborting before
+beginning, or proceeding by avoiding the offending parts.
+
+Improvements over mmv's predecessor, ren:
+
+. support for BSD, System 5, and MS-DOS
+
+. source and target files may (usually) reside in different directories
+
+. paths may contain wildcards
+
+. supports all csh wildcards: '*', '?', '['...']', and '~'
+
+. the ';' wildcard finds files at any level in the tree
+
+. can copy, append, or link instead of moving/renaming
+
+. reads multiple patterns from standard input (or one from command line)
+
+. no-execute option (whose output can be fed back in on standard input)
+
+Note to users familiar with ren: the -a and -k options have been renamed
+to -t and -g, respectively, and their semantics have somewhat changed.
+
+
+Mmv is freeware. That means that the entire package of software and
+documentation is copyrighted, and may not be distributed with any
+modifications or for any charge (without the author's explicit written
+permission). Other than that, it may be used and distributed freely.
+
+
+Vladimir Lanin
+330 Wadsworth Ave, Apt 6F
+New York, NY 10040
+
+lanin@csd2.nyu.edu
+...!cmcl2!csd2!lanin
diff --git a/ARTICLE b/ARTICLE
new file mode 100644 (file)
index 0000000..064f4a0
--- /dev/null
+++ b/ARTICLE
@@ -0,0 +1,82 @@
+From ibmbin-request@crdgw1.crd.ge.com Wed Aug 22 16:41:24 1990
+From: ibmbin-request@crdgw1.crd.ge.com
+Newsgroups: comp.binaries.ibm.pc
+Subject: v07i204: mmv10src, source for mmv10 file mover (part 01/02)
+Summary: Move files
+Date: 21 Aug 90 05:07:19 GMT
+Followup-To: comp.binaries.ibm.pc.d
+X-Submissions-to: ibmbin@crdgw1.crd.ge.com
+X-Questions-to: ibmbin-request@crdgw1.crd.ge.com
+
+Checksum: 3252004976  (Verify with "brik -cv")
+Posting-number: Volume 07, Issue 204
+Submitted-by: lanin@csd4.cs.nyu.edu
+Archive-name: mmv10src/part01
+
+This is the source for mmv10.
+
+
+[
+Checksums obtained with the 4.3BSD "sum" or System V "sum -r" command.
+
+checksum     size (bytes)  file (between BEGIN--cut and END--cut lines)
+   65341        28171      part01
+    8782        27866      part02
+
+checksum     size (bytes)  file
+   53647        40648      mmv10src.zoo
+
+-- bill
+]
+
+mmv10src part01/02
+BEGIN--cut here--cut here
+END--cut here--cut here
+
+From ibmbin-request@crdgw1.crd.ge.com Wed Aug 22 16:41:30 1990
+From: ibmbin-request@crdgw1.crd.ge.com
+Newsgroups: comp.binaries.ibm.pc
+Subject: v07i205: mmv10src, source for mmv10 file mover (part 02/02)
+Summary: Move files
+Date: 21 Aug 90 05:07:27 GMT
+Followup-To: comp.binaries.ibm.pc.d
+X-Submissions-to: ibmbin@crdgw1.crd.ge.com
+X-Questions-to: ibmbin-request@crdgw1.crd.ge.com
+
+Checksum:  550153189  (Verify with "brik -cv")
+Posting-number: Volume 07, Issue 205
+Submitted-by: lanin@csd4.cs.nyu.edu
+Archive-name: mmv10src/part02
+
+mmv10src part02/02
+BEGIN--cut here--cut here
+END--cut here--cut here
+
+
+From murthy@algron.cs.cornell.edu Mon Jun 18 15:35:14 1990
+From: murthy@algron.cs.cornell.edu (Chet Murthy)
+Newsgroups: comp.sources.bugs
+Subject: MMV patch HIGH priority
+Date: 9 Jun 90 21:03:03 GMT
+Distribution: comp
+Organization: Cornell Univ. CS Dept. Ithaca NY
+Status: RO
+
+I'm posting this for Vladimir Lanin:
+
+The following patch fixes a number of bugs in mmv 1.0
+(as distributed on comp.sources.unix)
+and provides V7 compatibility.
+The new version is known as mmv 1.01b.
+
+Since one of the bugs can result in the unintended DELETION
+of the source file, it is imperative that the old version
+be replaced with the new one.
+
+The two major bugs fixed are:
+1) in its BSD reincarnation, whenever a file had to be moved between devices
+(by copying, then deleting), mmv 1.0 made a symbolic link to "/" instead
+of copying the file. It still deleted the original, though. Oops!
+2) the uppercase/lowercase conversion feature did not work.
+
+Vladimir Lanin
diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..84d6a73
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,24 @@
+# Possible defines in CONF:
+#      IS_MSDOS IS_SYSV IS_V7 IS_BSD HAS_DIRENT HAS_RENAME MV_DIR
+
+CC             =gcc -traditional
+LD             =$(CC)
+CONF           =-DIS_SYSV -DHAS_DIRENT -DHAS_RENAME
+CFLAGS         =-O2 -m486 $(CONF)
+LDFLAGS                =-s -N
+
+#IBIN          =$(LOCAL)$(ARCH)/bin
+#IMAN          =$(LOCAL)$(ANY)/man
+IBIN=$(DESTDIR)/usr/bin/
+IMAN=$(DESTDIR)/usr/man/
+
+mmv:           mmv.o
+
+clean:
+       rm -f mmv mmv.o
+
+install:       $(DEST)$(IBIN)/mmv
+install:       $(DEST)$(IMAN)/man1/mmv.1
+
+$(DEST)$(IBIN)/mmv:            mmv;    cp $? $@
+$(DEST)$(IMAN)/man1/mmv.1:     mmv.1;  cp $? $@
diff --git a/READ.ME b/READ.ME
new file mode 100644 (file)
index 0000000..d7c151d
--- /dev/null
+++ b/READ.ME
@@ -0,0 +1,15 @@
+Files:
+
+mmv.exe                --      The mmv program, set for -x default and slow id method.
+
+mmvpatch.exe   --      Patches default task option and dir id method into mmv.
+
+mmv.man                --      Printable documentation. Edit to remove _^H's to
+                       display on the screen.
+
+mmv.c          --      Source for mmv, compatible with Turbo C 1.5.
+                       Compile in compact model with MSDOS defined.
+
+mmvpatch.c     --      Source for mmvpatch. Compile in small model.
+
+mmv.1          --      Source for mmv.man. Use nroff -man -rO2.
diff --git a/mmv.1 b/mmv.1
new file mode 100644 (file)
index 0000000..e23c5ae
--- /dev/null
+++ b/mmv.1
@@ -0,0 +1,657 @@
+.\" Under BSD, just give to nroff or troff (with -man).
+.\" To print the MS-DOS version, use option -rO2.
+.\" Under System V, take out the '.\"  ' from the next line.
+.\" .nr O 1
+.TH MMV 1 "November 20, 1989 (v1.0)"
+.ie !'\nO'2' \{\
+.SH NAME
+mmv \- move/copy/append/link multiple files by wildcard patterns
+\}
+.el \{
+.SH NAME
+mmv \- move/copy/append multiple files by wildcard patterns
+\}
+.ie '\nO'2' \{\
+.ds SL \\\\
+.ds ES '
+\}
+.el \{\
+.ds SL /
+.ds ES \\\\
+\}
+.SH SYNOPSIS
+.B mmv
+.if '\nO'2' [\fB-m\fP|\fBx\fP|\fBr\fP|\fBc\fP|\fBo\fP|\fBa\fP|\fBz\fP]
+.if '\nO'0' [\fB-m\fP|\fBx\fP|\fBr\fP|\fBc\fP|\fBo\fP|\fBa\fP|\fBl\fP|\fBs\fP]
+.if '\nO'1' [\fB-m\fP|\fBx\fP|\fBr\fP|\fBc\fP|\fBo\fP|\fBa\fP|\fBl\fP]
+[\fB-h\fP]
+[\fB-d\fP|\fBp\fP]
+[\fB-g\fP|\fBt\fP]
+[\fB-v\fP|\fBn\fP]
+[\fBfrom to\fP]
+.if '\nO'2' \{\
+.br
+.B mmvpatch
+[\fBexecutable\fP]
+\}
+.SH "DESCRIPTION"
+.I Mmv
+moves (or copies,
+.ie '\nO'2' or appends,
+.el appends, or links,
+as specified)
+each source file matching a
+.I from
+pattern to the target name specified by the
+.I to
+pattern.
+This multiple action is performed safely,
+i.e. without any unexpected deletion of files
+due to collisions of target names with existing filenames
+or with other target names.
+Furthermore, before doing anything,
+.I mmv
+attempts to detect any errors that would result
+from the entire set of actions specified
+and gives the user the choice of either
+proceeding by avoiding the offending parts
+or aborting.
+
+.ce
+The Task Options
+.PP
+Whether
+.I mmv
+moves, copies,
+.ie '\nO'2' or appends
+.el appends, or links
+is governed by the first set of options given
+above.
+If none of these are specified,
+.ie '\nO'2' \{\
+a default (patchable by
+.IR mmvpatch ,
+and initially -x)
+determines the task.
+\}
+.el \{\
+the task is given by the command name under which
+.I mmv
+was invoked (argv[0]):
+
+       command name    default task
+
+       mmv                     -x
+.br
+       mcp                     -c
+.br
+       mad                     -a
+.br
+       mln                     -l
+\}
+.PP
+The task option choices are:
+.TP
+-m :
+move source file to target name.
+Both must be on the same device.
+Will not move directories.
+.if '\nO'0' \{\
+If the source file is a symbolic link,
+moves the link without checking if the link's target from the new
+directory is different than the old.
+\}
+.TP
+-x :
+same as -m, except cross-device moves are done
+by copying, then deleting source.
+When copying, sets the
+.ie !'\nO'2' permission bits
+.el attributes
+and file modification time
+of the target file to that of the source file.
+.TP
+-r :
+rename source file or directory to target name.
+The target name must not include a path:
+the file remains in the same directory in all cases.
+This option is the only way of renaming directories under
+.IR mmv .
+.if '\nO'2' It is only available under DOS version 3.0 or higher.
+.TP
+-c :
+copy source file to target name.
+Sets the file modification time and
+.ie !'\nO'2' permission bits
+.el attributes
+of the target file to that of the source file,
+regardless of whether the target file already exists.
+Chains and cycles (to be explained below) are not allowed.
+.TP
+-o :
+overwrite target name with source file.
+.ie '\nO'2' \{\
+If target file exists, its attributes are left unchanged.
+If not, it is created with ordinary attributes
+unrelated to the source file's attributes.
+In either case, the file modification time is set to the current time.
+\}
+.el \{\
+If target file exists, it is overwritten,
+keeping its original owner and permission bits.
+If it does not exist, it is created, with read-write permission bits
+set according to
+.IR umask (1),
+and the execute permission bits copied from the source file.
+In either case, the file modification time is set to the current time.
+\}
+.TP
+-a :
+append contents of source file to target name.
+Target file modification time is set to the current time.
+If target file does not exist,
+it is created with
+.ie '\nO'2' attributes
+.el permission bits
+set as under -o.
+Unlike all other options, -a allows multiple source files to have the
+same target name, e.g. "mmv -a
+.ie '\nO'2' *.c
+.el \\*.c
+big" will append all ".c" files to "big".
+Chains and cycles are also allowed, so "mmv -a f f" will double up "f".
+.ie '\nO'2' \{\
+.TP
+-z :
+same as -a, but if the target file exists, and its last character is a ^Z,
+and the source file is not empty,
+this ^Z is truncated before doing the append.
+\}
+.el \{\
+.TP
+-l :
+link target name to source file.
+Both must be on the same device,
+and the source must not be a directory.
+Chains and cycles are not allowed.
+.if '\nO'0' \{\
+.TP
+-s :
+same as -l, but use symbolic links instead of hard links.
+For the resulting link to aim back at the source,
+either the source name must begin with a '/',
+or the target must reside in either the current or the source directory.
+If none of these conditions are met, the link is refused.
+However, source and target can reside on different devices,
+and the source can be a directory.
+\}
+\}
+.PP
+Only one of these option may be given,
+and it applies to all matching files.
+Remaining options need not be given separately,
+i.e. "mmv -mk" is allowed.
+
+.ce
+Multiple Pattern Pairs
+.PP
+Multiple
+.I from
+--
+.I to
+pattern pairs may be specified by omitting
+the pattern pair on the command line,
+and entering them on the standard input,
+one pair per line.
+(If a pattern pair is given on the command line,
+the standard input is not read.)
+Thus,
+
+.in +3
+mmv
+.br
+a b
+.br
+c d
+.in -3
+
+would rename "a" to "b" and "c" to "d".
+If a file can be matched to several of the given
+.I from
+patterns,
+the
+.I to
+pattern of the first matching pair is used.
+Thus,
+
+.in +3
+mmv
+.br
+a b
+.br
+a c
+.in -3
+
+would give the error message "a -> c : no match" because file "a"
+(even if it exists)
+was already matched by the first pattern pair.
+
+.ce
+The \fIFrom\fP Pattern
+.PP
+The
+.I from
+pattern is a filename
+with embedded wildcards: '*', '?', '['...']',
+.if '\nO'2' \{\
+\'!',
+\}
+and ';'.
+The first three have their usual
+.IR sh (1)
+meanings of, respectively,
+matching any string of characters,
+matching any single character,
+and matching any one of a set of characters.
+.PP
+Between the '[' and ']', a range from character 'a' through character 'z'
+is specified with "a-z".
+The set of matching characters can be negated by inserting
+a '^' after the '['.
+Thus, "[^b-e2-5_]"
+will match any character but 'b' through 'e', '2' through '5', and '_'.
+.if '\nO'2' \{\
+.PP
+Unlike DOS wildcards,
+all mmv wildcards (except for cases listed below)
+can occur anywhere in the pattern,
+whether preceding or following explicit characters or other wildcards.
+For example, the pattern "*z\\foo.bar" will search
+for files named "foo.bar" in all subdirectories whose names end in 'z'.
+However, no wildcards can occur in the drive letter.
+.PP
+The character '.' is not matched by any of '*', '?', or '['...']'.
+Thus, the pattern "*" will only match files with a null extension.
+To save yourself some typing, use the '!' wildcard instead,
+which matches the same as "*.*",
+except it is assigned only one wildcard index (see below).
+Thus, both "f!" and "f*.*"
+will match all of "f", "f.ext", "foo", and "foo.ext",
+while "f*" will match only the first and the third.
+\}
+.PP
+Note that paths are allowed in the patterns,
+and wildcards may be intermingled with slashes arbitrarily.
+The ';' wildcard
+is useful for matching files at any depth in the directory tree.
+It matches the same as "*\*(SL" repeated any number of times, including zero,
+and can only occur either at the beginning of the pattern
+or following a '\*(SL'.
+Thus ";*.c" will match all ".c" files in or below the current directory,
+while "\*(SL;*.c" will match them anywhere on the file system.
+.if !'\nO'2' \{\
+.PP
+In addition, if the
+.I from
+pattern
+(or the
+.I to
+pattern)
+begins with "~/", the '~' is replaced with the home directory name.
+(Note that the "~user" feature of
+.IR csh (1)
+is not implemented.)
+However, the '~' is not treated as a wildcard,
+in the sense that it is not assigned a wildcard index (see below).
+\}
+.PP
+Since matching a directory under a task option other than -r or -s
+would result in an error,
+tasks other than -r and -s
+match directories only against completely explicit
+.I from
+patterns (i.e. not containing wildcards).
+Under -r and -s, this applies only to "." and "..".
+.PP
+.ie '\nO'2' \{\
+Hidden and system files are also only matched
+against completely explicit
+.I from
+patterns.
+\}
+.el \{\
+Files beginning with '.' are only matched against
+.I from
+patterns that begin with an explicit '.'.
+\}
+However, if -h is specified, they are matched normally.
+.if !'\nO'2' \{\
+.PP
+Warning: since the shell normally expands wildcards
+before passing the command-line arguments to
+.IR mmv ,
+it is usually necessary to enclose the command-line
+.I from
+pattern
+in quotes.
+\}
+
+.ce
+The \fITo\fP Pattern
+.PP
+The
+.I to
+pattern is a filename
+with embedded
+.I wildcard
+.IR indexes ,
+where an index consists of the character '#'
+followed by a string of digits.
+When a source file matches a
+.I from
+pattern,
+a target name for the file is constructed out of the
+.I to
+pattern by
+replacing the wildcard indexes by the
+actual characters that matched the referenced wildcards
+in the source name.
+Thus, if the
+.I from
+pattern is "abc*.*" and the
+.I to
+pattern is "xyz#2.#1",
+then "abc.txt" is targeted to "xyztxt.".
+(The first '*' matched "", and the second matched "txt".)
+Similarly, for the pattern pair ";*.[clp]" -> "#1#3\*(SL#2",
+"foo1\*(SLfoo2\*(SLprog.c" is targeted to "foo1\*(SLfoo2\*(SLc\*(SLprog".
+Note that there is no '\*(SL' following the "#1" in the
+.I to
+pattern,
+since the string matched by any ';' is always either empty
+or ends in a '\*(SL'.
+In this case, it matches "foo1\*(SLfoo2\*(SL".
+.if !'\nO'2' \{\
+.PP
+To convert the string matched by a wildcard
+to either lowercase or uppercase before embedding it in the target name,
+insert 'l' or 'u', respectively,
+between the '#' and the string of digits.
+.PP
+The
+.I to
+pattern,
+like the
+.I from
+pattern,
+can begin with a "~/" (see above).
+This does not necessitate enclosing the
+.I to
+pattern in quotes on the command line
+since
+.IR csh (1)
+expands the '~' in the exact same manner as
+.I mmv
+(or, in the case of
+.IR sh (1),
+does not expand it at all).
+\}
+.PP
+For all task options other than -r, if the target name is a directory,
+the real target name is formed by appending
+a '\*(SL' followed by the last component
+of the source file name.
+For example, "mmv dir1\*(SLa dir2" will,
+if "dir2" is indeed a directory, actually move "dir1\*(SLa" to "dir2\*(SLa".
+However, if "dir2\*(SLa" already exists and is itself a directory,
+this is considered an error.
+.PP
+To strip any character (e.g. '*', '?', or '#')
+of its special meaning to
+.IR mmv ,
+as when the actual replacement name must contain the character '#',
+precede the special character with a
+.ie '\nO'2' \{\
+single quote (').
+\}
+.el \{\
+\'\\'
+(and enclose the argument in quotes because of the shell).
+\}
+This also works to terminate a wildcard index
+when it has to be followed by a digit in the filename, e.g. "a#1\*(ES1".
+
+.ce
+Chains and Cycles
+.PP
+A chain is a sequence of specified actions where the target name of
+one action refers to the source file of another action.
+For example,
+
+mmv
+.br
+a b
+.br
+b c
+
+specifies the chain "a" -> "b" -> "c".
+A cycle is a chain where the last target name
+refers back to the first source file,
+e.g. "mmv a a".
+.I Mmv
+detects chains and cycles regardless of the order in which
+their constituent actions are actually given.
+Where allowed, i.e. in moving, renaming, and appending files,
+chains and cycles are handled gracefully, by performing them in the proper
+order.
+Cycles are broken by first renaming one of the files to a temporary name
+(or just remembering its original size when doing appends).
+
+.ce
+Collisions and Deletions
+.PP
+When any two or more matching files
+would have to be
+.ie '\nO'2' moved or copied
+.el moved, copied, or linked
+to the same target filename,
+.I mmv
+detects the condition as an error before performing any actions.
+Furthermore,
+.I mmv
+checks if any of its actions will result
+in the destruction of existing files.
+If the -d (delete) option is specified,
+all file deletions or overwrites are done silently.
+Under -p (protect), all deletions or overwrites
+(except those specified with "(*)" on the standard input, see below)
+are treated as errors.
+And if neither option is specified,
+the user is queried about each deletion or overwrite separately.
+(A new stream to
+.ie '\nO'2' "\\dev\\con"
+.el "/dev/tty"
+is used for all interactive queries,
+not the standard input.)
+
+.ce
+Error Handling
+.PP
+Whenever any error in the user's action specifications is detected,
+an error message is given on the standard output,
+and
+.I mmv
+proceeds to check the rest of the specified actions.
+Once all errors are detected,
+.I mmv
+queries the user whether he wishes
+to continue by avoiding the erroneous actions or to abort altogether.
+This and all other queries may be avoided by specifying either the
+-g (go) or -t (terminate) option.
+The former will resolve all difficulties by avoiding the erroneous actions;
+the latter will abort
+.I mmv
+if any errors are detected.
+Specifying either of them defaults
+.I mmv
+to -p, unless -d is specified
+(see above).
+Thus, -g and -t are most useful when running
+.I mmv
+in the background or in
+a shell script,
+when interactive queries are undesirable.
+
+.ce
+Reports
+.PP
+Once the actions to be performed are determined,
+.I mmv
+performs them silently,
+unless either the -v (verbose) or -n (no-execute) option is specified.
+The former causes
+.I mmv
+to report each performed action
+on the standard output as
+
+a -> b : done.
+
+Here, "a" and "b" would be replaced by the source and target names,
+respectively.
+If the action deletes the old target,
+a "(*)" is inserted after the the target name.
+Also, the "->" symbol is modified when a cycle has to be broken:
+the '>' is changed to a '^' on the action prior to which the old target
+is renamed to a temporary,
+and the '-' is changed to a '=' on the action where the temporary is used.
+.PP
+Under -n, none of the actions are performed,
+but messages like the above are printed on the standard output
+with the ": done." omitted.
+.PP
+The output generated by -n can (after editing, if desired)
+be fed back to
+.I mmv
+on the standard input
+(by omitting the
+.I from
+--
+.I to
+pair on the
+.I mmv
+command line).
+To facilitate this,
+.I mmv
+ignores lines on the standard input that look
+like its own error and "done" messages,
+as well as all lines beginning with white space,
+and will accept pattern pairs with or without the intervening "->"
+(or "-^", "=>", or "=^").
+Lines with "(*)" after the target pattern have the effect of enabling -d
+for the files matching this pattern only,
+so that such deletions are done silently.
+When feeding
+.I mmv
+its own output,
+one must remember to specify again the task option (if any)
+originally used to generate it.
+.PP
+Although
+.I mmv
+attempts to predict all mishaps prior to performing any specified actions,
+accidents may happen.
+For example,
+.I mmv
+does not check for adequate free space when copying.
+Thus, despite all efforts,
+it is still possible for an action to fail
+after some others have already been done.
+To make recovery as easy as possible,
+.I mmv
+reports which actions have already been done and
+which are still to be performed
+after such a failure occurs.
+It then aborts, not attempting to do anything else.
+Once the user has cleared up the problem,
+he can feed this report back to
+.I mmv
+on the standard input
+to have it complete the task.
+(The user is queried for a file name to dump this report
+if the standard output has not been redirected.)
+.if '\nO'2' \{\
+
+.ce
+\fIMmvpatch\fP
+.PP
+You can customize a copy of
+.I mmv
+via the
+.I mmvpatch
+utility.
+If you wish to change the default task option,
+run
+.I mmvpatch
+on a copy of
+.I mmv
+named as follows:
+
+       -x, -m, -r              mmv.exe
+.br
+       -c, -o                  mcp.exe
+.br
+       -a, -z                  mad.exe
+.PP
+.I Mmvpatch
+also determines the best way to uniquely identify directories.
+As distributed,
+.I mmv
+is set to use a method that is guaranteed to work the same way
+for all versions of DOS,
+but is both slow
+and unable to correctly handle drives
+affected by the
+.I join
+and
+.I subst
+DOS commands.
+Alternatively,
+there is a method that is fast and correct,
+but uses an undocumented DOS feature
+that may not work properly under all versions of DOS.
+(However, 2.0 and 3.3 are known to work.)
+.I Mmv
+does
+.I not
+determine the best method to use on your system
+at run-time since this is too slow.
+The choice is left to
+.I mmvpatch,
+which determines if the fast method works,
+but also allows you to return to the slow method.
+\}
+.SH "EXIT STATUS"
+.I Mmv
+exits with status 1 if it aborts before doing anything,
+with status 2 if it aborts due to failure after completing some of the actions,
+and with status 0 otherwise.
+.if !'\nO'2' \{\
+.SH "SEE ALSO"
+mv(1), cp(1), ln(1), umask(1)
+\}
+.SH "AUTHOR"
+Vladimir Lanin
+.br
+lanin@csd2.nyu.edu
+.SH "BUGS"
+.if !'\nO'2' \{\
+If the search pattern is not quoted,
+the shell expands the wildcards.
+.I Mmv
+then (usually) gives some error message,
+but can not determine that the lack of quotes is the cause.
+.PP
+\}\
+To avoid difficulties in semantics and error checking,
+.I mmv
+refuses to move or create directories.
diff --git a/mmv.c b/mmv.c
new file mode 100644 (file)
index 0000000..2e72a73
--- /dev/null
+++ b/mmv.c
@@ -0,0 +1,2977 @@
+/*
+       mmv 1.01b
+       Copyright (c) 1990 Vladimir Lanin.
+       This program may be freely used and copied on a non-commercial basis.
+
+       Author may be reached at:
+
+       lanin@csd2.nyu.edu
+
+       Vladimir Lanin
+       330 Wadsworth Ave, Apt 6F,
+       New York, NY 10040
+
+       Many thanks to those who have to contributed to the design
+       and/or coding of this program:
+
+       Tom Albrecht:   initial Sys V adaptation, consultation, and testing
+       Carl Mascott:   V7 adaptation
+       Mark Lewis:     -n flag idea, consultation.
+       Dave Bernhold:  upper/lowercase conversion idea.
+       Paul Stodghill: copy option, argv[0] checking.
+       Frank Fiamingo: consultation and testing.
+       Tom Jordahl:    bug reports and testing.
+       John Lukas, Hugh Redelmeyer, Barry Nelson, John Sauter,
+       Phil Dench, John Nelson:
+                       bug reports.
+*/
+
+/*
+       Define SYSV to compile under System V.
+       Define both SYSV and V7 to compile under V7.
+       If your System V has a rename() call, define RENAME.
+       Otherwise, mmv will only be able to rename directories (via option -r)
+       when running as the super-user.
+       There is no reason to set the suid bit on mmv if rename() is available.
+       It is important that mmv not be run with effective uid set
+       to any value other than either the real uid or the super-user.
+       Even when running with effective uid set to super-user,
+       mmv will only perform actions permitted to the real uid.
+
+       Define MSDOS to compile under MS-D*S Turbo C 1.5.
+       If you prefer mmv's output to use /'s instead of \'s under MS-D*S,
+       define SLASH.
+
+       When neither MSDOS nor SYSV are defined, compiles under BSD.
+
+       RENAME is automatically defined under MSDOS and BSD.
+
+       If you are running a (UN*X) system that provides the
+       "struct dirent" readdir() directory reading standard,
+       define DIRENT. Otherwise, mmv uses the BSD-like
+       "struct direct" readdir().
+       If your (UN*X) system has neither of these, get the "dirent"
+       by Doug Gwyn, available as gwyn-dir-lib in volume 9
+       of the comp.sources.unix archives.
+*/
+
+static char USAGE[] =
+#ifdef IS_MSDOS
+
+"Usage: \
+%s [-m|x%s|c|o|a|z] [-h] [-d|p] [-g|t] [-v|n] [from to]\n\
+\n\
+Use #N in the ``to'' pattern to get the string matched\n\
+by the N'th ``from'' pattern wildcard.\n";
+
+#define OTHEROPT (_osmajor < 3 ? "" : "|r")
+
+#else
+
+"Usage: \
+%s [-m|x|r|c|o|a|l%s] [-h] [-d|p] [-g|t] [-v|n] [from to]\n\
+\n\
+Use #[l|u]N in the ``to'' pattern to get the [lowercase|uppercase of the]\n\
+string matched by the N'th ``from'' pattern wildcard.\n\
+\n\
+A ``from'' pattern containing wildcards should be quoted when given\n\
+on the command line.\n";
+
+#ifdef IS_SYSV
+#define OTHEROPT ""
+#else
+#define OTHEROPT "|s"
+#endif
+
+#endif
+
+#include <stdio.h>
+#include <ctype.h>
+
+#ifdef IS_MSDOS
+/* for MS-DOS (under Turbo C 1.5)*/
+
+#include <string.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <dos.h>
+#include <dir.h>
+#include <io.h>
+#include <fcntl.h>
+
+#define ESC '\''
+#ifdef SLASH
+#define SLASH '\\'
+#define OTHERSLASH '/'
+#else
+#define SLASH '/'
+#define OTHERSLASH '\\'
+#endif
+
+typedef int DIRID;
+typedef int DEVID;
+
+static char TTY[] = "/dev/con";
+extern unsigned _stklen = 10000;
+
+#undef HAS_RENAME
+#define HAS_RENAME 1
+
+#else
+/* for various flavors of UN*X */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/file.h>
+
+extern char *getenv();
+extern long lseek();
+extern char *malloc();
+
+#ifdef HAS_DIRENT
+#include <dirent.h>
+typedef struct dirent DIRENTRY;
+#else
+#ifdef IS_SYSV
+#include <sys/dir.h>
+/* might need to be changed to <dir.h> */
+#else
+#include <sys/dir.h>
+#endif
+typedef struct direct DIRENTRY;
+#endif
+
+#ifndef __STDC__
+#ifndef __GNUC__
+#ifndef IS_SYSV
+#ifndef IS_BSD
+#define void char      /* might want to remove this line */
+#endif
+#endif
+#endif
+#endif
+
+#ifndef O_BINARY
+#define O_BINARY 0
+#endif
+#ifndef R_OK
+#define R_OK 4
+#define W_OK 2
+#define X_OK 1
+#endif
+
+#define ESC '\\'
+#define SLASH '/'
+
+typedef ino_t DIRID;
+typedef dev_t DEVID;
+
+#define MAXPATH 1024
+
+static char TTY[] = "/dev/tty";
+
+#ifdef IS_V7
+/* for Version 7 */
+#include <errno.h>
+extern int errno;
+#define strchr index
+extern char *strcpy(), *strchr();
+#include <signal.h>
+#define O_RDONLY 0
+#define O_WRONLY 1
+#define O_RDWR   2
+
+#else
+/* for System V and BSD */
+#include <string.h>
+#include <signal.h>
+#include <fcntl.h>
+#endif
+
+#ifdef IS_SYSV
+
+/* for System V and Version 7*/
+struct utimbuf {
+       time_t actime;
+       time_t modtime;
+};
+#define utimes(f, t) utime((f), &(t))
+
+#ifndef HAS_RENAME
+#ifndef MV_DIR
+# define MV_DIR "/usr/lib/mv_dir"
+#endif
+#endif
+
+#ifdef MV_DIR
+# define HAS_RENAME
+#endif
+
+#else
+
+/* for BSD */
+#undef HAS_RENAME
+#define HAS_RENAME 1
+#include <sys/time.h>
+
+#endif
+#endif
+
+#define mylower(c) (isupper(c) ? (c)-'A'+'a' : (c))
+#define myupper(c) (islower(c) ? (c)-'a'+'A' : (c))
+#define STRLEN(s) (sizeof(s) - 1)
+#define mydup(s) (strcpy((char *)challoc(strlen(s) + 1, 0), (s)))
+
+
+#define DFLT 0x001
+#define NORMCOPY 0x002
+#define OVERWRITE 0x004
+#define NORMMOVE 0x008
+#define XMOVE 0x010
+#define DIRMOVE 0x020
+#define NORMAPPEND 0x040
+#define ZAPPEND 0x080
+#define HARDLINK 0x100
+#define SYMLINK 0x200
+
+#define COPY (NORMCOPY | OVERWRITE)
+#define MOVE (NORMMOVE | XMOVE | DIRMOVE)
+#define APPEND (NORMAPPEND | ZAPPEND)
+#define LINK (HARDLINK | SYMLINK)
+
+static char MOVENAME[] = "mmv";
+static char COPYNAME[] = "mcp";
+static char APPENDNAME[] = "mad";
+static char LINKNAME[] = "mln";
+
+#define ASKDEL 0
+#define ALLDEL 1
+#define NODEL 2
+
+#define ASKBAD 0
+#define SKIPBAD 1
+#define ABORTBAD 2
+
+#define STAY 0
+#define LOWER 1
+#define UPPER 2
+
+#define MAXWILD 20
+#define MAXPATLEN MAXPATH
+#define INITROOM 10
+#define CHUNKSIZE 2048
+#define BUFSIZE 4096
+
+#define FI_STTAKEN 0x01
+#define FI_LINKERR 0x02
+#define FI_INSTICKY 0x04
+#define FI_NODEL 0x08
+#define FI_KNOWWRITE 0x010
+#define FI_CANWRITE 0x20
+#define FI_ISDIR 0x40
+#define FI_ISLNK 0x80
+
+typedef struct {
+       char *fi_name;
+       struct rep *fi_rep;
+#ifdef IS_MSDOS
+       char fi_attrib;
+#else
+       short fi_mode;
+       char fi_stflags;
+#endif
+} FILEINFO;
+
+#define DI_KNOWWRITE 0x01
+#define DI_CANWRITE 0x02
+#define DI_CLEANED 0x04
+
+typedef struct {
+       DEVID di_vid;
+       DIRID di_did;
+       unsigned di_nfils;
+       FILEINFO **di_fils;
+       char di_flags;
+} DIRINFO;
+
+#define H_NODIR 1
+#define H_NOREADDIR 2
+
+typedef struct {
+       char *h_name;
+       DIRINFO *h_di;
+       char h_err;
+} HANDLE;
+
+#define R_ISX 0x01
+#define R_SKIP 0x02
+#define R_DELOK 0x04
+#define R_ISALIASED 0x08
+#define R_ISCYCLE 0x10
+#define R_ONEDIRLINK 0x20
+
+typedef struct rep {
+       HANDLE *r_hfrom;
+       FILEINFO *r_ffrom;
+       HANDLE *r_hto;
+       char *r_nto;                    /* non-path part of new name */
+       FILEINFO *r_fdel;
+       struct rep *r_first;
+       struct rep *r_thendo;
+       struct rep *r_next;
+       char r_flags;
+} REP;
+
+typedef struct {
+       REP *rd_p;
+       DIRINFO *rd_dto;
+       char *rd_nto;
+       unsigned rd_i;
+} REPDICT;
+
+typedef struct chunk {
+       struct chunk *ch_next;
+       unsigned ch_len;
+} CHUNK;
+
+typedef struct {
+       CHUNK *sl_first;
+       char *sl_unused;
+       int sl_len;
+} SLICER;
+
+
+static void init(/* */);
+static void procargs(/* int argc, char **argv,
+       char **pfrompat, char **ptopat */);
+static void domatch(/* char *cfrom, char *cto */);
+static int getpat(/* */);
+static int getword(/* char *buf */);
+static void matchpat(/*  */);
+static int parsepat(/*  */);
+static int dostage(/* char *lastend, char *pathend,
+       char **start1, int *len1, int stage, int anylev */);
+static int trymatch(/* FILEINFO *ffrom, char *pat */);
+static int keepmatch(/* FILEINFO *ffrom, char *pathend,
+       int *pk, int needslash, int dirs, int fils */);
+static int badrep(/* HANDLE *hfrom, FILEINFO *ffrom,
+       HANDLE **phto, char **pnto, FILEINFO **pfdel, int *pflags */);
+static int checkto(/* HANDLE *hfrom, char *f,
+       HANDLE **phto, char **pnto, FILEINFO **pfdel */);
+static char *getpath(/* char *tpath */);
+static int badname(/* char *s */);
+static FILEINFO *fsearch(/* char *s, DIRINFO *d */);
+static int ffirst(/* char *s, int n, DIRINFO *d */);
+static HANDLE *checkdir(/* char *p, char *pathend, int which */);
+static void takedir(/*
+       char *p, DIRINFO *di, int sticky
+or
+       struct ffblk *pff, DIRINFO *di
+*/);
+static int fcmp(/* FILEINFO **pf1, FILEINFO **pf2 */);
+static HANDLE *hadd(/* char *n */);
+static int hsearch(/* char *n, int which, HANDLE **ph */);
+static DIRINFO *dadd(/* DEVID v, DIRID d */);
+static DIRINFO *dsearch(/* DEVID v, DIRID d */);
+static int match(/* char *pat, char *s, char **start1, int *len1 */);
+static void makerep(/*  */);
+static void checkcollisions(/*  */);
+static int rdcmp(/* REPDICT *rd1, REPDICT *rd2 */);
+static void findorder(/*  */);
+static void scandeletes(/* int (*pkilldel)(REP *p) */);
+static int baddel(/* REP *p */);
+static int skipdel(/* REP *p */);
+static void nochains(/*  */);
+static void printchain(/* REP *p */);
+static void goonordie(/*  */);
+static void doreps(/*  */);
+static long appendalias(/* REP *first, REP *p, int *pprintaliased */);
+static int movealias(/* REP *first, REP *p, int *pprintaliased */);
+static int snap(/* REP *first, REP *p */);
+static void showdone(/* REP *fin */);
+static void breakout(/*  */);
+static int breakrep(/* */);
+static void breakstat(/* */);
+static void quit(/*  */);
+static int copymove(/* REP *p */);
+static int copy(/* FILENFO *f, long len */);
+static int myunlink(/* char *n, FILEINFO *f */);
+static int getreply(/* char *m, int failact */);
+static void *myalloc(/* unsigned k */);
+static void *challoc(/* int k, int which */);
+static void chgive(/* void *p, unsigned k */);
+static int mygetc(/* */);
+static char *mygets(/* char *s, int l */);
+#ifdef IS_MSDOS
+static int leave(/*  */);
+static void cleanup(/*  */);
+#else
+static int getstat(/* char *full, FILEINFO *f */);
+static int dwritable(/* HANDLE *h */);
+static int fwritable(/* char *hname, FILEINFO *f */);
+#ifndef __STDC__
+#ifndef IS_MSDOS
+#ifndef IS_SYSV
+static void memmove(/* void *to, void *from, int k */);
+#endif
+#endif
+#endif
+#endif
+#ifndef HAS_RENAME
+static int rename(/* char *from, char *to */);
+#endif
+
+static int op, badstyle, delstyle, verbose, noex, matchall;
+static int patflags;
+
+static unsigned ndirs = 0, dirroom;
+static DIRINFO **dirs;
+static unsigned nhandles = 0, handleroom;
+static HANDLE **handles;
+static HANDLE badhandle = {"\200", NULL, 0};
+static HANDLE *(lasthandle[2]) = {&badhandle, &badhandle};
+static unsigned nreps = 0;
+static REP hrep, *lastrep = &hrep;
+static CHUNK *freechunks = NULL;
+static SLICER slicer[2] = {{NULL, NULL, 0}, {NULL, NULL, 0}};
+
+static int badreps = 0, paterr = 0, direrr, failed = 0, gotsig = 0, repbad;
+static FILE *outfile = stdout;
+
+static char IDF[] = "$$mmvdid.";
+static char TEMP[] = "$$mmvtmp.";
+static char TOOLONG[] = "(too long)";
+static char EMPTY[] = "(empty)";
+
+static char SLASHSTR[] = {SLASH, '\0'};
+
+static char PATLONG[] = "%.40s... : pattern too long.\n";
+
+char from[MAXPATLEN], to[MAXPATLEN];
+static int fromlen, tolen;
+static char *(stagel[MAXWILD]), *(firstwild[MAXWILD]), *(stager[MAXWILD]);
+static int nwilds[MAXWILD];
+static int nstages;
+char pathbuf[MAXPATH];
+char fullrep[MAXPATH + 1];
+static char *(start[MAXWILD]);
+static int len[MAXWILD];
+static char hasdot[MAXWILD];
+static REP mistake;
+#define MISTAKE (&mistake)
+
+#ifdef IS_MSDOS
+
+static int olddevflag, curdisk, maxdisk;
+static struct {
+       char ph_banner[30];
+       char ph_name[9];
+       int ph_dfltop;
+       int ph_safeid;
+       int ph_clustoff;
+       int ph_driveoff;
+       int ph_drivea;
+} patch = {"mmv 1.0 patchable flags", "mmv", XMOVE, 1, 0};
+
+#define DFLTOP (patch.ph_dfltop)
+#define CLUSTNO(pff) (*(int *)(((char *)(pff)) + patch.ph_clustoff))
+#define DRIVENO(pff) (*(((char *)(pff)) + patch.ph_driveoff) - patch.ph_drivea)
+
+
+#else
+
+#define DFLTOP XMOVE
+
+static char *home;
+static int homelen;
+static int uid, euid, oldumask;
+static DIRID cwdd = -1;
+static DEVID cwdv = -1;
+
+#endif
+
+
+int main(argc, argv)
+       int argc;
+       char *(argv[]);
+{
+       char *frompat, *topat;
+
+       init();
+       procargs(argc, argv, &frompat, &topat);
+       domatch(frompat, topat);
+       if (!(op & APPEND))
+               checkcollisions();
+       findorder();
+       if (op & (COPY | LINK))
+               nochains();
+       scandeletes(baddel);
+       goonordie();
+       if (!(op & APPEND) && delstyle == ASKDEL)
+               scandeletes(skipdel);
+       doreps();
+       return(failed ? 2 : nreps == 0 && (paterr || badreps));
+}
+
+
+static void init()
+{
+#ifdef IS_MSDOS
+       curdisk = getdisk();
+       maxdisk = setdisk(curdisk);
+/*
+       Read device availability : undocumented internal MS-DOS function.
+       If (_DX == 0) then \dev\ must precede device names.
+*/
+       bdos(0x37, 0, 2);
+       olddevflag = _DX;
+/*
+       Write device availability: undocumented internal MS-DOS function.
+       Specify \dev\ must precede device names.
+*/
+       bdos(0x37, 0, 3);
+       atexit((atexit_t)cleanup);
+       ctrlbrk((int (*)())breakout);
+#else
+       struct stat dstat;
+
+       if ((home = getenv("HOME")) == NULL || strcmp(home, SLASHSTR) == 0)
+               home = "";
+       if (!stat(".", &dstat)) {
+               cwdd = dstat.st_ino;
+               cwdv = dstat.st_dev;
+       }
+       oldumask = umask(0);
+       euid = geteuid();
+       uid = getuid();
+       signal(SIGINT, breakout);
+#endif
+
+       dirroom = handleroom = INITROOM;
+       dirs = (DIRINFO **)myalloc(dirroom * sizeof(DIRINFO *));
+       handles = (HANDLE **)myalloc(handleroom * sizeof(HANDLE *));
+       ndirs = nhandles = 0;
+}
+
+
+static void procargs(argc, argv, pfrompat, ptopat)
+       int argc;
+       char **argv;
+       char **pfrompat, **ptopat;
+{
+       char *p, c;
+       char *cmdname = argv[0];
+
+#ifdef IS_MSDOS
+#define CMDNAME (patch.ph_name)
+#else
+#define CMDNAME cmdname
+#endif
+
+       op = DFLT;
+       verbose = noex = matchall = 0;
+       delstyle = ASKDEL;
+       badstyle = ASKBAD;
+       for (argc--, argv++; argc > 0 && **argv == '-'; argc--, argv++)
+               for (p = *argv + 1; *p != '\0'; p++) {
+                       c = mylower(*p);
+                       if (c == 'v' && !noex)
+                               verbose = 1;
+                       else if (c == 'n' && !verbose)
+                               noex = 1;
+                       else if (c == 'h')
+                               matchall = 1;
+                       else if (c == 'd' && delstyle == ASKDEL)
+                               delstyle = ALLDEL;
+                       else if (c == 'p' && delstyle == ASKDEL)
+                               delstyle = NODEL;
+                       else if (c == 'g' && badstyle == ASKBAD)
+                               badstyle = SKIPBAD;
+                       else if (c == 't' && badstyle == ASKBAD)
+                               badstyle = ABORTBAD;
+                       else if (c == 'm' && op == DFLT)
+                               op = NORMMOVE;
+                       else if (c == 'x' && op == DFLT)
+                               op = XMOVE;
+                       else if (c == 'r' && op == DFLT)
+                               op = DIRMOVE;
+                       else if (c == 'c' && op == DFLT)
+                               op = NORMCOPY;
+                       else if (c == 'o' && op == DFLT)
+                               op = OVERWRITE;
+                       else if (c == 'a' && op == DFLT)
+                               op = NORMAPPEND;
+#ifdef IS_MSDOS
+                       else if (c == 'z' && op == DFLT)
+                               op = ZAPPEND;
+#else
+                       else if (c == 'l' && op == DFLT)
+                               op = HARDLINK;
+#ifdef S_IFLNK
+                       else if (c == 's' && op == DFLT)
+                               op = SYMLINK;
+#endif
+#endif
+                       else {
+                               fprintf(stderr, USAGE, CMDNAME, OTHEROPT);
+                               exit(1);
+                       }
+               }
+
+       if (op == DFLT)
+               if (strcmp(cmdname, MOVENAME) == 0)
+                       op = XMOVE;
+               else if (strcmp(cmdname, COPYNAME) == 0)
+                       op = NORMCOPY;
+               else if (strcmp(cmdname, APPENDNAME) == 0)
+                       op = NORMAPPEND;
+               else if (strcmp(cmdname, LINKNAME) == 0)
+                       op = HARDLINK;
+               else
+                       op = DFLTOP;
+       if (
+               op & DIRMOVE &&
+#ifdef IS_MSDOS
+               _osmajor < 3
+#else
+#ifndef HAS_RENAME
+               euid != 0
+#else
+               0
+#endif
+#endif
+       ) {
+               fprintf(stderr,
+                       "Unable to do directory renames. Option -r refused.\n");
+               quit();
+       }
+
+#ifndef IS_MSDOS
+       if (euid != uid && !(op & DIRMOVE)) {
+               setuid(uid);
+               setgid(getgid());
+       }
+#endif
+
+       if (badstyle != ASKBAD && delstyle == ASKDEL)
+               delstyle = NODEL;
+
+       if (argc == 0)
+               *pfrompat = NULL;
+       else if (argc == 2) {
+               *pfrompat = *(argv++);
+               *ptopat = *(argv++);
+       }
+       else {
+               fprintf(stderr, USAGE, CMDNAME, OTHEROPT);
+               exit(1);
+       }
+}
+
+
+static void domatch(cfrom, cto)
+       char *cfrom, *cto;
+{
+       if (cfrom == NULL)
+               while (getpat())
+                       matchpat();
+       else if ((fromlen = strlen(cfrom)) >= MAXPATLEN) {
+               printf(PATLONG, cfrom);
+               paterr = 1;
+       }
+       else if ((tolen = strlen(cto)) >= MAXPATLEN) {
+               printf(PATLONG, cto);
+               paterr = 1;
+       }
+       else {
+               strcpy(from, cfrom);
+               strcpy(to, cto);
+               matchpat();
+       }
+}
+
+
+static int getpat()
+{
+       int c, gotit = 0;
+       char extra[MAXPATLEN];
+
+       patflags = 0;
+       do {
+               if ((fromlen = getword(from)) == 0 || fromlen == -1)
+                       goto nextline;
+
+               do {
+                       if ((tolen = getword(to)) == 0) {
+                               printf("%s -> ? : missing replacement pattern.\n", from);
+                               goto nextline;
+                       }
+                       if (tolen == -1)
+                               goto nextline;
+               } while (
+                       tolen == 2 &&
+                       (to[0] == '-' || to[0] == '=') &&
+                       (to[1] == '>' || to[1] == '^')
+               );
+               if (getword(extra) == 0)
+                       gotit = 1;
+               else if (strcmp(extra, "(*)") == 0) {
+                       patflags |= R_DELOK;
+            gotit = (getword(extra) == 0);
+               }
+
+nextline:
+               while ((c = mygetc()) != '\n' && c != EOF)
+                       ;
+               if (c == EOF)
+                       return(0);
+       } while (!gotit);
+
+       return(1);
+}
+
+
+static int getword(buf)
+       char *buf;
+{
+       int c, prevc, n;
+       char *p;
+
+       p = buf;
+       prevc = ' ';
+       n = 0;
+       while ((c = mygetc()) != EOF && (prevc == ESC || !isspace(c))) {
+               if (n == -1)
+                       continue;
+               if (n == MAXPATLEN - 1) {
+                       *p = '\0';
+                       printf(PATLONG, buf);
+                       n = -1;
+               }
+               *(p++) = c;
+               n++;
+               prevc = c;
+       }
+       *p = '\0';
+       while (c != EOF && isspace(c) && c != '\n')
+               c = mygetc();
+       if (c != EOF)
+               ungetc(c, stdin);
+       return(n);
+}
+
+
+static void matchpat()
+{
+       if (parsepat())
+               paterr = 1;
+       else if (dostage(from, pathbuf, start, len, 0, 0)) {
+               printf("%s -> %s : no match.\n", from, to);
+               paterr = 1;
+       }
+}
+
+
+static int parsepat()
+{
+       char *p, *lastname, c;
+       int totwilds, instage, x, havedot;
+       static char TRAILESC[] = "%s -> %s : trailing %c is superfluous.\n";
+
+       lastname = from;
+#ifdef IS_MSDOS
+       havedot = 0;
+       if (from[0] != '\0' && from[1] == ':')
+               lastname += 2;
+#else
+       if (from[0] == '~' && from[1] == SLASH) {
+               if ((homelen = strlen(home)) + fromlen > MAXPATLEN) {
+                       printf(PATLONG, from);
+                       return(-1);
+               }
+               memmove(from + homelen, from + 1, fromlen);
+               memmove(from, home, homelen);
+               lastname += homelen + 1;
+       }
+#endif
+       totwilds = nstages = instage = 0;
+       for (p = lastname; (c = *p) != '\0'; p++)
+               switch (c) {
+#ifdef IS_MSDOS
+               case '.':
+                       havedot = 1;
+                       break;
+               case OTHERSLASH:
+                       *p = SLASH;
+#endif
+               case SLASH:
+#ifdef IS_MSDOS
+                       if (!havedot && lastname != p) {
+                               if (fromlen++ == MAXPATLEN) {
+                                       printf(PATLONG, from);
+                                       return(-1);
+                               }
+                               memmove(p + 1, p, strlen(p) + 1);
+                               *(p++) = '.';
+                       }
+                       else
+                               havedot = 0;
+#endif
+                       lastname = p + 1;
+                       if (instage) {
+                               if (firstwild[nstages] == NULL)
+                                       firstwild[nstages] = p;
+                               stager[nstages++] = p;
+                               instage = 0;
+                       }
+                       break;
+               case ';':
+                       if (lastname != p) {
+                               printf("%s -> %s : badly placed ;.\n", from, to);
+                               return(-1);
+                       }
+               case '!':
+               case '*':
+               case '?':
+               case '[':
+#ifdef IS_MSDOS
+                       if ((hasdot[totwilds] = (c == '!')) != 0)
+                               havedot = 1;
+#endif
+                       if (totwilds++ == MAXWILD) {
+                               printf("%s -> %s : too many wildcards.\n", from, to);
+                               return(-1);
+                       }
+                       if (instage) {
+                               nwilds[nstages]++;
+                               if (firstwild[nstages] == NULL)
+                                       firstwild[nstages] = p;
+                       }
+                       else {
+                               stagel[nstages] = lastname;
+                               firstwild[nstages] = (c == ';' ? NULL : p);
+                               nwilds[nstages] = 1;
+                               instage = 1;
+                       }
+                       if (c != '[')
+                               break;
+                       while ((c = *(++p)) != ']') {
+                               switch (c) {
+                               case '\0':
+                                       printf("%s -> %s : missing ].\n", from, to);
+                                       return(-1);
+#ifdef IS_MSDOS
+                               case '.':
+                               case ':':
+                               case OTHERSLASH:
+#endif
+                               case SLASH:
+                                       printf("%s -> %s : '%c' can not be part of [].\n",
+                                               from, to, c);
+                                       return(-1);
+                               case ESC:
+                                       if ((c = *(++p)) == '\0') {
+                                               printf(TRAILESC, from, to, ESC);
+                                               return(-1);
+                                       }
+#ifdef IS_MSDOS
+                               default:
+                                       if (isupper(c))
+                                               *p = c + ('a' - 'A');
+#endif
+                               }
+                       }
+                       break;
+               case ESC:
+                       if ((c = *(++p)) == '\0') {
+                               printf(TRAILESC, from, to, ESC);
+                               return(-1);
+                       }
+#ifdef IS_MSDOS
+               default:
+                       if (isupper(c))
+                               *p = c + ('a' - 'A');
+#endif
+               }
+
+#ifdef IS_MSDOS
+       if (!havedot && lastname != p) {
+               if (fromlen++ == MAXPATLEN) {
+                       printf(PATLONG, from);
+                       return(-1);
+               }
+               strcpy(p++, ".");
+       }
+#endif
+
+       if (instage) {
+               if (firstwild[nstages] == NULL)
+                       firstwild[nstages] = p;
+               stager[nstages++] = p;
+       }
+       else {
+               stagel[nstages] = lastname;
+               nwilds[nstages] = 0;
+               firstwild[nstages] = p;
+               stager[nstages++] = p;
+       }
+
+       lastname = to;
+#ifdef IS_MSDOS
+       havedot = 0;
+       if (to[0] != '\0' && to[1] == ':')
+               lastname += 2;
+#else
+       if (to[0] == '~' && to[1] == SLASH) {
+               if ((homelen = strlen(home)) + tolen > MAXPATLEN) {
+                       printf(PATLONG, to);
+                               return(-1);
+               }
+               memmove(to + homelen, to + 1, tolen);
+               memmove(to, home, homelen);
+               lastname += homelen + 1;
+       }
+#endif
+
+       for (p = lastname; (c = *p) != '\0'; p++)
+               switch (c) {
+#ifdef IS_MSDOS
+               case '.':
+                       havedot = 1;
+                       break;
+               case OTHERSLASH:
+                       *p = SLASH;
+#endif
+               case SLASH:
+                       if (op & DIRMOVE) {
+                               printf("%s -> %s : no path allowed in target under -r.\n",
+                                       from, to);
+                               return(-1);
+                       }
+#ifdef IS_MSDOS
+                       if (!havedot && lastname != p) {
+                               if (tolen++ == MAXPATLEN) {
+                                       printf(PATLONG, to);
+                                       return(-1);
+                               }
+                               memmove(p + 1, p, strlen(p) + 1);
+                               *(p++) = '.';
+                       }
+                       else
+                               havedot = 0;
+#endif
+                       lastname = p + 1;
+                       break;
+               case '#':
+                       c = *(++p);
+                       if (c == 'l' || c == 'u') {
+#ifdef IS_MSDOS
+                               strcpy(p, p + 1);
+                               c = *p;
+#else
+                               c = *(++p);
+#endif
+                       }
+                       if (!isdigit(c)) {
+                               printf("%s -> %s : expected digit (not '%c') after #.\n",
+                                       from, to, c);
+                               return(-1);
+                       }
+                       for(x = 0; ;x *= 10) {
+                               x += c - '0';
+                               c = *(p+1);
+                               if (!isdigit(c))
+                                       break;
+                               p++;
+                       }
+                       if (x < 1 || x > totwilds) {
+                               printf("%s -> %s : wildcard #%d does not exist.\n",
+                                       from, to, x);
+                               return(-1);
+                       }
+#ifdef IS_MSDOS
+                       if (hasdot[x - 1])
+                               havedot = 1;
+#endif
+                       break;
+               case ESC:
+                       if ((c = *(++p)) == '\0') {
+                               printf(TRAILESC, from, to, ESC);
+                               return(-1);
+                       }
+               default:
+                       if (
+#ifdef IS_MSDOS
+                               c <= ' ' || c >= 127 ||
+                               strchr(":/\\*?[]=+;,\"|<>", c) != NULL
+#else
+                               c & 0x80
+#endif
+                       ) {
+                               printf("%s -> %s : illegal character '%c' (0x%02X).\n",
+                                       from, to, c, c);
+                               return(-1);
+                       }
+#ifdef IS_MSDOS
+                       if (isupper(c))
+                               *p = c + ('a' - 'A');
+#endif
+               }
+
+#ifdef IS_MSDOS
+       if (!havedot && lastname != p) {
+               if (tolen++ == MAXPATLEN) {
+                       printf(PATLONG, to);
+                       return(-1);
+               }
+               strcpy(p++, ".");
+       }
+#endif
+
+       return(0);
+}
+
+
+static int dostage(lastend, pathend, start1, len1, stage, anylev)
+       char *lastend, *pathend;
+       char **start1;
+       int *len1;
+       int stage;
+       int anylev;
+{
+       DIRINFO *di;
+       HANDLE *h, *hto;
+       int prelen, litlen, nfils, i, k, flags, try;
+       FILEINFO **pf, *fdel;
+       char *nto, *firstesc;
+       REP *p;
+       int wantdirs, ret = 1, laststage = (stage + 1 == nstages);
+
+       wantdirs = !laststage ||
+               (op & (DIRMOVE | SYMLINK)) ||
+               (nwilds[nstages - 1] == 0);
+
+       if (!anylev) {
+               prelen = stagel[stage] - lastend;
+               if (pathend - pathbuf + prelen >= MAXPATH) {
+                       printf("%s -> %s : search path after %s too long.\n",
+                               from, to, pathbuf);
+                       paterr = 1;
+                       return(1);
+               }
+               memmove(pathend, lastend, prelen);
+               pathend += prelen;
+               *pathend = '\0';
+               lastend = stagel[stage];
+       }
+
+       if ((h = checkdir(pathbuf, pathend, 0)) == NULL) {
+               if (stage == 0 || direrr == H_NOREADDIR) {
+                       printf("%s -> %s : directory %s does not %s.\n",
+                               from, to, pathbuf, direrr == H_NOREADDIR ?
+                               "allow reads/searches" : "exist");
+                       paterr = 1;
+               }
+               return(stage);
+       }
+       di = h->h_di;
+
+       if (*lastend == ';') {
+               anylev = 1;
+               *start1 = pathend;
+               *len1 = 0;
+               lastend++;
+       }
+
+       nfils = di->di_nfils;
+
+#ifndef IS_MSDOS
+       if ((op & MOVE) && !dwritable(h)) {
+               printf("%s -> %s : directory %s does not allow writes.\n",
+                       from, to, pathbuf);
+               paterr = 1;
+               goto skiplev;
+       }
+#endif
+
+       firstesc = strchr(lastend, ESC);
+       if (firstesc == NULL || firstesc > firstwild[stage])
+               firstesc = firstwild[stage];
+       litlen = firstesc - lastend;
+       pf = di->di_fils + (i = ffirst(lastend, litlen, di));
+       if (i < nfils)
+       do {
+               if (
+                       (try = trymatch(*pf, lastend)) != 0 &&
+                       (
+                               try == 1 ||
+                               match(lastend + litlen, (*pf)->fi_name + litlen,
+                                       start1 + anylev, len1 + anylev)
+                       ) &&
+                       keepmatch(*pf, pathend, &k, 0, wantdirs, laststage)
+               ) {
+                       if (!laststage)
+                               ret &= dostage(stager[stage], pathend + k,
+                                       start1 + nwilds[stage], len1 + nwilds[stage],
+                                       stage + 1, 0);
+                       else {
+                               ret = 0;
+                               makerep();
+                               if (badrep(h, *pf, &hto, &nto, &fdel, &flags))
+                                       (*pf)->fi_rep = MISTAKE;
+                               else {
+                                       (*pf)->fi_rep = p = (REP *)challoc(sizeof(REP), 1);
+                                       p->r_flags = flags | patflags;
+                                       p->r_hfrom = h;
+                                       p->r_ffrom = *pf;
+                                       p->r_hto = hto;
+                                       p->r_nto = nto;
+                                       p->r_fdel = fdel;
+                                       p->r_first = p;
+                                       p->r_thendo = NULL;
+                                       p->r_next = NULL;
+                                       lastrep->r_next = p;
+                                       lastrep = p;
+                                       nreps++;
+                               }
+                       }
+               }
+               i++, pf++;
+       } while (i < nfils && strncmp(lastend, (*pf)->fi_name, litlen) == 0);
+
+skiplev:
+       if (anylev)
+               for (pf = di->di_fils, i = 0; i < nfils; i++, pf++)
+                       if (
+                               *((*pf)->fi_name) != '.' &&
+#ifdef IS_MSDOS
+                               ((*pf)->fi_attrib & FA_DIREC) &&
+#endif
+                               keepmatch(*pf, pathend, &k, 1, 1, 0)
+                       ) {
+                               *len1 = pathend - *start1 + k;
+                               ret &= dostage(lastend, pathend + k, start1, len1, stage, 1);
+                       }
+
+       return(ret);
+}
+
+
+static int trymatch(ffrom, pat)
+       FILEINFO *ffrom;
+       char *pat;
+{
+       char *p;
+
+       if (ffrom->fi_rep != NULL)
+               return(0);
+
+       p = ffrom->fi_name;
+
+#ifdef IS_MSDOS
+       if (*p == '.' || (!matchall && ffrom->fi_attrib & (FA_HIDDEN | FA_SYSTEM)))
+               return(strcmp(pat, p) == 0);
+#else
+       if (*p == '.')
+               if (p[1] == '\0' || (p[1] == '.' && p[2] == '\0'))
+                       return(strcmp(pat, p) == 0);
+               else if (!matchall && *pat != '.')
+                       return(0);
+#endif
+       return(-1);
+}
+
+
+static int keepmatch(ffrom, pathend, pk, needslash, dirs, fils)
+       FILEINFO *ffrom;
+       char *pathend;
+       int *pk;
+       int needslash;
+       int dirs, fils;
+{
+       *pk = strlen(ffrom->fi_name);
+       if (pathend - pathbuf + *pk + needslash >= MAXPATH) {
+               *pathend = '\0';
+               printf("%s -> %s : search path %s%s too long.\n",
+                       from, to, pathbuf, ffrom->fi_name);
+               paterr = 1;
+               return(0);
+       }
+       strcpy(pathend, ffrom->fi_name);
+#ifdef IS_MSDOS
+       if ((ffrom->fi_attrib & FA_DIREC) ? !dirs : !fils)
+#else
+       getstat(pathbuf, ffrom);
+       if ((ffrom->fi_stflags & FI_ISDIR) ? !dirs : !fils)
+#endif
+               return(0);
+
+       if (needslash) {
+               strcpy(pathend + *pk, SLASHSTR);
+               (*pk)++;
+       }
+       return(1);
+}
+
+
+static int badrep(hfrom, ffrom, phto, pnto, pfdel, pflags)
+       HANDLE *hfrom;
+       FILEINFO *ffrom;
+       HANDLE **phto;
+       char **pnto;
+       FILEINFO **pfdel;
+       int *pflags;
+{
+       char *f = ffrom->fi_name;
+
+       *pflags = 0;
+       if (
+#ifdef IS_MSDOS
+               (ffrom->fi_attrib & FA_DIREC) &&
+#else
+               (ffrom->fi_stflags & FI_ISDIR) &&
+#endif
+               !(op & (DIRMOVE | SYMLINK))
+       )
+               printf("%s -> %s : source file is a directory.\n", pathbuf, fullrep);
+#ifndef IS_MSDOS
+#ifdef S_IFLNK
+       else if ((ffrom->fi_stflags & FI_LINKERR) && !(op & (MOVE | SYMLINK)))
+               printf("%s -> %s : source file is a badly aimed symbolic link.\n",
+                       pathbuf, fullrep);
+#endif
+#ifndef IS_SYSV
+       else if ((ffrom->fi_stflags & FI_NODEL) && (op & MOVE)) 
+               printf("%s -> %s : no delete permission for source file.\n",
+                       pathbuf, fullrep);
+#endif
+       else if ((op & (COPY | APPEND)) && access(pathbuf, R_OK))
+               printf("%s -> %s : no read permission for source file.\n",
+                       pathbuf, fullrep);
+#endif
+       else if (
+               *f == '.' &&
+               (f[1] == '\0' || strcmp(f, "..") == 0) &&
+               !(op & SYMLINK)
+       )
+               printf("%s -> %s : . and .. can't be renamed.\n", pathbuf, fullrep);
+       else if (repbad || checkto(hfrom, f, phto, pnto, pfdel) || badname(*pnto))
+               printf("%s -> %s : bad new name.\n", pathbuf, fullrep);
+       else if (*phto == NULL)
+               printf("%s -> %s : %s.\n", pathbuf, fullrep,
+#ifndef IS_MSDOS
+                       direrr == H_NOREADDIR ?
+                       "no read or search permission for target directory" :
+#endif
+                       "target directory does not exist");
+#ifndef IS_MSDOS
+       else if (!dwritable(*phto))
+               printf("%s -> %s : no write permission for target directory.\n",
+                       pathbuf, fullrep);
+#endif
+       else if (
+               (*phto)->h_di->di_vid != hfrom->h_di->di_vid &&
+               (*pflags = R_ISX, (op & (NORMMOVE | HARDLINK)))
+       )
+               printf("%s -> %s : cross-device move.\n",
+                       pathbuf, fullrep);
+#ifndef IS_MSDOS
+       else if (
+               *pflags && (op & MOVE) &&
+               !(ffrom->fi_stflags & FI_ISLNK) &&
+               access(pathbuf, R_OK)
+       )
+               printf("%s -> %s : no read permission for source file.\n",
+                       pathbuf, fullrep);
+#ifdef S_IFLNK
+       else if (
+               (op & SYMLINK) &&
+               !(
+                       ((*phto)->h_di->di_vid == cwdv && (*phto)->h_di->di_did == cwdd) ||
+                       *(hfrom->h_name) == SLASH ||
+                       (*pflags |= R_ONEDIRLINK, hfrom->h_di == (*phto)->h_di)
+               )
+       )
+               printf("%s -> %s : symbolic link would be badly aimed.\n",
+                       pathbuf, fullrep);
+#endif
+#endif
+       else
+               return(0);
+       badreps++;
+       return(-1);
+}
+
+
+static int checkto(hfrom, f, phto, pnto, pfdel)
+       HANDLE *hfrom;
+       char *f;
+       HANDLE **phto;
+       char **pnto;
+       FILEINFO **pfdel;
+{
+       char tpath[MAXPATH + 1];
+       char *pathend;
+       FILEINFO *fdel;
+       int hlen, tlen;
+
+       if (op & DIRMOVE) {
+               *phto = hfrom;
+               hlen = strlen(hfrom->h_name);
+               pathend = fullrep + hlen;
+               memmove(pathend, fullrep, strlen(fullrep) + 1);
+               memmove(fullrep, hfrom->h_name, hlen);
+               if ((fdel = *pfdel = fsearch(pathend, hfrom->h_di)) != NULL) {
+                       *pnto = fdel->fi_name;
+#ifndef IS_MSDOS
+                       getstat(fullrep, fdel);
+#endif
+               }
+               else
+                       *pnto = mydup(pathend);
+       }
+       else {
+               pathend = getpath(tpath);
+               hlen = pathend - fullrep;
+               *phto = checkdir(tpath, tpath + hlen, 1);
+               if (
+                       *phto != NULL &&
+                       *pathend != '\0' &&
+                       (fdel = *pfdel = fsearch(pathend, (*phto)->h_di)) != NULL &&
+#ifdef IS_MSDOS
+                       (fdel->fi_attrib & FA_DIREC)
+#else
+                       (getstat(fullrep, fdel), fdel->fi_stflags & FI_ISDIR)
+#endif
+               ) {
+                       tlen = strlen(pathend);
+                       strcpy(pathend + tlen, SLASHSTR);
+                       tlen++;
+                       strcpy(tpath + hlen, pathend);
+                       pathend += tlen;
+                       hlen += tlen;
+                       *phto = checkdir(tpath, tpath + hlen, 1);
+               }
+
+               if (*pathend == '\0') {
+                       *pnto = f;
+                       if (pathend - fullrep + strlen(f) >= MAXPATH) {
+                               strcpy(fullrep, TOOLONG);
+                               return(-1);
+                       }
+                       strcat(pathend, f);
+                       if (*phto != NULL) {
+                               fdel = *pfdel = fsearch(f, (*phto)->h_di);
+#ifndef IS_MSDOS
+                               if (fdel != NULL)
+                                       getstat(fullrep, fdel);
+#endif
+                       }
+               }
+               else if (fdel != NULL)
+                       *pnto = fdel->fi_name;
+               else
+                       *pnto = mydup(pathend);
+       }
+       return(0);
+}
+
+
+static char *getpath(tpath)
+       char *tpath;
+{
+       char *pathstart, *pathend, c;
+
+#ifdef IS_MSDOS
+       if (*fullrep != '\0' && fullrep[1] == ':')
+               pathstart = fullrep + 2;
+       else
+#endif
+               pathstart = fullrep;
+
+       pathend = pathstart + strlen(pathstart) - 1;
+       while (pathend >= pathstart && *pathend != SLASH)
+               --pathend;
+       pathend++;
+
+       c = *pathend;
+       *pathend = '\0';
+       strcpy(tpath, fullrep);
+       *pathend = c;
+       return(pathend);
+}
+
+
+static int badname(s)
+       char *s;
+{
+       char *ext;
+
+       return (
+#ifdef IS_MSDOS
+               *s == ' ' ||
+               *s == '.' ||
+               (ext = strchr(s, '.')) - s >= MAXFILE ||
+               (*ext == '.' && strchr(ext + 1, '.') != NULL) ||
+               strlen(ext) >= MAXEXT ||
+               strncmp(s, IDF, STRLEN(IDF)) == 0
+#else
+               (*s == '.' && (s[1] == '\0' || strcmp(s, "..") == 0)) ||
+               strlen(s) > MAXNAMLEN
+#endif
+       );
+}
+
+
+#ifndef IS_MSDOS
+static int getstat(ffull, f)
+       char *ffull;
+       FILEINFO *f;
+{
+       struct stat fstat;
+       int flags;
+
+       if ((flags = f->fi_stflags) & FI_STTAKEN)
+               return(flags & FI_LINKERR);
+       flags |= FI_STTAKEN;
+#ifndef S_IFLNK
+       if (stat(ffull, &fstat)) {
+               fprintf(stderr, "Strange, couldn't stat %s.\n", ffull);
+               quit();
+       }
+#else
+       if (lstat(ffull, &fstat)) {
+               fprintf(stderr, "Strange, couldn't lstat %s.\n", ffull);
+               quit();
+       }
+       if ((flags & FI_INSTICKY) && fstat.st_uid != uid && uid != 0)
+               flags |= FI_NODEL;
+       if ((fstat.st_mode & S_IFMT) == S_IFLNK) {
+               flags |= FI_ISLNK;
+               if (stat(ffull, &fstat)) {
+                       f->fi_stflags = flags | FI_LINKERR;
+                       return(1);
+               }
+       }
+#endif
+       if ((fstat.st_mode & S_IFMT) == S_IFDIR)
+               flags |= FI_ISDIR;
+       f->fi_stflags = flags;
+       f->fi_mode = fstat.st_mode;
+       return(0);
+}
+
+
+static int dwritable(h)
+       HANDLE *h;
+{
+       char *p = h->h_name, *myp, *lastslash = NULL, *pathend;
+       char *pw = &(h->h_di->di_flags), r;
+
+       if (uid == 0)
+               return(1);
+
+       if (*pw & DI_KNOWWRITE)
+               return(*pw & DI_CANWRITE);
+
+       pathend = p + strlen(p);
+       if (*p == '\0')
+               myp = ".";
+       else if (pathend == p + 1)
+               myp = SLASHSTR;
+       else {
+               lastslash = pathend - 1;
+               *lastslash = '\0';
+               myp = p;
+       }
+       r = !access(myp, W_OK) ? DI_CANWRITE : 0;
+       *pw |= DI_KNOWWRITE | r;
+
+       if (lastslash != NULL)
+               *lastslash = SLASH;
+       return(r);
+}
+
+
+static int fwritable(hname, f)
+       char *hname;
+       FILEINFO *f;
+{
+       int r;
+
+       if (f->fi_stflags & FI_KNOWWRITE)
+               return(f->fi_stflags & FI_CANWRITE);
+
+       strcpy(fullrep, hname);
+       strcat(fullrep, f->fi_name);
+       r = !access(fullrep, W_OK) ? FI_CANWRITE : 0;
+       f->fi_stflags |= FI_KNOWWRITE | r;
+       return(r);
+}
+#endif
+
+
+static FILEINFO *fsearch(s, d)
+       char *s;
+       DIRINFO *d;
+{
+       FILEINFO **fils = d->di_fils;
+       int nfils = d->di_nfils;
+       int first, k, last, res;
+
+       for(first = 0, last = nfils - 1;;) {
+               if (last < first)
+                       return(NULL);
+               k = (first + last) >> 1;
+               if ((res = strcmp(s, fils[k]->fi_name)) == 0)
+                       return(fils[k]);
+               if (res < 0)
+                       last = k - 1;
+               else
+                       first = k + 1;
+       }
+}
+
+
+static int ffirst(s, n, d)
+       char *s;
+       int n;
+       DIRINFO *d;
+{
+       int first, k, last, res;
+       FILEINFO **fils = d->di_fils;
+       int nfils = d->di_nfils;
+
+       if (nfils == 0 || n == 0)
+               return(0);
+       first = 0;
+       last = nfils - 1;
+       for(;;) {
+               k = (first + last) >> 1;
+               res = strncmp(s, fils[k]->fi_name, n);
+               if (first == last)
+                       return(res == 0 ? k : nfils);
+               else if (res > 0)
+                       first = k + 1;
+               else
+                       last = k;
+       }
+}
+
+
+#ifdef IS_MSDOS
+/* checkdir and takedir for MS-D*S */
+
+static HANDLE *checkdir(p, pathend, which)
+       char *p, *pathend;
+       int which;
+{
+       struct ffblk de;
+       DIRID d;
+       DEVID v;
+       HANDLE *h;
+       char *dirstart = p;
+       int fd;
+       int firstfound;
+       DIRINFO *di;
+
+       if (hsearch(p, which, &h))
+               if (h->h_di == NULL) {
+                       direrr = h->h_err;
+                       return(NULL);
+               }
+               else
+                       return(h);
+
+       if (*p == '\0' || p[1] != ':')
+               v = curdisk;
+       else {
+               dirstart += 2;
+               v = mylower(p[0]) - 'a';
+               if (v < 0 || v >= maxdisk)
+                       return(NULL);
+       }
+
+       if (patch.ph_safeid) {
+               strcpy(pathend, IDF);
+               strcpy(pathend + STRLEN(IDF), "*");
+               if (findfirst(p, &de, 0)) {
+                       if ((d = ndirs) == 1000) {
+                               fprintf(stderr, "Too many different directories.\n");
+                               quit();
+                       }
+                       sprintf(pathend + STRLEN(IDF), "%03d", d);
+                       if ((fd = _creat(p, 0)) < 0) {
+                               direrr = h->h_err = H_NODIR;
+                               return(NULL);
+                       }
+                       _close(fd);
+                       strcpy(pathend, "*.*");
+                       if (findfirst(p, &de, FA_DIREC | FA_SYSTEM | FA_HIDDEN))
+                               h->h_di = dadd(v, d);
+                       else
+                               takedir(&de, h->h_di = dadd(v, d));
+               }
+               else if ((d = atoi(de.ff_name + STRLEN(IDF))) < ndirs)
+                       h->h_di = dirs[d];
+               else {
+                       strcpy(pathend, de.ff_name);
+                       fprintf(stderr, "Strange dir-id file encountered: %s.\n", p);
+                       quit();
+               }
+               *pathend = '\0';
+       }
+       else {
+               strcpy(pathend, "*.*");
+               firstfound = !findfirst(p, &de, FA_DIREC | FA_SYSTEM | FA_HIDDEN);
+               *pathend = '\0';
+               if (firstfound) {
+                       v = DRIVENO(&de);
+                       d = CLUSTNO(&de);
+               }
+               else {
+                       strcpy(pathend, "T.D");
+                       if (mkdir(p)) {
+                               *pathend = '\0';
+                               direrr = h->h_err = H_NODIR;
+                               return(NULL);
+                       }
+                       strcpy(pathend, "*.*");
+                       firstfound = !findfirst(p, &de, FA_DIREC | FA_SYSTEM | FA_HIDDEN);
+                       *pathend = '\0';
+                       v = DRIVENO(&de);
+                       d = CLUSTNO(&de);
+                       rmdir(p);
+                       if (!firstfound || d != 0) {
+                               fprintf(stderr,
+                                       "Strange, %s does not seem to be a root dir.\n",
+                                       p);
+                               quit();
+                       }
+               }
+
+               if ((di = dsearch(v, d)) == NULL)
+                       if (firstfound)
+                               takedir(&de, h->h_di = dadd(v, d));
+                       else
+                               h->h_di = dadd(v, d);
+               else
+                       h->h_di = di;
+       }
+
+       return(h);
+}
+
+
+static void takedir(pff, di)
+       struct ffblk *pff;
+       DIRINFO *di;
+{
+       int cnt, room, namlen, needdot;
+       FILEINFO **fils, *f;
+       char c, *p, *p1;
+
+       room = INITROOM;
+       di->di_fils = fils = (FILEINFO **)myalloc(room * sizeof(FILEINFO *));
+       cnt = 0;
+       do {
+               if (strnicmp(pff->ff_name, IDF, STRLEN(IDF)) == 0)
+                       continue;
+               if (cnt == room) {
+                       room *= 2;
+                       fils = (FILEINFO **)myalloc(room * sizeof(FILEINFO *));
+                       memcpy(fils, di->di_fils, cnt * sizeof(FILEINFO *));
+                       chgive(di->di_fils, cnt * sizeof(FILEINFO *));
+                       di->di_fils = fils;
+                       fils = di->di_fils + cnt;
+               }
+               needdot = 1;
+               for (p = pff->ff_name, namlen = 0; (c = *p) != '\0'; p++, namlen++)
+                       if (c == '.')
+                               needdot = 0;
+               *fils = f = (FILEINFO *)challoc(sizeof(FILEINFO), 1);
+               f->fi_name = p = (char *)challoc(namlen + needdot + 1, 0);
+               for (p1 = pff->ff_name; (c = *p1) != '\0'; p1++)
+                       *(p++) = mylower(c);
+               if (needdot)
+                       *(p++) = '.';
+               *p = '\0';
+               f->fi_attrib = pff->ff_attrib;
+               f->fi_rep = NULL;
+               cnt++;
+               fils++;
+       } while (findnext(pff) == 0);
+       qsort(di->di_fils, cnt, sizeof(FILEINFO *), fcmp);
+       di->di_nfils = cnt;
+}
+
+#else
+/* checkdir, takedir for Un*x */
+
+static HANDLE *checkdir(p, pathend, which)
+       char *p, *pathend;
+       int which;
+{
+       struct stat dstat;
+       DIRID d;
+       DEVID v;
+       DIRINFO **newdirs, *di;
+       int nfils;
+       FILEINFO **fils;
+       char *myp, *lastslash = NULL;
+       int sticky;
+       HANDLE *h;
+
+       if (hsearch(p, which, &h))
+               if (h->h_di == NULL) {
+                       direrr = h->h_err;
+                       return(NULL);
+               }
+               else
+                       return(h);
+
+       if (*p == '\0')
+               myp = ".";
+       else if (pathend == p + 1)
+               myp = SLASHSTR;
+       else {
+               lastslash = pathend - 1;
+               *lastslash = '\0';
+               myp = p;
+       }
+
+       if (stat(myp, &dstat) || (dstat.st_mode & S_IFMT) != S_IFDIR)
+               direrr = h->h_err = H_NODIR;
+       else if (access(myp, R_OK | X_OK))
+               direrr = h->h_err = H_NOREADDIR;
+       else {
+               direrr = 0;
+               sticky = (dstat.st_mode & S_ISVTX) && uid != 0 && uid != dstat.st_uid ?
+                       FI_INSTICKY : 0;
+               v = dstat.st_dev;
+               d = dstat.st_ino;
+
+               if ((di = dsearch(v, d)) == NULL)
+                       takedir(myp, di = dadd(v, d), sticky);
+       }
+
+       if (lastslash != NULL)
+               *lastslash = SLASH;
+       if (direrr != 0)
+               return(NULL);
+       h->h_di = di;
+       return(h);
+}
+
+
+static void takedir(p, di, sticky)
+       char *p;
+       DIRINFO *di;
+       int sticky;
+{
+       int cnt, room;
+       DIRENTRY *dp;
+       FILEINFO *f, **fils;
+       DIR *dirp;
+
+       if ((dirp = opendir(p)) == NULL) {
+               fprintf(stderr, "Strange, can't scan %s.\n", p);
+               quit();
+       }
+       room = INITROOM;
+       di->di_fils = fils = (FILEINFO **)myalloc(room * sizeof(FILEINFO *));
+       cnt = 0;
+       while ((dp = readdir(dirp)) != NULL) {
+               if (cnt == room) {
+                       room *= 2;
+                       fils = (FILEINFO **)myalloc(room * sizeof(FILEINFO *));
+                       memcpy(fils, di->di_fils, cnt * sizeof(FILEINFO *));
+                       chgive(di->di_fils, cnt * sizeof(FILEINFO *));
+                       di->di_fils = fils;
+                       fils = di->di_fils + cnt;
+               }
+               *fils = f = (FILEINFO *)challoc(sizeof(FILEINFO), 1);
+               f->fi_name = mydup(dp->d_name);
+               f->fi_stflags = sticky;
+               f->fi_rep = NULL;
+               cnt++;
+               fils++;
+       }
+       closedir(dirp);
+       qsort(di->di_fils, cnt, sizeof(FILEINFO *), fcmp);
+       di->di_nfils = cnt;
+}
+
+/* end of Un*x checkdir, takedir; back to general program */
+#endif
+
+
+static int fcmp(pf1, pf2)
+       FILEINFO **pf1, **pf2;
+{
+        return(strcmp((*pf1)->fi_name, (*pf2)->fi_name));
+}
+
+
+static HANDLE *hadd(n)
+       char *n;
+{
+       HANDLE **newhandles, *h;
+
+       if (nhandles == handleroom) {
+               handleroom *= 2;
+               newhandles = (HANDLE **)myalloc(handleroom * sizeof(HANDLE *));
+               memcpy(newhandles, handles, nhandles * sizeof(HANDLE *));
+               chgive(handles, nhandles * sizeof(HANDLE *));
+               handles = newhandles;
+       }
+       handles[nhandles++] = h = (HANDLE *)challoc(sizeof(HANDLE), 1);
+       h->h_name = (char *)challoc(strlen(n) + 1, 0);
+       strcpy(h->h_name, n);
+       h->h_di = NULL;
+       return(h);
+}
+
+
+static int hsearch(n, which, pret)
+       char *n;
+       int which;
+       HANDLE **pret;
+{
+       int i;
+       HANDLE **ph;
+
+       if (strcmp(n, lasthandle[which]->h_name) == 0) {
+               *pret = lasthandle[which];
+               return(1);
+       }
+
+       for(i = 0, ph = handles; i < nhandles; i++, ph++)
+               if (strcmp(n, (*ph)->h_name) == 0) {
+                       lasthandle[which] = *pret = *ph;
+                       return(1);
+               }
+
+       lasthandle[which] = *pret = hadd(n);
+       return(0);
+}
+
+
+static DIRINFO *dadd(v, d)
+       DEVID v;
+       DIRID d;
+{
+       DIRINFO *di;
+       DIRINFO **newdirs;
+
+       if (ndirs == dirroom) {
+               dirroom *= 2;
+               newdirs = (DIRINFO **)myalloc(dirroom * sizeof(DIRINFO *));
+               memcpy(newdirs, dirs, ndirs * sizeof(DIRINFO *));
+               chgive(dirs, ndirs * sizeof(DIRINFO *));
+               dirs = newdirs;
+       }
+       dirs[ndirs++] = di = (DIRINFO *)challoc(sizeof(DIRINFO), 1);
+       di->di_vid = v;
+       di->di_did = d;
+       di->di_nfils = 0;
+       di->di_fils = NULL;
+       di->di_flags = 0;
+       return(di);
+}
+
+
+static DIRINFO *dsearch(v, d)
+       DEVID v;
+       DIRID d;
+{
+       int i;
+       DIRINFO *di;
+
+       for(i = 0, di = *dirs; i < ndirs; i++, di++)
+               if (v == di->di_vid && d == di->di_did)
+                       return(di);
+       return(NULL);
+}
+
+
+static int match(pat, s, start1, len1)
+       char *pat, *s, **start1;
+       int *len1;
+{
+       char c, *olds;
+
+       *start1 = 0;
+       for(;;)
+               switch (c = *pat) {
+               case '\0':
+               case SLASH:
+                       return(*s == '\0');
+#ifdef IS_MSDOS
+               case '!':
+                       *start1 = olds = s;
+                       if ((s = strchr(s, '.')) == NULL)
+                               return(0);
+                       s++;
+                       *len1 = s - olds;
+                       if ((c = *(++pat)) == '\0') {
+                               *len1 += strlen(s);
+                               return(1);
+                       }
+                       for ( ; !match(pat, s, start1 + 1, len1 + 1); (*len1)++, s++)
+                               if (*s == '\0')
+                                       return(0);
+                       return(1);
+#endif
+               case '*':
+                       *start1 = s;
+                       if ((c = *(++pat)) == '\0') {
+                               *len1 = strlen(s);
+                               return(1);
+                       }
+                       else {
+                               for (*len1=0; !match(pat, s, start1+1, len1+1); (*len1)++, s++)
+                                       if (
+#ifdef IS_MSDOS
+                                               *s == '.' ||
+#endif
+                                               *s == '\0'
+                                       )
+                                               return(0);
+                               return(1);
+                       }
+               case '?':
+                       if (
+#ifdef IS_MSDOS
+                               *s == '.' ||
+#endif
+                               *s == '\0'
+                       )
+                               return(0);
+                       *(start1++) = s;
+                       *(len1++) = 1;
+                       pat++;
+                       s++;
+                       break;
+               case '[':
+                       {
+                               int matched = 0, notin = 0, inrange = 0;
+                               char prevc = '\0';
+
+                               if ((c = *(++pat)) == '^') {
+                                       notin = 1;
+                                       c = *(++pat);
+                               }
+                               while (c != ']') {
+                                       if (c == '-' && !inrange)
+                                               inrange = 1;
+                                       else {
+                                               if (c == ESC) {
+                                                       c = *(++pat);
+                                               }
+                                               if (inrange) {
+                                                       if (*s >= prevc && *s <= c)
+                                                               matched = 1;
+                                                       inrange = 0;
+                                               }
+                                               else if (c == *s)
+                                                       matched = 1;
+                                               prevc = c;
+                                       }
+                                       c = *(++pat);
+                               }
+                               if (inrange && *s >= prevc)
+                                       matched = 1;
+                               if (!(matched ^ notin))
+                                       return(0);
+                               *(start1++) = s;
+                               *(len1++) = 1;
+                               pat++;
+                               s++;
+                       }
+                       break;
+               case ESC:
+                       c = *(++pat);
+               default:
+                       if (c == *s) {
+                               pat++;
+                               s++;
+                       }
+                       else
+                               return(0);
+               }
+}
+
+
+static void makerep()
+{
+       int l, x;
+#ifndef IS_MSDOS
+       int i, cnv;
+       char *q;
+#endif
+       char *p, *pat, c, pc;
+
+       repbad = 0;
+       p = fullrep;
+       for (pat = to, l = 0; (c = *pat) != '\0'; pat++, l++) {
+               if (c == '#') {
+                       c = *(++pat);
+#ifndef IS_MSDOS
+                       if (c == 'l') {
+                               cnv = LOWER;
+                               c = *(++pat);
+                       }
+                       else if (c == 'u') {
+                               cnv = UPPER;
+                               c = *(++pat);
+                       }
+                       else
+                               cnv = STAY;
+#endif
+                       for(x = 0; ;x *= 10) {
+                               x += c - '0';
+                               c = *(pat+1);
+                               if (!isdigit(c))
+                                       break;
+                               pat++;
+                       }
+                       --x;
+                       if (l + len[x] >= MAXPATH)
+                               goto toolong;
+#ifdef IS_MSDOS
+                       if (
+                               *(start[x]) == '.' &&
+                               (
+                                       p == fullrep ||
+                                       *(p - 1) == SLASH
+                               )
+                       ) {
+                               repbad = 1;
+                               if (l + STRLEN(EMPTY) >= MAXPATH)
+                                       goto toolong;
+                               strcpy(p, EMPTY);
+                               p += STRLEN(EMPTY);
+                               l += STRLEN(EMPTY);
+                       }
+#else
+                       switch (cnv) {
+                       case STAY:
+#endif
+                               memmove(p, start[x], len[x]);
+                               p += len[x];
+#ifndef IS_MSDOS
+                               break;
+                       case LOWER:
+                               for (i = len[x], q = start[x]; i > 0; i--, p++, q++)
+                                       *p = mylower(*q);
+                               break;
+                       case UPPER:
+                               for (i = len[x], q = start[x]; i > 0; i--, p++, q++)
+                                       *p = myupper(*q);
+                       }
+#endif
+               }
+               else {
+                       if (c == ESC)
+                               c = *(++pat);
+                       if (l == MAXPATH)
+                               goto toolong;
+                       if (
+                               (
+#ifdef IS_MSDOS
+                                       c == '.' ||
+#endif
+                                       c == SLASH
+                               ) &&
+                               (
+                                       p == fullrep ? pat != to :
+                                       (
+                                               (
+                                                       (pc = *(p - 1)) == SLASH
+#ifdef IS_MSDOS
+                                                       || pc == ':'
+#endif
+                                               ) &&
+                                               *(pat - 1) != pc
+                                       )
+                               )
+                       ) {
+                               repbad = 1;
+                               if (l + STRLEN(EMPTY) >= MAXPATH)
+                                       goto toolong;
+                               strcpy(p, EMPTY);
+                               p += STRLEN(EMPTY);
+                               l += STRLEN(EMPTY);
+                       }
+                       *(p++)= c;
+               }
+       }
+       if (p == fullrep) {
+               strcpy(fullrep, EMPTY);
+               repbad = 1;
+       }
+       *(p++) = '\0';
+       return;
+
+toolong:
+       repbad = 1;
+       strcpy(fullrep, TOOLONG);
+}
+
+
+static void checkcollisions()
+{
+       REPDICT *rd, *prd;
+       REP *p, *q;
+       int i, mult, oldnreps;
+
+       if (nreps == 0)
+               return;
+       rd = (REPDICT *)myalloc(nreps * sizeof(REPDICT));
+       for (
+               q = &hrep, p = q->r_next, prd = rd, i = 0;
+               p != NULL;
+               q = p, p = p->r_next, prd++, i++
+       ) {
+               prd->rd_p = p;
+               prd->rd_dto = p->r_hto->h_di;
+               prd->rd_nto = p->r_nto;
+               prd->rd_i = i;
+       }
+       qsort(rd, nreps, sizeof(REPDICT), rdcmp);
+       mult = 0;
+       for (i = 0, prd = rd, oldnreps = nreps; i < oldnreps; i++, prd++)
+               if (
+                       i < oldnreps - 1 &&
+                       prd->rd_dto == (prd + 1)->rd_dto &&
+                       strcmp(prd->rd_nto, (prd + 1)->rd_nto) == 0
+               ) {
+                       if (!mult)
+                               mult = 1;
+                       else
+                               printf(" , ");
+                       printf("%s%s", prd->rd_p->r_hfrom->h_name,
+                               prd->rd_p->r_ffrom->fi_name);
+                       prd->rd_p->r_flags |= R_SKIP;
+                       prd->rd_p->r_ffrom->fi_rep = MISTAKE;
+                       nreps--;
+                       badreps++;
+               }
+               else if (mult) {
+                       prd->rd_p->r_flags |= R_SKIP;
+                       prd->rd_p->r_ffrom->fi_rep = MISTAKE;
+                       nreps--;
+                       badreps++;
+                       printf(" , %s%s -> %s%s : collision.\n",
+                               prd->rd_p->r_hfrom->h_name, prd->rd_p->r_ffrom->fi_name,
+                               prd->rd_p->r_hto->h_name, prd->rd_nto);
+                       mult = 0;
+               }
+       chgive(rd, oldnreps * sizeof(REPDICT));
+}
+
+
+static int rdcmp(rd1, rd2)
+       REPDICT *rd1, *rd2;
+{
+       int ret;
+
+       if (
+               (ret = rd1->rd_dto - rd2->rd_dto) == 0 &&
+               (ret = strcmp(rd1->rd_nto, rd2->rd_nto)) == 0
+       )
+               ret = rd1->rd_i - rd2->rd_i;
+       return(ret);
+}
+
+
+static void findorder()
+{
+       REP *p, *q, *t, *first, *pred;
+       FILEINFO *fi;
+
+       for (q = &hrep, p = q->r_next; p != NULL; q = p, p = p->r_next)
+               if (p->r_flags & R_SKIP) {
+                       q->r_next = p->r_next;
+                       p = q;
+               }
+               else if (
+                       (fi = p->r_fdel) == NULL ||
+                       (pred = fi->fi_rep) == NULL ||
+                       pred == MISTAKE
+               )
+                       continue;
+               else if ((first = pred->r_first) == p) {
+                       p->r_flags |= R_ISCYCLE;
+                       pred->r_flags |= R_ISALIASED;
+                       if (op & MOVE)
+                               p->r_fdel = NULL;
+               }
+               else {
+                       if (op & MOVE)
+                               p->r_fdel = NULL;
+                       while (pred->r_thendo != NULL)
+                               pred = pred->r_thendo;
+                       pred->r_thendo = p;
+                       for (t = p; t != NULL; t = t->r_thendo)
+                               t->r_first = first;
+                       q->r_next = p->r_next;
+                       p = q;
+               }
+}
+
+
+static void nochains()
+{
+       REP *p, *q;
+
+       for (q = &hrep, p = q->r_next; p != NULL; q = p, p = p->r_next)
+               if (p->r_flags & R_ISCYCLE || p->r_thendo != NULL) {
+                       printchain(p);
+                       printf("%s%s : no chain copies allowed.\n",
+                               p->r_hto->h_name, p->r_nto);
+                       q->r_next = p->r_next;
+                       p = q;
+               }
+}
+
+
+static void printchain(p)
+       REP *p;
+{
+       if (p->r_thendo != NULL)
+               printchain(p->r_thendo);
+       printf("%s%s -> ", p->r_hfrom->h_name, p->r_ffrom->fi_name);
+       badreps++;
+       nreps--;
+       p->r_ffrom->fi_rep = MISTAKE;
+}
+
+
+static void scandeletes(pkilldel)
+       int (*pkilldel)();
+{
+       REP *p, *q, *n;
+
+       for (q = &hrep, p = q->r_next; p != NULL; q = p, p = p->r_next) {
+               if (p->r_fdel != NULL)
+                       while ((*pkilldel)(p)) {
+                               nreps--;
+                               p->r_ffrom->fi_rep = MISTAKE;
+                               if ((n = p->r_thendo) != NULL) {
+                                       if (op & MOVE)
+                                               n->r_fdel = p->r_ffrom;
+                                       n->r_next = p->r_next;
+                                       q->r_next = p = n;
+                               }
+                               else {
+                                       q->r_next = p->r_next;
+                                       p = q;
+                                       break;
+                               }
+                       }
+       }
+}
+
+
+static int baddel(p)
+       REP *p;
+{
+       HANDLE *hfrom = p->r_hfrom, *hto = p->r_hto;
+       FILEINFO *fto = p->r_fdel;
+       char *t = fto->fi_name, *f = p->r_ffrom->fi_name;
+       char *hnf = hfrom->h_name, *hnt = hto->h_name;
+
+       if (delstyle == NODEL && !(p->r_flags & R_DELOK) && !(op & APPEND))
+               printf("%s%s -> %s%s : old %s%s would have to be %s.\n",
+                       hnf, f, hnt, t, hnt, t,
+                       (op & OVERWRITE) ? "overwritten" : "deleted");
+       else if (fto->fi_rep == MISTAKE)
+               printf("%s%s -> %s%s : old %s%s was to be done first.\n",
+                       hnf, f, hnt, t, hnt, t);
+       else if (
+#ifdef IS_MSDOS
+               fto->fi_attrib & FA_DIREC
+#else
+               fto->fi_stflags & FI_ISDIR
+#endif
+       )
+               printf("%s%s -> %s%s : %s%s%s is a directory.\n",
+                       hnf, f, hnt, t, (op & APPEND) ? "" : "old ", hnt, t);
+#ifndef IS_MSDOS
+       else if ((fto->fi_stflags & FI_NODEL) && !(op & (APPEND | OVERWRITE)))
+               printf("%s%s -> %s%s : old %s%s lacks delete permission.\n",
+                       hnf, f, hnt, t, hnt, t);
+#endif
+       else if (
+               (op & (APPEND | OVERWRITE)) &&
+#ifdef IS_MSDOS
+               fto->fi_attrib & FA_RDONLY
+#else
+               !fwritable(hnt, fto)
+#endif
+       ) {
+               printf("%s%s -> %s%s : %s%s %s.\n",
+                       hnf, f, hnt, t, hnt, t,
+#ifndef IS_MSDOS
+#ifdef S_IFLNK
+                       fto->fi_stflags & FI_LINKERR ?
+                       "is a badly aimed symbolic link" :
+#endif
+#endif
+                       "lacks write permission");
+       }
+       else
+               return(0);
+       badreps++;
+       return(1);
+}
+
+
+static int skipdel(p)
+       REP *p;
+{
+       if (p->r_flags & R_DELOK)
+               return(0);
+       fprintf(stderr, "%s%s -> %s%s : ",
+               p->r_hfrom->h_name, p->r_ffrom->fi_name,
+               p->r_hto->h_name, p->r_nto);
+       if (
+#ifdef IS_MSDOS
+               p->r_fdel->fi_attrib & FA_RDONLY
+#else
+#ifdef S_IFLNK
+               !(p->r_ffrom->fi_stflags & FI_ISLNK) &&
+#endif
+               !fwritable(p->r_hto->h_name, p->r_fdel)
+#endif
+       )
+               fprintf(stderr, "old %s%s lacks write permission. delete it",
+                       p->r_hto->h_name, p->r_nto);
+       else
+               fprintf(stderr, "%s old %s%s",
+                       (op & OVERWRITE) ? "overwrite" : "delete",
+                       p->r_hto->h_name, p->r_nto);
+       return(!getreply("? ", -1));
+}
+
+
+static void goonordie()
+{
+       if ((paterr || badreps) && nreps > 0) {
+               fprintf(stderr, "Not everything specified can be done.");
+               if (badstyle == ABORTBAD) {
+                       fprintf(stderr, " Aborting.\n");
+                       exit(1);
+               }
+               else if (badstyle == SKIPBAD)
+                       fprintf(stderr, " Proceeding with the rest.\n");
+               else if (!getreply(" Proceed with the rest? ", -1))
+                       exit(1);
+       }
+}
+
+
+static void doreps()
+{
+       char *fstart;
+       int k, printaliased = 0, alias;
+       REP *first, *p;
+       long aliaslen;
+
+#ifdef IS_MSDOS
+       ctrlbrk(breakrep);
+#else
+       signal(SIGINT, breakrep);
+#endif
+
+       for (first = hrep.r_next, k = 0; first != NULL; first = first->r_next) {
+               for (p = first; p != NULL; p = p->r_thendo, k++) {
+                       if (gotsig) {
+                               fflush(stdout);
+                               fprintf(stderr, "User break.\n");
+                               printaliased = snap(first, p);
+                               gotsig = 0;
+                       }
+                       strcpy(fullrep, p->r_hto->h_name);
+                       strcat(fullrep, p->r_nto);
+                       if (!noex && (p->r_flags & R_ISCYCLE))
+                               if (op & APPEND)
+                                       aliaslen = appendalias(first, p, &printaliased);
+                               else
+                                       alias = movealias(first, p, &printaliased);
+                       strcpy(pathbuf, p->r_hfrom->h_name);
+                       fstart = pathbuf + strlen(pathbuf);
+                       if ((p->r_flags & R_ISALIASED) && !(op & APPEND))
+                               sprintf(fstart, "%s%03d", TEMP, alias);
+                       else
+                               strcpy(fstart, p->r_ffrom->fi_name);
+                       if (!noex) {
+                               if (p->r_fdel != NULL && !(op & (APPEND | OVERWRITE)))
+                                       myunlink(fullrep, p->r_fdel);
+                               if (
+                                       (op & (COPY | APPEND)) ?
+                                               copy(p->r_ffrom,
+                                                       p->r_flags & R_ISALIASED ? aliaslen : -1L) :
+#ifndef IS_MSDOS
+                                       (op & HARDLINK) ?
+                                               link(pathbuf, fullrep) :
+#ifdef S_IFLNK
+                                       (op & SYMLINK) ?
+                                               symlink((p->r_flags & R_ONEDIRLINK) ? fstart : pathbuf,
+                                                       fullrep) :
+#endif
+#endif
+                                       p->r_flags & R_ISX ?
+                                               copymove(p) :
+                                       /* move */
+                                               rename(pathbuf, fullrep)
+                               ) {
+                                       fprintf(stderr,
+                                               "%s -> %s has failed.\n", pathbuf, fullrep);
+                                       printaliased = snap(first, p);
+                               }
+                       }
+                       if (verbose || noex) {
+                               if (p->r_flags & R_ISALIASED && !printaliased)
+                                       strcpy(fstart, p->r_ffrom->fi_name);
+                               fprintf(outfile, "%s %c%c %s%s%s\n",
+                                       pathbuf,
+                                       p->r_flags & R_ISALIASED ? '=' : '-',
+                                       p->r_flags & R_ISCYCLE ? '^' : '>',
+                                       fullrep,
+                                       (p->r_fdel != NULL && !(op & APPEND)) ? " (*)" : "",
+                                       noex ? "" : " : done");
+                       }
+               }
+               printaliased = 0;
+       }
+       if (k != nreps)
+               fprintf(stderr, "Strange, did %d reps; %d were expected.\n",
+                       k, nreps);
+       if (k == 0)
+               fprintf(stderr, "Nothing done.\n");
+}
+
+
+static long appendalias(first, p, pprintaliased)
+       REP *first, *p;
+       int *pprintaliased;
+{
+       long ret;
+
+#ifdef IS_MSDOS
+       int fd;
+
+       if ((fd = open(fullrep, O_RDONLY | O_BINARY, 0)) < 0) {
+               fprintf(stderr, "stat on %s has failed.\n", fullrep);
+               *pprintaliased = snap(first, p);
+       }
+       else {
+               ret = filelength(fd);
+               close(fd);
+       }
+#else
+       struct stat fstat;
+
+       if (stat(fullrep, &fstat)) {
+               fprintf(stderr, "append cycle stat on %s has failed.\n", fullrep);
+               *pprintaliased = snap(first, p);
+       }
+       else
+               ret = fstat.st_size;
+#endif
+
+       return(ret);
+}
+
+
+static int movealias(first, p, pprintaliased)
+       REP *first, *p;
+       int *pprintaliased;
+{
+       char *fstart;
+       int ret;
+
+       strcpy(pathbuf, p->r_hto->h_name);
+       fstart = pathbuf + strlen(pathbuf);
+       strcpy(fstart, TEMP);
+       for (
+               ret = 0;
+               sprintf(fstart + STRLEN(TEMP), "%03d", ret),
+               fsearch(fstart, p->r_hto->h_di) != NULL;
+               ret++
+       )
+               ;
+       if (rename(fullrep, pathbuf)) {
+               fprintf(stderr,
+                       "%s -> %s has failed.\n", fullrep, pathbuf);
+               *pprintaliased = snap(first, p);
+       }
+       return(ret);
+}
+
+
+static int snap(first, p)
+       REP *first, *p;
+{
+       char fname[80];
+       int redirected = 0;
+
+       if (noex)
+               exit(1);
+
+       failed = 1;
+#ifdef IS_MSDOS
+       ctrlbrk((int (*)())breakstat);
+#else
+       signal(SIGINT, breakstat);
+#endif
+       if (
+               badstyle == ASKBAD &&
+               isatty(fileno(stdout)) &&
+               getreply("Redirect standard output to file? ", 0)
+       ) {
+               redirected = 1;
+#ifndef IS_MSDOS
+               umask(oldumask);
+#endif
+               while (
+                       fprintf(stderr, "File name> "),
+                       (outfile = fopen(mygets(fname, 80), "w")) == NULL
+               )
+                       fprintf(stderr, "Can't open %s.\n", fname);
+       }
+       if (redirected || !verbose)
+               showdone(p);
+       fprintf(outfile, "The following left undone:\n");
+       noex = 1;
+       return(first != p);
+}
+
+
+static void showdone(fin)
+       REP *fin;
+{
+       REP *first, *p;
+
+       for (first = hrep.r_next; ; first = first->r_next)
+               for (p = first; p != NULL; p = p->r_thendo) {
+                       if (p == fin)
+                               return;
+                       fprintf(outfile, "%s%s %c%c %s%s : done%s\n",
+                               p->r_hfrom->h_name, p->r_ffrom->fi_name,
+                               p->r_flags & R_ISALIASED ? '=' : '-',
+                               p->r_flags & R_ISCYCLE ? '^' : '>',
+                               p->r_hto->h_name, p->r_nto,
+                               (p->r_fdel != NULL && !(op & APPEND)) ? " (*)" : "");
+               }
+}
+
+
+static void breakout()
+{
+       fflush(stdout);
+       fprintf(stderr, "Aborting, nothing done.\n");
+       exit(1);
+}
+
+
+static int breakrep()
+{
+       gotsig = 1;
+       return(1);
+}
+
+
+static void breakstat()
+{
+       exit(1);
+}
+
+
+static void quit()
+{
+       fprintf(stderr, "Aborting, nothing done.\n");
+       exit(1);
+}
+
+
+static int copymove(p)
+       REP *p;
+{
+#ifndef IS_MSDOS
+#ifndef IS_SYSV
+       {
+               int llen;
+               char linkbuf[MAXPATH];
+
+               if ((llen = readlink(pathbuf, linkbuf, MAXPATH - 1)) >= 0) {
+                       linkbuf[llen] = '\0';
+                       return(symlink(linkbuf, fullrep) || myunlink(pathbuf, p->r_ffrom));
+               }
+       }
+#endif
+#endif
+       return(copy(p->r_ffrom, -1L) || myunlink(pathbuf, p->r_ffrom));
+}
+
+
+
+#define IRWMASK (S_IREAD | S_IWRITE)
+#define RWMASK (IRWMASK | (IRWMASK >> 3) | (IRWMASK >> 6))
+
+static int copy(ff, len)
+       FILEINFO *ff;
+       long len;
+{
+       char buf[BUFSIZE], c;
+       int f, t, k, mode, perm;
+#ifdef IS_MSDOS
+       struct ftime tim;
+#else
+#ifdef IS_SYSV
+       struct utimbuf tim;
+#else
+       struct timeval tim[2];
+#endif
+       struct stat fstat;
+#endif
+
+       if ((f = open(pathbuf, O_RDONLY | O_BINARY, 0)) < 0)
+               return(-1);
+       perm =
+#ifdef IS_MSDOS
+               IRWMASK         /* will _chmod it later (to get all the attributes) */
+#else
+               (op & (APPEND | OVERWRITE)) ?
+                       (~oldumask & RWMASK) | (ff->fi_mode & ~RWMASK) :
+                       ff->fi_mode
+#endif
+               ;
+
+#ifdef IS_V7
+       if (
+               !(op & APPEND) ||
+               (((t = open(fullrep, O_RDWR)) < 0 && errno == ENOENT)
+       )
+               t = creat(fullrep, perm);
+#else
+       mode = O_CREAT | (op & APPEND ? 0 : O_TRUNC) |
+#ifdef IS_MSDOS
+               O_BINARY | (op & ZAPPEND ? O_RDWR : O_WRONLY)
+#else
+               O_WRONLY
+#endif
+               ;
+       t = open(fullrep, mode, perm);
+#endif
+       if (t < 0) {
+               close(f);
+               return(-1);
+       }
+       if (op & APPEND)
+               lseek(t, 0L, 2);
+#ifdef IS_MSDOS
+       if (op & ZAPPEND && filelength(t) != 0) {
+               if (lseek(t, -1L, 1) == -1L || read(t, &c, 1) != 1) {
+                       close(f);
+                       close(t);
+                       return(-1);
+               }
+               if (c == 26)
+                       lseek(t, -1L, 1);
+       }
+#endif
+       if ((op & APPEND) && len != -1L) {
+               while (
+                       len != 0 &&
+                       (k = read(f, buf, len > BUFSIZE ? BUFSIZE : (unsigned)len)) > 0 &&
+                       write(t, buf, k) == k
+               )
+                       len -= k;
+               if (len == 0)
+                       k = 0;
+       }
+       else 
+               while ((k = read(f, buf, BUFSIZE)) > 0 && write(t, buf, k) == k)
+                       ;
+       if (!(op & (APPEND | OVERWRITE)))
+               if (
+#ifdef IS_MSDOS
+                       getftime(f, &tim) ||
+                       setftime(t, &tim) ||
+                       _chmod(fullrep, 1, ff->fi_attrib) == -1
+#else
+                       stat(pathbuf, &fstat) ||
+                       (
+#ifdef IS_SYSV
+                               tim.actime = fstat.st_atime,
+                               tim.modtime = fstat.st_mtime,
+#else
+                               tim[0].tv_sec = fstat.st_atime,
+                               tim[1].tv_sec = fstat.st_mtime,
+#endif
+                               utimes(fullrep, tim)
+                       )
+#endif
+               )
+                       fprintf(stderr, "Strange, couldn't transfer time from %s to %s.\n",
+                               pathbuf, fullrep);
+
+       close(f);
+       close(t);
+       if (k != 0) {
+               if (!(op & APPEND))
+                       unlink(fullrep);
+               return(-1);
+       }
+       return(0);
+}
+
+#ifdef MV_DIR
+
+#include <errno.h>
+extern int errno;
+
+static int rename(from, to)
+       char *from, *to;
+{
+       int pid;
+
+       if (link(from, to) == 0 && unlink(from) == 0)
+           return(0);
+       else {
+               struct stat s;
+               if (stat(from, &s) < 0 || (s.st_mode&S_IFMT) != S_IFDIR)
+                       return(-1);
+       }
+
+       do pid = fork(); while (pid >= 0 && errno == EAGAIN);
+
+       if (pid < 0)
+           return(-1);
+       else if (pid == 0) {
+           execl(MV_DIR, "mv_dir", from, to, (char *) 0);
+           perror(MV_DIR);
+           exit(errno);
+       } else if (pid > 0) {
+           int wid;
+           int status;
+
+           do wid = wait(&status);
+           while (wid != pid && wid >= 0);
+
+           return(status == 0 ? 0 : -1);
+       }
+}
+#else
+#ifndef HAS_RENAME
+static int rename(from, to)
+       char *from, *to;
+{
+       if (link(from, to))
+               return(-1);
+       if (unlink(from)) {
+               unlink(to);
+               return(-1);
+       }
+       return(0);
+}
+#endif
+#endif /* MV_DIR */
+
+static int myunlink(n, f)
+       char *n;
+       FILEINFO *f;
+{
+#ifdef IS_MSDOS
+       int a;
+
+       if (((a = f->fi_attrib) & FA_RDONLY) && _chmod(n, 1, a & ~FA_RDONLY) < 0) {
+               fprintf(stderr, "Strange, can not _chmod (or unlink) %s.\n", f);
+               return(-1);
+       }
+#endif
+       if (unlink(n)) {
+               fprintf(stderr, "Strange, can not unlink %s.\n", n);
+               return(-1);
+       }
+       return(0);
+}
+
+
+static int getreply(m, failact)
+       char *m;
+       int failact;
+{
+       static FILE *tty = NULL;
+       int c, r;
+
+       fprintf(stderr, m);
+       if (tty == NULL && (tty = fopen(TTY, "r")) == NULL) {
+               fprintf(stderr, "Can not open %s to get reply.\n", TTY);
+               if (failact == -1)
+                       quit();
+               else
+                       return(failact);
+       }
+       for (;;) {
+               r = fgetc(tty);
+               if (r == EOF) {
+                       fprintf(stderr, "Can not get reply.\n");
+                       if (failact == -1)
+                               quit();
+                       else
+                               return(failact);
+               }
+               if (r != '\n')
+                       while ((c = fgetc(tty)) != '\n' && c != EOF)
+                               ;
+               r = mylower(r);
+               if (r == 'y' || r == 'n')
+                       return(r == 'y');
+               fprintf(stderr, "Yes or No? ");
+       }
+}
+
+
+static void *myalloc(k)
+       unsigned k;
+{
+       void *ret;
+
+       if (k == 0)
+               return(NULL);
+       if ((ret = (void *)malloc(k)) == NULL) {
+               fprintf(stderr, "Insufficient memory.\n");
+               quit();
+       }
+       return(ret);
+}
+
+
+static void *challoc(k, which)
+       int which;
+       int k;
+{
+       void *ret;
+       CHUNK *p, *q;
+       SLICER *sl = &(slicer[which]);
+
+       if (k > sl->sl_len) {
+               for (
+                       q = NULL, p = freechunks;
+                       p != NULL && (sl->sl_len = p->ch_len) < k;
+                       q = p, p = p->ch_next
+               )
+                       ;
+               if (p == NULL) {
+                       sl->sl_len = CHUNKSIZE - sizeof(CHUNK *);
+                       p = (CHUNK *)myalloc(CHUNKSIZE);
+               }
+               else if (q == NULL)
+                       freechunks = p->ch_next;
+               else
+                       q->ch_next = p->ch_next;
+               p->ch_next = sl->sl_first;
+               sl->sl_first = p;
+               sl->sl_unused = (char *)&(p->ch_len);
+       }
+       sl->sl_len -= k;
+       ret = (void *)sl->sl_unused;
+       sl->sl_unused += k;
+       return(ret);
+}
+
+
+static void chgive(p, k)
+       void *p;
+       unsigned k;
+{
+       ((CHUNK *)p)->ch_len = k - sizeof(CHUNK *);
+       ((CHUNK *)p)->ch_next = freechunks;
+       freechunks = (CHUNK *)p;
+}
+
+
+#ifndef __STDC__
+#ifndef IS_MSDOS
+#ifndef IS_SYSV
+static void memmove(to, from, k)
+       char *to, *from;
+       unsigned k;
+{
+       if (from > to)
+               while (k-- != 0)
+                       *(to++) = *(from++);
+       else {
+               from += k;
+               to += k;
+               while (k-- != 0)
+                       *(--to) = *(--from);
+       }
+}
+#endif
+#endif
+#endif
+
+
+static int mygetc()
+{
+       static int lastc = 0;
+
+       if (lastc == EOF)
+               return(EOF);
+       return(lastc = getchar());
+}
+
+
+static char *mygets(s, l)
+       char *s;
+       int l;
+{
+       char *nl;
+
+       for (;;) {
+               if (fgets(s, l, stdin) == NULL)
+                       return(NULL);
+               if ((nl = strchr(s, '\n')) != NULL)
+                       break;
+               fprintf(stderr, "Input string too long. Try again> ");
+       }
+       *nl = '\0';
+       return(s);
+}
+
+
+#ifdef IS_MSDOS
+static int leave()
+{
+       return(0);
+}
+
+static void cleanup()
+{
+       int i;
+
+       if (patch.ph_safeid) {
+               for (i = 0; i < nhandles; i++) {
+                       if (!(handles[i]->h_di->di_flags & DI_CLEANED)) {
+                               sprintf(pathbuf, "%s%s%03d",
+                                       handles[i]->h_name, IDF, handles[i]->h_di->di_did);
+                               if (unlink(pathbuf))
+                                       fprintf(stderr, "Strange, couldn't unlink %s.\n", pathbuf);
+                               handles[i]->h_di->di_flags |= DI_CLEANED;
+                       }
+               }
+       }
+/*
+       Write device availability: undocumented internal MS-D*S function.
+       Restore previous value.
+*/
+       bdos(0x37, olddevflag, 3);
+}
+
+#endif