diff --git a/appveyor.yml b/appveyor.yml
index 5afea87227a05d12d7518ea85f4632519ad7d66b..b6a90b6476c3cdb0511fc83965a23e2f73919777 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -1,36 +1,76 @@
 version: 4.6.0.{build}
 
-cache:
-  - C:\Users\appveyor\.ant
-  - C:\ProgramData\chocolatey\bin
-  - C:\ProgramData\chocolatey\lib
-
-image: Visual Studio 2015
-
-install:
-  - ps: choco install nuget.commandline
-  - ps: choco install ant --ignore-dependencies
-  - ps: $env:Path="C:\Program Files\Java\jdk1.8.0\bin;$($env:Path);C:\ProgramData\chocolatey\lib\ant"
-  - set PATH=C:\Python36-x64\';%PATH%
-environment:
-  global:
-    TSK_HOME: "%APPVEYOR_BUILD_FOLDER%"
-    PYTHON: "C:\\Python36-x64"
-    JDK_HOME: C:\Program Files\Java\jdk1.8.0
-services:
-
-before_build:
-  - nuget restore win32\libtsk -PackagesDirectory win32\packages
-
-build_script:
-  - python win32\updateAndBuildAll.py -m
-  - ps: ant -version
-  - ps: pushd bindings/java
-  - cmd: ant -q dist
-  - ps: popd
-  - ps: pushd case-uco/java
-  - cmd: ant -q
-  - ps: popd
-
-test_script:
-  - cmd: ant -q -f bindings/java test
+environment: 
+  matrix:
+
+  - job_name: Windows Build
+    appveyor_build_worker_image: Visual Studio 2015
+  - job_name: Linux Build
+    appveyor_build_worker_image: Ubuntu
+  - job_name: macOS Build
+    appveyor_build_worker_image: macos-catalina
+
+matrix:
+  fast_finish: true
+
+
+# job-specific configurations
+for: 
+
+  - 
+    matrix:
+      only:
+        - job_name: Windows Build
+
+    cache:
+      - C:\Users\appveyor\.ant
+      - C:\ProgramData\chocolatey\bin
+      - C:\ProgramData\chocolatey\lib
+
+    install:
+      - ps: choco install nuget.commandline
+      - ps: choco install ant --ignore-dependencies
+      - ps: $env:Path="C:\Program Files\Java\jdk1.8.0\bin;$($env:Path);C:\ProgramData\chocolatey\lib\ant"
+      - set PATH=C:\Python36-x64\';%PATH%
+    environment:
+      global:
+        TSK_HOME: "%APPVEYOR_BUILD_FOLDER%"
+        PYTHON: "C:\\Python36-x64"
+        JDK_HOME: C:\Program Files\Java\jdk1.8.0
+    services:
+
+    before_build:
+      - nuget restore win32\libtsk -PackagesDirectory win32\packages
+
+    build_script:
+      - python win32\updateAndBuildAll.py -m
+      - ps: ant -version
+      - ps: pushd bindings/java
+      - cmd: ant -q dist
+      - ps: popd
+      - ps: pushd case-uco/java
+      - cmd: ant -q
+      - ps: popd
+
+    test_script:
+      - cmd: ant -q -f bindings/java test
+
+  - 
+    matrix:
+      only:
+        - job_name: Linux Build
+
+    build_script:
+      - ./bootstrap
+      - ./configure -q
+      - make -s
+
+  - 
+    matrix:
+      only:
+        - job_name: macOS Build
+
+    build_script:
+      - ./bootstrap
+      - ./configure -q
+      - make -s
diff --git a/bindings/java/doxygen/schema/db_schema_9_1.dox b/bindings/java/doxygen/schema/db_schema_9_1.dox
index cd768dab7fe1d30fd260a8aaa9a68506b183ff5c..e95884afe2c2067aeeb0a018559bddf64439ec64 100644
--- a/bindings/java/doxygen/schema/db_schema_9_1.dox
+++ b/bindings/java/doxygen/schema/db_schema_9_1.dox
@@ -80,6 +80,10 @@ Every object (image, volume system, file, etc.) has an entry in this table.  Thi
 
 # Hosts / Persons
 Stores data related to hosts and persons, which can help organize data sources. 
+Persons are optional, but hosts are required. When persons are defined, they are associated with one or more hosts. 
+The person and host tree is in parallel to the data source and file tree.  
+- A host is associated with a person
+- A data source is associated with a host (but not a child of it)
 
 ## tsk_persons
 Stores persons for the case. A peron is someone who owns or used a data source in the case. 
