/bin/mv: Sync with FreeBSD
authorJohn Marino <draco@marino.st>
Fri, 16 Nov 2012 09:21:09 +0000 (10:21 +0100)
committerJohn Marino <draco@marino.st>
Fri, 16 Nov 2012 09:27:32 +0000 (10:27 +0100)
1. Utilize central <paths.h> header
2. Meet posix spec for moving directory to existing non-empty directory
3. Meet posix spec for not moving directory over a file
4. Don't chop IO into pieces, use MAXPHYS
5. Add -h flag (similar to ln -h flag)
6. Test for input_is_terminal
7. Regression test added.

DETAILS
==========================================================================
1. remove pathnames.h file and add _PATH_RM to paths.h
2. Moving a directory to an existing non-empty directory will now fail as
   required.
3. Prior to change this test past, but after change it failed on
   cross-link devices.  Original code added to check for case and return
   -1 for the cross-link case.
4. Read files in one block and improve warn messages.
5. Using -h flag will force mv to treat a symbolic link to a directory
   for the target as a symbolic link instead of a directory.  This makes
   it possible to atomically update a symbolic link using rename().
6. POSIX requires this behavior:
   if (exists and (NOT f_options) AND
     ((not_writable AND input_is_terminal) OR i_option)) then
     prompt
   This behavior was implemented.
7. Passes all tests in suite.  Prior to change it failed 2 of 32 tests.

bin/mv/mv.1
bin/mv/mv.c
bin/mv/pathnames.h [deleted file]
include/paths.h
tools/regression/bin/mv/Makefile [new file with mode: 0644]
tools/regression/bin/mv/regress.sh [new file with mode: 0644]
tools/regression/bin/mv/regress.t [new file with mode: 0644]

index 19a8b8a..31eb6ec 100644 (file)
 .\" 2. Redistributions in binary form must reproduce the above copyright
 .\"    notice, this list of conditions and the following disclaimer in the
 .\"    documentation and/or other materials provided with the distribution.
-.\" 3. All advertising materials mentioning features or use of this software
-.\"    must display the following acknowledgement:
-.\"    This product includes software developed by the University of
-.\"    California, Berkeley and its contributors.
 .\" 4. Neither the name of the University nor the names of its contributors
 .\"    may be used to endorse or promote products derived from this software
 .\"    without specific prior written permission.
 .\" SUCH DAMAGE.
 .\"
 .\"    @(#)mv.1        8.1 (Berkeley) 5/31/93
-.\" $FreeBSD: src/bin/mv/mv.1,v 1.15.2.5 2003/01/24 02:29:13 keramida Exp $
-.\" $DragonFly: src/bin/mv/mv.1,v 1.4 2006/11/11 12:05:36 victor Exp $
+.\" $FreeBSD$
 .\"
-.Dd July 9, 2002
+.Dd November 16, 2012
 .Dt MV 1
 .Os
 .Sh NAME
@@ -45,7 +40,7 @@
 .Sh SYNOPSIS
 .Nm
 .Op Fl f | i | n
-.Op Fl v
+.Op Fl hv
 .Ar source target
 .Nm
 .Op Fl f | i | n
@@ -85,6 +80,21 @@ option overrides any previous
 or
 .Fl n
 options.)
+.It Fl h
+If the
+.Ar target
+operand is a symbolic link to a directory,
+do not follow it.
+This causes the
+.Nm
+utility to rename the file
+.Ar source
+to the destination path
+.Ar target
+rather than moving
+.Ar source
+into the directory referenced by
+.Ar target .
 .It Fl i
 Cause
 .Nm
@@ -117,9 +127,9 @@ Cause
 to be verbose, showing files after they are moved.
 .El
 .Pp
-It is an error for either the
+It is an error for the
 .Ar source
-operand or the destination path to specify a directory unless both do.
+operand to specify a directory if the target exists and is not a directory.
 .Pp
 If the destination path does not have a mode which permits writing,
 .Nm
@@ -157,7 +167,8 @@ will be written to standard error.
 .Ex -std
 .Sh COMPATIBILITY
 The
