##// END OF EJS Templates
mpatch: raise MemoryError instead of mpatchError if lalloc() failed...
Yuya Nishihara -
r29749:155f0cc3 default
parent child Browse files
Show More
@@ -1,283 +1,280 b''
1 1 /*
2 2 mpatch.c - efficient binary patching for Mercurial
3 3
4 4 This implements a patch algorithm that's O(m + nlog n) where m is the
5 5 size of the output and n is the number of patches.
6 6
7 7 Given a list of binary patches, it unpacks each into a hunk list,
8 8 then combines the hunk lists with a treewise recursion to form a
9 9 single hunk list. This hunk list is then applied to the original
10 10 text.
11 11
12 12 The text (or binary) fragments are copied directly from their source
13 13 Python objects into a preallocated output string to avoid the
14 14 allocation of intermediate Python objects. Working memory is about 2x
15 15 the total number of hunks.
16 16
17 17 Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
18 18
19 19 This software may be used and distributed according to the terms
20 20 of the GNU General Public License, incorporated herein by reference.
21 21 */
22 22
23 23 #include <stdlib.h>
24 24 #include <string.h>
25 25
26 26 #include "bitmanipulation.h"
27 27 #include "compat.h"
28 28 #include "mpatch.h"
29 29
30 char *mpatch_errors[] = {NULL, "invalid patch", "patch cannot be decoded",
31 "no memory"};
32
33 30 static struct mpatch_flist *lalloc(ssize_t size)
34 31 {
35 32 struct mpatch_flist *a = NULL;
36 33
37 34 if (size < 1)
38 35 size = 1;
39 36
40 37 a = (struct mpatch_flist *)malloc(sizeof(struct mpatch_flist));
41 38 if (a) {
42 39 a->base = (struct mpatch_frag *)malloc(sizeof(struct mpatch_frag) * size);
43 40 if (a->base) {
44 41 a->head = a->tail = a->base;
45 42 return a;
46 43 }
47 44 free(a);
48 45 }
49 46 return NULL;
50 47 }
51 48
52 49 void mpatch_lfree(struct mpatch_flist *a)
53 50 {
54 51 if (a) {
55 52 free(a->base);
56 53 free(a);
57 54 }
58 55 }
59 56
60 57 static ssize_t lsize(struct mpatch_flist *a)
61 58 {
62 59 return a->tail - a->head;
63 60 }
64 61
65 62 /* move hunks in source that are less cut to dest, compensating
66 63 for changes in offset. the last hunk may be split if necessary.
67 64 */
68 65 static int gather(struct mpatch_flist *dest, struct mpatch_flist *src, int cut,
69 66 int offset)
70 67 {
71 68 struct mpatch_frag *d = dest->tail, *s = src->head;
72 69 int postend, c, l;
73 70
74 71 while (s != src->tail) {
75 72 if (s->start + offset >= cut)
76 73 break; /* we've gone far enough */
77 74
78 75 postend = offset + s->start + s->len;
79 76 if (postend <= cut) {
80 77 /* save this hunk */
81 78 offset += s->start + s->len - s->end;
82 79 *d++ = *s++;
83 80 }
84 81 else {
85 82 /* break up this hunk */
86 83 c = cut - offset;
87 84 if (s->end < c)
88 85 c = s->end;
89 86 l = cut - offset - s->start;
90 87 if (s->len < l)
91 88 l = s->len;
92 89
93 90 offset += s->start + l - c;
94 91
95 92 d->start = s->start;
96 93 d->end = c;
97 94 d->len = l;
98 95 d->data = s->data;
99 96 d++;
100 97 s->start = c;
101 98 s->len = s->len - l;
102 99 s->data = s->data + l;
103 100
104 101 break;
105 102 }
106 103 }
107 104
108 105 dest->tail = d;
109 106 src->head = s;
110 107 return offset;
111 108 }
112 109
113 110 /* like gather, but with no output list */
114 111 static int discard(struct mpatch_flist *src, int cut, int offset)
115 112 {
116 113 struct mpatch_frag *s = src->head;
117 114 int postend, c, l;
118 115
119 116 while (s != src->tail) {
120 117 if (s->start + offset >= cut)
121 118 break;
122 119
123 120 postend = offset + s->start + s->len;
124 121 if (postend <= cut) {
125 122 offset += s->start + s->len - s->end;
126 123 s++;
127 124 }
128 125 else {
129 126 c = cut - offset;
130 127 if (s->end < c)
131 128 c = s->end;
132 129 l = cut - offset - s->start;
133 130 if (s->len < l)
134 131 l = s->len;
135 132
136 133 offset += s->start + l - c;
137 134 s->start = c;
138 135 s->len = s->len - l;
139 136 s->data = s->data + l;
140 137
141 138 break;
142 139 }
143 140 }
144 141
145 142 src->head = s;
146 143 return offset;
147 144 }
148 145
149 146 /* combine hunk lists a and b, while adjusting b for offset changes in a/
150 147 this deletes a and b and returns the resultant list. */
151 148 static struct mpatch_flist *combine(struct mpatch_flist *a,
152 149 struct mpatch_flist *b)
153 150 {
154 151 struct mpatch_flist *c = NULL;
155 152 struct mpatch_frag *bh, *ct;
156 153 int offset = 0, post;
157 154
158 155 if (a && b)
159 156 c = lalloc((lsize(a) + lsize(b)) * 2);
160 157
161 158 if (c) {
162 159
163 160 for (bh = b->head; bh != b->tail; bh++) {
164 161 /* save old hunks */
165 162 offset = gather(c, a, bh->start, offset);
166 163
167 164 /* discard replaced hunks */
168 165 post = discard(a, bh->end, offset);
169 166
170 167 /* insert new hunk */
171 168 ct = c->tail;
172 169 ct->start = bh->start - offset;
173 170 ct->end = bh->end - post;
174 171 ct->len = bh->len;
175 172 ct->data = bh->data;
176 173 c->tail++;
177 174 offset = post;
178 175 }
179 176
180 177 /* hold on to tail from a */
181 178 memcpy(c->tail, a->head, sizeof(struct mpatch_frag) * lsize(a));
182 179 c->tail += lsize(a);
183 180 }
184 181
185 182 mpatch_lfree(a);
186 183 mpatch_lfree(b);
187 184 return c;
188 185 }
189 186
190 187 /* decode a binary patch into a hunk list */
191 188 int mpatch_decode(const char *bin, ssize_t len, struct mpatch_flist **res)
192 189 {
193 190 struct mpatch_flist *l;
194 191 struct mpatch_frag *lt;
195 192 int pos = 0;
196 193
197 194 /* assume worst case size, we won't have many of these lists */
198 195 l = lalloc(len / 12 + 1);
199 196 if (!l)
200 197 return MPATCH_ERR_NO_MEM;
201 198
202 199 lt = l->tail;
203 200
204 201 while (pos >= 0 && pos < len) {
205 202 lt->start = getbe32(bin + pos);
206 203 lt->end = getbe32(bin + pos + 4);
207 204 lt->len = getbe32(bin + pos + 8);
208 205 lt->data = bin + pos + 12;
209 206 pos += 12 + lt->len;
210 207 if (lt->start > lt->end || lt->len < 0)
211 208 break; /* sanity check */
212 209 lt++;
213 210 }
214 211
215 212 if (pos != len) {
216 213 mpatch_lfree(l);
217 214 return MPATCH_ERR_CANNOT_BE_DECODED;
218 215 }
219 216
220 217 l->tail = lt;
221 218 *res = l;
222 219 return 0;
223 220 }
224 221
225 222 /* calculate the size of resultant text */
226 223 ssize_t mpatch_calcsize(ssize_t len, struct mpatch_flist *l)
227 224 {
228 225 ssize_t outlen = 0, last = 0;
229 226 struct mpatch_frag *f = l->head;
230 227
231 228 while (f != l->tail) {
232 229 if (f->start < last || f->end > len) {
233 230 return MPATCH_ERR_INVALID_PATCH;
234 231 }
235 232 outlen += f->start - last;
236 233 last = f->end;
237 234 outlen += f->len;
238 235 f++;
239 236 }
240 237
241 238 outlen += len - last;
242 239 return outlen;
243 240 }
244 241
245 242 int mpatch_apply(char *buf, const char *orig, ssize_t len,
246 243 struct mpatch_flist *l)
247 244 {
248 245 struct mpatch_frag *f = l->head;
249 246 int last = 0;
250 247 char *p = buf;
251 248
252 249 while (f != l->tail) {
253 250 if (f->start < last || f->end > len) {
254 251 return MPATCH_ERR_INVALID_PATCH;
255 252 }
256 253 memcpy(p, orig + last, f->start - last);
257 254 p += f->start - last;
258 255 memcpy(p, f->data, f->len);
259 256 last = f->end;
260 257 p += f->len;
261 258 f++;
262 259 }
263 260 memcpy(p, orig + last, len - last);
264 261 return 0;
265 262 }
266 263
267 264 /* recursively generate a patch of all bins between start and end */
268 265 struct mpatch_flist *mpatch_fold(void *bins,
269 266 struct mpatch_flist* (*get_next_item)(void*, ssize_t),
270 267 ssize_t start, ssize_t end)
271 268 {
272 269 ssize_t len;
273 270
274 271 if (start + 1 == end) {
275 272 /* trivial case, output a decoded list */
276 273 return get_next_item(bins, start);
277 274 }
278 275
279 276 /* divide and conquer, memory management is elsewhere */
280 277 len = (end - start) / 2;
281 278 return combine(mpatch_fold(bins, get_next_item, start, start + len),
282 279 mpatch_fold(bins, get_next_item, start + len, end));
283 280 }
@@ -1,28 +1,26 b''
1 1 #ifndef _HG_MPATCH_H_
2 2 #define _HG_MPATCH_H_
3 3
4 extern char *mpatch_errors[];
5
6 4 #define MPATCH_ERR_NO_MEM -3
7 5 #define MPATCH_ERR_CANNOT_BE_DECODED -2
8 6 #define MPATCH_ERR_INVALID_PATCH -1
9 7
10 8 struct mpatch_frag {
11 9 int start, end, len;
12 10 const char *data;
13 11 };
14 12
15 13 struct mpatch_flist {
16 14 struct mpatch_frag *base, *head, *tail;
17 15 };
18 16
19 17 int mpatch_decode(const char *bin, ssize_t len, struct mpatch_flist** res);
20 18 ssize_t mpatch_calcsize(ssize_t len, struct mpatch_flist *l);
21 19 void mpatch_lfree(struct mpatch_flist *a);
22 20 int mpatch_apply(char *buf, const char *orig, ssize_t len,
23 21 struct mpatch_flist *l);
24 22 struct mpatch_flist *mpatch_fold(void *bins,
25 23 struct mpatch_flist* (*get_next_item)(void*, ssize_t),
26 24 ssize_t start, ssize_t end);
27 25
28 26 #endif
@@ -1,180 +1,195 b''
1 1 /*
2 2 mpatch.c - efficient binary patching for Mercurial
3 3
4 4 This implements a patch algorithm that's O(m + nlog n) where m is the
5 5 size of the output and n is the number of patches.
6 6
7 7 Given a list of binary patches, it unpacks each into a hunk list,
8 8 then combines the hunk lists with a treewise recursion to form a
9 9 single hunk list. This hunk list is then applied to the original
10 10 text.
11 11
12 12 The text (or binary) fragments are copied directly from their source
13 13 Python objects into a preallocated output string to avoid the
14 14 allocation of intermediate Python objects. Working memory is about 2x
15 15 the total number of hunks.
16 16
17 17 Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
18 18
19 19 This software may be used and distributed according to the terms
20 20 of the GNU General Public License, incorporated herein by reference.
21 21 */
22 22
23 23 #define PY_SSIZE_T_CLEAN
24 24 #include <Python.h>
25 25 #include <stdlib.h>
26 26 #include <string.h>
27 27
28 28 #include "util.h"
29 29 #include "bitmanipulation.h"
30 30 #include "compat.h"
31 31 #include "mpatch.h"
32 32
33 33 static char mpatch_doc[] = "Efficient binary patching.";
34 34 static PyObject *mpatch_Error;
35 35
36 static void setpyerr(int r)
37 {
38 switch (r) {
39 case MPATCH_ERR_NO_MEM:
40 PyErr_NoMemory();
41 break;
42 case MPATCH_ERR_CANNOT_BE_DECODED:
43 PyErr_SetString(mpatch_Error, "patch cannot be decoded");
44 break;
45 case MPATCH_ERR_INVALID_PATCH:
46 PyErr_SetString(mpatch_Error, "invalid patch");
47 break;
48 }
49 }
50
36 51 struct mpatch_flist *cpygetitem(void *bins, ssize_t pos)
37 52 {
38 53 const char *buffer;
39 54 struct mpatch_flist *res;
40 55 ssize_t blen;
41 56 int r;
42 57
43 58 PyObject *tmp = PyList_GetItem((PyObject*)bins, pos);
44 59 if (!tmp)
45 60 return NULL;
46 61 if (PyObject_AsCharBuffer(tmp, &buffer, (Py_ssize_t*)&blen))
47 62 return NULL;
48 63 if ((r = mpatch_decode(buffer, blen, &res)) < 0) {
49 64 if (!PyErr_Occurred())
50 PyErr_SetString(mpatch_Error, mpatch_errors[-r]);
65 setpyerr(r);
51 66 return NULL;
52 67 }
53 68 return res;
54 69 }
55 70
56 71 static PyObject *
57 72 patches(PyObject *self, PyObject *args)
58 73 {
59 74 PyObject *text, *bins, *result;
60 75 struct mpatch_flist *patch;
61 76 const char *in;
62 77 int r = 0;
63 78 char *out;
64 79 Py_ssize_t len, outlen, inlen;
65 80
66 81 if (!PyArg_ParseTuple(args, "OO:mpatch", &text, &bins))
67 82 return NULL;
68 83
69 84 len = PyList_Size(bins);
70 85 if (!len) {
71 86 /* nothing to do */
72 87 Py_INCREF(text);
73 88 return text;
74 89 }
75 90
76 91 if (PyObject_AsCharBuffer(text, &in, &inlen))
77 92 return NULL;
78 93
79 94 patch = mpatch_fold(bins, cpygetitem, 0, len);
80 95 if (!patch) { /* error already set or memory error */
81 96 if (!PyErr_Occurred())
82 97 PyErr_NoMemory();
83 98 return NULL;
84 99 }
85 100
86 101 outlen = mpatch_calcsize(inlen, patch);
87 102 if (outlen < 0) {
88 103 r = (int)outlen;
89 104 result = NULL;
90 105 goto cleanup;
91 106 }
92 107 result = PyBytes_FromStringAndSize(NULL, outlen);
93 108 if (!result) {
94 109 result = NULL;
95 110 goto cleanup;
96 111 }
97 112 out = PyBytes_AsString(result);
98 113 if ((r = mpatch_apply(out, in, inlen, patch)) < 0) {
99 114 Py_DECREF(result);
100 115 result = NULL;
101 116 }
102 117 cleanup:
103 118 mpatch_lfree(patch);
104 119 if (!result && !PyErr_Occurred())
105 PyErr_SetString(mpatch_Error, mpatch_errors[-r]);
120 setpyerr(r);
106 121 return result;
107 122 }
108 123
109 124 /* calculate size of a patched file directly */
110 125 static PyObject *
111 126 patchedsize(PyObject *self, PyObject *args)
112 127 {
113 128 long orig, start, end, len, outlen = 0, last = 0, pos = 0;
114 129 Py_ssize_t patchlen;
115 130 char *bin;
116 131
117 132 if (!PyArg_ParseTuple(args, "ls#", &orig, &bin, &patchlen))
118 133 return NULL;
119 134
120 135 while (pos >= 0 && pos < patchlen) {
121 136 start = getbe32(bin + pos);
122 137 end = getbe32(bin + pos + 4);
123 138 len = getbe32(bin + pos + 8);
124 139 if (start > end)
125 140 break; /* sanity check */
126 141 pos += 12 + len;
127 142 outlen += start - last;
128 143 last = end;
129 144 outlen += len;
130 145 }
131 146
132 147 if (pos != patchlen) {
133 148 if (!PyErr_Occurred())
134 149 PyErr_SetString(mpatch_Error, "patch cannot be decoded");
135 150 return NULL;
136 151 }
137 152
138 153 outlen += orig - last;
139 154 return Py_BuildValue("l", outlen);
140 155 }
141 156
142 157 static PyMethodDef methods[] = {
143 158 {"patches", patches, METH_VARARGS, "apply a series of patches\n"},
144 159 {"patchedsize", patchedsize, METH_VARARGS, "calculed patched size\n"},
145 160 {NULL, NULL}
146 161 };
147 162
148 163 #ifdef IS_PY3K
149 164 static struct PyModuleDef mpatch_module = {
150 165 PyModuleDef_HEAD_INIT,
151 166 "mpatch",
152 167 mpatch_doc,
153 168 -1,
154 169 methods
155 170 };
156 171
157 172 PyMODINIT_FUNC PyInit_mpatch(void)
158 173 {
159 174 PyObject *m;
160 175
161 176 m = PyModule_Create(&mpatch_module);
162 177 if (m == NULL)
163 178 return NULL;
164 179
165 180 mpatch_Error = PyErr_NewException("mercurial.mpatch.mpatchError",
166 181 NULL, NULL);
167 182 Py_INCREF(mpatch_Error);
168 183 PyModule_AddObject(m, "mpatchError", mpatch_Error);
169 184
170 185 return m;
171 186 }
172 187 #else
173 188 PyMODINIT_FUNC
174 189 initmpatch(void)
175 190 {
176 191 Py_InitModule3("mpatch", methods, mpatch_doc);
177 192 mpatch_Error = PyErr_NewException("mercurial.mpatch.mpatchError",
178 193 NULL, NULL);
179 194 }
180 195 #endif
General Comments 0
You need to be logged in to leave comments. Login now