[SCM] Samba Shared Repository - branch master updated

Stefan Metzmacher metze at samba.org
Mon Feb 5 17:33:02 UTC 2018


The branch, master has been updated
       via  0c3348f dbcheck: skip find_missing_forward_links_from_backlinks() if the db has the sortedLinks feature
       via  5bf823d dbcheck: add support for restoring missing forward links
       via  d59f201 dbcheck: add find_missing_forward_links_from_backlinks()
       via  182fb3c dbcheck: make sure we ask for replPropertyMetaData if we need to process any forward link attributes
       via  2059803 dbcheck: make sure we always ask for the objectGUID attribute explicitly
       via  e258b4f dbcheck: add a helper function that checks is a value has duplicate links
       via  e4cc062 dbcheck: add a dict where we remember attributes with duplicate links
       via  44a8782 dbcheck: split out check_duplicate_links from check_dn
       via  7df17c0 dbcheck: store fixed forward link attributes with the correct sorting
       via  b0bc3f6 dbcheck: remove ldb.FLAG_MOD_REPLACE when replacing search results for forward links
       via  ec433f8 dbcheck: rename err_duplicate_links() to err_recover_forward_links() and adjust the output message
       via  dc43d31 dbcheck: add link direction to error message for duplicate links
       via  a651cc7 dbcheck: rename err_duplicate_links arguments
       via  9f47fe6 dbcheck: only pass obj_dn to err_orphaned_backlink()
       via  6f77503 dbcheck: add forward_syntax argument to err_orphaned_backlink
       via  4a71394 dbcheck: rename and reorder err_orphaned_backlink arguments
       via  52bd0b0 selftest/dbcheck: add a test for corrupt forward links restoration
       via  8c01acd Revert "dbcheck: disable fixing duplicate linked attributes until we can recover lost forward links"
       via  55d4665 python/common: add __cmp__ function to dsdb_Dn similar to parsed_dn_compare()
       via  c56eb49 python:tests: add test_dsdb_Dn_sorted() to "samba.tests.common"
       via  1341780 python:tests: remove test_dsdb_Dn() to test_dsdb_Dn_binary()
       via  681e0a1 python:tests: use TestCaseInTempDir for "samba.tests.common"
       via  28fcf63 python/netcmd: implement __repr__ for class CommandError
      from  84f07a8 s3/smbd: fix handling of delete-on-close on directories

https://git.samba.org/?p=samba.git;a=shortlog;h=master


- Log -----------------------------------------------------------------
commit 0c3348feb09f4f0ba85455b8c3ff5c5fa60d139b
Author: Stefan Metzmacher <metze at samba.org>
Date:   Wed Jan 31 09:50:47 2018 +0100

    dbcheck: skip find_missing_forward_links_from_backlinks() if the db has the sortedLinks feature
    
    BUG: https://bugzilla.samba.org/show_bug.cgi?id=13228
    
    Signed-off-by: Stefan Metzmacher <metze at samba.org>
    Reviewed-by: Ralph Boehme <slow at samba.org>
    
    Autobuild-User(master): Stefan Metzmacher <metze at samba.org>
    Autobuild-Date(master): Mon Feb  5 18:32:51 CET 2018 on sn-devel-144

commit 5bf823d68bd33ee3160175a18a3838eff4e3cbb2
Author: Ralph Boehme <slow at samba.org>
Date:   Thu Jan 25 14:48:55 2018 +0100

    dbcheck: add support for restoring missing forward links
    
    This recovers broken databases with duplicate and missing
    forward links.
    
    See commit a25c99c9f1fd1814c56c21848c748cd0e038eed7 for
    the fix that prevents to problem from happening.
    
    BUG: https://bugzilla.samba.org/show_bug.cgi?id=13228
    
    Pair-Programmed-With: Stefan Metzmacher <metze at samba.org>
    
    Signed-off-by: Ralph Boehme <slow at samba.org>
    Signed-off-by: Stefan Metzmacher <metze at samba.org>

commit d59f201388e8a16688adda145734dab8e27b785f
Author: Ralph Boehme <slow at samba.org>
Date:   Thu Jan 25 14:48:55 2018 +0100

    dbcheck: add find_missing_forward_links_from_backlinks()
    
    find_missing_forward_links_from_backlinks() finds and returns missing forward-links by
    searching all for all objects that link to the object in the backlink attribute.
    
    This will be used in the next commit to restore forward links in a corrupted
    forward link attribute by passing the missing backling objects to
    err_recover_forward_links().
    
    BUG: https://bugzilla.samba.org/show_bug.cgi?id=13228
    
    Pair-Programmed-With: Stefan Metzmacher <metze at samba.org>
    
    Signed-off-by: Ralph Boehme <slow at samba.org>
    Signed-off-by: Stefan Metzmacher <metze at samba.org>

commit 182fb3c4c9db8715d0dbcbc3d1aa0655b5cb29f1
Author: Stefan Metzmacher <metze at samba.org>
Date:   Tue Jan 30 12:19:31 2018 +0100

    dbcheck: make sure we ask for replPropertyMetaData if we need to process any forward link attributes
    
    BUG: https://bugzilla.samba.org/show_bug.cgi?id=13228
    
    Signed-off-by: Stefan Metzmacher <metze at samba.org>
    Reviewed-by: Ralph Boehme <slow at samba.org>

