Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Avoid traversing NTFS junction points in git clean -dfx #2268

Merged
merged 5 commits into from
Jul 23, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions builtin/clean.c
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ static const char *msg_remove = N_("Removing %s\n");
static const char *msg_would_remove = N_("Would remove %s\n");
static const char *msg_skip_git_dir = N_("Skipping repository %s\n");
static const char *msg_would_skip_git_dir = N_("Would skip repository %s\n");
#ifndef CAN_UNLINK_MOUNT_POINTS
static const char *msg_skip_mount_point = N_("Skipping mount point %s\n");
static const char *msg_would_skip_mount_point = N_("Would skip mount point %s\n");
#endif
static const char *msg_warn_remove_failed = N_("failed to remove %s");

enum color_clean {
Expand Down Expand Up @@ -168,6 +172,29 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,
goto out;
}

if (is_mount_point(path)) {
#ifndef CAN_UNLINK_MOUNT_POINTS
if (!quiet) {
quote_path_relative(path->buf, prefix, &quoted);
printf(dry_run ?
_(msg_would_skip_mount_point) :
_(msg_skip_mount_point), quoted.buf);
}
*dir_gone = 0;
#else
if (!dry_run && unlink(path->buf)) {
int saved_errno = errno;
quote_path_relative(path->buf, prefix, &quoted);
errno = saved_errno;
warning_errno(_(msg_warn_remove_failed), quoted.buf);
*dir_gone = 0;
ret = -1;
}
#endif

goto out;
}

dir = opendir(path->buf);
if (!dir) {
/* an empty dir could be removed even if it is unreadble */
Expand Down Expand Up @@ -957,6 +984,7 @@ int cmd_clean(int argc, const char **argv, const char *prefix)

if (read_cache() < 0)
die(_("index file corrupt"));
enable_fscache(active_nr);

if (!ignored)
setup_standard_excludes(&dir);
Expand Down Expand Up @@ -1046,6 +1074,7 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
strbuf_reset(&abs_path);
}

disable_fscache();
strbuf_release(&abs_path);
strbuf_release(&buf);
string_list_clear(&del_list, 0);
Expand Down
1 change: 1 addition & 0 deletions cache.h
Original file line number Diff line number Diff line change
Expand Up @@ -1267,6 +1267,7 @@ int normalize_path_copy_len(char *dst, const char *src, int *prefix_len);
int normalize_path_copy(char *dst, const char *src);
int longest_ancestor_length(const char *path, struct string_list *prefixes);
char *strip_path_suffix(const char *path, const char *suffix);
int is_mount_point_via_stat(struct strbuf *path);
int daemon_avoid_alias(const char *path);

/*
Expand Down
24 changes: 24 additions & 0 deletions compat/mingw.c
Original file line number Diff line number Diff line change
Expand Up @@ -2845,6 +2845,30 @@ pid_t waitpid(pid_t pid, int *status, int options)
return -1;
}

int (*win32_is_mount_point)(struct strbuf *path) = mingw_is_mount_point;

int mingw_is_mount_point(struct strbuf *path)
{
WIN32_FIND_DATAW findbuf = { 0 };
HANDLE handle;
wchar_t wfilename[MAX_LONG_PATH];
int wlen = xutftowcs_long_path(wfilename, path->buf);
if (wlen < 0)
die(_("could not get long path for '%s'"), path->buf);

/* remove trailing slash, if any */
if (wlen > 0 && wfilename[wlen - 1] == L'/')
wfilename[--wlen] = L'\0';

handle = FindFirstFileW(wfilename, &findbuf);
if (handle == INVALID_HANDLE_VALUE)
return 0;
FindClose(handle);

return (findbuf.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) &&
(findbuf.dwReserved0 == IO_REPARSE_TAG_MOUNT_POINT);
}

int xutftowcsn(wchar_t *wcs, const char *utfs, size_t wcslen, int utflen)
{
int upos = 0, wpos = 0;
Expand Down
5 changes: 5 additions & 0 deletions compat/mingw.h
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,11 @@ static inline void convert_slashes(char *path)
if (*path == '\\')
*path = '/';
}
struct strbuf;
int mingw_is_mount_point(struct strbuf *path);
extern int (*win32_is_mount_point)(struct strbuf *path);
#define is_mount_point win32_is_mount_point
#define CAN_UNLINK_MOUNT_POINTS 1
drizzd marked this conversation as resolved.
Show resolved Hide resolved
#define PATH_SEP ';'
extern char *mingw_query_user_email(void);
#define query_user_email mingw_query_user_email
Expand Down
42 changes: 40 additions & 2 deletions compat/win32/fscache.c
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ static struct trace_key trace_fscache = TRACE_KEY_INIT(FSCACHE);
struct fsentry {
struct hashmap_entry ent;
mode_t st_mode;
ULONG reparse_tag;
/* Length of name. */
unsigned short len;
/*
Expand Down Expand Up @@ -180,6 +181,10 @@ static struct fsentry *fseentry_create_entry(struct fscache *cache, struct fsent

fse = fsentry_alloc(cache, list, buf, len);

fse->reparse_tag =
fdata->FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT ?
fdata->EaSize : 0;

/*
* On certain Windows versions, host directories mapped into
* Windows Containers ("Volumes", see https://docs.docker.com/storage/volumes/)
Expand All @@ -189,8 +194,7 @@ static struct fsentry *fseentry_create_entry(struct fscache *cache, struct fsent
* Let's work around this by detecting that situation and
* telling Git that these are *not* symbolic links.
*/
if (fdata->FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT &&
fdata->EaSize == IO_REPARSE_TAG_SYMLINK &&
if (fse->reparse_tag == IO_REPARSE_TAG_SYMLINK &&
sizeof(buf) > (list ? list->len + 1 : 0) + fse->len + 1 &&
is_inside_windows_container()) {
size_t off = 0;
Expand Down Expand Up @@ -461,6 +465,7 @@ int fscache_enable(size_t initial_size)
/* redirect opendir and lstat to the fscache implementations */
opendir = fscache_opendir;
lstat = fscache_lstat;
win32_is_mount_point = fscache_is_mount_point;
}
initialized++;
LeaveCriticalSection(&fscache_cs);
Expand Down Expand Up @@ -521,6 +526,7 @@ void fscache_disable(void)
/* reset opendir and lstat to the original implementations */
opendir = dirent_opendir;
lstat = mingw_lstat;
win32_is_mount_point = mingw_is_mount_point;
}
LeaveCriticalSection(&fscache_cs);

Expand Down Expand Up @@ -588,6 +594,38 @@ int fscache_lstat(const char *filename, struct stat *st)
return 0;
}

