diff --git a/src/OpenFOAM/db/IOstreams/Fstreams/masterOFstream.C b/src/OpenFOAM/db/IOstreams/Fstreams/masterOFstream.C
index 79da7ae93b47eca540f218f240ca458fa3c098c0..cc80139f0e68f4c65b2d932962718b4a84fcb231 100644
--- a/src/OpenFOAM/db/IOstreams/Fstreams/masterOFstream.C
+++ b/src/OpenFOAM/db/IOstreams/Fstreams/masterOFstream.C
@@ -47,7 +47,7 @@ void Foam::masterOFstream::checkWrite
         return;
     }
 
-    mkDir(fName.path());
+    Foam::mkDir(fName.path());
 
     OFstream os
     (
@@ -89,19 +89,23 @@ void Foam::masterOFstream::checkWrite
 
 void Foam::masterOFstream::commit()
 {
-    if (Pstream::parRun())
+    if (UPstream::parRun())
     {
-        List<fileName> filePaths(Pstream::nProcs());
-        filePaths[Pstream::myProcNo()] = pathName_;
-        Pstream::gatherList(filePaths);
+        List<fileName> filePaths(UPstream::nProcs(comm_));
+        filePaths[UPstream::myProcNo(comm_)] = pathName_;
+        Pstream::gatherList(filePaths, UPstream::msgType(), comm_);
 
-        bool uniform = fileOperation::uniformFile(filePaths);
+        bool uniform =
+        (
+            UPstream::master(comm_)
+         && fileOperation::uniformFile(filePaths)
+        );
 
-        Pstream::broadcast(uniform);
+        Pstream::broadcast(uniform, comm_);
 
         if (uniform)
         {
-            if (Pstream::master() && writeOnProc_)
+            if (UPstream::master(comm_) && writeOnProc_)
             {
                 checkWrite(pathName_, this->str());
             }
@@ -111,36 +115,38 @@ void Foam::masterOFstream::commit()
         }
 
         // Different files
-        PstreamBuffers pBufs(Pstream::commsTypes::nonBlocking);
+        PstreamBuffers pBufs(comm_, UPstream::commsTypes::nonBlocking);
 
-        // Send my (valid) buffer to master
-        if (!Pstream::master())
+        if (!UPstream::master(comm_))
         {
             if (writeOnProc_)
             {
+                // Send buffer to master
                 string s(this->str());
 
-                UOPstream os(Pstream::masterNo(), pBufs);
+                UOPstream os(UPstream::masterNo(), pBufs);
                 os.write(s.data(), s.length());
             }
-            this->reset();
+            this->reset();  // Done with contents
         }
 
         pBufs.finishedGathers();
 
-        if (Pstream::master())
+
+        if (UPstream::master(comm_))
         {
-            // Write (valid) master data
             if (writeOnProc_)
             {
-                checkWrite(filePaths[Pstream::masterNo()], this->str());
+                // Write master data
+                checkWrite(filePaths[UPstream::masterNo()], this->str());
             }
-            this->reset();
+            this->reset();  // Done with contents
+
 
             // Allocate large enough to read without resizing
             List<char> buf(pBufs.maxRecvCount());
 
-            for (const int proci : Pstream::subProcs())
+            for (const int proci : UPstream::subProcs(comm_))
             {
                 const std::streamsize count(pBufs.recvDataCount(proci));
 
@@ -170,6 +176,7 @@ void Foam::masterOFstream::commit()
 Foam::masterOFstream::masterOFstream
 (
     IOstreamOption::atomicType atomic,
+    const label comm,
     const fileName& pathName,
     IOstreamOption streamOpt,
     IOstreamOption::appendType append,
@@ -181,7 +188,8 @@ Foam::masterOFstream::masterOFstream
     atomic_(atomic),
     compression_(streamOpt.compression()),
     append_(append),
-    writeOnProc_(writeOnProc)
+    writeOnProc_(writeOnProc),
+    comm_(comm)
 {}
 
 
diff --git a/src/OpenFOAM/db/IOstreams/Fstreams/masterOFstream.H b/src/OpenFOAM/db/IOstreams/Fstreams/masterOFstream.H
index 4abeaf17e8cd6077d6cb647c30f0429e1669723b..c139e00e0de7a1cbe3693c1b0723dc5d78b88186 100644
--- a/src/OpenFOAM/db/IOstreams/Fstreams/masterOFstream.H
+++ b/src/OpenFOAM/db/IOstreams/Fstreams/masterOFstream.H
@@ -6,7 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2017 OpenFOAM Foundation
-    Copyright (C) 2020-2022 OpenCFD Ltd.
+    Copyright (C) 2020-2023 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -30,6 +30,9 @@ Class
 Description
     Master-only drop-in replacement for OFstream.
 
+    Called on all processors (of the provided communicator).
+    Sends files to the master and writes them there.
+
 SourceFiles
     masterOFstream.C
 
@@ -39,6 +42,7 @@ SourceFiles
 #define Foam_masterOFstream_H
 
 #include "StringStream.H"
+#include "UPstream.H"
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
@@ -70,6 +74,9 @@ class masterOFstream
         //- Should file be written (on this processor)
         const bool writeOnProc_;
 
+        //- Communicator
+        const label comm_;
+
 
     // Private Member Functions
 
@@ -92,17 +99,62 @@ public:
 
     // Constructors
 
-        //- Construct with specified atomic behaviour (with worldComm)
+        //- Construct with specified atomic behaviour and communicator
         //- from pathname, stream option, optional append
         masterOFstream
         (
             IOstreamOption::atomicType atomic,
+            const label comm,
             const fileName& pathname,
             IOstreamOption streamOpt = IOstreamOption(),
             IOstreamOption::appendType append = IOstreamOption::NON_APPEND,
             const bool writeOnProc = true
         );
 
+        //- Construct with specified communicator
+        //- from pathname, stream option, optional append
+        masterOFstream
+        (
+            const label comm,
+            const fileName& pathname,
+            IOstreamOption streamOpt = IOstreamOption(),
+            IOstreamOption::appendType append = IOstreamOption::NON_APPEND,
+            const bool writeOnProc = true
+        )
+        :
+            masterOFstream
+            (
+                IOstreamOption::NON_ATOMIC,
+                comm,
+                pathname,
+                streamOpt,
+                append,
+                writeOnProc
+            )
+        {}
+
+        //- Construct with specified atomic behaviour (with worldComm)
+        //- from pathname, stream option, optional append
+        masterOFstream
+        (
+            IOstreamOption::atomicType atomic,
+            const fileName& pathname,
+            IOstreamOption streamOpt = IOstreamOption(),
+            IOstreamOption::appendType append = IOstreamOption::NON_APPEND,
+            const bool writeOnProc = true
+        )
+        :
+            masterOFstream
+            (
+                atomic,
+                UPstream::worldComm,
+                pathname,
+                streamOpt,
+                append,
+                writeOnProc
+            )
+        {}
+
         //- Construct (with worldComm)
         //- from pathname, stream option, optional append
         explicit masterOFstream
@@ -116,6 +168,7 @@ public:
             masterOFstream
             (
                 IOstreamOption::NON_ATOMIC,
+                UPstream::worldComm,
                 pathname,
                 streamOpt,
                 append,
diff --git a/src/OpenFOAM/global/fileOperations/collatedFileOperation/collatedFileOperation.C b/src/OpenFOAM/global/fileOperations/collatedFileOperation/collatedFileOperation.C
index 191ae1f83a70cc6286c17142d2f136ac4e01e8b8..1c846ae5d8702f110ab3edfb5d9efa492d760f43 100644
--- a/src/OpenFOAM/global/fileOperations/collatedFileOperation/collatedFileOperation.C
+++ b/src/OpenFOAM/global/fileOperations/collatedFileOperation/collatedFileOperation.C
@@ -50,6 +50,12 @@ namespace fileOperations
         collatedFileOperation,
         word
     );
+    addToRunTimeSelectionTable
+    (
+        fileOperation,
+        collatedFileOperation,
+        comm
+    );
 
     float collatedFileOperation::maxThreadFileBufferSize
     (
@@ -146,6 +152,7 @@ void Foam::fileOperations::collatedFileOperation::printBanner
         }
     }
 
+    //- fileModificationChecking already set by base class (masterUncollated)
     // if (IOobject::fileModificationChecking == IOobject::timeStampMaster)
     // {
     //     WarningInFunction
@@ -159,29 +166,6 @@ void Foam::fileOperations::collatedFileOperation::printBanner
 }
 
 
-bool Foam::fileOperations::collatedFileOperation::isMasterRank
-(
-    const label proci
-)
-const
-{
-    if (Pstream::parRun())
-    {
-        return Pstream::master(comm_);
-    }
-    else if (ioRanks_.size())
-    {
-        // Found myself in IO rank
-        return ioRanks_.found(proci);
-    }
-    else
-    {
-        // Assume all in single communicator
-        return proci == 0;
-    }
-}
-
-
 bool Foam::fileOperations::collatedFileOperation::appendObject
 (
     const regIOobject& io,
@@ -207,10 +191,10 @@ bool Foam::fileOperations::collatedFileOperation::appendObject
             << exit(FatalError);
     }
 
-    const bool isMaster = isMasterRank(proci);
+    const bool isIOmaster = fileOperation::isIOrank(proci);
 
     // Update meta-data for current state
-    if (isMaster)
+    if (isIOmaster)
     {
         const_cast<regIOobject&>(io).updateMetaData();
     }
@@ -227,7 +211,7 @@ bool Foam::fileOperations::collatedFileOperation::appendObject
         // UNCOMPRESSED (binary only)
         IOstreamOption(IOstreamOption::BINARY, streamOpt.version()),
         // Append on sub-ranks
-        (isMaster ? IOstreamOption::NON_APPEND : IOstreamOption::APPEND)
+        (isIOmaster ? IOstreamOption::NON_APPEND : IOstreamOption::APPEND)
     );
 
     if (!os.good())
@@ -237,7 +221,7 @@ bool Foam::fileOperations::collatedFileOperation::appendObject
             << exit(FatalIOError);
     }
 
-    if (isMaster)
+    if (isIOmaster)
     {
         decomposedBlockData::writeHeader(os, streamOpt, io);
     }
@@ -248,14 +232,44 @@ bool Foam::fileOperations::collatedFileOperation::appendObject
         streamOpt,
         io,
         proci,
-        // With FoamFile header on master?
-        isMaster
+        isIOmaster  // With FoamFile header on master
     );
 
     return (blockOffset >= 0) && os.good();
 }
 
 
+// * * * * * * * * * * * * * * * Local Functions * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+
+// Construction helper: self/world/local communicator and IO ranks
+static Tuple2<label, labelList> getCommPattern()
+{
+    // Default is COMM_WORLD (single master)
+    Tuple2<label, labelList> commAndIORanks
+    (
+        UPstream::worldComm,
+        fileOperation::getGlobalIORanks()
+    );
+
+    if (UPstream::parRun() && commAndIORanks.second().size() > 1)
+    {
+        // Multiple masters: ranks for my IO range
+        commAndIORanks.first() = UPstream::allocateCommunicator
+        (
+            UPstream::worldComm,
+            fileOperation::subRanks(commAndIORanks.second())
+        );
+    }
+
+    return commAndIORanks;
+}
+
+} // End namespace Foam
+
+
 // * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
 
 void Foam::fileOperations::collatedFileOperation::init(bool verbose)
@@ -276,21 +290,12 @@ Foam::fileOperations::collatedFileOperation::collatedFileOperation
 :
     masterUncollatedFileOperation
     (
-        (
-            fileOperation::getGlobalIORanks().size()
-          ? UPstream::allocateCommunicator
-            (
-                UPstream::worldComm,
-                subRanks(UPstream::nProcs())
-            )
-          : UPstream::worldComm
-        ),
-        false
+        getCommPattern(),
+        false,  // distributedRoots
+        false   // verbose
     ),
     managedComm_(getManagedComm(comm_)),  // Possibly locally allocated
