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