/* 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;
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. */
if (dsttmp != buf)
free (dsttmp);
- if (!err)
- return 1;
- errno = err;
- return -1;
+ return err;
}
}
/* 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;
{
/* 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;
}
#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"
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
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,
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));
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))
- {
- source_is_dir = true;
- if (! hard_dir_link)
- {
- error (0, 0, _("%s: hard link not allowed for directory"),
- quotef (source));
- return false;
- }
- }
}
- if (remove_existing_files || interactive || backup_type != no_backups)
+ if (link_errno)
{
- dest_lstat_ok = (lstat (dest, &dest_stats) == 0);
- if (!dest_lstat_ok && errno != ENOENT)
+ if (!symbolic_link && !hard_dir_link && S_ISDIR (source_stats.st_mode))
{
- error (0, errno, _("failed to access %s"), quoteaf (dest));
+ error (0, 0, _("%s: hard link not allowed for directory"),
+ quotef (source));
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 (relative)
+ source = rel_source = convert_abs_rel (source, dest);
- if (backup_type != no_backups)
+ bool force = (remove_existing_files || interactive
+ || backup_type != no_backups);
+ if (force)
{
- dest_backup = find_backup_file_name (dest, backup_type);
- if (rename (dest, dest_backup) != 0)
+ struct stat dest_stats;
+ if (lstat (dest, &dest_stats) != 0)
{
- int rename_errno = errno;
- free (dest_backup);
- dest_backup = NULL;
- if (rename_errno != ENOENT)
+ if (errno != ENOENT)
{
- error (0, rename_errno, _("cannot backup %s"),
- quoteaf (dest));
+ 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;
+ }
+ else if (seen_file (dest_set, dest, &dest_stats))
+ {
+ /* 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)
+ {
+ /* 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) */
}
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));
free (dest_backup);
free (rel_source);
- return ok;
+ return link_errno <= 0;
}
void
bool no_target_directory = false;
int n_files;
char **file;
+ int link_errno = -1;
initialize_main (&argc, &argv);
set_program_name (argv[0]);
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)
{
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
: no_backups);
set_simple_backup_suffix (backup_suffix);
- if (relative && !symbolic_link)
- {
- die (EXIT_FAILURE, 0,
- _("cannot do --relative without --symbolic"));
- }
-
if (target_directory)
{
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;
}