commit 20598033866ca3d0fdad1edf3cb39e4614eae112
Author: Stefan Metzmacher <metze at samba.org>
Date:   Tue Jan 30 12:19:31 2018 +0100

    dbcheck: make sure we always ask for the objectGUID attribute explicitly
    
    BUG: https://bugzilla.samba.org/show_bug.cgi?id=13228
    
    Signed-off-by: Stefan Metzmacher <metze at samba.org>
    Reviewed-by: Ralph Boehme <slow at samba.org>

commit e258b4fb281d8577c425e05b35ce05cf128617ea
Author: Ralph Boehme <slow at samba.org>
Date:   Wed Jan 24 22:24:15 2018 +0100

    dbcheck: add a helper function that checks is a value has duplicate links
    
    Will be used in a subsequent commit.
    
    BUG: https://bugzilla.samba.org/show_bug.cgi?id=13228
    
    Pair-Programmed-With: Stefan Metzmacher <metze at samba.org>
    
    Signed-off-by: Ralph Boehme <slow at samba.org>
    Signed-off-by: Stefan Metzmacher <metze at samba.org>

commit e4cc062fa98f65369f3bde24a987c2651632cb06
Author: Ralph Boehme <slow at samba.org>
Date:   Thu Jan 25 10:34:29 2018 +0100

    dbcheck: add a dict where we remember attributes with duplicate links
    
    BUG: https://bugzilla.samba.org/show_bug.cgi?id=13228
    
    Pair-Programmed-With: Stefan Metzmacher <metze at samba.org>
    
    Signed-off-by: Ralph Boehme <slow at samba.org>
    Signed-off-by: Stefan Metzmacher <metze at samba.org>

commit 44a8782d71676517f0991f279f2472391ecede3b
Author: Ralph Boehme <slow at samba.org>
Date:   Wed Jan 24 20:01:27 2018 +0100

    dbcheck: split out check_duplicate_links from check_dn
    
    Refactoring, no change in behaviour.
    
    BUG: https://bugzilla.samba.org/show_bug.cgi?id=13228
    
    Pair-Programmed-With: Stefan Metzmacher <metze at samba.org>
    
    Signed-off-by: Ralph Boehme <slow at samba.org>
    Signed-off-by: Stefan Metzmacher <metze at samba.org>

commit 7df17c0a8dffceb053ca806c9426d493b4837b1a
Author: Stefan Metzmacher <metze at samba.org>
Date:   Tue Jan 30 09:55:21 2018 +0100

    dbcheck: store fixed forward link attributes with the correct sorting
    
    The corruption we're trying to fix messed up the sorting,
    so there's no point in keeping the current order.
    
    BUG: https://bugzilla.samba.org/show_bug.cgi?id=13228
    
    Signed-off-by: Stefan Metzmacher <metze at samba.org>
    Reviewed-by: Ralph Boehme <slow at samba.org>

commit b0bc3f60084e5998dd34aada2ac7377d390affc6
Author: Stefan Metzmacher <metze at samba.org>
Date:   Tue Jan 30 09:39:40 2018 +0100

    dbcheck: remove ldb.FLAG_MOD_REPLACE when replacing search results for forward links
    
    Search results don't have an ldb.FLAG_MOD_* flags set.
    
    BUG: https://bugzilla.samba.org/show_bug.cgi?id=13228
    
    Signed-off-by: Stefan Metzmacher <metze at samba.org>
    Reviewed-by: Ralph Boehme <slow at samba.org>

commit ec433f8531a822dd40b343fbf3244157a5ecd544
Author: Ralph Boehme <slow at samba.org>
Date:   Thu Jan 25 14:36:52 2018 +0100

    dbcheck: rename err_duplicate_links() to err_recover_forward_links() and adjust the output message
    
    It's really a fatal error to have duplicate values as it's very likely that
    some forward links got lost.
    
    BUG: https://bugzilla.samba.org/show_bug.cgi?id=13228
    
    Pair-Programmed-With: Stefan Metzmacher <metze at samba.org>
    
    Signed-off-by: Ralph Boehme <slow at samba.org>
    Signed-off-by: Stefan Metzmacher <metze at samba.org>

commit dc43d31cd20fd12d2758b73ec0318215b8fbedfb
Author: Ralph Boehme <slow at samba.org>
Date:   Thu Jan 25 14:41:58 2018 +0100

    dbcheck: add link direction to error message for duplicate links
    
    BUG: https://bugzilla.samba.org/show_bug.cgi?id=13228
    
    Pair-Programmed-With: Stefan Metzmacher <metze at samba.org>
    
    Signed-off-by: Ralph Boehme <slow at samba.org>
    Signed-off-by: Stefan Metzmacher <metze at samba.org>

commit a651cc79d64b9bcc1d5fee9b2ef8800a1579dea1
Author: Ralph Boehme <slow at samba.org>
Date:   Wed Jan 24 19:37:55 2018 +0100

    dbcheck: rename err_duplicate_links arguments
    
    In preperation of adding more arguments.
    
    BUG: https://bugzilla.samba.org/show_bug.cgi?id=13228
    
    Pair-Programmed-With: Stefan Metzmacher <metze at samba.org>
    
    Signed-off-by: Ralph Boehme <slow at samba.org>
    Reviewed-by: Stefan Metzmacher <metze at samba.org>