-    writer_(mag(maxThreadFileBufferSize), comm_),
-    nProcs_(Pstream::nProcs()),
-    ioRanks_(fileOperation::getGlobalIORanks())
+    writer_(mag(maxThreadFileBufferSize), comm_)
 {
     init(verbose);
 }
@@ -298,16 +303,19 @@ Foam::fileOperations::collatedFileOperation::collatedFileOperation
 
 Foam::fileOperations::collatedFileOperation::collatedFileOperation
 (
-    const label comm,
-    const labelList& ioRanks,
+    const Tuple2<label, labelList>& commAndIORanks,
+    const bool distributedRoots,
     bool verbose
 )
 :
-    masterUncollatedFileOperation(comm, false),
+    masterUncollatedFileOperation
+    (
+        commAndIORanks,
+        distributedRoots,
+        false   // verbose
+    ),
     managedComm_(-1),  // Externally managed
-    writer_(mag(maxThreadFileBufferSize), comm),
-    nProcs_(Pstream::nProcs()),
-    ioRanks_(ioRanks)
+    writer_(mag(maxThreadFileBufferSize), comm_)
 {
     init(verbose);
 }
@@ -392,6 +400,7 @@ bool Foam::fileOperations::collatedFileOperation::writeObject
         // Note: currently still NON_ATOMIC (Dec-2022)
         masterOFstream os
         (
+            comm_,
             pathName,
             streamOpt,
             IOstreamOption::NON_APPEND,
@@ -423,7 +432,7 @@ bool Foam::fileOperations::collatedFileOperation::writeObject
         mkDir(path);
         fileName pathName(path/io.name());
 
-        if (io.global())
+        if (io.global() || io.globalObject())
         {
             if (debug)
             {
@@ -436,6 +445,7 @@ bool Foam::fileOperations::collatedFileOperation::writeObject
             // Note: currently still NON_ATOMIC (Dec-2022)
             masterOFstream os
             (
+                comm_,
                 pathName,
                 streamOpt,
                 IOstreamOption::NON_APPEND,
@@ -459,7 +469,7 @@ bool Foam::fileOperations::collatedFileOperation::writeObject
 
             return ok;
         }
-        else if (!Pstream::parRun())
+        else if (!UPstream::parRun())
         {
             // Special path for e.g. decomposePar. Append to
             // processorsDDD/ file
@@ -502,7 +512,7 @@ bool Foam::fileOperations::collatedFileOperation::writeObject
 
             bool ok = os.good();
 
-            if (Pstream::master(comm_))
+            if (UPstream::master(comm_))
             {
                 // Suppress comment banner
                 const bool old = IOobject::bannerEnabled(false);
@@ -548,13 +558,13 @@ Foam::word Foam::fileOperations::collatedFileOperation::processorsDir
     const fileName& fName
 ) const
 {
-    if (Pstream::parRun())
+    if (UPstream::parRun())
     {
         const List<int>& procs(UPstream::procID(comm_));
 
-        word procDir(processorsBaseDir+Foam::name(Pstream::nProcs()));
+        word procDir(processorsBaseDir+Foam::name(nProcs_));
 
-        if (procs.size() != Pstream::nProcs())
+        if (procs.size() != nProcs_)
         {
             procDir +=
               + "_"
@@ -616,16 +626,4 @@ Foam::word Foam::fileOperations::collatedFileOperation::processorsDir
 }
 
 
-void Foam::fileOperations::collatedFileOperation::setNProcs(const label nProcs)
-{
-    nProcs_ = nProcs;
-
-    if (debug)
-    {
-        Pout<< "collatedFileOperation::setNProcs :"
-            << " Setting number of processors to " << nProcs_ << endl;
-    }
-}
-
-
 // ************************************************************************* //
diff --git a/src/OpenFOAM/global/fileOperations/collatedFileOperation/collatedFileOperation.H b/src/OpenFOAM/global/fileOperations/collatedFileOperation/collatedFileOperation.H
index bfec2a9e322f33b9dd3077a7cdb15221175b454b..d2c9ad9888fc813e973a65bbc1bd5e7c1d37005d 100644
--- a/src/OpenFOAM/global/fileOperations/collatedFileOperation/collatedFileOperation.H
+++ b/src/OpenFOAM/global/fileOperations/collatedFileOperation/collatedFileOperation.H
@@ -6,7 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2017 OpenFOAM Foundation
-    Copyright (C) 2019-2022 OpenCFD Ltd.
+    Copyright (C) 2019-2023 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -86,24 +86,12 @@ protected:
         //- Threaded writer
         mutable OFstreamCollator writer_;
 
-        // For non-parallel operation
-
-            //- Number of processors (overall)
-            label nProcs_;
-
-            //- Ranks of IO handlers
-            const labelList ioRanks_;
-
 
     // Protected Member Functions
 
         //- Print banner information, optionally with io ranks
         void printBanner(const bool withRanks = false) const;
 
-        //- Is proci master of communicator (in parallel) or master of
-        //- the io ranks (non-parallel)
-        bool isMasterRank(const label proci) const;
-
         //- Append to processorsNN/ file
         bool appendObject
         (
@@ -115,8 +103,8 @@ protected:
 
 public:
 
-        //- Runtime type information
-        TypeName("collated");
+    //- Runtime type information
+    TypeName("collated");
 
 
     // Static Data
@@ -132,11 +120,11 @@ public:
         //- Default construct
         explicit collatedFileOperation(bool verbose = false);
 
-        //- Construct from user communicator
-        collatedFileOperation
+        //- Construct from communicator with specified io-ranks
+        explicit collatedFileOperation
         (
-            const label comm,
-            const labelList& ioRanks,
+            const Tuple2<label, labelList>& commAndIORanks,
+            const bool distributedRoots,
             bool verbose = false
         );
 
@@ -170,6 +158,7 @@ public:
                 const bool writeOnProc = true
             ) const;
 
+
         // Other
 
             //- Forcibly wait until all output done. Flush any cached data
@@ -180,10 +169,6 @@ public:
 
             //- Actual name of processors dir
             virtual word processorsDir(const fileName&) const;
-
-            //- Set number of processor directories/results.
-            //- Only used in decomposePar
-            virtual void setNProcs(const label nProcs);
 };
 
 
diff --git a/src/OpenFOAM/global/fileOperations/collatedFileOperation/hostCollatedFileOperation.C b/src/OpenFOAM/global/fileOperations/collatedFileOperation/hostCollatedFileOperation.C
index 1bbf105fe8879645ed8057b9813d1a2f424b0d4e..f7ede70f30fcafdfb3de65f04c5b0387851eda00 100644
--- a/src/OpenFOAM/global/fileOperations/collatedFileOperation/hostCollatedFileOperation.C
+++ b/src/OpenFOAM/global/fileOperations/collatedFileOperation/hostCollatedFileOperation.C
@@ -28,7 +28,6 @@ License
 
 #include "hostCollatedFileOperation.H"
 #include "addToRunTimeSelectionTable.H"
-#include "bitSet.H"
 
 /* * * * * * * * * * * * * * * Static Member Data  * * * * * * * * * * * * * */
 
@@ -43,6 +42,12 @@ namespace fileOperations
         hostCollatedFileOperation,
         word
     );
+    addToRunTimeSelectionTable
+    (
+        fileOperation,
+        hostCollatedFileOperation,
+        comm
+    );
 
     // Register initialisation routine. Signals need for threaded mpi and
     // handles command line arguments
@@ -57,25 +62,41 @@ namespace fileOperations
 }
 
 
-// * * * * * * * * * * * * * Private Member Functions  * * * * * * * * * * * //
+// * * * * * * * * * * * * * * * Local Functions * * * * * * * * * * * * * * //
 
-Foam::labelList Foam::fileOperations::hostCollatedFileOperation::subRanks
-(
-    const label n
-)
+namespace Foam
 {
-    labelList mainIOranks(fileOperation::getGlobalIORanks());
+// Construction helper: self/world/local communicator and IO ranks
+static Tuple2<label, labelList> getCommPattern()
+{
+    // Default is COMM_WORLD (single master)
+    Tuple2<label, labelList> commAndIORanks
+    (
+        UPstream::worldComm,
+        fileOperation::getGlobalIORanks()
+    );
 
-    if (mainIOranks.empty())
+    if (commAndIORanks.second().empty())
     {
-        mainIOranks = fileOperation::getGlobalHostIORanks();
+        // Default: one master per host
+        commAndIORanks.second() = fileOperation::getGlobalHostIORanks();
     }
 
-    labelRange subRange = fileOperation::subRanks(mainIOranks);
+    if (UPstream::parRun() && commAndIORanks.second().size() > 1)
+    {
+        // Multiple masters: ranks for my IO range
+        commAndIORanks.first() = UPstream::allocateCommunicator
+        (
+            UPstream::worldComm,
+            fileOperation::subRanks(commAndIORanks.second())
+        );
+    }
 
-    return identity(subRange);
+    return commAndIORanks;
 }
 
+} // End namespace Foam
+
 
 // * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
 
@@ -97,13 +118,9 @@ Foam::fileOperations::hostCollatedFileOperation::hostCollatedFileOperation
 :
     collatedFileOperation
     (
-        UPstream::allocateCommunicator
-        (
-            UPstream::worldComm,
-            subRanks(UPstream::nProcs())
-        ),
-        (UPstream::parRun() ? labelList() : getGlobalIORanks()),
-        false  // verbose
+        getCommPattern(),
+        false,  // distributedRoots
+        false   // verbose
     ),
     managedComm_(getManagedComm(comm_))  // Possibly locally allocated
 {
@@ -111,6 +128,25 @@ Foam::fileOperations::hostCollatedFileOperation::hostCollatedFileOperation
 }
 
 
+Foam::fileOperations::hostCollatedFileOperation::hostCollatedFileOperation
+(
+    const Tuple2<label, labelList>& commAndIORanks,
+    const bool distributedRoots,
+    bool verbose
+)
+:
+    collatedFileOperation
+    (
+        commAndIORanks,
+        distributedRoots,
+        false   // verbose
+    ),
+    managedComm_(-1)  // Externally managed
+{
+    init(verbose);
+}
+
+
 void Foam::fileOperations::hostCollatedFileOperation::storeComm() const
 {
     // From externally -> locally managed
diff --git a/src/OpenFOAM/global/fileOperations/collatedFileOperation/hostCollatedFileOperation.H b/src/OpenFOAM/global/fileOperations/collatedFileOperation/hostCollatedFileOperation.H
index f6ea90cf1732724b419c1ff8ccd857aae2357001..23840308c82539a8169b558a3be569b0414bbe45 100644
--- a/src/OpenFOAM/global/fileOperations/collatedFileOperation/hostCollatedFileOperation.H
+++ b/src/OpenFOAM/global/fileOperations/collatedFileOperation/hostCollatedFileOperation.H
@@ -96,8 +96,6 @@ class hostCollatedFileOperation
         //- Any initialisation steps after constructing
         void init(bool verbose);
 
-        //- Get the list of processors part of this set
-        static labelList subRanks(const label n);
 
 public:
 
@@ -110,6 +108,14 @@ public:
         //- Default construct
         explicit hostCollatedFileOperation(bool verbose = false);
 
+        //- Construct from communicator with specified io-ranks
+        explicit hostCollatedFileOperation
+        (
+            const Tuple2<label, labelList>& commAndIORanks,
+            const bool distributedRoots,
+            bool verbose = false
+        );
+
 
     //- Destructor
     virtual ~hostCollatedFileOperation();
diff --git a/src/OpenFOAM/global/fileOperations/fileOperation/fileOperation.C b/src/OpenFOAM/global/fileOperations/fileOperation/fileOperation.C
index 996c7d6e8f615fd8dc6491882b499ef0fe307ce9..08710c72fa321b3c714bb0ea3e062078f00d7ac6 100644
--- a/src/OpenFOAM/global/fileOperations/fileOperation/fileOperation.C
+++ b/src/OpenFOAM/global/fileOperations/fileOperation/fileOperation.C
@@ -27,16 +27,12 @@ License
 \*---------------------------------------------------------------------------*/
 
 #include "fileOperation.H"
-#include "regIOobject.H"
-#include "argList.H"
-#include "HashSet.H"
 #include "objectRegistry.H"
-#include "decomposedBlockData.H"
-#include "polyMesh.H"
+#include "labelIOList.H"
 #include "registerSwitch.H"
+#include "stringOps.H"
 #include "Time.H"
-#include "ITstream.H"
-#include <cerrno>
+#include "OSspecific.H"  // for Foam::isDir etc
 #include <cinttypes>
 
 /* * * * * * * * * * * * * * * Static Member Data  * * * * * * * * * * * * * */
@@ -45,6 +41,7 @@ namespace Foam
 {
     defineTypeNameAndDebug(fileOperation, 0);
     defineRunTimeSelectionTable(fileOperation, word);
+    defineRunTimeSelectionTable(fileOperation, comm);
 
     word fileOperation::defaultFileHandler
     (
@@ -80,6 +77,9 @@ Foam::fileOperation::pathTypeNames_
 
 Foam::word Foam::fileOperation::processorsBaseDir = "processors";
 
+//- Caching (e.g. of time directories) - enabled by default
+int Foam::fileOperation::cacheLevel_(1);
+
 
 // * * * * * * * * * * * * * * * Local Functions * * * * * * * * * * * * * * //
 
@@ -425,6 +425,13 @@ Foam::fileOperation::lookupAndCacheProcessorsPath
     // find the corresponding actual processor directory (e.g. 'processors4')
     // and index (2)
 
+    // Behaviour affected by
+    // - UPstream::parRun()
+    // - syncPar : usually true, only uncollated does false. Determines
+    //             if directory status gets synchronised
+    // - distributed() : different processors have different roots
+    // - fileModificationChecking : (uncollated only) do IO on master only
+
     fileName path, pDir, local;
     procRangeType group;
     label numProcs;
@@ -435,11 +442,14 @@ Foam::fileOperation::lookupAndCacheProcessorsPath
     {
         const fileName procPath(path/pDir);
 
-        const auto iter = procsDirs_.cfind(procPath);
-
-        if (iter.good())
+        if (cacheLevel() > 0)
         {
-            return iter.val();
+            const auto iter = procsDirs_.cfind(procPath);
+
+            if (iter.good())
+            {
+                return iter.val();
+            }
         }
 
         DynamicList<dirIndex> procDirs;
@@ -453,7 +463,7 @@ Foam::fileOperation::lookupAndCacheProcessorsPath
 
         const bool readDirMasterOnly
         (
-            Pstream::parRun() && !distributed()
+            UPstream::parRun() && !distributed()
          &&
             (
                 IOobject::fileModificationChecking == IOobject::timeStampMaster
@@ -469,7 +479,7 @@ Foam::fileOperation::lookupAndCacheProcessorsPath
             // Parallel and non-distributed
             // Read on master only and send to subProcs
 
-            if (Pstream::master(comm_))
+            if (UPstream::master(UPstream::worldComm))
             {
                 dirEntries = Foam::readDir(path, fileName::Type::DIRECTORY);
 
@@ -478,7 +488,7 @@ Foam::fileOperation::lookupAndCacheProcessorsPath
                     << " names to sub-processes" << endl;
             }
 
-            Pstream::broadcast(dirEntries, comm_);
+            Pstream::broadcast(dirEntries, UPstream::worldComm);
         }
         else
         {
@@ -534,7 +544,7 @@ Foam::fileOperation::lookupAndCacheProcessorsPath
                         pathTypeIdx.second() = proci;
                     }
                 }
-                else if (group.found(proci))
+                else if (group.contains(proci))
                 {
                     // "processorsNN_start-end"
                     // - save the local proc offset
@@ -564,7 +574,7 @@ Foam::fileOperation::lookupAndCacheProcessorsPath
                 << " detected:" << procDirs << endl;
         }
 
-        if (Pstream::parRun() && (!distributed() || syncPar))
+        if (UPstream::parRun() && (!distributed() || syncPar))
         {
             reduce(procDirsStatus, bitOrOp<unsigned>());  // worldComm
 
@@ -595,7 +605,8 @@ Foam::fileOperation::lookupAndCacheProcessorsPath
                     if
                     (
                         pathTypeIdx.first() == pathType::PROCBASEOBJECT
-                     && proci < nProcs
+                        // Do not restrict to currently used processors
+                        // && proci < nProcs
                     )
                     {
                         pathTypeIdx.second() = proci;
@@ -634,12 +645,12 @@ Foam::fileOperation::lookupAndCacheProcessorsPath
                 }
             }
         }
-        else if (!Pstream::parRun())
+        else if (!UPstream::parRun())
         {
             // Serial: use the number of decompositions (if found)
             if (nProcs)
             {
-                const_cast<fileOperation&>(*this).setNProcs(nProcs);
+                const_cast<fileOperation&>(*this).nProcs(nProcs);
             }
         }
 
@@ -648,10 +659,17 @@ Foam::fileOperation::lookupAndCacheProcessorsPath
 
         if (procDirsStatus & 2u)
         {
-            procsDirs_.insert(procPath, procDirs);
+            if (cacheLevel() > 0)
+            {
+                procsDirs_.insert(procPath, procDirs);
 
-            // Make sure to return a reference
-            return procsDirs_[procPath];
+                // Make sure to return a reference
+                return procsDirs_[procPath];
+            }
+            else
+            {
+                return refPtr<dirIndexList>::New(procDirs);
+            }
         }
     }
 
@@ -681,8 +699,11 @@ bool Foam::fileOperation::exists(IOobject& io) const
     else
     {
         ok =
+        (
             isFile(objPath)
-         && io.typeHeaderOk<IOList<label>>(false);// object with local scope
+            // object with local scope
+         && io.typeHeaderOk<labelIOList>(false)
+        );
     }
 
     if (!ok)
@@ -700,8 +721,11 @@ bool Foam::fileOperation::exists(IOobject& io) const
             else
             {
                 ok =
+                (
                     isFile(originalPath)
-                 && io.typeHeaderOk<IOList<label>>(false);
+                    // object with local scope
+                 && io.typeHeaderOk<labelIOList>(false)
+                );
             }
         }
     }
@@ -712,14 +736,30 @@ bool Foam::fileOperation::exists(IOobject& io) const
 
 // * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
 
+Foam::fileOperation::fileOperation
+(
+    const Tuple2<label, labelList>& commAndIORanks,
+    const bool distributedRoots
+)
+:
+    comm_(commAndIORanks.first()),
+    nProcs_(UPstream::nProcs(UPstream::worldComm)),
+    distributed_(distributedRoots),
+    ioRanks_(commAndIORanks.second())
+{}
+
+
 Foam::fileOperation::fileOperation
 (
     const label comm,
+    const labelUList& ioRanks,
     const bool distributedRoots
 )
 :
     comm_(comm),
-    distributed_(distributedRoots)
+    nProcs_(UPstream::nProcs(UPstream::worldComm)),
+    distributed_(distributedRoots),
+    ioRanks_(ioRanks)
 {}
 
 
@@ -920,7 +960,7 @@ void Foam::fileOperation::updateStates
     const bool syncPar
 ) const
 {
-    monitor().updateStates(masterOnly, Pstream::parRun());
+    monitor().updateStates(masterOnly, UPstream::parRun());
 }
 
 
@@ -1195,10 +1235,6 @@ Foam::fileNameList Foam::fileOperation::readObjects
 }
 
 
-void Foam::fileOperation::setNProcs(const label nProcs)
-{}
-
-
 Foam::label Foam::fileOperation::nProcs
 (
     const fileName& dir,
@@ -1206,7 +1242,7 @@ Foam::label Foam::fileOperation::nProcs
 ) const
 {
     label nProcs = 0;
-    if (Pstream::master(comm_))
+    if (UPstream::master(comm_))
     {
         fileNameList dirNames(Foam::readDir(dir, fileName::Type::DIRECTORY));
 
@@ -1259,6 +1295,23 @@ void Foam::fileOperation::flush() const
 }
 
 
+void Foam::fileOperation::sync()
+{
+    if (debug)
+    {
+        Pout<< "fileOperation::sync : parallel synchronisation"
+            << endl;
+    }
+
+    Pstream::broadcasts
+    (
+        UPstream::worldComm,
+        nProcs_,
+        procsDirs_
+    );
+}
+
+
 Foam::fileName Foam::fileOperation::processorsCasePath
 (
     const IOobject& io,
@@ -1491,9 +1544,17 @@ Foam::Ostream& Foam::operator<<
     const auto& fp = *iproxy;
 
     os  << "fileHandler:" << fp.type()
-        // << " nProcs:" << fp.nProcs()
+        << " nProcs:" << fp.nProcs()
         << " comm:" << fp.comm()
-        << " distributed:" << fp.distributed() << nl;
+        << " distributed:" << fp.distributed()
+        << " ioranks: " << flatOutput(fp.ioRanks())
+        << " ranks: ";
+
+    if (fp.comm() >= 0)
+    {
+        os << flatOutput(UPstream::procID(fp.comm()));
+    }
+    os  << nl;
 
     return os;
 }
diff --git a/src/OpenFOAM/global/fileOperations/fileOperation/fileOperation.H b/src/OpenFOAM/global/fileOperations/fileOperation/fileOperation.H
index 721d2ea2c014e63b4b219dee56ba0193fb18dbdb..6c78934f843937c07312c1bada11bef758602ead 100644
--- a/src/OpenFOAM/global/fileOperations/fileOperation/fileOperation.H
+++ b/src/OpenFOAM/global/fileOperations/fileOperation/fileOperation.H
@@ -80,6 +80,7 @@ Description
 #include "fileNameList.H"
 #include "instantList.H"
 #include "refPtr.H"
+#include "bitSet.H"
 #include "Enum.H"
 #include "Tuple2.H"
 #include "InfoProxy.H"
@@ -144,14 +145,30 @@ public:
 
 protected:
 
+    // Protected Static Data
+
+        //- Cache level (eg, for caching time directories). Default: 1
+        static int cacheLevel_;
+
+
     // Protected Data
 
         //- Communicator to use
         mutable label comm_;
 
+        //- Overall number of processors.
+        //  Used to synthesise processor directory naming:
+        //  - parallel: UPstream::nProcs(UPstream::worldComm)
+        //  - non-parallel: detected from processor dir naming ('processorsNN')
+        label nProcs_;
+
         //- Distributed roots (parallel run)
         mutable bool distributed_;
 
+        //- The list of IO ranks (global ranks)
+        //  Primarily for additional bookkeeping in non-parallel
+        const labelList ioRanks_;
+
         //- Detected processors directories
         mutable HashTable<dirIndexList> procsDirs_;
 
@@ -195,6 +212,15 @@ protected:
         //- Is either a directory (empty name()) or a file
         bool exists(IOobject& io) const;
 
+
+        //- Is proci a master rank in the communicator (in parallel)
+        //- or a master rank in the IO ranks (non-parallel)
+        bool isIOrank(const label proci) const;
+
+        //- Helper: output which ranks are IO
+        void printRanks() const;
+
+
         //- Construction helper: check for locally allocated communicator
         static inline label getManagedComm(const label communicator)
         {
@@ -235,13 +261,23 @@ public:
 
     // Constructors
 
-        //- Construct from communicator, optionally with distributed roots
+        //- Construct from communicator,
+        //- optionally with specified io-ranks and/or distributed roots
         explicit fileOperation
         (
             const label comm,
+            const labelUList& ioRanks = labelUList::null(),
+            const bool distributedRoots = false
+        );
+
+        //- Construct from communicator with specified io-ranks
+        explicit fileOperation
+        (
+            const Tuple2<label, labelList>& commAndIORanks,
             const bool distributedRoots = false
         );
 
+
         //- Clone fileHandler
         /// virtual autoPtr<fileOperation> clone() const = 0;
 
@@ -259,6 +295,19 @@ public:
             (verbose)
         );
 
+        declareRunTimeSelectionTable
+        (
+            autoPtr,
+            fileOperation,
+            comm,
+            (
+                const Tuple2<label, labelList>& commAndIORanks,
+                const bool distributedRoots,
+                bool verbose
+            ),
+            (commAndIORanks, distributedRoots, verbose)
+        );
+
 
     // Selectors
 
@@ -270,6 +319,16 @@ public:
             bool verbose = false
         );
 
+        //- Select fileHandler-type.
+        //- Uses defaultFileHandler if the handlerType is empty.
+        static autoPtr<fileOperation> New
+        (
+            const word& handlerType,
+            const Tuple2<label, labelList>& commAndIORanks,
+            const bool distributedRoots,
+            bool verbose = false
+        );
+
 
     //- Destructor
     virtual ~fileOperation() = default;
@@ -331,6 +390,20 @@ public:
 
    // Static Functions
 
+        //- Return cache level
+        static int cacheLevel() noexcept
+        {
+            return cacheLevel_;
+        }
+
+        //- Set cache level (0 = off). \return the previous value
+        static int cacheLevel(int level) noexcept
+        {
+            int old(cacheLevel_);
+            cacheLevel_ = level;
+            return old;
+        }
+
         //- Sort directory entries according to time value,
         //  with "constant" appearing first (if it exists)
         static instantList sortTimes
@@ -380,6 +453,10 @@ public:
             return old;
         }
 
