]> arthur.barton.de Git - bup.git/commitdiff
Merge branch 'meta'
authorAvery Pennarun <apenwarr@gmail.com>
Thu, 9 Jun 2011 03:15:48 +0000 (23:15 -0400)
committerAvery Pennarun <apenwarr@gmail.com>
Thu, 9 Jun 2011 03:15:48 +0000 (23:15 -0400)
* meta:
  Add utimes/lutimes implementations of _helpers utime() and lutime().
  Replace _helpers.utimensat() with utime() and lutime().
  Test for available nanosecond stat timestamp members.
  Add config.h dependency to _helpers in csetup.py.
  Add -*-shell-script-*- to configure.inc.
  Use FS_IOC_GETFLAGS/FS_IOC_SETFLAGS directly as the preprocessor guards.
  Verify the expected length of saved_errors in tmetadata.py.
  Don't use xstat.lutime() in test-meta.sh when xstat.utime() will do.
  Add meta support for restoring filesystem sockets.
  Add _recognized_file_types(); defer error for unrecognized restore.
  index.py: new format (V3), with inodes, link counts, and 64-bit times.
  Cap timestamps in index to avoid needing to worry about fractional parts.
  index.py: factor out an Entry._fixup_time method.
  Rely on options.parse() for more of the meta and xstat argument processing.
  Remove vestigal clean target comment regarding pybuptest.tmp permissions.
  Add initial timespec behavior tests.
  Return None from bup_set_linux_file_attr() and bup_utimensat().
  Replace os.*stat() with xstat.*stat(); use integer ns for all fs times.
  Drop xstat floating point timestamp support -- use integer ns.
  xstst-cmd.py: test for _have_utimensat rather than _have_ns_fs_timestamps.

18 files changed:
Makefile
cmd/index-cmd.py
cmd/meta-cmd.py
cmd/midx-cmd.py
cmd/xstat-cmd.py
config/configure
config/configure.inc
lib/bup/_helpers.c
lib/bup/csetup.py
lib/bup/git.py
lib/bup/index.py
lib/bup/metadata.py
lib/bup/t/tindex.py
lib/bup/t/tmetadata.py
lib/bup/t/txstat.py
lib/bup/xstat.py
t/mksock [new file with mode: 0755]
t/test-meta.sh

index b1154c17aabc6df12c75bfb29546809744523ef7..c71dac4cdb103186bdcb02327983485247436c18 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -146,8 +146,6 @@ import-docs: Documentation/clean
        git archive origin/html | (cd Documentation; tar -xvf -)
        git archive origin/man | (cd Documentation; tar -xvf -)
 
