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 _______________________________________________ freebsd-current@freebsd.org mailing list http://lists.freebsd.org/mailman/listinfo/freebsd-current To unsubscribe, send any mail to "freebsd-current-unsubscr...@freebsd.org"