+        //- The list of IO ranks (global ranks)
+        //  Primarily for additional bookkeeping in non-parallel
+        const labelList& ioRanks() const noexcept { return ioRanks_; }
+
         //- Return info proxy,
         //- used to print information to a stream
         InfoProxy<fileOperation> info() const noexcept { return *this; }
@@ -691,12 +768,23 @@ public:
                 return processorsBaseDir;
             }
 
-            //- Set number of processor directories/results. Only used in
-            //  decomposePar
-            virtual void setNProcs(const label nProcs);
+            //- Overall number of processors,
+            //- from UPstream::nProcs() or detected from directories/results.
+            label nProcs() const noexcept { return nProcs_; }
+
+            //- Set number of processor directories/results.
+            //  Used to cache format of e.g. processorsDDD.
+            //  Returns old number of processors.
+            //  Only used in decomposePar
+            label nProcs(const label numProcs) noexcept
+            {
+                label old(nProcs_);
+                nProcs_ = numProcs;
+                return old;
+            }
 
-            //- Get number of processor directories/results. Used for e.g.
-            //  reconstructPar, argList checking
+            //- Get number of processor directories/results.
+            //  Used for e.g. reconstructPar, argList checking
             virtual label nProcs
             (
                 const fileName& dir,
@@ -724,11 +812,14 @@ public:
             //- Forcibly wait until all output done. Flush any cached data
             virtual void flush() const;
 
+            //- Forcibly parallel sync
+            virtual void sync();
+
             //- Generate path (like io.path) from root+casename with any
             //  'processorXXX' replaced by procDir (usually 'processsors')
             fileName processorsCasePath
             (
-                const IOobject&,
+                const IOobject& io,
                 const word& procDir
             ) const;
 
@@ -736,7 +827,7 @@ public:
             //  'processorXXX' replaced by procDir (usually 'processsors')
             fileName processorsPath
             (
-                const IOobject&,
+                const IOobject& io,
                 const word& instance,
                 const word& procDir
             ) const;
@@ -778,6 +869,12 @@ public:
         //- Get list of global IO ranks from FOAM_IORANKS env variable.
         //- If set, these correspond to the IO master ranks.
         static labelList getGlobalIORanks();
+
+
+    // Housekeeping
+
+        //- Same as nProcs
+        label setNProcs(label numProcs) { return nProcs(numProcs); }
 };
 
 
