Merge branch 'jk/diagnose-config-mmap-failure' into maint
authorJunio C Hamano <gitster@pobox.com>
Thu, 25 Jun 2015 18:02:10 +0000 (11:02 -0700)
committerJunio C Hamano <gitster@pobox.com>
Thu, 25 Jun 2015 18:02:11 +0000 (11:02 -0700)
The configuration reader/writer uses mmap(2) interface to access
the files; when we find a directory, it barfed with "Out of memory?".

* jk/diagnose-config-mmap-failure:
xmmap(): drop "Out of memory?"
config.c: rewrite ENODEV into EISDIR when mmap fails
config.c: avoid xmmap error messages
config.c: fix mmap leak when writing config
read-cache.c: drop PROT_WRITE from mmap of index

1  2 
config.c
git-compat-util.h
read-cache.c
sha1_file.c
diff --combined config.c
index 286907b97e5a378e2be9677ee5b6a4fe7f6169af,6551de30dbc5345f8ae6c359a32b93e1322161e7..27a73c85005fbf709d47adb1458bea688993eba2
+++ b/config.c
@@@ -12,7 -12,6 +12,7 @@@
  #include "quote.h"
  #include "hashmap.h"
  #include "string-list.h"
 +#include "utf8.h"
  
  struct config_source {
        struct config_source *prev;
@@@ -74,12 -73,8 +74,12 @@@ static int config_buf_fgetc(struct conf
  
  static int config_buf_ungetc(int c, struct config_source *conf)
  {
 -      if (conf->u.buf.pos > 0)
 -              return conf->u.buf.buf[--conf->u.buf.pos];
 +      if (conf->u.buf.pos > 0) {
 +              conf->u.buf.pos--;
 +              if (conf->u.buf.buf[conf->u.buf.pos] != c)
 +                      die("BUG: config_buf can only ungetc the same character");
 +              return c;
 +      }
  
        return EOF;
  }
@@@ -240,8 -235,7 +240,8 @@@ static int get_next_char(void
                /* DOS like systems */
                c = cf->do_fgetc(cf);
                if (c != '\n') {
 -                      cf->do_ungetc(c, cf);
 +                      if (c != EOF)
 +                              cf->do_ungetc(c, cf);
                        c = '\r';
                }
        }
@@@ -418,7 -412,8 +418,7 @@@ static int git_parse_source(config_fn_
        struct strbuf *var = &cf->var;
  
        /* U+FEFF Byte Order Mark in UTF8 */
 -      static const unsigned char *utf8_bom = (unsigned char *) "\xef\xbb\xbf";
 -      const unsigned char *bomptr = utf8_bom;
 +      const char *bomptr = utf8_bom;
  
        for (;;) {
                int c = get_next_char();
                        /* We are at the file beginning; skip UTF8-encoded BOM
                         * if present. Sane editors won't put this in on their
                         * own, but e.g. Windows Notepad will do it happily. */
 -                      if ((unsigned char) c == *bomptr) {
 +                      if (c == (*bomptr & 0377)) {
                                bomptr++;
                                continue;
                        } else {
@@@ -1185,8 -1180,10 +1185,8 @@@ int git_config_system(void
  int git_config_early(config_fn_t fn, void *data, const char *repo_config)
  {
        int ret = 0, found = 0;
 -      char *xdg_config = NULL;
 -      char *user_config = NULL;
 -
 -      home_config_paths(&user_config, &xdg_config, "config");
 +      char *xdg_config = xdg_config_home("config");
 +      char *user_config = expand_user_path("~/.gitconfig");
  
        if (git_config_system() && !access_or_die(git_etc_gitconfig(), R_OK, 0)) {
                ret += git_config_from_file(fn, git_etc_gitconfig(),
@@@ -1343,7 -1340,7 +1343,7 @@@ static int configset_add_value(struct c
                string_list_init(&e->value_list, 1);
                hashmap_add(&cs->config_hash, e);
        }
 -      si = string_list_append_nodup(&e->value_list, value ? xstrdup(value) : NULL);
 +      si = string_list_append_nodup(&e->value_list, xstrdup_or_null(value));
  
        ALLOC_GROW(cs->list.items, cs->list.nr + 1, cs->list.alloc);
        l_item = &cs->list.items[cs->list.nr++];
@@@ -1937,6 -1934,8 +1937,8 @@@ int git_config_set_multivar_in_file(con
        int ret;
        struct lock_file *lock = NULL;
        char *filename_buf = NULL;
+       char *contents = NULL;
+       size_t contents_sz;
  
        /* parse-key returns negative; flip the sign to feed exit(3) */
        ret = 0 - git_config_parse_key(key, &store.key, &store.baselen);
                        goto write_err_out;
        } else {
                struct stat st;
-               char *contents;
-               size_t contents_sz, copy_begin, copy_end;
+               size_t copy_begin, copy_end;
                int i, new_line = 0;
  
                if (value_regex == NULL)
  
                fstat(in_fd, &st);
                contents_sz = xsize_t(st.st_size);
-               contents = xmmap(NULL, contents_sz, PROT_READ,
-                       MAP_PRIVATE, in_fd, 0);
+               contents = xmmap_gently(NULL, contents_sz, PROT_READ,
+                                       MAP_PRIVATE, in_fd, 0);
+               if (contents == MAP_FAILED) {
+                       if (errno == ENODEV && S_ISDIR(st.st_mode))
+                               errno = EISDIR;
+                       error("unable to mmap '%s': %s",
+                             config_filename, strerror(errno));
+                       ret = CONFIG_INVALID_FILE;
+                       contents = NULL;
+                       goto out_free;
+               }
                close(in_fd);
  
                if (chmod(lock->filename.buf, st.st_mode & 07777) < 0) {
                                          contents_sz - copy_begin) <
                            contents_sz - copy_begin)
                                goto write_err_out;
-               munmap(contents, contents_sz);
        }
  
        if (commit_lock_file(lock) < 0) {
@@@ -2133,6 -2138,8 +2141,8 @@@ out_free
        if (lock)
                rollback_lock_file(lock);
        free(filename_buf);
+       if (contents)
+               munmap(contents, contents_sz);
        return ret;
  
  write_err_out:
diff --combined git-compat-util.h
index b45c75fc7a6cfd8884e748ee128f72da4aa43e6e,dc1948aab0e28a48f2d6de47c631a55ca3da2333..3be44f146b3af57bc3195d0c526dc74daa903b95
@@@ -3,23 -3,6 +3,23 @@@
  
  #define _FILE_OFFSET_BITS 64
  
 +
 +/* Derived from Linux "Features Test Macro" header
 + * Convenience macros to test the versions of gcc (or
 + * a compatible compiler).
 + * Use them like this:
 + *  #if GIT_GNUC_PREREQ (2,8)
 + *   ... code requiring gcc 2.8 or later ...
 + *  #endif
 +*/
 +#if defined(__GNUC__) && defined(__GNUC_MINOR__)
 +# define GIT_GNUC_PREREQ(maj, min) \
 +      ((__GNUC__ << 16) + __GNUC_MINOR__ >= ((maj) << 16) + (min))
 +#else
 + #define GIT_GNUC_PREREQ(maj, min) 0
 +#endif
 +
 +
  #ifndef FLEX_ARRAY
  /*
   * See if our compiler is known to support flexible array members.
  #endif
  #endif
  
 -#define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0]))
 +
 +/*
 + * BUILD_ASSERT_OR_ZERO - assert a build-time dependency, as an expression.
 + * @cond: the compile-time condition which must be true.
 + *
 + * Your compile will fail if the condition isn't true, or can't be evaluated
 + * by the compiler.  This can be used in an expression: its value is "0".
 + *
 + * Example:
 + *    #define foo_to_char(foo)                                        \
 + *             ((char *)(foo)                                         \
 + *              + BUILD_ASSERT_OR_ZERO(offsetof(struct foo, string) == 0))
 + */
 +#define BUILD_ASSERT_OR_ZERO(cond) \
 +      (sizeof(char [1 - 2*!(cond)]) - 1)
 +
 +#if defined(__GNUC__) && (__GNUC__ >= 3)
 +# if GIT_GNUC_PREREQ(3, 1)
 + /* &arr[0] degrades to a pointer: a different type from an array */
 +# define BARF_UNLESS_AN_ARRAY(arr)                                            \
 +      BUILD_ASSERT_OR_ZERO(!__builtin_types_compatible_p(__typeof__(arr), \
 +                                                         __typeof__(&(arr)[0])))
 +# else
 +#  define BARF_UNLESS_AN_ARRAY(arr) 0
 +# endif
 +#endif
 +/*
 + * ARRAY_SIZE - get the number of elements in a visible array
 + *  <at> x: the array whose size you want.
 + *
 + * This does not work on pointers, or arrays declared as [], or
 + * function parameters.  With correct compiler support, such usage
 + * will cause a build error (see the build_assert_or_zero macro).
 + */
 +#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]) + BARF_UNLESS_AN_ARRAY(x))
 +
  #define bitsizeof(x)  (CHAR_BIT * sizeof(x))
  
  #define maximum_signed_value_of_type(a) \
  # endif
  #elif !defined(__APPLE__) && !defined(__FreeBSD__) && !defined(__USLC__) && \
        !defined(_M_UNIX) && !defined(__sgi) && !defined(__DragonFly__) && \
 -      !defined(__TANDEM) && !defined(__QNX__) && !defined(__MirBSD__)
 +      !defined(__TANDEM) && !defined(__QNX__) && !defined(__MirBSD__) && \
 +      !defined(__CYGWIN__)
  #define _XOPEN_SOURCE 600 /* glibc2 and AIX 5.3L need 500, OpenBSD needs 600 for S_ISLNK() */
  #define _XOPEN_SOURCE_EXTENDED 1 /* AIX 5.3L needs this */
  #endif
  #else
  #include <poll.h>
  #endif
 +#ifdef HAVE_BSD_SYSCTL
 +#include <sys/sysctl.h>
 +#endif
  
  #if defined(__MINGW32__)
  /* pull in Windows compatibility stuff */
  typedef long intptr_t;
  typedef unsigned long uintptr_t;
  #endif
 -#if defined(__CYGWIN__)
 -#undef _XOPEN_SOURCE
 -#include <grp.h>
 -#define _XOPEN_SOURCE 600
 -#else
  #undef _ALL_SOURCE /* AIX 5.3L defines a struct list with _ALL_SOURCE. */
  #include <grp.h>
  #define _ALL_SOURCE 1
  #endif
 -#endif
  
  /* used on Mac OS X */
  #ifdef PRECOMPOSE_UNICODE
@@@ -261,18 -211,8 +261,18 @@@ extern char *gitbasename(char *)
  #endif
  
  #ifndef NO_OPENSSL
 +#ifdef __APPLE__
 +#define __AVAILABILITY_MACROS_USES_AVAILABILITY 0
 +#include <AvailabilityMacros.h>
 +#undef DEPRECATED_ATTRIBUTE
 +#define DEPRECATED_ATTRIBUTE
 +#undef __AVAILABILITY_MACROS_USES_AVAILABILITY
 +#endif
  #include <openssl/ssl.h>
  #include <openssl/err.h>
 +#ifdef NO_HMAC_CTX_CLEANUP
 +#define HMAC_CTX_cleanup HMAC_cleanup
 +#endif
  #endif
  
  /* On most systems <netdb.h> would have given us this, but
@@@ -534,40 -474,6 +534,40 @@@ extern int git_munmap(void *start, size
  #define on_disk_bytes(st) ((st).st_blocks * 512)
  #endif
  
 +#ifdef NEEDS_MODE_TRANSLATION
 +#undef S_IFMT
 +#undef S_IFREG
 +#undef S_IFDIR
 +#undef S_IFLNK
 +#undef S_IFBLK
 +#undef S_IFCHR
 +#undef S_IFIFO
 +#undef S_IFSOCK
 +#define S_IFMT   0170000
 +#define S_IFREG  0100000
 +#define S_IFDIR  0040000
 +#define S_IFLNK  0120000
 +#define S_IFBLK  0060000
 +#define S_IFCHR  0020000
 +#define S_IFIFO  0010000
 +#define S_IFSOCK 0140000
 +#ifdef stat
 +#undef stat
 +#endif
 +#define stat(path, buf) git_stat(path, buf)
 +extern int git_stat(const char *, struct stat *);
 +#ifdef fstat
 +#undef fstat
 +#endif
 +#define fstat(fd, buf) git_fstat(fd, buf)
 +extern int git_fstat(int, struct stat *);
 +#ifdef lstat
 +#undef lstat
 +#endif
 +#define lstat(path, buf) git_lstat(path, buf)
 +extern int git_lstat(const char *, struct stat *);
 +#endif
 +
  #define DEFAULT_PACKED_GIT_LIMIT \
        ((1024L * 1024L) * (sizeof(void*) >= 8 ? 8192 : 256))
  
@@@ -717,6 -623,7 +717,7 @@@ extern char *xstrndup(const char *str, 
  extern void *xrealloc(void *ptr, size_t size);
  extern void *xcalloc(size_t nmemb, size_t size);
  extern void *xmmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);
+ extern void *xmmap_gently(void *start, size_t length, int prot, int flags, int fd, off_t offset);
  extern ssize_t xread(int fd, void *buf, size_t len);
  extern ssize_t xwrite(int fd, const void *buf, size_t len);
  extern ssize_t xpread(int fd, void *buf, size_t len, off_t offset);
@@@ -730,11 -637,6 +731,11 @@@ extern char *xgetcwd(void)
  
  #define REALLOC_ARRAY(x, alloc) (x) = xrealloc((x), (alloc) * sizeof(*(x)))
  
 +static inline char *xstrdup_or_null(const char *str)
 +{
 +      return str ? xstrdup(str) : NULL;
 +}
 +
  static inline size_t xsize_t(off_t len)
  {
        if (len > (size_t) len)
  }
  
  /* in ctype.c, for kwset users */
 -extern const char tolower_trans_tbl[256];
 +extern const unsigned char tolower_trans_tbl[256];
  
  /* Sane ctype - no locale, and works with signed chars */
  #undef isascii
@@@ -927,12 -829,4 +928,12 @@@ struct tm *git_gmtime_r(const time_t *
  #define gmtime_r git_gmtime_r
  #endif
  
 +#if !defined(USE_PARENS_AROUND_GETTEXT_N) && defined(__GNUC__)
 +#define USE_PARENS_AROUND_GETTEXT_N 1
 +#endif
 +
 +#ifndef SHELL_PATH
 +# define SHELL_PATH "/bin/sh"
 +#endif
 +
  #endif
diff --combined read-cache.c
index 36ff89f29e5f56a5b3dcfd803f12cd139295b8b8,cc67dd1d5d3b5d6005ec968f9fcde6276016ef62..bf322708f0ea7e855026950f6f1720ba056515ca
@@@ -681,18 -681,15 +681,18 @@@ int add_to_index(struct index_state *is
        alias = index_file_exists(istate, ce->name, ce_namelen(ce), ignore_case);
        if (alias && !ce_stage(alias) && !ie_match_stat(istate, alias, st, ce_option)) {
                /* Nothing changed, really */
 -              free(ce);
                if (!S_ISGITLINK(alias->ce_mode))
                        ce_mark_uptodate(alias);
                alias->ce_flags |= CE_ADDED;
 +
 +              free(ce);
                return 0;
        }
        if (!intent_only) {
 -              if (index_path(ce->sha1, path, st, HASH_WRITE_OBJECT))
 +              if (index_path(ce->sha1, path, st, HASH_WRITE_OBJECT)) {
 +                      free(ce);
                        return error("unable to index file %s", path);
 +              }
        } else
                set_object_name_for_intent_to_add_entry(ce);
  
                    ce->ce_mode == alias->ce_mode);
  
        if (pretend)
 -              ;
 -      else if (add_index_entry(istate, ce, add_option))
 -              return error("unable to add %s to index",path);
 +              free(ce);
 +      else if (add_index_entry(istate, ce, add_option)) {
 +              free(ce);
 +              return error("unable to add %s to index", path);
 +      }
        if (verbose && !was_same)
                printf("add '%s'\n", path);
        return 0;
@@@ -730,7 -725,7 +730,7 @@@ struct cache_entry *make_cache_entry(un
                unsigned int refresh_options)
  {
        int size, len;
 -      struct cache_entry *ce;
 +      struct cache_entry *ce, *ret;
  
        if (!verify_path(path)) {
                error("Invalid path '%s'", path);
        ce->ce_namelen = len;
        ce->ce_mode = create_ce_mode(mode);
  
 -      return refresh_cache_entry(ce, refresh_options);
 +      ret = refresh_cache_entry(ce, refresh_options);
 +      if (ret != ce)
 +              free(ce);
 +      return ret;
  }
  
  int ce_same_name(const struct cache_entry *a, const struct cache_entry *b)
@@@ -1488,25 -1480,18 +1488,25 @@@ static struct cache_entry *create_from_
        return ce;
  }
  
 -static void check_ce_order(struct cache_entry *ce, struct cache_entry *next_ce)
 +static void check_ce_order(struct index_state *istate)
  {
 -      int name_compare = strcmp(ce->name, next_ce->name);
 -      if (0 < name_compare)
 -              die("unordered stage entries in index");
 -      if (!name_compare) {
 -              if (!ce_stage(ce))
 -                      die("multiple stage entries for merged file '%s'",
 -                              ce->name);
 -              if (ce_stage(ce) > ce_stage(next_ce))
 -                      die("unordered stage entries for '%s'",
 -                              ce->name);
 +      unsigned int i;
 +
 +      for (i = 1; i < istate->cache_nr; i++) {
 +              struct cache_entry *ce = istate->cache[i - 1];
 +              struct cache_entry *next_ce = istate->cache[i];
 +              int name_compare = strcmp(ce->name, next_ce->name);
 +
 +              if (0 < name_compare)
 +                      die("unordered stage entries in index");
 +              if (!name_compare) {
 +                      if (!ce_stage(ce))
 +                              die("multiple stage entries for merged file '%s'",
 +                                  ce->name);
 +                      if (ce_stage(ce) > ce_stage(next_ce))
 +                              die("unordered stage entries for '%s'",
 +                                  ce->name);
 +              }
        }
  }
  
@@@ -1540,7 -1525,7 +1540,7 @@@ int do_read_index(struct index_state *i
        if (mmap_size < sizeof(struct cache_header) + 20)
                die("index file smaller than expected");
  
-       mmap = xmmap(NULL, mmap_size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
+       mmap = xmmap(NULL, mmap_size, PROT_READ, MAP_PRIVATE, fd, 0);
        if (mmap == MAP_FAILED)
                die_errno("unable to map index file");
        close(fd);
                ce = create_from_disk(disk_ce, &consumed, previous_name);
                set_index_entry(istate, i, ce);
  
 -              if (i > 0)
 -                      check_ce_order(istate->cache[i - 1], ce);
 -
                src_offset += consumed;
        }
        strbuf_release(&previous_name_buf);
@@@ -1614,10 -1602,11 +1614,10 @@@ int read_index_from(struct index_state 
  
        ret = do_read_index(istate, path, 0);
        split_index = istate->split_index;
 -      if (!split_index)
 -              return ret;
 -
 -      if (is_null_sha1(split_index->base_sha1))
 +      if (!split_index || is_null_sha1(split_index->base_sha1)) {
 +              check_ce_order(istate);
                return ret;
 +      }
  
        if (split_index->base)
                discard_index(split_index->base);
                                     sha1_to_hex(split_index->base_sha1)),
                    sha1_to_hex(split_index->base->sha1));
        merge_base_index(istate);
 +      check_ce_order(istate);
        return ret;
  }
  
diff --combined sha1_file.c
index ac0ca1a0a2e4770ef1b2e40b15721268dc138c91,1457069a1a1104ebdee910c0df09e48a132608e8..56c69cebc80f57f26f9898fbbc6587176f915339
@@@ -707,8 -707,8 +707,8 @@@ static void mmap_limit_check(size_t len
                    (uintmax_t)length, (uintmax_t)limit);
  }
  
- void *xmmap(void *start, size_t length,
-       int prot, int flags, int fd, off_t offset)
+ void *xmmap_gently(void *start, size_t length,
+                 int prot, int flags, int fd, off_t offset)
  {
        void *ret;
  
                        return NULL;
                release_pack_memory(length);
                ret = mmap(start, length, prot, flags, fd, offset);
-               if (ret == MAP_FAILED)
-                       die_errno("Out of memory? mmap failed");
        }
        return ret;
  }
  
+ void *xmmap(void *start, size_t length,
+       int prot, int flags, int fd, off_t offset)
+ {
+       void *ret = xmmap_gently(start, length, prot, flags, fd, offset);
+       if (ret == MAP_FAILED)
+               die_errno("mmap failed");
+       return ret;
+ }
  void close_pack_windows(struct packed_git *p)
  {
        while (p->windows) {
@@@ -1198,7 -1205,7 +1205,7 @@@ static void report_pack_garbage(struct 
        if (!report_garbage)
                return;
  
 -      sort_string_list(list);
 +      string_list_sort(list);
  
        for (i = 0; i < list->nr; i++) {
                const char *path = list->items[i].string;
@@@ -2473,8 -2480,10 +2480,8 @@@ static int fill_pack_entry(const unsign
         * answer, as it may have been deleted since the index was
         * loaded!
         */
 -      if (!is_pack_valid(p)) {
 -              warning("packfile %s cannot be accessed", p->pack_name);
 +      if (!is_pack_valid(p))
                return 0;
 -      }
        e->offset = offset;
        e->p = p;
        hashcpy(e->sha1, sha1);
@@@ -2941,6 -2950,7 +2948,6 @@@ static int write_loose_object(const uns
        }
  
        /* Set it up */
 -      memset(&stream, 0, sizeof(stream));
        git_deflate_init(&stream, zlib_compression_level);
        stream.next_out = compressed;
        stream.avail_out = sizeof(compressed);
@@@ -2997,18 -3007,12 +3004,18 @@@ static int freshen_loose_object(const u
  static int freshen_packed_object(const unsigned char *sha1)
  {
        struct pack_entry e;
 -      return find_pack_entry(sha1, &e) && freshen_file(e.p->pack_name);
 +      if (!find_pack_entry(sha1, &e))
 +              return 0;
 +      if (e.p->freshened)
 +              return 1;
 +      if (!freshen_file(e.p->pack_name))
 +              return 0;
 +      e.p->freshened = 1;
 +      return 1;
  }
  
 -int write_sha1_file(const void *buf, unsigned long len, const char *type, unsigned char *returnsha1)
 +int write_sha1_file(const void *buf, unsigned long len, const char *type, unsigned char *sha1)
  {
 -      unsigned char sha1[20];
        char hdr[32];
        int hdrlen;
  
         * it out into .git/objects/??/?{38} file.
         */
        write_sha1_file_prepare(buf, len, type, sha1, hdr, &hdrlen);
 -      if (returnsha1)
 -              hashcpy(returnsha1, sha1);
 -      if (freshen_loose_object(sha1) || freshen_packed_object(sha1))
 +      if (freshen_packed_object(sha1) || freshen_loose_object(sha1))
                return 0;
        return write_loose_object(sha1, hdr, hdrlen, buf, len, 0);
  }
  
 +int hash_sha1_file_literally(const void *buf, unsigned long len, const char *type,
 +                           unsigned char *sha1, unsigned flags)
 +{
 +      char *header;
 +      int hdrlen, status = 0;
 +
 +      /* type string, SP, %lu of the length plus NUL must fit this */
 +      header = xmalloc(strlen(type) + 32);
 +      write_sha1_file_prepare(buf, len, type, sha1, header, &hdrlen);
 +
 +      if (!(flags & HASH_WRITE_OBJECT))
 +              goto cleanup;
 +      if (freshen_packed_object(sha1) || freshen_loose_object(sha1))
 +              goto cleanup;
 +      status = write_loose_object(sha1, header, hdrlen, buf, len, 0);
 +
 +cleanup:
 +      free(header);
 +      return status;
 +}
 +
  int force_object_loose(const unsigned char *sha1, time_t mtime)
  {
        void *buf;
@@@ -3202,7 -3187,7 +3209,7 @@@ static int index_core(unsigned char *sh
        int ret;
  
        if (!size) {
 -              ret = index_mem(sha1, NULL, size, type, path, flags);
 +              ret = index_mem(sha1, "", size, type, path, flags);
        } else if (size <= SMALL_FILE_SIZE) {
                char *buf = xmalloc(size);
                if (size == read_in_full(fd, buf, size))
@@@ -3381,42 -3366,31 +3388,42 @@@ static int for_each_file_in_obj_subdir(
        return r;
  }
  
 -int for_each_loose_file_in_objdir(const char *path,
 +int for_each_loose_file_in_objdir_buf(struct strbuf *path,
                            each_loose_object_fn obj_cb,
                            each_loose_cruft_fn cruft_cb,
                            each_loose_subdir_fn subdir_cb,
                            void *data)
  {
 -      struct strbuf buf = STRBUF_INIT;
 -      size_t baselen;
 +      size_t baselen = path->len;
        int r = 0;
        int i;
  
 -      strbuf_addstr(&buf, path);
 -      strbuf_addch(&buf, '/');
 -      baselen = buf.len;
 -
        for (i = 0; i < 256; i++) {
 -              strbuf_addf(&buf, "%02x", i);
 -              r = for_each_file_in_obj_subdir(i, &buf, obj_cb, cruft_cb,
 +              strbuf_addf(path, "/%02x", i);
 +              r = for_each_file_in_obj_subdir(i, path, obj_cb, cruft_cb,
                                                subdir_cb, data);
 -              strbuf_setlen(&buf, baselen);
 +              strbuf_setlen(path, baselen);
                if (r)
                        break;
        }
  
 +      return r;
 +}
 +
 +int for_each_loose_file_in_objdir(const char *path,
 +                                each_loose_object_fn obj_cb,
 +                                each_loose_cruft_fn cruft_cb,
 +                                each_loose_subdir_fn subdir_cb,
 +                                void *data)
 +{
 +      struct strbuf buf = STRBUF_INIT;
 +      int r;
 +
 +      strbuf_addstr(&buf, path);
 +      r = for_each_loose_file_in_objdir_buf(&buf, obj_cb, cruft_cb,
 +                                            subdir_cb, data);
        strbuf_release(&buf);
 +
        return r;
  }
  
@@@ -3429,19 -3403,12 +3436,19 @@@ static int loose_from_alt_odb(struct al
                              void *vdata)
  {
        struct loose_alt_odb_data *data = vdata;
 -      return for_each_loose_file_in_objdir(alt->base,
 -                                           data->cb, NULL, NULL,
 -                                           data->data);
 +      struct strbuf buf = STRBUF_INIT;
 +      int r;
 +
 +      /* copy base not including trailing '/' */
 +      strbuf_add(&buf, alt->base, alt->name - alt->base - 1);
 +      r = for_each_loose_file_in_objdir_buf(&buf,
 +                                            data->cb, NULL, NULL,
 +                                            data->data);
 +      strbuf_release(&buf);
 +      return r;
  }
  
 -int for_each_loose_object(each_loose_object_fn cb, void *data)
 +int for_each_loose_object(each_loose_object_fn cb, void *data, unsigned flags)
  {
        struct loose_alt_odb_data alt;
        int r;
        if (r)
                return r;
  
 +      if (flags & FOR_EACH_OBJECT_LOCAL_ONLY)
 +              return 0;
 +
        alt.cb = cb;
        alt.data = data;
        return foreach_alt_odb(loose_from_alt_odb, &alt);
@@@ -3478,15 -3442,13 +3485,15 @@@ static int for_each_object_in_pack(stru
        return r;
  }
  
 -int for_each_packed_object(each_packed_object_fn cb, void *data)
 +int for_each_packed_object(each_packed_object_fn cb, void *data, unsigned flags)
  {
        struct packed_git *p;
        int r = 0;
  
        prepare_packed_git();
        for (p = packed_git; p; p = p->next) {
 +              if ((flags & FOR_EACH_OBJECT_LOCAL_ONLY) && !p->pack_local)
 +                      continue;
                r = for_each_object_in_pack(p, cb, data);
                if (r)
                        break;