251 lines
5.3 KiB
C
251 lines
5.3 KiB
C
|
// SPDX-License-Identifier: GPL-2.0-only
|
||
|
/*
|
||
|
* This file contains vfs directory ops for the 9P2000 protocol.
|
||
|
*
|
||
|
* Copyright (C) 2004 by Eric Van Hensbergen <ericvh@gmail.com>
|
||
|
* Copyright (C) 2002 by Ron Minnich <rminnich@lanl.gov>
|
||
|
*/
|
||
|
|
||
|
#include <linux/module.h>
|
||
|
#include <linux/errno.h>
|
||
|
#include <linux/fs.h>
|
||
|
#include <linux/file.h>
|
||
|
#include <linux/stat.h>
|
||
|
#include <linux/string.h>
|
||
|
#include <linux/sched.h>
|
||
|
#include <linux/inet.h>
|
||
|
#include <linux/slab.h>
|
||
|
#include <linux/uio.h>
|
||
|
#include <linux/fscache.h>
|
||
|
#include <net/9p/9p.h>
|
||
|
#include <net/9p/client.h>
|
||
|
|
||
|
#include "v9fs.h"
|
||
|
#include "v9fs_vfs.h"
|
||
|
#include "fid.h"
|
||
|
|
||
|
/**
|
||
|
* struct p9_rdir - readdir accounting
|
||
|
* @head: start offset of current dirread buffer
|
||
|
* @tail: end offset of current dirread buffer
|
||
|
* @buf: dirread buffer
|
||
|
*
|
||
|
* private structure for keeping track of readdir
|
||
|
* allocated on demand
|
||
|
*/
|
||
|
|
||
|
struct p9_rdir {
|
||
|
int head;
|
||
|
int tail;
|
||
|
uint8_t buf[];
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* dt_type - return file type
|
||
|
* @mistat: mistat structure
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
static inline int dt_type(struct p9_wstat *mistat)
|
||
|
{
|
||
|
unsigned long perm = mistat->mode;
|
||
|
int rettype = DT_REG;
|
||
|
|
||
|
if (perm & P9_DMDIR)
|
||
|
rettype = DT_DIR;
|
||
|
if (perm & P9_DMSYMLINK)
|
||
|
rettype = DT_LNK;
|
||
|
|
||
|
return rettype;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* v9fs_alloc_rdir_buf - Allocate buffer used for read and readdir
|
||
|
* @filp: opened file structure
|
||
|
* @buflen: Length in bytes of buffer to allocate
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
static struct p9_rdir *v9fs_alloc_rdir_buf(struct file *filp, int buflen)
|
||
|
{
|
||
|
struct p9_fid *fid = filp->private_data;
|
||
|
|
||
|
if (!fid->rdir)
|
||
|
fid->rdir = kzalloc(sizeof(struct p9_rdir) + buflen, GFP_KERNEL);
|
||
|
return fid->rdir;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* v9fs_dir_readdir - iterate through a directory
|
||
|
* @file: opened file structure
|
||
|
* @ctx: actor we feed the entries to
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
static int v9fs_dir_readdir(struct file *file, struct dir_context *ctx)
|
||
|
{
|
||
|
bool over;
|
||
|
struct p9_wstat st;
|
||
|
int err = 0;
|
||
|
struct p9_fid *fid;
|
||
|
int buflen;
|
||
|
struct p9_rdir *rdir;
|
||
|
struct kvec kvec;
|
||
|
|
||
|
p9_debug(P9_DEBUG_VFS, "name %pD\n", file);
|
||
|
fid = file->private_data;
|
||
|
|
||
|
buflen = fid->clnt->msize - P9_IOHDRSZ;
|
||
|
|
||
|
rdir = v9fs_alloc_rdir_buf(file, buflen);
|
||
|
if (!rdir)
|
||
|
return -ENOMEM;
|
||
|
kvec.iov_base = rdir->buf;
|
||
|
kvec.iov_len = buflen;
|
||
|
|
||
|
while (1) {
|
||
|
if (rdir->tail == rdir->head) {
|
||
|
struct iov_iter to;
|
||
|
int n;
|
||
|
|
||
|
iov_iter_kvec(&to, ITER_DEST, &kvec, 1, buflen);
|
||
|
n = p9_client_read(file->private_data, ctx->pos, &to,
|
||
|
&err);
|
||
|
if (err)
|
||
|
return err;
|
||
|
if (n == 0)
|
||
|
return 0;
|
||
|
|
||
|
rdir->head = 0;
|
||
|
rdir->tail = n;
|
||
|
}
|
||
|
while (rdir->head < rdir->tail) {
|
||
|
err = p9stat_read(fid->clnt, rdir->buf + rdir->head,
|
||
|
rdir->tail - rdir->head, &st);
|
||
|
if (err <= 0) {
|
||
|
p9_debug(P9_DEBUG_VFS, "returned %d\n", err);
|
||
|
return -EIO;
|
||
|
}
|
||
|
|
||
|
over = !dir_emit(ctx, st.name, strlen(st.name),
|
||
|
v9fs_qid2ino(&st.qid), dt_type(&st));
|
||
|
p9stat_free(&st);
|
||
|
if (over)
|
||
|
return 0;
|
||
|
|
||
|
rdir->head += err;
|
||
|
ctx->pos += err;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* v9fs_dir_readdir_dotl - iterate through a directory
|
||
|
* @file: opened file structure
|
||
|
* @ctx: actor we feed the entries to
|
||
|
*
|
||
|
*/
|
||
|
static int v9fs_dir_readdir_dotl(struct file *file, struct dir_context *ctx)
|
||
|
{
|
||
|
int err = 0;
|
||
|
struct p9_fid *fid;
|
||
|
int buflen;
|
||
|
struct p9_rdir *rdir;
|
||
|
struct p9_dirent curdirent;
|
||
|
|
||
|
p9_debug(P9_DEBUG_VFS, "name %pD\n", file);
|
||
|
fid = file->private_data;
|
||
|
|
||
|
buflen = fid->clnt->msize - P9_READDIRHDRSZ;
|
||
|
|
||
|
rdir = v9fs_alloc_rdir_buf(file, buflen);
|
||
|
if (!rdir)
|
||
|
return -ENOMEM;
|
||
|
|
||
|
while (1) {
|
||
|
if (rdir->tail == rdir->head) {
|
||
|
err = p9_client_readdir(fid, rdir->buf, buflen,
|
||
|
ctx->pos);
|
||
|
if (err <= 0)
|
||
|
return err;
|
||
|
|
||
|
rdir->head = 0;
|
||
|
rdir->tail = err;
|
||
|
}
|
||
|
|
||
|
while (rdir->head < rdir->tail) {
|
||
|
|
||
|
err = p9dirent_read(fid->clnt, rdir->buf + rdir->head,
|
||
|
rdir->tail - rdir->head,
|
||
|
&curdirent);
|
||
|
if (err < 0) {
|
||
|
p9_debug(P9_DEBUG_VFS, "returned %d\n", err);
|
||
|
return -EIO;
|
||
|
}
|
||
|
|
||
|
if (!dir_emit(ctx, curdirent.d_name,
|
||
|
strlen(curdirent.d_name),
|
||
|
v9fs_qid2ino(&curdirent.qid),
|
||
|
curdirent.d_type))
|
||
|
return 0;
|
||
|
|
||
|
ctx->pos = curdirent.d_off;
|
||
|
rdir->head += err;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* v9fs_dir_release - called on a close of a file or directory
|
||
|
* @inode: inode of the directory
|
||
|
* @filp: file pointer to a directory
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
int v9fs_dir_release(struct inode *inode, struct file *filp)
|
||
|
{
|
||
|
struct v9fs_inode *v9inode = V9FS_I(inode);
|
||
|
struct p9_fid *fid;
|
||
|
__le32 version;
|
||
|
loff_t i_size;
|
||
|
int retval = 0;
|
||
|
|
||
|
fid = filp->private_data;
|
||
|
p9_debug(P9_DEBUG_VFS, "inode: %p filp: %p fid: %d\n",
|
||
|
inode, filp, fid ? fid->fid : -1);
|
||
|
if (fid) {
|
||
|
spin_lock(&inode->i_lock);
|
||
|
hlist_del(&fid->ilist);
|
||
|
spin_unlock(&inode->i_lock);
|
||
|
retval = p9_fid_put(fid);
|
||
|
}
|
||
|
|
||
|
if ((filp->f_mode & FMODE_WRITE)) {
|
||
|
version = cpu_to_le32(v9inode->qid.version);
|
||
|
i_size = i_size_read(inode);
|
||
|
fscache_unuse_cookie(v9fs_inode_cookie(v9inode),
|
||
|
&version, &i_size);
|
||
|
} else {
|
||
|
fscache_unuse_cookie(v9fs_inode_cookie(v9inode), NULL, NULL);
|
||
|
}
|
||
|
return retval;
|
||
|
}
|
||
|
|
||
|
const struct file_operations v9fs_dir_operations = {
|
||
|
.read = generic_read_dir,
|
||
|
.llseek = generic_file_llseek,
|
||
|
.iterate_shared = v9fs_dir_readdir,
|
||
|
.open = v9fs_file_open,
|
||
|
.release = v9fs_dir_release,
|
||
|
};
|
||
|
|
||
|
const struct file_operations v9fs_dir_operations_dotl = {
|
||
|
.read = generic_read_dir,
|
||
|
.llseek = generic_file_llseek,
|
||
|
.iterate_shared = v9fs_dir_readdir_dotl,
|
||
|
.open = v9fs_file_open,
|
||
|
.release = v9fs_dir_release,
|
||
|
.fsync = v9fs_file_fsync_dotl,
|
||
|
};
|