diff --git a/src/OpenFOAM/global/fileOperations/fileOperation/fileOperationNew.C b/src/OpenFOAM/global/fileOperations/fileOperation/fileOperationNew.C
index ef7da7d6ddf9f726bbfa86e23aadfb387d7d25e8..dee4b1c4aaa2c3db6d41a77c179ae732ae16f8b3 100644
--- a/src/OpenFOAM/global/fileOperations/fileOperation/fileOperationNew.C
+++ b/src/OpenFOAM/global/fileOperations/fileOperation/fileOperationNew.C
@@ -5,7 +5,7 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2022 OpenCFD Ltd.
+    Copyright (C) 2022-2023 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -148,11 +148,12 @@ Foam::fileOperation::New
                 << abort(FatalError);
         }
 
+        // Forward to self
         return fileOperation::New(fileOperation::defaultFileHandler, verbose);
     }
 
     DebugInFunction
-        << "Constructing fileHandler" << endl;
+        << "Constructing fileHandler: " << handlerType << endl;
 
     auto* ctorPtr = wordConstructorTable(handlerType);
 
@@ -170,6 +171,56 @@ Foam::fileOperation::New
 }
 
 
+Foam::autoPtr<Foam::fileOperation>
+Foam::fileOperation::New
+(
+    const word& handlerType,
+    const Tuple2<label, labelList>& commAndIORanks,
+    const bool distributedRoots,
+    bool verbose
+)
+{
+    if (handlerType.empty())
+    {
+        if (fileOperation::defaultFileHandler.empty())
+        {
+            FatalErrorInFunction
+                << "defaultFileHandler name is undefined" << nl
+                << abort(FatalError);
+        }
+
+        // Forward to self
+        return fileOperation::New
+        (
+            fileOperation::defaultFileHandler,
+            commAndIORanks,
+            distributedRoots,
+            verbose
+        );
+    }
+
+    DebugInFunction
+        << "Constructing fileHandler: " << handlerType << endl;
+
+    auto* ctorPtr = commConstructorTable(handlerType);
+
+    if (!ctorPtr)
+    {
+        FatalErrorInLookup
+        (
+            "fileHandler",
+            handlerType,
+            *commConstructorTablePtr_
+        ) << abort(FatalError);
+    }
+
+    return autoPtr<fileOperation>
+    (
+        ctorPtr(commAndIORanks, distributedRoots, verbose)
+    );
+}
+
+
 // * * * * * * * * * * * * * * * Global Functions  * * * * * * * * * * * * * //
 
 Foam::autoPtr<Foam::fileOperation>
diff --git a/src/OpenFOAM/global/fileOperations/fileOperation/fileOperationRanks.C b/src/OpenFOAM/global/fileOperations/fileOperation/fileOperationRanks.C
index e0a79e83c213148e5b9138afae6b711d26d22998..318afc65e50d8314a68eeb0f532da9e50592c8f9 100644
--- a/src/OpenFOAM/global/fileOperations/fileOperation/fileOperationRanks.C
+++ b/src/OpenFOAM/global/fileOperations/fileOperation/fileOperationRanks.C
@@ -243,7 +243,6 @@ Foam::labelList Foam::fileOperation::getGlobalIORanks
 }
 
 
-#if 0  // FUTURE
 bool Foam::fileOperation::isIOrank(const label proci) const
 {
     return
@@ -313,7 +312,6 @@ void Foam::fileOperation::printRanks() const
             << ')' << nl;
     }
 }
-#endif
 
 
 // ************************************************************************* //
diff --git a/src/OpenFOAM/global/fileOperations/masterUncollatedFileOperation/masterUncollatedFileOperation.C b/src/OpenFOAM/global/fileOperations/masterUncollatedFileOperation/masterUncollatedFileOperation.C
index 6aa0893614c0fb5af796b7b3b26d7ca529803f8e..314cd76cf7a00a7f1988d841c8df7eb4a9d347c1 100644
--- a/src/OpenFOAM/global/fileOperations/masterUncollatedFileOperation/masterUncollatedFileOperation.C
+++ b/src/OpenFOAM/global/fileOperations/masterUncollatedFileOperation/masterUncollatedFileOperation.C
@@ -39,7 +39,6 @@ License
 #include "dummyISstream.H"
 #include "SubList.H"
 #include "unthreadedInitialise.H"
-#include "bitSet.H"
 
 /* * * * * * * * * * * * * * * Static Member Data  * * * * * * * * * * * * * */
 
@@ -54,6 +53,12 @@ namespace fileOperations
         masterUncollatedFileOperation,
         word
     );
+    addToRunTimeSelectionTable
+    (
+        fileOperation,
+        masterUncollatedFileOperation,
+        comm
+    );
 
     float masterUncollatedFileOperation::maxMasterFileBufferSize
     (
@@ -80,26 +85,6 @@ namespace fileOperations
 
 // * * * * * * * * * * * * * Private Member Functions  * * * * * * * * * * * //
 
-Foam::labelList Foam::fileOperations::masterUncollatedFileOperation::subRanks
-(
-    const label n
-)
-{
-    labelList mainIOranks(fileOperation::getGlobalIORanks());
-
-    if (mainIOranks.empty())
-    {
-        return identity(n);
-    }
-    else
-    {
-        labelRange subRange = fileOperation::subRanks(mainIOranks);
-
-        return identity(subRange);
-    }
-}
-
-
 Foam::word
 Foam::fileOperations::masterUncollatedFileOperation::findInstancePath
 (
@@ -121,7 +106,7 @@ Foam::fileOperations::masterUncollatedFileOperation::findInstancePath
         }
     }
 
-    return word::null;
+    return word();
 }
 
 
@@ -131,14 +116,15 @@ Foam::fileOperations::masterUncollatedFileOperation::filePathInfo
     const bool checkGlobal,
     const bool isFile,
     const IOobject& io,
+    const dirIndexList& pDirs,
     const bool search,
     pathType& searchType,
     word& procsDir,
     word& newInstancePath
 ) const
 {
-    procsDir = word::null;
-    newInstancePath = word::null;
+    procsDir.clear();
+    newInstancePath.clear();
 
     if (io.instance().isAbsolute())
     {
@@ -152,7 +138,7 @@ Foam::fileOperations::masterUncollatedFileOperation::filePathInfo
         else
         {
             searchType = fileOperation::NOTFOUND;
-            return fileName::null;
+            return fileName();
         }
     }
     else
@@ -169,9 +155,7 @@ Foam::fileOperations::masterUncollatedFileOperation::filePathInfo
         // 2. Check processors/
         if (io.time().processorCase())
         {
-            refPtr<dirIndexList> pDirs(lookupProcessorsPath(io.objectPath()));
-
-            for (const dirIndex& dirIdx : pDirs())
+            for (const dirIndex& dirIdx : pDirs)
             {
                 const fileName& pDir = dirIdx.first();
                 fileName objPath =
@@ -239,12 +223,7 @@ Foam::fileOperations::masterUncollatedFileOperation::filePathInfo
             if (newInstancePath.size() && newInstancePath != io.instance())
             {
                 // 1. Try processors equivalent
-                refPtr<dirIndexList> pDirs
-                (
-                    lookupProcessorsPath(io.objectPath())
-                );
-
-                for (const dirIndex& dirIdx : pDirs())
+                for (const dirIndex& dirIdx : pDirs)
                 {
                     const fileName& pDir = dirIdx.first();
 
@@ -295,10 +274,11 @@ Foam::fileOperations::masterUncollatedFileOperation::filePathInfo
                 }
             }
         }
-
-        searchType = fileOperation::NOTFOUND;
-        return fileName::null;
     }
+
+    // Nothing found
+    searchType = fileOperation::NOTFOUND;
+    return fileName();
 }
 
 
@@ -338,7 +318,7 @@ Foam::fileOperations::masterUncollatedFileOperation::localObjectPath
             // Uncollated type, e.g. processor1
             const word procName
             (
-                "processor" + Foam::name(Pstream::myProcNo(Pstream::worldComm))
+                "processor" + Foam::name(Pstream::myProcNo(UPstream::worldComm))
             );
             return
                 processorsPath
@@ -395,7 +375,7 @@ Foam::fileOperations::masterUncollatedFileOperation::localObjectPath
             const word procName
             (
                 "processor"
-               +Foam::name(Pstream::myProcNo(Pstream::worldComm))
+              + Foam::name(Pstream::myProcNo(UPstream::worldComm))
             );
             return
                 processorsPath
