Create the dbm package from PEP 3108. #2881.
diff --git a/Lib/anydbm.py b/Lib/anydbm.py
deleted file mode 100644
index 41335a3..0000000
--- a/Lib/anydbm.py
+++ /dev/null
@@ -1,83 +0,0 @@
-"""Generic interface to all dbm clones.
-
-Instead of
-
-        import dbm
-        d = dbm.open(file, 'w', 0o666)
-
-use
-
-        import anydbm
-        d = anydbm.open(file, 'w')
-
-The returned object is a dbhash, gdbm, dbm or dumbdbm object,
-dependent on the type of database being opened (determined by whichdb
-module) in the case of an existing dbm. If the dbm does not exist and
-the create or new flag ('c' or 'n') was specified, the dbm type will
-be determined by the availability of the modules (tested in the above
-order).
-
-It has the following interface (key and data are strings):
-
-        d[key] = data   # store data at key (may override data at
-                        # existing key)
-        data = d[key]   # retrieve data at key (raise KeyError if no
-                        # such key)
-        del d[key]      # delete data stored at key (raises KeyError
-                        # if no such key)
-        flag = key in d   # true if the key exists
-        list = d.keys() # return a list of all existing keys (slow!)
-
-Future versions may change the order in which implementations are
-tested for existence, add interfaces to other dbm-like
-implementations.
-
-The open function has an optional second argument.  This can be 'r',
-for read-only access, 'w', for read-write access of an existing
-database, 'c' for read-write access to a new or existing database, and
-'n' for read-write access to a new database.  The default is 'r'.
-
-Note: 'r' and 'w' fail if the database doesn't exist; 'c' creates it
-only if it doesn't exist; and 'n' always creates a new database.
-
-"""
-
-class error(Exception):
-    pass
-
-_names = ['dbhash', 'gdbm', 'dbm', 'dumbdbm']
-_errors = [error]
-_defaultmod = None
-
-for _name in _names:
-    try:
-        _mod = __import__(_name)
-    except ImportError:
-        continue
-    if not _defaultmod:
-        _defaultmod = _mod
-    _errors.append(_mod.error)
-
-if not _defaultmod:
-    raise ImportError("no dbm clone found; tried %s" % _names)
-
-error = tuple(_errors)
-
-def open(file, flag = 'r', mode = 0o666):
-    # guess the type of an existing database
-    from whichdb import whichdb
-    result=whichdb(file)
-    if result is None:
-        # db doesn't exist
-        if 'c' in flag or 'n' in flag:
-            # file doesn't exist and the new
-            # flag was used so use default type
-            mod = _defaultmod
-        else:
-            raise error("need 'c' or 'n' flag to open new db")
-    elif result == "":
-        # db type cannot be determined
-        raise error("db type could not be determined")
-    else:
-        mod = __import__(result)
-    return mod.open(file, flag, mode)
diff --git a/Lib/dbhash.py b/Lib/dbhash.py
deleted file mode 100644
index 3c60812..0000000
--- a/Lib/dbhash.py
+++ /dev/null
@@ -1,16 +0,0 @@
-"""Provide a (g)dbm-compatible interface to bsddb.hashopen."""
-
-import sys
-try:
-    import bsddb
-except ImportError:
-    # prevent a second import of this module from spuriously succeeding
-    del sys.modules[__name__]
-    raise
-
-__all__ = ["error","open"]
-
-error = bsddb.error                     # Exported for anydbm
-
-def open(file, flag = 'r', mode=0o666):
-    return bsddb.hashopen(file, flag, mode)
diff --git a/Lib/dbm/__init__.py b/Lib/dbm/__init__.py
new file mode 100644
index 0000000..9fdd414
--- /dev/null
+++ b/Lib/dbm/__init__.py
@@ -0,0 +1,198 @@
+"""Generic interface to all dbm clones.
+
+Use
+
+        import dbm
+        d = dbm.open(file, 'w', 0o666)
+
+The returned object is a dbm.bsd, dbm.gnu, dbm.ndbm or dbm.dumb
+object, dependent on the type of database being opened (determined by
+the whichdb function) in the case of an existing dbm.  If the dbm does
+not exist and the create or new flag ('c' or 'n') was specified, the
+dbm type will be determined by the availability of the modules (tested
+in the above order).
+
+It has the following interface (key and data are strings):
+
+        d[key] = data   # store data at key (may override data at
+                        # existing key)
+        data = d[key]   # retrieve data at key (raise KeyError if no
+                        # such key)
+        del d[key]      # delete data stored at key (raises KeyError
+                        # if no such key)
+        flag = key in d # true if the key exists
+        list = d.keys() # return a list of all existing keys (slow!)
+
+Future versions may change the order in which implementations are
+tested for existence, add interfaces to other dbm-like
+implementations.
+
+The open function has an optional second argument.  This can be 'r',
+for read-only access, 'w', for read-write access of an existing
+database, 'c' for read-write access to a new or existing database, and
+'n' for read-write access to a new database.  The default is 'r'.
+
+Note: 'r' and 'w' fail if the database doesn't exist; 'c' creates it
+only if it doesn't exist; and 'n' always creates a new database.
+"""
+
+__all__ = ['open', 'whichdb', 'error', 'errors']
+
+import io
+import os
+import struct
+import sys
+
+
+class error(Exception):
+    pass
+
+_names = ['dbm.bsd', 'dbm.gnu', 'dbm.ndbm', 'dbm.dumb']
+_errors = [error]
+_defaultmod = None
+_modules = {}
+
+for _name in _names:
+    try:
+        _mod = __import__(_name, fromlist=['open'])
+    except ImportError:
+        continue
+    if not _defaultmod:
+        _defaultmod = _mod
+    _modules[_name] = _mod
+    _errors.append(_mod.error)
+
+if not _defaultmod:
+    raise ImportError("no dbm clone found; tried %s" % _names)
+
+error = tuple(_errors)
+
+
+def open(file, flag = 'r', mode = 0o666):
+    # guess the type of an existing database
+    result = whichdb(file)
+    if result is None:
+        # db doesn't exist
+        if 'c' in flag or 'n' in flag:
+            # file doesn't exist and the new flag was used so use default type
+            mod = _defaultmod
+        else:
+            raise error("need 'c' or 'n' flag to open new db")
+    elif result == "":
+        # db type cannot be determined
+        raise error("db type could not be determined")
+    else:
+        mod = _modules[result]
+    return mod.open(file, flag, mode)
+
+
+try:
+    from dbm import ndbm
+    _dbmerror = ndbm.error
+except ImportError:
+    ndbm = None
+    # just some sort of valid exception which might be raised in the ndbm test
+    _dbmerror = IOError
+
+def whichdb(filename):
+    """Guess which db package to use to open a db file.
+
+    Return values:
+
+    - None if the database file can't be read;
+    - empty string if the file can be read but can't be recognized
+    - the name of the dbm submodule (e.g. "ndbm" or "gnu") if recognized.
+
+    Importing the given module may still fail, and opening the
+    database using that module may still fail.
+    """
+
+    # Check for ndbm first -- this has a .pag and a .dir file
+    try:
+        f = io.open(filename + ".pag", "rb")
+        f.close()
+        # dbm linked with gdbm on OS/2 doesn't have .dir file
+        if not (ndbm.library == "GNU gdbm" and sys.platform == "os2emx"):
+            f = io.open(filename + ".dir", "rb")
+            f.close()
+        return "dbm.ndbm"
+    except IOError:
+        # some dbm emulations based on Berkeley DB generate a .db file
+        # some do not, but they should be caught by the bsd checks
+        try:
+            f = io.open(filename + ".db", "rb")
+            f.close()
+            # guarantee we can actually open the file using dbm
+            # kind of overkill, but since we are dealing with emulations
+            # it seems like a prudent step
+            if ndbm is not None:
+                d = ndbm.open(filename)
+                d.close()
+                return "dbm.ndbm"
+        except (IOError, _dbmerror):
+            pass
+
+    # Check for dumbdbm next -- this has a .dir and a .dat file
+    try:
+        # First check for presence of files
+        os.stat(filename + ".dat")
+        size = os.stat(filename + ".dir").st_size
+        # dumbdbm files with no keys are empty
+        if size == 0:
+            return "dbm.dumb"
+        f = io.open(filename + ".dir", "rb")
+        try:
+            if f.read(1) in (b"'", b'"'):
+                return "dbm.dumb"
+        finally:
+            f.close()
+    except (OSError, IOError):
+        pass
+
+    # See if the file exists, return None if not
+    try:
+        f = io.open(filename, "rb")
+    except IOError:
+        return None
+
+    # Read the start of the file -- the magic number
+    s16 = f.read(16)
+    f.close()
+    s = s16[0:4]
+
+    # Return "" if not at least 4 bytes
+    if len(s) != 4:
+        return ""
+
+    # Convert to 4-byte int in native byte order -- return "" if impossible
+    try:
+        (magic,) = struct.unpack("=l", s)
+    except struct.error:
+        return ""
+
+    # Check for GNU dbm
+    if magic == 0x13579ace:
+        return "dbm.gnu"
+
+    ## Check for old Berkeley db hash file format v2
+    #if magic in (0x00061561, 0x61150600):
+    #    return "bsddb185" # not supported anymore
+
+    # Later versions of Berkeley db hash file have a 12-byte pad in
+    # front of the file type
+    try:
+        (magic,) = struct.unpack("=l", s16[-4:])
+    except struct.error:
+        return ""
+
+    # Check for BSD hash
+    if magic in (0x00061561, 0x61150600):
+        return "dbm.bsd"
+
+    # Unknown
+    return ""
+
+
+if __name__ == "__main__":
+    for filename in sys.argv[1:]:
+        print(whichdb(filename) or "UNKNOWN", filename)
diff --git a/Lib/dbm/bsd.py b/Lib/dbm/bsd.py
new file mode 100644
index 0000000..8353f50
--- /dev/null
+++ b/Lib/dbm/bsd.py
@@ -0,0 +1,10 @@
+"""Provide a (g)dbm-compatible interface to bsddb.hashopen."""
+
+import bsddb
+
+__all__ = ["error", "open"]
+
+error = bsddb.error
+
+def open(file, flag = 'r', mode=0o666):
+    return bsddb.hashopen(file, flag, mode)
diff --git a/Lib/dumbdbm.py b/Lib/dbm/dumb.py
similarity index 97%
rename from Lib/dumbdbm.py
rename to Lib/dbm/dumb.py
index 8d58f87..76f4a63 100644
--- a/Lib/dumbdbm.py
+++ b/Lib/dbm/dumb.py
@@ -25,9 +25,11 @@
 import os as _os
 import collections
 
