diff --git a/applications/test/etcFiles/Test-etcFiles.C b/applications/test/etcFiles/Test-etcFiles.C
index 59d3a8b47f0b6c9cd2a903e80469f9e41c552cbb..41958ac33f808301a4204f2f1d185bcb1bc9d199 100644
--- a/applications/test/etcFiles/Test-etcFiles.C
+++ b/applications/test/etcFiles/Test-etcFiles.C
@@ -102,6 +102,12 @@ int main(int argc, char *argv[])
         }
     }
 
+
+    // This should and will fail:
+    //// Info<<"find bad file:" << nl
+    ////     << findEtcFile("##BadName##", true) << "FAIL" << endl;
+
+
     const bool listAll = (args.found("all") || args.found("list"));
 
     int error = 0;
diff --git a/applications/test/string/Test-string.C b/applications/test/string/Test-string.C
index 52d2b58ab239bc7e611a665184b55a1da6314120..6f06ddd11a18929bb7f0a98db2cab5b80c1f4634 100644
--- a/applications/test/string/Test-string.C
+++ b/applications/test/string/Test-string.C
@@ -102,6 +102,13 @@ int main(int argc, char *argv[])
           :
             {
                 "~OpenFOAM/controlDict",    "<etc>/controlDict",
+                "<etc:ugo>/controlDict",
+                "<etc:u>/controlDict",
+                "<etc:ug>/controlDict",
+                "<etc:go>/controlDict",
+                "<etc:o>/controlDict",
+                "<etc:JUNK>/controlDict",   // rubbish input
+                "<etc:>/controlDict",       // rubbish input
                 "$FOAM_CASE/xyz",           "<case>/xyz",
                 "$FOAM_CASE/constant/xyz",  "<constant>/xyz",
                 "$FOAM_CASE/system/xyz",    "<system>/xyz",
diff --git a/applications/utilities/preProcessing/createZeroDirectory/createZeroDirectory.C b/applications/utilities/preProcessing/createZeroDirectory/createZeroDirectory.C
index 68b7e607a98f76c2ffc98f47594379bff77a03cc..e492bd37fffbb8536d481c11d729900a2d277ac0 100644
--- a/applications/utilities/preProcessing/createZeroDirectory/createZeroDirectory.C
+++ b/applications/utilities/preProcessing/createZeroDirectory/createZeroDirectory.C
@@ -240,12 +240,13 @@ int main(int argc, char *argv[])
 
     fileName baseDir
     (
-        "${WM_PROJECT_DIR}/etc/caseDicts/createZeroDirectoryTemplates"
+        args.opt<fileName>
+        (
+            "templateDir",
+            // Default is from PROJECT/etc directory
+            "<etc:o>/caseDicts/createZeroDirectoryTemplates"
+        )
     );
-    if (args.found("templateDir"))
-    {
-        baseDir = args["templateDir"];
-    }
 
     baseDir.expand();
     baseDir.toAbsolute();
diff --git a/src/OpenFOAM/global/etcFiles/etcFiles.C b/src/OpenFOAM/global/etcFiles/etcFiles.C
index fc9997b4bf956263421c65a97eafc47a924ec414..2b941f5fc3cb09df03a9cd81b31843e29551659f 100644
--- a/src/OpenFOAM/global/etcFiles/etcFiles.C
+++ b/src/OpenFOAM/global/etcFiles/etcFiles.C
@@ -30,12 +30,32 @@ License
 // * * * * * * * * * * * * * * Static Functions  * * * * * * * * * * * * * * //
 
 //
-// These could be exposed too (if required), but are fairly special purpose.
+// Some of these could be exposed too (if required),
+// but are fairly special purpose.
 //
 
 namespace
 {
 
+// Return the file location mode as a string.
+//
+// - u : location mask 0700
+// - g : location mask 0070
+// - o : location mask 0007
+//
+static inline std::string locationToString(unsigned short location)
+{
+    std::string mode;
+
+    if (location & 0700) { mode += 'u'; } // User
+    if (location & 0070) { mode += 'g'; } // Group
+    if (location & 0007) { mode += 'o'; } // Other
+    if (mode.empty()) { mode = "???"; }
+
+    return mode;
+}
+
+
 // Assign 'queried' parameter to the user resource directory.
 // Return true if this directory exists.
 //
@@ -64,6 +84,7 @@ static inline bool userResourceDir(Foam::fileName& queried)
 
 // Assign 'queried' parameter to the group resource directory.
 // Return true if this directory exists.
+// Otherwise clears the parameter and returns false.
 //
 // Corresponds to foamEtcFile -mode=g
 // Looks for
@@ -102,6 +123,7 @@ static inline bool groupResourceDir(Foam::fileName& queried)
 
 // Assign 'queried' parameter to the OpenFOAM etc/ resource directory.
 // Return true if it exists.
+// Otherwise clears the parameter and returns false.
 //
 // Corresponds to foamEtcFile -mode=o
 // Looks for
@@ -122,6 +144,7 @@ static inline bool projectResourceDir(Foam::fileName& queried)
 Foam::fileNameList searchEtc
 (
     const Foam::fileName& name,
+    unsigned short location,
     const bool findFirst,
     bool (*accept)(const Foam::fileName&)
 )
@@ -131,13 +154,23 @@ Foam::fileNameList searchEtc
     const Foam::fileName version(std::to_string(Foam::foamVersion::api));
 
     Foam::fileNameList list;
-    Foam::fileName dir, candidate;
+    Foam::fileName queried, candidate;
+
+    if (!(location & 0777))
+    {
+        // Warn about bad location (mode) ... or make it FATAL?
+        std::cerr
+            << "--> FOAM Error :\n    "
+            "No user/group/other location specified for 'etc' file"
+            " or directory\n    '"
+            << name.c_str() << "'\n\n" << std::endl;
+    }
 
 
     // User resource directories
-    if (userResourceDir(dir))
+    if ((location & 0700) && userResourceDir(queried))
     {
-        candidate = dir/version/name;
+        candidate = queried/version/name;
         if (accept(candidate))
         {
             list.append(std::move(candidate));
@@ -147,7 +180,7 @@ Foam::fileNameList searchEtc
             }
         }
 
-        candidate = dir/name;
+        candidate = queried/name;
         if (accept(candidate))
         {
             list.append(std::move(candidate));
@@ -159,9 +192,9 @@ Foam::fileNameList searchEtc
     }
 
     // Group (site) resource directories
-    if (groupResourceDir(dir))
+    if ((location & 0070) && groupResourceDir(queried))
     {
-        candidate = dir/version/name;
+        candidate = queried/version/name;
         if (accept(candidate))
         {
             list.append(std::move(candidate));
@@ -171,7 +204,7 @@ Foam::fileNameList searchEtc
             }
         }
 
-        candidate = dir/name;
+        candidate = queried/name;
         if (accept(candidate))
         {
             list.append(std::move(candidate));
@@ -183,9 +216,9 @@ Foam::fileNameList searchEtc
     }
 
     // Other (project) resource directory
-    if (projectResourceDir(dir))
+    if ((location & 0007) && projectResourceDir(queried))
     {
-        candidate = dir/name;
+        candidate = queried/name;
         if (accept(candidate))
         {
             list.append(std::move(candidate));
@@ -207,27 +240,27 @@ Foam::fileNameList Foam::etcDirs(bool test)
     const Foam::fileName version(std::to_string(Foam::foamVersion::api));
 
     Foam::fileNameList list(5);
-    Foam::fileName dir;
+    Foam::fileName queried;
     label nDirs = 0;
 
     // User resource directories
-    if (userResourceDir(dir) || (!test && dir.size()))
+    if (userResourceDir(queried) || (!test && queried.size()))
     {
-        list[nDirs++] = dir/version;
-        list[nDirs++] = dir;
+        list[nDirs++] = queried/version;
+        list[nDirs++] = queried;
     }
 
     // Group (site) resource directories
-    if (groupResourceDir(dir) || (!test && dir.size()))
+    if (groupResourceDir(queried) || (!test && queried.size()))
     {
-        list[nDirs++] = dir/version;
-        list[nDirs++] = dir;
+        list[nDirs++] = queried/version;
+        list[nDirs++] = queried;
     }
 
     // Other (project) resource directory
-    if (projectResourceDir(dir) || (!test && dir.size()))
+    if (projectResourceDir(queried) || (!test && queried.size()))
     {
-        list[nDirs++] = dir;
+        list[nDirs++] = queried;
     }
 
     list.resize(nDirs);
@@ -239,13 +272,15 @@ Foam::fileNameList Foam::etcDirs(bool test)
 Foam::fileNameList Foam::findEtcDirs
 (
     const fileName& name,
-    const bool findFirst
+    const bool findFirst,
+    unsigned short location
 )
 {
     return
         searchEtc
         (
             name,
+            location,
             findFirst,
             [](const fileName& f){ return Foam::isDir(f); }
         );
@@ -256,7 +291,8 @@ Foam::fileNameList Foam::findEtcFiles
 (
     const fileName& name,
     const bool mandatory,
-    const bool findFirst
+    const bool findFirst,
+    unsigned short location
 )
 {
     fileNameList list;
@@ -267,6 +303,7 @@ Foam::fileNameList Foam::findEtcFiles
         list = searchEtc
         (
             name,
+            location,
             findFirst,
             [](const fileName& f){ return Foam::isFile(f); }
         );
@@ -274,11 +311,16 @@ Foam::fileNameList Foam::findEtcFiles
 
     if (mandatory && list.empty())
     {
-        // Abort if file is mandatory but not found
+        // Abort if file is mandatory but not found.
+        // Use a direct exit, since this could occur before anything is
+        // setup at all.
+
         std::cerr
-            << "--> FOAM FATAL ERROR in Foam::findEtcFiles()"
-               " :  could not find mandatory file\n    '"
-            << name.c_str() << "'\n\n" << std::endl;
+            << "--> FOAM FATAL ERROR :\n    "
+            "Could not find mandatory etc file (mode="
+            << locationToString(location) << ")\n    '"
+            << name.c_str() << "'\n"
+            << std::endl;
         ::exit(1);
     }
 
@@ -286,16 +328,42 @@ Foam::fileNameList Foam::findEtcFiles
 }
 
 
-Foam::fileName Foam::findEtcFile(const fileName& name, const bool mandatory)
+Foam::fileName Foam::findEtcDir
+(
+    const fileName& name,
+    unsigned short location
+)
 {
-    fileNameList list(findEtcFiles(name, mandatory, true));
+    fileNameList list(findEtcDirs(name, true, location));
+
+    fileName found;
+
+    if (list.size())
+    {
+        found = std::move(list.first());
+    }
+
+    return found;
+}
+
+
+Foam::fileName Foam::findEtcFile
+(
+    const fileName& name,
+    const bool mandatory,
+    unsigned short location
+)
+{
+    fileNameList list(findEtcFiles(name, mandatory, true, location));
+
+    fileName found;
 
     if (list.size())
     {
-        return list.first();
+        found = std::move(list.first());
     }
 
-    return fileName();
+    return found;
 }
 
 
diff --git a/src/OpenFOAM/global/etcFiles/etcFiles.H b/src/OpenFOAM/global/etcFiles/etcFiles.H
index 9fdcc696c330cdbee8fa666287f5d8e79f46cf8f..7143756441b8a5c815f17c9e39f555d46a42e232 100644
--- a/src/OpenFOAM/global/etcFiles/etcFiles.H
+++ b/src/OpenFOAM/global/etcFiles/etcFiles.H
@@ -60,25 +60,38 @@ fileNameList etcDirs(bool test=true);
 fileNameList findEtcDirs
 (
     const fileName& name,       //!< The directory to search for
-    const bool findFirst=false  //!< Stop after locating the first directory
+    const bool findFirst=false, //!< Stop after locating the first directory
+    unsigned short location=0777 //!< User/group/other location
 );
 
 
+//- Search for a single directory using findEtcDirs().
+//
+//  \return The full path name of the first directory found in the
+//      search hierarchy or an empty fileName if the name cannot be found.
+fileName findEtcDir
+(
+    const fileName& name,       //!< The directory to search for
+    unsigned short location=0777 //!< User/group/other location
+);
+
 //- Search for files from user/group/other directories.
 //
-//  The search hierarchy corresponds to that of the foamEtcFile script,
-//  which allows for version-specific and version-independent files:
-//  -# \b user settings
+//  The search hierarchy corresponds to that of the \c foamEtcFile script,
+//  which allows for version-specific and version-independent files.
+//
+//  -# \b user settings (\c location=0700)
 //    - ~/.OpenFOAM/{PROJECT_API}
 //    - ~/.OpenFOAM/
-//  -# \b group settings
+//  -# \b group settings (\c location=0070)
 //    - $WM_PROJECT_SITE/{PROJECT_API}/etc/
 //    - $WM_PROJECT_SITE/etc/
-//  -# \b other (shipped) settings
+//  -# \b other (shipped) settings (\c location=0007)
 //    - $WM_PROJECT_DIR/etc/
 //
-//  Where {PROJECT_API} is the value of the OPENFOAM define.
-//  - \b \$WM_PROJECT_SITE : If unset, uses $WM_PROJECT_DIR/site
+//  Where {PROJECT_API} corresponds to the foamVersion::api value.
+//
+//  \note When \c \$WM_PROJECT_SITE is unset, uses \c $WM_PROJECT_DIR/site
 //
 //  \return The list of full paths of all the matching files or
 //      an empty list if the name cannot be found.
@@ -86,7 +99,8 @@ fileNameList findEtcFiles
 (
     const fileName& name,       //!< The file to search for
     const bool mandatory=false, //!< Abort if the file cannot be found
-    const bool findFirst=false  //!< Stop after locating the first directory
+    const bool findFirst=false, //!< Stop after locating the first directory
+    unsigned short location=0777 //!< User/group/other location
 );
 
 
@@ -97,7 +111,8 @@ fileNameList findEtcFiles
 fileName findEtcFile
 (
     const fileName& name,       //!< The file to search for
-    const bool mandatory=false  //!< Abort if the file cannot be found
+    const bool mandatory=false, //!< Abort if the file cannot be found
+    unsigned short location=0777 //!< User/group/other location
 );
 
 
diff --git a/src/OpenFOAM/primitives/strings/stringOps/stringOps.C b/src/OpenFOAM/primitives/strings/stringOps/stringOps.C
index 0d86363d3f9b0f0ab2d2b444963c15018110904f..eaf82965124285bfa3fb85bc2145e5ad04d2ef8f 100644
--- a/src/OpenFOAM/primitives/strings/stringOps/stringOps.C
+++ b/src/OpenFOAM/primitives/strings/stringOps/stringOps.C
@@ -37,10 +37,33 @@ License
 namespace Foam
 {
 
+// Return the file location mode (string) as a numerical value.
+//
+// - u : location mask 0700
+// - g : location mask 0070
+// - o : location mask 0007
+//
+static inline unsigned short modeToLocation
+(
+    const std::string& mode,
+    std::size_t pos = 0
+)
+{
+    unsigned short where(0);
+
+    if (std::string::npos != mode.find('u', pos)) { where |= 0700; } // User
+    if (std::string::npos != mode.find('g', pos)) { where |= 0070; } // Group
+    if (std::string::npos != mode.find('o', pos)) { where |= 0007; } // Other
+
+    return where;
+}
+
+
 // Expand a leading <tag>/
 // Convenient for frequently used directories
 //
-//   <etc>/        => user/group/other OpenFOAM directory
+//   <etc>/        => user/group/other etc - findEtcFile()
+//   <etc(:[ugo]+)?>/ => user/group/other etc - findEtcFile()
 //   <case>/       => FOAM_CASE directory
 //   <constant>/   => FOAM_CASE/constant directory
 //   <system>/     => FOAM_CASE/system directory
@@ -52,7 +75,7 @@ static void expandLeadingTag(std::string& s, const char b, const char e)
     }
 
     auto delim = s.find(e);
-    if (delim == std::string::npos)
+    if (std::string::npos == delim)
     {
         return;  // Error: no closing delim - ignore expansion
     }
@@ -73,6 +96,7 @@ static void expandLeadingTag(std::string& s, const char b, const char e)
     }
 
     const std::string tag(s, 1, delim-2);
+    const auto tagLen = tag.length();
 
     // Note that file is also allowed to be an empty string.
 
@@ -88,6 +112,12 @@ static void expandLeadingTag(std::string& s, const char b, const char e)
     {
         s = fileName(Foam::getEnv("FOAM_CASE"))/tag/file;
     }
+    else if (tagLen >= 4 && tag.compare(0, 4, "etc:") == 0)
+    {
+        // <etc:ugo> type of tag - convert "ugo" to numeric
+
+        s = findEtcFile(file, false, modeToLocation(tag, 4));
+    }
 }
 
 
diff --git a/src/OpenFOAM/primitives/strings/stringOps/stringOps.H b/src/OpenFOAM/primitives/strings/stringOps/stringOps.H
index 6e965085fa3ae669680cfae1294342b8742af509..74d11b051b7fa90d50aee476c7bdd6c52527238e 100644
--- a/src/OpenFOAM/primitives/strings/stringOps/stringOps.H
+++ b/src/OpenFOAM/primitives/strings/stringOps/stringOps.H
@@ -219,30 +219,45 @@ namespace stringOps
     //  -# environment variables
     //    - "$VAR", "${VAR}"
     //  -# current directory
-    //    - leading "./" : the current directory
+    //    - leading "./"
+    //          : the current directory - Foam::cwd()
     //  -# leading tag expansion for commonly used directories
-    //    - \<etc\>/        : user/group/other OpenFOAM directory
-    //    - \<case\>/       : FOAM_CASE directory
-    //    - \<constant\>/   : FOAM_CASE/constant directory
-    //    - \<system\>/     : FOAM_CASE/system directory
+    //    - <b> \<etc\>/ </b>
+    //          : user/group/other OpenFOAM etc directory
+    //    - <b> \<etc:</b><em>[ugo]+</em>)<b>\>/ </b>
+    //          : user/group/other etc with specified location mode
+    //    - <b> \<case\>/ </b>
+    //          : The \c $FOAM_CASE directory
+    //    - <b> \<constant\>/ </b>
+    //          : The \c $FOAM_CASE/constant directory
+    //    - <b> \<system\>/ </b>
+    //          : The \c $FOAM_CASE/system directory
     //  -# tilde expansion
     //    - leading "~/" : home directory
     //    - leading "~user" : home directory for specified user
     //
     //  Supports default and alternative values as per the POSIX shell.
     //  \code
-    //      a)  "${parameter:-defValue}"
-    //      b)  "${parameter:+altValue}"
+    //      1.  "${parameter:-defValue}"
+    //      2.  "${parameter:+altValue}"
     //  \endcode
-    //  a) If parameter is unset or null, the \c defValue is substituted.
-    //  Otherwise, the value of parameter is substituted.
-    //
-    //  b) If parameter is unset or null, nothing is substituted.
-    //  Otherwise the \c altValue is substituted.
+    //  -# If parameter is unset or null, the \c defValue is substituted.
+    //     Otherwise, the value of parameter is substituted.
+    //  -# If parameter is unset or null, nothing is substituted.
+    //     Otherwise the \c altValue is substituted.
+    //  .
     //
+    // General behavior:
     //  - Any unknown entries are removed silently, if allowEmpty is true.
     //  - Malformed entries (eg, brace mismatch, sigil followed by bad chars)
-    //  are left as is.
+    //    are left as is.
+    //
+    //  An example of using the specified location mode
+    //  \code
+    //      fileName controlDict(stringOps::expand("<etc:o>/controlDict"));
+    //      // OR
+    //      fileName controlDict(findEtcFile("controlDict", false, 0007));
+    //  \endcode
     //
     //  \note Deprecated(2018-11) Use "<etc>" instead of the rarely used
     //      "~OpenFOAM" expansion
@@ -263,28 +278,35 @@ namespace stringOps
     //  -# current directory
     //    - leading "./" : the current directory
     //  -# leading tag expansion for commonly used directories
-    //    - \<etc\>/        : user/group/other OpenFOAM directory
-    //    - \<case\>/       : FOAM_CASE directory
-    //    - \<constant\>/   : FOAM_CASE/constant directory
-    //    - \<system\>/     : FOAM_CASE/system directory
+    //    - <b> \<etc\>/ </b>
+    //          : user/group/other OpenFOAM etc directory
+    //    - <b> \<etc:</b><em>[ugo]+</em>)<b>\>/ </b>
+    //          : user/group/other etc with specified location mode
+    //    - <b> \<case\>/ </b>
+    //          : The \c $FOAM_CASE directory
+    //    - <b> \<constant\>/ </b>
+    //          : The \c $FOAM_CASE/constant directory
+    //    - <b> \<system\>/ </b>
+    //          : The \c $FOAM_CASE/system directory
     //  -# tilde expansion
     //    - leading "~/" : home directory
     //    - leading "~user" : home directory for specified user
     //
     //  Supports default and alternative values as per the POSIX shell.
     //  \code
-    //      a)  "${parameter:-defValue}"
-    //      b)  "${parameter:+altValue}"
+    //      1.  "${parameter:-defValue}"
+    //      2.  "${parameter:+altValue}"
     //  \endcode
-    //  a) If parameter is unset or null, the \c defValue is substituted.
-    //  Otherwise, the value of parameter is substituted.
-    //
-    //  b) If parameter is unset or null, nothing is substituted.
-    //  Otherwise the \c altValue is substituted.
+    //  -# If parameter is unset or null, the \c defValue is substituted.
+    //     Otherwise, the value of parameter is substituted.
+    //  -# If parameter is unset or null, nothing is substituted.
+    //     Otherwise the \c altValue is substituted.
+    //  .
     //
+    // General behavior:
     //  - Any unknown entries are removed silently if allowEmpty is true.
     //  - Malformed entries (eg, brace mismatch, sigil followed by bad chars)
-    //  are left as is.
+    //    are left as is.
     //
     //  \note Deprecated(2018-11) Use "<etc>" instead of the rarely used
     //      "~OpenFOAM" expansion