csum-file: introduce discard_hashfile()

The hashfile API is used to write out a "hashfile", which has a final checksum (typically SHA-1) at the end. An in-core hashfile structure has up to two file descriptors and a few buffers that can only be freed by calling a helper function that is private to the csum-file implementation. The usual flow of a user of the API is to first open a file descriptor for writing, obtain a hashfile associated with that write file descriptor by calling either hashfd() or hashfd_check(), call hashwrite() number of times to write data to the file, and then call finalize_hashfile(), which appends th checksum to the end of the file, closes file descriptors and releases associated buffers. But what if a caller finds some error after calling hashfd() to start the process and/or hashwrite() to send some data to the file, and wants to abort the operation? The underlying file descriptor is often managed by the tempfile API, so aborting will clean the file out of the filesystem, but the resources associated with the in-core hashfile structure is lost. Introduce discard_hashfile() API function to allow them to release the resources held by a hashfile structure the callers want to dispose of, and use that in read-cache.c:do_write_index(), which is a central place that writes the index file. Mark t2107 as leak-free, as this leak in "update-index --cacheinfo" test that deliberately makes it fail is now plugged. Signed-off-by: Junio C Hamano <gitster@pobox.com>

Junio C Hamano committed Jul 25, 2024 at 16:07 UTC c3d034df1606beca8a4ba3bc8a8266f4c711f23c
4 files changed +89 -21
csum-file.c
+9
@@ -102,6 +102,15 @@ int finalize_hashfile(struct hashfile *f, unsigned char *result,
102 return fd;
103 }
104
105 +void discard_hashfile(struct hashfile *f)
106 +{
107 + if (0 <= f->check_fd)
108 + close(f->check_fd);
109 + if (0 <= f->fd)
110 + close(f->fd);
111 + free_hashfile(f);
112 +}
113 +
114 void hashwrite(struct hashfile *f, const void *buf, unsigned int count)
115 {
116 while (count) {
csum-file.h
+1
@@ -47,6 +47,7 @@ struct hashfile *hashfd(int fd, const char *name);
47 struct hashfile *hashfd_check(const char *name);
48 struct hashfile *hashfd_throughput(int fd, const char *name, struct progress *tp);
49 int finalize_hashfile(struct hashfile *, unsigned char *, enum fsync_component, unsigned int);
50 +void discard_hashfile(struct hashfile *);
51 void hashwrite(struct hashfile *, const void *, unsigned int);
52 void hashflush(struct hashfile *f);
53 void crc32_begin(struct hashfile *);
read-cache.c
+78 -21
@@ -2963,7 +2963,7 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile,
2963
2964 if (err) {
2965 free(ieot);
2966 - return err;
2966 + goto cleanup;
2967 }
2968
2969 offset = hashfile_total(f);
@@ -2992,8 +2992,14 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile,
2992 hashwrite(f, sb.buf, sb.len);
2993 strbuf_release(&sb);
2994 free(ieot);
2995 - if (err)
2996 - return -1;
2995 + /*
2996 + * NEEDSWORK: write_index_ext_header() never returns a failure,
2997 + * and this part may want to be simplified.
2998 + */
2999 + if (err) {
3000 + err = -1;
3001 + goto cleanup;
3002 + }
3003 }
3004
3005 if (write_extensions & WRITE_SPLIT_INDEX_EXTENSION &&
@@ -3008,8 +3014,14 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile,
3014 sb.len) < 0;
3015 hashwrite(f, sb.buf, sb.len);
3016 strbuf_release(&sb);
3011 - if (err)
3012 - return -1;
3017 + /*
3018 + * NEEDSWORK: write_link_extension() never returns a failure,
3019 + * and this part may want to be simplified.
3020 + */
3021 + if (err) {
3022 + err = -1;
3023 + goto cleanup;
3024 + }
3025 }
3026 if (write_extensions & WRITE_CACHE_TREE_EXTENSION &&
3027 !drop_cache_tree && istate->cache_tree) {
@@ -3019,8 +3031,14 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile,
3031 err = write_index_ext_header(f, eoie_c, CACHE_EXT_TREE, sb.len) < 0;
3032 hashwrite(f, sb.buf, sb.len);
3033 strbuf_release(&sb);
3022 - if (err)
3023 - return -1;
3034 + /*
3035 + * NEEDSWORK: write_index_ext_header() never returns a failure,
3036 + * and this part may want to be simplified.
3037 + */
3038 + if (err) {
3039 + err = -1;
3040 + goto cleanup;
3041 + }
3042 }
3043 if (write_extensions & WRITE_RESOLVE_UNDO_EXTENSION &&
3044 istate->resolve_undo) {
@@ -3031,8 +3049,14 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile,
3049 sb.len) < 0;
3050 hashwrite(f, sb.buf, sb.len);
3051 strbuf_release(&sb);
3034 - if (err)
3035 - return -1;
3052 + /*
3053 + * NEEDSWORK: write_index_ext_header() never returns a failure,
3054 + * and this part may want to be simplified.
3055 + */
3056 + if (err) {
3057 + err = -1;
3058 + goto cleanup;
3059 + }
3060 }
3061 if (write_extensions & WRITE_UNTRACKED_CACHE_EXTENSION &&
3062 istate->untracked) {
@@ -3043,8 +3067,14 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile,
3067 sb.len) < 0;
3068 hashwrite(f, sb.buf, sb.len);
3069 strbuf_release(&sb);
3046 - if (err)
3047 - return -1;
3070 + /*
3071 + * NEEDSWORK: write_index_ext_header() never returns a failure,
3072 + * and this part may want to be simplified.
3073 + */
3074 + if (err) {
3075 + err = -1;
3076 + goto cleanup;
3077 + }
3078 }
3079 if (write_extensions & WRITE_FSMONITOR_EXTENSION &&
3080 istate->fsmonitor_last_update) {
@@ -3054,12 +3084,25 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile,
3084 err = write_index_ext_header(f, eoie_c, CACHE_EXT_FSMONITOR, sb.len) < 0;
3085 hashwrite(f, sb.buf, sb.len);
3086 strbuf_release(&sb);
3057 - if (err)
3058 - return -1;
3087 + /*
3088 + * NEEDSWORK: write_index_ext_header() never returns a failure,
3089 + * and this part may want to be simplified.
3090 + */
3091 + if (err) {
3092 + err = -1;
3093 + goto cleanup;
3094 + }
3095 }
3096 if (istate->sparse_index) {
3061 - if (write_index_ext_header(f, eoie_c, CACHE_EXT_SPARSE_DIRECTORIES, 0) < 0)
3062 - return -1;
3097 + err = write_index_ext_header(f, eoie_c, CACHE_EXT_SPARSE_DIRECTORIES, 0);
3098 + /*
3099 + * NEEDSWORK: write_index_ext_header() never returns a failure,
3100 + * and this part may want to be simplified.
3101 + */
3102 + if (err) {
3103 + err = -1;
3104 + goto cleanup;
3105 + }
3106 }
3107
3108 /*
@@ -3075,8 +3118,14 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile,
3118 err = write_index_ext_header(f, NULL, CACHE_EXT_ENDOFINDEXENTRIES, sb.len) < 0;
3119 hashwrite(f, sb.buf, sb.len);
3120 strbuf_release(&sb);
3078 - if (err)
3079 - return -1;
3121 + /*
3122 + * NEEDSWORK: write_index_ext_header() never returns a failure,
3123 + * and this part may want to be simplified.
3124 + */
3125 + if (err) {
3126 + err = -1;
3127 + goto cleanup;
3128 + }
3129 }
3130
3131 csum_fsync_flag = 0;
@@ -3085,13 +3134,16 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile,
3134
3135 finalize_hashfile(f, istate->oid.hash, FSYNC_COMPONENT_INDEX,
3136 CSUM_HASH_IN_STREAM | csum_fsync_flag);
3137 + f = NULL;
3138
3139 if (close_tempfile_gently(tempfile)) {
3090 - error(_("could not close '%s'"), get_tempfile_path(tempfile));
3091 - return -1;
3140 + err = error(_("could not close '%s'"), get_tempfile_path(tempfile));
3141 + goto cleanup;
3142 + }
3143 + if (stat(get_tempfile_path(tempfile), &st)) {
3144 + err = error_errno(_("could not stat '%s'"), get_tempfile_path(tempfile));
3145 + goto cleanup;
3146 }
3093 - if (stat(get_tempfile_path(tempfile), &st))
3094 - return -1;
3147 istate->timestamp.sec = (unsigned int)st.st_mtime;
3148 istate->timestamp.nsec = ST_MTIME_NSEC(st);
3149 trace_performance_since(start, "write index, changed mask = %x", istate->cache_changed);
@@ -3106,6 +3158,11 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile,
3158 istate->cache_nr);
3159
3160 return 0;
3161 +
3162 +cleanup:
3163 + if (f)
3164 + discard_hashfile(f);
3165 + return err;
3166 }
3167
3168 void set_alternate_index_output(const char *name)
t/t2107-update-index-basic.sh
+1
@@ -5,6 +5,7 @@ test_description='basic update-index tests
5 Tests for command-line parsing and basic operation.
6 '
7
8 +TEST_PASSES_SANITIZE_LEAK=true
9 . ./test-lib.sh
10
11 test_expect_success 'update-index --nonsense fails' '