2 * Licensed under a two-clause BSD-style license.
3 * See LICENSE for details.
8 #include "git-compat-util.h"
10 #include "line_buffer.h"
15 * See http://svn.apache.org/repos/asf/subversion/trunks/notes/svndiff.
17 * svndiff0 ::= 'SVN\0' window window*;
18 * window ::= int int int int int instructions inline_data;
19 * instructions ::= instruction*;
20 * instruction ::= view_selector int int
22 * | packed_view_selector int
23 * | packed_copyfrom_data
25 * view_selector ::= copyfrom_source
28 * copyfrom_source ::= # binary 00 000000;
29 * copyfrom_target ::= # binary 01 000000;
30 * copyfrom_data ::= # binary 10 000000;
31 * packed_view_selector ::= # view_selector OR-ed with 6 bit value;
32 * packed_copyfrom_data ::= # copyfrom_data OR-ed with 6 bit value;
33 * int ::= highdigit* lowdigit;
34 * highdigit ::= # binary 10000000 OR-ed with 7 bit value;
35 * lowdigit ::= # 7 bit value;
38 #define SVNDIFF_MAGIC "SVN\0"
40 #define INSN_MASK 0xc0
41 #define INSN_COPYFROM_SOURCE 0000
42 #define INSN_COPYFROM_TARGET 0x40
43 #define INSN_COPYFROM_DATA 0x80
44 #define OPERAND_MASK 0x3f
46 #define VLI_CONTINUE 0x80
47 #define VLI_DIGIT_MASK 0x7f
48 #define VLI_BITS_PER_DIGIT 7
65 const char *instructions
;
66 size_t instructions_len
;
71 static size_t fskip(FILE *file
, size_t nbytes
)
73 static char buf
[4096];
77 * Try to throw away nbytes bytes.
79 while (done
< nbytes
) {
80 size_t n
= nbytes
- done
;
85 in
= fread(buf
, 1, n
, file
);
93 static int move_window(struct view
*view
, size_t newoff
, size_t newlen
)
95 size_t oldoff
, oldlen
, len
;
98 if (newoff
> SIZE_MAX
- newlen
)
99 return error("Source file is too big: "
100 "%"PRIu64
" + %"PRIu64
" > SIZE_MAX",
101 (uint64_t) newoff
, (uint64_t) newlen
);
102 assert(view
&& view
->file
);
108 assert(oldoff
<= SIZE_MAX
- oldlen
);
109 assert(!oldlen
|| oldbuf
);
110 if (newoff
< oldoff
|| newoff
+ newlen
< oldoff
+ oldlen
)
111 return error("Corrupt delta? Window slides left.");
115 view
->buf
= buf
= malloc(newlen
);
117 return error("Address space exhausted; "
118 "no room for source window");
119 if (newoff
< oldoff
+ oldlen
) {
120 const size_t overlap
= oldoff
+ oldlen
- newoff
;
121 buf
= mempcpy(buf
, oldbuf
+ oldlen
- overlap
, overlap
);
124 if (oldoff
+ oldlen
< newoff
) {
125 const size_t gap
= newoff
- oldoff
- oldlen
;
126 if (fskip(view
->file
, gap
) != gap
)
127 return error("error seeking in source: %s",
130 len
= fread(buf
, 1, newlen
, view
->file
);
133 if (feof(view
->file
)) {
134 warning("Overlarge window specified; adjusting.");
135 view
->len
-= (newlen
- len
);
138 return error("error reading source: %s", strerror(errno
));
141 static int buf_parse_int(size_t *result
, const char **buf
, size_t *len
)
143 const char *p
= *buf
;
147 int ch
= (unsigned char) *p
++;
149 rv
<<= VLI_BITS_PER_DIGIT
;
150 rv
+= ch
& VLI_DIGIT_MASK
;
151 if (!(ch
& VLI_CONTINUE
)) {
158 return error("Corrupt delta? Argument with leading digits "
159 "%"PRIu64
" left unterminated.", (uint64_t) rv
);
162 static int parse_int(size_t *result
, size_t *len
)
167 int ch
= buffer_read_char();
171 rv
<<= VLI_BITS_PER_DIGIT
;
172 rv
+= (ch
& VLI_DIGIT_MASK
);
173 if (!(ch
& VLI_CONTINUE
)) {
179 return error("Corrupt delta? Integer with leading digits "
180 "%"PRIu64
" left unterminated.", (uint64_t) rv
);
183 static int copyfrom_source(char **out_pos
, struct window
*ctx
, size_t nbytes
)
187 if (buf_parse_int(&offset
, &ctx
->instructions
,
188 &ctx
->instructions_len
))
190 if (offset
> SIZE_MAX
- nbytes
)
191 return error("Source data is too long: "
192 "%"PRIu64
" + %"PRIu64
" > SIZE_MAX",
193 (uint64_t) offset
, (uint64_t) nbytes
);
194 if (offset
< ctx
->preimage_off
)
195 return error("Corrupt delta? Attempts to write source "
196 "data that has been forgotten already.");
197 if (ctx
->preimage_len
< nbytes
)
198 return error("Corrupt delta? Attempts to write source "
199 "data that has not been read yet.");
200 if (p
+ nbytes
> ctx
->out
+ ctx
->out_len
)
201 return error("Corrupt delta? Attempts to write "
202 "source data without making room for it.");
203 p
= mempcpy(p
, ctx
->preimage
- ctx
->preimage_off
+ offset
, nbytes
);
208 static int copyfrom_target(char **out_pos
, struct window
*ctx
, size_t nbytes
)
212 if (buf_parse_int(&offset
, &ctx
->instructions
,
213 &ctx
->instructions_len
))
215 if (offset
> SIZE_MAX
- nbytes
)
216 return error("Output data is too long: "
217 "%"PRIu64
" + %"PRIu64
" > SIZE_MAX",
218 (uint64_t) offset
, (uint64_t) nbytes
);
219 if (offset
< ctx
->out_off
)
220 return error("Corrupt delta? Attempts to copy target "
221 "data that has been written and forgotten.");
222 if (p
<= ctx
->out
- ctx
->out_off
+ offset
)
223 return error("Corrupt delta? Attempts to copy data "
224 "that has not been written yet.");
225 if (ctx
->out_len
< nbytes
)
226 return error("Corrupt delta? Attempts to copy target "
227 "data that has not even been allocated.");
228 if (p
+ nbytes
> ctx
->out
+ ctx
->out_len
)
229 return error("Corrupt delta? Attempts to copy target "
230 "data without making room for it.");
232 *p
++ = (ctx
->out
- ctx
->out_off
)[offset
++];
239 static int copyfrom_data(char **out_pos
, struct window
*ctx
,
240 size_t *data_pos
, size_t nbytes
)
243 size_t offset
= *data_pos
;
244 if (offset
> SIZE_MAX
- nbytes
)
245 return error("Inline data is too long: "
246 "%"PRIu64
" + %"PRIu64
" > SIZE_MAX",
247 (uint64_t) offset
, (uint64_t) nbytes
);
248 if (ctx
->data_len
< offset
+ nbytes
)
249 return error("Corrupt delta? Attempts to read inline "
250 "data where none exists.");
251 if (p
+ nbytes
> ctx
->out
+ ctx
->out_len
)
252 return error("Corrupt delta? Attempts to write inline "
253 "data without making room for it.");
254 *data_pos
= offset
+ nbytes
;
255 p
= mempcpy(p
, ctx
->data
+ offset
, nbytes
);
260 static int apply_one_instruction(struct window
*ctx
,
261 char **out
, size_t *data_pos
)
263 unsigned char instruction
;
266 assert(ctx
->instructions_len
);
267 assert(ctx
->instructions
);
269 instruction
= (unsigned char) *ctx
->instructions
;
271 ctx
->instructions_len
--;
272 nbytes
= instruction
& OPERAND_MASK
;
273 if (!nbytes
&& buf_parse_int(&nbytes
, &ctx
->instructions
,
274 &ctx
->instructions_len
))
276 switch (instruction
& INSN_MASK
) {
277 case INSN_COPYFROM_SOURCE
:
278 return copyfrom_source(out
, ctx
, nbytes
);
279 case INSN_COPYFROM_TARGET
:
280 return copyfrom_target(out
, ctx
, nbytes
);
281 case INSN_COPYFROM_DATA
:
282 return copyfrom_data(out
, ctx
, data_pos
, nbytes
);
284 return error("Delta contains invalid instruction %x",
285 (unsigned int) instruction
);
289 static int do_instructions(struct window
*ctx
)
292 size_t data_offset
= 0;
295 * Advance p while copying data from the source, target,
296 * and inline data views.
298 while (ctx
->instructions_len
)
299 if (apply_one_instruction(ctx
, &p
, &data_offset
))
304 static int apply_within(const struct view
*preimage
, size_t *out_offset
,
305 FILE *outf
, size_t *len_remaining
,
306 size_t *out_remaining
)
310 ctx
.preimage_len
= preimage
->len
;
311 ctx
.preimage_off
= preimage
->off
;
312 ctx
.preimage
= preimage
->buf
;
315 assert(len_remaining
);
319 * - "source view" offset and length (already handled);
320 * - "target view" length;
321 * - "instructions" length;
322 * - inline data length.
325 if (parse_int(&ctx
.out_len
, len_remaining
))
327 ctx
.out_off
= *out_offset
;
329 if (parse_int(&ctx
.instructions_len
, len_remaining
))
331 if (parse_int(&ctx
.data_len
, len_remaining
))
334 if (ctx
.instructions_len
> SIZE_MAX
- ctx
.data_len
)
335 return error("Instructions too long: "
336 "%"PRIu64
" + %"PRIu64
" > SIZE_MAX",
337 (uint64_t) ctx
.instructions_len
,
338 (uint64_t) ctx
.data_len
);
339 if (ctx
.instructions_len
> *len_remaining
)
340 warning("Delta instructions appear to be tructated "
341 "(%"PRIu64
" bytes out of %"PRIu64
")",
342 (uint64_t) *len_remaining
,
343 (uint64_t) ctx
.instructions_len
);
344 if (ctx
.instructions_len
+ ctx
.data_len
> *len_remaining
)
345 warning("Delta appears to be truncated");
347 ctx
.out
= malloc(ctx
.out_len
);
348 if (ctx
.out_len
&& !ctx
.out
)
349 return error("Address space exhausted; "
350 "no room for target window");
351 ctx
.out_off
= *out_offset
;
352 ctx
.instructions
= buffer_read_string(
353 ctx
.instructions_len
+ ctx
.data_len
);
354 *len_remaining
-= ctx
.instructions_len
+ ctx
.data_len
;
355 ctx
.data
= ctx
.instructions
+ ctx
.instructions_len
;
357 if (do_instructions(&ctx
)) {
361 if (ctx
.out_len
> *out_remaining
)
362 ctx
.out_len
= *out_remaining
;
363 if (fwrite(ctx
.out
, 1, ctx
.out_len
, outf
) == ctx
.out_len
) {
366 *out_offset
= ctx
.out_off
;
367 *out_remaining
-= ctx
.out_len
;
370 error("Error writing patched file: %s", strerror(errno
));
377 * Magic bytes ("SVN\0")
379 static int parse_magic(size_t *len
)
382 const size_t magic_len
= 4;
384 if (*len
< magic_len
)
385 return error("Invalid diff stream: no file type header");
386 buf
= buffer_read_string(magic_len
);
387 if (memcmp(buf
, SVNDIFF_MAGIC
, magic_len
))
388 return error("Unrecognized file type %s", buf
);
394 * Apply a delta from the file named by delta, truncated to len bytes.
396 int svndiff0_apply(const char *delta
, size_t len
, size_t out
, size_t in
,
397 FILE *preimage
, FILE *postimage
)
399 struct view preimage_view
= {preimage
, NULL
, 0, 0};
400 size_t out_offset
= 0;
402 assert(delta
&& preimage
&& postimage
);
403 if (buffer_init(delta
))
404 return error("cannot open %s: %s\n", delta
, strerror(errno
));
405 if (parse_magic(&len
))
407 while (len
> 0 && out
> 0) {
409 size_t pre_off
, pre_len
;
411 if (parse_int(&pre_off
, &len
))
413 if (parse_int(&pre_len
, &len
))
417 if (move_window(&preimage_view
, pre_off
, pre_len
))
420 if (apply_within(&preimage_view
, &out_offset
, postimage
, &len
, &out
))
422 if (ferror(stdin
) || feof(stdin
))
425 return buffer_deinit();