Merge branch 'rs/strbuf-getcwd'
authorJunio C Hamano <gitster@pobox.com>
Tue, 2 Sep 2014 20:27:45 +0000 (13:27 -0700)
committerJunio C Hamano <gitster@pobox.com>
Tue, 2 Sep 2014 20:28:44 +0000 (13:28 -0700)
Reduce the use of fixed sized buffer passed to getcwd() calls
by introducing xgetcwd() helper.

* rs/strbuf-getcwd:
use strbuf_add_absolute_path() to add absolute paths
abspath: convert absolute_path() to strbuf
use xgetcwd() to set $GIT_DIR
use xgetcwd() to get the current directory or die
wrapper: add xgetcwd()
abspath: convert real_path_internal() to strbuf
abspath: use strbuf_getcwd() to remember original working directory
setup: convert setup_git_directory_gently_1 et al. to strbuf
unix-sockets: use strbuf_getcwd()
strbuf: add strbuf_getcwd()

12 files changed:
1  2 
Documentation/technical/api-strbuf.txt
builtin/init-db.c
builtin/rev-parse.c
dir.c
git-compat-util.h
git.c
setup.c
sha1_file.c
strbuf.c
strbuf.h
trace.c
unix-socket.c
index f9c06a7573e9294a967d641cdfc2723b86c1c864,1639a4a56b420cab84391d2f9338ea582046cfcc..430302c2f4ee8c3881d663b1f395234fd8cad0a3
@@@ -7,10 -7,10 +7,10 @@@ use the mem* functions than a str* one 
  Though, one has to be careful about the fact that str* functions often
  stop on NULs and that strbufs may have embedded NULs.
  
 -An strbuf is NUL terminated for convenience, but no function in the
 +A strbuf is NUL terminated for convenience, but no function in the
  strbuf API actually relies on the string being free of NULs.
  
 -strbufs has some invariants that are very important to keep in mind:
 +strbufs have some invariants that are very important to keep in mind:
  
  . The `buf` member is never NULL, so it can be used in any usual C
  string operations safely. strbuf's _have_ to be initialized either by
@@@ -56,8 -56,8 +56,8 @@@ Data structure
  * `struct strbuf`
  
  This is the string buffer structure. The `len` member can be used to
 -determine the current length of the string, and `buf` member provides access to
 -the string itself.
 +determine the current length of the string, and `buf` member provides
 +access to the string itself.
  
  Functions
  ---------
  
  * Related to the contents of the buffer
  
 +`strbuf_trim`::
 +
 +      Strip whitespace from the beginning and end of a string.
 +      Equivalent to performing `strbuf_rtrim()` followed by `strbuf_ltrim()`.
 +
  `strbuf_rtrim`::
  
        Strip whitespace from the end of a string.
  
 +`strbuf_ltrim`::
 +
 +      Strip whitespace from the beginning of a string.
 +
 +`strbuf_reencode`::
 +
 +      Replace the contents of the strbuf with a reencoded form.  Returns -1
 +      on error, 0 on success.
 +
 +`strbuf_tolower`::
 +
 +      Lowercase each character in the buffer using `tolower`.
 +
  `strbuf_cmp`::
  
        Compare two buffers. Returns an integer less than, equal to, or greater
@@@ -202,7 -184,7 +202,7 @@@ strbuf_addstr(sb, "immediate string")
  
  `strbuf_addbuf`::
  
 -      Copy the contents of an other buffer at the end of the current one.
 +      Copy the contents of another buffer at the end of the current one.
  
  `strbuf_adddup`::
  
@@@ -307,6 -289,16 +307,16 @@@ same behaviour as well
        use it unless you need the correct position in the file
        descriptor.
  
+ `strbuf_getcwd`::
+       Set the buffer to the path of the current working directory.
+ `strbuf_add_absolute_path`
+       Add a path to a buffer, converting a relative path to an
+       absolute one in the process.  Symbolic links are not
+       resolved.
  `stripspace`::
  
        Strip whitespace from a buffer. The second parameter controls if
diff --combined builtin/init-db.c
index 6d8ac2cc33b25ebb29655742f241b5a61e7ca563,ab0ea02d25e817bb5bb4ee683336c76c867f250e..587a5055ed677c3541c85101d944ffb0a19b1962
@@@ -330,12 -330,12 +330,12 @@@ int set_git_dir_init(const char *git_di
                 * moving the target repo later on in separate_git_dir()
                 */
                git_link = xstrdup(real_path(git_dir));
 +              set_git_dir(real_path(real_git_dir));
        }
        else {
 -              real_git_dir = real_path(git_dir);
 +              set_git_dir(real_path(git_dir));
                git_link = NULL;
        }
 -      set_git_dir(real_path(real_git_dir));
        return 0;
  }
  
