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 | free(f->buffer); |
61 | 0 | free(f->check_buffer); |
62 | 0 | free(f); |
63 | 0 | } |
64 | | |
65 | | int finalize_hashfile(struct hashfile *f, unsigned char *result, |
66 | | enum fsync_component component, unsigned int flags) |
67 | 0 | { |
68 | 0 | int fd; |
69 | |
|
70 | 0 | hashflush(f); |
71 | |
|
72 | 0 | if (f->skip_hash) |
73 | 0 | hashclr(f->buffer, f->algop); |
74 | 0 | else |
75 | 0 | git_hash_final(f->buffer, &f->ctx); |
76 | |
|
77 | 0 | if (result) |
78 | 0 | hashcpy(result, f->buffer, f->algop); |
79 | 0 | if (flags & CSUM_HASH_IN_STREAM) |
80 | 0 | flush(f, f->buffer, f->algop->rawsz); |
81 | 0 | if (flags & CSUM_FSYNC) |
82 | 0 | fsync_component_or_die(component, f->fd, f->name); |
83 | 0 | if (flags & CSUM_CLOSE) { |
84 | 0 | if (close(f->fd)) |
85 | 0 | die_errno("%s: sha1 file error on close", f->name); |
86 | 0 | fd = 0; |
87 | 0 | } else |
88 | 0 | fd = f->fd; |
89 | 0 | if (0 <= f->check_fd) { |
90 | 0 | char discard; |
91 | 0 | int cnt = read_in_full(f->check_fd, &discard, 1); |
92 | 0 | if (cnt < 0) |
93 | 0 | die_errno("%s: error when reading the tail of sha1 file", |
94 | 0 | f->name); |
95 | 0 | if (cnt) |
96 | 0 | die("%s: sha1 file has trailing garbage", f->name); |
97 | 0 | if (close(f->check_fd)) |
98 | 0 | die_errno("%s: sha1 file error on close", f->name); |
99 | 0 | } |
100 | 0 | free_hashfile(f); |
101 | 0 | return fd; |
102 | 0 | } |
103 | | |
104 | | void discard_hashfile(struct hashfile *f) |
105 | 0 | { |
106 | 0 | if (0 <= f->check_fd) |
107 | 0 | close(f->check_fd); |
108 | 0 | if (0 <= f->fd) |
109 | 0 | close(f->fd); |
110 | 0 | free_hashfile(f); |
111 | 0 | } |
112 | | |
113 | | void hashwrite(struct hashfile *f, const void *buf, uint32_t count) |
114 | 0 | { |
115 | 0 | while (count) { |
116 | 0 | unsigned left = f->buffer_len - f->offset; |
117 | 0 | unsigned nr = count > left ? left : count; |
118 | |
|
119 | 0 | if (f->do_crc) |
120 | 0 | f->crc32 = crc32(f->crc32, buf, nr); |
121 | |
|
122 | 0 | 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 | 0 | if (!f->skip_hash) |
130 | 0 | git_hash_update(&f->ctx, buf, nr); |
131 | 0 | flush(f, buf, nr); |
132 | 0 | } else { |
133 | | /* |
134 | | * Copy to the hashfile's buffer, flushing only |
135 | | * if it became full. |
136 | | */ |
137 | 0 | memcpy(f->buffer + f->offset, buf, nr); |
138 | 0 | f->offset += nr; |
139 | 0 | left -= nr; |
140 | 0 | if (!left) |
141 | 0 | hashflush(f); |
142 | 0 | } |
143 | |
|
144 | 0 | count -= nr; |
145 | 0 | buf = (char *) buf + nr; |
146 | 0 | } |
147 | 0 | } |
148 | | |
149 | | struct hashfile *hashfd_check(const struct git_hash_algo *algop, |
150 | | const char *name) |
151 | 0 | { |
152 | 0 | int sink, check; |
153 | 0 | struct hashfile *f; |
154 | |
|
155 | 0 | sink = xopen("/dev/null", O_WRONLY); |
156 | 0 | check = xopen(name, O_RDONLY); |
157 | 0 | f = hashfd(algop, sink, name); |
158 | 0 | f->check_fd = check; |
159 | 0 | f->check_buffer = xmalloc(f->buffer_len); |
160 | |
|
161 | 0 | return f; |
162 | 0 | } |
163 | | |
164 | | struct hashfile *hashfd_ext(const struct git_hash_algo *algop, |
165 | | int fd, const char *name, |
166 | | const struct hashfd_options *opts) |
167 | 0 | { |
168 | 0 | struct hashfile *f = xmalloc(sizeof(*f)); |
169 | 0 | f->fd = fd; |
170 | 0 | f->check_fd = -1; |
171 | 0 | f->offset = 0; |
172 | 0 | f->total = 0; |
173 | 0 | f->tp = opts->progress; |
174 | 0 | f->name = name; |
175 | 0 | f->do_crc = 0; |
176 | 0 | f->skip_hash = 0; |
177 | |
|
178 | 0 | f->algop = unsafe_hash_algo(algop); |
179 | 0 | f->algop->init_fn(&f->ctx); |
180 | |
|
181 | 0 | f->buffer_len = opts->buffer_len ? opts->buffer_len : 128 * 1024; |
182 | 0 | f->buffer = xmalloc(f->buffer_len); |
183 | 0 | f->check_buffer = NULL; |
184 | |
|
185 | 0 | return f; |
186 | 0 | } |
187 | | |
188 | | struct hashfile *hashfd(const struct git_hash_algo *algop, |
189 | | int fd, const char *name) |
190 | 0 | { |
191 | | /* |
192 | | * Since we are not going to use a progress meter to |
193 | | * measure the rate of data passing through this hashfile, |
194 | | * use a larger buffer size to reduce fsync() calls. |
195 | | */ |
196 | 0 | struct hashfd_options opts = { 0 }; |
197 | 0 | return hashfd_ext(algop, fd, name, &opts); |
198 | 0 | } |
199 | | |
200 | | void hashfile_checkpoint_init(struct hashfile *f, |
201 | | struct hashfile_checkpoint *checkpoint) |
202 | 0 | { |
203 | 0 | memset(checkpoint, 0, sizeof(*checkpoint)); |
204 | 0 | f->algop->init_fn(&checkpoint->ctx); |
205 | 0 | } |
206 | | |
207 | | void hashfile_checkpoint(struct hashfile *f, struct hashfile_checkpoint *checkpoint) |
208 | 0 | { |
209 | 0 | hashflush(f); |
210 | 0 | checkpoint->offset = f->total; |
211 | 0 | git_hash_clone(&checkpoint->ctx, &f->ctx); |
212 | 0 | } |
213 | | |
214 | | int hashfile_truncate(struct hashfile *f, struct hashfile_checkpoint *checkpoint) |
215 | 0 | { |
216 | 0 | off_t offset = checkpoint->offset; |
217 | |
|
218 | 0 | if (ftruncate(f->fd, offset) || |
219 | 0 | lseek(f->fd, offset, SEEK_SET) != offset) |
220 | 0 | return -1; |
221 | 0 | f->total = offset; |
222 | 0 | git_hash_clone(&f->ctx, &checkpoint->ctx); |
223 | 0 | f->offset = 0; /* hashflush() was called in checkpoint */ |
224 | 0 | return 0; |
225 | 0 | } |
226 | | |
227 | | void crc32_begin(struct hashfile *f) |
228 | 0 | { |
229 | 0 | f->crc32 = crc32(0, NULL, 0); |
230 | 0 | f->do_crc = 1; |
231 | 0 | } |
232 | | |
233 | | uint32_t crc32_end(struct hashfile *f) |
234 | 0 | { |
235 | 0 | f->do_crc = 0; |
236 | 0 | return f->crc32; |
237 | 0 | } |
238 | | |
239 | | int hashfile_checksum_valid(const struct git_hash_algo *algop, |
240 | | const unsigned char *data, size_t total_len) |
241 | 0 | { |
242 | 0 | unsigned char got[GIT_MAX_RAWSZ]; |
243 | 0 | struct git_hash_ctx ctx; |
244 | 0 | size_t data_len = total_len - algop->rawsz; |
245 | |
|
246 | 0 | algop = unsafe_hash_algo(algop); |
247 | |
|
248 | 0 | if (total_len < algop->rawsz) |
249 | 0 | return 0; /* say "too short"? */ |
250 | | |
251 | 0 | algop->init_fn(&ctx); |
252 | 0 | git_hash_update(&ctx, data, data_len); |
253 | 0 | git_hash_final(got, &ctx); |
254 | |
|
255 | 0 | return hasheq(got, data + data_len, algop); |
256 | 0 | } |