Git fork
at reftables-rust 268 lines 6.2 kB view raw
1/* 2 * csum-file.c 3 * 4 * Copyright (C) 2005 Linus Torvalds 5 * 6 * Simple file write infrastructure for writing SHA1-summed 7 * files. Useful when you write a file that you want to be 8 * able to verify hasn't been messed with afterwards. 9 */ 10 11#include "git-compat-util.h" 12#include "csum-file.h" 13#include "git-zlib.h" 14#include "hash.h" 15#include "progress.h" 16 17static void verify_buffer_or_die(struct hashfile *f, 18 const void *buf, 19 unsigned int count) 20{ 21 ssize_t ret = read_in_full(f->check_fd, f->check_buffer, count); 22 23 if (ret < 0) 24 die_errno("%s: sha1 file read error", f->name); 25 if ((size_t)ret != count) 26 die("%s: sha1 file truncated", f->name); 27 if (memcmp(buf, f->check_buffer, count)) 28 die("sha1 file '%s' validation error", f->name); 29} 30 31static void flush(struct hashfile *f, const void *buf, unsigned int count) 32{ 33 if (0 <= f->check_fd && count) 34 verify_buffer_or_die(f, buf, count); 35 36 if (write_in_full(f->fd, buf, count) < 0) { 37 if (errno == ENOSPC) 38 die("sha1 file '%s' write error. Out of diskspace", f->name); 39 die_errno("sha1 file '%s' write error", f->name); 40 } 41 42 f->total += count; 43 display_throughput(f->tp, f->total); 44} 45 46void hashflush(struct hashfile *f) 47{ 48 unsigned offset = f->offset; 49 50 if (offset) { 51 if (!f->skip_hash) 52 git_hash_update(&f->ctx, f->buffer, offset); 53 flush(f, f->buffer, offset); 54 f->offset = 0; 55 } 56} 57 58void free_hashfile(struct hashfile *f) 59{ 60 free(f->buffer); 61 free(f->check_buffer); 62 free(f); 63} 64 65int finalize_hashfile(struct hashfile *f, unsigned char *result, 66 enum fsync_component component, unsigned int flags) 67{ 68 int fd; 69 70 hashflush(f); 71 72 if (f->skip_hash) 73 hashclr(f->buffer, f->algop); 74 else 75 git_hash_final(f->buffer, &f->ctx); 76 77 if (result) 78 hashcpy(result, f->buffer, f->algop); 79 if (flags & CSUM_HASH_IN_STREAM) 80 flush(f, f->buffer, f->algop->rawsz); 81 if (flags & CSUM_FSYNC) 82 fsync_component_or_die(component, f->fd, f->name); 83 if (flags & CSUM_CLOSE) { 84 if (close(f->fd)) 85 die_errno("%s: sha1 file error on close", f->name); 86 fd = 0; 87 } else 88 fd = f->fd; 89 if (0 <= f->check_fd) { 90 char discard; 91 int cnt = read_in_full(f->check_fd, &discard, 1); 92 if (cnt < 0) 93 die_errno("%s: error when reading the tail of sha1 file", 94 f->name); 95 if (cnt) 96 die("%s: sha1 file has trailing garbage", f->name); 97 if (close(f->check_fd)) 98 die_errno("%s: sha1 file error on close", f->name); 99 } 100 free_hashfile(f); 101 return fd; 102} 103 104void discard_hashfile(struct hashfile *f) 105{ 106 if (0 <= f->check_fd) 107 close(f->check_fd); 108 if (0 <= f->fd) 109 close(f->fd); 110 free_hashfile(f); 111} 112 113void hashwrite(struct hashfile *f, const void *buf, unsigned int count) 114{ 115 while (count) { 116 unsigned left = f->buffer_len - f->offset; 117 unsigned nr = count > left ? left : count; 118 119 if (f->do_crc) 120 f->crc32 = crc32(f->crc32, buf, nr); 121 122 if (nr == f->buffer_len) { 123 /* 124 * Flush a full batch worth of data directly 125 * from the input, skipping the memcpy() to 126 * the hashfile's buffer. In this block, 127 * f->offset is necessarily zero. 128 */ 129 if (!f->skip_hash) 130 git_hash_update(&f->ctx, buf, nr); 131 flush(f, buf, nr); 132 } else { 133 /* 134 * Copy to the hashfile's buffer, flushing only 135 * if it became full. 136 */ 137 memcpy(f->buffer + f->offset, buf, nr); 138 f->offset += nr; 139 left -= nr; 140 if (!left) 141 hashflush(f); 142 } 143 144 count -= nr; 145 buf = (char *) buf + nr; 146 } 147} 148 149struct hashfile *hashfd_check(const struct git_hash_algo *algop, 150 const char *name) 151{ 152 int sink, check; 153 struct hashfile *f; 154 155 sink = xopen("/dev/null", O_WRONLY); 156 check = xopen(name, O_RDONLY); 157 f = hashfd(algop, sink, name); 158 f->check_fd = check; 159 f->check_buffer = xmalloc(f->buffer_len); 160 161 return f; 162} 163 164static struct hashfile *hashfd_internal(const struct git_hash_algo *algop, 165 int fd, const char *name, 166 struct progress *tp, 167 size_t buffer_len) 168{ 169 struct hashfile *f = xmalloc(sizeof(*f)); 170 f->fd = fd; 171 f->check_fd = -1; 172 f->offset = 0; 173 f->total = 0; 174 f->tp = tp; 175 f->name = name; 176 f->do_crc = 0; 177 f->skip_hash = 0; 178 179 f->algop = unsafe_hash_algo(algop); 180 f->algop->init_fn(&f->ctx); 181 182 f->buffer_len = buffer_len; 183 f->buffer = xmalloc(buffer_len); 184 f->check_buffer = NULL; 185 186 return f; 187} 188 189struct hashfile *hashfd(const struct git_hash_algo *algop, 190 int fd, const char *name) 191{ 192 /* 193 * Since we are not going to use a progress meter to 194 * measure the rate of data passing through this hashfile, 195 * use a larger buffer size to reduce fsync() calls. 196 */ 197 return hashfd_internal(algop, fd, name, NULL, 128 * 1024); 198} 199 200struct hashfile *hashfd_throughput(const struct git_hash_algo *algop, 201 int fd, const char *name, struct progress *tp) 202{ 203 /* 204 * Since we are expecting to report progress of the 205 * write into this hashfile, use a smaller buffer 206 * size so the progress indicators arrive at a more 207 * frequent rate. 208 */ 209 return hashfd_internal(algop, fd, name, tp, 8 * 1024); 210} 211 212void hashfile_checkpoint_init(struct hashfile *f, 213 struct hashfile_checkpoint *checkpoint) 214{ 215 memset(checkpoint, 0, sizeof(*checkpoint)); 216 f->algop->init_fn(&checkpoint->ctx); 217} 218 219void hashfile_checkpoint(struct hashfile *f, struct hashfile_checkpoint *checkpoint) 220{ 221 hashflush(f); 222 checkpoint->offset = f->total; 223 git_hash_clone(&checkpoint->ctx, &f->ctx); 224} 225 226int hashfile_truncate(struct hashfile *f, struct hashfile_checkpoint *checkpoint) 227{ 228 off_t offset = checkpoint->offset; 229 230 if (ftruncate(f->fd, offset) || 231 lseek(f->fd, offset, SEEK_SET) != offset) 232 return -1; 233 f->total = offset; 234 git_hash_clone(&f->ctx, &checkpoint->ctx); 235 f->offset = 0; /* hashflush() was called in checkpoint */ 236 return 0; 237} 238 239void crc32_begin(struct hashfile *f) 240{ 241 f->crc32 = crc32(0, NULL, 0); 242 f->do_crc = 1; 243} 244 245uint32_t crc32_end(struct hashfile *f) 246{ 247 f->do_crc = 0; 248 return f->crc32; 249} 250 251int hashfile_checksum_valid(const struct git_hash_algo *algop, 252 const unsigned char *data, size_t total_len) 253{ 254 unsigned char got[GIT_MAX_RAWSZ]; 255 struct git_hash_ctx ctx; 256 size_t data_len = total_len - algop->rawsz; 257 258 algop = unsafe_hash_algo(algop); 259 260 if (total_len < algop->rawsz) 261 return 0; /* say "too short"? */ 262 263 algop->init_fn(&ctx); 264 git_hash_update(&ctx, data, data_len); 265 git_hash_final(got, &ctx); 266 267 return hasheq(got, data + data_len, algop); 268}