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