@@ -432,14 +412,14 @@ Foam::fileOperations::masterUncollatedFileOperation::localObjectPath
 
         case fileOperation::NOTFOUND:
         {
-            return fileName::null;
+            return fileName();
         }
         break;
 
         default:
         {
             NotImplemented;
-            return fileName::null;
+            return fileName();
         }
     }
 }
@@ -458,6 +438,8 @@ void Foam::fileOperations::masterUncollatedFileOperation::readAndSend
     {
         FatalIOErrorInFunction(filePath)
             << "Cannot open file " << filePath
+            //<< " using communicator " << pBufs.comm()
+            //<< " ioRanks:" << UPstream::procID(pBufs.comm())
             << exit(FatalIOError);
     }
 
@@ -529,11 +511,11 @@ Foam::fileOperations::masterUncollatedFileOperation::read
 {
     autoPtr<ISstream> isPtr;
 
-    // const bool uniform = uniformFile(filePaths);
+    // const bool uniform = fileOperation::uniformFile(filePaths);
 
     PstreamBuffers pBufs(comm, UPstream::commsTypes::nonBlocking);
 
-    if (Pstream::master(comm))
+    if (UPstream::master(comm))
     {
         if (uniform)
         {
@@ -543,6 +525,8 @@ Foam::fileOperations::masterUncollatedFileOperation::read
                 {
                     FatalIOErrorInFunction(filePaths[0])
                         << "cannot find file " << io.objectPath()
+                        << " fileHandler : comm:" << comm
+                        << " ioRanks:" << UPstream::procID(comm)
                         << exit(FatalIOError);
                 }
 
@@ -575,6 +559,8 @@ Foam::fileOperations::masterUncollatedFileOperation::read
                 {
                     FatalIOErrorInFunction(filePaths[0])
                         << "cannot find file " << io.objectPath()
+                        << " fileHandler : comm:" << comm
+                        << " ioRanks:" << UPstream::procID(comm)
                         << exit(FatalIOError);
                 }
 
@@ -586,7 +572,10 @@ Foam::fileOperations::masterUncollatedFileOperation::read
                 {
                     FatalIOErrorInFunction(*isPtr)
                         << "problem while reading header for object "
-                        << io.name() << exit(FatalIOError);
+                        << io.name()
+                        << " fileHandler : comm:" << comm
+                        << " ioRanks:" << UPstream::procID(comm)
+                        << exit(FatalIOError);
                 }
             }
 
@@ -612,7 +601,7 @@ Foam::fileOperations::masterUncollatedFileOperation::read
         }
     }
 
-    pBufs.finishedSends();
+    pBufs.finishedScatters();
 
     // isPtr will be valid on master and will be the unbuffered
     // IFstream. Else the information is in the PstreamBuffers (and
@@ -650,7 +639,10 @@ Foam::fileOperations::masterUncollatedFileOperation::read
             {
                 FatalIOErrorInFunction(*isPtr)
                     << "problem while reading header for object "
-                    << io.name() << exit(FatalIOError);
+                    << io.name()
+                    << " fileHandler : comm:" << comm
+                    << " ioRanks:" << UPstream::procID(comm)
+                    << exit(FatalIOError);
             }
         }
         else
@@ -659,11 +651,41 @@ Foam::fileOperations::masterUncollatedFileOperation::read
         }
     }
 
-
     return isPtr;
 }
 
 
+// * * * * * * * * * * * * * * * Local Functions * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+
+// Construction helper: self/world/local communicator and IO ranks
+static Tuple2<label, labelList> getCommPattern()
+{
+    // Default is COMM_WORLD (single master)
+    Tuple2<label, labelList> commAndIORanks
+    (
+        UPstream::worldComm,
+        fileOperation::getGlobalIORanks()
+    );
+
+    if (UPstream::parRun() && commAndIORanks.second().size() > 1)
+    {
+        // Multiple masters: ranks for my IO range
+        commAndIORanks.first() = UPstream::allocateCommunicator
+        (
+            UPstream::worldComm,
+            fileOperation::subRanks(commAndIORanks.second())
+        );
+    }
+
+    return commAndIORanks;
+}
+
+} // End namespace Foam
+
+
 // * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
 
 void Foam::fileOperations::masterUncollatedFileOperation::init(bool verbose)
@@ -708,11 +730,7 @@ masterUncollatedFileOperation
 :
     fileOperation
     (
-        UPstream::allocateCommunicator
-        (
-            UPstream::worldComm,
-            subRanks(UPstream::nProcs())
-        )
+        getCommPattern()
     ),
     managedComm_(getManagedComm(comm_))  // Possibly locally allocated
 {
@@ -723,11 +741,12 @@ masterUncollatedFileOperation
 Foam::fileOperations::masterUncollatedFileOperation::
 masterUncollatedFileOperation
 (
-    const label comm,
+    const Tuple2<label, labelList>& commAndIORanks,
+    const bool distributedRoots,
     bool verbose
 )
 :
-    fileOperation(comm),
+    fileOperation(commAndIORanks, distributedRoots),
     managedComm_(-1)  // Externally managed
 {
     init(verbose);
@@ -896,7 +915,7 @@ time_t Foam::fileOperations::masterUncollatedFileOperation::lastModified
         fName,
         lastModifiedOp(followLink),
         Pstream::msgType(),
-        comm_
+        UPstream::worldComm
     );
 }
 
@@ -912,7 +931,7 @@ double Foam::fileOperations::masterUncollatedFileOperation::highResLastModified
         fName,
         highResLastModifiedOp(followLink),
         Pstream::msgType(),
-        comm_
+        UPstream::worldComm
     );
 }
 
@@ -1050,16 +1069,20 @@ Foam::fileName Foam::fileOperations::masterUncollatedFileOperation::filePath
     {
         Pout<< "masterUncollatedFileOperation::filePath :"
             << " objectPath:" << io.objectPath()
-            << " checkGlobal:" << checkGlobal << endl;
+            << " checkGlobal:" << checkGlobal
+            << " parRun:" << Pstream::parRun()
+            << " localmaster:" << Pstream::master(comm_) << endl;
     }
 
     // Now that we have an IOobject path use it to detect & cache
     // processor directory naming
-    (void)lookupProcessorsPath(io.objectPath());
+    const refPtr<dirIndexList> pDirs(lookupProcessorsPath(io.objectPath()));
 
     // Trigger caching of times
-    (void)findTimes(io.time().path(), io.time().constant());
-
+    if (cacheLevel() > 0)
+    {
+        (void)findTimes(io.time().path(), io.time().constant());
+    }
 
     // Determine master filePath and scatter
 
@@ -1070,7 +1093,8 @@ Foam::fileName Foam::fileOperations::masterUncollatedFileOperation::filePath
 
     if (Pstream::master(comm_))
     {
-        const bool oldParRun(Pstream::parRun(false));
+        const bool oldParRun = UPstream::parRun(false);
+        const int oldCache = fileOperation::cacheLevel(0);
 
         // All masters search locally. Note that global objects might
         // fail (except on master). This gets handled later on (in PARENTOBJECT)
@@ -1080,13 +1104,15 @@ Foam::fileName Foam::fileOperations::masterUncollatedFileOperation::filePath
                 checkGlobal,
                 true,
                 io,
+                pDirs,
                 search,
                 searchType,
                 procsDir,
                 newInstancePath
             );
 
-        Pstream::parRun(oldParRun);
+        fileOperation::cacheLevel(oldCache);
+        UPstream::parRun(oldParRun);
 
         if (debug)
         {
@@ -1098,10 +1124,11 @@ Foam::fileName Foam::fileOperations::masterUncollatedFileOperation::filePath
         }
     }
 
-    // Scatter the information about where the master found the object
+    // Broadcast information about where the master found the object
     // Note: use the worldComm to make sure all processors decide
     //       the same type. Only procsDir is allowed to differ; searchType
     //       and instance have to be same
+    if (UPstream::parRun())
     {
         int masterType(searchType);
         Pstream::broadcasts(UPstream::worldComm, masterType, newInstancePath);
@@ -1177,7 +1204,8 @@ Foam::fileName Foam::fileOperations::masterUncollatedFileOperation::filePath
     if (debug)
     {
         Pout<< "masterUncollatedFileOperation::filePath :"
-            << " Returning from file searching:" << endl
+            << " Returning from file searching using type "
+            << fileOperation::pathTypeNames_[searchType] << endl
             << "    objectPath:" << io.objectPath() << endl
             << "    filePath  :" << objPath << endl << endl;
     }
@@ -1196,12 +1224,20 @@ Foam::fileName Foam::fileOperations::masterUncollatedFileOperation::dirPath
     {
         Pout<< "masterUncollatedFileOperation::dirPath :"
             << " objectPath:" << io.objectPath()
-            << " checkGlobal:" << checkGlobal << endl;
+            << " checkGlobal:" << checkGlobal
+            << " parRun:" << Pstream::parRun()
+            << " localmaster:" << Pstream::master(comm_) << endl;
     }
 
     // Now that we have an IOobject path use it to detect & cache
     // processor directory naming
-    (void)lookupProcessorsPath(io.objectPath());
+    const refPtr<dirIndexList> pDirs(lookupProcessorsPath(io.objectPath()));
+
+    // Trigger caching of times
+    if (cacheLevel() > 0)
+    {
+        (void)findTimes(io.time().path(), io.time().constant());
+    }
 
     // Determine master dirPath and broadcast
 
@@ -1210,31 +1246,50 @@ Foam::fileName Foam::fileOperations::masterUncollatedFileOperation::dirPath
     word procsDir;
     word newInstancePath;
 
+    // Local IO node searches for file
     if (Pstream::master(comm_))
     {
-        const bool oldParRun(Pstream::parRun(false));
+        const bool oldParRun = UPstream::parRun(false);
+        const int oldCache = fileOperation::cacheLevel(0);
 
         objPath = filePathInfo
         (
             checkGlobal,
             false,
             io,
+            pDirs,
             search,
             searchType,
             procsDir,
             newInstancePath
         );
 
-        Pstream::parRun(oldParRun);
+        fileOperation::cacheLevel(oldCache);
+        UPstream::parRun(oldParRun);
+
+        if (debug)
+        {
+            Pout<< "masterUncollatedFileOperation::dirPath :"
+                << " master objPath:" << objPath
+                << " searchType:" << fileOperation::pathTypeNames_[searchType]
+                << " procsDir:" << procsDir << " instance:" << newInstancePath
+                << endl;
+        }
     }
 
+
+    // Broadcast information about where the master found the object
+    // Note: use the worldComm to make sure all processors decide
+    //       the same type. Only procsDir is allowed to differ; searchType
+    //       and instance have to be same
+    if (UPstream::parRun())
     {
         int masterType(searchType);
-        // Future?: comm_,
         Pstream::broadcasts(UPstream::worldComm, masterType, newInstancePath);
         searchType = pathType(masterType);
     }
 
+
     if
     (
         checkGlobal
@@ -1250,6 +1305,7 @@ Foam::fileName Foam::fileOperations::masterUncollatedFileOperation::dirPath
     }
     else
     {
+        // Broadcast local processors dir amongst all local nodes
         Pstream::broadcast(procsDir, comm_);
 
         // Use the master type to determine if additional information is
@@ -1304,7 +1360,8 @@ Foam::fileName Foam::fileOperations::masterUncollatedFileOperation::dirPath
     if (debug)
     {
         Pout<< "masterUncollatedFileOperation::dirPath :"
-            << " Returning from file searching:" << endl
+            << " Returning from directory searching using type "
+            << fileOperation::pathTypeNames_[searchType] << endl
             << "    objectPath:" << io.objectPath() << endl
             << "    filePath  :" << objPath << endl << endl;
     }
@@ -1387,24 +1444,28 @@ Foam::fileOperations::masterUncollatedFileOperation::findInstance
     //         parent directory in case of parallel)
 
 
-    refPtr<dirIndexList> pDirs(lookupProcessorsPath(io.objectPath()));
+    const refPtr<dirIndexList> pDirs(lookupProcessorsPath(io.objectPath()));
 
     word foundInstance;
 
     // if (Pstream::master(comm_))
     if (Pstream::master(UPstream::worldComm))
     {
-        const bool oldParRun(Pstream::parRun(false));
+        const bool oldParRun = UPstream::parRun(false);
+        const int oldCache = fileOperation::cacheLevel(0);
+
         if (exists(pDirs, io))
         {
             foundInstance = io.instance();
         }
-        Pstream::parRun(oldParRun);
+        fileOperation::cacheLevel(oldCache);
+        UPstream::parRun(oldParRun);
     }
 
+
     // Do parallel early exit to avoid calling time.times()
-    // Pstream::broadcast(foundInstance, comm_);
     Pstream::broadcast(foundInstance, UPstream::worldComm);
+
     if (!foundInstance.empty())
     {
         io.instance() = foundInstance;
@@ -1429,7 +1490,8 @@ Foam::fileOperations::masterUncollatedFileOperation::findInstance
     // if (Pstream::master(comm_))
     if (Pstream::master(UPstream::worldComm))
     {
-        const bool oldParRun(Pstream::parRun(false));
+        const bool oldParRun = UPstream::parRun(false);
+        const int oldCache = fileOperation::cacheLevel(0);
 
         label instIndex = ts.size()-1;
 
@@ -1540,11 +1602,12 @@ Foam::fileOperations::masterUncollatedFileOperation::findInstance
             }
         }
 
+        fileOperation::cacheLevel(oldCache);
         UPstream::parRun(oldParRun);  // Restore parallel state
     }
 
-    // Pstream::broadcast(foundInstance, comm_);
     Pstream::broadcast(foundInstance, UPstream::worldComm);
+
     io.instance() = foundInstance;
 
 
@@ -1603,16 +1666,17 @@ Foam::fileOperations::masterUncollatedFileOperation::readObjects
     }
 
     fileNameList objectNames;
