# !/usr/bin/env python

"""Guess which db package to use to open a db file."""



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 = open(filename + os.extsep + "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 = open(filename + os.extsep + "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 = open(filename + os.extsep + "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 + os.extsep + "dat")

        size = os.stat(filename + os.extsep + "dir").st_size

        # dumbdbm files with no keys are empty

        if size == 0:

            return "dumbdbm"

        f = open(filename + os.extsep + "dir", "rb")

        try:

            if f.read(1) in ("'", '"'):

                return "dumbdbm"

        finally:

            f.close()

    except (OSError, IOError):

        pass



    # See if the file exists, return None if not

    try:

        f = 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