@@ -94,9 +98,13 @@ Stores hosts that have a data source in the case. Each data source must be assoc
 - **person_id** - Optional id of associated person
 - **merged_into** - Stores the host ID that this host was merged into
 
-# Data Source / Device Tables 
+# Data Source / Device, Disk Image Tables 
+
+A data source is the top-level container added to the database.  All files and artifacts must be children of a data source. There are different kinds of data sources and some will also have data in tsk_image_info and others will not. The data sources are the root of the object hierarchy. 
+
 ## data_source_info
-Contains information about a data source, which could be an image.  This is where we group data sources into devices (based on device ID).
+Contains information about a data source, which could be an image or logical folder.  The device_id concept allows multiple data source to be grouped together (if they share the same ID).
+The code will go to both tsk_image_info (for disk images) and tsk_files (for other types) for additional information. 
 - **obj_id** - Id of image/data source in tsk_objects
 - **device_id** - Unique ID (GUID) for the device that contains the data source
 - **time_zone** - Timezone that the data source was originally located in
@@ -108,10 +116,8 @@ Contains information about a data source, which could be an image.  This is wher
 - **host_id** - Host associated with this image (must be set)
 
 
-# Disk Image Tables
-
 ## tsk_image_info 
-Contains information about each set of images that is stored in the database. 
+Contains additional data source information if it is a disk image. These rows use the same object ID as data_source_info. 
 - **obj_id** - Id of image in tsk_objects
 - **type** - Type of disk image format (as org.sleuthkit.datamodel.TskData.TSK_IMG_TYPE_ENUM)
 - **ssize** - Sector size of device in bytes
@@ -130,6 +136,9 @@ Stores path(s) to file(s) on disk that make up an image set.
 
 
 # Volume System Tables
+
+The parent of a volume system is often a disk image / data source.
+
 ## tsk_vs_info
 Contains one row for every volume system found in the images.
 - **obj_id** - Id of volume system in tsk_objects
@@ -152,6 +161,9 @@ Contains information about pools (for APFS, logical disk management, etc.)
 - **pool_type** - Type of pool (as org.sleuthkit.datamodel.TskData.TSK_POOL_TYPE_ENUM)
 
 # File System Tables
+
+The parent of a file system is often either a partition or a disk image. These tables form together to create a parent / child structure of a root folder, subfolders, and files. 
+
 ## tsk_fs_info
 Contains one for for every file system in the images. 
 - **obj_id** - Id of filesystem in tsk_objects
diff --git a/bindings/java/src/org/sleuthkit/datamodel/HostManager.java b/bindings/java/src/org/sleuthkit/datamodel/HostManager.java
index d8255b2e7823a93e715bb3264b3f2794ac95f6ab..fb8d2eda00053413b525f4a5df7dd11c087f6b37 100755
--- a/bindings/java/src/org/sleuthkit/datamodel/HostManager.java
+++ b/bindings/java/src/org/sleuthkit/datamodel/HostManager.java
@@ -295,7 +295,7 @@ public List<DataSource> getDataSourcesForHost(Host host) throws TskCoreException
 
 			return dataSources;
 		} catch (SQLException | TskDataException ex) {
-			throw new TskCoreException(String.format("Error getting data sources for host " + host.getName()), ex);
+			throw new TskCoreException(String.format("Error getting data sources for host %s", host.getName()), ex);
 		} finally {
 			db.releaseSingleUserCaseReadLock();
 		}