commit 9f47fe6c4a8bde4abfee3c774d9667e6a3439a45
Author: Stefan Metzmacher <metze at samba.org>
Date:   Mon Jan 29 22:48:42 2018 +0100

    dbcheck: only pass obj_dn to err_orphaned_backlink()
    
    BUG: https://bugzilla.samba.org/show_bug.cgi?id=13228
    
    Signed-off-by: Stefan Metzmacher <metze at samba.org>
    Reviewed-by: Ralph Boehme <slow at samba.org>

commit 6f77503871fcb815e474cb76d14e22f7a8f083c9
Author: Ralph Boehme <slow at samba.org>
Date:   Thu Jan 25 10:52:35 2018 +0100

    dbcheck: add forward_syntax argument to err_orphaned_backlink
    
    Will be used in a subsequent commit.
    
    BUG: https://bugzilla.samba.org/show_bug.cgi?id=13228
    
    Pair-Programmed-With: Stefan Metzmacher <metze at samba.org>
    
    Signed-off-by: Ralph Boehme <slow at samba.org>
    Signed-off-by: Stefan Metzmacher <metze at samba.org>

commit 4a71394c6a30e8a1b5c6553f7410148dbf2e4a80
Author: Ralph Boehme <slow at samba.org>
Date:   Wed Jan 24 19:31:23 2018 +0100

    dbcheck: rename and reorder err_orphaned_backlink arguments
    
    In preperation of adding more arguments.
    
    BUG: https://bugzilla.samba.org/show_bug.cgi?id=13228
    
    Pair-Programmed-With: Stefan Metzmacher <metze at samba.org>
    
    Signed-off-by: Ralph Boehme <slow at samba.org>
    Signed-off-by: Stefan Metzmacher <metze at samba.org>

commit 52bd0b09804621e6de9ee0a377a442a42e07ee05
Author: Ralph Boehme <slow at samba.org>
Date:   Thu Jan 25 21:34:47 2018 +0100

    selftest/dbcheck: add a test for corrupt forward links restoration
    
    BUG: https://bugzilla.samba.org/show_bug.cgi?id=13228
    
    Signed-off-by: Ralph Boehme <slow at samba.org>
    Reviewed-by: Stefan Metzmacher <metze at samba.org>

commit 8c01acd56274a5cb5926622cacab997cb62dd5a9
Author: Ralph Boehme <slow at samba.org>
Date:   Wed Jan 24 11:34:43 2018 +0100

    Revert "dbcheck: disable fixing duplicate linked attributes until we can recover lost forward links"
    
    This reverts commit 43e3f79d54c5aeaea820865d298d4249cf47af99.
    
    The real fix will follow in the next commits.
    
    BUG: https://bugzilla.samba.org/show_bug.cgi?id=13228
    
    Signed-off-by: Ralph Boehme <slow at samba.org>
    Signed-off-by: Stefan Metzmacher <metze at samba.org>

commit 55d466549a3113f7625acdd6eb42f71cf63719b5
Author: Stefan Metzmacher <metze at samba.org>
Date:   Tue Jan 30 09:51:20 2018 +0100

    python/common: add __cmp__ function to dsdb_Dn similar to parsed_dn_compare()
    
    Linked attribute values are sorted by objectGUID of the link target.
    For C code we have parsed_dn_compare() to implement the logic,
    the same is now available on python dsdb_Dn objects.
    
    BUG: https://bugzilla.samba.org/show_bug.cgi?id=13228
    
    Signed-off-by: Stefan Metzmacher <metze at samba.org>
    Reviewed-by: Ralph Boehme <slow at samba.org>

commit c56eb49119117a1a06afb0a76630ae5c7a1ca30c
Author: Stefan Metzmacher <metze at samba.org>
Date:   Tue Jan 30 11:09:55 2018 +0100

    python:tests: add test_dsdb_Dn_sorted() to "samba.tests.common"
    
    Failing until dsdb_Dn implements the correct __cmp__() function.
    
    BUG: https://bugzilla.samba.org/show_bug.cgi?id=13228
    
    Signed-off-by: Stefan Metzmacher <metze at samba.org>
    Reviewed-by: Ralph Boehme <slow at samba.org>

commit 1341780dcf9ec0c5d852fbbb77c5e00db2ad6564
Author: Stefan Metzmacher <metze at samba.org>
Date:   Tue Jan 30 11:09:40 2018 +0100

    python:tests: remove test_dsdb_Dn() to test_dsdb_Dn_binary()
    
    BUG: https://bugzilla.samba.org/show_bug.cgi?id=13228
    
    Signed-off-by: Stefan Metzmacher <metze at samba.org>
    Reviewed-by: Ralph Boehme <slow at samba.org>

