Skip site navigation (1)Skip section navigation (2)
Date:      Tue, 28 Aug 2012 10:58:09 -0400
From:      John Baldwin <jhb@freebsd.org>
To:        current@freebsd.org
Subject:   [PATCH] Add a "-h" flag to mv
Message-ID:  <201208281058.10029.jhb@freebsd.org>

next in thread | raw e-mail | index | archive | help
I have a use case at work where I need to be able to update a symlink that 
points to a directory atomically (so that it points to a new directory).  To 
give a conrete example, suppose I have two directories 'foo' and 'bar', and a 
symlink 'a' that I wish to atomically flip from 'foo' to 'bar'.

Using 'ln -shf bar a' is not atomic as it uses separate unlink() and symlink() 
system calls, so there is a race where another thread may encounter ENOENT 
while traversing 'a'.

The approach we used was to create a new symbolic link 'a.new' (e.g. via
'ln -s bar a.new') and then use rename() to rename 'a.new' on top of 'a'.
Normally to do an atomic rename from userland one would use 'mv', but
'mv a.new a' doesn't do that.  Instead, it moves 'a.new' into the directory
referenced by the 'a' symlink.  At work we have resorted to invoking python's
os.rename() in a one-liner to handle this.

While rehashing this discussion today it occurred to me that a -h flag to
mv would allow it to work in this case (and is very similar to how ln treats
its -h flag).  To that end, I have a patch to add a new -h flag to mv that
allows one to atomically update a symlink that points to a directory.  I
could not find any other mv commands that have adopted a -h (or a different
flag that accomplishes the same task).  Given that it functions identically
to the -h flag for ln, -h seemed the "logical" choice.  Any objections?

Index: mv.1
===================================================================
--- mv.1	(revision 239731)
+++ mv.1	(working copy)
@@ -32,7 +32,7 @@
 .\"	@(#)mv.1	8.1 (Berkeley) 5/31/93
 .\" $FreeBSD$
 .\"
-.Dd May 12, 2007
+.Dd August 28, 2012
 .Dt MV 1
 .Os
 .Sh NAME
@@ -41,7 +41,7 @@
 .Sh SYNOPSIS
 .Nm
 .Op Fl f | i | n
-.Op Fl v
+.Op Fl hv
 .Ar source target
 .Nm
 .Op Fl f | i | n
@@ -81,6 +81,21 @@
 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
@@ -142,7 +157,8 @@
 .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: mv.c
===================================================================
--- mv.c	(revision 239731)
+++ mv.c	(working copy)
@@ -68,7 +68,7 @@
 /* Exit code for a failed exec. */
 #define EXEC_FAILED 127
 
-static int	fflg, iflg, nflg, vflg;
+static int	fflg, hflg, iflg, nflg, vflg;
 
 static int	copy(const char *, const char *);
 static int	do_move(const char *, const char *);
@@ -87,8 +87,11 @@
 	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;
@@ -123,6 +126,17 @@
 		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);
@@ -483,7 +497,7 @@
 {
 
 	(void)fprintf(stderr, "%s\n%s\n",
-		      "usage: mv [-f | -i | -n] [-v] source target",
+		      "usage: mv [-f | -i | -n] [-hv] source target",
 		      "       mv [-f | -i | -n] [-v] source ... directory");
 	exit(EX_USAGE);
 }

-- 
John Baldwin



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?201208281058.10029.jhb>