diff --git a/bindings/java/src/org/sleuthkit/datamodel/SlackFile.java b/bindings/java/src/org/sleuthkit/datamodel/SlackFile.java
index e457191577171806c3f9223e86aafa69c1edcff8..e86d98a9ac6ddca2b42fee438062408760b0fea8 100644
--- a/bindings/java/src/org/sleuthkit/datamodel/SlackFile.java
+++ b/bindings/java/src/org/sleuthkit/datamodel/SlackFile.java
@@ -117,7 +117,7 @@ public class SlackFile extends FsContent {
 	 */
 	@Override
 	@SuppressWarnings("deprecation")
-	protected int readInt(byte[] buf, long offset, long len) throws TskCoreException {
+	protected synchronized int readInt(byte[] buf, long offset, long len) throws TskCoreException {
 		if (offset == 0 && size == 0) {
 			//special case for 0-size file
 			return 0;
diff --git a/bindings/java/src/org/sleuthkit/datamodel/SleuthkitCase.java b/bindings/java/src/org/sleuthkit/datamodel/SleuthkitCase.java
index d76f85de22ea6a8b8af4e45669868167a3a8d2de..7371e4bf8b21918df0f8c74b03744d532d6c76eb 100644
--- a/bindings/java/src/org/sleuthkit/datamodel/SleuthkitCase.java
+++ b/bindings/java/src/org/sleuthkit/datamodel/SleuthkitCase.java
@@ -10090,16 +10090,16 @@ public void setImagePaths(long obj_id, List<String> paths) throws TskCoreExcepti
 	 *                          within tsk core and the update fails
 	 */
 	@Beta
-	public void setImagePaths(long obj_id, List<String> paths, CaseDbTransaction trans) throws TskCoreException {	
+	public void setImagePaths(long objId, List<String> paths, CaseDbTransaction trans) throws TskCoreException {	
 		try {
 			PreparedStatement statement = trans.getConnection().getPreparedStatement(PREPARED_STATEMENT.DELETE_IMAGE_NAME);
 			statement.clearParameters();
-			statement.setLong(1, obj_id);
+			statement.setLong(1, objId);
 			trans.getConnection().executeUpdate(statement);
 			for (int i = 0; i < paths.size(); i++) {
 				statement = trans.getConnection().getPreparedStatement(PREPARED_STATEMENT.INSERT_IMAGE_NAME);
 				statement.clearParameters();
-				statement.setLong(1, obj_id);
+				statement.setLong(1, objId);
 				statement.setString(2, paths.get(i));
 				statement.setLong(3, i);
 				trans.getConnection().executeUpdate(statement);
diff --git a/tsk/fs/ext2fs.c b/tsk/fs/ext2fs.c
index a7fe539590a72cdeed96b6be8193aadbea7eddcc..adb06e9ba453772d510e5039433cc55db28c314c 100755
--- a/tsk/fs/ext2fs.c
+++ b/tsk/fs/ext2fs.c
@@ -1613,7 +1613,7 @@ ext2fs_make_data_run_extent(TSK_FS_INFO * fs_info, TSK_FS_ATTR * fs_attr,
 static TSK_OFF_T
 ext2fs_make_data_run_extent_index(TSK_FS_INFO * fs_info,
     TSK_FS_ATTR * fs_attr, TSK_FS_ATTR * fs_attr_extent,
-    TSK_DADDR_T idx_block)
+    TSK_DADDR_T idx_block, TSK_DADDR_T * idx_offset)
 {
     ext2fs_extent_header *header;
     TSK_FS_ATTR_RUN *data_run;
@@ -1655,8 +1655,10 @@ ext2fs_make_data_run_extent_index(TSK_FS_INFO * fs_info,
         free(buf);
         return 1;
     }
+    data_run->offset = *idx_offset;
+    ++*idx_offset;
     data_run->addr = idx_block;
-    data_run->len = fs_blocksize;
+    data_run->len = 1;
 
     if (tsk_fs_attr_add_run(fs_info, fs_attr_extent, data_run)) {
         tsk_fs_attr_run_free(data_run);
@@ -1701,7 +1703,7 @@ ext2fs_make_data_run_extent_index(TSK_FS_INFO * fs_info,
                         index->ei_leaf_hi)) << 16) | tsk_getu32(fs_info->
                 endian, index->ei_leaf_lo);
             if (ext2fs_make_data_run_extent_index(fs_info, fs_attr,
-                    fs_attr_extent, child_block)) {
+                    fs_attr_extent, child_block, idx_offset)) {
                 free(buf);
                 return 1;
             }
@@ -1934,6 +1936,7 @@ ext4_load_attrs_extents(TSK_FS_FILE *fs_file)
     else {                  /* interior node */
         TSK_FS_ATTR *fs_attr_extent;
         int32_t extent_index_size;
+        TSK_DADDR_T idx_offset;
 
         // Ensure fs_meta->content_ptr is sufficiently large
         // Otherwise indices[i] below can cause an OOB read
@@ -1965,6 +1968,7 @@ ext4_load_attrs_extents(TSK_FS_FILE *fs_file)
         }
         
         indices = (ext2fs_extent_idx *) (header + 1);
+        idx_offset = 0;
         for (i = 0; i < num_entries; i++) {
             ext2fs_extent_idx *index = &indices[i];
             TSK_DADDR_T child_block =
@@ -1973,7 +1977,7 @@ ext4_load_attrs_extents(TSK_FS_FILE *fs_file)
                                     ei_leaf_hi)) << 16) | tsk_getu32(fs_info->
                                                                      endian, index->ei_leaf_lo);
             if (ext2fs_make_data_run_extent_index(fs_info, fs_attr,
-                                                  fs_attr_extent, child_block)) {
+                                                  fs_attr_extent, child_block, &idx_offset)) {
                 return 1;
             }
         }