"GOT", but the "O" is a cute, smiling pufferfish. Index | Thread | Search

From:
Yang Zhong <yzhong@freebsdfoundation.org>
Subject:
Re: change got_worktree_init, open_worktree to use fds
To:
Yang Zhong <yzhong@freebsdfoundation.org>, gameoftrees@openbsd.org
Date:
Tue, 1 Dec 2020 14:39:06 -0800

Download raw body.

Thread
Here are updated changes. I've attached them this time.
diff_1 contains the revised changes from before.

> Since you're adding 'int root_fd' to struct got_worktree I don't see a
> problem with adding a corresponding argument to got_worktree_open() in
> order to initialize the root_fd struct member.

diff_2 contains the changes needed for this. It's a lot of the same stuff, so
separating it out makes it easier to read.

> Careful, the realpath() call in open_worktree() was added to fix a bug.
> Is the test which was added in 7d61d89137e4704e685dacfcfb71a42a87ba9f78
> still passing?

We're having a bit of trouble getting the tests to work on FreeBSD,
but I've installed OpenBSD and have tested the patch there.

> > --- a/lib/got_lib_worktree.h
> > +++ b/lib/got_lib_worktree.h
> > @@ -17,6 +17,8 @@
> >  struct got_worktree {
> >      char *root_path;
> >      char *repo_path;
> > +    int root_fd;
> > +    int repo_fd;
>
> Why is 'repo_fd' stored here?
> Shouldn't the repository's directory fd be part of struct got_repository?

You're right; I added this early on and didn't notice until now that I
never use it. I've also moved all the variable declarations to the right
place.

On Tue, Dec 1, 2020 at 3:29 AM Stefan Sperling <stsp@stsp.name> wrote:
>
> On Mon, Nov 30, 2020 at 09:42:51AM -0800, Yang Zhong wrote:
> > Here are some simple changes for initializing and opening worktrees.
> > In addition to open() to openat()-style changes, I've also added
> > fd fields to the worktree struct, as I'll need it for changes like
> > this in other parts of the program.
>
> Thanks!
>
> The patch as received on the mailing list cannot be applied.
> Looks like your mailer wrapped some overlong lines:
>
> Patching file got/got.c using Plan A...
> Hunk #1 failed at 2711.
> patch: **** malformed patch at line 54: path_prefix, repo);
>
> After addressing the review comments below, could you send an updated patch?
> And if your mailer cannot send inline patches without mangling them, could
> you send the patch as an attachment?
>
> > The helper update_meta_file wasn't changed to use fds here, as it
> > requires a function like mkostempsat().
>
> Right. millert@ is already looking into that.
>
> > I've changed the signature of got_worktree_init to take in an fd, as
> > it's only called in one place. I understand that changing
> > got_worktree_open's signature should be avoided, so currently I have
> > got_worktree_open open the fd inside the function instead of taking
> > it in. I'm not sure what the best solution is in this case.
>
> So you'll eventually want to pass an fd into got_worktree_open(), right?
>
> Since you're adding 'int root_fd' to struct got_worktree I don't see a
> problem with adding a corresponding argument to got_worktree_open() in
> order to initialize the root_fd struct member. Upstream code can store an
> open directory file descriptor in struct got_worktree and close it in
> got_worktree_close(), even if this file descriptor is not otherwise used
> directly.
>
> > I've also changed got_worktree_open to no longer call realpath() on
> > the worktree_path. Instead, where it is needed I make the path
> > absolute beforehand (It's only needed for checkout). This is more
> > amenable to Capsicum, and doesn't require much change.
>
> Careful, the realpath() call in open_worktree() was added to fix a bug.
> Is the test which was added in 7d61d89137e4704e685dacfcfb71a42a87ba9f78
> still passing?
>
> Are all the tests passing, for that matter? :)
> It would be good to run regression tests on both FreeBSD and OpenBSD
> platforms for such patches. As things stand now, the Got 'main' branch
> should compile without any extra dependencies in a stock install of
> OpenBSD 6.8. How to compile Got and run tests on OpenBSD is documented
> in Got's README file.
>
> > I think these changes are mostly upstream-friendly. I would appreciate
> > if you could evaluate the direction of these changes.
>
> Please avoid declaring new variables in the middle of a scope. This is
> incompatible with OpenBSD style(9) rules, which we try to adhere to.
>
> More feedback inline below:
>
> > diff --git a/got/got.c b/got/got.c
> > index 0357368c..33c8cfa6 100644
> > --- a/got/got.c
> > +++ b/got/got.c
> > @@ -2711,6 +2711,20 @@ cmd_checkout(int argc, char *argv[])
> >              goto done;
> >          }
> >      }
> > +    int worktree_fd = open(worktree_path, O_DIRECTORY);
>
> So, for example, 'worktree_fd' should be declared at the beginning of
> cmd_checkout().
>
> > +    if (worktree_fd == -1) {
> > +        error = got_error_from_errno2("open", worktree_path);
> > +        goto done;
> > +    }
> > +
> > +    /* Make worktree path absolute */
> > +    char *worktree_path_abs = realpath(worktree_path, NULL);
>
> This should also be declared at the beginning of the scope. You could name
> this variable 'worktree_abspath' for consistency with similar variables
> elsewhere in the code base. (There's no right or wrong naming here, it's
> just that many variables already use the 'abspath' idiom.)
>
> > +    if (worktree_path_abs == NULL) {
> > +        error = got_error_from_errno2("realpath", worktree_path_abs);
> > +        goto done;
> > +    }
> > +    free(worktree_path);
> > +    worktree_path = worktree_path_abs;
> >
> >      error = apply_unveil(got_repo_get_path(repo), 0, worktree_path);
> >      if (error)
> > @@ -2720,7 +2734,7 @@ cmd_checkout(int argc, char *argv[])
> >      if (error != NULL)
> >          goto done;
> >
> > -    error = got_worktree_init(worktree_path, head_ref, path_prefix, repo);
> > +    error = got_worktree_init(worktree_fd, worktree_path, head_ref,
> > path_prefix, repo);
> >      if (error != NULL && !(error->code == GOT_ERR_ERRNO && errno == EEXIST))
> >          goto done;
> >
> > diff --git a/include/got_path.h b/include/got_path.h
> > index 0219d3d3..04558384 100644
> > --- a/include/got_path.h
> > +++ b/include/got_path.h
> > @@ -132,3 +132,4 @@ const struct got_error *got_path_find_prog(char
> > **, const char *);
> >
> >  /* Create a new file at a specified path, with optional content. */
> >  const struct got_error *got_path_create_file(const char *, const char *);
> > +const struct got_error *got_path_create_fileat(int, const char *,
> > const char *);
> > diff --git a/include/got_worktree.h b/include/got_worktree.h
> > index 24fddd52..13b13521 100644
> > --- a/include/got_worktree.h
> > +++ b/include/got_worktree.h
> > @@ -42,12 +42,11 @@ struct got_fileindex;
> >  /*
> >   * Attempt to initialize a new work tree on disk.
> >   * The first argument is the path to a directory where the work tree
> > - * will be created. The path itself must not yet exist, but the dirname(3)
> > - * of the path must already exist.
> > + * will be created. The dirname(3) of the path must already exist.
> >   * The reference provided will be used to determine the new worktree's
> >   * base commit. The third argument speficies the work tree's path prefix.
> >   */
> > -const struct got_error *got_worktree_init(const char *, struct got_reference *,
> > +const struct got_error *got_worktree_init(int, const char *, struct
> > got_reference *,
> >      const char *, struct got_repository *);
> >
> >  /*
> > diff --git a/lib/got_lib_worktree.h b/lib/got_lib_worktree.h
> > index 067bac22..2a1f9582 100644
> > --- a/lib/got_lib_worktree.h
> > +++ b/lib/got_lib_worktree.h
> > @@ -17,6 +17,8 @@
> >  struct got_worktree {
> >      char *root_path;
> >      char *repo_path;
> > +    int root_fd;
> > +    int repo_fd;
>
> Why is 'repo_fd' stored here?
> Shouldn't the repository's directory fd be part of struct got_repository?
>
> >      char *path_prefix;
> >      struct got_object_id *base_commit_id;
> >      char *head_ref_name;
> > diff --git a/lib/path.c b/lib/path.c
> > index a670334c..655304a0 100644
> > --- a/lib/path.c
> > +++ b/lib/path.c
> > @@ -513,11 +513,17 @@ got_path_find_prog(char **filename, const char *prog)
> >
> >  const struct got_error *
> >  got_path_create_file(const char *path, const char *content)
> > +{
> > +    return got_path_create_fileat(AT_FDCWD, path, content);
> > +}
> > +
> > +const struct got_error *
> > +got_path_create_fileat(int dir_fd, const char *path, const char *content)
> >  {
> >      const struct got_error *err = NULL;
> >      int fd = -1;
> >
> > -    fd = open(path, O_RDWR | O_CREAT | O_EXCL | O_NOFOLLOW,
> > +    fd = openat(dir_fd, path, O_RDWR | O_CREAT | O_EXCL | O_NOFOLLOW,
> >          GOT_DEFAULT_FILE_MODE);
> >      if (fd == -1) {
> >          err = got_error_from_errno2("open", path);
> > diff --git a/lib/worktree.c b/lib/worktree.c
> > index 2e265b00..30ce5dea 100644
> > --- a/lib/worktree.c
> > +++ b/lib/worktree.c
> > @@ -67,15 +67,15 @@
> >  #define GOT_MERGE_LABEL_BASE    "3-way merge base"
> >
> >  static const struct got_error *
> > -create_meta_file(const char *path_got, const char *name, const char *content)
> > +create_meta_file(int worktree_fd, const char *name, const char *content)
> >  {
> >      const struct got_error *err = NULL;
> >      char *path;
> >
> > -    if (asprintf(&path, "%s/%s", path_got, name) == -1)
> > +    if (asprintf(&path, "%s/%s", GOT_WORKTREE_GOT_DIR, name) == -1)
> >          return got_error_from_errno("asprintf");
> >
> > -    err = got_path_create_file(path, content);
> > +    err = got_path_create_fileat(worktree_fd, path, content);
> >      free(path);
> >      return err;
> >  }
> > @@ -120,7 +120,7 @@ done:
> >  }
> >
> >  static const struct got_error *
> > -read_meta_file(char **content, const char *path_got, const char *name)
> > +read_meta_file(char **content, int worktree_fd, const char *path_got,
> > const char *name)
> >  {
> >      const struct got_error *err = NULL;
> >      char *path;
> > @@ -136,7 +136,7 @@ read_meta_file(char **content, const char
> > *path_got, const char *name)
> >          goto done;
> >      }
> >
> > -    fd = open(path, O_RDONLY | O_NOFOLLOW);
> > +    fd = openat(worktree_fd, path, O_RDONLY | O_NOFOLLOW);
> >      if (fd == -1) {
> >          if (errno == ENOENT)
> >              err = got_error_path(path, GOT_ERR_WORKTREE_META);
> > @@ -204,7 +204,7 @@ write_head_ref(const char *path_got, struct
> > got_reference *head_ref)
> >  }
> >
> >  const struct got_error *
> > -got_worktree_init(const char *path, struct got_reference *head_ref,
> > +got_worktree_init(int fd, const char *path, struct got_reference *head_ref,
> >      const char *prefix, struct got_repository *repo)
> >  {
> >      const struct got_error *err = NULL;
> > @@ -237,29 +237,23 @@ got_worktree_init(const char *path, struct
> > got_reference *head_ref,
> >              return got_error_from_errno("asprintf");
> >      }
> >
> > -    /* Create top-level directory (may already exist). */
> > -    if (mkdir(path, GOT_DEFAULT_DIR_MODE) == -1 && errno != EEXIST) {
> > -        err = got_error_from_errno2("mkdir", path);
> > -        goto done;
> > -    }
>
> Is the above really not needed anymore? As long as the removal of this code
> doesn't make any tests fail, removing the code should be fine.
>
> > -
> >      /* Create .got directory (may already exist). */
> >      if (asprintf(&path_got, "%s/%s", path, GOT_WORKTREE_GOT_DIR) == -1) {
> >          err = got_error_from_errno("asprintf");
> >          goto done;
> >      }
> > -    if (mkdir(path_got, GOT_DEFAULT_DIR_MODE) == -1 && errno != EEXIST) {
> > +    if (mkdirat(fd, GOT_WORKTREE_GOT_DIR, GOT_DEFAULT_DIR_MODE) == -1
> > && errno != EEXIST) {
> >          err = got_error_from_errno2("mkdir", path_got);
> >          goto done;
> >      }
> >
> >      /* Create an empty lock file. */
> > -    err = create_meta_file(path_got, GOT_WORKTREE_LOCK, NULL);
> > +    err = create_meta_file(fd, GOT_WORKTREE_LOCK, NULL);
> >      if (err)
> >          goto done;
> >
> >      /* Create an empty file index. */
> > -    err = create_meta_file(path_got, GOT_WORKTREE_FILE_INDEX, NULL);
> > +    err = create_meta_file(fd, GOT_WORKTREE_FILE_INDEX, NULL);
> >      if (err)
> >          goto done;
> >
> > @@ -272,18 +266,18 @@ got_worktree_init(const char *path, struct
> > got_reference *head_ref,
> >      err = got_object_id_str(&basestr, commit_id);
> >      if (err)
> >          goto done;
> > -    err = create_meta_file(path_got, GOT_WORKTREE_BASE_COMMIT, basestr);
> > +    err = create_meta_file(fd, GOT_WORKTREE_BASE_COMMIT, basestr);
> >      if (err)
> >          goto done;
> >
> >      /* Store path to repository. */
> > -    err = create_meta_file(path_got, GOT_WORKTREE_REPOSITORY,
> > +    err = create_meta_file(fd, GOT_WORKTREE_REPOSITORY,
> >          got_repo_get_path(repo));
> >      if (err)
> >          goto done;
> >
> >      /* Store in-repository path prefix. */
> > -    err = create_meta_file(path_got, GOT_WORKTREE_PATH_PREFIX,
> > +    err = create_meta_file(fd, GOT_WORKTREE_PATH_PREFIX,
> >          absprefix ? absprefix : prefix);
> >      if (err)
> >          goto done;
> > @@ -299,7 +293,7 @@ got_worktree_init(const char *path, struct
> > got_reference *head_ref,
> >          err = got_error_uuid(uuid_status, "uuid_to_string");
> >          goto done;
> >      }
> > -    err = create_meta_file(path_got, GOT_WORKTREE_UUID, uuidstr);
> > +    err = create_meta_file(fd, GOT_WORKTREE_UUID, uuidstr);
> >      if (err)
> >          goto done;
> >
> > @@ -308,7 +302,7 @@ got_worktree_init(const char *path, struct
> > got_reference *head_ref,
> >          err = got_error_from_errno("asprintf");
> >          goto done;
> >      }
> > -    err = create_meta_file(path_got, GOT_WORKTREE_FORMAT, formatstr);
> > +    err = create_meta_file(fd, GOT_WORKTREE_FORMAT, formatstr);
> >      if (err)
> >          goto done;
> >
> > @@ -323,10 +317,9 @@ done:
> >  }
> >
> >  static const struct got_error *
> > -open_worktree(struct got_worktree **worktree, const char *path)
> > +open_worktree(struct got_worktree **worktree, int worktree_fd, const
> > char *path)
> >  {
> >      const struct got_error *err = NULL;
> > -    char *path_got;
> >      char *formatstr = NULL;
> >      char *uuidstr = NULL;
> >      char *path_lock = NULL;
> > @@ -338,26 +331,20 @@ open_worktree(struct got_worktree **worktree,
> > const char *path)
> >
> >      *worktree = NULL;
> >
> > -    if (asprintf(&path_got, "%s/%s", path, GOT_WORKTREE_GOT_DIR) == -1) {
> > -        err = got_error_from_errno("asprintf");
> > -        path_got = NULL;
> > -        goto done;
> > -    }
> > -
> > -    if (asprintf(&path_lock, "%s/%s", path_got, GOT_WORKTREE_LOCK) == -1) {
> > +    if (asprintf(&path_lock, "%s/%s", GOT_WORKTREE_GOT_DIR,
> > GOT_WORKTREE_LOCK) == -1) {
> >          err = got_error_from_errno("asprintf");
> >          path_lock = NULL;
> >          goto done;
> >      }
> >
> > -    fd = open(path_lock, O_RDWR | O_EXLOCK | O_NONBLOCK);
> > +    fd = openat(worktree_fd, path_lock, O_RDWR | O_EXLOCK | O_NONBLOCK);
> >      if (fd == -1) {
> >          err = (errno == EWOULDBLOCK ? got_error(GOT_ERR_WORKTREE_BUSY)
> >              : got_error_from_errno2("open", path_lock));
> >          goto done;
> >      }
> >
> > -    err = read_meta_file(&formatstr, path_got, GOT_WORKTREE_FORMAT);
> > +    err = read_meta_file(&formatstr, worktree_fd,
> > GOT_WORKTREE_GOT_DIR, GOT_WORKTREE_FORMAT);
> >      if (err)
> >          goto done;
> >
> > @@ -379,27 +366,27 @@ open_worktree(struct got_worktree **worktree,
> > const char *path)
> >      }
> >      (*worktree)->lockfd = -1;
> >
> > -    (*worktree)->root_path = realpath(path, NULL);
> > +    (*worktree)->root_path = strdup(path);
> >      if ((*worktree)->root_path == NULL) {
> > -        err = got_error_from_errno2("realpath", path);
> > +        err = got_error_from_errno2("strdup", path);
> >          goto done;
> >      }
> > -    err = read_meta_file(&(*worktree)->repo_path, path_got,
> > +    err = read_meta_file(&(*worktree)->repo_path, worktree_fd,
> > GOT_WORKTREE_GOT_DIR,
> >          GOT_WORKTREE_REPOSITORY);
> >      if (err)
> >          goto done;
> >
> > -    err = read_meta_file(&(*worktree)->path_prefix, path_got,
> > +    err = read_meta_file(&(*worktree)->path_prefix, worktree_fd,
> > GOT_WORKTREE_GOT_DIR,
> >          GOT_WORKTREE_PATH_PREFIX);
> >      if (err)
> >          goto done;
> >
> > -    err = read_meta_file(&base_commit_id_str, path_got,
> > +    err = read_meta_file(&base_commit_id_str, worktree_fd,
> > GOT_WORKTREE_GOT_DIR,
> >          GOT_WORKTREE_BASE_COMMIT);
> >      if (err)
> >          goto done;
> >
> > -    err = read_meta_file(&uuidstr, path_got, GOT_WORKTREE_UUID);
> > +    err = read_meta_file(&uuidstr, worktree_fd, GOT_WORKTREE_GOT_DIR,
> > GOT_WORKTREE_UUID);
> >      if (err)
> >          goto done;
> >      uuid_from_string(uuidstr, &(*worktree)->uuid, &uuid_status);
> > @@ -417,7 +404,7 @@ open_worktree(struct got_worktree **worktree,
> > const char *path)
> >      if (err)
> >          goto done;
> >
> > -    err = read_meta_file(&(*worktree)->head_ref_name, path_got,
> > +    err = read_meta_file(&(*worktree)->head_ref_name, worktree_fd,
> > GOT_WORKTREE_GOT_DIR,
> >          GOT_WORKTREE_HEAD_REF);
> >      if (err)
> >          goto done;
> > @@ -431,10 +418,14 @@ open_worktree(struct got_worktree **worktree,
> > const char *path)
> >
> >      err = got_gotconfig_read(&(*worktree)->gotconfig,
> >          (*worktree)->gotconfig_path);
> > +
> > +    int repo_fd = open(got_repo_get_path_git_dir(repo), O_DIRECTORY);
>
> Another variable declaration which should be moved to the beginning
> of this scope.
>
> Everything else about this patch is looking great, thank you!
>
> > +
> > +    (*worktree)->root_fd = worktree_fd;
> > +    (*worktree)->repo_fd = repo_fd;
> >  done:
> >      if (repo)
> >          got_repo_close(repo);
> > -    free(path_got);
> >      free(path_lock);
> >      free(base_commit_id_str);
> >      free(uuidstr);
> > @@ -456,15 +447,20 @@ got_worktree_open(struct got_worktree
> > **worktree, const char *path)
> >  {
> >      const struct got_error *err = NULL;
> >      char *worktree_path;
> > +    int worktree_fd;
> >
> >      worktree_path = strdup(path);
> >      if (worktree_path == NULL)
> >          return got_error_from_errno("strdup");
> >
> > +    worktree_fd = open(worktree_path, O_DIRECTORY);
> > +    if (worktree_fd == -1)
> > +        return got_error_from_errno2("open", worktree_path);
> > +
> >      for (;;) {
> >          char *parent_path;
> >
> > -        err = open_worktree(worktree, worktree_path);
> > +        err = open_worktree(worktree, worktree_fd, worktree_path);
> >          if (err && !(err->code == GOT_ERR_ERRNO && errno == ENOENT)) {
> >              free(worktree_path);
> >              return err;
> > @@ -484,7 +480,9 @@ got_worktree_open(struct got_worktree **worktree,
> > const char *path)
> >              break;
> >          }
> >          free(worktree_path);
> > +        close(worktree_fd);
> >          worktree_path = parent_path;
> > +        worktree_fd = open(parent_path, O_DIRECTORY);
> >      }
> >
> >      free(worktree_path);
> > @@ -507,6 +505,8 @@ got_worktree_close(struct got_worktree *worktree)
> >      free(worktree->gotconfig_path);
> >      got_gotconfig_free(worktree->gotconfig);
> >      free(worktree);
> > +    close(worktree->root_fd);
> > +    close(worktree->repo_fd);
> >      return err;
> >  }
> >
> >
diff --git a/got/got.c b/got/got.c
index 0357368c..e8cfb93a 100644
--- a/got/got.c
+++ b/got/got.c
@@ -2602,11 +2602,13 @@ cmd_checkout(int argc, char *argv[])
 	struct got_worktree *worktree = NULL;
 	char *repo_path = NULL;
 	char *worktree_path = NULL;
+	char *worktree_abspath = NULL;
 	const char *path_prefix = "";
 	const char *branch_name = GOT_REF_HEAD;
 	char *commit_id_str = NULL;
 	char *cwd = NULL;
 	int ch, same_path_prefix, allow_nonempty = 0;
+	int worktree_fd = -1;
 	struct got_pathlist_head paths;
 	struct got_checkout_progress_arg cpa;
 
@@ -2711,6 +2713,20 @@ cmd_checkout(int argc, char *argv[])
 			goto done;
 		}
 	}
+	worktree_fd = open(worktree_path, O_DIRECTORY);
+	if (worktree_fd == -1) {
+		error = got_error_from_errno2("open", worktree_path);
+		goto done;
+	}
+
+	/* Make worktree path absolute */
+	worktree_abspath = realpath(worktree_path, NULL);
+	if (worktree_abspath == NULL) {
+		error = got_error_from_errno2("realpath", worktree_abspath);
+		goto done;
+	}
+	free(worktree_path);
+	worktree_path = worktree_abspath;
 
 	error = apply_unveil(got_repo_get_path(repo), 0, worktree_path);
 	if (error)
@@ -2720,7 +2736,7 @@ cmd_checkout(int argc, char *argv[])
 	if (error != NULL)
 		goto done;
 
-	error = got_worktree_init(worktree_path, head_ref, path_prefix, repo);
+	error = got_worktree_init(worktree_fd, worktree_path, head_ref, path_prefix, repo);
 	if (error != NULL && !(error->code == GOT_ERR_ERRNO && errno == EEXIST))
 		goto done;
 
diff --git a/include/got_path.h b/include/got_path.h
index 0219d3d3..04558384 100644
--- a/include/got_path.h
+++ b/include/got_path.h
@@ -132,3 +132,4 @@ const struct got_error *got_path_find_prog(char **, const char *);
 
 /* Create a new file at a specified path, with optional content. */
 const struct got_error *got_path_create_file(const char *, const char *);
+const struct got_error *got_path_create_fileat(int, const char *, const char *);
diff --git a/include/got_worktree.h b/include/got_worktree.h
index 24fddd52..13b13521 100644
--- a/include/got_worktree.h
+++ b/include/got_worktree.h
@@ -42,12 +42,11 @@ struct got_fileindex;
 /*
  * Attempt to initialize a new work tree on disk.
  * The first argument is the path to a directory where the work tree
- * will be created. The path itself must not yet exist, but the dirname(3)
- * of the path must already exist.
+ * will be created. The dirname(3) of the path must already exist.
  * The reference provided will be used to determine the new worktree's
  * base commit. The third argument speficies the work tree's path prefix.
  */
-const struct got_error *got_worktree_init(const char *, struct got_reference *,
+const struct got_error *got_worktree_init(int, const char *, struct got_reference *,
     const char *, struct got_repository *);
 
 /*
diff --git a/lib/got_lib_worktree.h b/lib/got_lib_worktree.h
index 067bac22..651bcc8f 100644
--- a/lib/got_lib_worktree.h
+++ b/lib/got_lib_worktree.h
@@ -17,6 +17,7 @@
 struct got_worktree {
 	char *root_path;
 	char *repo_path;
+	int root_fd;
 	char *path_prefix;
 	struct got_object_id *base_commit_id;
 	char *head_ref_name;
diff --git a/lib/path.c b/lib/path.c
index a670334c..655304a0 100644
--- a/lib/path.c
+++ b/lib/path.c
@@ -513,11 +513,17 @@ got_path_find_prog(char **filename, const char *prog)
 
 const struct got_error *
 got_path_create_file(const char *path, const char *content)
+{
+	return got_path_create_fileat(AT_FDCWD, path, content);
+}
+
+const struct got_error *
+got_path_create_fileat(int dir_fd, const char *path, const char *content)
 {
 	const struct got_error *err = NULL;
 	int fd = -1;
 
-	fd = open(path, O_RDWR | O_CREAT | O_EXCL | O_NOFOLLOW,
+	fd = openat(dir_fd, path, O_RDWR | O_CREAT | O_EXCL | O_NOFOLLOW,
 	    GOT_DEFAULT_FILE_MODE);
 	if (fd == -1) {
 		err = got_error_from_errno2("open", path);
diff --git a/lib/worktree.c b/lib/worktree.c
index 2e265b00..8bf4b7ff 100644
--- a/lib/worktree.c
+++ b/lib/worktree.c
@@ -67,15 +67,15 @@
 #define GOT_MERGE_LABEL_BASE	"3-way merge base"
 
 static const struct got_error *
-create_meta_file(const char *path_got, const char *name, const char *content)
+create_meta_file(int worktree_fd, const char *name, const char *content)
 {
 	const struct got_error *err = NULL;
 	char *path;
 
-	if (asprintf(&path, "%s/%s", path_got, name) == -1)
+	if (asprintf(&path, "%s/%s", GOT_WORKTREE_GOT_DIR, name) == -1)
 		return got_error_from_errno("asprintf");
 
-	err = got_path_create_file(path, content);
+	err = got_path_create_fileat(worktree_fd, path, content);
 	free(path);
 	return err;
 }
@@ -120,7 +120,7 @@ done:
 }
 
 static const struct got_error *
-read_meta_file(char **content, const char *path_got, const char *name)
+read_meta_file(char **content, int worktree_fd, const char *path_got, const char *name)
 {
 	const struct got_error *err = NULL;
 	char *path;
@@ -136,7 +136,7 @@ read_meta_file(char **content, const char *path_got, const char *name)
 		goto done;
 	}
 
-	fd = open(path, O_RDONLY | O_NOFOLLOW);
+	fd = openat(worktree_fd, path, O_RDONLY | O_NOFOLLOW);
 	if (fd == -1) {
 		if (errno == ENOENT)
 			err = got_error_path(path, GOT_ERR_WORKTREE_META);
@@ -204,7 +204,7 @@ write_head_ref(const char *path_got, struct got_reference *head_ref)
 }
 
 const struct got_error *
-got_worktree_init(const char *path, struct got_reference *head_ref,
+got_worktree_init(int fd, const char *path, struct got_reference *head_ref,
     const char *prefix, struct got_repository *repo)
 {
 	const struct got_error *err = NULL;
@@ -237,29 +237,23 @@ got_worktree_init(const char *path, struct got_reference *head_ref,
 			return got_error_from_errno("asprintf");
 	}
 
-	/* Create top-level directory (may already exist). */
-	if (mkdir(path, GOT_DEFAULT_DIR_MODE) == -1 && errno != EEXIST) {
-		err = got_error_from_errno2("mkdir", path);
-		goto done;
-	}
-
 	/* Create .got directory (may already exist). */
 	if (asprintf(&path_got, "%s/%s", path, GOT_WORKTREE_GOT_DIR) == -1) {
 		err = got_error_from_errno("asprintf");
 		goto done;
 	}
-	if (mkdir(path_got, GOT_DEFAULT_DIR_MODE) == -1 && errno != EEXIST) {
+	if (mkdirat(fd, GOT_WORKTREE_GOT_DIR, GOT_DEFAULT_DIR_MODE) == -1 && errno != EEXIST) {
 		err = got_error_from_errno2("mkdir", path_got);
 		goto done;
 	}
 
 	/* Create an empty lock file. */
-	err = create_meta_file(path_got, GOT_WORKTREE_LOCK, NULL);
+	err = create_meta_file(fd, GOT_WORKTREE_LOCK, NULL);
 	if (err)
 		goto done;
 
 	/* Create an empty file index. */
-	err = create_meta_file(path_got, GOT_WORKTREE_FILE_INDEX, NULL);
+	err = create_meta_file(fd, GOT_WORKTREE_FILE_INDEX, NULL);
 	if (err)
 		goto done;
 
@@ -272,18 +266,18 @@ got_worktree_init(const char *path, struct got_reference *head_ref,
 	err = got_object_id_str(&basestr, commit_id);
 	if (err)
 		goto done;
-	err = create_meta_file(path_got, GOT_WORKTREE_BASE_COMMIT, basestr);
+	err = create_meta_file(fd, GOT_WORKTREE_BASE_COMMIT, basestr);
 	if (err)
 		goto done;
 
 	/* Store path to repository. */
-	err = create_meta_file(path_got, GOT_WORKTREE_REPOSITORY,
+	err = create_meta_file(fd, GOT_WORKTREE_REPOSITORY,
 	    got_repo_get_path(repo));
 	if (err)
 		goto done;
 
 	/* Store in-repository path prefix. */
-	err = create_meta_file(path_got, GOT_WORKTREE_PATH_PREFIX,
+	err = create_meta_file(fd, GOT_WORKTREE_PATH_PREFIX,
 	    absprefix ? absprefix : prefix);
 	if (err)
 		goto done;
@@ -299,7 +293,7 @@ got_worktree_init(const char *path, struct got_reference *head_ref,
 		err = got_error_uuid(uuid_status, "uuid_to_string");
 		goto done;
 	}
-	err = create_meta_file(path_got, GOT_WORKTREE_UUID, uuidstr);
+	err = create_meta_file(fd, GOT_WORKTREE_UUID, uuidstr);
 	if (err)
 		goto done;
 
@@ -308,7 +302,7 @@ got_worktree_init(const char *path, struct got_reference *head_ref,
 		err = got_error_from_errno("asprintf");
 		goto done;
 	}
-	err = create_meta_file(path_got, GOT_WORKTREE_FORMAT, formatstr);
+	err = create_meta_file(fd, GOT_WORKTREE_FORMAT, formatstr);
 	if (err)
 		goto done;
 
@@ -323,10 +317,9 @@ done:
 }
 
 static const struct got_error *
-open_worktree(struct got_worktree **worktree, const char *path)
+open_worktree(struct got_worktree **worktree, int worktree_fd, const char *path)
 {
 	const struct got_error *err = NULL;
-	char *path_got;
 	char *formatstr = NULL;
 	char *uuidstr = NULL;
 	char *path_lock = NULL;
@@ -338,26 +331,20 @@ open_worktree(struct got_worktree **worktree, const char *path)
 
 	*worktree = NULL;
 
-	if (asprintf(&path_got, "%s/%s", path, GOT_WORKTREE_GOT_DIR) == -1) {
-		err = got_error_from_errno("asprintf");
-		path_got = NULL;
-		goto done;
-	}
-
-	if (asprintf(&path_lock, "%s/%s", path_got, GOT_WORKTREE_LOCK) == -1) {
+	if (asprintf(&path_lock, "%s/%s", GOT_WORKTREE_GOT_DIR, GOT_WORKTREE_LOCK) == -1) {
 		err = got_error_from_errno("asprintf");
 		path_lock = NULL;
 		goto done;
 	}
 
-	fd = open(path_lock, O_RDWR | O_EXLOCK | O_NONBLOCK);
+	fd = openat(worktree_fd, path_lock, O_RDWR | O_EXLOCK | O_NONBLOCK);
 	if (fd == -1) {
 		err = (errno == EWOULDBLOCK ? got_error(GOT_ERR_WORKTREE_BUSY)
 		    : got_error_from_errno2("open", path_lock));
 		goto done;
 	}
 
-	err = read_meta_file(&formatstr, path_got, GOT_WORKTREE_FORMAT);
+	err = read_meta_file(&formatstr, worktree_fd, GOT_WORKTREE_GOT_DIR, GOT_WORKTREE_FORMAT);
 	if (err)
 		goto done;
 
@@ -379,27 +366,27 @@ open_worktree(struct got_worktree **worktree, const char *path)
 	}
 	(*worktree)->lockfd = -1;
 
-	(*worktree)->root_path = realpath(path, NULL);
+	(*worktree)->root_path = strdup(path);
 	if ((*worktree)->root_path == NULL) {
-		err = got_error_from_errno2("realpath", path);
+		err = got_error_from_errno2("strdup", path);
 		goto done;
 	}
-	err = read_meta_file(&(*worktree)->repo_path, path_got,
+	err = read_meta_file(&(*worktree)->repo_path, worktree_fd, GOT_WORKTREE_GOT_DIR,
 	    GOT_WORKTREE_REPOSITORY);
 	if (err)
 		goto done;
 
-	err = read_meta_file(&(*worktree)->path_prefix, path_got,
+	err = read_meta_file(&(*worktree)->path_prefix, worktree_fd, GOT_WORKTREE_GOT_DIR,
 	    GOT_WORKTREE_PATH_PREFIX);
 	if (err)
 		goto done;
 
-	err = read_meta_file(&base_commit_id_str, path_got,
+	err = read_meta_file(&base_commit_id_str, worktree_fd, GOT_WORKTREE_GOT_DIR,
 	    GOT_WORKTREE_BASE_COMMIT);
 	if (err)
 		goto done;
 
-	err = read_meta_file(&uuidstr, path_got, GOT_WORKTREE_UUID);
+	err = read_meta_file(&uuidstr, worktree_fd, GOT_WORKTREE_GOT_DIR, GOT_WORKTREE_UUID);
 	if (err)
 		goto done;
 	uuid_from_string(uuidstr, &(*worktree)->uuid, &uuid_status);
@@ -417,7 +404,7 @@ open_worktree(struct got_worktree **worktree, const char *path)
 	if (err)
 		goto done;
 
-	err = read_meta_file(&(*worktree)->head_ref_name, path_got,
+	err = read_meta_file(&(*worktree)->head_ref_name, worktree_fd, GOT_WORKTREE_GOT_DIR,
 	    GOT_WORKTREE_HEAD_REF);
 	if (err)
 		goto done;
@@ -431,10 +418,11 @@ open_worktree(struct got_worktree **worktree, const char *path)
 
 	err = got_gotconfig_read(&(*worktree)->gotconfig,
 	    (*worktree)->gotconfig_path);
+
+	(*worktree)->root_fd = worktree_fd;
 done:
 	if (repo)
 		got_repo_close(repo);
-	free(path_got);
 	free(path_lock);
 	free(base_commit_id_str);
 	free(uuidstr);
@@ -456,15 +444,20 @@ got_worktree_open(struct got_worktree **worktree, const char *path)
 {
 	const struct got_error *err = NULL;
 	char *worktree_path;
+	int worktree_fd;
 
 	worktree_path = strdup(path);
 	if (worktree_path == NULL)
 		return got_error_from_errno("strdup");
 
+	worktree_fd = open(worktree_path, O_DIRECTORY);
+	if (worktree_fd == -1)
+		return got_error_from_errno2("open", worktree_path);
+
 	for (;;) {
 		char *parent_path;
 
-		err = open_worktree(worktree, worktree_path);
+		err = open_worktree(worktree, worktree_fd, worktree_path);
 		if (err && !(err->code == GOT_ERR_ERRNO && errno == ENOENT)) {
 			free(worktree_path);
 			return err;
@@ -484,7 +477,9 @@ got_worktree_open(struct got_worktree **worktree, const char *path)
 			break;
 		}
 		free(worktree_path);
+		close(worktree_fd);
 		worktree_path = parent_path;
+		worktree_fd = open(parent_path, O_DIRECTORY);
 	}
 
 	free(worktree_path);
@@ -507,6 +502,7 @@ got_worktree_close(struct got_worktree *worktree)
 	free(worktree->gotconfig_path);
 	got_gotconfig_free(worktree->gotconfig);
 	free(worktree);
+	close(worktree->root_fd);
 	return err;
 }
 
diff --git a/got/got.c b/got/got.c
index e8cfb93a..a2cfacec 100644
--- a/got/got.c
+++ b/got/got.c
@@ -2007,6 +2007,7 @@ cmd_fetch(int argc, char *argv[])
 	struct got_fetch_progress_arg fpa;
 	int verbosity = 0, fetch_all_branches = 0, list_refs_only = 0;
 	int delete_refs = 0, replace_tags = 0;
+	int cwd_fd = -1;
 
 	TAILQ_INIT(&refs);
 	TAILQ_INIT(&symrefs);
@@ -2088,9 +2089,14 @@ cmd_fetch(int argc, char *argv[])
 		error = got_error_from_errno("getcwd");
 		goto done;
 	}
+	cwd_fd = open(cwd, O_DIRECTORY);
+	if (cwd_fd == -1) {
+		error = got_error_from_errno2("open", cwd);
+		goto done;
+	}
 
 	if (repo_path == NULL) {
-		error = got_worktree_open(&worktree, cwd);
+		error = got_worktree_open(&worktree, cwd_fd, cwd);
 		if (error && error->code != GOT_ERR_NOT_WORKTREE)
 			goto done;
 		else
@@ -2740,7 +2746,7 @@ cmd_checkout(int argc, char *argv[])
 	if (error != NULL && !(error->code == GOT_ERR_ERRNO && errno == EEXIST))
 		goto done;
 
-	error = got_worktree_open(&worktree, worktree_path);
+	error = got_worktree_open(&worktree, worktree_fd, worktree_path);
 	if (error != NULL)
 		goto done;
 
@@ -2983,7 +2989,7 @@ cmd_update(int argc, char *argv[])
 	const struct got_error *error = NULL;
 	struct got_repository *repo = NULL;
 	struct got_worktree *worktree = NULL;
-	char *worktree_path = NULL;
+	char *cwd = NULL;
 	struct got_object_id *commit_id = NULL;
 	char *commit_id_str = NULL;
 	const char *branch_name = NULL;
@@ -2991,6 +2997,7 @@ cmd_update(int argc, char *argv[])
 	struct got_pathlist_head paths;
 	struct got_pathlist_entry *pe;
 	int ch;
+	int cwd_fd = -1;
 	struct got_update_progress_arg upa;
 
 	TAILQ_INIT(&paths);
@@ -3019,16 +3026,22 @@ cmd_update(int argc, char *argv[])
 	    "unveil", NULL) == -1)
 		err(1, "pledge");
 #endif
-	worktree_path = getcwd(NULL, 0);
-	if (worktree_path == NULL) {
+	cwd = getcwd(NULL, 0);
+	if (cwd == NULL) {
 		error = got_error_from_errno("getcwd");
 		goto done;
 	}
-	error = got_worktree_open(&worktree, worktree_path);
+	cwd_fd = open(cwd, O_DIRECTORY);
+	if (cwd_fd == -1) {
+		error = got_error_from_errno2("open", cwd);
+		goto done;
+	}
+
+	error = got_worktree_open(&worktree, cwd_fd, cwd);
 	if (error) {
 		if (error->code == GOT_ERR_NOT_WORKTREE)
 			error = wrap_not_worktree_error(error, "update",
-			    worktree_path);
+			    cwd);
 		goto done;
 	}
 
@@ -3130,7 +3143,7 @@ cmd_update(int argc, char *argv[])
 		printf("Already up-to-date\n");
 	print_update_progress_stats(&upa);
 done:
-	free(worktree_path);
+	free(cwd);
 	TAILQ_FOREACH(pe, &paths, entry)
 		free((char *)pe->path);
 	got_pathlist_free(&paths);
@@ -3711,6 +3724,7 @@ cmd_log(int argc, char *argv[])
 	int diff_context = -1, ch;
 	int show_changed_paths = 0, show_patch = 0, limit = 0, log_branches = 0;
 	int reverse_display_order = 0;
+	int cwd_fd = -1;
 	const char *errstr;
 	struct got_reflist_head refs;
 
@@ -3785,9 +3799,13 @@ cmd_log(int argc, char *argv[])
 		error = got_error_from_errno("getcwd");
 		goto done;
 	}
-
+	cwd_fd = open(cwd, O_DIRECTORY);
+	if (cwd_fd == -1) {
+		error = got_error_from_errno2("open", cwd);
+		goto done;
+	}
 	if (repo_path == NULL) {
-		error = got_worktree_open(&worktree, cwd);
+		error = got_worktree_open(&worktree, cwd_fd, cwd);
 		if (error && error->code != GOT_ERR_NOT_WORKTREE)
 			goto done;
 		error = NULL;
@@ -4124,6 +4142,7 @@ cmd_diff(int argc, char *argv[])
 	int type1, type2;
 	int diff_context = 3, diff_staged = 0, ignore_whitespace = 0, ch;
 	int force_text_diff = 0;
+	int cwd_fd = -1;
 	const char *errstr;
 	char *path = NULL;
 
@@ -4171,11 +4190,16 @@ cmd_diff(int argc, char *argv[])
 		error = got_error_from_errno("getcwd");
 		goto done;
 	}
+	cwd_fd = open(cwd, O_DIRECTORY);
+	if (cwd_fd == -1) {
+		error = got_error_from_errno2("open", cwd);
+		goto done;
+	}
 	if (argc <= 1) {
 		if (repo_path)
 			errx(1,
 			    "-r option can't be used when diffing a work tree");
-		error = got_worktree_open(&worktree, cwd);
+		error = got_worktree_open(&worktree, cwd_fd, cwd);
 		if (error) {
 			if (error->code == GOT_ERR_NOT_WORKTREE)
 				error = wrap_not_worktree_error(error, "diff",
@@ -4206,7 +4230,7 @@ cmd_diff(int argc, char *argv[])
 		id_str1 = argv[0];
 		id_str2 = argv[1];
 		if (repo_path == NULL) {
-			error = got_worktree_open(&worktree, cwd);
+			error = got_worktree_open(&worktree, cwd_fd, cwd);
 			if (error && error->code != GOT_ERR_NOT_WORKTREE)
 				goto done;
 			if (worktree) {
@@ -4467,6 +4491,7 @@ cmd_blame(int argc, char *argv[])
 	char *commit_id_str = NULL;
 	struct blame_cb_args bca;
 	int ch, obj_type, i;
+	int cwd_fd = -1;
 	off_t filesize;
 
 	memset(&bca, 0, sizeof(bca));
@@ -4508,8 +4533,13 @@ cmd_blame(int argc, char *argv[])
 		error = got_error_from_errno("getcwd");
 		goto done;
 	}
+	cwd_fd = open(cwd, O_DIRECTORY);
+	if (cwd_fd == -1) {
+		error = got_error_from_errno2("open", cwd);
+		goto done;
+	}
 	if (repo_path == NULL) {
-		error = got_worktree_open(&worktree, cwd);
+		error = got_worktree_open(&worktree, cwd_fd, cwd);
 		if (error && error->code != GOT_ERR_NOT_WORKTREE)
 			goto done;
 		else
@@ -4789,6 +4819,7 @@ cmd_tree(int argc, char *argv[])
 	struct got_object_id *commit_id = NULL;
 	char *commit_id_str = NULL;
 	int show_ids = 0, recurse = 0;
+	int cwd_fd = -1;
 	int ch;
 
 #ifndef PROFILE
@@ -4836,8 +4867,13 @@ cmd_tree(int argc, char *argv[])
 		error = got_error_from_errno("getcwd");
 		goto done;
 	}
+	cwd_fd = open(cwd, O_DIRECTORY);
+	if (cwd_fd == -1) {
+		error = got_error_from_errno2("open", cwd);
+		goto done;
+	}
 	if (repo_path == NULL) {
-		error = got_worktree_open(&worktree, cwd);
+		error = got_worktree_open(&worktree, cwd_fd, cwd);
 		if (error && error->code != GOT_ERR_NOT_WORKTREE)
 			goto done;
 		else
@@ -4973,6 +5009,7 @@ cmd_status(int argc, char *argv[])
 	struct got_pathlist_head paths;
 	struct got_pathlist_entry *pe;
 	int ch, i;
+	int cwd_fd = -1;
 
 	TAILQ_INIT(&paths);
 
@@ -5017,8 +5054,13 @@ cmd_status(int argc, char *argv[])
 		error = got_error_from_errno("getcwd");
 		goto done;
 	}
+	cwd_fd = open(cwd, O_DIRECTORY);
+	if (cwd_fd == -1) {
+		error = got_error_from_errno2("open", cwd);
+		goto done;
+	}
 
-	error = got_worktree_open(&worktree, cwd);
+	error = got_worktree_open(&worktree, cwd_fd, cwd);
 	if (error) {
 		if (error->code == GOT_ERR_NOT_WORKTREE)
 			error = wrap_not_worktree_error(error, "status", cwd);
@@ -5182,6 +5224,7 @@ cmd_ref(int argc, char *argv[])
 	struct got_worktree *worktree = NULL;
 	char *cwd = NULL, *repo_path = NULL;
 	int ch, do_list = 0, do_delete = 0;
+	int cwd_fd = -1;
 	const char *obj_arg = NULL, *symref_target= NULL;
 	char *refname = NULL;
 
@@ -5267,9 +5310,14 @@ cmd_ref(int argc, char *argv[])
 		error = got_error_from_errno("getcwd");
 		goto done;
 	}
+	cwd_fd = open(cwd, O_DIRECTORY);
+	if (cwd_fd == -1) {
+		error = got_error_from_errno2("open", cwd);
+		goto done;
+	}
 
 	if (repo_path == NULL) {
-		error = got_worktree_open(&worktree, cwd);
+		error = got_worktree_open(&worktree, cwd_fd, cwd);
 		if (error && error->code != GOT_ERR_NOT_WORKTREE)
 			goto done;
 		else
@@ -5512,6 +5560,7 @@ cmd_branch(int argc, char *argv[])
 	struct got_worktree *worktree = NULL;
 	char *cwd = NULL, *repo_path = NULL;
 	int ch, do_list = 0, do_show = 0, do_update = 1;
+	int cwd_fd = -1;
 	const char *delref = NULL, *commit_id_arg = NULL;
 	struct got_reference *ref = NULL;
 	struct got_pathlist_head paths;
@@ -5582,9 +5631,14 @@ cmd_branch(int argc, char *argv[])
 		error = got_error_from_errno("getcwd");
 		goto done;
 	}
+	cwd_fd = open(cwd, O_DIRECTORY);
+	if (cwd_fd == -1) {
+		error = got_error_from_errno2("open", cwd);
+		goto done;
+	}
 
 	if (repo_path == NULL) {
-		error = got_worktree_open(&worktree, cwd);
+		error = got_worktree_open(&worktree, cwd_fd, cwd);
 		if (error && error->code != GOT_ERR_NOT_WORKTREE)
 			goto done;
 		else
@@ -6052,6 +6106,7 @@ cmd_tag(int argc, char *argv[])
 	char *gitconfig_path = NULL;
 	const char *tag_name, *commit_id_arg = NULL, *tagmsg = NULL;
 	int ch, do_list = 0;
+	int cwd_fd = -1;
 
 	while ((ch = getopt(argc, argv, "c:m:r:l")) != -1) {
 		switch (ch) {
@@ -6109,9 +6164,14 @@ cmd_tag(int argc, char *argv[])
 		error = got_error_from_errno("getcwd");
 		goto done;
 	}
+	cwd_fd = open(cwd, O_DIRECTORY);
+	if (cwd_fd == -1) {
+		error = got_error_from_errno2("open", cwd);
+		goto done;
+	}
 
 	if (repo_path == NULL) {
-		error = got_worktree_open(&worktree, cwd);
+		error = got_worktree_open(&worktree, cwd_fd, cwd);
 		if (error && error->code != GOT_ERR_NOT_WORKTREE)
 			goto done;
 		else
@@ -6214,6 +6274,7 @@ cmd_add(int argc, char *argv[])
 	struct got_pathlist_head paths;
 	struct got_pathlist_entry *pe;
 	int ch, can_recurse = 0, no_ignores = 0;
+	int cwd_fd = -1;
 
 	TAILQ_INIT(&paths);
 
@@ -6247,8 +6308,13 @@ cmd_add(int argc, char *argv[])
 		error = got_error_from_errno("getcwd");
 		goto done;
 	}
+	cwd_fd = open(cwd, O_DIRECTORY);
+	if (cwd_fd == -1) {
+		error = got_error_from_errno2("open", cwd);
+		goto done;
+	}
 
-	error = got_worktree_open(&worktree, cwd);
+	error = got_worktree_open(&worktree, cwd_fd, cwd);
 	if (error) {
 		if (error->code == GOT_ERR_NOT_WORKTREE)
 			error = wrap_not_worktree_error(error, "add", cwd);
@@ -6352,6 +6418,7 @@ cmd_remove(int argc, char *argv[])
 	struct got_pathlist_head paths;
 	struct got_pathlist_entry *pe;
 	int ch, delete_local_mods = 0, can_recurse = 0, keep_on_disk = 0, i;
+	int cwd_fd = -1;
 
 	TAILQ_INIT(&paths);
 
@@ -6403,7 +6470,13 @@ cmd_remove(int argc, char *argv[])
 		error = got_error_from_errno("getcwd");
 		goto done;
 	}
-	error = got_worktree_open(&worktree, cwd);
+	cwd_fd = open(cwd, O_DIRECTORY);
+	if (cwd_fd == -1) {
+		error = got_error_from_errno2("open", cwd);
+		goto done;
+	}
+
+	error = got_worktree_open(&worktree, cwd_fd, cwd);
 	if (error) {
 		if (error->code == GOT_ERR_NOT_WORKTREE)
 			error = wrap_not_worktree_error(error, "remove", cwd);
@@ -6611,6 +6684,7 @@ cmd_revert(int argc, char *argv[])
 	struct got_pathlist_head paths;
 	struct got_pathlist_entry *pe;
 	int ch, can_recurse = 0, pflag = 0;
+	int cwd_fd = -1;
 	FILE *patch_script_file = NULL;
 	const char *patch_script_path = NULL;
 	struct choose_patch_arg cpa;
@@ -6652,7 +6726,13 @@ cmd_revert(int argc, char *argv[])
 		error = got_error_from_errno("getcwd");
 		goto done;
 	}
-	error = got_worktree_open(&worktree, cwd);
+	cwd_fd = open(cwd, O_DIRECTORY);
+	if (cwd_fd == -1) {
+		error = got_error_from_errno2("open", cwd);
+		goto done;
+	}
+
+	error = got_worktree_open(&worktree, cwd_fd, cwd);
 	if (error) {
 		if (error->code == GOT_ERR_NOT_WORKTREE)
 			error = wrap_not_worktree_error(error, "revert", cwd);
@@ -6825,6 +6905,7 @@ cmd_commit(int argc, char *argv[])
 	char *gitconfig_path = NULL, *editor = NULL, *author = NULL;
 	int ch, rebase_in_progress, histedit_in_progress, preserve_logmsg = 0;
 	int allow_bad_symlinks = 0;
+	int cwd_fd = -1;
 	struct got_pathlist_head paths;
 
 	TAILQ_INIT(&paths);
@@ -6857,7 +6938,13 @@ cmd_commit(int argc, char *argv[])
 		error = got_error_from_errno("getcwd");
 		goto done;
 	}
-	error = got_worktree_open(&worktree, cwd);
+	cwd_fd = open(cwd, O_DIRECTORY);
+	if (cwd_fd == -1) {
+		error = got_error_from_errno2("open", cwd);
+		goto done;
+	}
+
+	error = got_worktree_open(&worktree, cwd_fd, cwd);
 	if (error) {
 		if (error->code == GOT_ERR_NOT_WORKTREE)
 			error = wrap_not_worktree_error(error, "commit", cwd);
@@ -6970,6 +7057,7 @@ cmd_cherrypick(int argc, char *argv[])
 	struct got_object_qid *pid;
 	struct got_reference *head_ref = NULL;
 	int ch;
+	int cwd_fd = -1;
 	struct got_update_progress_arg upa;
 
 	while ((ch = getopt(argc, argv, "")) != -1) {
@@ -6996,7 +7084,13 @@ cmd_cherrypick(int argc, char *argv[])
 		error = got_error_from_errno("getcwd");
 		goto done;
 	}
-	error = got_worktree_open(&worktree, cwd);
+	cwd_fd = open(cwd, O_DIRECTORY);
+	if (cwd_fd == -1) {
+		error = got_error_from_errno2("open", cwd);
+		goto done;
+	}
+
+	error = got_worktree_open(&worktree, cwd_fd, cwd);
 	if (error) {
 		if (error->code == GOT_ERR_NOT_WORKTREE)
 			error = wrap_not_worktree_error(error, "cherrypick",
@@ -7093,6 +7187,7 @@ cmd_backout(int argc, char *argv[])
 	struct got_object_qid *pid;
 	struct got_reference *head_ref = NULL;
 	int ch;
+	int cwd_fd = -1;
 	struct got_update_progress_arg upa;
 
 	while ((ch = getopt(argc, argv, "")) != -1) {
@@ -7119,7 +7214,13 @@ cmd_backout(int argc, char *argv[])
 		error = got_error_from_errno("getcwd");
 		goto done;
 	}
-	error = got_worktree_open(&worktree, cwd);
+	cwd_fd = open(cwd, O_DIRECTORY);
+	if (cwd_fd == -1) {
+		error = got_error_from_errno2("open", cwd);
+		goto done;
+	}
+
+	error = got_worktree_open(&worktree, cwd_fd, cwd);
 	if (error) {
 		if (error->code == GOT_ERR_NOT_WORKTREE)
 			error = wrap_not_worktree_error(error, "backout", cwd);
@@ -7483,6 +7584,7 @@ cmd_rebase(int argc, char *argv[])
 	struct got_commit_object *commit = NULL;
 	int ch, rebase_in_progress = 0, abort_rebase = 0, continue_rebase = 0;
 	int histedit_in_progress = 0;
+	int cwd_fd = -1;
 	unsigned char rebase_status = GOT_STATUS_NO_CHANGE;
 	struct got_object_id_queue commits;
 	struct got_pathlist_head merged_paths;
@@ -7527,7 +7629,13 @@ cmd_rebase(int argc, char *argv[])
 		error = got_error_from_errno("getcwd");
 		goto done;
 	}
-	error = got_worktree_open(&worktree, cwd);
+	cwd_fd = open(cwd, O_DIRECTORY);
+	if (cwd_fd == -1) {
+		error = got_error_from_errno2("open", cwd);
+		goto done;
+	}
+
+	error = got_worktree_open(&worktree, cwd_fd, cwd);
 	if (error) {
 		if (error->code == GOT_ERR_NOT_WORKTREE)
 			error = wrap_not_worktree_error(error, "rebase", cwd);
@@ -8593,6 +8701,7 @@ cmd_histedit(int argc, char *argv[])
 	struct got_update_progress_arg upa;
 	int edit_in_progress = 0, abort_edit = 0, continue_edit = 0;
 	int edit_logmsg_only = 0;
+	int cwd_fd = -1;
 	const char *edit_script_path = NULL;
 	unsigned char rebase_status = GOT_STATUS_NO_CHANGE;
 	struct got_object_id_queue commits;
@@ -8660,7 +8769,13 @@ cmd_histedit(int argc, char *argv[])
 		error = got_error_from_errno("getcwd");
 		goto done;
 	}
-	error = got_worktree_open(&worktree, cwd);
+	cwd_fd = open(cwd, O_DIRECTORY);
+	if (cwd_fd == -1) {
+		error = got_error_from_errno2("open", cwd);
+		goto done;
+	}
+
+	error = got_worktree_open(&worktree, cwd_fd, cwd);
 	if (error) {
 		if (error->code == GOT_ERR_NOT_WORKTREE)
 			error = wrap_not_worktree_error(error, "histedit", cwd);
@@ -9015,6 +9130,7 @@ cmd_integrate(int argc, char *argv[])
 	struct got_fileindex *fileindex = NULL;
 	struct got_object_id *commit_id = NULL, *base_commit_id = NULL;
 	int ch;
+	int cwd_fd = -1;
 	struct got_update_progress_arg upa;
 
 	while ((ch = getopt(argc, argv, "")) != -1) {
@@ -9041,8 +9157,13 @@ cmd_integrate(int argc, char *argv[])
 		error = got_error_from_errno("getcwd");
 		goto done;
 	}
+	cwd_fd = open(cwd, O_DIRECTORY);
+	if (cwd_fd == -1) {
+		error = got_error_from_errno2("open", cwd);
+		goto done;
+	}
 
-	error = got_worktree_open(&worktree, cwd);
+	error = got_worktree_open(&worktree, cwd_fd, cwd);
 	if (error) {
 		if (error->code == GOT_ERR_NOT_WORKTREE)
 			error = wrap_not_worktree_error(error, "integrate",
@@ -9182,6 +9303,7 @@ cmd_stage(int argc, char *argv[])
 	struct got_pathlist_head paths;
 	struct got_pathlist_entry *pe;
 	int ch, list_stage = 0, pflag = 0, allow_bad_symlinks = 0;
+	int cwd_fd = -1;
 	FILE *patch_script_file = NULL;
 	const char *patch_script_path = NULL;
 	struct choose_patch_arg cpa;
@@ -9226,8 +9348,13 @@ cmd_stage(int argc, char *argv[])
 		error = got_error_from_errno("getcwd");
 		goto done;
 	}
+	cwd_fd = open(cwd, O_DIRECTORY);
+	if (cwd_fd == -1) {
+		error = got_error_from_errno2("open", cwd);
+		goto done;
+	}
 
-	error = got_worktree_open(&worktree, cwd);
+	error = got_worktree_open(&worktree, cwd_fd, cwd);
 	if (error) {
 		if (error->code == GOT_ERR_NOT_WORKTREE)
 			error = wrap_not_worktree_error(error, "stage", cwd);
@@ -9302,6 +9429,7 @@ cmd_unstage(int argc, char *argv[])
 	struct got_pathlist_head paths;
 	struct got_pathlist_entry *pe;
 	int ch, pflag = 0;
+	int cwd_fd = -1;
 	struct got_update_progress_arg upa;
 	FILE *patch_script_file = NULL;
 	const char *patch_script_path = NULL;
@@ -9339,8 +9467,13 @@ cmd_unstage(int argc, char *argv[])
 		error = got_error_from_errno("getcwd");
 		goto done;
 	}
+	cwd_fd = open(cwd, O_DIRECTORY);
+	if (cwd_fd == -1) {
+		error = got_error_from_errno2("open", cwd);
+		goto done;
+	}
 
-	error = got_worktree_open(&worktree, cwd);
+	error = got_worktree_open(&worktree, cwd_fd, cwd);
 	if (error) {
 		if (error->code == GOT_ERR_NOT_WORKTREE)
 			error = wrap_not_worktree_error(error, "unstage", cwd);
@@ -9558,6 +9691,7 @@ cmd_cat(int argc, char *argv[])
 	const char *commit_id_str = NULL;
 	struct got_object_id *id = NULL, *commit_id = NULL;
 	int ch, obj_type, i, force_path = 0;
+	int cwd_fd = -1;
 
 #ifndef PROFILE
 	if (pledge("stdio rpath wpath cpath flock proc exec sendfd unveil",
@@ -9594,7 +9728,13 @@ cmd_cat(int argc, char *argv[])
 		error = got_error_from_errno("getcwd");
 		goto done;
 	}
-	error = got_worktree_open(&worktree, cwd);
+	cwd_fd = open(cwd, O_DIRECTORY);
+	if (cwd_fd == -1) {
+		error = got_error_from_errno2("open", cwd);
+		goto done;
+	}
+
+	error = got_worktree_open(&worktree, cwd_fd, cwd);
 	if (error && error->code != GOT_ERR_NOT_WORKTREE)
 		goto done;
 	if (worktree) {
@@ -9779,6 +9919,7 @@ cmd_info(int argc, char *argv[])
 	struct got_pathlist_entry *pe;
 	char *uuidstr = NULL;
 	int ch, show_files = 0;
+	int cwd_fd = -1;
 
 	TAILQ_INIT(&paths);
 
@@ -9803,8 +9944,13 @@ cmd_info(int argc, char *argv[])
 		error = got_error_from_errno("getcwd");
 		goto done;
 	}
+	cwd_fd = open(cwd, O_DIRECTORY);
+	if (cwd_fd == -1) {
+		error = got_error_from_errno2("open", cwd);
+		goto done;
+	}
 
-	error = got_worktree_open(&worktree, cwd);
+	error = got_worktree_open(&worktree, cwd_fd, cwd);
 	if (error) {
 		if (error->code == GOT_ERR_NOT_WORKTREE)
 			error = wrap_not_worktree_error(error, "status", cwd);
diff --git a/include/got_worktree.h b/include/got_worktree.h
index 13b13521..0e517bef 100644
--- a/include/got_worktree.h
+++ b/include/got_worktree.h
@@ -53,7 +53,7 @@ const struct got_error *got_worktree_init(int, const char *, struct got_referenc
  * Attempt to open a worktree at or above the specified path.
  * The caller must dispose of it with got_worktree_close().
  */
-const struct got_error *got_worktree_open(struct got_worktree **, const char *);
+const struct got_error *got_worktree_open(struct got_worktree **, int, const char *);
 
 /* Dispose of an open work tree. */
 const struct got_error *got_worktree_close(struct got_worktree *);
diff --git a/lib/worktree.c b/lib/worktree.c
index 8bf4b7ff..f4670cfb 100644
--- a/lib/worktree.c
+++ b/lib/worktree.c
@@ -440,20 +440,16 @@ done:
 }
 
 const struct got_error *
-got_worktree_open(struct got_worktree **worktree, const char *path)
+got_worktree_open(struct got_worktree **worktree,
+    int worktree_fd, const char *path)
 {
 	const struct got_error *err = NULL;
 	char *worktree_path;
-	int worktree_fd;
 
 	worktree_path = strdup(path);
 	if (worktree_path == NULL)
 		return got_error_from_errno("strdup");
 
-	worktree_fd = open(worktree_path, O_DIRECTORY);
-	if (worktree_fd == -1)
-		return got_error_from_errno2("open", worktree_path);
-
 	for (;;) {
 		char *parent_path;
 
diff --git a/tog/tog.c b/tog/tog.c
index 1bee4fa4..e47736b0 100644
--- a/tog/tog.c
+++ b/tog/tog.c
@@ -23,6 +23,7 @@
 #define _XOPEN_SOURCE_EXTENDED
 #include <curses.h>
 #undef _XOPEN_SOURCE_EXTENDED
+#include <fcntl.h>
 #include <panel.h>
 #include <locale.h>
 #include <signal.h>
@@ -2707,6 +2708,7 @@ cmd_log(int argc, char *argv[])
 	char *in_repo_path = NULL, *repo_path = NULL, *cwd = NULL;
 	char *start_commit = NULL, *head_ref_name = NULL;
 	int ch, log_branches = 0;
+	int cwd_fd = -1;
 	struct tog_view *view;
 
 #ifndef PROFILE
@@ -2744,8 +2746,11 @@ cmd_log(int argc, char *argv[])
 	cwd = getcwd(NULL, 0);
 	if (cwd == NULL)
 		return got_error_from_errno("getcwd");
+	cwd_fd = open(cwd, O_DIRECTORY);
+	if (cwd_fd == -1)
+		return got_error_from_errno2("open", cwd);
 
-	error = got_worktree_open(&worktree, cwd);
+	error = got_worktree_open(&worktree, cwd_fd, cwd);
 	if (error && error->code != GOT_ERR_NOT_WORKTREE)
 		goto done;
 
@@ -3800,6 +3805,7 @@ cmd_diff(int argc, char *argv[])
 	char *label1 = NULL, *label2 = NULL;
 	int diff_context = 3, ignore_whitespace = 0;
 	int ch, force_text_diff = 0;
+	int cwd_fd = -1;
 	const char *errstr;
 	struct tog_view *view;
 
@@ -3849,8 +3855,11 @@ cmd_diff(int argc, char *argv[])
 	cwd = getcwd(NULL, 0);
 	if (cwd == NULL)
 		return got_error_from_errno("getcwd");
+	cwd_fd = open(cwd, O_DIRECTORY);
+	if (cwd_fd == -1)
+		return got_error_from_errno2("open", cwd);
 
-	error = got_worktree_open(&worktree, cwd);
+	error = got_worktree_open(&worktree, cwd_fd, cwd);
 	if (error && error->code != GOT_ERR_NOT_WORKTREE)
 		goto done;
 
@@ -4721,6 +4730,7 @@ cmd_blame(int argc, char *argv[])
 	struct got_object_id *commit_id = NULL;
 	char *commit_id_str = NULL;
 	int ch;
+	int cwd_fd = -1;
 	struct tog_view *view;
 
 #ifndef PROFILE
@@ -4755,8 +4765,11 @@ cmd_blame(int argc, char *argv[])
 	cwd = getcwd(NULL, 0);
 	if (cwd == NULL)
 		return got_error_from_errno("getcwd");
+	cwd_fd = open(cwd, O_DIRECTORY);
+	if (cwd_fd == -1)
+		return got_error_from_errno2("open", cwd);
 
-	error = got_worktree_open(&worktree, cwd);
+	error = got_worktree_open(&worktree, cwd_fd, cwd);
 	if (error && error->code != GOT_ERR_NOT_WORKTREE)
 		goto done;
 
@@ -5541,6 +5554,7 @@ cmd_tree(int argc, char *argv[])
 	struct got_commit_object *commit = NULL;
 	struct got_tree_object *tree = NULL;
 	int ch;
+	int cwd_fd = -1;
 	struct tog_view *view;
 
 #ifndef PROFILE
@@ -5575,8 +5589,11 @@ cmd_tree(int argc, char *argv[])
 	cwd = getcwd(NULL, 0);
 	if (cwd == NULL)
 		return got_error_from_errno("getcwd");
+	cwd_fd = open(cwd, O_DIRECTORY);
+	if (cwd_fd == -1)
+		return got_error_from_errno2("open", cwd);
 
-	error = got_worktree_open(&worktree, cwd);
+	error = got_worktree_open(&worktree, cwd_fd, cwd);
 	if (error && error->code != GOT_ERR_NOT_WORKTREE)
 		goto done;
 
@@ -6280,6 +6297,7 @@ cmd_ref(int argc, char *argv[])
 	struct got_worktree *worktree = NULL;
 	char *cwd = NULL, *repo_path = NULL;
 	int ch;
+	int cwd_fd = -1;
 	struct tog_view *view;
 
 #ifndef PROFILE
@@ -6311,8 +6329,11 @@ cmd_ref(int argc, char *argv[])
 	cwd = getcwd(NULL, 0);
 	if (cwd == NULL)
 		return got_error_from_errno("getcwd");
+	cwd_fd = open(cwd, O_DIRECTORY);
+	if (cwd_fd == -1)
+		return got_error_from_errno2("open", cwd);
 
-	error = got_worktree_open(&worktree, cwd);
+	error = got_worktree_open(&worktree, cwd_fd, cwd);
 	if (error && error->code != GOT_ERR_NOT_WORKTREE)
 		goto done;
 
@@ -6428,12 +6449,16 @@ tog_log_with_path(int argc, char *argv[])
 	struct got_object_id *commit_id = NULL, *id = NULL;
 	char *cwd = NULL, *repo_path = NULL, *in_repo_path = NULL;
 	char *commit_id_str = NULL, **cmd_argv = NULL;
+	int cwd_fd = -1;
 
 	cwd = getcwd(NULL, 0);
 	if (cwd == NULL)
 		return got_error_from_errno("getcwd");
+	cwd_fd = open(cwd, O_DIRECTORY);
+	if (cwd_fd == -1)
+		return got_error_from_errno2("open", cwd);
 
-	error = got_worktree_open(&worktree, cwd);
+	error = got_worktree_open(&worktree, cwd_fd, cwd);
 	if (error && error->code != GOT_ERR_NOT_WORKTREE)
 		goto done;