##// END OF EJS Templates
changegroup: don't accept odd chunk headers
Mads Kiilerich -
r13458:9f2c407c stable
parent child Browse files
Show More
@@ -1,199 +1,205 b''
1 1 # changegroup.py - Mercurial changegroup manipulation functions
2 2 #
3 3 # Copyright 2006 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from i18n import _
9 9 import util
10 10 import struct, os, bz2, zlib, tempfile
11 11
12 12 def readexactly(stream, n):
13 13 '''read n bytes from stream.read and abort if less was available'''
14 14 s = stream.read(n)
15 15 if len(s) < n:
16 16 raise util.Abort(_("stream ended unexpectedly"
17 17 " (got %d bytes, expected %d)")
18 18 % (len(s), n))
19 19 return s
20 20
21 21 def getchunk(stream):
22 22 """return the next chunk from stream as a string"""
23 23 d = readexactly(stream, 4)
24 24 l = struct.unpack(">l", d)[0]
25 25 if l <= 4:
26 if l:
27 raise util.Abort(_("invalid chunk length %d") % l)
26 28 return ""
27 29 return readexactly(stream, l - 4)
28 30
29 31 def chunkheader(length):
30 32 """return a changegroup chunk header (string)"""
31 33 return struct.pack(">l", length + 4)
32 34
33 35 def closechunk():
34 36 """return a changegroup chunk header (string) for a zero-length chunk"""
35 37 return struct.pack(">l", 0)
36 38
37 39 class nocompress(object):
38 40 def compress(self, x):
39 41 return x
40 42 def flush(self):
41 43 return ""
42 44
43 45 bundletypes = {
44 46 "": ("", nocompress),
45 47 "HG10UN": ("HG10UN", nocompress),
46 48 "HG10BZ": ("HG10", lambda: bz2.BZ2Compressor()),
47 49 "HG10GZ": ("HG10GZ", lambda: zlib.compressobj()),
48 50 }
49 51
50 52 def collector(cl, mmfs, files):
51 53 # Gather information about changeset nodes going out in a bundle.
52 54 # We want to gather manifests needed and filelogs affected.
53 55 def collect(node):
54 56 c = cl.read(node)
55 57 files.update(c[3])
56 58 mmfs.setdefault(c[0], node)
57 59 return collect
58 60
59 61 # hgweb uses this list to communicate its preferred type
60 62 bundlepriority = ['HG10GZ', 'HG10BZ', 'HG10UN']
61 63
62 64 def writebundle(cg, filename, bundletype):
63 65 """Write a bundle file and return its filename.
64 66
65 67 Existing files will not be overwritten.
66 68 If no filename is specified, a temporary file is created.
67 69 bz2 compression can be turned off.
68 70 The bundle file will be deleted in case of errors.
69 71 """
70 72
71 73 fh = None
72 74 cleanup = None
73 75 try:
74 76 if filename:
75 77 fh = open(filename, "wb")
76 78 else:
77 79 fd, filename = tempfile.mkstemp(prefix="hg-bundle-", suffix=".hg")
78 80 fh = os.fdopen(fd, "wb")
79 81 cleanup = filename
80 82
81 83 header, compressor = bundletypes[bundletype]
82 84 fh.write(header)
83 85 z = compressor()
84 86
85 87 # parse the changegroup data, otherwise we will block
86 88 # in case of sshrepo because we don't know the end of the stream
87 89
88 90 # an empty chunkgroup is the end of the changegroup
89 91 # a changegroup has at least 2 chunkgroups (changelog and manifest).
90 92 # after that, an empty chunkgroup is the end of the changegroup
91 93 empty = False
92 94 count = 0
93 95 while not empty or count <= 2:
94 96 empty = True
95 97 count += 1
96 98 while 1:
97 99 chunk = getchunk(cg)
98 100 if not chunk:
99 101 break
100 102 empty = False
101 103 fh.write(z.compress(chunkheader(len(chunk))))
102 104 pos = 0
103 105 while pos < len(chunk):
104 106 next = pos + 2**20
105 107 fh.write(z.compress(chunk[pos:next]))
106 108 pos = next
107 109 fh.write(z.compress(closechunk()))
108 110 fh.write(z.flush())
109 111 cleanup = None
110 112 return filename
111 113 finally:
112 114 if fh is not None:
113 115 fh.close()
114 116 if cleanup is not None:
115 117 os.unlink(cleanup)
116 118
117 119 def decompressor(fh, alg):
118 120 if alg == 'UN':
119 121 return fh
120 122 elif alg == 'GZ':
121 123 def generator(f):
122 124 zd = zlib.decompressobj()
123 125 for chunk in f:
124 126 yield zd.decompress(chunk)
125 127 elif alg == 'BZ':
126 128 def generator(f):
127 129 zd = bz2.BZ2Decompressor()
128 130 zd.decompress("BZ")
129 131 for chunk in util.filechunkiter(f, 4096):
130 132 yield zd.decompress(chunk)
131 133 else:
132 134 raise util.Abort("unknown bundle compression '%s'" % alg)
133 135 return util.chunkbuffer(generator(fh))
134 136
135 137 class unbundle10(object):
136 138 def __init__(self, fh, alg):
137 139 self._stream = decompressor(fh, alg)
138 140 self._type = alg
139 141 self.callback = None
140 142 def compressed(self):
141 143 return self._type != 'UN'
142 144 def read(self, l):
143 145 return self._stream.read(l)
144 146 def seek(self, pos):
145 147 return self._stream.seek(pos)
146 148 def tell(self):
147 149 return self._stream.tell()
148 150 def close(self):
149 151 return self._stream.close()
150 152
151 153 def chunklength(self):
152 d = readexactly(self._stream, 4)
153 l = max(0, struct.unpack(">l", d)[0] - 4)
154 if l and self.callback:
154 d = readexactly(stream, 4)
155 l = struct.unpack(">l", d)[0]
156 if l <= 4:
157 if l:
158 raise util.Abort(_("invalid chunk length %d") % l)
159 return 0
160 if self.callback:
155 161 self.callback()
156 return l
162 return l - 4
157 163
158 164 def chunk(self):
159 165 """return the next chunk from changegroup 'source' as a string"""
160 166 l = self.chunklength()
161 167 return readexactly(self._stream, l)
162 168
163 169 def parsechunk(self):
164 170 l = self.chunklength()
165 171 if not l:
166 172 return {}
167 173 h = readexactly(self._stream, 80)
168 174 node, p1, p2, cs = struct.unpack("20s20s20s20s", h)
169 175 data = readexactly(self._stream, l - 80)
170 176 return dict(node=node, p1=p1, p2=p2, cs=cs, data=data)
171 177
172 178 class headerlessfixup(object):
173 179 def __init__(self, fh, h):
174 180 self._h = h
175 181 self._fh = fh
176 182 def read(self, n):
177 183 if self._h:
178 184 d, self._h = self._h[:n], self._h[n:]
179 185 if len(d) < n:
180 186 d += readexactly(self._fh, n - len(d))
181 187 return d
182 188 return readexactly(self._fh, n)
183 189
184 190 def readbundle(fh, fname):
185 191 header = readexactly(fh, 6)
186 192
187 193 if not fname:
188 194 fname = "stream"
189 195 if not header.startswith('HG') and header.startswith('\0'):
190 196 fh = headerlessfixup(fh, header)
191 197 header = "HG10UN"
192 198
193 199 magic, version, alg = header[0:2], header[2:4], header[4:6]
194 200
195 201 if magic != 'HG':
196 202 raise util.Abort(_('%s: not a Mercurial bundle') % fname)
197 203 if version != '10':
198 204 raise util.Abort(_('%s: unknown bundle version %s') % (fname, version))
199 205 return unbundle10(fh, alg)
General Comments 0
You need to be logged in to leave comments. Login now