Merge branch 'nd/sha1-file-delta-stack-leakage-fix'
authorJunio C Hamano <gitster@pobox.com>
Tue, 18 Mar 2014 20:49:22 +0000 (13:49 -0700)
committerJunio C Hamano <gitster@pobox.com>
Tue, 18 Mar 2014 20:49:23 +0000 (13:49 -0700)
Fix a small leak in the delta stack used when resolving a long
delta chain at runtime.

* nd/sha1-file-delta-stack-leakage-fix:
sha1_file: fix delta_stack memory leak in unpack_entry

1  2 
sha1_file.c
diff --combined sha1_file.c
index b37c6f67e4d97b95b4fb070aa03992eb92506421,ca31de2df91ff34c7f298940d1fed23ce1ea1557..18b2378d8a63e1dbb621854c311652fe0abef76f
@@@ -21,7 -21,6 +21,7 @@@
  #include "sha1-lookup.h"
  #include "bulk-checkin.h"
  #include "streaming.h"
 +#include "dir.h"
  
  #ifndef O_NOATIME
  #if defined(__linux__) && (defined(__i386__) || defined(__PPC__))
@@@ -36,9 -35,6 +36,9 @@@ 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
@@@ -60,12 -56,6 +60,12 @@@ static struct cached_object empty_tree 
        0
  };
  
 +/*
 + * A pointer to the last packed_git in which an object was found.
 + * When an object is sought, we look in this packfile first, because
 + * objects that are looked up at similar times are often in the same
 + * packfile as one another.
 + */
  static struct packed_git *last_found_pack;
  
  static struct cached_object *find_cached_object(const unsigned char *sha1)