commit 681e0a1745b45c6ac22d394b9e78cb67007d7dc4
Author: Stefan Metzmacher <metze at samba.org>
Date:   Tue Jan 30 10:39:30 2018 +0100

    python:tests: use TestCaseInTempDir for "samba.tests.common"
    
    BUG: https://bugzilla.samba.org/show_bug.cgi?id=13228
    
    Signed-off-by: Stefan Metzmacher <metze at samba.org>
    Reviewed-by: Ralph Boehme <slow at samba.org>

commit 28fcf631c8985e8418fdc67cc78053e503f56d50
Author: Stefan Metzmacher <metze at samba.org>
Date:   Wed Jan 24 19:14:53 2018 +0100

    python/netcmd: implement __repr__ for class CommandError
    
    Signed-off-by: Stefan Metzmacher <metze at samba.org>
    Reviewed-by: Ralph Boehme <slow at samba.org>

-----------------------------------------------------------------------

Summary of changes:
 python/samba/common.py                             |  17 ++
 python/samba/dbchecker.py                          | 332 +++++++++++++++++----
 python/samba/netcmd/__init__.py                    |   3 +
 python/samba/tests/common.py                       |  33 +-
 selftest/knownfail.d/dbcheck_duplicate_member      |   5 -
 ...ted-after-dbcheck-forward-link-corruption.ldif} |   8 +-
 ...dbcheck-link-output-forward-link-corruption.txt |  12 +
 ...pected-dbcheck-link-output_duplicate_member.txt |   5 +-
 testprogs/blackbox/dbcheck-links.sh                |  78 +++++
 9 files changed, 410 insertions(+), 83 deletions(-)
 delete mode 100644 selftest/knownfail.d/dbcheck_duplicate_member
 copy source4/selftest/provisions/release-4-5-0-pre1/{expected-duplicates-after-link-dbcheck.ldif => expected-after-dbcheck-forward-link-corruption.ldif} (65%)
 create mode 100644 source4/selftest/provisions/release-4-5-0-pre1/expected-dbcheck-link-output-forward-link-corruption.txt


Changeset truncated at 500 lines:

diff --git a/python/samba/common.py b/python/samba/common.py
index 20f170c..a915934 100644
--- a/python/samba/common.py
+++ b/python/samba/common.py
@@ -19,6 +19,8 @@
 
 import ldb
 import dsdb
+from samba.ndr import ndr_pack
+from samba.dcerpc import misc
 import binascii
 
 
@@ -93,6 +95,21 @@ class dsdb_Dn(object):
     def __str__(self):
         return self.prefix + str(self.dn.extended_str(mode=1))
 
+    def __cmp__(self, other):
+        ''' compare dsdb_Dn values similar to parsed_dn_compare()'''
+        dn1 = self
+        dn2 = other
+        guid1 = dn1.dn.get_extended_component("GUID")
+        guid1b = ndr_pack(misc.GUID(guid1))
+        guid2 = dn2.dn.get_extended_component("GUID")
+        guid2b = ndr_pack(misc.GUID(guid2))
+
+        v = cmp(guid1, guid2)
+        if v != 0:
+            return v
+        v = cmp(dn1.binary, dn2.binary)
+        return v
+
     def get_binary_integer(self):
         '''return binary part of a dsdb_Dn as an integer, or None'''
         if self.prefix == '':
diff --git a/python/samba/dbchecker.py b/python/samba/dbchecker.py
index 6e4c440..b2b8b0c 100644
--- a/python/samba/dbchecker.py
+++ b/python/samba/dbchecker.py
@@ -65,7 +65,9 @@ class dbcheck(object):
         self.fix_undead_linked_attributes = False
         self.fix_all_missing_backlinks = False
         self.fix_all_orphaned_backlinks = False
-        self.fix_all_duplicate_links = False
+        self.fix_all_missing_forward_links = False
+        self.duplicate_link_cache = dict()
+        self.recover_all_forward_links = False
         self.fix_rmd_flags = False
         self.fix_ntsecuritydescriptor = False
         self.fix_ntsecuritydescriptor_owner_group = False
@@ -184,6 +186,23 @@ class dbcheck(object):
         else:
             self.rid_set_dn = None
 
+        self.compatibleFeatures = []
+        self.requiredFeatures = []
+
+        try:
+            res = self.samdb.search(scope=ldb.SCOPE_BASE,
+                                    base="@SAMBA_DSDB",
+                                    attrs=["compatibleFeatures",
+                                    "requiredFeatures"])
+            if "compatibleFeatures" in res[0]:
+                self.compatibleFeatures = res[0]["compatibleFeatures"]
+            if "requiredFeatures" in res[0]:
+                self.requiredFeatures = res[0]["requiredFeatures"]
+        except ldb.LdbError as (enum, estr):
+            if enum != ldb.ERR_NO_SUCH_OBJECT:
+                raise
+            pass
+
     def check_database(self, DN=None, scope=ldb.SCOPE_SUBTREE, controls=[], attrs=['*']):
         '''perform a database check, returning the number of errors found'''
         res = self.samdb.search(base=DN, scope=scope, attrs=['dn'], controls=controls)
