diff options
author | Gabriel Krisman Bertazi <krisman@collabora.com> | 2019-06-19 23:45:09 -0400 |
---|---|---|
committer | Theodore Ts'o <tytso@mit.edu> | 2019-06-19 23:45:09 -0400 |
commit | 3ae72562ad917df36a1b1247d749240e3b4865db (patch) | |
tree | c458ce5abc17452b19e2ecc06ce7d46841d5ec63 | |
parent | b03755ad6f33b7b8cd7312a3596a2dbf496de6e7 (diff) |
ext4: optimize case-insensitive lookups
Temporarily cache a casefolded version of the file name under lookup in
ext4_filename, to avoid repeatedly casefolding it. I got up to 30%
speedup on lookups of large directories (>100k entries), depending on
the length of the string under lookup.
Signed-off-by: Gabriel Krisman Bertazi <krisman@collabora.com>
Signed-off-by: Theodore Ts'o <tytso@mit.edu>
-rw-r--r-- | fs/ext4/dir.c | 2 | ||||
-rw-r--r-- | fs/ext4/ext4.h | 39 | ||||
-rw-r--r-- | fs/ext4/namei.c | 43 | ||||
-rw-r--r-- | fs/unicode/utf8-core.c | 28 | ||||
-rw-r--r-- | include/linux/unicode.h | 3 |
5 files changed, 106 insertions, 9 deletions
diff --git a/fs/ext4/dir.c b/fs/ext4/dir.c index 1f7784bee42a..770a1e6d4672 100644 --- a/fs/ext4/dir.c +++ b/fs/ext4/dir.c | |||
@@ -677,7 +677,7 @@ static int ext4_d_compare(const struct dentry *dentry, unsigned int len, | |||
677 | return memcmp(str, name->name, len); | 677 | return memcmp(str, name->name, len); |
678 | } | 678 | } |
679 | 679 | ||
680 | return ext4_ci_compare(dentry->d_parent->d_inode, name, &qstr); | 680 | return ext4_ci_compare(dentry->d_parent->d_inode, name, &qstr, false); |
681 | } | 681 | } |
682 | 682 | ||
683 | static int ext4_d_hash(const struct dentry *dentry, struct qstr *str) | 683 | static int ext4_d_hash(const struct dentry *dentry, struct qstr *str) |
diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h index ceb74093e138..7215a2a2a0de 100644 --- a/fs/ext4/ext4.h +++ b/fs/ext4/ext4.h | |||
@@ -2078,6 +2078,9 @@ struct ext4_filename { | |||
2078 | #ifdef CONFIG_FS_ENCRYPTION | 2078 | #ifdef CONFIG_FS_ENCRYPTION |
2079 | struct fscrypt_str crypto_buf; | 2079 | struct fscrypt_str crypto_buf; |
2080 | #endif | 2080 | #endif |
2081 | #ifdef CONFIG_UNICODE | ||
2082 | struct fscrypt_str cf_name; | ||
2083 | #endif | ||
2081 | }; | 2084 | }; |
2082 | 2085 | ||
2083 | #define fname_name(p) ((p)->disk_name.name) | 2086 | #define fname_name(p) ((p)->disk_name.name) |
@@ -2303,6 +2306,12 @@ extern unsigned ext4_free_clusters_after_init(struct super_block *sb, | |||
2303 | struct ext4_group_desc *gdp); | 2306 | struct ext4_group_desc *gdp); |
2304 | ext4_fsblk_t ext4_inode_to_goal_block(struct inode *); | 2307 | ext4_fsblk_t ext4_inode_to_goal_block(struct inode *); |
2305 | 2308 | ||
2309 | #ifdef CONFIG_UNICODE | ||
2310 | extern void ext4_fname_setup_ci_filename(struct inode *dir, | ||
2311 | const struct qstr *iname, | ||
2312 | struct fscrypt_str *fname); | ||
2313 | #endif | ||
2314 | |||
2306 | #ifdef CONFIG_FS_ENCRYPTION | 2315 | #ifdef CONFIG_FS_ENCRYPTION |
2307 | static inline void ext4_fname_from_fscrypt_name(struct ext4_filename *dst, | 2316 | static inline void ext4_fname_from_fscrypt_name(struct ext4_filename *dst, |
2308 | const struct fscrypt_name *src) | 2317 | const struct fscrypt_name *src) |
@@ -2329,6 +2338,10 @@ static inline int ext4_fname_setup_filename(struct inode *dir, | |||
2329 | return err; | 2338 | return err; |
2330 | 2339 | ||
2331 | ext4_fname_from_fscrypt_name(fname, &name); | 2340 | ext4_fname_from_fscrypt_name(fname, &name); |
2341 | |||
2342 | #ifdef CONFIG_UNICODE | ||
2343 | ext4_fname_setup_ci_filename(dir, iname, &fname->cf_name); | ||
2344 | #endif | ||
2332 | return 0; | 2345 | return 0; |
2333 | } | 2346 | } |
2334 | 2347 | ||
@@ -2344,6 +2357,10 @@ static inline int ext4_fname_prepare_lookup(struct inode *dir, | |||
2344 | return err; | 2357 | return err; |
2345 | 2358 | ||
2346 | ext4_fname_from_fscrypt_name(fname, &name); | 2359 | ext4_fname_from_fscrypt_name(fname, &name); |
2360 | |||
2361 | #ifdef CONFIG_UNICODE | ||
2362 | ext4_fname_setup_ci_filename(dir, &dentry->d_name, &fname->cf_name); | ||
2363 | #endif | ||
2347 | return 0; | 2364 | return 0; |
2348 | } | 2365 | } |
2349 | 2366 | ||
@@ -2357,6 +2374,11 @@ static inline void ext4_fname_free_filename(struct ext4_filename *fname) | |||
2357 | fname->crypto_buf.name = NULL; | 2374 | fname->crypto_buf.name = NULL; |
2358 | fname->usr_fname = NULL; | 2375 | fname->usr_fname = NULL; |
2359 | fname->disk_name.name = NULL; | 2376 | fname->disk_name.name = NULL; |
2377 | |||
2378 | #ifdef CONFIG_UNICODE | ||
2379 | kfree(fname->cf_name.name); | ||
2380 | fname->cf_name.name = NULL; | ||
2381 | #endif | ||
2360 | } | 2382 | } |
2361 | #else /* !CONFIG_FS_ENCRYPTION */ | 2383 | #else /* !CONFIG_FS_ENCRYPTION */ |
2362 | static inline int ext4_fname_setup_filename(struct inode *dir, | 2384 | static inline int ext4_fname_setup_filename(struct inode *dir, |
@@ -2367,6 +2389,11 @@ static inline int ext4_fname_setup_filename(struct inode *dir, | |||
2367 | fname->usr_fname = iname; | 2389 | fname->usr_fname = iname; |
2368 | fname->disk_name.name = (unsigned char *) iname->name; | 2390 | fname->disk_name.name = (unsigned char *) iname->name; |
2369 | fname->disk_name.len = iname->len; | 2391 | fname->disk_name.len = iname->len; |
2392 | |||
2393 | #ifdef CONFIG_UNICODE | ||
2394 | ext4_fname_setup_ci_filename(dir, iname, &fname->cf_name); | ||
2395 | #endif | ||
2396 | |||
2370 | return 0; | 2397 | return 0; |
2371 | } | 2398 | } |
2372 | 2399 | ||
@@ -2377,7 +2404,13 @@ static inline int ext4_fname_prepare_lookup(struct inode *dir, | |||
2377 | return ext4_fname_setup_filename(dir, &dentry->d_name, 1, fname); | 2404 | return ext4_fname_setup_filename(dir, &dentry->d_name, 1, fname); |
2378 | } | 2405 | } |
2379 | 2406 | ||
2380 | static inline void ext4_fname_free_filename(struct ext4_filename *fname) { } | 2407 | static inline void ext4_fname_free_filename(struct ext4_filename *fname) |
2408 | { | ||
2409 | #ifdef CONFIG_UNICODE | ||
2410 | kfree(fname->cf_name.name); | ||
2411 | fname->cf_name.name = NULL; | ||
2412 | #endif | ||
2413 | } | ||
2381 | #endif /* !CONFIG_FS_ENCRYPTION */ | 2414 | #endif /* !CONFIG_FS_ENCRYPTION */ |
2382 | 2415 | ||
2383 | /* dir.c */ | 2416 | /* dir.c */ |
@@ -3120,8 +3153,8 @@ extern int ext4_handle_dirty_dirent_node(handle_t *handle, | |||
3120 | struct inode *inode, | 3153 | struct inode *inode, |
3121 | struct buffer_head *bh); | 3154 | struct buffer_head *bh); |
3122 | extern int ext4_ci_compare(const struct inode *parent, | 3155 | extern int ext4_ci_compare(const struct inode *parent, |
3123 | const struct qstr *name, | 3156 | const struct qstr *fname, |
3124 | const struct qstr *entry); | 3157 | const struct qstr *entry, bool quick); |
3125 | 3158 | ||
3126 | #define S_SHIFT 12 | 3159 | #define S_SHIFT 12 |
3127 | static const unsigned char ext4_type_by_mode[(S_IFMT >> S_SHIFT) + 1] = { | 3160 | static const unsigned char ext4_type_by_mode[(S_IFMT >> S_SHIFT) + 1] = { |
diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c index cd01c4a67ffb..4909ced4e672 100644 --- a/fs/ext4/namei.c +++ b/fs/ext4/namei.c | |||
@@ -1259,19 +1259,24 @@ static void dx_insert_block(struct dx_frame *frame, u32 hash, ext4_lblk_t block) | |||
1259 | #ifdef CONFIG_UNICODE | 1259 | #ifdef CONFIG_UNICODE |
1260 | /* | 1260 | /* |
1261 | * Test whether a case-insensitive directory entry matches the filename | 1261 | * Test whether a case-insensitive directory entry matches the filename |
1262 | * being searched for. | 1262 | * being searched for. If quick is set, assume the name being looked up |
1263 | * is already in the casefolded form. | ||
1263 | * | 1264 | * |
1264 | * Returns: 0 if the directory entry matches, more than 0 if it | 1265 | * Returns: 0 if the directory entry matches, more than 0 if it |
1265 | * doesn't match or less than zero on error. | 1266 | * doesn't match or less than zero on error. |
1266 | */ | 1267 | */ |
1267 | int ext4_ci_compare(const struct inode *parent, const struct qstr *name, | 1268 | int ext4_ci_compare(const struct inode *parent, const struct qstr *name, |
1268 | const struct qstr *entry) | 1269 | const struct qstr *entry, bool quick) |
1269 | { | 1270 | { |
1270 | const struct ext4_sb_info *sbi = EXT4_SB(parent->i_sb); | 1271 | const struct ext4_sb_info *sbi = EXT4_SB(parent->i_sb); |
1271 | const struct unicode_map *um = sbi->s_encoding; | 1272 | const struct unicode_map *um = sbi->s_encoding; |
1272 | int ret; | 1273 | int ret; |
1273 | 1274 | ||
1274 | ret = utf8_strncasecmp(um, name, entry); | 1275 | if (quick) |
1276 | ret = utf8_strncasecmp_folded(um, name, entry); | ||
1277 | else | ||
1278 | ret = utf8_strncasecmp(um, name, entry); | ||
1279 | |||
1275 | if (ret < 0) { | 1280 | if (ret < 0) { |
1276 | /* Handle invalid character sequence as either an error | 1281 | /* Handle invalid character sequence as either an error |
1277 | * or as an opaque byte sequence. | 1282 | * or as an opaque byte sequence. |
@@ -1287,6 +1292,27 @@ int ext4_ci_compare(const struct inode *parent, const struct qstr *name, | |||
1287 | 1292 | ||
1288 | return ret; | 1293 | return ret; |
1289 | } | 1294 | } |
1295 | |||
1296 | void ext4_fname_setup_ci_filename(struct inode *dir, const struct qstr *iname, | ||
1297 | struct fscrypt_str *cf_name) | ||
1298 | { | ||
1299 | if (!IS_CASEFOLDED(dir)) { | ||
1300 | cf_name->name = NULL; | ||
1301 | return; | ||
1302 | } | ||
1303 | |||
1304 | cf_name->name = kmalloc(EXT4_NAME_LEN, GFP_NOFS); | ||
1305 | if (!cf_name->name) | ||
1306 | return; | ||
1307 | |||
1308 | cf_name->len = utf8_casefold(EXT4_SB(dir->i_sb)->s_encoding, | ||
1309 | iname, cf_name->name, | ||
1310 | EXT4_NAME_LEN); | ||
1311 | if (cf_name->len <= 0) { | ||
1312 | kfree(cf_name->name); | ||
1313 | cf_name->name = NULL; | ||
1314 | } | ||
1315 | } | ||
1290 | #endif | 1316 | #endif |
1291 | 1317 | ||
1292 | /* | 1318 | /* |
@@ -1313,8 +1339,15 @@ static inline bool ext4_match(const struct inode *parent, | |||
1313 | #endif | 1339 | #endif |
1314 | 1340 | ||
1315 | #ifdef CONFIG_UNICODE | 1341 | #ifdef CONFIG_UNICODE |
1316 | if (EXT4_SB(parent->i_sb)->s_encoding && IS_CASEFOLDED(parent)) | 1342 | if (EXT4_SB(parent->i_sb)->s_encoding && IS_CASEFOLDED(parent)) { |
1317 | return (ext4_ci_compare(parent, fname->usr_fname, &entry) == 0); | 1343 | if (fname->cf_name.name) { |
1344 | struct qstr cf = {.name = fname->cf_name.name, | ||
1345 | .len = fname->cf_name.len}; | ||
1346 | return !ext4_ci_compare(parent, &cf, &entry, true); | ||
1347 | } | ||
1348 | return !ext4_ci_compare(parent, fname->usr_fname, &entry, | ||
1349 | false); | ||
1350 | } | ||
1318 | #endif | 1351 | #endif |
1319 | 1352 | ||
1320 | return fscrypt_match_name(&f, de->name, de->name_len); | 1353 | return fscrypt_match_name(&f, de->name, de->name_len); |
diff --git a/fs/unicode/utf8-core.c b/fs/unicode/utf8-core.c index 6afab4fdce90..71ca4d047d65 100644 --- a/fs/unicode/utf8-core.c +++ b/fs/unicode/utf8-core.c | |||
@@ -73,6 +73,34 @@ int utf8_strncasecmp(const struct unicode_map *um, | |||
73 | } | 73 | } |
74 | EXPORT_SYMBOL(utf8_strncasecmp); | 74 | EXPORT_SYMBOL(utf8_strncasecmp); |
75 | 75 | ||
76 | /* String cf is expected to be a valid UTF-8 casefolded | ||
77 | * string. | ||
78 | */ | ||
79 | int utf8_strncasecmp_folded(const struct unicode_map *um, | ||
80 | const struct qstr *cf, | ||
81 | const struct qstr *s1) | ||
82 | { | ||
83 | const struct utf8data *data = utf8nfdicf(um->version); | ||
84 | struct utf8cursor cur1; | ||
85 | int c1, c2; | ||
86 | int i = 0; | ||
87 | |||
88 | if (utf8ncursor(&cur1, data, s1->name, s1->len) < 0) | ||
89 | return -EINVAL; | ||
90 | |||
91 | do { | ||
92 | c1 = utf8byte(&cur1); | ||
93 | c2 = cf->name[i++]; | ||
94 | if (c1 < 0) | ||
95 | return -EINVAL; | ||
96 | if (c1 != c2) | ||
97 | return 1; | ||
98 | } while (c1); | ||
99 | |||
100 | return 0; | ||
101 | } | ||
102 | EXPORT_SYMBOL(utf8_strncasecmp_folded); | ||
103 | |||
76 | int utf8_casefold(const struct unicode_map *um, const struct qstr *str, | 104 | int utf8_casefold(const struct unicode_map *um, const struct qstr *str, |
77 | unsigned char *dest, size_t dlen) | 105 | unsigned char *dest, size_t dlen) |
78 | { | 106 | { |
diff --git a/include/linux/unicode.h b/include/linux/unicode.h index aec2c6d800aa..990aa97d8049 100644 --- a/include/linux/unicode.h +++ b/include/linux/unicode.h | |||
@@ -17,6 +17,9 @@ int utf8_strncmp(const struct unicode_map *um, | |||
17 | 17 | ||
18 | int utf8_strncasecmp(const struct unicode_map *um, | 18 | int utf8_strncasecmp(const struct unicode_map *um, |
19 | const struct qstr *s1, const struct qstr *s2); | 19 | const struct qstr *s1, const struct qstr *s2); |
20 | int utf8_strncasecmp_folded(const struct unicode_map *um, | ||
21 | const struct qstr *cf, | ||
22 | const struct qstr *s1); | ||
20 | 23 | ||
21 | int utf8_normalize(const struct unicode_map *um, const struct qstr *str, | 24 | int utf8_normalize(const struct unicode_map *um, const struct qstr *str, |
22 | unsigned char *dest, size_t dlen); | 25 | unsigned char *dest, size_t dlen); |