forked from jmacd/xdelta
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathxdelta3.go
279 lines (251 loc) · 8.48 KB
/
xdelta3.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
// Copyright 2018 Northern.tech AS
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package xdelta
/*
// Load C library and configure build flags
#cgo LDFLAGS: -lxdelta3
#cgo lzma CFLAGS: -DSECONDARY_LZMA=1
#cgo fgk CFLAGS: -DSECONDARY_FGK=1
#cgo no-encoder CFLAGS: -DXD3_ENCODER=0
#cgo CFLAGS: -DSECONDARY_DJW=1
#ifndef SECONDARY_LZMA
# define SECONDARY_LZMA 0
#endif
#ifndef SECONDARY_FGK
# define SECONDARY_FGK 0
#endif
// Set window size to 16kB, equates to approx. 32kB memory usage.
#define XD3_ALLOCSIZE (1U << 16)
#include <xdelta3.h>
// These structures has to reside in the C code in order
// for the cgo checks to pass.
static xd3_stream stream;
static xd3_source source;
static xd3_config config;
static inline xd3_stream *getStream () {return &stream;}
static inline xd3_source *getSource () {return &source;}
static inline xd3_config *getConfig () {return &config;}
// Wrap C function pointers to make them accessible in Go.
// from xdelta.h :
// int xd3_decode_input (xd3_stream *);
// int xd3_encode_input (xd3_stream *);
typedef int (*codeFunc) (xd3_stream *);
// encode / decode callback
int _xd3_code(codeFunc f, xd3_stream * stream) { return f(stream); }
*/
import "C"
import (
"io"
"unsafe"
"github.com/pkg/errors"
)
// Buffer allocation size
const XD3_ALLOCSIZE = C.XD3_ALLOCSIZE
/**
+--------------------------+ (io.ReadCloser - device.go (image))
|artifact.mender (patch) |==InFile=====++
+--------------------------+ ||
+--------------------------+ || srcFile.doPatch(InFile) -> OutFile
|/dev/active-part (ro-rfs) |==srcFile====++===========++
+--------------------------+ (blockdevice - delta.go) ||
+--------------------------+ VV +------------------+
|/dev/inactive-part |==OutFile=================++====>|updated partition |
+--------------------------+ (blockdevice) +------------------+
*/
// XdeltaCoder.flags
const (
// compression flags
// DJW is enabled by default.
XD3_SECONDARY_DJW = C.XD3_SEC_DJW
// FGK is NOT enabled by default. Enabled with fgk build tag.
XD3_SECONDARY_FGK = C.XD3_SEC_FGK
// LZMA depends on `lzma` build tag and the library exists on your system
XD3_SECONDARY_LZMA = C.XD3_SEC_LZMA
XD3_COMPLEVEL_1 = (1 << 20)
XD3_COMPLEVEL_2 = (2 << 20)
XD3_COMPLEVEL_3 = (3 << 20)
XD3_COMPLEVEL_6 = (6 << 20)
XD3_COMPLEVEL_9 = (9 << 20)
// disable ordinary data compression
XD3_NOCOMPRESS = C.XD3_NOCOMPRESS
// enable adler32 checksum computation in encoder
XD3_ADLER32 = C.XD3_ADLER32
// skip checksum verification in decoder
XD3_ADLER32_NOVER = C.XD3_ADLER32_NOVER
)
type XdeltaCoder struct {
// srcFile : old file
srcFile io.ReadSeeker
// xdelta encode/decode flags see constant definitions above.
flags int
// output file
outFile io.Writer
// input file
inFile io.Reader
}
// NewXdeltaCoder returns a new XdeltaCoder with a fixed source file
// and encoder/decoder flags. These flags has to be fixed if it ought
// to be used as both encoder and decoder w.r.t. the same patch.
// See flag definitions above.
func NewXdeltaCoder(src io.ReadSeeker, flags int) *XdeltaCoder {
return &XdeltaCoder{
srcFile: src,
flags: flags,
}
}
// Decodes source and patch (in) to an updated revision (out).
// patch is the input patch to decode
// dst is the target updated file
func (x *XdeltaCoder) Decode(patch io.Reader, dst io.Writer) error {
x.inFile = patch
x.outFile = dst
return x.encodeDecode(C.codeFunc(C.xd3_decode_input))
}
// Encodes source and updated revision (in) to a VCDIFF patch (out).
// target is the target updated file
// patch is the output patch to encode
func (x *XdeltaCoder) Encode(target io.Reader, patch io.Writer) error {
x.inFile = target
x.outFile = patch
return x.encodeDecode(C.codeFunc(C.xd3_encode_input))
}
// encodeDecode is a general function to encode / decode a byte stream
// to / from a patch / updated revision respectively. The actual encoding
// and decoding happens in xd3_encode_input and xd3_decode_input functions
// respectively, and the preparations of the streams are completely the same
// only the meaning of "in" and "out" is swapped.
func (x *XdeltaCoder) encodeDecode(XD3_CODE C.codeFunc) error {
var err error
stream := C.getStream()
source := C.getSource()
config := C.getConfig()
if x.srcFile == nil || x.inFile == nil || x.outFile == nil {
return errors.New("[XdeltaCoder]: Calling decode without configuring streams {source|patch|out}.")
}
// Setup buffers and initiate c interface
inBuf := make([]byte, XD3_ALLOCSIZE)
// temporary buffer for source
srcBuf := make([]byte, XD3_ALLOCSIZE)
// Configure source and stream
C.memset(unsafe.Pointer(stream), 0, C.sizeof_xd3_stream)
C.memset(unsafe.Pointer(source), 0, C.sizeof_xd3_stream)
C.xd3_init_config(config, C.int(x.flags))
config.winsize = C.uint(len(inBuf))
if C.xd3_config_stream(stream, config) != 0 {
return errors.Errorf("[Xdelta] Error configuring stream: %s", C.GoString(stream.msg))
}
source.curblk = (*C.uchar)(unsafe.Pointer(&srcBuf[0]))
source.blksize = C.uint(len(srcBuf))
// Load first block from stream
onblk, err := x.srcFile.Read(srcBuf)
if err != nil {
C.xd3_free_stream(stream)
return errors.Wrapf(err, "Xdelta: error reading from source file")
}
source.onblk = C.uint(onblk)
source.curblkno = 0
C.xd3_set_source(stream, source)
var ret C.int
for {
// get a new chunk of patch file
inBytesRead, err := x.inFile.Read(inBuf)
if inBytesRead < len(inBuf) {
C.xd3_set_flags(stream, C.XD3_FLUSH|stream.flags)
if err == io.EOF {
err = nil
}
} else if err != nil {
err = errors.Wrap(err, "[Xdelta] Error fetching input: ")
C.xd3_close_stream(stream)
C.xd3_free_stream(stream)
return err
}
C.xd3_avail_input(stream, (*C.uchar)(unsafe.Pointer(&inBuf[0])), C.uint(inBytesRead))
for {
ret = C._xd3_code(XD3_CODE, stream)
switch ret {
case C.XD3_INPUT:
// Need more input
goto CONTINUE
case C.XD3_OUTPUT:
// Output ready
bytesWritten, err := x.outFile.Write(C.GoBytes(unsafe.Pointer(stream.next_out),
C.int(stream.avail_out)))
if err != nil {
err = errors.Wrap(err, "[Xdelta] Error writing output: ")
C.xd3_close_stream(stream)
C.xd3_free_stream(stream)
return err
} else if bytesWritten != int(stream.avail_out) {
err = errors.Errorf("Wrote an unexpected amount "+
"(%d of %d) through xdelta stream, ABORTING.",
bytesWritten, int(stream.avail_out))
C.xd3_close_stream(stream)
C.xd3_free_stream(stream)
return err
}
C.xd3_consume_output(stream)
case C.XD3_GETSRCBLK:
// Fetch source
var srcBytesRead int
_, err = x.srcFile.Seek(int64(source.blksize)*int64(source.getblkno), io.SeekStart)
if err != nil {
err = errors.Wrapf(err, "Xdelta: error seeking in source file")
C.xd3_close_stream(stream)
C.xd3_free_stream(stream)
return err
}
if srcBytesRead, err = x.srcFile.Read(srcBuf); err != nil {
if err == io.EOF {
err = nil
} else {
err = errors.Wrapf(err, "Xdelta: error reading from source file")
C.xd3_close_stream(stream)
C.xd3_free_stream(stream)
return err
}
}
source.curblk = (*C.uchar)(unsafe.Pointer(&srcBuf[0]))
source.onblk = C.uint(srcBytesRead)
source.curblkno = C.ulong(source.getblkno)
/* Noop cases */
case C.XD3_GOTHEADER:
case C.XD3_WINSTART:
case C.XD3_WINFINISH:
// Error return values
case C.XD3_INVALID_INPUT:
err = errors.Errorf("Xdelta error: %s [exit code: %d]."+
" Possibly a source-patch mismatch.",
C.GoString(stream.msg), int(ret))
C.xd3_close_stream(stream)
C.xd3_free_stream(stream)
return err
default:
err = errors.Errorf("Xdelta error: %s [exit code: %d].",
C.GoString(stream.msg), int(ret))
C.xd3_close_stream(stream)
C.xd3_free_stream(stream)
return err
}
}
CONTINUE:
// do {...} while (inBytesRead != len(inBuf))
if inBytesRead != len(inBuf) {
break
}
}
C.xd3_close_stream(stream)
C.xd3_free_stream(stream)
return nil
}