~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to _groupcompress_pyx.pyx

  • Committer: John Arbash Meinel
  • Date: 2009-03-04 21:22:50 UTC
  • mto: (0.17.34 trunk)
  • mto: This revision was merged to the branch mainline in revision 4280.
  • Revision ID: john@arbash-meinel.com-20090304212250-xcvwt1yx4zt76pev
Have the GroupCompressBlock decide how to compress the header and content.
It can now decide whether they should be compressed together or not.
As long as we make the to_bytes() function match the from_bytes() one, we should be fine.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2008, 2009, 2010 Canonical Ltd
2
 
#
 
1
# Copyright (C) 2008 Canonical Limited.
 
2
3
3
# This program is free software; you can redistribute it and/or modify
4
 
# it under the terms of the GNU General Public License as published by
5
 
# the Free Software Foundation; either version 2 of the License, or
6
 
# (at your option) any later version.
7
 
#
 
4
# it under the terms of the GNU General Public License version 2 as published
 
5
# by the Free Software Foundation.
 
6
8
7
# This program is distributed in the hope that it will be useful,
9
8
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
9
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
10
# GNU General Public License for more details.
12
 
#
 
11
13
12
# You should have received a copy of the GNU General Public License
14
13
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
14
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
 
15
16
16
 
17
17
"""Compiled extensions for doing compression."""
18
18
 
19
 
#python2.4 support
20
 
cdef extern from "python-compat.h":
21
 
    pass
22
 
 
23
 
 
24
 
cdef extern from "Python.h":
25
 
    ctypedef struct PyObject:
26
 
        pass
27
 
    ctypedef int Py_ssize_t # Required for older pyrex versions
28
 
    int PyString_CheckExact(object)
29
 
    char * PyString_AS_STRING(object)
30
 
    Py_ssize_t PyString_GET_SIZE(object)
31
 
    object PyString_FromStringAndSize(char *, Py_ssize_t)
32
 
 
33
 
 
34
19
cdef extern from *:
35
20
    ctypedef unsigned long size_t
36
 
    void * malloc(size_t) nogil
37
 
    void * realloc(void *, size_t) nogil
38
 
    void free(void *) nogil
39
 
    void memcpy(void *, void *, size_t) nogil
40
 
 
 
21
    void * malloc(size_t)
 
22
    void * realloc(void *, size_t)
 
23
    void free(void *)
 
24
    void memcpy(void *, void *, size_t)
41
25
 
42
26
cdef extern from "delta.h":
43
27
    struct source_info:
46
30
        unsigned long agg_offset
47
31
    struct delta_index:
48
32
        pass
49
 
    delta_index * create_delta_index(source_info *src, delta_index *old) nogil
 
33
    delta_index * create_delta_index(source_info *src, delta_index *old)
50
34
    delta_index * create_delta_index_from_delta(source_info *delta,
51
 
                                                delta_index *old) nogil
52
 
    void free_delta_index(delta_index *index) nogil
 
35
                                                delta_index *old)
 
36
    void free_delta_index(delta_index *index)
53
37
    void *create_delta(delta_index *indexes,
54
38
             void *buf, unsigned long bufsize,
55
 
             unsigned long *delta_size, unsigned long max_delta_size) nogil
 
39
             unsigned long *delta_size, unsigned long max_delta_size)
56
40
    unsigned long get_delta_hdr_size(unsigned char **datap,
57
 
                                     unsigned char *top) nogil
58
 
    unsigned long sizeof_delta_index(delta_index *index)
 
41
                                     unsigned char *top)
59
42
    Py_ssize_t DELTA_SIZE_MIN
 
43
    void *patch_delta(void *src_buf, unsigned long src_size,
 
44
                      void *delta_buf, unsigned long delta_size,
 
45
                      unsigned long *dst_size)
 
46
 
 
47
cdef extern from "Python.h":
 
48
    int PyString_CheckExact(object)
 
