1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package binpatch
21
22 import (
23 "bytes"
24 "encoding/binary"
25 "errors"
26 "fmt"
27 "io"
28 "os"
29
30 "github.com/sassoftware/relic/lib/atomicfile"
31 )
32
33 const (
34 MimeType = "application/x-binary-patch"
35
36 uint32Max = 0xffffffff
37 )
38
39 type PatchSet struct {
40 Patches []PatchHeader
41 Blobs [][]byte
42 }
43
44 type PatchSetHeader struct {
45 Version, NumPatches uint32
46 }
47
48 type PatchHeader struct {
49 Offset int64
50 OldSize, NewSize uint32
51 }
52
53
54 func New() *PatchSet {
55 return new(PatchSet)
56 }
57
58
59
60 func (p *PatchSet) Add(offset, oldSize int64, blob []byte) {
61 if len(p.Patches) > 0 {
62 i := len(p.Patches) - 1
63 last := p.Patches[i]
64 lastEnd := last.Offset + int64(last.OldSize)
65 lastBlob := p.Blobs[i]
66 oldCombo := int64(last.OldSize) + oldSize
67 newCombo := int64(len(lastBlob)) + int64(len(blob))
68 if offset == lastEnd && oldCombo <= uint32Max && newCombo <= uint32Max {
69
70 p.Patches[i].OldSize = uint32(oldCombo)
71 p.Patches[i].NewSize = uint32(newCombo)
72 if len(blob) > 0 {
73 newBlob := make([]byte, newCombo)
74 copy(newBlob, lastBlob)
75 copy(newBlob[len(lastBlob):], blob)
76 p.Blobs[i] = newBlob
77 }
78 return
79 }
80 }
81 for oldSize > uint32Max {
82 p.Patches = append(p.Patches, PatchHeader{offset, uint32Max, 0})
83 p.Blobs = append(p.Blobs, nil)
84 offset += uint32Max
85 oldSize -= uint32Max
86 }
87 p.Patches = append(p.Patches, PatchHeader{offset, uint32(oldSize), uint32(len(blob))})
88 p.Blobs = append(p.Blobs, blob)
89 }
90
91
92 func Load(blob []byte) (*PatchSet, error) {
93 r := bytes.NewReader(blob)
94 var h PatchSetHeader
95 if err := binary.Read(r, binary.BigEndian, &h); err != nil {
96 return nil, err
97 } else if h.Version != 1 {
98 return nil, fmt.Errorf("unsupported binpatch version %d", h.Version)
99 }
100 num := int(h.NumPatches)
101 p := &PatchSet{
102 Patches: make([]PatchHeader, num),
103 Blobs: make([][]byte, num),
104 }
105 if err := binary.Read(r, binary.BigEndian, p.Patches); err != nil {
106 return nil, err
107 }
108 for i, hdr := range p.Patches {
109 p.Blobs[i] = make([]byte, int(hdr.NewSize))
110 if _, err := io.ReadFull(r, p.Blobs[i]); err != nil {
111 return nil, err
112 }
113 }
114 return p, nil
115 }
116
117
118 func (p *PatchSet) Dump() []byte {
119 header := PatchSetHeader{1, uint32(len(p.Patches))}
120 size := 8 + 16*len(p.Patches)
121 for _, hdr := range p.Patches {
122 size += int(hdr.NewSize)
123 }
124 buf := bytes.NewBuffer(make([]byte, 0, size))
125 binary.Write(buf, binary.BigEndian, header)
126 binary.Write(buf, binary.BigEndian, p.Patches)
127 for _, blob := range p.Blobs {
128 buf.Write(blob)
129 }
130 return buf.Bytes()
131 }
132
133
134
135
136
137
138
139 func (p *PatchSet) Apply(infile *os.File, outpath string) error {
140 if outpath == "" {
141 outpath = infile.Name()
142 }
143
144
145 ininfo, err := infile.Stat()
146 if err != nil {
147 return p.applyRewrite(infile, outpath)
148 }
149 outinfo, err := os.Lstat(outpath)
150 if err != nil || !canOverwrite(ininfo, outinfo) {
151 return p.applyRewrite(infile, outpath)
152 }
153 size := ininfo.Size()
154 for i, patch := range p.Patches {
155
156 if patch.OldSize == patch.NewSize {
157 continue
158 } else if i != len(p.Patches)-1 {
159 return p.applyRewrite(infile, outpath)
160 }
161
162
163
164 oldEnd := patch.Offset + int64(patch.OldSize)
165 if oldEnd != ininfo.Size() {
166 return p.applyRewrite(infile, outpath)
167 }
168 size = patch.Offset + int64(patch.NewSize)
169 }
170
171 for i, patch := range p.Patches {
172 if _, err := infile.WriteAt(p.Blobs[i], patch.Offset); err != nil {
173 return err
174 }
175 }
176 return infile.Truncate(size)
177 }
178
179
180
181 func (p *PatchSet) applyRewrite(infile *os.File, outpath string) error {
182 if _, err := infile.Seek(0, 0); err != nil {
183 return err
184 }
185 outfile, err := atomicfile.New(outpath)
186 if err != nil {
187 return err
188 }
189 defer outfile.Close()
190 var pos int64
191 for i, patch := range p.Patches {
192 blob := p.Blobs[i]
193 delta := patch.Offset - pos
194 if delta < 0 {
195 return errors.New("patches out of order")
196 }
197
198 if delta > 0 {
199 if _, err := io.CopyN(outfile, infile, delta); err != nil {
200 return err
201 }
202 pos += delta
203 }
204
205 delta = int64(patch.OldSize)
206 if _, err := infile.Seek(delta, io.SeekCurrent); err != nil {
207 return err
208 }
209 pos += delta
210
211 if _, err := outfile.Write(blob); err != nil {
212 return err
213 }
214 }
215
216 if _, err := io.Copy(outfile, infile); err != nil {
217 return err
218 }
219 infile.Close()
220 return outfile.Commit()
221 }
222
223 func canOverwrite(ininfo, outinfo os.FileInfo) bool {
224 if !outinfo.Mode().IsRegular() {
225 return false
226 }
227 if !os.SameFile(ininfo, outinfo) {
228 return false
229 }
230 if hasLinks(outinfo) {
231 return false
232 }
233 return true
234 }
235
View as plain text