@@ -708,42 +727,44 @@ newSuperior: %s""" % (str(from_dn), str(to_rdn), str(to_base)))
                           "Failed to fix incorrect RMD_FLAGS %u" % rmd_flags):
             self.report("Fixed incorrect RMD_FLAGS %u" % (rmd_flags))
 
-    def err_orphaned_backlink(self, obj, attrname, val, link_name, target_dn, duplicate_links):
+    def err_orphaned_backlink(self, obj_dn, backlink_attr, backlink_val,
+                              target_dn, forward_attr, forward_syntax,
+                              check_duplicates=True):
         '''handle a orphaned backlink value'''
-        self.report("ERROR: orphaned backlink attribute '%s' in %s for link %s in %s" % (attrname, obj.dn, link_name, target_dn))
-        if duplicate_links:
-            self.report("ERROR: FATAL! Most likely the corresponding forward link got lost!")
-            self.report("ERROR: FATAL! See https://bugzilla.samba.org/show_bug.cgi?id=13228")
-            self.report("Recovery handling will be implemented in a future version")
-            self.report("Not removing orphaned backlink %s" % attrname)
+        if check_duplicates is True and self.has_duplicate_links(target_dn, forward_attr, forward_syntax):
+            self.report("WARNING: Keep orphaned backlink attribute " + \
+                        "'%s' in '%s' for link '%s' in '%s'" % (
+                        backlink_attr, obj_dn, forward_attr, target_dn))
             return
-        if not self.confirm_all('Remove orphaned backlink %s' % attrname, 'fix_all_orphaned_backlinks'):
-            self.report("Not removing orphaned backlink %s" % attrname)
+        self.report("ERROR: orphaned backlink attribute '%s' in %s for link %s in %s" % (backlink_attr, obj_dn, forward_attr, target_dn))
+        if not self.confirm_all('Remove orphaned backlink %s' % backlink_attr, 'fix_all_orphaned_backlinks'):
+            self.report("Not removing orphaned backlink %s" % backlink_attr)
             return
         m = ldb.Message()
-        m.dn = obj.dn
-        m['value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
+        m.dn = obj_dn
+        m['value'] = ldb.MessageElement(backlink_val, ldb.FLAG_MOD_DELETE, backlink_attr)
         if self.do_modify(m, ["show_recycled:1", "relax:0"],
-                          "Failed to fix orphaned backlink %s" % attrname):
-            self.report("Fixed orphaned backlink %s" % (attrname))
+                          "Failed to fix orphaned backlink %s" % backlink_attr):
+            self.report("Fixed orphaned backlink %s" % (backlink_attr))
 
-    def err_duplicate_links(self, obj, attrname, vals):
+    def err_recover_forward_links(self, obj, forward_attr, forward_vals):
         '''handle a duplicate links value'''
 
-        self.report("ERROR: FATAL! Most likely some forward link values for attribute '%s' in '%s' got lost!" % (attrname, obj.dn))
-        self.report("ERROR: FATAL! See https://bugzilla.samba.org/show_bug.cgi?id=13228")
-        self.report("Recovery handling will be implemented in a future version")
-        self.report("Not removing duplicate links in attribute '%s'" % attrname)
-        return
-        if not self.confirm_all("Remove duplicate links in attribute '%s'" % attrname, 'fix_all_duplicate_links'):
-            self.report("Not removing duplicate links in attribute '%s'" % attrname)
+        self.report("RECHECK: 'Missing/Duplicate/Correct link' lines above for attribute '%s' in '%s'" % (forward_attr, obj.dn))
+
+        if not self.confirm_all("Commit fixes for (missing/duplicate) forward links in attribute '%s'" % forward_attr, 'recover_all_forward_links'):
+            self.report("Not fixing corrupted (missing/duplicate) forward links in attribute '%s' of '%s'" % (
+                        forward_attr, obj.dn))
             return
         m = ldb.Message()
         m.dn = obj.dn
-        m['value'] = ldb.MessageElement(vals, ldb.FLAG_MOD_REPLACE, attrname)
+        m['value'] = ldb.MessageElement(forward_vals, ldb.FLAG_MOD_REPLACE, forward_attr)
         if self.do_modify(m, ["local_oid:1.3.6.1.4.1.7165.4.3.19.2:1"],
-                "Failed to fix duplicate links in attribute '%s'" % attrname):
-            self.report("Fixed duplicate links in attribute '%s'" % (attrname))
+                "Failed to fix duplicate links in attribute '%s'" % forward_attr):
+            self.report("Fixed duplicate links in attribute '%s'" % (forward_attr))
+            duplicate_cache_key = "%s:%s" % (str(obj.dn), forward_attr)
+            assert duplicate_cache_key in self.duplicate_link_cache
+            self.duplicate_link_cache[duplicate_cache_key] = False
 
     def err_no_fsmoRoleOwner(self, obj):
         '''handle a missing fSMORoleOwner'''
@@ -896,30 +917,27 @@ newSuperior: %s""" % (str(from_dn), str(to_rdn), str(to_base)))
                 return dsdb_dn
         return None
 
-    def check_dn(self, obj, attrname, syntax_oid):
-        '''check a DN attribute for correctness'''
+    def check_duplicate_links(self, obj, forward_attr, forward_syntax, forward_linkID, backlink_attr):
+        '''check a linked values for duplicate forward links'''
         error_count = 0
-        obj_guid = obj['objectGUID'][0]
 