49
    char * PyString_AS_STRING(object)
 
50
    Py_ssize_t PyString_GET_SIZE(object)
 
51
    object PyString_FromStringAndSize(char *, Py_ssize_t)
60
52
 
61
53
 
62
54
cdef void *safe_malloc(size_t count) except NULL:
94
86
    cdef readonly object _sources
95
87
    cdef source_info *_source_infos
96
88
    cdef delta_index *_index
 
89
    cdef readonly unsigned int _max_num_sources
97
90
    cdef public unsigned long _source_offset
98
 
    cdef readonly unsigned int _max_num_sources
 
91
 
 
92
    def __repr__(self):
 
93
        return '%s(%d, %d)' % (self.__class__.__name__,
 
94
            len(self._sources), self._source_offset)
99
95
 
100
96
    def __init__(self, source=None):
101
97
        self._sources = []
108
104
        if source is not None:
109
105
            self.add_source(source, 0)
110
106
 
111
 
    def __sizeof__(self):
112
 
        # We want to track the _source_infos allocations, but the referenced
113
 
        # void* are actually tracked in _sources itself.
114
 
        # XXX: Cython is capable of doing sizeof(class) and returning the size
115
 
        #      of the underlying struct. Pyrex (<= 0.9.9) refuses, so we need
116
 
        #      to do it manually. *sigh* Note that we might get it wrong
117
 
        #      because of alignment issues.
118
 
        cdef Py_ssize_t size
119
 
        # PyObject start, vtable *, 3 object pointers, 2 C ints
120
 
        size = ((sizeof(PyObject) + sizeof(void*) + 3*sizeof(PyObject*)
121
 
                 + sizeof(unsigned long)
122
 
                 + sizeof(unsigned int))
123
 
                + (sizeof(source_info) * self._max_num_sources)
124
 
                + sizeof_delta_index(self._index))
125
 
        return size
126
 
 
127
 
    def __repr__(self):
128
 
        return '%s(%d, %d)' % (self.__class__.__name__,
129
 
            len(self._sources), self._source_offset)
130
 
 
131
107
    def __dealloc__(self):
132
108
        if self._index != NULL:
133
109
            free_delta_index(self._index)
134
110
            self._index = NULL
135
111
        safe_free(<void **>&self._source_infos)
136
112
 
137
 
    def _has_index(self):
138
 
        return (self._index != NULL)
139
 
 
140
113
    def add_delta_source(self, delta, unadded_bytes):
