summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJim Meyering <jim@meyering.net>2000-09-03 13:01:49 +0000
committerJim Meyering <jim@meyering.net>2000-09-03 13:01:49 +0000
commit76685fb1e90617c417cf7606ec9bd5cbf0abdd06 (patch)
tree76da55aefbda1bf3701a43eb17ed824d34213812 /src
parent3e8dff57b24f4bb9fe2117221a62d8e36a8247c0 (diff)
downloadcoreutils-76685fb1e90617c417cf7606ec9bd5cbf0abdd06.tar.xz
(same_file_ok): New function, extracted from copy_internal,
and partially rewritten. (copy_internal): Unlink destination file when unlink_dest_before_opening option is set, and when the source is neither a regular file nor a directory.
Diffstat (limited to 'src')
-rw-r--r--src/copy.c251
1 files changed, 193 insertions, 58 deletions
diff --git a/src/copy.c b/src/copy.c
index 70054752d..de7dfd87a 100644
--- a/src/copy.c
+++ b/src/copy.c
@@ -213,8 +213,7 @@ copy_reg (const char *src_path, const char *dst_path,
{
dest_desc = open (dst_path, O_WRONLY | O_TRUNC, dst_mode);
- /* FIXME: Rename force to unlink_dest_upon_failed_open. */
- if (dest_desc < 0 && x->force)
+ if (dest_desc < 0 && x->unlink_dest_after_failed_open)
{
if (unlink (dst_path))
{
@@ -377,6 +376,177 @@ close_src_desc:
return return_val;
}
+/* Return nonzero if it's ok that the source and destination
+ files are the `same' by some measure. The goal is to avoid
+ making the `copy' operation remove both copies of the file
+ in that case, while still allowing the user to e.g., move or
+ copy a regular file onto a symlink that points to it.
+ Try to minimize the cost of this function in the common case. */
+
+static int
+same_file_ok (const char *src_path, const struct stat *src_sb,
+ const char *dst_path, const struct stat *dst_sb,
+ const struct cp_options *x, int *return_now)
+{
+ const struct stat *src_sb_link;
+ const struct stat *dst_sb_link;
+ const struct stat *src_sb_no_link;
+ const struct stat *dst_sb_no_link;
+
+ int same = (SAME_INODE (*src_sb, *dst_sb));
+
+ *return_now = 0;
+
+ /* FIXME: this should (at the very least) be moved into the following
+ if-block. More likely, it should be removed, because it inhibits
+ making backups. But removing it will result in a change in behavior
+ that will probably have to be documented -- and tests will have to
+ be updated. */
+ if (same && x->hard_link)
+ {
+ *return_now = 1;
+ return 1;
+ }
+
+ if (x->xstat == lstat)
+ {
+
+ /* The backup code ensures there's a copy, so it's ok to remove
+ any destination file. But there's one exception: when both
+ source and destination are the same directory entry. In that
+ case, moving the destination file aside (in making the backup)
+ would also rename the source file and result in an error. */
+ if (x->backup_type != none)
+ {
+ if (!same)
+ return 1;
+
+ return ! same_name (src_path, dst_path);
+ }
+
+ /* They may refer to the same file if we're in move mode and the
+ target is a symlink. That is ok, since we remove any existing
+ destination file before opening it -- via `rename' if they're on
+ the same file system, via `unlink (DST_PATH)' otherwise. */
+ if (x->move_mode && S_ISLNK (dst_sb->st_mode))
+ return 1;
+
+ /* If both the source and destination files are symlinks (and we'll
+ know this here IFF preserving symlinks (aka xstat == lstat),
+ then it's ok. */
+ if (S_ISLNK (src_sb->st_mode) && S_ISLNK (dst_sb->st_mode))
+ return 1;
+
+ src_sb_link = src_sb;
+ dst_sb_link = dst_sb;
+ }
+ else
+ {
+ static struct stat tmp_dst_sb;
+ static struct stat tmp_src_sb;
+ if (!same)
+ return 1;
+
+ if (lstat (dst_path, &tmp_dst_sb)
+ || lstat (src_path, &tmp_src_sb))
+ return 1;
+
+ src_sb_link = &tmp_src_sb;
+ dst_sb_link = &tmp_dst_sb;
+
+ /* If both are symlinks, then it's ok, but only if the destination
+ will be unlinked before being opened. This is like the test
+ above, but with the addition of the unlink_dest_before_opening
+ conjunct because otherwise, with two symlinks to the same target,
+ we'd end up truncating the source file. */
+ if (S_ISLNK (src_sb_link->st_mode) && S_ISLNK (dst_sb_link->st_mode)
+ && x->unlink_dest_before_opening)
+ return 1;
+
+ /* FIXME: factor this code and above */
+ /* The backup code ensures there's a copy, so it's ok to remove
+ any destination file. But there's one exception: when both
+ source and destination are the same directory entry. In that
+ case, moving the destination file aside (in making the backup)
+ would also rename the source file and result in an error. */
+ if (x->backup_type != none)
+ {
+ if (!SAME_INODE (*src_sb_link, *dst_sb_link))
+ return 1;
+
+ return ! same_name (src_path, dst_path);
+ }
+ }
+
+#if 0
+ /* If we're making a backup, we'll detect the problem case in
+ copy_reg because SRC_PATH will no longer exist. Allowing
+ the test to be deferred lets cp do some useful things.
+ But when creating hardlinks and SRC_PATH is a symlink
+ but DST_PATH is not we must test anyway. */
+ if (x->hard_link
+ || !S_ISLNK (src_sb_link->st_mode)
+ || S_ISLNK (dst_sb_link->st_mode))
+ return 1;
+
+ /* FIXME: explain */
+ if (x->dereference != DEREF_NEVER)
+ return 1;
+#endif
+
+ /* If neither is a symlink, then it's ok as long as they aren't
+ links to the same file. */
+ if (!S_ISLNK (src_sb_link->st_mode) && !S_ISLNK (dst_sb_link->st_mode))
+ {
+ if (!SAME_INODE (*src_sb_link, *dst_sb_link))
+ return 1;
+
+ /* If they are the same file, it's ok if we're making hard links. */
+ if (x->hard_link)
+ {
+ *return_now = 1;
+ return 1;
+ }
+ }
+
+ if (x->xstat == lstat)
+ {
+ static struct stat tmp_dst_sb;
+ static struct stat tmp_src_sb;
+ if (stat (dst_path, &tmp_dst_sb)
+ || stat (src_path, &tmp_src_sb)
+ || ! SAME_INODE (tmp_dst_sb, tmp_dst_sb))
+ return 1;
+
+ /* FIXME: shouldn't this be testing whether we're making symlinks? */
+ if (x->hard_link)
+ {
+ *return_now = 1;
+ return 1;
+ }
+ src_sb_no_link = &tmp_src_sb;
+ dst_sb_no_link = &tmp_dst_sb;
+ }
+ else
+ {
+ src_sb_no_link = src_sb;
+ dst_sb_no_link = dst_sb;
+ }
+
+ /* It's ok to remove a destination symlink. But that works only when we
+ unlink before opening the destination and when they're on the same
+ partition. */
+ if (x->unlink_dest_before_opening
+ && S_ISLNK (dst_sb_no_link->st_mode))
+ return src_sb_link->st_dev == src_sb_link->st_dev;
+
+ /* FIXME: explain this!! */
+ if (x->unlink_dest_before_opening && ! same_name (src_path, dst_path))
+ return 1;
+
+ return 0;
+}
+
/* Copy the file SRC_PATH to the file DST_PATH. The files may be of
any type. NEW_DST should be nonzero if the file DST_PATH cannot
exist because its parent directory was just created; NEW_DST should
@@ -450,64 +620,17 @@ copy_internal (const char *src_path, const char *dst_path,
}
else
{
- int same;
+ int return_now;
+ int ok = same_file_ok (src_path, &src_sb, dst_path, &dst_sb,
+ x, &return_now);
+ if (return_now)
+ return 0;
- /* The destination file exists already. */
-
- same = (SAME_INODE (src_sb, dst_sb));
-
-#ifdef S_ISLNK
- /* If we're preserving symlinks (--no-dereference) and either
- file is a symlink, use stat (not xstat) to see if they refer
- to the same file. */
- if (!same
-
- /* If we'll remove DST_PATH first, then this doesn't matter. */
- && ! x->force
-
- /* Allow them to be the same (and don't set `same') if we're
- in move mode and the target is a symlink. That is ok, since
- we remove any existing destination file before opening it --
- via `rename' if they're on the same file system,
- via `unlink(DST_PATH)' otherwise. */
- && !(move_mode
- && S_ISLNK (dst_sb.st_mode))
-
- /* If we're making a backup, we'll detect the problem case in
- copy_reg because SRC_PATH will no longer exist. Allowing
- the test to be deferred lets cp do some useful things.
- But when creating hardlinks and SRC_PATH is a symlink
- but DST_PATH is not we must test anyway. */
- && (x->backup_type == none
- || (x->hard_link
- && S_ISLNK (src_sb.st_mode)
- && !S_ISLNK (dst_sb.st_mode)))
- && x->dereference == DEREF_NEVER
- && (S_ISLNK (dst_sb.st_mode) ^ S_ISLNK (src_sb.st_mode)))
+ if (! ok)
{
- struct stat dst2_sb;
- struct stat src2_sb;
- if (stat (dst_path, &dst2_sb) == 0
- && stat (src_path, &src2_sb) == 0
- && SAME_INODE (src2_sb, dst2_sb))
- {
- same = 1;
- }
- }
-#endif
-
- if (same)
- {
- if (x->hard_link)
- return 0;
-
- if (x->backup_type == none
- && (!x->force || same_name (src_path, dst_path)))
- {
- error (0, 0, _("%s and %s are the same file"),
- quote_n (0, src_path), quote_n (1, dst_path));
- return 1;
- }
+ error (0, 0, _("%s and %s are the same file"),
+ quote_n (0, src_path), quote_n (1, dst_path));
+ return 1;
}
if (!S_ISDIR (src_type))
@@ -607,6 +730,18 @@ copy_internal (const char *src_path, const char *dst_path,
}
new_dst = 1;
}
+ else if (x->unlink_dest_before_opening
+ || (x->xstat == lstat
+ && ! S_ISREG (src_sb.st_mode)
+ && ! S_ISDIR (src_sb.st_mode)))
+ {
+ if (unlink (dst_path) && errno != ENOENT)
+ {
+ error (0, errno, _("cannot remove %s"), quote (dst_path));
+ return 1;
+ }
+ new_dst = 1;
+ }
}
}