[PATCH 2/2] checkout: don't follow symlinks when removing entries

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



At 1d718a5108 ("do not overwrite untracked symlinks", 2011-02-20),
symlink.c:check_leading_path() started returning different codes for
FL_ENOENT and FL_SYMLINK. But one of its callers, unlink_entry(), was
not adjusted for this change, so it started to follow symlinks on the
leading path of to-be-removed entries. Fix that and add a regression
test.

Note that since 1d718a5108 check_leading_path() no longer differentiates
the case where it found a symlink in the path's leading components from
the cases where it found a regular file or failed to lstat() the
component. So, a side effect of this current patch is that
unlink_entry() now returns early in all of these three cases. And
because we no longer try to unlink such paths, we also don't get the
warning from remove_or_warn().

For the regular file and symlink cases, it's questionable whether the
warning was useful in the first place: unlink_entry() removes tracked
paths that should no longer be present in the state we are checking out
to. If the path had its leading dir replaced by another file, it means
that the basename already doesn't exist, so there is no need for a
warning. Sure, we are leaving a regular file or symlink behind at the
path's dirname, but this file is either untracked now (so again, no
need to warn), or it will be replaced by a tracked file during the next
phase of this checkout operation.

As for failing to lstat() one of the leading components, the basename
might still exist only we cannot unlink it (e.g. due to the lack of the
required permissions). Since the user expect it to be removed
(especially with checkout's --no-overlay option), add back the warning
in this more relevant case.

Signed-off-by: Matheus Tavares <matheus.bernardino@xxxxxx>
---
 cache.h                       |  2 +-
 entry.c                       |  2 +-
 symlinks.c                    | 30 ++++++++++++++++++++++--------
 t/t2021-checkout-overwrite.sh | 12 ++++++++++++
 unpack-trees.c                |  2 +-
 5 files changed, 37 insertions(+), 11 deletions(-)

diff --git a/cache.h b/cache.h
index 6fda8091f1..4f0b29bfe4 100644
--- a/cache.h
+++ b/cache.h
@@ -1659,7 +1659,7 @@ static inline void cache_def_clear(struct cache_def *cache)
 
 int has_symlink_leading_path(const char *name, int len);
 int threaded_has_symlink_leading_path(struct cache_def *, const char *, int);
-int check_leading_path(const char *name, int len);
+int check_leading_path(const char *name, int len, int warn_on_lstat_err);
 int has_dirs_only_path(const char *name, int len, int prefix_len);
 void invalidate_lstat_cache(void);
 void schedule_dir_for_removal(const char *name, int len);
diff --git a/entry.c b/entry.c
index 7b9f43716f..33ce80ca91 100644
--- a/entry.c
+++ b/entry.c
@@ -530,7 +530,7 @@ void unlink_entry(const struct cache_entry *ce)
 		submodule_move_head(ce->name, "HEAD", NULL,
 				    SUBMODULE_MOVE_HEAD_FORCE);
 	}
-	if (!check_leading_path(ce->name, ce_namelen(ce)))
+	if (check_leading_path(ce->name, ce_namelen(ce), 1) >= 0)
 		return;
 	if (remove_or_warn(ce->ce_mode, ce->name))
 		return;
diff --git a/symlinks.c b/symlinks.c
index fbccd340f0..5232d02020 100644
--- a/symlinks.c
+++ b/symlinks.c
@@ -1,6 +1,7 @@
 #include "cache.h"
 