-# tgit.py plays with permissions on lib/bup/t/pybuptest.tmp, so we should
-# ensure that we can delete the directory before doing it.
 clean: Documentation/clean
        rm -f *.o lib/*/*.o *.so lib/*/*.so *.dll lib/*/*.dll *.exe \
                .*~ *~ */*~ lib/*/*~ lib/*/*/*~ \
index 89854113aa66ddbd62cd50e22fdfa9cb61a5202e..8195674390758c4fad0f7f655601c31b4cd85b88 100755 (executable)
@@ -49,8 +49,9 @@ def check_index(reader):
 
 
 def update_index(top, excluded_paths):
+    tmax = time.time() - 1
     ri = index.Reader(indexfile)
-    wi = index.Writer(indexfile)
+    wi = index.Writer(indexfile, tmax)
     rig = IterHelper(ri.iter(name=top))
     tstart = int(time.time())
 
@@ -101,7 +102,7 @@ def update_index(top, excluded_paths):
                 check_index(ri)
                 log('check: before merging: newfile\n')
                 check_index(wr)
-            mi = index.Writer(indexfile)
+            mi = index.Writer(indexfile, tmax)
 
             for e in index.merge(ri, wr):
                 # FIXME: shouldn't we remove deleted entries eventually?  When?
index 4f6e013810024b2c5e60e59246dd381084039b5f..4d4aac6073f296531f0dfd486286931a7710bc1d 100755 (executable)
@@ -13,6 +13,14 @@ from bup import metadata
 from bup import options
 from bup.helpers import handle_ctrl_c, log, saved_errors
 
+
+def open_input(name):
+    if name != '-':
+        return open(name, 'r')
+    else:
+        return sys.stdin
+
+
 optspec = """
 bup meta --create [OPTION ...] <PATH ...>
 bup meta --extract [OPTION ...]
@@ -34,112 +42,57 @@ v,verbose      increase log output (can be used more than once)
 q,quiet        don't show progress meter
 """
 
-action = None
-target_filename = ''
-should_recurse = False
-restore_numeric_ids = False
-include_paths = True
-handle_symlinks = True
-xdev = False
-
 handle_ctrl_c()
 
 o = options.Options(optspec)
-(opt, flags, remainder) = o.parse(sys.argv[1:])
+(opt, flags, remainder) = o.parse(['--paths', '--symlinks'] + sys.argv[1:])
 
-for flag, value in flags:
-    if flag == '--create' or flag == '-c':
-        action = 'create'
-    elif flag == '--list' or flag == '-t':
-        action = 'list'
-    elif flag == '--extract' or flag == '-x':
-        action = 'extract'
-    elif flag == '--start-extract':
-        action = 'start-extract'
-    elif flag == '--finish-extract':
-        action = 'finish-extract'
-    elif flag == '--file' or flag == '-f':
-        target_filename = value
-    elif flag == '--recurse' or flag == '-R':
-        should_recurse = True
-    elif flag == '--no-recurse':
-        should_recurse = False
-    elif flag in frozenset(['--xdev', '--one-file-system']):
-        xdev = True
-    elif flag in frozenset(['--no-xdev', '--no-one-file-system']):
-        xdev = False
-    elif flag == '--numeric-ids':
-        restore_numeric_ids = True
-    elif flag == '--no-numeric-ids':
-        restore_numeric_ids = False
-    elif flag == '--paths':
-        include_paths = True
-    elif flag == '--no-paths':
-        include_paths = False
-    elif flag == '--symlinks':
-        handle_symlinks = True
-    elif flag == '--no-symlinks':
-        handle_symlinks = False
-    elif flag == '--verbose' or flag == '-v':
-        metadata.verbose += 1
-    elif flag == '--quiet' or flag == '-q':
-        metadata.verbose = 0
+opt.verbose = opt.verbose or 0
+opt.quiet = opt.quiet or 0
+metadata.verbose = opt.verbose - opt.quiet
 
-if not action:
-    o.fatal("no action specified")
+action_count = sum([bool(x) for x in [opt.create, opt.list, opt.extract,
+                                      opt.start_extract, opt.finish_extract]])
+if action_count > 1:
+    o.fatal("bup: only one action permitted: --create --list --extract")
+if action_count == 0:
+    o.fatal("bup: no action specified")
 
-if action == 'create':
+if opt.create:
     if len(remainder) < 1:
         o.fatal("no paths specified for create")
-    if target_filename != '-':
-        output_file = open(target_filename, 'w')
+    if opt.file != '-':
+        output_file = open(opt.file, 'w')
     else:
         output_file = sys.stdout
     metadata.save_tree(output_file,
                        remainder,
-                       recurse=should_recurse,
-                       write_paths=include_paths,
-                       save_symlinks=handle_symlinks,
-                       xdev=xdev)
-
-elif action == 'list':
+                       recurse=opt.recurse,
+                       write_paths=opt.paths,
+                       save_symlinks=opt.symlinks,
+                       xdev=opt.xdev)
+elif opt.list:
     if len(remainder) > 0:
         o.fatal("cannot specify paths for --list")
-    if target_filename != '-':
-        src = open(target_filename, 'r')
-    else:
-        src = sys.stdin
+    src = open_input(opt.file)
     metadata.display_archive(src)
-
-elif action == 'start-extract':
+elif opt.start_extract:
     if len(remainder) > 0:
         o.fatal("cannot specify paths for --start-extract")
-    if target_filename != '-':
-        src = open(target_filename, 'r')
-    else:
-        src = sys.stdin
-    metadata.start_extract(src, create_symlinks=handle_symlinks)
-
-elif action == 'finish-extract':
+    src = open_input(opt.file)
+    metadata.start_extract(src, create_symlinks=opt.symlinks)
+elif opt.finish_extract:
     if len(remainder) > 0:
         o.fatal("cannot specify paths for --finish-extract")
-    if target_filename != '-':
-        src = open(target_filename, 'r')
-    else:
-        src = sys.stdin
-    num_ids = restore_numeric_ids
-    metadata.finish_extract(src, restore_numeric_ids=num_ids)
-
-elif action == 'extract':
+    src = open_input(opt.file)
+    metadata.finish_extract(src, restore_numeric_ids=opt.numeric_ids)
+elif opt.extract:
     if len(remainder) > 0:
         o.fatal("cannot specify paths for --extract")
-    if target_filename != '-':
-        src = open(target_filename, 'r')
-    else:
-        src = sys.stdin
+    src = open_input(opt.file)
     metadata.extract(src,
-                     restore_numeric_ids=restore_numeric_ids,
-                     create_symlinks=handle_symlinks)
+                     restore_numeric_ids=opt.numeric_ids,
+                     create_symlinks=opt.symlinks)
 
 if saved_errors:
     log('WARNING: %d errors encountered.\n' % len(saved_errors))
index 38a5fdfca500639894111181edeb3a1007aea38a..bbbee079f6097b6083f1ba60571aa8ee3499a04d 100755 (executable)
@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 import sys, math, struct, glob, resource
 import tempfile
-from bup import options, git, midx, _helpers
+from bup import options, git, midx, _helpers, xstat
 from bup.helpers import *
 
 PAGE_SIZE=4096
@@ -162,7 +162,7 @@ def do_midx_dir(path):
                     
         # sort the biggest+newest midxes first, so that we can eliminate
         # smaller (or older) redundant ones that come later in the list
-        midxs.sort(key=lambda ix: (-sizes[ix], -os.stat(ix).st_mtime))
+        midxs.sort(key=lambda ix: (-sizes[ix], -xstat.stat(ix).st_mtime))
         
         for mname in midxs:
             any = 0
index b8e4200204d5b797126216f45d5255286368ee84..99b9f192d583578f059c2e92d468ee2b53908852 100755 (executable)
@@ -6,11 +6,12 @@
 import sys, stat, errno
 from bup import metadata, options, xstat
 from bup.helpers import handle_ctrl_c, saved_errors, add_error, log
-from bup import _helpers
 
 
 def fstimestr(fstime):
-    (s, ns) = fstime.secs_nsecs()
+    (s, ns) = xstat.fstime_to_timespec(fstime)
+    if(s < 0):
+        s += 1
     if ns == 0:
         return '%d' % s
     else:
@@ -50,11 +51,7 @@ o = options.Options(optspec)
 
 treat_include_fields_as_definitive = True
 for flag, value in flags:
-    if flag == '--verbose' or flag == '-v':
-        metadata.verbose += 1
-    elif flag == '--quiet' or flag == '-q':
-        metadata.verbose = 0
-    elif flag == '--exclude-fields':
+    if flag == '--exclude-fields':
         exclude_fields = frozenset(value.split(','))
         for f in exclude_fields:
             if not f in all_fields:
@@ -72,6 +69,10 @@ for flag, value in flags:
         else:
             active_fields = active_fields | include_fields
 
+opt.verbose = opt.verbose or 0
+opt.quiet = opt.quiet or 0
+metadata.verbose = opt.verbose - opt.quiet
+
 for path in remainder:
     try:
         m = metadata.from_path(path, archive_path = path)
@@ -98,17 +99,17 @@ for path in remainder:
     if 'group' in active_fields:
         print 'group:', m.group
     if 'atime' in active_fields:
-        # if we don't have_ns_fs_timestamps, that means we have to use
+        # If we don't have utimensat, that means we have to use
         # utime(), and utime() has no way to set the mtime/atime of a
-        # symlink.  Thus, the mtime/atime of a symlink is meaningless, so
-        # let's not report it.  (That way scripts comparing before/after
-        # won't trigger.)
-        if _helpers._have_ns_fs_timestamps or not stat.S_ISLNK(m.mode):
+        # symlink.  Thus, the mtime/atime of a symlink is meaningless,
+        # so let's not report it.  (That way scripts comparing
+        # before/after won't trigger.)
+        if xstat.lutime or not stat.S_ISLNK(m.mode):
             print 'atime: ' + fstimestr(m.atime)
         else:
             print 'atime: 0'
     if 'mtime' in active_fields:
-        if _helpers._have_ns_fs_timestamps or not stat.S_ISLNK(m.mode):
+        if xstat.lutime or not stat.S_ISLNK(m.mode):
             print 'mtime: ' + fstimestr(m.mtime)
         else:
             print 'mtime: 0'
index 19cac075026fd544d72ed0202d2471feb6bb35cc..8841fff7c0de662caab6f4586d3d6246f663cc80 100755 (executable)
@@ -50,8 +50,25 @@ MF_PATH_INCLUDE GIT git
 MF_PATH_INCLUDE TAR tar gtar
 MF_PATH_INCLUDE PYTHON python
 
-AC_CHECK_HEADERS sys/stat.h 
+# For stat.
+AC_CHECK_HEADERS sys/stat.h
+AC_CHECK_HEADERS sys/types.h
+AC_CHECK_HEADERS unistd.h
+
+# For FS_IOC_GETFLAGS and FS_IOC_SETFLAGS.
+AC_CHECK_HEADERS linux/fs.h
+AC_CHECK_HEADERS sys/ioctl.h
+
 AC_CHECK_FUNCS utimensat 
-AC_CHECK_FUNCS utime
+AC_CHECK_FUNCS utimes
+AC_CHECK_FUNCS lutimes
+
+AC_CHECK_FIELD stat st_atim sys/types.h sys/stat.h unistd.h
+AC_CHECK_FIELD stat st_mtim sys/types.h sys/stat.h unistd.h
+AC_CHECK_FIELD stat st_ctim sys/types.h sys/stat.h unistd.h
+
+AC_CHECK_FIELD stat st_atimensec sys/types.h sys/stat.h unistd.h
+AC_CHECK_FIELD stat st_mtimensec sys/types.h sys/stat.h unistd.h
+AC_CHECK_FIELD stat st_ctimensec sys/types.h sys/stat.h unistd.h
 
 AC_OUTPUT config.vars
index 0ecdc674903d37aa717041a09d63cca50aafcf13..2bafea00e8e3d3bf2b85a70d4657154adb2fb0e3 100644 (file)
@@ -1,3 +1,5 @@
+# -*-shell-script-*-
+
 #   @(#) configure.inc 1.40@(#)
 #   Copyright (c) 1999-2007 David Parsons. All rights reserved.
 #   
index a0656cc66cdca1ebae5939adc331956fbb67b34d..f58243017f029f9ba821a8e0205c4f7fca9cbbc1 100644 (file)
@@ -8,15 +8,24 @@
 #include <fcntl.h>
 #include <arpa/inet.h>
 #include <stdint.h>
-#include <unistd.h>
 #include <stdlib.h>
 #include <stdio.h>
 
-#ifdef linux
+#ifdef HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#endif
+#ifdef HAVE_SYS_STAT_H
+#include <sys/stat.h>
+#endif
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#ifdef HAVE_LINUX_FS_H
 #include <linux/fs.h>
+#endif
+#ifdef HAVE_SYS_IOCTL_H
 #include <sys/ioctl.h>
-#include <sys/stat.h>
-#include <sys/time.h>
 #endif
 
 static int istty2 = 0;
@@ -630,7 +639,7 @@ static PyObject *fadvise_done(PyObject *self, PyObject *args)
 }
 
 
-#if defined(linux) && defined(FS_IOC_GETFLAGS)
+#ifdef FS_IOC_GETFLAGS
 static PyObject *bup_get_linux_file_attr(PyObject *self, PyObject *args)
 {
     int rc;
@@ -656,8 +665,10 @@ static PyObject *bup_get_linux_file_attr(PyObject *self, PyObject *args)
     close(fd);
     return Py_BuildValue("k", attr);
 }
+#endif /* def FS_IOC_GETFLAGS */
 
 
+#ifdef FS_IOC_SETFLAGS
 static PyObject *bup_set_linux_file_attr(PyObject *self, PyObject *args)
 {
     int rc;
@@ -680,91 +691,233 @@ static PyObject *bup_set_linux_file_attr(PyObject *self, PyObject *args)
     }
 
     close(fd);
-    return Py_BuildValue("i", 1);
+    return Py_BuildValue("O", Py_None);
 }
-#endif /* def linux */
+#endif /* def FS_IOC_SETFLAGS */
 
-#ifdef HAVE_UTIMENSAT
-#if defined(_ATFILE_SOURCE) \
-  || _XOPEN_SOURCE >= 700 || _POSIX_C_SOURCE >= 200809L
-#define HAVE_BUP_UTIMENSAT 1
 