@@@ -426,8 -426,9 +426,9 @@@ int init_db(const char *template_dir, u
  
  static int guess_repository_type(const char *git_dir)
  {
-       char cwd[PATH_MAX];
        const char *slash;
+       char *cwd;
+       int cwd_is_git_dir;
  
        /*
         * "GIT_DIR=. git init" is always bare.
         */
        if (!strcmp(".", git_dir))
                return 1;
-       if (!getcwd(cwd, sizeof(cwd)))
-               die_errno(_("cannot tell cwd"));
-       if (!strcmp(git_dir, cwd))
+       cwd = xgetcwd();
+       cwd_is_git_dir = !strcmp(git_dir, cwd);
+       free(cwd);
+       if (cwd_is_git_dir)
                return 1;
        /*
         * "GIT_DIR=.git or GIT_DIR=something/.git is usually not.
@@@ -535,10 -537,9 +537,9 @@@ int cmd_init_db(int argc, const char **
                usage(init_db_usage[0]);
        }
        if (is_bare_repository_cfg == 1) {
-               static char git_dir[PATH_MAX+1];
-               setenv(GIT_DIR_ENVIRONMENT,
-                       getcwd(git_dir, sizeof(git_dir)), argc > 0);
+               char *cwd = xgetcwd();
+               setenv(GIT_DIR_ENVIRONMENT, cwd, argc > 0);
+               free(cwd);
        }
  
        if (init_shared_repository != -1)
                        git_work_tree_cfg = xstrdup(real_path(rel));
                        free(rel);
                }
-               if (!git_work_tree_cfg) {
-                       git_work_tree_cfg = xcalloc(PATH_MAX, 1);
-                       if (!getcwd(git_work_tree_cfg, PATH_MAX))
-                               die_errno (_("Cannot access current working directory"));
-               }
+               if (!git_work_tree_cfg)
+                       git_work_tree_cfg = xgetcwd();
                if (work_tree)
 -                      set_git_work_tree(real_path(work_tree));
 +                      set_git_work_tree(work_tree);
                else
                        set_git_work_tree(git_work_tree_cfg);
                if (access(get_git_work_tree(), X_OK))
        }
        else {
                if (work_tree)
 -                      set_git_work_tree(real_path(work_tree));
 +                      set_git_work_tree(work_tree);
        }
  
        set_git_dir_init(git_dir, real_git_dir, 1);
diff --combined builtin/rev-parse.c
index d85e08cc9cc22ee307470dbeb12fdd8d1eea655b,a88123ad3e347f443425007dc5a02b7d6a33de79..c911b456dedd648e36a876c6175c23e1ef028a43
@@@ -11,7 -11,6 +11,7 @@@
  #include "parse-options.h"
  #include "diff.h"
  #include "revision.h"
 +#include "split-index.h"
  
  #define DO_REVS               1
  #define DO_NOREV      2
@@@ -151,7 -150,6 +151,7 @@@ static void show_rev(int type, const un
                                error("refname '%s' is ambiguous", name);
                                break;
                        }
 +                      free(full);
                } else {
                        show_with_type(type, name);
                }
@@@ -736,7 -734,7 +736,7 @@@ int cmd_rev_parse(int argc, const char 
                        }
                        if (!strcmp(arg, "--git-dir")) {
                                const char *gitdir = getenv(GIT_DIR_ENVIRONMENT);
-                               static char cwd[PATH_MAX];
+                               char *cwd;
                                int len;
                                if (gitdir) {
                                        puts(gitdir);
                                        puts(".git");
                                        continue;
                                }
-                               if (!getcwd(cwd, PATH_MAX))
-                                       die_errno("unable to get current working directory");
+                               cwd = xgetcwd();
                                len = strlen(cwd);
                                printf("%s%s.git\n", cwd, len && cwd[len-1] != '/' ? "/" : "");
+                               free(cwd);
                                continue;
                        }
                        if (!strcmp(arg, "--resolve-git-dir")) {
                                                : "false");
                                continue;
                        }
 +                      if (!strcmp(arg, "--shared-index-path")) {
 +                              if (read_cache() < 0)
 +                                      die(_("Could not read the index"));
 +                              if (the_index.split_index) {
 +                                      const unsigned char *sha1 = the_index.split_index->base_sha1;
 +                                      puts(git_path("sharedindex.%s", sha1_to_hex(sha1)));
 +                              }
 +                              continue;
 +                      }
                        if (starts_with(arg, "--since=")) {
                                show_datestring("--max-age=", arg+8);
                                continue;
diff --combined dir.c
index fcb68729b1f559008e1797ff50f38acf81268d3d,2c50ccfb4963a854401cb8d03b429947c7400f93..bd274a73f1faaee40bce34024ef17bfd44fbcad7
--- 1/dir.c
--- 2/dir.c
+++ b/dir.c
@@@ -557,7 -557,8 +557,7 @@@ int add_excludes_from_file_to_list(cons
                        buf = xrealloc(buf, size+1);
                        buf[size++] = '\n';
                }
 -      }
 -      else {
 +      } else {
                size = xsize_t(st.st_size);
                if (size == 0) {
                        close(fd);
@@@ -792,19 -793,17 +792,19 @@@ static void prep_exclude(struct dir_str
  
        group = &dir->exclude_list_group[EXC_DIRS];
  
 -      /* Pop the exclude lists from the EXCL_DIRS exclude_list_group
 +      /*
 +       * Pop the exclude lists from the EXCL_DIRS exclude_list_group
         * which originate from directories not in the prefix of the
 -       * path being checked. */
 +       * path being checked.
 +       */
        while ((stk = dir->exclude_stack) != NULL) {
                if (stk->baselen <= baselen &&
 -                  !strncmp(dir->basebuf, base, stk->baselen))
 +                  !strncmp(dir->basebuf.buf, base, stk->baselen))
                        break;
                el = &group->el[dir->exclude_stack->exclude_ix];
                dir->exclude_stack = stk->prev;
                dir->exclude = NULL;
 -              free((char *)el->src); /* see strdup() below */
 +              free((char *)el->src); /* see strbuf_detach() below */
                clear_exclude_list(el);
                free(stk);
                group->nr--;
        if (dir->exclude)
                return;
  
 +      /*
 +       * Lazy initialization. All call sites currently just
 +       * memset(dir, 0, sizeof(*dir)) before use. Changing all of
 +       * them seems lots of work for little benefit.
 +       */
 +      if (!dir->basebuf.buf)
 +              strbuf_init(&dir->basebuf, PATH_MAX);
 +
        /* Read from the parent directories and push them down. */
        current = stk ? stk->baselen : -1;
 +      strbuf_setlen(&dir->basebuf, current < 0 ? 0 : current);
        while (current < baselen) {
                struct exclude_stack *stk = xcalloc(1, sizeof(*stk));
                const char *cp;
                if (current < 0) {
                        cp = base;
                        current = 0;
 -              }
 -              else {
 +              } else {
                        cp = strchr(base + current + 1, '/');
                        if (!cp)
                                die("oops in prep_exclude");
                stk->baselen = cp - base;
                stk->exclude_ix = group->nr;
                el = add_exclude_list(dir, EXC_DIRS, NULL);
 -              memcpy(dir->basebuf + current, base + current,
 -                     stk->baselen - current);
 +              strbuf_add(&dir->basebuf, base + current, stk->baselen - current);
 +              assert(stk->baselen == dir->basebuf.len);
  
                /* Abort if the directory is excluded */
                if (stk->baselen) {
                        int dt = DT_DIR;
 -                      dir->basebuf[stk->baselen - 1] = 0;
 +                      dir->basebuf.buf[stk->baselen - 1] = 0;
                        dir->exclude = last_exclude_matching_from_lists(dir,
 -                              dir->basebuf, stk->baselen - 1,
 -                              dir->basebuf + current, &dt);
 -                      dir->basebuf[stk->baselen - 1] = '/';
 +                              dir->basebuf.buf, stk->baselen - 1,
 +                              dir->basebuf.buf + current, &dt);
 +                      dir->basebuf.buf[stk->baselen - 1] = '/';
                        if (dir->exclude &&
                            dir->exclude->flags & EXC_FLAG_NEGATIVE)
                                dir->exclude = NULL;
                        if (dir->exclude) {
 -                              dir->basebuf[stk->baselen] = 0;
                                dir->exclude_stack = stk;
                                return;
                        }
                }
  
 -              /* Try to read per-directory file unless path is too long */
 -              if (dir->exclude_per_dir &&
 -                  stk->baselen + strlen(dir->exclude_per_dir) < PATH_MAX) {
 -                      strcpy(dir->basebuf + stk->baselen,
 -                                      dir->exclude_per_dir);
 +              /* Try to read per-directory file */
 +              if (dir->exclude_per_dir) {
                        /*
                         * dir->basebuf gets reused by the traversal, but we
                         * need fname to remain unchanged to ensure the src
                         * member of each struct exclude correctly
                         * back-references its source file.  Other invocations
                         * of add_exclude_list provide stable strings, so we
 -                       * strdup() and free() here in the caller.
 +                       * strbuf_detach() and free() here in the caller.
                         */
 -                      el->src = strdup(dir->basebuf);
 -                      add_excludes_from_file_to_list(dir->basebuf,
 -                                      dir->basebuf, stk->baselen, el, 1);
 +                      struct strbuf sb = STRBUF_INIT;
 +                      strbuf_addbuf(&sb, &dir->basebuf);
 +                      strbuf_addstr(&sb, dir->exclude_per_dir);
 +                      el->src = strbuf_detach(&sb, NULL);
 +                      add_excludes_from_file_to_list(el->src, el->src,
 +                                                     stk->baselen, el, 1);
                }
                dir->exclude_stack = stk;
                current = stk->baselen;
        }
 -      dir->basebuf[baselen] = '\0';
 +      strbuf_setlen(&dir->basebuf, baselen);
  }
  
  /*
@@@ -1362,7 -1354,8 +1362,7 @@@ static int cmp_name(const void *p1, con
        const struct dir_entry *e1 = *(const struct dir_entry **)p1;
        const struct dir_entry *e2 = *(const struct dir_entry **)p2;
  
 -      return cache_name_compare(e1->name, e1->len,
 -                                e2->name, e2->len);
 +      return name_compare(e1->name, e1->len, e2->name, e2->len);
  }
  
  static struct path_simplify *create_simplify(const char **pathspec)
@@@ -1507,12 -1500,16 +1507,16 @@@ int dir_inside_of(const char *subdir, c
  
  int is_inside_dir(const char *dir)
  {
-       char cwd[PATH_MAX];
+       char *cwd;
+       int rc;
        if (!dir)
                return 0;
-       if (!getcwd(cwd, sizeof(cwd)))
-               die_errno("can't find the current directory");
-       return dir_inside_of(cwd, dir) >= 0;
+       cwd = xgetcwd();
+       rc = (dir_inside_of(cwd, dir) >= 0);
+       free(cwd);
+       return rc;
  }
  
  int is_empty_dir(const char *path)
@@@ -1679,5 -1676,4 +1683,5 @@@ void clear_directory(struct dir_struct 
                free(stk);
                stk = prev;
        }
 +      strbuf_release(&dir->basebuf);
  }
diff --combined git-compat-util.h
index f587749b7cf6a74be376792eb0ad70b7e5e4f597,4010d350d79b5676e3b17518fcec56925895c2fc..c150e3f8e748aec442bc4483c20b2ecca0d28fae
@@@ -267,10 -267,6 +267,10 @@@ extern char *gitbasename(char *)
  #define has_dos_drive_prefix(path) 0
  #endif
  
 +#ifndef offset_1st_component
 +#define offset_1st_component(path) (is_dir_sep((path)[0]))
 +#endif
 +
  #ifndef is_dir_sep
  #define is_dir_sep(c) ((c) == '/')
  #endif
  #else
  #define NORETURN
  #define NORETURN_PTR
 +#ifndef __GNUC__
  #ifndef __attribute__
  #define __attribute__(x)
  #endif
  #endif
 +#endif
  
  /* The sentinel attribute is valid from gcc version 4.0 */
  #if defined(__GNUC__) && (__GNUC__ >= 4)
@@@ -336,12 -330,8 +336,12 @@@ extern void warning(const char *err, ..
   * trying to help gcc, anyway, it's OK; other compilers will fall back to
   * using the function as usual.
   */
 -#if defined(__GNUC__) && ! defined(__clang__)
 -#define error(...) (error(__VA_ARGS__), -1)
 +#if defined(__GNUC__)
 +static inline int const_error(void)
 +{
 +      return -1;
 +}
 +#define error(...) (error(__VA_ARGS__), const_error())
  #endif
  
  extern void set_die_routine(NORETURN_PTR void (*routine)(const char *err, va_list params));
@@@ -349,66 -339,15 +349,66 @@@ extern void set_error_routine(void (*ro
  extern void set_die_is_recursing_routine(int (*routine)(void));
  
  extern int starts_with(const char *str, const char *prefix);
 -extern int ends_with(const char *str, const char *suffix);
  
 -static inline const char *skip_prefix(const char *str, const char *prefix)
 +/*
 + * If the string "str" begins with the string found in "prefix", return 1.
 + * The "out" parameter is set to "str + strlen(prefix)" (i.e., to the point in
 + * the string right after the prefix).
 + *
 + * Otherwise, return 0 and leave "out" untouched.
 + *
 + * Examples:
 + *
 + *   [extract branch name, fail if not a branch]
 + *   if (!skip_prefix(ref, "refs/heads/", &branch)
 + *    return -1;
 + *
 + *   [skip prefix if present, otherwise use whole string]
 + *   skip_prefix(name, "refs/heads/", &name);
 + */
 +static inline int skip_prefix(const char *str, const char *prefix,
 +                            const char **out)
  {
        do {
 -              if (!*prefix)
 -                      return str;
 +              if (!*prefix) {
 +                      *out = str;
 +                      return 1;
 +              }
        } while (*str++ == *prefix++);
 -      return NULL;
 +      return 0;
 +}
 +
 +/*
 + * If buf ends with suffix, return 1 and subtract the length of the suffix
 + * from *len. Otherwise, return 0 and leave *len untouched.
 + */
 +static inline int strip_suffix_mem(const char *buf, size_t *len,
 +                                 const char *suffix)
 +{
 +      size_t suflen = strlen(suffix);
 +      if (*len < suflen || memcmp(buf + (*len - suflen), suffix, suflen))
 +              return 0;
 +      *len -= suflen;
 +      return 1;
 +}
 +
 +/*
 + * If str ends with suffix, return 1 and set *len to the size of the string
 + * without the suffix. Otherwise, return 0 and set *len to the size of the
 + * string.
 + *
 + * Note that we do _not_ NUL-terminate str to the new length.
 + */
 +static inline int strip_suffix(const char *str, const char *suffix, size_t *len)
 +{
 +      *len = strlen(str);
 +      return strip_suffix_mem(str, len, suffix);
 +}
 +
 +static inline int ends_with(const char *str, const char *suffix)
 +{
 +      size_t len;
 +      return strip_suffix(str, suffix, &len);
  }
  
  #if defined(NO_MMAP) || defined(USE_WIN32_MMAP)
@@@ -582,14 -521,6 +582,14 @@@ extern void release_pack_memory(size_t)
  typedef void (*try_to_free_t)(size_t);
  extern try_to_free_t set_try_to_free_routine(try_to_free_t);
  
 +#ifdef HAVE_ALLOCA_H
 +# include <alloca.h>
 +# define xalloca(size)      (alloca(size))
 +# define xalloca_free(p)    do {} while (0)
 +#else
 +# define xalloca(size)      (xmalloc(size))
 +# define xalloca_free(p)    (free(p))
 +#endif
  extern char *xstrdup(const char *str);
  extern void *xmalloc(size_t size);
  extern void *xmallocz(size_t size);
@@@ -607,6 -538,7 +607,7 @@@ extern int xmkstemp(char *template)
  extern int xmkstemp_mode(char *template, int mode);
  extern int odb_mkstemp(char *template, size_t limit, const char *pattern);
  extern int odb_pack_keep(char *name, size_t namesz, const unsigned char *sha1);
+ extern char *xgetcwd(void);
  
  static inline size_t xsize_t(off_t len)
  {
        return (size_t)len;
  }
  
 -static inline int has_extension(const char *filename, const char *ext)
 -{
 -      size_t len = strlen(filename);
 -      size_t extlen = strlen(ext);
 -      return len > extlen && !memcmp(filename + len - extlen, ext, extlen);
 -}
 -
  /* in ctype.c, for kwset users */
  extern const char tolower_trans_tbl[256];
  
@@@ -750,10 -689,6 +751,10 @@@ void git_qsort(void *base, size_t nmemb
  #endif
  #endif
  
 +#if defined(__GNUC__) || (_MSC_VER >= 1400)
 +#define HAVE_VARIADIC_MACROS 1
 +#endif
 +
  /*
   * Preserves errno, prints a message, but gives no warning for ENOENT.
   * Always returns the return value of unlink(2).
diff --combined git.c
index 9c495198317bf31ff40785a48bf0a44253548098,c4e8c5cfdfadac8eb19a977854cf79cf2bce3881..210f1ae9d04dda4d16f9f072ef710bf630f6d52e
--- 1/git.c
--- 2/git.c
+++ b/git.c
@@@ -20,43 -20,6 +20,43 @@@ const char git_more_info_string[] 
  
  static struct startup_info git_startup_info;
  static int use_pager = -1;
- static char orig_cwd[PATH_MAX];
++static char *orig_cwd;
 +static const char *env_names[] = {
 +      GIT_DIR_ENVIRONMENT,
 +      GIT_WORK_TREE_ENVIRONMENT,
 +      GIT_IMPLICIT_WORK_TREE_ENVIRONMENT,
 +      GIT_PREFIX_ENVIRONMENT
 +};
 +static char *orig_env[4];
 +static int saved_environment;
 +
 +static void save_env(void)
 +{
 +      int i;
 +      if (saved_environment)
 +              return;
 +      saved_environment = 1;
-       if (!getcwd(orig_cwd, sizeof(orig_cwd)))
-               die_errno("cannot getcwd");
++      orig_cwd = xgetcwd();
 +      for (i = 0; i < ARRAY_SIZE(env_names); i++) {
 +              orig_env[i] = getenv(env_names[i]);
 +              if (orig_env[i])
 +                      orig_env[i] = xstrdup(orig_env[i]);
 +      }
 +}
 +
 +static void restore_env(void)
 +{
 +      int i;
-       if (*orig_cwd && chdir(orig_cwd))
++      if (orig_cwd && chdir(orig_cwd))
 +              die_errno("could not move to %s", orig_cwd);
++      free(orig_cwd);
 +      for (i = 0; i < ARRAY_SIZE(env_names); i++) {
 +              if (orig_env[i])
 +                      setenv(env_names[i], orig_env[i], 1);
 +              else
 +                      unsetenv(env_names[i]);
 +      }
 +}
  
  static void commit_pager_choice(void) {
        switch (use_pager) {
@@@ -91,7 -54,8 +91,7 @@@ static int handle_options(const char **
                /*
                 * Check remaining flags.
                 */
 -              if (starts_with(cmd, "--exec-path")) {
 -                      cmd += 11;
 +              if (skip_prefix(cmd, "--exec-path", &cmd)) {
                        if (*cmd == '=')
                                git_set_argv_exec_path(cmd + 1);
                        else {
                                *envchanged = 1;
                        (*argv)++;
                        (*argc)--;
 -              } else if (starts_with(cmd, "--git-dir=")) {
 -                      setenv(GIT_DIR_ENVIRONMENT, cmd + 10, 1);
 +              } else if (skip_prefix(cmd, "--git-dir=", &cmd)) {
 +                      setenv(GIT_DIR_ENVIRONMENT, cmd, 1);
                        if (envchanged)
                                *envchanged = 1;
                } else if (!strcmp(cmd, "--namespace")) {
                                *envchanged = 1;
                        (*argv)++;
                        (*argc)--;
 -              } else if (starts_with(cmd, "--namespace=")) {
 -                      setenv(GIT_NAMESPACE_ENVIRONMENT, cmd + 12, 1);
 +              } else if (skip_prefix(cmd, "--namespace=", &cmd)) {
 +                      setenv(GIT_NAMESPACE_ENVIRONMENT, cmd, 1);
                        if (envchanged)
                                *envchanged = 1;
                } else if (!strcmp(cmd, "--work-tree")) {
                                *envchanged = 1;
                        (*argv)++;
                        (*argc)--;
 -              } else if (starts_with(cmd, "--work-tree=")) {
 -                      setenv(GIT_WORK_TREE_ENVIRONMENT, cmd + 12, 1);
 +              } else if (skip_prefix(cmd, "--work-tree=", &cmd)) {
 +                      setenv(GIT_WORK_TREE_ENVIRONMENT, cmd, 1);
                        if (envchanged)
                                *envchanged = 1;
                } else if (!strcmp(cmd, "--bare")) {
-                       static char git_dir[PATH_MAX+1];
+                       char *cwd = xgetcwd();
                        is_bare_repository_cfg = 1;
-                       setenv(GIT_DIR_ENVIRONMENT, getcwd(git_dir, sizeof(git_dir)), 0);
+                       setenv(GIT_DIR_ENVIRONMENT, cwd, 0);
+                       free(cwd);
                        setenv(GIT_IMPLICIT_WORK_TREE_ENVIRONMENT, "0", 1);
                        if (envchanged)
                                *envchanged = 1;
@@@ -308,7 -273,6 +309,7 @@@ static int handle_alias(int *argcp, con
   * RUN_SETUP for reading from the configuration file.
   */
  #define NEED_WORK_TREE                (1<<3)
 +#define NO_SETUP              (1<<4)
  
  struct cmd_struct {
        const char *cmd;
@@@ -327,7 -291,7 +328,7 @@@ static int run_builtin(struct cmd_struc
        if (!help) {
                if (p->option & RUN_SETUP)
                        prefix = setup_git_directory();
 -              if (p->option & RUN_SETUP_GENTLY) {
 +              else if (p->option & RUN_SETUP_GENTLY) {
                        int nongit_ok;
                        prefix = setup_git_directory_gently(&nongit_ok);
                }
@@@ -389,7 -353,7 +390,7 @@@ static struct cmd_struct commands[] = 
        { "cherry", cmd_cherry, RUN_SETUP },
        { "cherry-pick", cmd_cherry_pick, RUN_SETUP | NEED_WORK_TREE },
        { "clean", cmd_clean, RUN_SETUP | NEED_WORK_TREE },
 -      { "clone", cmd_clone },
 +      { "clone", cmd_clone, NO_SETUP },
        { "column", cmd_column, RUN_SETUP_GENTLY },
        { "commit", cmd_commit, RUN_SETUP | NEED_WORK_TREE },
        { "commit-tree", cmd_commit_tree, RUN_SETUP },
        { "hash-object", cmd_hash_object },
        { "help", cmd_help },
        { "index-pack", cmd_index_pack, RUN_SETUP_GENTLY },
 -      { "init", cmd_init_db },
 -      { "init-db", cmd_init_db },
 +      { "init", cmd_init_db, NO_SETUP },
 +      { "init-db", cmd_init_db, NO_SETUP },
        { "log", cmd_log, RUN_SETUP },
        { "ls-files", cmd_ls_files, RUN_SETUP },
        { "ls-remote", cmd_ls_remote, RUN_SETUP_GENTLY },
        { "upload-archive", cmd_upload_archive },
        { "upload-archive--writer", cmd_upload_archive_writer },
        { "var", cmd_var, RUN_SETUP_GENTLY },
 +      { "verify-commit", cmd_verify_commit, RUN_SETUP },
        { "verify-pack", cmd_verify_pack },
        { "verify-tag", cmd_verify_tag, RUN_SETUP },
        { "version", cmd_version },
@@@ -522,10 -485,6 +523,10 @@@ static void handle_builtin(int argc, co
                struct cmd_struct *p = commands+i;
                if (strcmp(p->cmd, cmd))
                        continue;
 +              if (saved_environment && (p->option & NO_SETUP)) {
 +                      restore_env();
 +                      break;
 +              }
                exit(run_builtin(p, argc, argv));
        }
  }
@@@ -581,10 -540,7 +582,10 @@@ static int run_argv(int *argcp, const c
                 * of overriding "git log" with "git show" by having
                 * alias.log = show
                 */
 -              if (done_alias || !handle_alias(argcp, argv))
 +              if (done_alias)
 +                      break;
 +              save_env();
 +              if (!handle_alias(argcp, argv))
                        break;
                done_alias = 1;
        }
@@@ -613,8 -569,6 +614,8 @@@ int main(int argc, char **av
  
        git_setup_gettext();
  
 +      trace_command_performance(argv);
 +
        /*
         * "git-xxxx" is the same as "git xxxx", but we obviously:
         *
         * So we just directly call the builtin handler, and die if
         * that one cannot handle it.
         */
 -      if (starts_with(cmd, "git-")) {
 -              cmd += 4;
 +      if (skip_prefix(cmd, "git-", &cmd)) {
                argv[0] = cmd;
                handle_builtin(argc, argv);
                die("cannot handle %s as a builtin", cmd);
        argc--;
        handle_options(&argv, &argc, NULL);
        if (argc > 0) {
 -              if (starts_with(argv[0], "--"))
 -                      argv[0] += 2;
 +              /* translate --help and --version into commands */
 +              skip_prefix(argv[0], "--", &argv[0]);
        } else {
                /* The user didn't specify a command; give them help */
                commit_pager_choice();
diff --combined setup.c
index 793369da36fa92bddc63c12e722d44bba99dab17,72a4978ba5414af523a2c88a60a4d2d624606e85..979b13f0c6cd6bc3c265187e8ea79628bfe99f87
+++ b/setup.c
@@@ -387,7 -387,7 +387,7 @@@ const char *read_gitfile(const char *pa
  }
  
  static const char *setup_explicit_git_dir(const char *gitdirenv,
-                                         char *cwd, int len,
+                                         struct strbuf *cwd,
                                          int *nongit_ok)
  {
        const char *work_tree_env = getenv(GIT_WORK_TREE_ENVIRONMENT);
                if (is_absolute_path(git_work_tree_cfg))
                        set_git_work_tree(git_work_tree_cfg);
                else {
-                       char core_worktree[PATH_MAX];
+                       char *core_worktree;
                        if (chdir(gitdirenv))
                                die_errno("Could not chdir to '%s'", gitdirenv);
                        if (chdir(git_work_tree_cfg))
                                die_errno("Could not chdir to '%s'", git_work_tree_cfg);
-                       if (!getcwd(core_worktree, PATH_MAX))
-                               die_errno("Could not get directory '%s'", git_work_tree_cfg);
-                       if (chdir(cwd))
+                       core_worktree = xgetcwd();
+                       if (chdir(cwd->buf))
                                die_errno("Could not come back to cwd");
                        set_git_work_tree(core_worktree);
+                       free(core_worktree);
                }
        }
        else if (!git_env_bool(GIT_IMPLICIT_WORK_TREE_ENVIRONMENT, 1)) {
        worktree = get_git_work_tree();
  
        /* both get_git_work_tree() and cwd are already normalized */
-       if (!strcmp(cwd, worktree)) { /* cwd == worktree */
+       if (!strcmp(cwd->buf, worktree)) { /* cwd == worktree */
                set_git_dir(gitdirenv);
                free(gitfile);
                return NULL;
        }
  
-       offset = dir_inside_of(cwd, worktree);
+       offset = dir_inside_of(cwd->buf, worktree);
        if (offset >= 0) {      /* cwd inside worktree? */
                set_git_dir(real_path(gitdirenv));
                if (chdir(worktree))
                        die_errno("Could not chdir to '%s'", worktree);
-               cwd[len++] = '/';
-               cwd[len] = '\0';
+               strbuf_addch(cwd, '/');
                free(gitfile);
-               return cwd + offset;
+               return cwd->buf + offset;
        }
  
        /* cwd outside worktree */
  }
  
  static const char *setup_discovered_git_dir(const char *gitdir,
-                                           char *cwd, int offset, int len,
+                                           struct strbuf *cwd, int offset,
                                            int *nongit_ok)
  {
        if (check_repository_format_gently(gitdir, nongit_ok))
  
        /* --work-tree is set without --git-dir; use discovered one */
        if (getenv(GIT_WORK_TREE_ENVIRONMENT) || git_work_tree_cfg) {
-               if (offset != len && !is_absolute_path(gitdir))
+               if (offset != cwd->len && !is_absolute_path(gitdir))
                        gitdir = xstrdup(real_path(gitdir));
-               if (chdir(cwd))
+               if (chdir(cwd->buf))
                        die_errno("Could not come back to cwd");
-               return setup_explicit_git_dir(gitdir, cwd, len, nongit_ok);
+               return setup_explicit_git_dir(gitdir, cwd, nongit_ok);
        }
  
        /* #16.2, #17.2, #20.2, #21.2, #24, #25, #28, #29 (see t1510) */
        if (is_bare_repository_cfg > 0) {
-               set_git_dir(offset == len ? gitdir : real_path(gitdir));
-               if (chdir(cwd))
+               set_git_dir(offset == cwd->len ? gitdir : real_path(gitdir));
+               if (chdir(cwd->buf))
                        die_errno("Could not come back to cwd");
                return NULL;
        }
                set_git_dir(gitdir);
        inside_git_dir = 0;
        inside_work_tree = 1;
-       if (offset == len)
+       if (offset == cwd->len)
                return NULL;
  
        /* Make "offset" point to past the '/', and add a '/' at the end */
        offset++;
-       cwd[len++] = '/';
-       cwd[len] = 0;
-       return cwd + offset;
+       strbuf_addch(cwd, '/');
+       return cwd->buf + offset;
  }
  
  /* #16.1, #17.1, #20.1, #21.1, #22.1 (see t1510) */
- static const char *setup_bare_git_dir(char *cwd, int offset, int len, int *nongit_ok)
+ static const char *setup_bare_git_dir(struct strbuf *cwd, int offset,
+                                     int *nongit_ok)
  {
        int root_len;
  
        if (getenv(GIT_WORK_TREE_ENVIRONMENT) || git_work_tree_cfg) {
                const char *gitdir;
  
-               gitdir = offset == len ? "." : xmemdupz(cwd, offset);
-               if (chdir(cwd))
+               gitdir = offset == cwd->len ? "." : xmemdupz(cwd->buf, offset);
+               if (chdir(cwd->buf))
                        die_errno("Could not come back to cwd");
-               return setup_explicit_git_dir(gitdir, cwd, len, nongit_ok);
+               return setup_explicit_git_dir(gitdir, cwd, nongit_ok);
        }
  
        inside_git_dir = 1;
        inside_work_tree = 0;
-       if (offset != len) {
-               if (chdir(cwd))
+       if (offset != cwd->len) {
+               if (chdir(cwd->buf))
                        die_errno("Cannot come back to cwd");
-               root_len = offset_1st_component(cwd);
-               cwd[offset > root_len ? offset : root_len] = '\0';
-               set_git_dir(cwd);
+               root_len = offset_1st_component(cwd->buf);
+               strbuf_setlen(cwd, offset > root_len ? offset : root_len);
+               set_git_dir(cwd->buf);
        }
        else
                set_git_dir(".");
@@@ -617,22 -616,13 +616,22 @@@ static const char *setup_git_directory_
  {
        const char *env_ceiling_dirs = getenv(CEILING_DIRECTORIES_ENVIRONMENT);
        struct string_list ceiling_dirs = STRING_LIST_INIT_DUP;
-       static char cwd[PATH_MAX + 1];
+       static struct strbuf cwd = STRBUF_INIT;
        const char *gitdirenv, *ret;
        char *gitfile;
-       int len, offset, offset_parent, ceil_offset = -1;
+       int offset, offset_parent, ceil_offset = -1;
        dev_t current_device = 0;
        int one_filesystem = 1;
  
 +      /*
 +       * We may have read an incomplete configuration before
 +       * setting-up the git directory. If so, clear the cache so
 +       * that the next queries to the configuration reload complete
 +       * configuration (including the per-repo config file that we
 +       * ignored previously).
 +       */
 +      git_config_clear();
 +
        /*
         * Let's assume that we are in a git repository.
         * If it turns out later that we are somewhere else, the value will be
        if (nongit_ok)
                *nongit_ok = 0;
  
-       if (!getcwd(cwd, sizeof(cwd) - 1))
+       if (strbuf_getcwd(&cwd))
                die_errno("Unable to read current working directory");
-       offset = len = strlen(cwd);
+       offset = cwd.len;
  
        /*
         * If GIT_DIR is set explicitly, we're not going
         */
        gitdirenv = getenv(GIT_DIR_ENVIRONMENT);
        if (gitdirenv)
-               return setup_explicit_git_dir(gitdirenv, cwd, len, nongit_ok);
+               return setup_explicit_git_dir(gitdirenv, &cwd, nongit_ok);
  
        if (env_ceiling_dirs) {
                int empty_entry_found = 0;
                string_list_split(&ceiling_dirs, env_ceiling_dirs, PATH_SEP, -1);
                filter_string_list(&ceiling_dirs, 0,
                                   canonicalize_ceiling_entry, &empty_entry_found);
-               ceil_offset = longest_ancestor_length(cwd, &ceiling_dirs);
+               ceil_offset = longest_ancestor_length(cwd.buf, &ceiling_dirs);
                string_list_clear(&ceiling_dirs, 0);
        }
  
-       if (ceil_offset < 0 && has_dos_drive_prefix(cwd))
+       if (ceil_offset < 0 && has_dos_drive_prefix(cwd.buf))
                ceil_offset = 1;
  
        /*
  
                if (gitdirenv) {
                        ret = setup_discovered_git_dir(gitdirenv,
-                                                      cwd, offset, len,
+                                                      &cwd, offset,
                                                       nongit_ok);
                        free(gitfile);
                        return ret;
                free(gitfile);
  
                if (is_git_directory("."))
-                       return setup_bare_git_dir(cwd, offset, len, nongit_ok);
+                       return setup_bare_git_dir(&cwd, offset, nongit_ok);
  
                offset_parent = offset;
-               while (--offset_parent > ceil_offset && cwd[offset_parent] != '/');
+               while (--offset_parent > ceil_offset && cwd.buf[offset_parent] != '/');
                if (offset_parent <= ceil_offset)
-                       return setup_nongit(cwd, nongit_ok);
+                       return setup_nongit(cwd.buf, nongit_ok);
                if (one_filesystem) {
-                       dev_t parent_device = get_device_or_die("..", cwd, offset);
+                       dev_t parent_device = get_device_or_die("..", cwd.buf,
+                                                               offset);
                        if (parent_device != current_device) {
                                if (nongit_ok) {
-                                       if (chdir(cwd))
+                                       if (chdir(cwd.buf))
                                                die_errno("Cannot come back to cwd");
                                        *nongit_ok = 1;
                                        return NULL;
                                }
-                               cwd[offset] = '\0';
+                               strbuf_setlen(&cwd, offset);
                                die("Not a git repository (or any parent up to mount point %s)\n"
-                               "Stopping at filesystem boundary (GIT_DISCOVERY_ACROSS_FILESYSTEM not set).", cwd);
+                               "Stopping at filesystem boundary (GIT_DISCOVERY_ACROSS_FILESYSTEM not set).",
+                                   cwd.buf);
                        }
                }
                if (chdir("..")) {
-                       cwd[offset] = '\0';
-                       die_errno("Cannot change to '%s/..'", cwd);
+                       strbuf_setlen(&cwd, offset);
+                       die_errno("Cannot change to '%s/..'", cwd.buf);
                }
                offset = offset_parent;
        }
diff --combined sha1_file.c
index 3f70b1d86aaa8e0648d8a3c6ebb6aca701ae7475,a38854ce553c1e59294d5542a37a5404ccd66dc5..95afd209107277da3154226dd08bd8040a9097b5
@@@ -36,6 -36,9 +36,6 @@@ static inline uintmax_t sz_fmt(size_t s
  
  const unsigned char null_sha1[20];
  
 -static const char *no_log_pack_access = "no_log_pack_access";
 -static const char *log_pack_access;
 -
  /*
   * This is meant to hold a *small* number of objects that you would
   * want read_sha1_file() to be able to return, but yet you do not want
@@@ -265,9 -268,9 +265,9 @@@ static struct alternate_object_databas
   * SHA1, an extra slash for the first level indirection, and the
   * terminating NUL.
   */
 -static int link_alt_odb_entry(const char *entry, const char *relative_base, int depth)
 +static int link_alt_odb_entry(const char *entry, const char *relative_base,
 +      int depth, const char *normalized_objdir)
  {
 -      const char *objdir = get_object_directory();
        struct alternate_object_database *ent;
        struct alternate_object_database *alt;
        int pfxlen, entlen;
                        return -1;
                }
        }
 -      if (!strcmp(ent->base, objdir)) {
 +      if (!strcmp_icase(ent->base, normalized_objdir)) {
                free(ent);
                return -1;
        }
@@@ -342,7 -345,6 +342,7 @@@ static void link_alt_odb_entries(const 
        struct string_list entries = STRING_LIST_INIT_NODUP;
        char *alt_copy;
        int i;
 +      struct strbuf objdirbuf = STRBUF_INIT;
  
        if (depth > 5) {
                error("%s: ignoring alternate object stores, nesting too deep.",
                return;
        }
  
-       strbuf_addstr(&objdirbuf, absolute_path(get_object_directory()));
++      strbuf_add_absolute_path(&objdirbuf, get_object_directory());
 +      normalize_path_copy(objdirbuf.buf, objdirbuf.buf);
 +
        alt_copy = xmemdupz(alt, len);
        string_list_split_in_place(&entries, alt_copy, sep, -1);
        for (i = 0; i < entries.nr; i++) {
                        error("%s: ignoring relative alternate object store %s",
                                        relative_base, entry);
                } else {
 -                      link_alt_odb_entry(entry, relative_base, depth);
 +                      link_alt_odb_entry(entry, relative_base, depth, objdirbuf.buf);
                }
        }
        string_list_clear(&entries, 0);
        free(alt_copy);
 +      strbuf_release(&objdirbuf);
  }
  
  void read_info_alternates(const char * relative_base, int depth)
@@@ -1180,42 -1178,48 +1180,42 @@@ static void report_pack_garbage(struct 
  
  static void prepare_packed_git_one(char *objdir, int local)
  {
 -      /* Ensure that this buffer is large enough so that we can
 -         append "/pack/" without clobbering the stack even if
 -         strlen(objdir) were PATH_MAX.  */
 -      char path[PATH_MAX + 1 + 4 + 1 + 1];
 -      int len;
 +      struct strbuf path = STRBUF_INIT;
 +      size_t dirnamelen;
        DIR *dir;
        struct dirent *de;
        struct string_list garbage = STRING_LIST_INIT_DUP;
  
 -      sprintf(path, "%s/pack", objdir);
 -      len = strlen(path);
 -      dir = opendir(path);
 +      strbuf_addstr(&path, objdir);
 +      strbuf_addstr(&path, "/pack");
 +      dir = opendir(path.buf);
        if (!dir) {
                if (errno != ENOENT)
                        error("unable to open object pack directory: %s: %s",
 -                            path, strerror(errno));
 +                            path.buf, strerror(errno));
 +              strbuf_release(&path);
                return;
        }
 -      path[len++] = '/';
 +      strbuf_addch(&path, '/');
 +      dirnamelen = path.len;
        while ((de = readdir(dir)) != NULL) {
 -              int namelen = strlen(de->d_name);
                struct packed_git *p;
 -
 -              if (len + namelen + 1 > sizeof(path)) {
 -                      if (report_garbage) {
 -                              struct strbuf sb = STRBUF_INIT;
 -                              strbuf_addf(&sb, "%.*s/%s", len - 1, path, de->d_name);
 -                              report_garbage("path too long", sb.buf);
 -                              strbuf_release(&sb);
 -                      }
 -                      continue;
 -              }
 +              size_t base_len;
  
                if (is_dot_or_dotdot(de->d_name))
                        continue;
  
 -              strcpy(path + len, de->d_name);
 +              strbuf_setlen(&path, dirnamelen);
 +              strbuf_addstr(&path, de->d_name);
  
 -              if (has_extension(de->d_name, ".idx")) {
 +              base_len = path.len;
 +              if (strip_suffix_mem(path.buf, &base_len, ".idx")) {
                        /* Don't reopen a pack we already have. */
                        for (p = packed_git; p; p = p->next) {
 -                              if (!memcmp(path, p->pack_name, len + namelen - 4))
 +                              size_t len;
 +                              if (strip_suffix(p->pack_name, ".pack", &len) &&
 +                                  len == base_len &&
 +                                  !memcmp(p->pack_name, path.buf, len))
                                        break;
                        }
                        if (p == NULL &&
                             * See if it really is a valid .idx file with
                             * corresponding .pack file that we can map.
                             */
 -                          (p = add_packed_git(path, len + namelen, local)) != NULL)
 +                          (p = add_packed_git(path.buf, path.len, local)) != NULL)
                                install_packed_git(p);
                }
  
                if (!report_garbage)
                        continue;
  
 -              if (has_extension(de->d_name, ".idx") ||
 -                  has_extension(de->d_name, ".pack") ||
 -                  has_extension(de->d_name, ".bitmap") ||
 -                  has_extension(de->d_name, ".keep"))
 -                      string_list_append(&garbage, path);
 +              if (ends_with(de->d_name, ".idx") ||
 +                  ends_with(de->d_name, ".pack") ||
 +                  ends_with(de->d_name, ".bitmap") ||
 +                  ends_with(de->d_name, ".keep"))
 +                      string_list_append(&garbage, path.buf);
                else
 -                      report_garbage("garbage found", path);
 +                      report_garbage("garbage found", path.buf);
        }
        closedir(dir);
        report_pack_garbage(&garbage);
        string_list_clear(&garbage, 0);
 +      strbuf_release(&path);
  }
  
  static int sort_pack(const void *a_, const void *b_)
@@@ -2083,9 -2086,27 +2083,9 @@@ static void *read_object(const unsigne
  
  static void write_pack_access_log(struct packed_git *p, off_t obj_offset)
  {
 -      static FILE *log_file;
 -
 -      if (!log_pack_access)
 -              log_pack_access = getenv("GIT_TRACE_PACK_ACCESS");
 -      if (!log_pack_access)
 -              log_pack_access = no_log_pack_access;
 -      if (log_pack_access == no_log_pack_access)
 -              return;
 -
 -      if (!log_file) {
 -              log_file = fopen(log_pack_access, "w");
 -              if (!log_file) {
 -                      error("cannot open pack access log '%s' for writing: %s",
 -                            log_pack_access, strerror(errno));
 -                      log_pack_access = no_log_pack_access;
 -                      return;
 -              }
 -      }
 -      fprintf(log_file, "%s %"PRIuMAX"\n",
 -              p->pack_name, (uintmax_t)obj_offset);
 -      fflush(log_file);
 +      static struct trace_key pack_access = TRACE_KEY_INIT(PACK_ACCESS);
 +      trace_printf_key(&pack_access, "%s %"PRIuMAX"\n",
 +                       p->pack_name, (uintmax_t)obj_offset);
  }
  
  int do_check_packed_object_crc;
@@@ -2110,7 -2131,8 +2110,7 @@@ void *unpack_entry(struct packed_git *p
        int delta_stack_nr = 0, delta_stack_alloc = UNPACK_ENTRY_STACK_PREALLOC;
        int base_from_cache = 0;
  
 -      if (log_pack_access != no_log_pack_access)
 -              write_pack_access_log(p, obj_offset);
 +      write_pack_access_log(p, obj_offset);
  
        /* PHASE 1: drill down to the innermost base object */
        for (;;) {
diff --combined strbuf.c
index 33018d847f08424211c02cff8002680f5a2f02e9,bf4c31b1579b01f925c9963c1ba7d49e32dc93ee..4d3144308feaf3fdc541b582ed589962c6c459c1
+++ b/strbuf.c
@@@ -1,6 -1,5 +1,6 @@@
  #include "cache.h"
  #include "refs.h"
 +#include "utf8.h"
  
  int starts_with(const char *str, const char *prefix)
  {
                        return 0;
  }
  
 -int ends_with(const char *str, const char *suffix)
 -{
 -      int len = strlen(str), suflen = strlen(suffix);
 -      if (len < suflen)
 -              return 0;
 -      else
 -              return !strcmp(str + len - suflen, suffix);
 -}
 -
  /*
   * Used as the default ->buf value, so that people can always assume
   * buf is non NULL and ->buf is NUL terminated even for a freshly
@@@ -70,8 -78,15 +70,8 @@@ void strbuf_grow(struct strbuf *sb, siz
  
  void strbuf_trim(struct strbuf *sb)
  {
 -      char *b = sb->buf;
 -      while (sb->len > 0 && isspace((unsigned char)sb->buf[sb->len - 1]))
 -              sb->len--;
 -      while (sb->len > 0 && isspace(*b)) {
 -              b++;
 -              sb->len--;
 -      }
 -      memmove(sb->buf, b, sb->len);
 -      sb->buf[sb->len] = '\0';
 +      strbuf_rtrim(sb);
 +      strbuf_ltrim(sb);
  }
  void strbuf_rtrim(struct strbuf *sb)
  {
@@@ -91,29 -106,6 +91,29 @@@ void strbuf_ltrim(struct strbuf *sb
        sb->buf[sb->len] = '\0';
  }
  
 +int strbuf_reencode(struct strbuf *sb, const char *from, const char *to)
 +{
 +      char *out;
 +      int len;
 +
 +      if (same_encoding(from, to))
 +              return 0;
 +
 +      out = reencode_string_len(sb->buf, sb->len, to, from, &len);
 +      if (!out)
 +              return -1;
 +
 +      strbuf_attach(sb, out, len, len);
 +      return 0;
 +}
 +
 +void strbuf_tolower(struct strbuf *sb)
 +{
 +      char *p = sb->buf, *end = sb->buf + sb->len;
 +      for (; p < end; p++)
 +              *p = tolower(*p);
 +}
 +
  struct strbuf **strbuf_split_buf(const char *str, size_t slen,
                                 int terminator, int max)
  {
@@@ -406,6 -398,27 +406,27 @@@ int strbuf_readlink(struct strbuf *sb, 
        return -1;
  }
  
+ int strbuf_getcwd(struct strbuf *sb)
+ {
+       size_t oldalloc = sb->alloc;
+       size_t guessed_len = 128;
+       for (;; guessed_len *= 2) {
+               strbuf_grow(sb, guessed_len);
+               if (getcwd(sb->buf, sb->alloc)) {
+                       strbuf_setlen(sb, strlen(sb->buf));
+                       return 0;
+               }
+               if (errno != ERANGE)
+                       break;
+       }
+       if (oldalloc == 0)
+               strbuf_release(sb);
+       else
+               strbuf_reset(sb);
+       return -1;
+ }
  int strbuf_getwholeline(struct strbuf *sb, FILE *fp, int term)
  {
        int ch;
@@@ -555,6 -568,31 +576,31 @@@ void strbuf_humanise_bytes(struct strbu
        }
  }
  
+ void strbuf_add_absolute_path(struct strbuf *sb, const char *path)
+ {
+       if (!*path)
+               die("The empty string is not a valid path");
+       if (!is_absolute_path(path)) {
+               struct stat cwd_stat, pwd_stat;
+               size_t orig_len = sb->len;
+               char *cwd = xgetcwd();
+               char *pwd = getenv("PWD");
+               if (pwd && strcmp(pwd, cwd) &&
+                   !stat(cwd, &cwd_stat) &&
+                   (cwd_stat.st_dev || cwd_stat.st_ino) &&
+                   !stat(pwd, &pwd_stat) &&
+                   pwd_stat.st_dev == cwd_stat.st_dev &&
+                   pwd_stat.st_ino == cwd_stat.st_ino)
+                       strbuf_addstr(sb, pwd);
+               else
+                       strbuf_addstr(sb, cwd);
+               if (sb->len > orig_len && !is_dir_sep(sb->buf[sb->len - 1]))
+                       strbuf_addch(sb, '/');
+               free(cwd);
+       }
+       strbuf_addstr(sb, path);
+ }
  int printf_ln(const char *fmt, ...)
  {
        int ret;
@@@ -578,35 -616,3 +624,35 @@@ int fprintf_ln(FILE *fp, const char *fm
                return -1;
        return ret + 1;
  }
 +
 +char *xstrdup_tolower(const char *string)
 +{
 +      char *result;
 +      size_t len, i;
 +
 +      len = strlen(string);
 +      result = xmalloc(len + 1);
 +      for (i = 0; i < len; i++)
 +              result[i] = tolower(string[i]);
 +      result[i] = '\0';
 +      return result;
 +}
 +
 +char *xstrvfmt(const char *fmt, va_list ap)
 +{
 +      struct strbuf buf = STRBUF_INIT;
 +      strbuf_vaddf(&buf, fmt, ap);
 +      return strbuf_detach(&buf, NULL);
 +}
 +
 +char *xstrfmt(const char *fmt, ...)
 +{
 +      va_list ap;
 +      char *ret;
 +
 +      va_start(ap, fmt);
 +      ret = xstrvfmt(fmt, ap);
 +      va_end(ap);
 +
 +      return ret;
 +}
diff --combined strbuf.h
index a7c0192e9ee392bd232b325692a9111b3e160e84,455826ceb075631fdd9c86e54344080895808259..7bdc1da50732bacad3ba33526ba10ee8f7e200a4
+++ b/strbuf.h
@@@ -45,19 -45,8 +45,19 @@@ static inline void strbuf_setlen(struc
  extern void strbuf_trim(struct strbuf *);
  extern void strbuf_rtrim(struct strbuf *);
  extern void strbuf_ltrim(struct strbuf *);
 +extern int strbuf_reencode(struct strbuf *sb, const char *from, const char *to);
 +extern void strbuf_tolower(struct strbuf *sb);
  extern int strbuf_cmp(const struct strbuf *, const struct strbuf *);
  
 +static inline int strbuf_strip_suffix(struct strbuf *sb, const char *suffix)
 +{
 +      if (strip_suffix_mem(sb->buf, &sb->len, suffix)) {
 +              strbuf_setlen(sb, sb->len);
 +              return 1;
 +      } else
 +              return 0;
 +}
 +
  /*
   * Split str (of length slen) at the specified terminator character.
   * Return a null-terminated array of pointers to strbuf objects
@@@ -174,6 -163,7 +174,7 @@@ extern size_t strbuf_fread(struct strbu
  extern ssize_t strbuf_read(struct strbuf *, int fd, size_t hint);
  extern int strbuf_read_file(struct strbuf *sb, const char *path, size_t hint);
  extern int strbuf_readlink(struct strbuf *sb, const char *path, size_t hint);
+ extern int strbuf_getcwd(struct strbuf *sb);
  
  extern int strbuf_getwholeline(struct strbuf *, FILE *, int);
  extern int strbuf_getline(struct strbuf *, FILE *, int);
@@@ -189,20 -179,11 +190,22 @@@ extern void strbuf_addstr_urlencode(str
                                    int reserved);
  extern void strbuf_humanise_bytes(struct strbuf *buf, off_t bytes);
  
+ extern void strbuf_add_absolute_path(struct strbuf *sb, const char *path);
  __attribute__((format (printf,1,2)))
  extern int printf_ln(const char *fmt, ...);
  __attribute__((format (printf,2,3)))
  extern int fprintf_ln(FILE *fp, const char *fmt, ...);
  
 +char *xstrdup_tolower(const char *);
 +
 +/*
 + * Create a newly allocated string using printf format. You can do this easily
 + * with a strbuf, but this provides a shortcut to save a few lines.
 + */
 +__attribute__((format (printf, 1, 0)))
 +char *xstrvfmt(const char *fmt, va_list ap);
 +__attribute__((format (printf, 1, 2)))
 +char *xstrfmt(const char *fmt, ...);
 +
  #endif /* STRBUF_H */
diff --combined trace.c
index e583dc63bb8d7062f8b735e701978574e9fcbf25,3523667f6f6fb2c9ca38917f8e45144e99186dc7..54aaee58183f880365caa1986824054a4533797e
+++ b/trace.c
  #include "quote.h"
  
  /* Get a trace file descriptor from "key" env variable. */
 -static int get_trace_fd(const char *key, int *need_close)
 +static int get_trace_fd(struct trace_key *key)
  {
 -      char *trace = getenv(key);
 +      static struct trace_key trace_default = { "GIT_TRACE" };
 +      const char *trace;
 +
 +      /* use default "GIT_TRACE" if NULL */
 +      if (!key)
 +              key = &trace_default;
 +
 +      /* don't open twice */
 +      if (key->initialized)
 +              return key->fd;
 +
 +      trace = getenv(key->key);
  
        if (!trace || !strcmp(trace, "") ||
            !strcmp(trace, "0") || !strcasecmp(trace, "false"))
 -              return 0;
 -      if (!strcmp(trace, "1") || !strcasecmp(trace, "true"))
 -              return STDERR_FILENO;
 -      if (strlen(trace) == 1 && isdigit(*trace))
 -              return atoi(trace);
 -      if (is_absolute_path(trace)) {
 +              key->fd = 0;
 +      else if (!strcmp(trace, "1") || !strcasecmp(trace, "true"))
 +              key->fd = STDERR_FILENO;
 +      else if (strlen(trace) == 1 && isdigit(*trace))
 +              key->fd = atoi(trace);
 +      else if (is_absolute_path(trace)) {
                int fd = open(trace, O_WRONLY | O_APPEND | O_CREAT, 0666);
                if (fd == -1) {
                        fprintf(stderr,
                                "Could not open '%s' for tracing: %s\n"
                                "Defaulting to tracing on stderr...\n",
                                trace, strerror(errno));
 -                      return STDERR_FILENO;
 +                      key->fd = STDERR_FILENO;
 +              } else {
 +                      key->fd = fd;
 +                      key->need_close = 1;
                }
 -              *need_close = 1;
 -              return fd;
 +      } else {
 +              fprintf(stderr, "What does '%s' for %s mean?\n"
 +                      "If you want to trace into a file, then please set "
 +                      "%s to an absolute pathname (starting with /).\n"
 +                      "Defaulting to tracing on stderr...\n",
 +                      trace, key->key, key->key);
 +              key->fd = STDERR_FILENO;
        }
  
 -      fprintf(stderr, "What does '%s' for %s mean?\n", trace, key);
 -      fprintf(stderr, "If you want to trace into a file, "
 -              "then please set %s to an absolute pathname "
 -              "(starting with /).\n", key);
 -      fprintf(stderr, "Defaulting to tracing on stderr...\n");
 +      key->initialized = 1;
 +      return key->fd;
 +}
  
 -      return STDERR_FILENO;
 +void trace_disable(struct trace_key *key)
 +{
 +      if (key->need_close)
 +              close(key->fd);
 +      key->fd = 0;
 +      key->initialized = 1;
 +      key->need_close = 0;
  }
  
  static const char err_msg[] = "Could not trace into fd given by "
        "GIT_TRACE environment variable";
  
 -static void trace_vprintf(const char *key, const char *fmt, va_list ap)
 +static int prepare_trace_line(const char *file, int line,
 +                            struct trace_key *key, struct strbuf *buf)
  {
 -      struct strbuf buf = STRBUF_INIT;
 +      static struct trace_key trace_bare = TRACE_KEY_INIT(BARE);
 +      struct timeval tv;
 +      struct tm tm;
 +      time_t secs;
  
        if (!trace_want(key))
 -              return;
 +              return 0;
  
        set_try_to_free_routine(NULL);  /* is never reset */
 -      strbuf_vaddf(&buf, fmt, ap);
 -      trace_strbuf(key, &buf);
 -      strbuf_release(&buf);
 +
 +      /* unit tests may want to disable additional trace output */
 +      if (trace_want(&trace_bare))
 +              return 1;
 +
 +      /* print current timestamp */
 +      gettimeofday(&tv, NULL);
 +      secs = tv.tv_sec;
 +      localtime_r(&secs, &tm);
 +      strbuf_addf(buf, "%02d:%02d:%02d.%06ld ", tm.tm_hour, tm.tm_min,
 +                  tm.tm_sec, (long) tv.tv_usec);
 +
 +#ifdef HAVE_VARIADIC_MACROS
 +      /* print file:line */
 +      strbuf_addf(buf, "%s:%d ", file, line);
 +      /* align trace output (column 40 catches most files names in git) */
 +      while (buf->len < 40)
 +              strbuf_addch(buf, ' ');
 +#endif
 +
 +      return 1;
  }
  
 -__attribute__((format (printf, 2, 3)))
 -void trace_printf_key(const char *key, const char *fmt, ...)
 +static void print_trace_line(struct trace_key *key, struct strbuf *buf)
 +{
 +      /* append newline if missing */
 +      if (buf->len && buf->buf[buf->len - 1] != '\n')
 +              strbuf_addch(buf, '\n');
 +
 +      write_or_whine_pipe(get_trace_fd(key), buf->buf, buf->len, err_msg);
 +      strbuf_release(buf);
 +}
 +
 +static void trace_vprintf_fl(const char *file, int line, struct trace_key *key,
 +                           const char *format, va_list ap)
 +{
 +      struct strbuf buf = STRBUF_INIT;
 +
 +      if (!prepare_trace_line(file, line, key, &buf))
 +              return;
 +
 +      strbuf_vaddf(&buf, format, ap);
 +      print_trace_line(key, &buf);
 +}
 +
 +static void trace_argv_vprintf_fl(const char *file, int line,
 +                                const char **argv, const char *format,
 +                                va_list ap)
 +{
 +      struct strbuf buf = STRBUF_INIT;
 +
 +      if (!prepare_trace_line(file, line, NULL, &buf))
 +              return;
 +
 +      strbuf_vaddf(&buf, format, ap);
 +
 +      sq_quote_argv(&buf, argv, 0);
 +      print_trace_line(NULL, &buf);
 +}
 +
 +void trace_strbuf_fl(const char *file, int line, struct trace_key *key,
 +                   const struct strbuf *data)
 +{
 +      struct strbuf buf = STRBUF_INIT;
 +
 +      if (!prepare_trace_line(file, line, key, &buf))
 +              return;
 +
 +      strbuf_addbuf(&buf, data);
 +      print_trace_line(key, &buf);
 +}
 +
 +static struct trace_key trace_perf_key = TRACE_KEY_INIT(PERFORMANCE);
 +
 +static void trace_performance_vprintf_fl(const char *file, int line,
 +                                       uint64_t nanos, const char *format,
 +                                       va_list ap)
 +{
 +      struct strbuf buf = STRBUF_INIT;
 +
 +      if (!prepare_trace_line(file, line, &trace_perf_key, &buf))
 +              return;
 +
 +      strbuf_addf(&buf, "performance: %.9f s", (double) nanos / 1000000000);
 +
 +      if (format && *format) {
 +              strbuf_addstr(&buf, ": ");
 +              strbuf_vaddf(&buf, format, ap);
 +      }
 +
 +      print_trace_line(&trace_perf_key, &buf);
 +}
 +
 +#ifndef HAVE_VARIADIC_MACROS
 +
 +void trace_printf(const char *format, ...)
  {
        va_list ap;
 -      va_start(ap, fmt);
 -      trace_vprintf(key, fmt, ap);
 +      va_start(ap, format);
 +      trace_vprintf_fl(NULL, 0, NULL, format, ap);
        va_end(ap);
  }
  
 -void trace_printf(const char *fmt, ...)
 +void trace_printf_key(struct trace_key *key, const char *format, ...)
  {
        va_list ap;
 -      va_start(ap, fmt);
 -      trace_vprintf("GIT_TRACE", fmt, ap);
 +      va_start(ap, format);
 +      trace_vprintf_fl(NULL, 0, key, format, ap);
        va_end(ap);
  }
  
 -void trace_strbuf(const char *key, const struct strbuf *buf)
 +void trace_argv_printf(const char **argv, const char *format, ...)
  {
 -      int fd, need_close = 0;
 -
 -      fd = get_trace_fd(key, &need_close);
 -      if (!fd)
 -              return;
 +      va_list ap;
 +      va_start(ap, format);
 +      trace_argv_vprintf_fl(NULL, 0, argv, format, ap);
 +      va_end(ap);
 +}
  
 -      write_or_whine_pipe(fd, buf->buf, buf->len, err_msg);
 +void trace_strbuf(const char *key, const struct strbuf *data)
 +{
 +      trace_strbuf_fl(NULL, 0, key, data);
 +}
  
 -      if (need_close)
 -              close(fd);
 +void trace_performance(uint64_t nanos, const char *format, ...)
 +{
 +      va_list ap;
 +      va_start(ap, format);
 +      trace_performance_vprintf_fl(NULL, 0, nanos, format, ap);
 +      va_end(ap);
  }
  
 -void trace_argv_printf(const char **argv, const char *fmt, ...)
 +void trace_performance_since(uint64_t start, const char *format, ...)
  {
 -      struct strbuf buf = STRBUF_INIT;
        va_list ap;
 -      int fd, need_close = 0;
 +      va_start(ap, format);
 +      trace_performance_vprintf_fl(NULL, 0, getnanotime() - start,
 +                                   format, ap);
 +      va_end(ap);
 +}
  
 -      fd = get_trace_fd("GIT_TRACE", &need_close);
 -      if (!fd)
 -              return;
 +#else
  
 -      set_try_to_free_routine(NULL);  /* is never reset */
 -      va_start(ap, fmt);
 -      strbuf_vaddf(&buf, fmt, ap);
 +void trace_printf_key_fl(const char *file, int line, struct trace_key *key,
 +                       const char *format, ...)
 +{
 +      va_list ap;
 +      va_start(ap, format);
 +      trace_vprintf_fl(file, line, key, format, ap);
        va_end(ap);
 +}
  
 -      sq_quote_argv(&buf, argv, 0);
 -      strbuf_addch(&buf, '\n');
 -      write_or_whine_pipe(fd, buf.buf, buf.len, err_msg);
 -      strbuf_release(&buf);
 +void trace_argv_printf_fl(const char *file, int line, const char **argv,
 +                        const char *format, ...)
 +{
 +      va_list ap;
 +      va_start(ap, format);
 +      trace_argv_vprintf_fl(file, line, argv, format, ap);
 +      va_end(ap);
 +}
  
 -      if (need_close)
 -              close(fd);
 +void trace_performance_fl(const char *file, int line, uint64_t nanos,
 +                            const char *format, ...)
 +{
 +      va_list ap;
 +      va_start(ap, format);
 +      trace_performance_vprintf_fl(file, line, nanos, format, ap);
 +      va_end(ap);
  }
  
 +#endif /* HAVE_VARIADIC_MACROS */
 +
 +
  static const char *quote_crnl(const char *path)
  {
        static char new_path[PATH_MAX];
  /* FIXME: move prefix to startup_info struct and get rid of this arg */
  void trace_repo_setup(const char *prefix)
  {
 -      static const char *key = "GIT_TRACE_SETUP";
 +      static struct trace_key key = TRACE_KEY_INIT(SETUP);
        const char *git_work_tree;
-       char cwd[PATH_MAX];
+       char *cwd;
  
 -      if (!trace_want(key))
 +      if (!trace_want(&key))
                return;
  
-       if (!getcwd(cwd, PATH_MAX))
-               die("Unable to get current working directory");
+       cwd = xgetcwd();
  
        if (!(git_work_tree = get_git_work_tree()))
                git_work_tree = "(null)";
        if (!prefix)
                prefix = "(null)";
  
 -      trace_printf_key(key, "setup: git_dir: %s\n", quote_crnl(get_git_dir()));
 -      trace_printf_key(key, "setup: worktree: %s\n", quote_crnl(git_work_tree));
 -      trace_printf_key(key, "setup: cwd: %s\n", quote_crnl(cwd));
 -      trace_printf_key(key, "setup: prefix: %s\n", quote_crnl(prefix));
 +      trace_printf_key(&key, "setup: git_dir: %s\n", quote_crnl(get_git_dir()));
 +      trace_printf_key(&key, "setup: worktree: %s\n", quote_crnl(git_work_tree));
 +      trace_printf_key(&key, "setup: cwd: %s\n", quote_crnl(cwd));
 +      trace_printf_key(&key, "setup: prefix: %s\n", quote_crnl(prefix));
+       free(cwd);
  }
  
 -int trace_want(const char *key)
 +int trace_want(struct trace_key *key)
  {
 -      const char *trace = getenv(key);
 +      return !!get_trace_fd(key);
 +}
  
 -      if (!trace || !strcmp(trace, "") ||
 -          !strcmp(trace, "0") || !strcasecmp(trace, "false"))
 +#ifdef HAVE_CLOCK_GETTIME
 +
 +static inline uint64_t highres_nanos(void)
 +{
 +      struct timespec ts;
 +      if (clock_gettime(CLOCK_MONOTONIC, &ts))
                return 0;
 -      return 1;
 +      return (uint64_t) ts.tv_sec * 1000000000 + ts.tv_nsec;
 +}
 +
 +#elif defined (GIT_WINDOWS_NATIVE)
 +
 +static inline uint64_t highres_nanos(void)
 +{
 +      static uint64_t high_ns, scaled_low_ns;
 +      static int scale;
 +      LARGE_INTEGER cnt;
 +
 +      if (!scale) {
 +              if (!QueryPerformanceFrequency(&cnt))
 +                      return 0;
 +
 +              /* high_ns = number of ns per cnt.HighPart */
 +              high_ns = (1000000000LL << 32) / (uint64_t) cnt.QuadPart;
 +
 +              /*
 +               * Number of ns per cnt.LowPart is 10^9 / frequency (or
 +               * high_ns >> 32). For maximum precision, we scale this factor
 +               * so that it just fits within 32 bit (i.e. won't overflow if
 +               * multiplied with cnt.LowPart).
 +               */
 +              scaled_low_ns = high_ns;
 +              scale = 32;
 +              while (scaled_low_ns >= 0x100000000LL) {
 +                      scaled_low_ns >>= 1;
 +                      scale--;
 +              }
 +      }
 +
 +      /* if QPF worked on initialization, we expect QPC to work as well */
 +      QueryPerformanceCounter(&cnt);
 +
 +      return (high_ns * cnt.HighPart) +
 +             ((scaled_low_ns * cnt.LowPart) >> scale);
 +}
 +
 +#else
 +# define highres_nanos() 0
 +#endif
 +
 +static inline uint64_t gettimeofday_nanos(void)
 +{
 +      struct timeval tv;
 +      gettimeofday(&tv, NULL);
 +      return (uint64_t) tv.tv_sec * 1000000000 + tv.tv_usec * 1000;
 +}
 +
 +/*
 + * Returns nanoseconds since the epoch (01/01/1970), for performance tracing
 + * (i.e. favoring high precision over wall clock time accuracy).
 + */
 +inline uint64_t getnanotime(void)
 +{
 +      static uint64_t offset;
 +      if (offset > 1) {
 +              /* initialization succeeded, return offset + high res time */
 +              return offset + highres_nanos();
 +      } else if (offset == 1) {
 +              /* initialization failed, fall back to gettimeofday */
 +              return gettimeofday_nanos();
 +      } else {
 +              /* initialize offset if high resolution timer works */
 +              uint64_t now = gettimeofday_nanos();
 +              uint64_t highres = highres_nanos();
 +              if (highres)
 +                      offset = now - highres;
 +              else
 +                      offset = 1;
 +              return now;
 +      }
 +}
 +
 +static uint64_t command_start_time;
 +static struct strbuf command_line = STRBUF_INIT;
 +
 +static void print_command_performance_atexit(void)
 +{
 +      trace_performance_since(command_start_time, "git command:%s",
 +                              command_line.buf);
 +}
 +
 +void trace_command_performance(const char **argv)
 +{
 +      if (!trace_want(&trace_perf_key))
 +              return;
 +
 +      if (!command_start_time)
 +              atexit(print_command_performance_atexit);
 +
 +      strbuf_reset(&command_line);
 +      sq_quote_argv(&command_line, argv, 0);
 +      command_start_time = getnanotime();
  }
diff --combined unix-socket.c
index 91bd6b89d4946bbd80a0b9e9e7d6af21d6a2f61a,943a94734a25a2b3e0c7e33b89e16b41a11ab520..19ed48be9902f321285bfbec3feaf8357fd5e9b9
@@@ -18,12 -18,12 +18,12 @@@ static int chdir_len(const char *orig, 
  }
  
  struct unix_sockaddr_context {
-       char orig_dir[PATH_MAX];
+       char *orig_dir;
  };
  
  static void unix_sockaddr_cleanup(struct unix_sockaddr_context *ctx)
  {
-       if (!ctx->orig_dir[0])
+       if (!ctx->orig_dir)
                return;
        /*
         * If we fail, we can't just return an error, since we have
@@@ -32,6 -32,7 +32,7 @@@
         */
        if (chdir(ctx->orig_dir) < 0)
                die("unable to restore original working directory");
+       free(ctx->orig_dir);
  }
  
  static int unix_sockaddr_init(struct sockaddr_un *sa, const char *path,
  {
        int size = strlen(path) + 1;
  
-       ctx->orig_dir[0] = '\0';
+       ctx->orig_dir = NULL;
        if (size > sizeof(sa->sun_path)) {
                const char *slash = find_last_dir_sep(path);
                const char *dir;
+               struct strbuf cwd = STRBUF_INIT;
  
                if (!slash) {
                        errno = ENAMETOOLONG;
                        errno = ENAMETOOLONG;
                        return -1;
                }
-               if (!getcwd(ctx->orig_dir, sizeof(ctx->orig_dir))) {
-                       errno = ENAMETOOLONG;
+               if (strbuf_getcwd(&cwd))
                        return -1;
-               }
+               ctx->orig_dir = strbuf_detach(&cwd, NULL);
                if (chdir_len(dir, slash - dir) < 0)
                        return -1;
        }
@@@ -99,12 -99,11 +99,12 @@@ int unix_stream_listen(const char *path
        struct sockaddr_un sa;
        struct unix_sockaddr_context ctx;
  
 +      unlink(path);
 +
        if (unix_sockaddr_init(&sa, path, &ctx) < 0)
                return -1;
        fd = unix_stream_socket();
  
 -      unlink(path);
        if (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0)
                goto fail;