rm_/force-link.c
2020-09-02 16:47:03 +08:00

185 lines
5.4 KiB
C

/* Implement ln -f "atomically"
Copyright 2017-2020 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;
}