test code v1
This commit is contained in:
@@ -0,0 +1,310 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (C) 2017-2018 HUAWEI, Inc.
|
||||
* https://www.huawei.com/
|
||||
* Copyright (C) 2022, Alibaba Cloud
|
||||
*/
|
||||
|
||||
#include <sys/param.h>
|
||||
#include <sys/systm.h>
|
||||
#include <sys/dirent.h>
|
||||
#include <sys/kernel.h>
|
||||
#include <sys/libkern.h>
|
||||
#include <sys/malloc.h>
|
||||
#include <sys/namei.h>
|
||||
#include <sys/proc.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/vnode.h>
|
||||
|
||||
#include "internal.h"
|
||||
|
||||
/*
|
||||
* Compare two directory entry names using an already-matched prefix.
|
||||
* (Linux equivalent: erofs_dirnamecmp in Linux's namei.c)
|
||||
*
|
||||
* qn_name / qn_len: search key (not necessarily null-terminated).
|
||||
* qd_name / qd_end: on-disk name range (may not be null-terminated).
|
||||
* matched: in/out count of prefix characters already known to match.
|
||||
*
|
||||
* Returns 0 if equal, 1 if qn > qd, -1 if qn < qd.
|
||||
*/
|
||||
static int
|
||||
erofs_dirnamecmp(const char *qn_name, size_t qn_len, const char *qd_name,
|
||||
const char *qd_end, unsigned int *matched)
|
||||
{
|
||||
size_t dname_span;
|
||||
unsigned int i;
|
||||
|
||||
dname_span = qd_end - qd_name;
|
||||
i = MIN(*matched, qn_len);
|
||||
i = MIN(i, dname_span);
|
||||
while (i < qn_len && i < dname_span && qd_name[i] != '\0') {
|
||||
if ((unsigned char)qn_name[i] != (unsigned char)qd_name[i]) {
|
||||
*matched = i;
|
||||
return ((unsigned char)qn_name[i] >
|
||||
(unsigned char)qd_name[i] ? 1 : -1);
|
||||
}
|
||||
++i;
|
||||
}
|
||||
*matched = i;
|
||||
if (i == qn_len)
|
||||
return (i == dname_span || qd_name[i] == '\0' ? 0 : -1);
|
||||
return (1);
|
||||
}
|
||||
|
||||
/*
|
||||
* Binary search within a directory block for the target name.
|
||||
*
|
||||
* Returns a pointer to the matching dirent, or NULL on miss.
|
||||
*/
|
||||
static struct erofs_dirent *
|
||||
find_target_dirent(const char *name, size_t namelen, char *data,
|
||||
uint32_t datasize, uint32_t ndirents)
|
||||
{
|
||||
uint32_t head, back;
|
||||
unsigned int startprfx, endprfx;
|
||||
struct erofs_dirent *const de = (struct erofs_dirent *)data;
|
||||
|
||||
/* The 1st dirent has already been evaluated by the caller. */
|
||||
head = 1;
|
||||
back = ndirents - 1;
|
||||
startprfx = endprfx = 0;
|
||||
|
||||
while (head <= back) {
|
||||
const uint32_t mid = head + (back - head) / 2;
|
||||
const uint32_t nameoff = le16toh(de[mid].nameoff);
|
||||
unsigned int matched = MIN(startprfx, endprfx);
|
||||
const char *dname_start = data + nameoff;
|
||||
const char *dname_end;
|
||||
|
||||
if (mid >= ndirents - 1)
|
||||
dname_end = data + datasize;
|
||||
else
|
||||
dname_end = data + le16toh(de[mid + 1].nameoff);
|
||||
|
||||
/* String comparison without already matched prefix */
|
||||
int ret = erofs_dirnamecmp(name, namelen, dname_start,
|
||||
dname_end, &matched);
|
||||
|
||||
if (ret == 0)
|
||||
return (de + mid);
|
||||
else if (ret > 0) {
|
||||
head = mid + 1;
|
||||
startprfx = matched;
|
||||
} else {
|
||||
back = mid - 1;
|
||||
endprfx = matched;
|
||||
}
|
||||
}
|
||||
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
/*
|
||||
* Find the directory block most likely to contain the target name.
|
||||
*
|
||||
* Uses two-level binary search: first across blocks, then within the
|
||||
* candidate block via find_target_dirent().
|
||||
*
|
||||
* Returns the block buffer on success (caller must erofs_brelse),
|
||||
* or NULL on error. *_ndirents is set to the number of dirents in
|
||||
* the returned block (0 means the first entry is the match).
|
||||
* On error, *errorp is set to a positive errno.
|
||||
*/
|
||||
static char *
|
||||
erofs_find_target_block(struct erofs_mount *em, struct erofs_node *dir,
|
||||
const char *name, size_t namelen, uint32_t *_ndirents, uint32_t *_datasize,
|
||||
int *errorp)
|
||||
{
|
||||
uint32_t bsz = em->block_size;
|
||||
uint64_t head, back;
|
||||
unsigned int startprfx = 0, endprfx = 0;
|
||||
char *candidate = NULL;
|
||||
int error;
|
||||
|
||||
*errorp = 0;
|
||||
*_ndirents = 0;
|
||||
*_datasize = 0;
|
||||
|
||||
if (dir->size == 0)
|
||||
return (NULL);
|
||||
|
||||
head = 0;
|
||||
back = (dir->size - 1) / bsz;
|
||||
|
||||
while (head <= back) {
|
||||
const uint64_t mid = head + (back - head) / 2;
|
||||
uint64_t block_off;
|
||||
uint32_t maxsize;
|
||||
const struct erofs_dirent *de;
|
||||
char *blk;
|
||||
int diff;
|
||||
uint32_t ndirents;
|
||||
uint32_t nameoff;
|
||||
unsigned int matched;
|
||||
const char *dname_start, *dname_end;
|
||||
|
||||
if (__builtin_mul_overflow(mid, (uint64_t)bsz, &block_off) ||
|
||||
block_off >= dir->size) {
|
||||
*errorp = EINTEGRITY;
|
||||
goto out;
|
||||
}
|
||||
maxsize = MIN((uint64_t)bsz, dir->size - block_off);
|
||||
error = erofs_read_data(em, dir, block_off, maxsize,
|
||||
(void **)&blk);
|
||||
if (error != 0) {
|
||||
*errorp = error;
|
||||
goto out;
|
||||
}
|
||||
error = erofs_validate_dirblock(blk, bsz, maxsize, &ndirents);
|
||||
if (error != 0) {
|
||||
erofs_brelse(blk);
|
||||
*errorp = error;
|
||||
goto out;
|
||||
}
|
||||
de = (const struct erofs_dirent *)blk;
|
||||
nameoff = le16toh(de[0].nameoff);
|
||||
|
||||
matched = MIN(startprfx, endprfx);
|
||||
dname_start = blk + nameoff;
|
||||
if (ndirents == 1)
|
||||
dname_end = blk + maxsize;
|
||||
else
|
||||
dname_end = blk + le16toh(de[1].nameoff);
|
||||
|
||||
/* String comparison without already matched prefix */
|
||||
diff = erofs_dirnamecmp(name, namelen, dname_start, dname_end,
|
||||
&matched);
|
||||
|
||||
if (diff < 0) {
|
||||
erofs_brelse(blk);
|
||||
if (mid == 0)
|
||||
break;
|
||||
back = mid - 1;
|
||||
endprfx = matched;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* diff >= 0: this block is a candidate. */
|
||||
if (candidate != NULL)
|
||||
erofs_brelse(candidate);
|
||||
candidate = blk;
|
||||
if (diff == 0) {
|
||||
*_ndirents = 0;
|
||||
*_datasize = maxsize;
|
||||
return (candidate);
|
||||
}
|
||||
head = mid + 1;
|
||||
startprfx = matched;
|
||||
*_ndirents = ndirents;
|
||||
*_datasize = maxsize;
|
||||
}
|
||||
return (candidate);
|
||||
out:
|
||||
if (candidate != NULL)
|
||||
erofs_brelse(candidate);
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
/*
|
||||
* Look up a name in a directory and return its nid and d_type.
|
||||
* (Linux equivalent: erofs_namei in Linux's namei.c)
|
||||
*/
|
||||
static int
|
||||
erofs_namei(struct erofs_mount *em, struct erofs_node *dir, const char *name,
|
||||
size_t namelen, uint64_t *nid, uint8_t *d_type)
|
||||
{
|
||||
int error;
|
||||
uint32_t ndirents;
|
||||
uint32_t datasize;
|
||||
char *blk;
|
||||
struct erofs_dirent *de;
|
||||
|
||||
if (dir->size == 0)
|
||||
return (ENOENT);
|
||||
|
||||
blk = erofs_find_target_block(em, dir, name, namelen, &ndirents,
|
||||
&datasize, &error);
|
||||
if (blk == NULL)
|
||||
return (error != 0 ? error : ENOENT);
|
||||
|
||||
de = (struct erofs_dirent *)blk;
|
||||
if (ndirents > 0)
|
||||
de = find_target_dirent(name, namelen, blk, datasize,
|
||||
ndirents);
|
||||
|
||||
if (de != NULL) {
|
||||
*nid = le64toh(de->nid);
|
||||
*d_type = de->file_type;
|
||||
}
|
||||
erofs_brelse(blk);
|
||||
return (de != NULL ? 0 : ENOENT);
|
||||
}
|
||||
|
||||
/*
|
||||
* Directory name lookup (VOP_CACHEDLOOKUP entry point).
|
||||
*
|
||||
* FreeBSD-side API requirements:
|
||||
* - "." must be returned under the caller's requested lock mode;
|
||||
* - ".." must go through vn_vget_ino() to avoid holding a child lock while
|
||||
* acquiring the parent directory lock in reverse;
|
||||
* - Both hit and miss must correctly update the namecache.
|
||||
*/
|
||||
int
|
||||
erofs_lookup(struct vop_cachedlookup_args *ap)
|
||||
{
|
||||
struct vnode *dvp, *vp;
|
||||
struct erofs_node *dir;
|
||||
struct erofs_mount *em;
|
||||
struct componentname *cnp;
|
||||
uint64_t nid;
|
||||
uint8_t dtype;
|
||||
int error, ltype;
|
||||
|
||||
dvp = ap->a_dvp;
|
||||
cnp = ap->a_cnp;
|
||||
*ap->a_vpp = NULL;
|
||||
if ((cnp->cn_flags & ISLASTCN) != 0 &&
|
||||
(cnp->cn_nameiop == DELETE || cnp->cn_nameiop == RENAME))
|
||||
return (EROFS);
|
||||
if (cnp->cn_namelen < 0)
|
||||
return (EINVAL);
|
||||
if (cnp->cn_namelen > EROFS_NAME_LEN)
|
||||
return (ENAMETOOLONG);
|
||||
if (cnp->cn_namelen == 1 && cnp->cn_nameptr[0] == '.') {
|
||||
vref(dvp);
|
||||
ltype = cnp->cn_lkflags & LK_TYPE_MASK;
|
||||
if (ltype != VOP_ISLOCKED(dvp)) {
|
||||
if (ltype == LK_EXCLUSIVE)
|
||||
vn_lock(dvp, LK_UPGRADE | LK_RETRY);
|
||||
else if (ltype == LK_SHARED)
|
||||
vn_lock(dvp, LK_DOWNGRADE | LK_RETRY);
|
||||
}
|
||||
*ap->a_vpp = dvp;
|
||||
return (0);
|
||||
}
|
||||
|
||||
dir = VTOE(dvp);
|
||||
em = MTOE(dvp->v_mount);
|
||||
error = erofs_namei(em, dir, cnp->cn_nameptr, cnp->cn_namelen, &nid,
|
||||
&dtype);
|
||||
if (error != 0) {
|
||||
if (error == ENOENT && (cnp->cn_flags & MAKEENTRY) != 0)
|
||||
cache_enter(dvp, NULL, cnp);
|
||||
if (error == ENOENT && (cnp->cn_flags & ISLASTCN) != 0 &&
|
||||
(cnp->cn_nameiop == CREATE || cnp->cn_nameiop == RENAME))
|
||||
return (EROFS);
|
||||
return (error);
|
||||
}
|
||||
|
||||
if ((cnp->cn_flags & ISDOTDOT) != 0)
|
||||
error = vn_vget_ino(dvp, nid, cnp->cn_lkflags, &vp);
|
||||
else
|
||||
error = erofs_vget(dvp->v_mount, nid, cnp->cn_lkflags, &vp);
|
||||
if (error != 0)
|
||||
return (error);
|
||||
*ap->a_vpp = vp;
|
||||
if ((cnp->cn_flags & MAKEENTRY) != 0)
|
||||
cache_enter(dvp, vp, cnp);
|
||||
return (0);
|
||||
}
|
||||
Reference in New Issue
Block a user