-static PyObject *bup_utimensat(PyObject *self, PyObject *args)
-{
-    int rc, dirfd, flags;
-    char *path;
-    long access, access_ns, modification, modification_ns;
-    struct timespec ts[2];
+#if defined(HAVE_UTIMENSAT) || defined(HAVE_FUTIMES) || defined(HAVE_LUTIMES)
 
-    if (!PyArg_ParseTuple(args, "is((ll)(ll))i",
-                          &dirfd,
-                          &path,
-                          &access, &access_ns,
-                          &modification, &modification_ns,
-                          &flags))
-        return NULL;
+static int bup_parse_xutime_args(char **path,
+                                 long *access,
+                                 long *access_ns,
+                                 long *modification,
+                                 long *modification_ns,
+                                 PyObject *self, PyObject *args)
+{
+    if (!PyArg_ParseTuple(args, "s((ll)(ll))",
+                          path,
+                          access, access_ns,
+                          modification, modification_ns))
+        return 0;
 
-    if (isnan(access))
+    if (isnan(*access))
     {
         PyErr_SetString(PyExc_ValueError, "access time is NaN");
-        return NULL;
+        return 0;
     }
-    else if (isinf(access))
+    else if (isinf(*access))
     {
         PyErr_SetString(PyExc_ValueError, "access time is infinite");
-        return NULL;
+        return 0;
     }
-    else if (isnan(modification))
+    else if (isnan(*modification))
     {
         PyErr_SetString(PyExc_ValueError, "modification time is NaN");
-        return NULL;
+        return 0;
     }
-    else if (isinf(modification))
+    else if (isinf(*modification))
     {
         PyErr_SetString(PyExc_ValueError, "modification time is infinite");
-        return NULL;
+        return 0;
     }
 
-    if (isnan(access_ns))
+    if (isnan(*access_ns))
     {
         PyErr_SetString(PyExc_ValueError, "access time ns is NaN");
-        return NULL;
+        return 0;
     }
-    else if (isinf(access_ns))
+    else if (isinf(*access_ns))
     {
         PyErr_SetString(PyExc_ValueError, "access time ns is infinite");
-        return NULL;
+        return 0;
     }
-    else if (isnan(modification_ns))
+    else if (isnan(*modification_ns))
     {
         PyErr_SetString(PyExc_ValueError, "modification time ns is NaN");
-        return NULL;
+        return 0;
     }
-    else if (isinf(modification_ns))
+    else if (isinf(*modification_ns))
     {
         PyErr_SetString(PyExc_ValueError, "modification time ns is infinite");
-        return NULL;
+        return 0;
     }
 
+    return 1;
+}
+
+#endif /* defined(HAVE_UTIMENSAT) || defined(HAVE_FUTIMES)
+          || defined(HAVE_LUTIMES) */
+
+
+#ifdef HAVE_UTIMENSAT
+
+static PyObject *bup_xutime_ns(PyObject *self, PyObject *args,
+                               int follow_symlinks)
+{
+    int rc;
+    char *path;
+    long access, access_ns, modification, modification_ns;
+    struct timespec ts[2];
+
+    if (!bup_parse_xutime_args(&path, &access, &access_ns,
+                               &modification, &modification_ns,
+                               self, args))
+       return NULL;
+
     ts[0].tv_sec = access;
     ts[0].tv_nsec = access_ns;
     ts[1].tv_sec = modification;
     ts[1].tv_nsec = modification_ns;
+    rc = utimensat(AT_FDCWD, path, ts,
+                   follow_symlinks ? 0 : AT_SYMLINK_NOFOLLOW);
+    if (rc != 0)
+        return PyErr_SetFromErrnoWithFilename(PyExc_OSError, path);
+
+    return Py_BuildValue("O", Py_None);
+}
+
+
+#define BUP_HAVE_BUP_UTIME_NS 1
+static PyObject *bup_utime_ns(PyObject *self, PyObject *args)
+{
+    return bup_xutime_ns(self, args, 1);
+}
+
+
+#define BUP_HAVE_BUP_LUTIME_NS 1
+static PyObject *bup_lutime_ns(PyObject *self, PyObject *args)
+{
+    return bup_xutime_ns(self, args, 0);
+}
+
 
-    rc = utimensat(dirfd, path, ts, flags);
+#else /* not defined(HAVE_UTIMENSAT) */
+
+
+#ifdef HAVE_UTIMES
+#define BUP_HAVE_BUP_UTIME_NS 1
+static PyObject *bup_utime_ns(PyObject *self, PyObject *args)
+{
+    int rc;
+    char *path;
+    long access, access_ns, modification, modification_ns;
+    struct timeval tv[2];
+
+    if (!bup_parse_xutime_args(&path, &access, &access_ns,
+                               &modification, &modification_ns,
+                               self, args))
+       return NULL;
+
+    tv[0].tv_sec = access;
+    tv[0].tv_usec = access_ns / 1000;
+    tv[1].tv_sec = modification;
+    tv[1].tv_usec = modification_ns / 1000;
+    rc = utimes(path, tv);
     if (rc != 0)
         return PyErr_SetFromErrnoWithFilename(PyExc_OSError, path);
 
-    return Py_BuildValue("i", 1);
+    return Py_BuildValue("O", Py_None);
 }
+#endif /* def HAVE_UTIMES */
+
+
+#ifdef HAVE_LUTIMES
+#define BUP_HAVE_BUP_LUTIME_NS 1
+static PyObject *bup_lutime_ns(PyObject *self, PyObject *args)
+{
+    int rc;
+    char *path;
+    long access, access_ns, modification, modification_ns;
+    struct timeval tv[2];
+
+    if (!bup_parse_xutime_args(&path, &access, &access_ns,
+                               &modification, &modification_ns,
+                               self, args))
+       return NULL;
+
+    tv[0].tv_sec = access;
+    tv[0].tv_usec = access_ns / 1000;
+    tv[1].tv_sec = modification;
+    tv[1].tv_usec = modification_ns / 1000;
+    rc = lutimes(path, tv);
+    if (rc != 0)
+        return PyErr_SetFromErrnoWithFilename(PyExc_OSError, path);
+
+    return Py_BuildValue("O", Py_None);
+}
+#endif /* def HAVE_LUTIMES */
+
+
+#endif /* not defined(HAVE_UTIMENSAT) */
+
+
+#ifdef HAVE_STAT_ST_ATIM
+# define BUP_STAT_ATIME_NS(st) (st)->st_atim.tv_nsec
+# define BUP_STAT_MTIME_NS(st) (st)->st_mtim.tv_nsec
+# define BUP_STAT_CTIME_NS(st) (st)->st_ctim.tv_nsec
+#elif defined HAVE_STAT_ST_ATIMENSEC
+# define BUP_STAT_ATIME_NS(st) (st)->st_atimespec.tv_nsec
+# define BUP_STAT_MTIME_NS(st) (st)->st_mtimespec.tv_nsec
+# define BUP_STAT_CTIME_NS(st) (st)->st_ctimespec.tv_nsec
+#else
+# define BUP_STAT_ATIME_NS(st) 0
+# define BUP_STAT_MTIME_NS(st) 0
+# define BUP_STAT_CTIME_NS(st) 0
+#endif
+
+
+static PyObject *stat_struct_to_py(const struct stat *st)
+{
+    long atime_ns = BUP_STAT_ATIME_NS(st);
+    long mtime_ns = BUP_STAT_MTIME_NS(st);
+    long ctime_ns = BUP_STAT_CTIME_NS(st);
 
-#endif /* defined(_ATFILE_SOURCE)
-          || _XOPEN_SOURCE >= 700 || _POSIX_C_SOURCE >= 200809L */
-#endif /* HAVE_UTIMENSAT */
+    /* Enforce the current timespec nanosecond range expectations. */
+    if (atime_ns < 0 || atime_ns > 999999999)
+    {
+        PyErr_SetString(PyExc_ValueError, "invalid atime timespec nanoseconds");
+        return NULL;
+    }
+    if (mtime_ns < 0 || mtime_ns > 999999999)
+    {
+        PyErr_SetString(PyExc_ValueError, "invalid mtime timespec nanoseconds");
+        return NULL;
+    }
+    if (ctime_ns < 0 || ctime_ns > 999999999)
+    {
+        PyErr_SetString(PyExc_ValueError, "invalid ctime timespec nanoseconds");
+        return NULL;
+    }
+
+    return Py_BuildValue("kkkkkkkk(Ll)(Ll)(Ll)",
+                         (unsigned long) st->st_mode,
+                         (unsigned long) st->st_ino,
+                         (unsigned long) st->st_dev,
+                         (unsigned long) st->st_nlink,
+                         (unsigned long) st->st_uid,
+                         (unsigned long) st->st_gid,
+                         (unsigned long) st->st_rdev,
+                         (unsigned long) st->st_size,
+                         (long long) st->st_atime,
+                         (long) atime_ns,
+                         (long long) st->st_mtime,
+                         (long) mtime_ns,
+                         (long long) st->st_ctime,
+                         (long) ctime_ns);
+}
 
-#ifdef linux /* and likely others */
 
-#define HAVE_BUP_STAT 1
 static PyObject *bup_stat(PyObject *self, PyObject *args)
 {
     int rc;
@@ -777,29 +930,10 @@ static PyObject *bup_stat(PyObject *self, PyObject *args)
     rc = stat(filename, &st);
     if (rc != 0)
         return PyErr_SetFromErrnoWithFilename(PyExc_OSError, filename);
+    return stat_struct_to_py(&st);
+}
+
 
-    return Py_BuildValue("kkkkkkkk"
-                         "(ll)"
-                         "(ll)"
-                         "(ll)",
-                         (unsigned long) st.st_mode,
-                         (unsigned long) st.st_ino,
-                         (unsigned long) st.st_dev,
-                         (unsigned long) st.st_nlink,
-                         (unsigned long) st.st_uid,
-                         (unsigned long) st.st_gid,
-                         (unsigned long) st.st_rdev,
-                         (unsigned long) st.st_size,
-                         (long) st.st_atime,
-                         (long) st.st_atim.tv_nsec,
-                         (long) st.st_mtime,
-                         (long) st.st_mtim.tv_nsec,
-                         (long) st.st_ctime,
-                         (long) st.st_ctim.tv_nsec);
-}
-
-
-#define HAVE_BUP_LSTAT 1
 static PyObject *bup_lstat(PyObject *self, PyObject *args)
 {
     int rc;
@@ -812,29 +946,10 @@ static PyObject *bup_lstat(PyObject *self, PyObject *args)
     rc = lstat(filename, &st);
     if (rc != 0)
         return PyErr_SetFromErrnoWithFilename(PyExc_OSError, filename);
+    return stat_struct_to_py(&st);
+}
+
 
