summaryrefslogtreecommitdiff
path: root/src/copy.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/copy.c')
-rw-r--r--src/copy.c96
1 files changed, 41 insertions, 55 deletions
diff --git a/src/copy.c b/src/copy.c
index e3832c23a..9dbd53655 100644
--- a/src/copy.c
+++ b/src/copy.c
@@ -46,6 +46,7 @@
#include "file-set.h"
#include "filemode.h"
#include "filenamecat.h"
+#include "force-link.h"
#include "full-write.h"
#include "hash.h"
#include "hash-triple.h"
@@ -1623,11 +1624,13 @@ same_file_ok (char const *src_name, struct stat const *src_sb,
}
}
- /* It's ok to remove a destination symlink. But that works only when we
- unlink before opening the destination and when the source and destination
- files are on the same partition. */
- if (x->unlink_dest_before_opening
- && S_ISLNK (dst_sb_link->st_mode))
+ /* It's ok to remove a destination symlink. But that works only
+ when creating symbolic links, or when the source and destination
+ are on the same file system and when creating hard links or when
+ unlinking before opening the destination. */
+ if (x->symbolic_link
+ || ((x->hard_link || x->unlink_dest_before_opening)
+ && S_ISLNK (dst_sb_link->st_mode)))
return dst_sb_link->st_dev == src_sb_link->st_dev;
if (x->dereference == DEREF_NEVER)
@@ -1779,36 +1782,17 @@ static bool
create_hard_link (char const *src_name, char const *dst_name,
bool replace, bool verbose, bool dereference)
{
- /* We want to guarantee that symlinks are not followed, unless requested. */
- int flags = 0;
- if (dereference)
- flags = AT_SYMLINK_FOLLOW;
-
- bool link_failed = (linkat (AT_FDCWD, src_name, AT_FDCWD, dst_name, flags)
- != 0);
-
- /* If the link failed because of an existing destination,
- remove that file and then call link again. */
- if (link_failed && replace && errno == EEXIST)
- {
- if (unlink (dst_name) != 0)
- {
- error (0, errno, _("cannot remove %s"), quoteaf (dst_name));
- return false;
- }
- if (verbose)
- printf (_("removed %s\n"), quoteaf (dst_name));
- link_failed = (linkat (AT_FDCWD, src_name, AT_FDCWD, dst_name, flags)
- != 0);
- }
-
- if (link_failed)
+ int status = force_linkat (AT_FDCWD, src_name, AT_FDCWD, dst_name,
+ dereference ? AT_SYMLINK_FOLLOW : 0,
+ replace);
+ if (status < 0)
{
error (0, errno, _("cannot create hard link %s to %s"),
quoteaf_n (0, dst_name), quoteaf_n (1, src_name));
return false;
}
-
+ if (0 < status && verbose)
+ printf (_("removed %s\n"), quoteaf (dst_name));
return true;
}
@@ -2592,7 +2576,9 @@ copy_internal (char const *src_name, char const *dst_name,
goto un_backup;
}
}
- if (symlink (src_name, dst_name) != 0)
+ if (force_symlinkat (src_name, AT_FDCWD, dst_name,
+ x->unlink_dest_after_failed_open)
+ < 0)
{
error (0, errno, _("cannot create symbolic link %s to %s"),
quoteaf_n (0, dst_name), quoteaf_n (1, src_name));
@@ -2617,7 +2603,9 @@ copy_internal (char const *src_name, char const *dst_name,
&& !(! CAN_HARDLINK_SYMLINKS && S_ISLNK (src_mode)
&& x->dereference == DEREF_NEVER))
{
- if (! create_hard_link (src_name, dst_name, false, false, dereference))
+ if (! create_hard_link (src_name, dst_name,
+ x->unlink_dest_after_failed_open,
+ false, dereference))
goto un_backup;
}
else if (S_ISREG (src_mode)
@@ -2671,33 +2659,31 @@ copy_internal (char const *src_name, char const *dst_name,
goto un_backup;
}
- if (symlink (src_link_val, dst_name) == 0)
- free (src_link_val);
- else
+ 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)
+ && dst_sb.st_size == strlen (src_link_val))
{
- int saved_errno = errno;
- bool same_link = false;
- if (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.
+ FIXME: This behavior isn't documented, and seems wrong
+ in some cases, e.g., if the destination symlink has the
+ wrong ownership, permissions, or timestamps. */
+ char *dest_link_val =
+ areadlink_with_size (dst_name, dst_sb.st_size);
+ if (dest_link_val)
{
- /* See if the destination is already the desired symlink.
- FIXME: This behavior isn't documented, and seems wrong
- in some cases, e.g., if the destination symlink has the
- wrong ownership, permissions, or timestamps. */
- char *dest_link_val =
- areadlink_with_size (dst_name, dst_sb.st_size);
- if (dest_link_val && STREQ (dest_link_val, src_link_val))
- same_link = true;
+ if (STREQ (dest_link_val, src_link_val))
+ symlink_err = 0;
free (dest_link_val);
}
- free (src_link_val);
-
- if (! same_link)
- {
- error (0, saved_errno, _("cannot create symbolic link %s"),
- quoteaf (dst_name));
- goto un_backup;
- }
+ }
+ free (src_link_val);
+ if (symlink_err)
+ {
+ error (0, symlink_err, _("cannot create symbolic link %s"),
+ quoteaf (dst_name));
+ goto un_backup;
}
if (x->preserve_security_context)