]> arthur.barton.de Git - bup.git/blob - lib/bup/_helpers.c
t/test-meta.sh: detect and handle fakeroot.
[bup.git] / lib / bup / _helpers.c
1 #define _LARGEFILE64_SOURCE 1
2
3 #include "bupsplit.h"
4 #include <Python.h>
5 #include <assert.h>
6 #include <errno.h>
7 #include <fcntl.h>
8 #include <arpa/inet.h>
9 #include <stdint.h>
10
11 #ifdef linux
12 #include <linux/fs.h>
13 #include <sys/ioctl.h>
14 #include <sys/stat.h>
15 #include <sys/time.h>
16 #endif
17
18
19 static PyObject *selftest(PyObject *self, PyObject *args)
20 {
21     if (!PyArg_ParseTuple(args, ""))
22         return NULL;
23     
24     return Py_BuildValue("i", !bupsplit_selftest());
25 }
26
27
28 static PyObject *blobbits(PyObject *self, PyObject *args)
29 {
30     if (!PyArg_ParseTuple(args, ""))
31         return NULL;
32     return Py_BuildValue("i", BUP_BLOBBITS);
33 }
34
35
36 static PyObject *splitbuf(PyObject *self, PyObject *args)
37 {
38     unsigned char *buf = NULL;
39     int len = 0, out = 0, bits = -1;
40
41     if (!PyArg_ParseTuple(args, "t#", &buf, &len))
42         return NULL;
43     out = bupsplit_find_ofs(buf, len, &bits);
44     return Py_BuildValue("ii", out, bits);
45 }
46
47
48 static PyObject *bitmatch(PyObject *self, PyObject *args)
49 {
50     unsigned char *buf1 = NULL, *buf2 = NULL;
51     int len1 = 0, len2 = 0;
52     int byte, bit;
53
54     if (!PyArg_ParseTuple(args, "t#t#", &buf1, &len1, &buf2, &len2))
55         return NULL;
56     
57     bit = 0;
58     for (byte = 0; byte < len1 && byte < len2; byte++)
59     {
60         int b1 = buf1[byte], b2 = buf2[byte];
61         if (b1 != b2)
62         {
63             for (bit = 0; bit < 8; bit++)
64                 if ( (b1 & (0x80 >> bit)) != (b2 & (0x80 >> bit)) )
65                     break;
66             break;
67         }
68     }
69     
70     return Py_BuildValue("i", byte*8 + bit);
71 }
72
73
74 static PyObject *firstword(PyObject *self, PyObject *args)
75 {
76     unsigned char *buf = NULL;
77     int len = 0;
78     uint32_t v;
79
80     if (!PyArg_ParseTuple(args, "t#", &buf, &len))
81         return NULL;
82     
83     if (len < 4)
84         return NULL;
85     
86     v = ntohl(*(uint32_t *)buf);
87     return Py_BuildValue("I", v);
88 }
89
90
91 static PyObject *extract_bits(PyObject *self, PyObject *args)
92 {
93     unsigned char *buf = NULL;
94     int len = 0, nbits = 0;
95     uint32_t v, mask;
96
97     if (!PyArg_ParseTuple(args, "t#i", &buf, &len, &nbits))
98         return NULL;
99     
100     if (len < 4)
101         return NULL;
102     
103     mask = (1<<nbits) - 1;
104     v = ntohl(*(uint32_t *)buf);
105     v = (v >> (32-nbits)) & mask;
106     return Py_BuildValue("I", v);
107 }
108
109
110 // I would have made this a lower-level function that just fills in a buffer
111 // with random values, and then written those values from python.  But that's
112 // about 20% slower in my tests, and since we typically generate random
113 // numbers for benchmarking other parts of bup, any slowness in generating
114 // random bytes will make our benchmarks inaccurate.  Plus nobody wants
115 // pseudorandom bytes much except for this anyway.
116 static PyObject *write_random(PyObject *self, PyObject *args)
117 {
118     uint32_t buf[1024/4];
119     int fd = -1, seed = 0;
120     ssize_t ret;
121     long long len = 0, kbytes = 0, written = 0;
122
123     if (!PyArg_ParseTuple(args, "iLi", &fd, &len, &seed))
124         return NULL;
125     
126     srandom(seed);
127     
128     for (kbytes = 0; kbytes < len/1024; kbytes++)
129     {
130         unsigned i;
131         for (i = 0; i < sizeof(buf)/sizeof(buf[0]); i++)
132             buf[i] = random();
133         ret = write(fd, buf, sizeof(buf));
134         if (ret < 0)
135             ret = 0;
136         written += ret;
137         if (ret < (int)sizeof(buf))
138             break;
139         if (kbytes/1024 > 0 && !(kbytes%1024))
140             fprintf(stderr, "Random: %lld Mbytes\r", kbytes/1024);
141     }
142     
143     // handle non-multiples of 1024
144     if (len % 1024)
145     {
146         unsigned i;
147         for (i = 0; i < sizeof(buf)/sizeof(buf[0]); i++)
148             buf[i] = random();
149         ret = write(fd, buf, len % 1024);
150         if (ret < 0)
151             ret = 0;
152         written += ret;
153     }
154     
155     if (kbytes/1024 > 0)
156         fprintf(stderr, "Random: %lld Mbytes, done.\n", kbytes/1024);
157     return Py_BuildValue("L", written);
158 }
159
160
161 static PyObject *open_noatime(PyObject *self, PyObject *args)
162 {
163     char *filename = NULL;
164     int attrs, attrs_noatime, fd;
165     if (!PyArg_ParseTuple(args, "s", &filename))
166         return NULL;
167     attrs = O_RDONLY;
168 #ifdef O_NOFOLLOW
169     attrs |= O_NOFOLLOW;
170 #endif
171 #ifdef O_LARGEFILE
172     attrs |= O_LARGEFILE;
173 #endif
174     attrs_noatime = attrs;
175 #ifdef O_NOATIME
176     attrs_noatime |= O_NOATIME;
177 #endif
178     fd = open(filename, attrs_noatime);
179     if (fd < 0 && errno == EPERM)
180     {
181         // older Linux kernels would return EPERM if you used O_NOATIME
182         // and weren't the file's owner.  This pointless restriction was
183         // relaxed eventually, but we have to handle it anyway.
184         // (VERY old kernels didn't recognized O_NOATIME, but they would
185         // just harmlessly ignore it, so this branch won't trigger)
186         fd = open(filename, attrs);
187     }
188     if (fd < 0)
189         return PyErr_SetFromErrnoWithFilename(PyExc_IOError, filename);
190     return Py_BuildValue("i", fd);
191 }
192
193
194 static PyObject *fadvise_done(PyObject *self, PyObject *args)
195 {
196     int fd = -1;
197     long long ofs = 0;
198     if (!PyArg_ParseTuple(args, "iL", &fd, &ofs))
199         return NULL;
200 #ifdef POSIX_FADV_DONTNEED
201     posix_fadvise(fd, 0, ofs, POSIX_FADV_DONTNEED);
202 #endif    
203     return Py_BuildValue("");
204 }
205
206
207 #ifdef linux
208 static PyObject *bup_get_linux_file_attr(PyObject *self, PyObject *args)
209 {
210     int rc;
211     unsigned long attr;
212     char *path;
213     int fd;
214
215     if (!PyArg_ParseTuple(args, "s", &path))
216         return NULL;
217
218     fd = open(path, O_RDONLY | O_NONBLOCK | O_LARGEFILE | O_NOFOLLOW);
219     if (fd == -1)
220         return PyErr_SetFromErrnoWithFilename(PyExc_IOError, path);
221
222     attr = 0;
223     rc = ioctl(fd, FS_IOC_GETFLAGS, &attr);
224     if (rc == -1)
225     {
226         close(fd);
227         return PyErr_SetFromErrnoWithFilename(PyExc_IOError, path);
228     }
229
230     close(fd);
231     return Py_BuildValue("k", attr);
232 }
233
234
235 static PyObject *bup_set_linux_file_attr(PyObject *self, PyObject *args)
236 {
237     int rc;
238     unsigned long attr;
239     char *path;
240     int fd;
241
242     if (!PyArg_ParseTuple(args, "sk", &path, &attr))
243         return NULL;
244
245     fd = open(path, O_RDONLY | O_NONBLOCK | O_LARGEFILE | O_NOFOLLOW);
246     if(fd == -1)
247         return PyErr_SetFromErrnoWithFilename(PyExc_IOError, path);
248
249     rc = ioctl(fd, FS_IOC_SETFLAGS, &attr);
250     if (rc == -1)
251     {
252         close(fd);
253         return PyErr_SetFromErrnoWithFilename(PyExc_IOError, path);
254     }
255
256     close(fd);
257     Py_RETURN_TRUE;
258 }
259 #endif /* def linux */
260
261
262 #if defined(_ATFILE_SOURCE) \
263   || _XOPEN_SOURCE >= 700 || _POSIX_C_SOURCE >= 200809L
264 #define HAVE_BUP_UTIMENSAT 1
265
266 static PyObject *bup_utimensat(PyObject *self, PyObject *args)
267 {
268     int rc, dirfd, flags;
269     char *path;
270     long access, access_ns, modification, modification_ns;
271     struct timespec ts[2];
272
273     if (!PyArg_ParseTuple(args, "is((ll)(ll))i",
274                           &dirfd,
275                           &path,
276                           &access, &access_ns,
277                           &modification, &modification_ns,
278                           &flags))
279         return NULL;
280
281     if (isnan(access))
282     {
283         PyErr_SetString(PyExc_ValueError, "access time is NaN");
284         return NULL;
285     }
286     else if (isinf(access))
287     {
288         PyErr_SetString(PyExc_ValueError, "access time is infinite");
289         return NULL;
290     }
291     else if (isnan(modification))
292     {
293         PyErr_SetString(PyExc_ValueError, "modification time is NaN");
294         return NULL;
295     }
296     else if (isinf(modification))
297     {
298         PyErr_SetString(PyExc_ValueError, "modification time is infinite");
299         return NULL;
300     }
301
302     if (isnan(access_ns))
303     {
304         PyErr_SetString(PyExc_ValueError, "access time ns is NaN");
305         return NULL;
306     }
307     else if (isinf(access_ns))
308     {
309         PyErr_SetString(PyExc_ValueError, "access time ns is infinite");
310         return NULL;
311     }
312     else if (isnan(modification_ns))
313     {
314         PyErr_SetString(PyExc_ValueError, "modification time ns is NaN");
315         return NULL;
316     }
317     else if (isinf(modification_ns))
318     {
319         PyErr_SetString(PyExc_ValueError, "modification time ns is infinite");
320         return NULL;
321     }
322
323     ts[0].tv_sec = access;
324     ts[0].tv_nsec = access_ns;
325     ts[1].tv_sec = modification;
326     ts[1].tv_nsec = modification_ns;
327
328     rc = utimensat(dirfd, path, ts, flags);
329     if (rc != 0)
330         return PyErr_SetFromErrnoWithFilename(PyExc_IOError, path);
331
332     Py_RETURN_TRUE;
333 }
334
335 #endif /* defined(_ATFILE_SOURCE)
336           || _XOPEN_SOURCE >= 700 || _POSIX_C_SOURCE >= 200809L */
337
338
339 #ifdef linux /* and likely others */
340
341 #define HAVE_BUP_STAT 1
342 static PyObject *bup_stat(PyObject *self, PyObject *args)
343 {
344     int rc;
345     char *filename;
346
347     if (!PyArg_ParseTuple(args, "s", &filename))
348         return NULL;
349
350     struct stat st;
351     rc = stat(filename, &st);
352     if (rc != 0)
353         return PyErr_SetFromErrnoWithFilename(PyExc_IOError, filename);
354
355     return Py_BuildValue("kkkkkkkk"
356                          "(ll)"
357                          "(ll)"
358                          "(ll)",
359                          (unsigned long) st.st_mode,
360                          (unsigned long) st.st_ino,
361                          (unsigned long) st.st_dev,
362                          (unsigned long) st.st_nlink,
363                          (unsigned long) st.st_uid,
364                          (unsigned long) st.st_gid,
365                          (unsigned long) st.st_rdev,
366                          (unsigned long) st.st_size,
367                          (long) st.st_atime,
368                          (long) st.st_atim.tv_nsec,
369                          (long) st.st_mtime,
370                          (long) st.st_mtim.tv_nsec,
371                          (long) st.st_ctime,
372                          (long) st.st_ctim.tv_nsec);
373 }
374
375
376 #define HAVE_BUP_LSTAT 1
377 static PyObject *bup_lstat(PyObject *self, PyObject *args)
378 {
379     int rc;
380     char *filename;
381
382     if (!PyArg_ParseTuple(args, "s", &filename))
383         return NULL;
384
385     struct stat st;
386     rc = lstat(filename, &st);
387     if (rc != 0)
388         return PyErr_SetFromErrnoWithFilename(PyExc_IOError, filename);
389
390     return Py_BuildValue("kkkkkkkk"
391                          "(ll)"
392                          "(ll)"
393                          "(ll)",
394                          (unsigned long) st.st_mode,
395                          (unsigned long) st.st_ino,
396                          (unsigned long) st.st_dev,
397                          (unsigned long) st.st_nlink,
398                          (unsigned long) st.st_uid,
399                          (unsigned long) st.st_gid,
400                          (unsigned long) st.st_rdev,
401                          (unsigned long) st.st_size,
402                          (long) st.st_atime,
403                          (long) st.st_atim.tv_nsec,
404                          (long) st.st_mtime,
405                          (long) st.st_mtim.tv_nsec,
406                          (long) st.st_ctime,
407                          (long) st.st_ctim.tv_nsec);
408 }
409
410
411 #define HAVE_BUP_FSTAT 1
412 static PyObject *bup_fstat(PyObject *self, PyObject *args)
413 {
414     int rc, fd;
415
416     if (!PyArg_ParseTuple(args, "i", &fd))
417         return NULL;
418
419     struct stat st;
420     rc = fstat(fd, &st);
421     if (rc != 0)
422         return PyErr_SetFromErrno(PyExc_IOError);
423
424     return Py_BuildValue("kkkkkkkk"
425                          "(ll)"
426                          "(ll)"
427                          "(ll)",
428                          (unsigned long) st.st_mode,
429                          (unsigned long) st.st_ino,
430                          (unsigned long) st.st_dev,
431                          (unsigned long) st.st_nlink,
432                          (unsigned long) st.st_uid,
433                          (unsigned long) st.st_gid,
434                          (unsigned long) st.st_rdev,
435                          (unsigned long) st.st_size,
436                          (long) st.st_atime,
437                          (long) st.st_atim.tv_nsec,
438                          (long) st.st_mtime,
439                          (long) st.st_mtim.tv_nsec,
440                          (long) st.st_ctime,
441                          (long) st.st_ctim.tv_nsec);
442 }
443
444 #endif /* def linux */
445
446
447 static PyMethodDef helper_methods[] = {
448     { "selftest", selftest, METH_VARARGS,
449         "Check that the rolling checksum rolls correctly (for unit tests)." },
450     { "blobbits", blobbits, METH_VARARGS,
451         "Return the number of bits in the rolling checksum." },
452     { "splitbuf", splitbuf, METH_VARARGS,
453         "Split a list of strings based on a rolling checksum." },
454     { "bitmatch", bitmatch, METH_VARARGS,
455         "Count the number of matching prefix bits between two strings." },
456     { "firstword", firstword, METH_VARARGS,
457         "Return an int corresponding to the first 32 bits of buf." },
458     { "extract_bits", extract_bits, METH_VARARGS,
459         "Take the first 'nbits' bits from 'buf' and return them as an int." },
460     { "write_random", write_random, METH_VARARGS,
461         "Write random bytes to the given file descriptor" },
462     { "open_noatime", open_noatime, METH_VARARGS,
463         "open() the given filename for read with O_NOATIME if possible" },
464     { "fadvise_done", fadvise_done, METH_VARARGS,
465         "Inform the kernel that we're finished with earlier parts of a file" },
466 #ifdef linux
467     { "get_linux_file_attr", bup_get_linux_file_attr, METH_VARARGS,
468       "Return the Linux attributes for the given file." },
469     { "set_linux_file_attr", bup_set_linux_file_attr, METH_VARARGS,
470       "Set the Linux attributes for the given file." },
471 #endif
472 #ifdef HAVE_BUP_UTIMENSAT
473     { "utimensat", bup_utimensat, METH_VARARGS,
474       "Change file timestamps with nanosecond precision." },
475 #endif
476 #ifdef HAVE_BUP_STAT
477     { "stat", bup_stat, METH_VARARGS,
478       "Extended version of stat." },
479 #endif
480 #ifdef HAVE_BUP_LSTAT
481     { "lstat", bup_lstat, METH_VARARGS,
482       "Extended version of lstat." },
483 #endif
484 #ifdef HAVE_BUP_FSTAT
485     { "fstat", bup_fstat, METH_VARARGS,
486       "Extended version of fstat." },
487 #endif
488     { NULL, NULL, 0, NULL },  // sentinel
489 };
490
491
492 PyMODINIT_FUNC init_helpers(void)
493 {
494     PyObject *m = Py_InitModule("_helpers", helper_methods);
495     if (m == NULL)
496         return;
497 #ifdef HAVE_BUP_UTIMENSAT
498     PyModule_AddObject(m, "AT_FDCWD", Py_BuildValue("i", AT_FDCWD));
499     PyModule_AddObject(m, "AT_SYMLINK_NOFOLLOW",
500                        Py_BuildValue("i", AT_SYMLINK_NOFOLLOW));
501 #endif
502 #ifdef HAVE_BUP_STAT
503     Py_INCREF(Py_True);
504     PyModule_AddObject(m, "_have_ns_fs_timestamps", Py_True);
505 #else
506     Py_INCREF(Py_False);
507     PyModule_AddObject(m, "_have_ns_fs_timestamps", Py_False);
508 #endif
509 }