From: Carlos Maiolino <cmaiolino@redhat.com>
To: linux-fsdevel@vger.kernel.org
Cc: hch@lst.de, adilger@dilger.ca, jaegeuk@kernel.org,
darrick.wong@oracle.com, miklos@szeredi.hu, rpeterso@redhat.com,
linux-xfs@vger.kernel.org
Subject: [PATCH 8/9] Use FIEMAP for FIBMAP calls
Date: Thu, 8 Aug 2019 10:27:43 +0200 [thread overview]
Message-ID: <20190808082744.31405-9-cmaiolino@redhat.com> (raw)
Message-ID: <20190808082743.iRuhDmeIBq5iLryGlXnosuxT-xza3nugLijSZMkhX80@z> (raw)
In-Reply-To: <20190808082744.31405-1-cmaiolino@redhat.com>
Enables the usage of FIEMAP ioctl infrastructure to handle FIBMAP calls.
From now on, ->bmap() methods can start to be removed from filesystems
which already provides ->fiemap().
This adds a new helper - bmap_fiemap() - which is used to fill in the
fiemap request, call into the underlying filesystem and check the flags
set in the extent requested.
Add a new fiemap fill extent callback to handle the in-kernel only
fiemap_extent structure used for FIBMAP.
The new FIEMAP_KERNEL_FIBMAP flag, is used to tell the filesystem
->fiemap interface, that the call is coming from ioctl_fibmap. The
addition of this new flag, requires an update to fiemap_check_flags(),
so it doesn't treat FIBMAP requests as invalid.
Signed-off-by: Carlos Maiolino <cmaiolino@redhat.com>
---
Changelog:
V5:
- Properly rebase against 5.3
- Fix xfs coding style
- Use xfs_is_cow_inode() check in xfs_vn_fiemap.
- Fix small conflict due indentation update in xfs_vn_fiemap
V4:
- Fix if conditional in bmap()
- Add filesystem-specific modifications
V3:
- Add FIEMAP_EXTENT_SHARED to the list of invalid extents in
bmap_fiemap()
- Rename fi_extents_start to fi_cb_data
- Use if conditional instead of ternary operator
- Make fiemap_fill_* callbacks static (which required the
removal of some macros
- Set FIEMAP_FLAG_SYNC when calling in ->fiemap method from
fibmap
- Add FIEMAP_KERNEL_FIBMAP flag, to identify the usage of fiemap
infrastructure for fibmap calls, defined in fs.h so it's not
exported to userspace.
- Update fiemap_check_flags() to understand FIEMAP_KERNEL_FIBMAP
- Update filesystems supporting both FIBMAP and FIEMAP, which
need extra checks on FIBMAP calls
V2:
- Now based on the updated fiemap_extent_info,
- move the fiemap call itself to a new helper
fs/ext4/extents.c | 7 +++-
fs/f2fs/data.c | 10 +++++-
fs/gfs2/inode.c | 6 +++-
fs/inode.c | 81 +++++++++++++++++++++++++++++++++++++++++--
fs/ioctl.c | 40 ++++++++++++++-------
fs/iomap/fiemap.c | 2 +-
fs/ocfs2/extent_map.c | 8 ++++-
fs/xfs/xfs_iops.c | 6 ++++
include/linux/fs.h | 4 +++
9 files changed, 145 insertions(+), 19 deletions(-)
diff --git a/fs/ext4/extents.c b/fs/ext4/extents.c
index 300339d1cd62..06160279a842 100644
--- a/fs/ext4/extents.c
+++ b/fs/ext4/extents.c
@@ -5018,7 +5018,9 @@ static int ext4_find_delayed_extent(struct inode *inode,
return next_del;
}
/* fiemap flags we can handle specified here */
-#define EXT4_FIEMAP_FLAGS (FIEMAP_FLAG_SYNC|FIEMAP_FLAG_XATTR)
+#define EXT4_FIEMAP_FLAGS (FIEMAP_FLAG_SYNC | \
+ FIEMAP_FLAG_XATTR| \
+ FIEMAP_KERNEL_FIBMAP)
static int ext4_xattr_fiemap(struct inode *inode,
struct fiemap_extent_info *fieinfo)
@@ -5065,6 +5067,9 @@ int ext4_fiemap(struct inode *inode, struct fiemap_extent_info *fieinfo)
if (ext4_has_inline_data(inode)) {
int has_inline = 1;
+ if (fieinfo->fi_flags & FIEMAP_KERNEL_FIBMAP)
+ return -EINVAL;
+
error = ext4_inline_data_fiemap(inode, fieinfo, &has_inline,
start, len);
diff --git a/fs/f2fs/data.c b/fs/f2fs/data.c
index 83e8c2d4a7a9..439863c84b34 100644
--- a/fs/f2fs/data.c
+++ b/fs/f2fs/data.c
@@ -1476,6 +1476,9 @@ static int f2fs_xattr_fiemap(struct inode *inode,
return (err < 0 ? err : 0);
}
+#define F2FS_FIEMAP_COMPAT (FIEMAP_FLAG_SYNC | \
+ FIEMAP_FLAG_XATTR| \
+ FIEMAP_KERNEL_FIBMAP)
int f2fs_fiemap(struct inode *inode, struct fiemap_extent_info *fieinfo)
{
u64 start = fieinfo->fi_start;
@@ -1493,7 +1496,7 @@ int f2fs_fiemap(struct inode *inode, struct fiemap_extent_info *fieinfo)
return ret;
}
- ret = fiemap_check_flags(fieinfo, FIEMAP_FLAG_SYNC | FIEMAP_FLAG_XATTR);
+ ret = fiemap_check_flags(fieinfo, F2FS_FIEMAP_COMPAT);
if (ret)
return ret;
@@ -1505,6 +1508,11 @@ int f2fs_fiemap(struct inode *inode, struct fiemap_extent_info *fieinfo)
}
if (f2fs_has_inline_data(inode)) {
+
+ ret = -EINVAL;
+ if (fieinfo->fi_flags & FIEMAP_KERNEL_FIBMAP)
+ goto out;
+
ret = f2fs_inline_data_fiemap(inode, fieinfo, start, len);
if (ret != -EAGAIN)
goto out;
diff --git a/fs/gfs2/inode.c b/fs/gfs2/inode.c
index dc192dfd8941..a8d1c0a29b45 100644
--- a/fs/gfs2/inode.c
+++ b/fs/gfs2/inode.c
@@ -2013,7 +2013,11 @@ static int gfs2_fiemap(struct inode *inode, struct fiemap_extent_info *fieinfo)
if (ret)
goto out;
- ret = iomap_fiemap(inode, fieinfo, &gfs2_iomap_ops);
+ if (gfs2_is_stuffed(ip) &&
+ (fieinfo->fi_flags & FIEMAP_KERNEL_FIBMAP))
+ ret = -EINVAL;
+ else
+ ret = iomap_fiemap(inode, fieinfo, &gfs2_iomap_ops);
gfs2_glock_dq_uninit(&gh);
diff --git a/fs/inode.c b/fs/inode.c
index 5dfe5e4bfa50..7a7a9f179639 100644
--- a/fs/inode.c
+++ b/fs/inode.c
@@ -1588,6 +1588,78 @@ void iput(struct inode *inode)
}
EXPORT_SYMBOL(iput);
+static int fiemap_fill_kernel_extent(struct fiemap_extent_info *fieinfo,
+ u64 logical, u64 phys, u64 len, u32 flags)
+{
+ struct fiemap_extent *extent = fieinfo->fi_cb_data;
+
+ /* only count the extents */
+ if (fieinfo->fi_cb_data == 0) {
+ fieinfo->fi_extents_mapped++;
+ goto out;
+ }
+
+ if (fieinfo->fi_extents_mapped >= fieinfo->fi_extents_max)
+ return 1;
+
+ if (flags & FIEMAP_EXTENT_DELALLOC)
+ flags |= FIEMAP_EXTENT_UNKNOWN;
+ if (flags & FIEMAP_EXTENT_DATA_ENCRYPTED)
+ flags |= FIEMAP_EXTENT_ENCODED;
+ if (flags & (FIEMAP_EXTENT_DATA_TAIL | FIEMAP_EXTENT_DATA_INLINE))
+ flags |= FIEMAP_EXTENT_NOT_ALIGNED;
+
+ extent->fe_logical = logical;
+ extent->fe_physical = phys;
+ extent->fe_length = len;
+ extent->fe_flags = flags;
+
+ fieinfo->fi_extents_mapped++;
+
+ if (fieinfo->fi_extents_mapped == fieinfo->fi_extents_max)
+ return 1;
+
+out:
+ if (flags & FIEMAP_EXTENT_LAST)
+ return 1;
+ return 0;
+}
+
+static int bmap_fiemap(struct inode *inode, sector_t *block)
+{
+ struct fiemap_extent_info fieinfo = { 0, };
+ struct fiemap_extent fextent;
+ u64 start = *block << inode->i_blkbits;
+ int error = -EINVAL;
+
+ fextent.fe_logical = 0;
+ fextent.fe_physical = 0;
+ fieinfo.fi_extents_max = 1;
+ fieinfo.fi_extents_mapped = 0;
+ fieinfo.fi_cb_data = &fextent;
+ fieinfo.fi_start = start;
+ fieinfo.fi_len = 1 << inode->i_blkbits;
+ fieinfo.fi_cb = fiemap_fill_kernel_extent;
+ fieinfo.fi_flags = (FIEMAP_KERNEL_FIBMAP | FIEMAP_FLAG_SYNC);
+
+ error = inode->i_op->fiemap(inode, &fieinfo);
+
+ if (error)
+ return error;
+
+ if (fieinfo.fi_flags & (FIEMAP_EXTENT_UNKNOWN |
+ FIEMAP_EXTENT_ENCODED |
+ FIEMAP_EXTENT_DATA_INLINE |
+ FIEMAP_EXTENT_UNWRITTEN |
+ FIEMAP_EXTENT_SHARED))
+ return -EINVAL;
+
+ *block = (fextent.fe_physical +
+ (start - fextent.fe_logical)) >> inode->i_blkbits;
+
+ return error;
+}
+
/**
* bmap - find a block number in a file
* @inode: inode owning the block number being requested
@@ -1604,10 +1676,15 @@ EXPORT_SYMBOL(iput);
*/
int bmap(struct inode *inode, sector_t *block)
{
- if (!inode->i_mapping->a_ops->bmap)
+ if (inode->i_op->fiemap)
+ return bmap_fiemap(inode, block);
+
+ if (inode->i_mapping->a_ops->bmap)
+ *block = inode->i_mapping->a_ops->bmap(inode->i_mapping,
+ *block);
+ else
return -EINVAL;
- *block = inode->i_mapping->a_ops->bmap(inode->i_mapping, *block);
return 0;
}
EXPORT_SYMBOL(bmap);
diff --git a/fs/ioctl.c b/fs/ioctl.c
index d72696c222de..0759ac6e4c7e 100644
--- a/fs/ioctl.c
+++ b/fs/ioctl.c
@@ -77,11 +77,8 @@ static int ioctl_fibmap(struct file *filp, int __user *p)
return error;
}
-#define SET_UNKNOWN_FLAGS (FIEMAP_EXTENT_DELALLOC)
-#define SET_NO_UNMOUNTED_IO_FLAGS (FIEMAP_EXTENT_DATA_ENCRYPTED)
-#define SET_NOT_ALIGNED_FLAGS (FIEMAP_EXTENT_DATA_TAIL|FIEMAP_EXTENT_DATA_INLINE)
-int fiemap_fill_user_extent(struct fiemap_extent_info *fieinfo, u64 logical,
- u64 phys, u64 len, u32 flags)
+static int fiemap_fill_user_extent(struct fiemap_extent_info *fieinfo,
+ u64 logical, u64 phys, u64 len, u32 flags)
{
struct fiemap_extent extent;
struct fiemap_extent __user *dest = fieinfo->fi_cb_data;
@@ -89,17 +86,17 @@ int fiemap_fill_user_extent(struct fiemap_extent_info *fieinfo, u64 logical,
/* only count the extents */
if (fieinfo->fi_extents_max == 0) {
fieinfo->fi_extents_mapped++;
- return (flags & FIEMAP_EXTENT_LAST) ? 1 : 0;
+ goto out;
}
if (fieinfo->fi_extents_mapped >= fieinfo->fi_extents_max)
return 1;
- if (flags & SET_UNKNOWN_FLAGS)
+ if (flags & FIEMAP_EXTENT_DELALLOC)
flags |= FIEMAP_EXTENT_UNKNOWN;
- if (flags & SET_NO_UNMOUNTED_IO_FLAGS)
+ if (flags & FIEMAP_EXTENT_DATA_ENCRYPTED)
flags |= FIEMAP_EXTENT_ENCODED;
- if (flags & SET_NOT_ALIGNED_FLAGS)
+ if (flags & (FIEMAP_EXTENT_DATA_TAIL | FIEMAP_EXTENT_DATA_INLINE))
flags |= FIEMAP_EXTENT_NOT_ALIGNED;
memset(&extent, 0, sizeof(extent));
@@ -115,7 +112,11 @@ int fiemap_fill_user_extent(struct fiemap_extent_info *fieinfo, u64 logical,
fieinfo->fi_extents_mapped++;
if (fieinfo->fi_extents_mapped == fieinfo->fi_extents_max)
return 1;
- return (flags & FIEMAP_EXTENT_LAST) ? 1 : 0;
+
+out:
+ if (flags & FIEMAP_EXTENT_LAST)
+ return 1;
+ return 0;
}
/**
@@ -151,13 +152,23 @@ EXPORT_SYMBOL(fiemap_fill_next_extent);
* flags, the invalid values will be written into the fieinfo structure, and
* -EBADR is returned, which tells ioctl_fiemap() to return those values to
* userspace. For this reason, a return code of -EBADR should be preserved.
+ * In case ->fiemap is being used for FIBMAP calls, and the filesystem does not
+ * support it, return -EINVAL.
*
- * Returns 0 on success, -EBADR on bad flags.
+ * Returns 0 on success, -EBADR on bad flags, -EINVAL for an unsupported FIBMAP
+ * request.
*/
int fiemap_check_flags(struct fiemap_extent_info *fieinfo, u32 fs_flags)
{
u32 incompat_flags;
+ if (fieinfo->fi_flags & FIEMAP_KERNEL_FIBMAP) {
+ if (fs_flags & FIEMAP_KERNEL_FIBMAP)
+ return 0;
+
+ return -EINVAL;
+ }
+
incompat_flags = fieinfo->fi_flags & ~(FIEMAP_FLAGS_COMPAT & fs_flags);
if (incompat_flags) {
fieinfo->fi_flags = incompat_flags;
@@ -208,6 +219,10 @@ static int ioctl_fiemap(struct file *filp, unsigned long arg)
if (fiemap.fm_extent_count > FIEMAP_MAX_EXTENTS)
return -EINVAL;
+ /* Userspace has no access to this flag */
+ if (fiemap.fm_flags & FIEMAP_KERNEL_FIBMAP)
+ return -EINVAL;
+
error = fiemap_check_ranges(sb, fiemap.fm_start, fiemap.fm_length,
&len);
if (error)
@@ -318,7 +333,8 @@ int __generic_block_fiemap(struct inode *inode,
bool past_eof = false, whole_file = false;
int ret = 0;
- ret = fiemap_check_flags(fieinfo, FIEMAP_FLAG_SYNC);
+ ret = fiemap_check_flags(fieinfo,
+ FIEMAP_FLAG_SYNC | FIEMAP_KERNEL_FIBMAP);
if (ret)
return ret;
diff --git a/fs/iomap/fiemap.c b/fs/iomap/fiemap.c
index 03f214f5df94..7116c451e67a 100644
--- a/fs/iomap/fiemap.c
+++ b/fs/iomap/fiemap.c
@@ -76,7 +76,7 @@ int iomap_fiemap(struct inode *inode, struct fiemap_extent_info *fi,
ctx.fi = fi;
ctx.prev.type = IOMAP_HOLE;
- ret = fiemap_check_flags(fi, FIEMAP_FLAG_SYNC);
+ ret = fiemap_check_flags(fi, FIEMAP_FLAG_SYNC | FIEMAP_KERNEL_FIBMAP);
if (ret)
return ret;
diff --git a/fs/ocfs2/extent_map.c b/fs/ocfs2/extent_map.c
index 1a5b6af62ee0..7d8ff6888c0d 100644
--- a/fs/ocfs2/extent_map.c
+++ b/fs/ocfs2/extent_map.c
@@ -734,7 +734,7 @@ static int ocfs2_fiemap_inline(struct inode *inode, struct buffer_head *di_bh,
return 0;
}
-#define OCFS2_FIEMAP_FLAGS (FIEMAP_FLAG_SYNC)
+#define OCFS2_FIEMAP_FLAGS (FIEMAP_FLAG_SYNC | FIEMAP_KERNEL_FIBMAP)
int ocfs2_fiemap(struct inode *inode, struct fiemap_extent_info *fieinfo)
{
@@ -743,6 +743,7 @@ int ocfs2_fiemap(struct inode *inode, struct fiemap_extent_info *fieinfo)
unsigned int hole_size;
struct ocfs2_super *osb = OCFS2_SB(inode->i_sb);
u64 len_bytes, phys_bytes, virt_bytes;
+
struct buffer_head *di_bh = NULL;
struct ocfs2_extent_rec rec;
u64 map_start = fieinfo->fi_start;
@@ -752,6 +753,11 @@ int ocfs2_fiemap(struct inode *inode, struct fiemap_extent_info *fieinfo)
if (ret)
return ret;
+ if (fieinfo->fi_flags & FIEMAP_KERNEL_FIBMAP) {
+ if (ocfs2_is_refcount_inode(inode))
+ return -EINVAL;
+ }
+
ret = ocfs2_inode_lock(inode, &di_bh, 0);
if (ret) {
mlog_errno(ret);
diff --git a/fs/xfs/xfs_iops.c b/fs/xfs/xfs_iops.c
index 514c4620a9e8..e43742fe101a 100644
--- a/fs/xfs/xfs_iops.c
+++ b/fs/xfs/xfs_iops.c
@@ -20,6 +20,7 @@
#include "xfs_symlink.h"
#include "xfs_dir2.h"
#include "xfs_iomap.h"
+#include "xfs_reflink.h"
#include <linux/xattr.h>
#include <linux/posix_acl.h>
@@ -1102,8 +1103,13 @@ xfs_vn_fiemap(
struct inode *inode,
struct fiemap_extent_info *fieinfo)
{
+ struct xfs_inode *ip = XFS_I(inode);
int error;
+ if (fieinfo->fi_flags & FIEMAP_KERNEL_FIBMAP)
+ if (xfs_is_cow_inode(ip) || XFS_IS_REALTIME_INODE(ip))
+ return -EINVAL;
+
xfs_ilock(XFS_I(inode), XFS_IOLOCK_SHARED);
if (fieinfo->fi_flags & FIEMAP_FLAG_XATTR) {
fieinfo->fi_flags &= ~FIEMAP_FLAG_XATTR;
diff --git a/include/linux/fs.h b/include/linux/fs.h
index 5f37f13be260..30aa65fc5ac4 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -1716,6 +1716,10 @@ extern bool may_open_dev(const struct path *path);
typedef int (*fiemap_fill_cb)(struct fiemap_extent_info *fieinfo, u64 logical,
u64 phys, u64 len, u32 flags);
+#define FIEMAP_KERNEL_FIBMAP 0x10000000 /* FIBMAP call through FIEMAP
+ interface. This is a kernel
+ only flag */
+
struct fiemap_extent_info {
unsigned int fi_flags; /* Flags as passed from user */
u64 fi_start; /* Logical offset at which
--
2.20.1
next prev parent reply other threads:[~2019-08-08 8:28 UTC|newest]
Thread overview: 48+ messages / expand[flat|nested] mbox.gz Atom feed top
2019-08-08 8:27 [PATCH 0/9 V5] New ->fiemap infrastructure and ->bmap removal Carlos Maiolino
2019-08-08 8:27 ` [PATCH 1/9] fs: Enable bmap() function to properly return errors Carlos Maiolino
2019-08-08 21:24 ` kbuild test robot
2019-08-14 11:14 ` Christoph Hellwig
2019-08-20 11:36 ` Carlos Maiolino
2019-08-08 8:27 ` [PATCH 2/9] cachefiles: drop direct usage of ->bmap method Carlos Maiolino
2019-08-14 11:15 ` Christoph Hellwig
2019-08-20 11:57 ` Carlos Maiolino
2019-08-20 12:50 ` David Howells
2019-08-29 7:13 ` Christoph Hellwig
2019-08-30 16:17 ` David Howells
2019-08-30 16:59 ` Christoph Hellwig
2019-08-31 0:45 ` David Howells
2019-09-05 22:44 ` Dave Chinner
2019-08-20 13:31 ` David Howells
2019-08-08 8:27 ` [PATCH 3/9] ecryptfs: drop direct calls to ->bmap Carlos Maiolino
2019-08-08 22:50 ` kbuild test robot
2019-08-08 8:27 ` [PATCH 4/9] fibmap: Use bmap instead of ->bmap method in ioctl_fibmap Carlos Maiolino
2019-08-08 20:38 ` kbuild test robot
2019-08-14 11:01 ` Carlos Maiolino
2019-08-14 11:08 ` Christoph Hellwig
2019-08-08 8:27 ` [PATCH 5/9] fs: Move start and length fiemap fields into fiemap_extent_info Carlos Maiolino
2019-08-08 20:21 ` kbuild test robot
2019-08-08 8:27 ` [PATCH 6/9] iomap: Remove length and start fields from iomap_fiemap Carlos Maiolino
2019-08-08 8:27 ` [PATCH 7/9] fiemap: Use a callback to fill fiemap extents Carlos Maiolino
2019-08-09 0:04 ` kbuild test robot
2019-08-14 11:16 ` Christoph Hellwig
2019-08-08 8:27 ` Carlos Maiolino [this message]
2019-08-08 8:27 ` [PATCH 8/9] Use FIEMAP for FIBMAP calls Carlos Maiolino
2019-08-09 1:56 ` kbuild test robot
2019-08-14 11:18 ` Christoph Hellwig
2019-08-20 13:01 ` Carlos Maiolino
2019-08-29 7:15 ` Christoph Hellwig
2019-09-10 12:28 ` Carlos Maiolino
2019-09-16 15:58 ` Darrick J. Wong
2019-08-08 8:27 ` [PATCH 9/9] xfs: Get rid of ->bmap Carlos Maiolino
-- strict thread matches above, loose matches on Subject: below --
2019-09-11 13:43 [PATCH 0/9 V6] New ->fiemap infrastructure and ->bmap removal Carlos Maiolino
2019-09-11 13:43 ` [PATCH 8/9] Use FIEMAP for FIBMAP calls Carlos Maiolino
2019-09-16 17:44 ` Darrick J. Wong
2019-07-31 14:12 [PATCH 0/9 V4] New ->fiemap infrastructure and ->bmap removal Carlos Maiolino
2019-07-31 14:12 ` [PATCH 8/9] Use FIEMAP for FIBMAP calls Carlos Maiolino
2019-07-31 14:12 ` Carlos Maiolino
2019-07-31 23:22 ` Darrick J. Wong
2019-07-31 23:31 ` Darrick J. Wong
2019-08-02 13:52 ` Carlos Maiolino
2019-08-06 5:41 ` Christoph Hellwig
2019-08-02 13:48 ` Carlos Maiolino
2019-08-02 15:29 ` Darrick J. Wong
2019-08-05 10:38 ` Carlos Maiolino
2019-08-06 5:46 ` Christoph Hellwig
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20190808082744.31405-9-cmaiolino@redhat.com \
--to=cmaiolino@redhat.com \
--cc=adilger@dilger.ca \
--cc=darrick.wong@oracle.com \
--cc=hch@lst.de \
--cc=jaegeuk@kernel.org \
--cc=linux-fsdevel@vger.kernel.org \
--cc=linux-xfs@vger.kernel.org \
--cc=miklos@szeredi.hu \
--cc=rpeterso@redhat.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).