+__all__ = ["error", "open"]
+
 _BLOCKSIZE = 512
 
-error = IOError                         # For anydbm
+error = IOError
 
 class _Database(collections.MutableMapping):
 
@@ -231,7 +233,7 @@
     """Open the database file, filename, and return corresponding object.
 
     The flag argument, used to control how the database is opened in the
-    other DBM implementations, is ignored in the dumbdbm module; the
+    other DBM implementations, is ignored in the dbm.dumb module; the
     database is always opened for update, and will be created if it does
     not exist.
 
diff --git a/Lib/dbm/gnu.py b/Lib/dbm/gnu.py
new file mode 100644
index 0000000..b07a1de
--- /dev/null
+++ b/Lib/dbm/gnu.py
@@ -0,0 +1,3 @@
+"""Provide the _gdbm module as a dbm submodule."""
+
+from _gdbm import *
diff --git a/Lib/dbm/ndbm.py b/Lib/dbm/ndbm.py
new file mode 100644
index 0000000..23056a2
--- /dev/null
+++ b/Lib/dbm/ndbm.py
@@ -0,0 +1,3 @@
+"""Provide the _dbm module as a dbm submodule."""
+
+from _dbm import *
diff --git a/Lib/io.py b/Lib/io.py
index 207e428..0c993b1 100644
--- a/Lib/io.py
+++ b/Lib/io.py
@@ -270,7 +270,7 @@
     """Wrapper for builtins.open
 
     Trick so that open won't become a bound method when stored