-        linkID, reverse_link_name = self.get_attr_linkID_and_reverse_name(attrname)
-        if reverse_link_name is not None:
-            reverse_syntax_oid = self.samdb_schema.get_syntax_oid_from_lDAPDisplayName(reverse_link_name)
-        else:
-            reverse_syntax_oid = None
-
-        duplicate_links = False
         duplicate_dict = dict()
-        duplicate_list = list()
         unique_dict = dict()
-        unique_list = list()
-        for val in obj[attrname]:
-            if linkID & 1:
-                #
-                # Only cleanup forward links here,
-                # back links are handled below.
-                break
 
-            dsdb_dn = dsdb_Dn(self.samdb, val, syntax_oid)
+        # Only forward links can have this problem
+        if forward_linkID & 1:
+            # If we got the reverse, skip it
+            return (error_count, duplicate_dict, unique_dict)
+
+        if backlink_attr is None:
+            return (error_count, duplicate_dict, unique_dict)
+
+        duplicate_cache_key = "%s:%s" % (str(obj.dn), forward_attr)
+        if duplicate_cache_key not in self.duplicate_link_cache:
+            self.duplicate_link_cache[duplicate_cache_key] = False
+
+        for val in obj[forward_attr]:
+            dsdb_dn = dsdb_Dn(self.samdb, val, forward_syntax)
 
             # all DNs should have a GUID component
             guid = dsdb_dn.dn.get_extended_component("GUID")
@@ -929,14 +947,12 @@ newSuperior: %s""" % (str(from_dn), str(to_rdn), str(to_base)))
             keystr = guidstr + dsdb_dn.prefix
             if keystr not in unique_dict:
                 unique_dict[keystr] = dsdb_dn
-                unique_list.append(keystr)
                 continue
             error_count += 1
             if keystr not in duplicate_dict:
                 duplicate_dict[keystr] = dict()
                 duplicate_dict[keystr]["keep"] = None
                 duplicate_dict[keystr]["delete"] = list()
-                duplicate_list.append(keystr)
 
             # Now check for the highest RMD_VERSION
             v1 = int(unique_dict[keystr].dn.get_extended_component("RMD_VERSION"))
@@ -961,25 +977,197 @@ newSuperior: %s""" % (str(from_dn), str(to_rdn), str(to_base)))
             duplicate_dict[keystr]["delete"].append(unique_dict[keystr])
             unique_dict[keystr] = dsdb_dn
 
-        if len(duplicate_list) != 0:
-            duplicate_links = True
-            self.report("ERROR: FATAL! Most likely some forward link values for attribute '%s' in '%s' got lost!" % (attrname, obj.dn))
-            self.report("ERROR: FATAL! See https://bugzilla.samba.org/show_bug.cgi?id=13228")
+        if error_count != 0:
+            self.duplicate_link_cache[duplicate_cache_key] = True
+
+        return (error_count, duplicate_dict, unique_dict)
+
+    def has_duplicate_links(self, dn, forward_attr, forward_syntax):
+        '''check a linked values for duplicate forward links'''
+        error_count = 0
+
+        duplicate_cache_key = "%s:%s" % (str(dn), forward_attr)
+        if duplicate_cache_key in self.duplicate_link_cache:
+            return self.duplicate_link_cache[duplicate_cache_key]
+
+        forward_linkID, backlink_attr = self.get_attr_linkID_and_reverse_name(forward_attr)
+
+        attrs = [forward_attr]
+        controls = ["extended_dn:1:1", "reveal_internals:0"]
+
+        # check its the right GUID
+        try:
+            res = self.samdb.search(base=str(dn), scope=ldb.SCOPE_BASE,
+                                    attrs=attrs, controls=controls)
+        except ldb.LdbError, (enum, estr):
+            if enum != ldb.ERR_NO_SUCH_OBJECT:
+                raise
+
+            return False
+
+        obj = res[0]
+        error_count, duplicate_dict, unique_dict = \
+            self.check_duplicate_links(obj, forward_attr, forward_syntax, forward_linkID, backlink_attr)
+
+        if duplicate_cache_key in self.duplicate_link_cache:
+            return self.duplicate_link_cache[duplicate_cache_key]
+
+        return False
+
+    def find_missing_forward_links_from_backlinks(self, obj,
+                                                  forward_attr,
+                                                  forward_syntax,
+                                                  backlink_attr,
+                                                  forward_unique_dict):
+        '''Find all backlinks linking to obj_guid_str not already in forward_unique_dict'''
+        missing_forward_links = []
+        error_count = 0
 