-.Fl n
+.Fl h ,
+.Fl n ,
 and
 .Fl v
 options are non-standard and their use in scripts is not recommended.
index de898e8..6c7d6a3 100644 (file)
@@ -1,4 +1,4 @@
-/*
+/*-
  * Copyright (c) 1989, 1993, 1994
  *     The Regents of the University of California.  All rights reserved.
  *
@@ -13,7 +13,7 @@
  * 2. Redistributions in binary form must reproduce the above copyright
  *    notice, this list of conditions and the following disclaimer in the
  *    documentation and/or other materials provided with the distribution.
- * 3. Neither the name of the University nor the names of its contributors
+ * 4. Neither the name of the University nor the names of its contributors
  *    may be used to endorse or promote products derived from this software
  *    without specific prior written permission.
  *
  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  * SUCH DAMAGE.
- *
- * @(#) Copyright (c) 1989, 1993, 1994 The Regents of the University of California.  All rights reserved.
- * @(#)mv.c    8.2 (Berkeley) 4/2/94
- * $FreeBSD: /usr/local/www/cvsroot/FreeBSD/src/bin/mv/mv.c,v 1.24.2.6 2004/03/24 08:34:36 pjd Exp $
- * $DragonFly: src/bin/mv/mv.c,v 1.14 2007/06/15 07:02:51 corecode Exp $
  */
 
 #include <sys/param.h>
@@ -46,6 +41,7 @@
 #include <fcntl.h>
 #include <grp.h>
 #include <limits.h>
+#include <paths.h>
 #include <pwd.h>
 #include <signal.h>
 #include <stdio.h>
 #include <sysexits.h>
 #include <unistd.h>
 
-#include "pathnames.h"
-
-#define        mv_pct(x,y)     (int)(100.0 * (double)(x) / (double)(y))
+/* Exit code for a failed exec. */
+#define EXEC_FAILED 127
 
-static int     fflg, iflg, nflg, vflg;
+static int     fflg, hflg, iflg, nflg, vflg;
 volatile sig_atomic_t info;
 
 static int     copy(const char *, const char *);
 static int     do_move(const char *, const char *);
 static int     fastcopy(const char *, const char *, struct stat *);
-static void    siginfo(int);
 static void    usage(void);
+static void    siginfo(int);
 
 int