-    as a class variable (as dumbdbm does).
+    as a class variable (as dbm.dumb does).
 
     See initstdio() in Python/pythonrun.c.
     """
diff --git a/Lib/shelve.py b/Lib/shelve.py
index e6d6d40..d651b9e 100644
--- a/Lib/shelve.py
+++ b/Lib/shelve.py
@@ -190,15 +190,15 @@
 
 
 class DbfilenameShelf(Shelf):
-    """Shelf implementation using the "anydbm" generic dbm interface.
+    """Shelf implementation using the "dbm" generic dbm interface.
 
     This is initialized with the filename for the dbm database.
     See the module's __doc__ string for an overview of the interface.
     """
 
     def __init__(self, filename, flag='c', protocol=None, writeback=False):
-        import anydbm
-        Shelf.__init__(self, anydbm.open(filename, flag), protocol, writeback)
+        import dbm
+        Shelf.__init__(self, dbm.open(filename, flag), protocol, writeback)
 
 
 def open(filename, flag='c', protocol=None, writeback=False):
@@ -208,7 +208,7 @@
     database.  As a side-effect, an extension may be added to the
     filename and more than one file may be created.  The optional flag
     parameter has the same interpretation as the flag parameter of
-    anydbm.open(). The optional protocol parameter specifies the
+    dbm.open(). The optional protocol parameter specifies the
     version of the pickle protocol (0, 1, or 2).
 
     See the module's __doc__ string for an overview of the interface.
