test code v1
This commit is contained in:
@@ -0,0 +1,346 @@
|
||||
// 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/limits.h>
|
||||
#include <sys/malloc.h>
|
||||
#include <sys/mount.h>
|
||||
#include <sys/vnode.h>
|
||||
|
||||
#include "internal.h"
|
||||
#include "erofs_defs.h"
|
||||
|
||||
/* Map EROFS directory entry file type to FreeBSD dirent.d_type. */
|
||||
static unsigned char
|
||||
erofs_ftype_to_dtype(uint8_t ftype)
|
||||
{
|
||||
switch (ftype) {
|
||||
case EROFS_FT_REG_FILE:
|
||||
return (DT_REG);
|
||||
case EROFS_FT_DIR:
|
||||
return (DT_DIR);
|
||||
case EROFS_FT_CHRDEV:
|
||||
return (DT_CHR);
|
||||
case EROFS_FT_BLKDEV:
|
||||
return (DT_BLK);
|
||||
case EROFS_FT_FIFO:
|
||||
return (DT_FIFO);
|
||||
case EROFS_FT_SOCK:
|
||||
return (DT_SOCK);
|
||||
case EROFS_FT_SYMLINK:
|
||||
return (DT_LNK);
|
||||
default:
|
||||
return (DT_UNKNOWN);
|
||||
}
|
||||
}
|
||||
|
||||
/* Validate one name slot and return its Linux-visible length. */
|
||||
int
|
||||
erofs_dirent_namelen(const char *blk, uint32_t nameoff, uint32_t endoff,
|
||||
bool trailing, size_t *namelenp)
|
||||
{
|
||||
size_t namelen, span;
|
||||
|
||||
if (endoff <= nameoff)
|
||||
return (EINTEGRITY);
|
||||
span = endoff - nameoff;
|
||||
if (trailing) {
|
||||
namelen = strnlen(blk + nameoff, span);
|
||||
} else {
|
||||
namelen = span;
|
||||
if (memchr(blk + nameoff, '\0', span) != NULL)
|
||||
return (EINTEGRITY);
|
||||
}
|
||||
if (namelen == 0 || namelen > EROFS_NAME_LEN)
|
||||
return (EINTEGRITY);
|
||||
for (size_t i = 0; i < namelen; i++) {
|
||||
if (blk[nameoff + i] == '/')
|
||||
return (EINTEGRITY);
|
||||
}
|
||||
*namelenp = namelen;
|
||||
return (0);
|
||||
}
|
||||
|
||||
/* Validate the dirent array, name offsets, and names in a block. */
|
||||
int
|
||||
erofs_validate_dirblock(const char *blk, uint32_t blksz, uint32_t maxsize,
|
||||
uint32_t *ndirentsp)
|
||||
{
|
||||
const struct erofs_dirent *de;
|
||||
uint32_t endoff, first_nameoff, idx, nameoff, ndirents, prev_nameoff;
|
||||
size_t namelen;
|
||||
int error;
|
||||
|
||||
if (blksz < EROFS_DIRENT_SIZE || maxsize < EROFS_DIRENT_SIZE ||
|
||||
maxsize > blksz)
|
||||
return (EINTEGRITY);
|
||||
de = (const struct erofs_dirent *)blk;
|
||||
first_nameoff = le16toh(de[0].nameoff);
|
||||
if (first_nameoff < EROFS_DIRENT_SIZE || first_nameoff >= maxsize ||
|
||||
(first_nameoff % EROFS_DIRENT_SIZE) != 0)
|
||||
return (EINTEGRITY);
|
||||
ndirents = first_nameoff / EROFS_DIRENT_SIZE;
|
||||
prev_nameoff = 0;
|
||||
for (idx = 0; idx < ndirents; idx++) {
|
||||
nameoff = le16toh(de[idx].nameoff);
|
||||
if ((idx == 0 && nameoff != first_nameoff) ||
|
||||
(idx != 0 && nameoff <= prev_nameoff) ||
|
||||
nameoff < first_nameoff || nameoff >= maxsize)
|
||||
return (EINTEGRITY);
|
||||
endoff = idx + 1 < ndirents ?
|
||||
le16toh(de[idx + 1].nameoff) : maxsize;
|
||||
if (endoff <= nameoff || endoff > maxsize)
|
||||
return (EINTEGRITY);
|
||||
error = erofs_dirent_namelen(blk, nameoff, endoff,
|
||||
idx + 1 == ndirents, &namelen);
|
||||
if (error != 0)
|
||||
return (error);
|
||||
prev_nameoff = nameoff;
|
||||
}
|
||||
*ndirentsp = ndirents;
|
||||
return (0);
|
||||
}
|
||||
|
||||
/* Extract name, nid, and type of the idx'th directory entry from a block. */
|
||||
static int
|
||||
erofs_dirent_name(const char *blk, uint32_t maxsize,
|
||||
uint32_t idx, uint32_t ndirents, char *name, size_t namesz, uint64_t *nid,
|
||||
uint8_t *ftype, size_t *namelenp)
|
||||
{
|
||||
const struct erofs_dirent *de;
|
||||
uint32_t nameoff, endoff;
|
||||
size_t namelen;
|
||||
int error;
|
||||
|
||||
de = (const struct erofs_dirent *)blk;
|
||||
nameoff = le16toh(de[idx].nameoff);
|
||||
if (idx + 1 < ndirents)
|
||||
endoff = le16toh(de[idx + 1].nameoff);
|
||||
else
|
||||
endoff = maxsize;
|
||||
error = erofs_dirent_namelen(blk, nameoff, endoff,
|
||||
idx + 1 == ndirents, &namelen);
|
||||
if (error != 0)
|
||||
return (error);
|
||||
if (namelen >= namesz)
|
||||
return (EINTEGRITY);
|
||||
memcpy(name, blk + nameoff, namelen);
|
||||
name[namelen] = '\0';
|
||||
*nid = le64toh(de[idx].nid);
|
||||
*ftype = de[idx].file_type;
|
||||
*namelenp = namelen;
|
||||
return (0);
|
||||
}
|
||||
|
||||
/* Per-call state for readdir dirent/cookie output. */
|
||||
struct erofs_uiodir {
|
||||
struct dirent *dirent;
|
||||
uint64_t *cookies;
|
||||
uint64_t last_cookie;
|
||||
int ncookies;
|
||||
int acookies;
|
||||
int eofflag;
|
||||
};
|
||||
|
||||
enum erofs_uiodir_result {
|
||||
EROFS_UIODIR_BUFFER_FULL = -1,
|
||||
EROFS_UIODIR_OK = 0,
|
||||
};
|
||||
|
||||
/* Push a dirent and its cookie to the caller, modelled after UDF. */
|
||||
static int
|
||||
erofs_uiodir(struct erofs_uiodir *uiodir, int de_size, struct uio *uio,
|
||||
uint64_t cookie)
|
||||
{
|
||||
int error;
|
||||
|
||||
if (cookie <= uiodir->last_cookie)
|
||||
return (EINTEGRITY);
|
||||
if (uio->uio_resid < de_size ||
|
||||
(uiodir->cookies != NULL &&
|
||||
uiodir->acookies >= uiodir->ncookies)) {
|
||||
return (EROFS_UIODIR_BUFFER_FULL);
|
||||
}
|
||||
error = uiomove(uiodir->dirent, de_size, uio);
|
||||
if (error != 0)
|
||||
return (error);
|
||||
uiodir->last_cookie = cookie;
|
||||
if (uiodir->cookies != NULL)
|
||||
uiodir->cookies[uiodir->acookies++] = cookie;
|
||||
return (EROFS_UIODIR_OK);
|
||||
}
|
||||
|
||||
/*
|
||||
* Process directory entries within a single block and output them to uio.
|
||||
* (Linux equivalent: erofs_fill_dentries in Linux's dir.c)
|
||||
*
|
||||
* Returns 0 on success (all entries consumed), -1 if uio is full, or a
|
||||
* positive error code on corruption.
|
||||
*/
|
||||
static int
|
||||
erofs_fill_dentries(struct erofs_uiodir *uiodir, struct uio *uio,
|
||||
struct dirent *d, const char *blk, uint32_t maxsize,
|
||||
uint32_t start_idx, uint32_t ndirents, uint64_t block_off,
|
||||
uint64_t *logical_offp)
|
||||
{
|
||||
char name[EROFS_NAME_LEN + 1];
|
||||
uint32_t idx;
|
||||
uint64_t curpos, nextoff, nid;
|
||||
size_t namelen;
|
||||
uint8_t ftype;
|
||||
int error;
|
||||
|
||||
for (idx = start_idx; idx < ndirents; idx++) {
|
||||
curpos = block_off + idx * EROFS_DIRENT_SIZE;
|
||||
nextoff = (idx + 1 < ndirents) ?
|
||||
(curpos + EROFS_DIRENT_SIZE) :
|
||||
(block_off + maxsize);
|
||||
error = erofs_dirent_name(blk, maxsize, idx, ndirents, name,
|
||||
sizeof(name), &nid, &ftype, &namelen);
|
||||
if (error != 0)
|
||||
return (error);
|
||||
bzero(d, sizeof(*d));
|
||||
d->d_fileno = nid;
|
||||
d->d_type = erofs_ftype_to_dtype(ftype);
|
||||
d->d_namlen = namelen;
|
||||
d->d_reclen = GENERIC_DIRSIZ(d);
|
||||
d->d_off = nextoff;
|
||||
strlcpy(d->d_name, name, sizeof(d->d_name));
|
||||
error = erofs_uiodir(uiodir, d->d_reclen, uio, d->d_off);
|
||||
if (error != 0)
|
||||
return (error);
|
||||
*logical_offp = nextoff;
|
||||
uio->uio_offset = *logical_offp;
|
||||
}
|
||||
return (0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Read directory contents and output a FreeBSD dirent stream to uio.
|
||||
*
|
||||
* Key points:
|
||||
* - On-disk entries use their logical file offsets as cookies;
|
||||
* - A dot_omitted directory appends a synthetic "." at i_size, matching
|
||||
* Linux, so existing on-disk cookies are not shifted;
|
||||
* - The dirent array occupies only the front portion of a block, so after
|
||||
* scanning all entries offset must jump to maxsize (the block end),
|
||||
* otherwise the loop would get stuck on the same block;
|
||||
* - Supports a_ncookies / a_cookies for NFS and other callers that need
|
||||
* resumable iteration.
|
||||
*/
|
||||
int
|
||||
erofs_readdir_block(struct vnode *vp, struct uio *uio, int *eofflag,
|
||||
int *ncookies, uint64_t **cookies)
|
||||
{
|
||||
struct erofs_node *dir;
|
||||
struct erofs_mount *em;
|
||||
struct erofs_uiodir uiodir;
|
||||
struct dirent d;
|
||||
uint64_t *cookiebuf;
|
||||
char *blk;
|
||||
uint64_t block_off, logical_off;
|
||||
uint32_t block_pos, blksz, ndirents, start_idx, maxsize;
|
||||
int error;
|
||||
|
||||
dir = VTOE(vp);
|
||||
em = MTOE(vp->v_mount);
|
||||
blksz = em->block_size;
|
||||
error = 0;
|
||||
cookiebuf = NULL;
|
||||
uiodir.eofflag = 0;
|
||||
uiodir.acookies = 0;
|
||||
uiodir.dirent = &d;
|
||||
uiodir.cookies = NULL;
|
||||
uiodir.ncookies = 0;
|
||||
if (cookies != NULL && ncookies != NULL) {
|
||||
*cookies = NULL;
|
||||
*ncookies = 0;
|
||||
uiodir.ncookies = MAX(1, uio->uio_resid / 8);
|
||||
cookiebuf = malloc(sizeof(*uiodir.cookies) * uiodir.ncookies,
|
||||
M_TEMP, M_WAITOK);
|
||||
uiodir.cookies = cookiebuf;
|
||||
}
|
||||
|
||||
if (uio->uio_offset < 0) {
|
||||
error = EINVAL;
|
||||
goto out;
|
||||
}
|
||||
if (dir->dot_omitted && dir->size == (uint64_t)OFF_MAX) {
|
||||
error = EINTEGRITY;
|
||||
goto out;
|
||||
}
|
||||
|
||||
logical_off = uio->uio_offset;
|
||||
uiodir.last_cookie = logical_off;
|
||||
uio->uio_offset = logical_off;
|
||||
|
||||
while (logical_off < dir->size) {
|
||||
block_off = rounddown2(logical_off, (uint64_t)blksz);
|
||||
maxsize = MIN((uint64_t)blksz, dir->size - block_off);
|
||||
block_pos = logical_off - block_off;
|
||||
if ((block_pos % EROFS_DIRENT_SIZE) != 0) {
|
||||
block_pos = roundup(block_pos, EROFS_DIRENT_SIZE);
|
||||
logical_off = block_off + block_pos;
|
||||
uio->uio_offset = logical_off;
|
||||
}
|
||||
error = erofs_read_data(em, dir, block_off, maxsize,
|
||||
(void **)&blk);
|
||||
if (error != 0)
|
||||
goto out;
|
||||
error = erofs_validate_dirblock(blk, blksz, maxsize, &ndirents);
|
||||
if (error != 0) {
|
||||
erofs_brelse(blk);
|
||||
goto out;
|
||||
}
|
||||
start_idx = block_pos / EROFS_DIRENT_SIZE;
|
||||
if (start_idx >= ndirents) {
|
||||
logical_off = block_off + maxsize;
|
||||
uio->uio_offset = logical_off;
|
||||
erofs_brelse(blk);
|
||||
continue;
|
||||
}
|
||||
error = erofs_fill_dentries(&uiodir, uio, &d, blk, maxsize,
|
||||
start_idx, ndirents, block_off, &logical_off);
|
||||
erofs_brelse(blk);
|
||||
if (error != 0)
|
||||
goto out;
|
||||
}
|
||||
if (dir->dot_omitted && logical_off == dir->size) {
|
||||
bzero(&d, sizeof(d));
|
||||
d.d_fileno = dir->nid;
|
||||
d.d_type = DT_DIR;
|
||||
d.d_namlen = 1;
|
||||
d.d_reclen = GENERIC_DIRSIZ(&d);
|
||||
d.d_off = dir->size + 1;
|
||||
d.d_name[0] = '.';
|
||||
d.d_name[1] = '\0';
|
||||
error = erofs_uiodir(&uiodir, d.d_reclen, uio, d.d_off);
|
||||
if (error != 0)
|
||||
goto out;
|
||||
logical_off++;
|
||||
uio->uio_offset = logical_off;
|
||||
}
|
||||
uiodir.eofflag = 1;
|
||||
out:
|
||||
if (error == EROFS_UIODIR_BUFFER_FULL)
|
||||
error = 0;
|
||||
if (eofflag != NULL && error == 0)
|
||||
*eofflag = uiodir.eofflag;
|
||||
if (cookies != NULL && ncookies != NULL) {
|
||||
if (error != 0) {
|
||||
free(cookiebuf, M_TEMP);
|
||||
} else {
|
||||
*ncookies = uiodir.acookies;
|
||||
*cookies = cookiebuf;
|
||||
}
|
||||
}
|
||||
return (error);
|
||||
}
|
||||
Reference in New Issue
Block a user