-    newInstance = word::null;
+    newInstance.clear();
 
     // Note: readObjects uses WORLD to make sure order of objects is the
     //       same everywhere
 
-    if (Pstream::master())  // comm_))
+    if (Pstream::master(UPstream::worldComm))
     {
         // Avoid fileOperation::readObjects from triggering parallel ops
         // (through call to filePath which triggers parallel )
         const bool oldParRun = UPstream::parRun(false);
+        const int oldCache = fileOperation::cacheLevel(0);
 
         //- Use non-time searching version
         objectNames = fileOperation::readObjects
@@ -1656,10 +1720,10 @@ Foam::fileOperations::masterUncollatedFileOperation::readObjects
             }
         }
 
+        fileOperation::cacheLevel(oldCache);
         UPstream::parRun(oldParRun);  // Restore parallel state
     }
 
-    // Future? comm_
     Pstream::broadcasts(UPstream::worldComm, newInstance, objectNames);
 
     if (debug)
@@ -1689,16 +1753,27 @@ bool Foam::fileOperations::masterUncollatedFileOperation::readHeader
             << "    filePath  :" << fName << endl;
     }
 
+    // We assume if filePath is the same
+    //  - headerClassName
+    //  - note
+    // are also the same, independent of where the file came from.
+
     // Get filePaths on world master
-    fileNameList filePaths(Pstream::nProcs(Pstream::worldComm));
-    filePaths[Pstream::myProcNo(Pstream::worldComm)] = fName;
-    Pstream::gatherList(filePaths, Pstream::msgType(), Pstream::worldComm);
-    bool uniform = uniformFile(filePaths);
+    fileNameList filePaths(Pstream::nProcs(UPstream::worldComm));
+    filePaths[UPstream::myProcNo(UPstream::worldComm)] = fName;
+    Pstream::gatherList(filePaths, UPstream::msgType(), UPstream::worldComm);
+
+    bool uniform
+    (
+        UPstream::master(UPstream::worldComm)
+     && fileOperation::uniformFile(filePaths)
+    );
+
     Pstream::broadcast(uniform, UPstream::worldComm);
 
     if (uniform)
     {
-        if (Pstream::master(Pstream::worldComm))
+        if (Pstream::master(UPstream::worldComm))
         {
             if (!fName.empty())
             {
@@ -1722,12 +1797,13 @@ bool Foam::fileOperations::masterUncollatedFileOperation::readHeader
     }
     else
     {
-        if (Pstream::nProcs(comm_) != Pstream::nProcs(Pstream::worldComm))
+        if (Pstream::nProcs(comm_) != Pstream::nProcs(UPstream::worldComm))
         {
-            // Re-gather file paths on local master
-            filePaths.resize(Pstream::nProcs(comm_));
-            filePaths[Pstream::myProcNo(comm_)] = fName;
-            Pstream::gatherList(filePaths, Pstream::msgType(), comm_);
+            // Assume if different nprocs the communicators are also
+            // different. Re-gather file paths on local master
+            filePaths.resize(UPstream::nProcs(comm_));
+            filePaths[UPstream::myProcNo(comm_)] = fName;
+            Pstream::gatherList(filePaths, UPstream::msgType(), comm_);
         }
 
         // Intermediate storage arrays (master only)
@@ -1798,7 +1874,8 @@ bool Foam::fileOperations::masterUncollatedFileOperation::readHeader
     if (debug)
     {
         Pout<< "masterUncollatedFileOperation::readHeader :" << " ok:" << ok
-            << " class:" << io.headerClassName() << endl;
+            << " class:" << io.headerClassName()
+            << " for file:" << fName << endl;;
     }
     return ok;
 }
@@ -1818,6 +1895,7 @@ Foam::fileOperations::masterUncollatedFileOperation::readStream
         Pout<< "masterUncollatedFileOperation::readStream :"
             << " object : " << io.name()
             << " global : " << io.global()
+            << " globalObject : " << io.globalObject()
             << " fName : " << fName << " readOnProc:" << readOnProc << endl;
     }
 
@@ -1828,7 +1906,7 @@ Foam::fileOperations::masterUncollatedFileOperation::readStream
 
     // Detect collated format. This could be done on the local communicator
     // but we do it on the master node only for now.
-    if (UPstream::master()) // comm_))
+    if (UPstream::master(UPstream::worldComm))
     {
         if (!fName.empty())
         {
@@ -1871,7 +1949,7 @@ Foam::fileOperations::masterUncollatedFileOperation::readStream
         }
     }
 
-    Pstream::broadcast(isCollated);  //, comm_);
+    Pstream::broadcast(isCollated, UPstream::worldComm);
 
     if (isCollated)
     {
@@ -1904,6 +1982,8 @@ Foam::fileOperations::masterUncollatedFileOperation::readStream
                 FatalIOErrorInFunction(*isPtr)
                     << "Could not detect processor number"
                     << " from objectPath:" << io.objectPath()
+                    << " fileHandler : comm:" << comm_
+                    << " ioRanks:" << flatOutput(ioRanks_)
                     << exit(FatalIOError);
             }
 
@@ -1954,7 +2034,7 @@ Foam::fileOperations::masterUncollatedFileOperation::readStream
             // Get size of file to determine communications type
             bool bigSize = false;
 
-            if (Pstream::master())   //, comm_))
+            if (Pstream::master(UPstream::worldComm))
             {
                 // TBD: handle multiple masters?
                 bigSize =
@@ -1965,7 +2045,7 @@ Foam::fileOperations::masterUncollatedFileOperation::readStream
             }
             // Reduce (not broadcast)
             // - if we have multiple master files (FUTURE)
-            Pstream::reduceOr(bigSize);  //, comm_);
+            Pstream::reduceOr(bigSize, UPstream::worldComm);
 
             const UPstream::commsTypes myCommsType
             (
@@ -1994,23 +2074,32 @@ Foam::fileOperations::masterUncollatedFileOperation::readStream
                 << " starting separated input from " << fName << endl;
         }
 
-        if (io.global())
+        if (io.global() || io.globalObject())
         {
             // Use worldComm. Note: should not really need to gather filePaths
             // since we enforce sending from master anyway ...
-            fileNameList filePaths(Pstream::nProcs());
-            filePaths[Pstream::myProcNo()] = fName;
-            Pstream::gatherList(filePaths);
+            fileNameList filePaths(Pstream::nProcs(UPstream::worldComm));
+            filePaths[Pstream::myProcNo(UPstream::worldComm)] = fName;
+            Pstream::gatherList
+            (
+                filePaths,
+                Pstream::msgType(),
+                UPstream::worldComm
+            );
 
             boolList procValid
             (
-                UPstream::listGatherValues<bool>(readOnProc)
+                UPstream::listGatherValues<bool>
+                (
+                    readOnProc,
+                    UPstream::worldComm
+                )
             );
             // NB: local proc validity information required on sub-ranks too!
-            procValid.resize(Pstream::nProcs());
-            procValid[Pstream::myProcNo()] = readOnProc;
+            procValid.resize(Pstream::nProcs(UPstream::worldComm));
+            procValid[Pstream::myProcNo(UPstream::worldComm)] = readOnProc;
 
-            return read(io, Pstream::worldComm, true, filePaths, procValid);
+            return read(io, UPstream::worldComm, true, filePaths, procValid);
         }
         else
         {
@@ -2028,7 +2117,7 @@ Foam::fileOperations::masterUncollatedFileOperation::readStream
             procValid[Pstream::myProcNo(comm_)] = readOnProc;
 
             // Uniform in local comm
-            const bool uniform = uniformFile(filePaths);
+            const bool uniform = fileOperation::uniformFile(filePaths);
 
             return read(io, comm_, uniform, filePaths, procValid);
         }
@@ -2046,12 +2135,17 @@ bool Foam::fileOperations::masterUncollatedFileOperation::read
 {
     bool ok = true;
 
-    if (io.globalObject())
+    if (io.global() || io.globalObject())
     {
         if (debug)
         {
             Pout<< "masterUncollatedFileOperation::read :"
-                << " Reading global object " << io.name() << endl;
+                << " Reading global object " << io.name()
+                << " worldComm:" << UPstream::worldComm
+                << " Pstream::myProcNo:"
+                << Pstream::myProcNo(UPstream::worldComm)
+                << " amMaster:" << Pstream::master(UPstream::worldComm)
+                << endl;
         }
 
         bool ok = false;
@@ -2059,10 +2153,13 @@ bool Foam::fileOperations::masterUncollatedFileOperation::read
         {
             // Do master-only reading always.
             const bool oldParRun = UPstream::parRun(false);
+            const int oldCache = fileOperation::cacheLevel(0);
 
-            ok = io.readData(io.readStream(typeName));
+            auto& is = io.readStream(typeName);
+            ok = io.readData(is);
             io.close();
 
+            fileOperation::cacheLevel(oldCache);
             UPstream::parRun(oldParRun);  // Restore parallel state
         }
 
@@ -2112,6 +2209,14 @@ bool Foam::fileOperations::masterUncollatedFileOperation::read
         io.close();
     }
 
+    if (debug)
+    {
+        Pout<< "masterUncollatedFileOperation::read :"
+            << " Read object:" << io.name()
+            << " isGlobal:" << (io.global() || io.globalObject())
+            << " status:" << ok << endl;
+    }
+
     return ok;
 }
 
@@ -2170,39 +2275,48 @@ Foam::instantList Foam::fileOperations::masterUncollatedFileOperation::findTimes
         if (debug)
         {
             Pout<< "masterUncollatedFileOperation::findTimes :"
-                << " Found " << iter.val()->size() << " cached times" << endl;
+                << " Found " << iter.val()->size() << " cached times" << nl
+                << "    for directory:" << directory << endl;
         }
         return *(iter.val());
     }
     else
     {
         instantList times;
-        if (Pstream::master())  // comm_))
+        if (Pstream::master(UPstream::worldComm))
         {
             // Do master-only reading always.
             const bool oldParRun = UPstream::parRun(false);
+            const int oldCache = fileOperation::cacheLevel(0);
 
             times = fileOperation::findTimes(directory, constantName);
 
+            fileOperation::cacheLevel(oldCache);
             UPstream::parRun(oldParRun);  // Restore parallel state
         }
-        Pstream::broadcast(times);    //, comm_);
+
+        Pstream::broadcast(times, UPstream::worldComm);
 
         if (debug)
         {
             Pout<< "masterUncollatedFileOperation::findTimes :"
-                << " Caching times:" << times << nl
+                << " Found times:" << flatOutput(times) << nl
                 << "    for directory:" << directory << endl;
         }
 
-        // Note: do we also cache if no times have been found since it might
-        //       indicate a directory that is being filled later on ...
-
-        auto* tPtr = new DynamicList<instant>(std::move(times));
+        // Caching
+        // - cache values even if no times were found since it might
+        //   indicate a directory that is being filled later on ...
+        if (cacheLevel() > 0)
+        {
+            auto* tPtr = new DynamicList<instant>(std::move(times));
+            times_.set(directory, tPtr);
 
-        times_.set(directory, tPtr);
+            return *tPtr;
+        }
 
-        return *tPtr;
+        // Times found (not cached)
+        return times;
     }
 }
 
