[Top][All Lists]
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[rdiff-backup-users] [PATCH] Backing up Windows ACLs
From: |
Josh Nisly |
Subject: |
[rdiff-backup-users] [PATCH] Backing up Windows ACLs |
Date: |
Thu, 26 Jun 2008 17:01:41 +0600 |
User-agent: |
Thunderbird 2.0.0.14 (X11/20080505) |
Attached is a patch that fixes any problems I know about with the
previous one, and includes several fixes by Fred Gansevles. It
serializes the ACL record to string in the RPath member, which allows
new versions to work with existing servers. I've also implemented the
correct checks for whether the remote connection supports ACLs, so
backing up a Windows client to a linux server should work fine.
Comments are welcome.
Thanks,
JoshN
Index: dist/makedist
===================================================================
RCS file: /sources/rdiff-backup/rdiff-backup/dist/makedist,v
retrieving revision 1.29
diff -u -r1.29 makedist
--- dist/makedist 23 Jun 2008 02:30:21 -0000 1.29
+++ dist/makedist 23 Jun 2008 06:35:53 -0000
@@ -118,7 +118,7 @@
"Security.py", "selection.py",
"SetConnections.py", "static.py",
"statistics.py", "TempFile.py",
"Time.py",
- "user_group.py"]:
+ "user_group.py", "win_acls.py"]:
shutil.copyfile(os.path.join(SourceDir, filename),
os.path.join(tardir,
"rdiff_backup", filename))
Index: rdiff_backup/Globals.py
===================================================================
RCS file: /sources/rdiff-backup/rdiff-backup/rdiff_backup/Globals.py,v
retrieving revision 1.45
diff -u -r1.45 Globals.py
--- rdiff_backup/Globals.py 13 Apr 2008 11:25:21 -0000 1.45
+++ rdiff_backup/Globals.py 26 Jun 2008 06:44:09 -0000
@@ -85,6 +85,12 @@
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
Index: rdiff_backup/connection.py
===================================================================
RCS file: /sources/rdiff-backup/rdiff-backup/rdiff_backup/connection.py,v
retrieving revision 1.29
diff -u -r1.29 connection.py
--- rdiff_backup/connection.py 9 Jul 2007 03:53:40 -0000 1.29
+++ rdiff_backup/connection.py 23 Jun 2008 06:35:52 -0000
@@ -27,7 +27,8 @@
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 @@
TempFile, SetConnections, librsync, log, regress, fs_abilities, \
eas_acls, user_group, compare
+try: import win_acls
+except: pass
+
Globals.local_connection = LocalConnection()
Globals.connections.append(Globals.local_connection)
# Following changed by server in SetConnections
Index: rdiff_backup/fs_abilities.py
===================================================================
RCS file: /sources/rdiff-backup/rdiff-backup/rdiff_backup/fs_abilities.py,v
retrieving revision 1.46
diff -u -r1.46 fs_abilities.py
--- rdiff_backup/fs_abilities.py 14 Jun 2008 18:17:57 -0000 1.46
+++ rdiff_backup/fs_abilities.py 25 Jun 2008 05:59:05 -0000
@@ -29,7 +29,7 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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')
@@ -489,6 +511,7 @@
% (subdir.path), 4)
self.escape_dos_devices = 1
+
def get_readonly_fsa(desc_string, rp):
"""Return an fsa with given description_string
@@ -521,6 +544,10 @@
log.Log.FatalError("--never-drop-acls specified, but
ACL support\n"
"missing from
destination 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,
self.dest_fsa.resource_forks,
@@ -729,6 +756,10 @@
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 +785,7 @@
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 +813,7 @@
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()
Index: rdiff_backup/metadata.py
===================================================================
RCS file: /sources/rdiff-backup/rdiff-backup/rdiff_backup/metadata.py,v
retrieving revision 1.29
diff -u -r1.29 metadata.py
--- rdiff_backup/metadata.py 14 Jun 2008 18:17:57 -0000 1.29
+++ rdiff_backup/metadata.py 23 Jun 2008 06:35:52 -0000
@@ -433,9 +433,10 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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
@@ -664,3 +691,4 @@
import eas_acls # put at bottom to avoid python circularity bug
+import win_acls
Index: rdiff_backup/rpath.py
===================================================================
RCS file: /sources/rdiff-backup/rdiff-backup/rdiff_backup/rpath.py,v
retrieving revision 1.120
diff -u -r1.120 rpath.py
--- rdiff_backup/rpath.py 10 Jun 2008 13:14:52 -0000 1.120
+++ rdiff_backup/rpath.py 26 Jun 2008 06:44:09 -0000
@@ -185,6 +185,7 @@
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
@@ -257,6 +258,8 @@
if error.errno != errno.EEXIST: raise
# On Windows, files can't be renamed on top of
an existing file
+ try: rp_source.conn.os.chmod(rp_dest.path,
stat.S_IWRITE)
+ except: pass
rp_source.conn.os.unlink(rp_dest.path)
rp_source.conn.os.rename(rp_source.path,
rp_dest.path)
@@ -338,6 +361,7 @@
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 +402,7 @@
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
@@ -395,8 +420,8 @@
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 +442,7 @@
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 +460,8 @@
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 +709,17 @@
"""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')
@@ -1304,6 +1342,16 @@
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 +1442,8 @@
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 +1477,7 @@
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
--- rdiff_backup/win_acls.py.orig Thu Jun 26 01:48:09 2008
+++ rdiff_backup/win_acls.py Thu Jun 26 01:44:09 2008
@@ -0,0 +1,196 @@
+# Copyright 2008 Fred Gansevles <address@hidden>
+#
+# 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
+
+__version__ = (0, 1, 0)
+
+import C, metadata, re, rorpiter, rpath
+
+from win32security import *
+
+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(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)
+