18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Copyright (C) 2017-2018 HUAWEI, Inc. 48c2ecf20Sopenharmony_ci * https://www.huawei.com/ 58c2ecf20Sopenharmony_ci * Created by Gao Xiang <gaoxiang25@huawei.com> 68c2ecf20Sopenharmony_ci */ 78c2ecf20Sopenharmony_ci#include "xattr.h" 88c2ecf20Sopenharmony_ci 98c2ecf20Sopenharmony_ci#include <trace/events/erofs.h> 108c2ecf20Sopenharmony_ci 118c2ecf20Sopenharmony_cistruct erofs_qstr { 128c2ecf20Sopenharmony_ci const unsigned char *name; 138c2ecf20Sopenharmony_ci const unsigned char *end; 148c2ecf20Sopenharmony_ci}; 158c2ecf20Sopenharmony_ci 168c2ecf20Sopenharmony_ci/* based on the end of qn is accurate and it must have the trailing '\0' */ 178c2ecf20Sopenharmony_cistatic inline int erofs_dirnamecmp(const struct erofs_qstr *qn, 188c2ecf20Sopenharmony_ci const struct erofs_qstr *qd, 198c2ecf20Sopenharmony_ci unsigned int *matched) 208c2ecf20Sopenharmony_ci{ 218c2ecf20Sopenharmony_ci unsigned int i = *matched; 228c2ecf20Sopenharmony_ci 238c2ecf20Sopenharmony_ci /* 248c2ecf20Sopenharmony_ci * on-disk error, let's only BUG_ON in the debugging mode. 258c2ecf20Sopenharmony_ci * otherwise, it will return 1 to just skip the invalid name 268c2ecf20Sopenharmony_ci * and go on (in consideration of the lookup performance). 278c2ecf20Sopenharmony_ci */ 288c2ecf20Sopenharmony_ci DBG_BUGON(qd->name > qd->end); 298c2ecf20Sopenharmony_ci 308c2ecf20Sopenharmony_ci /* qd could not have trailing '\0' */ 318c2ecf20Sopenharmony_ci /* However it is absolutely safe if < qd->end */ 328c2ecf20Sopenharmony_ci while (qd->name + i < qd->end && qd->name[i] != '\0') { 338c2ecf20Sopenharmony_ci if (qn->name[i] != qd->name[i]) { 348c2ecf20Sopenharmony_ci *matched = i; 358c2ecf20Sopenharmony_ci return qn->name[i] > qd->name[i] ? 1 : -1; 368c2ecf20Sopenharmony_ci } 378c2ecf20Sopenharmony_ci ++i; 388c2ecf20Sopenharmony_ci } 398c2ecf20Sopenharmony_ci *matched = i; 408c2ecf20Sopenharmony_ci /* See comments in __d_alloc on the terminating NUL character */ 418c2ecf20Sopenharmony_ci return qn->name[i] == '\0' ? 0 : 1; 428c2ecf20Sopenharmony_ci} 438c2ecf20Sopenharmony_ci 448c2ecf20Sopenharmony_ci#define nameoff_from_disk(off, sz) (le16_to_cpu(off) & ((sz) - 1)) 458c2ecf20Sopenharmony_ci 468c2ecf20Sopenharmony_cistatic struct erofs_dirent *find_target_dirent(struct erofs_qstr *name, 478c2ecf20Sopenharmony_ci u8 *data, 488c2ecf20Sopenharmony_ci unsigned int dirblksize, 498c2ecf20Sopenharmony_ci const int ndirents) 508c2ecf20Sopenharmony_ci{ 518c2ecf20Sopenharmony_ci int head, back; 528c2ecf20Sopenharmony_ci unsigned int startprfx, endprfx; 538c2ecf20Sopenharmony_ci struct erofs_dirent *const de = (struct erofs_dirent *)data; 548c2ecf20Sopenharmony_ci 558c2ecf20Sopenharmony_ci /* since the 1st dirent has been evaluated previously */ 568c2ecf20Sopenharmony_ci head = 1; 578c2ecf20Sopenharmony_ci back = ndirents - 1; 588c2ecf20Sopenharmony_ci startprfx = endprfx = 0; 598c2ecf20Sopenharmony_ci 608c2ecf20Sopenharmony_ci while (head <= back) { 618c2ecf20Sopenharmony_ci const int mid = head + (back - head) / 2; 628c2ecf20Sopenharmony_ci const int nameoff = nameoff_from_disk(de[mid].nameoff, 638c2ecf20Sopenharmony_ci dirblksize); 648c2ecf20Sopenharmony_ci unsigned int matched = min(startprfx, endprfx); 658c2ecf20Sopenharmony_ci struct erofs_qstr dname = { 668c2ecf20Sopenharmony_ci .name = data + nameoff, 678c2ecf20Sopenharmony_ci .end = mid >= ndirents - 1 ? 688c2ecf20Sopenharmony_ci data + dirblksize : 698c2ecf20Sopenharmony_ci data + nameoff_from_disk(de[mid + 1].nameoff, 708c2ecf20Sopenharmony_ci dirblksize) 718c2ecf20Sopenharmony_ci }; 728c2ecf20Sopenharmony_ci 738c2ecf20Sopenharmony_ci /* string comparison without already matched prefix */ 748c2ecf20Sopenharmony_ci int ret = erofs_dirnamecmp(name, &dname, &matched); 758c2ecf20Sopenharmony_ci 768c2ecf20Sopenharmony_ci if (!ret) { 778c2ecf20Sopenharmony_ci return de + mid; 788c2ecf20Sopenharmony_ci } else if (ret > 0) { 798c2ecf20Sopenharmony_ci head = mid + 1; 808c2ecf20Sopenharmony_ci startprfx = matched; 818c2ecf20Sopenharmony_ci } else { 828c2ecf20Sopenharmony_ci back = mid - 1; 838c2ecf20Sopenharmony_ci endprfx = matched; 848c2ecf20Sopenharmony_ci } 858c2ecf20Sopenharmony_ci } 868c2ecf20Sopenharmony_ci 878c2ecf20Sopenharmony_ci return ERR_PTR(-ENOENT); 888c2ecf20Sopenharmony_ci} 898c2ecf20Sopenharmony_ci 908c2ecf20Sopenharmony_cistatic struct page *find_target_block_classic(struct inode *dir, 918c2ecf20Sopenharmony_ci struct erofs_qstr *name, 928c2ecf20Sopenharmony_ci int *_ndirents) 938c2ecf20Sopenharmony_ci{ 948c2ecf20Sopenharmony_ci unsigned int startprfx, endprfx; 958c2ecf20Sopenharmony_ci int head, back; 968c2ecf20Sopenharmony_ci struct address_space *const mapping = dir->i_mapping; 978c2ecf20Sopenharmony_ci struct page *candidate = ERR_PTR(-ENOENT); 988c2ecf20Sopenharmony_ci 998c2ecf20Sopenharmony_ci startprfx = endprfx = 0; 1008c2ecf20Sopenharmony_ci head = 0; 1018c2ecf20Sopenharmony_ci back = erofs_inode_datablocks(dir) - 1; 1028c2ecf20Sopenharmony_ci 1038c2ecf20Sopenharmony_ci while (head <= back) { 1048c2ecf20Sopenharmony_ci const int mid = head + (back - head) / 2; 1058c2ecf20Sopenharmony_ci struct page *page = read_mapping_page(mapping, mid, NULL); 1068c2ecf20Sopenharmony_ci 1078c2ecf20Sopenharmony_ci if (!IS_ERR(page)) { 1088c2ecf20Sopenharmony_ci struct erofs_dirent *de = kmap_atomic(page); 1098c2ecf20Sopenharmony_ci const int nameoff = nameoff_from_disk(de->nameoff, 1108c2ecf20Sopenharmony_ci EROFS_BLKSIZ); 1118c2ecf20Sopenharmony_ci const int ndirents = nameoff / sizeof(*de); 1128c2ecf20Sopenharmony_ci int diff; 1138c2ecf20Sopenharmony_ci unsigned int matched; 1148c2ecf20Sopenharmony_ci struct erofs_qstr dname; 1158c2ecf20Sopenharmony_ci 1168c2ecf20Sopenharmony_ci if (!ndirents) { 1178c2ecf20Sopenharmony_ci kunmap_atomic(de); 1188c2ecf20Sopenharmony_ci put_page(page); 1198c2ecf20Sopenharmony_ci erofs_err(dir->i_sb, 1208c2ecf20Sopenharmony_ci "corrupted dir block %d @ nid %llu", 1218c2ecf20Sopenharmony_ci mid, EROFS_I(dir)->nid); 1228c2ecf20Sopenharmony_ci DBG_BUGON(1); 1238c2ecf20Sopenharmony_ci page = ERR_PTR(-EFSCORRUPTED); 1248c2ecf20Sopenharmony_ci goto out; 1258c2ecf20Sopenharmony_ci } 1268c2ecf20Sopenharmony_ci 1278c2ecf20Sopenharmony_ci matched = min(startprfx, endprfx); 1288c2ecf20Sopenharmony_ci 1298c2ecf20Sopenharmony_ci dname.name = (u8 *)de + nameoff; 1308c2ecf20Sopenharmony_ci if (ndirents == 1) 1318c2ecf20Sopenharmony_ci dname.end = (u8 *)de + EROFS_BLKSIZ; 1328c2ecf20Sopenharmony_ci else 1338c2ecf20Sopenharmony_ci dname.end = (u8 *)de + 1348c2ecf20Sopenharmony_ci nameoff_from_disk(de[1].nameoff, 1358c2ecf20Sopenharmony_ci EROFS_BLKSIZ); 1368c2ecf20Sopenharmony_ci 1378c2ecf20Sopenharmony_ci /* string comparison without already matched prefix */ 1388c2ecf20Sopenharmony_ci diff = erofs_dirnamecmp(name, &dname, &matched); 1398c2ecf20Sopenharmony_ci kunmap_atomic(de); 1408c2ecf20Sopenharmony_ci 1418c2ecf20Sopenharmony_ci if (!diff) { 1428c2ecf20Sopenharmony_ci *_ndirents = 0; 1438c2ecf20Sopenharmony_ci goto out; 1448c2ecf20Sopenharmony_ci } else if (diff > 0) { 1458c2ecf20Sopenharmony_ci head = mid + 1; 1468c2ecf20Sopenharmony_ci startprfx = matched; 1478c2ecf20Sopenharmony_ci 1488c2ecf20Sopenharmony_ci if (!IS_ERR(candidate)) 1498c2ecf20Sopenharmony_ci put_page(candidate); 1508c2ecf20Sopenharmony_ci candidate = page; 1518c2ecf20Sopenharmony_ci *_ndirents = ndirents; 1528c2ecf20Sopenharmony_ci } else { 1538c2ecf20Sopenharmony_ci put_page(page); 1548c2ecf20Sopenharmony_ci 1558c2ecf20Sopenharmony_ci back = mid - 1; 1568c2ecf20Sopenharmony_ci endprfx = matched; 1578c2ecf20Sopenharmony_ci } 1588c2ecf20Sopenharmony_ci continue; 1598c2ecf20Sopenharmony_ci } 1608c2ecf20Sopenharmony_ciout: /* free if the candidate is valid */ 1618c2ecf20Sopenharmony_ci if (!IS_ERR(candidate)) 1628c2ecf20Sopenharmony_ci put_page(candidate); 1638c2ecf20Sopenharmony_ci return page; 1648c2ecf20Sopenharmony_ci } 1658c2ecf20Sopenharmony_ci return candidate; 1668c2ecf20Sopenharmony_ci} 1678c2ecf20Sopenharmony_ci 1688c2ecf20Sopenharmony_ciint erofs_namei(struct inode *dir, 1698c2ecf20Sopenharmony_ci struct qstr *name, 1708c2ecf20Sopenharmony_ci erofs_nid_t *nid, unsigned int *d_type) 1718c2ecf20Sopenharmony_ci{ 1728c2ecf20Sopenharmony_ci int ndirents; 1738c2ecf20Sopenharmony_ci struct page *page; 1748c2ecf20Sopenharmony_ci void *data; 1758c2ecf20Sopenharmony_ci struct erofs_dirent *de; 1768c2ecf20Sopenharmony_ci struct erofs_qstr qn; 1778c2ecf20Sopenharmony_ci 1788c2ecf20Sopenharmony_ci if (!dir->i_size) 1798c2ecf20Sopenharmony_ci return -ENOENT; 1808c2ecf20Sopenharmony_ci 1818c2ecf20Sopenharmony_ci qn.name = name->name; 1828c2ecf20Sopenharmony_ci qn.end = name->name + name->len; 1838c2ecf20Sopenharmony_ci 1848c2ecf20Sopenharmony_ci ndirents = 0; 1858c2ecf20Sopenharmony_ci page = find_target_block_classic(dir, &qn, &ndirents); 1868c2ecf20Sopenharmony_ci 1878c2ecf20Sopenharmony_ci if (IS_ERR(page)) 1888c2ecf20Sopenharmony_ci return PTR_ERR(page); 1898c2ecf20Sopenharmony_ci 1908c2ecf20Sopenharmony_ci data = kmap_atomic(page); 1918c2ecf20Sopenharmony_ci /* the target page has been mapped */ 1928c2ecf20Sopenharmony_ci if (ndirents) 1938c2ecf20Sopenharmony_ci de = find_target_dirent(&qn, data, EROFS_BLKSIZ, ndirents); 1948c2ecf20Sopenharmony_ci else 1958c2ecf20Sopenharmony_ci de = (struct erofs_dirent *)data; 1968c2ecf20Sopenharmony_ci 1978c2ecf20Sopenharmony_ci if (!IS_ERR(de)) { 1988c2ecf20Sopenharmony_ci *nid = le64_to_cpu(de->nid); 1998c2ecf20Sopenharmony_ci *d_type = de->file_type; 2008c2ecf20Sopenharmony_ci } 2018c2ecf20Sopenharmony_ci 2028c2ecf20Sopenharmony_ci kunmap_atomic(data); 2038c2ecf20Sopenharmony_ci put_page(page); 2048c2ecf20Sopenharmony_ci 2058c2ecf20Sopenharmony_ci return PTR_ERR_OR_ZERO(de); 2068c2ecf20Sopenharmony_ci} 2078c2ecf20Sopenharmony_ci 2088c2ecf20Sopenharmony_ci/* NOTE: i_mutex is already held by vfs */ 2098c2ecf20Sopenharmony_cistatic struct dentry *erofs_lookup(struct inode *dir, 2108c2ecf20Sopenharmony_ci struct dentry *dentry, 2118c2ecf20Sopenharmony_ci unsigned int flags) 2128c2ecf20Sopenharmony_ci{ 2138c2ecf20Sopenharmony_ci int err; 2148c2ecf20Sopenharmony_ci erofs_nid_t nid; 2158c2ecf20Sopenharmony_ci unsigned int d_type; 2168c2ecf20Sopenharmony_ci struct inode *inode; 2178c2ecf20Sopenharmony_ci 2188c2ecf20Sopenharmony_ci DBG_BUGON(!d_really_is_negative(dentry)); 2198c2ecf20Sopenharmony_ci /* dentry must be unhashed in lookup, no need to worry about */ 2208c2ecf20Sopenharmony_ci DBG_BUGON(!d_unhashed(dentry)); 2218c2ecf20Sopenharmony_ci 2228c2ecf20Sopenharmony_ci trace_erofs_lookup(dir, dentry, flags); 2238c2ecf20Sopenharmony_ci 2248c2ecf20Sopenharmony_ci /* file name exceeds fs limit */ 2258c2ecf20Sopenharmony_ci if (dentry->d_name.len > EROFS_NAME_LEN) 2268c2ecf20Sopenharmony_ci return ERR_PTR(-ENAMETOOLONG); 2278c2ecf20Sopenharmony_ci 2288c2ecf20Sopenharmony_ci /* false uninitialized warnings on gcc 4.8.x */ 2298c2ecf20Sopenharmony_ci err = erofs_namei(dir, &dentry->d_name, &nid, &d_type); 2308c2ecf20Sopenharmony_ci 2318c2ecf20Sopenharmony_ci if (err == -ENOENT) { 2328c2ecf20Sopenharmony_ci /* negative dentry */ 2338c2ecf20Sopenharmony_ci inode = NULL; 2348c2ecf20Sopenharmony_ci } else if (err) { 2358c2ecf20Sopenharmony_ci inode = ERR_PTR(err); 2368c2ecf20Sopenharmony_ci } else { 2378c2ecf20Sopenharmony_ci erofs_dbg("%s, %s (nid %llu) found, d_type %u", __func__, 2388c2ecf20Sopenharmony_ci dentry->d_name.name, nid, d_type); 2398c2ecf20Sopenharmony_ci inode = erofs_iget(dir->i_sb, nid, d_type == FT_DIR); 2408c2ecf20Sopenharmony_ci } 2418c2ecf20Sopenharmony_ci return d_splice_alias(inode, dentry); 2428c2ecf20Sopenharmony_ci} 2438c2ecf20Sopenharmony_ci 2448c2ecf20Sopenharmony_ciconst struct inode_operations erofs_dir_iops = { 2458c2ecf20Sopenharmony_ci .lookup = erofs_lookup, 2468c2ecf20Sopenharmony_ci .getattr = erofs_getattr, 2478c2ecf20Sopenharmony_ci .listxattr = erofs_listxattr, 2488c2ecf20Sopenharmony_ci .get_acl = erofs_get_acl, 2498c2ecf20Sopenharmony_ci}; 2508c2ecf20Sopenharmony_ci 251