/*
* is_mount_point() replacement, uses cache if enabled, otherwise falls
* back to mingw_is_mount_point().
*/
int fscache_is_mount_point(struct strbuf *path)
{
int dirlen, base, len;
struct fsentry key[2], *fse;
struct fscache *cache = fscache_getcache();

if (!cache || !do_fscache_enabled(cache, path->buf))
return mingw_is_mount_point(path);

cache->lstat_requests++;
/* split path into path + name */
len = path->len;
if (len && is_dir_sep(path->buf[len - 1]))
len--;
base = len;
while (base && !is_dir_sep(path->buf[base - 1]))
base--;
dirlen = base ? base - 1 : 0;

/* lookup entry for path + name in cache */
fsentry_init(key, NULL, path->buf, dirlen);
fsentry_init(key + 1, key, path->buf + base, len - base);
fse = fscache_get(cache, key + 1);
if (!fse)
return mingw_is_mount_point(path);
return fse->reparse_tag == IO_REPARSE_TAG_MOUNT_POINT;
}

typedef struct fscache_DIR {
struct DIR base_dir; /* extend base struct DIR */
struct fsentry *pfsentry;
Expand Down
1 change: 1 addition & 0 deletions compat/win32/fscache.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ void fscache_flush(void);

DIR *fscache_opendir(const char *dir);
int fscache_lstat(const char *file_name, struct stat *buf);
int fscache_is_mount_point(struct strbuf *path);

/* opaque fscache structure */
struct fscache;
Expand Down
4 changes: 4 additions & 0 deletions git-compat-util.h
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,10 @@ static inline int git_create_symlink(struct index_state *index, const char *targ
#define create_symlink git_create_symlink
#endif

#ifndef is_mount_point
#define is_mount_point is_mount_point_via_stat
#endif

#ifndef query_user_email
#define query_user_email() NULL
#endif
Expand Down
39 changes: 39 additions & 0 deletions path.c
Original file line number Diff line number Diff line change
Expand Up @@ -1253,6 +1253,45 @@ char *strip_path_suffix(const char *path, const char *suffix)
return xstrndup(path, chomp_trailing_dir_sep(path, path_len));
}

int is_mount_point_via_stat(struct strbuf *path)
{
size_t len = path->len;
unsigned int current_dev;
struct stat st;

if (!strcmp("/", path->buf))
return 1;

strbuf_addstr(path, "/.");
if (lstat(path->buf, &st)) {
/*
* If we cannot access the current directory, we cannot say
* that it is a bind mount.
*/
strbuf_setlen(path, len);
return 0;
}
current_dev = st.st_dev;

/* Now look at the parent directory */
drizzd marked this conversation as resolved.
Show resolved Hide resolved
strbuf_addch(path, '.');
if (lstat(path->buf, &st)) {
/*
* If we cannot access the parent directory, we cannot say
drizzd marked this conversation as resolved.
Show resolved Hide resolved
* that it is a bind mount.
*/
strbuf_setlen(path, len);
return 0;
}
strbuf_setlen(path, len);

/*
* If the device ID differs between current and parent directory,
* then it is a bind mount.
*/
return current_dev != st.st_dev;
}

int daemon_avoid_alias(const char *p)
{
int sl, ndot;
Expand Down
10 changes: 10 additions & 0 deletions t/t7300-clean.sh
Original file line number Diff line number Diff line change
Expand Up @@ -680,4 +680,14 @@ test_expect_success MINGW 'handle clean & core.longpaths = false nicely' '
grep "too long" .git/err
'

test_expect_success MINGW 'clean does not traverse mount points' '
mkdir target &&
>target/dont-clean-me &&
git init with-mountpoint &&
cmd //c "mklink /j with-mountpoint\\mountpoint target" &&
git -C with-mountpoint clean -dfx &&
test_path_is_missing with-mountpoint/mountpoint &&
test_path_is_file target/dont-clean-me
'

test_done