From: Christopher Hoffman Date: Fri, 10 Oct 2025 14:47:08 +0000 (+0000) Subject: client: account for mixed quotas in statfs X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=8c05924f5430038e1c9b9cfe28252b4155fb5a5a;p=ceph.git client: account for mixed quotas in statfs In statfs, when the quota root for a dir is discovered, it uses that dir to base values for max_files and max_bytes. This can be an issue when a dir is found with only one of two potential quota fields. Take for instance, a dir with only max_files set and parent dir has only max_bytes set. During a statfs call, it will then use the max_files value for provided dir, but does not have a value for max_bytes. In this case, this behavior will cause the size of the filesystem to be displayed. Instead, find the quota root for max_files and max_bytes separately. This will allow for mixed quotas to inherit missing values from its parent. In the above example, max_files from current dir and max_bytes from parent dir will be displayed. Fixes: https://tracker.ceph.com/issues/73487 Signed-off-by: Christopher Hoffman (cherry picked from commit dd02ea9b18502b87ce815eba4286ae3516e334b3) --- diff --git a/src/client/Client.cc b/src/client/Client.cc index 56a07af9577..7c07e358e5f 100644 --- a/src/client/Client.cc +++ b/src/client/Client.cc @@ -12502,9 +12502,10 @@ int Client::_statfs(Inode *in, struct statvfs *stbuf, // Usually quota_root will == root_ancestor, but if the mount root has no // quota but we can see a parent of it that does have a quota, we'll // respect that one instead. - InodeRef quota_root = in->quota.is_enabled() ? in : get_quota_root(in, perms); + InodeRef qr_bytes = in->quota.is_enabled(QUOTA_MAX_BYTES) ? in : get_quota_root(in, perms, QUOTA_MAX_BYTES); + InodeRef qr_files = in->quota.is_enabled(QUOTA_MAX_FILES) ? in : get_quota_root(in, perms, QUOTA_MAX_FILES); - total_files_on_fs = quota_root->rstat.rfiles + quota_root->rstat.rsubdirs; + total_files_on_fs = qr_files->rstat.rfiles + qr_files->rstat.rsubdirs; if (rval < 0) { ldout(cct, 1) << "underlying call to statfs returned error: " @@ -12534,23 +12535,23 @@ int Client::_statfs(Inode *in, struct statvfs *stbuf, // get_quota_root should always give us something if client quotas are // enabled - ceph_assert(cct->_conf.get_val("client_quota") == false || quota_root != nullptr); + ceph_assert(cct->_conf.get_val("client_quota") == false || qr_bytes != nullptr); /* If bytes quota is set on a directory and conf option "client quota df" * is also set, available space = quota limit - used space. Else, * available space = total space - used space. */ - if (quota_root && cct->_conf->client_quota_df && quota_root->quota.max_bytes) { + if (qr_bytes && cct->_conf->client_quota_df && qr_bytes->quota.max_bytes) { // Skip the getattr if any sessions are stale, as we don't want to // block `df` if this client has e.g. been evicted, or if the MDS cluster // is unhealthy. if (!_any_stale_sessions()) { - int r = _getattr(quota_root, 0, perms, true); + int r = _getattr(qr_bytes, 0, perms, true); if (r != 0) { // Ignore return value: error getting latest inode metadata is not a good // reason to break "df". lderr(cct) << "Error in getattr on quota root 0x" - << std::hex << quota_root->ino << std::dec + << std::hex << qr_bytes->ino << std::dec << " statfs result may be outdated" << dendl; } } @@ -12558,8 +12559,8 @@ int Client::_statfs(Inode *in, struct statvfs *stbuf, // Special case: if there is a size quota set on the Inode acting // as the root for this client mount, then report the quota status // as the filesystem statistics. - const fsblkcnt_t total = quota_root->quota.max_bytes >> CEPH_BLOCK_SHIFT; - const fsblkcnt_t used = quota_root->rstat.rbytes >> CEPH_BLOCK_SHIFT; + const fsblkcnt_t total = qr_bytes->quota.max_bytes >> CEPH_BLOCK_SHIFT; + const fsblkcnt_t used = qr_bytes->rstat.rbytes >> CEPH_BLOCK_SHIFT; // It is possible for a quota to be exceeded: arithmetic here must // handle case where used > total. const fsblkcnt_t free = total > used ? total - used : 0;