From 4f3b35c157e43107cc7e1f1aa06694e8b22e10bb Mon Sep 17 00:00:00 2001 From: Al Viro Date: Wed, 1 Apr 2015 19:57:53 -0400 Subject: net/9p: switch the guts of p9_client_{read,write}() to iov_iter ... and have get_user_pages_fast() mapping fewer pages than requested to generate a short read/write. Signed-off-by: Al Viro --- include/net/9p/transport.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'include') diff --git a/include/net/9p/transport.h b/include/net/9p/transport.h index 2a25dec30211..5122b5e40f78 100644 --- a/include/net/9p/transport.h +++ b/include/net/9p/transport.h @@ -61,7 +61,7 @@ struct p9_trans_module { int (*cancel) (struct p9_client *, struct p9_req_t *req); int (*cancelled)(struct p9_client *, struct p9_req_t *req); int (*zc_request)(struct p9_client *, struct p9_req_t *, - char *, char *, int , int, int, int); + struct iov_iter *, struct iov_iter *, int , int, int); }; void v9fs_register_trans(struct p9_trans_module *m); -- cgit v1.2.3 From 070b3656cf228eaaef7b28b59264c5c7cdbdd0fb Mon Sep 17 00:00:00 2001 From: Al Viro Date: Wed, 1 Apr 2015 20:17:51 -0400 Subject: 9p: switch p9_client_write() to passing it struct iov_iter * ... and make it loop until it's done Signed-off-by: Al Viro --- fs/9p/vfs_file.c | 24 +++++------- fs/9p/xattr.c | 32 +++++----------- include/net/9p/client.h | 5 ++- net/9p/client.c | 98 +++++++++++++++++++++---------------------------- 4 files changed, 62 insertions(+), 97 deletions(-) (limited to 'include') diff --git a/fs/9p/vfs_file.c b/fs/9p/vfs_file.c index b40133796b87..75a2bb01420d 100644 --- a/fs/9p/vfs_file.c +++ b/fs/9p/vfs_file.c @@ -36,6 +36,7 @@ #include #include #include +#include #include #include @@ -457,24 +458,20 @@ v9fs_file_write_internal(struct inode *inode, struct p9_fid *fid, const char __user *data, size_t count, loff_t *offset, int invalidate) { - int n; - loff_t i_size; - size_t total = 0; loff_t origin = *offset; - unsigned long pg_start, pg_end; + struct iovec iov = {.iov_base = (void __user *)data, .iov_len = count}; + struct iov_iter from; + int total, err = 0; p9_debug(P9_DEBUG_VFS, "data %p count %d offset %x\n", data, (int)count, (int)*offset); - do { - n = p9_client_write(fid, NULL, data+total, origin+total, count); - if (n <= 0) - break; - count -= n; - total += n; - } while (count > 0); + iov_iter_init(&from, WRITE, &iov, 1, count); + total = p9_client_write(fid, origin, &from, &err); if (invalidate && (total > 0)) { + loff_t i_size; + unsigned long pg_start, pg_end; pg_start = origin >> PAGE_CACHE_SHIFT; pg_end = (origin + total - 1) >> PAGE_CACHE_SHIFT; if (inode->i_mapping && inode->i_mapping->nrpages) @@ -487,10 +484,7 @@ v9fs_file_write_internal(struct inode *inode, struct p9_fid *fid, i_size_write(inode, *offset); } } - if (n < 0) - return n; - - return total; + return total ? total : err; } /** diff --git a/fs/9p/xattr.c b/fs/9p/xattr.c index f95e01e058e4..d4cab07f668d 100644 --- a/fs/9p/xattr.c +++ b/fs/9p/xattr.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include @@ -120,8 +121,11 @@ int v9fs_xattr_set(struct dentry *dentry, const char *name, int v9fs_fid_xattr_set(struct p9_fid *fid, const char *name, const void *value, size_t value_len, int flags) { - u64 offset = 0; - int retval, msize, write_count; + struct kvec kvec = {.iov_base = (void *)value, .iov_len = value_len}; + struct iov_iter from; + int retval; + + iov_iter_kvec(&from, WRITE | ITER_KVEC, &kvec, 1, value_len); p9_debug(P9_DEBUG_VFS, "name = %s value_len = %zu flags = %d\n", name, value_len, flags); @@ -135,29 +139,11 @@ int v9fs_fid_xattr_set(struct p9_fid *fid, const char *name, * On success fid points to xattr */ retval = p9_client_xattrcreate(fid, name, value_len, flags); - if (retval < 0) { + if (retval < 0) p9_debug(P9_DEBUG_VFS, "p9_client_xattrcreate failed %d\n", retval); - goto err; - } - msize = fid->clnt->msize; - while (value_len) { - if (value_len > (msize - P9_IOHDRSZ)) - write_count = msize - P9_IOHDRSZ; - else - write_count = value_len; - write_count = p9_client_write(fid, ((char *)value)+offset, - NULL, offset, write_count); - if (write_count < 0) { - /* error in xattr write */ - retval = write_count; - goto err; - } - offset += write_count; - value_len -= write_count; - } - retval = 0; -err: + else + p9_client_write(fid, 0, &from, &retval); p9_client_clunk(fid); return retval; } diff --git a/include/net/9p/client.h b/include/net/9p/client.h index 6fab66c5c5af..63ae7653389d 100644 --- a/include/net/9p/client.h +++ b/include/net/9p/client.h @@ -211,6 +211,8 @@ struct p9_dirent { char d_name[256]; }; +struct iov_iter; + int p9_client_statfs(struct p9_fid *fid, struct p9_rstatfs *sb); int p9_client_rename(struct p9_fid *fid, struct p9_fid *newdirfid, const char *name); @@ -238,8 +240,7 @@ int p9_client_remove(struct p9_fid *fid); int p9_client_unlinkat(struct p9_fid *dfid, const char *name, int flags); int p9_client_read(struct p9_fid *fid, char *data, char __user *udata, u64 offset, u32 count); -int p9_client_write(struct p9_fid *fid, char *data, const char __user *udata, - u64 offset, u32 count); +int p9_client_write(struct p9_fid *fid, u64 offset, struct iov_iter *from, int *err); int p9_client_readdir(struct p9_fid *fid, char *data, u32 count, u64 offset); int p9dirent_read(struct p9_client *clnt, char *buf, int len, struct p9_dirent *dirent); diff --git a/net/9p/client.c b/net/9p/client.c index 9ef5d85f082f..aa38cfeb8615 100644 --- a/net/9p/client.c +++ b/net/9p/client.c @@ -1611,69 +1611,53 @@ error: EXPORT_SYMBOL(p9_client_read); int -p9_client_write(struct p9_fid *fid, char *data, const char __user *udata, - u64 offset, u32 count) +p9_client_write(struct p9_fid *fid, u64 offset, struct iov_iter *from, int *err) { - int err, rsize; - struct p9_client *clnt; + struct p9_client *clnt = fid->clnt; struct p9_req_t *req; - struct iov_iter from; - union { - struct kvec kv; - struct iovec iov; - } v; - - if (data) { - v.kv.iov_base = data; - v.kv.iov_len = count; - iov_iter_kvec(&from, ITER_KVEC | WRITE, &v.kv, 1, count); - } else { - v.iov.iov_base = udata; - v.iov.iov_len = count; - iov_iter_init(&from, WRITE, &v.iov, 1, count); - } - - p9_debug(P9_DEBUG_9P, ">>> TWRITE fid %d offset %llu count %d\n", - fid->fid, (unsigned long long) offset, count); - err = 0; - clnt = fid->clnt; - - rsize = fid->iounit; - if (!rsize || rsize > clnt->msize-P9_IOHDRSZ) - rsize = clnt->msize - P9_IOHDRSZ; + int total = 0; + + p9_debug(P9_DEBUG_9P, ">>> TWRITE fid %d offset %llu count %zd\n", + fid->fid, (unsigned long long) offset, + iov_iter_count(from)); + + while (iov_iter_count(from)) { + int count = iov_iter_count(from); + int rsize = fid->iounit; + if (!rsize || rsize > clnt->msize-P9_IOHDRSZ) + rsize = clnt->msize - P9_IOHDRSZ; + + if (count < rsize) + rsize = count; + + /* Don't bother zerocopy for small IO (< 1024) */ + if (clnt->trans_mod->zc_request && rsize > 1024) { + req = p9_client_zc_rpc(clnt, P9_TWRITE, NULL, from, 0, + rsize, P9_ZC_HDR_SZ, "dqd", + fid->fid, offset, rsize); + } else { + req = p9_client_rpc(clnt, P9_TWRITE, "dqV", fid->fid, + offset, rsize, from); + } + if (IS_ERR(req)) { + *err = PTR_ERR(req); + break; + } - if (count < rsize) - rsize = count; + *err = p9pdu_readf(req->rc, clnt->proto_version, "d", &count); + if (*err) { + trace_9p_protocol_dump(clnt, req->rc); + p9_free_req(clnt, req); + } - /* Don't bother zerocopy for small IO (< 1024) */ - if (clnt->trans_mod->zc_request && rsize > 1024) { - req = p9_client_zc_rpc(clnt, P9_TWRITE, NULL, &from, 0, rsize, - P9_ZC_HDR_SZ, "dqd", - fid->fid, offset, rsize); - } else { - req = p9_client_rpc(clnt, P9_TWRITE, "dqV", fid->fid, - offset, rsize, &from); - } - if (IS_ERR(req)) { - err = PTR_ERR(req); - goto error; - } + p9_debug(P9_DEBUG_9P, "<<< RWRITE count %d\n", count); - err = p9pdu_readf(req->rc, clnt->proto_version, "d", &count); - if (err) { - trace_9p_protocol_dump(clnt, req->rc); - goto free_and_error; + p9_free_req(clnt, req); + iov_iter_advance(from, count); + total += count; + offset += count; } - - p9_debug(P9_DEBUG_9P, "<<< RWRITE count %d\n", count); - - p9_free_req(clnt, req); - return count; - -free_and_error: - p9_free_req(clnt, req); -error: - return err; + return total; } EXPORT_SYMBOL(p9_client_write); -- cgit v1.2.3 From e1200fe68f20759f359698f8a8dc81d06d1265f5 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Wed, 1 Apr 2015 23:42:28 -0400 Subject: 9p: switch p9_client_read() to passing struct iov_iter * ... and make it loop Signed-off-by: Al Viro --- fs/9p/v9fs_vfs.h | 2 - fs/9p/vfs_addr.c | 20 ++++---- fs/9p/vfs_dir.c | 15 ++++-- fs/9p/vfs_file.c | 79 ++++-------------------------- fs/9p/xattr.c | 48 +++++++------------ include/net/9p/client.h | 3 +- net/9p/client.c | 124 ++++++++++++++++++++++++------------------------ 7 files changed, 108 insertions(+), 183 deletions(-) (limited to 'include') diff --git a/fs/9p/v9fs_vfs.h b/fs/9p/v9fs_vfs.h index cff1e61d9355..5a0db6dec8d1 100644 --- a/fs/9p/v9fs_vfs.h +++ b/fs/9p/v9fs_vfs.h @@ -68,8 +68,6 @@ int v9fs_file_open(struct inode *inode, struct file *file); void v9fs_inode2stat(struct inode *inode, struct p9_wstat *stat); int v9fs_uflags2omode(int uflags, int extended); -ssize_t v9fs_file_readn(struct file *, char *, char __user *, u32, u64); -ssize_t v9fs_fid_readn(struct p9_fid *, char *, char __user *, u32, u64); void v9fs_blank_wstat(struct p9_wstat *wstat); int v9fs_vfs_setattr_dotl(struct dentry *, struct iattr *); int v9fs_file_fsync_dotl(struct file *filp, loff_t start, loff_t end, diff --git a/fs/9p/vfs_addr.c b/fs/9p/vfs_addr.c index 402269c108cd..afe3225aaf36 100644 --- a/fs/9p/vfs_addr.c +++ b/fs/9p/vfs_addr.c @@ -51,12 +51,11 @@ */ static int v9fs_fid_readpage(struct p9_fid *fid, struct page *page) { - int retval; - loff_t offset; - char *buffer; - struct inode *inode; + struct inode *inode = page->mapping->host; + struct bio_vec bvec = {.bv_page = page, .bv_len = PAGE_SIZE}; + struct iov_iter to; + int retval, err; - inode = page->mapping->host; p9_debug(P9_DEBUG_VFS, "\n"); BUG_ON(!PageLocked(page)); @@ -65,16 +64,16 @@ static int v9fs_fid_readpage(struct p9_fid *fid, struct page *page) if (retval == 0) return retval; - buffer = kmap(page); - offset = page_offset(page); + iov_iter_bvec(&to, ITER_BVEC | READ, &bvec, 1, PAGE_SIZE); - retval = v9fs_fid_readn(fid, buffer, NULL, PAGE_CACHE_SIZE, offset); - if (retval < 0) { + retval = p9_client_read(fid, page_offset(page), &to, &err); + if (err) { v9fs_uncache_page(inode, page); + retval = err; goto done; } - memset(buffer + retval, 0, PAGE_CACHE_SIZE - retval); + zero_user(page, retval, PAGE_SIZE - retval); flush_dcache_page(page); SetPageUptodate(page); @@ -82,7 +81,6 @@ static int v9fs_fid_readpage(struct p9_fid *fid, struct page *page) retval = 0; done: - kunmap(page); unlock_page(page); return retval; } diff --git a/fs/9p/vfs_dir.c b/fs/9p/vfs_dir.c index 4f1151088ebe..76c3b1ab6361 100644 --- a/fs/9p/vfs_dir.c +++ b/fs/9p/vfs_dir.c @@ -33,6 +33,7 @@ #include #include #include +#include #include #include @@ -115,6 +116,7 @@ static int v9fs_dir_readdir(struct file *file, struct dir_context *ctx) int buflen; int reclen = 0; struct p9_rdir *rdir; + struct kvec kvec; p9_debug(P9_DEBUG_VFS, "name %pD\n", file); fid = file->private_data; @@ -124,16 +126,21 @@ static int v9fs_dir_readdir(struct file *file, struct dir_context *ctx) rdir = v9fs_alloc_rdir_buf(file, buflen); if (!rdir) return -ENOMEM; + kvec.iov_base = rdir->buf; + kvec.iov_len = buflen; while (1) { if (rdir->tail == rdir->head) { - err = v9fs_file_readn(file, rdir->buf, NULL, - buflen, ctx->pos); - if (err <= 0) + struct iov_iter to; + int n; + iov_iter_kvec(&to, READ | ITER_KVEC, &kvec, 1, buflen); + n = p9_client_read(file->private_data, ctx->pos, &to, + &err); + if (err) return err; rdir->head = 0; - rdir->tail = err; + rdir->tail = n; } while (rdir->head < rdir->tail) { p9stat_init(&st); diff --git a/fs/9p/vfs_file.c b/fs/9p/vfs_file.c index 1e4619693721..069f70d3758f 100644 --- a/fs/9p/vfs_file.c +++ b/fs/9p/vfs_file.c @@ -364,63 +364,6 @@ out_err: return ret; } -/** - * v9fs_fid_readn - read from a fid - * @fid: fid to read - * @data: data buffer to read data into - * @udata: user data buffer to read data into - * @count: size of buffer - * @offset: offset at which to read data - * - */ -ssize_t -v9fs_fid_readn(struct p9_fid *fid, char *data, char __user *udata, u32 count, - u64 offset) -{ - int n, total, size; - - p9_debug(P9_DEBUG_VFS, "fid %d offset %llu count %d\n", - fid->fid, (long long unsigned)offset, count); - n = 0; - total = 0; - size = fid->iounit ? fid->iounit : fid->clnt->msize - P9_IOHDRSZ; - do { - n = p9_client_read(fid, data, udata, offset, count); - if (n <= 0) - break; - - if (data) - data += n; - if (udata) - udata += n; - - offset += n; - count -= n; - total += n; - } while (count > 0 && n == size); - - if (n < 0) - total = n; - - return total; -} - -/** - * v9fs_file_readn - read from a file - * @filp: file pointer to read - * @data: data buffer to read data into - * @udata: user data buffer to read data into - * @count: size of buffer - * @offset: offset at which to read data - * - */ -ssize_t -v9fs_file_readn(struct file *filp, char *data, char __user *udata, u32 count, - u64 offset) -{ - return v9fs_fid_readn(filp->private_data, data, udata, count, offset); -} - /** * v9fs_file_read - read from a file * @filp: file pointer to read @@ -434,22 +377,20 @@ static ssize_t v9fs_file_read(struct file *filp, char __user *udata, size_t count, loff_t * offset) { - int ret; - struct p9_fid *fid; - size_t size; + struct p9_fid *fid = filp->private_data; + struct iovec iov = {.iov_base = udata, .iov_len = count}; + struct iov_iter to; + int ret, err; - p9_debug(P9_DEBUG_VFS, "count %zu offset %lld\n", count, *offset); - fid = filp->private_data; + iov_iter_init(&to, READ, &iov, 1, count); - size = fid->iounit ? fid->iounit : fid->clnt->msize - P9_IOHDRSZ; - if (count > size) - ret = v9fs_file_readn(filp, NULL, udata, count, *offset); - else - ret = p9_client_read(fid, NULL, udata, *offset, count); + p9_debug(P9_DEBUG_VFS, "count %zu offset %lld\n", count, *offset); - if (ret > 0) - *offset += ret; + ret = p9_client_read(fid, *offset, &to, &err); + if (!ret) + return err; + *offset += ret; return ret; } diff --git a/fs/9p/xattr.c b/fs/9p/xattr.c index d4cab07f668d..0cf44b6cccd6 100644 --- a/fs/9p/xattr.c +++ b/fs/9p/xattr.c @@ -26,50 +26,34 @@ ssize_t v9fs_fid_xattr_get(struct p9_fid *fid, const char *name, void *buffer, size_t buffer_size) { ssize_t retval; - int msize, read_count; - u64 offset = 0, attr_size; + u64 attr_size; struct p9_fid *attr_fid; + struct kvec kvec = {.iov_base = buffer, .iov_len = buffer_size}; + struct iov_iter to; + int err; + + iov_iter_kvec(&to, READ | ITER_KVEC, &kvec, 1, buffer_size); attr_fid = p9_client_xattrwalk(fid, name, &attr_size); if (IS_ERR(attr_fid)) { retval = PTR_ERR(attr_fid); p9_debug(P9_DEBUG_VFS, "p9_client_attrwalk failed %zd\n", retval); - attr_fid = NULL; - goto error; - } - if (!buffer_size) { - /* request to get the attr_size */ - retval = attr_size; - goto error; + return retval; } if (attr_size > buffer_size) { - retval = -ERANGE; - goto error; - } - msize = attr_fid->clnt->msize; - while (attr_size) { - if (attr_size > (msize - P9_IOHDRSZ)) - read_count = msize - P9_IOHDRSZ; + if (!buffer_size) /* request to get the attr_size */ + retval = attr_size; else - read_count = attr_size; - read_count = p9_client_read(attr_fid, ((char *)buffer)+offset, - NULL, offset, read_count); - if (read_count < 0) { - /* error in xattr read */ - retval = read_count; - goto error; - } - offset += read_count; - attr_size -= read_count; + retval = -ERANGE; + } else { + iov_iter_truncate(&to, attr_size); + retval = p9_client_read(attr_fid, 0, &to, &err); + if (err) + retval = err; } - /* Total read xattr bytes */ - retval = offset; -error: - if (attr_fid) - p9_client_clunk(attr_fid); + p9_client_clunk(attr_fid); return retval; - } diff --git a/include/net/9p/client.h b/include/net/9p/client.h index 63ae7653389d..c6b97e58cf84 100644 --- a/include/net/9p/client.h +++ b/include/net/9p/client.h @@ -238,8 +238,7 @@ int p9_client_clunk(struct p9_fid *fid); int p9_client_fsync(struct p9_fid *fid, int datasync); int p9_client_remove(struct p9_fid *fid); int p9_client_unlinkat(struct p9_fid *dfid, const char *name, int flags); -int p9_client_read(struct p9_fid *fid, char *data, char __user *udata, - u64 offset, u32 count); +int p9_client_read(struct p9_fid *fid, u64 offset, struct iov_iter *to, int *err); int p9_client_write(struct p9_fid *fid, u64 offset, struct iov_iter *from, int *err); int p9_client_readdir(struct p9_fid *fid, char *data, u32 count, u64 offset); int p9dirent_read(struct p9_client *clnt, char *buf, int len, diff --git a/net/9p/client.c b/net/9p/client.c index aa38cfeb8615..18583bb89db6 100644 --- a/net/9p/client.c +++ b/net/9p/client.c @@ -1534,79 +1534,77 @@ error: EXPORT_SYMBOL(p9_client_unlinkat); int -p9_client_read(struct p9_fid *fid, char *data, char __user *udata, u64 offset, - u32 count) +p9_client_read(struct p9_fid *fid, u64 offset, struct iov_iter *to, int *err) { - char *dataptr; + struct p9_client *clnt = fid->clnt; struct p9_req_t *req; - struct p9_client *clnt; - int err, rsize, non_zc = 0; - struct iov_iter to; - union { - struct kvec kv; - struct iovec iov; - } v; - - if (data) { - v.kv.iov_base = data; - v.kv.iov_len = count; - iov_iter_kvec(&to, ITER_KVEC | READ, &v.kv, 1, count); - } else { - v.iov.iov_base = udata; - v.iov.iov_len = count; - iov_iter_init(&to, READ, &v.iov, 1, count); - } + int total = 0; p9_debug(P9_DEBUG_9P, ">>> TREAD fid %d offset %llu %d\n", - fid->fid, (unsigned long long) offset, count); - err = 0; - clnt = fid->clnt; - - rsize = fid->iounit; - if (!rsize || rsize > clnt->msize-P9_IOHDRSZ) - rsize = clnt->msize - P9_IOHDRSZ; + fid->fid, (unsigned long long) offset, (int)iov_iter_count(to)); + + while (iov_iter_count(to)) { + int count = iov_iter_count(to); + int rsize, non_zc = 0; + char *dataptr; + + rsize = fid->iounit; + if (!rsize || rsize > clnt->msize-P9_IOHDRSZ) + rsize = clnt->msize - P9_IOHDRSZ; - if (count < rsize) - rsize = count; + if (count < rsize) + rsize = count; - /* Don't bother zerocopy for small IO (< 1024) */ - if (clnt->trans_mod->zc_request && rsize > 1024) { - /* - * response header len is 11 - * PDU Header(7) + IO Size (4) - */ - req = p9_client_zc_rpc(clnt, P9_TREAD, &to, NULL, rsize, 0, - 11, "dqd", fid->fid, - offset, rsize); - } else { - non_zc = 1; - req = p9_client_rpc(clnt, P9_TREAD, "dqd", fid->fid, offset, - rsize); - } - if (IS_ERR(req)) { - err = PTR_ERR(req); - goto error; - } + /* Don't bother zerocopy for small IO (< 1024) */ + if (clnt->trans_mod->zc_request && rsize > 1024) { + /* + * response header len is 11 + * PDU Header(7) + IO Size (4) + */ + req = p9_client_zc_rpc(clnt, P9_TREAD, to, NULL, rsize, + 0, 11, "dqd", fid->fid, + offset, rsize); + } else { + non_zc = 1; + req = p9_client_rpc(clnt, P9_TREAD, "dqd", fid->fid, offset, + rsize); + } + if (IS_ERR(req)) { + *err = PTR_ERR(req); + break; + } - err = p9pdu_readf(req->rc, clnt->proto_version, "D", &count, &dataptr); - if (err) { - trace_9p_protocol_dump(clnt, req->rc); - goto free_and_error; - } + *err = p9pdu_readf(req->rc, clnt->proto_version, + "D", &count, &dataptr); + if (*err) { + trace_9p_protocol_dump(clnt, req->rc); + p9_free_req(clnt, req); + break; + } - p9_debug(P9_DEBUG_9P, "<<< RREAD count %d\n", count); + p9_debug(P9_DEBUG_9P, "<<< RREAD count %d\n", count); + if (!count) { + p9_free_req(clnt, req); + break; + } - if (non_zc && copy_to_iter(dataptr, count, &to) != count) { - err = -EFAULT; - goto free_and_error; + if (non_zc) { + int n = copy_to_iter(dataptr, count, to); + total += n; + offset += n; + if (n != count) { + *err = -EFAULT; + p9_free_req(clnt, req); + break; + } + } else { + iov_iter_advance(to, count); + total += count; + offset += count; + } + p9_free_req(clnt, req); } - p9_free_req(clnt, req); - return count; - -free_and_error: - p9_free_req(clnt, req); -error: - return err; + return total; } EXPORT_SYMBOL(p9_client_read); -- cgit v1.2.3