| #include "git-compat-util.h" | |
| #include "abspath.h" | |
| #include "chdir-notify.h" | |
| #include "gettext.h" | |
| #include "hex.h" | |
| #include "loose.h" | |
| #include "object-file.h" | |
| #include "object-file-convert.h" | |
| #include "odb.h" | |
| #include "odb/source-files.h" | |
| #include "odb/source-loose.h" | |
| #include "odb/streaming.h" | |
| #include "oidtree.h" | |
| #include "path.h" | |
| #include "progress.h" | |
| #include "repository.h" | |
| #include "strbuf.h" | |
| #include "tempfile.h" | |
| #include "write-or-die.h" | |
| static int append_loose_object(const struct object_id *oid, | |
| const char *path UNUSED, | |
| void *data) | |
| { | |
| oidtree_insert(data, oid, NULL); | |
| return 0; | |
| } | |
| static struct oidtree *odb_source_loose_cache(struct odb_source_loose *loose, | |
| const struct object_id *oid) | |
| { | |
| int subdir_nr = oid->hash[0]; | |
| struct strbuf buf = STRBUF_INIT; | |
| size_t word_bits = bitsizeof(loose->subdir_seen[0]); | |
| size_t word_index = subdir_nr / word_bits; | |
| size_t mask = (size_t)1u << (subdir_nr % word_bits); | |
| uint32_t *bitmap; | |
| if (subdir_nr < 0 || | |
| (size_t) subdir_nr >= bitsizeof(loose->subdir_seen)) | |
| BUG("subdir_nr out of range"); | |
| bitmap = &loose->subdir_seen[word_index]; | |
| if (*bitmap & mask) | |
| return loose->cache; | |
| if (!loose->cache) { | |
| ALLOC_ARRAY(loose->cache, 1); | |
| oidtree_init(loose->cache); | |
| } | |
| strbuf_addstr(&buf, loose->base.path); | |
| for_each_file_in_obj_subdir(subdir_nr, &buf, | |
| loose->base.odb->repo->hash_algo, | |
| append_loose_object, | |
| NULL, NULL, | |
| loose->cache); | |
| *bitmap |= mask; | |
| strbuf_release(&buf); | |
| return loose->cache; | |
| } | |
| static int quick_has_loose(struct odb_source_loose *loose, | |
| const struct object_id *oid) | |
| { | |
| return !!oidtree_contains(odb_source_loose_cache(loose, oid), oid); | |
| } | |
| static int read_object_info_from_path(struct odb_source_loose *loose, | |
| const char *path, | |
| const struct object_id *oid, | |
| struct object_info *oi, | |
| enum object_info_flags flags, | |
| struct strbuf *errmsg) | |
| { | |
| int ret; | |
| int fd; | |
| unsigned long mapsize; | |
| void *map = NULL; | |
| git_zstream stream, *stream_to_end = NULL; | |
| char hdr[MAX_HEADER_LEN]; | |
| size_t size_scratch; | |
| enum object_type type_scratch; | |
| struct stat st; | |
| /* | |
| * 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 || (!oi->typep && !oi->sizep && !oi->contentp)) { | |
| struct stat st; | |
| if ((!oi || (!oi->disk_sizep && !oi->mtimep)) && (flags & OBJECT_INFO_QUICK)) { | |
| ret = quick_has_loose(loose, oid) ? 0 : ODB_READ_NOT_FOUND; | |
| goto out; | |
| } | |
| if (lstat(path, &st) < 0) { | |
| if (errno == ENOENT) { | |
| ret = ODB_READ_NOT_FOUND; | |
| goto out; | |
| } | |
| ret = -1; | |
| goto out; | |
| } | |
| if (oi) { | |
| if (oi->disk_sizep) | |
| *oi->disk_sizep = st.st_size; | |
| if (oi->mtimep) | |
| *oi->mtimep = st.st_mtime; | |
| } | |
| ret = 0; | |
| goto out; | |
| } | |
| fd = git_open(path); | |
| if (fd < 0) { | |
| if (errno == ENOENT) { | |
| ret = ODB_READ_NOT_FOUND; | |
| goto out; | |
| } | |
| ret = error_errno(_("unable to open loose object %s"), oid_to_hex(oid)); | |
| goto out; | |
| } | |
| if (fstat(fd, &st)) { | |
| close(fd); | |
| ret = -1; | |
| goto out; | |
| } | |
| mapsize = xsize_t(st.st_size); | |
| if (!mapsize) { | |
| close(fd); | |
| ret = error(_("object file %s is empty"), path); | |
| goto out; | |
| } | |
| map = xmmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, fd, 0); | |
| close(fd); | |
| if (!map) { | |
| ret = -1; | |
| goto out; | |
| } | |
| if (oi->disk_sizep) | |
| *oi->disk_sizep = mapsize; | |
| if (oi->mtimep) | |
| *oi->mtimep = st.st_mtime; | |
| stream_to_end = &stream; | |
| switch (unpack_loose_header(&stream, map, mapsize, hdr, sizeof(hdr))) { | |
| case ULHR_OK: | |
| if (!oi->sizep) | |
| oi->sizep = &size_scratch; | |
| if (!oi->typep) | |
| oi->typep = &type_scratch; | |
| if (parse_loose_header(hdr, oi) < 0) { | |
| ret = error(_("unable to parse %s header"), oid_to_hex(oid)); | |
| goto out; | |
| } | |
| if (*oi->typep < 0) | |
| die(_("invalid object type")); | |
| if (oi->contentp) { | |
| *oi->contentp = unpack_loose_rest(&stream, hdr, *oi->sizep, oid); | |
| if (!*oi->contentp) { | |
| ret = -1; | |
| goto out; | |
| } | |
| } | |
| break; | |
| case ULHR_BAD: | |
| ret = error(_("unable to unpack %s header"), | |
| oid_to_hex(oid)); | |
| goto out; | |
| case ULHR_TOO_LONG: | |
| ret = error(_("header for %s too long, exceeds %d bytes"), | |
| oid_to_hex(oid), MAX_HEADER_LEN); | |
| goto out; | |
| } | |
| ret = 0; | |
| out: | |
| if (ret && ret != ODB_READ_NOT_FOUND && errmsg) | |
| strbuf_addf(errmsg, _("loose object %s (stored in %s) is corrupt"), | |
| oid_to_hex(oid), path); | |
| if (stream_to_end) | |
| git_inflate_end(stream_to_end); | |
| if (map) | |
| munmap(map, mapsize); | |
| if (oi) { | |
| if (oi->sizep == &size_scratch) | |
| oi->sizep = NULL; | |
| if (oi->typep == &type_scratch) | |
| oi->typep = NULL; | |
| if (oi->delta_base_oid) | |
| oidclr(oi->delta_base_oid, loose->base.odb->repo->hash_algo); | |
| if (oi->source_infop && !ret) | |
| oi->source_infop->source = &loose->base; | |
| } | |
| return ret; | |
| } | |
| static enum odb_read_status odb_source_loose_read_object_info(struct odb_source *source, | |
| const struct object_id *oid, | |
| struct object_info *oi, | |
| enum object_info_flags flags, | |
| struct strbuf *errmsg) | |
| { | |
| struct odb_source_loose *loose = odb_source_loose_downcast(source); | |
| static struct strbuf buf = STRBUF_INIT; | |
| /* | |
| * The second read shouldn't cause new loose objects to show up, unless | |
| * there was a race condition with a secondary process. We don't care | |
| * about this case though, so we simply skip reading loose objects a | |
| * second time. | |
| */ | |
| if (flags & OBJECT_INFO_SECOND_READ) | |
| return ODB_READ_NOT_FOUND; | |
| odb_loose_path(loose, &buf, oid); | |
| return read_object_info_from_path(loose, buf.buf, oid, oi, flags, errmsg); | |
| } | |
| /* | |
| * Find "oid" as a loose object in given source, open the object and return its | |
| * file descriptor. Returns the file descriptor on success, negative on failure. | |
| * | |
| * The "path" out-parameter will give the path of the object we found (if any). | |
| * Note that it may point to static storage and is only valid until another | |
| * call to open_loose_object(). | |
| */ | |
| static int open_loose_object(struct odb_source_loose *loose, | |
| const struct object_id *oid, const char **path) | |
| { | |
| static struct strbuf buf = STRBUF_INIT; | |
| int fd; | |
| *path = odb_loose_path(loose, &buf, oid); | |
| fd = git_open(*path); | |
| if (fd >= 0) | |
| return fd; | |
| return -1; | |
| } | |
| static void *odb_source_loose_map_object(struct odb_source_loose *loose, | |
| const struct object_id *oid, | |
| unsigned long *size) | |
| { | |
| const char *p; | |
| int fd = open_loose_object(loose, oid, &p); | |
| void *map = NULL; | |
| struct stat st; | |
| if (fd < 0) | |
| return NULL; | |
| if (!fstat(fd, &st)) { | |
| *size = xsize_t(st.st_size); | |
| if (!*size) { | |
| /* mmap() is forbidden on empty files */ | |
| error(_("object file %s is empty"), p); | |
| goto out; | |
| } | |
| map = xmmap(NULL, *size, PROT_READ, MAP_PRIVATE, fd, 0); | |
| } | |
| out: | |
| close(fd); | |
| return map; | |
| } | |
| struct odb_loose_read_stream { | |
| struct odb_stream base; | |
| git_zstream z; | |
| enum { | |
| ODB_LOOSE_READ_STREAM_INUSE, | |
| ODB_LOOSE_READ_STREAM_DONE, | |
| ODB_LOOSE_READ_STREAM_ERROR, | |
| } z_state; | |
| void *mapped; | |
| unsigned long mapsize; | |
| char hdr[32]; | |
| int hdr_avail; | |
| int hdr_used; | |
| }; | |
| static ssize_t read_istream_loose(struct odb_stream *_st, char *buf, size_t sz) | |
| { | |
| struct odb_loose_read_stream *st = | |
| container_of(_st, struct odb_loose_read_stream, base); | |
| size_t total_read = 0; | |
| switch (st->z_state) { | |
| case ODB_LOOSE_READ_STREAM_DONE: | |
| return 0; | |
| case ODB_LOOSE_READ_STREAM_ERROR: | |
| return -1; | |
| default: | |
| break; | |
| } | |
| if (st->hdr_used < st->hdr_avail) { | |
| size_t to_copy = st->hdr_avail - st->hdr_used; | |
| if (sz < to_copy) | |
| to_copy = sz; | |
| memcpy(buf, st->hdr + st->hdr_used, to_copy); | |
| st->hdr_used += to_copy; | |
| total_read += to_copy; | |
| } | |
| while (total_read < sz) { | |
| int status; | |
| st->z.next_out = (unsigned char *)buf + total_read; | |
| st->z.avail_out = sz - total_read; | |
| status = git_inflate(&st->z, Z_FINISH); | |
| total_read = st->z.next_out - (unsigned char *)buf; | |
| if (status == Z_STREAM_END) { | |
| git_inflate_end(&st->z); | |
| st->z_state = ODB_LOOSE_READ_STREAM_DONE; | |
| break; | |
| } | |
| if (status != Z_OK && (status != Z_BUF_ERROR || total_read < sz)) { | |
| git_inflate_end(&st->z); | |
| st->z_state = ODB_LOOSE_READ_STREAM_ERROR; | |
| return -1; | |
| } | |
| } | |
| return total_read; | |
| } | |
| static int close_istream_loose(struct odb_stream *_st) | |
| { | |
| struct odb_loose_read_stream *st = | |
| container_of(_st, struct odb_loose_read_stream, base); | |
| if (st->z_state == ODB_LOOSE_READ_STREAM_INUSE) | |
| git_inflate_end(&st->z); | |
| munmap(st->mapped, st->mapsize); | |
| return 0; | |
| } | |
| static int odb_source_loose_read_object_stream(struct odb_stream **out, | |
| struct odb_source *source, | |
| const struct object_id *oid) | |
| { | |
| struct odb_source_loose *loose = odb_source_loose_downcast(source); | |
| struct object_info oi = OBJECT_INFO_INIT; | |
| struct odb_loose_read_stream *st; | |
| unsigned long mapsize; | |
| void *mapped; | |
| mapped = odb_source_loose_map_object(loose, oid, &mapsize); | |
| if (!mapped) | |
| return -1; | |
| /* | |
| * Note: we must allocate this structure early even though we may still | |
| * fail. This is because we need to initialize the zlib stream, and it | |
| * is not possible to copy the stream around after the fact because it | |
| * has self-referencing pointers. | |
| */ | |
| CALLOC_ARRAY(st, 1); | |
| switch (unpack_loose_header(&st->z, mapped, mapsize, st->hdr, | |
| sizeof(st->hdr))) { | |
| case ULHR_OK: | |
| break; | |
| case ULHR_BAD: | |
| case ULHR_TOO_LONG: | |
| goto error; | |
| } | |
| oi.sizep = &st->base.size; | |
| oi.typep = &st->base.type; | |
| if (parse_loose_header(st->hdr, &oi) < 0 || st->base.type < 0) | |
| goto error; | |
| st->mapped = mapped; | |
| st->mapsize = mapsize; | |
| st->hdr_used = strlen(st->hdr) + 1; | |
| st->hdr_avail = st->z.total_out; | |
| st->z_state = ODB_LOOSE_READ_STREAM_INUSE; | |
| st->base.close = close_istream_loose; | |
| st->base.read = read_istream_loose; | |
| *out = &st->base; | |
| return 0; | |
| error: | |
| git_inflate_end(&st->z); | |
| munmap(mapped, mapsize); | |
| free(st); | |
| return -1; | |
| } | |
| struct for_each_object_wrapper_data { | |
| struct odb_source_loose *loose; | |
| const struct object_info *request; | |
| odb_for_each_object_cb cb; | |
| void *cb_data; | |
| }; | |
| static int for_each_object_wrapper_cb(const struct object_id *oid, | |
| const char *path, | |
| void *cb_data) | |
| { | |
| struct for_each_object_wrapper_data *data = cb_data; | |
| if (data->request) { | |
| struct object_info oi = *data->request; | |
| if (read_object_info_from_path(data->loose, path, oid, &oi, 0, NULL) < 0) | |
| return -1; | |
| return data->cb(oid, &oi, data->cb_data); | |
| } else { | |
| return data->cb(oid, NULL, data->cb_data); | |
| } | |
| } | |
| static int for_each_prefixed_object_wrapper_cb(const struct object_id *oid, | |
| void *node_data UNUSED, | |
| void *cb_data) | |
| { | |
| struct for_each_object_wrapper_data *data = cb_data; | |
| if (data->request) { | |
| struct object_info oi = *data->request; | |
| if (odb_source_read_object_info(&data->loose->base, | |
| oid, &oi, 0, NULL) < 0) | |
| return -1; | |
| return data->cb(oid, &oi, data->cb_data); | |
| } else { | |
| return data->cb(oid, NULL, data->cb_data); | |
| } | |
| } | |
| static int odb_source_loose_for_each_object(struct odb_source *source, | |
| const struct object_info *request, | |
| odb_for_each_object_cb cb, | |
| void *cb_data, | |
| const struct odb_for_each_object_options *opts) | |
| { | |
| struct odb_source_loose *loose = odb_source_loose_downcast(source); | |
| struct for_each_object_wrapper_data data = { | |
| .loose = loose, | |
| .request = request, | |
| .cb = cb, | |
| .cb_data = cb_data, | |
| }; | |
| /* There are no loose promisor objects, so we can return immediately. */ | |
| if ((opts->flags & ODB_FOR_EACH_OBJECT_PROMISOR_ONLY)) | |
| return 0; | |
| if ((opts->flags & ODB_FOR_EACH_OBJECT_LOCAL_ONLY) && !source->local) | |
| return 0; | |
| if (opts->prefix) | |
| return oidtree_each(odb_source_loose_cache(loose, opts->prefix), | |
| opts->prefix, opts->prefix_hex_len, | |
| for_each_prefixed_object_wrapper_cb, &data); | |
| return for_each_loose_file_in_source(source, for_each_object_wrapper_cb, | |
| NULL, NULL, &data); | |
| } | |
| struct find_abbrev_len_data { | |
| const struct object_id *oid; | |
| unsigned len; | |
| }; | |
| static int find_abbrev_len_cb(const struct object_id *oid, | |
| struct object_info *oi UNUSED, | |
| void *cb_data) | |
| { | |
| struct find_abbrev_len_data *data = cb_data; | |
| unsigned len = oid_common_prefix_hexlen(oid, data->oid); | |
| if (len != hash_algos[oid->algo].hexsz && len >= data->len) | |
| data->len = len + 1; | |
| return 0; | |
| } | |
| static int odb_source_loose_find_abbrev_len(struct odb_source *source, | |
| const struct object_id *oid, | |
| unsigned min_len, | |
| unsigned *out) | |
| { | |
| struct odb_source_loose *loose = odb_source_loose_downcast(source); | |
| struct odb_for_each_object_options opts = { | |
| .prefix = oid, | |
| .prefix_hex_len = min_len, | |
| }; | |
| struct find_abbrev_len_data data = { | |
| .oid = oid, | |
| .len = min_len, | |
| }; | |
| int ret; | |
| ret = odb_source_for_each_object(&loose->base, NULL, find_abbrev_len_cb, | |
| &data, &opts); | |
| *out = data.len; | |
| return ret; | |
| } | |
| static int count_loose_object(const struct object_id *oid UNUSED, | |
| struct object_info *oi UNUSED, | |
| void *payload) | |
| { | |
| unsigned long *count = payload; | |
| (*count)++; | |
| return 0; | |
| } | |
| static int odb_source_loose_count_objects(struct odb_source *source, | |
| enum odb_count_objects_flags flags, | |
| unsigned long *out) | |
| { | |
| struct odb_source_loose *loose = odb_source_loose_downcast(source); | |
| const unsigned hexsz = source->odb->repo->hash_algo->hexsz - 2; | |
| char *path = NULL; | |
| DIR *dir = NULL; | |
| int ret; | |
| if (flags & ODB_COUNT_OBJECTS_APPROXIMATE) { | |
| unsigned long count = 0; | |
| struct dirent *ent; | |
| path = xstrfmt("%s/17", source->path); | |
| dir = opendir(path); | |
| if (!dir) { | |
| if (errno == ENOENT) { | |
| *out = 0; | |
| ret = 0; | |
| goto out; | |
| } | |
| ret = error_errno("cannot open object shard '%s'", path); | |
| goto out; | |
| } | |
| while ((ent = readdir(dir)) != NULL) { | |
| if (strspn(ent->d_name, "0123456789abcdef") != hexsz || | |
| ent->d_name[hexsz] != '\0') | |
| continue; | |
| count++; | |
| } | |
| *out = count * 256; | |
| ret = 0; | |
| } else { | |
| struct odb_for_each_object_options opts = { 0 }; | |
| *out = 0; | |
| ret = odb_source_for_each_object(&loose->base, NULL, count_loose_object, | |
| out, &opts); | |
| } | |
| out: | |
| if (dir) | |
| closedir(dir); | |
| free(path); | |
| return ret; | |
| } | |
| static int odb_source_loose_freshen_object(struct odb_source *source, | |
| const struct object_id *oid, | |
| const time_t *mtime) | |
| { | |
| struct odb_source_loose *loose = odb_source_loose_downcast(source); | |
| static struct strbuf path = STRBUF_INIT; | |
| odb_loose_path(loose, &path, oid); | |
| return !!check_and_freshen_file(path.buf, 1, mtime); | |
| } | |
| /* Finalize a file on disk, and close it. */ | |
| static void close_loose_object(struct odb_source_loose *loose, | |
| int fd, const char *filename) | |
| { | |
| if (loose->base.will_destroy) | |
| goto out; | |
| if (batch_fsync_enabled(FSYNC_COMPONENT_LOOSE_OBJECT)) | |
| odb_transaction_files_fsync(loose->base.odb->transaction, fd, filename); | |
| else if (fsync_object_files > 0) | |
| fsync_or_die(fd, filename); | |
| else | |
| fsync_component_or_die(FSYNC_COMPONENT_LOOSE_OBJECT, fd, | |
| filename); | |
| out: | |
| if (close(fd) != 0) | |
| die_errno(_("error when closing loose object file")); | |
| } | |
| /* Size of directory component, including the ending '/' */ | |
| static inline int directory_size(const char *filename) | |
| { | |
| const char *s = strrchr(filename, '/'); | |
| if (!s) | |
| return 0; | |
| return s - filename + 1; | |
| } | |
| /* | |
| * This creates a temporary file in the same directory as the final | |
| * 'filename' | |
| * | |
| * We want to avoid cross-directory filename renames, because those | |
| * can have problems on various filesystems (FAT, NFS, Coda). | |
| */ | |
| static int create_tmpfile(struct repository *repo, | |
| struct strbuf *tmp, const char *filename) | |
| { | |
| int fd, dirlen = directory_size(filename); | |
| strbuf_reset(tmp); | |
| strbuf_add(tmp, filename, dirlen); | |
| strbuf_addstr(tmp, "tmp_obj_XXXXXX"); | |
| fd = git_mkstemp_mode(tmp->buf, 0444); | |
| if (fd < 0 && dirlen && errno == ENOENT) { | |
| /* | |
| * Make sure the directory exists; note that the contents | |
| * of the buffer are undefined after mkstemp returns an | |
| * error, so we have to rewrite the whole buffer from | |
| * scratch. | |
| */ | |
| strbuf_reset(tmp); | |
| strbuf_add(tmp, filename, dirlen - 1); | |
| if (mkdir(tmp->buf, 0777) && errno != EEXIST) | |
| return -1; | |
| if (adjust_shared_perm(repo, tmp->buf)) | |
| return -1; | |
| /* Try again */ | |
| strbuf_addstr(tmp, "/tmp_obj_XXXXXX"); | |
| fd = git_mkstemp_mode(tmp->buf, 0444); | |
| } | |
| return fd; | |
| } | |
| /** | |
| * Common steps for loose object writers to start writing loose | |
| * objects: | |
| * | |
| * - Create tmpfile for the loose object. | |
| * - Setup zlib stream for compression. | |
| * - Start to feed header to zlib stream. | |
| * | |
| * Returns a "fd", which should later be provided to | |
| * end_loose_object_common(). | |
| */ | |
| static int start_loose_object_common(struct odb_source_loose *loose, | |
| struct strbuf *tmp_file, | |
| const char *filename, unsigned flags, | |
| git_zstream *stream, | |
| unsigned char *buf, size_t buflen, | |
| struct git_hash_ctx *c, struct git_hash_ctx *compat_c, | |
| char *hdr, int hdrlen) | |
| { | |
| const struct git_hash_algo *algo = loose->base.odb->repo->hash_algo; | |
| const struct git_hash_algo *compat = loose->base.odb->repo->compat_hash_algo; | |
| int fd; | |
| struct repo_config_values *cfg = repo_config_values(loose->base.odb->repo); | |
| fd = create_tmpfile(loose->base.odb->repo, tmp_file, filename); | |
| if (fd < 0) { | |
| if (flags & ODB_WRITE_OBJECT_SILENT) | |
| return -1; | |
| else if (errno == EACCES) | |
| return error(_("insufficient permission for adding " | |
| "an object to repository database %s"), | |
| loose->base.path); | |
| else | |
| return error_errno( | |
| _("unable to create temporary file")); | |
| } | |
| /* Setup zlib stream for compression */ | |
| git_deflate_init(stream, cfg->zlib_compression_level); | |
| stream->next_out = buf; | |
| stream->avail_out = buflen; | |
| git_hash_init(c, algo); | |
| if (compat && compat_c) | |
| git_hash_init(compat_c, compat); | |
| /* Start to feed header to zlib stream */ | |
| stream->next_in = (unsigned char *)hdr; | |
| stream->avail_in = hdrlen; | |
| while (git_deflate(stream, 0) == Z_OK) | |
| ; /* nothing */ | |
| git_hash_update(c, hdr, hdrlen); | |
| if (compat && compat_c) | |
| git_hash_update(compat_c, hdr, hdrlen); | |
| return fd; | |
| } | |
| /** | |
| * Common steps for the inner git_deflate() loop for writing loose | |
| * objects. Returns what git_deflate() returns. | |
| */ | |
| static int write_loose_object_common(struct odb_source_loose *loose, | |
| struct git_hash_ctx *c, struct git_hash_ctx *compat_c, | |
| git_zstream *stream, const int flush, | |
| unsigned char *in0, const int fd, | |
| unsigned char *compressed, | |
| const size_t compressed_len) | |
| { | |
| const struct git_hash_algo *compat = loose->base.odb->repo->compat_hash_algo; | |
| int ret; | |
| ret = git_deflate(stream, flush ? Z_FINISH : 0); | |
| git_hash_update(c, in0, stream->next_in - in0); | |
| if (compat && compat_c) | |
| git_hash_update(compat_c, in0, stream->next_in - in0); | |
| if (write_in_full(fd, compressed, stream->next_out - compressed) < 0) | |
| die_errno(_("unable to write loose object file")); | |
| stream->next_out = compressed; | |
| stream->avail_out = compressed_len; | |
| return ret; | |
| } | |
| /** | |
| * Common steps for loose object writers to end writing loose objects: | |
| * | |
| * - End the compression of zlib stream. | |
| * - Get the calculated oid to "oid". | |
| */ | |
| static int end_loose_object_common(struct odb_source_loose *loose, | |
| struct git_hash_ctx *c, struct git_hash_ctx *compat_c, | |
| git_zstream *stream, struct object_id *oid, | |
| struct object_id *compat_oid) | |
| { | |
| const struct git_hash_algo *compat = loose->base.odb->repo->compat_hash_algo; | |
| int ret; | |
| ret = git_deflate_end_gently(stream); | |
| if (ret != Z_OK) | |
| return ret; | |
| git_hash_final_oid(oid, c); | |
| if (compat && compat_c) | |
| git_hash_final_oid(compat_oid, compat_c); | |
| return Z_OK; | |
| } | |
| static int write_loose_object(struct odb_source_loose *loose, | |
| const struct object_id *oid, char *hdr, | |
| int hdrlen, const void *buf, unsigned long len, | |
| const time_t *mtime, unsigned flags) | |
| { | |
| int fd, ret; | |
| unsigned char compressed[4096]; | |
| git_zstream stream; | |
| struct git_hash_ctx c; | |
| struct object_id parano_oid; | |
| static struct strbuf tmp_file = STRBUF_INIT; | |
| static struct strbuf filename = STRBUF_INIT; | |
| if (batch_fsync_enabled(FSYNC_COMPONENT_LOOSE_OBJECT)) | |
| odb_transaction_files_prepare(loose->base.odb->transaction); | |
| odb_loose_path(loose, &filename, oid); | |
| fd = start_loose_object_common(loose, &tmp_file, filename.buf, flags, | |
| &stream, compressed, sizeof(compressed), | |
| &c, NULL, hdr, hdrlen); | |
| if (fd < 0) | |
| return -1; | |
| /* Then the data itself.. */ | |
| stream.next_in = (void *)buf; | |
| stream.avail_in = len; | |
| do { | |
| unsigned char *in0 = stream.next_in; | |
| ret = write_loose_object_common(loose, &c, NULL, &stream, 1, in0, fd, | |
| compressed, sizeof(compressed)); | |
| } while (ret == Z_OK); | |
| if (ret != Z_STREAM_END) | |
| die(_("unable to deflate new object %s (%d)"), oid_to_hex(oid), | |
| ret); | |
| ret = end_loose_object_common(loose, &c, NULL, &stream, ¶no_oid, NULL); | |
| if (ret != Z_OK) | |
| die(_("deflateEnd on object %s failed (%d)"), oid_to_hex(oid), | |
| ret); | |
| if (!oideq(oid, ¶no_oid)) | |
| die(_("confused by unstable object source data for %s"), | |
| oid_to_hex(oid)); | |
| close_loose_object(loose, fd, tmp_file.buf); | |
| if (mtime) { | |
| struct utimbuf utb = { | |
| .actime = *mtime, | |
| .modtime = *mtime, | |
| }; | |
| if (utime(tmp_file.buf, &utb) < 0 && | |
| !(flags & ODB_WRITE_OBJECT_SILENT)) | |
| warning_errno(_("failed utime() on %s"), tmp_file.buf); | |
| } | |
| return finalize_object_file_flags(loose->base.odb->repo, tmp_file.buf, filename.buf, | |
| FOF_SKIP_COLLISION_CHECK); | |
| } | |
| static int odb_source_loose_write_object(struct odb_source *source, | |
| const void *buf, size_t len, | |
| enum object_type type, | |
| const struct object_id *oid, | |
| const struct object_id *compat_oid, | |
| const time_t *mtime, | |
| enum odb_write_object_flags flags) | |
| { | |
| struct odb_source_loose *loose = odb_source_loose_downcast(source); | |
| char hdr[MAX_HEADER_LEN]; | |
| int hdrlen; | |
| hdrlen = format_object_header(hdr, sizeof(hdr), type, len); | |
| if (write_loose_object(loose, oid, hdr, hdrlen, buf, len, mtime, flags)) | |
| return -1; | |
| if (compat_oid) | |
| return repo_add_loose_object_map(loose, oid, compat_oid); | |
| return 0; | |
| } | |
| static int odb_source_loose_write_object_stream(struct odb_source *source, | |
| struct odb_stream *in_stream, | |
| struct object_id *oid) | |
| { | |
| struct odb_source_loose *loose = odb_source_loose_downcast(source); | |
| const struct git_hash_algo *compat = loose->base.odb->repo->compat_hash_algo; | |
| struct object_id compat_oid; | |
| int fd, ret, err = 0, flush = 0; | |
| unsigned char compressed[4096]; | |
| git_zstream stream; | |
| struct git_hash_ctx c, compat_c; | |
| struct strbuf tmp_file = STRBUF_INIT; | |
| struct strbuf filename = STRBUF_INIT; | |
| unsigned char buf[8192]; | |
| int dirlen; | |
| bool is_finished = false; | |
| char hdr[MAX_HEADER_LEN]; | |
| int hdrlen; | |
| if (batch_fsync_enabled(FSYNC_COMPONENT_LOOSE_OBJECT)) | |
| odb_transaction_files_prepare(loose->base.odb->transaction); | |
| /* Since oid is not determined, save tmp file to odb path. */ | |
| strbuf_addf(&filename, "%s/", loose->base.path); | |
| hdrlen = format_object_header(hdr, sizeof(hdr), in_stream->type, in_stream->size); | |
| /* | |
| * Common steps for write_loose_object and stream_loose_object to | |
| * start writing loose objects: | |
| * | |
| * - Create tmpfile for the loose object. | |
| * - Setup zlib stream for compression. | |
| * - Start to feed header to zlib stream. | |
| */ | |
| fd = start_loose_object_common(loose, &tmp_file, filename.buf, 0, | |
| &stream, compressed, sizeof(compressed), | |
| &c, &compat_c, hdr, hdrlen); | |
| if (fd < 0) { | |
| err = -1; | |
| goto cleanup; | |
| } | |
| /* Then the data itself.. */ | |
| do { | |
| unsigned char *in0 = stream.next_in; | |
| if (!stream.avail_in && !is_finished) { | |
| ssize_t read_len = odb_stream_read(in_stream, buf, | |
| sizeof(buf)); | |
| if (read_len < 0) { | |
| close(fd); | |
| err = -1; | |
| goto cleanup; | |
| } | |
| /* All data has been read. */ | |
| if (!read_len) { | |
| is_finished = true; | |
| flush = 1; | |
| } | |
| stream.avail_in = read_len; | |
| stream.next_in = buf; | |
| in0 = buf; | |
| } | |
| ret = write_loose_object_common(loose, &c, &compat_c, &stream, flush, in0, fd, | |
| compressed, sizeof(compressed)); | |
| /* | |
| * Unlike write_loose_object(), we do not have the entire | |
| * buffer. If we get Z_BUF_ERROR due to too few input bytes, | |
| * then we'll replenish them in the next input_stream->read() | |
| * call when we loop. | |
| */ | |
| } while (ret == Z_OK || ret == Z_BUF_ERROR); | |
| if (stream.total_in != in_stream->size + hdrlen) | |
| die(_("write stream object %"PRIuMAX" != %"PRIuMAX), (uintmax_t)stream.total_in, | |
| (uintmax_t)in_stream->size + hdrlen); | |
| /* | |
| * Common steps for write_loose_object and stream_loose_object to | |
| * end writing loose object: | |
| * | |
| * - End the compression of zlib stream. | |
| * - Get the calculated oid. | |
| */ | |
| if (ret != Z_STREAM_END) | |
| die(_("unable to stream deflate new object (%d)"), ret); | |
| ret = end_loose_object_common(loose, &c, &compat_c, &stream, oid, &compat_oid); | |
| if (ret != Z_OK) | |
| die(_("deflateEnd on stream object failed (%d)"), ret); | |
| close_loose_object(loose, fd, tmp_file.buf); | |
| if (odb_freshen_object(loose->base.odb, oid)) { | |
| unlink_or_warn(tmp_file.buf); | |
| goto cleanup; | |
| } | |
| odb_loose_path(loose, &filename, oid); | |
| /* We finally know the object path, and create the missing dir. */ | |
| dirlen = directory_size(filename.buf); | |
| if (dirlen) { | |
| struct strbuf dir = STRBUF_INIT; | |
| strbuf_add(&dir, filename.buf, dirlen); | |
| if (safe_create_dir_in_gitdir(loose->base.odb->repo, dir.buf) && | |
| errno != EEXIST) { | |
| err = error_errno(_("unable to create directory %s"), dir.buf); | |
| strbuf_release(&dir); | |
| goto cleanup; | |
| } | |
| strbuf_release(&dir); | |
| } | |
| err = finalize_object_file_flags(loose->base.odb->repo, tmp_file.buf, filename.buf, | |
| FOF_SKIP_COLLISION_CHECK); | |
| if (!err && compat) | |
| err = repo_add_loose_object_map(loose, oid, &compat_oid); | |
| cleanup: | |
| strbuf_release(&tmp_file); | |
| strbuf_release(&filename); | |
| return err; | |
| } | |
| static int odb_source_loose_begin_transaction(struct odb_source *source UNUSED, | |
| struct odb_transaction **out UNUSED, | |
| enum odb_transaction_flags flags UNUSED) | |
| { | |
| /* TODO: this is a known omission that we'll want to address eventually. */ | |
| return error("loose source does not support transactions"); | |
| } | |
| static int odb_source_loose_read_alternates(struct odb_source *source UNUSED, | |
| struct strvec *out UNUSED) | |
| { | |
| return 0; | |
| } | |
| static int odb_source_loose_write_alternate(struct odb_source *source UNUSED, | |
| const char *alternate UNUSED) | |
| { | |
| return error("loose source does not support alternates"); | |
| } | |
| static void odb_source_loose_clear_cache(struct odb_source_loose *loose) | |
| { | |
| oidtree_clear(loose->cache); | |
| FREE_AND_NULL(loose->cache); | |
| memset(&loose->subdir_seen, 0, | |
| sizeof(loose->subdir_seen)); | |
| } | |
| static void odb_source_loose_prepare(struct odb_source *source, | |
| enum odb_prepare_flags flags) | |
| { | |
| struct odb_source_loose *loose = odb_source_loose_downcast(source); | |
| if (flags & ODB_PREPARE_FLUSH_CACHES) | |
| odb_source_loose_clear_cache(loose); | |
| } | |
| static void odb_source_loose_close(struct odb_source *source UNUSED) | |
| { | |
| /* Nothing to do. */ | |
| } | |
| static void odb_source_loose_reparent(const char *old_cwd, | |
| const char *new_cwd, | |
| void *cb_data) | |
| { | |
| struct odb_source_loose *loose = cb_data; | |
| char *path = reparent_relative_path(old_cwd, new_cwd, | |
| loose->base.path); | |
| free(loose->base.path); | |
| loose->base.path = path; | |
| } | |
| static void odb_source_loose_free(struct odb_source *source) | |
| { | |
| struct odb_source_loose *loose = odb_source_loose_downcast(source); | |
| odb_source_loose_clear_cache(loose); | |
| loose_object_map_clear(&loose->map); | |
| chdir_notify_unregister(odb_source_loose_reparent, loose); | |
| odb_source_release(&loose->base); | |
| free(loose); | |
| } | |
| struct fsck_loose_data { | |
| struct odb_source_loose *source; | |
| struct odb_fsck_options *opts; | |
| struct progress *progress; | |
| bool error_found; | |
| }; | |
| static int fsck_loose(const struct object_id *oid, const char *path, | |
| void *cb_data) | |
| { | |
| struct fsck_loose_data *data = cb_data; | |
| enum object_type type = OBJ_NONE; | |
| size_t size; | |
| void *contents = NULL; | |
| int eaten = 0; | |
| struct object_info oi = OBJECT_INFO_INIT; | |
| struct object_id real_oid = *null_oid(data->source->base.odb->repo->hash_algo); | |
| int err = 0; | |
| oi.sizep = &size; | |
| oi.typep = &type; | |
| if (read_loose_object(data->source->base.odb->repo, | |
| path, oid, &real_oid, &contents, &oi) < 0) { | |
| if (contents && !oideq(&real_oid, oid)) | |
| err = error(_("%s: hash-path mismatch, found at: %s"), | |
| oid_to_hex(&real_oid), path); | |
| else | |
| err = error(_("%s: object corrupt or missing: %s"), | |
| oid_to_hex(oid), path); | |
| } | |
| if (err < 0) | |
| goto out; | |
| if (!contents && type != OBJ_BLOB) | |
| BUG("read_loose_object streamed a non-blob"); | |
| if (data->opts->object_cb(oid, type, size, contents, &eaten, | |
| data->opts->object_payload)) { | |
| err = -1; | |
| goto out; | |
| } | |
| out: | |
| if (err) | |
| data->error_found = true; | |
| if (!eaten) | |
| free(contents); | |
| return 0; /* keep checking other objects, even if we saw an error */ | |
| } | |
| static int fsck_cruft(const char *basename, const char *path, | |
| void *data UNUSED) | |
| { | |
| if (!starts_with(basename, "tmp_obj_")) | |
| fprintf_ln(stderr, _("bad sha1 file: %s"), path); | |
| return 0; | |
| } | |
| static int fsck_subdir(unsigned int nr, const char *path UNUSED, void *cb_data) | |
| { | |
| struct fsck_loose_data *data = cb_data; | |
| display_progress(data->progress, nr + 1); | |
| return 0; | |
| } | |
| static int odb_source_loose_fsck(struct odb_source *source, | |
| struct odb_fsck_options *opts) | |
| { | |
| struct odb_source_loose *loose = odb_source_loose_downcast(source); | |
| struct fsck_loose_data data = { | |
| .source = loose, | |
| .opts = opts, | |
| }; | |
| if (opts->flags & ODB_FSCK_VERBOSE) | |
| fprintf_ln(stderr, _("Checking object directory")); | |
| if (opts->flags & ODB_FSCK_PROGRESS) | |
| data.progress = start_progress(source->odb->repo, | |
| _("Checking object directories"), 256); | |
| for_each_loose_file_in_source(source, fsck_loose, | |
| fsck_cruft, fsck_subdir, &data); | |
| display_progress(data.progress, 256); | |
| stop_progress(&data.progress); | |
| return data.error_found ? -1 : 0; | |
| } | |
| struct odb_source_loose *odb_source_loose_new(struct object_database *odb, | |
| const char *path, | |
| bool local) | |
| { | |
| struct odb_source_loose *loose; | |
| CALLOC_ARRAY(loose, 1); | |
| odb_source_init(&loose->base, odb, ODB_SOURCE_LOOSE, path, local); | |
| loose->base.free = odb_source_loose_free; | |
| loose->base.close = odb_source_loose_close; | |
| loose->base.prepare = odb_source_loose_prepare; | |
| loose->base.fsck = odb_source_loose_fsck; | |
| loose->base.read_object_info = odb_source_loose_read_object_info; | |
| loose->base.read_object_stream = odb_source_loose_read_object_stream; | |
| loose->base.for_each_object = odb_source_loose_for_each_object; | |
| loose->base.find_abbrev_len = odb_source_loose_find_abbrev_len; | |
| loose->base.count_objects = odb_source_loose_count_objects; | |
| loose->base.freshen_object = odb_source_loose_freshen_object; | |
| loose->base.write_object = odb_source_loose_write_object; | |
| loose->base.write_object_stream = odb_source_loose_write_object_stream; | |
| loose->base.begin_transaction = odb_source_loose_begin_transaction; | |
| loose->base.read_alternates = odb_source_loose_read_alternates; | |
| loose->base.write_alternate = odb_source_loose_write_alternate; | |
| if (!is_absolute_path(loose->base.path)) | |
| chdir_notify_register(odb_source_loose_reparent, loose); | |
| loose_object_map_load(loose); | |
| return loose; | |
| } |