summaryrefslogtreecommitdiff
path: root/src/force-link.c
diff options
context:
space:
mode:
authorPaul Eggert <eggert@cs.ucla.edu>2017-02-11 23:12:31 -0800
committerPaul Eggert <eggert@cs.ucla.edu>2017-02-11 23:14:02 -0800
commit376967889ed7ed561e46ff6d88a66779db62737a (patch)
tree7ca32f07b738fc2f9902c1643bb01dedb7d1066b /src/force-link.c
parent2f69dba5df8caaf9eda658c1808b1379e9949f22 (diff)
downloadcoreutils-376967889ed7ed561e46ff6d88a66779db62737a.tar.xz
ln: replace destination links more atomically
If the file B already exists, commands like 'ln -f A B' and 'cp -fl A B' no longer remove B before creating the new link. Instead, they arrange for the new link to replace B atomically. This should fix a race condition reported by Mike Crowe (Bug#25680). * NEWS, doc/coreutils.texi (cp invocation, ln invocation): Document this. * bootstrap.conf (gnulib_modules): Add symlinkat. * src/copy.c, src/ln.c: Include force-link.h. * src/copy.c (same_file_ok): It's also OK to remove a destination symlink when creating symbolic links, or when the source and destination are on the same file system and when creating hard links. * src/copy.c (create_hard_link, copy_internal): * src/ln.c (do_link): Rewrite using force_linkat and force_symlinkat, to close a window where the destination temporarily does not exist. * src/cp.c (main): Do not set x.unlink_dest_before_opening merely because we are in link-creation mode. * src/force-link.c, src/force-link.h: New files. * src/local.mk (copy_sources, src_ln_SOURCES): Add them. * tests/cp/same-file.sh: Adjust test case to match fixed behavior.
Diffstat (limited to 'src/force-link.c')
-rw-r--r--src/force-link.c187
1 files changed, 187 insertions, 0 deletions
diff --git a/src/force-link.c b/src/force-link.c
new file mode 100644
index 000000000..e0db07574
--- /dev/null
+++ b/src/force-link.c
@@ -0,0 +1,187 @@
+/* Implement ln -f "atomically"
+
+ Copyright 2017 Free Software Foundation, Inc.
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+/* Written by Paul Eggert. */
+
+/* A naive "ln -f A B" unlinks B and then links A to B. This module
+ instead links A to a randomly-named temporary T in B's directory,
+ and then renames T to B. This approach has a window with a
+ randomly-named temporary, which is safer for many applications than
+ a window where B does not exist. */
+
+#include <config.h>
+
+#include "force-link.h"
+
+#include <dirname.h>
+#include <tempname.h>
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+/* A basename pattern suitable for a temporary file. It should work
+ even on file systems like FAT that support only short names.
+ "Cu" is short for "Coreutils" or for "Changeable unstable",
+ take your pick.... */
+
+static char const simple_pattern[] = "CuXXXXXX";
+enum { x_suffix_len = sizeof "XXXXXX" - 1 };
+
+/* A size for smallish buffers containing file names. Longer file
+ names can use malloc. */
+
+enum { smallsize = 256 };
+
+/* Return a template for a file in the same directory as DSTNAME.
+ Use BUF if the template fits, otherwise use malloc and return NULL
+ (setting errno) if unsuccessful. */
+
+static char *
+samedir_template (char const *dstname, char buf[smallsize])
+{
+ ptrdiff_t dstdirlen = last_component (dstname) - dstname;
+ size_t dsttmpsize = dstdirlen + sizeof simple_pattern;
+ char *dsttmp;
+ if (dsttmpsize <= smallsize)
+ dsttmp = buf;
+ else
+ {
+ dsttmp = malloc (dsttmpsize);
+ if (!dsttmp)
+ return dsttmp;
+ }
+ strcpy (mempcpy (dsttmp, dstname, dstdirlen), simple_pattern);
+ return dsttmp;
+}
+
+
+/* Auxiliaries for force_linkat. */
+
+struct link_arg
+{
+ int srcdir;
+ char const *srcname;
+ int dstdir;
+ int flags;
+};
+
+static int
+try_link (char *dest, void *arg)
+{
+ struct link_arg *a = arg;
+ return linkat (a->srcdir, a->srcname, a->dstdir, dest, a->flags);
+}
+
+/* Hard-link directory SRCDIR's file SRCNAME to directory DSTDIR's
+ file DSTNAME, using linkat-style FLAGS to control the linking.
+ If FORCE and DSTNAME already exists, replace it atomically. Return
+ 1 if successful and DSTNAME already existed,
+ 0 if successful and DSTNAME did not already exist, and
+ -1 (setting errno) on failure. */
+int
+force_linkat (int srcdir, char const *srcname,
+ int dstdir, char const *dstname, int flags, bool force)
+{
+ int r = linkat (srcdir, srcname, dstdir, dstname, flags);
+ if (!force || r == 0 || errno != EEXIST)
+ return r;
+
+ char buf[smallsize];
+ char *dsttmp = samedir_template (dstname, buf);
+ if (! dsttmp)
+ return -1;
+ struct link_arg arg = { srcdir, srcname, dstdir, flags };
+ int err;
+
+ if (try_tempname_len (dsttmp, 0, &arg, try_link, x_suffix_len) != 0)
+ err = errno;
+ else
+ {
+ err = renameat (dstdir, dsttmp, dstdir, dstname) == 0 ? 0 : errno;
+ /* Unlink DSTTMP even if renameat succeeded, in case DSTTMP
+ and DSTNAME were already the same hard link and renameat
+ was a no-op. */
+ unlinkat (dstdir, dsttmp, 0);
+ }
+
+ if (dsttmp != buf)
+ free (dsttmp);
+ if (!err)
+ return 1;
+ errno = err;
+ return -1;
+}
+
+
+/* Auxiliaries for force_symlinkat. */
+
+struct symlink_arg
+{
+ char const *srcname;
+ int dstdir;
+};
+
+static int
+try_symlink (char *dest, void *arg)
+{
+ struct symlink_arg *a = arg;
+ return symlinkat (a->srcname, a->dstdir, dest);
+}
+
+/* Create a symlink containing SRCNAME in directory DSTDIR's file DSTNAME.
+ If FORCE and DSTNAME already exists, replace it atomically. Return
+ 1 if successful and DSTNAME already existed,
+ 0 if successful and DSTNAME did not already exist, and
+ -1 (setting errno) on failure. */
+int
+force_symlinkat (char const *srcname, int dstdir, char const *dstname,
+ bool force)
+{
+ int r = symlinkat (srcname, dstdir, dstname);
+ if (!force || r == 0 || errno != EEXIST)
+ return r;
+
+ char buf[smallsize];
+ char *dsttmp = samedir_template (dstname, buf);
+ if (!dsttmp)
+ return -1;
+ struct symlink_arg arg = { srcname, dstdir };
+ int err;
+
+ if (try_tempname_len (dsttmp, 0, &arg, try_symlink, x_suffix_len) != 0)
+ err = errno;
+ else if (renameat (dstdir, dsttmp, dstdir, dstname) != 0)
+ {
+ err = errno;
+ unlinkat (dstdir, dsttmp, 0);
+ }
+ else
+ {
+ /* Don't worry about renameat being a no-op, since DSTTMP is
+ newly created. */
+ err = 0;
+ }
+
+ if (dsttmp != buf)
+ free (dsttmp);
+ if (!err)
+ return 1;
+ errno = err;
+ return -1;
+}