@@ -2220,7 +2334,7 @@ void Foam::fileOperations::masterUncollatedFileOperation::setTime
     // Mutable access to instant list for modification and sorting
     // - cannot use auto type deduction here
 
-    HashPtrTable<DynamicList<instant>>::iterator iter = times_.find(tm.path());
+    auto iter = times_.find(tm.path());
 
     if (iter.good())
     {
@@ -2287,20 +2401,16 @@ Foam::fileOperations::masterUncollatedFileOperation::NewIFstream
         // Insert logic of filePath. We assume that if a file is absolute
         // on the master it is absolute also on the sub-ranks etc.
 
-        fileNameList filePaths(Pstream::nProcs(Pstream::worldComm));
-        filePaths[Pstream::myProcNo(Pstream::worldComm)] = filePath;
-        Pstream::gatherList(filePaths, Pstream::msgType(), Pstream::worldComm);
+        fileNameList filePaths(Pstream::nProcs(comm_));
+        filePaths[Pstream::myProcNo(comm_)] = filePath;
+        Pstream::gatherList(filePaths, Pstream::msgType(), comm_);
 
-        PstreamBuffers pBufs
-        (
-            Pstream::commsTypes::nonBlocking,
-            Pstream::msgType(),
-            Pstream::worldComm
-        );
+        PstreamBuffers pBufs(comm_, Pstream::commsTypes::nonBlocking);
 
-        if (Pstream::master(Pstream::worldComm))
+        if (Pstream::master(comm_))
         {
-            const bool uniform = uniformFile(filePaths);
+            // Same filename on the IO node -> same file
+            const bool uniform = fileOperation::uniformFile(filePaths);
 
             if (uniform)
             {
@@ -2313,14 +2423,21 @@ Foam::fileOperations::masterUncollatedFileOperation::NewIFstream
                 readAndSend
                 (
                     filePath,
-                    identity(Pstream::nProcs(Pstream::worldComm)-1, 1),
+                    identity(Pstream::nProcs(comm_)-1, 1),
                     pBufs
                 );
             }
             else
             {
-                for (const int proci : Pstream::subProcs(Pstream::worldComm))
+                for (const int proci : Pstream::subProcs(comm_))
                 {
+                    if (debug)
+                    {
+                        Pout<< "masterUncollatedFileOperation::NewIFstream :"
+                            << " Opening local file " << filePath
+                            << " for rank " << proci << endl;
+                    }
+
                     readAndSend
                     (
                         filePaths[proci],
@@ -2334,7 +2451,7 @@ Foam::fileOperations::masterUncollatedFileOperation::NewIFstream
 
         pBufs.finishedSends();
 
-        if (Pstream::master(Pstream::worldComm))
+        if (Pstream::master(comm_))
         {
             // Read myself
             isPtr.reset(new IFstream(filePaths[Pstream::masterNo()]));
@@ -2394,6 +2511,7 @@ Foam::fileOperations::masterUncollatedFileOperation::NewOFstream
     (
         new masterOFstream
         (
+            comm_,
             pathName,
             streamOpt,
             IOstreamOption::NON_APPEND,
@@ -2417,6 +2535,7 @@ Foam::fileOperations::masterUncollatedFileOperation::NewOFstream
         new masterOFstream
         (
             atomic,
+            comm_,
             pathName,
             streamOpt,
             IOstreamOption::NON_APPEND,
@@ -2433,17 +2552,93 @@ void Foam::fileOperations::masterUncollatedFileOperation::flush() const
 }
 
 
+void Foam::fileOperations::masterUncollatedFileOperation::sync()
+{
+    if (debug)
+    {
+        Pout<< "masterUncollatedFileOperation::sync :"
+            << " syncing information across processors" << endl;
+    }
+
+    fileOperation::sync();
+
+
+    wordList timeNames;
+    List<DynamicList<instant>> instants;
+
+    if (Pstream::master(UPstream::worldComm))
+    {
+        timeNames.resize(times_.size());
+        instants.resize(times_.size());
+
+        // Flatten into two lists to preserve key/val pairing
+        label i = 0;
+        forAllConstIters(times_, iter)
+        {
+            timeNames[i] = iter.key();
+            instants[i] = std::move(*(iter.val()));
+            ++i;
+        }
+    }
+
+    Pstream::broadcasts(UPstream::worldComm, timeNames, instants);
+
+    times_.clear();
+    forAll(timeNames, i)
+    {
+        fileName dir(timeNames[i]);
+        auto ptr = autoPtr<DynamicList<instant>>::New(std::move(instants[i]));
+
+        if (Pstream::parRun() && !Pstream::master(UPstream::worldComm))
+        {
+            // Replace processor0 ending with processorDDD
+            fileName path;
+            fileName pDir;
+            fileName local;
+            procRangeType group;
+            label numProcs;
+            const label proci = splitProcessorPath
+            (
+                dir,
+                path,
+                pDir,
+                local,
+                group,
+                numProcs
+            );
+
+            //Pout<< "**sync : From dir : " << dir << nl
+            //    << "    path : " << path << nl
+            //    << "    pDir : " << pDir << nl
+            //    << "    local: " << local << nl
+            //    << "    proci: " << proci << nl
+            //    << endl;
+
+            const label myProci = Pstream::myProcNo(UPstream::worldComm);
+
+            if (proci != -1 && proci != myProci)
+            {
+                dir = path/"processor" + Foam::name(myProci);
+            }
+        }
+
+        times_.insert(dir, ptr);
+    }
+}
+
+
 Foam::label Foam::fileOperations::masterUncollatedFileOperation::addWatch
 (
     const fileName& fName
 ) const
 {
     label watchFd = -1;
-    if (Pstream::master())      // comm_))
+    if (!UPstream::parRun() || Pstream::master(UPstream::worldComm))
     {
         watchFd = monitor().addWatch(fName);
     }
-    Pstream::broadcast(watchFd);  //, comm_);
+
+    Pstream::broadcast(watchFd, UPstream::worldComm);
     return watchFd;
 }
 
@@ -2454,11 +2649,12 @@ bool Foam::fileOperations::masterUncollatedFileOperation::removeWatch
 ) const
 {
     bool ok = false;
-    if (Pstream::master())  // comm_))
+    if (!UPstream::parRun() || Pstream::master(UPstream::worldComm))
     {
         ok = monitor().removeWatch(watchIndex);
     }
-    Pstream::broadcast(ok);   //, comm_);
+
+    Pstream::broadcast(ok, UPstream::worldComm);
     return ok;
 }
 
@@ -2471,7 +2667,7 @@ Foam::label Foam::fileOperations::masterUncollatedFileOperation::findWatch
 {
     label index = -1;
 
-    if (Pstream::master())  // comm_))
+    if (!UPstream::parRun() || Pstream::master(UPstream::worldComm))
     {
         forAll(watchIndices, i)
         {
@@ -2482,7 +2678,8 @@ Foam::label Foam::fileOperations::masterUncollatedFileOperation::findWatch
             }
         }
     }
-    Pstream::broadcast(index);  //, comm_);
+
+    Pstream::broadcast(index, UPstream::worldComm);
     return index;
 }
 
@@ -2530,11 +2727,12 @@ Foam::fileName Foam::fileOperations::masterUncollatedFileOperation::getFile
 ) const
 {
     fileName fName;
-    if (Pstream::master())  // comm_))
+    if (!UPstream::parRun() || Pstream::master(UPstream::worldComm))
     {
         fName = monitor().getFile(watchIndex);
     }
-    Pstream::broadcast(fName);  //, comm_);
+
+    Pstream::broadcast(fName, UPstream::worldComm);
     return fName;
 }
 
@@ -2545,7 +2743,7 @@ void Foam::fileOperations::masterUncollatedFileOperation::updateStates
     const bool syncPar
 ) const
 {
-    if (Pstream::master())  // comm_))
+    if (!UPstream::parRun() || Pstream::master(UPstream::worldComm))
     {
         monitor().updateStates(true, false);
     }
@@ -2559,11 +2757,12 @@ Foam::fileOperations::masterUncollatedFileOperation::getState
 ) const
 {
     unsigned int state = fileMonitor::UNMODIFIED;
-    if (Pstream::master())  // comm_))
+    if (!UPstream::parRun() || Pstream::master(UPstream::worldComm))
     {
         state = monitor().getState(watchFd);
     }
-    Pstream::broadcast(state);  //, comm_);
+
+    Pstream::broadcast(state, UPstream::worldComm);
     return fileMonitor::fileState(state);
 }
 
@@ -2573,7 +2772,7 @@ void Foam::fileOperations::masterUncollatedFileOperation::setUnmodified
     const label watchFd
 ) const
 {
-    if (Pstream::master())  // comm_))
+    if (!UPstream::parRun() || Pstream::master(UPstream::worldComm))
     {
         monitor().setUnmodified(watchFd);
     }
diff --git a/src/OpenFOAM/global/fileOperations/masterUncollatedFileOperation/masterUncollatedFileOperation.H b/src/OpenFOAM/global/fileOperations/masterUncollatedFileOperation/masterUncollatedFileOperation.H
index 0b4848488e3d0ae6f378b338be8e23ba24042c01..6ddda2dcdda8328e5bf2971fef67f2bfc96a5e2d 100644
--- a/src/OpenFOAM/global/fileOperations/masterUncollatedFileOperation/masterUncollatedFileOperation.H
+++ b/src/OpenFOAM/global/fileOperations/masterUncollatedFileOperation/masterUncollatedFileOperation.H
@@ -392,9 +392,6 @@ protected:
 
     // Private Member Functions
 
-        //- Get the list of processors that are part of this communicator
-        static labelList subRanks(const label n);
-
         template<class Type>
         Type scatterList(const UList<Type>&, const int, const label comm) const;
 
@@ -436,6 +433,7 @@ protected:
             const bool checkGlobal,
             const bool isFile,
             const IOobject& io,
+            const dirIndexList& pDirs,
             const bool search,
             pathType& searchType,
             word& processorsDir,
@@ -494,8 +492,13 @@ public:
         //- Default construct
         explicit masterUncollatedFileOperation(bool verbose = false);
 
-        //- Construct from communicator
-        masterUncollatedFileOperation(const label comm, bool verbose);
+        //- Construct from communicator with specified io-ranks
+        explicit masterUncollatedFileOperation
+        (
+            const Tuple2<label, labelList>& commAndIORanks,
+            const bool distributedRoots,
+            bool verbose = false
+        );
 
 
     //- Destructor
@@ -773,6 +776,9 @@ public:
             //- Forcibly wait until all output done. Flush any cached data
             virtual void flush() const;
 
+            //- Forcibly parallel sync
+            virtual void sync();
+
             //- Return cached times
             const HashPtrTable<DynamicList<instant>>& times() const noexcept
             {
diff --git a/src/OpenFOAM/global/fileOperations/uncollatedFileOperation/hostUncollatedFileOperation.C b/src/OpenFOAM/global/fileOperations/uncollatedFileOperation/hostUncollatedFileOperation.C
new file mode 100644
index 0000000000000000000000000000000000000000..ab7b28b175d30ec9a6c2321068ef55d2dc1a4e9f
--- /dev/null
+++ b/src/OpenFOAM/global/fileOperations/uncollatedFileOperation/hostUncollatedFileOperation.C
@@ -0,0 +1,175 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2022-2023 OpenCFD Ltd.
+-------------------------------------------------------------------------------
+License
+    This file is part of OpenFOAM.
+
+    OpenFOAM 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, either version 3 of the License, or
+    (at your option) any later version.
+
+    OpenFOAM 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 OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
+
+\*---------------------------------------------------------------------------*/
+
+#include "hostUncollatedFileOperation.H"
+#include "addToRunTimeSelectionTable.H"
+
+/* * * * * * * * * * * * * * * Static Member Data  * * * * * * * * * * * * * */
+
+namespace Foam
+{
+namespace fileOperations
+{
+    defineTypeNameAndDebug(hostUncollatedFileOperation, 0);
+    addToRunTimeSelectionTable
+    (
+        fileOperation,
+        hostUncollatedFileOperation,
+        word
+    );
+    addToRunTimeSelectionTable
+    (
+        fileOperation,
+        hostUncollatedFileOperation,
+        comm
+    );
+
+    // Register initialisation routine. Signals need for threaded mpi and
+    // handles command line arguments
+    addNamedToRunTimeSelectionTable
+    (
+        fileOperationInitialise,
+        hostUncollatedFileOperationInitialise,
+        word,
+        hostUncollated
+    );
+}
+}
+
+
+// * * * * * * * * * * * * * * * Local Functions * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+
+// Construction helper: self/world/local communicator and IO ranks
+static Tuple2<label, labelList> getCommPattern()
+{
+    // Default is COMM_WORLD (single master)
+    Tuple2<label, labelList> commAndIORanks
+    (
+        UPstream::worldComm,
+        fileOperation::getGlobalIORanks()
+    );
+
+    if (commAndIORanks.second().empty())
+    {
+        // Default: one master per host
+        commAndIORanks.second() = fileOperation::getGlobalHostIORanks();
+    }
+
+    if (UPstream::parRun() && commAndIORanks.second().size() > 1)
+    {
+        // Multiple masters: ranks for my IO range
+        commAndIORanks.first() = UPstream::allocateCommunicator
+        (
+            UPstream::worldComm,
+            fileOperation::subRanks(commAndIORanks.second())
+        );
+    }
+
+    return commAndIORanks;
+}
+
+} // End namespace Foam
+
+
+// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
+
+void Foam::fileOperations::hostUncollatedFileOperation::init(bool verbose)
+{
+    verbose = (verbose && Foam::infoDetailLevel > 0);
+
+    if (verbose)
+    {
+        DetailInfo
+            << "I/O    : " << this->type() << nl;
+
+        if (ioRanks_.size())
+        {
+            fileOperation::printRanks();
+        }
+    }
+}
+
+
+Foam::fileOperations::hostUncollatedFileOperation::hostUncollatedFileOperation
+(
+    bool verbose
+)
+:
+    masterUncollatedFileOperation
+    (
+        getCommPattern(),
+        false,  // distributedRoots
+        false   // verbose
+    ),
+    managedComm_(getManagedComm(comm_))  // Possibly locally allocated
+{
+    init(verbose);
+}
+
+
+Foam::fileOperations::hostUncollatedFileOperation::hostUncollatedFileOperation
+(
+    const Tuple2<label, labelList>& commAndIORanks,
+    const bool distributedRoots,
+    bool verbose
+)
+:
+    masterUncollatedFileOperation
+    (
+        commAndIORanks,
+        distributedRoots,
+        false   // verbose
+    ),
+    managedComm_(-1)  // Externally managed
+{
+    init(verbose);
+}
+
+
+void Foam::fileOperations::hostUncollatedFileOperation::storeComm() const
+{
+    // From externally -> locally managed
+    managedComm_ = getManagedComm(comm_);
+}
+
+
+// * * * * * * * * * * * * * * * * Destructor  * * * * * * * * * * * * * * * //
+
+Foam::fileOperations::hostUncollatedFileOperation::
+~hostUncollatedFileOperation()
+{
+    // Wait for any outstanding file operations
+    flush();
+
+    UPstream::freeCommunicator(managedComm_);
+}
+
+
+// ************************************************************************* //
diff --git a/src/OpenFOAM/global/fileOperations/uncollatedFileOperation/hostUncollatedFileOperation.H b/src/OpenFOAM/global/fileOperations/uncollatedFileOperation/hostUncollatedFileOperation.H
new file mode 100644
index 0000000000000000000000000000000000000000..c76baf57164bc67dbe048fb055649fa639af7441
--- /dev/null
+++ b/src/OpenFOAM/global/fileOperations/uncollatedFileOperation/hostUncollatedFileOperation.H
@@ -0,0 +1,130 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2022-2023 OpenCFD Ltd.
+-------------------------------------------------------------------------------
+License
+    This file is part of OpenFOAM.
+
+    OpenFOAM 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, either version 3 of the License, or
+    (at your option) any later version.
+
+    OpenFOAM 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 OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
+
+Class
+    Foam::fileOperations::hostUncollatedFileOperation
+
+Description
+    Version of masterUncollated with host-based IO ranks
+
+\*---------------------------------------------------------------------------*/
+
+#ifndef Foam_fileOperations_hostUncollatedFileOperation_H
+#define Foam_fileOperations_hostUncollatedFileOperation_H
+
+#include "masterUncollatedFileOperation.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+namespace fileOperations
+{
+
+/*---------------------------------------------------------------------------*\
+                 Class hostUncollatedFileOperation Declaration
+\*---------------------------------------------------------------------------*/
+
+class hostUncollatedFileOperation
+:
+    public masterUncollatedFileOperation
+{
+    // Private Data
+
+        //- Communicator allocated/managed by us
+        mutable label managedComm_;
+
+
+    // Private Member Functions
+
+        //- Any initialisation steps after constructing
+        void init(bool verbose);
+
+public:
+
+        //- Runtime type information
+        TypeName("hostUncollated");
+
+
+    // Constructors
+
+        //- Default construct
+        explicit hostUncollatedFileOperation(bool verbose = false);
+
+        //- Construct from communicator with specified io-ranks
+        explicit hostUncollatedFileOperation
+        (
+            const Tuple2<label, labelList>& commAndIORanks,
+            const bool distributedRoots,
+            bool verbose = false
+        );
+
+
+    //- Destructor
+    virtual ~hostUncollatedFileOperation();
+
+
+    // Member Functions
+
+        //- Transfer ownership of communicator to this fileOperation.
+        //- Use with caution
+        virtual void storeComm() const;
+};
+
+
+/*---------------------------------------------------------------------------*\
+            Class hostUncollatedFileOperationInitialise Declaration
+\*---------------------------------------------------------------------------*/
+
+class hostUncollatedFileOperationInitialise
+:
+    public masterUncollatedFileOperationInitialise
+{
+public:
+
+    // Constructors
+
+        //- Construct from components
+        hostUncollatedFileOperationInitialise(int& argc, char**& argv)
+        :
+            masterUncollatedFileOperationInitialise(argc, argv)
+        {}
+
+
+    //- Destructor
+    virtual ~hostUncollatedFileOperationInitialise() = default;
+};
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+} // End namespace fileOperations
+} // End namespace Foam
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#endif
+
+// ************************************************************************* //
diff --git a/src/OpenFOAM/global/fileOperations/uncollatedFileOperation/uncollatedFileOperation.C b/src/OpenFOAM/global/fileOperations/uncollatedFileOperation/uncollatedFileOperation.C
index 0988f3b2515b0604f20195f38eeb163648c7da1e..2b380cc1f673dea25791e312be3b5a94393d2e58 100644
--- a/src/OpenFOAM/global/fileOperations/uncollatedFileOperation/uncollatedFileOperation.C
+++ b/src/OpenFOAM/global/fileOperations/uncollatedFileOperation/uncollatedFileOperation.C
@@ -41,7 +41,18 @@ namespace Foam
 namespace fileOperations
 {
     defineTypeNameAndDebug(uncollatedFileOperation, 0);
-    addToRunTimeSelectionTable(fileOperation, uncollatedFileOperation, word);
+    addToRunTimeSelectionTable
+    (
+        fileOperation,
+        uncollatedFileOperation,
+        word
+    );
+    addToRunTimeSelectionTable
+    (
+        fileOperation,
+        uncollatedFileOperation,
+        comm
+    );
 
     // Mark as not needing threaded mpi
     addNamedToRunTimeSelectionTable
@@ -159,7 +170,7 @@ Foam::fileName Foam::fileOperations::uncollatedFileOperation::filePathInfo
         }
     }
 
-    return fileName::null;
+    return fileName();
 }
 
 
@@ -174,6 +185,37 @@ Foam::fileOperations::uncollatedFileOperation::lookupProcessorsPath
 }
 
 