141
114
        """Add a new delta to the source texts.
142
115
 
164
137
        src.buf = c_delta
165
138
        src.size = c_delta_size
166
139
        src.agg_offset = self._source_offset + unadded_bytes
167
 
        with nogil:
168
 
            index = create_delta_index_from_delta(src, self._index)
 
140
        index = create_delta_index_from_delta(src, self._index)
169
141
        self._source_offset = src.agg_offset + src.size
170
142
        if index != NULL:
171
143
            free_delta_index(self._index)
191
163
        source_location = len(self._sources)
192
164
        if source_location >= self._max_num_sources:
193
165
            self._expand_sources()
194
 
        if source_location != 0 and self._index == NULL:
195
 
            # We were lazy about populating the index, create it now
196
 
            self._populate_first_index()
197
166
        self._sources.append(source)
198
167
        c_source = PyString_AS_STRING(source)
199
168
        c_source_size = PyString_GET_SIZE(source)
202
171
        src.size = c_source_size
203
172
 
204
173
        src.agg_offset = self._source_offset + unadded_bytes
 
174
        index = create_delta_index(src, self._index)
205
175
        self._source_offset = src.agg_offset + src.size
206
 
        # We delay creating the index on the first insert
207
 
        if source_location != 0:
208
 
            with nogil:
209
 
                index = create_delta_index(src, self._index)
210
 
            if index != NULL:
211
 
                free_delta_index(self._index)
212
 
                self._index = index
213
 
 
214
 
    cdef _populate_first_index(self):
215
 
        cdef delta_index *index
216
 
        if len(self._sources) != 1 or self._index != NULL:
217
 
            raise AssertionError('_populate_first_index should only be'
218
 
                ' called when we have a single source and no index yet')
219
 
 
220
 
        # We know that self._index is already NULL, so whatever
221
 
        # create_delta_index returns is fine
222
 
        with nogil:
223
 
            self._index = create_delta_index(&self._source_infos[0], NULL)
224
 
        assert self._index != NULL
 
176
        if index != NULL:
 
177
            free_delta_index(self._index)
 
178
            self._index = index
225
179
 
226
180
    cdef _expand_sources(self):
227
181
        raise RuntimeError('if we move self._source_infos, then we need to'
237
191
        cdef Py_ssize_t target_size
238
192
        cdef void * delta
239
193
        cdef unsigned long delta_size
240
 
        cdef unsigned long c_max_delta_size
241
194
 
242
195
        if self._index == NULL:
243
 
            if len(self._sources) == 0:
244
 
                return None
245
 
            # We were just lazy about generating the index
246
 
            self._populate_first_index()
 
196
            return None
247
197
 
248
198
        if not PyString_CheckExact(target_bytes):
249
199
            raise TypeError('target is not a str')
254
204
        # TODO: inline some of create_delta so we at least don't have to double
255
205
        #       malloc, and can instead use PyString_FromStringAndSize, to
256
206
        #       allocate the bytes into the final string
257
 
        c_max_delta_size = max_delta_size
258
 
        with nogil:
259
 
            delta = create_delta(self._index,
260
 
                                 target, target_size,
261
 
                                 &delta_size, c_max_delta_size)
 
207
        delta = create_delta(self._index,
 
208
                             target, target_size,
 
209
                             &delta_size, max_delta_size)
262
210
        result = None
263
211
        if delta:
264
212
            result = PyString_FromStringAndSize(<char *>delta, delta_size)
278
226
    cdef Py_ssize_t source_size
279
227
    cdef char *delta
280
228
    cdef Py_ssize_t delta_size
 
229
    cdef unsigned char *data, *top
 
230
    cdef unsigned char *dst_buf, *out, cmd
 
231
    cdef Py_ssize_t size
 
232
    cdef unsigned long cp_off, cp_size
281
233
 
282
234
    if not PyString_CheckExact(source_bytes):
283
235
        raise TypeError('source is not a str')
284
236
    if not PyString_CheckExact(delta_bytes):
285
237
        raise TypeError('delta is not a str')
 
238
 
286
239
    source = PyString_AS_STRING(source_bytes)
287
240
    source_size = PyString_GET_SIZE(source_bytes)
288
241
    delta = PyString_AS_STRING(delta_bytes)
289
242
    delta_size = PyString_GET_SIZE(delta_bytes)
 
243
 
290
244
    # Code taken from patch-delta.c, only brought here to give better error
291
245
    # handling, and to avoid double allocating memory
292
246
    if (delta_size < DELTA_SIZE_MIN):
294
248
        raise RuntimeError('delta_size %d smaller than min delta size %d'
295
249
                           % (delta_size, DELTA_SIZE_MIN))
296
250
 
297
 
    return _apply_delta(source, source_size, delta, delta_size)
298
 
 
299
 
 
300
 
cdef unsigned char *_decode_copy_instruction(unsigned char *bytes,
301
 
    unsigned char cmd, unsigned int *offset,
302
 
    unsigned int *length) nogil: # cannot_raise
303
 
    """Decode a copy instruction from the next few bytes.
304
 
 
305
 
    A copy instruction is a variable number of bytes, so we will parse the
306
 
    bytes we care about, and return the new position, as well as the offset and
307
 
    length referred to in the bytes.
308
 
 
309
 
    :param bytes: Pointer to the start of bytes after cmd
