mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-08-23 17:30:02 +00:00
![Kees Cook](/assets/img/avatar_default.png)
GCC does not like having a partially allocated object, since it cannot reason about it for bounds checking when it is passed to other code. Instead, fully allocate sig_inputArgs. (Alternatively, sig_inputArgs should be defined as a struct coda_in_hdr, if it is actually not using any other part of the union.) Seen under GCC 13: ../fs/coda/upcall.c: In function 'coda_upcall': ../fs/coda/upcall.c:801:22: warning: array subscript 'union inputArgs[0]' is partly outside array bounds of 'unsigned char[20]' [-Warray-bounds=] 801 | sig_inputArgs->ih.opcode = CODA_SIGNAL; | ^~ Cc: Jan Harkes <jaharkes@cs.cmu.edu> Cc: coda@cs.cmu.edu Cc: codalist@coda.cs.cmu.edu Signed-off-by: Kees Cook <keescook@chromium.org> Link: https://lore.kernel.org/r/20230127223921.never.882-kees@kernel.org
961 lines
24 KiB
C
961 lines
24 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Mostly platform independent upcall operations to Venus:
|
|
* -- upcalls
|
|
* -- upcall routines
|
|
*
|
|
* Linux 2.0 version
|
|
* Copyright (C) 1996 Peter J. Braam <braam@maths.ox.ac.uk>,
|
|
* Michael Callahan <callahan@maths.ox.ac.uk>
|
|
*
|
|
* Redone for Linux 2.1
|
|
* Copyright (C) 1997 Carnegie Mellon University
|
|
*
|
|
* Carnegie Mellon University encourages users of this code to contribute
|
|
* improvements to the Coda project. Contact Peter Braam <coda@cs.cmu.edu>.
|
|
*/
|
|
|
|
#include <linux/signal.h>
|
|
#include <linux/sched/signal.h>
|
|
#include <linux/types.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/mm.h>
|
|
#include <linux/time.h>
|
|
#include <linux/fs.h>
|
|
#include <linux/file.h>
|
|
#include <linux/stat.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/string.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/uaccess.h>
|
|
#include <linux/vmalloc.h>
|
|
#include <linux/vfs.h>
|
|
|
|
#include <linux/coda.h>
|
|
#include "coda_psdev.h"
|
|
#include "coda_linux.h"
|
|
#include "coda_cache.h"
|
|
|
|
#include "coda_int.h"
|
|
|
|
static int coda_upcall(struct venus_comm *vc, int inSize, int *outSize,
|
|
union inputArgs *buffer);
|
|
|
|
static void *alloc_upcall(int opcode, int size)
|
|
{
|
|
union inputArgs *inp;
|
|
|
|
inp = kvzalloc(size, GFP_KERNEL);
|
|
if (!inp)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
inp->ih.opcode = opcode;
|
|
inp->ih.pid = task_pid_nr_ns(current, &init_pid_ns);
|
|
inp->ih.pgid = task_pgrp_nr_ns(current, &init_pid_ns);
|
|
inp->ih.uid = from_kuid(&init_user_ns, current_fsuid());
|
|
|
|
return (void*)inp;
|
|
}
|
|
|
|
#define UPARG(op)\
|
|
do {\
|
|
inp = (union inputArgs *)alloc_upcall(op, insize); \
|
|
if (IS_ERR(inp)) { return PTR_ERR(inp); }\
|
|
outp = (union outputArgs *)(inp); \
|
|
outsize = insize; \
|
|
} while (0)
|
|
|
|
#define INSIZE(tag) sizeof(struct coda_ ## tag ## _in)
|
|
#define OUTSIZE(tag) sizeof(struct coda_ ## tag ## _out)
|
|
#define SIZE(tag) max_t(unsigned int, INSIZE(tag), OUTSIZE(tag))
|
|
|
|
|
|
/* the upcalls */
|
|
int venus_rootfid(struct super_block *sb, struct CodaFid *fidp)
|
|
{
|
|
union inputArgs *inp;
|
|
union outputArgs *outp;
|
|
int insize, outsize, error;
|
|
|
|
insize = SIZE(root);
|
|
UPARG(CODA_ROOT);
|
|
|
|
error = coda_upcall(coda_vcp(sb), insize, &outsize, inp);
|
|
if (!error)
|
|
*fidp = outp->coda_root.VFid;
|
|
|
|
kvfree(inp);
|
|
return error;
|
|
}
|
|
|
|
int venus_getattr(struct super_block *sb, struct CodaFid *fid,
|
|
struct coda_vattr *attr)
|
|
{
|
|
union inputArgs *inp;
|
|
union outputArgs *outp;
|
|
int insize, outsize, error;
|
|
|
|
insize = SIZE(getattr);
|
|
UPARG(CODA_GETATTR);
|
|
inp->coda_getattr.VFid = *fid;
|
|
|
|
error = coda_upcall(coda_vcp(sb), insize, &outsize, inp);
|
|
if (!error)
|
|
*attr = outp->coda_getattr.attr;
|
|
|
|
kvfree(inp);
|
|
return error;
|
|
}
|
|
|
|
int venus_setattr(struct super_block *sb, struct CodaFid *fid,
|
|
struct coda_vattr *vattr)
|
|
{
|
|
union inputArgs *inp;
|
|
union outputArgs *outp;
|
|
int insize, outsize, error;
|
|
|
|
insize = SIZE(setattr);
|
|
UPARG(CODA_SETATTR);
|
|
|
|
inp->coda_setattr.VFid = *fid;
|
|
inp->coda_setattr.attr = *vattr;
|
|
|
|
error = coda_upcall(coda_vcp(sb), insize, &outsize, inp);
|
|
|
|
kvfree(inp);
|
|
return error;
|
|
}
|
|
|
|
int venus_lookup(struct super_block *sb, struct CodaFid *fid,
|
|
const char *name, int length, int * type,
|
|
struct CodaFid *resfid)
|
|
{
|
|
union inputArgs *inp;
|
|
union outputArgs *outp;
|
|
int insize, outsize, error;
|
|
int offset;
|
|
|
|
offset = INSIZE(lookup);
|
|
insize = max_t(unsigned int, offset + length +1, OUTSIZE(lookup));
|
|
UPARG(CODA_LOOKUP);
|
|
|
|
inp->coda_lookup.VFid = *fid;
|
|
inp->coda_lookup.name = offset;
|
|
inp->coda_lookup.flags = CLU_CASE_SENSITIVE;
|
|
/* send Venus a null terminated string */
|
|
memcpy((char *)(inp) + offset, name, length);
|
|
*((char *)inp + offset + length) = '\0';
|
|
|
|
error = coda_upcall(coda_vcp(sb), insize, &outsize, inp);
|
|
if (!error) {
|
|
*resfid = outp->coda_lookup.VFid;
|
|
*type = outp->coda_lookup.vtype;
|
|
}
|
|
|
|
kvfree(inp);
|
|
return error;
|
|
}
|
|
|
|
int venus_close(struct super_block *sb, struct CodaFid *fid, int flags,
|
|
kuid_t uid)
|
|
{
|
|
union inputArgs *inp;
|
|
union outputArgs *outp;
|
|
int insize, outsize, error;
|
|
|
|
insize = SIZE(release);
|
|
UPARG(CODA_CLOSE);
|
|
|
|
inp->ih.uid = from_kuid(&init_user_ns, uid);
|
|
inp->coda_close.VFid = *fid;
|
|
inp->coda_close.flags = flags;
|
|
|
|
error = coda_upcall(coda_vcp(sb), insize, &outsize, inp);
|
|
|
|
kvfree(inp);
|
|
return error;
|
|
}
|
|
|
|
int venus_open(struct super_block *sb, struct CodaFid *fid,
|
|
int flags, struct file **fh)
|
|
{
|
|
union inputArgs *inp;
|
|
union outputArgs *outp;
|
|
int insize, outsize, error;
|
|
|
|
insize = SIZE(open_by_fd);
|
|
UPARG(CODA_OPEN_BY_FD);
|
|
|
|
inp->coda_open_by_fd.VFid = *fid;
|
|
inp->coda_open_by_fd.flags = flags;
|
|
|
|
error = coda_upcall(coda_vcp(sb), insize, &outsize, inp);
|
|
if (!error)
|
|
*fh = outp->coda_open_by_fd.fh;
|
|
|
|
kvfree(inp);
|
|
return error;
|
|
}
|
|
|
|
int venus_mkdir(struct super_block *sb, struct CodaFid *dirfid,
|
|
const char *name, int length,
|
|
struct CodaFid *newfid, struct coda_vattr *attrs)
|
|
{
|
|
union inputArgs *inp;
|
|
union outputArgs *outp;
|
|
int insize, outsize, error;
|
|
int offset;
|
|
|
|
offset = INSIZE(mkdir);
|
|
insize = max_t(unsigned int, offset + length + 1, OUTSIZE(mkdir));
|
|
UPARG(CODA_MKDIR);
|
|
|
|
inp->coda_mkdir.VFid = *dirfid;
|
|
inp->coda_mkdir.attr = *attrs;
|
|
inp->coda_mkdir.name = offset;
|
|
/* Venus must get null terminated string */
|
|
memcpy((char *)(inp) + offset, name, length);
|
|
*((char *)inp + offset + length) = '\0';
|
|
|
|
error = coda_upcall(coda_vcp(sb), insize, &outsize, inp);
|
|
if (!error) {
|
|
*attrs = outp->coda_mkdir.attr;
|
|
*newfid = outp->coda_mkdir.VFid;
|
|
}
|
|
|
|
kvfree(inp);
|
|
return error;
|
|
}
|
|
|
|
|
|
int venus_rename(struct super_block *sb, struct CodaFid *old_fid,
|
|
struct CodaFid *new_fid, size_t old_length,
|
|
size_t new_length, const char *old_name,
|
|
const char *new_name)
|
|
{
|
|
union inputArgs *inp;
|
|
union outputArgs *outp;
|
|
int insize, outsize, error;
|
|
int offset, s;
|
|
|
|
offset = INSIZE(rename);
|
|
insize = max_t(unsigned int, offset + new_length + old_length + 8,
|
|
OUTSIZE(rename));
|
|
UPARG(CODA_RENAME);
|
|
|
|
inp->coda_rename.sourceFid = *old_fid;
|
|
inp->coda_rename.destFid = *new_fid;
|
|
inp->coda_rename.srcname = offset;
|
|
|
|
/* Venus must receive an null terminated string */
|
|
s = ( old_length & ~0x3) +4; /* round up to word boundary */
|
|
memcpy((char *)(inp) + offset, old_name, old_length);
|
|
*((char *)inp + offset + old_length) = '\0';
|
|
|
|
/* another null terminated string for Venus */
|
|
offset += s;
|
|
inp->coda_rename.destname = offset;
|
|
s = ( new_length & ~0x3) +4; /* round up to word boundary */
|
|
memcpy((char *)(inp) + offset, new_name, new_length);
|
|
*((char *)inp + offset + new_length) = '\0';
|
|
|
|
error = coda_upcall(coda_vcp(sb), insize, &outsize, inp);
|
|
|
|
kvfree(inp);
|
|
return error;
|
|
}
|
|
|
|
int venus_create(struct super_block *sb, struct CodaFid *dirfid,
|
|
const char *name, int length, int excl, int mode,
|
|
struct CodaFid *newfid, struct coda_vattr *attrs)
|
|
{
|
|
union inputArgs *inp;
|
|
union outputArgs *outp;
|
|
int insize, outsize, error;
|
|
int offset;
|
|
|
|
offset = INSIZE(create);
|
|
insize = max_t(unsigned int, offset + length + 1, OUTSIZE(create));
|
|
UPARG(CODA_CREATE);
|
|
|
|
inp->coda_create.VFid = *dirfid;
|
|
inp->coda_create.attr.va_mode = mode;
|
|
inp->coda_create.excl = excl;
|
|
inp->coda_create.mode = mode;
|
|
inp->coda_create.name = offset;
|
|
|
|
/* Venus must get null terminated string */
|
|
memcpy((char *)(inp) + offset, name, length);
|
|
*((char *)inp + offset + length) = '\0';
|
|
|
|
error = coda_upcall(coda_vcp(sb), insize, &outsize, inp);
|
|
if (!error) {
|
|
*attrs = outp->coda_create.attr;
|
|
*newfid = outp->coda_create.VFid;
|
|
}
|
|
|
|
kvfree(inp);
|
|
return error;
|
|
}
|
|
|
|
int venus_rmdir(struct super_block *sb, struct CodaFid *dirfid,
|
|
const char *name, int length)
|
|
{
|
|
union inputArgs *inp;
|
|
union outputArgs *outp;
|
|
int insize, outsize, error;
|
|
int offset;
|
|
|
|
offset = INSIZE(rmdir);
|
|
insize = max_t(unsigned int, offset + length + 1, OUTSIZE(rmdir));
|
|
UPARG(CODA_RMDIR);
|
|
|
|
inp->coda_rmdir.VFid = *dirfid;
|
|
inp->coda_rmdir.name = offset;
|
|
memcpy((char *)(inp) + offset, name, length);
|
|
*((char *)inp + offset + length) = '\0';
|
|
|
|
error = coda_upcall(coda_vcp(sb), insize, &outsize, inp);
|
|
|
|
kvfree(inp);
|
|
return error;
|
|
}
|
|
|
|
int venus_remove(struct super_block *sb, struct CodaFid *dirfid,
|
|
const char *name, int length)
|
|
{
|
|
union inputArgs *inp;
|
|
union outputArgs *outp;
|
|
int error=0, insize, outsize, offset;
|
|
|
|
offset = INSIZE(remove);
|
|
insize = max_t(unsigned int, offset + length + 1, OUTSIZE(remove));
|
|
UPARG(CODA_REMOVE);
|
|
|
|
inp->coda_remove.VFid = *dirfid;
|
|
inp->coda_remove.name = offset;
|
|
memcpy((char *)(inp) + offset, name, length);
|
|
*((char *)inp + offset + length) = '\0';
|
|
|
|
error = coda_upcall(coda_vcp(sb), insize, &outsize, inp);
|
|
|
|
kvfree(inp);
|
|
return error;
|
|
}
|
|
|
|
int venus_readlink(struct super_block *sb, struct CodaFid *fid,
|
|
char *buffer, int *length)
|
|
{
|
|
union inputArgs *inp;
|
|
union outputArgs *outp;
|
|
int insize, outsize, error;
|
|
int retlen;
|
|
char *result;
|
|
|
|
insize = max_t(unsigned int,
|
|
INSIZE(readlink), OUTSIZE(readlink)+ *length);
|
|
UPARG(CODA_READLINK);
|
|
|
|
inp->coda_readlink.VFid = *fid;
|
|
|
|
error = coda_upcall(coda_vcp(sb), insize, &outsize, inp);
|
|
if (!error) {
|
|
retlen = outp->coda_readlink.count;
|
|
if (retlen >= *length)
|
|
retlen = *length - 1;
|
|
*length = retlen;
|
|
result = (char *)outp + (long)outp->coda_readlink.data;
|
|
memcpy(buffer, result, retlen);
|
|
*(buffer + retlen) = '\0';
|
|
}
|
|
|
|
kvfree(inp);
|
|
return error;
|
|
}
|
|
|
|
|
|
|
|
int venus_link(struct super_block *sb, struct CodaFid *fid,
|
|
struct CodaFid *dirfid, const char *name, int len )
|
|
{
|
|
union inputArgs *inp;
|
|
union outputArgs *outp;
|
|
int insize, outsize, error;
|
|
int offset;
|
|
|
|
offset = INSIZE(link);
|
|
insize = max_t(unsigned int, offset + len + 1, OUTSIZE(link));
|
|
UPARG(CODA_LINK);
|
|
|
|
inp->coda_link.sourceFid = *fid;
|
|
inp->coda_link.destFid = *dirfid;
|
|
inp->coda_link.tname = offset;
|
|
|
|
/* make sure strings are null terminated */
|
|
memcpy((char *)(inp) + offset, name, len);
|
|
*((char *)inp + offset + len) = '\0';
|
|
|
|
error = coda_upcall(coda_vcp(sb), insize, &outsize, inp);
|
|
|
|
kvfree(inp);
|
|
return error;
|
|
}
|
|
|
|
int venus_symlink(struct super_block *sb, struct CodaFid *fid,
|
|
const char *name, int len,
|
|
const char *symname, int symlen)
|
|
{
|
|
union inputArgs *inp;
|
|
union outputArgs *outp;
|
|
int insize, outsize, error;
|
|
int offset, s;
|
|
|
|
offset = INSIZE(symlink);
|
|
insize = max_t(unsigned int, offset + len + symlen + 8, OUTSIZE(symlink));
|
|
UPARG(CODA_SYMLINK);
|
|
|
|
/* inp->coda_symlink.attr = *tva; XXXXXX */
|
|
inp->coda_symlink.VFid = *fid;
|
|
|
|
/* Round up to word boundary and null terminate */
|
|
inp->coda_symlink.srcname = offset;
|
|
s = ( symlen & ~0x3 ) + 4;
|
|
memcpy((char *)(inp) + offset, symname, symlen);
|
|
*((char *)inp + offset + symlen) = '\0';
|
|
|
|
/* Round up to word boundary and null terminate */
|
|
offset += s;
|
|
inp->coda_symlink.tname = offset;
|
|
s = (len & ~0x3) + 4;
|
|
memcpy((char *)(inp) + offset, name, len);
|
|
*((char *)inp + offset + len) = '\0';
|
|
|
|
error = coda_upcall(coda_vcp(sb), insize, &outsize, inp);
|
|
|
|
kvfree(inp);
|
|
return error;
|
|
}
|
|
|
|
int venus_fsync(struct super_block *sb, struct CodaFid *fid)
|
|
{
|
|
union inputArgs *inp;
|
|
union outputArgs *outp;
|
|
int insize, outsize, error;
|
|
|
|
insize=SIZE(fsync);
|
|
UPARG(CODA_FSYNC);
|
|
|
|
inp->coda_fsync.VFid = *fid;
|
|
error = coda_upcall(coda_vcp(sb), insize, &outsize, inp);
|
|
|
|
kvfree(inp);
|
|
return error;
|
|
}
|
|
|
|
int venus_access(struct super_block *sb, struct CodaFid *fid, int mask)
|
|
{
|
|
union inputArgs *inp;
|
|
union outputArgs *outp;
|
|
int insize, outsize, error;
|
|
|
|
insize = SIZE(access);
|
|
UPARG(CODA_ACCESS);
|
|
|
|
inp->coda_access.VFid = *fid;
|
|
inp->coda_access.flags = mask;
|
|
|
|
error = coda_upcall(coda_vcp(sb), insize, &outsize, inp);
|
|
|
|
kvfree(inp);
|
|
return error;
|
|
}
|
|
|
|
|
|
int venus_pioctl(struct super_block *sb, struct CodaFid *fid,
|
|
unsigned int cmd, struct PioctlData *data)
|
|
{
|
|
union inputArgs *inp;
|
|
union outputArgs *outp;
|
|
int insize, outsize, error;
|
|
int iocsize;
|
|
|
|
insize = VC_MAXMSGSIZE;
|
|
UPARG(CODA_IOCTL);
|
|
|
|
/* build packet for Venus */
|
|
if (data->vi.in_size > VC_MAXDATASIZE) {
|
|
error = -EINVAL;
|
|
goto exit;
|
|
}
|
|
|
|
if (data->vi.out_size > VC_MAXDATASIZE) {
|
|
error = -EINVAL;
|
|
goto exit;
|
|
}
|
|
|
|
inp->coda_ioctl.VFid = *fid;
|
|
|
|
/* the cmd field was mutated by increasing its size field to
|
|
* reflect the path and follow args. We need to subtract that
|
|
* out before sending the command to Venus. */
|
|
inp->coda_ioctl.cmd = (cmd & ~(PIOCPARM_MASK << 16));
|
|
iocsize = ((cmd >> 16) & PIOCPARM_MASK) - sizeof(char *) - sizeof(int);
|
|
inp->coda_ioctl.cmd |= (iocsize & PIOCPARM_MASK) << 16;
|
|
|
|
/* in->coda_ioctl.rwflag = flag; */
|
|
inp->coda_ioctl.len = data->vi.in_size;
|
|
inp->coda_ioctl.data = (char *)(INSIZE(ioctl));
|
|
|
|
/* get the data out of user space */
|
|
if (copy_from_user((char *)inp + (long)inp->coda_ioctl.data,
|
|
data->vi.in, data->vi.in_size)) {
|
|
error = -EINVAL;
|
|
goto exit;
|
|
}
|
|
|
|
error = coda_upcall(coda_vcp(sb), SIZE(ioctl) + data->vi.in_size,
|
|
&outsize, inp);
|
|
|
|
if (error) {
|
|
pr_warn("%s: Venus returns: %d for %s\n",
|
|
__func__, error, coda_f2s(fid));
|
|
goto exit;
|
|
}
|
|
|
|
if (outsize < (long)outp->coda_ioctl.data + outp->coda_ioctl.len) {
|
|
error = -EINVAL;
|
|
goto exit;
|
|
}
|
|
|
|
/* Copy out the OUT buffer. */
|
|
if (outp->coda_ioctl.len > data->vi.out_size) {
|
|
error = -EINVAL;
|
|
goto exit;
|
|
}
|
|
|
|
/* Copy out the OUT buffer. */
|
|
if (copy_to_user(data->vi.out,
|
|
(char *)outp + (long)outp->coda_ioctl.data,
|
|
outp->coda_ioctl.len)) {
|
|
error = -EFAULT;
|
|
goto exit;
|
|
}
|
|
|
|
exit:
|
|
kvfree(inp);
|
|
return error;
|
|
}
|
|
|
|
int venus_statfs(struct dentry *dentry, struct kstatfs *sfs)
|
|
{
|
|
union inputArgs *inp;
|
|
union outputArgs *outp;
|
|
int insize, outsize, error;
|
|
|
|
insize = SIZE(statfs);
|
|
UPARG(CODA_STATFS);
|
|
|
|
error = coda_upcall(coda_vcp(dentry->d_sb), insize, &outsize, inp);
|
|
if (!error) {
|
|
sfs->f_blocks = outp->coda_statfs.stat.f_blocks;
|
|
sfs->f_bfree = outp->coda_statfs.stat.f_bfree;
|
|
sfs->f_bavail = outp->coda_statfs.stat.f_bavail;
|
|
sfs->f_files = outp->coda_statfs.stat.f_files;
|
|
sfs->f_ffree = outp->coda_statfs.stat.f_ffree;
|
|
}
|
|
|
|
kvfree(inp);
|
|
return error;
|
|
}
|
|
|
|
int venus_access_intent(struct super_block *sb, struct CodaFid *fid,
|
|
bool *access_intent_supported,
|
|
size_t count, loff_t ppos, int type)
|
|
{
|
|
union inputArgs *inp;
|
|
union outputArgs *outp;
|
|
int insize, outsize, error;
|
|
bool finalizer =
|
|
type == CODA_ACCESS_TYPE_READ_FINISH ||
|
|
type == CODA_ACCESS_TYPE_WRITE_FINISH;
|
|
|
|
if (!*access_intent_supported && !finalizer)
|
|
return 0;
|
|
|
|
insize = SIZE(access_intent);
|
|
UPARG(CODA_ACCESS_INTENT);
|
|
|
|
inp->coda_access_intent.VFid = *fid;
|
|
inp->coda_access_intent.count = count;
|
|
inp->coda_access_intent.pos = ppos;
|
|
inp->coda_access_intent.type = type;
|
|
|
|
error = coda_upcall(coda_vcp(sb), insize,
|
|
finalizer ? NULL : &outsize, inp);
|
|
|
|
/*
|
|
* we have to free the request buffer for synchronous upcalls
|
|
* or when asynchronous upcalls fail, but not when asynchronous
|
|
* upcalls succeed
|
|
*/
|
|
if (!finalizer || error)
|
|
kvfree(inp);
|
|
|
|
/* Chunked access is not supported or an old Coda client */
|
|
if (error == -EOPNOTSUPP) {
|
|
*access_intent_supported = false;
|
|
error = 0;
|
|
}
|
|
return error;
|
|
}
|
|
|
|
/*
|
|
* coda_upcall and coda_downcall routines.
|
|
*/
|
|
static void coda_block_signals(sigset_t *old)
|
|
{
|
|
spin_lock_irq(¤t->sighand->siglock);
|
|
*old = current->blocked;
|
|
|
|
sigfillset(¤t->blocked);
|
|
sigdelset(¤t->blocked, SIGKILL);
|
|
sigdelset(¤t->blocked, SIGSTOP);
|
|
sigdelset(¤t->blocked, SIGINT);
|
|
|
|
recalc_sigpending();
|
|
spin_unlock_irq(¤t->sighand->siglock);
|
|
}
|
|
|
|
static void coda_unblock_signals(sigset_t *old)
|
|
{
|
|
spin_lock_irq(¤t->sighand->siglock);
|
|
current->blocked = *old;
|
|
recalc_sigpending();
|
|
spin_unlock_irq(¤t->sighand->siglock);
|
|
}
|
|
|
|
/* Don't allow signals to interrupt the following upcalls before venus
|
|
* has seen them,
|
|
* - CODA_CLOSE or CODA_RELEASE upcall (to avoid reference count problems)
|
|
* - CODA_STORE (to avoid data loss)
|
|
* - CODA_ACCESS_INTENT (to avoid reference count problems)
|
|
*/
|
|
#define CODA_INTERRUPTIBLE(r) (!coda_hard && \
|
|
(((r)->uc_opcode != CODA_CLOSE && \
|
|
(r)->uc_opcode != CODA_STORE && \
|
|
(r)->uc_opcode != CODA_ACCESS_INTENT && \
|
|
(r)->uc_opcode != CODA_RELEASE) || \
|
|
(r)->uc_flags & CODA_REQ_READ))
|
|
|
|
static inline void coda_waitfor_upcall(struct venus_comm *vcp,
|
|
struct upc_req *req)
|
|
{
|
|
DECLARE_WAITQUEUE(wait, current);
|
|
unsigned long timeout = jiffies + coda_timeout * HZ;
|
|
sigset_t old;
|
|
int blocked;
|
|
|
|
coda_block_signals(&old);
|
|
blocked = 1;
|
|
|
|
add_wait_queue(&req->uc_sleep, &wait);
|
|
for (;;) {
|
|
if (CODA_INTERRUPTIBLE(req))
|
|
set_current_state(TASK_INTERRUPTIBLE);
|
|
else
|
|
set_current_state(TASK_UNINTERRUPTIBLE);
|
|
|
|
/* got a reply */
|
|
if (req->uc_flags & (CODA_REQ_WRITE | CODA_REQ_ABORT))
|
|
break;
|
|
|
|
if (blocked && time_after(jiffies, timeout) &&
|
|
CODA_INTERRUPTIBLE(req))
|
|
{
|
|
coda_unblock_signals(&old);
|
|
blocked = 0;
|
|
}
|
|
|
|
if (signal_pending(current)) {
|
|
list_del(&req->uc_chain);
|
|
break;
|
|
}
|
|
|
|
mutex_unlock(&vcp->vc_mutex);
|
|
if (blocked)
|
|
schedule_timeout(HZ);
|
|
else
|
|
schedule();
|
|
mutex_lock(&vcp->vc_mutex);
|
|
}
|
|
if (blocked)
|
|
coda_unblock_signals(&old);
|
|
|
|
remove_wait_queue(&req->uc_sleep, &wait);
|
|
set_current_state(TASK_RUNNING);
|
|
}
|
|
|
|
|
|
/*
|
|
* coda_upcall will return an error in the case of
|
|
* failed communication with Venus _or_ will peek at Venus
|
|
* reply and return Venus' error.
|
|
*
|
|
* As venus has 2 types of errors, normal errors (positive) and internal
|
|
* errors (negative), normal errors are negated, while internal errors
|
|
* are all mapped to -EINTR, while showing a nice warning message. (jh)
|
|
*/
|
|
static int coda_upcall(struct venus_comm *vcp,
|
|
int inSize, int *outSize,
|
|
union inputArgs *buffer)
|
|
{
|
|
union outputArgs *out;
|
|
union inputArgs *sig_inputArgs;
|
|
struct upc_req *req = NULL, *sig_req;
|
|
int error;
|
|
|
|
mutex_lock(&vcp->vc_mutex);
|
|
|
|
if (!vcp->vc_inuse) {
|
|
pr_notice("Venus dead, not sending upcall\n");
|
|
error = -ENXIO;
|
|
goto exit;
|
|
}
|
|
|
|
/* Format the request message. */
|
|
req = kmalloc(sizeof(struct upc_req), GFP_KERNEL);
|
|
if (!req) {
|
|
error = -ENOMEM;
|
|
goto exit;
|
|
}
|
|
|
|
buffer->ih.unique = ++vcp->vc_seq;
|
|
|
|
req->uc_data = (void *)buffer;
|
|
req->uc_flags = outSize ? 0 : CODA_REQ_ASYNC;
|
|
req->uc_inSize = inSize;
|
|
req->uc_outSize = (outSize && *outSize) ? *outSize : inSize;
|
|
req->uc_opcode = buffer->ih.opcode;
|
|
req->uc_unique = buffer->ih.unique;
|
|
init_waitqueue_head(&req->uc_sleep);
|
|
|
|
/* Append msg to pending queue and poke Venus. */
|
|
list_add_tail(&req->uc_chain, &vcp->vc_pending);
|
|
wake_up_interruptible(&vcp->vc_waitq);
|
|
|
|
/* We can return early on asynchronous requests */
|
|
if (outSize == NULL) {
|
|
mutex_unlock(&vcp->vc_mutex);
|
|
return 0;
|
|
}
|
|
|
|
/* We can be interrupted while we wait for Venus to process
|
|
* our request. If the interrupt occurs before Venus has read
|
|
* the request, we dequeue and return. If it occurs after the
|
|
* read but before the reply, we dequeue, send a signal
|
|
* message, and return. If it occurs after the reply we ignore
|
|
* it. In no case do we want to restart the syscall. If it
|
|
* was interrupted by a venus shutdown (psdev_close), return
|
|
* ENODEV. */
|
|
|
|
/* Go to sleep. Wake up on signals only after the timeout. */
|
|
coda_waitfor_upcall(vcp, req);
|
|
|
|
/* Op went through, interrupt or not... */
|
|
if (req->uc_flags & CODA_REQ_WRITE) {
|
|
out = (union outputArgs *)req->uc_data;
|
|
/* here we map positive Venus errors to kernel errors */
|
|
error = -out->oh.result;
|
|
*outSize = req->uc_outSize;
|
|
goto exit;
|
|
}
|
|
|
|
error = -EINTR;
|
|
if ((req->uc_flags & CODA_REQ_ABORT) || !signal_pending(current)) {
|
|
pr_warn("Unexpected interruption.\n");
|
|
goto exit;
|
|
}
|
|
|
|
/* Interrupted before venus read it. */
|
|
if (!(req->uc_flags & CODA_REQ_READ))
|
|
goto exit;
|
|
|
|
/* Venus saw the upcall, make sure we can send interrupt signal */
|
|
if (!vcp->vc_inuse) {
|
|
pr_info("Venus dead, not sending signal.\n");
|
|
goto exit;
|
|
}
|
|
|
|
error = -ENOMEM;
|
|
sig_req = kmalloc(sizeof(struct upc_req), GFP_KERNEL);
|
|
if (!sig_req) goto exit;
|
|
|
|
sig_inputArgs = kvzalloc(sizeof(*sig_inputArgs), GFP_KERNEL);
|
|
if (!sig_inputArgs) {
|
|
kfree(sig_req);
|
|
goto exit;
|
|
}
|
|
|
|
error = -EINTR;
|
|
sig_inputArgs->ih.opcode = CODA_SIGNAL;
|
|
sig_inputArgs->ih.unique = req->uc_unique;
|
|
|
|
sig_req->uc_flags = CODA_REQ_ASYNC;
|
|
sig_req->uc_opcode = sig_inputArgs->ih.opcode;
|
|
sig_req->uc_unique = sig_inputArgs->ih.unique;
|
|
sig_req->uc_data = (void *)sig_inputArgs;
|
|
sig_req->uc_inSize = sizeof(struct coda_in_hdr);
|
|
sig_req->uc_outSize = sizeof(struct coda_in_hdr);
|
|
|
|
/* insert at head of queue! */
|
|
list_add(&(sig_req->uc_chain), &vcp->vc_pending);
|
|
wake_up_interruptible(&vcp->vc_waitq);
|
|
|
|
exit:
|
|
kfree(req);
|
|
mutex_unlock(&vcp->vc_mutex);
|
|
return error;
|
|
}
|
|
|
|
/*
|
|
The statements below are part of the Coda opportunistic
|
|
programming -- taken from the Mach/BSD kernel code for Coda.
|
|
You don't get correct semantics by stating what needs to be
|
|
done without guaranteeing the invariants needed for it to happen.
|
|
When will be have time to find out what exactly is going on? (pjb)
|
|
*/
|
|
|
|
|
|
/*
|
|
* There are 7 cases where cache invalidations occur. The semantics
|
|
* of each is listed here:
|
|
*
|
|
* CODA_FLUSH -- flush all entries from the name cache and the cnode cache.
|
|
* CODA_PURGEUSER -- flush all entries from the name cache for a specific user
|
|
* This call is a result of token expiration.
|
|
*
|
|
* The next arise as the result of callbacks on a file or directory.
|
|
* CODA_ZAPFILE -- flush the cached attributes for a file.
|
|
|
|
* CODA_ZAPDIR -- flush the attributes for the dir and
|
|
* force a new lookup for all the children
|
|
of this dir.
|
|
|
|
*
|
|
* The next is a result of Venus detecting an inconsistent file.
|
|
* CODA_PURGEFID -- flush the attribute for the file
|
|
* purge it and its children from the dcache
|
|
*
|
|
* The last allows Venus to replace local fids with global ones
|
|
* during reintegration.
|
|
*
|
|
* CODA_REPLACE -- replace one CodaFid with another throughout the name cache */
|
|
|
|
int coda_downcall(struct venus_comm *vcp, int opcode, union outputArgs *out,
|
|
size_t nbytes)
|
|
{
|
|
struct inode *inode = NULL;
|
|
struct CodaFid *fid = NULL, *newfid;
|
|
struct super_block *sb;
|
|
|
|
/*
|
|
* Make sure we have received enough data from the cache
|
|
* manager to populate the necessary fields in the buffer
|
|
*/
|
|
switch (opcode) {
|
|
case CODA_PURGEUSER:
|
|
if (nbytes < sizeof(struct coda_purgeuser_out))
|
|
return -EINVAL;
|
|
break;
|
|
|
|
case CODA_ZAPDIR:
|
|
if (nbytes < sizeof(struct coda_zapdir_out))
|
|
return -EINVAL;
|
|
break;
|
|
|
|
case CODA_ZAPFILE:
|
|
if (nbytes < sizeof(struct coda_zapfile_out))
|
|
return -EINVAL;
|
|
break;
|
|
|
|
case CODA_PURGEFID:
|
|
if (nbytes < sizeof(struct coda_purgefid_out))
|
|
return -EINVAL;
|
|
break;
|
|
|
|
case CODA_REPLACE:
|
|
if (nbytes < sizeof(struct coda_replace_out))
|
|
return -EINVAL;
|
|
break;
|
|
}
|
|
|
|
/* Handle invalidation requests. */
|
|
mutex_lock(&vcp->vc_mutex);
|
|
sb = vcp->vc_sb;
|
|
if (!sb || !sb->s_root)
|
|
goto unlock_out;
|
|
|
|
switch (opcode) {
|
|
case CODA_FLUSH:
|
|
coda_cache_clear_all(sb);
|
|
shrink_dcache_sb(sb);
|
|
if (d_really_is_positive(sb->s_root))
|
|
coda_flag_inode(d_inode(sb->s_root), C_FLUSH);
|
|
break;
|
|
|
|
case CODA_PURGEUSER:
|
|
coda_cache_clear_all(sb);
|
|
break;
|
|
|
|
case CODA_ZAPDIR:
|
|
fid = &out->coda_zapdir.CodaFid;
|
|
break;
|
|
|
|
case CODA_ZAPFILE:
|
|
fid = &out->coda_zapfile.CodaFid;
|
|
break;
|
|
|
|
case CODA_PURGEFID:
|
|
fid = &out->coda_purgefid.CodaFid;
|
|
break;
|
|
|
|
case CODA_REPLACE:
|
|
fid = &out->coda_replace.OldFid;
|
|
break;
|
|
}
|
|
if (fid)
|
|
inode = coda_fid_to_inode(fid, sb);
|
|
|
|
unlock_out:
|
|
mutex_unlock(&vcp->vc_mutex);
|
|
|
|
if (!inode)
|
|
return 0;
|
|
|
|
switch (opcode) {
|
|
case CODA_ZAPDIR:
|
|
coda_flag_inode_children(inode, C_PURGE);
|
|
coda_flag_inode(inode, C_VATTR);
|
|
break;
|
|
|
|
case CODA_ZAPFILE:
|
|
coda_flag_inode(inode, C_VATTR);
|
|
break;
|
|
|
|
case CODA_PURGEFID:
|
|
coda_flag_inode_children(inode, C_PURGE);
|
|
|
|
/* catch the dentries later if some are still busy */
|
|
coda_flag_inode(inode, C_PURGE);
|
|
d_prune_aliases(inode);
|
|
break;
|
|
|
|
case CODA_REPLACE:
|
|
newfid = &out->coda_replace.NewFid;
|
|
coda_replace_fid(inode, fid, newfid);
|
|
break;
|
|
}
|
|
iput(inode);
|
|
return 0;
|
|
}
|