-    return Py_BuildValue("kkkkkkkk"
-                         "(ll)"
-                         "(ll)"
-                         "(ll)",
-                         (unsigned long) st.st_mode,
-                         (unsigned long) st.st_ino,
-                         (unsigned long) st.st_dev,
-                         (unsigned long) st.st_nlink,
-                         (unsigned long) st.st_uid,
-                         (unsigned long) st.st_gid,
-                         (unsigned long) st.st_rdev,
-                         (unsigned long) st.st_size,
-                         (long) st.st_atime,
-                         (long) st.st_atim.tv_nsec,
-                         (long) st.st_mtime,
-                         (long) st.st_mtim.tv_nsec,
-                         (long) st.st_ctime,
-                         (long) st.st_ctim.tv_nsec);
-}
-
-
-#define HAVE_BUP_FSTAT 1
 static PyObject *bup_fstat(PyObject *self, PyObject *args)
 {
     int rc, fd;
@@ -846,28 +961,8 @@ static PyObject *bup_fstat(PyObject *self, PyObject *args)
     rc = fstat(fd, &st);
     if (rc != 0)
         return PyErr_SetFromErrno(PyExc_OSError);
-
-    return Py_BuildValue("kkkkkkkk"
-                         "(ll)"
-                         "(ll)"
-                         "(ll)",
-                         (unsigned long) st.st_mode,
-                         (unsigned long) st.st_ino,
-                         (unsigned long) st.st_dev,
-                         (unsigned long) st.st_nlink,
-                         (unsigned long) st.st_uid,
-                         (unsigned long) st.st_gid,
-                         (unsigned long) st.st_rdev,
-                         (unsigned long) st.st_size,
-                         (long) st.st_atime,
-                         (long) st.st_atim.tv_nsec,
-                         (long) st.st_mtime,
-                         (long) st.st_mtim.tv_nsec,
-                         (long) st.st_ctime,
-                         (long) st.st_ctim.tv_nsec);
-}
-
-#endif /* def linux */
+    return stat_struct_to_py(&st);
+}
 
 
 static PyMethodDef helper_methods[] = {
@@ -899,28 +994,29 @@ static PyMethodDef helper_methods[] = {
        "open() the given filename for read with O_NOATIME if possible" },
     { "fadvise_done", fadvise_done, METH_VARARGS,
        "Inform the kernel that we're finished with earlier parts of a file" },
-#if defined(linux) && defined(FS_IOC_GETFLAGS)
+#ifdef FS_IOC_GETFLAGS
     { "get_linux_file_attr", bup_get_linux_file_attr, METH_VARARGS,
       "Return the Linux attributes for the given file." },
+#endif
+#ifdef FS_IOC_SETFLAGS
     { "set_linux_file_attr", bup_set_linux_file_attr, METH_VARARGS,
       "Set the Linux attributes for the given file." },
 #endif
-#ifdef HAVE_BUP_UTIMENSAT
-    { "utimensat", bup_utimensat, METH_VARARGS,
-      "Change file timestamps with nanosecond precision." },
+#ifdef BUP_HAVE_BUP_UTIME_NS
+    { "bup_utime_ns", bup_utime_ns, METH_VARARGS,
+      "Change path timestamps with up to nanosecond precision." },
+#endif
+#ifdef BUP_HAVE_BUP_LUTIME_NS
+    { "bup_lutime_ns", bup_lutime_ns, METH_VARARGS,
+      "Change path timestamps with up to nanosecond precision;"
+      " don't follow symlinks." },
 #endif
-#ifdef HAVE_BUP_STAT
     { "stat", bup_stat, METH_VARARGS,
       "Extended version of stat." },
-#endif
-#ifdef HAVE_BUP_LSTAT
     { "lstat", bup_lstat, METH_VARARGS,
       "Extended version of lstat." },
-#endif
-#ifdef HAVE_BUP_FSTAT
     { "fstat", bup_fstat, METH_VARARGS,
       "Extended version of fstat." },
-#endif
     { NULL, NULL, 0, NULL },  // sentinel
 };
 
@@ -931,16 +1027,6 @@ PyMODINIT_FUNC init_helpers(void)
     PyObject *m = Py_InitModule("_helpers", helper_methods);
     if (m == NULL)
         return;
-#ifdef HAVE_BUP_UTIMENSAT
-    PyModule_AddObject(m, "AT_FDCWD", Py_BuildValue("i", AT_FDCWD));
-    PyModule_AddObject(m, "AT_SYMLINK_NOFOLLOW",
-                       Py_BuildValue("i", AT_SYMLINK_NOFOLLOW));
-#endif
-#ifdef HAVE_BUP_STAT
-    PyModule_AddIntConstant(m, "_have_ns_fs_timestamps", 1);
-#else
-    PyModule_AddIntConstant(m, "_have_ns_fs_timestamps", 0);
-#endif
     e = getenv("BUP_FORCE_TTY");
     istty2 = isatty(2) || (atoi(e ? e : "0") & 2);
     unpythonize_argv();
index 2a0eeaf30d2be63e08db49291e07da377e863057..2446fe6fd4968dd7ef965d254dd525734b9018af 100644 (file)
@@ -1,6 +1,8 @@
 from distutils.core import setup, Extension
 
