[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Freeze exception for rdiff-backup 1.1.17-1



-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

[ In case a reply from me is necessary, note that I'll be away again
from today evening until next wednesday evening. ]

Hi,

rdiff-backup 1.1.17-1 fixes #410586 and #486653, was uploaded on
2008-07-17 already, but unfortunately didn't make it to testing before
the freeze.

The upstream change between 1.1.16 and 1.1.17 can be seen here (there
were no other changes done than that in 1.1.17-1, except
debian/changelog):
http://git.debian.net/?p=rdiff-backup.git;a=commitdiff;h=1a6f13e0c9ed36d17877ee1e5b3cbed30eeea08b

However, for everyone's convenince, I also attached the same information
again as debdiff between 1.1.16-1 and 1.1.17-1.

Regards,
Daniel

- --
Address:        Daniel Baumann, Burgunderstrasse 3, CH-4562 Biberist
Email:          daniel.baumann@panthera-systems.net
Internet:       http://people.panthera-systems.net/~daniel-baumann/
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (GNU/Linux)

iEYEARECAAYFAkiMp/sACgkQ+C5cwEsrK5631ACbB5ndvukEXUxbO8W+lgO8yDhT
QFIAmgJnGOkdmHmGn/NODJ1930aikRnl
=5gnS
-----END PGP SIGNATURE-----
diff --git a/CHANGELOG b/CHANGELOG
index 70aa77f..6b6dcae 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,45 @@
+New in v1.1.17 (2008/07/17)
+---------------------------
+
+Move make_file_dict_python so that it is run on the remote end instead of
+the local end. This improves performance for Windows hosts since it eliminates
+the lag due to checking os.name. It also makes the Windows design parallel
+to the Posix design, since the Windows method now returns a dictionary across
+the wire. (Andrew Ferguson)
+
+Catch EPERM error when trying to write extended attributes. (Andrew Ferguson)
+
+Allow rdiff-backup to be built into a single executable on Windows using
+py2exe ("setup.py py2exe --single-file"). (Patch from Josh Nisly)
+
+Properly handle uid/gid comparison when the metadata about a destination
+file has become corrupt. Closes Debian bug #410586. (Andrew Ferguson)
+
+Properly handle hardlink comparison when the metadata about a destination
+hardlink has become corrupt. Closes Debian bug #486653. (Andrew Ferguson)
+
+Fix typo in fs_abilities noticed by Martin Krafft. Add EILSEQ ("Invalid or
+incomplete multibyte or wide character") to the list of recoverable errors.
+Thanks to Hanno Stock for catching that. (Andrew Ferguson)
+
+Catch another reasonable error when reading EAs. (Andrew Ferguson)
+
+Use the Python os.lstat() on Windows. (Patch from Josh Nisly)
+
+Support for Windows ACLs. (Patch from Josh Nisly and Fred Gansevles)
+
+Fix user_group.py to run on native Windows, which lacks grp and pwd Python
+modules. (Patch from Fred Gansevles)
+
+Optimize --check-destination and other functions by determining the increment
+files server-side instead of client-side. (Patch from Josh Nisly)
+
+Actually make rdiff-backup robust to failure to read an ACL because the file
+cannot be found. (Andrew Ferguson)
+
+Get makedist working on Windows. (Patch from Josh Nisly)
+
+
 New in v1.1.16 (2008/06/17)
 ---------------------------
 
@@ -70,6 +112,7 @@ fixes an rdiff-backup error on AIX and IRIX. Closes Savannah bug #15839.
 Properly initialize new QuotedRPaths. Fixes --list-at-time, etc. when
 the target is remote. (Andrew Ferguson)
 
+
 New in v1.1.15 (2008/01/03)
 ---------------------------
 
diff --git a/cmodule.c b/cmodule.c
index 36915a6..ed82ed4 100644
--- a/cmodule.c
+++ b/cmodule.c
@@ -48,10 +48,6 @@
 /* This code taken from Python's posixmodule.c */
 #undef STAT
 #if defined(MS_WIN64) || defined(MS_WIN32)
-#	define LSTAT _stati64
-#	define STAT _stati64
-#	define FSTAT _fstati64
-#	define STRUCT_STAT struct _stati64
 #	define SYNC _flushall
 #else
 #	define LSTAT lstat
@@ -77,15 +73,6 @@
 #define S_ISFIFO(mode)        (((mode) & S_IFMT) == S_IFIFO)
 #endif
 
-#if defined(MS_WIN64) || defined(MS_WIN32)
-#define S_ISSOCK(mode) (0)
-#define S_ISFIFO(mode) (0)
-#define S_ISLNK(mode) (0)
-#define S_ISLNK(mode) (0)
-#define S_ISCHR(mode) (0)
-#define S_ISBLK(mode) (0)
-#endif
-
 static PyObject *UnknownFileTypeError;
 static PyObject *c_make_file_dict(PyObject *self, PyObject *args);
 static PyObject *long2str(PyObject *self, PyObject *args);
@@ -98,6 +85,10 @@ static PyObject *c_make_file_dict(self, args)
 	 PyObject *self;
 	 PyObject *args;
 {
+#if defined(MS_WINDOWS)
+	PyErr_SetString(PyExc_AttributeError, "This function is not implemented on Windows.");
+	return NULL;
+#else
   PyObject *size, *inode, *mtime, *atime, *ctime, *devloc, *return_val;
   char *filename, filetype[5];
   STRUCT_STAT sbuf;
@@ -118,10 +109,7 @@ static PyObject *c_make_file_dict(self, args)
 	  return NULL;
 	}
   }
-#if defined(MS_WINDOWS)
-  size = PyLong_FromLongLong((PY_LONG_LONG)sbuf.st_size);
-  inode = PyLong_FromLongLong((PY_LONG_LONG)-1);
-#else
+
 #ifdef HAVE_LARGEFILE_SUPPORT
   size = PyLong_FromLongLong((PY_LONG_LONG)sbuf.st_size);
   inode = PyLong_FromLongLong((PY_LONG_LONG)sbuf.st_ino);
@@ -129,10 +117,9 @@ static PyObject *c_make_file_dict(self, args)
   size = PyInt_FromLong(sbuf.st_size);
   inode = PyInt_FromLong((long)sbuf.st_ino);
 #endif /* HAVE_LARGEFILE_SUPPORT */
-#endif /* defined(MS_WINDOWS) */
   mode = (long)sbuf.st_mode;
   perms = mode & 07777;
-#if defined(HAVE_LONG_LONG) && !defined(MS_WINDOWS)
+#if defined(HAVE_LONG_LONG)
   devloc = PyLong_FromLongLong((PY_LONG_LONG)sbuf.st_dev);
 #else
   devloc = PyInt_FromLong((long)sbuf.st_dev);
@@ -189,7 +176,7 @@ static PyObject *c_make_file_dict(self, args)
   } else if (S_ISCHR(mode) || S_ISBLK(mode)) {
 	/* Device files */
 	char devtype[2];
-#if defined(HAVE_LONG_LONG) && !defined(MS_WINDOWS)
+#if defined(HAVE_LONG_LONG)
 	PY_LONG_LONG devnums = (PY_LONG_LONG)sbuf.st_rdev;
 	PyObject *major_num = PyLong_FromLongLong(major(devnums));
 #else
@@ -223,6 +210,7 @@ static PyObject *c_make_file_dict(self, args)
   Py_DECREF(atime);
   Py_DECREF(ctime);
   return return_val;
+#endif /* defined(MS_WINDOWS) */
 }
 
 /* Convert python long into 7 byte string */
