From bea4fcbac6e67cd952c671827d3710d61811c31c Mon Sep 17 00:00:00 2001
From: Kelly Kelly <kelly@basistech.com>
Date: Mon, 27 Apr 2020 14:50:28 -0400
Subject: [PATCH] merged in 1441 branch

---
 bindings/java/jni/auto_db_java.cpp            | 2097 +++++++++++++++++
 bindings/java/jni/auto_db_java.h              |  247 ++
 .../datamodel/CaseDatabaseFactory.java        |  616 +++++
 .../org/sleuthkit/datamodel/JniDbHelper.java  |  400 ++++
 .../org/sleuthkit/datamodel/SQLHelper.java    |   91 +
 5 files changed, 3451 insertions(+)
 create mode 100644 bindings/java/jni/auto_db_java.cpp
 create mode 100644 bindings/java/jni/auto_db_java.h
 create mode 100644 bindings/java/src/org/sleuthkit/datamodel/CaseDatabaseFactory.java
 create mode 100644 bindings/java/src/org/sleuthkit/datamodel/JniDbHelper.java
 create mode 100644 bindings/java/src/org/sleuthkit/datamodel/SQLHelper.java

diff --git a/bindings/java/jni/auto_db_java.cpp b/bindings/java/jni/auto_db_java.cpp
new file mode 100644
index 000000000..56b2d7581
--- /dev/null
+++ b/bindings/java/jni/auto_db_java.cpp
@@ -0,0 +1,2097 @@
+/*
+ ** The Sleuth Kit
+ **
+ ** Brian Carrier [carrier <at> sleuthkit [dot] org]
+ ** Copyright (c) 2020 Brian Carrier.  All Rights reserved
+ **
+ ** This software is distributed under the Common Public License 1.0
+ **
+ */
+
+/**
+ * \file auto_db_java.cpp
+ * Contains code to populate database with volume and file system information from a specific image.
+ */
+
+#include "auto_db_java.h"
+#include "jni.h"
+#include "tsk/img/img_writer.h"
+#if HAVE_LIBEWF
+#include "tsk/img/ewf.h"
+#include "tsk/img/tsk_img_i.h"
+#endif
+#include <string.h>
+
+#include <algorithm>
+#include <sstream>
+
+using std::stringstream;
+using std::for_each;
+
+/**
+ */
+TskAutoDbJava::TskAutoDbJava()
+{
+    m_curImgId = 0;
+    m_curVsId = 0;
+    m_curVolId = 0;
+    m_curFsId = 0;
+    m_curFileId = 0;
+    m_curUnallocDirId = 0;
+    m_curDirAddr = 0;
+    m_curDirPath = "";
+    m_vsFound = false;
+    m_volFound = false;
+    m_poolFound = false;
+    m_stopped = false;
+    m_foundStructure = false;
+    m_attributeAdded = false;
+    m_addFileSystems = true;
+    m_noFatFsOrphans = false;
+    m_addUnallocSpace = false;
+    m_minChunkSize = -1;
+    m_maxChunkSize = -1;
+
+    m_jniEnv = NULL;
+
+    tsk_init_lock(&m_curDirPathLock);
+}
+
+TskAutoDbJava::~TskAutoDbJava()
+{
+    closeImage();
+    tsk_deinit_lock(&m_curDirPathLock);
+}
+
+/**
+* Look up all callback method IDs
+* @param jniEnv pointer to java environment this was called from
+* @param jobj the JniDbHelper object this was called from
+*/
+TSK_RETVAL_ENUM
+TskAutoDbJava::initializeJni(JNIEnv * jniEnv, jobject jobj) {
+    m_jniEnv = jniEnv;
+    m_javaDbObj = m_jniEnv->NewGlobalRef(jobj);
+
+    jclass localCallbackClass = m_jniEnv->FindClass("org/sleuthkit/datamodel/JniDbHelper");
+    if (localCallbackClass == NULL) {
+        return TSK_ERR;
+    }
+    m_callbackClass = (jclass)m_jniEnv->NewGlobalRef(localCallbackClass);
+
+    m_addImageMethodID = m_jniEnv->GetMethodID(m_callbackClass, "addImageInfo", "(IJLjava/lang/String;JLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)J");
+    if (m_addImageMethodID == NULL) {
+        return TSK_ERR;
+    }
+
+    m_addImageNameMethodID = m_jniEnv->GetMethodID(m_callbackClass, "addImageName", "(JLjava/lang/String;J)I");
+    if (m_addImageNameMethodID == NULL) {
+        return TSK_ERR;
+    }
+
+    m_addVolumeSystemMethodID = m_jniEnv->GetMethodID(m_callbackClass, "addVsInfo", "(JIJJ)J");
+    if (m_addVolumeSystemMethodID == NULL) {
+        return TSK_ERR;
+    }
+
+    m_addVolumeMethodID = m_jniEnv->GetMethodID(m_callbackClass, "addVolume", "(JJJJLjava/lang/String;J)J");
+    if (m_addVolumeMethodID == NULL) {
+        return TSK_ERR;
+    }
+
+    m_addPoolMethodID = m_jniEnv->GetMethodID(m_callbackClass, "addPool", "(JI)J");
+    if (m_addPoolMethodID == NULL) {
+        return TSK_ERR;
+    }
+
+
+    m_addFileSystemMethodID = m_jniEnv->GetMethodID(m_callbackClass, "addFileSystem", "(JJIJJJJJ)J");
+    if (m_addFileSystemMethodID == NULL) {
+        return TSK_ERR;
+    }
+
+    m_addFileMethodID = m_jniEnv->GetMethodID(m_callbackClass, "addFile", "(JJJIIILjava/lang/String;JJIIIIJJJJJIIILjava/lang/String;Ljava/lang/String;)J");
+    if (m_addFileMethodID == NULL) {
+        return TSK_ERR;
+    }
+
+    m_getParentIdMethodID = m_jniEnv->GetMethodID(m_callbackClass, "findParentObjId", "(JJLjava/lang/String;Ljava/lang/String;)J");
+    if (m_getParentIdMethodID == NULL) {
+        return TSK_ERR;
+    }
+
+    m_addUnallocParentMethodID = m_jniEnv->GetMethodID(m_callbackClass, "addUnallocFsBlockFilesParent", "(JLjava/lang/String;)J");
+    if (m_addUnallocParentMethodID == NULL) {
+        return TSK_ERR;
+    }
+
+    m_addLayoutFileMethodID = m_jniEnv->GetMethodID(m_callbackClass, "addLayoutFile", "(JJJILjava/lang/String;J)J");
+    if (m_addLayoutFileMethodID == NULL) {
+        return TSK_ERR;
+    }
+    
+    m_addLayoutFileRangeMethodID = m_jniEnv->GetMethodID(m_callbackClass, "addLayoutFileRange", "(JJJJ)J");
+    if (m_addLayoutFileRangeMethodID == NULL) {
+        return TSK_ERR;
+    }
+    return TSK_OK;
+}
+
+/**
+* Cache a database object for later use. Should be called on image, volume system, volume,
+* pool, and file system.
+* @param objId    The object ID of the new object
+* @param parObjId The object ID of the new object's parent
+* @param type     The type of object
+*/
+void 
+TskAutoDbJava::saveObjectInfo(uint64_t objId, uint64_t parObjId, TSK_DB_OBJECT_TYPE_ENUM type) {
+    TSK_DB_OBJECT objectInfo;
+    objectInfo.objId = objId;
+    objectInfo.parObjId = parObjId;
+    objectInfo.type = type;
+    m_savedObjects.push_back(objectInfo);
+}
+
+/**
+* Get a previously cached database object.
+* @param objId The object ID of the object being loaded
+*/
+TSK_RETVAL_ENUM 
+TskAutoDbJava::getObjectInfo(uint64_t objId, TSK_DB_OBJECT** obj_info) {
+    for (vector<TSK_DB_OBJECT>::iterator itObjs = m_savedObjects.begin();
+            itObjs != m_savedObjects.end(); ++itObjs) {
+        TSK_DB_OBJECT* tskDbObj = &(*itObjs);
+        if (tskDbObj->objId == objId) {
+            *obj_info = tskDbObj;
+            return TSK_OK;
+        }
+    }
+
+    // Object not found
+    return TSK_ERR;
+}
+
+/**
+* Adds image details to the existing database tables. Object ID for new image stored in objId.
+*
+* @param type Image type
+* @param ssize Size of device sector in bytes (or 0 for default)
+* @param objId The object id assigned to the image (out param)
+* @param timeZone The timezone the image is from
+* @param size The size of the image in bytes.
+* @param md5 MD5 hash of the image
+* @param sha1 SHA1 hash of the image
+* @param sha256 SHA256 hash of the image
+* @param deviceId An ASCII-printable identifier for the device associated with the data source that is intended to be unique across multiple cases (e.g., a UUID).
+* @param collectionDetails collection details
+* @returns TSK_ERR on error, TSK_OK on success
+*/
+TSK_RETVAL_ENUM
+TskAutoDbJava::addImageInfo(int type, TSK_OFF_T ssize, int64_t & objId, const string & timezone, TSK_OFF_T size, const string &md5,
+    const string& sha1, const string& sha256, const string& deviceId, const string& collectionDetails) {
+
+    const char *tz_cstr = timezone.c_str();
+    jstring tzj = m_jniEnv->NewStringUTF(tz_cstr);
+
+    const char *md5_cstr = md5.c_str();
+    jstring md5j = m_jniEnv->NewStringUTF(md5_cstr);
+
+    const char *sha1_cstr = sha1.c_str();
+    jstring sha1j = m_jniEnv->NewStringUTF(sha1_cstr);
+
+    const char *sha256_cstr = sha256.c_str();
+    jstring sha256j = m_jniEnv->NewStringUTF(sha256_cstr);
+
+    const char *devId_cstr = deviceId.c_str();
+    jstring devIdj = m_jniEnv->NewStringUTF(devId_cstr);
+
+    const char *coll_cstr = collectionDetails.c_str();
+    jstring collj = m_jniEnv->NewStringUTF(coll_cstr);
+
+    jlong objIdj = m_jniEnv->CallLongMethod(m_javaDbObj, m_addImageMethodID,
+        type, ssize, tzj, size, md5j, sha1j, sha256j, devIdj, collj);
+    objId = (int64_t)objIdj;
+
+    if (objId < 0) {
+        return TSK_ERR;
+    }
+
+    saveObjectInfo(objId, 0, TSK_DB_OBJECT_TYPE_IMG);
+    return TSK_OK;
+}
+
+/**
+* Adds one image name
+* @param objId    The object ID of the image
+* @param imgName  The image name
+* @param sequence The sequence number for this image name
+* @returns TSK_ERR on error, TSK_OK on success
+*/
+TSK_RETVAL_ENUM
+TskAutoDbJava::addImageName(int64_t objId, char const* imgName, int sequence) {
+
+    jstring imgNamej = m_jniEnv->NewStringUTF(imgName);
+
+    jint res = m_jniEnv->CallIntMethod(m_javaDbObj, m_addImageNameMethodID,
+        objId, imgNamej, (int64_t)sequence);
+
+    if (res == 0) {
+        return TSK_OK;
+    }
+    else {
+        return TSK_ERR;
+    }
+}
+
+/**
+* Adds volume system to database. Object ID for new vs stored in objId.
+*
+* @param vs_info  Struct containing info for this volume system
+* @param parObjId Parent object ID for the volume system
+* @param objId    Object ID of new volume system
+* @returns TSK_ERR on error, TSK_OK on success
+*/
+TSK_RETVAL_ENUM
+TskAutoDbJava::addVsInfo(const TSK_VS_INFO* vs_info, int64_t parObjId, int64_t& objId) {
+
+    jlong objIdj = m_jniEnv->CallLongMethod(m_javaDbObj, m_addVolumeSystemMethodID,
+        parObjId, vs_info->vstype, vs_info->offset, (uint64_t)vs_info->block_size);
+    objId = (int64_t)objIdj;
+
+    if (objId < 0) {
+        return TSK_ERR;
+    }
+
+    // Save the vs info to use for unallocated blocks later
+    TSK_DB_VS_INFO vs_db;
+    vs_db.objId = objId;
+    vs_db.offset = vs_info->offset;
+    vs_db.vstype = vs_info->vstype;
+    vs_db.block_size = vs_info->block_size;
+    m_savedVsInfo.push_back(vs_db);
+
+    saveObjectInfo(objId, parObjId, TSK_DB_OBJECT_TYPE_VS);
+    return TSK_OK;
+}
+
+/**
+* Adds pool and pool volume system to database. Object ID for new pool vs stored in objId.
+*
+* @param pool_info  Struct containing info for this pool
+* @param parObjId   Parent object ID for the pool
+* @param objId      Object ID of new pool volume system
+* @returns TSK_ERR on error, TSK_OK on success
+*/
+TSK_RETVAL_ENUM
+TskAutoDbJava::addPoolInfoAndVS(const TSK_POOL_INFO *pool_info, int64_t parObjId, int64_t& objId) {
+
+    // Add the pool
+    jlong poolObjIdj = m_jniEnv->CallLongMethod(m_javaDbObj, m_addPoolMethodID,
+        parObjId, pool_info->ctype);
+    int64_t poolObjId = (int64_t)poolObjIdj;
+
+    if (poolObjId < 0) {
+        return TSK_ERR;
+    }
+    saveObjectInfo(poolObjId, parObjId, TSK_DB_OBJECT_TYPE_POOL);
+
+    // Add the pool volume
+    jlong objIdj = m_jniEnv->CallLongMethod(m_javaDbObj, m_addVolumeSystemMethodID,
+        poolObjIdj, TSK_VS_TYPE_APFS, pool_info->img_offset, (uint64_t)pool_info->block_size);
+    objId = (int64_t)objIdj;
+
+    saveObjectInfo(objId, poolObjId, TSK_DB_OBJECT_TYPE_VS);
+    return TSK_OK;
+}
+
+/**
+* Adds a pool volume to database. Object ID for new pool volume stored in objId.
+*
+* @param pool_vol  Struct containing info for this pool volume
+* @param parObjId  Parent object ID
+* @param objId     Object ID of new pool volume
+* @returns TSK_ERR on error, TSK_OK on success
+*/
+TSK_RETVAL_ENUM
+TskAutoDbJava::addPoolVolumeInfo(const TSK_POOL_VOLUME_INFO* pool_vol,
+    int64_t parObjId, int64_t& objId) {
+
+    jstring descj = m_jniEnv->NewStringUTF(pool_vol->desc);
+
+    jlong objIdj = m_jniEnv->CallLongMethod(m_javaDbObj, m_addVolumeMethodID,
+        parObjId, (int64_t)pool_vol->index, pool_vol->block, pool_vol->num_blocks,
+        descj, pool_vol->flags);
+    objId = (int64_t)objIdj;
+
+    if (objId < 0) {
+        return TSK_ERR;
+    }
+
+    saveObjectInfo(objId, parObjId, TSK_DB_OBJECT_TYPE_VOL);
+    return TSK_OK;
+}
+
+/**
+* Adds a volume to database. Object ID for new volume stored in objId.
+*
+* @param vs_part   Struct containing info for this volume
+* @param parObjId  Parent object ID
+* @param objId     Object ID of new volume
+* @returns TSK_ERR on error, TSK_OK on success
+*/
+TSK_RETVAL_ENUM
+TskAutoDbJava::addVolumeInfo(const TSK_VS_PART_INFO* vs_part,
+    int64_t parObjId, int64_t& objId) {
+
+    jstring descj = m_jniEnv->NewStringUTF(vs_part->desc);
+
+    jlong objIdj = m_jniEnv->CallLongMethod(m_javaDbObj, m_addVolumeMethodID,
+        parObjId, (uint64_t)vs_part->addr, vs_part->start, vs_part->len,
+        descj, vs_part->flags);
+    objId = (int64_t)objIdj;
+
+    if (objId < 0) {
+        return TSK_ERR;
+    }
+
+    // Save the volume info for creating unallocated blocks later
+    TSK_DB_VS_PART_INFO vs_part_db;
+    vs_part_db.objId = objId;
+    vs_part_db.addr = vs_part->addr;
+    vs_part_db.start = vs_part->start;
+    vs_part_db.len = vs_part->len;
+    strncpy(vs_part_db.desc, vs_part->desc, TSK_MAX_DB_VS_PART_INFO_DESC_LEN - 1);
+    vs_part_db.flags = vs_part->flags;
+    m_savedVsPartInfo.push_back(vs_part_db);
+
+    saveObjectInfo(objId, parObjId, TSK_DB_OBJECT_TYPE_VOL);
+    return TSK_OK;
+}
+
+/**
+* Adds a file system to database. Object ID for new file system stored in objId.
+*
+* @param fs_info   Struct containing info for this file system
+* @param parObjId  Parent object ID
+* @param objId     Object ID of new file system
+* @returns TSK_ERR on error, TSK_OK on success
+*/
+TSK_RETVAL_ENUM
+TskAutoDbJava::addFsInfo(const TSK_FS_INFO* fs_info, int64_t parObjId,
+    int64_t& objId) {
+
+    jlong objIdj = m_jniEnv->CallLongMethod(m_javaDbObj, m_addFileSystemMethodID,
+        parObjId, fs_info->offset, (int)fs_info->ftype, (uint64_t)fs_info->block_size,
+        fs_info->block_count, fs_info->root_inum, fs_info->first_inum,
+        fs_info->last_inum);
+    objId = (int64_t)objIdj;
+
+    if (objId < 0) {
+        return TSK_ERR;
+    }
+
+    // Save the file system info for creating unallocated blocks later
+    TSK_DB_FS_INFO fs_info_db;
+    fs_info_db.objId = objId;
+    fs_info_db.imgOffset = fs_info->offset;
+    fs_info_db.fType = fs_info->ftype;
+    fs_info_db.block_size = fs_info->block_size;
+    fs_info_db.block_count = fs_info->block_count;
+    fs_info_db.root_inum = fs_info->root_inum;
+    fs_info_db.first_inum = fs_info->first_inum;
+    fs_info_db.last_inum = fs_info->last_inum;
+    m_savedFsInfo.push_back(fs_info_db);
+
+    saveObjectInfo(objId, parObjId, TSK_DB_OBJECT_TYPE_FS);
+    return TSK_OK;
+}
+
+/**
+* Adds a file to database. Object ID for new file stored in objId.
+*
+* @param fs_file
+* @param fs_attr
+* @param path      File path
+* @param parObjId  Parent object ID
+* @param fsObjId   Object ID of the file system
+* @param objId     Object ID of new file
+* @param dataSourceObjId  Object ID of the data source
+* @returns TSK_ERR on error, TSK_OK on success
+*/
+TSK_RETVAL_ENUM
+TskAutoDbJava::addFsFile(TSK_FS_FILE* fs_file,
+    const TSK_FS_ATTR* fs_attr, const char* path,
+    int64_t fsObjId, int64_t& objId, int64_t dataSourceObjId) {
+
+    if (fs_file->name == NULL)
+        return TSK_ERR;
+
+    // Find the object id for the parent folder.
+    int64_t parObjId = 0;
+
+    // Root directory's parent should be the file system object.
+    // Make sure it doesn't have a name, so that we don't pick up ".." entries
+    if ((fs_file->fs_info->root_inum == fs_file->name->meta_addr) &&
+        ((fs_file->name->name == NULL) || (strlen(fs_file->name->name) == 0))) {
+        // File is in the root directory
+        parObjId = fsObjId;
+    } else {
+        // Look up parent object ID
+        parObjId = findParObjId(fs_file, path, fsObjId);
+        if (parObjId == -1) {
+            return TSK_ERR;
+        }
+    }
+
+    // Add the file to the database
+    return addFile(fs_file, fs_attr, path, fsObjId, parObjId, objId, dataSourceObjId);
+}
+
+/**
+* Extract the extension from the given file name and store it in the supplied string.
+* @param name A file name
+* @param extension The file name extension will be extracted to extension.
+*/
+void extractExtension(char *name, char *extension) {
+    char *ext = strrchr(name, '.');
+
+    //if ext is not null and is not the entire filename...
+    if (ext && (name != ext)) {
+        size_t extLen = strlen(ext);
+        //... and doesn't only contain the '.' and isn't too long to be a real extension.
+        if ((1 < extLen) && (extLen < 15)) {
+            strncpy(extension, ext + 1, extLen - 1);
+            //normalize to lower case, only works for ascii
+            for (int i = 0; extension[i]; i++) {
+                extension[i] = tolower(extension[i]);
+            }
+        }
+    }
+}
+
+/**
+* Store info about a directory in a complex map structure as a cache for the
+* files who are a child of this directory and want to know its object id.
+*
+* @param fsObjId fs id of this directory
+* @param fs_file File for the directory to store
+* @param path Full path (parent and this file) of the directory
+* @param objId object id of the directory
+*/
+void TskAutoDbJava::storeObjId(const int64_t& fsObjId, const TSK_FS_FILE* fs_file, const char* path, const int64_t& objId)
+{
+    // skip the . and .. entries
+    if ((fs_file->name) && (fs_file->name->name) && (TSK_FS_ISDOT(fs_file->name->name)))
+    {
+        return;
+    }
+
+    uint32_t seq;
+    uint32_t path_hash = hash((const unsigned char *)path);
+
+    /* NTFS uses sequence, otherwise we hash the path. We do this to map to the
+    * correct parent folder if there are two from the root dir that eventually point to
+    * the same folder (one deleted and one allocated) or two hard links. */
+    if (TSK_FS_TYPE_ISNTFS(fs_file->fs_info->ftype)) {
+        /* Use the sequence stored in meta (which could be one larger than the name value
+        * if the directory is deleted. We do this because the par_seq gets added to the
+        * name structure when it is added to the directory based on teh value stored in
+        * meta. */
+        seq = fs_file->meta->seq;
+    }
+    else {
+        seq = path_hash;
+    }
+
+    map<TSK_INUM_T, map<uint32_t, map<uint32_t, int64_t> > >& fsMap = m_parentDirIdCache[fsObjId];
+    if (fsMap.count(fs_file->name->meta_addr) == 0) {
+        fsMap[fs_file->name->meta_addr][seq][path_hash] = objId;
+    }
+    else {
+        map<uint32_t, map<uint32_t, int64_t> >& fileMap = fsMap[fs_file->name->meta_addr];
+        if (fileMap.count(seq) == 0) {
+            fileMap[seq][path_hash] = objId;
+        }
+    }
+}
+
+
+/**
+* Adds a file and its associated slack file to database. Object ID for new file stored in objId.
+*
+* @param fs_file
+* @param fs_attr
+* @param path      File path
+* @param fsObjId   Object ID of the file system
+* @param parObjId  Parent object ID
+* @param objId     Object ID of new file
+* @param dataSourceObjId  Object ID of the data source
+* @returns TSK_ERR on error, TSK_OK on success
+*/
+TSK_RETVAL_ENUM
+TskAutoDbJava::addFile(TSK_FS_FILE* fs_file,
+    const TSK_FS_ATTR* fs_attr, const char* path,
+    int64_t fsObjId, int64_t parObjId,
+    int64_t& objId, int64_t dataSourceObjId)
+{
+    time_t mtime = 0;
+    time_t crtime = 0;
+    time_t ctime = 0;
+    time_t atime = 0;
+    TSK_OFF_T size = 0;
+    int meta_type = 0;
+    int meta_flags = 0;
+    int meta_mode = 0;
+    int gid = 0;
+    int uid = 0;
+    int type = TSK_FS_ATTR_TYPE_NOT_FOUND;
+    int idx = 0;
+
+    if (fs_file->name == NULL)
+        return TSK_OK;
+
+    if (fs_file->meta) {
+        mtime = fs_file->meta->mtime;
+        atime = fs_file->meta->atime;
+        ctime = fs_file->meta->ctime;
+        crtime = fs_file->meta->crtime;
+        meta_type = fs_file->meta->type;
+        meta_flags = fs_file->meta->flags;
+        meta_mode = fs_file->meta->mode;
+        gid = fs_file->meta->gid;
+        uid = fs_file->meta->uid;
+    }
+
+    size_t attr_nlen = 0;
+    if (fs_attr) {
+        type = fs_attr->type;
+        idx = fs_attr->id;
+        size = fs_attr->size;
+        if (fs_attr->name) {
+            if ((fs_attr->type != TSK_FS_ATTR_TYPE_NTFS_IDXROOT) ||
+                (strcmp(fs_attr->name, "$I30") != 0)) {
+                attr_nlen = strlen(fs_attr->name);
+            }
+        }
+    }
+
+    // sanity check
+    if (size < 0) {
+        size = 0;
+    }
+
+    // combine name and attribute name
+    size_t len = strlen(fs_file->name->name);
+    char * name;
+    size_t nlen = len + attr_nlen + 11; // Extra space for possible colon and '-slack'
+    if ((name = (char *)tsk_malloc(nlen)) == NULL) {
+        return TSK_ERR;
+    }
+
+    strncpy(name, fs_file->name->name, nlen);
+
+    char extension[24] = "";
+    extractExtension(name, extension);
+
+    // Add the attribute name
+    if (attr_nlen > 0) {
+        strncat(name, ":", nlen - strlen(name));
+        if (fs_attr != NULL) {
+            strncat(name, fs_attr->name, nlen - strlen(name));
+        }
+    }
+
+    // clean up path
+    // +2 = space for leading slash and terminating null
+    size_t path_len = strlen(path) + 2;
+    char* escaped_path;
+    if ((escaped_path = (char *)tsk_malloc(path_len)) == NULL) {
+        free(name);
+        return TSK_ERR;
+    }
+    strncpy(escaped_path, "/", path_len);
+    strncat(escaped_path, path, path_len - strlen(escaped_path));
+
+    jstring namej = m_jniEnv->NewStringUTF(name);
+    jstring pathj = m_jniEnv->NewStringUTF(escaped_path);
+    jstring extj = m_jniEnv->NewStringUTF(extension);
+ 
+    // Add the file to the database
+    jlong objIdj = m_jniEnv->CallLongMethod(m_javaDbObj, m_addFileMethodID,
+        parObjId, fsObjId,
+        dataSourceObjId,
+        TSK_DB_FILES_TYPE_FS,
+        type, idx, namej,
+        fs_file->name->meta_addr, (uint64_t)fs_file->name->meta_seq,
+        fs_file->name->type, meta_type, fs_file->name->flags, meta_flags,
+        size,
+        (unsigned long long)crtime, (unsigned long long)ctime, (unsigned long long) atime, (unsigned long long) mtime,
+        meta_mode, gid, uid, 
+        pathj, extj);
+    objId = (int64_t)objIdj;
+
+    if (objId < 0) {
+        return TSK_ERR;
+    }
+
+    // If dir, update parent ID cache
+    if (TSK_FS_IS_DIR_META(meta_type)){
+        std::string fullPath = std::string(path) + fs_file->name->name;
+        storeObjId(fsObjId, fs_file, fullPath.c_str(), objId);
+    }
+
+    // Add entry for the slack space.
+    // Current conditions for creating a slack file:
+    //   - File name is not empty, "." or ".."
+    //   - Data is non-resident
+    //   - The allocated size is greater than the initialized file size
+    //     See github issue #756 on why initsize and not size.
+    //   - The data is not compressed
+    if ((fs_attr != NULL)
+        && ((strlen(name) > 0) && (!TSK_FS_ISDOT(name)))
+        && (!(fs_file->meta->flags & TSK_FS_META_FLAG_COMP))
+        && (fs_attr->flags & TSK_FS_ATTR_NONRES)
+        && (fs_attr->nrd.allocsize > fs_attr->nrd.initsize)) {
+        strncat(name, "-slack", 6);
+        if (strlen(extension) > 0) {
+            strncat(extension, "-slack", 6);
+        }
+        jstring slackNamej = m_jniEnv->NewStringUTF(name);
+        jstring slackExtj = m_jniEnv->NewStringUTF(extension);
+        TSK_OFF_T slackSize = fs_attr->nrd.allocsize - fs_attr->nrd.initsize;
+
+        // Add slack file to database
+        jlong objIdj = m_jniEnv->CallLongMethod(m_javaDbObj, m_addFileMethodID,
+            parObjId, fsObjId,
+            dataSourceObjId,
+            TSK_DB_FILES_TYPE_SLACK,
+            type, idx, slackNamej,
+            fs_file->name->meta_addr, (uint64_t)fs_file->name->meta_seq,
+            TSK_FS_NAME_TYPE_REG, TSK_FS_META_TYPE_REG, fs_file->name->flags, meta_flags,
+            slackSize,
+            (unsigned long long)crtime, (unsigned long long)ctime, (unsigned long long) atime, (unsigned long long) mtime,
+            meta_mode, gid, uid, // md5TextPtr, known,
+            pathj, slackExtj);
+        int64_t slackObjId = (int64_t)objIdj;
+
+        if (slackObjId < 0) {
+            return TSK_ERR;
+        }
+    }
+
+    free(name);
+    free(escaped_path);
+
+    return TSK_OK;
+}
+
+// Internal function object to check for range overlap
+typedef struct _checkFileLayoutRangeOverlap {
+    const vector<TSK_DB_FILE_LAYOUT_RANGE> & ranges;
+    bool hasOverlap;
+
+    explicit _checkFileLayoutRangeOverlap(const vector<TSK_DB_FILE_LAYOUT_RANGE> & ranges)
+        : ranges(ranges), hasOverlap(false) {}
+
+    bool getHasOverlap() const { return hasOverlap; }
+    void operator() (const TSK_DB_FILE_LAYOUT_RANGE & range) {
+        if (hasOverlap)
+            return; //no need to check other
+
+        uint64_t start = range.byteStart;
+        uint64_t end = start + range.byteLen;
+
+        vector<TSK_DB_FILE_LAYOUT_RANGE>::const_iterator it;
+        for (it = ranges.begin(); it != ranges.end(); ++it) {
+            const TSK_DB_FILE_LAYOUT_RANGE * otherRange = &(*it);
+            if (&range == otherRange)
+                continue; //skip, it's the same range
+            uint64_t otherStart = otherRange->byteStart;
+            uint64_t otherEnd = otherStart + otherRange->byteLen;
+            if (start <= otherEnd && end >= otherStart) {
+                hasOverlap = true;
+                break;
+            }
+        }
+    }
+
+} checkFileLayoutRangeOverlap;
+
+/**
+* Internal helper method to add unalloc, unused and carved files with layout ranges to db
+* Generates file_name and populates tsk_files, tsk_objects and tsk_file_layout tables
+* Adds a single entry to tsk_files table with an auto-generated file name, tsk_objects table, and one or more entries to tsk_file_layout table
+* @param dbFileType  Type of file
+* @param parentObjId Id of the parent object in the database (fs, volume, or image)
+* @param fsObjId     parent fs, or NULL if the file is not associated with fs
+* @param size        Number of bytes in file
+* @param ranges      vector containing one or more TSK_DB_FILE_LAYOUT_RANGE layout ranges (in)
+* @param objId       object id of the file object created (output)
+* @param dataSourceObjId  The object ID for the data source
+* @returns TSK_OK on success or TSK_ERR on error.
+*/
+TSK_RETVAL_ENUM
+TskAutoDbJava::addFileWithLayoutRange(const TSK_DB_FILES_TYPE_ENUM dbFileType, const int64_t parentObjId,
+    const int64_t fsObjId, const uint64_t size,
+    vector<TSK_DB_FILE_LAYOUT_RANGE>& ranges, int64_t& objId,
+    int64_t dataSourceObjId) {
+
+    const size_t numRanges = ranges.size();
+
+    if (numRanges < 1) {
+        tsk_error_reset();
+        tsk_error_set_errno(TSK_ERR_AUTO_DB);
+        tsk_error_set_errstr("Error addFileWithLayoutRange() - no ranges present");
+        return TSK_ERR;
+    }
+
+    stringstream fileNameSs;
+    switch (dbFileType) {
+    case TSK_DB_FILES_TYPE_UNALLOC_BLOCKS:
+        fileNameSs << "Unalloc";
+        break;
+
+    case TSK_DB_FILES_TYPE_UNUSED_BLOCKS:
+        fileNameSs << "Unused";
+        break;
+
+    case TSK_DB_FILES_TYPE_CARVED:
+        fileNameSs << "Carved";
+        break;
+    default:
+        stringstream sserr;
+        tsk_error_reset();
+        tsk_error_set_errno(TSK_ERR_AUTO_DB);
+        sserr << "Error addFileWithLayoutRange() - unsupported file type for file layout range: ";
+        sserr << (int)dbFileType;
+        tsk_error_set_errstr("%s", sserr.str().c_str());
+        return TSK_ERR;
+    }
+
+    //ensure layout ranges are sorted (to generate file name and to be inserted in sequence order)
+    sort(ranges.begin(), ranges.end());
+
+    //dome some checking
+    //ensure there is no overlap and each range has unique byte range
+    const checkFileLayoutRangeOverlap & overlapRes =
+        for_each(ranges.begin(), ranges.end(), checkFileLayoutRangeOverlap(ranges));
+    if (overlapRes.getHasOverlap()) {
+        tsk_error_reset();
+        tsk_error_set_errno(TSK_ERR_AUTO_DB);
+        tsk_error_set_errstr("Error addFileWithLayoutRange() - overlap detected between ranges");
+        return TSK_ERR;
+    }
+
+    //construct filename with parent obj id, start byte of first range, end byte of last range
+    fileNameSs << "_" << parentObjId << "_" << ranges[0].byteStart;
+    fileNameSs << "_" << (ranges[numRanges - 1].byteStart + ranges[numRanges - 1].byteLen);
+
+    jstring namej = m_jniEnv->NewStringUTF(fileNameSs.str().c_str());
+
+    // Insert into tsk files and tsk objects
+    jlong objIdj = m_jniEnv->CallLongMethod(m_javaDbObj, m_addLayoutFileMethodID,
+        parentObjId, fsObjId, dataSourceObjId, dbFileType, namej, size);
+    objId = (int64_t)objIdj;
+
+    if (objId < 0) {
+        return TSK_ERR;
+    }
+
+    // Fill in fileObjId and insert ranges
+    for (vector<TSK_DB_FILE_LAYOUT_RANGE>::iterator it = ranges.begin();
+        it != ranges.end(); ++it) {
+        TSK_DB_FILE_LAYOUT_RANGE & range = *it;
+        range.fileObjId = objId;
+        if (-1 == m_jniEnv->CallLongMethod(m_javaDbObj, m_addLayoutFileRangeMethodID,
+            objId, range.byteStart, range.byteLen, range.sequence)) {
+            return TSK_ERR;
+        }
+    }
+
+    return TSK_OK;
+}
+
+/**
+* Adds information about a unallocated file with layout ranges into the database.
+* Adds a single entry to tsk_files table with an auto-generated file name, tsk_objects table, and one or more entries to tsk_file_layout table
+* @param parentObjId Id of the parent object in the database (fs, volume, or image)
+* @param fsObjId parent fs, or NULL if the file is not associated with fs
+* @param size Number of bytes in file
+* @param ranges vector containing one or more TSK_DB_FILE_LAYOUT_RANGE layout ranges (in)
+* @param objId object id of the file object created (output)
+* @param dataSourceObjId The object ID for the data source
+* @returns TSK_OK on success or TSK_ERR on error.
+*/
+TSK_RETVAL_ENUM
+TskAutoDbJava::addUnallocBlockFile(const int64_t parentObjId, const int64_t fsObjId, const uint64_t size,
+    vector<TSK_DB_FILE_LAYOUT_RANGE>& ranges, int64_t& objId,
+    int64_t dataSourceObjId) {
+    return addFileWithLayoutRange(TSK_DB_FILES_TYPE_UNALLOC_BLOCKS, parentObjId, fsObjId, size, ranges, objId,
+        dataSourceObjId);
+}
+
+/**
+* Adds information about a unused file with layout ranges into the database.
+* Adds a single entry to tsk_files table with an auto-generated file name, tsk_objects table, and one or more entries to tsk_file_layout table
+* @param parentObjId Id of the parent object in the database (fs, volume, or image)
+* @param fsObjId parent fs, or NULL if the file is not associated with fs
+* @param size Number of bytes in file
+* @param ranges vector containing one or more TSK_DB_FILE_LAYOUT_RANGE layout ranges (in)
+* @param objId object id of the file object created (output)
+* @param dataSourceObjId The object ID for the data source
+* @returns TSK_OK on success or TSK_ERR on error.
+*/
+TSK_RETVAL_ENUM
+TskAutoDbJava::addUnusedBlockFile(const int64_t parentObjId, const int64_t fsObjId, const uint64_t size,
+    vector<TSK_DB_FILE_LAYOUT_RANGE>& ranges, int64_t& objId,
+    int64_t dataSourceObjId) {
+    return addFileWithLayoutRange(TSK_DB_FILES_TYPE_UNUSED_BLOCKS, parentObjId, fsObjId, size, ranges, objId,
+        dataSourceObjId);
+}
+
+
+
+/**
+* Add a virtual dir to hold unallocated block files for this file system.
+* @param fsObjId  Object ID of the file system
+* @param objId    Object ID of the created virtual dir
+* @param dataSourceObjId  Object ID of the data source
+*/
+TSK_RETVAL_ENUM
+TskAutoDbJava::addUnallocFsBlockFilesParent(const int64_t fsObjId, int64_t& objId,
+    int64_t dataSourceObjId) {
+
+    const char * const unallocDirName = "$Unalloc";
+    jstring namej = m_jniEnv->NewStringUTF(unallocDirName);
+
+    jlong objIdj = m_jniEnv->CallLongMethod(m_javaDbObj, m_addUnallocParentMethodID,
+        fsObjId, namej);
+    objId = (int64_t)objIdj;
+
+    if (objId < 0) {
+        return TSK_ERR;
+    }
+    return TSK_OK;
+}
+
+/**
+* Return a hash of the passed in string. We use this
+* for full paths.
+* From: http://www.cse.yorku.ca/~oz/hash.html
+*/
+uint32_t 
+TskAutoDbJava::hash(const unsigned char* str)
+{
+    uint32_t hash = 5381;
+    int c;
+
+    while ((c = *str++)) {
+        // skip slashes -> normalizes leading/ending/double slashes
+        if (c == '/')
+            continue;
+        hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
+    }
+
+    return hash;
+}
+
+/*
+* Utility method to break up path into parent folder and folder/file name.
+* @param path Path of folder that we want to analyze
+* @param ret_parent_path pointer to parent path (begins and ends with '/')
+* @param ret_name pointer to final folder/file name
+* @returns 0 on success, 1 on error
+*/
+bool 
+TskAutoDbJava::getParentPathAndName(const char *path, const char **ret_parent_path, const char **ret_name) {
+    // Need to break up 'path' in to the parent folder to match in 'parent_path' and the folder 
+    // name to match with the 'name' column in tsk_files table
+
+    // reset all arrays
+    parent_name[0] = '\0';
+    parent_path[0] = '\0';
+
+    size_t path_len = strlen(path);
+    if (path_len >= MAX_PATH_LENGTH_JAVA_DB_LOOKUP) {
+        tsk_error_reset();
+        tsk_error_set_errno(TSK_ERR_AUTO_DB);
+        tsk_error_set_errstr("TskDb::getParentPathAndName: Path is too long. Length = %zd, Max length = %d", path_len, MAX_PATH_LENGTH_JAVA_DB_LOOKUP);
+        // assign return values to pointers
+        *ret_parent_path = "";
+        *ret_name = "";
+        return 1;
+    }
+
+    // check if empty path or just "/" were passed in
+    if (path_len == 0 || (strcmp(path, "/") == 0)) {
+        *ret_name = "";
+        *ret_parent_path = "/";
+        return 0;
+    }
+
+
+    // step 1, copy everything into parent_path and clean it up
+    // add leading slash if its not in input.  
+    if (path[0] != '/') {
+        sprintf(parent_path, "%s", "/");
+    }
+
+    strncat(parent_path, path, MAX_PATH_LENGTH_JAVA_DB_LOOKUP);
+
+    // remove trailing slash
+    if (parent_path[strlen(parent_path) - 1] == '/') {
+        parent_path[strlen(parent_path) - 1] = '\0';
+    }
+
+    // replace all non-UTF8 characters
+    tsk_cleanupUTF8(parent_path, '^');
+
+    // Step 2, move the final folder/file to parent_file
+
+    // Find the last '/' 
+    char *chptr = strrchr(parent_path, '/');
+    if (chptr) {
+        // character found in the string
+        size_t position = chptr - parent_path;
+
+        sprintf(parent_name, "%s", chptr + 1);  // copy everything after slash into parent_name
+        *ret_name = parent_name;
+
+        parent_path[position + 1] = '\0';   // add terminating null after last "/"
+        *ret_parent_path = parent_path;
+    }
+    else {
+        // "/" character not found. the entire path is parent file name. parent path is "/"
+        *ret_name = parent_path;
+        *ret_parent_path = "/";
+    }
+    return 0;
+}
+
+/**
+* Find parent object id of TSK_FS_FILE. Use local cache map, if not found, fall back to SQL
+* @param fs_file file to find parent obj id for
+* @param parentPath Path of parent folder that we want to match
+* @param fsObjId fs id of this file
+* @returns parent obj id ( > 0), -1 on error
+*/
+int64_t 
+TskAutoDbJava::findParObjId(const TSK_FS_FILE* fs_file, const char* parentPath, const int64_t& fsObjId)
+{
+    uint32_t seq;
+    uint32_t path_hash = hash((const unsigned char *)parentPath);
+
+    /* NTFS uses sequence, otherwise we hash the path. We do this to map to the
+    * correct parent folder if there are two from the root dir that eventually point to
+    * the same folder (one deleted and one allocated) or two hard links. */
+    if (TSK_FS_TYPE_ISNTFS(fs_file->fs_info->ftype))
+    {
+        seq = fs_file->name->par_seq;
+    }
+    else
+    {
+        seq = path_hash;
+    }
+
+    //get from cache by parent meta addr, if available
+    map<TSK_INUM_T, map<uint32_t, map<uint32_t, int64_t> > >& fsMap = m_parentDirIdCache[fsObjId];
+    if (fsMap.count(fs_file->name->par_addr) > 0)
+    {
+        map<uint32_t, map<uint32_t, int64_t> >& fileMap = fsMap[fs_file->name->par_addr];
+        if (fileMap.count(seq) > 0) {
+            map<uint32_t, int64_t>& pathMap = fileMap[seq];
+            if (pathMap.count(path_hash) > 0) {
+                return pathMap[path_hash];
+            }
+        }
+    }
+
+    // Need to break up 'path' in to the parent folder to match in 'parent_path' and the folder 
+    // name to match with the 'name' column in tsk_files table
+    const char *parent_name = "";
+    const char *parent_path = "";
+    if (getParentPathAndName(parentPath, &parent_path, &parent_name)) {
+        return -1;
+    }
+
+    jstring jpath = m_jniEnv->NewStringUTF(parent_path);
+    jstring jname = m_jniEnv->NewStringUTF(parent_name);
+
+    // Look up in the database
+    jlong objIdj = m_jniEnv->CallLongMethod(m_javaDbObj, m_getParentIdMethodID,
+        fs_file->name->par_addr, fsObjId, jpath, jname);
+    int64_t objId = (int64_t)objIdj;
+
+    if (objId < 0) {
+        return -1;
+    }
+
+    return objId;
+}
+
+/**
+* Adds a new volume that will hold the unallocated blocks for the pool.
+*
+* @param vol_index The index for the new volume (should be one higher than the number of pool volumes)
+* @param parObjId  The object ID of the parent volume system
+* @param objId     Will be set to the object ID of the new volume
+*
+* @returns TSK_ERR on error, TSK_OK on success
+*/
+TSK_RETVAL_ENUM
+TskAutoDbJava::addUnallocatedPoolVolume(int vol_index, int64_t parObjId, int64_t& objId)
+{
+    char *desc = "Unallocated Blocks";
+    jstring descj = m_jniEnv->NewStringUTF(desc);
+
+    jlong objIdj = m_jniEnv->CallLongMethod(m_javaDbObj, m_addVolumeMethodID,
+        parObjId, vol_index, 0, 0,
+        descj, 0);
+    objId = (int64_t)objIdj;
+
+    if (objId < 0) {
+        return TSK_ERR;
+    }
+    return TSK_OK;
+}
+
+void TskAutoDbJava::close() {
+    if (m_jniEnv == NULL) {
+        return;
+    }
+
+    if (m_javaDbObj != NULL) {
+        m_jniEnv->DeleteGlobalRef(m_javaDbObj);
+    }
+
+    if (m_callbackClass != NULL) {
+        m_jniEnv->DeleteGlobalRef(m_callbackClass);
+    }
+}
+
+int64_t TskAutoDbJava::getImageID() {
+    return m_curImgId;
+}
+
+void TskAutoDbJava::closeImage() {
+    TskAuto::closeImage();
+}
+
+void TskAutoDbJava::setAddFileSystems(bool addFileSystems) {
+    m_addFileSystems = addFileSystems;
+}
+
+void TskAutoDbJava::setNoFatFsOrphans(bool noFatFsOrphans) {
+    m_noFatFsOrphans = noFatFsOrphans;
+}
+
+void TskAutoDbJava::setAddUnallocSpace(bool addUnallocSpace) {
+    setAddUnallocSpace(addUnallocSpace, -1);
+}
+
+void TskAutoDbJava::setAddUnallocSpace(bool addUnallocSpace, int64_t minChunkSize) {
+    m_addUnallocSpace = addUnallocSpace;
+    m_minChunkSize = minChunkSize;
+    m_maxChunkSize = -1;
+}
+
+void TskAutoDbJava::setAddUnallocSpace(int64_t minChunkSize, int64_t maxChunkSize) {
+    m_addUnallocSpace = true;
+    m_minChunkSize = minChunkSize;
+    m_maxChunkSize = maxChunkSize;
+}
+
+/**
+ * Adds an image to the database.
+ *
+ * @param a_num Number of image parts
+ * @param a_images Array of paths to the image parts
+ * @param a_type Image type
+ * @param a_ssize Size of device sector in bytes (or 0 for default)
+ * @param a_deviceId An ASCII-printable identifier for the device associated with the data source that is intended to be unique across multiple cases (e.g., a UUID).
+ * @return 0 for success, 1 for failure
+ */
+uint8_t
+    TskAutoDbJava::openImageUtf8(int a_num, const char *const a_images[],
+    TSK_IMG_TYPE_ENUM a_type, unsigned int a_ssize, const char* a_deviceId)
+{
+    uint8_t retval =
+        TskAuto::openImageUtf8(a_num, a_images, a_type, a_ssize);
+    if (retval != 0) {
+        return retval;
+    }
+
+    if (addImageDetails(a_deviceId)) {
+        return 1;
+    }
+    return 0;
+}
+
+/**
+ * Adds an image to the database.
+ *
+ * @param a_num Number of image parts
+ * @param a_images Array of paths to the image parts
+ * @param a_type Image type
+ * @param a_ssize Size of device sector in bytes (or 0 for default)
+ * @param a_deviceId An ASCII-printable identifier for the device associated with the data source that is intended to be unique across multiple cases (e.g., a UUID).
+ * @return 0 for success, 1 for failure
+ */
+uint8_t
+    TskAutoDbJava::openImage(int a_num, const TSK_TCHAR * const a_images[],
+    TSK_IMG_TYPE_ENUM a_type, unsigned int a_ssize, const char* a_deviceId)
+{
+
+#ifdef TSK_WIN32
+
+    uint8_t retval = TskAuto::openImage(a_num, a_images, a_type, a_ssize);
+
+    if (retval != 0) {
+        return retval;
+    }
+
+    return (addImageDetails(a_deviceId));
+#else
+    return openImageUtf8(a_num, a_images, a_type, a_ssize, a_deviceId);
+#endif
+}
+
+/**
+* Adds an image to the database. Requires that m_img_info is already initialized
+*
+* @param a_deviceId An ASCII-printable identifier for the device associated with the data source that is intended to be unique across multiple cases (e.g., a UUID).
+* @return 0 for success, 1 for failure
+*/
+uint8_t
+TskAutoDbJava::openImage(const char* a_deviceId)
+{
+    if (m_img_info == NULL) {
+        return 1;
+    }
+
+    return(addImageDetails(a_deviceId));
+}
+
+/**
+ * Adds image details to the existing database tables.
+ *
+ * @param deviceId An ASCII-printable identifier for the device associated with the data source that is intended to be unique across multiple cases (e.g., a UUID).
+ * @return Returns 0 for success, 1 for failure
+ */
+uint8_t
+TskAutoDbJava::addImageDetails(const char* deviceId)
+{
+   string md5 = "";
+   string sha1 = "";
+   string collectionDetails = "";
+#if HAVE_LIBEWF 
+   if (m_img_info->itype == TSK_IMG_TYPE_EWF_EWF) {
+     // @@@ This should really probably be inside of a tsk_img_ method
+       IMG_EWF_INFO *ewf_info = (IMG_EWF_INFO *)m_img_info;
+       if (ewf_info->md5hash_isset) {
+           md5 = ewf_info->md5hash;
+       }
+       if (ewf_info->sha1hash_isset) {
+           sha1 = ewf_info->sha1hash;
+       }
+
+       collectionDetails = ewf_get_details(ewf_info);   
+   }
+#endif
+
+    string devId;
+    if (NULL != deviceId) {
+        devId = deviceId; 
+    } else {
+        devId = "";
+    }
+    if (TSK_OK != addImageInfo(m_img_info->itype, m_img_info->sector_size,
+          m_curImgId, m_curImgTZone, m_img_info->size, md5, sha1, "", devId, collectionDetails)) {
+        registerError();
+        return 1;
+    }
+
+
+
+    char **img_ptrs;
+#ifdef TSK_WIN32
+    // convert image paths to UTF-8
+    img_ptrs = (char **)tsk_malloc(m_img_info->num_img * sizeof(char *));
+    if (img_ptrs == NULL) {
+        return 1;
+    }
+
+    for (int i = 0; i < m_img_info->num_img; i++) {
+        char * img2 = (char*)tsk_malloc(1024 * sizeof(char));
+        UTF8 *ptr8;
+        UTF16 *ptr16;
+
+        ptr8 = (UTF8 *)img2;
+        ptr16 = (UTF16 *)m_img_info->images[i];
+
+        uint8_t retval =
+            tsk_UTF16toUTF8_lclorder((const UTF16 **)&ptr16, (UTF16 *)
+                & ptr16[TSTRLEN(m_img_info->images[i]) + 1], &ptr8,
+                (UTF8 *)((uintptr_t)ptr8 + 1024), TSKlenientConversion);
+        if (retval != TSKconversionOK) {
+            tsk_error_reset();
+            tsk_error_set_errno(TSK_ERR_AUTO_UNICODE);
+            tsk_error_set_errstr("Error converting image to UTF-8\n");
+            return 1;
+        }
+        img_ptrs[i] = img2;
+    }
+#else 
+    img_ptrs = m_img_info->images;
+#endif
+
+    // Add the image names
+    for (int i = 0; i < m_img_info->num_img; i++) {
+        const char *img_ptr = img_ptrs[i];
+
+        if (TSK_OK != addImageName(m_curImgId, img_ptr, i)) {
+            registerError();
+            return 1;
+        }
+    }
+
+#ifdef TSK_WIN32
+    //cleanup
+    for (int i = 0; i < m_img_info->num_img; ++i) {
+        free(img_ptrs[i]);
+    }
+    free(img_ptrs);
+#endif
+
+    return 0;
+}
+
+
+TSK_FILTER_ENUM 
+TskAutoDbJava::filterVs(const TSK_VS_INFO * vs_info)
+{
+    m_vsFound = true;
+    if (TSK_OK != addVsInfo(vs_info, m_curImgId, m_curVsId)) {
+        registerError();
+        return TSK_FILTER_STOP;
+    }
+
+    return TSK_FILTER_CONT;
+}
+
+TSK_FILTER_ENUM
+TskAutoDbJava::filterPool(const TSK_POOL_INFO * pool_info)
+{
+    m_poolFound = true;
+
+    if (m_volFound && m_vsFound) {
+        // there's a volume system and volume
+        if (TSK_OK != addPoolInfoAndVS(pool_info, m_curVolId, m_curPoolVs)) {
+            registerError();
+            return TSK_FILTER_STOP;
+        }
+        // Save the parent obj ID for the pool
+        m_poolOffsetToParentId[pool_info->img_offset] = m_curVolId;
+    }
+    else {
+        // pool doesn't live in a volume, use image as parent
+        if (TSK_OK != addPoolInfoAndVS(pool_info, m_curImgId, m_curPoolVs)) {
+            registerError();
+            return TSK_FILTER_STOP;
+        }
+        // Save the parent obj ID for the pool
+        m_poolOffsetToParentId[pool_info->img_offset] = m_curImgId;
+    }
+
+    // Store the volume system object ID for later use
+    m_poolOffsetToVsId[pool_info->img_offset] = m_curPoolVs;
+
+    return TSK_FILTER_CONT;
+}
+
+/**
+* Adds unallocated pool blocks to a new volume.
+*
+* @param numPool Will be updated with the number of pools processed
+*
+* @return Returns 0 for success, 1 for failure
+*/
+TSK_RETVAL_ENUM
+TskAutoDbJava::addUnallocatedPoolBlocksToDb(size_t & numPool) {
+
+    for (int i = 0; i < m_poolInfos.size(); i++) {
+        const TSK_POOL_INFO * pool_info = m_poolInfos[i];
+        if (m_poolOffsetToVsId.find(pool_info->img_offset) == m_poolOffsetToVsId.end()) {
+            tsk_error_reset();
+            tsk_error_set_errno(TSK_ERR_AUTO_DB);
+            tsk_error_set_errstr("Error addUnallocatedPoolBlocksToDb() - could not find volume system object ID for pool at offset %lld", pool_info->img_offset);
+            return TSK_ERR;
+        }
+        int64_t curPoolVs = m_poolOffsetToVsId[pool_info->img_offset];
+
+        /* Make sure  the pool_info is still allocated */
+        if (pool_info->tag != TSK_POOL_INFO_TAG) {
+            tsk_error_reset();
+            tsk_error_set_errno(TSK_ERR_AUTO_DB);
+            tsk_error_set_errstr("Error addUnallocatedPoolBlocksToDb() - pool_info is not allocated");
+            return TSK_ERR;
+        }
+
+        /* Only APFS pools are currently supported */
+        if (pool_info->ctype != TSK_POOL_TYPE_APFS) {
+            continue;
+        }
+
+        /* Increment the count of pools found */
+        numPool++;
+
+        /* Create the volume */
+        int64_t unallocVolObjId;
+        if (TSK_ERR == addUnallocatedPoolVolume(pool_info->num_vols, curPoolVs, unallocVolObjId)) {
+            tsk_error_reset();
+            tsk_error_set_errno(TSK_ERR_AUTO_DB);
+            tsk_error_set_errstr("Error addUnallocatedPoolBlocksToDb() - error createing unallocated space pool volume");
+            return TSK_ERR;
+        }
+
+        /* Create the unallocated space files */
+        TSK_FS_ATTR_RUN * unalloc_runs = tsk_pool_unallocated_runs(pool_info);
+        TSK_FS_ATTR_RUN * current_run = unalloc_runs;
+        vector<TSK_DB_FILE_LAYOUT_RANGE> ranges;
+        while (current_run != NULL) {
+
+            TSK_DB_FILE_LAYOUT_RANGE tempRange(current_run->addr * pool_info->block_size, current_run->len * pool_info->block_size, 0);
+
+            ranges.push_back(tempRange);
+            int64_t fileObjId = 0;
+            if (TSK_ERR == addUnallocBlockFile(unallocVolObjId, NULL, current_run->len * pool_info->block_size, ranges, fileObjId, m_curImgId)) {
+                registerError();
+                tsk_fs_attr_run_free(unalloc_runs);
+                return TSK_ERR;
+            }
+
+            current_run = current_run->next;
+            ranges.clear();
+        }
+        tsk_fs_attr_run_free(unalloc_runs);
+    }
+
+    return TSK_OK;
+}
+
+TSK_FILTER_ENUM
+TskAutoDbJava::filterPoolVol(const TSK_POOL_VOLUME_INFO * pool_vol)
+{
+
+    if (TSK_OK != addPoolVolumeInfo(pool_vol, m_curPoolVs, m_curPoolVol)) {
+        registerError();
+        return TSK_FILTER_STOP;
+    }
+
+    return TSK_FILTER_CONT;
+}
+
+TSK_FILTER_ENUM
+TskAutoDbJava::filterVol(const TSK_VS_PART_INFO * vs_part)
+{
+    m_volFound = true;
+    m_foundStructure = true;
+    m_poolFound = false;
+
+    if (TSK_OK != addVolumeInfo(vs_part, m_curVsId, m_curVolId)) {
+        registerError();
+        return TSK_FILTER_STOP;
+    }
+
+    return TSK_FILTER_CONT;
+}
+
+
+TSK_FILTER_ENUM
+TskAutoDbJava::filterFs(TSK_FS_INFO * fs_info)
+{
+    TSK_FS_FILE *file_root;
+    m_foundStructure = true;
+
+    if (m_poolFound) {
+        // there's a pool
+        if (TSK_OK != addFsInfo(fs_info, m_curPoolVol, m_curFsId)) {
+            registerError();
+            return TSK_FILTER_STOP;
+        }
+    }
+    else if (m_volFound && m_vsFound) {
+        // there's a volume system and volume
+        if (TSK_OK != addFsInfo(fs_info, m_curVolId, m_curFsId)) {
+            registerError();
+            return TSK_FILTER_STOP;
+        }
+    }
+    else {
+        // file system doesn't live in a volume, use image as parent
+        if (TSK_OK != addFsInfo(fs_info, m_curImgId, m_curFsId)) {
+            registerError();
+            return TSK_FILTER_STOP;
+        }
+    }
+
+
+    // We won't hit the root directory on the walk, so open it now 
+    if ((file_root = tsk_fs_file_open(fs_info, NULL, "/")) != NULL) {
+        processFile(file_root, "");
+        tsk_fs_file_close(file_root);
+        file_root = NULL;
+    }
+
+
+    // make sure that flags are set to get all files -- we need this to
+    // find parent directory
+     
+    TSK_FS_DIR_WALK_FLAG_ENUM filterFlags = (TSK_FS_DIR_WALK_FLAG_ENUM)
+        (TSK_FS_DIR_WALK_FLAG_ALLOC | TSK_FS_DIR_WALK_FLAG_UNALLOC);
+
+    //check if to skip processing of FAT orphans
+    if (m_noFatFsOrphans 
+        && TSK_FS_TYPE_ISFAT(fs_info->ftype) ) {
+            filterFlags = (TSK_FS_DIR_WALK_FLAG_ENUM) (filterFlags | TSK_FS_DIR_WALK_FLAG_NOORPHAN);
+    }
+
+    setFileFilterFlags(filterFlags);
+
+    return TSK_FILTER_CONT;
+}
+
+/* Insert the file data into the file table.
+ * @param fs_file
+ * @param fs_attr
+ * @param path
+ * Returns TSK_ERR on error.
+ */
+TSK_RETVAL_ENUM
+    TskAutoDbJava::insertFileData(TSK_FS_FILE * fs_file,
+    const TSK_FS_ATTR * fs_attr, const char *path)
+{
+    if (-1 == addFsFile(fs_file, fs_attr, path, m_curFsId, m_curFileId,
+            m_curImgId)) {
+        registerError();
+        return TSK_ERR;
+    }
+
+    return TSK_OK;
+}
+
+/**
+ * Analyzes the open image and adds image info to a database.
+ * Does not deal with transactions and such.  Refer to startAddImage()
+ * for more control. 
+ * @returns 1 if a critical error occurred (DB doesn't exist, no file system, etc.), 2 if errors occurred at some point adding files to the DB (corrupt file, etc.), and 0 otherwise.  Errors will have been registered.
+ */
+uint8_t TskAutoDbJava::addFilesInImgToDb()
+{
+    // @@@ This seems bad because we are overriding what the user may
+    // have set. We should remove the public API if we are going to 
+    // override it -- presumably this was added so that we always have
+    // unallocated volume space...
+    setVolFilterFlags((TSK_VS_PART_FLAG_ENUM) (TSK_VS_PART_FLAG_ALLOC |
+            TSK_VS_PART_FLAG_UNALLOC));
+
+    uint8_t retVal = 0;
+    if (findFilesInImg()) {
+        // map the boolean return value from findFiles to the three-state return value we use
+        // @@@ findFiles should probably return this three-state enum too
+        if (m_foundStructure == false) {
+            retVal = 1;
+        }
+        else {
+            retVal = 2;
+        }
+    }
+
+    TSK_RETVAL_ENUM addUnallocRetval = TSK_OK;
+    if (m_addUnallocSpace)
+        addUnallocRetval = addUnallocSpaceToDb();
+
+    // findFiles return value trumps unalloc since it can return either 2 or 1.
+    if (retVal) {
+        return retVal;
+    }
+    else if (addUnallocRetval == TSK_ERR) {
+        return 2;
+    }
+    else {
+        return 0;
+    }
+}
+
+
+/**
+ * Start the process to add image/file metadata to database inside of a transaction. 
+ * User must call either commitAddImage() to commit the changes,
+ * or revertAddImage() to revert them.
+ *
+ * @param numImg Number of image parts
+ * @param imagePaths Array of paths to the image parts
+ * @param imgType Image type
+ * @param sSize Size of device sector in bytes (or 0 for default)
+ * @param deviceId An ASCII-printable identifier for the device associated with the data source that is intended to be unique across multiple cases (e.g., a UUID)
+ * @return 0 for success, 1 for failure
+ */
+uint8_t
+    TskAutoDbJava::startAddImage(int numImg, const TSK_TCHAR * const imagePaths[],
+    TSK_IMG_TYPE_ENUM imgType, unsigned int sSize, const char* deviceId)
+{
+    if (tsk_verbose)
+        tsk_fprintf(stderr, "TskAutoDbJava::startAddImage: Starting add image process\n");
+
+    if (openImage(numImg, imagePaths, imgType, sSize, deviceId)) {
+        tsk_error_set_errstr2("TskAutoDbJava::startAddImage");
+        registerError();
+        return 1;
+    }
+
+    if (m_imageWriterEnabled) {
+        tsk_img_writer_create(m_img_info, m_imageWriterPath);
+    }
+    
+    if (m_addFileSystems) {
+        return addFilesInImgToDb();
+    } else {
+        return 0;
+    }
+}
+
+/**
+* Start the process to add image/file metadata to database inside of a transaction.
+* User must call either commitAddImage() to commit the changes,
+* or revertAddImage() to revert them.
+*
+* @param img_info Previously initialized TSK_IMG_INFO object
+* @param deviceId An ASCII-printable identifier for the device associated with the data source that is intended to be unique across multiple cases (e.g., a UUID)
+* @return 0 for success, 1 for failure
+*/
+uint8_t
+TskAutoDbJava::startAddImage(TSK_IMG_INFO * img_info, const char* deviceId)
+{
+    openImageHandle(img_info);
+
+    if (m_img_info == NULL) {
+        return 1;
+    }
+
+    if (tsk_verbose)
+        tsk_fprintf(stderr, "TskAutoDbJava::startAddImage: Starting add image process\n");
+
+    if (openImage(deviceId)) {
+        tsk_error_set_errstr2("TskAutoDbJava::startAddImage");
+        registerError();
+        return 1;
+    }
+
+    if (m_imageWriterEnabled) {
+        if (tsk_img_writer_create(m_img_info, m_imageWriterPath)) {
+            registerError();
+            return 1;
+        }
+    }
+
+    if (m_addFileSystems) {
+        return addFilesInImgToDb();
+    }
+    else {
+        return 0;
+    }
+}
+
+
+#ifdef WIN32
+/**
+ * Start the process to add image/file metadata to database inside of a transaction. 
+ * Same functionality as addFilesInImgToDb().  Reverts
+ * all changes on error. User must call either commitAddImage() to commit the changes,
+ * or revertAddImage() to revert them.
+ *
+ * @param numImg Number of image parts
+ * @param imagePaths Array of paths to the image parts
+ * @param imgType Image type
+ * @param sSize Size of device sector in bytes (or 0 for default)
+ * @param deviceId An ASCII-printable identifier for the device associated with the data source that is intended to be unique across multiple cases (e.g., a UUID)
+ * @return 0 for success 1, for failure
+ */
+uint8_t
+    TskAutoDbJava::startAddImage(int numImg, const char *const imagePaths[],
+    TSK_IMG_TYPE_ENUM imgType, unsigned int sSize, const char* deviceId)
+{
+    if (tsk_verbose) 
+        tsk_fprintf(stderr, "TskAutoDbJava::startAddImage_utf8: Starting add image process\n");
+
+    if (openImageUtf8(numImg, imagePaths, imgType, sSize, deviceId)) {
+        tsk_error_set_errstr2("TskAutoDbJava::startAddImage");
+        registerError();
+        return 1;
+    }
+    if (m_imageWriterEnabled) {
+        tsk_img_writer_create(m_img_info, m_imageWriterPath);
+    }
+
+    if (m_addFileSystems) {
+        return addFilesInImgToDb();
+    } else {
+        return 0;
+    }
+}
+#endif
+
+
+/**
+ * Cancel the running process.  Will not be handled immediately. 
+ */
+void
+ TskAutoDbJava::stopAddImage()
+{
+    if (tsk_verbose)
+        tsk_fprintf(stderr, "TskAutoDbJava::stopAddImage: Stop request received\n");
+    
+    m_stopped = true;
+    setStopProcessing();
+    // flag is checked every time processFile() is called
+}
+
+/**
+ * Set the current image's timezone
+ */
+void
+TskAutoDbJava::setTz(string tzone)
+{
+    m_curImgTZone = tzone;
+}
+
+TSK_RETVAL_ENUM
+TskAutoDbJava::processFile(TSK_FS_FILE * fs_file, const char *path)
+{
+    // Check if the process has been canceled
+     if (m_stopped) {
+        if (tsk_verbose)
+            tsk_fprintf(stderr, "TskAutoDbJava::processFile: Stop request detected\n");
+        return TSK_STOP;
+    }
+
+    /* Update the current directory, which can be used to show
+     * progress.  If we get a directory, then use its name.  We
+     * do this so that when we are searching for orphan files, then
+     * we at least show $OrphanFiles as status.  The secondary check
+     * is to grab the parent folder from files once we return back 
+     * into a folder when we are doing our depth-first recursion. */
+    if (isDir(fs_file)) {
+        m_curDirAddr = fs_file->name->meta_addr;
+        tsk_take_lock(&m_curDirPathLock);
+        m_curDirPath = string(path) + fs_file->name->name;
+        tsk_release_lock(&m_curDirPathLock);
+    }
+    else if (m_curDirAddr != fs_file->name->par_addr) {
+        m_curDirAddr = fs_file->name->par_addr;
+        tsk_take_lock(&m_curDirPathLock);
+        m_curDirPath = path;
+        tsk_release_lock(&m_curDirPathLock);
+    }
+
+    /* process the attributes.  The case of having 0 attributes can occur
+     * with virtual / sparse files and HFS directories.  
+     * At some point, this can probably be cleaned
+     * up if TSK is more consistent about if there should always be an 
+     * attribute or not.  Sometimes, none of the attributes are added
+     * because of their type and we always want to add a reference to 
+     * every file. */
+    TSK_RETVAL_ENUM retval = TSK_OK;
+    m_attributeAdded = false;
+    if (tsk_fs_file_attr_getsize(fs_file) > 0) {
+        retval = processAttributes(fs_file, path);
+    }
+
+    // insert a general row if we didn't add a specific attribute one
+    if ((retval == TSK_OK) && (m_attributeAdded == false)) {
+        retval = insertFileData(fs_file, NULL, path);
+    }
+    
+    // reset the file id
+    m_curFileId = 0;
+
+    if (retval == TSK_STOP)
+        return TSK_STOP;
+    else 
+        return TSK_OK;
+}
+
+
+// we return only OK or STOP -- errors are registered only and OK is returned. 
+TSK_RETVAL_ENUM
+TskAutoDbJava::processAttribute(TSK_FS_FILE * fs_file,
+    const TSK_FS_ATTR * fs_attr, const char *path)
+{
+    // add the file metadata for the default attribute type
+    if (isDefaultType(fs_file, fs_attr)) {
+
+        if (insertFileData(fs_attr->fs_file, fs_attr, path) == TSK_ERR) {
+            registerError();
+            return TSK_OK;
+        }
+        else {
+            m_attributeAdded = true;
+        }
+    }
+
+    return TSK_OK;
+}
+
+
+/**
+* Callback invoked per every unallocated block in the filesystem
+* Creates file ranges and file entries 
+* A single file entry per consecutive range of blocks
+* @param a_block block being walked
+* @param a_ptr a pointer to an UNALLOC_BLOCK_WLK_TRACK struct
+* @returns TSK_WALK_CONT if continue, otherwise TSK_WALK_STOP if stop processing requested
+*/
+TSK_WALK_RET_ENUM TskAutoDbJava::fsWalkUnallocBlocksCb(const TSK_FS_BLOCK *a_block, void *a_ptr) {
+    UNALLOC_BLOCK_WLK_TRACK * unallocBlockWlkTrack = (UNALLOC_BLOCK_WLK_TRACK *) a_ptr;
+
+    if (unallocBlockWlkTrack->tskAutoDbJava.m_stopAllProcessing)
+        return TSK_WALK_STOP;
+
+    // initialize if this is the first block
+    if (unallocBlockWlkTrack->isStart) {
+        unallocBlockWlkTrack->isStart = false;
+        unallocBlockWlkTrack->curRangeStart = a_block->addr;
+        unallocBlockWlkTrack->prevBlock = a_block->addr;
+        unallocBlockWlkTrack->size = unallocBlockWlkTrack->fsInfo.block_size;
+        unallocBlockWlkTrack->nextSequenceNo = 0;
+        return TSK_WALK_CONT;
+    }
+
+    // We want to keep consecutive blocks in the same run, so simply update prevBlock and the size
+    // if this one is consecutive with the last call. But, if we have hit the max chunk
+    // size, then break up this set of consecutive blocks.
+    if ((a_block->addr == unallocBlockWlkTrack->prevBlock + 1) && ((unallocBlockWlkTrack->maxChunkSize <= 0) ||
+            (unallocBlockWlkTrack->size < unallocBlockWlkTrack->maxChunkSize))) {
+        unallocBlockWlkTrack->prevBlock = a_block->addr;
+		unallocBlockWlkTrack->size += unallocBlockWlkTrack->fsInfo.block_size;
+        return TSK_WALK_CONT;
+    }
+
+    // this block is not contiguous with the previous one or we've hit the maximum size; create and add a range object
+    const uint64_t rangeStartOffset = unallocBlockWlkTrack->curRangeStart * unallocBlockWlkTrack->fsInfo.block_size 
+        + unallocBlockWlkTrack->fsInfo.offset;
+    const uint64_t rangeSizeBytes = (1 + unallocBlockWlkTrack->prevBlock - unallocBlockWlkTrack->curRangeStart) 
+        * unallocBlockWlkTrack->fsInfo.block_size;
+    unallocBlockWlkTrack->ranges.push_back(TSK_DB_FILE_LAYOUT_RANGE(rangeStartOffset, rangeSizeBytes, unallocBlockWlkTrack->nextSequenceNo++));
+
+    // Return (instead of adding this run) if we are going to:
+    // a) Make one big file with all unallocated space (minChunkSize == 0)
+    // or
+    // b) Only make an unallocated file once we have at least chunkSize bytes
+    // of data in our current run (minChunkSize > 0)
+    // In either case, reset the range pointers and add this block to the size
+    if ((unallocBlockWlkTrack->minChunkSize == 0) ||
+        ((unallocBlockWlkTrack->minChunkSize > 0) &&
+        (unallocBlockWlkTrack->size < unallocBlockWlkTrack->minChunkSize))) {
+
+        unallocBlockWlkTrack->size += unallocBlockWlkTrack->fsInfo.block_size;
+        unallocBlockWlkTrack->curRangeStart = a_block->addr;
+        unallocBlockWlkTrack->prevBlock = a_block->addr;
+        return TSK_WALK_CONT;
+    }
+    
+    // at this point we are either chunking and have reached the chunk limit
+    // or we're not chunking. Either way we now add what we've got to the DB
+    int64_t fileObjId = 0;
+    TskAutoDbJava & tskAutoDbJava = unallocBlockWlkTrack->tskAutoDbJava;
+    if (-1 == tskAutoDbJava.addUnallocBlockFile(tskAutoDbJava.m_curUnallocDirId,
+        unallocBlockWlkTrack->fsObjId, unallocBlockWlkTrack->size, unallocBlockWlkTrack->ranges, fileObjId, tskAutoDbJava.m_curImgId) == TSK_ERR) {
+            // @@@ Handle error -> Don't have access to registerError() though...
+    }
+
+    // reset
+    unallocBlockWlkTrack->curRangeStart = a_block->addr;
+    unallocBlockWlkTrack->prevBlock = a_block->addr;
+    unallocBlockWlkTrack->size = unallocBlockWlkTrack->fsInfo.block_size; // The current block is part of the new range
+    unallocBlockWlkTrack->ranges.clear();
+    unallocBlockWlkTrack->nextSequenceNo = 0;
+
+    //we don't know what the last unalloc block is in advance
+    //and will handle the last range in addFsInfoUnalloc()
+    
+    return TSK_WALK_CONT;
+}
+
+
+/**
+* Add unallocated space for the given file system to the database.
+* Create files for consecutive unalloc block ranges.
+* @param dbFsInfo fs to process
+* @returns TSK_OK on success, TSK_ERR on error
+*/
+TSK_RETVAL_ENUM TskAutoDbJava::addFsInfoUnalloc(const TSK_DB_FS_INFO & dbFsInfo) {
+
+    // Unalloc space is handled separately for APFS
+    if (dbFsInfo.fType == TSK_FS_TYPE_APFS) {
+        return TSK_OK;
+    }
+
+    //open the fs we have from database
+    TSK_FS_INFO * fsInfo = tsk_fs_open_img(m_img_info, dbFsInfo.imgOffset, dbFsInfo.fType);
+    if (fsInfo == NULL) {
+        tsk_error_set_errstr2("TskAutoDbJava::addFsInfoUnalloc: error opening fs at offset %" PRIdOFF, dbFsInfo.imgOffset);
+        registerError();
+        return TSK_ERR;
+    }
+
+    //create a "fake" dir to hold the unalloc files for the fs
+    if (-1 == addUnallocFsBlockFilesParent(dbFsInfo.objId, m_curUnallocDirId, m_curImgId) == TSK_ERR) {
+        tsk_error_set_errstr2("addFsInfoUnalloc: error creating dir for unallocated space");
+        registerError();
+        return TSK_ERR;
+    }
+
+    //walk unalloc blocks on the fs and process them
+    //initialize the unalloc block walk tracking 
+    UNALLOC_BLOCK_WLK_TRACK unallocBlockWlkTrack(*this, *fsInfo, dbFsInfo.objId, m_minChunkSize, m_maxChunkSize);
+    uint8_t block_walk_ret = tsk_fs_block_walk(fsInfo, fsInfo->first_block, fsInfo->last_block, (TSK_FS_BLOCK_WALK_FLAG_ENUM)(TSK_FS_BLOCK_WALK_FLAG_UNALLOC | TSK_FS_BLOCK_WALK_FLAG_AONLY), 
+        fsWalkUnallocBlocksCb, &unallocBlockWlkTrack);
+
+    if (block_walk_ret == 1) {
+        stringstream errss;
+        tsk_fs_close(fsInfo);
+        errss << "TskAutoDbJava::addFsInfoUnalloc: error walking fs unalloc blocks, fs id: ";
+        errss << unallocBlockWlkTrack.fsObjId;
+        tsk_error_set_errstr2("%s", errss.str().c_str());
+        registerError();
+        return TSK_ERR;
+    }
+
+    if(m_stopAllProcessing) {
+        tsk_fs_close(fsInfo);
+        return TSK_OK;
+    }
+
+    // handle creation of the last range
+    // make range inclusive from curBlockStart to prevBlock
+    const uint64_t byteStart = unallocBlockWlkTrack.curRangeStart * fsInfo->block_size + fsInfo->offset;
+    const uint64_t byteLen = (1 + unallocBlockWlkTrack.prevBlock - unallocBlockWlkTrack.curRangeStart) * fsInfo->block_size;
+    unallocBlockWlkTrack.ranges.push_back(TSK_DB_FILE_LAYOUT_RANGE(byteStart, byteLen, unallocBlockWlkTrack.nextSequenceNo++));
+    int64_t fileObjId = 0;
+
+    if (-1 == addUnallocBlockFile(m_curUnallocDirId, dbFsInfo.objId, unallocBlockWlkTrack.size, unallocBlockWlkTrack.ranges, fileObjId, m_curImgId) == TSK_ERR) {
+        registerError();
+        tsk_fs_close(fsInfo);
+        return TSK_ERR;
+    }
+    
+    //cleanup 
+    tsk_fs_close(fsInfo);
+
+    return TSK_OK; 
+}
+
+/**
+* Process all unallocated space for this disk image and create "virtual" files with layouts
+* @returns TSK_OK on success, TSK_ERR on error
+*/
+TSK_RETVAL_ENUM TskAutoDbJava::addUnallocSpaceToDb() {
+    if (m_stopAllProcessing) {
+        return TSK_OK;
+    }
+
+    size_t numVsP = 0;
+    size_t numFs = 0;
+    size_t numPool = 0;
+
+    TSK_RETVAL_ENUM retFsSpace = addUnallocFsSpaceToDb(numFs);
+    TSK_RETVAL_ENUM retVsSpace = addUnallocVsSpaceToDb(numVsP);
+    TSK_RETVAL_ENUM retPoolSpace = addUnallocatedPoolBlocksToDb(numPool);
+
+    //handle case when no fs and no vs partitions and no pools
+    TSK_RETVAL_ENUM retImgFile = TSK_OK;
+    if (numVsP == 0 && numFs == 0 && numPool == 0) {
+        retImgFile = addUnallocImageSpaceToDb();
+    }
+    
+    
+    if (retFsSpace == TSK_ERR || retVsSpace == TSK_ERR || retPoolSpace == TSK_ERR || retImgFile == TSK_ERR)
+        return TSK_ERR;
+    else
+        return TSK_OK;
+}
+
+
+/**
+* Process each file system in the database and add its unallocated sectors to virtual files. 
+* @param numFs (out) number of filesystems found
+* @returns TSK_OK on success, TSK_ERR on error (if some or all fs could not be processed)
+*/
+TSK_RETVAL_ENUM TskAutoDbJava::addUnallocFsSpaceToDb(size_t & numFs) {
+
+    if(m_stopAllProcessing) {
+        return TSK_OK;
+    }
+
+    numFs = m_savedFsInfo.size();
+    TSK_RETVAL_ENUM allFsProcessRet = TSK_OK;
+    for (vector<TSK_DB_FS_INFO>::iterator it = m_savedFsInfo.begin(); it!= m_savedFsInfo.end(); ++it) {
+        if (m_stopAllProcessing) {
+            break;
+        }
+        if (addFsInfoUnalloc(*it) == TSK_ERR)
+            allFsProcessRet = TSK_ERR;
+    }
+
+    //TODO set parent_path for newly created virt dir/file hierarchy for consistency
+
+    return allFsProcessRet;
+}
+
+/**
+* Process each volume in the database and add its unallocated sectors to virtual files. 
+* @param numVsP (out) number of vs partitions found
+* @returns TSK_OK on success, TSK_ERR on error
+*/
+TSK_RETVAL_ENUM TskAutoDbJava::addUnallocVsSpaceToDb(size_t & numVsP) {
+
+    numVsP = m_savedVsPartInfo.size();
+
+    //get fs infos to see if this vspart has fs
+    for (vector<TSK_DB_VS_PART_INFO>::const_iterator it = m_savedVsPartInfo.begin();
+            it != m_savedVsPartInfo.end(); ++it) {
+        if (m_stopAllProcessing) {
+            break;
+        }
+        const TSK_DB_VS_PART_INFO &vsPart = *it;
+
+        //interested in unalloc, meta, or alloc and no fs
+        if ( (vsPart.flags & (TSK_VS_PART_FLAG_UNALLOC | TSK_VS_PART_FLAG_META)) == 0 ) {
+            //check if vspart has no fs
+            bool hasFs = false;
+            for (vector<TSK_DB_FS_INFO>::const_iterator itFs = m_savedFsInfo.begin();
+               itFs != m_savedFsInfo.end(); ++itFs) {
+               const TSK_DB_FS_INFO & fsInfo = *itFs;
+
+               TSK_DB_OBJECT* fsObjInfo = NULL;
+               if (getObjectInfo(fsInfo.objId, &fsObjInfo) == TSK_ERR ) {
+                   stringstream errss;
+                   errss << "addUnallocVsSpaceToDb: error getting object info for fs from db, objId: " << fsInfo.objId;
+                   tsk_error_set_errstr2("%s", errss.str().c_str());
+                   registerError();
+                   return TSK_ERR;
+               }
+
+               if (fsObjInfo->parObjId == vsPart.objId) {
+                   hasFs = true;
+                   break;
+               }
+            }
+        
+            if (hasFs == true) {
+                //skip processing this vspart
+                continue;
+            }
+
+            // Check if the volume contains a pool
+            bool hasPool = false;
+            for (std::map<int64_t, int64_t>::iterator iter = m_poolOffsetToParentId.begin(); iter != m_poolOffsetToParentId.end(); ++iter) {
+                if (iter->second == vsPart.objId) {
+                    hasPool = true;
+                }
+            }
+            if (hasPool) {
+                // Skip processing this vspart
+                continue;
+            }
+
+        }
+
+        // Get sector size and image offset from parent vs info
+        // Get parent id of this vs part
+        TSK_DB_OBJECT* vsPartObj = NULL;     
+        if (getObjectInfo(vsPart.objId, &vsPartObj) == TSK_ERR) {
+            stringstream errss;
+            errss << "addUnallocVsSpaceToDb: error getting object info for vs part from db, objId: " << vsPart.objId;
+            tsk_error_set_errstr2("%s", errss.str().c_str());
+            registerError();
+            return TSK_ERR;
+        }
+        if (vsPartObj == NULL) {
+            return TSK_ERR;
+        }
+
+        TSK_DB_VS_INFO* vsInfo = NULL;
+        for (vector<TSK_DB_VS_INFO>::iterator itVs = m_savedVsInfo.begin();
+                itVs != m_savedVsInfo.end(); ++itVs) {
+            TSK_DB_VS_INFO* temp_vs_info = &(*itVs);
+            if (temp_vs_info->objId == vsPartObj->parObjId) {
+                vsInfo = temp_vs_info;
+            }
+        }
+
+        if (vsInfo == NULL ) {
+            stringstream errss;
+            errss << "addUnallocVsSpaceToDb: error getting volume system info from db, objId: " << vsPartObj->parObjId;
+            tsk_error_set_errstr2("%s", errss.str().c_str());
+            registerError();
+            return TSK_ERR;
+        }
+
+        // Create an unalloc file with unalloc part, with vs part as parent
+        vector<TSK_DB_FILE_LAYOUT_RANGE> ranges;
+        const uint64_t byteStart = vsInfo->offset + vsInfo->block_size * vsPart.start;
+        const uint64_t byteLen = vsInfo->block_size * vsPart.len; 
+        TSK_DB_FILE_LAYOUT_RANGE tempRange(byteStart, byteLen, 0);
+        ranges.push_back(tempRange);
+        int64_t fileObjId = 0;
+        if (addUnallocBlockFile(vsPart.objId, 0, tempRange.byteLen, ranges, fileObjId, m_curImgId) == TSK_ERR) {
+            registerError();
+            return TSK_ERR;
+        }
+    }
+
+    return TSK_OK;
+}
+
+
+/**
+* Adds unalloc space for the image if there is no volumes and no file systems.
+*
+* @returns TSK_OK on success, TSK_ERR on error
+*/
+TSK_RETVAL_ENUM TskAutoDbJava::addUnallocImageSpaceToDb() {
+
+    const TSK_OFF_T imgSize = getImageSize();
+    if (imgSize == -1) {
+        tsk_error_set_errstr("addUnallocImageSpaceToDb: error getting current image size, can't create unalloc block file for the image.");
+        registerError();
+        return TSK_ERR;
+    }
+    else {
+        TSK_DB_FILE_LAYOUT_RANGE tempRange(0, imgSize, 0);
+        //add unalloc block file for the entire image
+        vector<TSK_DB_FILE_LAYOUT_RANGE> ranges;
+        ranges.push_back(tempRange);
+        int64_t fileObjId = 0;
+        if (-1 == addUnallocBlockFile(m_curImgId, 0, imgSize, ranges, fileObjId, m_curImgId)) {
+            return TSK_ERR;
+        }
+    }
+    return TSK_OK;
+}
+
+/**
+* Returns the directory currently being analyzed by processFile().
+* Safe to use from another thread than processFile().
+*
+* @returns curDirPath string representing currently analyzed directory
+*/
+const std::string TskAutoDbJava::getCurDir() {
+    string curDirPath;
+    tsk_take_lock(&m_curDirPathLock);
+    curDirPath = m_curDirPath;
+    tsk_release_lock(&m_curDirPathLock);
+    return curDirPath;
+}
diff --git a/bindings/java/jni/auto_db_java.h b/bindings/java/jni/auto_db_java.h
new file mode 100644
index 000000000..85a9dc69e
--- /dev/null
+++ b/bindings/java/jni/auto_db_java.h
@@ -0,0 +1,247 @@
+/*
+ ** The Sleuth Kit 
+ **
+ ** Brian Carrier [carrier <at> sleuthkit [dot] org]
+ ** Copyright (c) 2020 Brian Carrier.  All Rights reserved
+ **
+ ** This software is distributed under the Common Public License 1.0
+ **
+ */
+
+/**
+ * \file auto_db_java.h
+ * Contains the class that creates a case-level database of file system
+ * data from the JNI code.
+ */
+
+#ifndef _AUTO_DB_JAVA_H
+#define _AUTO_DB_JAVA_H
+
+#include <map>
+using std::map;
+
+#include <string>
+using std::string;
+
+#include "tsk/auto/tsk_auto_i.h"
+#include "tsk/auto/tsk_db.h"
+#include "jni.h"
+
+
+/** \internal
+ * C++ class that implements TskAuto to load file metadata into a database. 
+ * This is used by the TskCaseDb class. 
+ */
+class TskAutoDbJava :public TskAuto {
+  public:
+    TskAutoDbJava();
+    virtual ~TskAutoDbJava();
+    virtual uint8_t openImage(int, const TSK_TCHAR * const images[],
+        TSK_IMG_TYPE_ENUM, unsigned int a_ssize, const char* deviceId = NULL);
+    virtual uint8_t openImage(const char* a_deviceId = NULL);
+    virtual uint8_t openImageUtf8(int, const char *const images[],
+        TSK_IMG_TYPE_ENUM, unsigned int a_ssize, const char* deviceId = NULL);
+    virtual void closeImage();
+    void close();
+    virtual void setTz(string tzone);
+
+    virtual TSK_FILTER_ENUM filterVs(const TSK_VS_INFO * vs_info);
+    virtual TSK_FILTER_ENUM filterVol(const TSK_VS_PART_INFO * vs_part);
+    virtual TSK_FILTER_ENUM filterPool(const TSK_POOL_INFO * pool_info);
+    virtual TSK_FILTER_ENUM filterPoolVol(const TSK_POOL_VOLUME_INFO * pool_vol);
+    virtual TSK_FILTER_ENUM filterFs(TSK_FS_INFO * fs_info);
+    virtual TSK_RETVAL_ENUM processFile(TSK_FS_FILE * fs_file,
+        const char *path);
+    const std::string getCurDir();
+
+    /**
+     * Sets whether or not the file systems for an image should be added when 
+     * the image is added to the case database. The default value is true. 
+     */
+    void setAddFileSystems(bool addFileSystems);
+
+    /**
+     * Skip processing of orphans on FAT filesystems.  
+     * This will make the loading of the database much faster
+     * but you will not have all deleted files.  Default value is false. 
+     * @param noFatFsOrphans flag set to true if to skip processing orphans on FAT fs
+     */
+    virtual void setNoFatFsOrphans(bool noFatFsOrphans);
+
+    /**
+     * When enabled, records for unallocated file system space will be added to the database. Default value is false.
+     * @param addUnallocSpace If true, create records for contiguous unallocated file system sectors. 
+     */
+    virtual void setAddUnallocSpace(bool addUnallocSpace);
+
+    /**
+     * When enabled, records for unallocated file system space will be added to the database. Default value is false.
+     * @param addUnallocSpace If true, create records for contiguous unallocated file system sectors.
+     * @param minChunkSize the number of bytes to group unallocated data into. A value of 0 will create
+     * one large chunk and group only on volume boundaries. A value of -1 will group each consecutive
+     * chunk.
+     */
+    virtual void setAddUnallocSpace(bool addUnallocSpace, int64_t minChunkSize);
+
+    /**
+    * When enabled, records for unallocated file system space will be added to the database with the given parameters.
+    * Automatically sets the flag to create records for contiguous unallocated file system sectors.
+    * @param minChunkSize the number of bytes to group unallocated data into. A value of 0 will create
+    * one large chunk and group only on volume boundaries. A value of -1 will group each consecutive
+    * chunk.
+    * @param maxChunkSize the maximum number of bytes in one record of unallocated data. A value of -1 will not
+    * split the records based on size
+    */
+    virtual void setAddUnallocSpace(int64_t minChunkSize, int64_t maxChunkSize);
+
+    uint8_t addFilesInImgToDb();
+
+    /**
+     * 
+     */
+    uint8_t startAddImage(int numImg, const TSK_TCHAR * const imagePaths[],
+        TSK_IMG_TYPE_ENUM imgType, unsigned int sSize, const char* deviceId = NULL);
+    uint8_t startAddImage(TSK_IMG_INFO * img_info, const char* deviceId = NULL);
+#ifdef WIN32
+    uint8_t startAddImage(int numImg, const char *const imagePaths[],
+        TSK_IMG_TYPE_ENUM imgType, unsigned int sSize, const char* deviceId = NULL);
+#endif
+    void stopAddImage();
+
+    int64_t getImageID();
+
+    TSK_RETVAL_ENUM initializeJni(JNIEnv *, jobject);
+
+  private:
+    int64_t m_curImgId;     ///< Object ID of image currently being processed
+    int64_t m_curVsId;      ///< Object ID of volume system currently being processed
+    int64_t m_curVolId;     ///< Object ID of volume currently being processed
+    int64_t m_curPoolVol;   ///< Object ID of the pool volume currently being processed
+    int64_t m_curPoolVs;    ///< Object ID of the pool volume system currently being processed
+    int64_t m_curFsId;      ///< Object ID of file system currently being processed
+    int64_t m_curFileId;    ///< Object ID of file currently being processed
+    TSK_INUM_T m_curDirAddr;		///< Meta address the directory currently being processed
+    int64_t m_curUnallocDirId;	
+    string m_curDirPath;		//< Path of the current directory being processed
+    tsk_lock_t m_curDirPathLock; //< protects concurrent access to m_curDirPath
+    string m_curImgTZone;
+    bool m_vsFound;
+    bool m_volFound;
+    bool m_poolFound;
+    bool m_stopped;
+    bool m_addFileSystems;
+    bool m_noFatFsOrphans;
+    bool m_addUnallocSpace;
+    int64_t m_minChunkSize; ///< -1 for no minimum, 0 for no chunking at all, greater than 0 to wait for that number of chunks before writing to the database 
+    int64_t m_maxChunkSize; ///< Max number of unalloc bytes to process before writing to the database, even if there is no natural break. -1 for no chunking
+    bool m_foundStructure;  ///< Set to true when we find either a volume or file system
+    bool m_attributeAdded; ///< Set to true when an attribute was added by processAttributes
+
+    // These are used to write unallocated blocks for pools at the end of the add image
+    // process. We can't load the pool_info objects directly from the database so we will
+    // store info about them here.
+    std::map<int64_t, int64_t> m_poolOffsetToParentId;
+    std::map<int64_t, int64_t> m_poolOffsetToVsId;
+
+    // Used to look up object IDs for files
+    #define MAX_PATH_LENGTH_JAVA_DB_LOOKUP 2048
+    char parent_name[MAX_PATH_LENGTH_JAVA_DB_LOOKUP];
+    char parent_path[MAX_PATH_LENGTH_JAVA_DB_LOOKUP + 2]; // +2 is for leading slash and trailing slash
+    map<int64_t, map<TSK_INUM_T, map<uint32_t, map<uint32_t, int64_t> > > > m_parentDirIdCache; //maps a file system ID to a map, which maps a directory file system meta address to a map, which maps a sequence ID to a map, which maps a hash of a path to its object ID in the database
+    int64_t findParObjId(const TSK_FS_FILE* fs_file, const char* parentPath, const int64_t& fsObjId);
+    bool getParentPathAndName(const char *path, const char **ret_parent_path, const char **ret_name);
+    void storeObjId(const int64_t& fsObjId, const TSK_FS_FILE* fs_file, const char* path, const int64_t& objId);
+    uint32_t hash(const unsigned char* str);
+
+    // JNI data
+    JNIEnv * m_jniEnv = NULL;
+    jclass m_callbackClass = NULL;
+    jobject m_javaDbObj = NULL;
+    jmethodID m_addImageMethodID = NULL;
+    jmethodID m_addImageNameMethodID = NULL;
+    jmethodID m_addVolumeSystemMethodID = NULL;
+    jmethodID m_addVolumeMethodID = NULL;
+    jmethodID m_addPoolMethodID = NULL;
+    jmethodID m_addFileSystemMethodID = NULL;
+    jmethodID m_addFileMethodID = NULL;
+    jmethodID m_getParentIdMethodID = NULL;
+    jmethodID m_addUnallocParentMethodID = NULL;
+    jmethodID m_addLayoutFileMethodID = NULL;
+    jmethodID m_addLayoutFileRangeMethodID = NULL;
+
+    // Cached objects
+    vector<TSK_DB_FS_INFO> m_savedFsInfo;
+    vector<TSK_DB_VS_INFO> m_savedVsInfo;
+    vector<TSK_DB_VS_PART_INFO> m_savedVsPartInfo;
+    vector<TSK_DB_OBJECT> m_savedObjects;
+
+    void saveObjectInfo(uint64_t objId, uint64_t parObjId, TSK_DB_OBJECT_TYPE_ENUM type);
+    TSK_RETVAL_ENUM getObjectInfo(uint64_t objId, TSK_DB_OBJECT** obj_info);
+
+    // prevent copying until we add proper logic to handle it
+    TskAutoDbJava(const TskAutoDbJava&);
+    TskAutoDbJava & operator=(const TskAutoDbJava&);
+
+    //internal structure to keep track of temp. unalloc block range
+    typedef struct _UNALLOC_BLOCK_WLK_TRACK {
+        _UNALLOC_BLOCK_WLK_TRACK(TskAutoDbJava & tskAutoDbJava, const TSK_FS_INFO & fsInfo, const int64_t fsObjId, int64_t minChunkSize, int64_t maxChunkSize)
+            : tskAutoDbJava(tskAutoDbJava),fsInfo(fsInfo),fsObjId(fsObjId),curRangeStart(0), minChunkSize(minChunkSize), maxChunkSize(maxChunkSize), prevBlock(0), isStart(true), nextSequenceNo(0) {}
+        TskAutoDbJava & tskAutoDbJava;
+        const TSK_FS_INFO & fsInfo;
+        const int64_t fsObjId;
+        vector<TSK_DB_FILE_LAYOUT_RANGE> ranges;																																										
+        TSK_DADDR_T curRangeStart;
+        int64_t size;
+        const int64_t minChunkSize;
+        const int64_t maxChunkSize;
+        TSK_DADDR_T prevBlock;
+        bool isStart;
+        uint32_t nextSequenceNo;
+    } UNALLOC_BLOCK_WLK_TRACK;
+
+    uint8_t addImageDetails(const char *);
+    TSK_RETVAL_ENUM insertFileData(TSK_FS_FILE * fs_file,
+        const TSK_FS_ATTR *, const char *path);
+    virtual TSK_RETVAL_ENUM processAttribute(TSK_FS_FILE *,
+        const TSK_FS_ATTR * fs_attr, const char *path);
+
+    TSK_RETVAL_ENUM addUnallocatedPoolBlocksToDb(size_t & numPool);
+    static TSK_WALK_RET_ENUM fsWalkUnallocBlocksCb(const TSK_FS_BLOCK *a_block, void *a_ptr);
+    TSK_RETVAL_ENUM addFsInfoUnalloc(const TSK_DB_FS_INFO & dbFsInfo);
+    TSK_RETVAL_ENUM addUnallocFsSpaceToDb(size_t & numFs);
+    TSK_RETVAL_ENUM addUnallocVsSpaceToDb(size_t & numVsP);
+    TSK_RETVAL_ENUM addUnallocImageSpaceToDb();
+    TSK_RETVAL_ENUM addUnallocSpaceToDb();
+
+    // JNI methods
+    TSK_RETVAL_ENUM addImageInfo(int type, TSK_OFF_T ssize, int64_t & objId, const string & timezone, TSK_OFF_T size, const string &md5,
+        const string& sha1, const string& sha256, const string& deviceId, const string& collectionDetails);
+    TSK_RETVAL_ENUM addImageName(int64_t objId, char const* imgName, int sequence);
+    TSK_RETVAL_ENUM addVsInfo(const TSK_VS_INFO* vs_info, int64_t parObjId, int64_t& objId);
+    TSK_RETVAL_ENUM addPoolInfoAndVS(const TSK_POOL_INFO *pool_info, int64_t parObjId, int64_t& objId);
+    TSK_RETVAL_ENUM addPoolVolumeInfo(const TSK_POOL_VOLUME_INFO* pool_vol, int64_t parObjId, int64_t& objId);
+    TSK_RETVAL_ENUM addVolumeInfo(const TSK_VS_PART_INFO* vs_part, int64_t parObjId, int64_t& objId);
+    TSK_RETVAL_ENUM addFsInfo(const TSK_FS_INFO* fs_info, int64_t parObjId, int64_t& objId);
+    TSK_RETVAL_ENUM addFsFile(TSK_FS_FILE* fs_file,
+        const TSK_FS_ATTR* fs_attr, const char* path,
+        int64_t fsObjId, int64_t& objId, int64_t dataSourceObjId);
+    TSK_RETVAL_ENUM addFile(TSK_FS_FILE* fs_file,
+        const TSK_FS_ATTR* fs_attr, const char* path,
+        int64_t fsObjId, int64_t parObjId,
+        int64_t& objId, int64_t dataSourceObjId);
+    TSK_RETVAL_ENUM addFileWithLayoutRange(const TSK_DB_FILES_TYPE_ENUM dbFileType, const int64_t parentObjId,
+        const int64_t fsObjId, const uint64_t size,
+        vector<TSK_DB_FILE_LAYOUT_RANGE>& ranges, int64_t& objId,
+        int64_t dataSourceObjId);
+    TSK_RETVAL_ENUM addUnallocBlockFile(const int64_t parentObjId, const int64_t fsObjId, const uint64_t size,
+        vector<TSK_DB_FILE_LAYOUT_RANGE>& ranges, int64_t& objId,
+        int64_t dataSourceObjId);
+    TSK_RETVAL_ENUM addUnusedBlockFile(const int64_t parentObjId, const int64_t fsObjId, const uint64_t size,
+        vector<TSK_DB_FILE_LAYOUT_RANGE>& ranges, int64_t& objId,
+        int64_t dataSourceObjId);
+    TSK_RETVAL_ENUM addUnallocFsBlockFilesParent(const int64_t fsObjId, int64_t& objId, int64_t dataSourceObjId);
+    TSK_RETVAL_ENUM addUnallocatedPoolVolume(int vol_index, int64_t parObjId, int64_t& objId);
+
+};
+
+#endif
diff --git a/bindings/java/src/org/sleuthkit/datamodel/CaseDatabaseFactory.java b/bindings/java/src/org/sleuthkit/datamodel/CaseDatabaseFactory.java
new file mode 100644
index 000000000..0de142d73
--- /dev/null
+++ b/bindings/java/src/org/sleuthkit/datamodel/CaseDatabaseFactory.java
@@ -0,0 +1,616 @@
+/*
+ * Sleuth Kit Data Model
+ *
+ * Copyright 2020 Basis Technology Corp.
+ * Contact: carrier <at> sleuthkit <dot> org
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.sleuthkit.datamodel;
+
+import java.io.File;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.Properties;
+import org.sleuthkit.datamodel.SQLHelper.PostgreSQLHelper;
+import org.sleuthkit.datamodel.SQLHelper.SQLiteHelper;
+
+/**
+ * Creates a SQLite or PostgreSQL case database.
+ */
+class CaseDatabaseFactory {
+	
+	private final SQLHelper dbQueryHelper;
+	private final DbCreationHelper dbCreationHelper;
+		
+	/**
+	 * Create a new SQLite case
+	 * 
+	 * @param dbPath Full path to the database
+	 */
+	CaseDatabaseFactory(String dbPath) {		
+		this.dbQueryHelper = new SQLiteHelper();
+		this.dbCreationHelper = new SQLiteDbCreationHelper(dbPath);
+	}
+	
+	/**
+	 * Create a new PostgreSQL case
+	 * 
+	 * @param caseName    The name of the case. It will be used to create a case
+	 *                    database name that can be safely used in SQL commands
+	 *                    and will not be subject to name collisions on the case
+	 *                    database server. Use getDatabaseName to get the
+	 *                    created name.
+	 * @param info        The information to connect to the database.
+	 */
+	CaseDatabaseFactory(String caseName, CaseDbConnectionInfo info) {
+		this.dbQueryHelper = new PostgreSQLHelper();
+		this.dbCreationHelper = new PostgreSQLDbCreationHelper(caseName, info);
+	}
+	
+	/**
+	 * Creates and initializes the case database.
+	 * Currently the case must be reopened after creation.
+	 * 
+	 * @throws TskCoreException 
+	 */
+	void createCaseDatabase() throws TskCoreException {
+		createDatabase();
+		initializeSchema();
+	}
+	
+	/**
+	 * Create the database itself (if necessary)
+	 * 
+	 * @throws TskCoreException 
+	 */
+	private void createDatabase() throws TskCoreException {
+		dbCreationHelper.createDatabase();
+	}
+	
+	/**
+	 * Initialize the database schema
+	 * 
+	 * @throws TskCoreException 
+	 */
+	private void initializeSchema() throws TskCoreException {
+		try (Connection conn = dbCreationHelper.getConnection()) {
+			// Perform any needed steps before creating the tables
+			dbCreationHelper.performPreInitialization(conn);
+
+			// Add schema version
+			addDbInfo(conn);
+
+			// Add tables
+			addTables(conn);
+			dbCreationHelper.performPostTableInitialization(conn);
+		
+			// Add indexes
+			addIndexes(conn);
+		} catch (SQLException ex) {
+			throw new TskCoreException("Error initializing case database", ex);
+		}
+	}
+	
+	/**
+	 * Create and populate the db_info tables
+	 * 
+	 * @param conn the database connection
+	 * 
+	 * @throws TskCoreException 
+	 */
+	private void addDbInfo(Connection conn) throws TskCoreException {
+		CaseDbSchemaVersionNumber version = SleuthkitCase.CURRENT_DB_SCHEMA_VERSION;
+		long tskVersionNum = SleuthkitJNI.getSleuthkitVersion(); // This is the current version of TSK
+		
+		try (Statement stmt = conn.createStatement()) {
+			stmt.execute("CREATE TABLE tsk_db_info (schema_ver INTEGER, tsk_ver INTEGER, schema_minor_ver INTEGER)");
+			stmt.execute("INSERT INTO tsk_db_info (schema_ver, tsk_ver, schema_minor_ver) VALUES (" + 
+					version.getMajor() + ", " + tskVersionNum + ", " + version.getMinor() + ");");
+
+			stmt.execute("CREATE TABLE tsk_db_info_extended (name TEXT PRIMARY KEY, value TEXT NOT NULL);");
+			stmt.execute("INSERT INTO tsk_db_info_extended (name, value) VALUES ('TSK_VERSION', '" + tskVersionNum + "');");
+			stmt.execute("INSERT INTO tsk_db_info_extended (name, value) VALUES ('SCHEMA_MAJOR_VERSION', '" + version.getMajor() + "');");
+			stmt.execute("INSERT INTO tsk_db_info_extended (name, value) VALUES ('SCHEMA_MINOR_VERSION', '" + version.getMinor() + "');");
+			stmt.execute("INSERT INTO tsk_db_info_extended (name, value) VALUES ('CREATION_SCHEMA_MAJOR_VERSION', '" + version.getMajor() + "');");
+			stmt.execute("INSERT INTO tsk_db_info_extended (name, value) VALUES ('CREATION_SCHEMA_MINOR_VERSION', '" + version.getMinor() + "');");
+		} catch (SQLException ex) {
+			throw new TskCoreException("Error initializing db_info tables", ex);
+		}
+	}
+	
+	/**
+	 * Add and initialize the database tables 
+	 * 
+	 * @param conn the database connection
+	 * 
+	 * @throws TskCoreException 
+	 */
+	private void addTables(Connection conn) throws TskCoreException {
+		try (Statement stmt = conn.createStatement()) {
+			createFileTables(stmt);
+			createArtifactTables(stmt);
+			createTagTables(stmt);
+			createIngestTables(stmt);
+			createAccountTables(stmt);
+			createEventTables(stmt);
+		} catch (SQLException ex) {
+			throw new TskCoreException("Error initializing tables", ex);
+		}
+	}
+	
+	private void createFileTables(Statement stmt) throws SQLException {
+		// The UNIQUE here on the object ID is to create an index
+		stmt.execute("CREATE TABLE tsk_objects (obj_id " + dbQueryHelper.getPrimaryKey() + " PRIMARY KEY, par_obj_id " + dbQueryHelper.getBigIntType() 
+				+ ", type INTEGER NOT NULL, UNIQUE (obj_id), FOREIGN KEY (par_obj_id) REFERENCES tsk_objects (obj_id) ON DELETE CASCADE)");
+
+		stmt.execute("CREATE TABLE tsk_image_info (obj_id " + dbQueryHelper.getPrimaryKey() + " PRIMARY KEY, type INTEGER, ssize INTEGER, " 
+				+ "tzone TEXT, size " + dbQueryHelper.getBigIntType() + ", md5 TEXT, sha1 TEXT, sha256 TEXT, display_name TEXT, "
+				+ "FOREIGN KEY(obj_id) REFERENCES tsk_objects(obj_id) ON DELETE CASCADE)");
+
+		stmt.execute("CREATE TABLE tsk_image_names (obj_id " + dbQueryHelper.getBigIntType() + " NOT NULL, name TEXT NOT NULL, "
+				+ "sequence INTEGER NOT NULL, FOREIGN KEY(obj_id) REFERENCES tsk_objects(obj_id) ON DELETE CASCADE)");
+
+		stmt.execute("CREATE TABLE tsk_vs_info (obj_id " + dbQueryHelper.getPrimaryKey() + " PRIMARY KEY, vs_type INTEGER NOT NULL, "
+				+ "img_offset " + dbQueryHelper.getBigIntType() + " NOT NULL, block_size " + dbQueryHelper.getBigIntType() + " NOT NULL, "
+				+ "FOREIGN KEY(obj_id) REFERENCES tsk_objects(obj_id) ON DELETE CASCADE)");
+
+		stmt.execute("CREATE TABLE tsk_vs_parts (obj_id " + dbQueryHelper.getPrimaryKey() + " PRIMARY KEY, "
+				+ "addr " + dbQueryHelper.getBigIntType() + " NOT NULL, start " + dbQueryHelper.getBigIntType() + " NOT NULL, "
+				+ "length " + dbQueryHelper.getBigIntType() + " NOT NULL, "
+				+ dbQueryHelper.getVSDescColName() + " TEXT, "
+				+ "flags INTEGER NOT NULL, FOREIGN KEY(obj_id) REFERENCES tsk_objects(obj_id) ON DELETE CASCADE);");		
+		
+		stmt.execute("CREATE TABLE tsk_pool_info (obj_id " + dbQueryHelper.getPrimaryKey() + " PRIMARY KEY, "
+				+ "pool_type INTEGER NOT NULL, FOREIGN KEY(obj_id) REFERENCES tsk_objects(obj_id) ON DELETE CASCADE);");
+
+		stmt.execute("CREATE TABLE data_source_info (obj_id " + dbQueryHelper.getBigIntType() + " PRIMARY KEY, device_id TEXT NOT NULL, "
+				+ "time_zone TEXT NOT NULL, acquisition_details TEXT, FOREIGN KEY(obj_id) REFERENCES tsk_objects(obj_id) ON DELETE CASCADE)");
+
+		stmt.execute("CREATE TABLE tsk_fs_info (obj_id " + dbQueryHelper.getPrimaryKey() + " PRIMARY KEY, "
+				+ "img_offset " + dbQueryHelper.getBigIntType() + " NOT NULL, fs_type INTEGER NOT NULL, "
+				+ "block_size " + dbQueryHelper.getBigIntType() + " NOT NULL, "
+				+ "block_count " + dbQueryHelper.getBigIntType() + " NOT NULL, root_inum " + dbQueryHelper.getBigIntType() + " NOT NULL, "
+				+ "first_inum " + dbQueryHelper.getBigIntType() + " NOT NULL, last_inum " + dbQueryHelper.getBigIntType() + " NOT NULL, "
+				+ "display_name TEXT, FOREIGN KEY(obj_id) REFERENCES tsk_objects(obj_id) ON DELETE CASCADE)");
+
+		stmt.execute("CREATE TABLE tsk_files (obj_id " + dbQueryHelper.getPrimaryKey() + " PRIMARY KEY, "
+				+ "fs_obj_id " + dbQueryHelper.getBigIntType() + ", data_source_obj_id " + dbQueryHelper.getBigIntType() + " NOT NULL, "
+				+ "attr_type INTEGER, attr_id INTEGER, " 
+				+ "name TEXT NOT NULL, meta_addr " + dbQueryHelper.getBigIntType() + ", meta_seq " + dbQueryHelper.getBigIntType() + ", "
+				+ "type INTEGER, has_layout INTEGER, has_path INTEGER, "
+				+ "dir_type INTEGER, meta_type INTEGER, dir_flags INTEGER, meta_flags INTEGER, size " + dbQueryHelper.getBigIntType() + ", "
+				+ "ctime " + dbQueryHelper.getBigIntType() + ", "
+				+ "crtime " + dbQueryHelper.getBigIntType() + ", atime " + dbQueryHelper.getBigIntType() + ", "
+				+ "mtime " + dbQueryHelper.getBigIntType() + ", mode INTEGER, uid INTEGER, gid INTEGER, md5 TEXT, known INTEGER, "
+				+ "parent_path TEXT, mime_type TEXT, extension TEXT, "
+				+ "FOREIGN KEY(obj_id) REFERENCES tsk_objects(obj_id) ON DELETE CASCADE, "
+				+ "FOREIGN KEY(fs_obj_id) REFERENCES tsk_fs_info(obj_id) ON DELETE CASCADE, "
+				+ "FOREIGN KEY(data_source_obj_id) REFERENCES data_source_info(obj_id) ON DELETE CASCADE)");
+
+		stmt.execute("CREATE TABLE file_encoding_types (encoding_type INTEGER PRIMARY KEY, name TEXT NOT NULL)");
+
+		stmt.execute("CREATE TABLE tsk_files_path (obj_id " + dbQueryHelper.getPrimaryKey() + " PRIMARY KEY, path TEXT NOT NULL, "
+				+ "encoding_type INTEGER NOT NULL, FOREIGN KEY(encoding_type) references file_encoding_types(encoding_type), "
+				+ "FOREIGN KEY(obj_id) REFERENCES tsk_objects(obj_id) ON DELETE CASCADE)");
+
+		stmt.execute("CREATE TABLE tsk_files_derived (obj_id " + dbQueryHelper.getPrimaryKey() + " PRIMARY KEY, "
+				+ "derived_id " + dbQueryHelper.getBigIntType() + " NOT NULL, rederive TEXT, "
+				+ "FOREIGN KEY(obj_id) REFERENCES tsk_objects(obj_id) ON DELETE CASCADE)");
+
+		stmt.execute("CREATE TABLE tsk_files_derived_method (derived_id " + dbQueryHelper.getPrimaryKey() + " PRIMARY KEY, "
+				+ "tool_name TEXT NOT NULL, tool_version TEXT NOT NULL, other TEXT)");		
+		
+		stmt.execute("CREATE TABLE tsk_file_layout (obj_id " + dbQueryHelper.getBigIntType() + " NOT NULL, "
+				+ "byte_start " + dbQueryHelper.getBigIntType() + " NOT NULL, byte_len " + dbQueryHelper.getBigIntType() + " NOT NULL, "
+				+ "sequence INTEGER NOT NULL, FOREIGN KEY(obj_id) REFERENCES tsk_objects(obj_id) ON DELETE CASCADE);");
+		
+		stmt.execute("CREATE TABLE reports (obj_id " + dbQueryHelper.getPrimaryKey() + " PRIMARY KEY, path TEXT NOT NULL, "
+				+ "crtime INTEGER NOT NULL, src_module_name TEXT NOT NULL, report_name TEXT NOT NULL, "
+				+ "FOREIGN KEY(obj_id) REFERENCES tsk_objects(obj_id) ON DELETE CASCADE);");		
+	}
+	
+	private void createArtifactTables(Statement stmt) throws SQLException {
+		stmt.execute("CREATE TABLE blackboard_artifact_types (artifact_type_id " + dbQueryHelper.getPrimaryKey() + " PRIMARY KEY, "
+				+ "type_name TEXT NOT NULL, display_name TEXT)");
+
+		stmt.execute("CREATE TABLE blackboard_attribute_types (attribute_type_id " + dbQueryHelper.getPrimaryKey() + " PRIMARY KEY, "
+				+ "type_name TEXT NOT NULL, display_name TEXT, value_type INTEGER NOT NULL)");
+
+		stmt.execute("CREATE TABLE review_statuses (review_status_id INTEGER PRIMARY KEY, "
+				+ "review_status_name TEXT NOT NULL, "
+				+ "display_name TEXT NOT NULL)");
+
+		stmt.execute("CREATE TABLE blackboard_artifacts (artifact_id " + dbQueryHelper.getPrimaryKey() + " PRIMARY KEY, "
+				+ "obj_id " + dbQueryHelper.getBigIntType() + " NOT NULL, "
+				+ "artifact_obj_id " + dbQueryHelper.getBigIntType() + " NOT NULL, "
+				+ "data_source_obj_id " + dbQueryHelper.getBigIntType() + " NOT NULL, "
+				+ "artifact_type_id " + dbQueryHelper.getBigIntType() + " NOT NULL, "
+				+ "review_status_id INTEGER NOT NULL, "
+				+ "FOREIGN KEY(obj_id) REFERENCES tsk_objects(obj_id) ON DELETE CASCADE, "
+				+ "FOREIGN KEY(artifact_obj_id) REFERENCES tsk_objects(obj_id) ON DELETE CASCADE, "
+				+ "FOREIGN KEY(data_source_obj_id) REFERENCES tsk_objects(obj_id) ON DELETE CASCADE, "
+				+ "FOREIGN KEY(artifact_type_id) REFERENCES blackboard_artifact_types(artifact_type_id), "
+				+ "FOREIGN KEY(review_status_id) REFERENCES review_statuses(review_status_id))");
+
+		/* Binary representation of BYTEA is a bunch of bytes, which could
+		* include embedded nulls so we have to pay attention to field length.
+		* http://www.postgresql.org/docs/9.4/static/libpq-example.html
+		*/
+		stmt.execute("CREATE TABLE blackboard_attributes (artifact_id " + dbQueryHelper.getBigIntType() + " NOT NULL, "
+				+ "artifact_type_id " + dbQueryHelper.getBigIntType() + " NOT NULL, "
+				+ "source TEXT, context TEXT, attribute_type_id " + dbQueryHelper.getBigIntType() + " NOT NULL, "
+				+ "value_type INTEGER NOT NULL, value_byte " + dbQueryHelper.getBlobType() + ", "
+				+ "value_text TEXT, value_int32 INTEGER, value_int64 " + dbQueryHelper.getBigIntType() + ", value_double NUMERIC(20, 10), "
+				+ "FOREIGN KEY(artifact_id) REFERENCES blackboard_artifacts(artifact_id) ON DELETE CASCADE, "
+				+ "FOREIGN KEY(artifact_type_id) REFERENCES blackboard_artifact_types(artifact_type_id), "
+				+ "FOREIGN KEY(attribute_type_id) REFERENCES blackboard_attribute_types(attribute_type_id))");		
+	}
+	
+	private void createTagTables(Statement stmt) throws SQLException {
+		stmt.execute("CREATE TABLE tsk_tag_sets (tag_set_id " + dbQueryHelper.getPrimaryKey() + " PRIMARY KEY, name TEXT UNIQUE)");
+		stmt.execute("CREATE TABLE tag_names (tag_name_id " + dbQueryHelper.getPrimaryKey() + " PRIMARY KEY, display_name TEXT UNIQUE, "
+				+ "description TEXT NOT NULL, color TEXT NOT NULL, knownStatus INTEGER NOT NULL,"
+				+ " tag_set_id INTEGER, FOREIGN KEY(tag_set_id) REFERENCES tsk_tag_sets(tag_set_id) ON DELETE SET NULL)");
+		
+		stmt.execute("CREATE TABLE tsk_examiners (examiner_id " + dbQueryHelper.getPrimaryKey() + " PRIMARY KEY, "
+				+ "login_name TEXT NOT NULL, display_name TEXT, UNIQUE(login_name))");
+
+		stmt.execute("CREATE TABLE content_tags (tag_id " + dbQueryHelper.getPrimaryKey() + " PRIMARY KEY, "
+				+ "obj_id " + dbQueryHelper.getBigIntType() + " NOT NULL, tag_name_id " + dbQueryHelper.getBigIntType() + " NOT NULL, "
+				+ "comment TEXT NOT NULL, begin_byte_offset " + dbQueryHelper.getBigIntType() + " NOT NULL, "
+				+ "end_byte_offset " + dbQueryHelper.getBigIntType() + " NOT NULL, "
+				+ "examiner_id " + dbQueryHelper.getBigIntType() + ", "
+				+ "FOREIGN KEY(examiner_id) REFERENCES tsk_examiners(examiner_id) ON DELETE CASCADE, "
+				+ "FOREIGN KEY(obj_id) REFERENCES tsk_objects(obj_id) ON DELETE CASCADE, "
+				+ "FOREIGN KEY(tag_name_id) REFERENCES tag_names(tag_name_id) ON DELETE CASCADE)");
+
+		stmt.execute("CREATE TABLE blackboard_artifact_tags (tag_id " + dbQueryHelper.getPrimaryKey() + " PRIMARY KEY, "
+				+ "artifact_id " + dbQueryHelper.getBigIntType() + " NOT NULL, tag_name_id " + dbQueryHelper.getBigIntType() + " NOT NULL, "
+				+ "comment TEXT NOT NULL,  examiner_id " + dbQueryHelper.getBigIntType() + ", "
+				+ "FOREIGN KEY(examiner_id) REFERENCES tsk_examiners(examiner_id) ON DELETE CASCADE, "
+				+ "FOREIGN KEY(artifact_id) REFERENCES blackboard_artifacts(artifact_id) ON DELETE CASCADE, "
+				+ "FOREIGN KEY(tag_name_id) REFERENCES tag_names(tag_name_id) ON DELETE CASCADE)");
+	}
+	
+	/**
+	 * Add indexes
+	 * 
+	 * @param conn the database connection
+	 * @throws TskCoreException 
+	 */
+	private void addIndexes(Connection conn) throws TskCoreException {
+		try (Statement stmt = conn.createStatement()) {
+			// tsk_objects index
+			stmt.execute("CREATE INDEX parObjId ON tsk_objects(par_obj_id)");
+			
+			// file layout index
+			stmt.execute("CREATE INDEX layout_objID ON tsk_file_layout(obj_id)");
+			
+			// blackboard indexes
+			stmt.execute("CREATE INDEX artifact_objID ON blackboard_artifacts(obj_id)");
+			stmt.execute("CREATE INDEX artifact_artifact_objID ON blackboard_artifacts(artifact_obj_id)");
+			stmt.execute("CREATE INDEX artifact_typeID ON blackboard_artifacts(artifact_type_id)");
+			stmt.execute("CREATE INDEX attrsArtifactID ON blackboard_attributes(artifact_id)");
+			
+			//file type indexes
+			stmt.execute("CREATE INDEX mime_type ON tsk_files(dir_type,mime_type,type)");
+			stmt.execute("CREATE INDEX file_extension ON tsk_files(extension)");
+			
+			// account indexes
+			stmt.execute("CREATE INDEX relationships_account1 ON account_relationships(account1_id)");
+			stmt.execute("CREATE INDEX relationships_account2 ON account_relationships(account2_id)");
+			stmt.execute("CREATE INDEX relationships_relationship_source_obj_id ON account_relationships(relationship_source_obj_id)");
+			stmt.execute("CREATE INDEX relationships_date_time ON account_relationships(date_time)");
+			stmt.execute("CREATE INDEX relationships_relationship_type ON account_relationships(relationship_type)");
+			stmt.execute("CREATE INDEX relationships_data_source_obj_id ON account_relationships(data_source_obj_id)");
+			
+			//tsk_events indices
+			stmt.execute("CREATE INDEX events_data_source_obj_id ON tsk_event_descriptions(data_source_obj_id)");
+			stmt.execute("CREATE INDEX events_content_obj_id ON tsk_event_descriptions(content_obj_id)");
+			stmt.execute("CREATE INDEX events_artifact_id ON tsk_event_descriptions(artifact_id)");
+			stmt.execute("CREATE INDEX events_sub_type_time ON tsk_events(event_type_id,  time)");
+			stmt.execute("CREATE INDEX events_time ON tsk_events(time)");
+		} catch (SQLException ex) {
+			throw new TskCoreException("Error initializing db_info tables", ex);
+		}
+	}
+	
+	private void createIngestTables(Statement stmt) throws SQLException {
+		stmt.execute("CREATE TABLE ingest_module_types (type_id INTEGER PRIMARY KEY, type_name TEXT NOT NULL)");
+            
+		stmt.execute("CREATE TABLE ingest_job_status_types (type_id INTEGER PRIMARY KEY, type_name TEXT NOT NULL)");
+
+		stmt.execute("CREATE TABLE ingest_modules (ingest_module_id " + dbQueryHelper.getPrimaryKey() + " PRIMARY KEY, "
+				+ "display_name TEXT NOT NULL, unique_name TEXT UNIQUE NOT NULL, type_id INTEGER NOT NULL, "
+				+ "version TEXT NOT NULL, FOREIGN KEY(type_id) REFERENCES ingest_module_types(type_id) ON DELETE CASCADE);");
+
+		stmt.execute("CREATE TABLE ingest_jobs (ingest_job_id " + dbQueryHelper.getPrimaryKey() + " PRIMARY KEY, "
+				+ "obj_id " + dbQueryHelper.getBigIntType() + " NOT NULL, host_name TEXT NOT NULL, "
+				+ "start_date_time " + dbQueryHelper.getBigIntType() + " NOT NULL, "
+				+ "end_date_time " + dbQueryHelper.getBigIntType() + " NOT NULL, status_id INTEGER NOT NULL, "
+				+ "settings_dir TEXT, FOREIGN KEY(obj_id) REFERENCES tsk_objects(obj_id) ON DELETE CASCADE, "
+				+ "FOREIGN KEY(status_id) REFERENCES ingest_job_status_types(type_id) ON DELETE CASCADE);");
+
+		stmt.execute("CREATE TABLE ingest_job_modules (ingest_job_id INTEGER, ingest_module_id INTEGER, "
+				+ "pipeline_position INTEGER, PRIMARY KEY(ingest_job_id, ingest_module_id), "
+				+ "FOREIGN KEY(ingest_job_id) REFERENCES ingest_jobs(ingest_job_id) ON DELETE CASCADE, "
+				+ "FOREIGN KEY(ingest_module_id) REFERENCES ingest_modules(ingest_module_id) ON DELETE CASCADE);");
+	}
+	
+	private void createAccountTables(Statement stmt) throws SQLException {
+		stmt.execute("CREATE TABLE account_types (account_type_id " + dbQueryHelper.getPrimaryKey() + " PRIMARY KEY, "
+				+ "type_name TEXT UNIQUE NOT NULL, display_name TEXT NOT NULL)");
+
+		stmt.execute("CREATE TABLE accounts (account_id " + dbQueryHelper.getPrimaryKey() + " PRIMARY KEY, "
+				+ "account_type_id INTEGER NOT NULL, account_unique_identifier TEXT NOT NULL, "
+				+ "UNIQUE(account_type_id, account_unique_identifier), "
+				+ "FOREIGN KEY(account_type_id) REFERENCES account_types(account_type_id))");
+
+		stmt.execute("CREATE TABLE account_relationships (relationship_id " + dbQueryHelper.getPrimaryKey() + " PRIMARY KEY, "
+				+ "account1_id INTEGER NOT NULL, account2_id INTEGER NOT NULL, "
+				+ "relationship_source_obj_id " + dbQueryHelper.getBigIntType() + " NOT NULL, "
+				+ "date_time " + dbQueryHelper.getBigIntType() + ", relationship_type INTEGER NOT NULL, "
+				+ "data_source_obj_id " + dbQueryHelper.getBigIntType() + " NOT NULL, "
+				+ "UNIQUE(account1_id, account2_id, relationship_source_obj_id), "
+				+ "FOREIGN KEY(account1_id) REFERENCES accounts(account_id), "
+				+ "FOREIGN KEY(account2_id) REFERENCES accounts(account_id), "
+				+ "FOREIGN KEY(relationship_source_obj_id) REFERENCES tsk_objects(obj_id) ON DELETE CASCADE, "
+				+ "FOREIGN KEY(data_source_obj_id) REFERENCES tsk_objects(obj_id) ON DELETE CASCADE)");
+	}
+	
+	private void createEventTables(Statement stmt) throws SQLException {
+		stmt.execute("CREATE TABLE tsk_event_types ("
+				+ " event_type_id " + dbQueryHelper.getPrimaryKey() + " PRIMARY KEY,"
+				+ " display_name TEXT UNIQUE NOT NULL , "
+				+ " super_type_id INTEGER REFERENCES tsk_event_types(event_type_id) )");
+
+		stmt.execute("INSERT INTO tsk_event_types(event_type_id, display_name, super_type_id) VALUES(0, 'Event Types', null)");
+		stmt.execute("INSERT INTO tsk_event_types(event_type_id, display_name, super_type_id) VALUES(1, 'File System', 0)");
+		stmt.execute("INSERT INTO tsk_event_types(event_type_id, display_name, super_type_id) VALUES(2, 'Web Activity', 0)");
+		stmt.execute("INSERT INTO tsk_event_types(event_type_id, display_name, super_type_id) VALUES(3, 'Misc Types', 0)");
+		stmt.execute("INSERT INTO tsk_event_types(event_type_id, display_name, super_type_id) VALUES(4, 'Modified', 1)");
+		stmt.execute("INSERT INTO tsk_event_types(event_type_id, display_name, super_type_id) VALUES(5, 'Accessed', 1)");
+		stmt.execute("INSERT INTO tsk_event_types(event_type_id, display_name, super_type_id) VALUES(6, 'Created', 1)");
+		stmt.execute("INSERT INTO tsk_event_types(event_type_id, display_name, super_type_id) VALUES(7, 'Changed', 1)");
+		/*
+		* Regarding the timeline event tables schema, note that several columns
+		* in the tsk_event_descriptions table seem, at first glance, to be
+		* attributes of events rather than their descriptions and would appear
+		* to belong in tsk_events table instead. The rationale for putting the
+		* data source object ID, content object ID, artifact ID and the flags
+		* indicating whether or not the event source has a hash set hit or is
+		* tagged were motivated by the fact that these attributes are identical
+		* for each event in a set of file system file MAC time events. The
+		* decision was made to avoid duplication and save space by placing this
+		* data in the tsk_event-descriptions table.
+		*/			
+		stmt.execute(
+			"CREATE TABLE tsk_event_descriptions ( "
+			+ " event_description_id " + dbQueryHelper.getPrimaryKey() + " PRIMARY KEY, "
+			+ " full_description TEXT NOT NULL, "
+			+ " med_description TEXT, "
+			+ " short_description TEXT,"
+			+ " data_source_obj_id " + dbQueryHelper.getBigIntType() + " NOT NULL, "
+			+ " content_obj_id " + dbQueryHelper.getBigIntType() + " NOT NULL, "
+			+ " artifact_id " + dbQueryHelper.getBigIntType() + ", "
+			+ " hash_hit INTEGER NOT NULL, " //boolean 
+			+ " tagged INTEGER NOT NULL, " //boolean 
+			+ " FOREIGN KEY(data_source_obj_id) REFERENCES data_source_info(obj_id) ON DELETE CASCADE, "
+			+ " FOREIGN KEY(content_obj_id) REFERENCES tsk_objects(obj_id) ON DELETE CASCADE, "
+			+ " FOREIGN KEY(artifact_id) REFERENCES blackboard_artifacts(artifact_id) ON DELETE CASCADE,"
+			+ " UNIQUE (full_description, content_obj_id, artifact_id))");
+
+		stmt.execute(
+			"CREATE TABLE tsk_events ("
+			+ " event_id " + dbQueryHelper.getPrimaryKey() + " PRIMARY KEY, "
+			+ " event_type_id " + dbQueryHelper.getBigIntType() + " NOT NULL REFERENCES tsk_event_types(event_type_id) ,"
+			+ " event_description_id " + dbQueryHelper.getBigIntType() + " NOT NULL REFERENCES tsk_event_descriptions(event_description_id) ON DELETE CASCADE ,"
+			+ " time " + dbQueryHelper.getBigIntType() + " NOT NULL , "
+			+ " UNIQUE (event_type_id, event_description_id, time))");			
+	}
+	
+	/**
+	 * Helper class for holding code unique to each database type.
+	 */
+	private abstract class DbCreationHelper {
+		
+		/**
+		 * Create the database itself (if necessary)
+		 * 
+		 * @throws TskCoreException 
+		 */
+		abstract void createDatabase() throws TskCoreException;
+		
+		/**
+		 * Get an connection to the case database
+		 * 
+		 * @return the connection
+		 */
+		abstract Connection getConnection() throws TskCoreException;
+		
+		/**
+		 * Do any needed initialization before creating the tables.
+		 * This is where SQLite pragmas are set up.
+		 * 
+		 * @param conn The database connection
+		 * 
+		 * @throws TskCoreException 
+		 */
+		abstract void performPreInitialization(Connection conn) throws TskCoreException;
+		
+		/**
+		 * Do any additional steps after the tables are created.
+		 * 
+		 * @param conn The database connection
+		 * @throws TskCoreException 
+		 */
+		abstract void performPostTableInitialization(Connection conn) throws TskCoreException;
+	}
+	
+	/**
+	 * Implements the PostgreSQL-specific methods for creating the case
+	 */
+	private class PostgreSQLDbCreationHelper extends DbCreationHelper {
+		
+		private final static String JDBC_BASE_URI = "jdbc:postgresql://"; // NON-NLS
+		private final static String JDBC_DRIVER = "org.postgresql.Driver"; // NON-NLS
+		
+		final private String caseName;
+		final private CaseDbConnectionInfo info;
+		
+		PostgreSQLDbCreationHelper(String caseName, CaseDbConnectionInfo info) {
+			this.caseName = caseName;
+			this.info = info;
+		}
+		
+		@Override
+		void createDatabase() throws TskCoreException{
+			try(Connection conn = getPostgresConnection();
+					Statement stmt = conn.createStatement()) {
+				stmt.execute("CREATE DATABASE \"" + caseName + "\" WITH ENCODING='UTF8'");		
+			} catch (SQLException ex) {
+				throw new TskCoreException("Error creating PostgreSQL case " + caseName, ex);
+			}
+		}
+		
+		@Override
+		Connection getConnection() throws TskCoreException {
+			return getConnection(caseName);
+		}		
+		
+		/**
+		 * Connects to the "postgres" database for creating new databases.
+		 * 
+		 * @return the connection to the "postgres" database
+		 */
+		Connection getPostgresConnection() throws TskCoreException {
+			return getConnection("postgres");
+		}
+		
+		/**
+		 * Connects to an existing database with the given name.
+		 * 
+		 * @param databaseName the name of the database
+		 * 
+		 * @return the connection to the database
+		 */
+		Connection getConnection(String databaseName) throws TskCoreException {
+			
+			StringBuilder url = new StringBuilder();
+			url.append(JDBC_BASE_URI)
+				.append(info.getHost())
+				.append('/') // NON-NLS
+				.append(databaseName);
+			
+			Connection conn;
+			try {
+				Properties props = new Properties();
+				props.setProperty("user", info.getUserName());     // NON-NLS
+				props.setProperty("password", info.getPassword()); // NON-NLS
+
+				Class.forName(JDBC_DRIVER);
+				conn = DriverManager.getConnection(url.toString(), props);
+			} catch (ClassNotFoundException | SQLException ex) {
+				throw new TskCoreException("Failed to acquire ephemeral connection to PostgreSQL database " + databaseName, ex); // NON-NLS
+			}
+			return conn;
+		}	
+		
+		@Override
+		void performPreInitialization(Connection conn) throws TskCoreException {
+			// Nothing to do here for PostgreSQL
+		}
+		
+		@Override
+		void performPostTableInitialization(Connection conn) throws TskCoreException {
+			try (Statement stmt = conn.createStatement()) {
+				stmt.execute("ALTER SEQUENCE blackboard_artifacts_artifact_id_seq minvalue -9223372036854775808 restart with -9223372036854775808");
+			} catch (SQLException ex) {
+				throw new TskCoreException("Error altering artifact ID sequence", ex);
+			}
+		}
+	}
+	
+	/**
+	 * Implements the SQLite-specific methods for creating the case
+	 */
+	private class SQLiteDbCreationHelper extends DbCreationHelper {
+		
+		private final static String PRAGMA_SYNC_OFF = "PRAGMA synchronous = OFF"; // NON-NLS
+		private final static String PRAGMA_READ_UNCOMMITTED_TRUE = "PRAGMA read_uncommitted = True"; // NON-NLS
+		private final static String PRAGMA_ENCODING_UTF8 = "PRAGMA encoding = 'UTF-8'"; // NON-NLS
+		private final static String PRAGMA_PAGE_SIZE_4096 = "PRAGMA page_size = 4096"; // NON-NLS
+		private final static String PRAGMA_FOREIGN_KEYS_ON = "PRAGMA foreign_keys = ON"; // NON-NLS
+		
+		private final static String JDBC_DRIVER = "org.sqlite.JDBC"; // NON-NLS
+        private final static String JDBC_BASE_URI = "jdbc:sqlite:"; // NON-NLS
+		
+		String dbPath;
+		
+		SQLiteDbCreationHelper(String dbPath) {
+			this.dbPath = dbPath;
+		}
+		
+		@Override
+		void createDatabase() throws TskCoreException {
+			// SQLite doesn't need to explicitly create the case database but we will
+			// check that the folder exists and the database does not
+			File dbFile = new File(dbPath);
+			if (dbFile.exists()) {
+				throw new TskCoreException("Case database already exists : " + dbPath);
+			}
+
+			if (dbFile.getParentFile() != null && !dbFile.getParentFile().exists()) {
+				throw new TskCoreException("Case database folder does not exist : " + dbFile.getParent());
+			}
+		}
+		
+		@Override
+		Connection getConnection() throws TskCoreException {
+			
+			StringBuilder url = new StringBuilder();
+			url.append(JDBC_BASE_URI)
+				.append(dbPath);
+			
+			Connection conn;
+			try {
+				Class.forName(JDBC_DRIVER);
+				conn = DriverManager.getConnection(url.toString());
+			} catch (ClassNotFoundException | SQLException ex) {
+				throw new TskCoreException("Failed to acquire ephemeral connection SQLite database " + dbPath, ex); // NON-NLS
+			}
+			return conn;
+		}
+		
+		@Override
+		void performPreInitialization(Connection conn) throws TskCoreException {
+			try (Statement stmt = conn.createStatement()) {
+				stmt.execute(PRAGMA_SYNC_OFF);
+				stmt.execute(PRAGMA_READ_UNCOMMITTED_TRUE);
+				stmt.execute(PRAGMA_ENCODING_UTF8);
+				stmt.execute(PRAGMA_PAGE_SIZE_4096);
+				stmt.execute(PRAGMA_FOREIGN_KEYS_ON);
+			} catch (SQLException ex) {
+				throw new TskCoreException("Error setting pragmas", ex);
+			}
+		}	
+
+		@Override
+		void performPostTableInitialization(Connection conn) throws TskCoreException {
+			// Nothing to do here for SQLite
+		}
+	}
+}
diff --git a/bindings/java/src/org/sleuthkit/datamodel/JniDbHelper.java b/bindings/java/src/org/sleuthkit/datamodel/JniDbHelper.java
new file mode 100644
index 000000000..2e9bc7210
--- /dev/null
+++ b/bindings/java/src/org/sleuthkit/datamodel/JniDbHelper.java
@@ -0,0 +1,400 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2020 Basis Technology Corp.
+ * Contact: carrier <at> sleuthkit <dot> org
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.sleuthkit.datamodel;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import org.sleuthkit.datamodel.SleuthkitCase.CaseDbTransaction;
+
+/**
+ * This is a utility class to allow the native C code to write to the 
+ * case database. All callbacks from the native code should come through this class.
+ * Any changes to the method signatures in this class will require changes to the 
+ * native code.
+ */
+class JniDbHelper {
+    
+    private static final Logger logger = Logger.getLogger(JniDbHelper.class.getName());
+    
+    private final SleuthkitCase caseDb;
+    private CaseDbTransaction trans = null;
+    
+    private final Map<Long, Long> fsIdToRootDir = new HashMap<>();
+    
+    JniDbHelper(SleuthkitCase caseDb) {
+        this.caseDb = caseDb;
+        trans = null;
+    }
+    
+    /**
+     * Start the add image transaction
+     * 
+     * @throws TskCoreException 
+     */
+    void beginTransaction() throws TskCoreException {
+        trans = caseDb.beginTransaction();
+    }
+    
+    /**
+     * Commit the add image transaction
+     * 
+     * @throws TskCoreException 
+     */
+    void commitTransaction() throws TskCoreException {
+        trans.commit();
+        trans = null;
+    }
+    
+    /**
+     * Revert the add image transaction
+     * 
+     * @throws TskCoreException 
+     */
+    void revertTransaction() throws TskCoreException {
+        trans.rollback();
+        trans = null;
+    }        
+    
+    /**
+     * Add a new image to the database.
+     * Intended to be called from the native code during the add image process.
+     * 
+     * @param type        Type of image.
+     * @param ssize       Sector size.
+     * @param timezone    Time zone.
+     * @param size        Image size.
+     * @param md5         MD5 hash.
+     * @param sha1        SHA1 hash.
+     * @param sha256      SHA256 hash.
+     * @param deviceId    Device ID.
+     * @param collectionDetails  The collection details.
+     * 
+     * @return The object ID of the new image or -1 if an error occurred
+     */
+    long addImageInfo(int type, long ssize, String timezone, 
+            long size, String md5, String sha1, String sha256, String deviceId, 
+            String collectionDetails) {
+        try {
+            return caseDb.addImageJNI(TskData.TSK_IMG_TYPE_ENUM.valueOf(type), ssize, size,
+                    timezone, md5, sha1, sha256, deviceId, collectionDetails, trans);
+        } catch (TskCoreException ex) {
+            logger.log(Level.SEVERE, "Error adding image to the database", ex);
+            return -1;
+        }
+    }
+    
+    /**
+     * Add an image name to the database. 
+     * Intended to be called from the native code during the add image process.
+     * 
+     * @param objId    The object id of the image.
+     * @param name     The file name for the image
+     * @param sequence The sequence number of this file.
+     * 
+     * @return 0 if successful, -1 if not
+     */
+    int addImageName(long objId, String name, long sequence) {
+        try {
+            caseDb.addImageNameJNI(objId, name, sequence, trans);
+            return 0;
+        } catch (TskCoreException ex) {
+            logger.log(Level.SEVERE, "Error adding image name to the database - image obj ID: " + objId + ", image name: " + name
+                    + ", sequence: " + sequence, ex);
+            return -1;
+        }
+    }
+    
+    /**
+     * Add a volume system to the database. 
+     * Intended to be called from the native code during the add image process.
+     * 
+     * @param parentObjId
+     * @param vsType
+     * @param imgOffset
+     * @param blockSize
+     * 
+     * @return The object ID of the new volume system or -1 if an error occurred
+     */
+    long addVsInfo(long parentObjId, int vsType, long imgOffset, long blockSize) {
+        try {
+            VolumeSystem vs = caseDb.addVolumeSystem(parentObjId, TskData.TSK_VS_TYPE_ENUM.valueOf(vsType), imgOffset, blockSize, trans);
+            return vs.getId();
+        } catch (TskCoreException ex) {
+            logger.log(Level.SEVERE, "Error adding volume system to the database - parent obj ID: " + parentObjId 
+                    + ", image offset: " + imgOffset, ex);
+            return -1;
+        }
+    }
+    
+    /**
+     * Add a volume to the database. 
+     * Intended to be called from the native code during the add image process.
+     * 
+     * @param parentObjId
+     * @param addr
+     * @param start
+     * @param length
+     * @param desc
+     * @param flags
+     * 
+     * @return The object ID of the new volume or -1 if an error occurred
+     */
+    long addVolume(long parentObjId, long addr, long start, long length, String desc,
+            long flags) {
+        try {
+            Volume vol = caseDb.addVolume(parentObjId, addr, start, length, desc, flags, trans);
+            return vol.getId();
+        } catch (TskCoreException ex) {
+            logger.log(Level.SEVERE, "Error adding volume to the database - parent object ID: " + parentObjId
+                + ", addr: " + addr, ex);
+            return -1;
+        }
+    }
+
+    /**
+     * Add a pool to the database. 
+     * Intended to be called from the native code during the add image process.
+     * 
+     * @param parentObjId
+     * @param poolType
+     * 
+     * @return The object ID of the new pool or -1 if an error occurred
+     */
+    long addPool(long parentObjId, int poolType) {
+        try {
+            Pool pool = caseDb.addPool(parentObjId, TskData.TSK_POOL_TYPE_ENUM.valueOf(poolType), trans);
+            return pool.getId();
+        } catch (TskCoreException ex) {
+            logger.log(Level.SEVERE, "Error adding pool to the database - parent object ID: " + parentObjId, ex);
+            return -1;
+        }
+    }
+
+    /**
+     * Add a file system to the database. 
+     * Intended to be called from the native code during the add image process.
+     * 
+     * @param parentObjId
+     * @param imgOffset
+     * @param fsType
+     * @param blockSize
+     * @param blockCount
+     * @param rootInum
+     * @param firstInum
+     * @param lastInum
+     * 
+     * @return The object ID of the new file system or -1 if an error occurred
+     */
+    long addFileSystem(long parentObjId, long imgOffset, int fsType, long blockSize, long blockCount,
+            long rootInum, long firstInum, long lastInum) {
+        try {
+            FileSystem fs = caseDb.addFileSystem(parentObjId, imgOffset, TskData.TSK_FS_TYPE_ENUM.valueOf(fsType), blockSize, blockCount,
+                    rootInum, firstInum, lastInum, null, trans);
+            return fs.getId();
+        } catch (TskCoreException ex) {
+            logger.log(Level.SEVERE, "Error adding file system to the database - parent object ID: " + parentObjId
+                    + ", offset: " + imgOffset, ex);
+            return -1;
+        }
+    }
+
+    /**
+     * Add a file to the database. 
+     * Intended to be called from the native code during the add image process.
+     * 
+     * @param parentObjId     The parent of the file.
+     * @param fsObjId         The object ID of the file system.
+     * @param dataSourceObjId The data source object ID.
+     * @param fsType    The type.
+     * @param attrType  The type attribute given to the file by the file system.
+     * @param attrId    The type id given to the file by the file  system.
+     * @param name      The name of the file.
+     * @param metaAddr  The meta address of the file.
+     * @param metaSeq   The meta sequence number of the file.
+     * @param dirType   The type of the file, usually as reported in
+     *                     the name structure of the file system. 
+     * @param metaType  The type of the file, usually as reported in
+     *                     the metadata structure of the file system.
+     * @param dirFlags  The allocated status of the file, usually as
+     *                     reported in the name structure of the file system.
+     * @param metaFlags The allocated status of the file, usually as
+     *                     reported in the metadata structure of the file system.
+     * @param size      The file size.
+     * @param crtime    The created time.
+     * @param ctime     The last changed time
+     * @param atime     The last accessed time.
+     * @param mtime     The last modified time.
+     * @param meta_mode The modes for the file.
+     * @param gid       The group identifier.
+     * @param uid       The user identifier.
+     * @param md5       The MD5 hash.
+     * @param known     The file known status.
+     * @param escaped_path The escaped path to the file.
+     * @param extension    The file extension.
+     * 
+     * @return The object ID of the new file or -1 if an error occurred
+     */
+    long addFile(long parentObjId, 
+        long fsObjId, long dataSourceObjId,
+        int fsType,
+        int attrType, int attrId, String name,
+        long metaAddr, long metaSeq,
+        int dirType, int metaType, int dirFlags, int metaFlags,
+        long size,
+        long crtime, long ctime, long atime, long mtime,
+        int meta_mode, int gid, int uid,
+        String escaped_path, String extension) {
+        try {
+            long objId = caseDb.addFileJNI(parentObjId, 
+                fsObjId, dataSourceObjId,
+                fsType,
+                attrType, attrId, name,
+                metaAddr, metaSeq,
+                dirType, metaType, dirFlags, metaFlags,
+                size,
+                crtime, ctime, atime, mtime,
+                meta_mode, gid, uid,
+                null, TskData.FileKnown.UNKNOWN,
+                escaped_path, extension, 
+                false, trans);
+            
+            // If we're adding the root directory for the file system, cache it
+            if (parentObjId == fsObjId) {
+                fsIdToRootDir.put(fsObjId, objId);
+            }
+            return objId;
+        } catch (TskCoreException ex) {
+            logger.log(Level.SEVERE, "Error adding file to the database - parent object ID: " + parentObjId
+                    + ", file system object ID: " + fsObjId + ", name: " + name, ex);
+            return -1;
+        }
+    }
+    
+    /**
+     * Add a layout file to the database. 
+     * Intended to be called from the native code during the add image process.
+     * 
+     * @param parentObjId     The parent object ID of the layout file.
+     * @param fsObjId         The file system object ID.
+     * @param dataSourceObjId The data source object ID.
+     * @param fileType        The file type.
+     * @param name            The file name.
+     * @param size            The file size.
+     * 
+     * @return The object ID of the new file or -1 if an error occurred
+     */
+    long addLayoutFile(long parentObjId, 
+        long fsObjId, long dataSourceObjId,
+        int fileType,
+        String name, long size) {
+        try {
+            // The file system may be null for layout files
+            Long fsObjIdForDb = fsObjId;
+            if (fsObjId == 0) {
+                fsObjIdForDb = null;
+            }
+            
+            return caseDb.addFileJNI(parentObjId, 
+                fsObjIdForDb, dataSourceObjId,
+                fileType,
+                null, null, name,
+                null, null,
+                TskData.TSK_FS_NAME_TYPE_ENUM.REG.getValue(), 
+                TskData.TSK_FS_META_TYPE_ENUM.TSK_FS_META_TYPE_REG.getValue(), 
+                TskData.TSK_FS_NAME_FLAG_ENUM.UNALLOC.getValue(), 
+                TskData.TSK_FS_META_FLAG_ENUM.UNALLOC.getValue(),
+                size,
+                null, null, null, null,
+                null, null, null,
+                null, TskData.FileKnown.UNKNOWN,
+                null, null, 
+                true, trans);
+        } catch (TskCoreException ex) {
+            logger.log(Level.SEVERE, "Error adding layout file to the database - parent object ID: " + parentObjId
+                    + ", file system object ID: " + fsObjId + ", name: " + name, ex);
+            return -1;
+        }
+    }    
+    
+    /**
+     * Add a layout file range to the database. 
+     * Intended to be called from the native code during the add image process.
+     * 
+     * @param objId     Object ID of the layout file.
+     * @param byteStart Start byte.
+     * @param byteLen   Length in bytes.
+     * @param seq       Sequence number of this range.
+     * 
+     * @return 0 if successful, -1 if not
+     */
+    long addLayoutFileRange(long objId, long byteStart, long byteLen, long seq) {
+        try {
+            caseDb.addLayoutFileRangeJNI(objId, byteStart, byteLen, seq, trans);
+            return 0;
+        } catch (TskCoreException ex) {
+            logger.log(Level.SEVERE, "Error adding layout file range to the database - layout file ID: " + objId 
+                + ", byte start: " + byteStart, ex);
+            return -1;
+        }
+    }
+    
+    /**
+     * Look up the parent of a file based on metadata address and name/path.
+     * Intended to be called from the native code during the add image process.
+     * 
+     * @param metaAddr
+     * @param fsObjId
+     * @param path
+     * @param name
+     * 
+     * @return The object ID of the parent or -1 if not found
+     */
+    long findParentObjId(long metaAddr, long fsObjId, String path, String name) {
+        try {
+            return caseDb.findParentObjIdJNI(metaAddr, fsObjId, path, name, trans);
+        } catch (TskCoreException ex) {
+            logger.log(Level.WARNING, "Error looking up parent with meta addr: " + metaAddr + " and name " + name, ex);
+            return -1;
+        }
+    }
+    
+    /**
+     * Add a virtual directory to hold unallocated file system blocks.
+     * Intended to be called from the native code during the add image process.
+     * 
+     * @param fsObjId
+     * @param name
+     * 
+     * @return The object ID of the new virtual directory or -1 if an error occurred
+     */
+    long addUnallocFsBlockFilesParent(long fsObjId, String name) {
+        try {
+            if (! fsIdToRootDir.containsKey(fsObjId)) {
+                logger.log(Level.SEVERE, "Error - root directory for file system ID {0} not found", fsObjId);
+                return -1;
+            }
+            return caseDb.addVirtualDirectoryJNI(fsIdToRootDir.get(fsObjId), name, trans);
+        } catch (TskCoreException ex) {
+            logger.log(Level.SEVERE, "Error creating virtual directory " + name + " under file system ID " + fsObjId, ex);
+            return -1;
+        }
+    }
+}
diff --git a/bindings/java/src/org/sleuthkit/datamodel/SQLHelper.java b/bindings/java/src/org/sleuthkit/datamodel/SQLHelper.java
new file mode 100644
index 000000000..1ae0f8f4c
--- /dev/null
+++ b/bindings/java/src/org/sleuthkit/datamodel/SQLHelper.java
@@ -0,0 +1,91 @@
+/*
+ * Sleuth Kit Data Model
+ *
+ * Copyright 2020 Basis Technology Corp.
+ * Contact: carrier <at> sleuthkit <dot> org
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.sleuthkit.datamodel;
+
+/**
+ * Interface for classes to help create queries for SQLite or PostgreSQL
+ */
+interface SQLHelper {
+	
+	// Get the type for the primary key
+	String getPrimaryKey();
+	
+	// Get the type for big int-type data
+	String getBigIntType();
+	
+	// Get the type for blob-type data
+	String getBlobType();
+	
+	// Get the description column name for the tsk_vs_parts table.
+	// This varies between SQLite and PostgreSQL.
+	String getVSDescColName();
+
+
+	/**
+	 * PostgreSQL-specific implementation
+	 */
+	class PostgreSQLHelper implements SQLHelper {
+
+		@Override
+		public String getPrimaryKey() {
+			return "BIGSERIAL";
+		}
+
+		@Override
+		public String getBigIntType() {
+			return "BIGINT";
+		}
+
+		@Override
+		public String getBlobType() {
+			return "BYTEA";
+		}
+
+		@Override
+		public String getVSDescColName() {
+			return "descr";
+		}
+	}
+	
+	/**
+	 * SQLite-specific implementation
+	 */
+	class SQLiteHelper implements SQLHelper {
+
+		@Override
+		public String getPrimaryKey() {
+			return "INTEGER";
+		}
+
+		@Override
+		public String getBigIntType() {
+			return "INTEGER";
+		}
+
+		@Override
+		public String getBlobType() {
+			return "BLOB";
+		}
+
+		@Override
+		public String getVSDescColName() {
+			return "desc";
+		}
+	}
+}
\ No newline at end of file
-- 
GitLab