-static int threaded_check_leading_path(struct cache_def *cache, const char *name, int len);
+static int threaded_check_leading_path(struct cache_def *cache, const char *name,
+				       int len, int warn_on_lstat_err);
 static int threaded_has_dirs_only_path(struct cache_def *cache, const char *name, int len, int prefix_len);
 
 /*
@@ -72,7 +73,7 @@ static int lstat_cache_matchlen(struct cache_def *cache,
 				int prefix_len_stat_func)
 {
 	int match_len, last_slash, last_slash_dir, previous_slash;
-	int save_flags, ret;
+	int save_flags, ret, saved_errno = 0;
 	struct stat st;
 
 	if (cache->track_flags != track_flags ||
@@ -139,6 +140,7 @@ static int lstat_cache_matchlen(struct cache_def *cache,
 
 		if (ret) {
 			*ret_flags = FL_LSTATERR;
+			saved_errno = errno;
 			if (errno == ENOENT)
 				*ret_flags |= FL_NOENT;
 		} else if (S_ISDIR(st.st_mode)) {
@@ -180,6 +182,8 @@ static int lstat_cache_matchlen(struct cache_def *cache,
 	} else {
 		reset_lstat_cache(cache);
 	}
+	if (saved_errno)
+		errno = saved_errno;
 	return match_len;
 }
 
@@ -207,9 +211,10 @@ int has_symlink_leading_path(const char *name, int len)
 	return threaded_has_symlink_leading_path(&default_cache, name, len);
 }
 
-int check_leading_path(const char *name, int len)
+int check_leading_path(const char *name, int len, int warn_on_lstat_err)
 {
-	return threaded_check_leading_path(&default_cache, name, len);
+	return threaded_check_leading_path(&default_cache, name, len,
+					   warn_on_lstat_err);
 }
 
 /*
@@ -218,19 +223,28 @@ int check_leading_path(const char *name, int len)
  * Return -1 if leading path exists and is a directory.
  *
  * Return the length of a leading component if it either exists but it's not a
- * directory, or if we were unable to lstat() it.
+ * directory, or if we were unable to lstat() it. If warn_on_lstat_err is true,
+ * also emit a warning for this error.
  */
-static int threaded_check_leading_path(struct cache_def *cache, const char *name, int len)
+static int threaded_check_leading_path(struct cache_def *cache, const char *name,
+				       int len, int warn_on_lstat_err)
 {
 	int flags;
 	int match_len = lstat_cache_matchlen(cache, name, len, &flags,
 			   FL_SYMLINK|FL_NOENT|FL_DIR, USE_ONLY_LSTAT);
+	int saved_errno = errno;
+
 	if (flags & FL_NOENT)
 		return 0;
 	else if (flags & FL_DIR)
 		return -1;
-	else
-		return match_len;
+	if (warn_on_lstat_err && (flags & FL_LSTATERR)) {
+		char *path = xmemdupz(name, match_len);
+		errno = saved_errno;
+		warning_errno(_("failed to lstat '%s'"), path);
+		free(path);
+	}
+	return match_len;
 }
 
 int has_dirs_only_path(const char *name, int len, int prefix_len)
diff --git a/t/t2021-checkout-overwrite.sh b/t/t2021-checkout-overwrite.sh
index c2ada7de37..70d69263e6 100755
--- a/t/t2021-checkout-overwrite.sh
+++ b/t/t2021-checkout-overwrite.sh
@@ -51,4 +51,16 @@ test_expect_success SYMLINKS 'the symlink remained' '
 	test -h a/b
 '
 
+test_expect_success SYMLINKS 'checkout -f must not follow symlinks when removing entries' '
+	git checkout -f start &&
+	mkdir dir &&
+	>dir/f &&
+	git add dir/f &&
+	git commit -m "add dir/f" &&
+	mv dir untracked &&
+	ln -s untracked dir &&
+	git checkout -f HEAD~ &&
+	test_path_is_file untracked/f
+'
+
 test_done
diff --git a/unpack-trees.c b/unpack-trees.c
index eb8fcda31b..ed0db8031d 100644
--- a/unpack-trees.c
+++ b/unpack-trees.c
@@ -2097,7 +2097,7 @@ static int verify_absent_1(const struct cache_entry *ce,
 	if (o->index_only || o->reset || !o->update)
 		return 0;
 
-	len = check_leading_path(ce->name, ce_namelen(ce));
+	len = check_leading_path(ce->name, ce_namelen(ce), 0);
 	if (!len)
 		return 0;
 	else if (len > 0) {
-- 
2.30.1




[Index of Archives]     [Linux Kernel Development]     [Gcc Help]     [IETF Annouce]     [DCCP]     [Netdev]     [Networking]     [Security]     [V4L]     [Bugtraq]     [Yosemite]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Linux SCSI]     [Fedora Users]

  Powered by Linux