-_helpers_mod = Extension('_helpers', sources=['_helpers.c', 'bupsplit.c'])
+_helpers_mod = Extension('_helpers',
+                         sources=['_helpers.c', 'bupsplit.c'],
+                         depends=['../../config/config.h'])
 
 setup(name='_helpers',
       version='0.1',
index a86f80e5b151571cb432126abaf8f23a47bef04d..5cb282922c392169dffc6bb9083ae441be606256 100644 (file)
@@ -4,7 +4,7 @@ interact with the Git data structures.
 """
 import os, sys, zlib, time, subprocess, struct, stat, re, tempfile, glob
 from bup.helpers import *
-from bup import _helpers, path, midx, bloom
+from bup import _helpers, path, midx, bloom, xstat
 
 max_pack_size = 1000*1000*1000  # larger packs will slow down pruning
 max_pack_objects = 200*1000  # cache memory usage is about 83 bytes per object
@@ -413,7 +413,7 @@ class PackIdxList:
                         else:
                             midxl.append(mx)
                 midxl.sort(key=lambda ix:
-                           (-len(ix), -os.stat(ix.name).st_mtime))
+                           (-len(ix), -xstat.stat(ix.name).st_mtime))
                 for ix in midxl:
                     any_needed = False
                     for sub in ix.idxnames:
index 8d2471e3eeac9138346cb2086ce4ef9d01be6a73..4efc03be1dea32bc29b4cd378ac0460d0ef6cae0 100644 (file)
@@ -1,14 +1,13 @@
 import os, stat, struct, tempfile
+from bup import xstat
 from bup.helpers import *
 
 EMPTY_SHA = '\0'*20
 FAKE_SHA = '\x01'*20
-INDEX_HDR = 'BUPI\0\0\0\2'
+INDEX_HDR = 'BUPI\0\0\0\3'
 
-# FIXME: guess I should have used 64-bit integers to store the mtime/ctime.
-# NTFS mtime=0 corresponds to the year 1600, which can't be stored in a 32-bit
-# time_t.  Next time we update the bupindex format, keep that in mind.
-INDEX_SIG = '!IiiIIQII20sHII'
+# Use 64-bit integers for mtime/ctime to handle NTFS zero (Y1600) and Y2038.
+INDEX_SIG = '!QQQqqIIQII20sHII'
 
 ENTLEN = struct.calcsize(INDEX_SIG)
 FOOTER_SIG = '!Q'
@@ -42,11 +41,11 @@ class Level:
         return (ofs,n)
 
 
-def _golevel(level, f, ename, newentry):
+def _golevel(level, f, ename, newentry, tmax):
     # close nodes back up the tree
     assert(level)
     while ename[:len(level.ename)] != level.ename:
-        n = BlankNewEntry(level.ename[-1])
+        n = BlankNewEntry(level.ename[-1], tmax)
         n.flags |= IX_EXISTS
         (n.children_ofs,n.children_n) = level.write(f)
         level.parent.list.append(n)
@@ -58,7 +57,7 @@ def _golevel(level, f, ename, newentry):
 
     # are we in precisely the right place?
     assert(ename == level.ename)
-    n = newentry or BlankNewEntry(ename and level.ename[-1] or None)
+    n = newentry or BlankNewEntry(ename and level.ename[-1] or None, tmax)
     (n.children_ofs,n.children_n) = level.write(f)
     if level.parent:
         level.parent.list.append(n)
@@ -68,15 +67,16 @@ def _golevel(level, f, ename, newentry):
 
 
 class Entry:
-    def __init__(self, basename, name):
+    def __init__(self, basename, name, tmax):
         self.basename = str(basename)
         self.name = str(name)
+        self.tmax = tmax
         self.children_ofs = 0
         self.children_n = 0
 
     def __repr__(self):
         return ("(%s,0x%04x,%d,%d,%d,%d,%d,%s/%s,0x%04x,0x%08x/%d)" 
-                % (self.name, self.dev,
+                % (self.name, self.dev, self.ino, self.nlink,
                    self.ctime, self.mtime, self.uid, self.gid,
                    self.size, oct(self.mode), oct(self.gitmode),
                    self.flags, self.children_ofs, self.children_n))
@@ -84,7 +84,8 @@ class Entry:
     def packed(self):
         try:
             return struct.pack(INDEX_SIG,
-                           self.dev, self.ctime, self.mtime, 
+                           self.dev, self.ino, self.nlink,
+                           self.ctime, self.mtime, 
                            self.uid, self.gid, self.size, self.mode,
                            self.gitmode, self.sha, self.flags,
                            self.children_ofs, self.children_n)
@@ -93,21 +94,23 @@ class Entry:
             raise
 
     def from_stat(self, st, tstart):
-        old = (self.dev, self.ctime, self.mtime,
+        old = (self.dev, self.ino, self.nlink, self.ctime, self.mtime,
                self.uid, self.gid, self.size, self.flags & IX_EXISTS)
-        new = (st.st_dev,
-               int(st.st_ctime.approx_secs()),
-               int(st.st_mtime.approx_secs()),
+        new = (st.st_dev, st.st_ino, st.st_nlink,
+               xstat.fstime_floor_secs(st.st_ctime),
+               xstat.fstime_floor_secs(st.st_mtime),
                st.st_uid, st.st_gid, st.st_size, IX_EXISTS)
         self.dev = st.st_dev
-        self.ctime = int(st.st_ctime.approx_secs())
-        self.mtime = int(st.st_mtime.approx_secs())
+        self.ino = st.st_ino
+        self.nlink = st.st_nlink
+        self.ctime = xstat.fstime_floor_secs(st.st_ctime)
+        self.mtime = xstat.fstime_floor_secs(st.st_mtime)
         self.uid = st.st_uid
         self.gid = st.st_gid
         self.size = st.st_size
         self.mode = st.st_mode
         self.flags |= IX_EXISTS
-        if int(st.st_ctime.approx_secs()) >= tstart or old != new \
+        if xstat.fstime_floor_secs(st.st_ctime) >= tstart or old != new \
               or self.sha == EMPTY_SHA or not self.gitmode:
             self.invalidate()
         self._fixup()
@@ -119,14 +122,14 @@ class Entry:
             self.gid += 0x100000000
         assert(self.uid >= 0)
         assert(self.gid >= 0)
-        if self.mtime < -0x80000000:  # can happen in NTFS on 64-bit linux
-            self.mtime = 0
-        if self.ctime < -0x80000000:
-            self.ctime = 0
-        if self.mtime > 0x7fffffff:
-            self.mtime = 0x7fffffff
-        if self.ctime > 0x7fffffff:
-            self.ctime = 0x7fffffff
+        self.mtime = self._fixup_time(self.mtime)
+        self.ctime = self._fixup_time(self.ctime)
+
+    def _fixup_time(self, t):
+        if self.tmax != None and t > self.tmax:
+            return self.tmax
+        else:
+            return t
 
     def is_valid(self):
         f = IX_HASHVALID|IX_EXISTS
@@ -172,32 +175,33 @@ class Entry:
 
 
 class NewEntry(Entry):
-    def __init__(self, basename, name, dev, ctime, mtime, uid, gid,
-                 size, mode, gitmode, sha, flags, children_ofs, children_n):
-        Entry.__init__(self, basename, name)
-        (self.dev, self.ctime, self.mtime, self.uid, self.gid,
-         self.size, self.mode, self.gitmode, self.sha,
+    def __init__(self, basename, name, tmax, dev, ino, nlink, ctime, mtime,
+                 uid, gid, size, mode, gitmode, sha, flags,
+                 children_ofs, children_n):
+        Entry.__init__(self, basename, name, tmax)
+        (self.dev, self.ino, self.nlink, self.ctime, self.mtime,
+         self.uid, self.gid, self.size, self.mode, self.gitmode, self.sha,
          self.flags, self.children_ofs, self.children_n
-         ) = (dev, int(ctime), int(mtime), uid, gid,
+         ) = (dev, ino, nlink, int(ctime), int(mtime), uid, gid,
               size, mode, gitmode, sha, flags, children_ofs, children_n)
         self._fixup()
 
 
 class BlankNewEntry(NewEntry):
-    def __init__(self, basename):
-        NewEntry.__init__(self, basename, basename,
-                          0, 0, 0, 0, 0, 0, 0,
+    def __init__(self, basename, tmax):
+        NewEntry.__init__(self, basename, basename, tmax,
+                          0, 0, 0, 0, 0, 0, 0, 0, 0,
                           0, EMPTY_SHA, 0, 0, 0)
 
 
 class ExistingEntry(Entry):
     def __init__(self, parent, basename, name, m, ofs):
-        Entry.__init__(self, basename, name)
+        Entry.__init__(self, basename, name, None)
         self.parent = parent
         self._m = m
         self._ofs = ofs
-        (self.dev, self.ctime, self.mtime, self.uid, self.gid,
-         self.size, self.mode, self.gitmode, self.sha,
+        (self.dev, self.ino, self.nlink, self.ctime, self.mtime,
+         self.uid, self.gid, self.size, self.mode, self.gitmode, self.sha,
          self.flags, self.children_ofs, self.children_n
          ) = struct.unpack(INDEX_SIG, str(buffer(m, ofs, ENTLEN)))
 
@@ -347,13 +351,14 @@ def pathsplit(p):
 
 
 class Writer:
-    def __init__(self, filename):
+    def __init__(self, filename, tmax):
         self.rootlevel = self.level = Level([], None)
         self.f = None
         self.count = 0
         self.lastfile = None
         self.filename = None
         self.filename = filename = realpath(filename)
+        self.tmax = tmax
         (dir,name) = os.path.split(filename)
         (ffd,self.tmpname) = tempfile.mkstemp('.tmp', filename, dir)
         self.f = os.fdopen(ffd, 'wb', 65536)
@@ -371,7 +376,7 @@ class Writer:
 
     def flush(self):
         if self.level:
-            self.level = _golevel(self.level, self.f, [], None)
+            self.level = _golevel(self.level, self.f, [], None, self.tmax)
             self.count = self.rootlevel.count
             if self.count:
                 self.count += 1
@@ -392,7 +397,7 @@ class Writer:
             raise Error('%r must come before %r' 
                              % (''.join(e.name), ''.join(self.lastfile)))
             self.lastfile = e.name
-        self.level = _golevel(self.level, self.f, ename, entry)
+        self.level = _golevel(self.level, self.f, ename, entry, self.tmax)
 
     def add(self, name, st, hashgen = None):
         endswith = name.endswith('/')
@@ -409,15 +414,16 @@ class Writer:
         if st:
             isdir = stat.S_ISDIR(st.st_mode)
             assert(isdir == endswith)
-            e = NewEntry(basename, name, st.st_dev,
-                         int(st.st_ctime.approx_secs()),
-                         int(st.st_mtime.approx_secs()),
+            e = NewEntry(basename, name, self.tmax,
+                         st.st_dev, st.st_ino, st.st_nlink,
+                         xstat.fstime_floor_secs(st.st_ctime),
+                         xstat.fstime_floor_secs(st.st_mtime),
                          st.st_uid, st.st_gid,
                          st.st_size, st.st_mode, gitmode, sha, flags,
                          0, 0)
         else:
             assert(endswith)
-            e = BlankNewEntry(basename)
+            e = BlankNewEntry(basename, tmax)
             e.gitmode = gitmode
             e.sha = sha
             e.flags = flags
index 981ad096a5857367af49fffc6710f853f19d795e..61df9fbd92e108a387ee52b1c4c6718ad03d674d 100644 (file)
@@ -6,10 +6,10 @@
 # Public License as described in the bup LICENSE file.
 import errno, os, sys, stat, pwd, grp, struct, re
 from cStringIO import StringIO
-from bup import vint
+from bup import vint, xstat
 from bup.drecurse import recursive_dirlist
 from bup.helpers import add_error, mkdirp, log, is_superuser
-from bup.xstat import utime, lutime, lstat, FSTime
+from bup.xstat import utime, lutime, lstat
 import bup._helpers as _helpers
 
 try:
@@ -201,9 +201,9 @@ class Metadata:
             add_error("no group name for id %s '%s'" % (st.st_gid, path))
 
     def _encode_common(self):
-        atime = self.atime.to_timespec()
-        mtime = self.mtime.to_timespec()
-        ctime = self.ctime.to_timespec()
+        atime = xstat.nsecs_to_timespec(self.atime)
+        mtime = xstat.nsecs_to_timespec(self.mtime)
+        ctime = xstat.nsecs_to_timespec(self.ctime)
         result = vint.pack('VVsVsVvVvVvV',
                            self.mode,
                            self.uid,
@@ -233,16 +233,25 @@ class Metadata:
          mtime_ns,
          self.ctime,
          ctime_ns) = vint.unpack('VVsVsVvVvVvV', data)
-        self.atime = FSTime.from_timespec((self.atime, atime_ns))
-        self.mtime = FSTime.from_timespec((self.mtime, mtime_ns))
-        self.ctime = FSTime.from_timespec((self.ctime, ctime_ns))
+        self.atime = xstat.timespec_to_nsecs((self.atime, atime_ns))
+        self.mtime = xstat.timespec_to_nsecs((self.mtime, mtime_ns))
+        self.ctime = xstat.timespec_to_nsecs((self.ctime, ctime_ns))
+
+    def _recognized_file_type(self):
+        return stat.S_ISREG(self.mode) \
+            or stat.S_ISDIR(self.mode) \
+            or stat.S_ISCHR(self.mode) \
+            or stat.S_ISBLK(self.mode) \
+            or stat.S_ISFIFO(self.mode) \
+            or stat.S_ISSOCK(self.mode) \
+            or stat.S_ISLNK(self.mode)
 
     def _create_via_common_rec(self, path, create_symlinks=True):
         # If the path already exists and is a dir, try rmdir.
         # If the path already exists and is anything else, try unlink.
         st = None
         try:
-            st = lstat(path)
+            st = xstat.lstat(path)
         except OSError, e:
             if e.errno != errno.ENOENT:
                 raise
@@ -259,20 +268,28 @@ class Metadata:
                 os.unlink(path)
 
         if stat.S_ISREG(self.mode):
+            assert(self._recognized_file_type())
             fd = os.open(path, os.O_CREAT|os.O_WRONLY|os.O_EXCL, 0600)
             os.close(fd)
         elif stat.S_ISDIR(self.mode):
+            assert(self._recognized_file_type())
             os.mkdir(path, 0700)
         elif stat.S_ISCHR(self.mode):
+            assert(self._recognized_file_type())
             os.mknod(path, 0600 | stat.S_IFCHR, self.rdev)
         elif stat.S_ISBLK(self.mode):
+            assert(self._recognized_file_type())
             os.mknod(path, 0600 | stat.S_IFBLK, self.rdev)
         elif stat.S_ISFIFO(self.mode):
+            assert(self._recognized_file_type())
             os.mknod(path, 0600 | stat.S_IFIFO)
+        elif stat.S_ISSOCK(self.mode):
+            os.mknod(path, 0600 | stat.S_IFSOCK)
         elif stat.S_ISLNK(self.mode):
+            assert(self._recognized_file_type())
             if self.symlink_target and create_symlinks:
-                # on MacOS, symlink() permissions depend on umask, and there's no
-                # way to chown a symlink after creating it, so we have to
+                # on MacOS, symlink() permissions depend on umask, and there's
+                # no way to chown a symlink after creating it, so we have to
                 # be careful here!
                 oldumask = os.umask((self.mode & 0777) ^ 0777)
                 try:
@@ -280,12 +297,15 @@ class Metadata:
                 finally:
                     os.umask(oldumask)
         # FIXME: S_ISDOOR, S_IFMPB, S_IFCMP, S_IFNWK, ... see stat(2).
-        # Otherwise, do nothing.
+        else:
+            assert(not self._recognized_file_type())
+            add_error('not creating "%s" with unrecognized mode "0x%x"\n'
+                      % (path, self.mode))
 
     def _apply_common_rec(self, path, restore_numeric_ids=False):
         # FIXME: S_ISDOOR, S_IFMPB, S_IFCMP, S_IFNWK, ... see stat(2).
         # EACCES errors at this stage are fatal for the current path.
-        if stat.S_ISLNK(self.mode):
+        if lutime and stat.S_ISLNK(self.mode):
             try:
                 lutime(path, (self.atime, self.mtime))
             except OSError, e:
@@ -598,6 +618,10 @@ class Metadata:
             path = self.path
         if not path:
             raise Exception('Metadata.apply_to_path() called with no path');
+        if not self._recognized_file_type():
+            add_error('not applying metadata to "%s"' % path
+                      + ' with unrecognized mode "0x%x"\n' % self.mode)
+            return
         num_ids = restore_numeric_ids
         try:
             self._apply_common_rec(path, restore_numeric_ids=num_ids)
@@ -611,7 +635,7 @@ class Metadata:
 def from_path(path, statinfo=None, archive_path=None, save_symlinks=True):
     result = Metadata()
     result.path = archive_path
-    st = statinfo or lstat(path)
+    st = statinfo or xstat.lstat(path)
     result._add_common(path, st)
     if save_symlinks:
         result._add_symlink_target(path, st)
index 48cc2f73d540d33910f50fbf9fd27c0a96c00f3a..4dacd0c60e3085485e37b1670b8c0f3294bf4f94 100644 (file)
@@ -21,7 +21,7 @@ def index_writer():
     unlink('index.tmp')
     ds = xstat.stat('.')
     fs = xstat.stat('tindex.py')
-    w = index.Writer('index.tmp')
+    w = index.Writer('index.tmp', time.time() - 1)
     w.add('/var/tmp/sporky', fs)
     w.add('/etc/passwd', fs)
     w.add('/etc/', ds)
@@ -54,15 +54,16 @@ def index_negative_timestamps():
 
     # Dec 31, 1969
     os.utime("foo", (-86400, -86400))
-    e = index.BlankNewEntry("foo")
-    e.from_stat(xstat.stat("foo"), time.time())
+    now = time.time()
+    e = index.BlankNewEntry("foo", now - 1)
+    e.from_stat(xstat.stat("foo"), now)
     assert len(e.packed())
     WVPASS()
 
     # Jun 10, 1893
     os.utime("foo", (-0x80000000, -0x80000000))
-    e = index.BlankNewEntry("foo")
-    e.from_stat(xstat.stat("foo"), time.time())
+    e = index.BlankNewEntry("foo", now - 1)
+    e.from_stat(xstat.stat("foo"), now)
     assert len(e.packed())
     WVPASS()
 
@@ -75,8 +76,9 @@ def index_dirty():
     unlink('index2.tmp')
     ds = xstat.stat('.')
     fs = xstat.stat('tindex.py')
+    tmax = time.time() - 1
     
-    w1 = index.Writer('index.tmp')
+    w1 = index.Writer('index.tmp', tmax)
     w1.add('/a/b/x', fs)
     w1.add('/a/b/c', fs)
     w1.add('/a/b/', ds)
@@ -84,12 +86,12 @@ def index_dirty():
     #w1.close()
     WVPASS()
 
-    w2 = index.Writer('index2.tmp')
+    w2 = index.Writer('index2.tmp', tmax)
     w2.add('/a/b/n/2', fs)
     #w2.close()
     WVPASS()
 
-    w3 = index.Writer('index3.tmp')
+    w3 = index.Writer('index3.tmp', tmax)
     w3.add('/a/c/n/3', fs)
     #w3.close()
     WVPASS()
index 2d227998533f2cfbed59ecb48587322b5158c9a9..fc5df0c1cd3c664003ce945e8e509a0192c3dbbf 100644 (file)
@@ -127,6 +127,7 @@ def test_from_path_error():
         os.chmod(path, 000)
         metadata.from_path(path, archive_path=path, save_symlinks=True)
         if metadata.get_linux_file_attr:
+            WVPASS(len(helpers.saved_errors) == 1)
             errmsg = _first_err()
             WVPASS(errmsg.startswith('read Linux attr'))
             clear_errors()
@@ -147,6 +148,7 @@ def test_apply_to_path_restricted_access():
         WVPASSEQ(m.path, path)
         os.chmod(tmpdir, 000)
         m.apply_to_path(path)
+        WVPASS(len(helpers.saved_errors) == 1)
         errmsg = _first_err()
         WVPASS(errmsg.startswith('utime: '))
         clear_errors()
@@ -168,12 +170,14 @@ def test_restore_restricted_user_group():
         orig_uid = m.uid
         m.uid = 0;
         m.apply_to_path(path, restore_numeric_ids=True)
+        WVPASS(len(helpers.saved_errors) == 1)
         errmsg = _first_err()
         WVPASS(errmsg.startswith('lchown: '))
         clear_errors()
         m.uid = orig_uid
         m.gid = 0;
         m.apply_to_path(path, restore_numeric_ids=True)
+        WVPASS(len(helpers.saved_errors) == 1)
         errmsg = _first_err()
         WVPASS(errmsg.startswith('lchown: ') or os.stat(path).st_gid == m.gid)
         clear_errors()
index a56ac05b07936e1d5f74699dca1c0cd72ec5c92c..e378c75b6180986bc251a33d5ff3ffefe07c276f 100644 (file)
@@ -1,56 +1,74 @@
-import math
+import math, tempfile, subprocess
 from wvtest import *
 import bup._helpers as _helpers
-from bup.xstat import FSTime
+from bup import xstat
 
-def _test_fstime():
-    def approx_eq(x, y):
-        return math.fabs(x - y) < 1 / 10e8
-    def ts_eq_ish(x, y):
-        return approx_eq(x[0], y[0]) and approx_eq(x[1], y[1])
-    def fst_eq_ish(x, y):
-        return approx_eq(x.approx_secs(), y.approx_secs())
-    def s_ns_eq_ish(fst, s, ns):
-        (fst_s, fst_ns) = fst.secs_nsecs()
-        return approx_eq(fst_s, s) and approx_eq(fst_ns, ns)
-    from_secs = FSTime.from_secs
-    from_ts = FSTime.from_timespec
-    WVPASS(from_secs(0) == from_secs(0))
-    WVPASS(from_secs(0) < from_secs(1))
-    WVPASS(from_secs(-1) < from_secs(1))
-    WVPASS(from_secs(1) > from_secs(0))
-    WVPASS(from_secs(1) > from_secs(-1))
+@wvtest
+def test_fstime():
+    WVPASSEQ(xstat.timespec_to_nsecs((0, 0)), 0)
+    WVPASSEQ(xstat.timespec_to_nsecs((1, 0)), 10**9)
+    WVPASSEQ(xstat.timespec_to_nsecs((0, 10**9 / 2)), 500000000)
+    WVPASSEQ(xstat.timespec_to_nsecs((1, 10**9 / 2)), 1500000000)
+    WVPASSEQ(xstat.timespec_to_nsecs((-1, 0)), -10**9)
+    WVPASSEQ(xstat.timespec_to_nsecs((-1, 10**9 / 2)), -500000000)
+    WVPASSEQ(xstat.timespec_to_nsecs((-2, 10**9 / 2)), -1500000000)
+    WVEXCEPT(Exception, xstat.timespec_to_nsecs, (0, -1))
+    WVPASSEQ(type(xstat.timespec_to_nsecs((2, 22222222))), type(0))
+    WVPASSEQ(type(xstat.timespec_to_nsecs((-2, 22222222))), type(0))
 
-    WVPASS(fst_eq_ish(from_secs(0), from_ts((0, 0))))
-    WVPASS(fst_eq_ish(from_secs(1), from_ts((1, 0))))
-    WVPASS(fst_eq_ish(from_secs(-1), from_ts((-1, 0))))
-    WVPASS(fst_eq_ish(from_secs(-0.5), from_ts((-1, 10**9 / 2))))
-    WVPASS(fst_eq_ish(from_secs(-1.5), from_ts((-2, 10**9 / 2))))
-    WVPASS(fst_eq_ish(from_secs(1 / 10e8), from_ts((0, 1))))
-    WVPASS(fst_eq_ish(from_secs(-1 / 10e8), from_ts((-1, 10**9 - 1))))
+    WVPASSEQ(xstat.nsecs_to_timespec(0), (0, 0))
+    WVPASSEQ(xstat.nsecs_to_timespec(10**9), (1, 0))
+    WVPASSEQ(xstat.nsecs_to_timespec(500000000), (0, 10**9 / 2))
+    WVPASSEQ(xstat.nsecs_to_timespec(1500000000), (1, 10**9 / 2))
+    WVPASSEQ(xstat.nsecs_to_timespec(-10**9), (-1, 0))
+    WVPASSEQ(xstat.nsecs_to_timespec(-500000000), (-1, 10**9 / 2))
+    WVPASSEQ(xstat.nsecs_to_timespec(-1500000000), (-2, 10**9 / 2))
+    x = xstat.nsecs_to_timespec(1977777778)
+    WVPASSEQ(type(x[0]), type(0))
+    WVPASSEQ(type(x[1]), type(0))
+    x = xstat.nsecs_to_timespec(-1977777778)
+    WVPASSEQ(type(x[0]), type(0))
+    WVPASSEQ(type(x[1]), type(0))
 
-    WVPASS(ts_eq_ish(from_secs(0).to_timespec(), (0, 0)))
-    WVPASS(ts_eq_ish(from_secs(1).to_timespec(), (1, 0)))
-    WVPASS(ts_eq_ish(from_secs(-1).to_timespec(), (-1, 0)))
-    WVPASS(ts_eq_ish(from_secs(-0.5).to_timespec(), (-1, 10**9 / 2)))
-    WVPASS(ts_eq_ish(from_secs(-1.5).to_timespec(), (-2, 10**9 / 2)))
-    WVPASS(ts_eq_ish(from_secs(1 / 10e8).to_timespec(), (0, 1)))
-    WVPASS(ts_eq_ish(from_secs(-1 / 10e8).to_timespec(), (-1, 10**9 - 1)))
+    WVPASSEQ(xstat.fstime_floor_secs(0), 0)
+    WVPASSEQ(xstat.fstime_floor_secs(10**9 / 2), 0)
+    WVPASSEQ(xstat.fstime_floor_secs(10**9), 1)
+    WVPASSEQ(xstat.fstime_floor_secs(-10**9 / 2), -1)
+    WVPASSEQ(xstat.fstime_floor_secs(-10**9), -1)
+    WVPASSEQ(type(xstat.fstime_floor_secs(10**9 / 2)), type(0))
+    WVPASSEQ(type(xstat.fstime_floor_secs(-10**9 / 2)), type(0))
 
-    WVPASS(s_ns_eq_ish(from_secs(0), 0, 0))
-    WVPASS(s_ns_eq_ish(from_secs(1), 1, 0))
-    WVPASS(s_ns_eq_ish(from_secs(-1), -1, 0))
-    WVPASS(s_ns_eq_ish(from_secs(-0.5), 0, - 10**9 / 2))
-    WVPASS(s_ns_eq_ish(from_secs(-1.5), -1, - 10**9 / 2))
-    WVPASS(s_ns_eq_ish(from_secs(-1 / 10e8), 0, -1))
+
+try:
+    _have_bup_utime_ns = _helpers.bup_utime_ns
+except AttributeError, e:
+    _have_bup_utime_ns = False
 
 @wvtest
-def test_fstime():
-    _test_fstime();
-    if _helpers._have_ns_fs_timestamps: # Test native python timestamp rep too.
-        orig = _helpers._have_ns_fs_timestamps
-        try:
-            _helpers._have_ns_fs_timestamps = None
-            _test_fstime();
-        finally:
-            _helpers._have_ns_fs_timestamps = orig
+def test_timespec_behavior():
+    if not _have_bup_utime_ns:
+        return
+    tmpdir = tempfile.mkdtemp(prefix='bup-tmetadata-')
+    try:
+        path = tmpdir + '/foo'
+        open(path, 'w').close()
+        frac_ts = (0, 10**9 / 2)
+        _helpers.bup_utime_ns(path, (frac_ts, frac_ts))
+        st = _helpers.stat(path)
+        atime_ts = st[8]
+        mtime_ts = st[9]
+        WVPASSEQ(atime_ts[0], 0)
+        WVPASS(atime_ts[1] == 0 or atime_ts[1] == frac_ts[1])
+        WVPASSEQ(mtime_ts[0], 0)
+        WVPASS(mtime_ts[1] == 0 or mtime_ts[1] == frac_ts[1])
+        if(mtime_ts[1] == frac_ts[1]):
+            # Sub-second resolution -- check behavior of negative timespecs.
+            neg_ts = (-43, 10**9 / 2)
+            _helpers.bup_utime_ns(path, (neg_ts, neg_ts))
+            st = _helpers.stat(path)
+            atime_ts = st[8]
+            mtime_ts = st[9]
+            WVPASSEQ(atime_ts, neg_ts)
+            WVPASSEQ(mtime_ts, neg_ts)
+    finally:
+        subprocess.call(['rm', '-rf', tmpdir])
index abc93fe3756cc7959692e4cbe0b78a69da0a9783..f3e11de341fcb4f874bc3d77ede25c73261edcbd 100644 (file)
@@ -4,139 +4,91 @@ from bup import _helpers
 
 
 try:
-    _have_utimensat = _helpers.utimensat
+    _have_bup_utime_ns = _helpers.bup_utime_ns
 except AttributeError, e:
-    _have_utimensat = False
-
-
-class FSTime:
-    # Class to represent filesystem timestamps.  Use integer
-    # nanoseconds on platforms where we have the higher resolution
-    # lstat.  Use the native python stat representation (floating
-    # point seconds) otherwise.
-
-    def __cmp__(self, x):
-        return self._value.__cmp__(x._value)
-        
-    def __repr__(self):
-        return 'FSTime(%d)' % self._value
-        
-    def to_timespec(self):
-        """Return (s, ns) where ns is always non-negative
-        and t = s + ns / 10e8""" # metadata record rep (and libc rep)
-        s_ns = self.secs_nsecs()
-        if s_ns[0] > 0 or s_ns[1] >= 0:
-            return s_ns
-        return (s_ns[0] - 1, 10**9 + s_ns[1]) # ns is negative
+    _have_bup_utime_ns = False
 
-    @staticmethod
-    def from_secs(secs):
-        ts = FSTime()
-        ts._value = int(round(secs * 10**9))
-        return ts
+try:
+    _have_bup_lutime_ns = _helpers.bup_lutime_ns
+except AttributeError, e:
+    _have_bup_lutime_ns = False
 
-    @staticmethod
-    def from_timespec(timespec):
-        ts = FSTime()
-        ts._value = timespec[0] * 10**9 + timespec[1]
-        return ts
-
-    def approx_secs(self):
-        return self._value / 10e8;
-
-    def secs_nsecs(self):
-        "Return a (s, ns) pair: -1.5s -> (-1, -10**9 / 2)."
-        if self._value >= 0:
-            return (self._value / 10**9, self._value % 10**9)
-        abs_val = -self._value
-        return (- (abs_val / 10**9), - (abs_val % 10**9))
-
-    if _helpers._have_ns_fs_timestamps: # Use integer nanoseconds.
-        @staticmethod
-        def from_stat_time(stat_time):
-            return FSTime.from_timespec(stat_time)
-    else: # Use python default floating-point seconds.
-        @staticmethod
-        def from_stat_time(stat_time):
-            return FSTime.from_secs(stat_time)
-
-
-if _have_utimensat:
-    def lutime(path, times):
-        atime = times[0].to_timespec()
-        mtime = times[1].to_timespec()
-        return _helpers.utimensat(_helpers.AT_FDCWD, path, (atime, mtime),
-                                  _helpers.AT_SYMLINK_NOFOLLOW)
+
+def timespec_to_nsecs((ts_s, ts_ns)):
+    # c.f. _helpers.c: timespec_vals_to_py_ns()
+    if ts_ns < 0 or ts_ns > 999999999:
+        raise Exception('invalid timespec nsec value')
+    return ts_s * 10**9 + ts_ns
+
+
+def nsecs_to_timespec(ns):
+    """Return (s, ns) where ns is always non-negative
+    and t = s + ns / 10e8""" # metadata record rep (and libc rep)
+    ns = int(ns)
+    return (ns / 10**9, ns % 10**9)
+
+
+def fstime_floor_secs(ns):
+    """Return largest integer not greater than ns / 10e8."""
+    return int(ns) / 10**9;
+
+
+def fstime_to_timespec(ns):
+    return nsecs_to_timespec(ns)
+
+
+if _have_bup_utime_ns:
     def utime(path, times):
-        atime = times[0].to_timespec()
-        mtime = times[1].to_timespec()
-        return _helpers.utimensat(_helpers.AT_FDCWD, path, (atime, mtime), 0)
+        """Times must be provided as (atime_ns, mtime_ns)."""
+        atime = nsecs_to_timespec(times[0])
+        mtime = nsecs_to_timespec(times[1])
+        _helpers.bup_utime_ns(path, (atime, mtime))
 else:
-    def lutime(path, times):
-        return None
-
     def utime(path, times):
-        atime = times[0].approx_secs()
-        mtime = times[1].approx_secs()
+        """Times must be provided as (atime_ns, mtime_ns)."""
+        atime = fstime_floor_secs(times[0])
+        mtime = fstime_floor_secs(times[1])
         os.utime(path, (atime, mtime))
 
 
+if _have_bup_lutime_ns:
+    def lutime(path, times):
+        """Times must be provided as (atime_ns, mtime_ns)."""
+        atime = nsecs_to_timespec(times[0])
+        mtime = nsecs_to_timespec(times[1])
+        _helpers.bup_lutime_ns(path, (atime, mtime))
+else:
+    lutime = False
+
+
 class stat_result:
     @staticmethod
-    def from_stat_rep(st):
+    def from_xstat_rep(st):
         result = stat_result()
-        if _helpers._have_ns_fs_timestamps:
-            (result.st_mode,
-             result.st_ino,
-             result.st_dev,
-             result.st_nlink,
-             result.st_uid,
-             result.st_gid,
-             result.st_rdev,
-             result.st_size,
-             atime,
-             mtime,
-             ctime) = st
-        else:
-            result.st_mode = st.st_mode
-            result.st_ino = st.st_ino
-            result.st_dev = st.st_dev
-            result.st_nlink = st.st_nlink
-            result.st_uid = st.st_uid
-            result.st_gid = st.st_gid
-            result.st_rdev = st.st_rdev
-            result.st_size = st.st_size
-            atime = st.st_atime
-            mtime = st.st_mtime
-            ctime = st.st_ctime
-        result.st_atime = FSTime.from_stat_time(atime)
-        result.st_mtime = FSTime.from_stat_time(mtime)
-        result.st_ctime = FSTime.from_stat_time(ctime)
+        (result.st_mode,
+         result.st_ino,
+         result.st_dev,
+         result.st_nlink,
+         result.st_uid,
+         result.st_gid,
+         result.st_rdev,
+         result.st_size,
+         result.st_atime,
+         result.st_mtime,
+         result.st_ctime) = st
+        result.st_atime = timespec_to_nsecs(result.st_atime)
+        result.st_mtime = timespec_to_nsecs(result.st_mtime)
+        result.st_ctime = timespec_to_nsecs(result.st_ctime)
         return result
 
 
-try:
-    _stat = _helpers.stat
-except AttributeError, e:
-    _stat = os.stat
-
 def stat(path):
-    return stat_result.from_stat_rep(_stat(path))
-
+    return stat_result.from_xstat_rep(_helpers.stat(path))
 
-try:
-    _fstat = _helpers.fstat
-except AttributeError, e:
-    _fstat = os.fstat
 
 def fstat(path):
-    return stat_result.from_stat_rep(_fstat(path))
+    return stat_result.from_xstat_rep(_helpers.fstat(path))
 
 
-try:
-    _lstat = _helpers.lstat
-except AttributeError, e:
-    _lstat = os.lstat
-
 def lstat(path):
-    return stat_result.from_stat_rep(_lstat(path))
+    return stat_result.from_xstat_rep(_helpers.lstat(path))
diff --git a/t/mksock b/t/mksock
new file mode 100755 (executable)
index 0000000..ee2dbfe
--- /dev/null
+++ b/t/mksock
@@ -0,0 +1,6 @@
+#!/usr/bin/env python
+
+import socket, sys
+
+s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM, 0)
+s.bind(sys.argv[1])
index 8d565b63511b841bd034fda042ffb9b8ec4e4c52..5ce48d55d0e80ec8cf49800938691d8c59a84d57 100755 (executable)
@@ -78,10 +78,12 @@ force-delete "$TOP/bupmeta.tmp"
 
 # Create a test tree.
 (
+    set -e
     rm -rf "$TOP/bupmeta.tmp/src"
     mkdir -p "$TOP/bupmeta.tmp/src"
     #cp -a Documentation cmd lib t "$TOP/bupmeta.tmp"/src
     cp -pPR Documentation cmd lib t "$TOP/bupmeta.tmp"/src
+    t/mksock "$TOP/bupmeta.tmp/src/test-socket" || true
 ) || WVFAIL
 
 # Use the test tree to check bup meta.
@@ -124,10 +126,10 @@ if actually-root; then
             mkdir testfs/src/foo
             touch testfs/src/bar
             PYTHONPATH="$TOP/lib" \
-                python -c "from bup.xstat import lutime, FSTime; \
-                x = FSTime.from_secs(42);\
-                   lutime('testfs/src/foo', (x, x));\
-                   lutime('testfs/src/bar', (x, x));"
+                python -c "from bup import xstat; \
+                x = xstat.timespec_to_nsecs((42, 0));\
+                   xstat.utime('testfs/src/foo', (x, x));\
+                   xstat.utime('testfs/src/bar', (x, x));"
             cd testfs
             WVPASS bup meta -v --create --recurse --file src.meta src
             bup meta -tvf src.meta