310
 
    :param cmd: The command code
311
 
    :return: Pointer to the bytes just after the last decode byte
312
 
    """
313
 
    cdef unsigned int off, size, count
314
 
    off = 0
315
 
    size = 0
316
 
    count = 0
317
 
    if (cmd & 0x01):
318
 
        off = bytes[count]
319
 
        count = count + 1
320
 
    if (cmd & 0x02):
321
 
        off = off | (bytes[count] << 8)
322
 
        count = count + 1
323
 
    if (cmd & 0x04):
324
 
        off = off | (bytes[count] << 16)
325
 
        count = count + 1
326
 
    if (cmd & 0x08):
327
 
        off = off | (bytes[count] << 24)
328
 
        count = count + 1
329
 
    if (cmd & 0x10):
330
 
        size = bytes[count]
331
 
        count = count + 1
332
 
    if (cmd & 0x20):
333
 
        size = size | (bytes[count] << 8)
334
 
        count = count + 1
335
 
    if (cmd & 0x40):
336
 
        size = size | (bytes[count] << 16)
337
 
        count = count + 1
338
 
    if (size == 0):
339
 
        size = 0x10000
340
 
    offset[0] = off
341
 
    length[0] = size
342
 
    return bytes + count
343
 
 
344
 
 
345
 
cdef object _apply_delta(char *source, Py_ssize_t source_size,
346
 
                         char *delta, Py_ssize_t delta_size):
347
 
    """common functionality between apply_delta and apply_delta_to_source."""
348
 
    cdef unsigned char *data, *top
349
 
    cdef unsigned char *dst_buf, *out, cmd
350
 
    cdef Py_ssize_t size
351
 
    cdef unsigned int cp_off, cp_size
352
 
    cdef int failed
353
 
 
354
251
    data = <unsigned char *>delta
355
252
    top = data + delta_size
356
253
 
 
254
    # make sure the orig file size matches what we expect
 
255
    # XXX: gcc warns because data isn't defined as 'const'
 
256
    size = get_delta_hdr_size(&data, top)
 
257
    if (size > source_size):
 
258
        # XXX: mismatched source size
 
259
        raise RuntimeError('source size %d < expected source size %d'
 
260
                           % (source_size, size))
 
261
    source_size = size
 
262
 
357
263
    # now the result size
358
264
    size = get_delta_hdr_size(&data, top)
359
265
    result = PyString_FromStringAndSize(NULL, size)
360
266
    dst_buf = <unsigned char*>PyString_AS_STRING(result)
361
 
 
362
 
    failed = 0
363
 
    with nogil:
364
 
        out = dst_buf
365
 
        while (data < top):
366
 
            cmd = data[0]
367
 
            data = data + 1
368
 
            if (cmd & 0x80):
369
 
                # Copy instruction
370
 
                data = _decode_copy_instruction(data, cmd, &cp_off, &cp_size)
371
 
                if (cp_off + cp_size < cp_size or
372
 
                    cp_off + cp_size > source_size or
373
 
                    cp_size > size):
374
 
                    failed = 1
375
 
                    break
376
 
                memcpy(out, source + cp_off, cp_size)
377
 
                out = out + cp_size
378
 
                size = size - cp_size
379
 
            else:
380
 
                # Insert instruction
381
 
                if cmd == 0:
382
 
                    # cmd == 0 is reserved for future encoding
383
 
                    # extensions. In the mean time we must fail when
384
 
                    # encountering them (might be data corruption).
385
 
                    failed = 2
386
 
                    break
387
 
                if cmd > size:
388
 
                    failed = 3
389
 
                    break
390
 
                memcpy(out, data, cmd)
391
 
                out = out + cmd
392
 
                data = data + cmd
393
 
                size = size - cmd
394
 
    if failed:
395
 
        if failed == 1:
396
 
            raise ValueError('Something wrong with:'
397
 
                ' cp_off = %s, cp_size = %s'
398
 
                ' source_size = %s, size = %s'
399
 
                % (cp_off, cp_size, source_size, size))
400
 
        elif failed == 2:
401
 
            raise ValueError('Got delta opcode: 0, not supported')
402
 
        elif failed == 3:
403
 
            raise ValueError('Insert instruction longer than remaining'
404
 
                ' bytes: %d > %d' % (cmd, size))
405
 
 
406
 
    # sanity check
 
267
    # XXX: The original code added a trailing null here, but this shouldn't be
 
268
    #      necessary when using PyString_FromStringAndSize
 
269
    # dst_buf[size] = 0
 
270
 
 
271
    out = dst_buf
 
272
    while (data < top):
 
273
        cmd = data[0]
 
274
        data = data + 1
 
275
        if (cmd & 0x80):
 
276
            cp_off = cp_size = 0
 
277
            if (cmd & 0x01):
 
278
                cp_off = data[0]
 
279
                data = data + 1
 
280
            if (cmd & 0x02):
 
281
                cp_off = cp_off | (data[0] << 8)
 
282
                data = data + 1
 
283
            if (cmd & 0x04):
 
284
                cp_off = cp_off | (data[0] << 16)
 
285
                data = data + 1
 
286
            if (cmd & 0x08):
 
287
                cp_off = cp_off | (data[0] << 24)
 
288
                data = data + 1
 
289
            if (cmd & 0x10):
 
290
                cp_size = data[0]
 
291
                data = data + 1
 
292
            if (cmd & 0x20):
 
293
                cp_size = cp_size | (data[0] << 8)
 
294
                data = data + 1
 
295
            if (cmd & 0x40):
 
296
                cp_size = cp_size | (data[0] << 16)
 
297
                data = data + 1
 
298
            if (cp_size == 0):
 
299
                cp_size = 0x10000
 
300
            if (cp_off + cp_size < cp_size or
 
301
                cp_off + cp_size > source_size or
 
302
                cp_size > size):
 
303
                raise RuntimeError('Something wrong with:'
 
304
                    ' cp_off = %s, cp_size = %s'
 
305
                    ' source_size = %s, size = %s'
 
306
                    % (cp_off, cp_size, source_size, size))
 
307
            memcpy(out, source + cp_off, cp_size)
 
308
            out = out + cp_size
 
309
            size = size - cp_size
 
310
        elif (cmd):
 
311
            if (cmd > size):
 
312
                raise RuntimeError('Insert instruction longer than remaining'
 
313
                    ' bytes: %d > %d' % (cmd, size))
 
314
            memcpy(out, data, cmd)
 
315
            out = out + cmd
 
316
            data = data + cmd
 
317
            size = size - cmd
 
318
        else:
 
319
            # /*
 
320
            #  * cmd == 0 is reserved for future encoding
 
321
            #  * extensions. In the mean time we must fail when
 
322
            #  * encountering them (might be data corruption).
 
323
            #  */
 
324
            ## /* XXX: error("unexpected delta opcode 0"); */
 
325
            raise RuntimeError('Got delta opcode: 0, not supported')
 
326
 
 
327
    # /* sanity check */
407
328
    if (data != top or size != 0):
 
329
        ## /* XXX: error("delta replay has gone wild"); */
408
330
        raise RuntimeError('Did not extract the number of bytes we expected'
409
331
            ' we were left with %d bytes in "size", and top - data = %d'
410
332
            % (size, <int>(top - data)))
411
333
        return None
412
334
 
413
335
    # *dst_size = out - dst_buf;
414
 
    if (out - dst_buf) != PyString_GET_SIZE(result):
415
 
        raise RuntimeError('Number of bytes extracted did not match the'
416
 
            ' size encoded in the delta header.')
 
336
    assert (out - dst_buf) == PyString_GET_SIZE(result)
417
337
    return result
418
 
 
419
 
 
420
 
def apply_delta_to_source(source, delta_start, delta_end):
421
 
    """Extract a delta from source bytes, and apply it."""
422
 
    cdef char *c_source
423
 
    cdef Py_ssize_t c_source_size
424
 
    cdef char *c_delta
425
 
    cdef Py_ssize_t c_delta_size
426
 
    cdef Py_ssize_t c_delta_start, c_delta_end
427
 
 
428
 
    if not PyString_CheckExact(source):
429
 
        raise TypeError('source is not a str')
430
 
    c_source_size = PyString_GET_SIZE(source)
431
 
    c_delta_start = delta_start
432
 
    c_delta_end = delta_end
433
 
    if c_delta_start >= c_source_size:
434
 
        raise ValueError('delta starts after source')
435
 
    if c_delta_end > c_source_size:
436
 
        raise ValueError('delta ends after source')
437
 
    if c_delta_start >= c_delta_end:
438
 
        raise ValueError('delta starts after it ends')
439
 
 
440
 
    c_delta_size = c_delta_end - c_delta_start
441
 
    c_source = PyString_AS_STRING(source)
442
 
    c_delta = c_source + c_delta_start
443
 
    # We don't use source_size, because we know the delta should not refer to
444
 
    # any bytes after it starts
445
 
    return _apply_delta(c_source, c_delta_start, c_delta, c_delta_size)
446
 
 
447
 
 
448
 
def encode_base128_int(val):
449
 
    """Convert an integer into a 7-bit lsb encoding."""
450
 
    cdef unsigned int c_val
451
 
    cdef Py_ssize_t count
452
 
    cdef unsigned int num_bytes
453
 
    cdef unsigned char c_bytes[8] # max size for 32-bit int is 5 bytes
454
 
 
455
 
    c_val = val
456
 
    count = 0
457
 
    while c_val >= 0x80 and count < 8:
458
 
        c_bytes[count] = <unsigned char>((c_val | 0x80) & 0xFF)
459
 
        c_val = c_val >> 7
460
 
        count = count + 1
461
 
    if count >= 8 or c_val >= 0x80:
462
 
        raise ValueError('encode_base128_int overflowed the buffer')
463
 
    c_bytes[count] = <unsigned char>(c_val & 0xFF)
464
 
    count = count + 1
465
 
    return PyString_FromStringAndSize(<char *>c_bytes, count)
466
 
 
467
 
 
468
 
def decode_base128_int(bytes):
469
 
    """Decode an integer from a 7-bit lsb encoding."""
470
 
    cdef int offset
471
 
    cdef int val
472
 
    cdef unsigned int uval
473
 
    cdef int shift
474
 
    cdef Py_ssize_t num_low_bytes
475
 
    cdef unsigned char *c_bytes
476
 
 
477
 
    offset = 0
478
 
    val = 0
479
 
    shift = 0
480
 
    if not PyString_CheckExact(bytes):
481
 
        raise TypeError('bytes is not a string')
482
 
    c_bytes = <unsigned char*>PyString_AS_STRING(bytes)
483
 
    # We take off 1, because we have to be able to decode the non-expanded byte
484
 
    num_low_bytes = PyString_GET_SIZE(bytes) - 1
485
 
    while (c_bytes[offset] & 0x80) and offset < num_low_bytes:
486
 
        val = val | ((c_bytes[offset] & 0x7F) << shift)
487
 
        shift = shift + 7
488
 
        offset = offset + 1
489
 
    if c_bytes[offset] & 0x80:
490
 
        raise ValueError('Data not properly formatted, we ran out of'
491
 
                         ' bytes before 0x80 stopped being set.')
492
 
    val = val | (c_bytes[offset] << shift)
493
 
    offset = offset + 1
494
 
    if val < 0:
495
 
        uval = <unsigned int> val
496
 
        return uval, offset
497
 
    return val, offset
498
 
 
499