+// * * * * * * * * * * * * * * * Local Functions * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+
+// Construction helper: self/world/local communicator and IO ranks
+static Tuple2<label, labelList> getCommPattern()
+{
+    // Default is COMM_SELF (only involves itself)
+    Tuple2<label, labelList> commAndIORanks
+    (
+        UPstream::selfComm,
+        fileOperation::getGlobalIORanks()
+    );
+
+    if (UPstream::parRun() && commAndIORanks.second().size() > 1)
+    {
+        // Multiple masters: ranks for my IO range
+        commAndIORanks.first() = UPstream::allocateCommunicator
+        (
+            UPstream::worldComm,
+            fileOperation::subRanks(commAndIORanks.second())
+        );
+    }
+
+    return commAndIORanks;
+}
+
+} // End namespace Foam
+
+
 // * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
 
 void Foam::fileOperations::uncollatedFileOperation::init(bool verbose)
@@ -193,7 +235,24 @@ Foam::fileOperations::uncollatedFileOperation::uncollatedFileOperation
     bool verbose
 )
 :
-    fileOperation(UPstream::worldComm),
+    fileOperation
+    (
+        getCommPattern()
+    ),
+    managedComm_(getManagedComm(comm_))  // Possibly locally allocated
+{
+    init(verbose);
+}
+
+
+Foam::fileOperations::uncollatedFileOperation::uncollatedFileOperation
+(
+    const Tuple2<label, labelList>& commAndIORanks,
+    const bool distributedRoots,
+    bool verbose
+)
+:
+    fileOperation(commAndIORanks, distributedRoots),
     managedComm_(-1)  // Externally managed
 {
     init(verbose);
@@ -211,6 +270,9 @@ void Foam::fileOperations::uncollatedFileOperation::storeComm() const
 
 Foam::fileOperations::uncollatedFileOperation::~uncollatedFileOperation()
 {
+    // Wait for any outstanding file operations
+    flush();
+
     UPstream::freeCommunicator(managedComm_);
 }
 
diff --git a/src/OpenFOAM/global/fileOperations/uncollatedFileOperation/uncollatedFileOperation.H b/src/OpenFOAM/global/fileOperations/uncollatedFileOperation/uncollatedFileOperation.H
index a58d60a58d91856f122b2a8ac48e567afacb53f3..41ddb96b5fb1dff4f6a799783e19ae88876758eb 100644
--- a/src/OpenFOAM/global/fileOperations/uncollatedFileOperation/uncollatedFileOperation.H
+++ b/src/OpenFOAM/global/fileOperations/uncollatedFileOperation/uncollatedFileOperation.H
@@ -64,6 +64,7 @@ class uncollatedFileOperation
         //- Any initialisation steps after constructing
         void init(bool verbose);
 
+
 protected:
 
     // Protected Member Functions
@@ -90,8 +91,8 @@ protected:
 
 public:
 
-        //- Runtime type information
-        TypeName("uncollated");
+    //- Runtime type information
+    TypeName("uncollated");
 
 
     // Constructors
@@ -99,6 +100,14 @@ public:
         //- Default construct
         explicit uncollatedFileOperation(bool verbose = false);
 
+        //- Construct from communicator with specified io-ranks
+        explicit uncollatedFileOperation
+        (
+            const Tuple2<label, labelList>& commAndIORanks,
+            const bool distributedRoots,
+            bool verbose = false
+        );
+
 
     //- Destructor
     virtual ~uncollatedFileOperation();