/** * Copyright (C) 2017 MongoDB, Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program 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 Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * * As a special exception, the copyright holders give permission to link the * code of portions of this program with the OpenSSL library under certain * conditions as described in each individual source file and distribute * linked combinations including the program with the OpenSSL library. You * must comply with the GNU Affero General Public License in all respects for * all of the code used other than as permitted herein. If you modify file(s) * with this exception, you may extend this exception to your version of the * file(s), but you are not obligated to do so. If you do not wish to do so, * delete this exception statement from your version. If you delete this * exception statement from all source files in the program, then also delete * it in the license file. */ #pragma once #include #include #include #include #include "mongo/base/status_with.h" #include "mongo/base/string_data.h" #include "mongo/db/repl/optime.h" #include "mongo/platform/hash_namespace.h" #include "mongo/util/assert_util.h" #include "mongo/util/uuid.h" namespace mongo { const size_t MaxDatabaseNameLen = 128; // max str len for the db name, including null char class NamespaceString { public: // Reserved system namespaces // Namespace for the admin database static constexpr StringData kAdminDb = "admin"_sd; // Namespace for the local database static constexpr StringData kLocalDb = "local"_sd; // Namespace for the sharding config database static constexpr StringData kConfigDb = "config"_sd; // Name for the system views collection static constexpr StringData kSystemDotViewsCollectionName = "system.views"_sd; // Name for a shard's collections metadata collection, each document of which indicates the // state of a specific collection. static constexpr StringData kShardConfigCollectionsCollectionName = "config.cache.collections"_sd; // Name for a shard's databases metadata collection, each document of which indicates the // state of a specific database. static constexpr StringData kShardConfigDatabasesCollectionName = "config.cache.databases"_sd; // Name for causal consistency's key collection. static constexpr StringData kSystemKeysCollectionName = "admin.system.keys"_sd; // Namespace for storing configuration data, which needs to be replicated if the server is // running as a replica set. Documents in this collection should represent some configuration // state of the server, which needs to be recovered/consulted at startup. Each document in this // namespace should have its _id set to some string, which meaningfully describes what it // represents. For example, 'shardIdentity' and 'featureCompatibilityVersion'. static const NamespaceString kServerConfigurationNamespace; // Namespace for storing the transaction information for each session static const NamespaceString kSessionTransactionsTableNamespace; // Namespace of the the oplog collection. static const NamespaceString kRsOplogNamespace; /** * Constructs an empty NamespaceString. */ NamespaceString() : _ns(), _dotIndex(std::string::npos) {} /** * Constructs a NamespaceString from the fully qualified namespace named in "ns". */ explicit NamespaceString(StringData ns) { _ns = ns.toString(); // copy to our buffer _dotIndex = _ns.find('.'); uassert(ErrorCodes::InvalidNamespace, "namespaces cannot have embedded null characters", _ns.find('\0') == std::string::npos); } /** * Constructs a NamespaceString for the given database and collection names. * "dbName" must not contain a ".", and "collectionName" must not start with one. */ NamespaceString(StringData dbName, StringData collectionName) : _ns(dbName.size() + collectionName.size() + 1, '\0') { uassert(ErrorCodes::InvalidNamespace, "'.' is an invalid character in a database name", dbName.find('.') == std::string::npos); uassert(ErrorCodes::InvalidNamespace, "Collection names cannot start with '.'", collectionName.empty() || collectionName[0] != '.'); std::string::iterator it = std::copy(dbName.begin(), dbName.end(), _ns.begin()); *it = '.'; ++it; it = std::copy(collectionName.begin(), collectionName.end(), it); _dotIndex = dbName.size(); dassert(it == _ns.end()); dassert(_ns[_dotIndex] == '.'); uassert(ErrorCodes::InvalidNamespace, "namespaces cannot have embedded null characters", _ns.find('\0') == std::string::npos); } /** * Constructs the namespace '.$cmd.aggregate', which we use as the namespace for * aggregation commands with the format {aggregate: 1}. */ static NamespaceString makeCollectionlessAggregateNSS(StringData dbName); /** * Constructs a NamespaceString representing a listCollections namespace. The format for this * namespace is ".$cmd.listCollections". */ static NamespaceString makeListCollectionsNSS(StringData dbName); /** * Constructs a NamespaceString representing a listIndexes namespace. The format for this * namespace is ".$cmd.listIndexes.". */ static NamespaceString makeListIndexesNSS(StringData dbName, StringData collectionName); /** * Note that these values are derived from the mmap_v1 implementation and that is the only * reason they are constrained as such. */ enum MaxNsLenValue { // Maximum possible length of name any namespace, including special ones like $extra. // This includes rum for the NUL byte so it can be used when sizing buffers. MaxNsLenWithNUL = 128, // MaxNsLenWithNUL excluding the NUL byte. Use this when comparing std::string lengths. MaxNsLen = MaxNsLenWithNUL - 1, // Maximum allowed length of fully qualified namespace name of any real collection. // Does not include NUL so it can be directly compared to std::string lengths. MaxNsCollectionLen = MaxNsLen - 7 /*strlen(".$extra")*/, }; /** * NOTE: DollarInDbNameBehavior::allow is deprecated. * * Please use DollarInDbNameBehavior::disallow and check explicitly for any DB names that must * contain a $. */ enum class DollarInDbNameBehavior { Disallow, Allow, // Deprecated }; StringData db() const { return _dotIndex == std::string::npos ? StringData() : StringData(_ns.c_str(), _dotIndex); } StringData coll() const { return _dotIndex == std::string::npos ? StringData() : StringData(_ns.c_str() + _dotIndex + 1, _ns.size() - 1 - _dotIndex); } const std::string& ns() const { return _ns; } const std::string& toString() const { return ns(); } size_t size() const { return _ns.size(); } bool isEmpty() const { return _ns.empty(); } struct Hasher { size_t operator()(const NamespaceString& nss) const { return std::hash()(nss._ns); } }; // // The following methods assume isValid() is true for this NamespaceString. // bool isHealthlog() const { return isLocal() && coll() == "system.healthlog"; } bool isSystem() const { return coll().startsWith("system."); } bool isAdminDB() const { return db() == kAdminDb; } bool isLocal() const { return db() == kLocalDb; } bool isSystemDotIndexes() const { return coll() == "system.indexes"; } bool isSystemDotProfile() const { return coll() == "system.profile"; } bool isSystemDotViews() const { return coll() == kSystemDotViewsCollectionName; } bool isServerConfigurationCollection() const { return (db() == kAdminDb) && (coll() == "system.version"); } bool isConfigDB() const { return db() == kConfigDb; } bool isCommand() const { return coll() == "$cmd"; } bool isOplog() const { return oplog(_ns); } bool isSpecial() const { return special(_ns); } bool isOnInternalDb() const { if (db() == kAdminDb) return true; if (db() == kLocalDb) return true; if (db() == kConfigDb) return true; return false; } bool isNormal() const { return normal(_ns); } /** * Returns whether the NamespaceString references a special collection that cannot be used for * generic data storage. */ bool isVirtualized() const { return virtualized(_ns); } /** * Returns whether a namespace is replicated, based only on its string value. One notable * omission is that map reduce `tmp.mr` collections may or may not be replicated. Callers must * decide how to handle that case separately. */ bool isReplicated() const; /** * Returns true if cursors for this namespace are registered with the global cursor manager. */ bool isGloballyManagedNamespace() const { return coll().startsWith("$cmd."_sd); } bool isCollectionlessAggregateNS() const; bool isListCollectionsCursorNS() const; bool isListIndexesCursorNS() const; /** * Returns true if a client can modify this namespace even though it is under ".system." * For example .system.users is ok for regular clients to update. */ bool isLegalClientSystemNS() const; /** * Given a NamespaceString for which isGloballyManagedNamespace() returns true, returns the * namespace the command targets, or boost::none for commands like 'listCollections' which * do not target a collection. */ boost::optional getTargetNSForGloballyManagedNamespace() const; /** * Returns true if this namespace refers to a drop-pending collection. */ bool isDropPendingNamespace() const; /** * Returns the drop-pending namespace name for this namespace, provided the given optime. * * Example: * test.foo -> test.system.drop.it.foo * * Original collection name may be truncated so that the generated namespace length does not * exceed MaxNsCollectionLen. */ NamespaceString makeDropPendingNamespace(const repl::OpTime& opTime) const; /** * Returns the optime used to generate the drop-pending namespace. * Returns an error if this namespace is not drop-pending. */ StatusWith getDropPendingNamespaceOpTime() const; /** * Checks if this namespace is valid as a target namespace for a rename operation, given * the length of the longest index name in the source collection. */ Status checkLengthForRename(const std::string::size_type longestIndexNameLength) const; /** * Given a NamespaceString for which isListIndexesCursorNS() returns true, returns the * NamespaceString for the collection that the "listIndexes" targets. */ NamespaceString getTargetNSForListIndexes() const; /** * Returns true if the namespace is valid. Special namespaces for internal use are considered as * valid. */ bool isValid() const { return validDBName(db(), DollarInDbNameBehavior::Allow) && !coll().empty(); } /** * NamespaceString("foo.bar").getSisterNS("blah") returns "foo.blah". */ std::string getSisterNS(StringData local) const; std::string getSystemIndexesCollection() const { return db().toString() + ".system.indexes"; } NamespaceString getCommandNS() const { return {db(), "$cmd"}; } /** * @return true if ns is 'normal'. A "$" is used for namespaces holding index data, * which do not contain BSON objects in their records. ("oplog.$main" is the exception) */ static bool normal(StringData ns) { return !virtualized(ns); } /** * @return true if the ns is an oplog one, otherwise false. */ static bool oplog(StringData ns) { return ns.startsWith("local.oplog."); } static bool special(StringData ns) { return !normal(ns) || ns.substr(ns.find('.')).startsWith(".system."); } /** * Check if `ns` references a special collection that cannot be used for generic data storage. */ static bool virtualized(StringData ns) { return ns.find('$') != std::string::npos && ns != "local.oplog.$main"; } /** * samples: * good * foo * bar * foo-bar * bad: * foo bar * foo.bar * foo"bar * * @param db - a possible database name * @param DollarInDbNameBehavior - please do not change the default value. DB names that must * contain a $ should be checked explicitly. * @return if db is an allowed database name */ static bool validDBName(StringData db, DollarInDbNameBehavior behavior = DollarInDbNameBehavior::Disallow); /** * Takes a fully qualified namespace (ie dbname.collectionName), and returns true if * the collection name component of the namespace is valid. * samples: * good: * foo.bar * bad: * foo. * * @param ns - a full namespace (a.b) * @return if db.coll is an allowed collection name */ static bool validCollectionComponent(StringData ns); /** * Takes a collection name and returns true if it is a valid collection name. * samples: * good: * foo * system.indexes * bad: * $foo * @param coll - a collection name component of a namespace * @return if the input is a valid collection name */ static bool validCollectionName(StringData coll); // Relops among `NamespaceString`. friend bool operator==(const NamespaceString& a, const NamespaceString& b) { return a.ns() == b.ns(); } friend bool operator!=(const NamespaceString& a, const NamespaceString& b) { return a.ns() != b.ns(); } friend bool operator<(const NamespaceString& a, const NamespaceString& b) { return a.ns() < b.ns(); } friend bool operator>(const NamespaceString& a, const NamespaceString& b) { return a.ns() > b.ns(); } friend bool operator<=(const NamespaceString& a, const NamespaceString& b) { return a.ns() <= b.ns(); } friend bool operator>=(const NamespaceString& a, const NamespaceString& b) { return a.ns() >= b.ns(); } private: std::string _ns; size_t _dotIndex; }; /** * This class is intented to be used by commands which can accept either a collection name or * database + collection UUID. It will never be initialized with both. */ class NamespaceStringOrUUID { public: NamespaceStringOrUUID(NamespaceString nss) : _nss(std::move(nss)) {} NamespaceStringOrUUID(std::string dbname, UUID uuid) : _uuid(std::move(uuid)), _dbname(std::move(dbname)) {} const boost::optional& nss() const { return _nss; } const boost::optional& uuid() const { return _uuid; } const std::string& dbname() const { return _dbname; } std::string toString() const; private: // At any given time exactly one of these optionals will be initialized boost::optional _nss; boost::optional _uuid; // Empty string when '_nss' is non-none, and contains the database name when '_uuid' is // non-none. Although the UUID specifies a collection uniquely, we must later verify that the // collection belongs to the database named here. std::string _dbname; }; std::ostream& operator<<(std::ostream& stream, const NamespaceString& nss); std::ostream& operator<<(std::ostream& stream, const NamespaceStringOrUUID& nsOrUUID); /** * "database.a.b.c" -> "database" */ inline StringData nsToDatabaseSubstring(StringData ns) { size_t i = ns.find('.'); if (i == std::string::npos) { massert(10078, "nsToDatabase: db too long", ns.size() < MaxDatabaseNameLen); return ns; } massert(10088, "nsToDatabase: db too long", i < static_cast(MaxDatabaseNameLen)); return ns.substr(0, i); } /** * "database.a.b.c" -> "database" * * TODO: make this return a StringData */ inline std::string nsToDatabase(StringData ns) { return nsToDatabaseSubstring(ns).toString(); } /** * "database.a.b.c" -> "a.b.c" */ inline StringData nsToCollectionSubstring(StringData ns) { size_t i = ns.find('.'); massert(16886, "nsToCollectionSubstring: no .", i != std::string::npos); return ns.substr(i + 1); } /** * foo = false * foo. = false * foo.a = true */ inline bool nsIsFull(StringData ns) { size_t i = ns.find('.'); if (i == std::string::npos) return false; if (i == ns.size() - 1) return false; return true; } /** * foo = true * foo. = false * foo.a = false */ inline bool nsIsDbOnly(StringData ns) { size_t i = ns.find('.'); if (i == std::string::npos) return true; return false; } inline bool NamespaceString::validDBName(StringData db, DollarInDbNameBehavior behavior) { if (db.size() == 0 || db.size() >= 64) return false; for (StringData::const_iterator iter = db.begin(), end = db.end(); iter != end; ++iter) { switch (*iter) { case '\0': case '/': case '\\': case '.': case ' ': case '"': return false; case '$': if (behavior == DollarInDbNameBehavior::Disallow) return false; continue; #ifdef _WIN32 // We prohibit all FAT32-disallowed characters on Windows case '*': case '<': case '>': case ':': case '|': case '?': return false; #endif default: continue; } } return true; } inline bool NamespaceString::validCollectionComponent(StringData ns) { size_t idx = ns.find('.'); if (idx == std::string::npos) return false; return validCollectionName(ns.substr(idx + 1)) || oplog(ns); } inline bool NamespaceString::validCollectionName(StringData coll) { if (coll.empty()) return false; if (coll[0] == '.') return false; for (StringData::const_iterator iter = coll.begin(), end = coll.end(); iter != end; ++iter) { switch (*iter) { case '\0': case '$': return false; default: continue; } } return true; } } // namespace mongo MONGO_HASH_NAMESPACE_START template <> struct hash { size_t operator()(const mongo::NamespaceString& nss) const { mongo::NamespaceString::Hasher hasher; return hasher(nss); } }; MONGO_HASH_NAMESPACE_END