]> arthur.barton.de Git - bup.git/blobdiff - lib/bup/_helpers.c
restore: fix --sparse fix (find_non_sparse_end)
[bup.git] / lib / bup / _helpers.c
index 2a5dc468b7b660a849c6e67df60c8fa91da26b1a..d4525855ee841a0a3285857ce126798066687374 100644 (file)
@@ -11,6 +11,7 @@
 #include <errno.h>
 #include <fcntl.h>
 #include <arpa/inet.h>
+#include <stddef.h>
 #include <stdint.h>
 #include <stdlib.h>
 #include <stdio.h>
 #include <sys/ioctl.h>
 #endif
 
+#ifdef HAVE_TM_TM_GMTOFF
+#include <time.h>
+#endif
+
 #include "bupsplit.h"
 
 #if defined(FS_IOC_GETFLAGS) && defined(FS_IOC_SETFLAGS)
@@ -52,6 +57,9 @@
 #define FS_NOCOW_FL 0
 #endif
 
+
+typedef unsigned char byte;
+
 static int istty2 = 0;
 
 
@@ -229,16 +237,6 @@ static void unpythonize_argv(void)
 #endif // not __WIN32__ or __CYGWIN__
 
 
-static unsigned long long count_leading_zeros(const unsigned char * const buf,
-                                              unsigned long long len)
-{
-    const unsigned char *cur = buf;
-    while(len-- && *cur == 0)
-        cur++;
-    return cur - buf;
-}
-
-
 static int write_all(int fd, const void *buf, const size_t count)
 {
     size_t written = 0;
@@ -265,6 +263,141 @@ static int uadd(unsigned long long *dest,
 }
 
 
+static PyObject *append_sparse_region(const int fd, unsigned long long n)
+{
+    while (n)
+    {
+        off_t new_off;
+        if (!INTEGRAL_ASSIGNMENT_FITS(&new_off, n))
+            new_off = INT_MAX;
+        const off_t off = lseek(fd, new_off, SEEK_CUR);
+        if (off == (off_t) -1)
+            return PyErr_SetFromErrno(PyExc_IOError);
+        n -= new_off;
+    }
+    return NULL;
+}
+
+
+static PyObject *record_sparse_zeros(unsigned long long *new_pending,
+                                     const int fd,
+                                     unsigned long long prev_pending,
+                                     const unsigned long long count)
+{
+    // Add count additional sparse zeros to prev_pending and store the
+    // result in new_pending, or if the total won't fit in
+    // new_pending, write some of the zeros to fd sparsely, and store
+    // the remaining sum in new_pending.
+    if (!uadd(new_pending, prev_pending, count))
+    {
+        PyObject *err = append_sparse_region(fd, prev_pending);
+        if (err != NULL)
+            return err;
+        *new_pending = count;
+    }
+    return NULL;
+}
+
+
+static const byte * find_not_zero(const byte * const start,
+                                  const byte * const end)
+{
+    // Return a pointer to first non-zero byte between start and end,
+    // or end if there isn't one.
+    assert(start <= end);
+    const unsigned char *cur = start;
+    while (cur < end && *cur == 0)
+        cur++;
+    return cur;
+}
+
+
+static const byte * const find_trailing_zeros(const byte * const start,
+                                              const byte * const end)
+{
+    // Return a pointer to the start of any trailing run of zeros, or
+    // end if there isn't one.
+    assert(start <= end);
+    if (start == end)
+        return end;
+    const byte * cur = end;
+    while (cur > start && *--cur == 0) {}
+    if (*cur == 0)
+        return cur;
+    else
+        return cur + 1;
+}
+
+
+static const byte *find_non_sparse_end(const byte * const start,
+                                       const byte * const end,
+                                       const unsigned long long min_len)
+{
+    // Return the first pointer to a min_len sparse block in [start,
+    // end) if there is one, otherwise a pointer to the start of any
+    // trailing run of zeros.  If there are no trailing zeros, return
+    // end.
+    if (start == end)
+        return end;
+    assert(start < end);
+    assert(min_len);
+    // Probe in min_len jumps, searching backward from the jump
+    // destination for a non-zero byte.  If such a byte is found, move
+    // just past it and try again.
+    const byte *candidate = start;
+    // End of any run of zeros, starting at candidate, that we've already seen
+    const byte *end_of_known_zeros = candidate;
+    while (end - candidate >= min_len) // Handle all min_len candidate blocks
+    {
+        const byte * const probe_end = candidate + min_len;
+        const byte * const trailing_zeros =
+            find_trailing_zeros(end_of_known_zeros, probe_end);
+        if (trailing_zeros == probe_end)
+            end_of_known_zeros = candidate = probe_end;
+        else if (trailing_zeros == end_of_known_zeros)
+        {
+            assert(candidate >= start);
+            assert(candidate <= end);
+            assert(*candidate == 0);
+            return candidate;
+        }
+        else
+        {
+            candidate = trailing_zeros;
+            end_of_known_zeros = probe_end;
+        }
+    }
+
+    if (candidate == end)
+        return end;
+
+    // No min_len sparse run found, search backward from end
+    const byte * const trailing_zeros = find_trailing_zeros(end_of_known_zeros,
+                                                            end);
+
+    if (trailing_zeros == end_of_known_zeros)
+    {
+        assert(candidate >= start);
+        assert(candidate < end);
+        assert(*candidate == 0);
+        assert(end - candidate < min_len);
+        return candidate;
+    }
+
+    if (trailing_zeros == end)
+    {
+        assert(*(end - 1) != 0);
+        return end;
+    }
+
+    assert(end - trailing_zeros < min_len);
+    assert(trailing_zeros >= start);
+    assert(trailing_zeros < end);
+    assert(*trailing_zeros == 0);
+    return trailing_zeros;
+}
+
+
 static PyObject *bup_write_sparsely(PyObject *self, PyObject *args)
 {
     int fd;
@@ -285,69 +418,50 @@ static PyObject *bup_write_sparsely(PyObject *self, PyObject *args)
     if (!INTEGRAL_ASSIGNMENT_FITS(&buf_len, sbuf_len))
         return PyErr_Format(PyExc_OverflowError, "buffer length too large");
 
-    // For now, there are some cases where we just give up if the
-    // values are too large, but we could try to break up the relevant
-    // operations into chunks.
-
-    // Deal with preceding zeros.  Just make them sparse, along with
-    // any leading zeros in buf, even if the region's not >= min,
-    // since the alternative is a potentially extra small write.
-    if (prev_sparse_len)
+    const byte * block = buf; // Start of pending block
+    const byte * const end = buf + buf_len;
+    unsigned long long zeros = prev_sparse_len;
+    while (1)
     {
-        const unsigned long long zeros = count_leading_zeros(buf, buf_len);
-        unsigned long long new_sparse_len = 0;
-        if (!uadd(&new_sparse_len, prev_sparse_len, zeros))
-            return PyErr_Format (PyExc_OverflowError, "sparse region too large");
-        if (zeros == buf_len)
-            return PyLong_FromUnsignedLongLong(new_sparse_len);
-
-        off_t new_off;
-        if (!INTEGRAL_ASSIGNMENT_FITS(&new_off, new_sparse_len))
-            return PyErr_Format(PyExc_OverflowError,
-                                "sparse region too large for seek");
-        const off_t off = lseek(fd, new_off, SEEK_CUR);
-        if (off == -1)
-            return PyErr_SetFromErrno(PyExc_IOError);
-        buf += zeros;
-        buf_len -= zeros;
-    }
+        assert(block <= end);
+        if (block == end)
+            return PyLong_FromUnsignedLongLong(zeros);
 
-    int rc;
-    unsigned long long unexamined = buf_len;
-    unsigned char *block_start = buf, *cur = buf;
-    while(unexamined)
-    {
-        const unsigned long long zeros = count_leading_zeros(cur, unexamined);
-        assert(zeros <= unexamined);
-        unexamined -= zeros;
-        if (unexamined == 0)  // Runs off the end.
+        if (*block != 0)
         {
-            rc = write_all(fd, block_start, cur - block_start);
+            // Look for the end of block, i.e. the next sparse run of
+            // at least min_sparse_len zeros, or the end of the
+            // buffer.
+            const byte * const probe = find_non_sparse_end(block + 1, end,
+                                                           min_sparse_len);
+            // Either at end of block, or end of non-sparse; write pending data
+            PyObject *err = append_sparse_region(fd, zeros);
+            if (err != NULL)
+                return err;
+            int rc = write_all(fd, block, probe - block);
             if (rc)
                 return PyErr_SetFromErrno(PyExc_IOError);
-            return PyLong_FromUnsignedLongLong(zeros);
-        }
-        cur += zeros;
-        if (zeros >= min_sparse_len)
-        {
-            off_t new_off;
-            if (!INTEGRAL_ASSIGNMENT_FITS(&new_off, zeros))
-                return PyErr_Format(PyExc_ValueError,
-                                    "zero count overflows off_t");
-            off_t off = lseek(fd, new_off, SEEK_CUR);
-            if (off == -1)
-                return PyErr_SetFromErrno(PyExc_IOError);
-            block_start = cur;
+
+            if (end - probe < min_sparse_len)
+                zeros = end - probe;
+            else
+                zeros = min_sparse_len;
+            block = probe + zeros;
         }
-        while (unexamined && *cur != 0)
+        else // *block == 0
         {
-            cur++; unexamined--;
+            // Should be in the first loop iteration, a sparse run of
+            // zeros, or nearly at the end of the block (within
+            // min_sparse_len).
+            const byte * const zeros_end = find_not_zero(block, end);
+            PyObject *err = record_sparse_zeros(&zeros, fd,
+                                                zeros, zeros_end - block);
+            if (err != NULL)
+                return err;
+            assert(block <= zeros_end);
+            block = zeros_end;
         }
     }
-    rc = write_all(fd, block_start, cur - block_start);
-    if (rc)
-        return PyErr_SetFromErrno(PyExc_IOError);
-    return PyInt_FromLong(0);
 }
 
 
@@ -1290,6 +1404,30 @@ static PyObject *bup_fstat(PyObject *self, PyObject *args)
 }
 
 
