summaryrefslogtreecommitdiffstats
path: root/src/force-link.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/force-link.c')
-rw-r--r--src/force-link.c184
1 files changed, 184 insertions, 0 deletions
diff --git a/src/force-link.c b/src/force-link.c
new file mode 100644
index 0000000..bd6ea25
--- /dev/null
+++ b/src/force-link.c
@@ -0,0 +1,184 @@
+/* Implement ln -f "atomically"
+
+ Copyright 2017-2022 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 <https://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 "system.h"
+
+#include "force-link.h"
+
+#include <tempname.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.
+ If LINKAT_ERRNO is 0, the hard link is already done; if positive,
+ the hard link was tried and failed with errno == LINKAT_ERRNO. Return
+ -1 if successful and DSTNAME already existed,
+ 0 if successful and DSTNAME did not already exist, and
+ a positive errno value on failure. */
+extern int
+force_linkat (int srcdir, char const *srcname,
+ int dstdir, char const *dstname, int flags, bool force,
+ int linkat_errno)
+{
+ if (linkat_errno < 0)
+ linkat_errno = (linkat (srcdir, srcname, dstdir, dstname, flags) == 0
+ ? 0 : errno);
+ if (!force || linkat_errno != EEXIST)
+ return linkat_errno;
+
+ char buf[smallsize];
+ char *dsttmp = samedir_template (dstname, buf);
+ if (! dsttmp)
+ return errno;
+ 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 ? -1 : 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);
+ return err;
+}
+
+
+/* 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.
+ If SYMLINKAT_ERRNO is 0, the symlink is already done; if positive,
+ the symlink was tried and failed with errno == SYMLINKAT_ERRNO. Return
+ -1 if successful and DSTNAME already existed,
+ 0 if successful and DSTNAME did not already exist, and
+ a positive errno value on failure. */
+extern int
+force_symlinkat (char const *srcname, int dstdir, char const *dstname,
+ bool force, int symlinkat_errno)
+{
+ if (symlinkat_errno < 0)
+ symlinkat_errno = symlinkat (srcname, dstdir, dstname) == 0 ? 0 : errno;
+ if (!force || symlinkat_errno != EEXIST)
+ return symlinkat_errno;
+
+ char buf[smallsize];
+ char *dsttmp = samedir_template (dstname, buf);
+ if (!dsttmp)
+ return errno;
+ 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 = -1;
+ }
+
+ if (dsttmp != buf)
+ free (dsttmp);
+ return err;
+}