Coverage Report

Created: 2026-10-17 07:15

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