-main(int argc, char **argv)
+main(int argc, char *argv[])
 {
        size_t baselen, len;
        int rval;
@@ -77,8 +72,11 @@ main(int argc, char **argv)
        int ch;
        char path[PATH_MAX];
 
-       while ((ch = getopt(argc, argv, "finv")) != -1)
+       while ((ch = getopt(argc, argv, "fhinv")) != -1)
                switch (ch) {
+               case 'h':
+                       hflg = 1;
+                       break;
                case 'i':
                        iflg = 1;
                        fflg = nflg = 0;
@@ -115,10 +113,21 @@ main(int argc, char **argv)
                exit(do_move(argv[0], argv[1]));
        }
 
+       /*
+        * If -h was specified, treat the target as a symlink instead of
+        * directory.
+        */
+       if (hflg) {
+               if (argc > 2)
+                       usage();
+               if (lstat(argv[1], &sb) == 0 && S_ISLNK(sb.st_mode))
+                       exit(do_move(argv[0], argv[1]));
+       }
+
        /* It's a directory, move each file into it. */
        if (strlen(argv[argc - 1]) > sizeof(path) - 1)
                errx(1, "%s: destination pathname too long", *argv);
-       strcpy(path, argv[argc - 1]);
+       (void)strcpy(path, argv[argc - 1]);
        baselen = strlen(path);
        endp = &path[baselen];
        if (!baselen || *(endp - 1) != '/') {
@@ -163,7 +172,7 @@ do_move(const char *from, const char *to)
        if (!fflg && !access(to, F_OK)) {
 
                /* prompt only if source exist */
-               if (lstat(from, &sb) == -1) {
+               if (lstat(from, &sb) == -1) {
                        warn("%s", from);
                        return (1);
                }
@@ -175,11 +184,11 @@ do_move(const char *from, const char *to)
                                printf("%s not overwritten\n", to);
                        return (0);
                } else if (iflg) {
-                       fprintf(stderr, "overwrite %s? %s", to, YESNO);
+                       (void)fprintf(stderr, "overwrite %s? %s", to, YESNO);
                        ask = 1;
-               } else if (access(to, W_OK) && !stat(to, &sb)) {
+               } else if (access(to, W_OK) && !stat(to, &sb) && isatty(STDIN_FILENO)) {
                        strmode(sb.st_mode, modep);
-                       fprintf(stderr, "override %s%s%s/%s for %s? %s",
+                       (void)fprintf(stderr, "override %s%s%s/%s for %s? %s",
                            modep + 1, modep[9] == ' ' ? "" : " ",
                            user_from_uid((unsigned long)sb.st_uid, 0),
                            group_from_gid((unsigned long)sb.st_gid, 0), to, YESNO);
@@ -190,11 +199,16 @@ do_move(const char *from, const char *to)
                        while (ch != '\n' && ch != EOF)
                                ch = getchar();
                        if (first != 'y' && first != 'Y') {
-                               fprintf(stderr, "not overwritten\n");
+                               (void)fprintf(stderr, "not overwritten\n");
                                return (0);
                        }
                }
        }
+       /*
+        * Rename on FreeBSD will fail with EISDIR and ENOTDIR, before failing
+        * with EXDEV.  Therefore, copy() doesn't have to perform the checks
+        * specified in the Step 3 of the POSIX mv specification.
+        */
        if (!rename(from, to)) {
                if (vflg)
                        printf("%s -> %s\n", from, to);
@@ -204,7 +218,10 @@ do_move(const char *from, const char *to)
        if (errno == EXDEV) {
                struct statfs sfs;
                char path[PATH_MAX];
+               int target_is_file = 0;
 
+               if (!lstat(to, &sb) && S_ISREG(sb.st_mode))
+                       target_is_file = 1;
                /*
                 * If the source is a symbolic link and is on another
                 * filesystem, it can be recreated at the destination.
@@ -213,10 +230,15 @@ do_move(const char *from, const char *to)
                        warn("%s", from);
                        return (1);
                }
+               /* Can't mv(1) directory into a file ever. */
+               if (target_is_file && S_ISDIR(sb.st_mode)) {
+                       warn("cannot overwrite %s with directory", to);
+                       return (1);
+               }
                if (!S_ISLNK(sb.st_mode)) {
                        /* Can't mv(1) a mount point. */
                        if (realpath(from, path) == NULL) {
-                               warnx("cannot resolve %s: %s", from, path);
+                               warn("cannot resolve %s: %s", from, path);
                                return (1);
                        }
                        if (!statfs(path, &sfs) &&
@@ -247,60 +269,41 @@ static int
 fastcopy(const char *from, const char *to, struct stat *sbp)
 {
        struct timeval tval[2];
-       static u_int blen;
-       static char *bp;
+       static u_int blen = MAXPHYS;
+       static char *bp = NULL;
        mode_t oldmode;
        int from_fd, to_fd;
-       size_t nread, wcount;
-       off_t wtotal = 0;
+       ssize_t nread;
 
        if ((from_fd = open(from, O_RDONLY, 0)) < 0) {
-               warn("%s", from);
+               warn("fastcopy: open() failed (from): %s", from);
                return (1);
        }
-       if (blen < sbp->st_blksize) {
-               if (bp != NULL)
-                       free(bp);
-               if ((bp = malloc((size_t)sbp->st_blksize)) == NULL) {
-                       blen = 0;
-                       warnx("malloc failed");
-                       return (1);
-               }
-               blen = sbp->st_blksize;
+       if (bp == NULL && (bp = malloc((size_t)blen)) == NULL) {
+               warnx("malloc(%u) failed", blen);
+               return (1);
        }
        while ((to_fd =
            open(to, O_CREAT | O_EXCL | O_TRUNC | O_WRONLY, 0)) < 0) {
                if (errno == EEXIST && unlink(to) == 0)
                        continue;
-               warn("%s", to);
-               close(from_fd);
+               warn("fastcopy: open() failed (to): %s", to);
+               (void)close(from_fd);
                return (1);
        }
-       while ((ssize_t)(nread = read(from_fd, bp, (size_t)blen)) > 0) {
-               wcount = write(to_fd, bp, nread);
-               wtotal += wcount;
-
-               if (wcount != nread) {
-                       warn("%s", to);
+       while ((nread = read(from_fd, bp, (size_t)blen)) > 0)
+               if (write(to_fd, bp, (size_t)nread) != nread) {
+                       warn("fastcopy: write() failed: %s", to);
                        goto err;
                }
-
-               if (info) {
-                       info = 0;
-                       fprintf(stderr, "%s -> %s %3d%%\n", 
-                                       from, to, 
-                                       mv_pct(wtotal, sbp->st_size));
-               }
-       }
-       if ((ssize_t)nread == -1) {
-               warn("%s", from);
+       if (nread < 0) {
+               warn("fastcopy: read() failed: %s", from);
 err:           if (unlink(to))
                        warn("%s: remove", to);
-               close(from_fd);
-               close(to_fd);
+               (void)close(from_fd);
+               (void)close(to_fd);
                return (1);
        }
-       close(from_fd);
 
        oldmode = sbp->st_mode & ALLPERMS;
        if (fchown(to_fd, sbp->st_uid, sbp->st_gid)) {
@@ -315,6 +318,8 @@ err:                if (unlink(to))
        }
        if (fchmod(to_fd, sbp->st_mode))
                warn("%s: set mode (was: 0%03o)", to, oldmode);
+
+       (void)close(from_fd);
        /*
         * XXX
         * NFS doesn't support chflags; ignore errors unless there's reason
@@ -350,44 +355,76 @@ err:              if (unlink(to))
 static int
 copy(const char *from, const char *to)
 {
-       int status;
-       pid_t pid;
+       struct stat sb;
+       int pid, status;
+
+       if (lstat(to, &sb) == 0) {
+               /* Destination path exists. */
+               if (S_ISDIR(sb.st_mode)) {
+                       if (rmdir(to) != 0) {
+                               warn("rmdir %s", to);
+                               return (1);
+                       }
+               } else {
+                       if (unlink(to) != 0) {
+                               warn("unlink %s", to);
+                               return (1);
+                       }
+               }
+       } else if (errno != ENOENT) {
+               warn("%s", to);
+               return (1);
+       }
 
-       if ((pid = fork()) == 0) {
+       /* Copy source to destination. */
+       if (!(pid = vfork())) {
                execl(_PATH_CP, "mv", vflg ? "-PRpv" : "-PRp", "--", from, to,
-                   NULL);
-               warn("%s", _PATH_CP);
-               _exit(1);
+                   (char *)NULL);
+               _exit(EXEC_FAILED);
        }
        if (waitpid(pid, &status, 0) == -1) {
-               warn("%s: waitpid", _PATH_CP);
+               warn("%s %s %s: waitpid", _PATH_CP, from, to);
                return (1);
        }
        if (!WIFEXITED(status)) {
-               warnx("%s: did not terminate normally", _PATH_CP);
+               warnx("%s %s %s: did not terminate normally",
+                   _PATH_CP, from, to);
                return (1);
        }
-       if (WEXITSTATUS(status)) {
-               warnx("%s: terminated with %d (non-zero) status",
-                   _PATH_CP, WEXITSTATUS(status));
+       switch (WEXITSTATUS(status)) {
+       case 0:
+               break;
+       case EXEC_FAILED:
+               warnx("%s %s %s: exec failed", _PATH_CP, from, to);
+               return (1);
+       default:
+               warnx("%s %s %s: terminated with %d (non-zero) status",
+                   _PATH_CP, from, to, WEXITSTATUS(status));
                return (1);
        }
+
+       /* Delete the source. */
        if (!(pid = vfork())) {
-               execl(_PATH_RM, "mv", "-rf", "--", from, NULL);
-               warn("%s", _PATH_RM);
-               _exit(1);
+               execl(_PATH_RM, "mv", "-rf", "--", from, (char *)NULL);
+               _exit(EXEC_FAILED);
        }
        if (waitpid(pid, &status, 0) == -1) {
-               warn("%s: waitpid", _PATH_RM);
+               warn("%s %s: waitpid", _PATH_RM, from);
                return (1);
        }
        if (!WIFEXITED(status)) {
-               warnx("%s: did not terminate normally", _PATH_RM);
+               warnx("%s %s: did not terminate normally", _PATH_RM, from);
                return (1);
        }
-       if (WEXITSTATUS(status)) {
-               warnx("%s: terminated with %d (non-zero) status",
-                   _PATH_RM, WEXITSTATUS(status));
+       switch (WEXITSTATUS(status)) {
+       case 0:
+               break;
+       case EXEC_FAILED:
+               warnx("%s %s: exec failed", _PATH_RM, from);
+               return (1);
+       default:
+               warnx("%s %s: terminated with %d (non-zero) status",
+                   _PATH_RM, from, WEXITSTATUS(status));
                return (1);
        }
        return (0);
@@ -397,8 +434,8 @@ static void
 usage(void)
 {
 
-       fprintf(stderr, "%s\n%s\n",
-                     "usage: mv [-f | -i | -n] [-v] source target",
+       (void)fprintf(stderr, "%s\n%s\n",
+                     "usage: mv [-f | -i | -n] [-hv] source target",
                      "       mv [-f | -i | -n] [-v] source ... directory");
        exit(EX_USAGE);
 }
diff --git a/bin/mv/pathnames.h b/bin/mv/pathnames.h
deleted file mode 100644 (file)
index cce60e4..0000000
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (c) 1989, 1993
- *     The Regents of the University of California.  All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- *    notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- *    notice, this list of conditions and the following disclaimer in the
- *    documentation and/or other materials provided with the distribution.
- * 3. Neither the name of the University nor the names of its contributors
- *    may be used to endorse or promote products derived from this software
- *    without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
- * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
- * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
- * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
- * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
- * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
- * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
- * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
- * SUCH DAMAGE.
- *
- *     @(#)pathnames.h 8.1 (Berkeley) 5/31/93
- * $FreeBSD: src/bin/mv/pathnames.h,v 1.5 1999/08/27 23:14:37 peter Exp $
- * $DragonFly: src/bin/mv/pathnames.h,v 1.3 2004/08/19 17:36:42 dillon Exp $
- */
-
-#define        _PATH_RM        "/bin/rm"
-#define        _PATH_CP        "/bin/cp"
index 68ca75a..dc96586 100644 (file)
@@ -71,6 +71,7 @@
 #define        _PATH_NOLOGIN   "/var/run/nologin"
 #define        _PATH_RCP       "/bin/rcp"
 #define        _PATH_RLOGIN    "/usr/bin/rlogin"
+#define        _PATH_RM        "/bin/rm"
 #define        _PATH_RSH       "/usr/bin/rsh"
 #define        _PATH_SENDMAIL  "/usr/sbin/sendmail"
 #define        _PATH_SHELLS    "/etc/shells"
diff --git a/tools/regression/bin/mv/Makefile b/tools/regression/bin/mv/Makefile
new file mode 100644 (file)
index 0000000..2c9ca59
--- /dev/null
@@ -0,0 +1,4 @@
+# $FreeBSD$
+
+all:
+       sh regress.sh
diff --git a/tools/regression/bin/mv/regress.sh b/tools/regression/bin/mv/regress.sh
new file mode 100644 (file)
index 0000000..d0a5e83
--- /dev/null
@@ -0,0 +1,296 @@
+#!/bin/sh
+# $FreeBSD$
+
+# A directory in a device different from that where the tests are run
+TMPDIR=/tmp/regress.$$
+COUNT=0
+
+# Begin an individual test
+begin()
+{
+       COUNT=`expr $COUNT + 1`
+       OK=1
+       if [ -z "$FS" ]
+       then
+               NAME="$1"
+       else
+               NAME="$1 (cross device)"
+       fi
+       rm -rf testdir $TMPDIR/testdir
+       mkdir -p testdir $TMPDIR/testdir
+       cd testdir
+}
+
+# End an individual test
+end()
+{
+       if [ $OK = 1 ]
+       then
+               printf 'ok '
+       else
+               printf 'not ok '
+       fi
+       echo "$COUNT - $NAME"
+       cd ..
+       rm -rf testdir $TMPDIR/testdir
+}
+
+# Make a file that can later be verified
+mkf()
+{
+       CN=`basename $1`
+       echo "$CN-$CN" >$1
+}
+
+# Verify that the file specified is correct
+ckf()
+{
+       if [ -f $2 ] && echo "$1-$1" | diff - $2 >/dev/null
+       then
+               ok
+       else
+               notok
+       fi
+}
+
+# Make a fifo that can later be verified
+mkp()
+{
+       mkfifo $1
+}
+
+# Verify that the file specified is correct
+ckp()
+{
+       if [ -p $2 ]
+       then
+               ok
+       else
+               notok
+       fi
+}
+
+# Make a directory that can later be verified
+mkd()
+{
+       CN=`basename $1`
+       mkdir -p $1/"$CN-$CN"
+}
+
+# Verify that the directory specified is correct
+ckd()
+{
+       if [ -d $2/$1-$1 ]
+       then
+               ok
+       else
+               notok
+       fi
+}
+
+# Verify that the specified file does not exist
+# (is not there)
+cknt()
+{
+       if [ -r $1 ]
+       then
+               notok
+       else
+               ok
+       fi
+}
+
+# A part of a test succeeds
+ok()
+{
+       :
+}
+
+# A part of a test fails
+notok()
+{
+       OK=0
+}
+
+# Verify that the exit code passed is for unsuccessful termination
+ckfail()
+{
+       if [ $1 -gt 0 ]
+       then
+               ok
+       else
+               notok
+       fi
+}
+
+# Verify that the exit code passed is for successful termination
+ckok()
+{
+       if [ $1 -eq 0 ]
+       then
+               ok
+       else
+               notok
+       fi
+}
+
+# Run all tests locally and across devices
+echo 1..32
+for FS in '' $TMPDIR/testdir/
+do
+       begin 'Rename file'
+       mkf fa
+       mv fa ${FS}fb
+       ckok $?
+       ckf fa ${FS}fb
+       cknt fa
+       end
+
+       begin 'Move files into directory'
+       mkf fa
+       mkf fb
+       mkdir -p ${FS}1/2/3
+       mv fa fb ${FS}1/2/3
+       ckok $?
+       ckf fa ${FS}1/2/3/fa
+       ckf fb ${FS}1/2/3/fb
+       cknt fa
+       cknt fb
+       end
+
+       begin 'Move file from directory to file'
+       mkdir -p 1/2/3
+       mkf 1/2/3/fa
+       mv 1/2/3/fa ${FS}fb
+       ckok $?
+       ckf fa ${FS}fb
+       cknt 1/2/3/fa
+       end
+
+       begin 'Move file from directory to existing file'
+       mkdir -p 1/2/3
+       mkf 1/2/3/fa
+       :> ${FS}fb
+       mv 1/2/3/fa ${FS}fb
+       ckok $?
+       ckf fa ${FS}fb
+       cknt 1/2/3/fa
+       end
+
+       begin 'Move file from directory to existing directory'
+       mkdir -p 1/2/3
+       mkf 1/2/3/fa
+       mkdir -p ${FS}db/fa
+       # Should fail per POSIX step 3a:
+       # Destination path is a file of type directory and
+       # source_file is not a file of type directory
+       mv 1/2/3/fa ${FS}db 2>/dev/null
+       ckfail $?
+       ckf fa 1/2/3/fa
+       end
+
+       begin 'Move file from directory to directory'
+       mkdir -p da1/da2/da3
+       mkdir -p ${FS}db1/db2/db3
+       mkf da1/da2/da3/fa
+       mv da1/da2/da3/fa ${FS}db1/db2/db3/fb
+       ckok $?
+       ckf fa ${FS}db1/db2/db3/fb
+       cknt da1/da2/da3/fa
+       end
+
+       begin 'Rename directory'
+       mkd da
+       mv da ${FS}db
+       ckok $?
+       ckd da ${FS}db
+       cknt da
+       end
+
+       begin 'Move directory to directory name'
+       mkd da1/da2/da3/da
+       mkdir -p ${FS}db1/db2/db3
+       mv da1/da2/da3/da ${FS}db1/db2/db3/db
+       ckok $?
+       ckd da ${FS}db1/db2/db3/db
+       cknt da1/da2/da3/da
+       end
+
+       begin 'Move directory to directory'
+       mkd da1/da2/da3/da
+       mkdir -p ${FS}db1/db2/db3
+       mv da1/da2/da3/da ${FS}db1/db2/db3
+       ckok $?
+       ckd da ${FS}db1/db2/db3/da
+       cknt da1/da2/da3/da
+       end
+
+       begin 'Move directory to existing empty directory'
+       mkd da1/da2/da3/da
+       mkdir -p ${FS}db1/db2/db3/da
+       mv da1/da2/da3/da ${FS}db1/db2/db3
+       ckok $?
+       ckd da ${FS}db1/db2/db3/da
+       cknt da1/da2/da3/da
+       end
+
+       begin 'Move directory to existing non-empty directory'
+       mkd da1/da2/da3/da
+       mkdir -p ${FS}db1/db2/db3/da/full
+       # Should fail (per the semantics of rename(2))
+       mv da1/da2/da3/da ${FS}db1/db2/db3 2>/dev/null
+       ckfail $?
+       ckd da da1/da2/da3/da
+       end
+
+       begin 'Move directory to existing file'
+       mkd da1/da2/da3/da
+       mkdir -p ${FS}db1/db2/db3
+       :> ${FS}db1/db2/db3/da
+       # Should fail per POSIX step 3b:
+       # Destination path is a file not of type directory
+       # and source_file is a file of type directory
+       mv da1/da2/da3/da ${FS}db1/db2/db3/da 2>/dev/null
+       ckfail $?
+       ckd da da1/da2/da3/da
+       end
+
+       begin 'Rename fifo'
+       mkp fa
+       mv fa ${FS}fb
+       ckok $?
+       ckp fa ${FS}fb
+       cknt fa
+       end
+
+       begin 'Move fifos into directory'
+       mkp fa
+       mkp fb
+       mkdir -p ${FS}1/2/3
+       mv fa fb ${FS}1/2/3
+       ckok $?
+       ckp fa ${FS}1/2/3/fa
+       ckp fb ${FS}1/2/3/fb
+       cknt fa
+       cknt fb
+       end
+
+       begin 'Move fifo from directory to fifo'
+       mkdir -p 1/2/3
+       mkp 1/2/3/fa
+       mv 1/2/3/fa ${FS}fb
+       ckok $?
+       ckp fa ${FS}fb
+       cknt 1/2/3/fa
+       end
+
+       begin 'Move fifo from directory to directory'
+       mkdir -p da1/da2/da3
+       mkdir -p ${FS}db1/db2/db3
+       mkp da1/da2/da3/fa
+       mv da1/da2/da3/fa ${FS}db1/db2/db3/fb
+       ckok $?
+       ckp fa ${FS}db1/db2/db3/fb
+       cknt da1/da2/da3/fa
+       end
+done
diff --git a/tools/regression/bin/mv/regress.t b/tools/regression/bin/mv/regress.t
new file mode 100644 (file)
index 0000000..c36d834
--- /dev/null
@@ -0,0 +1,6 @@
+#!/bin/sh
+# $FreeBSD$
+
+cd `dirname $0`
+
+sh regress.sh