+#ifdef HAVE_TM_TM_GMTOFF
+static PyObject *bup_localtime(PyObject *self, PyObject *args)
+{
+    long long lltime;
+    time_t ttime;
+    if (!PyArg_ParseTuple(args, "L", &lltime))
+       return NULL;
+    if (!INTEGRAL_ASSIGNMENT_FITS(&ttime, lltime))
+        return PyErr_Format(PyExc_OverflowError, "time value too large");
+
+    struct tm tm;
+    if(localtime_r(&ttime, &tm) == NULL)
+        return PyErr_SetFromErrno(PyExc_OSError);
+
+    // Match the Python struct_time values.
+    return Py_BuildValue("[i,i,i,i,i,i,i,i,i,i,s]",
+                         1900 + tm.tm_year, tm.tm_mon + 1, tm.tm_mday,
+                         tm.tm_hour, tm.tm_min, tm.tm_sec,
+                         tm.tm_wday, tm.tm_yday + 1,
+                         tm.tm_isdst, tm.tm_gmtoff, tm.tm_zone);
+}
+#endif /* def HAVE_TM_TM_GMTOFF */
+
+
 static PyMethodDef helper_methods[] = {
     { "write_sparsely", bup_write_sparsely, METH_VARARGS,
       "Write buf excepting zeros at the end. Return trailing zero count." },
@@ -1348,6 +1486,10 @@ static PyMethodDef helper_methods[] = {
       "Extended version of lstat." },
     { "fstat", bup_fstat, METH_VARARGS,
       "Extended version of fstat." },
+#ifdef HAVE_TM_TM_GMTOFF
+    { "localtime", bup_localtime, METH_VARARGS,
+      "Return struct_time elements plus the timezone offset and name." },
+#endif
     { NULL, NULL, 0, NULL },  // sentinel
 };
 
@@ -1365,6 +1507,16 @@ PyMODINIT_FUNC init_helpers(void)
     assert(sizeof(PY_LONG_LONG) <= sizeof(long long));
     assert(sizeof(unsigned PY_LONG_LONG) <= sizeof(unsigned long long));
 
+    // Originally required by append_sparse_region()
+    {
+        off_t probe;
+        if (!INTEGRAL_ASSIGNMENT_FITS(&probe, INT_MAX))
+        {
+            fprintf(stderr, "off_t can't hold INT_MAX; please report.\n");
+            exit(1);
+        }
+    }
+
     char *e;
     PyObject *m = Py_InitModule("_helpers", helper_methods);
     if (m == NULL)