From nobody Mon Apr 20 07:28:17 2026 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id D237DC433EF for ; Tue, 21 Jun 2022 11:45:17 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1350120AbiFULpQ (ORCPT ); Tue, 21 Jun 2022 07:45:16 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:57982 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S232156AbiFULpM (ORCPT ); Tue, 21 Jun 2022 07:45:12 -0400 Received: from relayaws-01.paragon-software.com (relayaws-01.paragon-software.com [35.157.23.187]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 7FF5C29CB8; Tue, 21 Jun 2022 04:45:10 -0700 (PDT) Received: from relayfre-01.paragon-software.com (unknown [172.30.72.12]) by relayaws-01.paragon-software.com (Postfix) with ESMTPS id A52751D4B; Tue, 21 Jun 2022 11:44:19 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=paragon-software.com; s=mail; t=1655811859; bh=7NS4O+ZSA8MS21mYwjsrNrmn+TQh2D/GYXNbxwpoTLQ=; h=Date:Subject:From:To:CC:References:In-Reply-To; b=U7940P36aC19+458PvCxUzYFY+kuPXBqBWx4fhwE4LoTBvQUVfjOXJlfq7BKpte+z T9CB1CtkLVQTCRDcIqJFF1UktfcCf+fiH4Ux1EAXAhrOisLY8HLwKQhFLtOmrp4V1Z Gqdaa6Sw3hNenEnYATWBwSF+Rwp4LusduyXqdkvU= Received: from dlg2.mail.paragon-software.com (vdlg-exch-02.paragon-software.com [172.30.1.105]) by relayfre-01.paragon-software.com (Postfix) with ESMTPS id 6B7A31FED; Tue, 21 Jun 2022 11:45:08 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=paragon-software.com; s=mail; t=1655811908; bh=7NS4O+ZSA8MS21mYwjsrNrmn+TQh2D/GYXNbxwpoTLQ=; h=Date:Subject:From:To:CC:References:In-Reply-To; b=ra7hTaJ18l8sz4GOzy9Yx2M8zSOzau4LynPQtpK3rfRBCLTOzvxvGA+LcPOc0ie34 KeRA0nLY9PlzF8Ys4regr0bzbUUa/DT2UUZ/8rwCghoL6QQw6LxRUTO13sMt9MD3ZK MwVfcVd+8O8MDIEfD7okXSQ6fOQ/XkZ63gsGNYTU= Received: from [172.30.8.65] (172.30.8.65) by vdlg-exch-02.paragon-software.com (172.30.1.105) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2375.7; Tue, 21 Jun 2022 14:45:08 +0300 Message-ID: <15ef7c0e-32d4-d3db-0b22-d5f0c1341894@paragon-software.com> Date: Tue, 21 Jun 2022 14:45:07 +0300 MIME-Version: 1.0 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:91.0) Gecko/20100101 Thunderbird/91.9.0 Subject: [PATCH 1/2] fs/ntfs3: Fallocate (FALLOC_FL_INSERT_RANGE) implementation Content-Language: en-US From: Konstantin Komarov To: CC: , References: In-Reply-To: Content-Transfer-Encoding: quoted-printable X-Originating-IP: [172.30.8.65] X-ClientProxiedBy: vdlg-exch-02.paragon-software.com (172.30.1.105) To vdlg-exch-02.paragon-software.com (172.30.1.105) Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Type: text/plain; charset="utf-8"; format="flowed" Add functions for inserting hole in file and inserting range in run. Signed-off-by: Konstantin Komarov --- fs/ntfs3/attrib.c | 176 +++++++++++++++++++++++++++++++++++++++++++++ fs/ntfs3/ntfs_fs.h | 4 +- fs/ntfs3/run.c | 43 +++++++++++ 3 files changed, 222 insertions(+), 1 deletion(-) diff --git a/fs/ntfs3/attrib.c b/fs/ntfs3/attrib.c index fc0623b029e6..86e688b95ad5 100644 --- a/fs/ntfs3/attrib.c +++ b/fs/ntfs3/attrib.c @@ -2081,3 +2081,179 @@ int attr_punch_hole(struct ntfs_inode *ni, u64 vbo,= u64 bytes, u32 *frame_size) =20 return err; } + +/* + * attr_insert_range - Insert range (hole) in file. + * Not for normal files. + */ +int attr_insert_range(struct ntfs_inode *ni, u64 vbo, u64 bytes) +{ + int err =3D 0; + struct runs_tree *run =3D &ni->file.run; + struct ntfs_sb_info *sbi =3D ni->mi.sbi; + struct ATTRIB *attr =3D NULL, *attr_b; + struct ATTR_LIST_ENTRY *le, *le_b; + struct mft_inode *mi, *mi_b; + CLST vcn, svcn, evcn1, len, next_svcn; + u64 data_size, alloc_size; + u32 mask; + __le16 a_flags; + + if (!bytes) + return 0; + + le_b =3D NULL; + attr_b =3D ni_find_attr(ni, NULL, &le_b, ATTR_DATA, NULL, 0, NULL, &mi_b); + if (!attr_b) + return -ENOENT; + + if (!is_attr_ext(attr_b)) { + /* It was checked above. See fallocate. */ + return -EOPNOTSUPP; + } + + if (!attr_b->non_res) { + data_size =3D le32_to_cpu(attr_b->res.data_size); + mask =3D sbi->cluster_mask; /* cluster_size - 1 */ + } else { + data_size =3D le64_to_cpu(attr_b->nres.data_size); + mask =3D (sbi->cluster_size << attr_b->nres.c_unit) - 1; + } + + if (vbo > data_size) { + /* Insert range after the file size is not allowed. */ + return -EINVAL; + } + + if ((vbo & mask) || (bytes & mask)) { + /* Allow to insert only frame aligned ranges. */ + return -EINVAL; + } + + vcn =3D vbo >> sbi->cluster_bits; + len =3D bytes >> sbi->cluster_bits; + + down_write(&ni->file.run_lock); + + if (!attr_b->non_res) { + err =3D attr_set_size(ni, ATTR_DATA, NULL, 0, run, + data_size + bytes, NULL, false, &attr); + if (err) + goto out; + if (!attr->non_res) { + /* Still resident. */ + char *data =3D Add2Ptr(attr, attr->res.data_off); + + memmove(data + bytes, data, bytes); + memset(data, 0, bytes); + err =3D 0; + goto out; + } + /* Resident files becomes nonresident. */ + le_b =3D NULL; + attr_b =3D ni_find_attr(ni, NULL, &le_b, ATTR_DATA, NULL, 0, NULL, + &mi_b); + if (!attr_b) + return -ENOENT; + if (!attr_b->non_res) { + err =3D -EINVAL; + goto out; + } + data_size =3D le64_to_cpu(attr_b->nres.data_size); + alloc_size =3D le64_to_cpu(attr_b->nres.alloc_size); + } + + /* + * Enumerate all attribute segments and shift start vcn. + */ + a_flags =3D attr_b->flags; + svcn =3D le64_to_cpu(attr_b->nres.svcn); + evcn1 =3D le64_to_cpu(attr_b->nres.evcn) + 1; + + if (svcn <=3D vcn && vcn < evcn1) { + attr =3D attr_b; + le =3D le_b; + mi =3D mi_b; + } else if (!le_b) { + err =3D -EINVAL; + goto out; + } else { + le =3D le_b; + attr =3D ni_find_attr(ni, attr_b, &le, ATTR_DATA, NULL, 0, &vcn, + &mi); + if (!attr) { + err =3D -EINVAL; + goto out; + } + + svcn =3D le64_to_cpu(attr->nres.svcn); + evcn1 =3D le64_to_cpu(attr->nres.evcn) + 1; + } + + run_truncate(run, 0); /* clear cached values. */ + err =3D attr_load_runs(attr, ni, run, NULL); + if (err) + goto out; + + if (!run_insert_range(run, vcn, len)) { + err =3D -ENOMEM; + goto out; + } + + /* Try to pack in current record as much as possible. */ + err =3D mi_pack_runs(mi, attr, run, evcn1 + len - svcn); + if (err) + goto out; + + next_svcn =3D le64_to_cpu(attr->nres.evcn) + 1; + run_truncate_head(run, next_svcn); + + while ((attr =3D ni_enum_attr_ex(ni, attr, &le, &mi)) && + attr->type =3D=3D ATTR_DATA && !attr->name_len) { + le64_add_cpu(&attr->nres.svcn, len); + le64_add_cpu(&attr->nres.evcn, len); + if (le) { + le->vcn =3D attr->nres.svcn; + ni->attr_list.dirty =3D true; + } + mi->dirty =3D true; + } + + /* + * Update primary attribute segment in advance. + * pointer attr_b may become invalid (layout of mft is changed) + */ + if (vbo <=3D ni->i_valid) + ni->i_valid +=3D bytes; + + attr_b->nres.data_size =3D le64_to_cpu(data_size + bytes); + attr_b->nres.alloc_size =3D le64_to_cpu(alloc_size + bytes); + + /* ni->valid may be not equal valid_size (temporary). */ + if (ni->i_valid > data_size + bytes) + attr_b->nres.valid_size =3D attr_b->nres.data_size; + else + attr_b->nres.valid_size =3D cpu_to_le64(ni->i_valid); + mi_b->dirty =3D true; + + if (next_svcn < evcn1 + len) { + err =3D ni_insert_nonresident(ni, ATTR_DATA, NULL, 0, run, + next_svcn, evcn1 + len - next_svcn, + a_flags, NULL, NULL); + if (err) + goto out; + } + + ni->vfs_inode.i_size +=3D bytes; + ni->ni_flags |=3D NI_FLAG_UPDATE_PARENT; + mark_inode_dirty(&ni->vfs_inode); + +out: + run_truncate(run, 0); /* clear cached values. */ + + up_write(&ni->file.run_lock); + if (err) + make_bad_inode(&ni->vfs_inode); + + return err; +} diff --git a/fs/ntfs3/ntfs_fs.h b/fs/ntfs3/ntfs_fs.h index fb825059d488..1f92e3a05f61 100644 --- a/fs/ntfs3/ntfs_fs.h +++ b/fs/ntfs3/ntfs_fs.h @@ -440,6 +440,7 @@ int attr_is_frame_compressed(struct ntfs_inode *ni, str= uct ATTRIB *attr, int attr_allocate_frame(struct ntfs_inode *ni, CLST frame, size_t compr_s= ize, u64 new_valid); int attr_collapse_range(struct ntfs_inode *ni, u64 vbo, u64 bytes); +int attr_insert_range(struct ntfs_inode *ni, u64 vbo, u64 bytes); int attr_punch_hole(struct ntfs_inode *ni, u64 vbo, u64 bytes, u32 *frame= _size); =20 /* Functions from attrlist.c */ @@ -775,10 +776,11 @@ bool run_lookup_entry(const struct runs_tree *run, CL= ST vcn, CLST *lcn, void run_truncate(struct runs_tree *run, CLST vcn); void run_truncate_head(struct runs_tree *run, CLST vcn); void run_truncate_around(struct runs_tree *run, CLST vcn); -bool run_lookup(const struct runs_tree *run, CLST vcn, size_t *Index); +bool run_lookup(const struct runs_tree *run, CLST vcn, size_t *index); bool run_add_entry(struct runs_tree *run, CLST vcn, CLST lcn, CLST len, bool is_mft); bool run_collapse_range(struct runs_tree *run, CLST vcn, CLST len); +bool run_insert_range(struct runs_tree *run, CLST vcn, CLST len); bool run_get_entry(const struct runs_tree *run, size_t index, CLST *vcn, CLST *lcn, CLST *len); bool run_is_mapped_full(const struct runs_tree *run, CLST svcn, CLST evcn= ); diff --git a/fs/ntfs3/run.c b/fs/ntfs3/run.c index a8fec651f973..7609d45a2d72 100644 --- a/fs/ntfs3/run.c +++ b/fs/ntfs3/run.c @@ -547,6 +547,49 @@ bool run_collapse_range(struct runs_tree *run, CLST vc= n, CLST len) return true; } =20 +/* run_insert_range + * + * Helper for attr_insert_range(), + * which is helper for fallocate(insert_range). + */ +bool run_insert_range(struct runs_tree *run, CLST vcn, CLST len) +{ + size_t index; + struct ntfs_run *r, *e; + + if (WARN_ON(!run_lookup(run, vcn, &index))) + return false; /* Should never be here. */ + + e =3D run->runs + run->count; + r =3D run->runs + index; + + r =3D run->runs + index; + if (vcn > r->vcn) + r +=3D 1; + + for (; r < e; r++) + r->vcn +=3D len; + + r =3D run->runs + index; + + if (vcn > r->vcn) { + /* split fragment. */ + CLST len1 =3D vcn - r->vcn; + CLST len2 =3D r->len - len1; + CLST lcn2 =3D r->lcn =3D=3D SPARSE_LCN ? SPARSE_LCN : (r->lcn + len1); + + r->len =3D len1; + + if (!run_add_entry(run, vcn + len, lcn2, len2, false)) + return false; + } + + if (!run_add_entry(run, vcn, SPARSE_LCN, len, false)) + return false; + + return true; +} + /* * run_get_entry - Return index-th mapped region. */ --=20 2.36.1 From nobody Mon Apr 20 07:28:17 2026 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 609C1C433EF for ; Tue, 21 Jun 2022 11:46:03 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1350043AbiFULqC (ORCPT ); Tue, 21 Jun 2022 07:46:02 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:58604 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229495AbiFULqA (ORCPT ); Tue, 21 Jun 2022 07:46:00 -0400 Received: from relayaws-01.paragon-software.com (relayaws-01.paragon-software.com [35.157.23.187]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id A38D12A40D; Tue, 21 Jun 2022 04:45:59 -0700 (PDT) Received: from dlg2.mail.paragon-software.com (vdlg-exch-02.paragon-software.com [172.30.1.105]) by relayaws-01.paragon-software.com (Postfix) with ESMTPS id 08A901D4B; Tue, 21 Jun 2022 11:45:09 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=paragon-software.com; s=mail; t=1655811909; bh=RXUlCLC0RWJq8YLAPCBfAJBKfeRdqiLy5wUHx9yv11k=; h=Date:Subject:From:To:CC:References:In-Reply-To; b=Yiegb2w+TBKoW1FBrs4NLYltTdPZDLhNyBZFUZ8icMMpWYqnsaJBN68VZQAYCmmOx UcW0bk59jeoTB8QZpLdOoE0pgM9inOva99gBZQUa898G/suBPbBAq7Zd3g0A5qXaUV XuYVMGGkUB/+73OXYgQxMrOiek8dkRu2yin/TTec= Received: from [172.30.8.65] (172.30.8.65) by vdlg-exch-02.paragon-software.com (172.30.1.105) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2375.7; Tue, 21 Jun 2022 14:45:57 +0300 Message-ID: <4fc73e0d-3987-1c2c-5ec7-6b3a94d18f63@paragon-software.com> Date: Tue, 21 Jun 2022 14:45:57 +0300 MIME-Version: 1.0 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:91.0) Gecko/20100101 Thunderbird/91.9.0 Subject: [PATCH 2/2] fs/ntfs3: Enable FALLOC_FL_INSERT_RANGE Content-Language: en-US From: Konstantin Komarov To: CC: , References: In-Reply-To: Content-Transfer-Encoding: quoted-printable X-Originating-IP: [172.30.8.65] X-ClientProxiedBy: vdlg-exch-02.paragon-software.com (172.30.1.105) To vdlg-exch-02.paragon-software.com (172.30.1.105) Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Type: text/plain; charset="utf-8"; format="flowed" Changed logic in ntfs_fallocate - more clear checks in beginning instead of the middle of function and added FALLOC_FL_INSERT_RANGE. Fixes xfstest generic/064 Fixes: 4342306f0f0d ("fs/ntfs3: Add file operations and implementation") Signed-off-by: Konstantin Komarov --- fs/ntfs3/file.c | 97 ++++++++++++++++++++++++++++--------------------- 1 file changed, 55 insertions(+), 42 deletions(-) diff --git a/fs/ntfs3/file.c b/fs/ntfs3/file.c index 27c32692513c..bdffe4b8554b 100644 --- a/fs/ntfs3/file.c +++ b/fs/ntfs3/file.c @@ -533,21 +533,35 @@ static int ntfs_truncate(struct inode *inode, loff_t = new_size) static long ntfs_fallocate(struct file *file, int mode, loff_t vbo, loff_= t len) { struct inode *inode =3D file->f_mapping->host; + struct address_space *mapping =3D inode->i_mapping; struct super_block *sb =3D inode->i_sb; struct ntfs_sb_info *sbi =3D sb->s_fs_info; struct ntfs_inode *ni =3D ntfs_i(inode); loff_t end =3D vbo + len; loff_t vbo_down =3D round_down(vbo, PAGE_SIZE); - loff_t i_size; + bool is_supported_holes =3D is_sparsed(ni) || is_compressed(ni); + loff_t i_size, new_size; + bool map_locked; int err; =20 /* No support for dir. */ if (!S_ISREG(inode->i_mode)) return -EOPNOTSUPP; =20 - /* Return error if mode is not supported. */ - if (mode & ~(FALLOC_FL_KEEP_SIZE | FALLOC_FL_PUNCH_HOLE | - FALLOC_FL_COLLAPSE_RANGE)) { + /* + * vfs_fallocate checks all possible combinations of mode. + * Do additional checks here before ntfs_set_state(dirty). + */ + if (mode & FALLOC_FL_PUNCH_HOLE) { + if (!is_supported_holes) + return -EOPNOTSUPP; + } else if (mode & FALLOC_FL_COLLAPSE_RANGE) { + } else if (mode & FALLOC_FL_INSERT_RANGE) { + if (!is_supported_holes) + return -EOPNOTSUPP; + } else if (mode & + ~(FALLOC_FL_KEEP_SIZE | FALLOC_FL_PUNCH_HOLE | + FALLOC_FL_COLLAPSE_RANGE | FALLOC_FL_INSERT_RANGE)) { ntfs_inode_warn(inode, "fallocate(0x%x) is not supported", mode); return -EOPNOTSUPP; @@ -557,6 +571,8 @@ static long ntfs_fallocate(struct file *file, int mode,= loff_t vbo, loff_t len) =20 inode_lock(inode); i_size =3D inode->i_size; + new_size =3D max(end, i_size); + map_locked =3D false; =20 if (WARN_ON(ni->ni_flags & NI_FLAG_COMPRESSED_MASK)) { /* Should never be here, see ntfs_file_open. */ @@ -564,38 +580,27 @@ static long ntfs_fallocate(struct file *file, int mod= e, loff_t vbo, loff_t len) goto out; } =20 + if (mode & (FALLOC_FL_PUNCH_HOLE | FALLOC_FL_COLLAPSE_RANGE | + FALLOC_FL_INSERT_RANGE)) { + inode_dio_wait(inode); + filemap_invalidate_lock(mapping); + map_locked =3D true; + } + if (mode & FALLOC_FL_PUNCH_HOLE) { u32 frame_size; loff_t mask, vbo_a, end_a, tmp; =20 - if (!(mode & FALLOC_FL_KEEP_SIZE)) { - err =3D -EINVAL; - goto out; - } - - err =3D filemap_write_and_wait_range(inode->i_mapping, vbo, - end - 1); + err =3D filemap_write_and_wait_range(mapping, vbo, end - 1); if (err) goto out; =20 - err =3D filemap_write_and_wait_range(inode->i_mapping, end, - LLONG_MAX); + err =3D filemap_write_and_wait_range(mapping, end, LLONG_MAX); if (err) goto out; =20 - inode_dio_wait(inode); - truncate_pagecache(inode, vbo_down); =20 - if (!is_sparsed(ni) && !is_compressed(ni)) { - /* - * Normal file, can't make hole. - * TODO: Try to find way to save info about hole. - */ - err =3D -EOPNOTSUPP; - goto out; - } - ni_lock(ni); err =3D attr_punch_hole(ni, vbo, len, &frame_size); ni_unlock(ni); @@ -627,17 +632,11 @@ static long ntfs_fallocate(struct file *file, int mod= e, loff_t vbo, loff_t len) ni_unlock(ni); } } else if (mode & FALLOC_FL_COLLAPSE_RANGE) { - if (mode & ~FALLOC_FL_COLLAPSE_RANGE) { - err =3D -EINVAL; - goto out; - } - /* * Write tail of the last page before removed range since * it will get removed from the page cache below. */ - err =3D filemap_write_and_wait_range(inode->i_mapping, vbo_down, - vbo); + err =3D filemap_write_and_wait_range(mapping, vbo_down, vbo); if (err) goto out; =20 @@ -645,34 +644,45 @@ static long ntfs_fallocate(struct file *file, int mod= e, loff_t vbo, loff_t len) * Write data that will be shifted to preserve them * when discarding page cache below. */ - err =3D filemap_write_and_wait_range(inode->i_mapping, end, - LLONG_MAX); + err =3D filemap_write_and_wait_range(mapping, end, LLONG_MAX); if (err) goto out; =20 - /* Wait for existing dio to complete. */ - inode_dio_wait(inode); - truncate_pagecache(inode, vbo_down); =20 ni_lock(ni); err =3D attr_collapse_range(ni, vbo, len); ni_unlock(ni); - } else { - /* - * Normal file: Allocate clusters, do not change 'valid' size. - */ - loff_t new_size =3D max(end, i_size); + } else if (mode & FALLOC_FL_INSERT_RANGE) { + /* Check new size. */ + err =3D inode_newsize_ok(inode, new_size); + if (err) + goto out; + + /* Write out all dirty pages. */ + err =3D filemap_write_and_wait_range(mapping, vbo_down, + LLONG_MAX); + if (err) + goto out; + truncate_pagecache(inode, vbo_down); =20 + ni_lock(ni); + err =3D attr_insert_range(ni, vbo, len); + ni_unlock(ni); + } else { + /* Check new size. */ err =3D inode_newsize_ok(inode, new_size); if (err) goto out; =20 + /* + * Allocate clusters, do not change 'valid' size. + */ err =3D ntfs_set_size(inode, new_size); if (err) goto out; =20 - if (is_sparsed(ni) || is_compressed(ni)) { + if (is_supported_holes) { CLST vcn_v =3D ni->i_valid >> sbi->cluster_bits; CLST vcn =3D vbo >> sbi->cluster_bits; CLST cend =3D bytes_to_cluster(sbi, end); @@ -720,6 +730,9 @@ static long ntfs_fallocate(struct file *file, int mode,= loff_t vbo, loff_t len) } =20 out: + if (map_locked) + filemap_invalidate_unlock(mapping); + if (err =3D=3D -EFBIG) err =3D -ENOSPC; =20 --=20 2.36.1