summaryrefslogtreecommitdiffstats
path: root/fs/erofs/namei.c
diff options
context:
space:
mode:
authorGao Xiang <hsiangkao@aol.com>2019-08-22 17:36:59 -0400
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2019-08-24 08:20:10 -0400
commit47e4937a4a7ca4184fd282791dfee76c6799966a (patch)
treefc68338c13a00ac74ac9f1a838491bd3f7649c28 /fs/erofs/namei.c
parentf401441deda68326852560bf70d59e95f585bbb3 (diff)
erofs: move erofs out of staging
EROFS filesystem has been merged into linux-staging for a year. EROFS is designed to be a better solution of saving extra storage space with guaranteed end-to-end performance for read-only files with the help of reduced metadata, fixed-sized output compression and decompression inplace technologies. In the past year, EROFS was greatly improved by many people as a staging driver, self-tested, betaed by a large number of our internal users, successfully applied to almost all in-service HUAWEI smartphones as the part of EMUI 9.1 and proven to be stable enough to be moved out of staging. EROFS is a self-contained filesystem driver. Although there are still some TODOs to be more generic, we have a dedicated team actively keeping on working on EROFS in order to make it better with the evolution of Linux kernel as the other in-kernel filesystems. As Pavel suggested, it's better to do as one commit since git can do moves and all histories will be saved in this way. Let's promote it from staging and enhance it more actively as a "real" part of kernel for more wider scenarios! Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org> Cc: Alexander Viro <viro@zeniv.linux.org.uk> Cc: Andrew Morton <akpm@linux-foundation.org> Cc: Stephen Rothwell <sfr@canb.auug.org.au> Cc: Theodore Ts'o <tytso@mit.edu> Cc: Pavel Machek <pavel@denx.de> Cc: David Sterba <dsterba@suse.cz> Cc: Amir Goldstein <amir73il@gmail.com> Cc: Christoph Hellwig <hch@infradead.org> Cc: Darrick J . Wong <darrick.wong@oracle.com> Cc: Dave Chinner <david@fromorbit.com> Cc: Jaegeuk Kim <jaegeuk@kernel.org> Cc: Jan Kara <jack@suse.cz> Cc: Richard Weinberger <richard@nod.at> Cc: Linus Torvalds <torvalds@linux-foundation.org> Cc: Chao Yu <yuchao0@huawei.com> Cc: Miao Xie <miaoxie@huawei.com> Cc: Li Guifu <bluce.liguifu@huawei.com> Cc: Fang Wei <fangwei1@huawei.com> Signed-off-by: Gao Xiang <gaoxiang25@huawei.com> Link: https://lore.kernel.org/r/20190822213659.5501-1-hsiangkao@aol.com Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'fs/erofs/namei.c')
-rw-r--r--fs/erofs/namei.c251
1 files changed, 251 insertions, 0 deletions
diff --git a/fs/erofs/namei.c b/fs/erofs/namei.c
new file mode 100644
index 000000000000..8832b5d95d91
--- /dev/null
+++ b/fs/erofs/namei.c
@@ -0,0 +1,251 @@
1// SPDX-License-Identifier: GPL-2.0-only
2/*
3 * Copyright (C) 2017-2018 HUAWEI, Inc.
4 * http://www.huawei.com/
5 * Created by Gao Xiang <gaoxiang25@huawei.com>
6 */
7#include "xattr.h"
8
9#include <trace/events/erofs.h>
10
11struct erofs_qstr {
12 const unsigned char *name;
13 const unsigned char *end;
14};
15
16/* based on the end of qn is accurate and it must have the trailing '\0' */
17static inline int dirnamecmp(const struct erofs_qstr *qn,
18 const struct erofs_qstr *qd,
19 unsigned int *matched)
20{
21 unsigned int i = *matched;
22
23 /*
24 * on-disk error, let's only BUG_ON in the debugging mode.
25 * otherwise, it will return 1 to just skip the invalid name
26 * and go on (in consideration of the lookup performance).
27 */
28 DBG_BUGON(qd->name > qd->end);
29
30 /* qd could not have trailing '\0' */
31 /* However it is absolutely safe if < qd->end */
32 while (qd->name + i < qd->end && qd->name[i] != '\0') {
33 if (qn->name[i] != qd->name[i]) {
34 *matched = i;
35 return qn->name[i] > qd->name[i] ? 1 : -1;
36 }
37 ++i;
38 }
39 *matched = i;
40 /* See comments in __d_alloc on the terminating NUL character */
41 return qn->name[i] == '\0' ? 0 : 1;
42}
43
44#define nameoff_from_disk(off, sz) (le16_to_cpu(off) & ((sz) - 1))
45
46static struct erofs_dirent *find_target_dirent(struct erofs_qstr *name,
47 u8 *data,
48 unsigned int dirblksize,
49 const int ndirents)
50{
51 int head, back;
52 unsigned int startprfx, endprfx;
53 struct erofs_dirent *const de = (struct erofs_dirent *)data;
54
55 /* since the 1st dirent has been evaluated previously */
56 head = 1;
57 back = ndirents - 1;
58 startprfx = endprfx = 0;
59
60 while (head <= back) {
61 const int mid = head + (back - head) / 2;
62 const int nameoff = nameoff_from_disk(de[mid].nameoff,
63 dirblksize);
64 unsigned int matched = min(startprfx, endprfx);
65 struct erofs_qstr dname = {
66 .name = data + nameoff,
67 .end = unlikely(mid >= ndirents - 1) ?
68 data + dirblksize :
69 data + nameoff_from_disk(de[mid + 1].nameoff,
70 dirblksize)
71 };
72
73 /* string comparison without already matched prefix */
74 int ret = dirnamecmp(name, &dname, &matched);
75
76 if (unlikely(!ret)) {
77 return de + mid;
78 } else if (ret > 0) {
79 head = mid + 1;
80 startprfx = matched;
81 } else {
82 back = mid - 1;
83 endprfx = matched;
84 }
85 }
86
87 return ERR_PTR(-ENOENT);
88}
89
90static struct page *find_target_block_classic(struct inode *dir,
91 struct erofs_qstr *name,
92 int *_ndirents)
93{
94 unsigned int startprfx, endprfx;
95 int head, back;
96 struct address_space *const mapping = dir->i_mapping;
97 struct page *candidate = ERR_PTR(-ENOENT);
98
99 startprfx = endprfx = 0;
100 head = 0;
101 back = inode_datablocks(dir) - 1;
102
103 while (head <= back) {
104 const int mid = head + (back - head) / 2;
105 struct page *page = read_mapping_page(mapping, mid, NULL);
106
107 if (!IS_ERR(page)) {
108 struct erofs_dirent *de = kmap_atomic(page);
109 const int nameoff = nameoff_from_disk(de->nameoff,
110 EROFS_BLKSIZ);
111 const int ndirents = nameoff / sizeof(*de);
112 int diff;
113 unsigned int matched;
114 struct erofs_qstr dname;
115
116 if (unlikely(!ndirents)) {
117 kunmap_atomic(de);
118 put_page(page);
119 errln("corrupted dir block %d @ nid %llu",
120 mid, EROFS_V(dir)->nid);
121 DBG_BUGON(1);
122 page = ERR_PTR(-EFSCORRUPTED);
123 goto out;
124 }
125
126 matched = min(startprfx, endprfx);
127
128 dname.name = (u8 *)de + nameoff;
129 if (ndirents == 1)
130 dname.end = (u8 *)de + EROFS_BLKSIZ;
131 else
132 dname.end = (u8 *)de +
133 nameoff_from_disk(de[1].nameoff,
134 EROFS_BLKSIZ);
135
136 /* string comparison without already matched prefix */
137 diff = dirnamecmp(name, &dname, &matched);
138 kunmap_atomic(de);
139
140 if (unlikely(!diff)) {
141 *_ndirents = 0;
142 goto out;
143 } else if (diff > 0) {
144 head = mid + 1;
145 startprfx = matched;
146
147 if (!IS_ERR(candidate))
148 put_page(candidate);
149 candidate = page;
150 *_ndirents = ndirents;
151 } else {
152 put_page(page);
153
154 back = mid - 1;
155 endprfx = matched;
156 }
157 continue;
158 }
159out: /* free if the candidate is valid */
160 if (!IS_ERR(candidate))
161 put_page(candidate);
162 return page;
163 }
164 return candidate;
165}
166
167int erofs_namei(struct inode *dir,
168 struct qstr *name,
169 erofs_nid_t *nid, unsigned int *d_type)
170{
171 int ndirents;
172 struct page *page;
173 void *data;
174 struct erofs_dirent *de;
175 struct erofs_qstr qn;
176
177 if (unlikely(!dir->i_size))
178 return -ENOENT;
179
180 qn.name = name->name;
181 qn.end = name->name + name->len;
182
183 ndirents = 0;
184 page = find_target_block_classic(dir, &qn, &ndirents);
185
186 if (IS_ERR(page))
187 return PTR_ERR(page);
188
189 data = kmap_atomic(page);
190 /* the target page has been mapped */
191 if (ndirents)
192 de = find_target_dirent(&qn, data, EROFS_BLKSIZ, ndirents);
193 else
194 de = (struct erofs_dirent *)data;
195
196 if (!IS_ERR(de)) {
197 *nid = le64_to_cpu(de->nid);
198 *d_type = de->file_type;
199 }
200
201 kunmap_atomic(data);
202 put_page(page);
203
204 return PTR_ERR_OR_ZERO(de);
205}
206
207/* NOTE: i_mutex is already held by vfs */
208static struct dentry *erofs_lookup(struct inode *dir,
209 struct dentry *dentry,
210 unsigned int flags)
211{
212 int err;
213 erofs_nid_t nid;
214 unsigned int d_type;
215 struct inode *inode;
216
217 DBG_BUGON(!d_really_is_negative(dentry));
218 /* dentry must be unhashed in lookup, no need to worry about */
219 DBG_BUGON(!d_unhashed(dentry));
220
221 trace_erofs_lookup(dir, dentry, flags);
222
223 /* file name exceeds fs limit */
224 if (unlikely(dentry->d_name.len > EROFS_NAME_LEN))
225 return ERR_PTR(-ENAMETOOLONG);
226
227 /* false uninitialized warnings on gcc 4.8.x */
228 err = erofs_namei(dir, &dentry->d_name, &nid, &d_type);
229
230 if (err == -ENOENT) {
231 /* negative dentry */
232 inode = NULL;
233 } else if (unlikely(err)) {
234 inode = ERR_PTR(err);
235 } else {
236 debugln("%s, %s (nid %llu) found, d_type %u", __func__,
237 dentry->d_name.name, nid, d_type);
238 inode = erofs_iget(dir->i_sb, nid, d_type == FT_DIR);
239 }
240 return d_splice_alias(inode, dentry);
241}
242
243const struct inode_operations erofs_dir_iops = {
244 .lookup = erofs_lookup,
245 .getattr = erofs_getattr,
246#ifdef CONFIG_EROFS_FS_XATTR
247 .listxattr = erofs_listxattr,
248#endif
249 .get_acl = erofs_get_acl,
250};
251