diff --git a/Lib/test/test___all__.py b/Lib/test/test___all__.py
index 615f6c5..ed8d8d9 100644
--- a/Lib/test/test___all__.py
+++ b/Lib/test/test___all__.py
@@ -57,7 +57,7 @@
         self.check_all("copy")
         self.check_all("copyreg")
         self.check_all("csv")
-        self.check_all("dbhash")
+        self.check_all("dbm.bsd")
         self.check_all("decimal")
         self.check_all("difflib")
         self.check_all("dircache")
diff --git a/Lib/test/test_anydbm.py b/Lib/test/test_anydbm.py
index ace9dd2..aab1388 100644
--- a/Lib/test/test_anydbm.py
+++ b/Lib/test/test_anydbm.py
@@ -1,50 +1,34 @@
 #! /usr/bin/env python
-"""Test script for the anydbm module
-   based on testdumbdbm.py
-"""
+"""Test script for the dbm.open function based on testdumbdbm.py"""
 
 import os
 import unittest
-import anydbm
+import dbm
 import glob
-from test import support
+import test.support
 
-_fname = support.TESTFN
-
-_all_modules = []
-
-for _name in anydbm._names:
-    try:
-        _module = __import__(_name)
-    except ImportError:
-        continue
-    _all_modules.append(_module)
-
+_fname = test.support.TESTFN
 
 #
-# Iterates over every database module supported by anydbm
-# currently available, setting anydbm to use each in turn,
-# and yielding that module
+# Iterates over every database module supported by dbm currently available,
+# setting dbm to use each in turn, and yielding that module
 #
 def dbm_iterator():
-    old_default = anydbm._defaultmod
-    for module in _all_modules:
-        anydbm._defaultmod = module
+    old_default = dbm._defaultmod
+    for module in dbm._modules.values():
+        dbm._defaultmod = module
         yield module
-    anydbm._defaultmod = old_default
+    dbm._defaultmod = old_default
 
 #
-# Clean up all scratch databases we might have created
-# during testing
+# Clean up all scratch databases we might have created during testing
 #
 def delete_files():
     # we don't know the precise name the underlying database uses
     # so we use glob to locate all names
     for f in glob.glob(_fname + "*"):
-        try:
-            os.unlink(f)
-        except OSError:
-            pass
+        test.support.unlink(f)
+
 
 class AnyDBMTestCase(unittest.TestCase):
     _dict = {'0': b'',
@@ -60,7 +44,7 @@
         unittest.TestCase.__init__(self, *args)
 
     def test_anydbm_creation(self):
-        f = anydbm.open(_fname, 'c')
+        f = dbm.open(_fname, 'c')
         self.assertEqual(list(f.keys()), [])
         for key in self._dict:
             f[key.encode("ascii")] = self._dict[key]
@@ -69,26 +53,26 @@
 
     def test_anydbm_modification(self):
         self.init_db()
-        f = anydbm.open(_fname, 'c')
+        f = dbm.open(_fname, 'c')
         self._dict['g'] = f[b'g'] = b"indented"
         self.read_helper(f)
         f.close()
 
     def test_anydbm_read(self):
         self.init_db()
-        f = anydbm.open(_fname, 'r')
+        f = dbm.open(_fname, 'r')
         self.read_helper(f)
         f.close()
 
     def test_anydbm_keys(self):
         self.init_db()
-        f = anydbm.open(_fname, 'r')
+        f = dbm.open(_fname, 'r')
         keys = self.keys_helper(f)
         f.close()
 
     def test_anydbm_access(self):
         self.init_db()
-        f = anydbm.open(_fname, 'r')
+        f = dbm.open(_fname, 'r')
         key = "a".encode("ascii")
         assert(key in f)
         assert(f[key] == b"Python:")
@@ -100,7 +84,7 @@
             self.assertEqual(self._dict[key], f[key.encode("ascii")])
 
     def init_db(self):
-        f = anydbm.open(_fname, 'n')
+        f = dbm.open(_fname, 'n')
         for k in self._dict:
             f[k.encode("ascii")] = self._dict[k]
         f.close()
@@ -118,10 +102,44 @@
         delete_files()
 
 
+class WhichDBTestCase(unittest.TestCase):
+    # Actual test methods are added to namespace after class definition.
+    def __init__(self, *args):
+        unittest.TestCase.__init__(self, *args)
+
+    def test_whichdb(self):
+        for module in dbm_iterator():
+            # Check whether whichdb correctly guesses module name
+            # for databases opened with "module" module.
+            # Try with empty files first
+            name = module.__name__
+            if name == 'dbm.dumb':
+                continue   # whichdb can't support dbm.dumb
+            test.support.unlink(_fname)
+            f = module.open(_fname, 'c')
+            f.close()
+            self.assertEqual(name, dbm.whichdb(_fname))
+            # Now add a key
+            f = module.open(_fname, 'w')
+            f[b"1"] = b"1"
+            # and test that we can find it
+            self.assertTrue(b"1" in f)
+            # and read it
+            self.assertTrue(f[b"1"] == b"1")
+            f.close()
+            self.assertEqual(name, dbm.whichdb(_fname))
+
+    def tearDown(self):
+        delete_files()
+
+    def setUp(self):
+        delete_files()
+
+
 def test_main():
     try:
         for module in dbm_iterator():
-            support.run_unittest(AnyDBMTestCase)
+            test.support.run_unittest(AnyDBMTestCase, WhichDBTestCase)
     finally:
         delete_files()
 
diff --git a/Lib/test/test_bsddb.py b/Lib/test/test_bsddb.py
index 3eb291f..a722d8c 100755
--- a/Lib/test/test_bsddb.py
+++ b/Lib/test/test_bsddb.py
@@ -5,7 +5,7 @@
 import os, sys
 import copy
 import bsddb
-import dbhash # Just so we know it's imported
+import dbm.bsd # Just so we know it's imported
 import unittest
 from test import support
 
diff --git a/Lib/test/test_dumbdbm.py b/Lib/test/test_dbm_dumb.py
similarity index 99%
rename from Lib/test/test_dumbdbm.py
rename to Lib/test/test_dbm_dumb.py
index 24c178f..9bdc240 100644
--- a/Lib/test/test_dumbdbm.py
+++ b/Lib/test/test_dbm_dumb.py
@@ -6,7 +6,7 @@
 import io
 import os
 import unittest
-import dumbdbm
+import dbm.dumb as dumbdbm
 from test import support
 
 _fname = support.TESTFN
diff --git a/Lib/test/test_gdbm.py b/Lib/test/test_dbm_gnu.py
similarity index 98%
rename from Lib/test/test_gdbm.py
rename to Lib/test/test_dbm_gnu.py
index 42cb136..eddb970 100755
--- a/Lib/test/test_gdbm.py
+++ b/Lib/test/test_dbm_gnu.py
@@ -1,4 +1,4 @@
-import gdbm
+import dbm.gnu as gdbm
 import unittest
 import os
 from test.support import verbose, TESTFN, run_unittest, unlink
diff --git a/Lib/test/test_dbm.py b/Lib/test/test_dbm_ndbm.py
similarity index 77%
rename from Lib/test/test_dbm.py
rename to Lib/test/test_dbm_ndbm.py
index 2c6ce99..74d3238 100755
--- a/Lib/test/test_dbm.py
+++ b/Lib/test/test_dbm_ndbm.py
@@ -2,14 +2,14 @@
 import unittest
 import os
 import random
-import dbm
-from dbm import error
+import dbm.ndbm
+from dbm.ndbm import error
 
 class DbmTestCase(unittest.TestCase):
 
     def setUp(self):
         self.filename = support.TESTFN
-        self.d = dbm.open(self.filename, 'c')
+        self.d = dbm.ndbm.open(self.filename, 'c')
         self.d.close()
 
     def tearDown(self):
@@ -17,7 +17,7 @@
             support.unlink(self.filename + suffix)
 
     def test_keys(self):
-        self.d = dbm.open(self.filename, 'c')
+        self.d = dbm.ndbm.open(self.filename, 'c')
         self.assert_(self.d.keys() == [])
         self.d['a'] = 'b'
         self.d['12345678910'] = '019237410982340912840198242'
@@ -28,9 +28,9 @@
     def test_modes(self):
         for mode in ['r', 'rw', 'w', 'n']:
             try:
-                self.d = dbm.open(self.filename, mode)
+                self.d = dbm.ndbm.open(self.filename, mode)
                 self.d.close()
-            except dbm.error:
+            except error:
                 self.fail()
 
 def test_main():
diff --git a/Lib/test/test_whichdb.py b/Lib/test/test_whichdb.py
deleted file mode 100644
index d908ac5..0000000
--- a/Lib/test/test_whichdb.py
+++ /dev/null
@@ -1,58 +0,0 @@
-#! /usr/bin/env python
-"""Test script for the whichdb module
-   based on test_anydbm.py
-"""
-
-import os
-import test.support
-import unittest
-import whichdb
-import anydbm
-import glob
-from test.test_anydbm import delete_files, dbm_iterator
-
-_fname = test.support.TESTFN
-
-class WhichDBTestCase(unittest.TestCase):
-    # Actual test methods are added to namespace
-    # after class definition.
-    def __init__(self, *args):
-        unittest.TestCase.__init__(self, *args)
-
-    def test_whichdb(self):
-        for module in dbm_iterator():
-            # Check whether whichdb correctly guesses module name
-            # for databases opened with "module" module.
-            # Try with empty files first
-            name = module.__name__
-            if name == 'dumbdbm':
-                continue   # whichdb can't support dumbdbm
-            test.support.unlink(_fname)
-            f = module.open(_fname, 'c')
-            f.close()
-            self.assertEqual(name, whichdb.whichdb(_fname))
-            # Now add a key
-            f = module.open(_fname, 'w')
-            f[b"1"] = b"1"
-            # and test that we can find it
-            self.assertTrue(b"1" in f)
-            # and read it
-            self.assertTrue(f[b"1"] == b"1")
-            f.close()
-            self.assertEqual(name, whichdb.whichdb(_fname))
-
-    def tearDown(self):
-        delete_files()
-
-    def setUp(self):
-        delete_files()
-
-
-def test_main():
-    try:
-        test.support.run_unittest(WhichDBTestCase)
-    finally:
-        delete_files()
-
-if __name__ == "__main__":
-    test_main()
diff --git a/Lib/whichdb.py b/Lib/whichdb.py
deleted file mode 100644
index ca9c736..0000000
--- a/Lib/whichdb.py
+++ /dev/null
@@ -1,118 +0,0 @@
-# !/usr/bin/env python
-"""Guess which db package to use to open a db file."""
-
-import io
-import os
-import struct
-import sys
-
-try:
-    import dbm
-    _dbmerror = dbm.error
-except ImportError:
-    dbm = None
-    # just some sort of valid exception which might be raised in the
-    # dbm test
-    _dbmerror = IOError
-
-def whichdb(filename):
-    """Guess which db package to use to open a db file.
-
-    Return values:
-
-    - None if the database file can't be read;
-    - empty string if the file can be read but can't be recognized
-    - the module name (e.g. "dbm" or "gdbm") if recognized.
-
-    Importing the given module may still fail, and opening the
-    database using that module may still fail.
-    """
-
-    # Check for dbm first -- this has a .pag and a .dir file
-    try:
-        f = io.open(filename + ".pag", "rb")
-        f.close()
-        # dbm linked with gdbm on OS/2 doesn't have .dir file
-        if not (dbm.library == "GNU gdbm" and sys.platform == "os2emx"):
-            f = io.open(filename + ".dir", "rb")
-            f.close()
-        return "dbm"
-    except IOError:
-        # some dbm emulations based on Berkeley DB generate a .db file
-        # some do not, but they should be caught by the dbhash checks
-        try:
-            f = io.open(filename + ".db", "rb")
-            f.close()
-            # guarantee we can actually open the file using dbm
-            # kind of overkill, but since we are dealing with emulations
-            # it seems like a prudent step
-            if dbm is not None:
-                d = dbm.open(filename)
-                d.close()
-                return "dbm"
-        except (IOError, _dbmerror):
-            pass
-
-    # Check for dumbdbm next -- this has a .dir and a .dat file
-    try:
-        # First check for presence of files
-        os.stat(filename + ".dat")
-        size = os.stat(filename + ".dir").st_size
-        # dumbdbm files with no keys are empty
-        if size == 0:
-            return "dumbdbm"
-        f = io.open(filename + ".dir", "rb")
-        try:
-            if f.read(1) in (b"'", b'"'):
-                return "dumbdbm"
-        finally:
-            f.close()
-    except (OSError, IOError):
-        pass
-
-    # See if the file exists, return None if not
-    try:
-        f = io.open(filename, "rb")
-    except IOError:
-        return None
-
-    # Read the start of the file -- the magic number
-    s16 = f.read(16)
-    f.close()
-    s = s16[0:4]
-
-    # Return "" if not at least 4 bytes
-    if len(s) != 4:
-        return ""
-
-    # Convert to 4-byte int in native byte order -- return "" if impossible
-    try:
-        (magic,) = struct.unpack("=l", s)
-    except struct.error:
-        return ""
-
-    # Check for GNU dbm
-    if magic == 0x13579ace:
-        return "gdbm"
-
-    # Check for old Berkeley db hash file format v2
-    if magic in (0x00061561, 0x61150600):
-        return "bsddb185"
-
-    # Later versions of Berkeley db hash file have a 12-byte pad in
-    # front of the file type
-    try:
-        (magic,) = struct.unpack("=l", s16[-4:])
-    except struct.error:
-        return ""
-
-    # Check for BSD hash
-    if magic in (0x00061561, 0x61150600):
-        return "dbhash"
-
-    # Unknown
-    return ""
-
-if __name__ == "__main__":
-    for filename in sys.argv[1:]:
-        print(whichdb(filename) or "UNKNOWN", filename)