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) _at__at_ -32,7 +32,7 _at__at_ .\" _at_(#)mv.1 8.1 (Berkeley) 5/31/93 .\" $FreeBSD$ .\" -.Dd May 12, 2007 +.Dd August 28, 2012 .Dt MV 1 .Os .Sh NAME _at__at_ -41,7 +41,7 _at__at_ .Sh SYNOPSIS .Nm .Op Fl f | i | n -.Op Fl v +.Op Fl hv .Ar source target .Nm .Op Fl f | i | n _at__at_ -81,6 +81,21 _at__at_ 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 _at__at_ -142,7 +157,8 _at__at_ .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) _at__at_ -68,7 +68,7 _at__at_ /* 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 *); _at__at_ -87,8 +87,11 _at__at_ 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; _at__at_ -123,6 +126,17 _at__at_ 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); _at__at_ -483,7 +497,7 _at__at_ { (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 BaldwinReceived on Tue Aug 28 2012 - 12:58:11 UTC
This archive was generated by hypermail 2.4.0 : Wed May 19 2021 - 11:40:30 UTC