| """Generic interface to all dbm clones. |
| |
| Use |
| |
| import dbm |
| d = dbm.open(file, 'w', 0o666) |
| |
| The returned object is a 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, and add interfaces to other dbm-like |
| implementations. |
| """ |
| |
| __all__ = ['open', 'whichdb', 'error'] |
| |
| import io |
| import os |
| import struct |
| import sys |
| |
| |
| class error(Exception): |
| pass |
| |
| _names = ['dbm.gnu', 'dbm.ndbm', 'dbm.dumb'] |
| _defaultmod = None |
| _modules = {} |
| |
| error = (error, OSError) |
| |
| try: |
| from dbm import ndbm |
| except ImportError: |
| ndbm = None |
| |
| |
| def open(file, flag='r', mode=0o666): |
| """Open or create database at path given by *file*. |
| |
| Optional argument *flag* can be 'r' (default) 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. |
| |
| 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. |
| """ |
| global _defaultmod |
| if _defaultmod is None: |
| for name in _names: |
| try: |
| mod = __import__(name, fromlist=['open']) |
| except ImportError: |
| continue |
| if not _defaultmod: |
| _defaultmod = mod |
| _modules[name] = mod |
| if not _defaultmod: |
| raise ImportError("no dbm clone found; tried %s" % _names) |
| |
| # guess the type of an existing database, if not creating a new one |
| result = whichdb(file) if 'n' not in flag else None |
| if result is None: |
| # db doesn't exist or 'n' flag was specified to create a new db |
| 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[0]("need 'c' or 'n' flag to open new db") |
| elif result == "": |
| # db type cannot be determined |
| raise error[0]("db type could not be determined") |
| elif result not in _modules: |
| raise error[0]("db type is {0}, but the module is not " |
| "available".format(result)) |
| else: |
| mod = _modules[result] |
| return mod.open(file, flag, mode) |
| |
| |
| 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() |
| f = io.open(filename + ".dir", "rb") |
| f.close() |
| return "dbm.ndbm" |
| except OSError: |
| # 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 OSError: |
| 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: |
| pass |
| |
| # See if the file exists, return None if not |
| try: |
| f = io.open(filename, "rb") |
| except OSError: |
| return None |
| |
| with f: |
| # Read the start of the file -- the magic number |
| s16 = f.read(16) |
| 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 in (0x13579ace, 0x13579acd, 0x13579acf): |
| return "dbm.gnu" |
| |
| # 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 "" |
| |
| # Unknown |
| return "" |
| |
| |
| if __name__ == "__main__": |
| for filename in sys.argv[1:]: |
| print(whichdb(filename) or "UNKNOWN", filename) |