diff --git a/debian/changelog b/debian/changelog
index 851c585..8d8bee0 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,17 @@
+rdiff-backup (1.1.17-1) unstable; urgency=low
+
+  * Merging upstream version 1.1.17 (Closes: #491185):
+    - fixes failure with 'exceptions.KeyError' (Closes: #410586).
+    - fixes failure with 'KeyError inode' exception (Closes: #486653).
+
+ -- Daniel Baumann <daniel@debian.org>  Thu, 17 Jul 2008 16:30:00 +0200
+
+rdiff-backup (1.1.16-2) unstable; urgency=medium
+
+  * Making build-depends against librsync version to ensure 4gb bugfix.
+
+ -- Daniel Baumann <daniel@debian.org>  Wed, 25 Jun 2008 11:42:00 +0200
+
 rdiff-backup (1.1.16-1) unstable; urgency=medium
 
   * Removing 01-manpage.dpatch, went upstream.
diff --git a/debian/control b/debian/control
index fef2206..7b1c31d 100644
--- a/debian/control
+++ b/debian/control
@@ -2,7 +2,7 @@ Source: rdiff-backup
 Section: utils
 Priority: optional
 Maintainer: Daniel Baumann <daniel@debian.org>
-Build-Depends: debhelper (>= 7), python-support, python-dev, python-pylibacl, python-pyxattr, librsync-dev
+Build-Depends: debhelper (>= 7), python-support, python-dev, python-pylibacl, python-pyxattr, librsync-dev (>= 0.9.7-4)
 Standards-Version: 3.8.0
 Homepage: http://rdiff-backup.nongnu.org/
 Vcs-Browser: http://git.debian.net/?p=rdiff-backup.git
diff --git a/rdiff-backup b/rdiff-backup
index 5dbe832..e210450 100644
--- a/rdiff-backup
+++ b/rdiff-backup
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
 # rdiff-backup -- Mirror files while keeping incremental changes
-# Version 1.1.16 released June 17, 2008
+# Version 1.1.17 released July 17, 2008
 # Copyright (C) 2001-2005  Ben Escoto <rdiff-backup@emerose.org>
 #
 # This program is licensed under the GNU General Public License (GPL).
diff --git a/rdiff-backup-statistics b/rdiff-backup-statistics
index ead56a9..c476049 100644
--- a/rdiff-backup-statistics
+++ b/rdiff-backup-statistics
@@ -1,6 +1,6 @@
 #!/usr/bin/python
 # rdiff-backup-statistics -- Summarize rdiff-backup statistics files
-# Version 1.1.16 released June 17, 2008
+# Version 1.1.17 released July 17, 2008
 # Copyright 2005 Dean Gaudet, Ben Escoto
 #
 # This file is part of rdiff-backup.
diff --git a/rdiff-backup-statistics.1 b/rdiff-backup-statistics.1
index aff561b..a3108e7 100644
--- a/rdiff-backup-statistics.1
+++ b/rdiff-backup-statistics.1
@@ -1,4 +1,4 @@
-.TH RDIFF-BACKUP 1 "June 2008" "Version 1.1.16" "User Manuals"
+.TH RDIFF-BACKUP 1 "July 2008" "Version 1.1.17" "User Manuals"
 .SH NAME
 rdiff-backup-statistics \- summarize rdiff-backup statistics files
 .SH SYNOPSIS
diff --git a/rdiff-backup.1 b/rdiff-backup.1
index a69e852..9c059aa 100644
--- a/rdiff-backup.1
+++ b/rdiff-backup.1
@@ -1,4 +1,4 @@
-.TH RDIFF-BACKUP 1 "June 2008" "Version 1.1.16" "User Manuals"
+.TH RDIFF-BACKUP 1 "July 2008" "Version 1.1.17" "User Manuals"
 .SH NAME
 rdiff-backup \- local/remote mirror and incremental backup
 .SH SYNOPSIS
diff --git a/rdiff-backup.spec b/rdiff-backup.spec
index 054c6ae..76a76cc 100644
--- a/rdiff-backup.spec
+++ b/rdiff-backup.spec
@@ -2,7 +2,7 @@
 %define PYTHON_VERSION %(%{PYTHON_NAME} -c 'import sys; print sys.version[:3],')
 %define NEXT_PYTHON_VERSION %(%{PYTHON_NAME} -c 'import sys; print "%d.%d" % (sys.version_info[0], sys.version_info[1]+1),')
 
-Version: 1.1.16
+Version: 1.1.17
 Summary: Convenient and transparent local/remote incremental mirror/backup
 Name: rdiff-backup
 Release: 1
diff --git a/rdiff-backup.spec-fedora b/rdiff-backup.spec-fedora
index 82dc1be..a1546ef 100644
--- a/rdiff-backup.spec-fedora
+++ b/rdiff-backup.spec-fedora
@@ -1,7 +1,7 @@
 %define PYTHON_VERSION %(python -c 'import sys; print sys.version[:3],')
 %define NEXT_PYTHON_VERSION %(python -c 'import sys; print "%d.%d" % (sys.version_info[0], sys.version_info[1]+1),')
 
-Version: 1.1.16
+Version: 1.1.17
 Summary: Convenient and transparent local/remote incremental mirror/backup
 Name: rdiff-backup
 Release: 0.fdr.1
diff --git a/rdiff_backup/Globals.py b/rdiff_backup/Globals.py
index e685ef2..b6445f0 100644
--- a/rdiff_backup/Globals.py
+++ b/rdiff_backup/Globals.py
@@ -23,7 +23,7 @@ import re, os
 
 
 # The current version of rdiff-backup
-version = "1.1.16"
+version = "1.1.17"
 
 # If this is set, use this value in seconds as the current time
 # instead of reading it from the clock.
@@ -85,6 +85,12 @@ acls_active = None
 acls_write = None
 acls_conn = None
 
+# Like the above, but applies to support of Windows
+# access control lists.
+win_acls_active = None
+win_acls_write = None
+win_acls_conn = None
+
 # Like above two setting groups, but applies to support of Mac OS X
 # style resource forks.
 resource_forks_active = None
diff --git a/rdiff_backup/Hardlink.py b/rdiff_backup/Hardlink.py
index 1dddbbb..d0cb19f 100644
--- a/rdiff_backup/Hardlink.py
+++ b/rdiff_backup/Hardlink.py
@@ -103,7 +103,10 @@ def rorp_eq(src_rorp, dest_rorp):
 		# subsequent ones
 		_inode_index[src_key] = (index, remaining, None, None)
 		return 1
-	return dest_key == get_inode_key(dest_rorp)
+	try:
+		return dest_key == get_inode_key(dest_rorp)
+	except KeyError:
+		return 0 # Inode key might be missing if the metadata file is corrupt
 
 def islinked(rorp):
 	"""True if rorp's index is already linked to something on src side"""
diff --git a/rdiff_backup/Security.py b/rdiff_backup/Security.py
index 2e9cd9d..8dc26bc 100644
--- a/rdiff_backup/Security.py
+++ b/rdiff_backup/Security.py
@@ -41,7 +41,7 @@ disallowed_server_globals = ["server", "security_level", "restrict_path"]
 #
 # The keys are files request, the value is the index of the argument
 # taking a file.
-file_requests = {'os.listdir':0, 'C.make_file_dict':0, 'os.chmod':0,
+file_requests = {'os.listdir':0, 'rpath.make_file_dict':0, 'os.chmod':0,
 				 'os.chown':0, 'os.remove':0, 'os.removedirs':0,
 				 'os.rename':0, 'os.renames':0, 'os.rmdir':0, 'os.unlink':0,
 				 'os.utime':0, 'os.lchown':0, 'os.link':1, 'os.symlink':1,
@@ -136,7 +136,7 @@ def set_allowed_requests(sec_level):
 		 "sys.stdout.write", "robust.install_signal_handlers"]
 	if (sec_level == "read-only" or sec_level == "update-only" or
 		sec_level == "all"):
-		l.extend(["C.make_file_dict", "os.listdir", "rpath.ea_get",
+		l.extend(["rpath.make_file_dict", "os.listdir", "rpath.ea_get",
 				  "rpath.acl_get", "rpath.setdata_local",
 				  "log.Log.log_to_file", "os.getuid", "Time.setcurtime_local",
 				  "rpath.gzip_open_local_read", "rpath.open_local_read",
diff --git a/rdiff_backup/connection.py b/rdiff_backup/connection.py
index 0ba1204..34aebae 100644
--- a/rdiff_backup/connection.py
+++ b/rdiff_backup/connection.py
@@ -27,7 +27,8 @@ try: import xattr
 except ImportError: pass
 try: import posix1e
 except ImportError: pass
-
+try: import win32security
+except ImportError: pass
 
 class ConnectionError(Exception): pass
 class ConnectionReadError(ConnectionError): pass
@@ -539,6 +540,9 @@ import Globals, Time, Rdiff, Hardlink, FilenameMapping, C, Security, \
 	   TempFile, SetConnections, librsync, log, regress, fs_abilities, \
 	   eas_acls, user_group, compare
 
+try: import win_acls
+except ImportError: pass
+
 Globals.local_connection = LocalConnection()
 Globals.connections.append(Globals.local_connection)
 # Following changed by server in SetConnections
diff --git a/rdiff_backup/eas_acls.py b/rdiff_backup/eas_acls.py
index 513af14..6b1e128 100644
--- a/rdiff_backup/eas_acls.py
+++ b/rdiff_backup/eas_acls.py
@@ -58,7 +58,7 @@ class ExtendedAttributes:
 		"""Set the extended attributes from an rpath"""
 		try: attr_list = rp.conn.xattr.listxattr(rp.path)
 		except IOError, exc:
-			if exc[0] == errno.EOPNOTSUPP or exc[0] == errno.EPERM:
+			if exc[0] in (errno.EOPNOTSUPP, errno.EPERM, errno.ETXTBSY):
 				return # if not supported, consider empty
 			if exc[0] == errno.EACCES or exc[0] == errno.ENOENT:
 				log.Log("Warning: listattr(%s): %s" % (repr(rp.path), exc), 3)
@@ -96,7 +96,7 @@ class ExtendedAttributes:
 		except IOError, exc:
 			if exc[0] == errno.EOPNOTSUPP or exc[0] == errno.EPERM:
 				return # if not supported, consider empty
-			if exc[0] == errno.ENOENT: # path is bad
+			elif exc[0] == errno.ENOENT: # path is bad
 				log.Log("Warning: unable to clear xattrs on %s: %s" %
 						(repr(rp.path), exc), 3)
 				return
@@ -111,8 +111,8 @@ class ExtendedAttributes:
 			except IOError, exc:
 				# Mac and Linux attributes have different namespaces, so
 				# fail gracefully if can't call setxattr
-				if exc[0] == errno.EOPNOTSUPP or exc[0] == errno.EACCES \
-						 or exc[0] == errno.ENOENT:
+				if exc[0] in (errno.EOPNOTSUPP, errno.EPERM, errno.EACCES,
+						errno.ENOENT):
 					log.Log("Warning: unable to write xattr %s to %s"
 							% (name, repr(rp.path)), 6)
 					continue
@@ -382,8 +382,9 @@ def get_acl_lists_from_rp(rp):
 	assert rp.conn is Globals.local_connection
 	try: acl = posix1e.ACL(file=rp.path)
 	except IOError, exc:
-		if exc[0] == errno.EOPNOTSUPP: acl = None
-		if exc[0] == errno.ENOENT:
+		if exc[0] == errno.EOPNOTSUPP:
+			acl = None
+		elif exc[0] == errno.ENOENT:
 			log.Log("Warning: unable to read ACL from %s: %s"
 					% (repr(rp.path), exc), 3)
 			acl = None
@@ -391,8 +392,9 @@ def get_acl_lists_from_rp(rp):
 	if rp.isdir():
 		try: def_acl = posix1e.ACL(filedef=rp.path)
 		except IOError, exc:
-			if exc[0] == errno.EOPNOTSUPP: def_acl = None
-			if exc[0] == errno.ENOENT:
+			if exc[0] == errno.EOPNOTSUPP:
+				def_acl = None
+			elif exc[0] == errno.ENOENT:
 				log.Log("Warning: unable to read default ACL from %s: %s"
 					% (repr(rp.path), exc), 3)
 				def_acl = None
diff --git a/rdiff_backup/fs_abilities.py b/rdiff_backup/fs_abilities.py
index 9d125b1..2be4740 100644
--- a/rdiff_backup/fs_abilities.py
+++ b/rdiff_backup/fs_abilities.py
@@ -29,7 +29,7 @@ FSAbilities object describing it.
 
 import errno, os
 import Globals, log, TempFile, selection, robust, SetConnections, \
-	   static, FilenameMapping
+	   static, FilenameMapping, win_acls
 
 class FSAbilities:
 	"""Store capabilities of given file system"""
@@ -39,6 +39,7 @@ class FSAbilities:
 	ownership = None # True if chown works on this filesystem
 	acls = None # True if access control lists supported
 	eas = None # True if extended attributes supported
+	win_acls = None # True if windows access control lists supported
 	hardlinks = None # True if hard linking supported
 	fsync_dirs = None # True if directories can be fsync'd
 	dir_inc_perms = None # True if regular files can have full permissions
@@ -97,6 +98,7 @@ class FSAbilities:
 							   self.win_reserved_filenames)])
 		add_boolean_list([('Access control lists', self.acls),
 						  ('Extended attributes', self.eas),
+						  ('Windows access control lists', self.win_acls),
 						  ('Case sensitivity', self.case_sensitive),
 						  ('Escape DOS devices', self.escape_dos_devices),
 						  ('Mac OS X style resource forks',
@@ -120,6 +122,7 @@ class FSAbilities:
 		self.read_only = 1
 		self.set_eas(rp, 0)
 		self.set_acls(rp)
+		self.set_win_acls(rp)
 		self.set_resource_fork_readonly(rp)
 		self.set_carbonfile()
 		self.set_case_sensitive_readonly(rp)
@@ -151,6 +154,7 @@ class FSAbilities:
 		self.set_fsync_dirs(subdir)
 		self.set_eas(subdir, 1)
 		self.set_acls(subdir)
+		self.set_win_acls(subdir)
 		self.set_dir_inc_perms(subdir)
 		self.set_resource_fork_readwrite(subdir)
 		self.set_carbonfile()
@@ -364,6 +368,24 @@ class FSAbilities:
 			self.eas = 0
 		else: self.eas = 1
 
+	def set_win_acls(self, dir_rp):
+		"""Test if windows access control lists are supported"""
+		try:
+			import win32security
+		except ImportError:
+			log.Log("Unable to import win32security module. Windows ACLs\n"
+					"not supported by filesystem at %s" % dir_rp.path, 4)
+			self.win_acls = 0
+			return
+		try:
+			win_acls.init_acls()
+		except OSError:
+			log.Log("Windows ACLs not supported by filesystem\n"
+					"at %s" % dir_rp.path, 4)
+			self.win_acls = 0
+			return
+		self.win_acls = 1
+
 	def set_dir_inc_perms(self, rp):
 		"""See if increments can have full permissions like a directory"""
 		test_rp = rp.append('dir_inc_check')
@@ -519,7 +541,11 @@ class SetGlobals:
 						   ('acls_active', 'acls_write', 'acls_conn'))
 		if Globals.never_drop_acls and not Globals.acls_active:
 			log.Log.FatalError("--never-drop-acls specified, but ACL support\n"
-							   "missing from destination filesystem")
+							   "missing from source filesystem")
+
+	def set_win_acls(self):
+		self.update_triple(self.src_fsa.win_acls, self.dest_fsa.win_acls,
+			  ('win_acls_active', 'win_acls_write', 'win_acls_conn'))
 
 	def set_resource_forks(self):
 		self.update_triple(self.src_fsa.resource_forks,
@@ -729,6 +755,10 @@ class SingleSetGlobals(RestoreSetGlobals):
 	def set_acls(self):
 		self.update_triple(self.dest_fsa.acls,
 						  ('acls_active', 'acls_write', 'acls_conn'))
+	def set_win_acls(self):
+		self.update_triple(self.src_fsa.win_acls, self.dest_fsa.win_acls,
+			  ('win_acls_active', 'win_acls_write', 'win_acls_conn'))
+
 	def set_resource_forks(self):
 		self.update_triple(self.dest_fsa.resource_forks,
 						   ('resource_forks_active',
@@ -754,6 +784,7 @@ def backup_set_globals(rpin, force):
 	bsg = BackupSetGlobals(rpin.conn, Globals.rbdir.conn, src_fsa, dest_fsa)
 	bsg.set_eas()
 	bsg.set_acls()
+	bsg.set_win_acls()
 	bsg.set_resource_forks()
 	bsg.set_carbonfile()
 	bsg.set_hardlinks()
@@ -781,6 +812,7 @@ def restore_set_globals(rpout):
 	rsg = RestoreSetGlobals(Globals.rbdir.conn, rpout.conn, src_fsa, dest_fsa)
 	rsg.set_eas()
 	rsg.set_acls()
+	rsg.set_win_acls()
 	rsg.set_resource_forks()
 	rsg.set_carbonfile()
 	rsg.set_hardlinks()
diff --git a/rdiff_backup/metadata.py b/rdiff_backup/metadata.py
index c20092b..1bbc5c6 100644
--- a/rdiff_backup/metadata.py
+++ b/rdiff_backup/metadata.py
@@ -433,9 +433,10 @@ class MetadataFile(FlatFile):
 
 class CombinedWriter:
 	"""Used for simultaneously writting metadata, eas, and acls"""
-	def __init__(self, metawriter, eawriter, aclwriter):
+	def __init__(self, metawriter, eawriter, aclwriter, winaclwriter):
 		self.metawriter = metawriter
-		self.eawriter, self.aclwriter = eawriter, aclwriter # these can be None
+		self.eawriter, self.aclwriter, self.winaclwriter = \
+				eawriter, aclwriter, winaclwriter # these can be None
 
 	def write_object(self, rorp):
 		"""Write information in rorp to all the writers"""
@@ -444,11 +445,14 @@ class CombinedWriter:
 			self.eawriter.write_object(rorp.get_ea())
 		if self.aclwriter and not rorp.get_acl().is_basic():
 			self.aclwriter.write_object(rorp.get_acl())
+		if self.winaclwriter:
+			self.winaclwriter.write_object(rorp.get_win_acl())
 
 	def close(self):
 		self.metawriter.close()
 		if self.eawriter: self.eawriter.close()
 		if self.aclwriter: self.aclwriter.close()
+		if self.winaclwriter: self.winaclwriter.close()
 
 
 class Manager:
@@ -456,6 +460,7 @@ class Manager:
 	meta_prefix = 'mirror_metadata'
 	acl_prefix = 'access_control_lists'
 	ea_prefix = 'extended_attributes'
+	wacl_prefix = 'win_access_control_lists'
 
 	def __init__(self):
 		"""Set listing of rdiff-backup-data dir"""
@@ -501,6 +506,11 @@ class Manager:
 		return self._iter_helper(self.acl_prefix,
 					  eas_acls.AccessControlListFile, time, restrict_index)
 
+	def get_win_acls_at_time(self, time, restrict_index):
+		"""Return WACLs iter at given time from recordfile (or None)"""
+		return self._iter_helper(self.wacl_prefix,
+					  win_acls.WinAccessControlListFile, time, restrict_index)
+
 	def GetAtTime(self, time, restrict_index = None):
 		"""Return combined metadata iter with ea/acl info if necessary"""
 		cur_iter = self.get_meta_at_time(time, restrict_index)
@@ -521,6 +531,14 @@ class Manager:
 				log.Log("Warning: Extended Attributes file not found", 2)
 				ea_iter = iter([])
 			cur_iter = eas_acls.join_ea_iter(cur_iter, ea_iter)
+		if Globals.win_acls_active:
+			wacl_iter = self.get_win_acls_at_time(time, restrict_index)
+			if not wacl_iter:
+				log.Log("Warning: Windows Access Control List file not"
+						" found.", 2)
+				wacl_iter = iter([])
+			cur_iter = win_acls.join_wacl_iter(cur_iter, wacl_iter)
+
 		return cur_iter
 
 	def _writer_helper(self, prefix, flatfileclass, typestr, time):
@@ -548,17 +566,26 @@ class Manager:
 		return self._writer_helper(self.acl_prefix,
 						 eas_acls.AccessControlListFile, typestr, time)
 
+	def get_win_acl_writer(self, typestr, time):
+		"""Return WinAccessControlListFile opened for writing"""
+		return self._writer_helper(self.wacl_prefix,
+						 win_acls.WinAccessControlListFile, typestr, time)
+
 	def GetWriter(self, typestr = 'snapshot', time = None):
 		"""Get a writer object that can write meta and possibly acls/eas"""
 		metawriter = self.get_meta_writer(typestr, time)
-		if not Globals.eas_active and not Globals.acls_active:
+		if not Globals.eas_active and not Globals.acls_active and \
+				not Globals.win_acls_active:
 			return metawriter # no need for a CombinedWriter
 
 		if Globals.eas_active: ea_writer = self.get_ea_writer(typestr, time)
 		else: ea_writer = None
 		if Globals.acls_active: acl_writer = self.get_acl_writer(typestr, time)
 		else: acl_writer = None
-		return CombinedWriter(metawriter, ea_writer, acl_writer)
+		if Globals.win_acls_active: win_acl_writer = \
+				self.get_win_acl_writer(typestr, time)
+		else: win_acl_writer = None
+		return CombinedWriter(metawriter, ea_writer, acl_writer, win_acl_writer)
 
 class PatchDiffMan(Manager):
 	"""Contains functions for patching and diffing metadata
@@ -663,4 +690,4 @@ def SetManager():
 	return ManagerObj
 
 
-import eas_acls # put at bottom to avoid python circularity bug
+import eas_acls, win_acls # put at bottom to avoid python circularity bug
diff --git a/rdiff_backup/restore.py b/rdiff_backup/restore.py
index 414f04d..85734ef 100644
--- a/rdiff_backup/restore.py
+++ b/rdiff_backup/restore.py
@@ -47,8 +47,10 @@ def get_inclist(inc_rpath):
 
 	inc_list = []
 	for filename in parent_dir.listdir():
-		inc = parent_dir.append(filename)
-		if inc.isincfile() and inc.getincbase_str() == basename:
+		inc_info = rpath.get_incfile_info(filename)
+		if inc_info and inc_info[3] == basename:
+			inc = parent_dir.append(filename)
+			assert inc.isincfile()
 			inc_list.append(inc)
 	return inc_list
 
diff --git a/rdiff_backup/robust.py b/rdiff_backup/robust.py
index df190d4..d339c24 100644
--- a/rdiff_backup/robust.py
+++ b/rdiff_backup/robust.py
@@ -52,7 +52,7 @@ def catch_error(exc):
 		(exc[0] in ('invalid mode: rb', 'Not a gzipped file') or
 		 errno.errorcode.has_key(exc[0]) and
 		 errno.errorcode[exc[0]] in ('EPERM', 'ENOENT', 'EACCES', 'EBUSY',
-									 'EEXIST', 'ENOTDIR',
+									 'EEXIST', 'ENOTDIR', 'EILSEQ',
 									 'ENAMETOOLONG', 'EINTR',
 									 'ENOTEMPTY', 'EIO', 'ETXTBSY',
 									 'ESRCH', 'EINVAL', 'EDEADLOCK',
diff --git a/rdiff_backup/rpath.py b/rdiff_backup/rpath.py
index a65df45..ec276d4 100644
--- a/rdiff_backup/rpath.py
+++ b/rdiff_backup/rpath.py
@@ -36,7 +36,7 @@ are dealing with are local or remote.
 """
 
 import os, stat, re, sys, shutil, gzip, socket, time, errno
-import Globals, Time, static, log, user_group
+import Globals, Time, static, log, user_group, C
 
 
 class SkipFileException(Exception):
@@ -185,6 +185,7 @@ def copy_attribs(rpin, rpout):
 	rpout.chmod(rpin.getperms())
 	if Globals.acls_write: rpout.write_acl(rpin.get_acl())
 	if not rpin.isdev(): rpout.setmtime(rpin.getmtime())
+	if Globals.win_acls_write: rpout.write_win_acl(rpin.get_win_acl())
 
 def copy_attribs_inc(rpin, rpout):
 	"""Change file attributes of rpout to match rpin
@@ -245,7 +246,7 @@ def rename(rp_source, rp_dest):
 	if not rp_source.lstat(): rp_dest.delete()
 	else:
 		if rp_dest.lstat() and rp_source.getinode() == rp_dest.getinode() and \
-				rp_source.getinode() != -1:
+				rp_source.getinode() != 0:
 			log.Log("Warning: Attempt to rename over same inode: %s to %s"
 					% (rp_source.path, rp_dest.path), 2)
 			# You can't rename one hard linked file over another
@@ -263,16 +264,65 @@ def rename(rp_source, rp_dest):
 		rp_dest.data = rp_source.data
 		rp_source.data = {'type': None}
 
-def tupled_lstat(filename):
-	"""Like os.lstat, but return only a tuple, or None if os.error
+def make_file_dict(filename):
+	"""Generate the data dictionary for the given RPath
 
-	Later versions of os.lstat return a special lstat object,
-	which can confuse the pickler and cause errors in remote
-	operations.  This has been fixed in Python 2.2.1.
+	This is a global function so that os.name can be called locally,
+	thus avoiding network lag and so that we only need to send the
+	filename over the network, thus avoiding the need to pickle an
+	(incomplete) rpath object.
+	"""
+	if os.name != 'nt':
+		return C.make_file_dict(filename)
+	else:
+		return make_file_dict_python(filename)
 
+def make_file_dict_python(filename):
+	"""Create the data dictionary using a Python call to os.lstat
+	
+	We do this on Windows since Python's implementation is much better
+	than the one in cmodule.c    Eventually, we will move to using
+	this on all platforms since CPUs have gotten much faster than
+	they were when it was necessary to write cmodule.c
 	"""
-	try: return tuple(os.lstat(filename))
-	except os.error: return None
+	try:
+		statblock = os.lstat(filename)
+	except os.error:
+		return {'type':None}
+	data = {}
+	mode = statblock[stat.ST_MODE]
+
+	if stat.S_ISREG(mode): type = 'reg'
+	elif stat.S_ISDIR(mode): type = 'dir'
+	elif stat.S_ISCHR(mode):
+		type = 'dev'
+		s = statblock.st_rdev
+		data['devnums'] = ('c',) + (s >> 8, s & 0xff)
+	elif stat.S_ISBLK(mode):
+		type = 'dev'
+		s = statblock.st_rdev
+		data['devnums'] = ('b',) + (s >> 8, s & 0xff)
+	elif stat.S_ISFIFO(mode): type = 'fifo'
+	elif stat.S_ISLNK(mode):
+		type = 'sym'
+		data['linkname'] = os.readlink(filename)
+	elif stat.S_ISSOCK(mode): type = 'sock'
+	else: raise C.UnknownFileError(filename)
+	data['type'] = type
+	data['size'] = statblock[stat.ST_SIZE]
+	data['perms'] = stat.S_IMODE(mode)
+	data['uid'] = statblock[stat.ST_UID]
+	data['gid'] = statblock[stat.ST_GID]
+	data['inode'] = statblock[stat.ST_INO]
+	data['devloc'] = statblock[stat.ST_DEV]
+	data['nlink'] = statblock[stat.ST_NLINK]
+
+	if not (type == 'sym' or type == 'dev'):
+		# mtimes on symlinks and dev files don't work consistently
+		data['mtime'] = long(statblock[stat.ST_MTIME])
+		data['atime'] = long(statblock[stat.ST_ATIME])
+		data['ctime'] = long(statblock[stat.ST_CTIME])
+	return data
 
 def make_socket_local(rpath):
 	"""Make a local socket at the given path
@@ -297,6 +347,26 @@ def open_local_read(rpath):
 	assert rpath.conn is Globals.local_connection
 	return open(rpath.path, "rb")
 
+def get_incfile_info(basename):
+	"""Returns None or tuple of 
+	(is_compressed, timestr, type, and basename)"""
+	dotsplit = basename.split(".")
+	if dotsplit[-1] == "gz":
+		compressed = 1
+		if len(dotsplit) < 4: return None
+		timestring, ext = dotsplit[-3:-1]
+	else:
+		compressed = None
+		if len(dotsplit) < 3: return None
+		timestring, ext = dotsplit[-2:]
+	if Time.stringtotime(timestring) is None: return None
+	if not (ext == "snapshot" or ext == "dir" or
+			ext == "missing" or ext == "diff" or ext == "data"):
+		return None
+	if compressed: basestr = ".".join(dotsplit[:-3])
+	else: basestr = ".".join(dotsplit[:-2])
+	return (compressed, timestring, ext, basestr)
+
 
 class RORPath:
 	"""Read Only RPath - carry information about a path
@@ -338,6 +408,7 @@ class RORPath:
 			elif key == 'size' and not self.isreg(): pass
 			elif key == 'ea' and not Globals.eas_active: pass
 			elif key == 'acl' and not Globals.acls_active: pass
+			elif key == 'win_acl' and not Globals.win_acls_active: pass
 			elif key == 'carbonfile' and not Globals.carbonfile_active: pass
 			elif key == 'resourcefork' and not Globals.resource_forks_active:
 				pass
@@ -378,6 +449,7 @@ class RORPath:
 			elif key == 'inode': pass
 			elif key == 'ea' and not Globals.eas_write: pass
 			elif key == 'acl' and not Globals.acls_write: pass
+			elif key == 'win_acl' and not Globals.win_acls_write: pass
 			elif key == 'carbonfile' and not Globals.carbonfile_write: pass
 			elif key == 'resourcefork' and not Globals.resource_forks_write:
 				pass
@@ -389,14 +461,17 @@ class RORPath:
 
 		if self.lstat() and not self.issym() and Globals.change_ownership:
 			# Now compare ownership.  Symlinks don't have ownership
-			if user_group.map_rpath(self) != other.getuidgid(): return 0
+			try:
+				if user_group.map_rpath(self) != other.getuidgid(): return 0
+			except KeyError:
+				return 0 # uid/gid might be missing if metadata file is corrupt
 
 		return 1
 
 	def equal_verbose(self, other, check_index = 1,
 					  compare_inodes = 0, compare_ownership = 0,
-					  compare_acls = 0, compare_eas = 0, compare_size = 1,
-					  compare_type = 1, verbosity = 2):
+					  compare_acls = 0, compare_eas = 0, compare_win_acls = 0,
+					  compare_size = 1, compare_type = 1, verbosity = 2):
 		"""Like __eq__, but log more information.  Useful when testing"""
 		if check_index and self.index != other.index:
 			log.Log("Index %s != index %s" % (self.index, other.index),
@@ -417,6 +492,7 @@ class RORPath:
 				pass
 			elif key == 'ea' and not compare_eas: pass
 			elif key == 'acl' and not compare_acls: pass
+			elif key == 'win_acl' and not compare_win_acls: pass
 			elif (not other.data.has_key(key) or
 				  self.data[key] != other.data[key]):
 				if not other.data.has_key(key):
@@ -434,7 +510,8 @@ class RORPath:
 		return self.equal_verbose(other,
 								  compare_inodes = compare_inodes,
 								  compare_eas = Globals.eas_active,
-								  compare_acls = Globals.acls_active)
+								  compare_acls = Globals.acls_active,
+								  compare_win_acls = Globals.win_acls_active)
 							 
 	def __ne__(self, other): return not self.__eq__(other)
 
@@ -682,6 +759,17 @@ class RORPath:
 		"""Record resource fork in dictionary.  Does not write"""
 		self.data['resourcefork'] = rfork
 
+	def set_win_acl(self, acl):
+		"""Record Windows access control list in dictionary. Does not write"""
+		self.data['win_acl'] = acl
+
+	def get_win_acl(self):
+		"""Return access control list object from dictionary"""
+		try: return self.data['win_acl']
+		except KeyError:
+			acl = self.data['win_acl'] = get_blank_win_acl(self.index)
+			return acl
+
 	def has_alt_mirror_name(self):
 		"""True if rorp has an alternate mirror name specified"""
 		return self.data.has_key('mirrorname')
@@ -787,48 +875,10 @@ class RPath(RORPath):
 		self.path = "/".join((self.base,) + self.index)
 
 	def setdata(self):
-		"""Set data dictionary using C extension"""
-		self.data = self.conn.C.make_file_dict(self.path)
+		"""Set data dictionary using the wrapper"""
+		self.data = self.conn.rpath.make_file_dict(self.path)
 		if self.lstat(): self.conn.rpath.setdata_local(self)
 
-	def make_file_dict_old(self):
-		"""Create the data dictionary"""
-		statblock = self.conn.rpath.tupled_lstat(self.path)
-		if statblock is None:
-			return {'type':None}
-		data = {}
-		mode = statblock[stat.ST_MODE]
-
-		if stat.S_ISREG(mode): type = 'reg'
-		elif stat.S_ISDIR(mode): type = 'dir'
-		elif stat.S_ISCHR(mode):
-			type = 'dev'
-			data['devnums'] = ('c',) + self._getdevnums()
-		elif stat.S_ISBLK(mode):
-			type = 'dev'
-			data['devnums'] = ('b',) + self._getdevnums()
-		elif stat.S_ISFIFO(mode): type = 'fifo'
-		elif stat.S_ISLNK(mode):
-			type = 'sym'
-			data['linkname'] = self.conn.os.readlink(self.path)
-		elif stat.S_ISSOCK(mode): type = 'sock'
-		else: raise C.UnknownFileError(self.path)
-		data['type'] = type
-		data['size'] = statblock[stat.ST_SIZE]
-		data['perms'] = stat.S_IMODE(mode)
-		data['uid'] = statblock[stat.ST_UID]
-		data['gid'] = statblock[stat.ST_GID]
-		data['inode'] = statblock[stat.ST_INO]
-		data['devloc'] = statblock[stat.ST_DEV]
-		data['nlink'] = statblock[stat.ST_NLINK]
-
-		if not (type == 'sym' or type == 'dev'):
-			# mtimes on symlinks and dev files don't work consistently
-			data['mtime'] = long(statblock[stat.ST_MTIME])
-			data['atime'] = long(statblock[stat.ST_ATIME])
-			data['ctime'] = long(statblock[stat.ST_CTIME])
-		return data
-
 	def check_consistency(self):
 		"""Raise an error if consistency of rp broken
 
@@ -842,11 +892,6 @@ class RPath(RORPath):
 			   "\nName: %s\nOld: %s --> New: %s\n" % \
 			   (self.path, temptype, self.data['type'])
 
-	def _getdevnums(self):
-		"""Return tuple for special file (major, minor)"""
-		s = self.conn.reval("lambda path: os.lstat(path).st_rdev", self.path)
-		return (s >> 8, s & 0xff)
-
 	def chmod(self, permissions):
 		"""Wrapper around os.chmod"""
 		try:
@@ -1112,25 +1157,17 @@ class RPath(RORPath):
 		Also sets various inc information used by the *inc* functions.
 
 		"""
-		if self.index: dotsplit = self.index[-1].split(".")
-		else: dotsplit = self.base.split(".")
-		if dotsplit[-1] == "gz":
-			self.inc_compressed = 1
-			if len(dotsplit) < 4: return None
-			timestring, ext = dotsplit[-3:-1]
+		if self.index: basename = self.index[-1]
+		else: basename = self.base
+
+		inc_info = get_incfile_info(basename)
+
+		if inc_info:
+			self.inc_compressed, self.inc_timestr, \
+				self.inc_type, self.inc_basestr = inc_info
+			return 1
 		else:
-			self.inc_compressed = None
-			if len(dotsplit) < 3: return None
-			timestring, ext = dotsplit[-2:]
-		if Time.stringtotime(timestring) is None: return None
-		if not (ext == "snapshot" or ext == "dir" or
-				ext == "missing" or ext == "diff" or ext == "data"):
 			return None
-		self.inc_timestr = timestring
-		self.inc_type = ext
-		if self.inc_compressed: self.inc_basestr = ".".join(dotsplit[:-3])
-		else: self.inc_basestr = ".".join(dotsplit[:-2])
-		return 1
 
 	def isinccompressed(self):
 		"""Return true if inc file is compressed"""
@@ -1304,6 +1341,16 @@ class RPath(RORPath):
 		assert not fp.close()
 		self.set_resource_fork(rfork_data)
 
+	def get_win_acl(self):
+		"""Return Windows access control list, setting if necessary"""
+		try: acl = self.data['win_acl']
+		except KeyError: acl = self.data['win_acl'] = win_acl_get(self)
+		return acl
+
+	def write_win_acl(self, acl):
+		"""Change access control list of rp"""
+		write_win_acl(self, acl)
+		self.data['win_acl'] = acl
 
 class RPathFileHook:
 	"""Look like a file, but add closing hook"""
@@ -1394,6 +1441,8 @@ def setdata_local(rpath):
 	rpath.data['gname'] = user_group.gid2gname(rpath.data['gid'])
 	if Globals.eas_conn: rpath.data['ea'] = ea_get(rpath)
 	if Globals.acls_conn: rpath.data['acl'] = acl_get(rpath)
+	if Globals.win_acls_conn:
+		rpath.data['win_acl'] = win_acl_get(rpath)
 	if Globals.resource_forks_conn and rpath.isreg():
 		rpath.get_resource_fork()
 	if Globals.carbonfile_conn and rpath.isreg():
@@ -1427,3 +1476,7 @@ def acl_get(rp): assert 0
 def get_blank_acl(index): assert 0
 def ea_get(rp): assert 0
 def get_blank_ea(index): assert 0
+
+def win_acl_get(rp): assert 0
+def write_win_acl(rp): assert 0
+def get_blank_win_acl(): assert 0
diff --git a/rdiff_backup/user_group.py b/rdiff_backup/user_group.py
index 39b27e1..e670f75 100644
--- a/rdiff_backup/user_group.py
+++ b/rdiff_backup/user_group.py
@@ -30,7 +30,11 @@ objects should only be used on the destination.
 
 """
 
-import grp, pwd
+try:
+	import grp, pwd
+except ImportError:
+	pass
+
 import log, Globals
 
 ############ "Private" section - don't use outside user_group ###########
@@ -52,7 +56,7 @@ def uname2uid(uname):
 	try: return uname2uid_dict[uname]
 	except KeyError:
 		try: uid = pwd.getpwnam(uname)[2]
-		except KeyError: uid = None
+		except (KeyError, NameError): uid = None
 		uname2uid_dict[uname] = uid
 		return uid
 
@@ -62,7 +66,7 @@ def gname2gid(gname):
 	try: return gname2gid_dict[gname]
 	except KeyError:
 		try: gid = grp.getgrnam(gname)[2]
-		except KeyError: gid = None
+		except (KeyError, NameError): gid = None
 		gname2gid_dict[gname] = gid
 		return gid
 
@@ -163,7 +167,7 @@ def uid2uname(uid):
 	try: return uid2uname_dict[uid]
 	except KeyError:
 		try: uname = pwd.getpwuid(uid)[0]
-		except (KeyError, OverflowError), e: uname = None
+		except (KeyError, OverflowError, NameError), e: uname = None
 		uid2uname_dict[uid] = uname
 		return uname
 
@@ -172,7 +176,7 @@ def gid2gname(gid):
 	try: return gid2gname_dict[gid]
 	except KeyError:
 		try: gname = grp.getgrgid(gid)[0]
-		except (KeyError, OverflowError), e: gname = None
+		except (KeyError, OverflowError, NameError), e: gname = None
 		gid2gname_dict[gid] = gname
 		return gname
 
diff --git a/rdiff_backup/win_acls.py b/rdiff_backup/win_acls.py
new file mode 100644
index 0000000..1e95533
--- /dev/null
+++ b/rdiff_backup/win_acls.py
@@ -0,0 +1,199 @@
+# Copyright 2008 Fred Gansevles <fred@betterbe.com>
+#
+# This file is part of rdiff-backup.
+#
+# rdiff-backup is free software; you can redistribute it and/or modify
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# rdiff-backup is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with rdiff-backup; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+# USA
+
+import C, metadata, re, rorpiter, rpath
+
+try:
+	from win32security import *
+except:
+	GROUP_SECURITY_INFORMATION = 0
+	OWNER_SECURITY_INFORMATION = 0
+	DACL_SECURITY_INFORMATION = 0
+
+class ACL:
+	flags = (GROUP_SECURITY_INFORMATION|
+		 OWNER_SECURITY_INFORMATION|
+		 DACL_SECURITY_INFORMATION)
+
+	def __init__(self, index=()):
+		self.__acl = ""
+		self.index = index
+
+	def get_indexpath(self): return self.index and '/'.join(self.index) or '.'
+
+	def load_from_rp(self, rp, skip_inherit_only = True):
+		self.index = rp.index
+		try:
+			sd = rp.conn.win32security.GetFileSecurity(rp.path, ACL.flags)
+		except:
+			return
+
+		if skip_inherit_only:
+			# skip the inherit_only aces
+			acl = sd.GetSecurityDescriptorDacl()
+			if acl:
+				n = acl.GetAceCount()
+				# traverse the ACL in reverse, so the indices stay correct
+				while n:
+					n -= 1
+					ace_flags = acl.GetAce(n)[0][1]
+					if ace_flags & INHERIT_ONLY_ACE:
+						acl.DeleteAce(n)
+			sd.SetSecurityDescriptorDacl(1, acl, 0)
+
+			if ACL.flags & SACL_SECURITY_INFORMATION:
+				acl = sd.GetSecurityDescriptorSacl()
+				if acl:
+					n = acl.GetAceCount()
+					# traverse the ACL in reverse, so the indices stay correct
+					while n:
+						n -= 1
+						ace_flags = acl.GetAce(n)[0][1]
+						if ace_flags & INHERIT_ONLY_ACE:
+							acl.DeleteAce(n)
+					sd.SetSecurityDescriptorSacl(1, acl, 0)
+
+		self.__acl = \
+			rp.conn.win32security.ConvertSecurityDescriptorToStringSecurityDescriptor(sd,
+					SDDL_REVISION_1, ACL.flags)
+
+	def clear_rp(self, rp):
+		# not sure how to interpret this
+		# I'll jus clear all acl-s from rp.path
+		sd = rp.conn.win32security.GetFileSecurity(rp.path, ACL.flags)
+
+		acl = sd.GetSecurityDescriptorDacl()
+		if acl:
+			n = acl.GetAceCount()
+			# traverse the ACL in reverse, so the indices stay correct
+			while n:
+				n -= 1
+				acl.DeleteAce(n)
+			sd.SetSecurityDescriptorDacl(1, acl, 0)
+
+		if ACL.flags & SACL_SECURITY_INFORMATION:
+			acl = sd.GetSecurityDescriptorSacl()
+			if acl:
+				n = acl.GetAceCount()
+				# traverse the ACL in reverse, so the indices stay correct
+				while n:
+					n -= 1
+					acl.DeleteAce(n)
+				sd.SetSecurityDescriptorSacl(1, acl, 0)
+
+		SetFileSecurity(rp.path, ACL.flags, sd)
+
+	def write_to_rp(self, rp):
+		if self.__acl:
+			sd = rp.conn.win32security.ConvertStringSecurityDescriptorToSecurityDescriptor(self.__acl,
+						SDDL_REVISION_1)
+			rp.conn.win32security.SetFileSecurity(rp.path, ACL.flags, sd)
+
+	def __str__(self):
+		return '# file: %s\n%s\n' % \
+				(C.acl_quote(self.get_indexpath()), unicode(self.__acl))
+
+	def from_string(self, acl_str):
+		lines = acl_str.splitlines()
+		if len(lines) != 2 or not lines[0][:8] == "# file: ":
+			raise metadata.ParsingError("Bad record beginning: " + lines[0][:8])
+		filename = lines[0][8:]
+		if filename == '.': self.index = ()
+		else: self.index = tuple(C.acl_unquote(filename).split('/'))
+		self.__acl = lines[1]
+
+def Record2WACL(record):
+	acl = ACL()
+	acl.from_string(record)
+	return acl
+
+def WACL2Record(wacl):
+	return unicode(wacl)
+
+class WACLExtractor(metadata.FlatExtractor):
+	"""Iterate ExtendedAttributes objects from the WACL information file"""
+	record_boundary_regexp = re.compile('(?:\\n|^)(# file: (.*?))\\n')
+	record_to_object = staticmethod(Record2WACL)
+	def filename_to_index(self, filename):
+		"""Convert possibly quoted filename to index tuple"""
+		if filename == '.': return ()
+		else: return tuple(C.acl_unquote(filename).split('/'))
+
+class WinAccessControlListFile(metadata.FlatFile):
+	"""Store/retrieve ACLs from extended_attributes file"""
+	_prefix = "win_access_control_lists"
+	_extractor = WACLExtractor
+	_object_to_record = staticmethod(WACL2Record)
+
+def join_wacl_iter(rorp_iter, wacl_iter):
+	"""Update a rorp iter by adding the information from acl_iter"""
+	for rorp, wacl in rorpiter.CollateIterators(rorp_iter, wacl_iter):
+		assert rorp, "Missing rorp for index %s" % (wacl.index,)
+		if not wacl: wacl = ACL(rorp.index)
+		rorp.set_win_acl(unicode(wacl))
+		yield rorp
+	
+def rpath_acl_win_get(rpath):
+	acl = ACL()
+	acl.load_from_rp(rpath)
+	return unicode(acl)
+rpath.win_acl_get = rpath_acl_win_get
+
+def rpath_get_blank_win_acl(index):
+	acl = ACL(index)
+	return unicode(acl)
+rpath.get_blank_win_acl = rpath_get_blank_win_acl
+
+def rpath_set_win_acl(rp, acl_str):
+	acl = ACL()
+	acl.from_string(acl_str)
+	acl.write_to_rp(rp)
+rpath.write_win_acl = rpath_set_win_acl
+
+def init_acls():
+	# A process that tries to read or write a SACL needs
+	# to have and enable the SE_SECURITY_NAME privilege.
+	# And inorder to backup/restore, the SE_BACKUP_NAME and
+	# SE_RESTORE_NAME privileges are needed.
+	import win32api
+	try:
+		hnd = OpenProcessToken(win32api.GetCurrentProcess(),
+			TOKEN_ADJUST_PRIVILEGES| TOKEN_QUERY)
+	except win32api.error:
+		return
+	try:
+		try:
+			lpv = lambda priv: LookupPrivilegeValue(None, priv)
+			# enable the SE_*_NAME priveleges
+			SecurityName = lpv(SE_SECURITY_NAME)
+			AdjustTokenPrivileges(hnd, False, [
+				(SecurityName, SE_PRIVILEGE_ENABLED),
+				(lpv(SE_BACKUP_NAME), SE_PRIVILEGE_ENABLED),
+				(lpv(SE_RESTORE_NAME), SE_PRIVILEGE_ENABLED)
+				])
+		except win32api.error:
+			return
+		for name, enabled in GetTokenInformation(hnd, TokenPrivileges):
+			if name == SecurityName and enabled:
+				# now we *may* access the SACL (sigh)
+				ACL.flags |= SACL_SECURITY_INFORMATION
+				break
+	finally:
+		win32api.CloseHandle(hnd)
+
diff --git a/setup.py b/setup.py
index 87e47dd..9b1f4d8 100755
--- a/setup.py
+++ b/setup.py
@@ -3,7 +3,7 @@
 import sys, os, getopt
 from distutils.core import setup, Extension
 
-version_string = "1.1.16"
+version_string = "1.1.17"
 
 if sys.version_info[:2] < (2,2):
 	print "Sorry, rdiff-backup requires version 2.2 or later of python"
@@ -13,6 +13,7 @@ if sys.version_info[:2] < (2,2):
 lflags_arg = []
 libname = ['rsync']
 incdir_list = libdir_list = None
+extra_options = {}
 
 if os.name == 'posix' or os.name == 'nt':
 	LIBRSYNC_DIR = os.environ.get('LIBRSYNC_DIR', '')
@@ -41,6 +42,22 @@ if os.name == 'posix' or os.name == 'nt':
 		if '-lrsync' in LIBS:
 			libname = []
 
+if os.name == 'nt':
+	try:
+		import py2exe
+	except ImportError:
+		pass
+	else:
+		extra_options = {
+			'console': ['rdiff-backup'],
+		}
+		if '--single-file' in sys.argv[1:]:
+			sys.argv.remove('--single-file')
+			extra_options.update({
+				'options': {'py2exe': {'bundle_files': 1}},
+				'zipfile': None
+			})
+
 setup(name="rdiff-backup",
 	  version=version_string,
 	  description="Local/remote mirroring+incremental backup",
@@ -59,5 +76,6 @@ setup(name="rdiff-backup",
 	  data_files = [('share/man/man1', ['rdiff-backup.1',
 										'rdiff-backup-statistics.1']),
 					('share/doc/rdiff-backup-%s' % (version_string,),
-					 ['CHANGELOG', 'COPYING', 'README', 'FAQ.html'])])
+					 ['CHANGELOG', 'COPYING', 'README', 'FAQ.html'])],
+					**extra_options)
 

Reply to: