+#define PACK_MAX_SZ (1<<26)
+static int pack_used_ctr;
+static unsigned long pack_mapped;
+struct packed_git *packed_git;
+
+static int check_packed_git_idx(const char *path, unsigned long *idx_size_,
+ void **idx_map_)
+{
+ void *idx_map;
+ unsigned int *index;
+ unsigned long idx_size;
+ int nr, i;
+ int fd = open(path, O_RDONLY);
+ struct stat st;
+ if (fd < 0)
+ return -1;
+ if (fstat(fd, &st)) {
+ close(fd);
+ return -1;
+ }
+ idx_size = st.st_size;
+ idx_map = mmap(NULL, idx_size, PROT_READ, MAP_PRIVATE, fd, 0);
+ close(fd);
+ if (idx_map == MAP_FAILED)
+ return -1;
+
+ index = idx_map;
+ *idx_map_ = idx_map;
+ *idx_size_ = idx_size;
+
+ /* check index map */
+ if (idx_size < 4*256 + 20 + 20)
+ return error("index file too small");
+ nr = 0;
+ for (i = 0; i < 256; i++) {
+ unsigned int n = ntohl(index[i]);
+ if (n < nr)
+ return error("non-monotonic index");
+ nr = n;
+ }
+
+ /*
+ * Total size:
+ * - 256 index entries 4 bytes each
+ * - 24-byte entries * nr (20-byte sha1 + 4-byte offset)
+ * - 20-byte SHA1 of the packfile
+ * - 20-byte SHA1 file checksum
+ */
+ if (idx_size != 4*256 + nr * 24 + 20 + 20)
+ return error("wrong index file size");
+
+ return 0;
+}
+
+static int unuse_one_packed_git(void)
+{
+ struct packed_git *p, *lru = NULL;
+
+ for (p = packed_git; p; p = p->next) {
+ if (p->pack_use_cnt || !p->pack_base)
+ continue;
+ if (!lru || p->pack_last_used < lru->pack_last_used)
+ lru = p;
+ }
+ if (!lru)
+ return 0;
+ munmap(lru->pack_base, lru->pack_size);
+ lru->pack_base = NULL;
+ return 1;
+}
+
+void unuse_packed_git(struct packed_git *p)
+{
+ p->pack_use_cnt--;
+}
+
+int use_packed_git(struct packed_git *p)
+{
+ if (!p->pack_base) {
+ int fd;
+ struct stat st;
+ void *map;
+
+ pack_mapped += p->pack_size;
+ while (PACK_MAX_SZ < pack_mapped && unuse_one_packed_git())
+ ; /* nothing */
+ fd = open(p->pack_name, O_RDONLY);
+ if (fd < 0)
+ die("packfile %s cannot be opened", p->pack_name);
+ if (fstat(fd, &st)) {
+ close(fd);
+ die("packfile %s cannot be opened", p->pack_name);
+ }
+ if (st.st_size != p->pack_size)
+ die("packfile %s size mismatch.", p->pack_name);
+ map = mmap(NULL, p->pack_size, PROT_READ, MAP_PRIVATE, fd, 0);
+ close(fd);
+ if (map == MAP_FAILED)
+ die("packfile %s cannot be mapped.", p->pack_name);
+ p->pack_base = map;
+
+ /* Check if the pack file matches with the index file.
+ * this is cheap.
+ */
+ if (memcmp((char*)(p->index_base) + p->index_size - 40,
+ p->pack_base + p->pack_size - 20, 20))
+ die("packfile %s does not match index.", p->pack_name);
+ }
+ p->pack_last_used = pack_used_ctr++;
+ p->pack_use_cnt++;
+ return 0;
+}
+
+struct packed_git *add_packed_git(char *path, int path_len)
+{
+ struct stat st;
+ struct packed_git *p;
+ unsigned long idx_size;
+ void *idx_map;
+
+ if (check_packed_git_idx(path, &idx_size, &idx_map))
+ return NULL;
+
+ /* do we have a corresponding .pack file? */
+ strcpy(path + path_len - 4, ".pack");
+ if (stat(path, &st) || !S_ISREG(st.st_mode)) {
+ munmap(idx_map, idx_size);
+ return NULL;
+ }
+ /* ok, it looks sane as far as we can check without
+ * actually mapping the pack file.
+ */
+ p = xmalloc(sizeof(*p) + path_len + 2);
+ strcpy(p->pack_name, path);
+ p->index_size = idx_size;
+ p->pack_size = st.st_size;
+ p->index_base = idx_map;
+ p->next = NULL;
+ p->pack_base = NULL;
+ p->pack_last_used = 0;
+ p->pack_use_cnt = 0;
+ return p;
+}
+
+static void prepare_packed_git_one(char *objdir)
+{
+ char path[PATH_MAX];
+ int len;
+ DIR *dir;
+ struct dirent *de;
+
+ sprintf(path, "%s/pack", objdir);
+ len = strlen(path);
+ dir = opendir(path);
+ if (!dir)
+ return;
+ path[len++] = '/';
+ while ((de = readdir(dir)) != NULL) {
+ int namelen = strlen(de->d_name);
+ struct packed_git *p;
+
+ if (strcmp(de->d_name + namelen - 4, ".idx"))
+ continue;
+
+ /* we have .idx. Is it a file we can map? */
+ strcpy(path + len, de->d_name);
+ p = add_packed_git(path, len + namelen);
+ if (!p)
+ continue;
+ p->next = packed_git;
+ packed_git = p;
+ }
+ closedir(dir);
+}
+
+void prepare_packed_git(void)
+{
+ int i;
+ static int run_once = 0;
+
+ if (run_once++)
+ return;
+
+ prepare_packed_git_one(get_object_directory());
+ prepare_alt_odb();
+ for (i = 0; alt_odb[i].base != NULL; i++) {
+ alt_odb[i].name[0] = 0;
+ prepare_packed_git_one(alt_odb[i].base);
+ }
+}
+