diff --git a/bindings/java/src/org/sleuthkit/datamodel/SleuthkitCase.java b/bindings/java/src/org/sleuthkit/datamodel/SleuthkitCase.java
index 2c5cdb94bc269249001647d01aa8e50225684885..06658ee236441eaa9ec352bbe2bff1530dc1ea8f 100644
--- a/bindings/java/src/org/sleuthkit/datamodel/SleuthkitCase.java
+++ b/bindings/java/src/org/sleuthkit/datamodel/SleuthkitCase.java
@@ -11351,10 +11351,11 @@ public List<TagName> getTagNamesInUse(long dsObjId) throws TskCoreException {
 	 * @return A TagName data transfer object (DTO) for the new row.
 	 *
 	 * @throws TskCoreException
-	 * @deprecated addOrUpdateTagName should be used this method calls
-	 * addOrUpdateTagName with a default knownStatus value
+	 * @deprecated TaggingManager.addOrUpdateTagName should be used instead
+	 *	with the default knowStatus of TskData.FileKnown.UNKNOWN
 	 */
 	@Deprecated
+	@SuppressWarnings("deprecation")
 	public TagName addTagName(String displayName, String description, TagName.HTML_COLOR color) throws TskCoreException {
 		return addOrUpdateTagName(displayName, description, color, TskData.FileKnown.UNKNOWN);
 	}
@@ -11372,35 +11373,11 @@ public TagName addTagName(String displayName, String description, TagName.HTML_C
 	 * @return A TagName data transfer object (DTO) for the new row.
 	 *
 	 * @throws TskCoreException
+	 * @deprecated This method has been replaced by TaggingManager.addOrUpdateTagName.
 	 */
+	@Deprecated
 	public TagName addOrUpdateTagName(String displayName, String description, TagName.HTML_COLOR color, TskData.FileKnown knownStatus) throws TskCoreException {
-		acquireSingleUserCaseWriteLock();
-		try (CaseDbConnection connection = connections.getConnection();) {
-			PreparedStatement statement;
-			// INSERT INTO tag_names (display_name, description, color, knownStatus) VALUES (?, ?, ?, ?) ON CONFLICT (display_name) DO UPDATE SET description = ?, color = ?, knownStatus = ?
-			statement = connection.getPreparedStatement(PREPARED_STATEMENT.INSERT_OR_UPDATE_TAG_NAME, Statement.RETURN_GENERATED_KEYS);
-			statement.clearParameters();
-			statement.setString(5, description);
-			statement.setString(6, color.getName());
-			statement.setByte(7, knownStatus.getFileKnownValue());
-			statement.setString(1, displayName);
-			statement.setString(2, description);
-			statement.setString(3, color.getName());
-			statement.setByte(4, knownStatus.getFileKnownValue());
-			connection.executeUpdate(statement);
-
-			statement = connection.getPreparedStatement(PREPARED_STATEMENT.SELECT_TAG_NAME_BY_NAME);
-			statement.clearParameters();
-			statement.setString(1, displayName);
-			try (ResultSet resultSet = connection.executeQuery(statement)) {
-				resultSet.next();
-				return new TagName(resultSet.getLong("tag_name_id"), displayName, description, color, knownStatus, resultSet.getLong("tag_set_id"), resultSet.getInt("rank"));
-			}
-		} catch (SQLException ex) {
-			throw new TskCoreException("Error adding row for " + displayName + " tag name to tag_names table", ex);
-		} finally {
-			releaseSingleUserCaseWriteLock();
-		}
+		return getTaggingManager().addOrUpdateTagName(displayName, description, color, knownStatus);
 	}
 
 	/**
@@ -12922,7 +12899,6 @@ private enum PREPARED_STATEMENT {
 				+ "FROM tsk_objects INNER JOIN blackboard_artifacts " //NON-NLS
 				+ "ON tsk_objects.obj_id=blackboard_artifacts.obj_id " //NON-NLS
 				+ "WHERE (tsk_objects.par_obj_id = ?)"),
-		INSERT_OR_UPDATE_TAG_NAME("INSERT INTO tag_names (display_name, description, color, knownStatus) VALUES (?, ?, ?, ?) ON CONFLICT (display_name) DO UPDATE SET description = ?, color = ?, knownStatus = ?"),
 		SELECT_EXAMINER_BY_ID("SELECT * FROM tsk_examiners WHERE examiner_id = ?"),
 		SELECT_EXAMINER_BY_LOGIN_NAME("SELECT * FROM tsk_examiners WHERE login_name = ?"),
 		INSERT_EXAMINER_POSTGRESQL("INSERT INTO tsk_examiners (login_name) VALUES (?) ON CONFLICT DO NOTHING"),
@@ -12941,8 +12917,7 @@ private enum PREPARED_STATEMENT {
 		INSERT_POOL_INFO("INSERT INTO tsk_pool_info (obj_id, pool_type) VALUES (?, ?)"),
 		INSERT_FS_INFO("INSERT INTO tsk_fs_info (obj_id, data_source_obj_id, img_offset, fs_type, block_size, block_count, root_inum, first_inum, last_inum, display_name)"
 				+ "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"),
-		SELECT_TAG_NAME_BY_ID("SELECT * FROM tag_names where tag_name_id = ?"),
-		SELECT_TAG_NAME_BY_NAME("SELECT * FROM tag_names where display_name = ?");
+		SELECT_TAG_NAME_BY_ID("SELECT * FROM tag_names where tag_name_id = ?");
 
 		private final String sql;
 
diff --git a/bindings/java/src/org/sleuthkit/datamodel/TagSet.java b/bindings/java/src/org/sleuthkit/datamodel/TagSet.java
index 31e86808e21e04a915bb1ecb40da58b7c175469b..8c8dec9ca15b8ffeee706db08587c42b34a8f462 100755
--- a/bindings/java/src/org/sleuthkit/datamodel/TagSet.java
+++ b/bindings/java/src/org/sleuthkit/datamodel/TagSet.java
@@ -72,7 +72,7 @@ public List<TagName> getTagNames() {
 	 *
 	 * @return TagSet id value.
 	 */
-	long getId() {
+	public long getId() {
 		return id;
 	}
 
diff --git a/bindings/java/src/org/sleuthkit/datamodel/TaggingManager.java b/bindings/java/src/org/sleuthkit/datamodel/TaggingManager.java
index 52b8fcddf7cb4d9ff93886dfa69cc9e7d66c6528..f568b08fd09fb5e5d7d42f2f8d27f348ff00a1eb 100755
--- a/bindings/java/src/org/sleuthkit/datamodel/TaggingManager.java
+++ b/bindings/java/src/org/sleuthkit/datamodel/TaggingManager.java
@@ -1,7 +1,7 @@
 /*
  * Sleuth Kit Data Model
  *
- * Copyright 2020 Basis Technology Corp.
+ * Copyright 2020-2021 Basis Technology Corp.
  * Contact: carrier <at> sleuthkit <dot> org
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
@@ -18,6 +18,7 @@
  */
 package org.sleuthkit.datamodel;
 
+import java.sql.PreparedStatement;
 import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.sql.Statement;
@@ -28,6 +29,11 @@
 import org.sleuthkit.datamodel.SleuthkitCase.CaseDbConnection;
 import org.sleuthkit.datamodel.SleuthkitCase.CaseDbTransaction;
 import static org.sleuthkit.datamodel.TskData.DbType.POSTGRESQL;
+import org.sleuthkit.datamodel.TskEvent.TagNamesAddedTskEvent;
+import org.sleuthkit.datamodel.TskEvent.TagNamesDeletedTskEvent;
+import org.sleuthkit.datamodel.TskEvent.TagNamesUpdatedTskEvent;
+import org.sleuthkit.datamodel.TskEvent.TagSetsAddedTskEvent;
+import org.sleuthkit.datamodel.TskEvent.TagSetsDeletedTskEvent;
 
 /**
  * Provides an API to manage Tags.
@@ -54,10 +60,10 @@ public class TaggingManager {
 	 */
 	public List<TagSet> getTagSets() throws TskCoreException {
 		List<TagSet> tagSetList = new ArrayList<>();
-		
+
 		skCase.acquireSingleUserCaseReadLock();
 		String getAllTagSetsQuery = "SELECT * FROM tsk_tag_sets";
-		try (CaseDbConnection connection = skCase.getConnection();Statement stmt = connection.createStatement(); ResultSet resultSet = stmt.executeQuery(getAllTagSetsQuery);) {
+		try (CaseDbConnection connection = skCase.getConnection(); Statement stmt = connection.createStatement(); ResultSet resultSet = stmt.executeQuery(getAllTagSetsQuery);) {
 			while (resultSet.next()) {
 				int setID = resultSet.getInt("tag_set_id");
 				String setName = resultSet.getString("name");
@@ -121,6 +127,8 @@ public TagSet addTagSet(String name, List<TagName> tagNames) throws TskCoreExcep
 					}
 				}
 				tagSet = new TagSet(setID, name, updatedTags);
+				skCase.fireTSKEvent(new TagSetsAddedTskEvent(Collections.singletonList(tagSet)));
+				skCase.fireTSKEvent(new TagNamesUpdatedTskEvent(updatedTags));
 			}
 			trans.commit();
 		} catch (SQLException ex) {
@@ -158,6 +166,14 @@ public void deleteTagSet(TagSet tagSet) throws TskCoreException {
 			queryTemplate = "DELETE FROM tsk_tag_sets WHERE tag_set_id = '%d'";
 			stmt.execute(String.format(queryTemplate, tagSet.getId()));
 			trans.commit();
+
+			List<Long> tagNameIds = new ArrayList<>();
+			for (TagName tagName : tagSet.getTagNames()) {
+				tagNameIds.add(tagName.getId());
+			}
+
+			skCase.fireTSKEvent(new TagSetsDeletedTskEvent(Collections.singletonList(tagSet.getId())));
+			skCase.fireTSKEvent(new TagNamesDeletedTskEvent(tagNameIds));
 		} catch (SQLException ex) {
 			trans.rollback();
 			throw new TskCoreException(String.format("Error deleting tag set where id = %d.", tagSet.getId()), ex);
@@ -177,15 +193,15 @@ public TagSet getTagSet(TagName tagName) throws TskCoreException {
 		if (tagName == null) {
 			throw new IllegalArgumentException("Null tagName argument");
 		}
-		
+
 		if (tagName.getTagSetId() <= 0) {
 			return null;
 		}
-		
+
 		skCase.acquireSingleUserCaseReadLock();
 		TagSet tagSet = null;
 		String sqlQuery = String.format("SELECT * FROM tsk_tag_sets WHERE tag_set_id = %d", tagName.getTagSetId());
-		try (CaseDbConnection connection = skCase.getConnection();Statement stmt = connection.createStatement(); ResultSet resultSet = stmt.executeQuery(sqlQuery);) {
+		try (CaseDbConnection connection = skCase.getConnection(); Statement stmt = connection.createStatement(); ResultSet resultSet = stmt.executeQuery(sqlQuery);) {
 			if (resultSet.next()) {
 				int setID = resultSet.getInt("tag_set_id");
 				String setName = resultSet.getString("name");
@@ -199,6 +215,39 @@ public TagSet getTagSet(TagName tagName) throws TskCoreException {
 		}
 	}
 
+	/**
+	 * Return a TagSet object for the given id.
+	 *
+	 * @param id TagSet id.
+	 *
+	 * @return The TagSet represented by the given it, or null if one was not
+	 *         found.
+	 *
+	 * @throws TskCoreException
+	 */
+	public TagSet getTagSet(long id) throws TskCoreException {
+		TagSet tagSet = null;
+		String preparedQuery = "Select * FROM tsk_tag_sets WHERE tag_set_id = ?";
+		skCase.acquireSingleUserCaseReadLock();
+		try (CaseDbConnection connection = skCase.getConnection(); PreparedStatement statement = connection.getPreparedStatement(preparedQuery, Statement.NO_GENERATED_KEYS)) {
+			statement.setLong(1, id);
+			try (ResultSet resultSet = statement.executeQuery()) {
+				if (resultSet.next()) {
+					int setID = resultSet.getInt("tag_set_id");
+					String setName = resultSet.getString("name");
+					tagSet = new TagSet(setID, setName, getTagNamesByTagSetID(setID));
+				}
+			}
+
+		} catch (SQLException ex) {
+			throw new TskCoreException(String.format("Error occurred getting TagSet (ID=%d)", id), ex);
+		} finally {
+			skCase.releaseSingleUserCaseReadLock();
+		}
+
+		return tagSet;
+	}
+
 	/**
 	 * Inserts a row into the blackboard_artifact_tags table in the case
 	 * database.
@@ -216,7 +265,7 @@ public BlackboardArtifactTagChange addArtifactTag(BlackboardArtifact artifact, T
 		if (artifact == null || tagName == null) {
 			throw new IllegalArgumentException("NULL argument passed to addArtifactTag");
 		}
-		
+
 		List<BlackboardArtifactTag> removedTags = new ArrayList<>();
 		List<String> removedTagIds = new ArrayList<>();
 		CaseDbTransaction trans = null;
@@ -254,15 +303,14 @@ public BlackboardArtifactTagChange addArtifactTag(BlackboardArtifact artifact, T
 					}
 				}
 
-				
 			}
-			
+
 			Content content = skCase.getContentById(artifact.getObjectID());
 			Examiner currentExaminer = skCase.getCurrentExaminer();
-			
+
 			trans = skCase.beginTransaction();
 			CaseDbConnection connection = trans.getConnection();
-			
+
 			if (!removedTags.isEmpty()) {
 				// Remove the tags.
 				String removeQuery = String.format("DELETE FROM blackboard_artifact_tags WHERE tag_id IN (%s)", String.join(",", removedTagIds));
@@ -274,7 +322,7 @@ public BlackboardArtifactTagChange addArtifactTag(BlackboardArtifact artifact, T
 			// Add the new Tag.
 			BlackboardArtifactTag artifactTag;
 			try (Statement stmt = connection.createStatement()) {
-				
+
 				String query = String.format(
 						"INSERT INTO blackboard_artifact_tags (artifact_id, tag_name_id, comment, examiner_id) VALUES (%d, %d, '%s', %d)",
 						artifact.getArtifactID(),
@@ -294,7 +342,7 @@ public BlackboardArtifactTagChange addArtifactTag(BlackboardArtifact artifact, T
 							artifact, content, tagName, comment, currentExaminer.getLoginName());
 				}
 			}
-			
+
 			skCase.getScoringManager().updateAggregateScoreAfterAddition(
 					artifact.getId(), artifact.getDataSourceObjectID(), getTagScore(tagName.getKnownStatus()), trans);
 
@@ -302,35 +350,36 @@ public BlackboardArtifactTagChange addArtifactTag(BlackboardArtifact artifact, T
 
 			return new BlackboardArtifactTagChange(artifactTag, removedTags);
 		} catch (SQLException ex) {
-			if(trans != null) {
+			if (trans != null) {
 				trans.rollback();
 			}
 			throw new TskCoreException("Error adding row to blackboard_artifact_tags table (obj_id = " + artifact.getArtifactID() + ", tag_name_id = " + tagName.getId() + ")", ex);
 		}
 	}
-	
 
 	/**
 	 * Returns the score based on this TagName object.
+	 *
 	 * @param knownStatus The known status of the tag.
+	 *
 	 * @return The relevant score.
 	 */
 	static Score getTagScore(TskData.FileKnown knownStatus) {
 		switch (knownStatus) {
-			case BAD: 
+			case BAD:
 				return Score.SCORE_NOTABLE;
-			case UNKNOWN: 
+			case UNKNOWN:
 			case KNOWN:
 			default:
 				return Score.SCORE_LIKELY_NOTABLE;
 		}
 	}
-	
-		/**
+
+	/**
 	 * Retrieves the maximum FileKnown status of any tag associated with the
 	 * object id.
 	 *
-	 * @param objectId   The object id of the item.
+	 * @param objectId    The object id of the item.
 	 * @param transaction The case db transaction to perform this query.
 	 *
 	 * @return The maximum FileKnown status for this object or empty.
@@ -385,7 +434,7 @@ public ContentTagChange addContentTag(Content content, TagName tagName, String c
 		Examiner currentExaminer = skCase.getCurrentExaminer();
 		CaseDbTransaction trans = skCase.beginTransaction();
 		CaseDbConnection connection = trans.getConnection();
-		
+
 		try {
 			long tagSetId = tagName.getTagSetId();
 
@@ -429,7 +478,7 @@ public ContentTagChange addContentTag(Content content, TagName tagName, String c
 			String queryTemplate = "INSERT INTO content_tags (obj_id, tag_name_id, comment, begin_byte_offset, end_byte_offset, examiner_id) VALUES (%d, %d, '%s', %d, %d, %d)";
 			ContentTag contentTag = null;
 			try (Statement stmt = connection.createStatement()) {
-				
+
 				String query = String.format(queryTemplate,
 						content.getId(),
 						tagName.getId(),
@@ -450,7 +499,7 @@ public ContentTagChange addContentTag(Content content, TagName tagName, String c
 							content, tagName, comment, beginByteOffset, endByteOffset, currentExaminer.getLoginName());
 				}
 			}
-			
+
 			Long dataSourceId = content.getDataSource() != null ? content.getDataSource().getId() : null;
 			skCase.getScoringManager().updateAggregateScoreAfterAddition(
 					content.getId(), dataSourceId, getTagScore(tagName.getKnownStatus()), trans);
@@ -463,6 +512,104 @@ public ContentTagChange addContentTag(Content content, TagName tagName, String c
 		}
 	}
 
+	/**
+	 * Inserts row into the tags_names table, or updates the existing row if the
+	 * displayName already exists in the tag_names table in the case database.
+	 *
+	 * @param displayName The display name for the new tag name.
+	 * @param description The description for the new tag name.
+	 * @param color       The HTML color to associate with the new tag name.
+	 * @param knownStatus The TskData.FileKnown value to associate with the new
+	 *                    tag name.
+	 *
+	 * @return A TagName data transfer object (DTO) for the new row.
+	 *
+	 * @throws TskCoreException
+	 */
+	public TagName addOrUpdateTagName(String displayName, String description, TagName.HTML_COLOR color, TskData.FileKnown knownStatus) throws TskCoreException {
+		String insertQuery = "INSERT INTO tag_names (display_name, description, color, knownStatus) VALUES (?, ?, ?, ?) ON CONFLICT (display_name) DO UPDATE SET description = ?, color = ?, knownStatus = ?";
+		boolean isUpdated = false;
+		skCase.acquireSingleUserCaseWriteLock();
+		try (CaseDbConnection connection = skCase.getConnection()) {
+			try (PreparedStatement statement = connection.getPreparedStatement("SELECT * FROM tag_names WHERE display_name = ?", Statement.NO_GENERATED_KEYS)) {
+				statement.setString(1, displayName);
+				try (ResultSet resultSet = statement.executeQuery()) {
+					isUpdated = resultSet.next();
+				}
+			}
+
+			try (PreparedStatement statement = connection.getPreparedStatement(insertQuery, Statement.RETURN_GENERATED_KEYS);) {
+				statement.clearParameters();
+				statement.setString(5, description);
+				statement.setString(6, color.getName());
+				statement.setByte(7, knownStatus.getFileKnownValue());
+				statement.setString(1, displayName);
+				statement.setString(2, description);
+				statement.setString(3, color.getName());
+				statement.setByte(4, knownStatus.getFileKnownValue());
+				statement.executeUpdate();
+			}
+
+			try (PreparedStatement statement = connection.getPreparedStatement("SELECT * FROM tag_names where display_name = ?", Statement.NO_GENERATED_KEYS)) {
+				statement.setString(1, displayName);
+				try (ResultSet resultSet = connection.executeQuery(statement)) {
+					resultSet.next();
+					TagName newTag = new TagName(resultSet.getLong("tag_name_id"), displayName, description, color, knownStatus, resultSet.getLong("tag_set_id"), resultSet.getInt("rank"));
+
+					if (!isUpdated) {
+						skCase.fireTSKEvent(new TagNamesAddedTskEvent(Collections.singletonList(newTag)));
+					} else {
+						skCase.fireTSKEvent(new TagNamesUpdatedTskEvent(Collections.singletonList(newTag)));
+					}
+
+					return newTag;
+				}
+			}
+		} catch (SQLException ex) {
+			throw new TskCoreException("Error adding row for " + displayName + " tag name to tag_names table", ex);
+		} finally {
+			skCase.releaseSingleUserCaseWriteLock();
+		}
+	}
+
+	/**
+	 * Return the TagName object for the given id.
+	 *
+	 * @param id The TagName id.
+	 *
+	 * @return The TagName object for the given id.
+	 *
+	 * @throws TskCoreException
+	 */
+	public TagName getTagName(long id) throws TskCoreException {
+		String preparedQuery = "SELECT * FROM tag_names where tag_name_id = ?";
+
+		skCase.acquireSingleUserCaseReadLock();
+		try (CaseDbConnection connection = skCase.getConnection()) {
+			try (PreparedStatement statement = connection.getPreparedStatement(preparedQuery, Statement.NO_GENERATED_KEYS)) {
+				statement.clearParameters();
+				statement.setLong(1, id);
+				try (ResultSet resultSet = statement.executeQuery()) {
+					if (resultSet.next()) {
+						return new TagName(resultSet.getLong("tag_name_id"),
+								resultSet.getString("display_name"),
+								resultSet.getString("description"),
+								TagName.HTML_COLOR.getColorByName(resultSet.getString("color")),
+								TskData.FileKnown.valueOf(resultSet.getByte("knowStatus")),
+								resultSet.getLong("tag_set_id"),
+								resultSet.getInt("rank"));
+					}
+				}
+			}
+		} catch (SQLException ex) {
+			throw new TskCoreException("", ex);
+		} finally {
+			skCase.releaseSingleUserCaseWriteLock();
+		}
+
+		return null;
+	}
+
 	/**
 	 * Determine if the given TagSet contains TagNames that are currently in
 	 * use, ie there is an existing ContentTag or ArtifactTag that uses TagName.
@@ -522,7 +669,7 @@ private List<TagName> getTagNamesByTagSetID(int tagSetId) throws TskCoreExceptio
 
 		skCase.acquireSingleUserCaseReadLock();
 		String query = String.format("SELECT * FROM tag_names WHERE tag_set_id = %d", tagSetId);
-		try (CaseDbConnection connection = skCase.getConnection();Statement stmt = connection.createStatement(); ResultSet resultSet = stmt.executeQuery(query)) {
+		try (CaseDbConnection connection = skCase.getConnection(); Statement stmt = connection.createStatement(); ResultSet resultSet = stmt.executeQuery(query)) {
 			while (resultSet.next()) {
 				tagNameList.add(new TagName(resultSet.getLong("tag_name_id"),
 						resultSet.getString("display_name"),
diff --git a/bindings/java/src/org/sleuthkit/datamodel/TskCaseDbBridge.java b/bindings/java/src/org/sleuthkit/datamodel/TskCaseDbBridge.java
index d932f948347f30fa8c6b847408af925096b14a3b..5eb37f099cffd20947e10472e32e68d9726c48d9 100644
--- a/bindings/java/src/org/sleuthkit/datamodel/TskCaseDbBridge.java
+++ b/bindings/java/src/org/sleuthkit/datamodel/TskCaseDbBridge.java
@@ -386,7 +386,10 @@ private long addBatchedFilesToDb() {
 							// Currently we expect only NTFS systems to provide a windows style SID as owner id.
 							OsAccountManager accountMgr = caseDb.getOsAccountManager();
 							OsAccount newAccount = accountMgr.newWindowsOsAccount(ownerUid, null, null, imageHost, OsAccountRealm.RealmScope.UNKNOWN);
-							accountMgr.newOsAccountInstance(newAccount.getId(), fileInfo.dataSourceObjId, OsAccountInstance.OsAccountInstanceType.ACCESSED, caseDb.getConnection());
+							Content ds = caseDb.getContentById(fileInfo.dataSourceObjId); // Data sources are cached so this will only access the database once
+							if (ds instanceof DataSource) {
+								accountMgr.newOsAccountInstance(newAccount, (DataSource)ds, OsAccountInstance.OsAccountInstanceType.ACCESSED);
+							}
 							ownerIdToAccountMap.put(ownerUid, newAccount);
 						}
 					} catch (NotUserSIDException ex) {
diff --git a/bindings/java/src/org/sleuthkit/datamodel/TskEvent.java b/bindings/java/src/org/sleuthkit/datamodel/TskEvent.java
index d67686c466ace311eec1d2da324fbdd517d4929f..f02623b931fd5b9aa9ff3c7311a85b4657b683c0 100755
--- a/bindings/java/src/org/sleuthkit/datamodel/TskEvent.java
+++ b/bindings/java/src/org/sleuthkit/datamodel/TskEvent.java
@@ -462,7 +462,7 @@ public final static class HostsRemovedFromPersonTskEvent extends TskObjectsEvent
 		private final Person person;
 
 		/**
-		 * Contructs an event published when one or more hosts are removed from
+		 * Constructs an event published when one or more hosts are removed from
 		 * a person.
 		 *
 		 * @param person  The person.
@@ -493,4 +493,123 @@ public List<Long> getHostIds() {
 
 	}
 
+	static abstract class TagNamesTskEvent extends TskObjectsEvent<TagName> {
+
+		public TagNamesTskEvent(List<TagName> tagNames) {
+			super(tagNames);
+		}
+
+		/**
+		 * Returns the list of added or updated TagName objects.
+		 *
+		 * @return The TagName list.
+		 */
+		public List<TagName> getTagNames() {
+			return getDataModelObjects();
+		}
+
+	}
+
+	/**
+	 * An event published when one or more TagName are added.
+	 */
+	public final static class TagNamesAddedTskEvent extends TagNamesTskEvent {
+
+		/**
+		 * Construct an event when one or more TagName are created or updated.
+		 *
+		 * @param tagNames List of added or modified TagName.
+		 */
+		public TagNamesAddedTskEvent(List<TagName> tagNames) {
+			super(tagNames);
+		}
+	}
+
+	/**
+	 * An event published when one or more TagName are updated.
+	 */
+	public final static class TagNamesUpdatedTskEvent extends TagNamesTskEvent {
+
+		/**
+		 * Construct an event when one or more TagName are updated.
+		 *
+		 * @param tagNames List of added or modified TagName.
+		 */
+		public TagNamesUpdatedTskEvent(List<TagName> tagNames) {
+			super(tagNames);
+		}
+	}
+
+	/**
+	 * An event published when one or more TagName are deleted.
+	 */
+	public final static class TagNamesDeletedTskEvent extends TskObjectsEvent<Long> {
+
+		/**
+		 * Constructs a new event with the given list of TagName ids.
+		 *
+		 * @param tagNameIds Deleted TagName id list.
+		 */
+		public TagNamesDeletedTskEvent(List<Long> tagNameIds) {
+			super(tagNameIds);
+		}
+
+		/**
+		 * List of the deleted TagName ids.
+		 *
+		 * @return The list of deleted TagName Ids.
+		 */
+		public List<Long> getTagNameIds() {
+			return getDataModelObjects();
+		}
+
+	}
+
+	/**
+	 * An event published when one or more TagSets have been added.
+	 */
+	public final static class TagSetsAddedTskEvent extends TskObjectsEvent<TagSet> {
+
+		/**
+		 * Constructs an added event for one or more TagSets.
+		 *
+		 * @param tagSets The added TagSet.
+		 */
+		public TagSetsAddedTskEvent(List<TagSet> tagSets) {
+			super(tagSets);
+		}
+
+		/**
+		 * Return the TagSets list.
+		 *
+		 * @return The TagSet list.
+		 */
+		public List<TagSet> getTagSets() {
+			return getDataModelObjects();
+		}
+	}
+
+	/**
+	 * An event published when one or more TagSets have been deleted.
+	 */
+	public final static class TagSetsDeletedTskEvent extends TskObjectsEvent<Long> {
+
+		/**
+		 * Constructs a deleted event for one or more TagSets.
+		 *
+		 * @param tagSetIds The ids of the deleted TagSets.
+		 */
+		public TagSetsDeletedTskEvent(List<Long> tagSetIds) {
+			super(tagSetIds);
+		}
+
+		/**
+		 * Returns the list of deleted TagSet ids.
+		 *
+		 * @return The list of deleted TagSet ids.
+		 */
+		public List<Long> getTagSetIds() {
+			return getDataModelObjects();
+		}
+	}
 }
diff --git a/tsk/fs/apfs_compat.cpp b/tsk/fs/apfs_compat.cpp
index 4afacd5cb48f97a194d4bcced74c9962425e33aa..8ba784aaefe1b0cb4a8d25f011499de9a6e70821 100755
--- a/tsk/fs/apfs_compat.cpp
+++ b/tsk/fs/apfs_compat.cpp
@@ -226,8 +226,8 @@ APFSFSCompat::APFSFSCompat(TSK_IMG_INFO* img_info, const TSK_POOL_INFO* pool_inf
   };
 
   _fsinfo.dir_open_meta = [](TSK_FS_INFO* fs, TSK_FS_DIR** a_fs_dir,
-                             TSK_INUM_T inode) {
-    return to_fs(fs).dir_open_meta(a_fs_dir, inode);
+                             TSK_INUM_T inode, int recursion_depth) {
+    return to_fs(fs).dir_open_meta(a_fs_dir, inode, recursion_depth);
   };
 
   _fsinfo.fscheck = [](TSK_FS_INFO*, FILE*) {
@@ -478,7 +478,8 @@ uint8_t tsk_apfs_fsstat(TSK_FS_INFO* fs_info, apfs_fsstat_info* info) try {
 }
 
 TSK_RETVAL_ENUM APFSFSCompat::dir_open_meta(TSK_FS_DIR** a_fs_dir,
-                                            TSK_INUM_T inode_num) const
+                                            TSK_INUM_T inode_num,
+                                            int recursion_depth) const
     noexcept try {
   // Sanity checks
   if (a_fs_dir == NULL) {
@@ -568,7 +569,7 @@ uint8_t APFSFSCompat::inode_walk(TSK_FS_INFO* fs, TSK_INUM_T start_inum, TSK_INU
         tsk_error_reset();
         tsk_error_set_errno(TSK_ERR_FS_WALK_RNG);
         tsk_error_set_errstr("inode_walk: end object id must be >= start object id: "
-            "%" PRIx32 " must be >= %" PRIx32 "",
+            "%" PRIuINUM " must be >= %" PRIuINUM "",
             end_inum, start_inum);
         return 1;
     }
@@ -1430,9 +1431,6 @@ uint8_t tsk_apfs_istat(TSK_FS_FILE* fs_file, apfs_istat_info* info) try {
  */
 TSK_FS_BLOCK_FLAG_ENUM APFSFSCompat::block_getflags(TSK_FS_INFO* fs, TSK_DADDR_T addr) {
 
-    TSK_FS_FILE *fs_file;
-    int result;
-
     if (fs->img_info->itype != TSK_IMG_TYPE_POOL) {
         // No way to return an error
         return TSK_FS_BLOCK_FLAG_UNALLOC;
@@ -1641,7 +1639,7 @@ uint8_t tsk_apfs_free_snapshot_list(apfs_snapshot_list* list) try {
     return 1;
   }
 
-  for (auto i = 0; i < list->num_snapshots; i++) {
+  for (size_t i = 0; i < list->num_snapshots; i++) {
     auto& snapshot = list->snapshots[i];
     delete[] snapshot.name;
   }
diff --git a/tsk/fs/apfs_compat.hpp b/tsk/fs/apfs_compat.hpp
index 4c40ddf5ac47752d97c1003b57424625c0d4ed64..e724e81e23c85115331a5bd2b4dc4d3a4fd152e3 100644
--- a/tsk/fs/apfs_compat.hpp
+++ b/tsk/fs/apfs_compat.hpp
@@ -56,5 +56,5 @@ class APFSFSCompat : public APFSJObjTree {
   uint8_t decrypt_block(TSK_DADDR_T, void*) noexcept;
   int name_cmp(const char*, const char*) const noexcept;
 
-  TSK_RETVAL_ENUM dir_open_meta(TSK_FS_DIR**, TSK_INUM_T) const noexcept;
+  TSK_RETVAL_ENUM dir_open_meta(TSK_FS_DIR**, TSK_INUM_T, int) const noexcept;
 };
diff --git a/tsk/fs/exfatfs_dent.c b/tsk/fs/exfatfs_dent.c
index 714d88a21dc1a6aa8c34a96b1452f331508be3cc..6459e4a06dc97e21dc93d6dec08f25fb16c39885 100755
--- a/tsk/fs/exfatfs_dent.c
+++ b/tsk/fs/exfatfs_dent.c
@@ -475,15 +475,16 @@ exfats_parse_special_file_dentry(EXFATFS_FS_NAME_INFO *a_name_info, FATFS_DENTRY
  * be added.
  * @param a_buf Buffer that contains the directory contents.
  * @param a_buf_len Length of buffer in bytes (must be a multiple of sector
-*  size).
+ *  size).
  * @param a_sector_addrs Array where each element is the original address of
  * the corresponding sector in a_buf (size of array is number of sectors in
  * the directory).
+ * @param recursion_depth Recursion depth to limit the number of self-calls
  * @return TSK_RETVAL_ENUM
 */
 TSK_RETVAL_ENUM
 exfatfs_dent_parse_buf(FATFS_INFO *a_fatfs, TSK_FS_DIR *a_fs_dir, char *a_buf,
-    TSK_OFF_T a_buf_len, TSK_DADDR_T *a_sector_addrs)
+    TSK_OFF_T a_buf_len, TSK_DADDR_T *a_sector_addrs, int recursion_depth)
 {
     const char *func_name = "exfatfs_parse_directory_buf";
     TSK_FS_INFO *fs = NULL;
diff --git a/tsk/fs/ext2fs.c b/tsk/fs/ext2fs.c
index 29bed30ce79d2e726a1a21de0e64a613ab6dbb65..546db8cbf80e4a99b2645957727ca9541e2b0624 100755
--- a/tsk/fs/ext2fs.c
+++ b/tsk/fs/ext2fs.c
@@ -635,7 +635,7 @@ ext4_load_attrs_inline(TSK_FS_FILE *fs_file, const uint8_t * ea_buf, size_t ea_b
 
                 // This is the right attribute. Check that the length and offset are valid.
                 // The offset is from the beginning of the entries, i.e., four bytes into the buffer.
-                uint32_t offset = tsk_getu32(fs_file->fs_info->endian, ea_entry->val_off);
+                uint16_t offset = tsk_getu16(fs_file->fs_info->endian, ea_entry->val_off);
                 uint32_t size = tsk_getu32(fs_file->fs_info->endian, ea_entry->val_size);
                 if (4 + offset + size <= ea_buf_len) {
                     ea_inline_data = &(ea_buf[4 + offset]);
@@ -1665,7 +1665,7 @@ ext2fs_make_data_run_extent_index(TSK_FS_INFO * fs_info,
 
         // Ensure buf is sufficiently large
         // Otherwise extents[i] below can cause an OOB read
-        if ((fs_blocksize < sizeof(ext2fs_extent_header)) || (num_entries > (fs_blocksize - sizeof(ext2fs_extent_header)) / sizeof(ext2fs_extent))) {
+        if (((unsigned long)fs_blocksize < sizeof(ext2fs_extent_header)) || (num_entries > (fs_blocksize - sizeof(ext2fs_extent_header)) / sizeof(ext2fs_extent))) {
             free(buf);
             return 1;
         }
@@ -1684,7 +1684,7 @@ ext2fs_make_data_run_extent_index(TSK_FS_INFO * fs_info,
 
         // Ensure buf is sufficiently large
         // Otherwise indices[i] below can cause an OOB read
-        if ((fs_blocksize < sizeof(ext2fs_extent_header)) || (num_entries > (fs_blocksize - sizeof(ext2fs_extent_header)) / sizeof(ext2fs_extent_idx))) {
+        if (((unsigned long)fs_blocksize < sizeof(ext2fs_extent_header)) || (num_entries > (fs_blocksize - sizeof(ext2fs_extent_header)) / sizeof(ext2fs_extent_idx))) {
             free(buf);
             return 1;
         }
@@ -1715,7 +1715,7 @@ ext2fs_make_data_run_extent_index(TSK_FS_INFO * fs_info,
  */
 static int32_t
 ext2fs_extent_tree_index_count(TSK_FS_INFO * fs_info,
-    TSK_FS_META * fs_meta, ext2fs_extent_header * header)
+    TSK_FS_META * fs_meta, ext2fs_extent_header * header, int recursion_depth)
 {
     int fs_blocksize = fs_info->block_size;
     ext2fs_extent_idx *indices;
@@ -1723,6 +1723,13 @@ ext2fs_extent_tree_index_count(TSK_FS_INFO * fs_info,
     uint8_t *buf;
     int i;
 
+    // 32 is an arbitrary chosen value.
+    if (recursion_depth > 32) {
+        tsk_error_set_errno(TSK_ERR_FS_INODE_COR);
+        tsk_error_set_errstr
+            ("ext2fs_load_attrs: exceeded maximum recursion depth!");
+        return -1;
+    }
     if (tsk_getu16(fs_info->endian, header->eh_magic) != 0xF30A) {
         tsk_error_set_errno(TSK_ERR_FS_INODE_COR);
         tsk_error_set_errstr
@@ -1761,7 +1768,7 @@ ext2fs_extent_tree_index_count(TSK_FS_INFO * fs_info,
 
         if ((ret =
                 ext2fs_extent_tree_index_count(fs_info, fs_meta,
-                    (ext2fs_extent_header *) buf)) < 0) {
+                    (ext2fs_extent_header *) buf, recursion_depth + 1)) < 0) {
             return -1;
         }
         count += ret;
@@ -1936,7 +1943,7 @@ ext4_load_attrs_extents(TSK_FS_FILE *fs_file)
          }
         
         extent_index_size =
-        ext2fs_extent_tree_index_count(fs_info, fs_meta, header);
+        ext2fs_extent_tree_index_count(fs_info, fs_meta, header, 0);
         if (extent_index_size < 0) {
             return 1;
         }
diff --git a/tsk/fs/ext2fs_dent.c b/tsk/fs/ext2fs_dent.c
index 95e8b256961bdd504dcc91b34a2d4abeb2140e30..8fa140a8f3046d0d85b0038b3f02409a61ac2b80 100644
--- a/tsk/fs/ext2fs_dent.c
+++ b/tsk/fs/ext2fs_dent.c
@@ -232,12 +232,13 @@ ext2fs_dent_parse_block(EXT2FS_INFO * ext2fs, TSK_FS_DIR * a_fs_dir,
 * @param a_fs_dir Pointer to FS_DIR pointer. Can contain an already allocated
 * structure or a new structure.
 * @param a_addr Address of directory to process.
+* @param recursion_depth Recursion depth to limit the number of self-calls
 * @returns error, corruption, ok etc.
 */
 
 TSK_RETVAL_ENUM
 ext2fs_dir_open_meta(TSK_FS_INFO * a_fs, TSK_FS_DIR ** a_fs_dir,
-    TSK_INUM_T a_addr)
+    TSK_INUM_T a_addr, int recursion_depth)
 {
     EXT2FS_INFO *ext2fs = (EXT2FS_INFO *) a_fs;
     char *dirbuf;
diff --git a/tsk/fs/fatfs_dent.cpp b/tsk/fs/fatfs_dent.cpp
index c1e86f0558e4caa8d8151adec8682b26e8cf22c4..93d80bca90961b31f93b25bc165c32a759ba7843 100644
--- a/tsk/fs/fatfs_dent.cpp
+++ b/tsk/fs/fatfs_dent.cpp
@@ -219,12 +219,13 @@ static TSK_WALK_RET_ENUM
 * @param a_fs_dir Pointer to FS_DIR pointer. Can contain an already allocated
 * structure or a new structure.
 * @param a_addr Address of directory to process.
+* @param recursion_depth Recursion depth to limit the number of self-calls
 * @returns error, corruption, ok etc.
 */
 
 TSK_RETVAL_ENUM
     fatfs_dir_open_meta(TSK_FS_INFO * a_fs, TSK_FS_DIR ** a_fs_dir,
-    TSK_INUM_T a_addr)
+    TSK_INUM_T a_addr, int recursion_depth)
 {
     const char *func_name = "fatfs_dir_open_meta";
     TSK_OFF_T size, len;
@@ -344,7 +345,7 @@ TSK_RETVAL_ENUM
         "%s: Parsing directory %" PRIuINUM "\n",
         func_name, a_addr);
 
-    retval = fatfs->dent_parse_buf(fatfs, fs_dir, dirbuf, len, addrbuf);
+    retval = fatfs->dent_parse_buf(fatfs, fs_dir, dirbuf, len, addrbuf, recursion_depth);
 
     free(dirbuf);
     free(addrbuf);
diff --git a/tsk/fs/fatxxfs_dent.c b/tsk/fs/fatxxfs_dent.c
index a68c7c6f99a7e29a52358cc3f200891a7dbfc32d..ad191bafaf3c22a651eaf81e75bd7028127073cd 100755
--- a/tsk/fs/fatxxfs_dent.c
+++ b/tsk/fs/fatxxfs_dent.c
@@ -48,15 +48,16 @@ typedef struct {
  * be added.
  * @param buf Buffer that contains the directory contents.
  * @param len Length of buffer in bytes (must be a multiple of sector
-*  size).
+ *  size).
  * @param addrs Array where each element is the original address of
  * the corresponding sector in a_buf (size of array is number of sectors in
  * the directory).
+ * @param recursion_depth Recursion depth to limit the number of self-calls
  * @return TSK_RETVAL_ENUM
 */
 TSK_RETVAL_ENUM
 fatxxfs_dent_parse_buf(FATFS_INFO *fatfs, TSK_FS_DIR *a_fs_dir, char *buf,
-    TSK_OFF_T len, TSK_DADDR_T *addrs)
+    TSK_OFF_T len, TSK_DADDR_T *addrs, int recursion_depth)
 {
     char *func_name = "fatxxfs_dent_parse_buf";
     unsigned int idx = 0; 
@@ -370,12 +371,12 @@ fatxxfs_dent_parse_buf(FATFS_INFO *fatfs, TSK_FS_DIR *a_fs_dir, char *buf,
                         /* The parent directory is not in the list.  We are going to walk
                         * the directory until we hit this directory. This process will
                         * populate the buffer table and we will then rescan it */
-                        if (tsk_fs_dir_walk(fs, fs->root_inum,
+                        if (tsk_fs_dir_walk_internal(fs, fs->root_inum,
                             (TSK_FS_DIR_WALK_FLAG_ENUM)(TSK_FS_DIR_WALK_FLAG_ALLOC |
                             TSK_FS_DIR_WALK_FLAG_UNALLOC |
                             TSK_FS_DIR_WALK_FLAG_RECURSE),
                             fatfs_find_parent_act,
-                            (void *) &a_fs_dir->fs_file->meta->addr)) {
+                            (void *) &a_fs_dir->fs_file->meta->addr, recursion_depth)) {
                                 return TSK_OK;
                         }
 
diff --git a/tsk/fs/ffs_dent.c b/tsk/fs/ffs_dent.c
index a9e9aec334c72fb7343acb4c2bd7d2f6c83cd984..7a032ccda70e6074554e014a52f5efb9682b5561 100644
--- a/tsk/fs/ffs_dent.c
+++ b/tsk/fs/ffs_dent.c
@@ -222,11 +222,12 @@ ffs_dent_parse_block(FFS_INFO * ffs, TSK_FS_DIR * fs_dir, uint8_t a_is_del,
  * @param a_fs_dir Pointer to FS_DIR pointer. Can contain an already allocated
  * structure or a new structure.
  * @param a_addr Address of directory to process.
+ * @param recursion_depth Recursion depth to limit the number of self-calls
  * @returns error, corruption, ok etc.
  */
 TSK_RETVAL_ENUM
 ffs_dir_open_meta(TSK_FS_INFO * a_fs, TSK_FS_DIR ** a_fs_dir,
-    TSK_INUM_T a_addr)
+    TSK_INUM_T a_addr, int recursion_depth)
 {
     TSK_OFF_T size;
     FFS_INFO *ffs = (FFS_INFO *) a_fs;
diff --git a/tsk/fs/fs_dir.c b/tsk/fs/fs_dir.c
index b80c0012fbaa0895f28c27fe24c01ef5f76d2db5..873994712636d436572cd780dc27739fef5a8f24 100644
--- a/tsk/fs/fs_dir.c
+++ b/tsk/fs/fs_dir.c
@@ -266,14 +266,17 @@ tsk_fs_dir_add(TSK_FS_DIR * a_fs_dir, const TSK_FS_NAME * a_fs_name)
 
 
 
-/** \ingroup fslib
+/** \internal
+* Internal version of the tsk_fs_dir_open_meta function with macro recursion depth.  
+*
 * Open a directory (using its metadata addr) so that each of the files in it can be accessed.
 * @param a_fs File system to analyze
 * @param a_addr Metadata address of the directory to open
+* @param macro_recursion_depth Recursion depth to limit the number of calls if the underlying file system needs to call methods to resolve. 
 * @returns NULL on error
 */
-TSK_FS_DIR *
-tsk_fs_dir_open_meta(TSK_FS_INFO * a_fs, TSK_INUM_T a_addr)
+static TSK_FS_DIR *
+tsk_fs_dir_open_meta_internal(TSK_FS_INFO * a_fs, TSK_INUM_T a_addr, int macro_recursion_depth)
 {
     TSK_FS_DIR *fs_dir = NULL;
     TSK_RETVAL_ENUM retval;
@@ -282,11 +285,11 @@ tsk_fs_dir_open_meta(TSK_FS_INFO * a_fs, TSK_INUM_T a_addr)
         || (a_fs->dir_open_meta == NULL)) {
         tsk_error_set_errno(TSK_ERR_FS_ARG);
         tsk_error_set_errstr
-            ("tsk_fs_dir_open_meta: called with NULL or unallocated structures");
+            ("tsk_fs_dir_open_meta_internal: called with NULL or unallocated structures");
         return NULL;
     }
 
-    retval = a_fs->dir_open_meta(a_fs, &fs_dir, a_addr);
+    retval = a_fs->dir_open_meta(a_fs, &fs_dir, a_addr, macro_recursion_depth);
     if (retval != TSK_OK) {
         tsk_fs_dir_close(fs_dir);
         return NULL;
@@ -296,6 +299,21 @@ tsk_fs_dir_open_meta(TSK_FS_INFO * a_fs, TSK_INUM_T a_addr)
 }
 
 
+
+/** \ingroup fslib
+* Open a directory (using its metadata addr) so that each of the files in it can be accessed.
+*
+* @param a_fs File system to analyze
+* @param a_addr Metadata address of the directory to open
+* @returns NULL on error
+*/
+TSK_FS_DIR *
+tsk_fs_dir_open_meta(TSK_FS_INFO * a_fs, TSK_INUM_T a_addr)
+{
+    return tsk_fs_dir_open_meta_internal(a_fs, a_addr, 0);
+}
+
+
 /** \ingroup fslib
 * Open a directory (using its path) so that each of the files in it can be accessed.
 * @param a_fs File system to analyze
@@ -486,7 +504,7 @@ tsk_fs_dir_get_name(const TSK_FS_DIR * a_fs_dir, size_t a_idx)
 #define DIR_STRSZ   4096
 
 /** \internal
- * used to keep state between calls to dir_walk_lcl
+ * used to keep state between calls to dir_walk_recurse
  */
 typedef struct {
     /* Recursive path stuff */
@@ -610,11 +628,11 @@ prioritizeDirNames(TSK_FS_NAME * names, size_t count, int * indexToOrderedIndex)
 }
 
 /* dir_walk local function that is used for recursive calls.  Callers
- * should initially call the non-local version. */
+ * should initially call the non-recursive version. */
 static TSK_WALK_RET_ENUM
-tsk_fs_dir_walk_lcl(TSK_FS_INFO * a_fs, DENT_DINFO * a_dinfo,
+tsk_fs_dir_walk_recursive(TSK_FS_INFO * a_fs, DENT_DINFO * a_dinfo,
     TSK_INUM_T a_addr, TSK_FS_DIR_WALK_FLAG_ENUM a_flags,
-    TSK_FS_DIR_WALK_CB a_action, void *a_ptr)
+    TSK_FS_DIR_WALK_CB a_action, void *a_ptr, int macro_recursion_depth)
 {
     TSK_FS_DIR *fs_dir;
     TSK_FS_FILE *fs_file;
@@ -622,7 +640,7 @@ tsk_fs_dir_walk_lcl(TSK_FS_INFO * a_fs, DENT_DINFO * a_dinfo,
     int* indexToOrderedIndex = NULL;
 
     // get the list of entries in the directory
-    if ((fs_dir = tsk_fs_dir_open_meta(a_fs, a_addr)) == NULL) {
+    if ((fs_dir = tsk_fs_dir_open_meta_internal(a_fs, a_addr, macro_recursion_depth + 1)) == NULL) {
         return TSK_WALK_ERROR;
     }
 
@@ -779,14 +797,21 @@ tsk_fs_dir_walk_lcl(TSK_FS_INFO * a_fs, DENT_DINFO * a_dinfo,
                 }
 
                 /* If we've exceeded the max depth or max length, don't
-                 * recurse any further into this directory */
+                 * recurse any further into this directory 
+                 * NOTE: We have two concepts of recursion detection in
+                 * here.  This one is based on within a top-level call
+                 * to dir_walk.  The macro_recursion_depth value allows
+                 * us to detect when file systems need to call dir_walk
+                 * to resolve things and they get into an infinite loop.
+                 * Perhaps they can be unified some day. 
+                 */
                 if ((a_dinfo->depth >= MAX_DEPTH) ||
                     (DIR_STRSZ <=
                         strlen(a_dinfo->dirs) +
                         strlen(fs_file->name->name))) {   
                     if (tsk_verbose) {
                         tsk_fprintf(stdout,
-                            "tsk_fs_dir_walk_lcl: directory : %"
+                            "tsk_fs_dir_walk_recursive: directory : %"
                             PRIuINUM " exceeded max length / depth\n", fs_file->name->meta_addr);
                     }
 
@@ -817,15 +842,15 @@ tsk_fs_dir_walk_lcl(TSK_FS_INFO * a_fs, DENT_DINFO * a_dinfo,
                     save_bak = a_dinfo->save_inum_named;
                     a_dinfo->save_inum_named = 0;
                 }
-                retval = tsk_fs_dir_walk_lcl(a_fs,
+                retval = tsk_fs_dir_walk_recursive(a_fs,
                     a_dinfo, fs_file->name->meta_addr, a_flags,
-                    a_action, a_ptr);
+                    a_action, a_ptr, macro_recursion_depth + 1);
                 if (retval == TSK_WALK_ERROR) {
                     /* If this fails because the directory could not be
                      * loaded, then we still continue */
                     if (tsk_verbose) {
                         tsk_fprintf(stderr,
-                            "tsk_fs_dir_walk_lcl: error reading directory: %"
+                            "tsk_fs_dir_walk_recursive: error reading directory: %"
                             PRIuINUM "\n", fs_file->name->meta_addr);
                         tsk_error_print(stderr);
                     }
@@ -857,7 +882,7 @@ tsk_fs_dir_walk_lcl(TSK_FS_INFO * a_fs, DENT_DINFO * a_dinfo,
             else {
                 if (tsk_verbose)
                     fprintf(stderr,
-                        "tsk_fs_dir_walk_lcl: Loop detected with address %"
+                        "tsk_fs_dir_walk_recursive: Loop detected with address %"
                         PRIuINUM, fs_file->name->meta_addr);
             }
         }
@@ -883,20 +908,23 @@ tsk_fs_dir_walk_lcl(TSK_FS_INFO * a_fs, DENT_DINFO * a_dinfo,
 }
 
 
-/** \ingroup fslib
-* Walk the file names in a directory and obtain the details of the files via a callback.
+/** \internal
+* Internal version of the tsk_fs_dir_walk function with recursion depth.
+* This should be called by file systems when they need to start a new dir_walk
+* to resolve something and they may already be inside of a walk. 
 *
 * @param a_fs File system to analyze
 * @param a_addr Metadata address of the directory to analyze
 * @param a_flags Flags used during analysis
 * @param a_action Callback function that is called for each file name
 * @param a_ptr Pointer to data that is passed to the callback function each time
+* @param macro_recursion_depth Recursion depth to limit the number of self-calls in case the underlying file system also needs to make calls into dir_walk
 * @returns 1 on error and 0 on success
 */
 uint8_t
-tsk_fs_dir_walk(TSK_FS_INFO * a_fs, TSK_INUM_T a_addr,
+tsk_fs_dir_walk_internal(TSK_FS_INFO * a_fs, TSK_INUM_T a_addr,
     TSK_FS_DIR_WALK_FLAG_ENUM a_flags, TSK_FS_DIR_WALK_CB a_action,
-    void *a_ptr)
+    void *a_ptr, int macro_recursion_depth)
 {
     DENT_DINFO dinfo;
     TSK_WALK_RET_ENUM retval;
@@ -904,7 +932,17 @@ tsk_fs_dir_walk(TSK_FS_INFO * a_fs, TSK_INUM_T a_addr,
     if ((a_fs == NULL) || (a_fs->tag != TSK_FS_INFO_TAG)) {
         tsk_error_set_errno(TSK_ERR_FS_ARG);
         tsk_error_set_errstr
-            ("tsk_fs_dir_walk: called with NULL or unallocated structures");
+            ("tsk_fs_dir_walk_internal: called with NULL or unallocated structures");
+        return 1;
+    }
+
+    // 128 is a somewhat arbitrary value.
+    // https://github.com/sleuthkit/sleuthkit/issues/1859 identified
+    // an overflow with 240 levels of recursion with FAT
+    if (macro_recursion_depth > 128) {
+        tsk_error_set_errno(TSK_ERR_FS_ARG);
+        tsk_error_set_errstr
+            ("tsk_fs_dir_walk_internal: recursion depth exceeds maximum (%d)", macro_recursion_depth);
         return 1;
     }
 
@@ -930,8 +968,8 @@ tsk_fs_dir_walk(TSK_FS_INFO * a_fs, TSK_INUM_T a_addr,
     }
     tsk_release_lock(&a_fs->list_inum_named_lock);
 
-    retval = tsk_fs_dir_walk_lcl(a_fs, &dinfo, a_addr, a_flags,
-        a_action, a_ptr);
+    retval = tsk_fs_dir_walk_recursive(a_fs, &dinfo, a_addr, a_flags,
+        a_action, a_ptr, macro_recursion_depth);
 
     // if we were saving the list of named files in the temp list,
     // then now save them to FS_INFO
@@ -957,6 +995,24 @@ tsk_fs_dir_walk(TSK_FS_INFO * a_fs, TSK_INUM_T a_addr,
 }
 
 
+/** \ingroup fslib
+* Walk the file names in a directory and obtain the details of the files via a callback.
+*
+* @param a_fs File system to analyze
+* @param a_addr Metadata address of the directory to analyze
+* @param a_flags Flags used during analysis
+* @param a_action Callback function that is called for each file name
+* @param a_ptr Pointer to data that is passed to the callback function each time
+* @returns 1 on error and 0 on success
+*/
+uint8_t
+tsk_fs_dir_walk(TSK_FS_INFO * a_fs, TSK_INUM_T a_addr,
+    TSK_FS_DIR_WALK_FLAG_ENUM a_flags, TSK_FS_DIR_WALK_CB a_action,
+    void *a_ptr)
+{
+	return tsk_fs_dir_walk_internal(a_fs, a_addr, a_flags, a_action, a_ptr, 0);
+}
+
 /** \internal
 * Create a dummy NAME entry for the Orphan file virtual directory.
 * @param a_fs File system directory is for
@@ -1079,9 +1135,9 @@ tsk_fs_dir_load_inum_named(TSK_FS_INFO * a_fs)
      * specify UNALLOC only as a flag on the assumption that there will
      * be fewer callbacks for UNALLOC than ALLOC.
      */
-    if (tsk_fs_dir_walk(a_fs, a_fs->root_inum,
+    if (tsk_fs_dir_walk_internal(a_fs, a_fs->root_inum,
             TSK_FS_NAME_FLAG_UNALLOC | TSK_FS_DIR_WALK_FLAG_RECURSE |
-            TSK_FS_DIR_WALK_FLAG_NOORPHAN, load_named_dir_walk_cb, NULL)) {
+            TSK_FS_DIR_WALK_FLAG_NOORPHAN, load_named_dir_walk_cb, NULL, 0)) {
         tsk_error_errstr2_concat
             ("- tsk_fs_dir_load_inum_named: identifying inodes allocated by file names");
         return TSK_ERR;
@@ -1221,10 +1277,10 @@ find_orphan_meta_walk_cb(TSK_FS_FILE * a_fs_file, void *a_ptr)
                 "find_orphan_meta_walk_cb: Going into directory %" PRIuINUM
                 " to mark contents as seen\n", a_fs_file->meta->addr);
 
-        if (tsk_fs_dir_walk(fs, a_fs_file->meta->addr,
+        if (tsk_fs_dir_walk_internal(fs, a_fs_file->meta->addr,
                 TSK_FS_DIR_WALK_FLAG_UNALLOC | TSK_FS_DIR_WALK_FLAG_RECURSE
                 | TSK_FS_DIR_WALK_FLAG_NOORPHAN, load_orphan_dir_walk_cb,
-                data)) {
+                data, 0)) {
             tsk_error_errstr2_concat
                 (" - find_orphan_meta_walk_cb: identifying inodes allocated by file names");
             return TSK_WALK_ERROR;
diff --git a/tsk/fs/fs_open.c b/tsk/fs/fs_open.c
index c3e96e4e3a2a6a2f2af63310f834b01da4320b52..dc5eed72d52a01ec3a4edbe63b391f2b43eee7be 100644
--- a/tsk/fs/fs_open.c
+++ b/tsk/fs/fs_open.c
@@ -206,7 +206,7 @@ tsk_fs_open_img_decrypt(TSK_IMG_INFO * a_img_info, TSK_OFF_T a_offset,
                     unsupportedSignatureFound = 1;
                     tsk_error_reset();
                     tsk_error_set_errno(TSK_ERR_IMG_UNSUPTYPE);
-                    tsk_error_set_errstr(imageType);
+                    tsk_error_set_errstr("%s", imageType);
                     free(imageType);
                 }
             }
@@ -217,11 +217,11 @@ tsk_fs_open_img_decrypt(TSK_IMG_INFO * a_img_info, TSK_OFF_T a_offset,
                 if (result != NULL) {
                     if (result->encryptionType == ENCRYPTION_DETECTED_SIGNATURE) {
                         tsk_error_set_errno(TSK_ERR_FS_ENCRYPTED);
-                        tsk_error_set_errstr(result->desc);
+                        tsk_error_set_errstr("%s", result->desc);
                     }
                     else if (result->encryptionType == ENCRYPTION_DETECTED_ENTROPY) {
                         tsk_error_set_errno(TSK_ERR_FS_POSSIBLY_ENCRYPTED);
-                        tsk_error_set_errstr(result->desc);
+                        tsk_error_set_errstr("%s", result->desc);
                     }
                     else {
                         tsk_error_set_errno(TSK_ERR_FS_UNKTYPE);
diff --git a/tsk/fs/hfs_dent.c b/tsk/fs/hfs_dent.c
index 54460f14bfe078ca31ef3edf85d849ecb79af46d..0c1f97dd12333e671e32a929040c7859fe525dd7 100644
--- a/tsk/fs/hfs_dent.c
+++ b/tsk/fs/hfs_dent.c
@@ -384,11 +384,12 @@ hfs_dir_open_meta_cb(HFS_INFO * hfs, int8_t level_type,
 * @param a_fs_dir Pointer to FS_DIR pointer. Can contain an already allocated
 * structure or a new structure.
 * @param a_addr Address of directory to process.
+* @param recursion_depth Recursion depth to limit the number of self-calls
 * @returns error, corruption, ok etc.
 */
 TSK_RETVAL_ENUM
 hfs_dir_open_meta(TSK_FS_INFO * fs, TSK_FS_DIR ** a_fs_dir,
-    TSK_INUM_T a_addr)
+    TSK_INUM_T a_addr, int recursion_depth)
 {
     HFS_INFO *hfs = (HFS_INFO *) fs;
     uint32_t cnid;              /* catalog node ID of the entry (= inum) */
diff --git a/tsk/fs/iso9660_dent.c b/tsk/fs/iso9660_dent.c
index 34de246a422e63863ad0a0118c70e7981149740b..8765f2a33f0187652e24ce91286b723690b15de9 100644
--- a/tsk/fs/iso9660_dent.c
+++ b/tsk/fs/iso9660_dent.c
@@ -218,11 +218,12 @@ iso9660_proc_dir(TSK_FS_INFO * a_fs, TSK_FS_DIR * a_fs_dir, const char *buf,
  * @param a_fs_dir Pointer to FS_DIR pointer. Can contain an already allocated
  * structure or a new structure.
  * @param a_addr Address of directory to process.
+ * @param recursion_depth Recursion depth to limit the number of self-calls
  * @returns error, corruption, ok etc.
  */
 TSK_RETVAL_ENUM
 iso9660_dir_open_meta(TSK_FS_INFO * a_fs, TSK_FS_DIR ** a_fs_dir,
-    TSK_INUM_T a_addr)
+    TSK_INUM_T a_addr, int recursion_depth)
 {
     TSK_RETVAL_ENUM retval;
     TSK_FS_DIR *fs_dir;
diff --git a/tsk/fs/nofs_misc.c b/tsk/fs/nofs_misc.c
index 0829d1a7dd879e5a5ce0538892510fd3c4151f10..5e24dc59183488e1ef43323fbe959782d76c381a 100644
--- a/tsk/fs/nofs_misc.c
+++ b/tsk/fs/nofs_misc.c
@@ -215,7 +215,7 @@ tsk_fs_nofs_istat(TSK_FS_INFO * a_fs, TSK_FS_ISTAT_FLAG_ENUM istat_flags, FILE *
  */
 TSK_RETVAL_ENUM
 tsk_fs_nofs_dir_open_meta(TSK_FS_INFO * a_fs, TSK_FS_DIR ** a_fs_dir,
-    TSK_INUM_T a_addr)
+    TSK_INUM_T a_addr, int recursion_depth)
 {
     tsk_error_reset();
     tsk_error_set_errno(TSK_ERR_FS_UNSUPFUNC);
diff --git a/tsk/fs/ntfs.c b/tsk/fs/ntfs.c
old mode 100755
new mode 100644
index 1761856acb6dd37498da081a945f67e0ecfd7583..ce46f1909a8e9cb6491c91ebbd1d2aaee9f8086a
--- a/tsk/fs/ntfs.c
+++ b/tsk/fs/ntfs.c
@@ -379,6 +379,9 @@ ntfs_dinode_lookup(NTFS_INFO * a_ntfs, char *a_buf, TSK_INUM_T a_mftnum)
     uint16_t upd_off = tsk_getu16(fs->endian, mft->upd_off);
 
     // Make sure upd_cnt > 0 to prevent an integer wrap around.
+    // NOTE: There is a bug here because upd_cnt can be for unused entries.
+    // They are now skipped (as of July 2021). We shoudl refactor this code
+    // to allow upd_cnt = 0. 
     if ((upd_cnt == 0) || (upd_cnt > (((a_ntfs->mft_rsize_b) / 2) + 1))) {
         tsk_error_reset();
         tsk_error_set_errno(TSK_ERR_FS_INODE_COR);
@@ -851,7 +854,16 @@ static int
 ntfs_uncompress_setup(TSK_FS_INFO * fs, NTFS_COMP_INFO * comp,
     uint32_t compunit_size_c)
 {
+    if (fs->block_size == 0 || compunit_size_c == 0) {
+        return 1;
+    }
     comp->buf_size_b = fs->block_size * compunit_size_c;
+
+    // Detect an integer overflow e.g. 65536 * 65536
+    if (comp->buf_size_b < fs->block_size) {
+        return 1;
+    }
+
     if ((comp->uncomp_buf = tsk_malloc(comp->buf_size_b)) == NULL) {
         comp->buf_size_b = 0;
         return 1;
@@ -1214,6 +1226,14 @@ ntfs_proc_compunit(NTFS_INFO * ntfs, NTFS_COMP_INFO * comp,
         for (a = 0; a < comp_unit_size; a++) {
             ssize_t cnt;
 
+            // Prevent an OOB write of comp->uncomp_buf
+            if ((comp->uncomp_idx >= comp->buf_size_b) || (fs->block_size > comp->buf_size_b - comp->uncomp_idx)) {
+                tsk_error_reset();
+                tsk_error_set_errno(TSK_ERR_FS_READ);
+                tsk_error_set_errstr("ntfs_proc_compunit: Buffer not big enough for uncompressed data (Index: %"PRIuSIZE ")", comp->uncomp_idx);
+                return 1;
+            }
+
             cnt =
                 tsk_fs_read_block(fs, comp_unit[a],
                 &comp->uncomp_buf[comp->uncomp_idx], fs->block_size);
diff --git a/tsk/fs/ntfs_dent.cpp b/tsk/fs/ntfs_dent.cpp
index 9070182fe3ec326f97ea47ed1405cbbde2f706a7..ee3b772b447af7ae0062a034c6b12e9356769d92 100644
--- a/tsk/fs/ntfs_dent.cpp
+++ b/tsk/fs/ntfs_dent.cpp
@@ -743,11 +743,12 @@ ntfs_fix_idxrec(NTFS_INFO * ntfs, ntfs_idxrec * idxrec, uint32_t len)
 * @param a_fs_dir Pointer to FS_DIR pointer. Can contain an already allocated
 * structure or a new structure.
 * @param a_addr Address of directory to process.
+* @param recursion_depth Recursion depth to limit the number of self-calls
 * @returns error, corruption, ok etc.
 */
 TSK_RETVAL_ENUM
 ntfs_dir_open_meta(TSK_FS_INFO * a_fs, TSK_FS_DIR ** a_fs_dir,
-    TSK_INUM_T a_addr)
+    TSK_INUM_T a_addr, int recursion_depth)
 {
     NTFS_INFO *ntfs = (NTFS_INFO *) a_fs;
     TSK_FS_DIR *fs_dir;
diff --git a/tsk/fs/tsk_exfatfs.h b/tsk/fs/tsk_exfatfs.h
index 2f88ab145c2a9b47a7f0c431c4c8888f98859b55..475672fe1f6c482ec27dfc1d5f43be5a0261bcd4 100755
--- a/tsk/fs/tsk_exfatfs.h
+++ b/tsk/fs/tsk_exfatfs.h
@@ -401,7 +401,7 @@ extern "C" {
 
     extern TSK_RETVAL_ENUM
     exfatfs_dent_parse_buf(FATFS_INFO *a_fatfs, TSK_FS_DIR *a_fs_dir, char *a_buf,
-        TSK_OFF_T a_buf_len, TSK_DADDR_T *a_sector_addrs);
+        TSK_OFF_T a_buf_len, TSK_DADDR_T *a_sector_addrs, int recursion_depth);
 
 #ifdef __cplusplus
 }
diff --git a/tsk/fs/tsk_ext2fs.h b/tsk/fs/tsk_ext2fs.h
index 71640f5fd67ffbb68815568886a7bd8263e17adc..348b6b9ae2070db67a8a78b06d24f3ae02bd8ea0 100644
--- a/tsk/fs/tsk_ext2fs.h
+++ b/tsk/fs/tsk_ext2fs.h
@@ -679,7 +679,7 @@ extern "C" {
 
     extern TSK_RETVAL_ENUM
         ext2fs_dir_open_meta(TSK_FS_INFO * a_fs, TSK_FS_DIR ** a_fs_dir,
-        TSK_INUM_T a_addr);
+        TSK_INUM_T a_addr, int recursion_depth);
     extern uint8_t ext2fs_jentry_walk(TSK_FS_INFO *, int,
         TSK_FS_JENTRY_WALK_CB, void *);
     extern uint8_t ext2fs_jblk_walk(TSK_FS_INFO *, TSK_DADDR_T,
diff --git a/tsk/fs/tsk_fatfs.h b/tsk/fs/tsk_fatfs.h
index ba60294c282bdd8ee67ccf72f5545d4af756a07b..1e3133728618852d025fbe9fb02149f0d011f848 100644
--- a/tsk/fs/tsk_fatfs.h
+++ b/tsk/fs/tsk_fatfs.h
@@ -266,7 +266,7 @@ extern "C" {
 
         TSK_RETVAL_ENUM (*dent_parse_buf)(FATFS_INFO *a_fatfs, 
             TSK_FS_DIR *a_fs_dir, char *a_buf, TSK_OFF_T a_buf_len, 
-            TSK_DADDR_T *a_sector_addrs);
+            TSK_DADDR_T *a_sector_addrs, int recursion_depth);
 
         TSK_RETVAL_ENUM (*dinode_copy)(FATFS_INFO *a_fatfs, TSK_INUM_T a_inum, 
             FATFS_DENTRY *a_dentry, uint8_t a_cluster_is_alloc, TSK_FS_FILE *a_fs_file);
@@ -347,7 +347,7 @@ extern "C" {
 
     extern TSK_RETVAL_ENUM
         fatfs_dir_open_meta(TSK_FS_INFO * a_fs, TSK_FS_DIR ** a_fs_dir,
-        TSK_INUM_T a_addr);
+        TSK_INUM_T a_addr, int recursion_depth);
 
     extern int fatfs_name_cmp(TSK_FS_INFO *, const char *, const char *);
 
diff --git a/tsk/fs/tsk_fatxxfs.h b/tsk/fs/tsk_fatxxfs.h
index e315a244811f02ebd9f2c921e6df97d87883eecc..919458e74d095357102c5f250784810b97a3ca4f 100755
--- a/tsk/fs/tsk_fatxxfs.h
+++ b/tsk/fs/tsk_fatxxfs.h
@@ -195,7 +195,7 @@ extern "C" {
 
     extern TSK_RETVAL_ENUM
     fatxxfs_dent_parse_buf(FATFS_INFO * fatfs, TSK_FS_DIR * a_fs_dir, char *buf,
-        TSK_OFF_T len, TSK_DADDR_T * addrs);
+        TSK_OFF_T len, TSK_DADDR_T * addrs, int recursion_depth);
 
 #ifdef __cplusplus
 }
diff --git a/tsk/fs/tsk_ffs.h b/tsk/fs/tsk_ffs.h
index 08f55341506142248d1700b0bb5b809d56795212..0255b5c91e461c2772f949798c7251092b192164 100644
--- a/tsk/fs/tsk_ffs.h
+++ b/tsk/fs/tsk_ffs.h
@@ -498,7 +498,7 @@ extern "C" {
     } FFS_INFO;
 
     extern TSK_RETVAL_ENUM ffs_dir_open_meta(TSK_FS_INFO * a_fs,
-        TSK_FS_DIR ** a_fs_dir, TSK_INUM_T a_addr);
+        TSK_FS_DIR ** a_fs_dir, TSK_INUM_T a_addr, int recursion_depth);
 
 #ifdef __cplusplus
 }
diff --git a/tsk/fs/tsk_fs.h b/tsk/fs/tsk_fs.h
index d0b5d1dc59697222a59fc889a8e3de8248a17e45..dca96c0b5b6182cd6c4bb6438c8f4dc0265485d8 100644
--- a/tsk/fs/tsk_fs.h
+++ b/tsk/fs/tsk_fs.h
@@ -1016,7 +1016,7 @@ extern "C" {
          uint8_t(*istat) (TSK_FS_INFO * fs, TSK_FS_ISTAT_FLAG_ENUM flags, FILE * hFile, TSK_INUM_T inum,
             TSK_DADDR_T numblock, int32_t sec_skew);
 
-         TSK_RETVAL_ENUM(*dir_open_meta) (TSK_FS_INFO * fs, TSK_FS_DIR ** a_fs_dir, TSK_INUM_T inode);  ///< \internal Call tsk_fs_dir_open_meta() instead.
+         TSK_RETVAL_ENUM(*dir_open_meta) (TSK_FS_INFO * fs, TSK_FS_DIR ** a_fs_dir, TSK_INUM_T inode, int recursion_depth);  ///< \internal Call tsk_fs_dir_open_meta() instead.
 
          uint8_t(*jopen) (TSK_FS_INFO *, TSK_INUM_T);   ///< \internal
 
diff --git a/tsk/fs/tsk_fs_i.h b/tsk/fs/tsk_fs_i.h
index 0340e82acf2e602e8b2929acab4ac8cd5ceada27..e0298f93eef17397a7d089cee684aed95e53bf53 100644
--- a/tsk/fs/tsk_fs_i.h
+++ b/tsk/fs/tsk_fs_i.h
@@ -141,6 +141,9 @@ extern "C" {
     extern void tsk_fs_dir_reset(TSK_FS_DIR * a_fs_dir);
     extern uint8_t tsk_fs_dir_contains(TSK_FS_DIR * a_fs_dir, TSK_INUM_T meta_addr, uint32_t hash);
     extern uint32_t tsk_fs_dir_hash(const char *str);
+    extern uint8_t tsk_fs_dir_walk_internal(TSK_FS_INFO * a_fs, TSK_INUM_T a_addr,
+        TSK_FS_DIR_WALK_FLAG_ENUM a_flags, TSK_FS_DIR_WALK_CB a_action,
+        void *a_ptr, int macro_recursion_depth);
 
     /* Orphan Directory Support */
     TSK_RETVAL_ENUM tsk_fs_dir_load_inum_named(TSK_FS_INFO * a_fs);
@@ -228,7 +231,7 @@ extern "C" {
     extern uint8_t tsk_fs_nofs_istat(TSK_FS_INFO * a_fs, TSK_FS_ISTAT_FLAG_ENUM istat_flags, FILE * hFile,
         TSK_INUM_T inum, TSK_DADDR_T numblock, int32_t sec_skew);
     extern TSK_RETVAL_ENUM tsk_fs_nofs_dir_open_meta(TSK_FS_INFO * a_fs,
-        TSK_FS_DIR ** a_fs_dir, TSK_INUM_T a_addr);
+        TSK_FS_DIR ** a_fs_dir, TSK_INUM_T a_addr, int recursion_depth);
     extern uint8_t tsk_fs_nofs_jopen(TSK_FS_INFO * a_fs, TSK_INUM_T inum);
     extern uint8_t tsk_fs_nofs_jentry_walk(TSK_FS_INFO * a_fs,
         int a_flags, TSK_FS_JENTRY_WALK_CB a_action, void *a_ptr);
diff --git a/tsk/fs/tsk_hfs.h b/tsk/fs/tsk_hfs.h
index 2530e0cfe324680852dd25d011cc5bbb49eb03c5..93323518ac1ac148ffab1297902943bd538c06bd 100644
--- a/tsk/fs/tsk_hfs.h
+++ b/tsk/fs/tsk_hfs.h
@@ -740,7 +740,7 @@ extern uint16_t hfs_get_idxkeylen(HFS_INFO * hfs, uint16_t keylen,
 
 
 extern TSK_RETVAL_ENUM hfs_dir_open_meta(TSK_FS_INFO *, TSK_FS_DIR **,
-    TSK_INUM_T);
+    TSK_INUM_T, int);
 extern int hfs_name_cmp(TSK_FS_INFO *, const char *, const char *);
 
 extern uint8_t hfs_jopen(TSK_FS_INFO *, TSK_INUM_T);
diff --git a/tsk/fs/tsk_iso9660.h b/tsk/fs/tsk_iso9660.h
index c822d1847d454214bdd00a73fefe4a0b51438153..7deaa97272dba803d9b774ae9ef3eaae6bbd99dd 100644
--- a/tsk/fs/tsk_iso9660.h
+++ b/tsk/fs/tsk_iso9660.h
@@ -392,7 +392,7 @@ typedef struct {
 } ISO_INFO;
 
 extern TSK_RETVAL_ENUM iso9660_dir_open_meta(TSK_FS_INFO * a_fs,
-    TSK_FS_DIR ** a_fs_dir, TSK_INUM_T a_addr);
+    TSK_FS_DIR ** a_fs_dir, TSK_INUM_T a_addr, int recursion_depth);
 
 extern uint8_t iso9660_dinode_load(ISO_INFO * iso, TSK_INUM_T inum,
     iso9660_inode * dinode);
diff --git a/tsk/fs/tsk_ntfs.h b/tsk/fs/tsk_ntfs.h
index 6364dc8bf219ed99612d5d17f0db098bc0424e74..c16696a75c5209341872913297f7910e035ace3b 100644
--- a/tsk/fs/tsk_ntfs.h
+++ b/tsk/fs/tsk_ntfs.h
@@ -750,7 +750,7 @@ extern "C" {
     extern TSK_RETVAL_ENUM ntfs_dinode_lookup(NTFS_INFO *, char *,
         TSK_INUM_T);
     extern TSK_RETVAL_ENUM ntfs_dir_open_meta(TSK_FS_INFO * a_fs,
-        TSK_FS_DIR ** a_fs_dir, TSK_INUM_T a_addr);
+        TSK_FS_DIR ** a_fs_dir, TSK_INUM_T a_addr, int recursion_depth);
 
     extern void ntfs_orphan_map_free(NTFS_INFO * a_ntfs);
 
diff --git a/tsk/fs/yaffs.cpp b/tsk/fs/yaffs.cpp
index 620f38e70410303c6a9d840b7e556e70322613fa..b8798126ae76bde351e734461cf486c037cecfc0 100755
--- a/tsk/fs/yaffs.cpp
+++ b/tsk/fs/yaffs.cpp
@@ -2686,7 +2686,7 @@ static TSK_RETVAL_ENUM
 
 static TSK_RETVAL_ENUM
     yaffsfs_dir_open_meta(TSK_FS_INFO *a_fs, TSK_FS_DIR ** a_fs_dir,
-    TSK_INUM_T a_addr)
+    TSK_INUM_T a_addr, int recursion_depth)
 {
     TSK_FS_DIR *fs_dir;
     TSK_FS_NAME *fs_name;
diff --git a/tsk/img/img_open.cpp b/tsk/img/img_open.cpp
index 6f879e3cadcdd6b121c3d8f3c07f254a2200bc4e..f9f2e672734923637bf4afa587a183fd78f987b2 100644
--- a/tsk/img/img_open.cpp
+++ b/tsk/img/img_open.cpp
@@ -230,18 +230,6 @@ tsk_img_open(int num_img,
         return NULL;
     }
 
-#if HAVE_LIBVHDI
-    case TSK_IMG_TYPE_VHD_VHD:
-        img_info = vhdi_open(num_img, images, a_ssize);
-        break;
-#endif
-
-#if HAVE_LIBVMDK
-    case TSK_IMG_TYPE_VMDK_VMDK:
-        img_info = vmdk_open(num_img, images, a_ssize);
-        break;
-#endif
-
     case TSK_IMG_TYPE_RAW:
         img_info = raw_open(num_img, images, a_ssize);
         break;
@@ -261,6 +249,18 @@ tsk_img_open(int num_img,
         break;
 #endif
 
+#if HAVE_LIBVMDK
+    case TSK_IMG_TYPE_VMDK_VMDK:
+        img_info = vmdk_open(num_img, images, a_ssize);
+        break;
+#endif
+
+#if HAVE_LIBVHDI
+    case TSK_IMG_TYPE_VHD_VHD:
+        img_info = vhdi_open(num_img, images, a_ssize);
+        break;
+#endif
+
     default:
         tsk_error_reset();
         tsk_error_set_errno(TSK_ERR_IMG_UNSUPTYPE);
diff --git a/tsk/util/detect_encryption.c b/tsk/util/detect_encryption.c
index 9e64cbcaac5996059ec05c7eb0a4dc18261d3e62..f50db1549da8f3eff0cb283fdd60b72008e2ca72 100644
--- a/tsk/util/detect_encryption.c
+++ b/tsk/util/detect_encryption.c
@@ -121,11 +121,11 @@ calculateEntropy(TSK_IMG_INFO * img_info, TSK_DADDR_T offset) {
             break;
         }
 
-        if (tsk_img_read(img_info, offset + i * bufLen, buf, bufLen) != bufLen) {
+        if (tsk_img_read(img_info, offset + i * bufLen, buf, bufLen) != (ssize_t) bufLen) {
             break;
         }
 
-        for (int j = 0; j < bufLen; j++) {
+        for (size_t j = 0; j < bufLen; j++) {
             unsigned char b = buf[j] & 0xff;
             byteCounts[b]++;
         }
@@ -176,7 +176,7 @@ detectVolumeEncryption(TSK_IMG_INFO * img_info, TSK_DADDR_T offset) {
     if (buf == NULL) {
         return result;
     }
-    if (tsk_img_read(img_info, offset, buf, len) != len) {
+    if (tsk_img_read(img_info, offset, buf, len) != (ssize_t)len) {
         free(buf);
         return result;
     }
@@ -250,7 +250,7 @@ detectDiskEncryption(TSK_IMG_INFO * img_info, TSK_DADDR_T offset) {
     if (buf == NULL) {
         return result;
     }
-    if (tsk_img_read(img_info, offset, buf, len) != len) {
+    if (tsk_img_read(img_info, offset, buf, len) != (ssize_t)len) {
         free(buf);
         return result;
     }
diff --git a/tsk/vs/mm_open.c b/tsk/vs/mm_open.c
index 6ac5a6ce8daf774fe24128ea6e9173ab9916fb83..ca1a413db66439100b16be15df08b8f4a3610ed6 100644
--- a/tsk/vs/mm_open.c
+++ b/tsk/vs/mm_open.c
@@ -184,7 +184,7 @@ tsk_vs_open(TSK_IMG_INFO * img_info, TSK_DADDR_T offset,
             if (result != NULL) {
                 if (result->encryptionType == ENCRYPTION_DETECTED_SIGNATURE) {
                     tsk_error_set_errno(TSK_ERR_VS_ENCRYPTED);
-                    tsk_error_set_errstr(result->desc);
+                    tsk_error_set_errstr("%s", result->desc);
                 }
                 free(result);
                 result = NULL;