mirror of
git://git.sv.gnu.org/coreutils.git
synced 2026-02-12 02:12:06 +02:00
ln: avoid directory hard-link races
Previously, 'ln A B' did 'stat("B"), lstat("A"), link("A","B")'
where the stat and lstat were necessary to avoid hard-linking
directories on systems that can hard-link directories.
Now, in situations that prohibit hard links to directories,
'ln A B' merely does 'link("A","B")'. The new behavior
avoids some races and should be more efficient.
This patch was inspired by Bug#10020, which was about 'ln'.
* bootstrap.conf (gnulib_modules): Add unlinkdir.
* src/force-link.c (force_linkat, force_symlinkat): New arg for
error number of previous try. Return error number, 0, or -1 if
error, success, or success after removal. All callers changed.
* src/ln.c: Include priv-set.h, unlinkdir.h.
(beware_hard_dir_link): New static var.
(errnoize, atomic_link): New functions.
(target_directory_operand): Use errnoize for simplicity.
(do_link): New arg for error number of previous try. All callers
changed. Do each link atomically if possible.
(main): Do -r check earlier. Remove linkdir privileges so we can
use a single linkat/symlinkat instead of a racy substitute for the
common case of 'ln A B' and 'ln -s A B'. Set beware_hard_dir_link
to disable this optimization.
This commit is contained in:
6
NEWS
6
NEWS
@@ -10,6 +10,12 @@ GNU coreutils NEWS -*- outline -*-
|
||||
|
||||
df no longer corrupts displayed multibyte characters on macOS.
|
||||
|
||||
When possible 'ln A B' now merely links A to B and reports an error
|
||||
if this fails, instead of statting A and B before linking. This
|
||||
uses fewer system calls and avoids some races. The old statting
|
||||
approach is still used in situations where hard links to directories
|
||||
are allowed (e.g., NetBSD when superuser).
|
||||
|
||||
** New features
|
||||
|
||||
id now supports specifying multiple users.
|
||||
|
||||
@@ -257,6 +257,7 @@ gnulib_modules="
|
||||
unistd-safer
|
||||
unlink-busy
|
||||
unlinkat
|
||||
unlinkdir
|
||||
unlocked-io
|
||||
unsetenv
|
||||
update-copyright
|
||||
|
||||
30
src/copy.c
30
src/copy.c
@@ -1783,16 +1783,16 @@ static bool
|
||||
create_hard_link (char const *src_name, char const *dst_name,
|
||||
bool replace, bool verbose, bool dereference)
|
||||
{
|
||||
int status = force_linkat (AT_FDCWD, src_name, AT_FDCWD, dst_name,
|
||||
dereference ? AT_SYMLINK_FOLLOW : 0,
|
||||
replace);
|
||||
if (status < 0)
|
||||
int err = force_linkat (AT_FDCWD, src_name, AT_FDCWD, dst_name,
|
||||
dereference ? AT_SYMLINK_FOLLOW : 0,
|
||||
replace, -1);
|
||||
if (0 < err)
|
||||
{
|
||||
error (0, errno, _("cannot create hard link %s to %s"),
|
||||
error (0, err, _("cannot create hard link %s to %s"),
|
||||
quoteaf_n (0, dst_name), quoteaf_n (1, src_name));
|
||||
return false;
|
||||
}
|
||||
if (0 < status && verbose)
|
||||
if (err < 0 && verbose)
|
||||
printf (_("removed %s\n"), quoteaf (dst_name));
|
||||
return true;
|
||||
}
|
||||
@@ -2630,11 +2630,12 @@ copy_internal (char const *src_name, char const *dst_name,
|
||||
goto un_backup;
|
||||
}
|
||||
}
|
||||
if (force_symlinkat (src_name, AT_FDCWD, dst_name,
|
||||
x->unlink_dest_after_failed_open)
|
||||
< 0)
|
||||
|
||||
int err = force_symlinkat (src_name, AT_FDCWD, dst_name,
|
||||
x->unlink_dest_after_failed_open, -1);
|
||||
if (0 < err)
|
||||
{
|
||||
error (0, errno, _("cannot create symbolic link %s to %s"),
|
||||
error (0, err, _("cannot create symbolic link %s to %s"),
|
||||
quoteaf_n (0, dst_name), quoteaf_n (1, src_name));
|
||||
goto un_backup;
|
||||
}
|
||||
@@ -2713,10 +2714,9 @@ copy_internal (char const *src_name, char const *dst_name,
|
||||
goto un_backup;
|
||||
}
|
||||
|
||||
int symlink_r = force_symlinkat (src_link_val, AT_FDCWD, dst_name,
|
||||
x->unlink_dest_after_failed_open);
|
||||
int symlink_err = symlink_r < 0 ? errno : 0;
|
||||
if (symlink_err && x->update && !new_dst && S_ISLNK (dst_sb.st_mode)
|
||||
int symlink_err = force_symlinkat (src_link_val, AT_FDCWD, dst_name,
|
||||
x->unlink_dest_after_failed_open, -1);
|
||||
if (0 < symlink_err && x->update && !new_dst && S_ISLNK (dst_sb.st_mode)
|
||||
&& dst_sb.st_size == strlen (src_link_val))
|
||||
{
|
||||
/* See if the destination is already the desired symlink.
|
||||
@@ -2733,7 +2733,7 @@ copy_internal (char const *src_name, char const *dst_name,
|
||||
}
|
||||
}
|
||||
free (src_link_val);
|
||||
if (symlink_err)
|
||||
if (0 < symlink_err)
|
||||
{
|
||||
error (0, symlink_err, _("cannot create symbolic link %s"),
|
||||
quoteaf (dst_name));
|
||||
|
||||
@@ -85,22 +85,27 @@ try_link (char *dest, void *arg)
|
||||
|
||||
/* 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,
|
||||
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
|
||||
-1 (setting errno) on failure. */
|
||||
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 dstdir, char const *dstname, int flags, bool force,
|
||||
int linkat_errno)
|
||||
{
|
||||
int r = linkat (srcdir, srcname, dstdir, dstname, flags);
|
||||
if (!force || r == 0 || errno != EEXIST)
|
||||
return r;
|
||||
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 -1;
|
||||
return errno;
|
||||
struct link_arg arg = { srcdir, srcname, dstdir, flags };
|
||||
int err;
|
||||
|
||||
@@ -108,7 +113,7 @@ force_linkat (int srcdir, char const *srcname,
|
||||
err = errno;
|
||||
else
|
||||
{
|
||||
err = renameat (dstdir, dsttmp, dstdir, dstname) == 0 ? 0 : errno;
|
||||
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. */
|
||||
@@ -117,10 +122,7 @@ force_linkat (int srcdir, char const *srcname,
|
||||
|
||||
if (dsttmp != buf)
|
||||
free (dsttmp);
|
||||
if (!err)
|
||||
return 1;
|
||||
errno = err;
|
||||
return -1;
|
||||
return err;
|
||||
}
|
||||
|
||||
|
||||
@@ -140,22 +142,25 @@ try_symlink (char *dest, void *arg)
|
||||
}
|
||||
|
||||
/* 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,
|
||||
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
|
||||
-1 (setting errno) on failure. */
|
||||
a positive errno value on failure. */
|
||||
extern int
|
||||
force_symlinkat (char const *srcname, int dstdir, char const *dstname,
|
||||
bool force)
|
||||
bool force, int symlinkat_errno)
|
||||
{
|
||||
int r = symlinkat (srcname, dstdir, dstname);
|
||||
if (!force || r == 0 || errno != EEXIST)
|
||||
return r;
|
||||
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 -1;
|
||||
return errno;
|
||||
struct symlink_arg arg = { srcname, dstdir };
|
||||
int err;
|
||||
|
||||
@@ -170,13 +175,10 @@ force_symlinkat (char const *srcname, int dstdir, char const *dstname,
|
||||
{
|
||||
/* Don't worry about renameat being a no-op, since DSTTMP is
|
||||
newly created. */
|
||||
err = 0;
|
||||
err = -1;
|
||||
}
|
||||
|
||||
if (dsttmp != buf)
|
||||
free (dsttmp);
|
||||
if (!err)
|
||||
return 1;
|
||||
errno = err;
|
||||
return -1;
|
||||
return err;
|
||||
}
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
extern int force_linkat (int, char const *, int, char const *, int, bool);
|
||||
extern int force_symlinkat (char const *, int, char const *, bool);
|
||||
extern int force_linkat (int, char const *, int, char const *, int, bool, int);
|
||||
extern int force_symlinkat (char const *, int, char const *, bool, int);
|
||||
|
||||
343
src/ln.c
343
src/ln.c
@@ -30,8 +30,10 @@
|
||||
#include "force-link.h"
|
||||
#include "hash.h"
|
||||
#include "hash-triple.h"
|
||||
#include "priv-set.h"
|
||||
#include "relpath.h"
|
||||
#include "same.h"
|
||||
#include "unlinkdir.h"
|
||||
#include "yesno.h"
|
||||
#include "canonicalize.h"
|
||||
|
||||
@@ -69,6 +71,9 @@ static bool verbose;
|
||||
directories on most existing systems (Solaris being an exception). */
|
||||
static bool hard_dir_link;
|
||||
|
||||
/* If true, watch out for creating or removing hard links to directories. */
|
||||
static bool beware_hard_dir_link;
|
||||
|
||||
/* If nonzero, and the specified destination is a symbolic link to a
|
||||
directory, treat it just as if it were a directory. Otherwise, the
|
||||
command 'ln --force --no-dereference file symlink-to-dir' deletes
|
||||
@@ -113,6 +118,14 @@ errno_nonexisting (int err)
|
||||
return err == ENOENT || err == ENAMETOOLONG || err == ENOTDIR || err == ELOOP;
|
||||
}
|
||||
|
||||
/* Return an errno value for a system call that returned STATUS.
|
||||
This is zero if STATUS is zero, and is errno otherwise. */
|
||||
|
||||
static int
|
||||
errnoize (int status)
|
||||
{
|
||||
return status < 0 ? errno : 0;
|
||||
}
|
||||
|
||||
/* FILE is the last operand of this command. Return true if FILE is a
|
||||
directory. But report an error if there is a problem accessing FILE,
|
||||
@@ -126,9 +139,8 @@ target_directory_operand (char const *file)
|
||||
size_t blen = strlen (b);
|
||||
bool looks_like_a_dir = (blen == 0 || ISSLASH (b[blen - 1]));
|
||||
struct stat st;
|
||||
int stat_result =
|
||||
(dereference_dest_dir_symlinks ? stat (file, &st) : lstat (file, &st));
|
||||
int err = (stat_result == 0 ? 0 : errno);
|
||||
int flags = dereference_dest_dir_symlinks ? 0 : AT_SYMLINK_NOFOLLOW;
|
||||
int err = errnoize (fstatat (AT_FDCWD, file, &st, flags));
|
||||
bool is_a_dir = !err && S_ISDIR (st.st_mode);
|
||||
if (err && ! errno_nonexisting (errno))
|
||||
die (EXIT_FAILURE, err, _("failed to access %s"), quoteaf (file));
|
||||
@@ -171,162 +183,181 @@ convert_abs_rel (const char *from, const char *target)
|
||||
return relative_from ? relative_from : xstrdup (from);
|
||||
}
|
||||
|
||||
/* Link SOURCE to DEST atomically. Return 0 if successful, a positive
|
||||
errno value on failure, and -1 if an atomic link cannot be done.
|
||||
This handles the common case where DEST does not already exist and
|
||||
-r is not specified. */
|
||||
|
||||
static int
|
||||
atomic_link (char const *source, char const *dest)
|
||||
{
|
||||
return (symbolic_link
|
||||
? (relative ? -1 : errnoize (symlink (source, dest)))
|
||||
: beware_hard_dir_link
|
||||
? -1
|
||||
: errnoize (linkat (AT_FDCWD, source, AT_FDCWD, dest,
|
||||
logical ? AT_SYMLINK_FOLLOW : 0)));
|
||||
}
|
||||
|
||||
/* Make a link DEST to the (usually) existing file SOURCE.
|
||||
Symbolic links to nonexistent files are allowed.
|
||||
LINK_ERRNO is zero if the link has already been made,
|
||||
positive if attempting the link failed with errno == LINK_ERRNO,
|
||||
-1 if no attempt has been made to create the link.
|
||||
Return true if successful. */
|
||||
|
||||
static bool
|
||||
do_link (const char *source, const char *dest)
|
||||
do_link (const char *source, const char *dest, int link_errno)
|
||||
{
|
||||
struct stat source_stats;
|
||||
struct stat dest_stats;
|
||||
int source_status = 1;
|
||||
char *dest_backup = NULL;
|
||||
char *rel_source = NULL;
|
||||
bool dest_lstat_ok = false;
|
||||
bool source_is_dir = false;
|
||||
int nofollow_flag = logical ? 0 : AT_SYMLINK_NOFOLLOW;
|
||||
if (link_errno < 0)
|
||||
link_errno = atomic_link (source, dest);
|
||||
|
||||
if (!symbolic_link)
|
||||
/* Get SOURCE_STATS if later code will need it, if only for sharper
|
||||
diagnostics. */
|
||||
if ((link_errno || dest_set) && !symbolic_link)
|
||||
{
|
||||
/* Which stat to use depends on whether linkat will follow the
|
||||
symlink. We can't use the shorter
|
||||
(logical?stat:lstat) (source, &source_stats)
|
||||
since stat might be a function-like macro. */
|
||||
if ((logical ? stat (source, &source_stats)
|
||||
: lstat (source, &source_stats))
|
||||
!= 0)
|
||||
source_status = fstatat (AT_FDCWD, source, &source_stats, nofollow_flag);
|
||||
if (source_status != 0)
|
||||
{
|
||||
error (0, errno, _("failed to access %s"), quoteaf (source));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (S_ISDIR (source_stats.st_mode))
|
||||
if (link_errno)
|
||||
{
|
||||
if (!symbolic_link && !hard_dir_link && S_ISDIR (source_stats.st_mode))
|
||||
{
|
||||
source_is_dir = true;
|
||||
if (! hard_dir_link)
|
||||
error (0, 0, _("%s: hard link not allowed for directory"),
|
||||
quotef (source));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (relative)
|
||||
source = rel_source = convert_abs_rel (source, dest);
|
||||
|
||||
bool force = (remove_existing_files || interactive
|
||||
|| backup_type != no_backups);
|
||||
if (force)
|
||||
{
|
||||
struct stat dest_stats;
|
||||
if (lstat (dest, &dest_stats) != 0)
|
||||
{
|
||||
error (0, 0, _("%s: hard link not allowed for directory"),
|
||||
quotef (source));
|
||||
if (errno != ENOENT)
|
||||
{
|
||||
error (0, errno, _("failed to access %s"), quoteaf (dest));
|
||||
return false;
|
||||
}
|
||||
force = false;
|
||||
}
|
||||
else if (S_ISDIR (dest_stats.st_mode))
|
||||
{
|
||||
error (0, 0, _("%s: cannot overwrite directory"), quotef (dest));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (remove_existing_files || interactive || backup_type != no_backups)
|
||||
{
|
||||
dest_lstat_ok = (lstat (dest, &dest_stats) == 0);
|
||||
if (!dest_lstat_ok && errno != ENOENT)
|
||||
{
|
||||
error (0, errno, _("failed to access %s"), quoteaf (dest));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* If the current target was created as a hard link to another
|
||||
source file, then refuse to unlink it. */
|
||||
if (dest_lstat_ok
|
||||
&& dest_set != NULL
|
||||
&& seen_file (dest_set, dest, &dest_stats))
|
||||
{
|
||||
error (0, 0,
|
||||
_("will not overwrite just-created %s with %s"),
|
||||
quoteaf_n (0, dest), quoteaf_n (1, source));
|
||||
return false;
|
||||
}
|
||||
|
||||
/* If --force (-f) has been specified without --backup, then before
|
||||
making a link ln must remove the destination file if it exists.
|
||||
(with --backup, it just renames any existing destination file)
|
||||
But if the source and destination are the same, don't remove
|
||||
anything and fail right here. */
|
||||
if ((remove_existing_files
|
||||
/* Ensure that "ln --backup f f" fails here, with the
|
||||
"... same file" diagnostic, below. Otherwise, subsequent
|
||||
code would give a misleading "file not found" diagnostic.
|
||||
This case is different than the others handled here, since
|
||||
the command in question doesn't use --force. */
|
||||
|| (!symbolic_link && backup_type != no_backups))
|
||||
&& dest_lstat_ok
|
||||
/* Allow 'ln -sf --backup k k' to succeed in creating the
|
||||
self-referential symlink, but don't allow the hard-linking
|
||||
equivalent: 'ln -f k k' (with or without --backup) to get
|
||||
beyond this point, because the error message you'd get is
|
||||
misleading. */
|
||||
&& (backup_type == no_backups || !symbolic_link)
|
||||
&& (!symbolic_link || stat (source, &source_stats) == 0)
|
||||
&& SAME_INODE (source_stats, dest_stats)
|
||||
/* The following detects whether removing DEST will also remove
|
||||
SOURCE. If the file has only one link then both are surely
|
||||
the same link. Otherwise check whether they point to the same
|
||||
name in the same directory. */
|
||||
&& (source_stats.st_nlink == 1 || same_name (source, dest)))
|
||||
{
|
||||
error (0, 0, _("%s and %s are the same file"),
|
||||
quoteaf_n (0, source), quoteaf_n (1, dest));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (dest_lstat_ok)
|
||||
{
|
||||
if (S_ISDIR (dest_stats.st_mode))
|
||||
{
|
||||
error (0, 0, _("%s: cannot overwrite directory"), quotef (dest));
|
||||
return false;
|
||||
}
|
||||
if (interactive)
|
||||
{
|
||||
fprintf (stderr, _("%s: replace %s? "), program_name, quoteaf (dest));
|
||||
if (!yesno ())
|
||||
return true;
|
||||
remove_existing_files = true;
|
||||
}
|
||||
|
||||
if (backup_type != no_backups)
|
||||
{
|
||||
dest_backup = find_backup_file_name (dest, backup_type);
|
||||
if (rename (dest, dest_backup) != 0)
|
||||
else if (seen_file (dest_set, dest, &dest_stats))
|
||||
{
|
||||
int rename_errno = errno;
|
||||
free (dest_backup);
|
||||
dest_backup = NULL;
|
||||
if (rename_errno != ENOENT)
|
||||
/* The current target was created as a hard link to another
|
||||
source file. */
|
||||
error (0, 0,
|
||||
_("will not overwrite just-created %s with %s"),
|
||||
quoteaf_n (0, dest), quoteaf_n (1, source));
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Beware removing DEST if it is the same directory entry as
|
||||
SOURCE, because in that case removing DEST can cause the
|
||||
subsequent link creation either to fail (for hard links), or
|
||||
to replace a non-symlink DEST with a self-loop (for symbolic
|
||||
links) which loses the contents of DEST. So, when backing
|
||||
up, worry about creating hard links (since the backups cover
|
||||
the symlink case); otherwise, worry about about -f. */
|
||||
if (backup_type != no_backups
|
||||
? !symbolic_link
|
||||
: remove_existing_files)
|
||||
{
|
||||
error (0, rename_errno, _("cannot backup %s"),
|
||||
quoteaf (dest));
|
||||
return false;
|
||||
/* Detect whether removing DEST would also remove SOURCE.
|
||||
If the file has only one link then both are surely the
|
||||
same directory entry. Otherwise check whether they point
|
||||
to the same name in the same directory. */
|
||||
if (source_status != 0)
|
||||
source_status = stat (source, &source_stats);
|
||||
if (source_status == 0
|
||||
&& SAME_INODE (source_stats, dest_stats)
|
||||
&& (source_stats.st_nlink == 1
|
||||
|| same_name (source, dest)))
|
||||
{
|
||||
error (0, 0, _("%s and %s are the same file"),
|
||||
quoteaf_n (0, source), quoteaf_n (1, dest));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (link_errno < 0 || link_errno == EEXIST)
|
||||
{
|
||||
if (interactive)
|
||||
{
|
||||
fprintf (stderr, _("%s: replace %s? "),
|
||||
program_name, quoteaf (dest));
|
||||
if (!yesno ())
|
||||
return true;
|
||||
}
|
||||
|
||||
if (backup_type != no_backups)
|
||||
{
|
||||
dest_backup = find_backup_file_name (dest, backup_type);
|
||||
if (rename (dest, dest_backup) != 0)
|
||||
{
|
||||
int rename_errno = errno;
|
||||
free (dest_backup);
|
||||
dest_backup = NULL;
|
||||
if (rename_errno != ENOENT)
|
||||
{
|
||||
error (0, rename_errno, _("cannot backup %s"),
|
||||
quoteaf (dest));
|
||||
return false;
|
||||
}
|
||||
force = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* If the attempt to create a link fails and we are removing or
|
||||
backing up destinations, unlink the destination and try again.
|
||||
|
||||
On the surface, POSIX states that 'ln -f A B' unlinks B before trying
|
||||
to link A to B. But strictly following this has the counterintuitive
|
||||
effect of losing the contents of B if A does not exist. Fortunately,
|
||||
POSIX 2008 clarified that an application is free to fail early if it
|
||||
can prove that continuing onwards cannot succeed, so we can try to
|
||||
link A to B before blindly unlinking B, thus sometimes attempting to
|
||||
link a second time during a successful 'ln -f A B'.
|
||||
|
||||
Try to unlink DEST even if we may have backed it up successfully.
|
||||
In some unusual cases (when DEST and DEST_BACKUP are hard-links
|
||||
that refer to the same file), rename succeeds and DEST remains.
|
||||
If we didn't remove DEST in that case, the subsequent symlink or
|
||||
link call would fail. */
|
||||
link_errno
|
||||
= (symbolic_link
|
||||
? force_symlinkat (source, AT_FDCWD, dest, force, link_errno)
|
||||
: force_linkat (AT_FDCWD, source, AT_FDCWD, dest,
|
||||
logical ? AT_SYMLINK_FOLLOW : 0,
|
||||
force, link_errno));
|
||||
/* Until now, link_errno < 0 meant the link has not been tried.
|
||||
From here on, link_errno < 0 means the link worked but
|
||||
required removing the destination first. */
|
||||
}
|
||||
|
||||
if (relative)
|
||||
source = rel_source = convert_abs_rel (source, dest);
|
||||
|
||||
/* If the attempt to create a link fails and we are removing or
|
||||
backing up destinations, unlink the destination and try again.
|
||||
|
||||
On the surface, POSIX describes an algorithm that states that
|
||||
'ln -f A B' will call unlink() on B before ever attempting
|
||||
link() on A. But strictly following this has the counterintuitive
|
||||
effect of losing the contents of B, if A does not exist.
|
||||
Fortunately, POSIX 2008 clarified that an application is free
|
||||
to fail early if it can prove that continuing onwards cannot
|
||||
succeed, so we are justified in trying link() before blindly
|
||||
removing B, thus sometimes calling link() a second time during
|
||||
a successful 'ln -f A B'.
|
||||
|
||||
Try to unlink DEST even if we may have backed it up successfully.
|
||||
In some unusual cases (when DEST and DEST_BACKUP are hard-links
|
||||
that refer to the same file), rename succeeds and DEST remains.
|
||||
If we didn't remove DEST in that case, the subsequent symlink or link
|
||||
call would fail. */
|
||||
bool ok_to_remove = remove_existing_files || dest_backup;
|
||||
bool ok = 0 <= (symbolic_link
|
||||
? force_symlinkat (source, AT_FDCWD, dest, ok_to_remove)
|
||||
: force_linkat (AT_FDCWD, source, AT_FDCWD, dest,
|
||||
logical ? AT_SYMLINK_FOLLOW : 0,
|
||||
ok_to_remove));
|
||||
|
||||
if (ok)
|
||||
if (link_errno <= 0)
|
||||
{
|
||||
/* Right after creating a hard link, do this: (note dest name and
|
||||
source_stats, which are also the just-linked-destinations stats) */
|
||||
@@ -343,15 +374,15 @@ do_link (const char *source, const char *dest)
|
||||
}
|
||||
else
|
||||
{
|
||||
error (0, errno,
|
||||
error (0, link_errno,
|
||||
(symbolic_link
|
||||
? (errno != ENAMETOOLONG && *source
|
||||
? (link_errno != ENAMETOOLONG && *source
|
||||
? _("failed to create symbolic link %s")
|
||||
: _("failed to create symbolic link %s -> %s"))
|
||||
: (errno == EMLINK && !source_is_dir
|
||||
: (link_errno == EMLINK
|
||||
? _("failed to create hard link to %.0s%s")
|
||||
: (errno == EDQUOT || errno == EEXIST || errno == ENOSPC
|
||||
|| errno == EROFS)
|
||||
: (link_errno == EDQUOT || link_errno == EEXIST
|
||||
|| link_errno == ENOSPC || link_errno == EROFS)
|
||||
? _("failed to create hard link %s")
|
||||
: _("failed to create hard link %s => %s"))),
|
||||
quoteaf_n (0, dest), quoteaf_n (1, source));
|
||||
@@ -365,7 +396,7 @@ do_link (const char *source, const char *dest)
|
||||
|
||||
free (dest_backup);
|
||||
free (rel_source);
|
||||
return ok;
|
||||
return link_errno <= 0;
|
||||
}
|
||||
|
||||
void
|
||||
@@ -444,6 +475,7 @@ main (int argc, char **argv)
|
||||
bool no_target_directory = false;
|
||||
int n_files;
|
||||
char **file;
|
||||
int link_errno = -1;
|
||||
|
||||
initialize_main (&argc, &argv);
|
||||
set_program_name (argv[0]);
|
||||
@@ -535,6 +567,15 @@ main (int argc, char **argv)
|
||||
usage (EXIT_FAILURE);
|
||||
}
|
||||
|
||||
if (relative && !symbolic_link)
|
||||
die (EXIT_FAILURE, 0, _("cannot do --relative without --symbolic"));
|
||||
|
||||
if (!hard_dir_link)
|
||||
{
|
||||
priv_set_remove_linkdir ();
|
||||
beware_hard_dir_link = !cannot_unlink_dir ();
|
||||
}
|
||||
|
||||
if (no_target_directory)
|
||||
{
|
||||
if (target_directory)
|
||||
@@ -556,11 +597,17 @@ main (int argc, char **argv)
|
||||
{
|
||||
if (n_files < 2)
|
||||
target_directory = ".";
|
||||
else if (2 <= n_files && target_directory_operand (file[n_files - 1]))
|
||||
target_directory = file[--n_files];
|
||||
else if (2 < n_files)
|
||||
die (EXIT_FAILURE, 0, _("target %s is not a directory"),
|
||||
quoteaf (file[n_files - 1]));
|
||||
else
|
||||
{
|
||||
if (n_files == 2)
|
||||
link_errno = atomic_link (file[0], file[1]);
|
||||
if ((link_errno < 0 || link_errno == EEXIST || link_errno == ENOTDIR)
|
||||
&& target_directory_operand (file[n_files - 1]))
|
||||
target_directory = file[--n_files];
|
||||
else if (2 < n_files)
|
||||
die (EXIT_FAILURE, 0, _("target %s is not a directory"),
|
||||
quoteaf (file[n_files - 1]));
|
||||
}
|
||||
}
|
||||
|
||||
backup_type = (make_backups
|
||||
@@ -568,12 +615,6 @@ main (int argc, char **argv)
|
||||
: no_backups);
|
||||
set_simple_backup_suffix (backup_suffix);
|
||||
|
||||
if (relative && !symbolic_link)
|
||||
{
|
||||
die (EXIT_FAILURE, 0,
|
||||
_("cannot do --relative without --symbolic"));
|
||||
}
|
||||
|
||||
|
||||
if (target_directory)
|
||||
{
|
||||
@@ -607,12 +648,12 @@ main (int argc, char **argv)
|
||||
last_component (file[i]),
|
||||
&dest_base);
|
||||
strip_trailing_slashes (dest_base);
|
||||
ok &= do_link (file[i], dest);
|
||||
ok &= do_link (file[i], dest, -1);
|
||||
free (dest);
|
||||
}
|
||||
}
|
||||
else
|
||||
ok = do_link (file[0], file[1]);
|
||||
ok = do_link (file[0], file[1], link_errno);
|
||||
|
||||
return ok ? EXIT_SUCCESS : EXIT_FAILURE;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user