Repository: leil-io/saunafs (language: cpp)

Issue:
recursive_remove.cc: fail to build
I'm trying to build for arm64 (in docker, ubuntu jammy), but getting the following error:

> /tmp/saunafs_deb_working_directory.2024-05-07_143835.1Z3oVs/saunafs/src/tools/recursive_remove.cc: In function 'int recursive_remove_run(int, char**)':
> /tmp/saunafs_deb_working_directory.2024-05-07_143835.1Z3oVs/saunafs/src/tools/recursive_remove.cc:129:47: error: comparison is always true due to limited range of data type [-Werror=type-limits]
>   129 |         while ((ch = getopt(argc, argv, "l")) != -1) {
>       |                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~
> cc1plus: all warnings being treated as errors

It seems that the issue is not related to the architecture, but rather that `ch` is unsigned.


Discussion / hints:

Fix patch:
diff --git a/.clang-tidy b/.clang-tidy
new file mode 100644
index 000000000..ff91f053e
--- /dev/null
+++ b/.clang-tidy
@@ -0,0 +1,43 @@
+#
+# -modernize-use-trailing-return-type,
+# -modernize-use-nodiscard,
+# -modernize-avoid-bind,
+# -modernize-use-transparent-functors,
+# -modernize-use-bool-literals,
+# -modernize-use-uncaught-exceptions,
+# -modernize-use-noexcept,
+# -modernize-concat-nested-namespaces,
+# -modernize-deprecated-headers,
+# -modernize-raw-string-literal,
+# -modernize-replace-random-shuffle,
+# -modernize-use-nullptr,
+# -modernize-use-override,
+# -modernize-use-using,
+#
+# -cppcoreguidelines-pro-type-vararg,
+# -cppcoreguidelines-avoid-magic-numbers,
+# -cppcoreguidelines-pro-bounds-pointer-arithmetic,
+# -readability-magic-numbers,
+# -misc-non-private-member-variables-in-classes,
+Checks: "-*,
+  bugprone-*,
+    -bugprone-easily-swappable-parameters,
+  cert-*,
+  -cert-err58-cpp
+  clang-analyzer-*,
+  cppcoreguidelines-*,
+  -google-*,
+  -llvm*,
+  misc-*,
+    -misc-non-private-member-variables-in-classes,
+  modernize-*,
+    -modernize-use-trailing-return-type,
+    -modernize-use-nodiscard,
+  performance-*,
+  portability-*,
+  readability-*,"
+
+WarningsAsErrors: ''
+HeaderFilterRegex: ''
+FormatStyle:     file
+
diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 000000000..e54d3b7dd
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,77 @@
+*~
+.DS_Store
+.git
+
+# Vim files #
+#############
+.clang_complete
+tags
+*.taghl
+compile_commands.json
+.ycm_extra_conf.py
+
+# Commonly used build directory #
+#################################
+/_packages/
+/build*
+/cmake-build-*
+/install/
+/test_output/
+
+# Eclipse project files #
+#########################
+/.project
+/.cproject
+/.settings
+
+# Jetbrains IDE project files #
+###############################
+/.idea
+
+# KDevelop4 project file #
+##########################
+*.kdev4
+
+# QtCreator user profiles #
+###########################
+*.user
+
+# Debian builds #
+#################
+/Makefile
+/debian/files
+/debian/*.debhelper
+/debian/*.substvars
+/debian/*.debhelper.log
+/debian/debhelper-build-stamp
+/debian/saunafs-adm/
+/debian/saunafs-cgi/
+/debian/saunafs-cgiserv/
+/debian/saunafs-chunkserver/
+/debian/saunafs-client/
+/debian/saunafs-client3/
+/debian/saunafs-common/
+/debian/saunafs-dbg/
+/debian/saunafs-lib-client/
+/debian/saunafs-master/
+/debian/saunafs-metalogger/
+/debian/saunafs-nfs-ganesha/
+/debian/saunafs-uraft/
+/debian/tmp/
+/saunafs-bundle-*
+
+# Mypy #
+########
+.mypy_cache
+
+# VSCode #
+##########
+.vscode
+
+# clangd #
+##########
+.cache
+
+# Test setup #
+##############
+tests/hdd*
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 000000000..4f70f29c3
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,26 @@
+# Top most .editorconfig file
+root = true
+
+# Unix-style newlines with a newline ending every file
+[*]
+charset = utf-8
+end_of_line = lf
+indent_size = 4
+indent_style = space
+insert_final_newline = true
+trim_trailing_whitespace = true
+
+# Tab indentation for shell scripts, Dockerfiles and Makefiles
+[{*.{bash,sh},Dockerfile,Dockerfile.*,Makefile,*.mk}]
+indent_style = tab
+# Unsupported in some editors
+keep_padding = true
+space_redirects = true
+
+# Tab indentation for C/C++ files
+[*.{c,cc,h,cpp,hpp,c++,h++,cxx,hxx}]
+indent_style = tab
+
+# Markdown files should allow trailing whitespace
+[*.{md,markdown,txt}]
+trim_trailing_whitespace = false
diff --git a/.gitignore b/.gitignore
index b39852758..90f732ee8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -18,6 +18,7 @@ compile_commands.json
 
 # Commonly used build directory #
 #################################
+/_packages/
 /build*
 /cmake-build-*
 /install/
@@ -78,12 +79,18 @@ compile_commands.json
 .cache
 
 # submodules #
-##########
+##############
 src/chunkserver/plugins/zonefs_disk
+src/chunkserver/plugins/ice_disk_energy_manager
 tests/ci_build/run-smr-tests.sh
 tests/test_suites/SMRTests
+tests/test_suites/ICETests
 
-# Windows
-##########
+# Windows #
+###########
 src/mount/windows
 create-win-release.bat
+
+# Test setup #
+##############
+tests/hdd*
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 48cd42c30..61b5d2fd0 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -136,11 +136,6 @@ message(STATUS "USE_LEGACY_READ_MESSAGES: ${USE_LEGACY_READ_MESSAGES}")
 message(STATUS "ENABLE_NFS_GANESHA: ${ENABLE_NFS_GANESHA}")
 message(STATUS "ENABLE_COMPILE_COMMANDS: ${CMAKE_EXPORT_COMPILE_COMMANDS}")
 
-execute_process(COMMAND "git" "rev-parse" "HEAD"
-  OUTPUT_VARIABLE GIT_SHA1 RESULT_VARIABLE GIT_SHA1_RETURN_VALUE
-  OUTPUT_STRIP_TRAILING_WHITESPACE)
-
-
 if(ENABLE_TESTS)
   if(NOT THROW_INSTEAD_OF_ABORT)
     message(STATUS "Tests require THROW_INSTEAD_OF_ABORT to be set to YES, changing passed value:")
@@ -193,7 +188,15 @@ endif()
 project(saunafs)
 
 if(NOT PACKAGE_VERSION)
-  set(PACKAGE_VERSION "4.1.1-devel" CACHE STRING "Package version")
+  set(DEFAULT_MIN_VERSION "4.2.0")
+  execute_process(COMMAND "git" "rev-parse" "--short" "HEAD"
+    OUTPUT_VARIABLE GIT_SHA1_SHORT RESULT_VARIABLE GIT_SHA1_SHORT_RETURN_VALUE
+    OUTPUT_STRIP_TRAILING_WHITESPACE)
+  execute_process(COMMAND "git" "rev-parse" "--abbrev-ref" "HEAD"
+    OUTPUT_VARIABLE GIT_BRANCH_NAME RESULT_VARIABLE GIT_BRANCH_NAME_RETURN_VALUE
+    OUTPUT_STRIP_TRAILING_WHITESPACE)
+  string(TIMESTAMP BUILD_TIMESTAMP "%y%m%d-%H%M%S")
+  set(PACKAGE_VERSION "${DEFAULT_MIN_VERSION}-${BUILD_TIMESTAMP}-unstable-${GIT_BRANCH_NAME}-${GIT_SHA1_SHORT}")
 endif()
 
 string(REGEX MATCH "^([0-9]+)\\.([0-9]+)\\.([0-9]+)" PACKAGE_VERSION_MATCH ${PACKAGE_VERSION})
@@ -358,6 +361,8 @@ endif()
 
 add_subdirectory(external)
 add_subdirectory(src/admin)
+add_subdirectory(src/errors)
+add_subdirectory(src/slogger)
 add_subdirectory(src/common)
 add_subdirectory(src/protocol)
 add_subdirectory(src/mount)
@@ -399,3 +404,5 @@ endif()
 if (ENABLE_URAFT)
   add_subdirectory(src/uraft)
 endif()
+
+include(Packing)
diff --git a/Jenkinsfile b/Jenkinsfile
index 763f08b95..f2c2784e0 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -128,6 +128,22 @@ pipeline {
                 }
             }
         }
+        stage('ShortSystemTests') {
+            agent {
+                docker {
+                    label 'docker'
+                    image env.dockerImageSaunafs
+                    registryUrl env.DOCKER_INTERNAL_REGISTRY_URL
+                    registryCredentialsId env.dockerRegistrySecretId
+                    args  '--security-opt seccomp=unconfined --cap-add SYS_ADMIN --device=/dev/fuse:/dev/fuse --security-opt="apparmor=unconfined" --tmpfs /mnt/ramdisk:rw,mode=1777,size=2g --ulimit core=-1'
+                }
+            }
+            steps {
+                cleanAndClone()
+                unstash 'test-binaries'
+                sh 'tests/ci_build/run-sanity-check.sh "ShortSystemTests*"'
+            }
+        }
         stage('Delivery') {
             when {
                 beforeAgent true
diff --git a/NEWS b/NEWS
index 37f64100a..d634193ee 100644
--- a/NEWS
+++ b/NEWS
@@ -1,5 +1,34 @@
 This file lists noteworthy changes in SaunaFS.
 
+ * SaunaFS (4.2.0) (2024-06-19)
+ - (all) Fix slogger dependencies compilation
+ - (common) Split off slogger from common
+ - (common) Split off error code from common
+ - (master) Improve auction optimization
+ - (master) Cache auction optimizer input/output
+ - (master) Apply result of auction optimization
+ - (chunkserver) Improve plugin related logs
+ - (mount) Fix master connection checking
+ - (mount) Fix mount when master is down
+ - (mount) Apply fixes to DirEntryCache
+ - (mount) Label mount threads for better debugging
+ - (admin) Add missing option to master default config
+ - (protocol): Add protocol documentation
+ - (tests) Bump OS version to Ubuntu 24.04
+ - (tests) Update GaneshaTests suite
+ - (tests) Fix race condition in metadata_dump_period_seconds
+ - (tests) Add ss and pv dependencies
+ - (tests) Fix test_cgi_validate_html test
+ - (tests) Clean up space used by the tests
+ - (tests) Fix LongSystemTests related to building
+ - (tests) Migrate XOR tests to erasure coding
+ - (tests) Improve DirEntryCache tests coverage
+ - (tests) Remove Ganesha unneeded dependencies
+ - (tests) Add Ganesha test for validating file corruption
+ - (tests) Enable nfs v4.1 in Ganesha multi export test
+ - (ci) Create NFS-Ganesha config's directory
+ - (ci) Fix nfs-ganesha image build for latest OS
+
  * SaunaFS (4.1.1) (2024-05-22)
  - (docs) Add missing options in sfsmount.1
 
diff --git a/cmake/Packing.cmake b/cmake/Packing.cmake
new file mode 100644
index 000000000..7cf93a81a
--- /dev/null
+++ b/cmake/Packing.cmake
@@ -0,0 +1,62 @@
+set(CPACK_GENERATOR "DEB")
+set(CPACK_PACKAGE_NAME "${PROJECT_NAME}"
+        CACHE STRING "Name of package to be built"
+)
+
+set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "SaunaFS - A distributed filesystem for the cloud"
+        CACHE STRING "Summary of package description"
+)
+
+set(CPACK_PACKAGE_CONTACT "contact@saunafs.com"
+        CACHE STRING "Contact email for package"
+)
+
+set(CPACK_PACKAGE_VENDOR "The SaunaFS Team <"${CPACK_PACKAGE_CONTACT}">"
+        CACHE STRING "Vendor of package"
+)
+
+set(CPACK_DEBIAN_PACKAGE_MAINTAINER "${CPACK_PACKAGE_VENDOR}"
+        CACHE STRING "Maintainer of package"
+)
+
+set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/COPYING")
+set(CPACK_RESOURCE_FILE_README "${CMAKE_CURRENT_SOURCE_DIR}/README.md")
+
+set(CPACK_VERBATIM_VARIABLES YES)
+
+set(CPACK_PACKAGE_INSTALL_DIRECTORY ${CPACK_PACKAGE_NAME})
+SET(CPACK_OUTPUT_FILE_PREFIX "${CMAKE_SOURCE_DIR}/_packages")
+#set(CPACK_STRIP_FILES YES)
+
+set(
+    CPACK_INSTALL_DEFAULT_DIRECTORY_PERMISSIONS
+    OWNER_READ OWNER_WRITE OWNER_EXECUTE
+    GROUP_READ GROUP_EXECUTE
+    WORLD_READ WORLD_EXECUTE
+)
+
+set(CPACK_PACKAGING_INSTALL_PREFIX ${CMAKE_INSTALL_PREFIX})
+
+set(CPACK_PACKAGE_VERSION_MAJOR ${PACKAGE_VERSION_MAJOR})
+set(CPACK_PACKAGE_VERSION_MINOR ${PACKAGE_VERSION_MINOR})
+set(CPACK_PACKAGE_VERSION_PATCH ${PACKAGE_VERSION_PATCH})
+
+set(CPACK_COMPONENTS_GROUPING IGNORE)
+
+set(CPACK_DEB_COMPONENT_INSTALL ON)
+set(CPACK_DEBIAN_PACKAGE_VERSION "${PACKAGE_VERSION}")
+set(CPACK_DEBIAN_FILE_NAME "DEB-DEFAULT")
+set(CPACK_DEBIAN_COMPRESSION_TYPE "xz")
+set(CPACK_DEBIAN_DEBUGINFO_PACKAGE ON)
+
+set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE "amd64")
+## TODO(Baldor): Set dependencies for the package
+#set(CPACK_DEBIAN_PACKAGE_DEPENDS "asciidoc, debhelper, cmake, libfuse3-dev, pkg-config, zlib1g-dev, libspdlog-dev, libfmt-dev, libboost-system-dev, libboost-program-options-dev, python3")
+set(CPACK_DEBIAN_ENABLE_COMPONENT_DEPENDS ON)
+
+# Remove the Unspecified package
+get_cmake_property(CPACK_COMPONENTS_ALL COMPONENTS)
+list(REMOVE_ITEM CPACK_COMPONENTS_ALL "Unspecified")
+
+include(CPack)
+message(STATUS "Components to pack: ${CPACK_COMPONENTS_ALL}")
diff --git a/debian/changelog b/debian/changelog
index 038a1c878..692e5f77d 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,34 @@
+saunafs (4.2.0) stable; urgency=medium
+  * (all) Fix slogger dependencies compilation
+  * (common) Split off slogger from common
+  * (common) Split off error code from common
+  * (master) Improve auction optimization
+  * (master) Cache auction optimizer input/output
+  * (master) Apply result of auction optimization
+  * (chunkserver) Improve plugin related logs
+  * (mount) Fix master connection checking
+  * (mount) Fix mount when master is down
+  * (mount) Apply fixes to DirEntryCache
+  * (mount) Label mount threads for better debugging
+  * (admin) Add missing option to master default config
+  * (protocol): Add protocol documentation
+  * (tests) Bump OS version to Ubuntu 24.04
+  * (tests) Update GaneshaTests suite
+  * (tests) Fix race condition in metadata_dump_period_seconds
+  * (tests) Add ss and pv dependencies
+  * (tests) Fix test_cgi_validate_html test
+  * (tests) Clean up space used by the tests
+  * (tests) Fix LongSystemTests related to building
+  * (tests) Migrate XOR tests to erasure coding
+  * (tests) Improve DirEntryCache tests coverage
+  * (tests) Remove Ganesha unneeded dependencies
+  * (tests) Add Ganesha test for validating file corruption
+  * (tests) Enable nfs v4.1 in Ganesha multi export test
+  * (ci) Create NFS-Ganesha config's directory
+  * (ci) Fix nfs-ganesha image build for latest OS
+
+ -- SaunaFS Team <contact@saunafs.com>  Wed, 19 Jun 2024 21:46:17 -0400
+
 saunafs (4.1.1) stable; urgency=low
   * (docs) Add missing options in sfsmount.1
 
diff --git a/doc/saunafs-admin.8.adoc b/doc/saunafs-admin.8.adoc
index 98fcb3100..b02f98b76 100644
--- a/doc/saunafs-admin.8.adoc
+++ b/doc/saunafs-admin.8.adoc
@@ -9,6 +9,11 @@ saunafs-admin, saunafs-probe - SaunaFS monitoring and administration tool
 
 **saunafs-admin** __COMMAND__ [OPTIONS...] [ARGUMENTS...]
 
+== NOTES
+
+**saunafs-probe** is deprecated, saunafs-admin should be used instead.
+
+
 == Available COMMANDs
 
 *chunks-health* __<master ip> <master port>__:: Returns chunks health reports
@@ -148,6 +153,7 @@ chunks with number of copies specified in the label to replicate/delete. +
 Report bugs to the Github repository <https://github.com/leil/saunafs> as an
 issue
 
+
 == COPYRIGHT
 
 Copyright 2008-2009 Gemius SA
diff --git a/doc/sfschunkserver.cfg.5.adoc b/doc/sfschunkserver.cfg.5.adoc
index 29b93e654..2ad0d6eb2 100644
--- a/doc/sfschunkserver.cfg.5.adoc
+++ b/doc/sfschunkserver.cfg.5.adoc
@@ -74,6 +74,11 @@ This number is always added to the used disk space reported by chunkserver.
 *HDD_CHECK_CRC_WHEN_READING*:: whether to check the CRC on every read operation
 (default is 1)
 
+*HDD_CHECK_CRC_WHEN_WRITING*:: whether to check the CRC on every write
+operation. The safest and recommended value is 1, disable it only to test
+maximum throughput.
+(default is 1)
+
 *HDD_ADVISE_NO_CACHE*:: whether to remove each chunk from page when closing it
 to reduce cache pressure generated by chunkserver (default is 0, i.e. no)
 
diff --git a/doc/sfsmount.1.adoc b/doc/sfsmount.1.adoc
index b322a9f2e..d59cdc9a9 100644
--- a/doc/sfsmount.1.adoc
+++ b/doc/sfsmount.1.adoc
@@ -210,6 +210,11 @@ Equivalent to *--nostdopts* (*-n*) option for use in fstab.
 *-o nonempty*::
 Equivalent to *--nonempty* option for use in fstab.
 
+*-o sfsignoreflush*::
+Advanced: use with caution. Ignore flush usual behavior by replying SUCCESS to it
+immediately. Targets fast creation of small files, but may cause data loss
+during crashes when allegedly flushed data is still being processed.
+
 *-o sfsdirectio=*'0|1'::
 Whether to use FUSE DirectIO. This may improve performance when reading large
 files under certain conditions.
diff --git a/src/admin/chunk_health_command.cc b/src/admin/chunk_health_command.cc
index 174c17944..6b8ebd391 100644
--- a/src/admin/chunk_health_command.cc
+++ b/src/admin/chunk_health_command.cc
@@ -38,7 +38,7 @@ std::string ChunksHealthCommand::name() const {
 	return "chunks-health";
 }
 
-SaunaFsProbeCommand::SupportedOptions ChunksHealthCommand::supportedOptions() const {
+SaunaFsAdminCommand::SupportedOptions ChunksHealthCommand::supportedOptions() const {
 	return {
 		{kPorcelainMode,      kPorcelainModeDescription},
 		{kOptionAvailability, "Print report about availability of chunks."},
diff --git a/src/admin/chunk_health_command.h b/src/admin/chunk_health_command.h
index 11b13309e..f7c4d18dc 100644
--- a/src/admin/chunk_health_command.h
+++ b/src/admin/chunk_health_command.h
@@ -28,7 +28,7 @@
 #include "common/server_connection.h"
 #include "admin/saunafs_admin_command.h"
 
-class ChunksHealthCommand : public SaunaFsProbeCommand {
+class ChunksHealthCommand : public SaunaFsAdminCommand {
 public:
 	virtual std::string name() const;
 	virtual SupportedOptions supportedOptions() const;
diff --git a/src/admin/delete_sessions_command.h b/src/admin/delete_sessions_command.h
index cd3cfc355..790c9a65d 100644
--- a/src/admin/delete_sessions_command.h
+++ b/src/admin/delete_sessions_command.h
@@ -19,7 +19,7 @@
 #include "admin/saunafs_admin_command.h"
 #include "common/server_connection.h"
 
-class DeleteSessionsCommand : public SaunaFsProbeCommand {
+class DeleteSessionsCommand : public SaunaFsAdminCommand {
 public:
 	std::string name() const final;
 	void usage() const final;
diff --git a/src/admin/dump_config_command.cc b/src/admin/dump_config_command.cc
index de4417c21..9417f9b27 100644
--- a/src/admin/dump_config_command.cc
+++ b/src/admin/dump_config_command.cc
@@ -38,7 +38,7 @@ void DumpConfigurationCommand::usage() const {
 	std::cerr << "    Authentication with the admin password is required\n";
 }
 
-SaunaFsProbeCommand::SupportedOptions DumpConfigurationCommand::supportedOptions() const {
+SaunaFsAdminCommand::SupportedOptions DumpConfigurationCommand::supportedOptions() const {
 	return {
 		{
 			defaultsMode,
@@ -102,6 +102,7 @@ const static std::unordered_map<std::string, std::string> defaultOptionsMaster =
     {"CHUNKS_LOOP_MIN_TIME", "300"},
     {"CHUNKS_LOOP_PERIOD", "1000"},
     {"CHUNKS_LOOP_MAX_CPU", "60"},
+    {"USE_LINEAR_ASSIGNMENT_OPTIMIZER", "1"},
     {"CHUNKS_SOFT_DEL_LIMIT", "10"},
     {"CHUNKS_HARD_DEL_LIMIT", "25"},
     {"CHUNKS_WRITE_REP_LIMIT", "2"},
@@ -155,6 +156,7 @@ const static std::unordered_map<std::string, std::string> defaultOptionsCS = {
     {"HDD_CONF_FILENAME", ETC_PATH "/sfshdd.cfg"},
     {"HDD_TEST_FREQ", "10.0"},
     {"HDD_CHECK_CRC_WHEN_READING", "1"},
+    {"HDD_CHECK_CRC_WHEN_WRITING", "1"},
     {"HDD_ADVISE_NO_CACHE", "0"},
     {"HDD_PUNCH_HOLES", "0"},
     {"ENABLE_LOAD_FACTOR", "0"},
diff --git a/src/admin/dump_config_command.h b/src/admin/dump_config_command.h
index 6410dc5ae..2c13e3863 100644
--- a/src/admin/dump_config_command.h
+++ b/src/admin/dump_config_command.h
@@ -24,7 +24,7 @@
 #include <string>
 #include <unordered_map>
 
-class DumpConfigurationCommand : public SaunaFsProbeCommand {
+class DumpConfigurationCommand : public SaunaFsAdminCommand {
 public:
 	std::string defaultsMode = "--defaults";
 
diff --git a/src/admin/info_command.cc b/src/admin/info_command.cc
index 08e4730e6..01fda4495 100644
--- a/src/admin/info_command.cc
+++ b/src/admin/info_command.cc
@@ -32,7 +32,7 @@ std::string InfoCommand::name() const {
 	return "info";
 }
 
-SaunaFsProbeCommand::SupportedOptions InfoCommand::supportedOptions() const {
+SaunaFsAdminCommand::SupportedOptions InfoCommand::supportedOptions() const {
 	return {
 		{kPorcelainMode, kPorcelainModeDescription},
 	};
diff --git a/src/admin/info_command.h b/src/admin/info_command.h
index 6c1a8a6ad..7d53ccb62 100644
--- a/src/admin/info_command.h
+++ b/src/admin/info_command.h
@@ -24,7 +24,7 @@
 
 #include "admin/saunafs_admin_command.h"
 
-class InfoCommand : public SaunaFsProbeCommand {
+class InfoCommand : public SaunaFsAdminCommand {
 public:
 	virtual std::string name() const;
 	virtual SupportedOptions supportedOptions() const;
diff --git a/src/admin/io_limits_status_command.cc b/src/admin/io_limits_status_command.cc
index 0019c8276..2a6b5cdce 100644
--- a/src/admin/io_limits_status_command.cc
+++ b/src/admin/io_limits_status_command.cc
@@ -36,7 +36,7 @@ void IoLimitsStatusCommand::usage() const {
 	std::cerr << "    Prints current configuration of global I/O limiting" << std::endl;
 }
 
-SaunaFsProbeCommand::SupportedOptions IoLimitsStatusCommand::supportedOptions() const {
+SaunaFsAdminCommand::SupportedOptions IoLimitsStatusCommand::supportedOptions() const {
 	return { {kPorcelainMode, kPorcelainModeDescription} };
 }
 
diff --git a/src/admin/io_limits_status_command.h b/src/admin/io_limits_status_command.h
index 3a76ce011..19e025f37 100644
--- a/src/admin/io_limits_status_command.h
+++ b/src/admin/io_limits_status_command.h
@@ -28,7 +28,7 @@
 /**
  * A command for saunafs-admin that checks the global I/O limiting status
  */
-class IoLimitsStatusCommand : public SaunaFsProbeCommand {
+class IoLimitsStatusCommand : public SaunaFsAdminCommand {
 public:
 	/**
 	 * Return name of a command
diff --git a/src/admin/list_chunkservers_command.cc b/src/admin/list_chunkservers_command.cc
index 7f495db1e..9c2661855 100644
--- a/src/admin/list_chunkservers_command.cc
+++ b/src/admin/list_chunkservers_command.cc
@@ -34,7 +34,7 @@ std::string ListChunkserversCommand::name() const {
 	return "list-chunkservers";
 }
 
-SaunaFsProbeCommand::SupportedOptions ListChunkserversCommand::supportedOptions() const {
+SaunaFsAdminCommand::SupportedOptions ListChunkserversCommand::supportedOptions() const {
 	return {
 		{kPorcelainMode, kPorcelainModeDescription},
 	};
diff --git a/src/admin/list_chunkservers_command.h b/src/admin/list_chunkservers_command.h
index b7a595956..893f0c304 100644
--- a/src/admin/list_chunkservers_command.h
+++ b/src/admin/list_chunkservers_command.h
@@ -28,7 +28,7 @@
 #include "protocol/chunkserver_list_entry.h"
 
 
-class ListChunkserversCommand : public SaunaFsProbeCommand {
+class ListChunkserversCommand : public SaunaFsAdminCommand {
 public:
 	virtual std::string name() const;
 	virtual SupportedOptions supportedOptions() const;
diff --git a/src/admin/list_defective_files_command.cc b/src/admin/list_defective_files_command.cc
index b6168681f..3dd40cceb 100644
--- a/src/admin/list_defective_files_command.cc
+++ b/src/admin/list_defective_files_command.cc
@@ -45,7 +45,7 @@ void ListDefectiveFilesCommand::usage() const {
 	std::cerr << "    Lists files which currently have defective chunks" << std::endl;
 }
 
-SaunaFsProbeCommand::SupportedOptions ListDefectiveFilesCommand::supportedOptions() const {
+SaunaFsAdminCommand::SupportedOptions ListDefectiveFilesCommand::supportedOptions() const {
 	return {
 		{kPorcelainMode, kPorcelainModeDescription},
 		{"--unavailable", "Print unavailable files"},
diff --git a/src/admin/list_defective_files_command.h b/src/admin/list_defective_files_command.h
index 0e1789aaf..06074d24b 100644
--- a/src/admin/list_defective_files_command.h
+++ b/src/admin/list_defective_files_command.h
@@ -25,7 +25,7 @@
 #include "admin/saunafs_admin_command.h"
 #include "common/server_connection.h"
 
-class ListDefectiveFilesCommand : public SaunaFsProbeCommand {
+class ListDefectiveFilesCommand : public SaunaFsAdminCommand {
 public:
 	std::string name() const override;
 	void usage() const override;
diff --git a/src/admin/list_disks_command.cc b/src/admin/list_disks_command.cc
index c4a722c13..ab0106873 100644
--- a/src/admin/list_disks_command.cc
+++ b/src/admin/list_disks_command.cc
@@ -183,7 +183,7 @@ std::string ListDisksCommand::name() const {
 	return "list-disks";
 }
 
-SaunaFsProbeCommand::SupportedOptions ListDisksCommand::supportedOptions() const {
+SaunaFsAdminCommand::SupportedOptions ListDisksCommand::supportedOptions() const {
 	return {
 		{kPorcelainMode, kPorcelainModeDescription},
 		{kVerboseMode,   "Be a little more verbose and show operations statistics."},
diff --git a/src/admin/list_disks_command.h b/src/admin/list_disks_command.h
index fc8a4b133..86535a10c 100644
--- a/src/admin/list_disks_command.h
+++ b/src/admin/list_disks_command.h
@@ -24,7 +24,7 @@
 
 #include "admin/saunafs_admin_command.h"
 
-class ListDisksCommand : public SaunaFsProbeCommand {
+class ListDisksCommand : public SaunaFsAdminCommand {
 public:
 	virtual std::string name() const;
 	virtual SupportedOptions supportedOptions() const;
diff --git a/src/admin/list_goals_command.cc b/src/admin/list_goals_command.cc
index 7b458aff5..c9e78bea9 100644
--- a/src/admin/list_goals_command.cc
+++ b/src/admin/list_goals_command.cc
@@ -37,7 +37,7 @@ std::string ListGoalsCommand::name() const {
 	return "list-goals";
 }
 
-SaunaFsProbeCommand::SupportedOptions ListGoalsCommand::supportedOptions() const {
+SaunaFsAdminCommand::SupportedOptions ListGoalsCommand::supportedOptions() const {
 	return {
 		{kPorcelainMode, kPorcelainModeDescription},
 		{"--pretty", "Print nice table"}
diff --git a/src/admin/list_goals_command.h b/src/admin/list_goals_command.h
index bcd4089b4..84475ad72 100644
--- a/src/admin/list_goals_command.h
+++ b/src/admin/list_goals_command.h
@@ -24,7 +24,7 @@
 
 #include "admin/saunafs_admin_command.h"
 
-class ListGoalsCommand : public SaunaFsProbeCommand {
+class ListGoalsCommand : public SaunaFsAdminCommand {
 public:
 	virtual std::string name() const;
 	virtual SupportedOptions supportedOptions() const;
diff --git a/src/admin/list_metadataservers_command.cc b/src/admin/list_metadataservers_command.cc
index 564d3989c..4e943170f 100644
--- a/src/admin/list_metadataservers_command.cc
+++ b/src/admin/list_metadataservers_command.cc
@@ -41,7 +41,7 @@ void ListMetadataserversCommand::usage() const {
 	std::cerr << "    Prints status of active metadata servers" << std::endl;
 }
 
-SaunaFsProbeCommand::SupportedOptions ListMetadataserversCommand::supportedOptions() const {
+SaunaFsAdminCommand::SupportedOptions ListMetadataserversCommand::supportedOptions() const {
 	return { {kPorcelainMode, kPorcelainModeDescription} };
 }
 
diff --git a/src/admin/list_metadataservers_command.h b/src/admin/list_metadataservers_command.h
index 2e90e88bf..a844fedf8 100644
--- a/src/admin/list_metadataservers_command.h
+++ b/src/admin/list_metadataservers_command.h
@@ -24,7 +24,7 @@
 
 #include "admin/saunafs_admin_command.h"
 
-class ListMetadataserversCommand : public SaunaFsProbeCommand {
+class ListMetadataserversCommand : public SaunaFsAdminCommand {
 public:
 	std::string name() const override;
 	void usage() const override;
diff --git a/src/admin/list_mounts_command.cc b/src/admin/list_mounts_command.cc
index 434d02622..03ef26ee3 100644
--- a/src/admin/list_mounts_command.cc
+++ b/src/admin/list_mounts_command.cc
@@ -62,7 +62,7 @@ std::string ListMountsCommand::name() const {
 	return "list-mounts";
 }
 
-SaunaFsProbeCommand::SupportedOptions ListMountsCommand::supportedOptions() const {
+SaunaFsAdminCommand::SupportedOptions ListMountsCommand::supportedOptions() const {
 	return {
 		{kPorcelainMode, kPorcelainModeDescription},
 		{kVerboseMode,   "Be a little more verbose and show goal and trash time limits."},
diff --git a/src/admin/list_mounts_command.h b/src/admin/list_mounts_command.h
index 9481a5e42..9c1a96f12 100644
--- a/src/admin/list_mounts_command.h
+++ b/src/admin/list_mounts_command.h
@@ -24,7 +24,7 @@
 
 #include "admin/saunafs_admin_command.h"
 
-class ListMountsCommand : public SaunaFsProbeCommand {
+class ListMountsCommand : public SaunaFsAdminCommand {
 public:
 	virtual std::string name() const;
 	virtual SupportedOptions supportedOptions() const;
diff --git a/src/admin/list_sessions_command.cc b/src/admin/list_sessions_command.cc
index 64d4eb57a..5bbc5e445 100644
--- a/src/admin/list_sessions_command.cc
+++ b/src/admin/list_sessions_command.cc
@@ -38,7 +38,7 @@ void ListSessionsCommand::usage() const {
 	std::cerr << "    Lists all currently open sessions\n";
 }
 
-SaunaFsProbeCommand::SupportedOptions ListSessionsCommand::supportedOptions() const {
+SaunaFsAdminCommand::SupportedOptions ListSessionsCommand::supportedOptions() const {
 	return {};
 }
 
diff --git a/src/admin/list_sessions_command.h b/src/admin/list_sessions_command.h
index 0735a1aea..03fb93cb5 100644
--- a/src/admin/list_sessions_command.h
+++ b/src/admin/list_sessions_command.h
@@ -17,7 +17,7 @@
 
 #include "admin/saunafs_admin_command.h"
 
-class ListSessionsCommand : public SaunaFsProbeCommand {
+class ListSessionsCommand : public SaunaFsAdminCommand {
 public:
 	std::string name() const final;
 	SupportedOptions supportedOptions() const final;
diff --git a/src/admin/list_tasks_command.h b/src/admin/list_tasks_command.h
index b0894b9d4..c0b5debce 100644
--- a/src/admin/list_tasks_command.h
+++ b/src/admin/list_tasks_command.h
@@ -25,7 +25,7 @@
 #include "admin/saunafs_admin_command.h"
 #include "common/server_connection.h"
 
-class ListTasksCommand : public SaunaFsProbeCommand {
+class ListTasksCommand : public SaunaFsAdminCommand {
 public:
 	std::string name() const override;
 	void usage() const override;
diff --git a/src/admin/magic_recalculate_metadata_checksum_command.cc b/src/admin/magic_recalculate_metadata_checksum_command.cc
index 063421066..23e63f0b1 100644
--- a/src/admin/magic_recalculate_metadata_checksum_command.cc
+++ b/src/admin/magic_recalculate_metadata_checksum_command.cc
@@ -37,7 +37,7 @@ void MagicRecalculateMetadataChecksumCommand::usage() const {
 	std::cerr << "    Authentication with the admin password is required." << std::endl;
 }
 
-SaunaFsProbeCommand::SupportedOptions MagicRecalculateMetadataChecksumCommand::supportedOptions() const {
+SaunaFsAdminCommand::SupportedOptions MagicRecalculateMetadataChecksumCommand::supportedOptions() const {
 	return { {"--async", "Don't wait for the task to finish."},
 	         {"--timeout=", "Operation timeout" }};
 }
diff --git a/src/admin/magic_recalculate_metadata_checksum_command.h b/src/admin/magic_recalculate_metadata_checksum_command.h
index cc2d52251..a924187a6 100644
--- a/src/admin/magic_recalculate_metadata_checksum_command.h
+++ b/src/admin/magic_recalculate_metadata_checksum_command.h
@@ -24,7 +24,7 @@
 
 #include "admin/saunafs_admin_command.h"
 
-class MagicRecalculateMetadataChecksumCommand : public SaunaFsProbeCommand {
+class MagicRecalculateMetadataChecksumCommand : public SaunaFsAdminCommand {
 public:
 	std::string name() const override;
 	void usage() const override;
diff --git a/src/admin/main.cc b/src/admin/main.cc
index babc1fb63..3d9c7cf98 100644
--- a/src/admin/main.cc
+++ b/src/admin/main.cc
@@ -47,10 +47,11 @@
 #include "admin/list_sessions_command.h"
 #include "common/human_readable_format.h"
 #include "protocol/SFSCommunication.h"
-#include "common/sfserr.h"
+#include "errors/sfserr.h"
+#include "common/sockets.h"
 
 int main(int argc, const char** argv) {
-	std::vector<const SaunaFsProbeCommand*> allCommands = {
+	std::vector<const SaunaFsAdminCommand*> allCommands = {
 			new ChunksHealthCommand(),
 			new InfoCommand(),
 			new IoLimitsStatusCommand(),
@@ -80,6 +81,9 @@ int main(int argc, const char** argv) {
 		if (argc < 2) {
 			throw WrongUsageException("No command name provided");
 		}
+#ifdef _WIN32
+		socketinit();
+#endif
 		command_name = argv[1];
 		std::vector<std::string> arguments(argv + 2, argv + argc);
 		for (auto command : allCommands) {
@@ -90,6 +94,9 @@ int main(int argc, const char** argv) {
 						supportedOptions.push_back(optionWithDescription.first);
 					}
 					command->run(Options(supportedOptions, arguments));
+#ifdef _WIN32
+					socketrelease();
+#endif
 					return 0;
 				} catch (Options::ParseError& ex) {
 					throw WrongUsageException("Wrong usage of " + command->name()
@@ -139,8 +146,14 @@ int main(int argc, const char** argv) {
 				}
 			}
 		}
+#ifdef _WIN32
+		socketrelease();
+#endif
 		return 1;
 	} catch (Exception& ex) {
+#ifdef _WIN32
+		socketrelease();
+#endif
 		std::cerr << "Error: " << ex.what() << std::endl;
 		return 1;
 	}
diff --git a/src/admin/manage_locks_command.cc b/src/admin/manage_locks_command.cc
index 16c42b018..877ad6f9c 100644
--- a/src/admin/manage_locks_command.cc
+++ b/src/admin/manage_locks_command.cc
@@ -41,7 +41,7 @@ void ManageLocksCommand::usage() const {
 	std::cerr << "    Manages file locks" << std::endl;
 }
 
-SaunaFsProbeCommand::SupportedOptions ManageLocksCommand::supportedOptions() const {
+SaunaFsAdminCommand::SupportedOptions ManageLocksCommand::supportedOptions() const {
 	return {
 		{kPorcelainMode, kPorcelainModeDescription},
 		{"--active", "Print only active locks"},
@@ -131,7 +131,7 @@ static void processListType(uint32_t inode, RegisteredAdminConnection &conn, con
 		}
 
 		for (auto &lock : locks) {
-			if (options.isSet(SaunaFsProbeCommand::kPorcelainMode)) {
+			if (options.isSet(SaunaFsAdminCommand::kPorcelainMode)) {
 				std::cout << lock.inode
 				<< " " << lock.owner
 				<< " " << lock.sessionid
diff --git a/src/admin/manage_locks_command.h b/src/admin/manage_locks_command.h
index 025ad5a9f..9d34d2285 100644
--- a/src/admin/manage_locks_command.h
+++ b/src/admin/manage_locks_command.h
@@ -24,7 +24,7 @@
 
 #include "admin/saunafs_admin_command.h"
 
-class ManageLocksCommand : public SaunaFsProbeCommand {
+class ManageLocksCommand : public SaunaFsAdminCommand {
 public:
 	std::string name() const override;
 	void usage() const override;
diff --git a/src/admin/metadataserver_status_command.cc b/src/admin/metadataserver_status_command.cc
index 0d8641c2d..780468347 100644
--- a/src/admin/metadataserver_status_command.cc
+++ b/src/admin/metadataserver_status_command.cc
@@ -36,7 +36,7 @@ void MetadataserverStatusCommand::usage() const {
 	std::cerr << "    Prints status of a master or shadow master server" << std::endl;
 }
 
-SaunaFsProbeCommand::SupportedOptions MetadataserverStatusCommand::supportedOptions() const {
+SaunaFsAdminCommand::SupportedOptions MetadataserverStatusCommand::supportedOptions() const {
 	return { {kPorcelainMode, kPorcelainModeDescription} };
 }
 
diff --git a/src/admin/metadataserver_status_command.h b/src/admin/metadataserver_status_command.h
index a617ae7fc..4f8d09b86 100644
--- a/src/admin/metadataserver_status_command.h
+++ b/src/admin/metadataserver_status_command.h
@@ -31,7 +31,7 @@ struct MetadataserverStatus {
 	uint64_t metadataVersion;
 };
 
-class MetadataserverStatusCommand : public SaunaFsProbeCommand {
+class MetadataserverStatusCommand : public SaunaFsAdminCommand {
 public:
 	std::string name() const override;
 	void usage() const override;
diff --git a/src/admin/promote_shadow_command.h b/src/admin/promote_shadow_command.h
index 990f4377d..cf9561072 100644
--- a/src/admin/promote_shadow_command.h
+++ b/src/admin/promote_shadow_command.h
@@ -25,7 +25,7 @@
 #include "common/server_connection.h"
 #include "admin/saunafs_admin_command.h"
 
-class PromoteShadowCommand : public SaunaFsProbeCommand {
+class PromoteShadowCommand : public SaunaFsAdminCommand {
 public:
 	std::string name() const override;
 	void usage() const override;
diff --git a/src/admin/ready_chunkservers_count_command.h b/src/admin/ready_chunkservers_count_command.h
index e73231fdc..f192f5396 100644
--- a/src/admin/ready_chunkservers_count_command.h
+++ b/src/admin/ready_chunkservers_count_command.h
@@ -24,7 +24,7 @@
 
 #include "admin/saunafs_admin_command.h"
 
-class ReadyChunkserversCountCommand : public SaunaFsProbeCommand {
+class ReadyChunkserversCountCommand : public SaunaFsAdminCommand {
 public:
 	virtual std::string name() const;
 	virtual void usage() const;
diff --git a/src/admin/reload_config_command.h b/src/admin/reload_config_command.h
index 69f0c906f..0efb1cc3e 100644
--- a/src/admin/reload_config_command.h
+++ b/src/admin/reload_config_command.h
@@ -24,7 +24,7 @@
 
 #include "admin/saunafs_admin_command.h"
 
-class ReloadConfigCommand : public SaunaFsProbeCommand {
+class ReloadConfigCommand : public SaunaFsAdminCommand {
 public:
 	virtual std::string name() const override;
 	virtual void usage() const override;
diff --git a/src/admin/saunafs_admin_command.cc b/src/admin/saunafs_admin_command.cc
index bc275262a..e49f0594e 100644
--- a/src/admin/saunafs_admin_command.cc
+++ b/src/admin/saunafs_admin_command.cc
@@ -21,7 +21,7 @@
 #include "common/platform.h"
 #include "admin/saunafs_admin_command.h"
 
-const std::string SaunaFsProbeCommand::kPorcelainMode = "--porcelain";
-const std::string SaunaFsProbeCommand::kPorcelainModeDescription =
+const std::string SaunaFsAdminCommand::kPorcelainMode = "--porcelain";
+const std::string SaunaFsAdminCommand::kPorcelainModeDescription =
 		"Make the output parsing-friendly.";
-const std::string SaunaFsProbeCommand::kVerboseMode = "--verbose";
+const std::string SaunaFsAdminCommand::kVerboseMode = "--verbose";
diff --git a/src/admin/saunafs_admin_command.h b/src/admin/saunafs_admin_command.h
index 6eb2f0c52..bae86791b 100644
--- a/src/admin/saunafs_admin_command.h
+++ b/src/admin/saunafs_admin_command.h
@@ -30,7 +30,7 @@
 
 SAUNAFS_CREATE_EXCEPTION_CLASS(WrongUsageException, Exception);
 
-class SaunaFsProbeCommand {
+class SaunaFsAdminCommand {
 public:
 	typedef std::vector<std::pair<std::string, std::string>> SupportedOptions;
 
@@ -38,7 +38,7 @@ class SaunaFsProbeCommand {
 	static const std::string kPorcelainModeDescription;
 	static const std::string kVerboseMode;
 
-	virtual ~SaunaFsProbeCommand() {}
+	virtual ~SaunaFsAdminCommand() {}
 	virtual std::string name() const = 0;
 	virtual void usage() const = 0;
 	virtual SupportedOptions supportedOptions() const { return {}; }
diff --git a/src/admin/save_metadata_command.cc b/src/admin/save_metadata_command.cc
index 7b4f1f0e4..6faaeddc2 100644
--- a/src/admin/save_metadata_command.cc
+++ b/src/admin/save_metadata_command.cc
@@ -40,7 +40,7 @@ void SaveMetadataCommand::usage() const {
 			"    Authentication with the admin password is required." << std::endl;
 }
 
-SaunaFsProbeCommand::SupportedOptions SaveMetadataCommand::supportedOptions() const {
+SaunaFsAdminCommand::SupportedOptions SaveMetadataCommand::supportedOptions() const {
 	return { {"--async", "Don't wait for the task to finish."} };
 }
 
diff --git a/src/admin/save_metadata_command.h b/src/admin/save_metadata_command.h
index b2b106522..c39485c75 100644
--- a/src/admin/save_metadata_command.h
+++ b/src/admin/save_metadata_command.h
@@ -24,7 +24,7 @@
 
 #include "admin/saunafs_admin_command.h"
 
-class SaveMetadataCommand : public SaunaFsProbeCommand {
+class SaveMetadataCommand : public SaunaFsAdminCommand {
 public:
 	std::string name() const override;
 	void usage() const override;
diff --git a/src/admin/stop_master_without_saving_metadata.h b/src/admin/stop_master_without_saving_metadata.h
index 4f0f85ea3..4bbcc7b04 100644
--- a/src/admin/stop_master_without_saving_metadata.h
+++ b/src/admin/stop_master_without_saving_metadata.h
@@ -25,7 +25,7 @@
 #include "common/server_connection.h"
 #include "admin/saunafs_admin_command.h"
 
-class MetadataserverStopWithoutSavingMetadataCommand : public SaunaFsProbeCommand {
+class MetadataserverStopWithoutSavingMetadataCommand : public SaunaFsAdminCommand {
 public:
 	std::string name() const override;
 	void usage() const override;
diff --git a/src/admin/stop_task_command.h b/src/admin/stop_task_command.h
index 7b72f313a..a6577a246 100644
--- a/src/admin/stop_task_command.h
+++ b/src/admin/stop_task_command.h
@@ -25,7 +25,7 @@
 #include "admin/saunafs_admin_command.h"
 #include "common/server_connection.h"
 
-class StopTaskCommand : public SaunaFsProbeCommand {
+class StopTaskCommand : public SaunaFsAdminCommand {
 public:
 	std::string name() const override;
 	void usage() const override;
diff --git a/src/chunkserver/bgjobs.cc b/src/chunkserver/bgjobs.cc
index 2dd4ddffb..b42d93c0c 100644
--- a/src/chunkserver/bgjobs.cc
+++ b/src/chunkserver/bgjobs.cc
@@ -182,6 +182,11 @@ static inline int job_receive_status(jobpool *jp,uint32_t *jobid,uint8_t *status
 
 void* job_worker(void *th_arg) {
 	TRACETHIS();
+
+	static std::atomic_uint16_t workersCounter(0);
+	std::string threadName = "jobWorker " + std::to_string(workersCounter++);
+	pthread_setname_np(pthread_self(), threadName.c_str());
+
 	jobpool *jp = (jobpool*)th_arg;
 	job *jptr;
 	uint8_t *jptrarg;
diff --git a/src/chunkserver/buffers_pool.h b/src/chunkserver/buffers_pool.h
index 7b06cc74e..861cf591d 100644
--- a/src/chunkserver/buffers_pool.h
+++ b/src/chunkserver/buffers_pool.h
@@ -48,11 +48,12 @@ class BuffersPool {
 	 * @return The existent buffer or a newly created one.
 	 */
 	std::shared_ptr<T> get(size_t capacity) {
+		std::lock_guard lock(mutex_);
+
 		if (buffers_.empty()) {
 			return std::make_shared<T>(capacity);
 		}
 
-		std::lock_guard lock(mutex_);
 		auto buffer = buffers_.front();
 
 		if (buffer->capacity() == capacity) {
@@ -80,7 +81,7 @@ class BuffersPool {
 
 private:
 	/// Maximum number of buffers in the pool.
-	static constexpr size_t kMaxSize = 128;
+	static constexpr size_t kMaxSize = 1024;
 	/// Buffers pool (container).
 	std::queue<std::shared_ptr<T>> buffers_;
 	/// Mutex to protect the pool.
diff --git a/src/chunkserver/chunkserver-common/chunk_with_fd.h b/src/chunkserver/chunkserver-common/chunk_with_fd.h
index 15967df8d..6bdac3bdc 100644
--- a/src/chunkserver/chunkserver-common/chunk_with_fd.h
+++ b/src/chunkserver/chunkserver-common/chunk_with_fd.h
@@ -1,4 +1,4 @@
-﻿/*
+/*
    Copyright 2013-2015 Skytechnology sp. z o.o.
    Copyright 2023      Leil Storage OÜ
 
diff --git a/src/chunkserver/chunkserver-common/global_shared_resources.h b/src/chunkserver/chunkserver-common/global_shared_resources.h
index dfa3e21ed..c9fb1e74c 100644
--- a/src/chunkserver/chunkserver-common/global_shared_resources.h
+++ b/src/chunkserver/chunkserver-common/global_shared_resources.h
@@ -39,3 +39,5 @@ inline std::vector<std::unique_ptr<CondVarWithWaitCount>> gFreeCondVars;
 inline std::atomic_int gScansInProgress(0);
 
 inline std::atomic_bool gPerformFsync(true);
+
+inline std::atomic_bool gCheckCrcWhenWriting{true};
diff --git a/src/chunkserver/chunkserver-common/hdd_utils.h b/src/chunkserver/chunkserver-common/hdd_utils.h
index acf8b20ed..c6512dc33 100644
--- a/src/chunkserver/chunkserver-common/hdd_utils.h
+++ b/src/chunkserver/chunkserver-common/hdd_utils.h
@@ -28,8 +28,11 @@ inline std::atomic_bool gResetTester = false;
 // master reports = damaged chunks, lost chunks, new chunks
 inline std::mutex gMasterReportsLock;
 
-/// Avoid deadlock by waiting for specified time
-static constexpr uint8_t kSecondsToWaitForLockedChunk_ = 2;
+/// Avoid and point out possible deadlocks, by waiting for the specified time to
+/// release the condition variable waiting on a Chunk. The number is small
+/// enough not to stop the operation (mount retries) and big enough to detect
+/// unusual/unexpected behavior.
+static constexpr uint8_t kSecondsToWaitForLockedChunk_ = 20;
 
 /// Adds the error to the Disk owner of chunk in a thread sage way and preserves
 /// the errno to be used in later checks.
diff --git a/src/chunkserver/cmr_disk.cc b/src/chunkserver/cmr_disk.cc
index 6c9878be8..f023e3130 100644
--- a/src/chunkserver/cmr_disk.cc
+++ b/src/chunkserver/cmr_disk.cc
@@ -8,7 +8,7 @@
 #include "chunkserver-common/subfolder.h"
 #include "chunkserver/cmr_chunk.h"
 #include "common/crc.h"
-#include "common/saunafs_error_codes.h"
+#include "errors/saunafs_error_codes.h"
 #include "devtools/TracePrinter.h"
 #include "devtools/request_log.h"
 
@@ -369,8 +369,9 @@ int CmrDisk::writeChunkBlock(IChunk *chunk, uint32_t version, uint16_t blocknum,
 	    (offsetInBlock + size > SFSBLOCKSIZE)) {
 		return SAUNAFS_ERROR_WRONGOFFSET;
 	}
-	if (crc != mycrc32(0, buffer, size)) {
-		return SAUNAFS_ERROR_CRC;
+
+	if (gCheckCrcWhenWriting) {
+		if (crc != mycrc32(0, buffer, size)) { return SAUNAFS_ERROR_CRC; }
 	}
 
 	chunk->setWasChanged(1U);
diff --git a/src/chunkserver/hddspacemgr.cc b/src/chunkserver/hddspacemgr.cc
index 4e388f33c..1e920bb90 100644
--- a/src/chunkserver/hddspacemgr.cc
+++ b/src/chunkserver/hddspacemgr.cc
@@ -85,10 +85,10 @@
 #include "common/exceptions.h"
 #include "common/massert.h"
 #include "common/legacy_vector.h"
-#include "common/saunafs_error_codes.h"
+#include "errors/saunafs_error_codes.h"
 #include "common/serialization.h"
 #include "common/slice_traits.h"
-#include "common/slogger.h"
+#include "slogger/slogger.h"
 #include "common/time_utils.h"
 #include "common/unique_queue.h"
 #include "devtools/TracePrinter.h"
@@ -775,6 +775,8 @@ int hddRead(uint64_t chunkId, uint32_t version, ChunkPartType chunkType,
 	LOG_AVG_TILL_END_OF_SCOPE0("hddRead");
 	TRACETHIS3(chunkId, offset, size);
 
+	safs_silent_syslog(LOG_DEBUG, "chunkserver.hddRead");
+
 	uint32_t offsetWithinBlock = offset % SFSBLOCKSIZE;
 
 	if ((size == 0) || ((offsetWithinBlock + size) > SFSBLOCKSIZE)) {
@@ -2127,6 +2129,8 @@ int hddChunkOperation(uint64_t chunkId, uint32_t chunkVersion,
 static UniqueQueue<ChunkWithVersionAndType> gTestChunkQueue;
 
 static void hddTestChunkThread() {
+	pthread_setname_np(pthread_self(), "testChunkThread");
+
 	bool terminate = false;
 
 	while (!terminate) {
@@ -2162,6 +2166,9 @@ void hddAddChunkToTestQueue(ChunkWithVersionAndType chunk) {
 
 void hddTesterThread() {
 	TRACETHIS();
+
+	pthread_setname_np(pthread_self(), "testerThread");
+
 	IChunk *chunk;
 	uint64_t chunkId;
 	uint32_t version;
@@ -2464,6 +2471,9 @@ void hddDiskScanThread(IDisk *disk) {
 
 void hddDisksThread() {
 	TRACETHIS();
+
+	pthread_setname_np(pthread_self(), "disksThread");
+
 	while (!gTerminate) {
 		hddCheckDisks();
 		sleep(1);
@@ -2475,6 +2485,8 @@ void hddFreeResourcesThread() {
 	static const int kMaxFreeUnused = 1024;
 	TRACETHIS();
 
+	pthread_setname_np(pthread_self(), "freeResThread");
+
 	while (!gTerminate) {
 		gOpenChunks.freeUnused(eventloop_time(), gChunksMapMutex,
 		                       kMaxFreeUnused);
@@ -2863,6 +2875,7 @@ void hddReload(void) {
 	gHDDTestFreq_ms =
 	    cfg_ranged_get("HDD_TEST_FREQ", 10., 0.001, 1000000.) * 1000;
 	gCheckCrcWhenReading = cfg_getuint8("HDD_CHECK_CRC_WHEN_READING", 1) != 0U;
+	gCheckCrcWhenWriting = cfg_getuint8("HDD_CHECK_CRC_WHEN_WRITING", 1) != 0U;
 	gPunchHolesInFiles = cfg_getuint32("HDD_PUNCH_HOLES", 0);
 
 	char *LeaveFreeStr = cfg_getstr("HDD_LEAVE_SPACE_DEFAULT",
@@ -2911,14 +2924,21 @@ int loadPlugins() {
 	std::string pluginsInstallDirPath = PLUGINS_PATH "/chunkserver";
 	std::string pluginsBuildDirPath = BUILD_PATH "/plugins/chunkserver";
 
+	// Try to load plugins first from the installation directory
 	if (!pluginManager.loadPlugins(pluginsInstallDirPath)) {
-		safs_pretty_syslog(LOG_WARNING, "Loading plugins from %s failed!!!",
+		safs_pretty_syslog(LOG_NOTICE,
+		                   "PluginManager: No plugins loaded from: %s",
 		                   pluginsInstallDirPath.c_str());
+
+		// If no plugins were loaded from the installation directory,
+		// try to load them from the build directory (useful for development)
 		if (!pluginManager.loadPlugins(pluginsBuildDirPath)) {
-			safs_pretty_syslog(LOG_WARNING, "Loading plugins from %s failed!!!",
+			safs_pretty_syslog(LOG_NOTICE,
+			                   "PluginManager: No plugins loaded from: %s",
 			                   pluginsBuildDirPath.c_str());
 		}
 	}
+
 	pluginManager.showLoadedPlugins();
 
 	return SAUNAFS_STATUS_OK;
@@ -2975,6 +2995,7 @@ int hddInit() {
 	gHDDTestFreq_ms =
 	    cfg_ranged_get("HDD_TEST_FREQ", 10., 0.001, 1000000.) * 1000;
 	gCheckCrcWhenReading = cfg_getuint8("HDD_CHECK_CRC_WHEN_READING", 1) != 0U;
+	gCheckCrcWhenWriting = cfg_getuint8("HDD_CHECK_CRC_WHEN_WRITING", 1) != 0U;
 
 	gPunchHolesInFiles = cfg_getuint32("HDD_PUNCH_HOLES", 0);
 
diff --git a/src/chunkserver/masterconn.cc b/src/chunkserver/masterconn.cc
index f955bb657..5eccff38b 100644
--- a/src/chunkserver/masterconn.cc
+++ b/src/chunkserver/masterconn.cc
@@ -51,7 +51,7 @@
 #include "common/legacy_vector.h"
 #include "common/output_packet.h"
 #include "common/random.h"
-#include "common/slogger.h"
+#include "slogger/slogger.h"
 #include "common/sockets.h"
 #include "common/time_utils.h"
 #include "devtools/request_log.h"
diff --git a/src/chunkserver/network_main_thread.cc b/src/chunkserver/network_main_thread.cc
index 508cecc41..f795cf3b1 100644
--- a/src/chunkserver/network_main_thread.cc
+++ b/src/chunkserver/network_main_thread.cc
@@ -57,7 +57,7 @@
 #include "common/massert.h"
 #include "protocol/SFSCommunication.h"
 #include "protocol/packet.h"
-#include "common/slogger.h"
+#include "slogger/slogger.h"
 #include "common/sockets.h"
 #include "devtools/TracePrinter.h"
 
diff --git a/src/chunkserver/network_worker_thread.cc b/src/chunkserver/network_worker_thread.cc
index 3fea7276e..410c9d2db 100644
--- a/src/chunkserver/network_worker_thread.cc
+++ b/src/chunkserver/network_worker_thread.cc
@@ -53,7 +53,7 @@
 #include "protocol/SFSCommunication.h"
 #include "common/legacy_vector.h"
 #include "protocol/packet.h"
-#include "common/slogger.h"
+#include "slogger/slogger.h"
 #include "common/sockets.h"
 #include "devtools/request_log.h"
 #include "devtools/TracePrinter.h"
@@ -1527,6 +1527,11 @@ NetworkWorkerThread::NetworkWorkerThread(uint32_t nrOfBgjobsWorkers, uint32_t bg
 
 void NetworkWorkerThread::operator()() {
 	TRACETHIS();
+
+	static std::atomic_uint16_t threadCounter(0);
+	std::string threadName = "networkWorker " + std::to_string(threadCounter++);
+	pthread_setname_np(pthread_self(), threadName.c_str());
+
 	while (!doTerminate) {
 		preparePollFds();
 		int i = poll(pdesc.data(), pdesc.size(), 50);
diff --git a/src/chunkserver/plugin_manager.cc b/src/chunkserver/plugin_manager.cc
index 60f5dee2f..95dd51439 100644
--- a/src/chunkserver/plugin_manager.cc
+++ b/src/chunkserver/plugin_manager.cc
@@ -2,15 +2,16 @@
 
 #include <sys/syslog.h>
 
-#include "common/slogger.h"
+#include "slogger/slogger.h"
 
 bool PluginManager::loadPlugins(const std::string &directory) {
-	// Check whether directory exists or is empty
 	if (!boost::filesystem::is_directory(directory) ||
 	    boost::filesystem::is_empty(directory)) {
-		safs_pretty_errlog(LOG_WARNING,
-		                   "Directory %s does not exist or is empty",
-		                   directory.c_str());
+		// It is normal to not have any plugins in many scenarios
+		safs_pretty_syslog(
+		    LOG_NOTICE,
+		    "PluginManager: Directory %s does not exist or is empty",
+		    directory.c_str());
 		return false;
 	}
 
diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt
index 7f763143d..93d7cfe6a 100644
--- a/src/common/CMakeLists.txt
+++ b/src/common/CMakeLists.txt
@@ -19,6 +19,8 @@ endif()
 
 shared_add_library(sfscommon ${COMMON_SOURCES})
 shared_target_link_libraries(sfscommon ${YAML_CPP_LIBRARIES})
+shared_target_link_libraries(sfscommon STATIC sfserr SHARED sfserr_pic)
+shared_target_link_libraries(sfscommon STATIC slogger SHARED slogger_pic)
 
 if (HAVE_CRCUTIL)
   # Use crcutil Library
diff --git a/src/common/cfg.cc b/src/common/cfg.cc
index 38c5ca18f..33168f3ec 100644
--- a/src/common/cfg.cc
+++ b/src/common/cfg.cc
@@ -22,6 +22,7 @@
 #include "common/cfg.h"
 
 #include <map>
+#include <cinttypes>
 #include <yaml-cpp/yaml.h>
 
 #include "common/massert.h"
diff --git a/src/common/cfg.h b/src/common/cfg.h
index e2174c6bb..b6c24fb42 100644
--- a/src/common/cfg.h
+++ b/src/common/cfg.h
@@ -25,7 +25,7 @@
 #include <map>
 #include <string>
 
-#include "common/slogger.h"
+#include "slogger/slogger.h"
 
 #define _CONFIG_MAKE_PROTOTYPE(fname,type) type cfg_get##fname(const char *name,const type def)
 
diff --git a/src/common/charts.cc b/src/common/charts.cc
index 69d828156..a85f52868 100644
--- a/src/common/charts.cc
+++ b/src/common/charts.cc
@@ -42,7 +42,7 @@
 #include "common/cwrap.h"
 #include "common/datapack.h"
 #include "common/massert.h"
-#include "common/slogger.h"
+#include "slogger/slogger.h"
 
 #ifdef SAUNAFS_HAVE_ZLIB_H
 #  include <zlib.h>
diff --git a/src/common/chunk_connector.cc b/src/common/chunk_connector.cc
index 00505c747..754d4b8b9 100644
--- a/src/common/chunk_connector.cc
+++ b/src/common/chunk_connector.cc
@@ -24,7 +24,7 @@
 #include <algorithm>
 
 #include "common/exceptions.h"
-#include "common/sfserr.h"
+#include "errors/sfserr.h"
 #include "common/sockets.h"
 #include "common/time_utils.h"
 
diff --git a/src/common/chunk_copies_calculator.cc b/src/common/chunk_copies_calculator.cc
index 881059b75..635f3960b 100644
--- a/src/common/chunk_copies_calculator.cc
+++ b/src/common/chunk_copies_calculator.cc
@@ -63,44 +63,88 @@ void ChunkCopiesCalculator::removePart(const Goal::Slice::Type &slice_type, int
 	}
 }
 
-void ChunkCopiesCalculator::optimize() {
+/**
+ * @brief Check if the provided slice should be skipped or not by the optimize function. 
+ * 
+ * A slice can be skipped if all their parts require the same because all of its 
+ * permutations are equal.
+ * 
+ * @param slice ```Slice``` to be checked.
+ * @return true The slice should be skipped. --
+ * @return false The slice should not be skipped.
+ */
+bool isSkippable(Goal::Slice &slice) {
+	for (int i = 1; i < slice.size(); ++i) {
+		if (!LinearAssignmentCache::OptimizerInputOutput::partsMatch(
+		        slice[0], slice[i])) {
+			return false;
+		}
+	}
+	return true;
+}
+
+void ChunkCopiesCalculator::optimize(
+    bool useLinearAssignmentOptimizer,
+    LinearAssignmentCache *linearAssignmentCache) {
 	constexpr int max_slice_op = 10 * Goal::kMaxExpectedCopies;
 
 	for (auto &target_slice : target_) {
+		if (!useLinearAssignmentOptimizer) {
+			break;
+		}
+
 		auto slice_it = available_.find(target_slice.getType());
-		if (slice_it == available_.end() ||
-		    target_slice.size() <= 1) {
+		if (slice_it == available_.end() || target_slice.size() <= 1) {
+			continue;
+		}
+
+		if (isSkippable(target_slice)) {
 			continue;
 		}
-		const Goal::Slice &src_slice = *slice_it;
 
-		std::array<std::array<int, Goal::Slice::kMaxPartsCount>,
-		           Goal::Slice::kMaxPartsCount> cost;
-		std::array<int, Goal::Slice::kMaxPartsCount> assignment, object_assignment;
+		Goal::Slice &src_slice = *slice_it;
+		std::array<int, Goal::Slice::kMaxPartsCount> assignment;
+		bool isCacheAvailable =
+		    (linearAssignmentCache != LinearAssignmentCache::kNotProvidedCache);
+		if (!isCacheAvailable ||
+		    !linearAssignmentCache
+		         ->checkMatchAndGetResult<Goal::Slice::kMaxPartsCount>(
+		             target_slice, src_slice, assignment)) {
+			std::array<std::array<int, Goal::Slice::kMaxPartsCount>,
+			           Goal::Slice::kMaxPartsCount>
+			    cost;
+			std::array<int, Goal::Slice::kMaxPartsCount> object_assignment;
+
+			int src = 0;
+			for (const auto &src_part :
+			     static_cast<const Goal::Slice&>(src_slice)) {
+				int target = 0;
+				for (const auto &target_part :
+				     static_cast<const Goal::Slice&>(target_slice)) {
+					auto op_count = operationCount(src_part, target_part);
+					op_count = std::make_pair(
+					    std::min(op_count.first, max_slice_op - 1),
+					    std::min(op_count.second, max_slice_op - 1));
+					cost[src][target] =
+					    max_slice_op * max_slice_op -
+					    (max_slice_op * op_count.first + op_count.second);
+					++target;
+				}
+				++src;
+			}
 
-		int i = 0;
-		for(const auto &src_part : src_slice) {
-			int j = 0;
-			for(const auto &target_part : static_cast<const Goal::Slice&>(target_slice)) {
-				auto op_count = operationCount(src_part, target_part);
-				op_count =
-				        std::make_pair(std::min(op_count.first, max_slice_op - 1),
-				                       std::min(op_count.second, max_slice_op - 1));
-				cost[i][j] = max_slice_op * max_slice_op -
-				             (max_slice_op * op_count.first + op_count.second);
-				++j;
+			linear_assignment::auctionOptimization(
+			    cost, assignment, object_assignment, target_slice.size());
+
+			if (isCacheAvailable) {
+				linearAssignmentCache->store<Goal::Slice::kMaxPartsCount>(
+				    target_slice, src_slice, assignment);
 			}
-			++i;
 		}
 
-		linear_assignment::auctionOptimization(cost, assignment, object_assignment,
-		                                       target_slice.size());
-
 		Goal::Slice result(target_slice.getType());
-		i = 0;
-		for (auto result_part : result) {
-			result_part = target_slice[assignment[i]];
-			++i;
+		for (int k = 0; k < target_slice.size(); ++k) {
+			result[k] = target_slice[assignment[k]];
 		}
 		target_slice = std::move(result);
 	}
diff --git a/src/common/chunk_copies_calculator.h b/src/common/chunk_copies_calculator.h
index 20997ae6b..d6c245d71 100644
--- a/src/common/chunk_copies_calculator.h
+++ b/src/common/chunk_copies_calculator.h
@@ -27,6 +27,7 @@
 #include "common/chunks_availability_state.h"
 #include "common/goal.h"
 #include "common/flat_map.h"
+#include "common/linear_assignment_cache.h"
 #include "common/media_label.h"
 
 /*! \brief Class used to calculate required operations to achieve chunk goal.
@@ -79,7 +80,9 @@ class ChunkCopiesCalculator {
 	 *
 	 * All query functions might be used only after this function has been executed.
 	 */
-	void optimize();
+	void optimize(bool useLinearAssignmentOptimizer = true,
+	              LinearAssignmentCache *linearAssignmentCache =
+	                  LinearAssignmentCache::kNotProvidedCache);
 
 	/*! \brief Evaluate chunk redundancy level.
 	 *
diff --git a/src/common/event_loop.cc b/src/common/event_loop.cc
index 7f3f11d62..925f1e100 100644
--- a/src/common/event_loop.cc
+++ b/src/common/event_loop.cc
@@ -31,6 +31,7 @@
 #include "common/cfg.h"
 #include "common/exception.h"
 #include "common/massert.h"
+#include "errors/sfserr.h"
 
 #if defined(_WIN32)
   #include "common/sockets.h"
diff --git a/src/common/exception.h b/src/common/exception.h
index 514ad5e96..c910540be 100644
--- a/src/common/exception.h
+++ b/src/common/exception.h
@@ -26,8 +26,7 @@
 #include <exception>
 #include <string>
 
-#include "protocol/SFSCommunication.h"
-#include "common/sfserr.h"
+#include "errors/saunafs_error_codes.h"
 
 class Exception : public std::exception {
 public:
diff --git a/src/common/exceptions.h b/src/common/exceptions.h
index 16fe4e52f..f0b05e6dc 100644
--- a/src/common/exceptions.h
+++ b/src/common/exceptions.h
@@ -24,7 +24,7 @@
 
 #include "common/chunk_part_type.h"
 #include "common/exception.h"
-#include "common/sfserr.h"
+#include "errors/sfserr.h"
 #include "common/network_address.h"
 
 SAUNAFS_CREATE_EXCEPTION_CLASS(ConfigurationException, Exception);
diff --git a/src/common/linear_assignment_cache.h b/src/common/linear_assignment_cache.h
new file mode 100644
index 000000000..9c572ac40
--- /dev/null
+++ b/src/common/linear_assignment_cache.h
@@ -0,0 +1,196 @@
+/*
+   Copyright 2023 Leil Storage OÜ
+
+   This file is part of SaunaFS.
+
+   SaunaFS is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation, version 3.
+
+   SaunaFS is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with SaunaFS. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "common/platform.h"
+
+#include <algorithm>
+#include <array>
+
+#include "common/goal.h"
+
+/**
+ * @brief Cache for linear assignment optimizer input/output.
+ *
+ * Note this cache can be used in multiple scenarios, and the output for the
+ * same input can differ for different scenarios.
+ *
+ * Implementation allows:
+ *   - query (```checkMatchAndGetResult```) for the assignment calculated by the
+ * ```auctionOptimization``` function given a pair of target and source slice.
+ * If no cached entry matches input, then no assignment is replied.
+ *   - ```store``` the assignment calculated by the ```auctionOptimization```
+ * function and the input that generated it.
+ *
+ * The cost of both functions is proportional to the sum of the number of parts
+ * in the two slices and the sum of the number of labels with copies for each of
+ * those parts.
+ *
+ * Polynomial hashing on the concatenation of both slices is used to find an
+ * integer that identifies the input. This values is then used to fast index in
+ * the entries container and query/store data.
+ */
+class LinearAssignmentCache {
+public:
+	static constexpr LinearAssignmentCache *kNotProvidedCache = nullptr;
+
+	// Single linear optimizer input/output pair.
+	class OptimizerInputOutput {
+	public:
+		OptimizerInputOutput()
+		    : target_slice_(Goal::Slice::Type(1)),
+		      source_slice_(Goal::Slice::Type(1)),
+		      assignment_() {}
+
+		static bool partsMatch(const detail::Slice::PartProxy &part1,
+		                       const detail::Slice::PartProxy &part2) {
+			return part1.size() == part2.size() &&
+			       std::equal(part1.begin(), part1.end(), part2.begin());
+		}
+
+		template <std::size_t N>
+		bool checkMatchAndGetResult(Goal::Slice &target_slice,
+		                            Goal::Slice &source_slice,
+		                            std::array<int, N> &assignment) {
+			if (target_slice.getType() != target_slice_.getType() ||
+			    source_slice.getType() != source_slice_.getType()) {
+				return false;
+			}
+			assert(target_slice.size() == target_slice_.size());
+			assert(source_slice.size() == source_slice_.size());
+
+			for (int i = 0; i < target_slice.size(); ++i) {
+				if (!partsMatch(target_slice[i], target_slice_[i])) {
+					return false;
+				}
+			}
+
+			for (int i = 0; i < source_slice.size(); ++i) {
+				if (!partsMatch(source_slice[i], source_slice_[i])) {
+					return false;
+				}
+			}
+
+			// match is good
+			std::copy(assignment_.begin(), assignment_.end(),
+			          assignment.begin());
+
+			return true;
+		}
+
+		template <std::size_t N>
+		void store(Goal::Slice &target_slice, Goal::Slice &source_slice,
+		           std::array<int, N> &assignment) {
+			target_slice_ = target_slice;
+			source_slice_ = source_slice;
+			assignment_.assign(assignment.begin(), assignment.end());
+		}
+
+	private:
+		Goal::Slice target_slice_, source_slice_;
+		std::vector<int> assignment_;
+	};
+
+	using HashType = int64_t;
+	using EntriesContainer = std::unordered_map<HashType, OptimizerInputOutput>;
+
+	LinearAssignmentCache() : cachedData_() {}
+
+	static HashType getLabelValue(MediaLabel label) {
+		return static_cast<MediaLabelManager::HandleValue>(label);
+	}
+
+	/**
+	 * @brief Calculate hash of a part.
+	 *
+	 * Hash of a part is P(```kBasePart_```) mod ```kModPart_```. P is a
+	 * polynomial whose coeficients are values dependant on each (labelId,
+	 * copies) pair of the part.
+	 *
+	 * @param part Part to be calculated hash.
+	 * @return Hash of the given part.
+	 */
+	static HashType hashPart(const detail::Slice::PartProxy &part) {
+		HashType hash = 1;
+		for (const auto &[labelId, copies] : part) {
+			HashType label_hash =
+			    (getLabelValue(labelId) * Goal::kMaxExpectedCopies + copies) %
+			    kBasePart_;
+			hash = (hash * kBasePart_ + label_hash) % kModPart_;
+		}
+		return hash;
+	}
+
+	/**
+	 * @brief Calculate hash of a slice starting from a given hash.
+	 *
+	 * Hash of a part is Q(```kBaseSlice_```) mod ```kModSlice_```. Q is a
+	 * polynomial whose coeficients are the hashed of each of the parts in the
+	 * slice.
+	 *
+	 * @param startingHash Could be the ```kInitialStartingHash_``` or some hash
+	 * calculation from a previos slice.
+	 * @param slice Slice to be calculated hash.
+	 * @return Hash of the given slice starting from the provided hash.
+	 */
+	static HashType hashSlice(HashType startingHash, Goal::Slice &slice) {
+		HashType hash = startingHash;
+		for (const auto &part : slice) {
+			HashType part_value = hashPart(part);
+			hash = (hash * kBaseSlice_ + part_value) % kModSlice_;
+		}
+		return hash;
+	}
+
+	/**
+	 * @brief Calculate the hash of a concatenation of slices.
+	 *
+	 * Note the order is important.
+	 */
+	static HashType hashSlicePair(Goal::Slice &target_slice,
+	                              Goal::Slice &source_slice) {
+		HashType hash = hashSlice(kInitialStartingHash_, target_slice);
+		hash = hashSlice(hash, source_slice);
+		return hash;
+	}
+
+	template <std::size_t N>
+	bool checkMatchAndGetResult(Goal::Slice &target_slice,
+	                            Goal::Slice &source_slice,
+	                            std::array<int, N> &assignment) {
+		HashType hash = hashSlicePair(target_slice, source_slice);
+		return cachedData_[hash].checkMatchAndGetResult<N>(
+		    target_slice, source_slice, assignment);
+	}
+
+	template <std::size_t N>
+	void store(Goal::Slice &target_slice, Goal::Slice &source_slice,
+	           std::array<int, N> &assignment) {
+		HashType hash = hashSlicePair(target_slice, source_slice);
+		cachedData_[hash].store<N>(target_slice, source_slice, assignment);
+	}
+
+protected:
+	static constexpr HashType kBasePart_ = 50000017;
+	static constexpr HashType kModPart_ = 100000007;
+	static constexpr HashType kBaseSlice_ = 500000009;
+	static constexpr HashType kModSlice_ = 1000000007;
+	static constexpr HashType kInitialStartingHash_ = 1;
+	EntriesContainer cachedData_;
+};
diff --git a/src/common/massert.h b/src/common/massert.h
index 8afbe8577..59e21d032 100644
--- a/src/common/massert.h
+++ b/src/common/massert.h
@@ -25,9 +25,11 @@
 #include <errno.h>
 #include <stdio.h>
 #include <stdlib.h>
-
-#include "common/sfserr.h"
-#include "common/slogger.h"
+#include "slogger/slogger.h"
+#include "errors/sfserr.h"
+#ifndef _WIN32
+#include <sys/syslog.h>
+#endif
 
 #ifdef THROW_INSTEAD_OF_ABORT
 #  include <stdexcept>
diff --git a/src/common/memory_mapped_file.cc b/src/common/memory_mapped_file.cc
index 3a92cc86d..31ee27e8e 100644
--- a/src/common/memory_mapped_file.cc
+++ b/src/common/memory_mapped_file.cc
@@ -24,7 +24,7 @@
 #include <stdexcept>
 #include <vector>
 
-#include "common/slogger.h"
+#include "slogger/slogger.h"
 #include "cwrap.h"
 
 class MemoryMappedFile::Impl {
diff --git a/src/common/metadata.cc b/src/common/metadata.cc
index 4bcedbdee..1e5470202 100644
--- a/src/common/metadata.cc
+++ b/src/common/metadata.cc
@@ -23,7 +23,6 @@
 
 #include <fcntl.h>
 #include <sys/mman.h>
-#include "common/stat32.h"
 #include <sys/types.h>
 #include <syslog.h>
 #include <unistd.h>
@@ -34,8 +33,8 @@
 
 #include "common/cwrap.h"
 #include "common/datapack.h"
-#include "common/sfserr.h"
-#include "common/slogger.h"
+#include "slogger/slogger.h"
+#include "protocol/SFSCommunication.h"
 
 #define METADATA_FILENAME_TEMPL "metadata.sfs"
 const char kMetadataFilename[] = METADATA_FILENAME_TEMPL;
diff --git a/src/common/pcqueue.cc b/src/common/pcqueue.cc
index be0050f59..ff50fd6b1 100644
--- a/src/common/pcqueue.cc
+++ b/src/common/pcqueue.cc
@@ -28,6 +28,7 @@
 
 #include "common/massert.h"
 #include "devtools/TracePrinter.h"
+#include "errors/sfserr.h"
 
 typedef struct _qentry {
 	uint32_t id;
diff --git a/src/common/read_plan_executor.cc b/src/common/read_plan_executor.cc
index b07ac6b12..d14d76a55 100644
--- a/src/common/read_plan_executor.cc
+++ b/src/common/read_plan_executor.cc
@@ -30,7 +30,7 @@
 #include "common/lambda_guard.h"
 #include "common/saunafs_version.h"
 #include "common/massert.h"
-#include "common/sfserr.h"
+#include "errors/sfserr.h"
 #include "common/read_operation_executor.h"
 #include "common/sockets.h"
 #include "common/time_utils.h"
diff --git a/src/common/rotate_files.cc b/src/common/rotate_files.cc
index 124bd4253..5502c298e 100644
--- a/src/common/rotate_files.cc
+++ b/src/common/rotate_files.cc
@@ -26,7 +26,7 @@
 #include "common/cwrap.h"
 #include "common/exceptions.h"
 #include "common/massert.h"
-#include "common/slogger.h"
+#include "slogger/slogger.h"
 
 namespace {
 
diff --git a/src/common/scoped_timer.cc b/src/common/scoped_timer.cc
index 788b4368d..b26006685 100644
--- a/src/common/scoped_timer.cc
+++ b/src/common/scoped_timer.cc
@@ -19,7 +19,7 @@
 
 #include <sys/syslog.h>
 
-#include "common/slogger.h"
+#include "slogger/slogger.h"
 #include "common/time_utils.h"
 
 namespace util {
diff --git a/src/common/server_connection.cc b/src/common/server_connection.cc
index a6121c63a..9041cb035 100644
--- a/src/common/server_connection.cc
+++ b/src/common/server_connection.cc
@@ -25,7 +25,7 @@
 #include "common/exceptions.h"
 #include "common/message_receive_buffer.h"
 #include "protocol/SFSCommunication.h"
-#include "common/sfserr.h"
+#include "errors/sfserr.h"
 #include "common/multi_buffer_writer.h"
 #include "protocol/packet.h"
 #include "common/sockets.h"
diff --git a/src/data/CMakeLists.txt b/src/data/CMakeLists.txt
index d8479e9ac..d2d03d462 100644
--- a/src/data/CMakeLists.txt
+++ b/src/data/CMakeLists.txt
@@ -32,7 +32,7 @@ if(BUILD_TESTS)
       ${CMAKE_SOURCE_DIR}/src/mount/client/saunafs_c_api.h
       ${CMAKE_CURRENT_BINARY_DIR}/include/saunafs/saunafs_c_api.h
     COMMAND ${CMAKE_COMMAND} -E create_symlink
-      ${CMAKE_SOURCE_DIR}/src/common/saunafs_error_codes.h
+      ${CMAKE_SOURCE_DIR}/src/errors/saunafs_error_codes.h
       ${CMAKE_CURRENT_BINARY_DIR}/include/saunafs/saunafs_error_codes.h
   )
   add_executable(c-client-example
diff --git a/src/data/sfschunkserver.cfg.in b/src/data/sfschunkserver.cfg.in
index 157e810ae..e88ed07ff 100644
--- a/src/data/sfschunkserver.cfg.in
+++ b/src/data/sfschunkserver.cfg.in
@@ -76,6 +76,15 @@
 ## (Default: 1)
 # HDD_CHECK_CRC_WHEN_READING = 1
 
+## Whether to check the CRC on every write operation.
+## This option enabled can detect CRC errors immediately when writing. The CRC
+## calculation can become the function that consumes most of the CPU. The
+## default, safest and recommended value is 1, but it can be disabled to test
+## the influence of the CRC checking in the write performance during profiling.
+## Disabling it is only intended for expert users and developers.
+## (Default: 1)
+# HDD_CHECK_CRC_WHEN_WRITING = 1
+
 ## Whether to remove each chunk from page when closing it to reduce cache pressure
 ## generated by chunkserver, boolean (0 means "no").
 ## (Default: 0)
diff --git a/src/data/sfsmaster.cfg.in b/src/data/sfsmaster.cfg.in
index aed0706e8..6bb2a785e 100644
--- a/src/data/sfsmaster.cfg.in
+++ b/src/data/sfsmaster.cfg.in
@@ -118,6 +118,11 @@
 ## (Default: 60)
 # CHUNKS_LOOP_MAX_CPU = 60
 
+## Use linear assignment in chunks optimization. It is suggested to 
+## disable it if not using labeled chunkservers (Boolean, 0 or 1).
+## (Default: 1)
+# USE_LINEAR_ASSIGNMENT_OPTIMIZER = 1
+
 ## Soft maximum number of chunks to delete on one chunkserver.
 ## (Default: 10)
 # CHUNKS_SOFT_DEL_LIMIT = 10
diff --git a/src/devtools/TracePrinter.h b/src/devtools/TracePrinter.h
index 4e7de001d..a32144596 100644
--- a/src/devtools/TracePrinter.h
+++ b/src/devtools/TracePrinter.h
@@ -38,7 +38,7 @@
 #include <boost/format.hpp>
 
 #ifdef ENABLE_SYSLOG_FOR_TRACES
-#include "common/slogger.h"
+#include "slogger/slogger.h"
 #endif
 
 class ThreadPrinter {
diff --git a/src/devtools/request_log.h b/src/devtools/request_log.h
index c68cbfce3..ccfebaeb4 100644
--- a/src/devtools/request_log.h
+++ b/src/devtools/request_log.h
@@ -42,7 +42,7 @@
 #include <boost/iostreams/filtering_stream.hpp>
 
 #include "common/massert.h"
-#include "common/slogger.h"
+#include "slogger/slogger.h"
 #include "devtools/configuration.h"
 
 class RequestLogConfiguration : Configuration {
diff --git a/src/errors/CMakeLists.txt b/src/errors/CMakeLists.txt
new file mode 100644
index 000000000..031a793cb
--- /dev/null
+++ b/src/errors/CMakeLists.txt
@@ -0,0 +1,3 @@
+collect_sources(SFSERR)
+
+shared_add_library(sfserr ${SFSERR_SOURCES})
diff --git a/src/common/saunafs_error_codes.cc b/src/errors/saunafs_error_codes.cc
similarity index 100%
rename from src/common/saunafs_error_codes.cc
rename to src/errors/saunafs_error_codes.cc
diff --git a/src/common/saunafs_error_codes.h b/src/errors/saunafs_error_codes.h
similarity index 98%
rename from src/common/saunafs_error_codes.h
rename to src/errors/saunafs_error_codes.h
index 2e13643c9..f55232005 100644
--- a/src/common/saunafs_error_codes.h
+++ b/src/errors/saunafs_error_codes.h
@@ -19,8 +19,7 @@
    along with SaunaFS  If not, see <http://www.gnu.org/licenses/>.
  */
 
-#ifndef __SAUNAFS_ERROR_CODES_H
-#define __SAUNAFS_ERROR_CODES_H
+#pragma once
 
 #include <stdint.h>
 
@@ -86,5 +85,3 @@ enum saunafs_error_code {
 };
 
 const char *saunafs_error_string(uint8_t status);
-
-#endif
diff --git a/src/common/sfserr.cc b/src/errors/sfserr.cc
similarity index 93%
rename from src/common/sfserr.cc
rename to src/errors/sfserr.cc
index 51ed75f77..1731d874c 100644
--- a/src/common/sfserr.cc
+++ b/src/errors/sfserr.cc
@@ -19,20 +19,14 @@
  */
 
 #include "common/platform.h"
-#include "common/sfserr.h"
+#include "sfserr.h"
 
-#include <cstdio>
-#include <cstdlib>
+#include <cerrno>
 #include <cstring>
-#include <errno.h>
-#include <inttypes.h>
-
-#include <array>
 #include <mutex>
-#include <string>
 #include <unordered_map>
 
-#include "common/errno_defs.h"
+#include "saunafs_error_codes.h"
 
 #ifndef EDQUOT
 # define EDQUOT ENOSPC
diff --git a/src/common/sfserr.h b/src/errors/sfserr.h
similarity index 82%
rename from src/common/sfserr.h
rename to src/errors/sfserr.h
index 6c50a65ef..69a3a268a 100644
--- a/src/common/sfserr.h
+++ b/src/errors/sfserr.h
@@ -22,10 +22,14 @@
 
 #include "common/platform.h"
 
-#include <inttypes.h>
-
-#include "common/saunafs_error_codes.h"
-#include "protocol/SFSCommunication.h"
+#include <cstdint>
+
+/* Workaround for Debian/kFreeBSD which does not define ENODATA */
+#if defined(__FreeBSD_kernel__) || defined(__FreeBSD__)
+#ifndef ENODATA
+#define ENODATA ENOATTR
+#endif
+#endif
 
 /// returns errno string representation
 const char* strerr(int error);
diff --git a/src/main/main.cc b/src/main/main.cc
index 84bb8f15b..3aabf250a 100644
--- a/src/main/main.cc
+++ b/src/main/main.cc
@@ -53,9 +53,8 @@
 #include "common/event_loop.h"
 #include "common/main.h"
 #include "common/massert.h"
-#include "common/sfserr.h"
 #include "common/setup.h"
-#include "common/slogger.h"
+#include "slogger/slogger.h"
 #include "common/time_utils.h"
 #include "protocol/SFSCommunication.h"
 
diff --git a/src/master/changelog.cc b/src/master/changelog.cc
index 54ba3f626..9c59c123c 100644
--- a/src/master/changelog.cc
+++ b/src/master/changelog.cc
@@ -31,7 +31,7 @@
 #include "common/main.h"
 #include "common/metadata.h"
 #include "common/rotate_files.h"
-#include "common/slogger.h"
+#include "slogger/slogger.h"
 
 /// Base name of a changelog file.
 /// Sometthing like "changelog.sfs" or "changelog_ml.sfs"
diff --git a/src/master/chunks.cc b/src/master/chunks.cc
index 2990ab4ca..4a5414d2f 100644
--- a/src/master/chunks.cc
+++ b/src/master/chunks.cc
@@ -90,6 +90,8 @@ static uint32_t gRedundancyLevel;
 static uint64_t gEndangeredChunksServingLimit;
 static uint64_t gEndangeredChunksMaxCapacity;
 static uint64_t gDisconnectedCounter = 0;
+inline LinearAssignmentCache gLinearAssignmentCache;
+inline bool gUseLinearAssignmentOptimizer;
 bool gAvoidSameIpChunkservers = false;
 
 struct ChunkPart {
@@ -378,7 +380,7 @@ class Chunk {
 			all.addPart(part.type, csdb_find(part.csid)->label);
 		}
 
-		all.optimize();
+		all.optimize(gUseLinearAssignmentOptimizer, &gLinearAssignmentCache);
 
 		allFullCopies_ = std::min(kMaxStatCount, all.getFullCopiesCount());
 		allAvailabilityState_ = all.getState();
@@ -1312,7 +1314,7 @@ int chunk_repair(uint8_t goal, uint64_t ochunkid, uint32_t *nversion, uint8_t co
 	for (auto &version_and_calculator : calculators) {
 		uint32_t version = version_and_calculator.first;
 		ChunkCopiesCalculator &calculator = version_and_calculator.second;
-		calculator.optimize();
+		calculator.optimize(gUseLinearAssignmentOptimizer, &gLinearAssignmentCache);
 		// calculator.isRecoveryPossible() won't work below, because target goal is empty.
 		if (calculator.getFullCopiesCount() > 0) {
 			best_version = version;
@@ -2374,7 +2376,7 @@ void ChunkWorker::doChunkJobs(Chunk *c, uint16_t serverCount) {
 			++invalid_parts;
 		}
 	}
-	calc.optimize();
+	calc.optimize(gUseLinearAssignmentOptimizer, &gLinearAssignmentCache);
 
 	// step 1a. count number of chunk parts on servers with the same ip
 	IpCounter ip_occurrence;
@@ -2758,6 +2760,7 @@ void chunk_reload(void) {
 	gOperationsDelayDisconnect = cfg_getuint32("OPERATIONS_DELAY_DISCONNECT", gOperationsDelayDisconnect);
 	gAvoidSameIpChunkservers = cfg_getuint32("AVOID_SAME_IP_CHUNKSERVERS", 0);
 	gRedundancyLevel = cfg_getuint32("REDUNDANCY_LEVEL", 0);
+	gUseLinearAssignmentOptimizer = cfg_getuint32("USE_LINEAR_ASSIGNMENT_OPTIMIZER", 1);
 
 	uint32_t disableChunksDel = cfg_getuint32("DISABLE_CHUNKS_DEL", 0);
 	if (disableChunksDel) {
@@ -2852,6 +2855,7 @@ int chunk_strinit(void) {
 	gOperationsDelayDisconnect = cfg_getuint32("OPERATIONS_DELAY_DISCONNECT", gOperationsDelayDisconnect);
 	gAvoidSameIpChunkservers = cfg_getuint32("AVOID_SAME_IP_CHUNKSERVERS", 0);
 	gRedundancyLevel = cfg_getuint32("REDUNDANCY_LEVEL", 0);
+	gUseLinearAssignmentOptimizer = cfg_getuint32("USE_LINEAR_ASSIGNMENT_OPTIMIZER", 1);
 
 	if (disableChunksDel) {
 		MaxDelHardLimit = MaxDelSoftLimit = 0;
diff --git a/src/master/exports.cc b/src/master/exports.cc
index 39972fc52..55e9aa5be 100644
--- a/src/master/exports.cc
+++ b/src/master/exports.cc
@@ -42,7 +42,7 @@
 #include "common/massert.h"
 #include "common/md5.h"
 #include "protocol/SFSCommunication.h"
-#include "common/slogger.h"
+#include "slogger/slogger.h"
 
 typedef struct _exports {
 	uint32_t pleng;
diff --git a/src/master/locks.cc b/src/master/locks.cc
index e543b133c..f3db6c984 100644
--- a/src/master/locks.cc
+++ b/src/master/locks.cc
@@ -22,7 +22,7 @@
 #include "master/locks.h"
 
 #include "common/memory_mapped_file.h"
-#include "common/slogger.h"
+#include "slogger/slogger.h"
 
 #include <functional>
 
diff --git a/src/master/masterconn.cc b/src/master/masterconn.cc
index 8d681dbdf..a520fdda4 100644
--- a/src/master/masterconn.cc
+++ b/src/master/masterconn.cc
@@ -46,7 +46,7 @@
 #include "common/massert.h"
 #include "common/metadata.h"
 #include "common/rotate_files.h"
-#include "common/slogger.h"
+#include "slogger/slogger.h"
 #include "common/sockets.h"
 #include "common/time_utils.h"
 #include "master/changelog.h"
diff --git a/src/master/matoclserv.cc b/src/master/matoclserv.cc
index 79c9d8ce3..c514ee35b 100644
--- a/src/master/matoclserv.cc
+++ b/src/master/matoclserv.cc
@@ -62,7 +62,7 @@
 #include "common/network_address.h"
 #include "common/random.h"
 #include "common/serialized_goal.h"
-#include "common/slogger.h"
+#include "slogger/slogger.h"
 #include "common/sockets.h"
 #include "common/user_groups.h"
 #include "master/changelog.h"
diff --git a/src/master/matocsserv.cc b/src/master/matocsserv.cc
index bdf272446..f68d14e56 100644
--- a/src/master/matocsserv.cc
+++ b/src/master/matocsserv.cc
@@ -46,11 +46,11 @@
 #include "common/saunafs_version.h"
 #include "common/loop_watchdog.h"
 #include "common/massert.h"
-#include "common/sfserr.h"
+#include "errors/sfserr.h"
 #include "common/output_packet.h"
 #include "common/random.h"
 #include "common/slice_traits.h"
-#include "common/slogger.h"
+#include "slogger/slogger.h"
 #include "common/sockets.h"
 #include "common/time_utils.h"
 #include "master/chunks.h"
diff --git a/src/master/matomlserv.cc b/src/master/matomlserv.cc
index c8e2080fd..41123690d 100644
--- a/src/master/matomlserv.cc
+++ b/src/master/matomlserv.cc
@@ -44,7 +44,7 @@
 #include "common/loop_watchdog.h"
 #include "common/massert.h"
 #include "common/metadata.h"
-#include "common/slogger.h"
+#include "slogger/slogger.h"
 #include "common/sockets.h"
 #include "master/filesystem.h"
 #include "master/personality.h"
diff --git a/src/master/metadata_dumper.h b/src/master/metadata_dumper.h
index 57a61a2fd..ed4283d0a 100644
--- a/src/master/metadata_dumper.h
+++ b/src/master/metadata_dumper.h
@@ -29,7 +29,7 @@
 #include <string>
 #include <vector>
 
-#include "common/slogger.h"
+#include "slogger/slogger.h"
 #include "common/time_utils.h"
 
 class MetadataDumper {
diff --git a/src/master/personality.cc b/src/master/personality.cc
index e38d25703..11a68ccd7 100644
--- a/src/master/personality.cc
+++ b/src/master/personality.cc
@@ -28,7 +28,7 @@
 #include "common/event_loop.h"
 #include "common/main.h"
 #include "common/massert.h"
-#include "common/slogger.h"
+#include "slogger/slogger.h"
 
 namespace metadataserver {
 
diff --git a/src/master/restore.cc b/src/master/restore.cc
index b611b0f01..90b51dd0b 100644
--- a/src/master/restore.cc
+++ b/src/master/restore.cc
@@ -27,8 +27,8 @@
 #include <string.h>
 
 #include "protocol/SFSCommunication.h"
-#include "common/saunafs_error_codes.h"
-#include "common/slogger.h"
+#include "errors/saunafs_error_codes.h"
+#include "slogger/slogger.h"
 #include "master/filesystem.h"
 #include "master/filesystem_snapshot.h"
 #include "master/filesystem_operations.h"
diff --git a/src/master/topology.cc b/src/master/topology.cc
index fe6ddf68c..57c96d4ba 100644
--- a/src/master/topology.cc
+++ b/src/master/topology.cc
@@ -33,7 +33,8 @@
 #include "common/cfg.h"
 #include "common/event_loop.h"
 #include "common/massert.h"
-#include "common/slogger.h"
+#include "errors/sfserr.h"
+#include "slogger/slogger.h"
 #include "master/itree.h"
 
 static void *racktree;
diff --git a/src/metarestore/main.cc b/src/metarestore/main.cc
index ac7a64388..10cfef78f 100644
--- a/src/metarestore/main.cc
+++ b/src/metarestore/main.cc
@@ -36,10 +36,10 @@
 
 #include "common/cfg.h"
 #include "common/metadata.h"
-#include "common/sfserr.h"
+#include "errors/sfserr.h"
 #include "common/rotate_files.h"
 #include "common/setup.h"
-#include "common/slogger.h"
+#include "slogger/slogger.h"
 #include "master/chunks.h"
 #include "master/filesystem.h"
 #include "master/hstring_memstorage.h"
diff --git a/src/metarestore/merger.cc b/src/metarestore/merger.cc
index d3449e54b..604fce43c 100644
--- a/src/metarestore/merger.cc
+++ b/src/metarestore/merger.cc
@@ -28,8 +28,8 @@
 #include <syslog.h>
 
 #include "protocol/SFSCommunication.h"
-#include "common/saunafs_error_codes.h"
-#include "common/slogger.h"
+#include "errors/saunafs_error_codes.h"
+#include "slogger/slogger.h"
 #include "master/restore.h"
 
 #define BSIZE 200000
diff --git a/src/mount/chunk_locator.cc b/src/mount/chunk_locator.cc
index b78d8058f..2ae327e19 100644
--- a/src/mount/chunk_locator.cc
+++ b/src/mount/chunk_locator.cc
@@ -24,7 +24,7 @@
 
 #include "protocol/SFSCommunication.h"
 #include "common/exceptions.h"
-#include "common/sfserr.h"
+#include "errors/sfserr.h"
 #include "devtools/request_log.h"
 #include "mount/mastercomm.h"
 
diff --git a/src/mount/chunk_locator.h b/src/mount/chunk_locator.h
index 4e3820ab9..545ff09db 100644
--- a/src/mount/chunk_locator.h
+++ b/src/mount/chunk_locator.h
@@ -27,7 +27,7 @@
 #include <vector>
 
 #include "common/chunk_type_with_address.h"
-#include "common/slogger.h"
+#include "slogger/slogger.h"
 
 struct ChunkLocationInfo {
 	typedef std::vector<ChunkTypeWithAddress> ChunkLocations;
@@ -97,6 +97,10 @@ class WriteChunkLocator {
 	virtual void locateAndLockChunk(uint32_t inode, uint32_t index);
 	virtual void unlockChunk();
 
+	inline uint64_t fileLength() const {
+		return locationInfo_.fileLength;
+	}
+
 	uint32_t chunkIndex() {
 		return index_;
 	}
diff --git a/src/mount/chunk_writer.cc b/src/mount/chunk_writer.cc
index dbdfb9d95..8b959539e 100644
--- a/src/mount/chunk_writer.cc
+++ b/src/mount/chunk_writer.cc
@@ -33,7 +33,7 @@
 #include "common/massert.h"
 #include "common/read_operation_executor.h"
 #include "common/read_plan_executor.h"
-#include "common/slogger.h"
+#include "slogger/slogger.h"
 #include "common/sockets.h"
 #include "common/time_utils.h"
 #include "devtools/request_log.h"
@@ -436,6 +436,48 @@ void ChunkWriter::fillOperation(Operation &operation, int first_block, int first
 	}
 }
 
+/*!
+ * Fills given operation with a range of blocks beyond file size required to be read.
+ * \param operation operation to be filled
+ * \param first_block first block of the operation
+ * \param first_index index of the first block required to be read
+ * \param size number of required blocks
+ * \param stripe_element map of blocks in a stripe
+ */
+void ChunkWriter::fillNotExisting(Operation &operation, int first_block,
+                                  int first_index, int blocks_number,
+                                  std::vector<uint8_t *> &stripe_element) {
+	assert(blocks_number >= 0);
+	if (blocks_number == 0) {
+		return;
+	}
+	int block_from = operation.journalPositions.front()->from;
+	int block_to = operation.journalPositions.front()->to;
+	int beyond_start = first_block + first_index;
+
+	std::vector<WriteCacheBlock> blocks;
+	blocks.reserve(blocks_number);
+	// Fills not existing blocks with zeroes
+	for (int index = beyond_start; index < beyond_start + blocks_number;
+	     ++index) {
+		WriteCacheBlock block(locator_->chunkIndex(), index,
+		                      WriteCacheBlock::kReadBlock);
+		memset(block.data(), 0, SFSBLOCKSIZE);
+		block.from = block_from;
+		block.to = block_to;
+		blocks.push_back(std::move(block));
+	}
+	assert(blocks.size() == (size_t)blocks_number);
+
+	for (int index = 0; index < blocks_number; ++index) {
+		// Insert the new block into the journal just after the last block of the operation
+		auto position = journal_.insert(operation.journalPositions.back(), std::move(blocks[index]));
+		operation.journalPositions.push_back(position);
+
+		stripe_element[first_index + index] = position->data();
+	}
+}
+
 /*!
  * Fills given operation with blocks required to be read.
  * \param operation operation to be filled
@@ -455,6 +497,17 @@ void ChunkWriter::fillStripe(Operation &operation, int first_block, std::vector<
 	int hole_size = 0;
 	int range_end = std::min(combinedStripeSize_, SFSBLOCKSINCHUNK - first_block);
 	for (int i = 0; i < range_end; ++i) {
+		if (first_block + i >= chunkSizeInBlocks_) {
+			if (hole_size > 0) {
+				fillOperation(operation, first_block, hole_start, hole_size,
+				              stripe_element);
+				hole_size = 0;
+			}
+			if (stripe_element[i] == nullptr) {
+				fillNotExisting(operation, first_block, i, 1, stripe_element);
+			}
+			continue;
+		}
 		if (stripe_element[i] == nullptr) {
 			if (hole_size == 0) {
 				hole_start = i;
diff --git a/src/mount/chunk_writer.h b/src/mount/chunk_writer.h
index a527c82d3..6ee26d6e1 100644
--- a/src/mount/chunk_writer.h
+++ b/src/mount/chunk_writer.h
@@ -127,6 +127,11 @@ class ChunkWriter {
 
 	bool acceptsNewOperations() { return acceptsNewOperations_; }
 
+	inline void setChunkSizeInBlocks(uint32_t chunkSize) {
+		chunkSizeInBlocks_ =
+		    (chunkSize == 0 ? 0 : (chunkSize - 1) / SFSBLOCKSIZE + 1);
+	}
+
 private:
 	typedef uint32_t WriteId;
 	typedef uint32_t OperationId;
@@ -175,6 +180,7 @@ class ChunkWriter {
 	bool acceptsNewOperations_;
 	int combinedStripeSize_;
 	int dataChainFd_;
+	int chunkSizeInBlocks_;
 
 	std::map<int, std::unique_ptr<WriteExecutor>> executors_;
 	std::list<WriteCacheBlock> journal_;
@@ -186,6 +192,9 @@ class ChunkWriter {
 	void startOperation(Operation operation);
 	void fillOperation(Operation &operation, int first_block, int first_index, int size,
 			std::vector<uint8_t *> &stripe_element);
+	void fillNotExisting(Operation &operation, int first_block, int first_index,
+	                     int blocks_number,
+	                     std::vector<uint8_t *> &stripe_element);
 	void fillStripe(Operation &operation, int first_block, std::vector<uint8_t *> &stripe_element);
 	void readBlocks(int block_index, int size, int block_from, int block_to,
 			std::vector<WriteCacheBlock> &blocks);
diff --git a/src/mount/client/CMakeLists.txt b/src/mount/client/CMakeLists.txt
index 277e3c57f..f7ccca165 100644
--- a/src/mount/client/CMakeLists.txt
+++ b/src/mount/client/CMakeLists.txt
@@ -23,4 +23,4 @@ install(TARGETS saunafsmount_shared DESTINATION ${LIB_SUBDIR})
 install(TARGETS saunafs-client_shared DESTINATION ${LIB_SUBDIR})
 
 install(FILES saunafs_c_api.h DESTINATION ${INCL_SUBDIR})
-install(FILES ../../common/saunafs_error_codes.h DESTINATION ${INCL_SUBDIR})
+install(FILES ../../errors/saunafs_error_codes.h DESTINATION ${INCL_SUBDIR})
diff --git a/src/mount/client/client_error_code.cc b/src/mount/client/client_error_code.cc
index 408f77d3d..95a73a2ee 100644
--- a/src/mount/client/client_error_code.cc
+++ b/src/mount/client/client_error_code.cc
@@ -20,7 +20,8 @@
 
 #include <string>
 #include "common/platform.h"
-#include "common/sfserr.h"
+#include "errors/saunafs_error_codes.h"
+#include "errors/sfserr.h"
 
 #include "client_error_code.h"
 
diff --git a/src/mount/client/saunafs_c_api.cc b/src/mount/client/saunafs_c_api.cc
index 413e0b5f8..dc95dd70e 100644
--- a/src/mount/client/saunafs_c_api.cc
+++ b/src/mount/client/saunafs_c_api.cc
@@ -28,7 +28,7 @@
 #pragma GCC diagnostic pop
 
 #include "saunafs_c_api.h"
-#include "common/saunafs_error_codes.h"
+#include "errors/saunafs_error_codes.h"
 #include "common/md5.h"
 #include "common/small_vector.h"
 #include "mount/client/iovec_traits.h"
diff --git a/src/mount/direntry_cache.h b/src/mount/direntry_cache.h
index e8b970d1b..89cf72112 100644
--- a/src/mount/direntry_cache.h
+++ b/src/mount/direntry_cache.h
@@ -493,9 +493,7 @@ class DirEntryCache {
 		// the parent, like in unlink
 		auto iter = inode_multiset_.lower_bound(parent_inode, InodeCompare());
 
-		while (iter != inode_multiset_.end() &&
-		       (iter->inode == parent_inode ||
-		        iter->parent_inode == kInvalidParent)) {
+		while (iter != inode_multiset_.end() && iter->inode == parent_inode) {
 			DirEntry *entry = std::addressof(*iter);
 			++iter;
 			erase(entry);
@@ -514,12 +512,14 @@ class DirEntryCache {
 		// lookup_set_ should contain all the elements inside index_set
 		auto it = lookup_set_.lower_bound(
 		    std::make_tuple(parent_inode, ctx.uid, ctx.gid, ""), LookupCompare());
-		while (it != lookup_set_.end() &&
-		       std::make_tuple(parent_inode, ctx.uid, ctx.gid) ==
-		               std::make_tuple(it->parent_inode, it->uid, it->gid)) {
-			DirEntry *entry = std::addressof(*it);
+		while (it != lookup_set_.end() && parent_inode == it->parent_inode) {
+			if (ctx.uid == it->uid && ctx.gid == it->gid) {
+				DirEntry *entry = std::addressof(*it);
+				++it;
+				erase(entry);
+				continue;
+			}
 			++it;
-			erase(entry);
 		}
 
 		// Make sure inode_multiset_ is also clean of outdated entries.
@@ -527,12 +527,14 @@ class DirEntryCache {
 		// the parent, like in unlink
 		auto iter = inode_multiset_.lower_bound(parent_inode, InodeCompare());
 
-		while (iter != inode_multiset_.end() &&
-		       (iter->inode == parent_inode ||
-		        iter->parent_inode == kInvalidParent)) {
-			DirEntry *entry = std::addressof(*iter);
+		while (iter != inode_multiset_.end() && iter->inode == parent_inode) {
+			if (ctx.uid == iter->uid && ctx.gid == iter->gid) {
+				DirEntry *entry = std::addressof(*iter);
+				++iter;
+				erase(entry);
+				continue;
+			}
 			++iter;
-			erase(entry);
 		}
 	}
 
diff --git a/src/mount/fuse/main.cc b/src/mount/fuse/main.cc
index c1a62eb72..582914cff 100644
--- a/src/mount/fuse/main.cc
+++ b/src/mount/fuse/main.cc
@@ -29,7 +29,7 @@
 
 #include "common/crc.h"
 #include "common/md5.h"
-#include "common/sfserr.h"
+#include "errors/sfserr.h"
 #include "common/sockets.h"
 #include "mount/fuse/daemonize.h"
 #include "mount/fuse/sfs_fuse.h"
@@ -250,6 +250,7 @@ static int mainloop(struct fuse_args *args, struct fuse_cmdline_opts *fuse_opts,
 	params.acl_cache_size = gMountOptions.aclcachesize;
 	params.debug_mode = gMountOptions.debug;
 	params.direct_io = gMountOptions.directio;
+	params.ignore_flush = gMountOptions.ignoreflush;
 
 	if (!gMountOptions.meta) {
 		SaunaClient::fs_init(params);
diff --git a/src/mount/fuse/mount_config.cc b/src/mount/fuse/mount_config.cc
index ba26dd386..a4c7cc484 100644
--- a/src/mount/fuse/mount_config.cc
+++ b/src/mount/fuse/mount_config.cc
@@ -91,6 +91,7 @@ struct fuse_opt gSfsOptsStage2[] = {
 	SFS_OPT("bandwidthoveruse=%lf", bandwidthoveruse, 1),
 	SFS_OPT("sfsdirentrycachesize=%u", direntrycachesize, 0),
 	SFS_OPT("nostdmountoptions", nostdmountoptions, 1),
+	SFS_OPT("sfsignoreflush", ignoreflush, 1),
 
 	SFS_OPT("enablefilelocks=%u", filelocks, 0),
 	SFS_OPT("nonempty", nonemptymount, 1),
@@ -221,6 +222,9 @@ void usage(const char *progname) {
 "    -o enablefilelocks=0|1      enables/disables global file locking "
 				"(disabled by default)\n"
 "    -o nonempty                 allow mounts over non-empty file/dir\n"
+"    -o sfsignoreflush           Advanced: use with caution. Ignore flush usual "
+				"behavior by replying SUCCESS to it immediately. Targets fast "
+				"creation of small files, but may cause data loss during crashes.\n"
 "\n",
 		SaunaClient::FsInitParams::kDefaultUseRwLock,
 		SaunaClient::FsInitParams::kDefaultMkdirCopySgid,
diff --git a/src/mount/fuse/mount_config.h b/src/mount/fuse/mount_config.h
index 9db4025d2..30bb617bb 100644
--- a/src/mount/fuse/mount_config.h
+++ b/src/mount/fuse/mount_config.h
@@ -113,6 +113,7 @@ struct sfsopts_ {
 	double bandwidthoveruse;
 	int nonemptymount;
 	bool directio;
+	int ignoreflush;
 
 	sfsopts_()
 		: masterhost(NULL),
@@ -167,7 +168,8 @@ struct sfsopts_ {
 		symlinkcachetimeout(SaunaClient::FsInitParams::kDefaultSymlinkCacheTimeout),
 		bandwidthoveruse(SaunaClient::FsInitParams::kDefaultBandwidthOveruse),
 		nonemptymount(SaunaClient::FsInitParams::kDefaultNonEmptyMounts),
-		directio(SaunaClient::FsInitParams::kDirectIO)
+		directio(SaunaClient::FsInitParams::kDirectIO),
+		ignoreflush(SaunaClient::FsInitParams::kDefaultIgnoreFlush)
 	{ }
 };
 
diff --git a/src/mount/fuse/sfs_meta_fuse.cc b/src/mount/fuse/sfs_meta_fuse.cc
index 401345cfd..f46ba1b68 100644
--- a/src/mount/fuse/sfs_meta_fuse.cc
+++ b/src/mount/fuse/sfs_meta_fuse.cc
@@ -31,7 +31,7 @@
 
 #include "common/datapack.h"
 #include "protocol/SFSCommunication.h"
-#include "common/slogger.h"
+#include "slogger/slogger.h"
 #include "common/special_inode_defs.h"
 #include "mount/exports.h"
 #include "mount/mastercomm.h"
diff --git a/src/mount/global_io_limiter.cc b/src/mount/global_io_limiter.cc
index 1ac50aabc..33b8e8d3d 100644
--- a/src/mount/global_io_limiter.cc
+++ b/src/mount/global_io_limiter.cc
@@ -27,7 +27,7 @@
 #include "protocol/cltoma.h"
 #include "protocol/matocl.h"
 #include "common/token_bucket.h"
-#include "common/slogger.h"
+#include "slogger/slogger.h"
 #include "mount/io_limit_group.h"
 
 using namespace ioLimiting;
diff --git a/src/mount/mastercomm.cc b/src/mount/mastercomm.cc
index 381c61cd1..b9f9e96dd 100644
--- a/src/mount/mastercomm.cc
+++ b/src/mount/mastercomm.cc
@@ -47,9 +47,9 @@
 #include "common/goal.h"
 #include "common/saunafs_version.h"
 #include "common/md5.h"
-#include "common/sfserr.h"
+#include "errors/sfserr.h"
 #include "common/sockets.h"
-#include "common/slogger.h"
+#include "slogger/slogger.h"
 #include "mount/exports.h"
 #include "mount/stats.h"
 #include "protocol/cltoma.h"
@@ -80,10 +80,9 @@ typedef struct _acquired_file {
 	struct _acquired_file *next;
 } acquired_file;
 
-
 #define DEFAULT_OUTPUT_BUFFSIZE 0x1000
 #define DEFAULT_INPUT_BUFFSIZE 0x10000
-
+#define NO_DATA_RECEIVED_FROM_MASTER NULL
 #define RECEIVE_TIMEOUT 10
 
 static threc *threchead=NULL;
@@ -205,6 +204,9 @@ void master_stats_add(uint8_t id,uint64_t s) {
 static inline void setDisconnect(bool value) {
 	std::unique_lock<std::mutex> fdLock(fdMutex);
 	disconnect = value;
+#ifdef _WIN32
+	gIsDisconnectedFromMaster.store(value);
+#endif
 	if(value) {
 		SaunaClient::masterDisconnectedCallback();
 		safs_pretty_syslog(LOG_WARNING,"master: disconnected");
@@ -363,6 +365,11 @@ static bool fs_threc_send_receive(threc *rec, bool filter, PacketHeader::Type ex
 					}
 				}
 			}
+#ifdef _WIN32
+			else if (gIsDisconnectedFromMaster.load()) {
+				return false;
+			}
+#endif
 			sleep(sleep_time(cnt));
 			continue;
 		}
@@ -374,6 +381,7 @@ static bool fs_threc_send_receive(threc *rec, bool filter, PacketHeader::Type ex
 const uint8_t* fs_sendandreceive(threc *rec, uint32_t expected_cmd, uint32_t *answer_leng) {
 	// this function is only for compatibility with Legacy code
 	sassert(expected_cmd <= PacketHeader::kMaxOldPacketType);
+
 	if (fs_threc_send_receive(rec, true, expected_cmd)) {
 		const uint8_t *answer;
 		answer = rec->inputBuffer.data();
@@ -385,7 +393,7 @@ const uint8_t* fs_sendandreceive(threc *rec, uint32_t expected_cmd, uint32_t *an
 
 		return answer;
 	}
-	return NULL;
+	return NO_DATA_RECEIVED_FROM_MASTER;
 }
 
 bool fs_sausendandreceive(threc *rec, uint32_t expectedCommand, MessageBuffer& messageData) {
@@ -1024,6 +1032,8 @@ static void usr1_handler(int) {
 #endif
 
 void* fs_nop_thread(void *arg) {
+	pthread_setname_np(pthread_self(), "nopThread");
+
 	uint8_t *ptr,hdr[12],*inodespacket;
 	int32_t inodesleng;
 	acquired_file *afptr;
@@ -1140,6 +1150,9 @@ bool fs_deserialize_from_master(uint32_t& remainingBytes, Args&... destination)
 void* fs_receive_thread(void *) {
 	uint32_t initialReconnectSleep_ms = 100;
 	uint32_t reconnectSleep_ms = initialReconnectSleep_ms;
+
+	pthread_setname_np(pthread_self(), "recFromMaster");
+
 	for (;;) {
 		std::unique_lock<std::mutex>fdLock(fdMutex);
 		if (fterm) {
@@ -1193,6 +1206,9 @@ void* fs_receive_thread(void *) {
 			continue;
 		} else {
 			// connecection succeeded -- reset timeout the initial value
+#ifdef _WIN32
+			gIsDisconnectedFromMaster.store(false);
+#endif
 			reconnectSleep_ms = initialReconnectSleep_ms;
 		}
 		fdLock.unlock();
diff --git a/src/mount/mastercomm.h b/src/mount/mastercomm.h
index 6f53a3797..10669a1b6 100644
--- a/src/mount/mastercomm.h
+++ b/src/mount/mastercomm.h
@@ -36,6 +36,10 @@
 #include "protocol/directory_entry.h"
 #include "protocol/named_inode_entry.h"
 
+#ifdef _WIN32
+inline std::atomic_bool gIsDisconnectedFromMaster;
+#endif
+
 void fs_getmasterlocation(uint8_t loc[14]);
 uint32_t fs_getsrcip(void);
 
diff --git a/src/mount/masterproxy.cc b/src/mount/masterproxy.cc
index de58925c7..79204053b 100644
--- a/src/mount/masterproxy.cc
+++ b/src/mount/masterproxy.cc
@@ -49,6 +49,8 @@ void masterproxy_getlocation(uint8_t *masterinfo) {
 }
 
 static void* masterproxy_server(void *args) {
+	pthread_setname_np(pthread_self(), "masterProxyServ");
+
 	std::vector<uint8_t> buffer;
 	int sock = *((int*)args);
 	free(args);
@@ -107,6 +109,8 @@ static void* masterproxy_server(void *args) {
 }
 
 static void* masterproxy_acceptor(void *args) {
+	pthread_setname_np(pthread_self(), "masterProxyAcc");
+
 	pthread_t clientthread;
 	pthread_attr_t thattr;
 	int sock;
diff --git a/src/mount/polonaise/main.cc b/src/mount/polonaise/main.cc
index fce41fc93..eb3d01f51 100644
--- a/src/mount/polonaise/main.cc
+++ b/src/mount/polonaise/main.cc
@@ -35,7 +35,7 @@
 
 #include "common/crc.h"
 #include "common/errno_defs.h"
-#include "common/slogger.h"
+#include "slogger/slogger.h"
 #include "common/sockets.h"
 #include "mount/g_io_limiters.h"
 #include "mount/sauna_client.h"
diff --git a/src/mount/readdata.cc b/src/mount/readdata.cc
index 3ea4e88d0..41ae8ded7 100644
--- a/src/mount/readdata.cc
+++ b/src/mount/readdata.cc
@@ -37,9 +37,9 @@
 #include "common/connection_pool.h"
 #include "common/datapack.h"
 #include "common/exceptions.h"
-#include "common/sfserr.h"
+#include "errors/sfserr.h"
 #include "common/read_plan_executor.h"
-#include "common/slogger.h"
+#include "slogger/slogger.h"
 #include "common/sockets.h"
 #include "common/time_utils.h"
 #include "mount/chunk_locator.h"
@@ -326,6 +326,9 @@ inline void clear_active_read_records()
 
 void* read_data_delayed_ops(void *arg) {
 	(void)arg;
+
+	pthread_setname_np(pthread_self(), "readDelayedOps");
+
 	for (;;) {
 		gReadConnectionPool.cleanup();
 		std::unique_lock lock(gMutex);
@@ -354,6 +357,11 @@ void* read_data_delayed_ops(void *arg) {
 
 void* read_worker(void *arg) {
 	(void)arg;
+
+	static std::atomic_uint16_t readWorkersCounter(0);
+	std::string threadName = "readWorker " + std::to_string(readWorkersCounter++);
+	pthread_setname_np(pthread_self(), threadName.c_str());
+
 	for (;;) {
 		std::unique_lock operationsLock(gReadaheadOperationsManagerMutex);
 		if (gReadaheadOperationsManager.empty()) {
diff --git a/src/mount/sauna_client.cc b/src/mount/sauna_client.cc
index 16512675a..077d3d4e9 100644
--- a/src/mount/sauna_client.cc
+++ b/src/mount/sauna_client.cc
@@ -47,9 +47,9 @@
 #include "common/datapack.h"
 #include "common/errno_defs.h"
 #include "common/lru_cache.h"
-#include "common/sfserr.h"
+#include "errors/sfserr.h"
 #include "common/richacl_converter.h"
-#include "common/slogger.h"
+#include "slogger/slogger.h"
 #include "common/sockets.h"
 #include "common/special_inode_defs.h"
 #include "common/time_utils.h"
@@ -211,6 +211,7 @@ static unsigned gDirEntryCacheMaxSize = 100000;
 
 static int debug_mode = 0;
 static int usedircache = 1;
+static bool ignore_flush = false;
 static int keep_cache = 0;
 static double direntry_cache_timeout = 0.1;
 static double entry_cache_timeout = 0.0;
@@ -229,6 +230,8 @@ uint8_t session_flags;
 
 uint8_t get_session_flags() { return session_flags; }
 
+bool isMasterDisconnected() { return gIsDisconnectedFromMaster.load(); }
+
 #define LOCAL_USERS_THRESHOLD 0x30000
 static int32_t mounting_uid = USE_LOCAL_ID;
 static int32_t mounting_gid = USE_LOCAL_ID;
@@ -837,8 +840,10 @@ EntryParam lookup(Context &ctx, Inode parent, const char *name) {
 		e.attr.st_size=maxfleng;
 	}
 
-	// If lookup succeeded and data did not come from cache, then cache it
-	if (!icacheflag) {
+	// If lookup succeeded and data did not come from cache, then cache it.
+	// Files with at least one hardlink are impossible to keep track of, so it is
+	// better to don't track them.
+	if (!icacheflag && !(e.attr.st_nlink > 1 && attr[0] == TYPE_FILE)) {
 		auto data_acquire_time = gDirEntryCache.updateTime();
 
 		std::unique_lock<shared_mutex> write_guard(gDirEntryCache.rwlock());
@@ -918,8 +923,10 @@ AttrReply getattr(Context &ctx, Inode ino) {
 	patch_uid_gid_fields(o_stbuf);
 #endif
 
-	// If getattr succeeded and data did not come from cache, then cache it
-	if (!fromCache) {
+	// If lookup succeeded and data did not come from cache, then cache it.
+	// Files with at least one hardlink are impossible to keep track of, so it is
+	// better to don't track them.
+	if (!fromCache && !(o_stbuf.st_nlink > 1 && attr[0] == TYPE_FILE)) {
 		auto data_acquire_time = gDirEntryCache.updateTime();
 
 		std::unique_lock<shared_mutex> write_guard(gDirEntryCache.rwlock());
@@ -2359,7 +2366,18 @@ BytesWritten write(Context &ctx, Inode ino, const char *buf, size_t size, off_t
 		fileinfo->mode = IO_WRITE;
 		fileinfo->data = write_data_new(ino);
 	}
-	err = write_data(fileinfo->data,off,size,(const uint8_t*)buf);
+
+	Attributes attr;
+	attr.fill(0);
+	if (usedircache) {
+		gDirEntryCache.lookup(ctx, ino, attr);
+	}
+	struct stat stbuf;
+	attr_to_stat(ino, attr, &stbuf);
+	size_t currentSize = stbuf.st_size;
+
+	err = write_data(fileinfo->data, off, size, (const uint8_t *)buf,
+	                 currentSize);
 	gDirEntryCache.lockAndInvalidateInode(ino);
 	if (err != SAUNAFS_STATUS_OK) {
 		oplog_printf(ctx, "write (%lu,%" PRIu64 ",%" PRIu64 "): (physical) %s",
@@ -2379,6 +2397,12 @@ BytesWritten write(Context &ctx, Inode ino, const char *buf, size_t size, off_t
 }
 
 void flush(Context &ctx, Inode ino, FileInfo* fi) {
+	if (ignore_flush) {
+		oplog_printf(ctx, "flush (%lu): OK",
+				(unsigned long int)ino);
+		return;
+	}
+
 	finfo *fileinfo = reinterpret_cast<finfo*>(fi->fh);
 	int err;
 
@@ -3369,11 +3393,13 @@ void init(int debug_mode_, int keep_cache_, double direntry_cache_timeout_, unsi
 #ifdef _WIN32
 		, int mounting_uid_, int mounting_gid_
 #endif
+		, bool ignore_flush_
 		) {
 #ifdef _WIN32
 	mounting_uid = mounting_uid_;
 	mounting_gid = mounting_gid_;
 #endif
+	ignore_flush = ignore_flush_;
 	debug_mode = debug_mode_;
 	keep_cache = keep_cache_;
 	direntry_cache_timeout = direntry_cache_timeout_;
@@ -3469,6 +3495,7 @@ void fs_init(FsInitParams &params) {
 #ifdef _WIN32
 		, params.mounting_uid, params.mounting_gid
 #endif
+		, params.ignore_flush
 		);
 }
 
diff --git a/src/mount/sauna_client.h b/src/mount/sauna_client.h
index b65672a20..068b9aafd 100644
--- a/src/mount/sauna_client.h
+++ b/src/mount/sauna_client.h
@@ -78,6 +78,7 @@ struct FsInitParams {
 
 	static constexpr float    kDefaultBandwidthOveruse = 1.0;
 	static constexpr unsigned kDefaultChunkserverWriteTo = 5000;
+	static constexpr bool     kDefaultIgnoreFlush = false;
 #ifdef _WIN32
 	static constexpr unsigned kDefaultWriteCacheSize = 50;
 #else
@@ -143,9 +144,9 @@ struct FsInitParams {
 	             use_rw_lock(kDefaultUseRwLock),
 	             acl_cache_timeout(kDefaultAclCacheTimeout), acl_cache_size(kDefaultAclCacheSize),
 #ifdef _WIN32
-				 mounting_uid(USE_LOCAL_ID), mounting_gid(USE_LOCAL_ID), 
+	             mounting_uid(USE_LOCAL_ID), mounting_gid(USE_LOCAL_ID),
 #endif
-	             verbose(kDefaultVerbose), direct_io(kDirectIO) {
+	             ignore_flush(kDefaultIgnoreFlush), verbose(kDefaultVerbose), direct_io(kDirectIO) {
 	}
 
 	FsInitParams(const std::string &bind_host, const std::string &host, const std::string &port, const std::string &mountpoint)
@@ -175,9 +176,9 @@ struct FsInitParams {
 	             use_rw_lock(kDefaultUseRwLock),
 	             acl_cache_timeout(kDefaultAclCacheTimeout), acl_cache_size(kDefaultAclCacheSize),
 #ifdef _WIN32
-				 mounting_uid(USE_LOCAL_ID), mounting_gid(USE_LOCAL_ID), 
+	             mounting_uid(USE_LOCAL_ID), mounting_gid(USE_LOCAL_ID),
 #endif
-	             verbose(kDefaultVerbose), direct_io(kDirectIO) {
+	             ignore_flush(kDefaultIgnoreFlush), verbose(kDefaultVerbose), direct_io(kDirectIO) {
 	}
 
 	std::string bind_host;
@@ -227,6 +228,7 @@ struct FsInitParams {
 	int mounting_gid;
 #endif
 
+	bool ignore_flush;
 	bool verbose;
 	bool direct_io;
 
@@ -332,6 +334,8 @@ struct RequestException : public std::exception {
 
 uint8_t get_session_flags();
 
+bool isMasterDisconnected();
+
 void update_last_winfsp_context(const unsigned int uid, const unsigned int gid);
 
 void convert_winfsp_context_to_master_context(unsigned int& uid, unsigned int& gid);
diff --git a/src/mount/writedata.cc b/src/mount/writedata.cc
index ebfd65d5d..8d7df89c3 100644
--- a/src/mount/writedata.cc
+++ b/src/mount/writedata.cc
@@ -43,11 +43,11 @@
 #include "common/goal.h"
 #include "common/massert.h"
 #include "common/message_receive_buffer.h"
-#include "common/sfserr.h"
+#include "errors/sfserr.h"
 #include "common/multi_buffer_writer.h"
 #include "common/pcqueue.h"
 #include "common/slice_traits.h"
-#include "common/slogger.h"
+#include "slogger/slogger.h"
 #include "common/sockets.h"
 #include "common/time_utils.h"
 #include "devtools/request_log.h"
@@ -311,6 +311,8 @@ static bool delayed_queue_remove(inodedata* id, Glock&) {
 }
 
 void* delayed_queue_worker(void*) {
+	pthread_setname_np(pthread_self(), "delQueueWriter");
+
 	for (;;) {
 		Timeout timeout(std::chrono::microseconds(1000000 / DelayedQueueEntry::kTicksPerSecond));
 		Glock lock(gMutex);
@@ -441,6 +443,10 @@ void InodeChunkWriter::processJob(inodedata* inodeData) {
 	try {
 		try {
 			locator->locateAndLockChunk(inodeData_->inode, chunkIndex_);
+			Glock lock(gMutex);
+			inodeData_->maxfleng =
+			    std::max(inodeData_->maxfleng, locator->fileLength());
+			lock.unlock();
 
 			// Optimization -- talk with chunkservers only if we have to write any data.
 			// Don't do this if we just have to release some previously unlocked lock.
@@ -449,13 +455,14 @@ void InodeChunkWriter::processJob(inodedata* inodeData) {
 				processDataChain(writer);
 				writer.finish(kTimeToFinishOperations * 1000);
 
-				Glock lock(gMutex);
+				lock.lock();
 				returnJournalToDataChain(writer.releaseJournal(), lock);
+				lock.unlock();
 			}
 			locator->unlockChunk();
 			read_inode_ops(inodeData_->inode);
 
-			Glock lock(gMutex);
+			lock.lock();
 			inodeData_->minimumBlocksToWrite = writer.getMinimumBlockCountWorthWriting();
 			bool canWait = !inodeData_->requiresFlushing();
 			if (!haveAnyBlockInCurrentChunk(lock)) {
@@ -573,6 +580,9 @@ void InodeChunkWriter::processDataChain(ChunkWriter& writer) {
 			can_expect_next_block = haveAnyBlockInCurrentChunk(lock);
 		}
 
+		writer.setChunkSizeInBlocks(
+		    std::min(inodeData_->maxfleng - chunkIndex_ * SFSCHUNKSIZE,
+		             (uint64_t)SFSCHUNKSIZE));
 		if (writer.startNewOperations(can_expect_next_block) > 0) {
 			Glock lock(gMutex);
 			inodeData_->lastWriteToChunkservers.reset();
@@ -647,6 +657,11 @@ bool InodeChunkWriter::haveBlockWorthWriting(uint32_t unfinishedOperationCount,
 /* main working thread | glock:UNLOCKED */
 void* write_worker(void*) {
 	InodeChunkWriter inodeDataWriter;
+
+	static std::atomic_uint16_t writeWorkersCounter(0);
+	std::string threadName = "writeWorker " + std::to_string(writeWorkersCounter++);
+	pthread_setname_np(pthread_self(), threadName.c_str());
+
 	for (;;) {
 		// get next job
 		uint32_t z1, z2, z3;
@@ -798,7 +813,8 @@ int write_blocks(inodedata *id, uint64_t offset, uint32_t size, const uint8_t* d
 	return 0;
 }
 
-int write_data(void *vid, uint64_t offset, uint32_t size, const uint8_t* data) {
+int write_data(void *vid, uint64_t offset, uint32_t size, const uint8_t *data,
+               size_t currentSize) {
 	LOG_AVG_TILL_END_OF_SCOPE0("write_data");
 	int status;
 	inodedata *id = (inodedata*) vid;
@@ -808,6 +824,7 @@ int write_data(void *vid, uint64_t offset, uint32_t size, const uint8_t* data) {
 
 	Glock lock(gMutex);
 	status = id->status;
+	id->maxfleng = std::max(id->maxfleng, currentSize);
 	if (status == SAUNAFS_STATUS_OK) {
 		if (offset + size > id->maxfleng) {     // move fleng
 			id->maxfleng = offset + size;
@@ -842,13 +859,22 @@ static void write_data_lcnt_increase(inodedata *id, Glock&) {
 	id->lcnt++;
 }
 
-static void write_data_lcnt_decrease(inodedata *id, Glock& lock) {
+static void	write_data_lcnt_decrease_check_deleted(inodedata *id, Glock& lock, bool& isDeleted) {
+	// As long as it is not freed, then we don't consider the inodedata deleted
+	isDeleted = false;
 	id->lcnt--;
 	if (id->lcnt == 0 && !id->inqueue && id->flushwaiting == 0 && id->writewaiting == 0) {
 		write_free_inodedata(id, lock);
+		isDeleted = true;
 	}
 }
 
+inline void write_data_lcnt_decrease(inodedata *id, Glock& lock) {
+	bool dummy_isDeleted;
+	write_data_lcnt_decrease_check_deleted(id, lock, dummy_isDeleted);
+	(void) dummy_isDeleted;
+}
+
 void* write_data_new(uint32_t inode) {
 	inodedata* id;
 	Glock lock(gMutex);
@@ -953,10 +979,13 @@ int write_data_truncate(uint32_t inode, bool opened, uint32_t uid, uint32_t gid,
 	lock.lock();
 	if (status != 0 || !writeNeeded) {
 		// Something failed or we have nothing to do more (master server managed to do the truncate)
+		bool isDeleted = false;
 		write_data_flushwaiting_decrease(id, lock);
-		write_data_lcnt_decrease(id, lock);
+		write_data_lcnt_decrease_check_deleted(id, lock, isDeleted);
 		if (status == SAUNAFS_STATUS_OK) {
-			id->maxfleng = length;
+			if (!isDeleted) {
+				id->maxfleng = length;
+			}
 			return 0;
 		} else {
 			// status is now SFS status, so we cannot return any errno
@@ -973,6 +1002,9 @@ int write_data_truncate(uint32_t inode, bool opened, uint32_t uid, uint32_t gid,
 	});
 
 	if (endOffset > length) {
+		// Make maxfleng big enough for the upcoming writes
+		id->maxfleng = endOffset;
+
 		// Something has to be written, so pass our lock to writing threads
 		sassert(id->dataChain.empty());
 		id->locator.reset(new TruncateWriteChunkLocator(inode, length / SFSCHUNKSIZE, lockId));
diff --git a/src/mount/writedata.h b/src/mount/writedata.h
index f45d4408f..547d0d1ac 100644
--- a/src/mount/writedata.h
+++ b/src/mount/writedata.h
@@ -22,6 +22,7 @@
 
 #include "common/platform.h"
 
+#include <cstddef>
 #include <inttypes.h>
 
 #include "common/attributes.h"
@@ -37,4 +38,5 @@ uint64_t write_data_getmaxfleng(uint32_t inode);
 int write_data_flush_inode(uint32_t inode);
 int write_data_truncate(uint32_t inode, bool opened, uint32_t uid, uint32_t gid, uint64_t length,
 		Attributes& attr);
-int write_data(void *vid, uint64_t offset, uint32_t size, const uint8_t *buff);
+int write_data(void *vid, uint64_t offset, uint32_t size, const uint8_t *buff,
+               size_t currentSize);
diff --git a/src/nfs-ganesha/handle.c b/src/nfs-ganesha/handle.c
index dfbda42d9..4dbc998fd 100644
--- a/src/nfs-ganesha/handle.c
+++ b/src/nfs-ganesha/handle.c
@@ -1,4 +1,4 @@
-﻿/*
+/*
    Copyright 2017 Skytechnology sp. z o.o.
    Copyright 2023 Leil Storage OÜ
 
@@ -28,7 +28,7 @@
 #include "fsal_convert.h"
 #include "FSAL/fsal_commonlib.h"
 
-#include "common/saunafs_error_codes.h"
+#include "errors/saunafs_error_codes.h"
 #include "context_wrap.h"
 #include "saunafs_internal.h"
 
diff --git a/src/protocol/README.md b/src/protocol/README.md
new file mode 100644
index 000000000..3cc616779
--- /dev/null
+++ b/src/protocol/README.md
@@ -0,0 +1,45 @@
+## Description of protocol
+
+This is not a complete description, but describes the very basics. Contributions
+are welcome.
+
+There are two versions of protocol used. One is older and used for legacy purposes,
+and a newer version currently used.
+
+Both protocols have a header and include at least two fields: type and length.
+All the types and what kind of data it includes are described in
+SFSCommunication.h. The format in the comments are:
+
+- name:\[bits\], where the bits part describes how many bits is included. Since
+you are most likely running this on modern hardware, these will always be a
+multiple of 8, so think of them as how many bytes it includes (8 for 1 byte, 16
+for 2 etc.). As for the exact type, it's not described and you must look at the
+implementation code (mostly it's some type of unsigned integer).
+- name:STDSTRING, where the data type is a string
+- name:(N * \[various data\]), represents a vector for various data types
+
+All multi-byte values are ordered big-endian.
+
+### V1 protocol
+
+The first version of the protocol uses a data type and length fields as its
+header fields, both of which are 32-bit integers (may be both unsigned and
+signed). The length field indicates the length of the data after the header.
+
+Strings include a 8, 16 or 32-bit integer before the string to indicate the size
+of the string. The string does not end with a NULL byte.
+
+### V2 protocol
+
+In this new version, the length is the sum of the version and data lengths.
+The version field is a 32-byte unsigned integer.
+
+These are prefixed by "SAU" in SFSCommunication.h, and includes several changes
+to the first protocol:
+
+- String lengths are always 32-bit unsigned integers.
+- Strings end with a NULL byte.
+- Vectors and other collections include a 32-bit integer for indicating length.
+
+In addition, the packet types can have multiple versions, which is indicated
+by `version==N`. The default version is 0.
diff --git a/src/slogger/CMakeLists.txt b/src/slogger/CMakeLists.txt
new file mode 100644
index 000000000..4fb00556c
--- /dev/null
+++ b/src/slogger/CMakeLists.txt
@@ -0,0 +1,4 @@
+collect_sources(SLOGGER)
+
+shared_add_library(slogger ${SLOGGER_SOURCES})
+shared_target_link_libraries(slogger STATIC sfserr SHARED sfserr_pic)
diff --git a/src/common/slogger.cc b/src/slogger/slogger.cc
similarity index 98%
rename from src/common/slogger.cc
rename to src/slogger/slogger.cc
index fc11f5bb6..c7602f76a 100644
--- a/src/common/slogger.cc
+++ b/src/slogger/slogger.cc
@@ -19,7 +19,7 @@
  */
 
 #include "common/platform.h"
-#include "common/slogger.h"
+#include "slogger/slogger.h"
 
 #include <errno.h>
 #include <stdarg.h>
@@ -28,7 +28,7 @@
 #include <string>
 
 #include "common/cfg.h"
-#include "common/sfserr.h"
+#include "errors/sfserr.h"
 
 static safs::log_level::LogLevel log_level_from_syslog(int priority) {
 	static const std::array<safs::log_level::LogLevel, 8> kSyslogToLevel = {{
diff --git a/src/common/slogger.h b/src/slogger/slogger.h
similarity index 100%
rename from src/common/slogger.h
rename to src/slogger/slogger.h
diff --git a/src/tools/append_file.cc b/src/tools/append_file.cc
index 05e0fcece..11b57cc91 100644
--- a/src/tools/append_file.cc
+++ b/src/tools/append_file.cc
@@ -26,7 +26,8 @@
 #include <sys/stat.h>
 
 #include "common/datapack.h"
-#include "common/sfserr.h"
+#include "errors/saunafs_error_codes.h"
+#include "errors/sfserr.h"
 #include "tools/tools_commands.h"
 #include "tools/tools_common_functions.h"
 
diff --git a/src/tools/check_file.cc b/src/tools/check_file.cc
index a47d3c052..6cf009b11 100644
--- a/src/tools/check_file.cc
+++ b/src/tools/check_file.cc
@@ -24,7 +24,8 @@
 #include <stdlib.h>
 
 #include "common/datapack.h"
-#include "common/sfserr.h"
+#include "errors/saunafs_error_codes.h"
+#include "errors/sfserr.h"
 #include "tools/tools_commands.h"
 #include "tools/tools_common_functions.h"
 
diff --git a/src/tools/dir_info.cc b/src/tools/dir_info.cc
index f2df537a3..a7f2cbfdf 100644
--- a/src/tools/dir_info.cc
+++ b/src/tools/dir_info.cc
@@ -24,7 +24,8 @@
 #include <stdlib.h>
 
 #include "common/datapack.h"
-#include "common/sfserr.h"
+#include "errors/saunafs_error_codes.h"
+#include "errors/sfserr.h"
 #include "tools/tools_commands.h"
 #include "tools/tools_common_functions.h"
 
diff --git a/src/tools/file_repair.cc b/src/tools/file_repair.cc
index c1c9ab5e5..853a102d7 100644
--- a/src/tools/file_repair.cc
+++ b/src/tools/file_repair.cc
@@ -24,7 +24,8 @@
 #include <stdlib.h>
 
 #include "common/datapack.h"
-#include "common/sfserr.h"
+#include "errors/saunafs_error_codes.h"
+#include "errors/sfserr.h"
 #include "tools/tools_commands.h"
 #include "tools/tools_common_functions.h"
 
diff --git a/src/tools/get_eattr.cc b/src/tools/get_eattr.cc
index dc37686ba..e7e5ffdf2 100644
--- a/src/tools/get_eattr.cc
+++ b/src/tools/get_eattr.cc
@@ -24,7 +24,8 @@
 #include <stdlib.h>
 
 #include "common/datapack.h"
-#include "common/sfserr.h"
+#include "errors/saunafs_error_codes.h"
+#include "errors/sfserr.h"
 #include "tools/tools_commands.h"
 #include "tools/tools_common_functions.h"
 
diff --git a/src/tools/get_trashtime.cc b/src/tools/get_trashtime.cc
index 14955e8df..33860e37b 100644
--- a/src/tools/get_trashtime.cc
+++ b/src/tools/get_trashtime.cc
@@ -25,7 +25,8 @@
 #include <stdlib.h>
 
 #include "common/datapack.h"
-#include "common/sfserr.h"
+#include "errors/saunafs_error_codes.h"
+#include "errors/sfserr.h"
 #include "tools/tools_commands.h"
 #include "tools/tools_common_functions.h"
 
diff --git a/src/tools/main.cc b/src/tools/main.cc
index 430114c1e..24a5d5c5a 100644
--- a/src/tools/main.cc
+++ b/src/tools/main.cc
@@ -25,7 +25,6 @@
 #include <unistd.h>
 #include <stdio.h>
 
-#include "common/sfserr.h"
 #include "tools/tools_commands.h"
 #include "tools/tools_common_functions.h"
 
diff --git a/src/tools/master_functions.cc b/src/tools/master_functions.cc
index c89fedcaf..e1279e0b2 100644
--- a/src/tools/master_functions.cc
+++ b/src/tools/master_functions.cc
@@ -30,7 +30,7 @@
 #include <sys/statvfs.h>
 
 #include "common/datapack.h"
-#include "common/sfserr.h"
+#include "errors/sfserr.h"
 #include "common/serialization.h"
 #include "common/special_inode_defs.h"
 #include "common/sockets.h"
diff --git a/src/tools/recursive_remove.cc b/src/tools/recursive_remove.cc
index 161cd82c3..8d09a7f37 100644
--- a/src/tools/recursive_remove.cc
+++ b/src/tools/recursive_remove.cc
@@ -122,7 +122,7 @@ static int recursive_remove(const char *file_name, int long_wait) {
 }
 
 int recursive_remove_run(int argc, char **argv) {
-	char ch;
+	int ch;
 	int status;
 	int long_wait = 0;
 
diff --git a/src/tools/set_eattr.cc b/src/tools/set_eattr.cc
index 13650ddb0..8bbce9b6e 100644
--- a/src/tools/set_eattr.cc
+++ b/src/tools/set_eattr.cc
@@ -25,7 +25,8 @@
 #include <stdlib.h>
 
 #include "common/datapack.h"
-#include "common/sfserr.h"
+#include "errors/saunafs_error_codes.h"
+#include "errors/sfserr.h"
 #include "tools/tools_commands.h"
 #include "tools/tools_common_functions.h"
 
diff --git a/src/tools/set_trashtime.cc b/src/tools/set_trashtime.cc
index 66dc28b94..978128992 100644
--- a/src/tools/set_trashtime.cc
+++ b/src/tools/set_trashtime.cc
@@ -24,7 +24,8 @@
 #include <stdlib.h>
 
 #include "common/datapack.h"
-#include "common/sfserr.h"
+#include "errors/saunafs_error_codes.h"
+#include "errors/sfserr.h"
 #include "tools/tools_commands.h"
 #include "tools/tools_common_functions.h"
 
diff --git a/src/tools/tools_common_functions.cc b/src/tools/tools_common_functions.cc
index 2253d80dd..077b189fb 100644
--- a/src/tools/tools_common_functions.cc
+++ b/src/tools/tools_common_functions.cc
@@ -32,7 +32,7 @@
 #include <string.h>
 
 #include "common/human_readable_format.h"
-#include "common/sfserr.h"
+#include "errors/sfserr.h"
 #include "common/server_connection.h"
 #include "protocol/cltoma.h"
 #include "protocol/matocl.h"
diff --git a/src/uraft/saunafs-uraft-helper.in b/src/uraft/saunafs-uraft-helper.in
index 4cc347874..a8b057e0a 100755
--- a/src/uraft/saunafs-uraft-helper.in
+++ b/src/uraft/saunafs-uraft-helper.in
@@ -116,14 +116,14 @@ saunafs_metadata_version() {
 	else
 		load_config
 	fi
-	local probe_result=$(saunafs_admin metadataserver-status --porcelain "${matocl_host}" "$matocl_port")
+	local admin_result=$(saunafs_admin metadataserver-status --porcelain "${matocl_host}" "$matocl_port")
 	if [[ $? != 0 ]] ; then
 		log "failed to query SaunaFS master status"
 		return $SAUNAFS_URAFT_ERROR
 	fi
-	local personality=$(echo "$probe_result" | cut -f1)
-	local connection=$(echo "$probe_result" | cut -f2)
-	local metadata_version_in_ram=$(echo "$probe_result" | cut -f3)
+	local personality=$(echo "$admin_result" | cut -f1)
+	local connection=$(echo "$admin_result" | cut -f2)
+	local metadata_version_in_ram=$(echo "$admin_result" | cut -f3)
 	case "$personality/$connection" in
 		master/running)
 			metadata_location="ram"
@@ -145,7 +145,7 @@ saunafs_metadata_version() {
 			fi
 		;;
 		*)
-			log "unexpected output from saunafs-admin: $probe_result"
+			log "unexpected output from saunafs-admin: $admin_result"
 			return $SAUNAFS_URAFT_OK
 		;;
 	esac


Test patch:
diff --git a/src/common/linear_assignment_cache_unittest.cc b/src/common/linear_assignment_cache_unittest.cc
new file mode 100644
index 000000000..6191e66f4
--- /dev/null
+++ b/src/common/linear_assignment_cache_unittest.cc
@@ -0,0 +1,238 @@
+/*
+   Copyright 2023 Leil Storage OÜ
+
+   This file is part of SaunaFS.
+
+   SaunaFS is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation, version 3.
+
+   SaunaFS is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with SaunaFS. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "common/platform.h"
+#include "common/linear_assignment_cache.h"
+
+#include <gtest/gtest.h>
+#include <vector>
+
+#include "common/random.h"
+#include "common/slice_traits.h"
+
+class LinearAssignmentCacheIntrospect : public LinearAssignmentCache {
+public:
+	EntriesContainer &data() { return cachedData_; }
+
+	static HashType basePart() { return kBasePart_; }
+
+	static HashType modPart() { return kModPart_; }
+
+	static HashType baseSlice() { return kBaseSlice_; }
+
+	static HashType modSlice() { return kModSlice_; }
+
+	static HashType initialStartingHash() { return kInitialStartingHash_; }
+};
+
+using LACI = LinearAssignmentCacheIntrospect;
+using SimpleSlice = std::vector<std::vector<std::pair<int, int>>>;
+
+int64_t expectedHashSlice(int64_t initialHash, SimpleSlice &slice) {
+	int64_t hash = initialHash;
+	for (auto part : slice) {
+		int64_t hashPart = 1;
+		for (auto [labelId, copies] : part) {
+			hashPart = (hashPart * LACI::basePart() +
+			            (labelId * Goal::kMaxExpectedCopies + copies) %
+			                LACI::basePart()) %
+			           LACI::modPart();
+		}
+		hash = (hash * LACI::baseSlice() + hashPart) % LACI::modSlice();
+	}
+	return hash;
+}
+
+int64_t expectedHash(SimpleSlice &targetSlice, SimpleSlice &sourceSlice) {
+	int64_t hash = LACI::initialStartingHash();
+	hash = expectedHashSlice(hash, targetSlice);
+	hash = expectedHashSlice(hash, sourceSlice);
+	return hash;
+}
+
+void buildSliceFromSimpleSlice(SimpleSlice &simpleSlice, Goal::Slice &slice) {
+	for (size_t i = 0; i < simpleSlice.size(); i++) {
+		for (auto [labelId, copies] : simpleSlice[i]) {
+			slice[i][MediaLabel(labelId)] = copies;
+		}
+	}
+}
+
+TEST(LinearAssignmentCacheTests, TestBasic) {
+	LACI laci;
+
+	Goal::Slice targetSlice(
+	    Goal::Slice::Type{slice_traits::ec::getSliceType(5, 2)});
+	Goal::Slice sourceSlice(
+	    Goal::Slice::Type{slice_traits::ec::getSliceType(5, 2)});
+	Goal::Slice sourceSliceSwapped(
+	    Goal::Slice::Type{slice_traits::ec::getSliceType(5, 2)});
+	SimpleSlice simpleTargetSlice = {{{1, 1}, {65535, 2}},
+	                                 {{2, 1}},
+	                                 {{3, 1}},
+	                                 {{1, 3}, {3, 1}, {65535, 1}},
+	                                 {{65535, 1}},
+	                                 {{1, 1}},
+	                                 {{1, 100}, {3, 2}}};
+	SimpleSlice simpleSourceSlice = {
+	    {{1, 1}},     {{3, 1}, {65535, 1}}, {{2, 1}}, {{1, 1}, {65535, 1}},
+	    {{65535, 1}}, {{1, 10}, {3, 1}},    {{3, 1}},
+	};
+	std::array<int, 7> assignment = {5, 3, 1, 0, 4, 6, 2};
+
+	SimpleSlice simpleSourceSliceSwapped = simpleSourceSlice;
+	swap(simpleSourceSliceSwapped[1], simpleSourceSliceSwapped[4]);
+	// for some reason it is not possible to call swap on arrays, so manual
+	// swapping goes between elements at indexes 1 and 4
+	std::array<int, 7> assignmentSwapped = {5, 4, 1, 0, 3, 6, 2};
+
+	buildSliceFromSimpleSlice(simpleTargetSlice, targetSlice);
+	buildSliceFromSimpleSlice(simpleSourceSlice, sourceSlice);
+	buildSliceFromSimpleSlice(simpleSourceSliceSwapped, sourceSliceSwapped);
+	std::array<int, 7> replyAssignment;
+	replyAssignment.fill(0);
+
+	int64_t hash = expectedHash(simpleTargetSlice, simpleSourceSlice);
+	int64_t innerHash =
+	    LinearAssignmentCache::hashSlicePair(targetSlice, sourceSlice);
+	ASSERT_EQ(hash, innerHash);
+
+	// the cache is empty
+	ASSERT_FALSE(
+	    laci.checkMatchAndGetResult(targetSlice, sourceSlice, replyAssignment));
+	for (auto value : replyAssignment) { ASSERT_EQ(value, 0); }
+
+	laci.store(targetSlice, sourceSlice, assignment);
+
+	// the assignment can be found querying the cache
+	ASSERT_TRUE(
+	    laci.checkMatchAndGetResult(targetSlice, sourceSlice, replyAssignment));
+	for (size_t i = 0; i < replyAssignment.size(); i++) {
+		ASSERT_EQ(replyAssignment[i], assignment[i]);
+	}
+	replyAssignment.fill(0);
+
+	// and directly to the expected entry
+	ASSERT_TRUE(laci.data()[hash].checkMatchAndGetResult(
+	    targetSlice, sourceSlice, replyAssignment));
+	for (size_t i = 0; i < replyAssignment.size(); i++) {
+		ASSERT_EQ(replyAssignment[i], assignment[i]);
+	}
+	replyAssignment.fill(0);
+
+	// the swapped input cannot be flund until now
+	ASSERT_FALSE(laci.checkMatchAndGetResult(targetSlice, sourceSliceSwapped,
+	                                         replyAssignment));
+	for (auto value : replyAssignment) { ASSERT_EQ(value, 0); }
+
+	laci.store(targetSlice, sourceSliceSwapped, assignmentSwapped);
+
+	// now both can be found
+	ASSERT_TRUE(
+	    laci.checkMatchAndGetResult(targetSlice, sourceSlice, replyAssignment));
+	for (size_t i = 0; i < replyAssignment.size(); i++) {
+		ASSERT_EQ(replyAssignment[i], assignment[i]);
+	}
+	replyAssignment.fill(0);
+	ASSERT_TRUE(laci.checkMatchAndGetResult(targetSlice, sourceSliceSwapped,
+	                                        replyAssignment));
+	for (size_t i = 0; i < replyAssignment.size(); i++) {
+		ASSERT_EQ(replyAssignment[i], assignmentSwapped[i]);
+	}
+	replyAssignment.fill(0);
+
+	laci.data()[hash] = LinearAssignmentCache::OptimizerInputOutput();
+
+	// the original entry has already been deleted, so cache should not find it
+	ASSERT_FALSE(
+	    laci.checkMatchAndGetResult(targetSlice, sourceSlice, replyAssignment));
+	for (auto value : replyAssignment) { ASSERT_EQ(value, 0); }
+}
+
+TEST(LinearAssignmentCacheTests, TestOverwriteCollide) {
+	LACI laci;
+
+	Goal::Slice targetSliceEC(
+	    Goal::Slice::Type{slice_traits::ec::getSliceType(2, 2)});
+	Goal::Slice sourceSliceEC(
+	    Goal::Slice::Type{slice_traits::ec::getSliceType(2, 2)});
+	SimpleSlice simpleTargetSliceEC = {{{1, 1}}, {{1, 1}}, {{2, 1}}, {{2, 1}}};
+	SimpleSlice simpleSourceSliceEC = {{{1, 1}}, {{2, 1}}, {}, {}};
+	std::array<int, 7> assignmentEC = {0, 2, 1, 3};
+
+	// note the number of part copies is not realistic, but will serve for the
+	// test purposes
+	Goal::Slice targetSliceXor(Goal::Slice::Type{Goal::Slice::Type::kXor3});
+	Goal::Slice sourceSliceXor(Goal::Slice::Type{Goal::Slice::Type::kXor3});
+	SimpleSlice simpleTargetSliceXor = {
+	    {{1, 1}}, {{1, 1}}, {{2, 1}}, {{1, Goal::kMaxExpectedCopies + 1}}};
+	SimpleSlice simpleSourceSliceXor = {
+	    {{1, 1}}, {{1, Goal::kMaxExpectedCopies + 1}}, {}, {}};
+	std::array<int, 7> assignmentXor = {0, 3, 1, 2};
+
+	buildSliceFromSimpleSlice(simpleTargetSliceEC, targetSliceEC);
+	buildSliceFromSimpleSlice(simpleSourceSliceEC, sourceSliceEC);
+	buildSliceFromSimpleSlice(simpleTargetSliceXor, targetSliceXor);
+	buildSliceFromSimpleSlice(simpleSourceSliceXor, sourceSliceXor);
+	std::array<int, 7> replyAssignment;
+	replyAssignment.fill(0);
+
+	int64_t hashEC = expectedHash(simpleTargetSliceEC, simpleSourceSliceEC);
+	int64_t hashXor = expectedHash(simpleTargetSliceXor, simpleSourceSliceXor);
+	int64_t innerHashEC =
+	    LinearAssignmentCache::hashSlicePair(targetSliceEC, sourceSliceEC);
+	int64_t innerHashXor =
+	    LinearAssignmentCache::hashSlicePair(targetSliceXor, sourceSliceXor);
+	ASSERT_EQ(hashEC, innerHashEC);
+	ASSERT_EQ(hashXor, innerHashXor);
+	ASSERT_EQ(hashXor, hashEC);  // the pair of slices will collide
+
+	// cache is empty, neither should be found
+	ASSERT_FALSE(laci.checkMatchAndGetResult(targetSliceEC, sourceSliceEC,
+	                                         replyAssignment));
+	for (auto value : replyAssignment) { ASSERT_EQ(value, 0); }
+	ASSERT_FALSE(laci.checkMatchAndGetResult(targetSliceXor, sourceSliceXor,
+	                                         replyAssignment));
+	for (auto value : replyAssignment) { ASSERT_EQ(value, 0); }
+
+	laci.store(targetSliceEC, sourceSliceEC, assignmentEC);
+
+	// cache has EC input, Xor is not found
+	ASSERT_TRUE(laci.checkMatchAndGetResult(targetSliceEC, sourceSliceEC,
+	                                        replyAssignment));
+	for (size_t i = 0; i < replyAssignment.size(); i++) {
+		ASSERT_EQ(replyAssignment[i], assignmentEC[i]);
+	}
+	replyAssignment.fill(0);
+	ASSERT_FALSE(laci.checkMatchAndGetResult(targetSliceXor, sourceSliceXor,
+	                                         replyAssignment));
+	for (auto value : replyAssignment) { ASSERT_EQ(value, 0); }
+
+	// Xor input overwrites previous EC input
+	laci.store(targetSliceXor, sourceSliceXor, assignmentXor);
+
+	// cache has only Xor input, EC is not found
+	ASSERT_FALSE(laci.checkMatchAndGetResult(targetSliceEC, sourceSliceEC,
+	                                         replyAssignment));
+	for (auto value : replyAssignment) { ASSERT_EQ(value, 0); }
+	ASSERT_TRUE(laci.checkMatchAndGetResult(targetSliceXor, sourceSliceXor,
+	                                        replyAssignment));
+	for (size_t i = 0; i < replyAssignment.size(); i++) {
+		ASSERT_EQ(replyAssignment[i], assignmentXor[i]);
+	}
+}
diff --git a/src/mount/direntry_cache_unittest.cc b/src/mount/direntry_cache_unittest.cc
index e3e8328d5..f783b3987 100644
--- a/src/mount/direntry_cache_unittest.cc
+++ b/src/mount/direntry_cache_unittest.cc
@@ -290,3 +290,134 @@ TEST(DirEntryCache, RandomOrder) {
 	};
 	check_expected_content(cache, index_output, lookup_output);
 }
+
+TEST(DirEntryCache, LatestLookup) {
+	DirEntryCacheIntrospect cache(5000000);
+
+	Attributes dummy_attributes;
+	dummy_attributes.fill(0);
+	Attributes attributes_with_6 = dummy_attributes;
+	Attributes attributes_with_9 = dummy_attributes;
+	attributes_with_6[0] = 6;
+	attributes_with_9[0] = 9;
+	auto current_time = cache.updateTime();
+
+	cache.insert(SaunaClient::Context(0, 0, 0, 0), 11, attributes_with_9,
+	             current_time + 500);
+	cache.insert(SaunaClient::Context(0, 0, 0, 0), 12, attributes_with_6,
+	             current_time + 500);
+	cache.insertSequence(
+		SaunaClient::Context(0, 0, 0, 0), 9,
+		std::vector<DirectoryEntry>{
+			{7, 8, 10, "a1", dummy_attributes},
+			{8, 9, 11, "a2", dummy_attributes},
+			{9, 10, 12, "a3", dummy_attributes}
+		}, current_time + 1
+	);
+
+	Attributes attr;
+	dummy_attributes.fill(0);
+	ASSERT_FALSE(cache.lookup(SaunaClient::Context(0, 0, 0, 0), 9, attr));
+
+	ASSERT_TRUE(cache.lookup(SaunaClient::Context(0, 0, 0, 0), 10, attr));
+	ASSERT_EQ(attr[0], 0);
+
+	ASSERT_TRUE(cache.lookup(SaunaClient::Context(0, 0, 0, 0), 11, attr));
+	ASSERT_EQ(attr[0], 9);
+
+	ASSERT_TRUE(cache.lookup(SaunaClient::Context(0, 0, 0, 0), 12, attr));
+	ASSERT_EQ(attr[0], 6);
+}
+
+TEST(DirEntryCache, InvalidateParentItself) {
+	DirEntryCacheIntrospect cache(5000000);
+
+	Attributes dummy_attributes;
+	dummy_attributes.fill(0);
+	Attributes attributes_with_6 = dummy_attributes;
+	Attributes attributes_with_9 = dummy_attributes;
+	attributes_with_6[0] = 6;
+	attributes_with_9[0] = 9;
+	auto current_time = cache.updateTime();
+
+	cache.insert(SaunaClient::Context(0, 0, 0, 0), 8, attributes_with_6,
+	             current_time + 500);
+	cache.insert(SaunaClient::Context(1, 1, 0, 0), 8, attributes_with_9,
+	             current_time + 500);
+	cache.insert(SaunaClient::Context(0, 0, 0, 0), 9, attributes_with_6,
+	             current_time + 500);
+	cache.insert(SaunaClient::Context(1, 0, 0, 0), 11, attributes_with_9,
+	             current_time + 500);
+	cache.insert(SaunaClient::Context(0, 0, 0, 0), 12, attributes_with_6,
+	             current_time + 500);
+	cache.insert(SaunaClient::Context(0, 0, 0, 0), 13, attributes_with_9,
+	             current_time + 500);
+	cache.insert(SaunaClient::Context(0, 0, 0, 0), 15, attributes_with_6,
+	             current_time + 500);
+	cache.insertSequence(
+		SaunaClient::Context(0, 0, 0, 0), 8,
+		std::vector<DirectoryEntry>{
+			{7, 8, 10, "a1", dummy_attributes},
+			{8, 9, 11, "a2", dummy_attributes},
+			{9, 10, 12, "a3", dummy_attributes}
+		}, current_time + 1
+	);
+	cache.insertSequence(
+		SaunaClient::Context(1, 0, 0, 0), 8,
+		std::vector<DirectoryEntry>{
+			{7, 8, 10, "a10", dummy_attributes},
+			{8, 9, 11, "a11", dummy_attributes},
+			{9, 10, 22, "a22", dummy_attributes}
+		}, current_time + 1
+	);
+	cache.insertSequence(
+		SaunaClient::Context(1, 1, 0, 0), 12,
+		std::vector<DirectoryEntry>{
+			{7, 8, 14, "a4", dummy_attributes},
+			{8, 9, 15, "a5", dummy_attributes},
+			{9, 10, 16, "a6", dummy_attributes}
+		}, current_time + 1
+	);
+
+	Attributes attr;
+	dummy_attributes.fill(0);
+	cache.lockAndInvalidateParent(12);
+
+	ASSERT_FALSE(cache.lookup(SaunaClient::Context(0, 0, 0, 0), 12, attr));
+	ASSERT_FALSE(cache.lookup(SaunaClient::Context(1, 1, 0, 0), 12, attr));
+
+	ASSERT_TRUE(cache.lookup(SaunaClient::Context(0, 0, 0, 0), 13, attr));
+	ASSERT_EQ(attr[0], 9);
+
+	ASSERT_FALSE(cache.lookup(SaunaClient::Context(1, 1, 0, 0), 14, attr));
+	ASSERT_FALSE(cache.lookup(SaunaClient::Context(1, 1, 0, 0), 16, attr));
+
+	ASSERT_FALSE(cache.lookup(SaunaClient::Context(1, 1, 0, 0), 15, attr));
+	ASSERT_TRUE(cache.lookup(SaunaClient::Context(0, 0, 0, 0), 15, attr));
+	ASSERT_EQ(attr[0], 6);
+
+	cache.lockAndInvalidateParent(SaunaClient::Context(0, 0, 0, 0), 8);
+
+	ASSERT_FALSE(cache.lookup(SaunaClient::Context(0, 0, 0, 0), 8, attr));
+	ASSERT_FALSE(cache.lookup(SaunaClient::Context(0, 0, 0, 0), 10, attr));
+	ASSERT_FALSE(cache.lookup(SaunaClient::Context(0, 0, 0, 0), 11, attr));
+	ASSERT_FALSE(cache.lookup(SaunaClient::Context(0, 0, 0, 0), 12, attr));
+
+	ASSERT_TRUE(cache.lookup(SaunaClient::Context(1, 1, 0, 0), 8, attr));
+	ASSERT_EQ(attr[0], 9);
+
+	ASSERT_TRUE(cache.lookup(SaunaClient::Context(0, 0, 0, 0), 9, attr));
+	ASSERT_EQ(attr[0], 6);
+
+	ASSERT_TRUE(cache.lookup(SaunaClient::Context(1, 0, 0, 0), 10, attr));
+	ASSERT_EQ(attr[0], 0);
+
+	ASSERT_TRUE(cache.lookup(SaunaClient::Context(1, 0, 0, 0), 10, attr));
+	ASSERT_EQ(attr[0], 0);
+
+	ASSERT_TRUE(cache.lookup(SaunaClient::Context(1, 0, 0, 0), 11, attr));
+	ASSERT_EQ(attr[0], 9);
+
+	ASSERT_TRUE(cache.lookup(SaunaClient::Context(1, 0, 0, 0), 22, attr));
+	ASSERT_EQ(attr[0], 0);
+}
diff --git a/src/protocol/cstoma_unittest.cc b/src/protocol/cstoma_unittest.cc
index 525c868f8..4cf75c866 100644
--- a/src/protocol/cstoma_unittest.cc
+++ b/src/protocol/cstoma_unittest.cc
@@ -23,7 +23,7 @@
 #include <gtest/gtest.h>
 
 #include "common/crc.h"
-#include "common/sfserr.h"
+#include "errors/sfserr.h"
 #include "unittests/chunk_type_constants.h"
 #include "unittests/inout_pair.h"
 #include "unittests/packet.h"
diff --git a/src/unittests/main.cc b/src/unittests/main.cc
index 3d055a106..907e9e5c2 100644
--- a/src/unittests/main.cc
+++ b/src/unittests/main.cc
@@ -24,7 +24,7 @@
 #include <gtest/gtest.h>
 
 #include "common/crc.h"
-#include "common/sfserr.h"
+#include "errors/sfserr.h"
 #include "common/random.h"
 #include "common/sockets.h"
 
diff --git a/src/unittests/plan_tester.cc b/src/unittests/plan_tester.cc
index baee2ac9e..e8aa5bf37 100644
--- a/src/unittests/plan_tester.cc
+++ b/src/unittests/plan_tester.cc
@@ -24,9 +24,9 @@
 #include <iostream>
 
 #include "common/block_xor.h"
-#include "common/massert.h"
 #include "common/reed_solomon.h"
 #include "common/slice_traits.h"
+#include "protocol/SFSCommunication.h"
 
 namespace unittests {
 
diff --git a/tests/ci_build/docker-build.sh b/tests/ci_build/docker-build.sh
index 77702b44a..f41a87cd4 100755
--- a/tests/ci_build/docker-build.sh
+++ b/tests/ci_build/docker-build.sh
@@ -142,7 +142,14 @@ case "${option,,}" in
 		;;
 	ganesha)
 		docker_context_dir="${PROJECT_DIR}/tests/ci_build/ganesha"
-		dockerfile="${docker_context_dir}/Dockerfile"
+		case ${DOCKER_BASE_IMAGE,,} in
+			*ubuntu-24.04* | *debian-trixie*)
+				dockerfile="${docker_context_dir}/Dockerfile.ubuntu-24.04"
+				;;
+			*)
+				dockerfile="${docker_context_dir}/Dockerfile"
+				;;
+		esac
 		;;
 	*)
 		docker_context_dir="${PROJECT_DIR}"
diff --git a/tests/ci_build/ganesha/Dockerfile b/tests/ci_build/ganesha/Dockerfile
index 59461e4eb..7c1eb0319 100644
--- a/tests/ci_build/ganesha/Dockerfile
+++ b/tests/ci_build/ganesha/Dockerfile
@@ -34,7 +34,6 @@ RUN set -e ; \
           bison \
           build-essential \
           byacc \
-          ceph \
           cmake \
           dbus \
           docbook \
@@ -50,22 +49,18 @@ RUN set -e ; \
           libboost-program-options-dev \
           libboost-system-dev \
           libcap-dev \
-          libcephfs-dev \
           libdbus-1-dev \
-          libglusterfs-dev \
           libgssapi-krb5-2 \
           libjemalloc-dev \
           libjudy-dev \
           libkrb5-dev \
           libkrb5support0 \
           libnfsidmap-dev \
-          libradospp-dev \
-          libradosstriper-dev \
-          librgw-dev \
+          libnsl-dev \
           libsqlite3-dev \
           liburcu-dev \
-          xfslibs-dev \
           pkg-config \
+          pv \
           wget \
           unzip \
           rpcbind \
@@ -84,8 +79,18 @@ RUN set -e ; \
         mkdir nfs-ganesha-${GANESHA_VERSION}/build ; \
         cmake -Bnfs-ganesha-${GANESHA_VERSION}/build \
             -DCMAKE_INSTALL_PREFIX=/ \
+            -DUSE_9P=NO \
+            -DUSE_FSAL_CEPH=NO \
+            -DUSE_FSAL_GLUSTER=NO \
+            -DUSE_FSAL_GPFS=NO \
+            -DUSE_FSAL_KVSFS=NO \
+            -DUSE_FSAL_LIZARDFS=NO \
+            -DUSE_FSAL_LUSTRE=NO \
+            -DUSE_FSAL_PROXY_V3=NO \
+            -DUSE_FSAL_PROXY_V4=NO \
+            -DUSE_FSAL_RGW=NO \
+            -DUSE_FSAL_XFS=NO \
             -DUSE_GSS=NO \
-            -DUSE_FSAL_SAUNAFS=NO \
             -Snfs-ganesha-${GANESHA_VERSION}/src; \
         make -Cnfs-ganesha-${GANESHA_VERSION}/build -j$(($(nproc)*3/4+1)) install ; \
         rm -rf nfs-ganesha-${GANESHA_VERSION} ntirpc-${NTIRPC_VERSION} ; \
@@ -135,10 +140,11 @@ COPY --from=ganesha-builder /tmp/dependencies/lib /lib
 COPY --from=ganesha-builder /tmp/dependencies/lib64 /lib64
 
 RUN set -e ; \
-    if ! type showmount > /dev/null 2>&1; then \
-        apt update; apt install -y --no-install-recommends nfs-common ; \
-        rm -rf /var/lib/apt/lists/* ; \
-    fi ;
+    apt update; \
+    apt install -y --no-install-recommends \
+        nfs-common ; \
+    rm -rf /var/lib/apt/lists/* ; \
+    true
 
 COPY --chmod=0755 services/ganesha/entrypoint.sh /docker-entrypoint.d/99-ganesha.sh
 COPY --chmod=0755 services/ganesha/healthcheck.sh /healthcheck.d/99-ganesha.sh
diff --git a/tests/ci_build/ganesha/Dockerfile.ubuntu-24.04 b/tests/ci_build/ganesha/Dockerfile.ubuntu-24.04
new file mode 100644
index 000000000..a87cfeabb
--- /dev/null
+++ b/tests/ci_build/ganesha/Dockerfile.ubuntu-24.04
@@ -0,0 +1,69 @@
+ARG BASE_IMAGE=ubuntu:24.04
+FROM ${BASE_IMAGE} as base
+
+ARG DEBIAN_FRONTEND=noninteractive
+ENV TZ=Europe/Madrid
+ENV LC_ALL=C.UTF-8
+
+RUN set -e ; \
+    if ! type tini > /dev/null 2>&1; then \
+        apt update; apt install -y --no-install-recommends tini ; \
+        rm -rf /var/lib/apt/lists/* ; \
+    fi ;
+
+COPY --chmod=755 services/base/docker_entrypoint.sh /usr/local/bin/entrypoint.sh
+ENTRYPOINT ["/usr/bin/tini", "--", "/usr/local/bin/entrypoint.sh"]
+
+COPY --chmod=755 services/base/docker_healthcheck.sh /usr/local/bin/base-healthcheck.sh
+HEALTHCHECK --interval=30s --timeout=3s \
+    CMD /usr/local/bin/base-healthcheck.sh || exit 1
+
+FROM base AS dbus
+
+RUN set -e ; \
+    if ! type dbus-daemon > /dev/null 2>&1; then \
+        apt update; apt install -y --no-install-recommends dbus ; \
+        rm -rf /var/lib/apt/lists/* ; \
+    fi ;
+COPY --chmod=0755 services/dbus/entrypoint.sh /docker-entrypoint.d/10-dbus.sh
+COPY --chmod=0755 services/dbus/healthcheck.sh /healthcheck.d/10-dbus.sh
+
+FROM dbus AS rpcbind
+
+RUN set -e ; \
+    if ! type rpcbind > /dev/null 2>&1; then \
+        apt update; apt install -y --no-install-recommends rpcbind ; \
+        rm -rf /var/lib/apt/lists/* ; \
+    fi ;
+COPY --chmod=0755 services/rpcbind/entrypoint.sh /docker-entrypoint.d/20-rpcbind.sh
+COPY --chmod=0755 services/rpcbind/healthcheck.sh /healthcheck.d/20-rpcbind.sh
+
+FROM rpcbind AS rpc-statd
+
+RUN set -e ; \
+    if ! type rpc.statd > /dev/null 2>&1; then \
+        apt update; apt install -y --no-install-recommends nfs-common ; \
+        rm -rf /var/lib/apt/lists/* ; \
+    fi ;
+
+COPY --chmod=0755 services/rpc-statd/entrypoint.sh /docker-entrypoint.d/30-rpc-statd.sh
+COPY --chmod=0755 services/rpc-statd/healthcheck.sh /healthcheck.d/30-rpc-statd.sh
+
+FROM rpc-statd AS ganesha
+
+#COPY --from=ganesha-builder /usr/bin/ganesha.nfsd /usr/bin/ganesha.nfsd
+#COPY --from=ganesha-builder /etc/ganesha /etc/ganesha
+#COPY --from=ganesha-builder /usr/lib/ganesha /usr/lib/ganesha
+#COPY --from=ganesha-builder /tmp/dependencies/lib /lib
+#COPY --from=ganesha-builder /tmp/dependencies/lib64 /lib64
+
+RUN set -e ; \
+    apt update; \
+    apt install -y --no-install-recommends \
+        nfs-common \
+        nfs-ganesha ; \
+    rm -rf /var/lib/apt/lists/* ; \
+    true
+
+COPY --chmod=0755 services/ganesha/entrypoint.sh /docker-entrypoint.d/99-ganesha.sh
+COPY --chmod=0755 services/ganesha/healthcheck.sh /healthcheck.d/99-ganesha.sh
diff --git a/tests/ci_build/run-build.sh b/tests/ci_build/run-build.sh
index 76f77ec8d..0fe0e5a2b 100755
--- a/tests/ci_build/run-build.sh
+++ b/tests/ci_build/run-build.sh
@@ -3,6 +3,7 @@ set -eux -o pipefail
 PROJECT_DIR="$(readlink -f "$(dirname "${BASH_SOURCE[0]}")/../..")"
 WORKSPACE=${WORKSPACE:-"${PROJECT_DIR}"}
 die() { echo "Error: $*" >&2; exit 1; }
+warn() { echo "Warning: $*" >&2; }
 
 usage() {
 	cat <<-EOT
@@ -31,6 +32,7 @@ declare -a CMAKE_SAUNAFS_ARGUMENTS=(
 [ -n "${1:-}" ] || usage
 declare build_type="${1}"
 declare build_dir
+declare do_install=true
 shift
 case "${build_type,,}" in
 	debug)
@@ -68,7 +70,7 @@ case "${build_type,,}" in
 		;;
 	release)
 		CMAKE_SAUNAFS_ARGUMENTS+=(
-			-DCMAKE_BUILD_TYPE=Release
+			-DCMAKE_BUILD_TYPE=RelWithDebInfo
 			-DCMAKE_INSTALL_PREFIX=/
 			-DENABLE_TESTS=OFF
 			-DCODE_COVERAGE=OFF
@@ -76,6 +78,7 @@ case "${build_type,,}" in
 			-DENABLE_WERROR=OFF
 		)
 		build_dir="${WORKSPACE}/build/saunafs-release"
+		do_install=false
 		;;
 	*) die "Unsupported build type: ${build_type}"
 		;;
@@ -92,4 +95,13 @@ cmake -B "${build_dir}" \
 	"${CMAKE_SAUNAFS_ARGUMENTS[@]}" \
 	"${EXTRA_ARGUMENTS[@]}" "${WORKSPACE}"
 
-nice make -C "${build_dir}" -j "$(nproc)" install
+nice make -C "${build_dir}" -j "$(nproc)"
+if [ "${do_install}" = true ]; then
+	nice make -C "${build_dir}" install
+fi
+
+if [ -f "${build_dir}/CPackConfig.cmake" ]; then
+	nice cpack -B "${build_dir}" --config "${build_dir}/CPackConfig.cmake" -j "$(nproc)"
+else
+	warn "No CPack configuration found in ${build_dir}. Skipping packaging."
+fi
diff --git a/tests/ci_build/setup-test-machine.sh b/tests/ci_build/setup-test-machine.sh
index 7556e0d35..9e6b81e33 100755
--- a/tests/ci_build/setup-test-machine.sh
+++ b/tests/ci_build/setup-test-machine.sh
@@ -10,12 +10,12 @@ die() {  echo "Error: ${*}" >&2;  exit 1; }
 [ ${UID} -eq 0 ] || die "Run this script as root"
 
 # Run SaunaFS setup script
-mkdir -p /mnt/hdd_0 /mnt/hdd_1 /mnt/hdd_2 /mnt/hdd_3 /mnt/hdd_4
+mkdir -p /mnt/hdd_0 /mnt/hdd_1 /mnt/hdd_2 /mnt/hdd_3 /mnt/hdd_4 /mnt/hdd_5 /mnt/hdd_6 /mnt/hdd_7
 readonly setup_machine_script="${script_dir}/../setup_machine.sh"
 [ -f "${setup_machine_script}" ] || die "Script not found: ${setup_machine_script}"
 grep -v '^[[:space:]]*mount[[:space:]]*[^[:space:]]*$' "${setup_machine_script}" \
-	| bash -x /dev/stdin setup /mnt/hdd_0 /mnt/hdd_1 /mnt/hdd_2 /mnt/hdd_3 /mnt/hdd_4
-chown saunafstest:saunafstest /mnt/hdd_0 /mnt/hdd_1 /mnt/hdd_2 /mnt/hdd_3 /mnt/hdd_4
+	| bash -x /dev/stdin setup /mnt/hdd_0 /mnt/hdd_1 /mnt/hdd_2 /mnt/hdd_3 /mnt/hdd_4 /mnt/hdd_5 /mnt/hdd_6 /mnt/hdd_7
+chown saunafstest:saunafstest /mnt/hdd_0 /mnt/hdd_1 /mnt/hdd_2 /mnt/hdd_3 /mnt/hdd_4 /mnt/hdd_5 /mnt/hdd_6 /mnt/hdd_7
 
 # Extras
 # Install zonefs-tools
diff --git a/tests/data/ShortSystemTests.txt b/tests/data/ShortSystemTests.txt
index 29ae483a1..dd961a070 100644
--- a/tests/data/ShortSystemTests.txt
+++ b/tests/data/ShortSystemTests.txt
@@ -98,15 +98,15 @@ test_pread_eio_in_chunkserver_single_disk=20
 test_pread_eio_in_chunkserver_some_disks=10
 test_pread_eio_nonheader_in_chunkserver=10
 test_prefetching_xor_stripes=5
-test_probe_chunks_health=10
-test_probe_chunks_health_custom_goals=5
-test_probe_info=5
-test_probe_iolimits_status=5
-test_probe_list_chunkservers=5
-test_probe_list_disks=5
-test_probe_list_metadataservers=5
-test_probe_list_mounts=5
-test_probe_metadataserver_status=5
+test_admin_chunks_health=10
+test_admin_chunks_health_custom_goals=5
+test_admin_info=5
+test_admin_iolimits_status=5
+test_admin_list_chunkservers=5
+test_admin_list_disks=5
+test_admin_list_metadataservers=5
+test_admin_list_mounts=5
+test_admin_metadataserver_status=5
 test_pwrite_eio_in_chunkserver=5
 test_pwrite_eio_nonheader_in_chunkserver=5
 test_quota_dir_inodes=5
diff --git a/tests/run-test.sh b/tests/run-test.sh
index f84186df1..199670022 100755
--- a/tests/run-test.sh
+++ b/tests/run-test.sh
@@ -94,20 +94,22 @@ unwrap_generators $1
 
 nice nice sudo -HEu saunafstest sh -c "chmod -Rf a+rwX ${ERROR_DIR}"
 for log_file in "$ERROR_DIR"/* ; do
-	log_file_name=$(basename "$log_file")
+	log_file_basename=$(basename "$log_file")
 	if [[ -s ${log_file} ]]; then
 		status=1
-		if [[ $log_file_name != syslog.log ]]; then
+		if [[ $log_file_basename != syslog.log ]]; then
 			# Do not inform users that there is nonempty syslog
 			# It is always nonempty if the test failed
-			echo "(FATAL) Errors in ${log_file_name}" | tee "${ERROR_FILE}" #error file lifetime is: all tests
+			echo "(FATAL) Errors in ${log_file_basename}" | tee "${ERROR_FILE}" #error file lifetime is: all tests
 			# print all non-binary files to stdout
-			if file --mime-encoding "${log_file_name}" | awk '{exit $2=="binary"}'; then
+			# awk will return 0 if it doesn't find binary, and 1 if
+			# it does, which is what we want for the return value
+			if file --mime-encoding "${log_file}" | awk '{exit $2=="binary"}'; then
 				cat "${log_file}"
 			fi
 		fi
 		if [[ $TEST_OUTPUT_DIR ]]; then
-			cp "${log_file}" "$TEST_OUTPUT_DIR/$(date '+%F_%T')__$(basename $1 .sh)__${log_file_name}"
+			cp "${log_file}" "$TEST_OUTPUT_DIR/$(date '+%F_%T')__$(basename $1 .sh)__${log_file_basename}"
 		fi
 	fi
 done
diff --git a/tests/setup_machine.sh b/tests/setup_machine.sh
index ed467de7a..c46e78186 100755
--- a/tests/setup_machine.sh
+++ b/tests/setup_machine.sh
@@ -86,6 +86,7 @@ common_packages=(
 	python3-setuptools
 	python3-wheel
 	psmisc
+	pv
 	rsync
 	rsyslog
 	socat
@@ -101,7 +102,6 @@ common_packages=(
 	# fio
 	bison
 	byacc
-	ceph
 	dbus
 	doxygen
 	flex
@@ -109,6 +109,7 @@ common_packages=(
 )
 apt_packages=(
 	build-essential
+	iproute2
 	libblkid-dev
 	libboost-filesystem-dev
 	libboost-iostreams-dev
@@ -147,19 +148,14 @@ apt_packages=(
 	krb5-user
 	libacl1-dev
 	libcap-dev
-	libcephfs-dev
 	libdbus-1-dev
-	libglusterfs-dev
 	libgssapi-krb5-2
 	libjemalloc-dev
 	libkrb5-dev
 	libkrb5support0
 	libnfsidmap-dev
-	libradospp-dev
-	libradosstriper-dev
-	librgw-dev
+	libnsl-dev
 	libsqlite3-dev
-	xfslibs-dev
 )
 dnf_packages=(
 	boost-filesystem
@@ -172,12 +168,14 @@ dnf_packages=(
 	gcc-c++
 	gperftools-libs
 	gtest-devel
+	iproute
 	isa-l-devel
 	Judy-devel
 	kernel-devel
 	libblkid-devel
 	libcutl-devel
 	libdb-devel
+	libnsl
 	libtirpc-devel
 	netcat
 	pam-devel
@@ -209,14 +207,8 @@ dnf_packages=(
 	krb5-workstation
 	libacl-devel
 	libcap-devel
-	libcephfs-devel
-	libglusterfs-devel
 	libnfsidmap-devel
-	librados-devel
-	libradosstriper-devel
-	librgw-devel
 	libsqlite3x-devel
-	xfsprogs-devel
 )
 python_packages=(
 	asciidoc
@@ -256,9 +248,14 @@ rm -rf "${gtest_temp_build_dir:?}"
 # Setup python3 with a global virtual env (new recommended way)
 VIRTUAL_ENV=/var/lib/saunafs_setup_machine_venv
 export VIRTUAL_ENV
+if ! grep -Eq 'PATH=.*'"${VIRTUAL_ENV}/bin\b"'' /etc/environment ; then
+	# Add the virtualenv to the end of the PATH in /etc/environment
+	# Select three groups: PATH=, optional quote, rest of the path,
+	# then add the virtualenv bin directory to the end of the path, keeping the correct quoting
+	sed -E 's@(PATH=)([:"'\'']?)(.*)\2@\1\2\3:'"${VIRTUAL_ENV}/bin"'\2@' -i /etc/environment;
+fi
+# Add VIRTUAL_ENV to /etc/environment if it's not there
 sed '\@VIRTUAL_ENV="'"${VIRTUAL_ENV}"'"@!s@$@\nVIRTUAL_ENV="'"${VIRTUAL_ENV}"'"@' -zi /etc/environment
-# shellcheck disable=SC2016
-sed '\@PATH="'"${VIRTUAL_ENV}"'/bin@!s@$@\nPATH="'"${VIRTUAL_ENV}"'/bin:${PATH}"@' -zi /etc/environment
 python3 -m venv "${VIRTUAL_ENV}"
 "${VIRTUAL_ENV}/bin/python3" -m pip install install "${python_packages[@]}"
 
@@ -289,6 +286,7 @@ fi
 if ! [[ -f /etc/sudoers.d/saunafstest ]] || \
 		! grep drop_caches /etc/sudoers.d/saunafstest >/dev/null; then
 	cat >/etc/sudoers.d/saunafstest <<-END
+		Defaults rlimit_core=default
 		ALL ALL = (saunafstest) NOPASSWD: ALL
 		ALL ALL = NOPASSWD: /usr/bin/pkill -9 -u saunafstest
 		ALL ALL = NOPASSWD: /bin/rm -rf /tmp/saunafs_error_dir
@@ -313,23 +311,6 @@ if ! grep 'pam_limits.so' /etc/pam.d/sudo > /dev/null; then
 	END
 fi
 
-case "${release}" in
-	LinuxMint/*|Ubuntu/*|Debian/*)
-		echo ; echo 'Configure SaunaFS repository'
-		gpg --no-default-keyring \
-			--keyring /usr/share/keyrings/saunafs-archive-keyring.gpg \
-			--keyserver hkps://keyserver.ubuntu.com \
-			--receive-keys 0xA80B96E2C79457D4
-		sudo tee /etc/apt/sources.list.d/saunafs.list <<-EOF
-		deb [arch=amd64 signed-by=/usr/share/keyrings/saunafs-archive-keyring.gpg] https://repo.saunafs.com/repository/saunafs-$(lsb_release -si | tr '[:upper:]' '[:lower:]')-$(lsb_release -sr | tr '/' '-') $(lsb_release -sc) main
-		EOF
-		;;
-	*)
-		set +x
-		echo "Configuration of packages repository SKIPPED, '${release}' isn't supported by this script"
-		;;
-esac
-
 echo ; echo 'Add users saunafstest_{0..9}'
 for username in saunafstest_{0..9}; do
 	if ! getent passwd ${username} > /dev/null; then
diff --git a/tests/test_suites/Benchmarks/test_basic_performance_estimation.sh b/tests/test_suites/Benchmarks/test_basic_performance_estimation.sh
new file mode 100644
index 000000000..5a22e88a0
--- /dev/null
+++ b/tests/test_suites/Benchmarks/test_basic_performance_estimation.sh
@@ -0,0 +1,25 @@
+# This test does a very basic estimation for write and read speed of the system.
+# The idea is to use it to compare the performance after some changes.
+
+CHUNKSERVERS=1 \
+	USE_RAMDISK=YES \
+	MOUNT_EXTRA_CONFIG="mfscachemode=NEVER" \
+	CHUNKSERVER_EXTRA_CONFIG="GARBAGE_COLLECTION_FREQ_MS = 0|HDD_TEST_FREQ = 100000" \
+	AUTO_SHADOW_MASTER="NO" \
+	setup_local_empty_saunafs info
+
+cd "${info[mount0]}"
+
+sizeFittingRamdisk=1536
+
+# Create a file and store the dd output
+dd if=/dev/zero of=file bs=1M count=${sizeFittingRamdisk} oflag=direct \
+	&> "${TEMP_DIR}/dd_write.log"
+
+drop_caches
+
+# Read the file and store the dd output
+dd if=file of=/dev/null bs=1M iflag=direct &> "${TEMP_DIR}/dd_read.log"
+
+echo "Write: $(grep copied ${TEMP_DIR}/dd_write.log | awk '{print $10, $11}')"
+echo " Read: $(grep copied ${TEMP_DIR}/dd_read.log | awk '{print $10, $11}')"
diff --git a/tests/test_suites/GaneshaTests/test_nfs_ganesha_connectathon_nfs_suite.sh b/tests/test_suites/GaneshaTests/test_nfs_ganesha_connectathon_nfs_suite.sh
index 0ac6273b3..6e7fff9e1 100644
--- a/tests/test_suites/GaneshaTests/test_nfs_ganesha_connectathon_nfs_suite.sh
+++ b/tests/test_suites/GaneshaTests/test_nfs_ganesha_connectathon_nfs_suite.sh
@@ -7,7 +7,7 @@
 # The path for the Ganesha daemon should match the installation folder inside the test.
 #
 
-timeout_set 3 minutes
+timeout_set 2 minutes
 
 CHUNKSERVERS=5 \
 	USE_RAMDISK=YES \
diff --git a/tests/test_suites/GaneshaTests/test_nfs_ganesha_copy_file.sh b/tests/test_suites/GaneshaTests/test_nfs_ganesha_copy_file.sh
index a0151e8c2..37176a5a2 100644
--- a/tests/test_suites/GaneshaTests/test_nfs_ganesha_copy_file.sh
+++ b/tests/test_suites/GaneshaTests/test_nfs_ganesha_copy_file.sh
@@ -7,7 +7,7 @@
 # The path for the Ganesha daemon should match the installation folder inside the test.
 #
 
-timeout_set 3 minutes
+timeout_set 45 seconds
 
 CHUNKSERVERS=5 \
 	USE_RAMDISK=YES \
diff --git a/tests/test_suites/GaneshaTests/test_nfs_ganesha_file_corruption_on_master_failover.sh b/tests/test_suites/GaneshaTests/test_nfs_ganesha_file_corruption_on_master_failover.sh
new file mode 100644
index 000000000..a0b3d318d
--- /dev/null
+++ b/tests/test_suites/GaneshaTests/test_nfs_ganesha_file_corruption_on_master_failover.sh
@@ -0,0 +1,120 @@
+#
+# To run this test you need to add the following lines to /etc/sudoers.d/saunafstest:
+#
+# saunafstest ALL = NOPASSWD: /bin/mount, /bin/umount, /bin/pkill, /bin/mkdir, /bin/touch
+# saunafstest ALL = NOPASSWD: /usr/bin/ganesha.nfsd
+#
+# The path for the Ganesha daemon should match the installation folder inside the test.
+#
+
+timeout_set 2 minutes
+
+CHUNKSERVERS=3 \
+	MOUNT_EXTRA_CONFIG="sfscachemode=NEVER" \
+	CHUNKSERVER_EXTRA_CONFIG="READ_AHEAD_KB = 1024|MAX_READ_BEHIND_KB = 2048"
+	setup_local_empty_saunafs info
+
+test_error_cleanup() {
+	cd "${TEMP_DIR}"
+	sudo umount -l "${TEMP_DIR}/mnt/ganesha"
+	sudo pkill -9 ganesha.nfsd
+}
+
+# Function to get the checksum of a given file
+get_checksum() {
+	sha256sum $1 | awk '{ print $1 }'
+}
+
+mkdir -p "${TEMP_DIR}/mnt/ganesha"
+
+# Create PID file for Ganesha
+PID_FILE=/var/run/ganesha/ganesha.pid
+if [ ! -f ${PID_FILE} ]; then
+	echo "ganesha.pid doesn't exists, creating it...";
+	sudo mkdir -p /var/run/ganesha;
+	sudo touch "${PID_FILE}";
+else
+	echo "ganesha.pid already exists";
+fi
+
+cd "${info[mount0]}"
+
+cat <<EOF > "${info[mount0]}/ganesha.conf"
+NFS_KRB5 {
+	Active_krb5=false;
+}
+NFSV4 {
+	Grace_Period = 5;
+	Lease_Lifetime = 5;
+}
+EXPORT {
+	Attr_Expiration_Time = 0;
+	Export_Id = 99;
+	Path = /;
+	Pseudo = /;
+	Access_Type = RW;
+	FSAL {
+		Name = SaunaFS;
+		hostname = localhost;
+		port = ${saunafs_info_[matocl]};
+		# How often to retry to connect
+		io_retries = 5;
+		cache_expiration_time_ms = 2500;
+	}
+	Protocols = 4;
+	CLIENT {
+		Clients = localhost;
+	}
+}
+EOF
+
+sudo /usr/bin/ganesha.nfsd -f "${info[mount0]}/ganesha.conf"
+assert_eventually 'showmount -e localhost'
+
+sudo mount -vvvv localhost:/ "${TEMP_DIR}/mnt/ganesha"
+
+# Create a file for testing with checksum
+head -c 3G /dev/random | tee "${TEMP_DIR}/test_file" > /dev/null
+
+# Restart master server after 15 seconds
+(
+	sleep 15
+	assert_success saunafs_master_daemon restart
+) &
+
+# Wait for Grace period so NFS Ganesha server will be ready
+sleep 5
+
+# Try to copy the file after master restart
+while true; do
+	cp "${TEMP_DIR}/test_file" "$TEMP_DIR/mnt/ganesha/test_file" && break
+	echo "Unable to copy test_file through NFS, retrying in 5 seconds..."
+	sleep 5
+done
+
+# Get checksums
+checksum1=$(get_checksum "${TEMP_DIR}/test_file")
+
+# To get the checksum of the file from NFS could require several retries in case
+# NFS mount will not be available after restarting master server
+while true; do
+	checksum2=$(get_checksum "${TEMP_DIR}/mnt/ganesha/test_file")
+	if [ "${checksum2}" != "" ]; then
+		break
+	fi
+	echo "Unable to get checksums through NFS, retrying in 5 seconds..."
+	sleep 5
+done
+
+checksum3=$(get_checksum "${info[mount0]}/test_file")
+
+# Print checksums
+echo "Checksum of original file: ${checksum1}"
+echo "Checksum of file from NFS mount: ${checksum2}"
+echo "Checksum of file from SaunaFS mount: ${checksum3}"
+
+# Verify checksums
+assert_equals "${checksum1}" "${checksum2}"
+assert_equals "${checksum1}" "${checksum3}"
+
+test_error_cleanup || true
diff --git a/tests/test_suites/GaneshaTests/test_nfs_ganesha_fio_random_mix_reads_writes.sh b/tests/test_suites/GaneshaTests/test_nfs_ganesha_fio_random_mix_reads_writes.sh
index 2fc5b71ee..19d705e70 100644
--- a/tests/test_suites/GaneshaTests/test_nfs_ganesha_fio_random_mix_reads_writes.sh
+++ b/tests/test_suites/GaneshaTests/test_nfs_ganesha_fio_random_mix_reads_writes.sh
@@ -11,7 +11,7 @@
 #
 assert_program_installed fio
 
-timeout_set 3 minutes
+timeout_set 45 seconds
 
 CHUNKSERVERS=5 \
 	USE_RAMDISK=YES \
@@ -74,7 +74,7 @@ EOF
 
 sudo /usr/bin/ganesha.nfsd -f ${info[mount0]}/ganesha.conf
 
-sleep 15
+assert_eventually 'showmount -e localhost'
 sudo mount -vvvv localhost:/ $TEMP_DIR/mnt/ganesha
 
 echo ""
@@ -84,7 +84,7 @@ cd ${TEMP_DIR}/mnt/ganesha
 # Run fio random mix of read and write on top of Ganesha Client
 fio --name=fiotest_random_mix_read_write --directory=${TEMP_DIR}/mnt/ganesha \
     --size=200M --rw=randrw --numjobs=5 --ioengine=psync --group_reporting   \
-    --bs=4M --direct=1 --iodepth=4
+    --bs=4M --direct=1 --iodepth=1
 
 echo ""
 echo "List of created files at the Ganesha Client:"
diff --git a/tests/test_suites/GaneshaTests/test_nfs_ganesha_fio_random_reads.sh b/tests/test_suites/GaneshaTests/test_nfs_ganesha_fio_random_reads.sh
index aa7a7536f..2e290b819 100644
--- a/tests/test_suites/GaneshaTests/test_nfs_ganesha_fio_random_reads.sh
+++ b/tests/test_suites/GaneshaTests/test_nfs_ganesha_fio_random_reads.sh
@@ -11,7 +11,7 @@
 #
 assert_program_installed fio
 
-timeout_set 3 minutes
+timeout_set 45 seconds
 
 CHUNKSERVERS=5 \
 	USE_RAMDISK=YES \
@@ -74,7 +74,7 @@ EOF
 
 sudo /usr/bin/ganesha.nfsd -f ${info[mount0]}/ganesha.conf
 
-sleep 15
+assert_eventually 'showmount -e localhost'
 sudo mount -vvvv localhost:/ $TEMP_DIR/mnt/ganesha
 
 echo ""
diff --git a/tests/test_suites/GaneshaTests/test_nfs_ganesha_fio_random_writes.sh b/tests/test_suites/GaneshaTests/test_nfs_ganesha_fio_random_writes.sh
index a3e872d1d..1e75831f7 100644
--- a/tests/test_suites/GaneshaTests/test_nfs_ganesha_fio_random_writes.sh
+++ b/tests/test_suites/GaneshaTests/test_nfs_ganesha_fio_random_writes.sh
@@ -11,7 +11,7 @@
 #
 assert_program_installed fio
 
-timeout_set 3 minutes
+timeout_set 45 seconds
 
 CHUNKSERVERS=5 \
 	USE_RAMDISK=YES \
@@ -74,7 +74,7 @@ EOF
 
 sudo /usr/bin/ganesha.nfsd -f ${info[mount0]}/ganesha.conf
 
-sleep 15
+assert_eventually 'showmount -e localhost'
 sudo mount -vvvv localhost:/ $TEMP_DIR/mnt/ganesha
 
 echo ""
diff --git a/tests/test_suites/GaneshaTests/test_nfs_ganesha_fio_sequential_mix_reads_writes.sh b/tests/test_suites/GaneshaTests/test_nfs_ganesha_fio_sequential_mix_reads_writes.sh
index 7c5f32a2b..47f66d753 100644
--- a/tests/test_suites/GaneshaTests/test_nfs_ganesha_fio_sequential_mix_reads_writes.sh
+++ b/tests/test_suites/GaneshaTests/test_nfs_ganesha_fio_sequential_mix_reads_writes.sh
@@ -12,7 +12,7 @@
 
 assert_program_installed fio
 
-timeout_set 3 minutes
+timeout_set 45 seconds
 
 CHUNKSERVERS=5 \
 	USE_RAMDISK=YES \
@@ -75,7 +75,7 @@ EOF
 
 sudo /usr/bin/ganesha.nfsd -f ${info[mount0]}/ganesha.conf
 
-sleep 15
+assert_eventually 'showmount -e localhost'
 sudo mount -vvvv localhost:/ $TEMP_DIR/mnt/ganesha
 
 echo ""
@@ -85,7 +85,7 @@ cd ${TEMP_DIR}/mnt/ganesha
 # Run fio sequential mix of read and write on top of Ganesha Client
 fio --name=fiotest_seq_mix_read_write --directory=${TEMP_DIR}/mnt/ganesha \
     --size=200M --rw=rw --numjobs=5 --ioengine=psync --group_reporting    \
-    --bs=4M --direct=1 --iodepth=4
+    --bs=4M --direct=1 --iodepth=1
 
 echo ""
 echo "List of created files at the Ganesha Client:"
diff --git a/tests/test_suites/GaneshaTests/test_nfs_ganesha_fio_sequential_reads.sh b/tests/test_suites/GaneshaTests/test_nfs_ganesha_fio_sequential_reads.sh
index 91ee96e3e..e2abab8e5 100644
--- a/tests/test_suites/GaneshaTests/test_nfs_ganesha_fio_sequential_reads.sh
+++ b/tests/test_suites/GaneshaTests/test_nfs_ganesha_fio_sequential_reads.sh
@@ -12,7 +12,7 @@
 
 assert_program_installed fio
 
-timeout_set 3 minutes
+timeout_set 45 seconds
 
 CHUNKSERVERS=5 \
 	USE_RAMDISK=YES \
@@ -75,7 +75,7 @@ EOF
 
 sudo /usr/bin/ganesha.nfsd -f ${info[mount0]}/ganesha.conf
 
-sleep 15
+assert_eventually 'showmount -e localhost'
 sudo mount -vvvv localhost:/ $TEMP_DIR/mnt/ganesha
 
 echo ""
diff --git a/tests/test_suites/GaneshaTests/test_nfs_ganesha_fio_sequential_writes.sh b/tests/test_suites/GaneshaTests/test_nfs_ganesha_fio_sequential_writes.sh
index b4f8125cf..8e69943d5 100644
--- a/tests/test_suites/GaneshaTests/test_nfs_ganesha_fio_sequential_writes.sh
+++ b/tests/test_suites/GaneshaTests/test_nfs_ganesha_fio_sequential_writes.sh
@@ -12,7 +12,7 @@
 
 assert_program_installed fio
 
-timeout_set 3 minutes
+timeout_set 45 seconds
 
 CHUNKSERVERS=5 \
 	USE_RAMDISK=YES \
@@ -76,7 +76,7 @@ EOF
 
 sudo /usr/bin/ganesha.nfsd -f ${info[mount0]}/ganesha.conf
 
-sleep 15
+assert_eventually 'showmount -e localhost'
 sudo mount -vvvv localhost:/data $TEMP_DIR/mnt/ganesha
 
 echo ""
diff --git a/tests/test_suites/GaneshaTests/test_nfs_ganesha_multi_export.sh b/tests/test_suites/GaneshaTests/test_nfs_ganesha_multi_export.sh
index 4da72e788..667249bb1 100644
--- a/tests/test_suites/GaneshaTests/test_nfs_ganesha_multi_export.sh
+++ b/tests/test_suites/GaneshaTests/test_nfs_ganesha_multi_export.sh
@@ -7,7 +7,7 @@
 # The path for the Ganesha daemon should match the installation folder inside the test.
 #
 
-timeout_set 2 minutes
+timeout_set 45 seconds
 
 CHUNKSERVERS=5 \
 	USE_RAMDISK=YES \
@@ -113,7 +113,7 @@ sudo /usr/bin/ganesha.nfsd -f ${info[mount0]}/ganesha.conf
 assert_eventually 'showmount -e localhost'
 
 for x in 1 2 99; do
-	sudo mount localhost:/e$x $TEMP_DIR/mnt/nfs$x
+	sudo mount -o v4.1 localhost:/e${x} ${TEMP_DIR}/mnt/nfs${x}
 done
 sudo mount -o nfsvers=4 localhost:/e97 $TEMP_DIR/mnt/nfs97
 
diff --git a/tests/test_suites/GaneshaTests/test_nfs_ganesha_vs_linux_client_dd_read.sh b/tests/test_suites/GaneshaTests/test_nfs_ganesha_vs_linux_client_dd_read.sh
index 478f336d4..0178cc27e 100644
--- a/tests/test_suites/GaneshaTests/test_nfs_ganesha_vs_linux_client_dd_read.sh
+++ b/tests/test_suites/GaneshaTests/test_nfs_ganesha_vs_linux_client_dd_read.sh
@@ -10,9 +10,8 @@
 # Verify the performance of Ganesha and Linux clients when performing read
 # with dd tool.
 #
-assert_program_installed fio
 
-timeout_set 3 minutes
+timeout_set 45 seconds
 
 CHUNKSERVERS=5 \
 	USE_RAMDISK=YES \
@@ -75,7 +74,7 @@ EOF
 
 sudo /usr/bin/ganesha.nfsd -f ${info[mount0]}/ganesha.conf
 
-sleep 15
+assert_eventually 'showmount -e localhost'
 sudo mount -vvvv localhost:/ $TEMP_DIR/mnt/ganesha
 
 # Generate the file to be read
diff --git a/tests/test_suites/GaneshaTests/test_nfs_ganesha_vs_linux_client_dd_write.sh b/tests/test_suites/GaneshaTests/test_nfs_ganesha_vs_linux_client_dd_write.sh
index cc006cf26..b0c5cd6ac 100644
--- a/tests/test_suites/GaneshaTests/test_nfs_ganesha_vs_linux_client_dd_write.sh
+++ b/tests/test_suites/GaneshaTests/test_nfs_ganesha_vs_linux_client_dd_write.sh
@@ -10,9 +10,8 @@
 # Verify the performance of Ganesha and Linux clients when performing write
 # with dd tool.
 #
-assert_program_installed fio
 
-timeout_set 3 minutes
+timeout_set 45 seconds
 
 CHUNKSERVERS=5 \
 	USE_RAMDISK=YES \
@@ -76,7 +75,7 @@ EOF
 
 sudo /usr/bin/ganesha.nfsd -f ${info[mount0]}/ganesha.conf
 
-sleep 15
+assert_eventually 'showmount -e localhost'
 sudo mount -vvvv localhost:/ $TEMP_DIR/mnt/ganesha
 
 echo ""
diff --git a/tests/test_suites/GaneshaTests/test_nfs_ganesha_vs_linux_client_random_mix_rw.sh b/tests/test_suites/GaneshaTests/test_nfs_ganesha_vs_linux_client_random_mix_rw.sh
index f701beaf9..188dc7349 100644
--- a/tests/test_suites/GaneshaTests/test_nfs_ganesha_vs_linux_client_random_mix_rw.sh
+++ b/tests/test_suites/GaneshaTests/test_nfs_ganesha_vs_linux_client_random_mix_rw.sh
@@ -12,7 +12,7 @@
 #
 assert_program_installed fio
 
-timeout_set 3 minutes
+timeout_set 45 seconds
 
 CHUNKSERVERS=5 \
 	USE_RAMDISK=YES \
@@ -76,7 +76,7 @@ EOF
 
 sudo /usr/bin/ganesha.nfsd -f ${info[mount0]}/ganesha.conf
 
-sleep 15
+assert_eventually 'showmount -e localhost'
 sudo mount -vvvv localhost:/ $TEMP_DIR/mnt/ganesha
 
 echo ""
diff --git a/tests/test_suites/GaneshaTests/test_nfs_ganesha_vs_linux_client_sequential_read.sh b/tests/test_suites/GaneshaTests/test_nfs_ganesha_vs_linux_client_sequential_read.sh
index 55a91e418..2eb8d4324 100644
--- a/tests/test_suites/GaneshaTests/test_nfs_ganesha_vs_linux_client_sequential_read.sh
+++ b/tests/test_suites/GaneshaTests/test_nfs_ganesha_vs_linux_client_sequential_read.sh
@@ -12,7 +12,7 @@
 #
 assert_program_installed fio
 
-timeout_set 3 minutes
+timeout_set 45 seconds
 
 CHUNKSERVERS=5 \
 	USE_RAMDISK=YES \
@@ -76,7 +76,7 @@ EOF
 
 sudo /usr/bin/ganesha.nfsd -f ${info[mount0]}/ganesha.conf
 
-sleep 15
+assert_eventually 'showmount -e localhost'
 sudo mount -vvvv localhost:/ $TEMP_DIR/mnt/ganesha
 
 echo ""
diff --git a/tests/test_suites/GaneshaTests/test_nfs_ganesha_vs_linux_client_sequential_write.sh b/tests/test_suites/GaneshaTests/test_nfs_ganesha_vs_linux_client_sequential_write.sh
index f1106c4f3..629fc01e9 100644
--- a/tests/test_suites/GaneshaTests/test_nfs_ganesha_vs_linux_client_sequential_write.sh
+++ b/tests/test_suites/GaneshaTests/test_nfs_ganesha_vs_linux_client_sequential_write.sh
@@ -12,7 +12,7 @@
 #
 assert_program_installed fio
 
-timeout_set 3 minutes
+timeout_set 45 seconds
 
 CHUNKSERVERS=5 \
 	USE_RAMDISK=YES \
@@ -76,7 +76,7 @@ EOF
 
 sudo /usr/bin/ganesha.nfsd -f ${info[mount0]}/ganesha.conf
 
-sleep 15
+assert_eventually 'showmount -e localhost'
 sudo mount -vvvv localhost:/ $TEMP_DIR/mnt/ganesha
 
 echo ""
diff --git a/tests/test_suites/LongSystemTests/test_custom_goals_generate_no_accidental_rebalancing.sh b/tests/test_suites/LongFailingTests/test_custom_goals_generate_no_accidental_rebalancing.sh
similarity index 96%
rename from tests/test_suites/LongSystemTests/test_custom_goals_generate_no_accidental_rebalancing.sh
rename to tests/test_suites/LongFailingTests/test_custom_goals_generate_no_accidental_rebalancing.sh
index 57e70677d..2f9140e36 100644
--- a/tests/test_suites/LongSystemTests/test_custom_goals_generate_no_accidental_rebalancing.sh
+++ b/tests/test_suites/LongFailingTests/test_custom_goals_generate_no_accidental_rebalancing.sh
@@ -6,7 +6,7 @@ touch "$replication_log"
 
 # Prints the current human-readable status of chunkservers (disk usage, number of chunks, etc)
 status() {
-	saunafs-probe list-chunkservers localhost "${info[matocl]}"
+	saunafs-admin list-chunkservers localhost "${info[matocl]}"
 }
 
 # Set up an installation with 6 chunkservers on loop disks, with 4 different labels.
diff --git a/tests/test_suites/LongSystemTests/test_custom_goals_rebalancing_case_1.sh b/tests/test_suites/LongFailingTests/test_custom_goals_rebalancing_case_1.sh
similarity index 100%
rename from tests/test_suites/LongSystemTests/test_custom_goals_rebalancing_case_1.sh
rename to tests/test_suites/LongFailingTests/test_custom_goals_rebalancing_case_1.sh
diff --git a/tests/test_suites/LongSystemTests/test_custom_goals_rebalancing_case_2.sh b/tests/test_suites/LongFailingTests/test_custom_goals_rebalancing_case_2.sh
similarity index 100%
rename from tests/test_suites/LongSystemTests/test_custom_goals_rebalancing_case_2.sh
rename to tests/test_suites/LongFailingTests/test_custom_goals_rebalancing_case_2.sh
diff --git a/tests/test_suites/LongSystemTests/test_custom_goals_rebalancing_case_3.sh b/tests/test_suites/LongFailingTests/test_custom_goals_rebalancing_case_3.sh
similarity index 100%
rename from tests/test_suites/LongSystemTests/test_custom_goals_rebalancing_case_3.sh
rename to tests/test_suites/LongFailingTests/test_custom_goals_rebalancing_case_3.sh
diff --git a/tests/test_suites/LongSystemTests/test_data_integrity.sh b/tests/test_suites/LongFailingTests/test_data_integrity.sh
similarity index 100%
rename from tests/test_suites/LongSystemTests/test_data_integrity.sh
rename to tests/test_suites/LongFailingTests/test_data_integrity.sh
diff --git a/tests/test_suites/LongSystemTests/test_xor_overwriting_faulty_chunkservers.sh b/tests/test_suites/LongFailingTests/test_xor_overwriting_faulty_chunkservers.sh
similarity index 100%
rename from tests/test_suites/LongSystemTests/test_xor_overwriting_faulty_chunkservers.sh
rename to tests/test_suites/LongFailingTests/test_xor_overwriting_faulty_chunkservers.sh
diff --git a/tests/test_suites/LongSystemTests/test_xor_overwriting_faulty_master.sh b/tests/test_suites/LongFailingTests/test_xor_overwriting_faulty_master.sh
similarity index 100%
rename from tests/test_suites/LongSystemTests/test_xor_overwriting_faulty_master.sh
rename to tests/test_suites/LongFailingTests/test_xor_overwriting_faulty_master.sh
diff --git a/tests/test_suites/LongSystemTests/test_xor_overwriting_faulty_master_and_chunkservers.sh b/tests/test_suites/LongFailingTests/test_xor_overwriting_faulty_master_and_chunkservers.sh
similarity index 100%
rename from tests/test_suites/LongSystemTests/test_xor_overwriting_faulty_master_and_chunkservers.sh
rename to tests/test_suites/LongFailingTests/test_xor_overwriting_faulty_master_and_chunkservers.sh
diff --git a/tests/test_suites/ShortSystemTests/test_advanced_readahead_parameters.sh b/tests/test_suites/LongManualTests/test_advanced_readahead_parameters.sh
similarity index 100%
rename from tests/test_suites/ShortSystemTests/test_advanced_readahead_parameters.sh
rename to tests/test_suites/LongManualTests/test_advanced_readahead_parameters.sh
diff --git a/tests/test_suites/LongSystemTests/test_compilation_and_rsync.sh b/tests/test_suites/LongSystemTests/test_build_and_rsync.sh
similarity index 71%
rename from tests/test_suites/LongSystemTests/test_compilation_and_rsync.sh
rename to tests/test_suites/LongSystemTests/test_build_and_rsync.sh
index 4ea8ec804..d8929c813 100644
--- a/tests/test_suites/LongSystemTests/test_compilation_and_rsync.sh
+++ b/tests/test_suites/LongSystemTests/test_build_and_rsync.sh
@@ -10,11 +10,11 @@ PARALLEL_JOBS=$(get_nproc_clamped_between ${MINIMUM_PARALLEL_JOBS} ${MAXIMUM_PAR
 test_worker() {
 	export MESSAGE="Testing directory $1"
 	cd "$1"
-	assertlocal_success git clone https://github.com/saunafs/saunafs.git
+	assertlocal_success git clone https://github.com/leil-io/saunafs.git
 	mkdir saunafs/build
 	cd saunafs/build
-	assertlocal_success cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=../install
-	make -j${PARALLEL_JOBS} install
+	assert_success cmake .. -G 'Unix Makefiles' -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_INSTALL_PREFIX=../install
+	assert_success make -j${PARALLEL_JOBS} install
 	cd ../..
 	assertlocal_success rsync -a saunafs/ copy_saunafs
 	find saunafs -type f | while read file; do
@@ -24,7 +24,7 @@ test_worker() {
 
 CHUNKSERVERS=3 \
 	MOUNTS=1 \
-	MOUNT_EXTRA_CONFIG="sfscachemode=NEVER" \
+	MOUNT_EXTRA_CONFIG="sfscachemode=NEVER|cacheexpirationtime=0" \
 	setup_local_empty_saunafs info
 
 cd "${info[mount0]}"
diff --git a/tests/test_suites/LongSystemTests/test_build_saunafs.sh b/tests/test_suites/LongSystemTests/test_build_saunafs.sh
index 468ac34a0..7762a811b 100644
--- a/tests/test_suites/LongSystemTests/test_build_saunafs.sh
+++ b/tests/test_suites/LongSystemTests/test_build_saunafs.sh
@@ -1,7 +1,9 @@
 timeout_set 10 minutes
+assert_program_installed git
+assert_program_installed cmake
 
 CHUNKSERVERS=8 \
-	MOUNT_EXTRA_CONFIG="sfscachemode=NEVER" \
+	MOUNT_EXTRA_CONFIG="sfscachemode=NEVER|cacheexpirationtime=0" \
 	CHUNKSERVER_EXTRA_CONFIG="READ_AHEAD_KB = 1024|MAX_READ_BEHIND_KB = 2048"
 	setup_local_empty_saunafs info
 
@@ -16,11 +18,11 @@ saunafs setgoal ec43 work
 
 cd work
 
-git clone "https://github.com/saunafs/saunafs.git"
+assert_success git clone "https://github.com/leil-io/saunafs.git"
 
 cd saunafs
 
 mkdir -p build
 cd build
-cmake .. -DCMAKE_BUILD_TYPE=Debug
-make -j${PARALLEL_JOBS}
+assert_success cmake .. -G 'Unix Makefiles' -DCMAKE_BUILD_TYPE=RelWithDebInfo
+assert_success make -j${PARALLEL_JOBS}
diff --git a/tests/test_suites/LongSystemTests/test_auto_recovery_build.sh b/tests/test_suites/LongSystemTests/test_build_while_auto_recovery.sh
similarity index 74%
rename from tests/test_suites/LongSystemTests/test_auto_recovery_build.sh
rename to tests/test_suites/LongSystemTests/test_build_while_auto_recovery.sh
index 1adec6a59..7a9e3ad6c 100644
--- a/tests/test_suites/LongSystemTests/test_auto_recovery_build.sh
+++ b/tests/test_suites/LongSystemTests/test_build_while_auto_recovery.sh
@@ -2,11 +2,11 @@ timeout_set 1 hour
 assert_program_installed git
 assert_program_installed cmake
 
-CHUNKSERVERS=2 \
+CHUNKSERVERS=3 \
 	MOUNTS=1 \
 	CHUNKSERVER_EXTRA_CONFIG="MASTER_RECONNECTION_DELAY = 1" \
 	MASTER_EXTRA_CONFIG="AUTO_RECOVERY = 1"\
-	MOUNT_EXTRA_CONFIG="sfscachemode=NEVER" \
+	MOUNT_EXTRA_CONFIG="sfscachemode=NEVER|cacheexpirationtime=0" \
 	setup_local_empty_saunafs info
 
 MINIMUM_PARALLEL_JOBS=5
@@ -26,9 +26,9 @@ master_kill_loop() {
 ( master_kill_loop & )
 
 cd "${info[mount0]}"
-assert_success git clone https://github.com/saunafs/saunafs.git
+assert_success git clone https://github.com/leil-io/saunafs.git
 saunafs setgoal -r 2 saunafs
 mkdir saunafs/build
 cd saunafs/build
-assert_success cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=../install
+assert_success cmake .. -G 'Unix Makefiles' -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=../install
 assert_success make -j${PARALLEL_JOBS} install
diff --git a/tests/test_suites/LongSystemTests/test_changing_master_during_build.sh b/tests/test_suites/LongSystemTests/test_build_while_changing_master.sh
similarity index 87%
rename from tests/test_suites/LongSystemTests/test_changing_master_during_build.sh
rename to tests/test_suites/LongSystemTests/test_build_while_changing_master.sh
index 14590cbf8..61d25fe85 100644
--- a/tests/test_suites/LongSystemTests/test_changing_master_during_build.sh
+++ b/tests/test_suites/LongSystemTests/test_build_while_changing_master.sh
@@ -5,7 +5,7 @@ MASTERSERVERS=$metaservers_nr \
 	CHUNKSERVERS=2 \
 	CHUNKSERVER_EXTRA_CONFIG="MASTER_RECONNECTION_DELAY = 1" \
 	SFSEXPORTS_EXTRA_OPTIONS="allcanchangequota" \
-	MOUNT_EXTRA_CONFIG="sfscachemode=NEVER" \
+	MOUNT_EXTRA_CONFIG="sfscachemode=NEVER|cacheexpirationtime=0" \
 	MASTER_EXTRA_CONFIG="MAGIC_AUTO_FILE_REPAIR = 1" \
 	setup_local_empty_saunafs info
 
@@ -55,9 +55,9 @@ master_kill_loop() {
 master_kill_loop &
 
 cd "${info[mount0]}"
-assert_success git clone https://github.com/saunafs/saunafs.git
+assert_success git clone https://github.com/leil-io/saunafs.git
 saunafs setgoal -r 2 saunafs
 mkdir saunafs/build
 cd saunafs/build
-assert_success cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=../install
+assert_success cmake .. -G 'Unix Makefiles' -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_INSTALL_PREFIX=../install
 assert_success make -j${PARALLEL_JOBS} install
diff --git a/tests/test_suites/LongSystemTests/test_ec_reading_consistency_faulty_chunkservers.sh b/tests/test_suites/LongSystemTests/test_ec_reading_consistency_faulty_chunkservers.sh
new file mode 100644
index 000000000..7245ba6ac
--- /dev/null
+++ b/tests/test_suites/LongSystemTests/test_ec_reading_consistency_faulty_chunkservers.sh
@@ -0,0 +1,1 @@
+FAULTY_CHUNKSERVERS=YES source test_suites/TestTemplates/test_ec_reading_consistency.inc
diff --git a/tests/test_suites/LongSystemTests/test_ec_reading_consistency_faulty_master.sh b/tests/test_suites/LongSystemTests/test_ec_reading_consistency_faulty_master.sh
new file mode 100644
index 000000000..4dadd3928
--- /dev/null
+++ b/tests/test_suites/LongSystemTests/test_ec_reading_consistency_faulty_master.sh
@@ -0,0 +1,1 @@
+FAULTY_MASTER=YES source test_suites/TestTemplates/test_ec_reading_consistency.inc
diff --git a/tests/test_suites/LongSystemTests/test_ec_reading_consistency_faulty_master_and_chunkservers.sh b/tests/test_suites/LongSystemTests/test_ec_reading_consistency_faulty_master_and_chunkservers.sh
new file mode 100644
index 000000000..cebf51266
--- /dev/null
+++ b/tests/test_suites/LongSystemTests/test_ec_reading_consistency_faulty_master_and_chunkservers.sh
@@ -0,0 +1,2 @@
+FAULTY_MASTER=YES \
+	FAULTY_CHUNKSERVERS=YES source test_suites/TestTemplates/test_ec_reading_consistency.inc
diff --git a/tests/test_suites/LongSystemTests/test_ec_writing_faulty_chunkservers.sh b/tests/test_suites/LongSystemTests/test_ec_writing_faulty_chunkservers.sh
new file mode 100644
index 000000000..5c51dca1e
--- /dev/null
+++ b/tests/test_suites/LongSystemTests/test_ec_writing_faulty_chunkservers.sh
@@ -0,0 +1,1 @@
+FAULTY_CHUNKSERVERS=YES source test_suites/TestTemplates/test_ec_writing.inc
diff --git a/tests/test_suites/LongSystemTests/test_ec_writing_faulty_master.sh b/tests/test_suites/LongSystemTests/test_ec_writing_faulty_master.sh
new file mode 100644
index 000000000..909b223b7
--- /dev/null
+++ b/tests/test_suites/LongSystemTests/test_ec_writing_faulty_master.sh
@@ -0,0 +1,1 @@
+FAULTY_MASTER=YES source test_suites/TestTemplates/test_ec_writing.inc
diff --git a/tests/test_suites/LongSystemTests/test_ec_writing_faulty_master_and_chunkservers.sh b/tests/test_suites/LongSystemTests/test_ec_writing_faulty_master_and_chunkservers.sh
new file mode 100644
index 000000000..d0e753ede
--- /dev/null
+++ b/tests/test_suites/LongSystemTests/test_ec_writing_faulty_master_and_chunkservers.sh
@@ -0,0 +1,2 @@
+FAULTY_MASTER=YES \
+	FAULTY_CHUNKSERVERS=YES source test_suites/TestTemplates/test_ec_writing.inc
diff --git a/tests/test_suites/LongSystemTests/test_xor_reading_consistency_faulty_chunkservers.sh b/tests/test_suites/LongSystemTests/test_xor_reading_consistency_faulty_chunkservers.sh
deleted file mode 100644
index 16f9540d6..000000000
--- a/tests/test_suites/LongSystemTests/test_xor_reading_consistency_faulty_chunkservers.sh
+++ /dev/null
@@ -1,1 +0,0 @@
-FAULTY_CHUNKSERVERS=YES source test_suites/TestTemplates/test_xor_reading_consistency.inc
diff --git a/tests/test_suites/LongSystemTests/test_xor_reading_consistency_faulty_master.sh b/tests/test_suites/LongSystemTests/test_xor_reading_consistency_faulty_master.sh
deleted file mode 100644
index 4be1f02e3..000000000
--- a/tests/test_suites/LongSystemTests/test_xor_reading_consistency_faulty_master.sh
+++ /dev/null
@@ -1,1 +0,0 @@
-FAULTY_MASTER=YES source test_suites/TestTemplates/test_xor_reading_consistency.inc
diff --git a/tests/test_suites/LongSystemTests/test_xor_reading_consistency_faulty_master_and_chunkservers.sh b/tests/test_suites/LongSystemTests/test_xor_reading_consistency_faulty_master_and_chunkservers.sh
deleted file mode 100644
index 1355704e1..000000000
--- a/tests/test_suites/LongSystemTests/test_xor_reading_consistency_faulty_master_and_chunkservers.sh
+++ /dev/null
@@ -1,2 +0,0 @@
-FAULTY_MASTER=YES \
-	FAULTY_CHUNKSERVERS=YES source test_suites/TestTemplates/test_xor_reading_consistency.inc
diff --git a/tests/test_suites/LongSystemTests/test_xor_writing_faulty_chunkservers.sh b/tests/test_suites/LongSystemTests/test_xor_writing_faulty_chunkservers.sh
deleted file mode 100644
index e652c8911..000000000
--- a/tests/test_suites/LongSystemTests/test_xor_writing_faulty_chunkservers.sh
+++ /dev/null
@@ -1,1 +0,0 @@
-FAULTY_CHUNKSERVERS=YES source test_suites/TestTemplates/test_xor_writing.inc
diff --git a/tests/test_suites/LongSystemTests/test_xor_writing_faulty_master.sh b/tests/test_suites/LongSystemTests/test_xor_writing_faulty_master.sh
deleted file mode 100644
index 257e3ad56..000000000
--- a/tests/test_suites/LongSystemTests/test_xor_writing_faulty_master.sh
+++ /dev/null
@@ -1,1 +0,0 @@
-FAULTY_MASTER=YES source test_suites/TestTemplates/test_xor_writing.inc
diff --git a/tests/test_suites/LongSystemTests/test_xor_writing_faulty_master_and_chunkservers.sh b/tests/test_suites/LongSystemTests/test_xor_writing_faulty_master_and_chunkservers.sh
deleted file mode 100644
index 1a97ddbd1..000000000
--- a/tests/test_suites/LongSystemTests/test_xor_writing_faulty_master_and_chunkservers.sh
+++ /dev/null
@@ -1,2 +0,0 @@
-FAULTY_MASTER=YES \
-	FAULTY_CHUNKSERVERS=YES source test_suites/TestTemplates/test_xor_writing.inc
diff --git a/tests/test_suites/LongSystemTests/test_saunafs_upgrade_overwrite_file.sh b/tests/test_suites/LongUpgradeTests/test_saunafs_upgrade_overwrite_file.sh
similarity index 100%
rename from tests/test_suites/LongSystemTests/test_saunafs_upgrade_overwrite_file.sh
rename to tests/test_suites/LongUpgradeTests/test_saunafs_upgrade_overwrite_file.sh
diff --git a/tests/test_suites/LongSystemTests/test_saunafs_upgrade_overwrite_file_legacy_chunkformat.sh b/tests/test_suites/LongUpgradeTests/test_saunafs_upgrade_overwrite_file_legacy_chunkformat.sh
similarity index 100%
rename from tests/test_suites/LongSystemTests/test_saunafs_upgrade_overwrite_file_legacy_chunkformat.sh
rename to tests/test_suites/LongUpgradeTests/test_saunafs_upgrade_overwrite_file_legacy_chunkformat.sh
diff --git a/tests/test_suites/LongSystemTests/test_saunafs_upgrade_undergoal_chunks_ec21.sh b/tests/test_suites/LongUpgradeTests/test_saunafs_upgrade_undergoal_chunks_ec21.sh
similarity index 100%
rename from tests/test_suites/LongSystemTests/test_saunafs_upgrade_undergoal_chunks_ec21.sh
rename to tests/test_suites/LongUpgradeTests/test_saunafs_upgrade_undergoal_chunks_ec21.sh
diff --git a/tests/test_suites/LongSystemTests/test_saunafs_upgrade_undergoal_chunks_ec21_legacy_chunkformat.sh b/tests/test_suites/LongUpgradeTests/test_saunafs_upgrade_undergoal_chunks_ec21_legacy_chunkformat.sh
similarity index 100%
rename from tests/test_suites/LongSystemTests/test_saunafs_upgrade_undergoal_chunks_ec21_legacy_chunkformat.sh
rename to tests/test_suites/LongUpgradeTests/test_saunafs_upgrade_undergoal_chunks_ec21_legacy_chunkformat.sh
diff --git a/tests/test_suites/LongSystemTests/test_saunafs_upgrade_undergoal_chunks_ec32.sh b/tests/test_suites/LongUpgradeTests/test_saunafs_upgrade_undergoal_chunks_ec32.sh
similarity index 100%
rename from tests/test_suites/LongSystemTests/test_saunafs_upgrade_undergoal_chunks_ec32.sh
rename to tests/test_suites/LongUpgradeTests/test_saunafs_upgrade_undergoal_chunks_ec32.sh
diff --git a/tests/test_suites/LongSystemTests/test_saunafs_upgrade_undergoal_chunks_ec32_legacy_chunkformat.sh b/tests/test_suites/LongUpgradeTests/test_saunafs_upgrade_undergoal_chunks_ec32_legacy_chunkformat.sh
similarity index 100%
rename from tests/test_suites/LongSystemTests/test_saunafs_upgrade_undergoal_chunks_ec32_legacy_chunkformat.sh
rename to tests/test_suites/LongUpgradeTests/test_saunafs_upgrade_undergoal_chunks_ec32_legacy_chunkformat.sh
diff --git a/tests/test_suites/SanityChecks/test_xor_read_part_1_corrupted.sh b/tests/test_suites/SanityChecks/test_ec_read_part_1_corrupted.sh
similarity index 75%
rename from tests/test_suites/SanityChecks/test_xor_read_part_1_corrupted.sh
rename to tests/test_suites/SanityChecks/test_ec_read_part_1_corrupted.sh
index 34b5f2168..ac1a39728 100644
--- a/tests/test_suites/SanityChecks/test_xor_read_part_1_corrupted.sh
+++ b/tests/test_suites/SanityChecks/test_ec_read_part_1_corrupted.sh
@@ -3,19 +3,20 @@ timeout_set 45 seconds
 CHUNKSERVERS=4 \
 	DISK_PER_CHUNKSERVER=1 \
 	MOUNT_EXTRA_CONFIG="sfscachemode=NEVER" \
+	MASTER_CUSTOM_GOALS="10 ec_3_1: \$ec(3,1)" \
 	USE_RAMDISK=YES \
 	setup_local_empty_saunafs info
 
 dir="${info[mount0]}/dir"
 mkdir "$dir"
-saunafs setgoal xor3 "$dir"
+saunafs setgoal ec_3_1 "$dir"
 FILE_SIZE=6M file-generate "$dir/file"
 
 # Corrupt data in part 1 of the chunk
 csid=$(find_first_chunkserver_with_chunks_matching \
-	"chunk_xor_1_of_3*${chunk_metadata_extension}")
+	"chunk_ec2_1_of_3*${chunk_metadata_extension}")
 hdd=$(get_metadata_path "${info[chunkserver${csid}_hdd]}")
-chunk=$(find "$hdd" -name "chunk_xor_1_of_3_*${chunk_metadata_extension}")
+chunk=$(find "$hdd" -name "chunk_ec2_1_of_3_*${chunk_metadata_extension}")
 echo aaaa | dd of="$chunk" bs=1 count=4 seek=6k conv=notrunc
 
 if ! file-validate "$dir/file"; then
diff --git a/tests/test_suites/ShortSystemTests/test_cgi_validate_html.sh b/tests/test_suites/ShortFailingTests/test_cgi_validate_html.sh
similarity index 100%
rename from tests/test_suites/ShortSystemTests/test_cgi_validate_html.sh
rename to tests/test_suites/ShortFailingTests/test_cgi_validate_html.sh
diff --git a/tests/test_suites/ShortSystemTests/test_richacl.sh b/tests/test_suites/ShortFailingTests/test_richacl.sh
similarity index 100%
rename from tests/test_suites/ShortSystemTests/test_richacl.sh
rename to tests/test_suites/ShortFailingTests/test_richacl.sh
diff --git a/tests/test_suites/ShortSystemTests/test_sockets_leak_check.sh b/tests/test_suites/ShortFailingTests/test_sockets_leak_check.sh
similarity index 100%
rename from tests/test_suites/ShortSystemTests/test_sockets_leak_check.sh
rename to tests/test_suites/ShortFailingTests/test_sockets_leak_check.sh
diff --git a/tests/test_suites/ShortSystemTests/test_whole_file_locks.sh b/tests/test_suites/ShortFailingTests/test_whole_file_locks.sh
similarity index 100%
rename from tests/test_suites/ShortSystemTests/test_whole_file_locks.sh
rename to tests/test_suites/ShortFailingTests/test_whole_file_locks.sh
diff --git a/tests/test_suites/ShortSystemTests/test_probe_chunks_health.sh b/tests/test_suites/ShortSystemTests/test_admin_chunks_health.sh
similarity index 88%
rename from tests/test_suites/ShortSystemTests/test_probe_chunks_health.sh
rename to tests/test_suites/ShortSystemTests/test_admin_chunks_health.sh
index fa0e96610..54a9e0fa0 100644
--- a/tests/test_suites/ShortSystemTests/test_probe_chunks_health.sh
+++ b/tests/test_suites/ShortSystemTests/test_admin_chunks_health.sh
@@ -16,7 +16,7 @@ for goal in $goals; do
 done
 
 export MESSAGE="Veryfing health report with all the chunkservers up"
-health4=$(saunafs-probe chunks-health --porcelain localhost "${info[matocl]}")
+health4=$(saunafs-admin chunks-health --porcelain localhost "${info[matocl]}")
 expect_equals 4 $(awk '/AVA/ {chunks += ($3 + $4 + $5)} END {print chunks}' <<< "$health4")
 for goal in $goals; do
 	expect_awk_finds "/AVA $goal 1 0 0/" "$health4"
@@ -37,7 +37,7 @@ csid4=$(( (csid3 + 1) % 4 ))
 export MESSAGE="Veryfing health report one chunkserver down and chunk with goal 2 endangered"
 saunafs_chunkserver_daemon $csid1 stop
 saunafs_wait_for_ready_chunkservers 3
-health3=$(saunafs-probe chunks-health --porcelain localhost "${info[matocl]}")
+health3=$(saunafs-admin chunks-health --porcelain localhost "${info[matocl]}")
 expect_equals 4 $(awk '/AVA/ {chunks += ($3 + $4 + $5)} END {print chunks}' <<< "$health3")
 expect_awk_finds '/AVA 2 0 1 0/' "$health3"
 expect_awk_finds '/AVA xor3 0 1 0/' "$health3"
@@ -50,7 +50,7 @@ expect_awk_finds_no '/DEL [xor0-9]+ [0-9]+ .*[1-9]/' "$health3"
 export MESSAGE="Veryfing health report with two out of four chunkservers down"
 saunafs_chunkserver_daemon $csid2 stop
 saunafs_wait_for_ready_chunkservers 2
-health2=$(saunafs-probe chunks-health --porcelain localhost "${info[matocl]}")
+health2=$(saunafs-admin chunks-health --porcelain localhost "${info[matocl]}")
 expect_equals 4 $(awk '/AVA/ {chunks += ($3 + $4 + $5)} END {print chunks}' <<< "$health2")
 expect_awk_finds '/AVA xor3 0 0 1/' "$health2"
 expect_awk_finds_no '/REP xor/ && $3 > 0' "$health2"
@@ -61,7 +61,7 @@ expect_awk_finds_no '/DEL [xor0-9]+ [0-9]+ .*[1-9]/' "$health2"
 export MESSAGE="Veryfing health report with three out of four chunkservers down"
 saunafs_chunkserver_daemon $csid3 stop
 saunafs_wait_for_ready_chunkservers 1
-health1=$(saunafs-probe chunks-health --porcelain localhost "${info[matocl]}")
+health1=$(saunafs-admin chunks-health --porcelain localhost "${info[matocl]}")
 expect_equals 4 $(awk '/AVA/ {chunks += ($3 + $4 + $5)} END {print chunks}' <<< "$health1")
 expect_awk_finds_no '/AVA/ && $3 > 0' "$health1"
 expect_awk_finds_no '/AVA xor/ && $4 > 0' "$health1"
@@ -73,7 +73,7 @@ expect_awk_finds_no '/DEL [xor0-9]+ [0-9]+ .*[1-9]/' "$health1"
 export MESSAGE="Veryfing health report with all chunkservers down"
 saunafs_chunkserver_daemon $csid4 stop
 saunafs_wait_for_ready_chunkservers 0
-health0=$(saunafs-probe chunks-health --porcelain localhost "${info[matocl]}")
+health0=$(saunafs-admin chunks-health --porcelain localhost "${info[matocl]}")
 expect_equals 4 $(awk '/AVA/ {chunks += ($3 + $4 + $5)} END {print chunks}' <<< "$health0")
 expect_awk_finds_no '/AVA/ && $3 > 0' "$health0"
 expect_awk_finds_no '/AVA/ && $4 > 0' "$health0"
@@ -88,19 +88,19 @@ expect_awk_finds_no '/DEL [xor0-9]+ [0-9]+ .*[1-9]/' "$health0"
 export MESSAGE="Veryfing health report with one out of four chunkservers up again"
 saunafs_chunkserver_daemon $csid4 start
 saunafs_wait_for_ready_chunkservers 1
-expect_equals "$health1" "$(saunafs-probe chunks-health --porcelain localhost "${info[matocl]}")"
+expect_equals "$health1" "$(saunafs-admin chunks-health --porcelain localhost "${info[matocl]}")"
 
 export MESSAGE="Veryfing health report with two out of four chunkservers up again"
 saunafs_chunkserver_daemon $csid3 start
 saunafs_wait_for_ready_chunkservers 2
-expect_equals "$health2" "$(saunafs-probe chunks-health --porcelain localhost "${info[matocl]}")"
+expect_equals "$health2" "$(saunafs-admin chunks-health --porcelain localhost "${info[matocl]}")"
 
 export MESSAGE="Veryfing health report with three out of four chunkservers up again"
 saunafs_chunkserver_daemon $csid2 start
 saunafs_wait_for_ready_chunkservers 3
-expect_equals "$health3" "$(saunafs-probe chunks-health --porcelain localhost "${info[matocl]}")"
+expect_equals "$health3" "$(saunafs-admin chunks-health --porcelain localhost "${info[matocl]}")"
 
 export MESSAGE="Veryfing health report with all the chunkservers up again"
 saunafs_chunkserver_daemon $csid1 start
 saunafs_wait_for_ready_chunkservers 4
-expect_equals "$health4" "$(saunafs-probe chunks-health --porcelain localhost "${info[matocl]}")"
+expect_equals "$health4" "$(saunafs-admin chunks-health --porcelain localhost "${info[matocl]}")"
diff --git a/tests/test_suites/ShortSystemTests/test_probe_chunks_health_custom_goals.sh b/tests/test_suites/ShortSystemTests/test_admin_chunks_health_custom_goals.sh
similarity index 92%
rename from tests/test_suites/ShortSystemTests/test_probe_chunks_health_custom_goals.sh
rename to tests/test_suites/ShortSystemTests/test_admin_chunks_health_custom_goals.sh
index d20268807..f821a9ecc 100644
--- a/tests/test_suites/ShortSystemTests/test_probe_chunks_health_custom_goals.sh
+++ b/tests/test_suites/ShortSystemTests/test_admin_chunks_health_custom_goals.sh
@@ -18,12 +18,12 @@ for goal in $goals; do
 done
 
 function chunks-health-trimmed() {
-	saunafs-probe chunks-health --porcelain localhost "${info[matocl]}" \
+	saunafs-admin chunks-health --porcelain localhost "${info[matocl]}" \
 		| egrep -o "^... [234AB_]+ (0 )*[1-9]+" \
 		| tr "\n" "|" \
 		| sed 's/|$//'
 }
-# Check if the probe outputs proper values
+# Check if the admin outputs proper values
 first_output="AVA 2 1|AVA 3 1|AVA 4 1|REP 2 1|REP 3 0 1|REP 4 0 0 1|DEL 2 1|DEL 3 1|DEL 4 1"
 expect_equals "$first_output" "$(chunks-health-trimmed)"
 
@@ -39,7 +39,7 @@ output+=([AB_]="AVA AB_ 3|REP AB_ 0 3|DEL AB_ 3")
 output+=([BB_]="AVA BB_ 3|REP BB_ 0 0 3|DEL BB_ 0 3")
 output+=([B__]="AVA B__ 3|REP B__ 0 3|DEL B__ 3")
 
-# Check if the probe outputs proper values when we change
+# Check if the admin outputs proper values when we change
 # files goal back and forth
 for new_goal in "${!output[@]}"; do
 	MESSAGE="Testing goal ${new_goal}"
diff --git a/tests/test_suites/ShortSystemTests/test_probe_info.sh b/tests/test_suites/ShortSystemTests/test_admin_info.sh
similarity index 87%
rename from tests/test_suites/ShortSystemTests/test_probe_info.sh
rename to tests/test_suites/ShortSystemTests/test_admin_info.sh
index b3da810e5..f697d7c85 100644
--- a/tests/test_suites/ShortSystemTests/test_probe_info.sh
+++ b/tests/test_suites/ShortSystemTests/test_admin_info.sh
@@ -14,5 +14,6 @@ done
 
 rm dir_3/file
 rm dir_xor2/file
+
 expect_equals "$SAUNAFS_VERSION 2 0 0 9 5 4 0 4 7 7" \
-	"$(saunafs-probe info --porcelain localhost "${info[matocl]}" | cut -d' ' -f 1,6-)"
+	"$(saunafs-admin info --porcelain localhost "${info[matocl]}" | cut -d' ' -f 1,6-)"
diff --git a/tests/test_suites/ShortSystemTests/test_probe_iolimits_status.sh b/tests/test_suites/ShortSystemTests/test_admin_iolimits_status.sh
similarity index 92%
rename from tests/test_suites/ShortSystemTests/test_probe_iolimits_status.sh
rename to tests/test_suites/ShortSystemTests/test_admin_iolimits_status.sh
index a9be7e4dc..a86d35201 100644
--- a/tests/test_suites/ShortSystemTests/test_probe_iolimits_status.sh
+++ b/tests/test_suites/ShortSystemTests/test_admin_iolimits_status.sh
@@ -2,7 +2,7 @@ iolimits="$TEMP_DIR/iolimits.cfg"
 printf "subsystem blkio\nlimit /aaa 203\nlimit unclassified 301\n" > "$iolimits"
 
 status() {
-	saunafs-probe iolimits-status --porcelain localhost "${info[matocl]}"
+	saunafs-admin iolimits-status --porcelain localhost "${info[matocl]}"
 }
 
 CHUNKSERVERS=1 \
diff --git a/tests/test_suites/ShortSystemTests/test_probe_list_chunkservers.sh b/tests/test_suites/ShortSystemTests/test_admin_list_chunkservers.sh
similarity index 91%
rename from tests/test_suites/ShortSystemTests/test_probe_list_chunkservers.sh
rename to tests/test_suites/ShortSystemTests/test_admin_list_chunkservers.sh
index f8d32fe67..d24103add 100644
--- a/tests/test_suites/ShortSystemTests/test_probe_list_chunkservers.sh
+++ b/tests/test_suites/ShortSystemTests/test_admin_list_chunkservers.sh
@@ -14,10 +14,10 @@ for goal in $goals; do
 done
 
 list_chunkservers() {
-	saunafs-probe list-chunkservers --porcelain localhost "${info[matocl]}"
+	saunafs-admin list-chunkservers --porcelain localhost "${info[matocl]}"
 }
 
-# Wait for all chunkservers to report chunk creation and see what saunafs-probe prints then
+# Wait for all chunkservers to report chunk creation and see what saunafs-admin prints then
 export MESSAGE="Veryfing chunkservers list with all the chunkservers up"
 expect_eventually_prints 7 'list_chunkservers | awk "{chunks += \$3} END {print chunks}"'
 cslist=$(list_chunkservers)
@@ -28,7 +28,7 @@ expect_equals 4 $(wc -l <<< "$cslist")
 expect_equals 4 $(awk -v version="$SAUNAFS_VERSION" '$2 == version' <<< "$cslist" | wc -l)
 expect_equals "cs0 cs1 cs2 cs3" "$(awk '{print $10}' <<< "$cslist" | sort | xargs echo)"
 
-# Turn off one chunkserver and see what saunafs-probe prints now
+# Turn off one chunkserver and see what saunafs-admin prints now
 export MESSAGE="Veryfing chunkservers list with one chunkserver down"
 saunafs_chunkserver_daemon 0 stop
 saunafs_wait_for_ready_chunkservers 3
diff --git a/tests/test_suites/ShortSystemTests/test_probe_list_disks.sh b/tests/test_suites/ShortSystemTests/test_admin_list_disks.sh
similarity index 93%
rename from tests/test_suites/ShortSystemTests/test_probe_list_disks.sh
rename to tests/test_suites/ShortSystemTests/test_admin_list_disks.sh
index 50d116c12..2a3bf8063 100644
--- a/tests/test_suites/ShortSystemTests/test_probe_list_disks.sh
+++ b/tests/test_suites/ShortSystemTests/test_admin_list_disks.sh
@@ -12,7 +12,7 @@ for goal in xor3 3; do
 	FILE_SIZE=60M file-generate dir_$goal/file
 done
 
-disks=$(saunafs-probe list-disks --porcelain localhost "${info[matocl]}")
+disks=$(saunafs-admin list-disks --porcelain localhost "${info[matocl]}")
 expect_equals 12 $(wc -l <<< "$disks")
 for i in {0..3}; do
 	cs_data="$(grep ":${info[chunkserver${i}_port]} " <<< "$disks")"
diff --git a/tests/test_suites/ShortSystemTests/test_probe_list_metadataservers.sh b/tests/test_suites/ShortSystemTests/test_admin_list_metadataservers.sh
similarity index 94%
rename from tests/test_suites/ShortSystemTests/test_probe_list_metadataservers.sh
rename to tests/test_suites/ShortSystemTests/test_admin_list_metadataservers.sh
index 3931b4c7c..929a01407 100644
--- a/tests/test_suites/ShortSystemTests/test_probe_list_metadataservers.sh
+++ b/tests/test_suites/ShortSystemTests/test_admin_list_metadataservers.sh
@@ -3,7 +3,7 @@ MASTERSERVERS=2 \
 	setup_local_empty_saunafs info
 
 list_metadata_servers() {
-	saunafs_probe_master list-metadataservers --porcelain
+	saunafs_admin_master_no_password list-metadataservers --porcelain
 }
 
 nr="[0-9]+"
diff --git a/tests/test_suites/ShortSystemTests/test_probe_list_mounts.sh b/tests/test_suites/ShortSystemTests/test_admin_list_mounts.sh
similarity index 89%
rename from tests/test_suites/ShortSystemTests/test_probe_list_mounts.sh
rename to tests/test_suites/ShortSystemTests/test_admin_list_mounts.sh
index 9a39e554e..e2a230d85 100644
--- a/tests/test_suites/ShortSystemTests/test_probe_list_mounts.sh
+++ b/tests/test_suites/ShortSystemTests/test_admin_list_mounts.sh
@@ -2,7 +2,7 @@ MOUNTS=4 \
 	USE_RAMDISK=YES \
 	setup_local_empty_saunafs info
 
-mounts=$(saunafs-probe list-mounts --porcelain --verbose localhost "${info[matocl]}")
+mounts=$(saunafs-admin list-mounts --porcelain --verbose localhost "${info[matocl]}")
 expect_equals "4" $(wc -l <<< "$mounts")
 for i in {1..4}; do
 	if is_windows_system; then
diff --git a/tests/test_suites/ShortSystemTests/test_probe_metadataserver_status.sh b/tests/test_suites/ShortSystemTests/test_admin_metadataserver_status.sh
similarity index 67%
rename from tests/test_suites/ShortSystemTests/test_probe_metadataserver_status.sh
rename to tests/test_suites/ShortSystemTests/test_admin_metadataserver_status.sh
index 8f9379c84..d4649944b 100644
--- a/tests/test_suites/ShortSystemTests/test_probe_metadataserver_status.sh
+++ b/tests/test_suites/ShortSystemTests/test_admin_metadataserver_status.sh
@@ -5,21 +5,21 @@ CHUNKSERVERS=1 \
 	setup_local_empty_saunafs info
 
 assert_eventually_prints $'master\trunning' \
-		"saunafs-probe metadataserver-status --porcelain localhost ${info[matocl]} | cut -f-2"
+		"saunafs-admin metadataserver-status --porcelain localhost ${info[matocl]} | cut -f-2"
 
-version=$(saunafs-probe metadataserver-status --porcelain localhost ${info[matocl]} | cut -f3)
+version=$(saunafs-admin metadataserver-status --porcelain localhost ${info[matocl]} | cut -f3)
 # Version of last changelog entry.
 changelog_version=$(tail -1 "${info[master_data_path]}"/changelog.sfs | grep -o '^[0-9]*')
 
-# Make sure probe returned correct metadata version.
+# Make sure admin returned correct metadata version.
 assert_equals $version "$((changelog_version + 1))"
 
 saunafs_master_n 1 start
 
 assert_eventually_prints $'shadow\tconnected\t'$version \
-		"saunafs-probe metadataserver-status --porcelain localhost ${info[master1_matocl]}"
+		"saunafs-admin metadataserver-status --porcelain localhost ${info[master1_matocl]}"
 
 saunafs_master_n 0 stop
 
 assert_eventually_prints $'shadow\tdisconnected\t'$version \
-		"saunafs-probe metadataserver-status --porcelain localhost ${info[master1_matocl]}"
+		"saunafs-admin metadataserver-status --porcelain localhost ${info[master1_matocl]}"
diff --git a/tests/test_suites/ShortSystemTests/test_admin_stop_master.sh b/tests/test_suites/ShortSystemTests/test_admin_stop_master.sh
index d87675734..cc141f927 100644
--- a/tests/test_suites/ShortSystemTests/test_admin_stop_master.sh
+++ b/tests/test_suites/ShortSystemTests/test_admin_stop_master.sh
@@ -9,7 +9,8 @@ assert_failure saunafs-admin stop-master-without-saving-metadata \
 saunafs_master_n 1 start
 
 # Remember version of metadata before stopping the master server
-last_metadata_version=$(saunafs_probe_master metadataserver-status | cut -f3)
+last_metadata_version=$(saunafs_admin_master_no_password \
+		metadataserver-status | cut -f3)
 
 # Make sure admin stop will succeed with connected shadow server
 assert_success saunafs-admin stop-master-without-saving-metadata \
diff --git a/tests/test_suites/ShortSystemTests/test_auto_recovery_metarestore_while_starting.sh b/tests/test_suites/ShortSystemTests/test_auto_recovery_metarestore_while_starting.sh
index 083b14fca..f693bf8b1 100644
--- a/tests/test_suites/ShortSystemTests/test_auto_recovery_metarestore_while_starting.sh
+++ b/tests/test_suites/ShortSystemTests/test_auto_recovery_metarestore_while_starting.sh
@@ -19,4 +19,4 @@ sleep 1
 expect_failure sfsmetarestore -a -d "${info[master_data_path]}"
 
 # Expect that the master server still applies the changelog.
-expect_failure saunafs-probe info localhost "${info[matocl]}"
+expect_failure saunafs-admin info localhost "${info[matocl]}"
diff --git a/tests/test_suites/ShortSystemTests/test_chunkserver_start_with_damaged_disk.sh b/tests/test_suites/ShortSystemTests/test_chunkserver_start_with_damaged_disk.sh
index dfe17ebba..ebaf6626b 100644
--- a/tests/test_suites/ShortSystemTests/test_chunkserver_start_with_damaged_disk.sh
+++ b/tests/test_suites/ShortSystemTests/test_chunkserver_start_with_damaged_disk.sh
@@ -43,7 +43,7 @@ assert_success saunafs_chunkserver_daemon 0 start
 saunafs_wait_for_all_ready_chunkservers
 
 # Ensure that disk 1 is damaged and other disks work
-list=$(saunafs_probe_master list-disks | sort)
+list=$(saunafs_admin_master_no_password list-disks | sort)
 assert_equals 3 "$(wc -l <<< "$list")"
 assert_awk_finds 'NR==1 && $4=="yes"' "$list"
 assert_awk_finds 'NR==2 && $4=="no"' "$list"
@@ -52,10 +52,11 @@ assert_awk_finds 'NR==3 && $4=="no"' "$list"
 # Remove the third disk from the chunkserver
 sed -i -e '3s/^/#/' "${info[chunkserver0_hdd]}"
 saunafs_chunkserver_daemon 0 reload
-assert_eventually_prints 2 'saunafs_probe_master list-disks | wc -l'
+assert_eventually_prints 2 'saunafs_admin_master_no_password list-disks | wc -l'
 
-# Expect that one line disappears from the output of saunafs-probe
+# Expect that one line disappears from the output of saunafs-admin
 # Ignore columns 6-..., because disk usage might have changed
 expected_list=$(head -n2 <<< "$list" | cut -d ' ' -f 1-5)
-actual_list=$(saunafs_probe_master list-disks | sort | cut -d ' ' -f 1-5)
+actual_list=$(saunafs_admin_master_no_password \
+	list-disks | sort | cut -d ' ' -f 1-5)
 assert_no_diff "$expected_list" "$actual_list"
diff --git a/tests/test_suites/ShortSystemTests/test_close_eio_in_chunkserver.sh b/tests/test_suites/ShortSystemTests/test_close_eio_in_chunkserver.sh
index 382d89475..aff5d26de 100644
--- a/tests/test_suites/ShortSystemTests/test_close_eio_in_chunkserver.sh
+++ b/tests/test_suites/ShortSystemTests/test_close_eio_in_chunkserver.sh
@@ -26,10 +26,10 @@ FILE_SIZE=1234 assert_success file-generate test/small_{1..10}
 FILE_SIZE=1M   assert_success file-generate test/big_{1..10}
 
 # Wait for the failure to be detected
-assert_eventually_prints yes "saunafs_probe_master list-disks | awk '/EIO/ {print \$4}'"
+assert_eventually_prints yes "saunafs_admin_master_no_password list-disks | awk '/EIO/ {print \$4}'"
 
 # Verify that exactly disks marked "close_EIO" are marked as damaged
-list=$(saunafs_probe_master list-disks)
+list=$(saunafs_admin_master_no_password list-disks)
 assert_equals 3 "$(wc -l <<< "$list")"
 assert_awk_finds_no '(/EIO/ && $4 != "yes") || (!/EIO/ && $4 != "no")' "$list"
 
diff --git a/tests/test_suites/ShortSystemTests/test_direntrycache_checks.sh b/tests/test_suites/ShortSystemTests/test_direntrycache_checks.sh
new file mode 100644
index 000000000..2fc7fd831
--- /dev/null
+++ b/tests/test_suites/ShortSystemTests/test_direntrycache_checks.sh
@@ -0,0 +1,156 @@
+#
+# The goal of this test is to check the DirEntryCache behavior from the point of view of 
+# the syscalls involved in its behavior. Performs checks on the currently expected behavior
+# of the lookup, getattr, setattr, setxattr, mknod, mkdir, unlink, rmdir, rename, link and 
+# write syscalls regarding DirEntryCache.
+#
+CHUNKSERVERS=1 \
+	MOUNTS=2 \
+	MOUNT_EXTRA_CONFIG="sfsdirentrycacheto=2" \
+	USE_RAMDISK=YES \
+	setup_local_empty_saunafs info
+
+# A couple of functions to easily get the size and number of links to an inode
+get_size() {
+	ls -l $1 | cut -d " " -f 5
+}
+
+get_nliks() {
+	ls -l $1 | cut -d " " -f 2
+}
+
+cd ${info[mount0]}
+
+echo -n "1234" > file
+
+assert_equals "$(get_size file)" "$(get_size ${info[mount1]}/file)" 4
+assert_equals "$(get_nliks file)" "$(get_nliks ${info[mount1]}/file)" 1
+
+dd if=/dev/zero of=file count=1 bs=1 seek=4 &>/dev/null
+
+assert_equals "$(get_size file)" 5
+assert_equals "$(get_size ${info[mount1]}/file)" 4
+
+# mkdir clears cache of the parent folder
+mkdir ${info[mount1]}/folder
+
+assert_equals "$(get_size file)" "$(get_size ${info[mount1]}/file)" 5
+
+dd if=/dev/zero of=${info[mount1]}/file count=1 bs=1 seek=5 &>/dev/null
+
+assert_equals "$(get_size file)" 5
+assert_equals "$(get_size ${info[mount1]}/file)" 6
+
+# mknod clears cache of the parent folder
+touch file2
+
+assert_equals "$(get_size file)" "$(get_size ${info[mount1]}/file)" 6
+
+cd folder
+dd if=/dev/zero of=${info[mount1]}/file count=1 bs=1 seek=6 &>/dev/null
+assert_equals "$(get_size ${info[mount0]}/file)" 6
+
+# create only clear cache of the parent folder
+echo -n "0" > file3
+assert_equals "$(get_size ${info[mount0]}/file)" 6
+assert_equals "$(get_size file3)" 1
+
+dd if=/dev/zero of=${info[mount1]}/folder/file3 count=1 bs=1 seek=1 &>/dev/null
+
+assert_equals "$(get_size ${info[mount0]}/file)" 6
+assert_equals "$(get_size ${info[mount1]}/file)" 7
+assert_equals "$(get_size file3)" 1
+assert_equals "$(get_size ${info[mount1]}/folder/file3)" 2
+
+# link clears cache of the involved inode and the parent folder
+link "${info[mount0]}/file" link
+
+assert_equals "$(get_nliks ${info[mount0]}/file)" 2
+assert_equals "$(get_nliks ${info[mount1]}/file)" 1
+assert_equals "$(get_size ${info[mount0]}/file)" "$(get_size ${info[mount1]}/file)" 7
+assert_equals "$(get_size file3)" "$(get_size ${info[mount1]}/folder/file3)" 2
+
+# setattr clears cache of the involved inode
+truncate -s 6 ${info[mount1]}/folder/link
+echo -n "0" > file4
+
+# remember new file created (file4) cleared parent folder cache, but files with at least one 
+# hardlink are also not kept in cache
+assert_equals "$(get_size ${info[mount0]}/file)" "$(get_size ${info[mount0]}/folder/link)" 6 
+assert_equals "$(get_size ${info[mount1]}/file)" "$(get_size ${info[mount1]}/folder/link)" 6
+assert_equals "$(get_size file4)" "$(get_size ${info[mount1]}/folder/file4)" 1
+assert_equals "$(get_nliks ${info[mount0]}/file)" "$(get_nliks ${info[mount1]}/file)" 2
+
+dd if=/dev/zero of=${info[mount0]}/file count=1 bs=1 seek=6 &>/dev/null
+dd if=/dev/zero of=file4 count=1 bs=1 seek=1 &>/dev/null
+
+# files with at least one hardlink are not kept in cache
+assert_equals "$(get_size ${info[mount1]}/file)" "$(get_size ${info[mount0]}/file)" 7
+assert_equals "$(get_size file4)" 2
+assert_equals "$(get_size ${info[mount1]}/folder/file4)" 1
+
+# rename clears cache of both current and new parent folder
+mv ${info[mount1]}/folder/file3 ${info[mount1]}/file3
+
+assert_equals "$(get_size ${info[mount0]}/file)" "$(get_size ${info[mount1]}/file)" 7
+assert_equals "$(get_size file4)" "$(get_size ${info[mount1]}/folder/file4)" 2
+
+rm link
+cd ..
+
+# files with at least one hardlink are also not kept in cache
+assert_equals "$(get_nliks ${info[mount1]}/file)" "$(get_nliks file)" 1
+
+dd if=/dev/zero of=file count=1 bs=1 seek=7 &>/dev/null
+
+assert_equals "$(get_size file)" 8
+assert_equals "$(get_size ${info[mount1]}/file)" 7
+
+# once again setattr
+chmod 777 ${info[mount1]}/file
+
+assert_equals "$(get_size file)" "$(get_size ${info[mount1]}/file)" 8
+
+dd if=/dev/zero of=${info[mount1]}/file count=1 bs=1 seek=8 &>/dev/null
+
+assert_equals "$(get_size file)" 8
+assert_equals "$(get_size ${info[mount1]}/file)" 9
+
+# unlink clears cache of the parent folder
+unlink file2
+
+assert_equals "$(get_size file)" "$(get_size ${info[mount1]}/file)" 9
+
+truncate -s 7 file
+
+assert_equals "$(get_size file)" 7
+assert_equals "$(get_size ${info[mount1]}/file)" 9
+
+# unlinking anywhere else does not clear the parent cache
+rm ${info[mount1]}/folder/file4
+assert_equals "$(get_size ${info[mount1]}/file)" 9
+
+# rmdir clears cache of the parent folder
+rmdir ${info[mount1]}/folder
+
+assert_equals "$(get_size file)" "$(get_size ${info[mount1]}/file)" 7
+
+truncate -s 5 ${info[mount1]}/file
+
+assert_equals "$(get_size file)" 7
+assert_equals "$(get_size ${info[mount1]}/file)" 5
+
+# setxattr clears cache of the involved inode
+setfacl -m u:saunafstest:r-x file
+
+assert_equals "$(get_size file)" "$(get_size ${info[mount1]}/file)" 5
+
+truncate -s 3 file
+
+assert_equals "$(get_size file)" 3
+assert_equals "$(get_size ${info[mount1]}/file)" 5
+
+# readdir overrides the cache of the involved folder
+ls ${info[mount1]} &>/dev/null
+
+assert_equals "$(get_size file)" "$(get_size ${info[mount1]}/file)" 3
diff --git a/tests/test_suites/ShortSystemTests/test_disk_removed_from_config.sh b/tests/test_suites/ShortSystemTests/test_disk_removed_from_config.sh
index 0531f3037..355a2661e 100644
--- a/tests/test_suites/ShortSystemTests/test_disk_removed_from_config.sh
+++ b/tests/test_suites/ShortSystemTests/test_disk_removed_from_config.sh
@@ -19,5 +19,5 @@ fi
 saunafs_chunkserver_daemon 0 reload
 
 # Check the disk was removed correctly
-assert_eventually_prints 2 "saunafs-probe list-disks \
+assert_eventually_prints 2 "saunafs-admin list-disks \
 	--porcelain localhost ${info[matocl]} | wc -l" "30 seconds"
diff --git a/tests/test_suites/ShortSystemTests/test_xor_goal_with_labels.sh b/tests/test_suites/ShortSystemTests/test_ec2_goal_with_labels.sh
similarity index 67%
rename from tests/test_suites/ShortSystemTests/test_xor_goal_with_labels.sh
rename to tests/test_suites/ShortSystemTests/test_ec2_goal_with_labels.sh
index 565951e16..8479aa297 100644
--- a/tests/test_suites/ShortSystemTests/test_xor_goal_with_labels.sh
+++ b/tests/test_suites/ShortSystemTests/test_ec2_goal_with_labels.sh
@@ -1,14 +1,14 @@
 timeout_set 2 minutes
 
-# Returns number of standard chunks and number of different parts for each xor level.
+# Returns number of standard chunks and number of different parts for each ec level.
 # If the same chunk has two copies of the same parts, these will be counted as one, eg:
 # '3 standard' -- just 3 standard chunks
-# '2 standard 3 xor2' -- 2 standard chunks and 3 xor2 chunks
-# '6 xor5' -- 6 xor5 chunks (each is different)
+# '2 standard 3 ec2' -- 2 standard chunks and 3 ec2 chunks
+# '6 ec2' -- 6 ec2 chunks (each is different)
 chunks_state() {
 	{
 		find_all_metadata_chunks | grep -o chunk_.* | grep -o chunk_00000 | sed -e 's/.*/standard/'
-		find_all_metadata_chunks | grep -o chunk_.* | sort -u | grep -o '_of_[2-9]' | sed -e 's/_of_/xor/'
+		find_all_metadata_chunks | grep -o chunk_.* | sort -u | grep -o '_of_[2-9]_1' | sed -e 's/_of_/ec2_/'
 	} | sort | uniq -c | tr '\n' ' ' | trim_hard
 }
 
@@ -21,9 +21,9 @@ count_chunks_on_chunkservers() {
 USE_RAMDISK=YES \
 	CHUNKSERVERS=9 \
 	CHUNKSERVER_LABELS="3,4,5:hdd|6,7,8:ssd" \
-	MASTER_CUSTOM_GOALS="10 xor2_ssd: \$xor2 {ssd ssd ssd}`
-			`|11 xor3_hdd: \$xor3 {hdd hdd hdd hdd}`
-			`|12 xor5_mix: \$xor5 {hdd ssd hdd ssd}" \
+	MASTER_CUSTOM_GOALS="10 ec21_ssd: \$ec(2,1) {ssd ssd ssd}`
+			`|11 ec31_hdd: \$ec(3,1) {hdd hdd hdd hdd}`
+			`|12 ec51_mix: \$ec(5,1) {hdd ssd hdd ssd}" \
 	MASTER_EXTRA_CONFIG="CHUNKS_LOOP_MIN_TIME = 1`
 			`|CHUNKS_LOOP_MAX_CPU = 90`
 			`|CHUNKS_SOFT_DEL_LIMIT = 10`
@@ -35,19 +35,21 @@ USE_RAMDISK=YES \
 
 cd "${info[mount0]}"
 mkdir dir
-saunafs setgoal xor2_ssd dir
+saunafs setgoal ec21_ssd dir
 FILE_SIZE=1K file-generate dir/file
 
-assert_equals "3 xor2" "$(chunks_state)"
+assert_equals "3 ec2_2_1" "$(chunks_state)"
 assert_equals 3 "$(count_chunks_on_chunkservers {6..8})"
 assert_equals 3 "$(count_chunks_on_chunkservers {0..8})"
 
-saunafs setgoal xor3_hdd dir/file
-assert_eventually_prints '4 xor3' 'chunks_state' '2 minutes'
+saunafs setgoal ec31_hdd dir/file
+#Wait for migration to finish
+assert_eventually_prints '4 ec2_3_1' 'chunks_state' '2 minutes'
 assert_equals 3 "$(count_chunks_on_chunkservers {3..5})"
 assert_equals 4 "$(count_chunks_on_chunkservers {0..8})"
 
-saunafs setgoal xor5_mix dir/file
-assert_eventually_prints '6 xor5' 'chunks_state' '2 minutes'
+saunafs setgoal ec51_mix dir/file
+#Wait for migration to finish
+assert_eventually_prints '6 ec2_5_1' 'chunks_state' '2 minutes'
 assert_less_or_equal 2 "$(count_chunks_on_chunkservers {3..5})"
 assert_less_or_equal 2 "$(count_chunks_on_chunkservers {6..8})"
diff --git a/tests/test_suites/ShortSystemTests/test_xor_overwriting.sh b/tests/test_suites/ShortSystemTests/test_ec_overwriting.sh
similarity index 88%
rename from tests/test_suites/ShortSystemTests/test_xor_overwriting.sh
rename to tests/test_suites/ShortSystemTests/test_ec_overwriting.sh
index 4039389dd..b682bba63 100644
--- a/tests/test_suites/ShortSystemTests/test_xor_overwriting.sh
+++ b/tests/test_suites/ShortSystemTests/test_ec_overwriting.sh
@@ -2,12 +2,13 @@ timeout_set 1 minute
 CHUNKSERVERS=4 \
 	DISK_PER_CHUNKSERVER=1 \
 	MOUNT_EXTRA_CONFIG="sfscachemode=NEVER" \
+	MASTER_CUSTOM_GOALS="10 ec_3_1: \$ec(3,1)" \
 	USE_RAMDISK=YES \
 	setup_local_empty_saunafs info
 
 dir="${info[mount0]}/dir"
 mkdir "$dir"
-saunafs setgoal xor3 "$dir"
+saunafs setgoal ec_3_1 "$dir"
 
 # Create a temporary file with some data
 file_size_mb=5
@@ -28,7 +29,7 @@ if ! file-validate "$dir/file"; then
 fi
 
 # Find the chunkserver serving part 1 of 3 and stop it
-csid=$(find_first_chunkserver_with_chunks_matching 'chunk_xor_1_of_3*')
+csid=$(find_first_chunkserver_with_chunks_matching 'chunk_ec2_1_of_3*')
 saunafs_chunkserver_daemon $csid stop
 
 # Validate the parity part
diff --git a/tests/test_suites/ShortSystemTests/test_xor_parallel_writing.sh b/tests/test_suites/ShortSystemTests/test_ec_parallel_writing.sh
similarity index 83%
rename from tests/test_suites/ShortSystemTests/test_xor_parallel_writing.sh
rename to tests/test_suites/ShortSystemTests/test_ec_parallel_writing.sh
index 54bd4d163..e626d8803 100644
--- a/tests/test_suites/ShortSystemTests/test_xor_parallel_writing.sh
+++ b/tests/test_suites/ShortSystemTests/test_ec_parallel_writing.sh
@@ -8,13 +8,14 @@ fi
 CHUNKSERVERS=4 \
 	MOUNTS=10 \
 	MOUNT_EXTRA_CONFIG="sfscachemode=NEVER" \
+	MASTER_CUSTOM_GOALS="10 ec_3_1: \$ec(3,1)" \
 	USE_RAMDISK=YES \
 	setup_local_empty_saunafs info
 
-# Create an empty file on the SaunaFS filesystem with xor3 goal
+# Create an empty file on the SaunaFS filesystem with ec_3_1 goal
 file="${info[mount0]}/file"
 touch "$file"
-saunafs setgoal xor3 "$file"
+saunafs setgoal ec_3_1 "$file"
 
 # Create a temporary file with 25 megabytes of generated data
 tmpf=$RAMDISK_DIR/tmpf
@@ -35,7 +36,7 @@ wait
 MESSAGE="Data is corrupted after writing" expect_success file-validate "$file"
 
 # Validate the parity part
-csid=$(find_first_chunkserver_with_chunks_matching 'chunk_xor_1_of_3*')
+csid=$(find_first_chunkserver_with_chunks_matching 'chunk_ec2_1_of_3*')
 saunafs_chunkserver_daemon $csid stop
 
 MESSAGE="Parity is corrupted after writing" expect_success file-validate "$file"
diff --git a/tests/test_suites/ShortSystemTests/test_xor_read_all_combinations.sh b/tests/test_suites/ShortSystemTests/test_ec_read_all_combinations.sh
similarity index 87%
rename from tests/test_suites/ShortSystemTests/test_xor_read_all_combinations.sh
rename to tests/test_suites/ShortSystemTests/test_ec_read_all_combinations.sh
index 81070ab9f..94002b01c 100644
--- a/tests/test_suites/ShortSystemTests/test_xor_read_all_combinations.sh
+++ b/tests/test_suites/ShortSystemTests/test_ec_read_all_combinations.sh
@@ -3,12 +3,13 @@ timeout_set 10 minutes
 CHUNKSERVERS=10 \
 	DISK_PER_CHUNKSERVER=1 \
 	MOUNT_EXTRA_CONFIG="sfscachemode=NEVER" \
+	MASTER_CUSTOM_GOALS="10 ec_9_1: \$ec(9,1)" \
 	USE_RAMDISK=YES \
 	setup_local_empty_saunafs info
 
 dir="${info[mount0]}/dir"
 mkdir "$dir"
-saunafs setgoal xor9 "$dir"
+saunafs setgoal ec_9_1 "$dir"
 FILE_SIZE=876M file-generate "$dir/file"
 
 for i in {0..9}; do
diff --git a/tests/test_suites/ShortSystemTests/test_xor_read_without_parity.sh b/tests/test_suites/ShortSystemTests/test_ec_read_without_parity.sh
similarity index 79%
rename from tests/test_suites/ShortSystemTests/test_xor_read_without_parity.sh
rename to tests/test_suites/ShortSystemTests/test_ec_read_without_parity.sh
index 0a2e2d01b..8042dfe1b 100644
--- a/tests/test_suites/ShortSystemTests/test_xor_read_without_parity.sh
+++ b/tests/test_suites/ShortSystemTests/test_ec_read_without_parity.sh
@@ -6,11 +6,11 @@ CHUNKSERVERS=3 \
 
 dir="${info[mount0]}/dir"
 mkdir "$dir"
-saunafs setgoal xor2 "$dir"
+saunafs setgoal ec21 "$dir"
 FILE_SIZE=6M file-generate "$dir/file"
 
 # Find the chunkserver serving parity part and stop it
-csid=$(find_first_chunkserver_with_chunks_matching 'chunk_xor_parity_of_2*')
+csid=$(find_first_chunkserver_with_chunks_matching 'chunk_ec2_3_of_2*')
 saunafs_chunkserver_daemon $csid stop
 
 if ! file-validate "$dir/file"; then
diff --git a/tests/test_suites/ShortSystemTests/test_ec_truncate_atomicity.sh b/tests/test_suites/ShortSystemTests/test_ec_truncate_atomicity.sh
new file mode 100644
index 000000000..5ff3cc627
--- /dev/null
+++ b/tests/test_suites/ShortSystemTests/test_ec_truncate_atomicity.sh
@@ -0,0 +1,46 @@
+timeout_set '1 minute'
+
+CHUNKSERVERS=11 \
+	MASTER_CUSTOM_GOALS="3 ec32: \$ec(3,2)`
+	`|4 ec42: \$ec(4,2)`
+	`|7 ec72: \$ec(7,2)`
+	`|9 ec92: \$ec(9,2)" \
+	DISK_PER_CHUNKSERVER=1 \
+	MOUNTS=5 \
+	MOUNT_EXTRA_CONFIG="sfscachemode=NEVER" \
+	USE_RAMDISK=YES \
+	setup_local_empty_saunafs info
+
+# Create a source file -- a valid file-generated file which consists of 200 kB of data
+source="$RAMDISK_DIR/source"
+FILE_SIZE=200K file-generate "$source"
+
+cd "${info[mount0]}"
+for ec in 3 4 7 9; do
+	# Create a file which consists of 400 kB of random data
+	file="file$ec"
+	touch "$file"
+	saunafs setgoal ec${ec}2 "$file"
+	head -c 400K /dev/urandom > "$file"
+
+	# Run in parallel 200 dd processes, each copies different 1 kB of data from the source file
+	# Distribute these processes between 3 first mountpoints (mount0, mount1, mount2)
+	for i in {0..199}; do
+		( sleep 0.$((2 * i + 100)) && dd if="$source" of="${info[mount$((i%3))]}/$file" \
+				bs=1KiB skip=$i seek=$i count=1 conv=notrunc 2>/dev/null ) &
+	done
+	# In the meanwhile, shorten file from 400K to 200K, chopping 1 kB in each of 200 steps
+	# Sometimes share mountpoint with dd (mount2), sometimes not (mount3, mount4)
+	for i in {399..200}; do
+		truncate -s ${i}K "${info[mount$((2 + i % 3))]}/$file"
+	done
+	wait # Wait for all dd processes to finish
+
+	# Now file should be equal to the source file. Let's validate it!
+	MESSAGE="Testing ec(${ec},2)" assert_success file-validate "$file"
+done
+
+# Remove data parts 1 and 2 of each chunk to verify parity parts
+find_all_chunks -name "*ec2_1_of*" | xargs rm -vf
+find_all_chunks -name "*ec2_2_of*" | xargs rm -vf
+MESSAGE="Verification of parity parts" expect_success file-validate file*
diff --git a/tests/test_suites/ShortSystemTests/test_xor_truncate_missing_parts.sh b/tests/test_suites/ShortSystemTests/test_ec_truncate_missing_parts.sh
similarity index 51%
rename from tests/test_suites/ShortSystemTests/test_xor_truncate_missing_parts.sh
rename to tests/test_suites/ShortSystemTests/test_ec_truncate_missing_parts.sh
index f7288d3e5..1952e7151 100644
--- a/tests/test_suites/ShortSystemTests/test_xor_truncate_missing_parts.sh
+++ b/tests/test_suites/ShortSystemTests/test_ec_truncate_missing_parts.sh
@@ -2,6 +2,11 @@ timeout_set '3 minutes'
 
 CHUNKSERVERS=10 \
 	DISK_PER_CHUNKSERVER=1 \
+	MASTER_CUSTOM_GOALS="10 ec21: \$ec(2,1)`
+	`|11 ec31: \$ec(3,1)`
+	`|12 ec41: \$ec(4,1)`
+	`|13 ec71: \$ec(7,1)`
+	`|14 ec91: \$ec(9,1)" \
 	MOUNT_EXTRA_CONFIG="sfscachemode=NEVER" \
 	USE_RAMDISK=YES \
 	setup_local_empty_saunafs info
@@ -15,18 +20,18 @@ sizes=(100 200 300 1000 2000 3000 10000 20000 30000 100000 200000 300000 \
 # List of xor levels which will be tested
 levels=(2 3 4 7 9)
 
-# For each xor level and each file size generate file of this size (using file-generate) and
+# For each ec level and each file size generate file of this size (using file-generate) and
 # append some random amount of random bytes to it. Then make snapshot of such a file.
 pseudorandom_init
 cd "${info[mount0]}"
 for i in "${levels[@]}"; do
-	mkdir xor$i
-	saunafs setgoal xor$i xor$i
+	mkdir "ec${i}1"
+	saunafs setgoal "ec${i}1" "ec${i}1"
 	for size in "${sizes[@]}"; do
-		FILE_SIZE=$size file-generate xor$i/file_$size
-		assert_success file-validate xor$i/file_$size
-		head -c $(pseudorandom 1 $((i * 100000))) /dev/urandom >> xor$i/file_$size
-		saunafs makesnapshot xor$i/file_$size xor$i/snapshot_$size
+		FILE_SIZE=$size file-generate "ec${i}1/file_${size}"
+		assert_success file-validate "ec${i}1/file_${size}"
+		head -c $(pseudorandom 1 $((i * 100000))) /dev/urandom >> "ec${i}1/file_${size}"
+		saunafs makesnapshot "ec${i}1/file_${size}" "ec${i}1/snapshot_${size}"
 	done
 done
 
@@ -37,14 +42,19 @@ saunafs_chunkserver_daemon 0 stop
 # appended after generating the file) and verify if the data is OK.
 for i in "${levels[@]}"; do
 	for size in "${sizes[@]}"; do
-		MESSAGE="Truncating xor$i from $(stat -c %s xor$i/file_$size) bytes to $size bytes"
-		assert_success truncate -s $size xor$i/snapshot_$size
-		assert_success truncate -s $size xor$i/file_$size
-		assert_success file-validate xor$i/file_$size xor$i/snapshot_$size
+		MESSAGE="Truncating ec${i}1 from $(stat -c %s ec${i}1/file_${size}) bytes to ${size} bytes"
+		assert_success truncate -s ${size} "ec${i}1/snapshot_${size}"
+		assert_success truncate -s ${size} "ec${i}1/file_${size}"
+		assert_success file-validate "ec${i}1/file_${size}" "ec${i}1/snapshot_${size}"
 	done
 done
 
 # Verify again after starting the chunkserver
 saunafs_chunkserver_daemon 0 start
 saunafs_wait_for_ready_chunkservers 10
-MESSAGE="Verification after starting the chunkserver" assert_success file-validate xor*/*
+for i in "${levels[@]}"; do
+	for size in "${sizes[@]}"; do
+		MESSAGE="Verification after starting the chunkserver" \
+		assert_success file-validate "ec${i}1/file_${size}" "ec${i}1/snapshot_${size}"
+	done
+done
diff --git a/tests/test_suites/ShortSystemTests/test_fsync_eio_in_chunkserver.sh b/tests/test_suites/ShortSystemTests/test_fsync_eio_in_chunkserver.sh
index 7263422ec..ba0090972 100644
--- a/tests/test_suites/ShortSystemTests/test_fsync_eio_in_chunkserver.sh
+++ b/tests/test_suites/ShortSystemTests/test_fsync_eio_in_chunkserver.sh
@@ -26,10 +26,10 @@ FILE_SIZE=1234 assert_success file-generate test/small_{1..10}
 FILE_SIZE=1M   assert_success file-generate test/big_{1..10}
 
 # Wait for the failure to be detected
-assert_eventually_prints yes "saunafs_probe_master list-disks | awk '/EIO/ {print \$4}'"
+assert_eventually_prints yes "saunafs_admin_master_no_password list-disks | awk '/EIO/ {print \$4}'"
 
 # Verify that exactly disks marked "fsync_EIO" are marked as damaged
-list=$(saunafs_probe_master list-disks)
+list=$(saunafs_admin_master_no_password list-disks)
 assert_equals 3 "$(wc -l <<< "$list")"
 assert_awk_finds_no '(/EIO/ && $4 != "yes") || (!/EIO/ && $4 != "no")' "$list"
 
diff --git a/tests/test_suites/ShortSystemTests/test_metadata_dump_period_seconds.sh b/tests/test_suites/ShortSystemTests/test_metadata_dump_period_seconds.sh
index ee537e130..61f289dae 100644
--- a/tests/test_suites/ShortSystemTests/test_metadata_dump_period_seconds.sh
+++ b/tests/test_suites/ShortSystemTests/test_metadata_dump_period_seconds.sh
@@ -1,5 +1,6 @@
 timeout_set '1 minute'
 period=5
+wait_period=3
 master_cfg="METADATA_DUMP_PERIOD_SECONDS = ${period}"
 master_cfg+="|MAGIC_DEBUG_LOG = ${TEMP_DIR}/log|LOG_FLUSH_ON=DEBUG"
 
@@ -10,17 +11,31 @@ CHUNKSERVERS=1 \
 	setup_local_empty_saunafs info
 
 is_time_for_metadata_dump() {
-	# The period is 5 seconds, so the modulo should be 0 every 5 seconds
+	# The period is 5 seconds, so the modulo should be 0 every 5 seconds in UNIX time
 	[ "$(($(date +%s) % period))" -eq 0 ]
 }
 
+echo "Checking metadata dump every ${period} seconds, current seconds: $(date +%S)"
+# Wait for first metadata dump to be in sync with the next steps
+while ! is_time_for_metadata_dump; do
+	echo "Waiting for metadata dump to sync up, current seconds: $(date +%S)"
+	sleep 1
+done
+
 truncate -s0 "${TEMP_DIR}/log"
+# Wait a second, otherwise a race condition could happen where it skips
+# waiting until the next metadata dump
+sleep 1
 while ! is_time_for_metadata_dump; do
 	assert_awk_finds_no '/periodic metadata dump:/' "${TEMP_DIR}/log"
+	echo "Waiting for first metadata dump, current seconds: $(date +%S)"
 	sleep 1
 done
+
 # It's time for the metadata dump, wait a bit more for the master to finish it
-sleep 2
+echo "Time for first metadata dump, waiting ${wait_period} seconds, current seconds: $(date +%S)"
+sleep ${wait_period}
+echo "Looking for first metadata dump, current seconds: $(date +%S)"
 
 # The metadata dump should be in the log now
 log=$(cat "${TEMP_DIR}/log")
@@ -31,9 +46,12 @@ assert_awk_finds '/periodic metadata dump:/' "${log}"
 truncate -s0 "${TEMP_DIR}/log"
 while ! is_time_for_metadata_dump; do
 	assert_awk_finds_no '/periodic metadata dump:/' "${TEMP_DIR}/log"
+	echo "Waiting for second metadata dump, current seconds: $(date +%S)"
 	sleep 1
 done
-sleep 2
+echo "Time for second metadata dump, waiting ${wait_period} seconds, current seconds: $(date +%S)"
+sleep ${wait_period}
+echo "Looking for second metadata dump, current seconds: $(date +%S)"
 
 log=$(cat "${TEMP_DIR}/log")
 truncate -s0 "${TEMP_DIR}/log"
diff --git a/tests/test_suites/ShortSystemTests/test_metarestore_autorecovery_metadata_version.sh b/tests/test_suites/ShortSystemTests/test_metarestore_autorecovery_metadata_version.sh
index a6f8c4ca7..f9d44a405 100644
--- a/tests/test_suites/ShortSystemTests/test_metarestore_autorecovery_metadata_version.sh
+++ b/tests/test_suites/ShortSystemTests/test_metarestore_autorecovery_metadata_version.sh
@@ -12,7 +12,7 @@ for i in {1..5} ; do
 done
 
 # Test fresh metadata + changelogs
-latest_metadata_version=$(saunafs_probe_master metadataserver-status | cut -f3)
+latest_metadata_version=$(saunafs_admin_master_no_password metadataserver-status | cut -f3)
 on_disk_metadata_version=$(sfsmetarestore -g -d "${info[master_data_path]}")
 assert_equals "$latest_metadata_version" "$on_disk_metadata_version"
 
@@ -21,12 +21,12 @@ FILE_SIZE=1K assert_success file-generate "${info[mount0]}"/file_{6..10}_{1..10}
 # and don't save it
 
 # Test old metadata + changelogs
-latest_metadata_version=$(saunafs_probe_master metadataserver-status | cut -f3)
+latest_metadata_version=$(saunafs_admin_master_no_password metadataserver-status | cut -f3)
 saunafs_master_daemon kill
 on_disk_metadata_version=$(sfsmetarestore -g -d "${info[master_data_path]}")
 assert_equals "$latest_metadata_version" "$on_disk_metadata_version"
 assert_success saunafs_master_daemon start -o auto-recovery
-assert_equals "$(saunafs_probe_master metadataserver-status | cut -f3)" "$on_disk_metadata_version"
+assert_equals "$(saunafs_admin_master_no_password metadataserver-status | cut -f3)" "$on_disk_metadata_version"
 
 # Test broken changelogs fail
 rm "${info[mount0]}"/file_*
@@ -44,7 +44,7 @@ assert_equals "0" "$on_disk_metadata_version"
 mv "${info[master_data_path]}"/changelog.sfs.2.tmp "${info[master_data_path]}"/changelog.sfs.2
 on_disk_metadata_version=$(sfsmetarestore -g -d "${info[master_data_path]}")
 assert_success saunafs_master_daemon start -o auto-recovery
-assert_equals "$(saunafs_probe_master metadataserver-status | cut -f3)" "$on_disk_metadata_version"
+assert_equals "$(saunafs_admin_master_no_password metadataserver-status | cut -f3)" "$on_disk_metadata_version"
 
 # Test sfsmetarestore on clean installation
 assert_success saunafs_master_daemon kill
diff --git a/tests/test_suites/ShortSystemTests/test_no_reads_during_small_ec_files_write.sh b/tests/test_suites/ShortSystemTests/test_no_reads_during_small_ec_files_write.sh
new file mode 100644
index 000000000..b24c15c03
--- /dev/null
+++ b/tests/test_suites/ShortSystemTests/test_no_reads_during_small_ec_files_write.sh
@@ -0,0 +1,20 @@
+CHUNKSERVERS=6 \
+	USE_RAMDISK=YES \
+	MASTER_CUSTOM_GOALS="1 ec42: \$ec(4,2)" \
+	AUTO_SHADOW_MASTER="NO" \
+	MOUNT_EXTRA_CONFIG="sfscachemode=NEVER" \
+	CHUNKSERVER_EXTRA_CONFIG="GARBAGE_COLLECTION_FREQ_MS = 0`
+			`|HDD_TEST_FREQ = 100000`
+			`|MAGIC_DEBUG_LOG = $TEMP_DIR/log`
+			`|LOG_FLUSH_ON=DEBUG" \
+	setup_local_empty_saunafs info
+
+cd "${info[mount0]}"
+
+for i in {1..100}; do
+	dd if=/dev/zero of=file${i} bs=33 count=1 oflag=direct status=progress
+done
+
+number_of_extra_reads=$(grep chunkserver.hddRead $TEMP_DIR/log | wc -l);
+
+assert_equals "0" "${number_of_extra_reads}"
diff --git a/tests/test_suites/ShortSystemTests/test_pread_eio_in_chunkserver_all_disks.sh b/tests/test_suites/ShortSystemTests/test_pread_eio_in_chunkserver_all_disks.sh
index 1dbbc4889..d5d7b357f 100644
--- a/tests/test_suites/ShortSystemTests/test_pread_eio_in_chunkserver_all_disks.sh
+++ b/tests/test_suites/ShortSystemTests/test_pread_eio_in_chunkserver_all_disks.sh
@@ -43,7 +43,7 @@ done
 
 # Assert that exactly disks marked "pread_EIO" are marked as damaged
 sleep 1
-list=$(saunafs_probe_master list-disks)
+list=$(saunafs_admin_master_no_password list-disks)
 assert_equals 9 "$(wc -l <<< "$list")"
 assert_awk_finds_no '(/EIO/ && $4 != "yes") || (!/EIO/ && $4 == "yes")' "$list"
 
diff --git a/tests/test_suites/ShortSystemTests/test_pread_eio_in_chunkserver_single_disk.sh b/tests/test_suites/ShortSystemTests/test_pread_eio_in_chunkserver_single_disk.sh
index 5d314ba84..b3f9b9df1 100644
--- a/tests/test_suites/ShortSystemTests/test_pread_eio_in_chunkserver_single_disk.sh
+++ b/tests/test_suites/ShortSystemTests/test_pread_eio_in_chunkserver_single_disk.sh
@@ -39,7 +39,7 @@ done
 
 # Assert that exactly disks marked "pread_EIO" are marked as damaged
 sleep 1
-list=$(saunafs_probe_master list-disks)
+list=$(saunafs_admin_master_no_password list-disks)
 assert_equals 3 "$(wc -l <<< "$list")"
 assert_awk_finds_no '(/EIO/ && $4 != "yes") || (!/EIO/ && $4 != "no")' "$list"
 
diff --git a/tests/test_suites/ShortSystemTests/test_pread_eio_in_chunkserver_some_disks.sh b/tests/test_suites/ShortSystemTests/test_pread_eio_in_chunkserver_some_disks.sh
index b24ac21f2..6ee9745c6 100644
--- a/tests/test_suites/ShortSystemTests/test_pread_eio_in_chunkserver_some_disks.sh
+++ b/tests/test_suites/ShortSystemTests/test_pread_eio_in_chunkserver_some_disks.sh
@@ -33,9 +33,10 @@ FILE_SIZE=300K file-generate goal{1..2}/medium_{1..10}
 FILE_SIZE=5M   file-generate goal{1..2}/big_{1..10}
 
 # Count chunks in the system. Expect that each chunk has one copy on CS0 and one copy on CS1.
-chunks=$(saunafs_probe_master info | awk '{print $13}')
+chunks=$(saunafs_admin_master_no_password info | awk '{print $13}')
 assert_equals 60 $chunks
-assert_equals 90 $(saunafs_probe_master info | awk '{print $14}')
+assert_equals 90 $(saunafs_admin_master_no_password info | awk '{print $14}')
+
 assert_less_than 30 "$(find_chunkserver_metadata_chunks 0 | wc -l)"
 assert_less_than 30 "$(find_chunkserver_metadata_chunks 1 | wc -l)"
 
@@ -55,7 +56,7 @@ done
 
 # Assert that exactly disks marked "pread_EIO" are marked as damaged.
 sleep 1
-list=$(saunafs_probe_master list-disks)
+list=$(saunafs_admin_master_no_password list-disks)
 assert_equals 6 "$(wc -l <<< "$list")"
 assert_awk_finds_no '(/EIO/ && $4 != "yes") || (!/EIO/ && $4 == "yes")' "$list"
 
diff --git a/tests/test_suites/ShortSystemTests/test_pread_eio_nonheader_in_chunkserver.sh b/tests/test_suites/ShortSystemTests/test_pread_eio_nonheader_in_chunkserver.sh
index 7dc839c13..3a25b4995 100644
--- a/tests/test_suites/ShortSystemTests/test_pread_eio_nonheader_in_chunkserver.sh
+++ b/tests/test_suites/ShortSystemTests/test_pread_eio_nonheader_in_chunkserver.sh
@@ -32,7 +32,7 @@ for i in {1..10}; do
 	assert_success file-validate goal2/small_*
 done
 sleep 1
-list=$(saunafs_probe_master list-disks)
+list=$(saunafs_admin_master_no_password list-disks)
 assert_equals 3 "$(wc -l <<< "$list")"
 assert_awk_finds_no '$4 != "no"' "$list"
 
@@ -46,7 +46,7 @@ done
 
 # Assert that exactly disks marked "pread_far_EIO" are marked as damaged
 sleep 1
-list=$(saunafs_probe_master list-disks)
+list=$(saunafs_admin_master_no_password list-disks)
 assert_equals 3 "$(wc -l <<< "$list")"
 assert_awk_finds_no '(/EIO/ && $4 != "yes") || (!/EIO/ && $4 != "no")' "$list"
 
diff --git a/tests/test_suites/ShortSystemTests/test_pwrite_eio_in_chunkserver.sh b/tests/test_suites/ShortSystemTests/test_pwrite_eio_in_chunkserver.sh
index 1751d6fa5..26e0c3153 100644
--- a/tests/test_suites/ShortSystemTests/test_pwrite_eio_in_chunkserver.sh
+++ b/tests/test_suites/ShortSystemTests/test_pwrite_eio_in_chunkserver.sh
@@ -27,7 +27,7 @@ FILE_SIZE=1M   assert_success file-generate test/big_{1..10}
 
 # Assert that exactly disks marked "pwrite_EIO" are marked as damaged
 sleep 1
-list=$(saunafs_probe_master list-disks)
+list=$(saunafs_admin_master_no_password list-disks)
 assert_equals 3 "$(wc -l <<< "$list")"
 assert_awk_finds_no '(/EIO/ && $4 != "yes") || (!/EIO/ && $4 != "no")' "$list"
 
diff --git a/tests/test_suites/ShortSystemTests/test_pwrite_eio_nonheader_in_chunkserver.sh b/tests/test_suites/ShortSystemTests/test_pwrite_eio_nonheader_in_chunkserver.sh
index c6f2ba639..bf5e1d72f 100644
--- a/tests/test_suites/ShortSystemTests/test_pwrite_eio_nonheader_in_chunkserver.sh
+++ b/tests/test_suites/ShortSystemTests/test_pwrite_eio_nonheader_in_chunkserver.sh
@@ -24,14 +24,14 @@ mkdir test
 saunafs setgoal 2 test
 FILE_SIZE=1234 assert_success file-generate test/small_{1..20}
 sleep 1
-assert_awk_finds_no '$4 != "no"' "$(saunafs_probe_master list-disks)"
+assert_awk_finds_no '$4 != "no"' "$(saunafs_admin_master_no_password list-disks)"
 
 # Write a couple of bigger files. This should trigger a failure on CS0.
 FILE_SIZE=1M assert_success file-generate test/big_{1..10}
 
 # Assert that exactly disks marked "pwrite_far_EIO" are marked as damaged
 sleep 1
-list=$(saunafs_probe_master list-disks)
+list=$(saunafs_admin_master_no_password list-disks)
 assert_equals 3 "$(wc -l <<< "$list")"
 assert_awk_finds_no '(/EIO/ && $4 != "yes") || (!/EIO/ && $4 != "no")' "$list"
 
diff --git a/tests/test_suites/ShortSystemTests/test_reads_during_small_ec_files_write_parallel.sh b/tests/test_suites/ShortSystemTests/test_reads_during_small_ec_files_write_parallel.sh
new file mode 100644
index 000000000..3a4268d9b
--- /dev/null
+++ b/tests/test_suites/ShortSystemTests/test_reads_during_small_ec_files_write_parallel.sh
@@ -0,0 +1,82 @@
+CHUNKSERVERS=6 \
+	MOUNTS=2 \
+	USE_RAMDISK=YES \
+	MASTER_CUSTOM_GOALS="1 ec42: \$ec(4,2)" \
+	AUTO_SHADOW_MASTER="NO" \
+	MOUNT_0_EXTRA_CONFIG="sfscachemode=NEVER" \
+	MOUNT_1_EXTRA_CONFIG="sfscachemode=NEVER" \
+	CHUNKSERVER_EXTRA_CONFIG="GARBAGE_COLLECTION_FREQ_MS = 0`
+			`|HDD_TEST_FREQ = 100000`
+			`|MAGIC_DEBUG_LOG = $TEMP_DIR/log`
+			`|LOG_FLUSH_ON=DEBUG" \
+	setup_local_empty_saunafs info
+
+# This script tests an optimization in handling 
+# small files during Erasure Coding (EC) writes. 
+# It addresses a previous issue where the client 
+# would unnecessarily request missing data parts 
+# from chunk servers, even beyond the file size, 
+# adding overhead. The script creates a small 
+# 1-byte file with known content and writes this 
+# content at specific positions (0, 64K, 128K, 192K) 
+# on two mount points. After each write operation, 
+# it reads back the data from these positions and 
+# checks that the read data is the same as the 
+# known content. It also checks that there were 
+# no extra reads during these operations. 
+# This test ensures that remaining blocks are not 
+# requested, eliminating unnecessary requests, 
+# reducing overhead, and improving efficiency, 
+# especially for small files.
+
+# Array of positions to write to
+positions=(0 64K 128K 192K)
+expected_reads_one_mountpoint=(0 1 3 6)
+total_expected_reads=24
+
+# Write known content from one mountpoint
+for index in ${!positions[@]}; do
+	position=${positions[$index]}
+
+	# Write known content at positions 0, 64K, 128K, and 192K on mount0
+	echo -n "1" | dd of="${info[mount0]}/file0" seek=$position bs=1 count=1 oflag=direct status=progress
+
+	# Check the number of extra reads
+	number_of_extra_reads=$(grep chunkserver.hddRead $TEMP_DIR/log | wc -l);
+
+	# Assert that the number of extra reads is as expected
+	assert_equals ${expected_reads_one_mountpoint[$index]} "${number_of_extra_reads}"
+done
+
+# Write known content from two mountpoints in parallel 
+for index in ${!positions[@]}; do
+	position=${positions[$index]}
+
+	# Write known content at positions 0, 64K, 128K, and 192K on mount0
+	echo -n "1" | dd of="${info[mount0]}/file1" seek=$position bs=1 count=1 oflag=direct status=progress &
+
+	# In parallel, write known content at the same positions on mount1
+	echo -n "1" | dd of="${info[mount1]}/file1" seek=$position bs=1 count=1 oflag=direct status=progress &
+
+	# Wait for the writes to finish
+	wait
+done
+
+# Check the number of extra reads
+number_of_extra_reads=$(grep chunkserver.hddRead $TEMP_DIR/log | wc -l);
+
+# Assert that the number of extra reads is as expected
+assert_less_or_equal "${number_of_extra_reads}" "${total_expected_reads}"
+
+# Check that all data was correctly written
+for position in ${positions[@]}; do
+	# Read back the data from mount0 and mount1
+	data_mount0_file0=$(dd if="${info[mount0]}/file0" bs=1 count=1 skip=$position status=none)
+	data_mount0_file1=$(dd if="${info[mount0]}/file1" bs=1 count=1 skip=$position status=none)
+	data_mount1_file1=$(dd if="${info[mount1]}/file1" bs=1 count=1 skip=$position status=none)
+
+	# Assert that the data read back is the same as the known content
+	assert_equals "1" "$data_mount0_file0"
+	assert_equals "1" "$data_mount0_file1"
+	assert_equals "1" "$data_mount1_file1"
+done
diff --git a/tests/test_suites/ShortSystemTests/test_replication_bandwidth_limiting.sh b/tests/test_suites/ShortSystemTests/test_replication_bandwidth_limiting.sh
index 7e93fcc73..f2e6a58ec 100644
--- a/tests/test_suites/ShortSystemTests/test_replication_bandwidth_limiting.sh
+++ b/tests/test_suites/ShortSystemTests/test_replication_bandwidth_limiting.sh
@@ -14,7 +14,7 @@ CHUNKSERVERS=2 \
 	setup_local_empty_saunafs info
 
 chunks_health() {
-	saunafs-probe chunks-health --porcelain localhost "${info[matocl]}"
+	saunafs-admin chunks-health --porcelain localhost "${info[matocl]}"
 }
 
 cd "${info[mount0]}"
diff --git a/tests/test_suites/ShortSystemTests/test_saunafs_upgrade_ec_basic.sh b/tests/test_suites/ShortUpgradeTests/test_saunafs_upgrade_ec_basic.sh
similarity index 100%
rename from tests/test_suites/ShortSystemTests/test_saunafs_upgrade_ec_basic.sh
rename to tests/test_suites/ShortUpgradeTests/test_saunafs_upgrade_ec_basic.sh
diff --git a/tests/test_suites/ShortSystemTests/test_saunafs_upgrade_general.sh b/tests/test_suites/ShortUpgradeTests/test_saunafs_upgrade_general.sh
similarity index 100%
rename from tests/test_suites/ShortSystemTests/test_saunafs_upgrade_general.sh
rename to tests/test_suites/ShortUpgradeTests/test_saunafs_upgrade_general.sh
diff --git a/tests/test_suites/ShortSystemTests/test_saunafs_upgrade_new_mount_with_old_saunafs.sh b/tests/test_suites/ShortUpgradeTests/test_saunafs_upgrade_new_mount_with_old_saunafs.sh
similarity index 100%
rename from tests/test_suites/ShortSystemTests/test_saunafs_upgrade_new_mount_with_old_saunafs.sh
rename to tests/test_suites/ShortUpgradeTests/test_saunafs_upgrade_new_mount_with_old_saunafs.sh
diff --git a/tests/test_suites/TestTemplates/test_xor_reading_consistency.inc b/tests/test_suites/TestTemplates/test_ec_reading_consistency.inc
similarity index 92%
rename from tests/test_suites/TestTemplates/test_xor_reading_consistency.inc
rename to tests/test_suites/TestTemplates/test_ec_reading_consistency.inc
index 8c6cf5d7c..94e5f1a39 100644
--- a/tests/test_suites/TestTemplates/test_xor_reading_consistency.inc
+++ b/tests/test_suites/TestTemplates/test_ec_reading_consistency.inc
@@ -1,13 +1,13 @@
 timeout_set 3 hours
 
-source test_utils/test_xor_consistency_utils.sh
+source test_utils/test_consistency_utils.sh
 
 CHUNKSERVERS=3
 MOUNT_EXTRA_CONFIG="sfscachemode=NEVER" \
 	setup_local_empty_saunafs info
 dir="${info[mount0]}/dir"
 mkdir "$dir"
-saunafs setgoal xor2 "$dir"
+saunafs setgoal ec21 "$dir"
 
 DATA_SIZE_PER_THREAD=$(parse_si_suffix 3G)
 thread_count=10
diff --git a/tests/test_suites/TestTemplates/test_xor_writing.inc b/tests/test_suites/TestTemplates/test_ec_writing.inc
similarity index 91%
rename from tests/test_suites/TestTemplates/test_xor_writing.inc
rename to tests/test_suites/TestTemplates/test_ec_writing.inc
index 1a151c10d..6a442c48e 100644
--- a/tests/test_suites/TestTemplates/test_xor_writing.inc
+++ b/tests/test_suites/TestTemplates/test_ec_writing.inc
@@ -1,13 +1,13 @@
 timeout_set 3 hours
 
-source test_utils/test_xor_consistency_utils.sh
+source test_utils/test_consistency_utils.sh
 
 CHUNKSERVERS=3
 MOUNT_EXTRA_CONFIG="sfscachemode=NEVER" \
 	setup_local_empty_saunafs info
 dir="${info[mount0]}/dir"
 mkdir "$dir"
-saunafs setgoal xor2 "$dir"
+saunafs setgoal ec21 "$dir"
 
 DATA_SIZE_PER_THREAD=$(parse_si_suffix 3G)
 thread_count=10
diff --git a/tests/test_suites/TestTemplates/test_xor_overwriting.inc b/tests/test_suites/TestTemplates/test_xor_overwriting.inc
index 2b1fe02cb..b3fa873c0 100644
--- a/tests/test_suites/TestTemplates/test_xor_overwriting.inc
+++ b/tests/test_suites/TestTemplates/test_xor_overwriting.inc
@@ -1,6 +1,6 @@
 timeout_set 3 hours
 
-source test_utils/test_xor_consistency_utils.sh
+source test_utils/test_consistency_utils.sh
 
 CHUNKSERVERS=3
 MOUNT_EXTRA_CONFIG="sfscachemode=NEVER" \
diff --git a/tests/test_utils/test_xor_consistency_utils.sh b/tests/test_utils/test_consistency_utils.sh
similarity index 100%
rename from tests/test_utils/test_xor_consistency_utils.sh
rename to tests/test_utils/test_consistency_utils.sh
diff --git a/tests/tools/assert.sh b/tests/tools/assert.sh
index 999ac0316..9bfaf0f2e 100644
--- a/tests/tools/assert.sh
+++ b/tests/tools/assert.sh
@@ -178,8 +178,7 @@ assert_template_eventually_equals_() {
 	local timeout=$(rescale_timeout_for_assert_eventually_ "${3:-}")
 	if ! wait_for "[[ \$($command1) == \$($command2) ]]" "$timeout"; then
 		diff="$(diff -u5 <(eval "$command1") <(eval "$command2") || true)"
-		$FAIL_FUNCTION "'$command1' didn't output the same as '$command2' within $timeout`
-				`"$'\n'"$diff"
+		$FAIL_FUNCTION "'$command1' didn't output the same as '$command2' within $timeout "$'\n'"$diff"
 	fi
 }
 
diff --git a/tests/tools/saunafs.sh b/tests/tools/saunafs.sh
index a16039495..308d7e018 100644
--- a/tests/tools/saunafs.sh
+++ b/tests/tools/saunafs.sh
@@ -115,7 +115,7 @@ setup_local_empty_saunafs() {
 		add_cgi_server_
 	fi
 
-	# Wait for chunkservers (use saunafs-probe only for SaunaFS -- MooseFS doesn't support it)
+	# Wait for chunkservers (use saunafs-admin only for SaunaFS -- MooseFS doesn't support it)
 	if [[ ! $use_legacy ]]; then
 		saunafs_wait_for_all_ready_chunkservers
 	else
@@ -701,7 +701,8 @@ add_cgi_server_() {
 	saunafs-cgiserver -P "$cgi_server_port" -p "$pidfile"
 	saunafs_info_[cgi_pidfile]=$pidfile
 	saunafs_info_[cgi_port]=$cgi_server_port
-	saunafs_info_[cgi_url]="http://localhost:$cgi_server_port/sfs.cgi?masterport=${saunafs_info_[matocl]}"
+	saunafs_info_[cgi_url]="http://localhost:$cgi_server_port/sfs.cgi?masterhost=localhost&masterport=${saunafs_info_[matocl]}"
+	echo "CGI URL: ${saunafs_info_[cgi_url]}"
 }
 
 # Some helper functions for tests to manipulate the existing installation
@@ -793,14 +794,14 @@ find_all_metadata_chunks() {
 	done
 }
 
-# A useful shortcut for saunafs-probe
-# Usage: saunafs_probe_master <command> [option...]
-# Calls saunafs-probe with the given command and and automatically adds address
+# A useful shortcut for saunafs-admin
+# Usage: saunafs_admin_master_no_password <command> [option...]
+# Calls saunafs-admin with the given command and and automatically adds address
 # of the master server
-saunafs_probe_master() {
+saunafs_admin_master_no_password() {
 	local command="$1"
 	shift
-	saunafs-probe "$command" localhost "${saunafs_info_[matocl]}" --porcelain "$@"
+	saunafs-admin "$command" localhost "${saunafs_info_[matocl]}" --porcelain "$@"
 }
 
 # A useful shortcut for saunafs-admin commands which require authentication
@@ -847,7 +848,7 @@ saunafs_stop_master_without_saving_metadata() {
 
 # print the number of fully operational chunkservers
 saunafs_ready_chunkservers_count() {
-	saunafs-probe ready-chunkservers-count localhost ${saunafs_info_[matocl]}
+	saunafs-admin ready-chunkservers-count localhost ${saunafs_info_[matocl]}
 }
 
 # saunafs_wait_for_ready_chunkservers <num> -- waits until <num> chunkservers are fully operational
@@ -868,9 +869,9 @@ saunafs_shadow_synchronized() {
 	local num=$1
 	local port1=${saunafs_info_[matocl]}
 	local port2=${saunafs_info_[master${num}_matocl]}
-	local probe1="saunafs-probe metadataserver-status --porcelain localhost $port1"
-	local probe2="saunafs-probe metadataserver-status --porcelain localhost $port2"
-	if [[ "$($probe1 | cut -f3)" == "$($probe2 | cut -f3)" ]]; then
+	local admin1="saunafs-admin metadataserver-status --porcelain localhost $port1"
+	local admin2="saunafs-admin metadataserver-status --porcelain localhost $port2"
+	if [[ "$($admin1 | cut -f3)" == "$($admin2 | cut -f3)" ]]; then
 		return 0
 	else
 		return 1
@@ -882,7 +883,8 @@ saunafs_shadow_synchronized() {
 # <ip2>:<port2>:<label> <chunks2>
 # ...
 saunafs_rebalancing_status() {
-	saunafs_probe_master list-chunkservers | sort | awk '$2 == "'$SAUNAFS_VERSION'" {print $1":"$10,$3}'
+	saunafs_admin_master_no_password \
+		list-chunkservers | sort | awk '$2 == "'$SAUNAFS_VERSION'" {print $1":"$10,$3}'
 }
 
 # Tells if the hdd configuration line is for a zoned disk
diff --git a/tests/tools/saunafsXX.sh b/tests/tools/saunafsXX.sh
index fc799f5f0..bd3de7c45 100644
--- a/tests/tools/saunafsXX.sh
+++ b/tests/tools/saunafsXX.sh
@@ -4,22 +4,25 @@
 # TODO: Don't use build information in debian package versions. It causes
 # problems when downgrading, because you need to include the build information
 # in the downgrade command.
-SAUNAFSXX_TAG_APT="4.0.1-20240301-164017-stable-main-82161d4f"
-SAUNAFSXX_TAG="4.0.1"
+
+SAUNAFSXX_TAG="4.1.0"
 
 install_saunafsXX() {
-	rm -rf "$SAUNAFSXX_DIR"
-	mkdir -p "$SAUNAFSXX_DIR"
+	rm -rf "${SAUNAFSXX_DIR}"
+	mkdir -p "${SAUNAFSXX_DIR}"
 	local distro="$(lsb_release -si)"
-	case "$distro" in
+	case "${distro}" in
 	Ubuntu | Debian)
-		local codename="$(lsb_release -sc)"
-		mkdir -p ${TEMP_DIR}/apt/apt.conf.d
-		mkdir -p ${TEMP_DIR}/apt/var/lib/apt/partial
-		mkdir -p ${TEMP_DIR}/apt/var/cache/apt/archives/partial
-		mkdir -p ${TEMP_DIR}/apt/var/lib/dpkg
-		cp /var/lib/dpkg/status ${TEMP_DIR}/apt/var/lib/dpkg/status
-		cat >${TEMP_DIR}/apt/apt.conf <<END
+		local distro_id="$(lsb_release -si | tr '[:upper:]' '[:lower:]' | tail -1)"
+		local codename="$(lsb_release -sc | tail -1)"
+		local release="$(lsb_release -sr | tail -1)"
+		mkdir -p "${TEMP_DIR}/apt/apt.conf.d"
+		mkdir -p "${TEMP_DIR}/apt/var/lib/apt/partial"
+		mkdir -p "${TEMP_DIR}/apt/var/cache/apt/archives/partial"
+		mkdir -p "${TEMP_DIR}/apt/var/lib/dpkg"
+		mkdir -p "${TEMP_DIR}/usr/share/keyrings"
+		cp /var/lib/dpkg/status "${TEMP_DIR}/apt/var/lib/dpkg/status"
+		cat >"${TEMP_DIR}/apt/apt.conf" <<END
 Dir::State "${TEMP_DIR}/apt/var/lib/apt";
 Dir::State::status "${TEMP_DIR}/apt/var/lib/dpkg/status";
 Dir::Etc::SourceList "${TEMP_DIR}/apt/saunafs.list";
@@ -27,56 +30,70 @@ Dir::Cache "${TEMP_DIR}/apt/var/cache/apt";
 Dir::Etc::Parts "${TEMP_DIR}/apt/apt.conf.d";
 END
 		local destdir="${TEMP_DIR}/apt/var/cache/apt/archives"
+		echo "deb [arch=amd64 signed-by=/usr/share/keyrings/saunafs-archive-keyring.gpg] https://repo.saunafs.com/repository/saunafs-${distro_id}-${release}/ ${codename} main" >"${TEMP_DIR}/apt/saunafs.list"
 		env APT_CONFIG="${TEMP_DIR}/apt/apt.conf" apt-get update
-		env APT_CONFIG="${TEMP_DIR}/apt/apt.conf" apt-get -y --allow-downgrades install -d \
-			saunafs-master=${SAUNAFSXX_TAG_APT} \
-			saunafs-chunkserver=${SAUNAFSXX_TAG_APT} \
-			saunafs-client=${SAUNAFSXX_TAG_APT} \
-			saunafs-adm=${SAUNAFSXX_TAG_APT}
+		env APT_CONFIG="${TEMP_DIR}/apt/apt.conf" apt-get install --yes libyaml-cpp*
+		SAUNAFSXX_TAG_APT="4.1.0-20240509-152518-stable-main-a7cb5669"
+		if [ "${distro}" == Ubuntu ]; then
+			case "${release}" in
+				'22.04')
+					SAUNAFSXX_TAG_APT="4.1.0-20240509-152513-stable-main-a7cb5669"
+				;;
+				'24.04')
+					SAUNAFSXX_TAG_APT="4.1.0-20240509-152518-stable-main-a7cb5669"
+				;;
+				*)
+					test_fail "Your Ubuntu release (${release}) is not supported."
+				;;
+			esac
+		fi
+		env APT_CONFIG="${TEMP_DIR}/apt/apt.conf" apt-get --yes --allow-downgrades install --download-only \
+			saunafs-master="${SAUNAFSXX_TAG_APT}" \
+			saunafs-chunkserver="${SAUNAFSXX_TAG_APT}" \
+			saunafs-client="${SAUNAFSXX_TAG_APT}" \
+			saunafs-adm="${SAUNAFSXX_TAG_APT}"
 			# Not used right now
-			# saunafs-metalogger=${SAUNAFSXX_TAG_APT}
+			# saunafs-metalogger="${SAUNAFSXX_TAG_APT}"
 		# unpack binaries
-		cd ${destdir}
-		find . -name "*master*.deb" | xargs dpkg-deb --fsys-tarfile | tar -x ./usr/sbin/sfsmaster
-		find . -name "*chunkserver*.deb" | xargs dpkg-deb --fsys-tarfile | tar -x ./usr/sbin/sfschunkserver
-		find . -name "*client*.deb" | xargs dpkg-deb --fsys-tarfile | tar -x ./usr/bin/
-		find . -name "*adm*.deb" | xargs dpkg-deb --fsys-tarfile | tar -x ./usr/bin/
-		# find . -name "*metalogger*.deb" | xargs dpkg-deb --fsys-tarfile | tar -x ./usr/sbin/
-		cp -Rp usr/ ${SAUNAFSXX_DIR_BASE}/install
-		cd -
-		;;
-#  We don't support testing CentOS and Fedora anymore, but the code is left
-#  here for reference.
-#
-# 	CentOS | Fedora)
-# 		local destdir="${TEMP_DIR}/saunafsxx_packages"
-# 		mkdir ${destdir}
-# 		local url=""
-# 		if [ "$distro" == CentOS ]; then
-# 			url="http://dev.saunafs.com/packages/centos.saunafs.repo"
-# 		else
-# 			url="http://dev.saunafs.com/packages/fedora.saunafs.repo"
-# 		fi
-# 		mkdir -p ${TEMP_DIR}/dnf/etc/yum.repos.d
-# 		cat >${TEMP_DIR}/dnf/dnf.conf <<END
-# [main]
-# logdir=${TEMP_DIR}/dnf/var/log
-# cachedir=${TEMP_DIR}/dnf/var/cache
-# persistdir=${TEMP_DIR}/dnf/var/lib/dnf
-# reposdir=${TEMP_DIR}/dnf/etc/yum.repos.d
-# END
-# 		wget "$url" -O ${TEMP_DIR}/dnf/etc/yum.repos.d/saunafs.repo
-# 		for pkg in {saunafs-master,saunafs-chunkserver,saunafs-client}-${SAUNAFSXX_TAG}; do
-# 			fakeroot dnf -y --config=${TEMP_DIR}/dnf/dnf.conf --destdir=${destdir} download ${pkg}
-# 		done
-# 		# unpack binaries
-# 		cd ${destdir}
-# 		find . -name "*master*.rpm" | xargs rpm2cpio | cpio -idm ./usr/sbin/sfsmaster
-# 		find . -name "*chunkserver*.rpm" | xargs rpm2cpio | cpio -idm ./usr/sbin/sfschunkserver
-# 		find . -name "*client*.rpm" | xargs rpm2cpio | cpio -idm ./usr/bin/*
-#
+		(	cd "${destdir}" || return 1
+			find . -name "*master*.deb" -print0 | xargs -0 dpkg-deb --fsys-tarfile | tar -x ./usr/sbin/sfsmaster
+			find . -name "*chunkserver*.deb" -print0 | xargs -0 dpkg-deb --fsys-tarfile | tar -x ./usr/sbin/sfschunkserver
+			find . -name "*client*.deb" -print0 | xargs -0 dpkg-deb --fsys-tarfile | tar -x ./usr/bin/
+			find . -name "*adm*.deb" -print0 | xargs -0 dpkg-deb --fsys-tarfile | tar -x ./usr/bin/
+			# find . -name "*metalogger*.deb" -print0 | xargs -0 dpkg-deb --fsys-tarfile | tar -x ./usr/sbin/
+			cp -Rp usr/ "${SAUNAFSXX_DIR_BASE}/install"
+		)
+		;; # end of Ubuntu | Debian case
+	CentOS | Fedora)
+		local destdir="${TEMP_DIR}/saunafsxx_packages"
+		mkdir "${destdir}"
+		local url=""
+		if [ "${distro}" == CentOS ]; then
+			url="http://dev.saunafs.com/packages/centos.saunafs.repo"
+		else
+			url="http://dev.saunafs.com/packages/fedora.saunafs.repo"
+		fi
+		mkdir -p "${TEMP_DIR}/dnf/etc/yum.repos.d"
+		cat >"${TEMP_DIR}/dnf/dnf.conf" <<END
+[main]
+logdir="${TEMP_DIR}/dnf/var/log"
+cachedir="${TEMP_DIR}/dnf/var/cache"
+persistdir="${TEMP_DIR}/dnf/var/lib/dnf"
+reposdir="${TEMP_DIR}/dnf/etc/yum.repos.d"
+END
+		wget "${url}" -O "${TEMP_DIR}/dnf/etc/yum.repos.d/saunafs.repo"
+		for pkg in {saunafs-master,saunafs-chunkserver,saunafs-client}-${SAUNAFSXX_TAG}; do
+			fakeroot dnf -y --config="${TEMP_DIR}/dnf/dnf.conf" --destdir="${destdir}" download "${pkg}"
+		done
+		# unpack binaries
+		(	cd "${destdir}"
+		find . -name "*master*.rpm" -print0 | xargs -0 rpm2cpio | cpio -idm ./usr/sbin/sfsmaster
+		find . -name "*chunkserver*.rpm" -print0 | xargs -0 rpm2cpio | cpio -idm ./usr/sbin/sfschunkserver
+		find . -name "*client*.rpm" -print0 | xargs -0 rpm2cpio | cpio -idm ./usr/bin/*
+		)
+		;; # end of CentOS | Fedora case
 	*)
-		test_fail "Your distribution ($distro) is not supported."
+		test_fail "Your distribution (${distro}) is not supported."
 		;;
 	esac
 	test_saunafsXX_executables
@@ -84,37 +101,38 @@ END
 }
 
 test_saunafsXX_executables() {
-	local awk_version_pattern="/$(sed 's/[.]/[.]/g' <<<$SAUNAFSXX_TAG)/"
-	echo $($SAUNAFSXX_DIR/bin/sfsmount --version 2>&1)
-	echo $($SAUNAFSXX_DIR/sbin/sfschunkserver -v)
-	echo $($SAUNAFSXX_DIR/sbin/sfsmaster -v)
-	test -x "$SAUNAFSXX_DIR/bin/sfsmount"
-	test -x "$SAUNAFSXX_DIR/sbin/sfschunkserver"
-	test -x "$SAUNAFSXX_DIR/sbin/sfsmaster"
-	assert_awk_finds $awk_version_pattern "$($SAUNAFSXX_DIR/bin/sfsmount --version 2>&1)"
-	assert_awk_finds $awk_version_pattern "$($SAUNAFSXX_DIR/sbin/sfschunkserver -v)"
-	assert_awk_finds $awk_version_pattern "$($SAUNAFSXX_DIR/sbin/sfsmaster -v)"
+	local awk_version_pattern="/${SAUNAFSXX_TAG//./\\.}/"
+	test -x "${SAUNAFSXX_DIR}/bin/sfsmount"
+	test -x "${SAUNAFSXX_DIR}/sbin/sfschunkserver"
+	test -x "${SAUNAFSXX_DIR}/sbin/sfsmaster"
+	"${SAUNAFSXX_DIR}/bin/sfsmount" --version 2>&1
+	"${SAUNAFSXX_DIR}/sbin/sfschunkserver" -v
+	"${SAUNAFSXX_DIR}/sbin/sfsmaster" -v
+	assert_awk_finds "${awk_version_pattern}" "$("${SAUNAFSXX_DIR}/bin/sfsmount" --version 2>&1)"
+	assert_awk_finds "${awk_version_pattern}" "$("${SAUNAFSXX_DIR}/sbin/sfschunkserver" -v)"
+	assert_awk_finds "${awk_version_pattern}" "$("${SAUNAFSXX_DIR}/sbin/sfsmaster" -v)"
 }
 
 saunafsXX_chunkserver_daemon() {
-	"$SAUNAFSXX_DIR/sbin/sfschunkserver" -c "${saunafs_info_[chunkserver$1_cfg]}" "$2" | cat
-	return ${PIPESTATUS[0]}
+	# shellcheck disable=SC2154
+	"${SAUNAFSXX_DIR}/sbin/sfschunkserver" -c "${saunafs_info_[chunkserver${1_cfg}]}" "${2}" | cat
+	return "${PIPESTATUS[0]}"
 }
 
 saunafsXX_master_daemon() {
-	"$SAUNAFSXX_DIR/sbin/sfsmaster" -c "${saunafs_info_[master_cfg]}" "$1" | cat
-	return ${PIPESTATUS[0]}
+	"${SAUNAFSXX_DIR}/sbin/sfsmaster" -c "${saunafs_info_[master_cfg]}" "${1}" | cat
+	return "${PIPESTATUS[0]}"
 }
 
 saunafsXX_shadow_daemon_n() {
-	local id=$1
+	local id="${1}"
 	shift
 	if is_windows_system; then
-		windows_server_aux "$SAUNAFSXX_DIR/sbin/sfsmaster -c ${saunafs_info_[master${id}_cfg]}" "$@"
+		windows_server_aux "${SAUNAFSXX_DIR}/sbin/sfsmaster -c ${saunafs_info_[master${id}_cfg]}" "${@}"
 	else
-		"$SAUNAFSXX_DIR/sbin/sfsmaster" -c "${saunafs_info_[master${id}_cfg]}" "$@" | cat
+		"${SAUNAFSXX_DIR}/sbin/sfsmaster" -c "${saunafs_info_[master${id}_cfg]}" "${@}" | cat
 	fi
-	return ${PIPESTATUS[0]}
+	return "${PIPESTATUS[0]}"
 }
 
 # A generic function to run SaunaFS commands.
@@ -124,19 +142,25 @@ saunafsXX_shadow_daemon_n() {
 #   sfs sfsdirinfo file
 #   sfs sfsmetalogger stop
 saunafsXX() {
-	local command="$1"
+	local command="${1}"
 	shift
-	"$SAUNAFSXX_DIR/"*bin"/$command" "$@" | cat
-	return ${PIPESTATUS[0]}
+	for path in "${SAUNAFSXX_DIR}/bin" "${SAUNAFSXX_DIR}/sbin"; do
+		if [ -x "${path}/${command}" ]; then
+			command="${path}/${command}"
+			break
+		fi
+	done
+	"${command}" "${@}"
+	return "${PIPESTATUS[0]}"
 }
 
 assert_saunafsXX_services_count_equals() {
 	local mas_expected="${1}"
 	local cs_expected="${2}"
 	local cli_expected="${3}"
-	assert_equals "${mas_expected}" "$(saunafs_old_admin_master info | grep "${SAUNAFSXX_TAG}" | wc -l)"
-	assert_equals "${cs_expected}" "$(saunafs_old_admin_master list-chunkservers | grep "${SAUNAFSXX_TAG}" | wc -l)"
-	assert_equals "${cli_expected}" "$(saunafs_old_admin_master list-mounts | grep "${SAUNAFSXX_TAG}" | wc -l)"
+	assert_equals "${mas_expected}" "$(saunafs_old_admin_master info | grep -c "${SAUNAFSXX_TAG}")"
+	assert_equals "${cs_expected}" "$(saunafs_old_admin_master list-chunkservers | grep -c "${SAUNAFSXX_TAG}")"
+	assert_equals "${cli_expected}" "$(saunafs_old_admin_master list-mounts | grep -c "${SAUNAFSXX_TAG}")"
 }
 
 assert_no_saunafsXX_services_active() {
diff --git a/tests/tools/test.sh b/tests/tools/test.sh
index 8d13bb84f..bcc471fed 100644
--- a/tests/tools/test.sh
+++ b/tests/tools/test.sh
@@ -86,8 +86,12 @@ debug_command() {
 	fi
 	if (( depth <= DEBUG_LEVEL )); then
 		local indent=">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"
-		echo "${indent:0:$depth} $(basename "${BASH_SOURCE[1]}"):${BASH_LINENO[0]}:`
-				`${FUNCNAME[1]}(): ${BASH_COMMAND}" >&2 | true
+		local level=${indent:0:$depth}
+		local source=$(basename "${BASH_SOURCE[1]}")
+		local line=${BASH_LINENO[0]}
+		local function=${FUNCNAME[1]}
+		local command=${BASH_COMMAND}
+		echo "${level} ${source}:${line}: ${function}(): ${command}" >&2 | true
 	fi
 }
 
@@ -200,9 +204,10 @@ test_cleanup() {
 	fi
 
 	# Clean the disks used by chunkservers
-	for d in $SAUNAFS_DISKS $SAUNAFS_LOOP_DISKS; do
-		rm -rf "$d"/chunks[0-9A-F][0-9A-F]
-		rm -f "$d"/.lock
+	for disk in ${SAUNAFS_DISKS} ${SAUNAFS_LOOP_DISKS}; do
+		rm -rf "${disk}"/meta/chunks[0-9A-F][0-9A-F]
+		rm -rf "${disk}"/data/chunks[0-9A-F][0-9A-F]
+		rm -f "${disk}"/.lock
 	done
 }