diff mbox series

[v3,1/2] worktree: fix worktree add race.

Message ID e134801d570d0a0c85424eb80b41893f4d8383ca.1550679076.git.msuchanek@suse.de (mailing list archive)
State New, archived
Headers show
Series [v3,1/2] worktree: fix worktree add race. | expand

Commit Message

Michal Suchánek Feb. 20, 2019, 4:16 p.m. UTC
Git runs a stat loop to find a worktree name that's available and then does
mkdir on the found name. Turn it to mkdir loop to avoid another invocation of
worktree add finding the same free name and creating the directory first.

Signed-off-by: Michal Suchanek <msuchanek@suse.de>
---
v2:
- simplify loop exit condition
- exit early if the mkdir fails for reason other than already present
worktree
- make counter unsigned
---
 builtin/worktree.c | 12 +++++++-----
 1 file changed, 7 insertions(+), 5 deletions(-)

Comments

Eric Sunshine Feb. 20, 2019, 4:34 p.m. UTC | #1
On Wed, Feb 20, 2019 at 11:17 AM Michal Suchanek <msuchanek@suse.de> wrote:
> Git runs a stat loop to find a worktree name that's available and then does
> mkdir on the found name. Turn it to mkdir loop to avoid another invocation of
> worktree add finding the same free name and creating the directory first.
>
> Signed-off-by: Michal Suchanek <msuchanek@suse.de>
> ---
> diff --git a/builtin/worktree.c b/builtin/worktree.c
> @@ -295,8 +295,12 @@ static int add_worktree(const char *path, const char *refname,
>         if (safe_create_leading_directories_const(sb_repo.buf))
>                 die_errno(_("could not create leading directories of '%s'"),
>                           sb_repo.buf);
> -       while (!stat(sb_repo.buf, &st)) {
> +       while (mkdir(sb_repo.buf, 0777)) {
>                 counter++;
> +               if ((errno != EEXIST) || !counter /* overflow */)
> +                       die_errno(_("could not create directory of '%s'"),
> +                                 sb_repo.buf);
>                 strbuf_setlen(&sb_repo, len);
>                 strbuf_addf(&sb_repo, "%d", counter);
>         }
> @@ -306,8 +310,6 @@ static int add_worktree(const char *path, const char *refname,
>         atexit(remove_junk);
>         sigchain_push_common(remove_junk_on_signal);
> -       if (mkdir(sb_repo.buf, 0777))
> -               die_errno(_("could not create directory of '%s'"), sb_repo.buf);
>         junk_git_dir = xstrdup(sb_repo.buf);
>         is_junk = 1;

Did you audit this "junk" handling to verify that stuff which ought to
be cleaned up still is cleaned up now that the mkdir() and die() have
been moved above the atexit(remove_junk) invocation?

I did just audit it, and I _think_ that it still works as expected,
but it would be good to hear that someone else has come to the same
conclusion.
Michal Suchánek Feb. 20, 2019, 5:29 p.m. UTC | #2
On Wed, 20 Feb 2019 11:34:54 -0500
Eric Sunshine <sunshine@sunshineco.com> wrote:

> On Wed, Feb 20, 2019 at 11:17 AM Michal Suchanek <msuchanek@suse.de> wrote:
> > Git runs a stat loop to find a worktree name that's available and then does
> > mkdir on the found name. Turn it to mkdir loop to avoid another invocation of
> > worktree add finding the same free name and creating the directory first.
> >
> > Signed-off-by: Michal Suchanek <msuchanek@suse.de>
> > ---
> > diff --git a/builtin/worktree.c b/builtin/worktree.c
> > @@ -295,8 +295,12 @@ static int add_worktree(const char *path, const char *refname,
> >         if (safe_create_leading_directories_const(sb_repo.buf))
> >                 die_errno(_("could not create leading directories of '%s'"),
> >                           sb_repo.buf);
> > -       while (!stat(sb_repo.buf, &st)) {
> > +       while (mkdir(sb_repo.buf, 0777)) {
> >                 counter++;
> > +               if ((errno != EEXIST) || !counter /* overflow */)
> > +                       die_errno(_("could not create directory of '%s'"),
> > +                                 sb_repo.buf);
> >                 strbuf_setlen(&sb_repo, len);
> >                 strbuf_addf(&sb_repo, "%d", counter);
> >         }
> > @@ -306,8 +310,6 @@ static int add_worktree(const char *path, const char *refname,
> >         atexit(remove_junk);
> >         sigchain_push_common(remove_junk_on_signal);
> > -       if (mkdir(sb_repo.buf, 0777))
> > -               die_errno(_("could not create directory of '%s'"), sb_repo.buf);
> >         junk_git_dir = xstrdup(sb_repo.buf);
> >         is_junk = 1;  
> 
> Did you audit this "junk" handling to verify that stuff which ought to
> be cleaned up still is cleaned up now that the mkdir() and die() have
> been moved above the atexit(remove_junk) invocation?
> 
> I did just audit it, and I _think_ that it still works as expected,
> but it would be good to hear that someone else has come to the same
> conclusion.

The die() is executed only when mkdir() fails so there is no junk to
clean up in that case.

Thanks

Michal
Duy Nguyen March 8, 2019, 9:20 a.m. UTC | #3
Junio, it seems 2/2 is stuck in an endless discussion. But 1/2 is good
regardless, maybe pick it up now and let 2/2 come later whenever it's
ready?

On Wed, Feb 20, 2019 at 11:16 PM Michal Suchanek <msuchanek@suse.de> wrote:
>
> Git runs a stat loop to find a worktree name that's available and then does
> mkdir on the found name. Turn it to mkdir loop to avoid another invocation of
> worktree add finding the same free name and creating the directory first.
>
> Signed-off-by: Michal Suchanek <msuchanek@suse.de>
> ---
> v2:
> - simplify loop exit condition
> - exit early if the mkdir fails for reason other than already present
> worktree
> - make counter unsigned
> ---
>  builtin/worktree.c | 12 +++++++-----
>  1 file changed, 7 insertions(+), 5 deletions(-)
>
> diff --git a/builtin/worktree.c b/builtin/worktree.c
> index 3f9907fcc994..85a604cfe98c 100644
> --- a/builtin/worktree.c
> +++ b/builtin/worktree.c
> @@ -268,10 +268,10 @@ static int add_worktree(const char *path, const char *refname,
>         struct strbuf sb_git = STRBUF_INIT, sb_repo = STRBUF_INIT;
>         struct strbuf sb = STRBUF_INIT;
>         const char *name;
> -       struct stat st;
>         struct child_process cp = CHILD_PROCESS_INIT;
>         struct argv_array child_env = ARGV_ARRAY_INIT;
> -       int counter = 0, len, ret;
> +       unsigned int counter = 0;
> +       int len, ret;
>         struct strbuf symref = STRBUF_INIT;
>         struct commit *commit = NULL;
>         int is_branch = 0;
> @@ -295,8 +295,12 @@ static int add_worktree(const char *path, const char *refname,
>         if (safe_create_leading_directories_const(sb_repo.buf))
>                 die_errno(_("could not create leading directories of '%s'"),
>                           sb_repo.buf);
> -       while (!stat(sb_repo.buf, &st)) {
> +
> +       while (mkdir(sb_repo.buf, 0777)) {
>                 counter++;
> +               if ((errno != EEXIST) || !counter /* overflow */)
> +                       die_errno(_("could not create directory of '%s'"),
> +                                 sb_repo.buf);
>                 strbuf_setlen(&sb_repo, len);
>                 strbuf_addf(&sb_repo, "%d", counter);
>         }
> @@ -306,8 +310,6 @@ static int add_worktree(const char *path, const char *refname,
>         atexit(remove_junk);
>         sigchain_push_common(remove_junk_on_signal);
>
> -       if (mkdir(sb_repo.buf, 0777))
> -               die_errno(_("could not create directory of '%s'"), sb_repo.buf);
>         junk_git_dir = xstrdup(sb_repo.buf);
>         is_junk = 1;
>
> --
> 2.20.1
>
Eric Sunshine March 8, 2019, 9:37 a.m. UTC | #4
On Fri, Mar 8, 2019 at 4:20 AM Duy Nguyen <pclouds@gmail.com> wrote:
> Junio, it seems 2/2 is stuck in an endless discussion. But 1/2 is good
> regardless, maybe pick it up now and let 2/2 come later whenever it's
> ready?

Yep, 1/2 seems a good idea and has not been controversial. It may not
solve all the race conditions, but it is a good step forward.
Junio C Hamano March 11, 2019, 1:55 a.m. UTC | #5
Duy Nguyen <pclouds@gmail.com> writes:

> Junio, it seems 2/2 is stuck in an endless discussion. But 1/2 is good
> regardless, maybe pick it up now and let 2/2 come later whenever it's
> ready?

Thanks for poking, and I think it is a good idea.
diff mbox series

Patch

diff --git a/builtin/worktree.c b/builtin/worktree.c
index 3f9907fcc994..85a604cfe98c 100644
--- a/builtin/worktree.c
+++ b/builtin/worktree.c
@@ -268,10 +268,10 @@  static int add_worktree(const char *path, const char *refname,
 	struct strbuf sb_git = STRBUF_INIT, sb_repo = STRBUF_INIT;
 	struct strbuf sb = STRBUF_INIT;
 	const char *name;
-	struct stat st;
 	struct child_process cp = CHILD_PROCESS_INIT;
 	struct argv_array child_env = ARGV_ARRAY_INIT;
-	int counter = 0, len, ret;
+	unsigned int counter = 0;
+	int len, ret;
 	struct strbuf symref = STRBUF_INIT;
 	struct commit *commit = NULL;
 	int is_branch = 0;
@@ -295,8 +295,12 @@  static int add_worktree(const char *path, const char *refname,
 	if (safe_create_leading_directories_const(sb_repo.buf))
 		die_errno(_("could not create leading directories of '%s'"),
 			  sb_repo.buf);
-	while (!stat(sb_repo.buf, &st)) {
+
+	while (mkdir(sb_repo.buf, 0777)) {
 		counter++;
+		if ((errno != EEXIST) || !counter /* overflow */)
+			die_errno(_("could not create directory of '%s'"),
+				  sb_repo.buf);
 		strbuf_setlen(&sb_repo, len);
 		strbuf_addf(&sb_repo, "%d", counter);
 	}
@@ -306,8 +310,6 @@  static int add_worktree(const char *path, const char *refname,
 	atexit(remove_junk);
 	sigchain_push_common(remove_junk_on_signal);
 
-	if (mkdir(sb_repo.buf, 0777))
-		die_errno(_("could not create directory of '%s'"), sb_repo.buf);
 	junk_git_dir = xstrdup(sb_repo.buf);
 	is_junk = 1;