@@@ -111,63 -101,45 +111,63 @@@ int mkdir_in_gitdir(const char *path
        return adjust_shared_perm(path);
  }
  
 -int safe_create_leading_directories(char *path)
 +enum scld_error safe_create_leading_directories(char *path)
  {
 -      char *pos = path + offset_1st_component(path);
 -      struct stat st;
 +      char *next_component = path + offset_1st_component(path);
 +      enum scld_error ret = SCLD_OK;
 +
 +      while (ret == SCLD_OK && next_component) {
 +              struct stat st;
 +              char *slash = next_component, slash_character;
 +
 +              while (*slash && !is_dir_sep(*slash))
 +                      slash++;
  
 -      while (pos) {
 -              pos = strchr(pos, '/');
 -              if (!pos)
 +              if (!*slash)
                        break;
 -              while (*++pos == '/')
 -                      ;
 -              if (!*pos)
 +
 +              next_component = slash + 1;
 +              while (is_dir_sep(*next_component))
 +                      next_component++;
 +              if (!*next_component)
                        break;
 -              *--pos = '\0';
 +
 +              slash_character = *slash;
 +              *slash = '\0';
                if (!stat(path, &st)) {
                        /* path exists */
 -                      if (!S_ISDIR(st.st_mode)) {
 -                              *pos = '/';
 -                              return -3;
 -                      }
 +                      if (!S_ISDIR(st.st_mode))
 +                              ret = SCLD_EXISTS;
 +              } else if (mkdir(path, 0777)) {
 +                      if (errno == EEXIST &&
 +                          !stat(path, &st) && S_ISDIR(st.st_mode))
 +                              ; /* somebody created it since we checked */
 +                      else if (errno == ENOENT)
 +                              /*
 +                               * Either mkdir() failed because
 +                               * somebody just pruned the containing
 +                               * directory, or stat() failed because
 +                               * the file that was in our way was
 +                               * just removed.  Either way, inform
 +                               * the caller that it might be worth
 +                               * trying again:
 +                               */
 +                              ret = SCLD_VANISHED;
 +                      else
 +                              ret = SCLD_FAILED;
 +              } else if (adjust_shared_perm(path)) {
 +                      ret = SCLD_PERMS;
                }
 -              else if (mkdir(path, 0777)) {
 -                      *pos = '/';
 -                      return -1;
 -              }
 -              else if (adjust_shared_perm(path)) {
 -                      *pos = '/';
 -                      return -2;
 -              }
 -              *pos++ = '/';
 +              *slash = slash_character;
        }
 -      return 0;
 +      return ret;
  }
  
 -int safe_create_leading_directories_const(const char *path)
 +enum scld_error safe_create_leading_directories_const(const char *path)
  {
        /* path points to cache entries, so xstrdup before messing with it */
        char *buf = xstrdup(path);
 -      int result = safe_create_leading_directories(buf);
 +      enum scld_error result = safe_create_leading_directories(buf);
        free(buf);
        return result;
  }
@@@ -184,7 -156,17 +184,7 @@@ static void fill_sha1_path(char *pathbu
        }
  }
  
 -/*
 - * NOTE! This returns a statically allocated buffer, so you have to be
 - * careful about using it. Do an "xstrdup()" if you need to save the
 - * filename.
 - *
 - * Also note that this returns the location for creating.  Reading
 - * SHA1 file can happen from any alternate directory listed in the
 - * DB_ENVIRONMENT environment variable if it is not found in
 - * the primary object database.
 - */
 -char *sha1_file_name(const unsigned char *sha1)
 +const char *sha1_file_name(const unsigned char *sha1)
  {
        static char buf[PATH_MAX];
        const char *objdir;
        return buf;
  }
  
 +/*
 + * Return the name of the pack or index file with the specified sha1
 + * in its filename.  *base and *name are scratch space that must be
 + * provided by the caller.  which should be "pack" or "idx".
 + */
  static char *sha1_get_pack_name(const unsigned char *sha1,
                                char **name, char **base, const char *which)
  {
@@@ -253,6 -230,8 +253,6 @@@ char *sha1_pack_index_name(const unsign
  struct alternate_object_database *alt_odb_list;
  static struct alternate_object_database **alt_odb_tail;
  
 -static int git_open_noatime(const char *name);
 -
  /*
   * Prepare alternate object database registry.
   *
@@@ -437,7 -416,8 +437,7 @@@ void prepare_alt_odb(void
  
  static int has_loose_object_local(const unsigned char *sha1)
  {
 -      char *name = sha1_file_name(sha1);
 -      return !access(name, F_OK);
 +      return !access(sha1_file_name(sha1), F_OK);
  }
  
  int has_loose_object_nonlocal(const unsigned char *sha1)
@@@ -489,12 -469,7 +489,12 @@@ void pack_report(void
                sz_fmt(pack_mapped), sz_fmt(peak_pack_mapped));
  }
  
 -static int check_packed_git_idx(const char *path,  struct packed_git *p)
 +/*
 + * Open and mmap the index file at path, perform a couple of
 + * consistency checks, then record its information to p.  Return 0 on
 + * success.
 + */
 +static int check_packed_git_idx(const char *path, struct packed_git *p)
  {
        void *idx_map;
        struct pack_idx_header *hdr;
@@@ -630,7 -605,7 +630,7 @@@ static void scan_windows(struct packed_
        }
  }
  
 -static int unuse_one_window(struct packed_git *current, int keep_fd)
 +static int unuse_one_window(struct packed_git *current)
  {
        struct packed_git *p, *lru_p = NULL;
        struct pack_window *lru_w = NULL, *lru_l = NULL;
                pack_mapped -= lru_w->len;
                if (lru_l)
                        lru_l->next = lru_w->next;
 -              else {
 +              else
                        lru_p->windows = lru_w->next;
 -                      if (!lru_p->windows && lru_p->pack_fd != -1
 -                              && lru_p->pack_fd != keep_fd) {
 -                              close(lru_p->pack_fd);
 -                              pack_open_fds--;
 -                              lru_p->pack_fd = -1;
 -                      }
 -              }
                free(lru_w);
                pack_open_windows--;
                return 1;
        return 0;
  }
  
 -void release_pack_memory(size_t need, int fd)
 +void release_pack_memory(size_t need)
  {
        size_t cur = pack_mapped;
 -      while (need >= (cur - pack_mapped) && unuse_one_window(NULL, fd))
 +      while (need >= (cur - pack_mapped) && unuse_one_window(NULL))
                ; /* nothing */
  }
  
@@@ -667,7 -649,7 +667,7 @@@ void *xmmap(void *start, size_t length
        if (ret == MAP_FAILED) {
                if (!length)
                        return NULL;
 -              release_pack_memory(length, fd);
 +              release_pack_memory(length);
                ret = mmap(start, length, prot, flags, fd, offset);
                if (ret == MAP_FAILED)
                        die_errno("Out of memory? mmap failed");
@@@ -691,83 -673,6 +691,83 @@@ void close_pack_windows(struct packed_g
        }
  }
  
 +/*
 + * The LRU pack is the one with the oldest MRU window, preferring packs
 + * with no used windows, or the oldest mtime if it has no windows allocated.
 + */
 +static void find_lru_pack(struct packed_git *p, struct packed_git **lru_p, struct pack_window **mru_w, int *accept_windows_inuse)
 +{
 +      struct pack_window *w, *this_mru_w;
 +      int has_windows_inuse = 0;
 +
 +      /*
 +       * Reject this pack if it has windows and the previously selected
 +       * one does not.  If this pack does not have windows, reject
 +       * it if the pack file is newer than the previously selected one.
 +       */
 +      if (*lru_p && !*mru_w && (p->windows || p->mtime > (*lru_p)->mtime))
 +              return;
 +
 +      for (w = this_mru_w = p->windows; w; w = w->next) {
 +              /*
 +               * Reject this pack if any of its windows are in use,
 +               * but the previously selected pack did not have any
 +               * inuse windows.  Otherwise, record that this pack
 +               * has windows in use.
 +               */
 +              if (w->inuse_cnt) {
 +                      if (*accept_windows_inuse)
 +                              has_windows_inuse = 1;
 +                      else
 +                              return;
 +              }
 +
 +              if (w->last_used > this_mru_w->last_used)
 +                      this_mru_w = w;
 +
 +              /*
 +               * Reject this pack if it has windows that have been
 +               * used more recently than the previously selected pack.
 +               * If the previously selected pack had windows inuse and
 +               * we have not encountered a window in this pack that is
 +               * inuse, skip this check since we prefer a pack with no
 +               * inuse windows to one that has inuse windows.
 +               */
 +              if (*mru_w && *accept_windows_inuse == has_windows_inuse &&
 +                  this_mru_w->last_used > (*mru_w)->last_used)
 +                      return;
 +      }
 +
 +      /*
 +       * Select this pack.
 +       */
 +      *mru_w = this_mru_w;
 +      *lru_p = p;
 +      *accept_windows_inuse = has_windows_inuse;
 +}
 +
 +static int close_one_pack(void)
 +{
 +      struct packed_git *p, *lru_p = NULL;
 +      struct pack_window *mru_w = NULL;
 +      int accept_windows_inuse = 1;
 +
 +      for (p = packed_git; p; p = p->next) {
 +              if (p->pack_fd == -1)
 +                      continue;
 +              find_lru_pack(p, &lru_p, &mru_w, &accept_windows_inuse);
 +      }
 +
 +      if (lru_p) {
 +              close(lru_p->pack_fd);
 +              pack_open_fds--;
 +              lru_p->pack_fd = -1;
 +              return 1;
 +      }
 +
 +      return 0;
 +}
 +
  void unuse_pack(struct pack_window **w_cursor)
  {
        struct pack_window *w = *w_cursor;
@@@ -823,38 -728,15 +823,38 @@@ void free_pack_by_name(const char *pack
  static unsigned int get_max_fd_limit(void)
  {
  #ifdef RLIMIT_NOFILE
 -      struct rlimit lim;
 +      {
 +              struct rlimit lim;
  
 -      if (getrlimit(RLIMIT_NOFILE, &lim))
 -              die_errno("cannot get RLIMIT_NOFILE");
 +              if (!getrlimit(RLIMIT_NOFILE, &lim))
 +                      return lim.rlim_cur;
 +      }
 +#endif
  
 -      return lim.rlim_cur;
 -#elif defined(_SC_OPEN_MAX)
 -      return sysconf(_SC_OPEN_MAX);
 -#elif defined(OPEN_MAX)
 +#ifdef _SC_OPEN_MAX
 +      {
 +              long open_max = sysconf(_SC_OPEN_MAX);
 +              if (0 < open_max)
 +                      return open_max;
 +              /*
 +               * Otherwise, we got -1 for one of the two
 +               * reasons:
 +               *
 +               * (1) sysconf() did not understand _SC_OPEN_MAX
 +               *     and signaled an error with -1; or
 +               * (2) sysconf() said there is no limit.
 +               *
 +               * We _could_ clear errno before calling sysconf() to
 +               * tell these two cases apart and return a huge number
 +               * in the latter case to let the caller cap it to a
 +               * value that is not so selfish, but letting the
 +               * fallback OPEN_MAX codepath take care of these cases
 +               * is a lot simpler.
 +               */
 +      }
 +#endif
 +
 +#ifdef OPEN_MAX
        return OPEN_MAX;
  #else
        return 1; /* see the caller ;-) */
@@@ -886,7 -768,7 +886,7 @@@ static int open_packed_git_1(struct pac
                        pack_max_fds = 1;
        }
  
 -      while (pack_max_fds <= pack_open_fds && unuse_one_window(NULL, -1))
 +      while (pack_max_fds <= pack_open_fds && close_one_pack())
                ; /* nothing */
  
        p->pack_fd = git_open_noatime(p->pack_name);
@@@ -1002,7 -884,7 +1002,7 @@@ unsigned char *use_pack(struct packed_g
                        win->len = (size_t)len;
                        pack_mapped += win->len;
                        while (packed_git_limit < pack_mapped
 -                              && unuse_one_window(p, p->pack_fd))
 +                              && unuse_one_window(p))
                                ; /* nothing */
                        win->base = xmmap(NULL, win->len,
                                PROT_READ, MAP_PRIVATE,
@@@ -1048,7 -930,7 +1048,7 @@@ static struct packed_git *alloc_packed_
  
  static void try_to_free_pack_memory(size_t size)
  {
 -      release_pack_memory(size, -1);
 +      release_pack_memory(size);
  }
  
  struct packed_git *add_packed_git(const char *path, int path_len, int local)
@@@ -1118,63 -1000,6 +1118,63 @@@ void install_packed_git(struct packed_g
        packed_git = pack;
  }
  
 +void (*report_garbage)(const char *desc, const char *path);
 +
 +static void report_helper(const struct string_list *list,
 +                        int seen_bits, int first, int last)
 +{
 +      const char *msg;
 +      switch (seen_bits) {
 +      case 0:
 +              msg = "no corresponding .idx nor .pack";
 +              break;
 +      case 1:
 +              msg = "no corresponding .idx";
 +              break;
 +      case 2:
 +              msg = "no corresponding .pack";
 +              break;
 +      default:
 +              return;
 +      }
 +      for (; first < last; first++)
 +              report_garbage(msg, list->items[first].string);
 +}
 +
 +static void report_pack_garbage(struct string_list *list)
 +{
 +      int i, baselen = -1, first = 0, seen_bits = 0;
 +
 +      if (!report_garbage)
 +              return;
 +
 +      sort_string_list(list);
 +
 +      for (i = 0; i < list->nr; i++) {
 +              const char *path = list->items[i].string;
 +              if (baselen != -1 &&
 +                  strncmp(path, list->items[first].string, baselen)) {
 +                      report_helper(list, seen_bits, first, i);
 +                      baselen = -1;
 +                      seen_bits = 0;
 +              }
 +              if (baselen == -1) {
 +                      const char *dot = strrchr(path, '.');
 +                      if (!dot) {
 +                              report_garbage("garbage found", path);
 +                              continue;
 +                      }
 +                      baselen = dot - path + 1;
 +                      first = i;
 +              }
 +              if (!strcmp(path + baselen, "pack"))
 +                      seen_bits |= 1;
 +              else if (!strcmp(path + baselen, "idx"))
 +                      seen_bits |= 2;
 +      }
 +      report_helper(list, seen_bits, first, list->nr);
 +}
 +
  static void prepare_packed_git_one(char *objdir, int local)
  {
        /* Ensure that this buffer is large enough so that we can
        int len;
        DIR *dir;
        struct dirent *de;
 +      struct string_list garbage = STRING_LIST_INIT_DUP;
  
        sprintf(path, "%s/pack", objdir);
        len = strlen(path);
                int namelen = strlen(de->d_name);
                struct packed_git *p;
  
 -              if (!has_extension(de->d_name, ".idx"))
 +              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;
 +              }
  
 -              if (len + namelen + 1 > sizeof(path))
 +              if (is_dot_or_dotdot(de->d_name))
                        continue;
  
 -              /* Don't reopen a pack we already have. */
                strcpy(path + len, de->d_name);
 -              for (p = packed_git; p; p = p->next) {
 -                      if (!memcmp(path, p->pack_name, len + namelen - 4))
 -                              break;
 +
 +              if (has_extension(de->d_name, ".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))
 +                                      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)
 +                              install_packed_git(p);
                }
 -              if (p)
 -                      continue;
 -              /* 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);
 -              if (!p)
 +
 +              if (!report_garbage)
                        continue;
 -              install_packed_git(p);
 +
 +              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);
 +              else
 +                      report_garbage("garbage found", path);
        }
        closedir(dir);
 +      report_pack_garbage(&garbage);
 +      string_list_clear(&garbage, 0);
  }
  
  static int sort_pack(const void *a_, const void *b_)
@@@ -1320,6 -1123,7 +1320,6 @@@ void prepare_packed_git(void
  
  void reprepare_packed_git(void)
  {
 -      discard_revindex();
        prepare_packed_git_run_once = 0;
        prepare_packed_git();
  }
@@@ -1383,10 -1187,6 +1383,10 @@@ int check_sha1_signature(const unsigne
                char buf[1024 * 16];
                ssize_t readlen = read_istream(st, buf, sizeof(buf));
  
 +              if (readlen < 0) {
 +                      close_istream(st);
 +                      return -1;
 +              }
                if (!readlen)
                        break;
                git_SHA1_Update(&c, buf, readlen);
        return hashcmp(sha1, real_sha1) ? -1 : 0;
  }
  
 -static int git_open_noatime(const char *name)
 +int git_open_noatime(const char *name)
  {
        static int sha1_file_open_flag = O_NOATIME;
  
        }
  }
  
 +static int stat_sha1_file(const unsigned char *sha1, struct stat *st)
 +{
 +      struct alternate_object_database *alt;
 +
 +      if (!lstat(sha1_file_name(sha1), st))
 +              return 0;
 +
 +      prepare_alt_odb();
 +      errno = ENOENT;
 +      for (alt = alt_odb_list; alt; alt = alt->next) {
 +              fill_sha1_path(alt->name, sha1);
 +              if (!lstat(alt->base, st))
 +                      return 0;
 +      }
 +
 +      return -1;
 +}
 +
  static int open_sha1_file(const unsigned char *sha1)
  {
        int fd;
 -      char *name = sha1_file_name(sha1);
        struct alternate_object_database *alt;
  
 -      fd = git_open_noatime(name);
 +      fd = git_open_noatime(sha1_file_name(sha1));
        if (fd >= 0)
                return fd;
  
        prepare_alt_odb();
        errno = ENOENT;
        for (alt = alt_odb_list; alt; alt = alt->next) {
 -              name = alt->name;
 -              fill_sha1_path(name, sha1);
 +              fill_sha1_path(alt->name, sha1);
                fd = git_open_noatime(alt->base);
                if (fd >= 0)
                        return fd;
@@@ -1477,6 -1261,51 +1477,6 @@@ void *map_sha1_file(const unsigned cha
        return map;
  }
  
 -/*
 - * There used to be a second loose object header format which
 - * was meant to mimic the in-pack format, allowing for direct
 - * copy of the object data.  This format turned up not to be
 - * really worth it and we no longer write loose objects in that
 - * format.
 - */
 -static int experimental_loose_object(unsigned char *map)
 -{
 -      unsigned int word;
 -
 -      /*
 -       * We must determine if the buffer contains the standard
 -       * zlib-deflated stream or the experimental format based
 -       * on the in-pack object format. Compare the header byte
 -       * for each format:
 -       *
 -       * RFC1950 zlib w/ deflate : 0www1000 : 0 <= www <= 7
 -       * Experimental pack-based : Stttssss : ttt = 1,2,3,4
 -       *
 -       * If bit 7 is clear and bits 0-3 equal 8, the buffer MUST be
 -       * in standard loose-object format, UNLESS it is a Git-pack
 -       * format object *exactly* 8 bytes in size when inflated.
 -       *
 -       * However, RFC1950 also specifies that the 1st 16-bit word
 -       * must be divisible by 31 - this checksum tells us our buffer
 -       * is in the standard format, giving a false positive only if
 -       * the 1st word of the Git-pack format object happens to be
 -       * divisible by 31, ie:
 -       *      ((byte0 * 256) + byte1) % 31 = 0
 -       *   =>        0ttt10000www1000 % 31 = 0
 -       *
 -       * As it happens, this case can only arise for www=3 & ttt=1
 -       * - ie, a Commit object, which would have to be 8 bytes in
 -       * size. As no Commit can be that small, we find that the
 -       * combination of these two criteria (bitmask & checksum)
 -       * can always correctly determine the buffer format.
 -       */
 -      word = (map[0] << 8) + map[1];
 -      if ((map[0] & 0x8F) == 0x08 && !(word % 31))
 -              return 0;
 -      else
 -              return 1;
 -}
 -
  unsigned long unpack_object_header_buffer(const unsigned char *buf,
                unsigned long len, enum object_type *type, unsigned long *sizep)
  {
  
  int unpack_sha1_header(git_zstream *stream, unsigned char *map, unsigned long mapsize, void *buffer, unsigned long bufsiz)
  {
 -      unsigned long size, used;
 -      static const char valid_loose_object_type[8] = {
 -              0, /* OBJ_EXT */
 -              1, 1, 1, 1, /* "commit", "tree", "blob", "tag" */
 -              0, /* "delta" and others are invalid in a loose object */
 -      };
 -      enum object_type type;
 -
        /* Get the data stream */
        memset(stream, 0, sizeof(*stream));
        stream->next_in = map;
        stream->next_out = buffer;
        stream->avail_out = bufsiz;
  
 -      if (experimental_loose_object(map)) {
 -              /*
 -               * The old experimental format we no longer produce;
 -               * we can still read it.
 -               */
 -              used = unpack_object_header_buffer(map, mapsize, &type, &size);
 -              if (!used || !valid_loose_object_type[type])
 -                      return -1;
 -              map += used;
 -              mapsize -= used;
 -
 -              /* Set up the stream for the rest.. */
 -              stream->next_in = map;
 -              stream->avail_in = mapsize;
 -              git_inflate_init(stream);
 -
 -              /* And generate the fake traditional header */
 -              stream->total_out = 1 + snprintf(buffer, bufsiz, "%s %lu",
 -                                               typename(type), size);
 -              return 0;
 -      }
        git_inflate_init(stream);
        return git_inflate(stream, 0);
  }
@@@ -1702,38 -1560,6 +1702,38 @@@ static off_t get_delta_base(struct pack
        return base_offset;
  }
  
 +/*
 + * Like get_delta_base above, but we return the sha1 instead of the pack
 + * offset. This means it is cheaper for REF deltas (we do not have to do
 + * the final object lookup), but more expensive for OFS deltas (we
 + * have to load the revidx to convert the offset back into a sha1).
 + */
 +static const unsigned char *get_delta_base_sha1(struct packed_git *p,
 +                                              struct pack_window **w_curs,
 +                                              off_t curpos,
 +                                              enum object_type type,
 +                                              off_t delta_obj_offset)
 +{
 +      if (type == OBJ_REF_DELTA) {
 +              unsigned char *base = use_pack(p, w_curs, curpos, NULL);
 +              return base;
 +      } else if (type == OBJ_OFS_DELTA) {
 +              struct revindex_entry *revidx;
 +              off_t base_offset = get_delta_base(p, w_curs, &curpos,
 +                                                 type, delta_obj_offset);
 +
 +              if (!base_offset)
 +                      return NULL;
 +
 +              revidx = find_pack_revindex(p, base_offset);
 +              if (!revidx)
 +                      return NULL;
 +
 +              return nth_packed_object_sha1(p, revidx->nr);
 +      } else
 +              return NULL;
 +}
 +
  int unpack_object_header(struct packed_git *p,
                         struct pack_window **w_curs,
                         off_t *curpos,
@@@ -1776,21 -1602,46 +1776,21 @@@ static int retry_bad_packed_offset(stru
        return type;
  }
  
 -
  #define POI_STACK_PREALLOC 64
  
 -static int packed_object_info(struct packed_git *p, off_t obj_offset,
 -                            unsigned long *sizep, int *rtype)
 +static enum object_type packed_to_object_type(struct packed_git *p,
 +                                            off_t obj_offset,
 +                                            enum object_type type,
 +                                            struct pack_window **w_curs,
 +                                            off_t curpos)
  {
 -      struct pack_window *w_curs = NULL;
 -      unsigned long size;
 -      off_t curpos = obj_offset;
 -      enum object_type type;
        off_t small_poi_stack[POI_STACK_PREALLOC];
        off_t *poi_stack = small_poi_stack;
        int poi_stack_nr = 0, poi_stack_alloc = POI_STACK_PREALLOC;
  
 -      type = unpack_object_header(p, &w_curs, &curpos, &size);
 -
 -      if (rtype)
 -              *rtype = type; /* representation type */
 -
 -      if (sizep) {
 -              if (type == OBJ_OFS_DELTA || type == OBJ_REF_DELTA) {
 -                      off_t tmp_pos = curpos;
 -                      off_t base_offset = get_delta_base(p, &w_curs, &tmp_pos,
 -                                                         type, obj_offset);
 -                      if (!base_offset) {
 -                              type = OBJ_BAD;
 -                              goto out;
 -                      }
 -                      *sizep = get_size_from_delta(p, &w_curs, tmp_pos);
 -                      if (*sizep == 0) {
 -                              type = OBJ_BAD;
 -                              goto out;
 -                      }
 -              } else {
 -                      *sizep = size;
 -              }
 -      }
 -
        while (type == OBJ_OFS_DELTA || type == OBJ_REF_DELTA) {
                off_t base_offset;
 +              unsigned long size;
                /* Push the object we're going to leave behind */
                if (poi_stack_nr >= poi_stack_alloc && poi_stack == small_poi_stack) {
                        poi_stack_alloc = alloc_nr(poi_stack_nr);
                }
                poi_stack[poi_stack_nr++] = obj_offset;
                /* If parsing the base offset fails, just unwind */
 -              base_offset = get_delta_base(p, &w_curs, &curpos, type, obj_offset);
 +              base_offset = get_delta_base(p, w_curs, &curpos, type, obj_offset);
                if (!base_offset)
                        goto unwind;
                curpos = obj_offset = base_offset;
 -              type = unpack_object_header(p, &w_curs, &curpos, &size);
 +              type = unpack_object_header(p, w_curs, &curpos, &size);
                if (type <= OBJ_NONE) {
                        /* If getting the base itself fails, we first
                         * retry the base, otherwise unwind */
  out:
        if (poi_stack != small_poi_stack)
                free(poi_stack);
 -      unuse_pack(&w_curs);
        return type;
  
  unwind:
        goto out;
  }
  
 +static int packed_object_info(struct packed_git *p, off_t obj_offset,
 +                            struct object_info *oi)
 +{
 +      struct pack_window *w_curs = NULL;
 +      unsigned long size;
 +      off_t curpos = obj_offset;
 +      enum object_type type;
 +
 +      /*
 +       * We always get the representation type, but only convert it to
 +       * a "real" type later if the caller is interested.
 +       */
 +      type = unpack_object_header(p, &w_curs, &curpos, &size);
 +
 +      if (oi->sizep) {
 +              if (type == OBJ_OFS_DELTA || type == OBJ_REF_DELTA) {
 +                      off_t tmp_pos = curpos;
 +                      off_t base_offset = get_delta_base(p, &w_curs, &tmp_pos,
 +                                                         type, obj_offset);
 +                      if (!base_offset) {
 +                              type = OBJ_BAD;
 +                              goto out;
 +                      }
 +                      *oi->sizep = get_size_from_delta(p, &w_curs, tmp_pos);
 +                      if (*oi->sizep == 0) {
 +                              type = OBJ_BAD;
 +                              goto out;
 +                      }
 +              } else {
 +                      *oi->sizep = size;
 +              }
 +      }
 +
 +      if (oi->disk_sizep) {
 +              struct revindex_entry *revidx = find_pack_revindex(p, obj_offset);
 +              *oi->disk_sizep = revidx[1].offset - obj_offset;
 +      }
 +
 +      if (oi->typep) {
 +              *oi->typep = packed_to_object_type(p, obj_offset, type, &w_curs, curpos);
 +              if (*oi->typep < 0) {
 +                      type = OBJ_BAD;
 +                      goto out;
 +              }
 +      }
 +
 +      if (oi->delta_base_sha1) {
 +              if (type == OBJ_OFS_DELTA || type == OBJ_REF_DELTA) {
 +                      const unsigned char *base;
 +
 +                      base = get_delta_base_sha1(p, &w_curs, curpos,
 +                                                 type, obj_offset);
 +                      if (!base) {
 +                              type = OBJ_BAD;
 +                              goto out;
 +                      }
 +
 +                      hashcpy(oi->delta_base_sha1, base);
 +              } else
 +                      hashclr(oi->delta_base_sha1);
 +      }
 +
 +out:
 +      unuse_pack(&w_curs);
 +      return type;
 +}
 +
  static void *unpack_compressed_entry(struct packed_git *p,
                                    struct pack_window **w_curs,
                                    off_t curpos,
@@@ -2083,19 -1868,12 +2083,19 @@@ static void write_pack_access_log(struc
  {
        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 = NULL;
 +                      log_pack_access = no_log_pack_access;
                        return;
                }
        }
@@@ -2126,7 -1904,7 +2126,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)
 +      if (log_pack_access != no_log_pack_access)
                write_pack_access_log(p, obj_offset);
  
        /* PHASE 1: drill down to the innermost base object */
                int i;
                struct delta_base_cache_entry *ent;
  
 +              ent = get_delta_base_cache_entry(p, curpos);
 +              if (eq_delta_base_cache_entry(ent, p, curpos)) {
 +                      type = ent->type;
 +                      data = ent->data;
 +                      size = ent->size;
 +                      clear_delta_base_cache_entry(ent);
 +                      base_from_cache = 1;
 +                      break;
 +              }
 +
                if (do_check_packed_object_crc && p->index_version > 1) {
                        struct revindex_entry *revidx = find_pack_revindex(p, obj_offset);
                        unsigned long len = revidx[1].offset - obj_offset;
                        }
                }
  
 -              ent = get_delta_base_cache_entry(p, curpos);
 -              if (eq_delta_base_cache_entry(ent, p, curpos)) {
 -                      type = ent->type;
 -                      data = ent->data;
 -                      size = ent->size;
 -                      clear_delta_base_cache_entry(ent);
 -                      base_from_cache = 1;
 -                      break;
 -              }
 -
                type = unpack_object_header(p, &w_curs, &curpos, &size);
                if (type != OBJ_OFS_DELTA && type != OBJ_REF_DELTA)
                        break;
                        error("failed to unpack compressed delta "
                              "at offset %"PRIuMAX" from %s",
                              (uintmax_t)curpos, p->pack_name);
 -                      free(base);
                        data = NULL;
                        continue;
                }
                data = patch_delta(base, base_size,
                                   delta_data, delta_size,
                                   &size);
 +
 +              /*
 +               * We could not apply the delta; warn the user, but keep going.
 +               * Our failure will be noticed either in the next iteration of
 +               * the loop, or if this is the final delta, in the caller when
 +               * we return NULL. Those code paths will take care of making
 +               * a more explicit warning and retrying with another copy of
 +               * the object.
 +               */
                if (!data)
 -                      die("failed to apply delta");
 +                      error("failed to apply delta");
  
 -              free (delta_data);
 +              free(delta_data);
        }
  
        *final_type = type;
        *final_size = size;
  
        unuse_pack(&w_curs);
+       if (delta_stack != small_delta_stack)
+               free(delta_stack);
        return data;
  }
  
@@@ -2447,10 -2221,6 +2451,10 @@@ static int fill_pack_entry(const unsign
        return 1;
  }
  
 +/*
 + * Iff a pack file contains the object named by sha1, return true and
 + * store its location to e.
 + */
  static int find_pack_entry(const unsigned char *sha1, struct pack_entry *e)
  {
        struct packed_git *p;
                return 1;
  
        for (p = packed_git; p; p = p->next) {
 -              if (p == last_found_pack || !fill_pack_entry(sha1, e, p))
 -                      continue;
 +              if (p == last_found_pack)
 +                      continue; /* we already checked this one */
  
 -              last_found_pack = p;
 -              return 1;
 +              if (fill_pack_entry(sha1, e, p)) {
 +                      last_found_pack = p;
 +                      return 1;
 +              }
        }
        return 0;
  }
@@@ -2487,8 -2255,7 +2491,8 @@@ struct packed_git *find_sha1_pack(cons
  
  }
  
 -static int sha1_loose_object_info(const unsigned char *sha1, unsigned long *sizep)
 +static int sha1_loose_object_info(const unsigned char *sha1,
 +                                struct object_info *oi)
  {
        int status;
        unsigned long mapsize, size;
        git_zstream stream;
        char hdr[32];
  
 +      if (oi->delta_base_sha1)
 +              hashclr(oi->delta_base_sha1);
 +
 +      /*
 +       * If we don't care about type or size, then we don't
 +       * need to look inside the object at all. Note that we
 +       * do not optimize out the stat call, even if the
 +       * caller doesn't care about the disk-size, since our
 +       * return value implicitly indicates whether the
 +       * object even exists.
 +       */
 +      if (!oi->typep && !oi->sizep) {
 +              struct stat st;
 +              if (stat_sha1_file(sha1, &st) < 0)
 +                      return -1;
 +              if (oi->disk_sizep)
 +                      *oi->disk_sizep = st.st_size;
 +              return 0;
 +      }
 +
        map = map_sha1_file(sha1, &mapsize);
        if (!map)
 -              return error("unable to find %s", sha1_to_hex(sha1));
 +              return -1;
 +      if (oi->disk_sizep)
 +              *oi->disk_sizep = mapsize;
        if (unpack_sha1_header(&stream, map, mapsize, hdr, sizeof(hdr)) < 0)
                status = error("unable to unpack %s header",
                               sha1_to_hex(sha1));
        else if ((status = parse_sha1_header(hdr, &size)) < 0)
                status = error("unable to parse %s header", sha1_to_hex(sha1));
 -      else if (sizep)
 -              *sizep = size;
 +      else if (oi->sizep)
 +              *oi->sizep = size;
        git_inflate_end(&stream);
        munmap(map, mapsize);
 -      return status;
 +      if (oi->typep)
 +              *oi->typep = status;
 +      return 0;
  }
  
 -/* returns enum object_type or negative */
 -int sha1_object_info_extended(const unsigned char *sha1, struct object_info *oi)
 +int sha1_object_info_extended(const unsigned char *sha1, struct object_info *oi, unsigned flags)
  {
        struct cached_object *co;
        struct pack_entry e;
 -      int status, rtype;
 +      int rtype;
 +      const unsigned char *real = lookup_replace_object_extended(sha1, flags);
  
 -      co = find_cached_object(sha1);
 +      co = find_cached_object(real);
        if (co) {
 +              if (oi->typep)
 +                      *(oi->typep) = co->type;
                if (oi->sizep)
                        *(oi->sizep) = co->size;
 +              if (oi->disk_sizep)
 +                      *(oi->disk_sizep) = 0;
 +              if (oi->delta_base_sha1)
 +                      hashclr(oi->delta_base_sha1);
                oi->whence = OI_CACHED;
 -              return co->type;
 +              return 0;
        }
  
 -      if (!find_pack_entry(sha1, &e)) {
 +      if (!find_pack_entry(real, &e)) {
                /* Most likely it's a loose object. */
 -              status = sha1_loose_object_info(sha1, oi->sizep);
 -              if (status >= 0) {
 +              if (!sha1_loose_object_info(real, oi)) {
                        oi->whence = OI_LOOSE;
 -                      return status;
 +                      return 0;
                }
  
                /* Not a loose object; someone else may have just packed it. */
                reprepare_packed_git();
 -              if (!find_pack_entry(sha1, &e))
 -                      return status;
 +              if (!find_pack_entry(real, &e))
 +                      return -1;
        }
  
 -      status = packed_object_info(e.p, e.offset, oi->sizep, &rtype);
 -      if (status < 0) {
 -              mark_bad_packed_object(e.p, sha1);
 -              status = sha1_object_info_extended(sha1, oi);
 +      rtype = packed_object_info(e.p, e.offset, oi);
 +      if (rtype < 0) {
 +              mark_bad_packed_object(e.p, real);
 +              return sha1_object_info_extended(real, oi, 0);
        } else if (in_delta_base_cache(e.p, e.offset)) {
                oi->whence = OI_DBCACHED;
        } else {
                                         rtype == OBJ_OFS_DELTA);
        }
  
 -      return status;
 +      return 0;
  }
  
 +/* returns enum object_type or negative */
  int sha1_object_info(const unsigned char *sha1, unsigned long *sizep)
  {
 -      struct object_info oi;
 +      enum object_type type;
 +      struct object_info oi = {NULL};
  
 +      oi.typep = &type;
        oi.sizep = sizep;
 -      return sha1_object_info_extended(sha1, &oi);
 +      if (sha1_object_info_extended(sha1, &oi, LOOKUP_REPLACE_OBJECT) < 0)
 +              return -1;
 +      return type;
  }
  
  static void *read_packed_sha1(const unsigned char *sha1,
@@@ -2684,8 -2417,10 +2688,8 @@@ void *read_sha1_file_extended(const uns
                              unsigned flag)
  {
        void *data;
 -      char *path;
        const struct packed_git *p;
 -      const unsigned char *repl = (flag & READ_SHA1_FILE_REPLACE)
 -              ? lookup_replace_object(sha1) : sha1;
 +      const unsigned char *repl = lookup_replace_object_extended(sha1, flag);
  
        errno = 0;
        data = read_object(repl, type, size);
                    sha1_to_hex(repl), sha1_to_hex(sha1));
  
        if (has_loose_object(repl)) {
 -              path = sha1_file_name(sha1);
 +              const char *path = sha1_file_name(sha1);
 +
                die("loose object %s (stored in %s) is corrupt",
                    sha1_to_hex(repl), path);
        }
@@@ -2880,9 -2614,7 +2884,9 @@@ static int create_tmpfile(char *buffer
                /* Make sure the directory exists */
                memcpy(buffer, filename, dirlen);
                buffer[dirlen-1] = 0;
 -              if (mkdir(buffer, 0777) || adjust_shared_perm(buffer))
 +              if (mkdir(buffer, 0777) && errno != EEXIST)
 +                      return -1;
 +              if (adjust_shared_perm(buffer))
                        return -1;
  
                /* Try again */
@@@ -2900,9 -2632,10 +2904,9 @@@ static int write_loose_object(const uns
        git_zstream stream;
        git_SHA_CTX c;
        unsigned char parano_sha1[20];
 -      char *filename;
        static char tmp_file[PATH_MAX];
 +      const char *filename = sha1_file_name(sha1);
  
 -      filename = sha1_file_name(sha1);
        fd = create_tmpfile(tmp_file, sizeof(tmp_file), filename);
        if (fd < 0) {
                if (errno == EACCES)
@@@ -3019,10 -2752,7 +3023,10 @@@ int has_sha1_file(const unsigned char *
  
        if (find_pack_entry(sha1, &e))
                return 1;
 -      return has_loose_object(sha1);
 +      if (has_loose_object(sha1))
 +              return 1;
 +      reprepare_packed_git();
 +      return find_pack_entry(sha1, &e);
  }
  
  static void check_tree(const void *buf, size_t size)