From owner-freebsd-current@FreeBSD.ORG Tue Aug 28 14:58:11 2012 Return-Path: Delivered-To: current@freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [69.147.83.52]) by hub.freebsd.org (Postfix) with ESMTP id 50DB8106564A for ; Tue, 28 Aug 2012 14:58:11 +0000 (UTC) (envelope-from jhb@freebsd.org) Received: from bigwig.baldwin.cx (bigknife-pt.tunnel.tserv9.chi1.ipv6.he.net [IPv6:2001:470:1f10:75::2]) by mx1.freebsd.org (Postfix) with ESMTP id 250C68FC14 for ; Tue, 28 Aug 2012 14:58:11 +0000 (UTC) Received: from jhbbsd.localnet (unknown [209.249.190.124]) by bigwig.baldwin.cx (Postfix) with ESMTPSA id 86771B91C for ; Tue, 28 Aug 2012 10:58:10 -0400 (EDT) From: John Baldwin To: current@freebsd.org Date: Tue, 28 Aug 2012 10:58:09 -0400 User-Agent: KMail/1.13.5 (FreeBSD/8.2-CBSD-20110714-p17; KDE/4.5.5; amd64; ; ) MIME-Version: 1.0 Content-Type: Text/Plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Message-Id: <201208281058.10029.jhb@freebsd.org> X-Greylist: Sender succeeded SMTP AUTH, not delayed by milter-greylist-4.2.7 (bigwig.baldwin.cx); Tue, 28 Aug 2012 10:58:10 -0400 (EDT) Cc: Subject: [PATCH] Add a "-h" flag to mv X-BeenThere: freebsd-current@freebsd.org X-Mailman-Version: 2.1.5 Precedence: list List-Id: Discussions about the use of FreeBSD-current List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Tue, 28 Aug 2012 14:58:11 -0000 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