-            self.report("ERROR: Duplicate link values for attribute '%s' in '%s'" % (attrname, obj.dn))
-            for keystr in duplicate_list:
+        if backlink_attr is None:
+            return (missing_forward_links, error_count)
+
+        if forward_syntax != ldb.SYNTAX_DN:
+            self.report("Not checking for missing forward links for syntax: %s",
+                        forward_syntax)
+            return (missing_forward_links, error_count)
+
+        if "sortedLinks" in self.compatibleFeatures:
+            self.report("Not checking for missing forward links because the db " + \
+                        "has the sortedLinks feature")
+            return (missing_forward_links, error_count)
+
+        try:
+            obj_guid = obj['objectGUID'][0]
+            obj_guid_str = str(ndr_unpack(misc.GUID, obj_guid))
+            filter = "(%s=<GUID=%s>)" % (backlink_attr, obj_guid_str)
+
+            res = self.samdb.search(expression=filter,
+                                    scope=ldb.SCOPE_SUBTREE, attrs=["objectGUID"],
+                                    controls=["extended_dn:1:1",
+                                              "search_options:1:2",
+                                              "paged_results:1:1000"])
+        except ldb.LdbError, (enum, estr):
+            raise
+
+        for r in res:
+            target_dn = dsdb_Dn(self.samdb, r.dn.extended_str(), forward_syntax)
+
+            guid = target_dn.dn.get_extended_component("GUID")
+            guidstr = str(misc.GUID(guid))
+            if guidstr in forward_unique_dict:
+                continue
+
+            # A valid forward link looks like this:
+            #
+            #    <GUID=9f92d30a-fc23-11e4-a5f6-30be15454808>;
+            #    <RMD_ADDTIME=131607546230000000>;
+            #    <RMD_CHANGETIME=131607546230000000>;
+            #    <RMD_FLAGS=0>;
+            #    <RMD_INVOCID=4e4496a3-7fb8-4f97-8a33-d238db8b5e2d>;
+            #    <RMD_LOCAL_USN=3765>;
+            #    <RMD_ORIGINATING_USN=3765>;
+            #    <RMD_VERSION=1>;
+            #    <SID=S-1-5-21-4177067393-1453636373-93818738-1124>;
+            #    CN=unsorted-u8,CN=Users,DC=release-4-5-0-pre1,DC=samba,DC=corp
+            #
+            # Note that versions older than Samba 4.8 create
+            # links with RMD_VERSION=0.
+            #
+            # Try to get the local_usn and time from objectClass
+            # if possible and fallback to any other one.
+            repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
+                              obj['replPropertyMetadata'][0])
+            for o in repl.ctr.array:
+                local_usn = o.local_usn
+                t = o.originating_change_time
+                if o.attid == drsuapi.DRSUAPI_ATTID_objectClass:
+                    break
+
+            # We use a magic invocationID for restoring missing
+            # forward links to recover from bug #13228.
+            # This should allow some more future magic to fix the
+            # problem.
+            #
+            # It also means it looses the conflict resolution
+            # against almost every real invocation, if the
+            # version is also 0.
+            originating_invocid = misc.GUID("ffffffff-4700-4700-4700-000000b13228")
+            originating_usn = 1
+
+            rmd_addtime = t
+            rmd_changetime = t
+            rmd_flags = 0
+            rmd_invocid = originating_invocid
+            rmd_originating_usn = originating_usn
+            rmd_local_usn = local_usn
+            rmd_version = 0
+
+            target_dn.dn.set_extended_component("RMD_ADDTIME", str(rmd_addtime))
+            target_dn.dn.set_extended_component("RMD_CHANGETIME", str(rmd_changetime))
+            target_dn.dn.set_extended_component("RMD_FLAGS", str(rmd_flags))
+            target_dn.dn.set_extended_component("RMD_INVOCID", ndr_pack(rmd_invocid))
+            target_dn.dn.set_extended_component("RMD_ORIGINATING_USN", str(rmd_originating_usn))
+            target_dn.dn.set_extended_component("RMD_LOCAL_USN", str(rmd_local_usn))
+            target_dn.dn.set_extended_component("RMD_VERSION", str(rmd_version))
+
+            error_count += 1
+            missing_forward_links.append(target_dn)
+
+        return (missing_forward_links, error_count)
+
+    def check_dn(self, obj, attrname, syntax_oid):
+        '''check a DN attribute for correctness'''
+        error_count = 0
+        obj_guid = obj['objectGUID'][0]
+
+        linkID, reverse_link_name = self.get_attr_linkID_and_reverse_name(attrname)
+        if reverse_link_name is not None:
+            reverse_syntax_oid = self.samdb_schema.get_syntax_oid_from_lDAPDisplayName(reverse_link_name)
+        else:
+            reverse_syntax_oid = None
+
+        error_count, duplicate_dict, unique_dict = \
+            self.check_duplicate_links(obj, attrname, syntax_oid, linkID, reverse_link_name)
+
+        if len(duplicate_dict) != 0:
+
+            missing_forward_links, missing_error_count = \
+                self.find_missing_forward_links_from_backlinks(obj,
+                                                         attrname, syntax_oid,
+                                                         reverse_link_name,
+                                                         unique_dict)
+            error_count += missing_error_count
+
+            forward_links = [dn for dn in unique_dict.values()]
+
+            if missing_error_count != 0:
+                self.report("ERROR: Missing and duplicate forward link values for attribute '%s' in '%s'" % (
+                            attrname, obj.dn))
+            else:
+                self.report("ERROR: Duplicate forward link values for attribute '%s' in '%s'" % (attrname, obj.dn))
+            for m in missing_forward_links:
+                self.report("Missing   link '%s'" % (m))
+                if not self.confirm_all("Schedule readding missing forward link for attribute %s" % attrname,
+                                        'fix_all_missing_forward_links'):
+                    self.err_orphaned_backlink(m.dn, reverse_link_name,
+                                               obj.dn.extended_str(), obj.dn,
+                                               attrname, syntax_oid,
+                                               check_duplicates=False)
+                    continue
+                forward_links += [m]
+            for keystr in duplicate_dict.keys():
                 d = duplicate_dict[keystr]
                 for dd in d["delete"]:
                     self.report("Duplicate link '%s'" % dd)
                 self.report("Correct   link '%s'" % d["keep"])
 
-            vals = []
-            for keystr in unique_list:
-                dsdb_dn = unique_dict[keystr]
-                vals.append(str(dsdb_dn))
-            self.err_duplicate_links(obj, attrname, vals)
+            # We now construct the sorted dn values.
+            # They're sorted by the objectGUID of the target
+            # See dsdb_Dn.__cmp__()
+            vals = [str(dn) for dn in sorted(forward_links)]
+            self.err_recover_forward_links(obj, attrname, vals)
             # We should continue with the fixed values
-            obj[attrname] = ldb.MessageElement(vals, ldb.FLAG_MOD_REPLACE, attrname)
+            obj[attrname] = ldb.MessageElement(vals, 0, attrname)
 
         for val in obj[attrname]:
             dsdb_dn = dsdb_Dn(self.samdb, val, syntax_oid)
@@ -1162,9 +1350,10 @@ newSuperior: %s""" % (str(from_dn), str(to_rdn), str(to_base)))
                 # UNLESS, there is no forward link detected.
                 if match_count == 0:
                     error_count += 1
-                    self.err_orphaned_backlink(obj, attrname,
-                                               val, reverse_link_name,
-                                               dsdb_dn.dn, duplicate_links)
+                    self.err_orphaned_backlink(obj.dn, attrname,
+                                               val, dsdb_dn.dn,
+                                               reverse_link_name,
+                                               reverse_syntax_oid)
                     continue
                 # Only warn here and let the forward link logic fix it.
                 self.report("WARNING: Link (back) mismatch for '%s' (%d) on '%s' to '%s' (%d) on '%s'" % (
@@ -1194,9 +1383,9 @@ newSuperior: %s""" % (str(from_dn), str(to_rdn), str(to_base)))
                                               dsdb_dn.dn)
                     diff_count -= 1
                 else:
-                    self.err_orphaned_backlink(res[0], reverse_link_name,
-                                               obj.dn.extended_str(), attrname,
-                                               obj.dn, duplicate_links)
+                    self.err_orphaned_backlink(res[0].dn, reverse_link_name,
+                                               obj.dn.extended_str(), obj.dn,
+                                               attrname, syntax_oid)
                     diff_count += 1
 
 
@@ -1774,10 +1963,21 @@ newSuperior: %s""" % (str(from_dn), str(to_rdn), str(to_base)))
             attrs.append(dn.get_rdn_name())
             attrs.append("isDeleted")
             attrs.append("systemFlags")
+        need_replPropertyMetaData = False
         if '*' in attrs:
-            attrs.append("replPropertyMetaData")
+            need_replPropertyMetaData = True
         else:
-            attrs.append("objectGUID")
+            for a in attrs:
+                linkID, _ = self.get_attr_linkID_and_reverse_name(a)
+                if linkID == 0:
+                    continue
+                if linkID & 1:
+                    continue
+                need_replPropertyMetaData = True
+                break
+        if need_replPropertyMetaData:
+            attrs.append("replPropertyMetaData")
+        attrs.append("objectGUID")
 
         try:
             sd_flags = 0
diff --git a/python/samba/netcmd/__init__.py b/python/samba/netcmd/__init__.py
index 05ecc43..fcad7f6 100644
--- a/python/samba/netcmd/__init__.py
+++ b/python/samba/netcmd/__init__.py
@@ -243,3 +243,6 @@ class CommandError(Exception):
         self.message = message
         self.inner_exception = inner_exception
         self.exception_info = sys.exc_info()
+
+    def __repr__(self):
+        return "CommandError(%s)" % self.message
diff --git a/python/samba/tests/common.py b/python/samba/tests/common.py
index 8794e9d..49ae2b0 100644
--- a/python/samba/tests/common.py
+++ b/python/samba/tests/common.py
@@ -23,7 +23,7 @@ from samba.common import *
 from samba.samdb import SamDB
 
 
-class CommonTests(samba.tests.TestCase):
+class CommonTests(samba.tests.TestCaseInTempDir):
 
     def test_normalise_int32(self):
         self.assertEquals('17', normalise_int32(17))
@@ -31,10 +31,35 @@ class CommonTests(samba.tests.TestCase):
         self.assertEquals('-123', normalise_int32('-123'))
         self.assertEquals('-1294967296', normalise_int32('3000000000'))
 
-    def test_dsdb_Dn(self):
-        sam = samba.Ldb(url='dntest.ldb')
+    def test_dsdb_Dn_binary(self):
+        url = self.tempdir + "/test_dsdb_Dn_binary.ldb"
+        sam = samba.Ldb(url=url)
         dn1 = dsdb_Dn(sam, "DC=foo,DC=bar")


-- 
Samba Shared Repository



More information about the samba-cvs mailing list