#include <sys/cdefs.h>
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/file.h>
#include <sys/filedesc.h>
#include <sys/stat.h>
#include <sys/buf.h>
#include <sys/proc.h>
#include <sys/mount.h>
#include <sys/namei.h>
#include <sys/vnode.h>
#include <sys/ioctl.h>
#include <sys/tty.h>
#include <sys/malloc.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <mach/mach_types.h>
#include <mach/vm_prot.h>
#include <mach/vm_inherit.h>
#include <mach/kern_return.h>
#include <mach/memory_object_control.h>
#define PSHMNAMLEN 31
struct pshminfo {
unsigned int pshm_flags;
unsigned int pshm_usecount;
off_t pshm_length;
mode_t pshm_mode;
uid_t pshm_uid;
gid_t pshm_gid;
char pshm_name[PSHMNAMLEN + 1];
void * pshm_memobject;
#if DIAGNOSTIC
unsigned int pshm_readcount;
unsigned int pshm_writecount;
struct proc * pshm_proc;
#endif
};
#define PSHMINFO_NULL (struct pshminfo *)0
#define PSHM_NONE 1
#define PSHM_DEFINED 2
#define PSHM_ALLOCATED 4
#define PSHM_MAPPED 8
#define PSHM_INUSE 0x10
#define PSHM_REMOVED 0x20
#define PSHM_INCREATE 0x40
#define PSHM_INDELETE 0x80
struct pshmcache {
LIST_ENTRY(pshmcache) pshm_hash;
struct pshminfo *pshminfo;
int pshm_nlen;
char pshm_name[PSHMNAMLEN + 1];
};
#define PSHMCACHE_NULL (struct pshmcache *)0
struct pshmstats {
long goodhits;
long neghits;
long badhits;
long falsehits;
long miss;
long longnames;
};
struct pshmname {
char *pshm_nameptr;
long pshm_namelen;
u_long pshm_hash;
};
struct pshmnode {
off_t mapp_addr;
size_t map_size;
struct pshminfo *pinfo;
unsigned int pshm_usecount;
#if DIAGNOSTIC
unsigned int readcnt;
unsigned int writecnt;
#endif
};
#define PSHMNODE_NULL (struct pshmnode *)0
#define PSHMHASH(pnp) \
(&pshmhashtbl[(pnp)->pshm_hash & pshmhash])
LIST_HEAD(pshmhashhead, pshmcache) *pshmhashtbl;
u_long pshmhash;
long pshmnument;
struct pshmstats pshmstats;
int pshm_read __P((struct file *fp, struct uio *uio,
struct ucred *cred));
int pshm_write __P((struct file *fp, struct uio *uio,
struct ucred *cred));
int pshm_ioctl __P((struct file *fp, u_long com,
caddr_t data, struct proc *p));
int pshm_select __P((struct file *fp, int which,
struct proc *p));
int pshm_closefile __P((struct file *fp, struct proc *p));
struct fileops pshmops =
{ pshm_read, pshm_write, pshm_ioctl, pshm_select, pshm_closefile };
int
pshm_cache_search(pshmp, pnp, pcache)
struct pshminfo **pshmp;
struct pshmname *pnp;
struct pshmcache **pcache;
{
register struct pshmcache *pcp, *nnp;
register struct pshmhashhead *pcpp;
if (pnp->pshm_namelen > PSHMNAMLEN) {
pshmstats.longnames++;
return (0);
}
pcpp = PSHMHASH(pnp);
for (pcp = pcpp->lh_first; pcp != 0; pcp = nnp) {
nnp = pcp->pshm_hash.le_next;
if (pcp->pshm_nlen == pnp->pshm_namelen &&
!bcmp(pcp->pshm_name, pnp->pshm_nameptr, (u_int)pcp-> pshm_nlen))
break;
}
if (pcp == 0) {
pshmstats.miss++;
return (0);
}
if (pcp->pshminfo) {
pshmstats.goodhits++;
*pshmp = pcp->pshminfo;
*pcache = pcp;
return (-1);
}
pshmstats.neghits++;
return (ENOENT);
}
int
pshm_cache_add(pshmp, pnp)
struct pshminfo *pshmp;
struct pshmname *pnp;
{
register struct pshmcache *pcp;
register struct pshmhashhead *pcpp;
register struct pshminfo *dpinfo;
register struct pshmcache *dpcp;
#if DIAGNOSTIC
if (pnp->pshm_namelen > NCHNAMLEN)
panic("cache_enter: name too long");
#endif
pcp = (struct pshmcache *)_MALLOC(sizeof(struct pshmcache), M_SHM, M_WAITOK);
if (pshm_cache_search(&dpinfo, pnp, &dpcp) == -1) {
_FREE(pcp, M_SHM);
return(EEXIST);
}
pshmnument++;
bzero(pcp, sizeof(struct pshmcache));
pcp->pshminfo = pshmp;
pcp->pshm_nlen = pnp->pshm_namelen;
bcopy(pnp->pshm_nameptr, pcp->pshm_name, (unsigned)pcp->pshm_nlen);
pcpp = PSHMHASH(pnp);
#if DIAGNOSTIC
{
register struct pshmcache *p;
for (p = pcpp->lh_first; p != 0; p = p->pshm_hash.le_next)
if (p == pcp)
panic("cache_enter: duplicate");
}
#endif
LIST_INSERT_HEAD(pcpp, pcp, pshm_hash);
return(0);
}
void
pshm_cache_init()
{
pshmhashtbl = hashinit(desiredvnodes, M_SHM, &pshmhash);
}
void
pshm_cache_purge(void)
{
struct pshmcache *pcp;
struct pshmhashhead *pcpp;
for (pcpp = &pshmhashtbl[pshmhash]; pcpp >= pshmhashtbl; pcpp--) {
while (pcp = pcpp->lh_first)
pshm_cache_delete(pcp);
}
}
pshm_cache_delete(pcp)
struct pshmcache *pcp;
{
#if DIAGNOSTIC
if (pcp->pshm_hash.le_prev == 0)
panic("namecache purge le_prev");
if (pcp->pshm_hash.le_next == pcp)
panic("namecache purge le_next");
#endif
LIST_REMOVE(pcp, pshm_hash);
pcp->pshm_hash.le_prev = 0;
pshmnument--;
}
struct shm_open_args {
const char *name;
int oflag;
int mode;
};
int
shm_open(p, uap, retval)
struct proc *p;
register struct shm_open_args *uap;
register_t *retval;
{
register struct filedesc *fdp = p->p_fd;
register struct file *fp;
register struct vnode *vp;
int i;
struct file *nfp;
int type, indx, error;
struct pshmname nd;
struct pshminfo *pinfo;
extern struct fileops pshmops;
char * pnbuf;
char * nameptr;
char * cp;
size_t pathlen, plen;
int fmode ;
int cmode = uap->mode;
int incache = 0;
struct pshmnode * pnode = PSHMNODE_NULL;
struct pshmcache * pcache = PSHMCACHE_NULL;
pinfo = PSHMINFO_NULL;
MALLOC_ZONE(pnbuf, caddr_t,
MAXPATHLEN, M_NAMEI, M_WAITOK);
pathlen = MAXPATHLEN;
error = copyinstr(uap->name, pnbuf,
MAXPATHLEN, &pathlen);
if (error) {
goto bad;
}
if (pathlen > PSHMNAMLEN) {
error = ENAMETOOLONG;
goto bad;
}
#ifdef PSXSHM_NAME_RESTRICT
nameptr = pnbuf;
if (*nameptr == '/') {
while (*(nameptr++) == '/') {
plen--;
error = EINVAL;
goto bad;
}
} else {
error = EINVAL;
goto bad;
}
#endif
plen = pathlen;
nameptr = pnbuf;
nd.pshm_nameptr = nameptr;
nd.pshm_namelen = plen;
nd. pshm_hash =0;
for (cp = nameptr, i=1; *cp != 0 && i <= plen; i++, cp++) {
nd.pshm_hash += (unsigned char)*cp * i;
}
error = pshm_cache_search(&pinfo, &nd, &pcache);
if (error == ENOENT) {
error = EINVAL;
goto bad;
}
if (!error) {
incache = 0;
} else
incache = 1;
fmode = FFLAGS(uap->oflag);
if ((fmode & (FREAD | FWRITE))==0)
return(EINVAL);
if (error = falloc(p, &nfp, &indx))
return (error);
fp = nfp;
cmode &= ALLPERMS;
if (fmode & O_CREAT) {
if ((fmode & O_EXCL) && incache) {
#if notyet
if (pinfo->pshm_flags & PSHM_INDELETE) {
}
#endif
error = EEXIST;
goto bad;
}
if (!incache) {
pinfo = (struct pshminfo *)_MALLOC(sizeof(struct pshminfo), M_SHM, M_WAITOK);
bzero(pinfo, sizeof(struct pshminfo));
pinfo->pshm_flags = PSHM_DEFINED | PSHM_INCREATE;
pinfo->pshm_usecount = 1;
pinfo->pshm_mode = cmode;
pinfo->pshm_uid = p->p_ucred->cr_uid;
pinfo->pshm_gid = p->p_ucred->cr_gid;
} else {
if( pinfo->pshm_flags & PSHM_INDELETE) {
error = ENOENT;
goto bad;
}
if (error = pshm_access(pinfo, fmode, p->p_ucred, p))
goto bad;
}
} else {
if (!incache) {
error = ENOENT;
goto bad;
}
if( pinfo->pshm_flags & PSHM_INDELETE) {
error = ENOENT;
goto bad;
}
if (error = pshm_access(pinfo, fmode, p->p_ucred, p))
goto bad;
}
if (fmode & O_TRUNC) {
error = EINVAL;
goto bad1;
}
#if DIAGNOSTIC
if (fmode & FWRITE)
pinfo->pshm_writecount++;
if (fmode & FREAD)
pinfo->pshm_readcount++;
#endif
pnode = (struct pshmnode *)_MALLOC(sizeof(struct pshmnode), M_SHM, M_WAITOK);
bzero(pnode, sizeof(struct pshmnode));
if (!incache) {
if (error = pshm_cache_add(pinfo, &nd)) {
goto bad2;
}
}
pinfo->pshm_flags &= ~PSHM_INCREATE;
pinfo->pshm_usecount++;
pnode->pinfo = pinfo;
fp->f_flag = fmode & FMASK;
fp->f_type = DTYPE_PSXSHM;
fp->f_ops = &pshmops;
fp->f_data = (caddr_t)pnode;
*fdflags(p, indx) &= ~UF_RESERVED;
*retval = indx;
_FREE_ZONE(pnbuf, MAXPATHLEN, M_NAMEI);
return (0);
bad2:
_FREE(pnode, M_SHM);
bad1:
_FREE(pinfo, M_SHM);
bad:
_FREE_ZONE(pnbuf, MAXPATHLEN, M_NAMEI);
return (error);
}
int
pshm_truncate(p, fp, fd, length, retval)
struct proc *p;
struct file *fp;
int fd;
off_t length;
register_t *retval;
{
struct pshminfo * pinfo;
struct pshmnode * pnode ;
kern_return_t kret;
vm_offset_t user_addr;
void * mem_object;
vm_size_t size;
if (fp->f_type != DTYPE_PSXSHM) {
return(EINVAL);
}
if (((pnode = (struct pshmnode *)fp->f_data)) == PSHMNODE_NULL )
return(EINVAL);
if ((pinfo = pnode->pinfo) == PSHMINFO_NULL)
return(EINVAL);
if ((pinfo->pshm_flags & (PSHM_DEFINED | PSHM_ALLOCATED))
!= PSHM_DEFINED) {
return(EINVAL);
}
size = round_page (length);
kret = vm_allocate(current_map(), &user_addr, size, TRUE);
if (kret != KERN_SUCCESS)
goto out;
kret = mach_make_memory_entry (current_map(), &size,
user_addr, VM_PROT_DEFAULT, &mem_object, 0);
if (kret != KERN_SUCCESS)
goto out;
vm_deallocate(current_map(), user_addr, size);
pinfo->pshm_flags &= ~PSHM_DEFINED;
pinfo->pshm_flags = PSHM_ALLOCATED;
pinfo->pshm_memobject = mem_object;
pinfo->pshm_length = size;
return(0);
out:
switch (kret) {
case KERN_INVALID_ADDRESS:
case KERN_NO_SPACE:
return (ENOMEM);
case KERN_PROTECTION_FAILURE:
return (EACCES);
default:
return (EINVAL);
}
}
int
pshm_stat(pnode, sb)
struct pshmnode *pnode;
struct stat *sb;
{
struct pshminfo *pinfo;
if ((pinfo = pnode->pinfo) == PSHMINFO_NULL)
return(EINVAL);
bzero(sb, sizeof(struct stat));
sb->st_mode = pinfo->pshm_mode;
sb->st_uid = pinfo->pshm_uid;
sb->st_gid = pinfo->pshm_gid;
sb->st_size = pinfo->pshm_length;
return(0);
}
int
pshm_access(struct pshminfo *pinfo, int mode, struct ucred *cred, struct proc *p)
{
mode_t mask;
register gid_t *gp;
int i, error;
if (cred->cr_uid == 0)
return (0);
mask = 0;
if (cred->cr_uid == pinfo->pshm_uid) {
if (mode & FREAD)
mask |= S_IRUSR;
if (mode & FWRITE)
mask |= S_IWUSR;
return ((pinfo->pshm_mode & mask) == mask ? 0 : EACCES);
}
for (i = 0, gp = cred->cr_groups; i < cred->cr_ngroups; i++, gp++)
if (pinfo->pshm_gid == *gp) {
if (mode & FREAD)
mask |= S_IRGRP;
if (mode & FWRITE)
mask |= S_IWGRP;
return ((pinfo->pshm_mode & mask) == mask ? 0 : EACCES);
}
if (mode & FREAD)
mask |= S_IROTH;
if (mode & FWRITE)
mask |= S_IWOTH;
return ((pinfo->pshm_mode & mask) == mask ? 0 : EACCES);
}
struct mmap_args {
caddr_t addr;
size_t len;
int prot;
int flags;
int fd;
#ifdef DOUBLE_ALIGN_PARAMS
long pad;
#endif
off_t pos;
};
int
pshm_mmap(struct proc *p, struct mmap_args *uap, register_t *retval, struct file *fp, vm_size_t pageoff)
{
vm_offset_t user_addr = uap->addr;
vm_size_t user_size = uap->len ;
int prot = uap->prot;
int flags = uap->flags;
vm_object_offset_t file_pos = (vm_object_offset_t)uap->pos;
int fd = uap->fd;
vm_map_t user_map;
boolean_t find_space,docow;
kern_return_t kret;
struct pshminfo * pinfo;
struct pshmnode * pnode;
void * mem_object;
if (user_size == 0)
return(0);
if ((flags & MAP_SHARED) == 0)
return(EINVAL);
if ((prot & PROT_WRITE) && ((fp->f_flag & FWRITE) == 0)) {
return(EPERM);
}
if (((pnode = (struct pshmnode *)fp->f_data)) == PSHMNODE_NULL )
return(EINVAL);
if ((pinfo = pnode->pinfo) == PSHMINFO_NULL)
return(EINVAL);
if ((pinfo->pshm_flags & PSHM_ALLOCATED) != PSHM_ALLOCATED) {
return(EINVAL);
}
if (user_size > pinfo->pshm_length) {
return(EINVAL);
}
if ((off_t)user_size + file_pos > pinfo->pshm_length) {
return(EINVAL);
}
if ((mem_object = pinfo->pshm_memobject) == NULL) {
return(EINVAL);
}
user_map = current_map();
if ((flags & MAP_FIXED) == 0) {
find_space = TRUE;
user_addr = round_page(user_addr);
} else {
if (user_addr != trunc_page(user_addr))
return (EINVAL);
find_space = FALSE;
(void) vm_deallocate(user_map, user_addr, user_size);
}
docow = FALSE;
kret = vm_map_64(user_map, &user_addr, user_size,
0, find_space, pinfo->pshm_memobject, file_pos, docow,
prot, VM_PROT_DEFAULT,
VM_INHERIT_DEFAULT);
if (kret != KERN_SUCCESS)
goto out;
kret = vm_inherit(user_map, user_addr, user_size,
VM_INHERIT_SHARE);
if (kret != KERN_SUCCESS) {
(void) vm_deallocate(user_map, user_addr, user_size);
goto out;
}
pnode->mapp_addr = user_addr;
pnode->map_size = user_size;
pinfo->pshm_flags |= (PSHM_MAPPED | PSHM_INUSE);
out:
switch (kret) {
case KERN_SUCCESS:
*fdflags(p, fd) |= UF_MAPPED;
*retval = (register_t)(user_addr + pageoff);
return (0);
case KERN_INVALID_ADDRESS:
case KERN_NO_SPACE:
return (ENOMEM);
case KERN_PROTECTION_FAILURE:
return (EACCES);
default:
return (EINVAL);
}
}
struct shm_unlink_args {
const char *name;
};
int
shm_unlink(p, uap, retval)
struct proc *p;
register struct shm_unlink_args *uap;
register_t *retval;
{
register struct filedesc *fdp = p->p_fd;
register struct file *fp;
int flags, i;
int error=0;
struct pshmname nd;
struct pshminfo *pinfo;
extern struct fileops pshmops;
char * pnbuf;
char * nameptr;
char * cp;
size_t pathlen, plen;
int fmode, cmode ;
int incache = 0;
struct pshmnode * pnode = PSHMNODE_NULL;
struct pshmcache *pcache = PSHMCACHE_NULL;
kern_return_t kret;
pinfo = PSHMINFO_NULL;
MALLOC_ZONE(pnbuf, caddr_t,
MAXPATHLEN, M_NAMEI, M_WAITOK);
pathlen = MAXPATHLEN;
error = copyinstr(uap->name, pnbuf,
MAXPATHLEN, &pathlen);
if (error) {
goto bad;
}
if (pathlen > PSHMNAMLEN) {
error = ENAMETOOLONG;
goto bad;
}
#ifdef PSXSHM_NAME_RESTRICT
nameptr = pnbuf;
if (*nameptr == '/') {
while (*(nameptr++) == '/') {
plen--;
error = EINVAL;
goto bad;
}
} else {
error = EINVAL;
goto bad;
}
#endif
plen = pathlen;
nameptr = pnbuf;
nd.pshm_nameptr = nameptr;
nd.pshm_namelen = plen;
nd. pshm_hash =0;
for (cp = nameptr, i=1; *cp != 0 && i <= plen; i++, cp++) {
nd.pshm_hash += (unsigned char)*cp * i;
}
error = pshm_cache_search(&pinfo, &nd, &pcache);
if (error == ENOENT) {
error = EINVAL;
goto bad;
}
if (!error) {
error = EINVAL;
goto bad;
} else
incache = 1;
if ((pinfo->pshm_flags & (PSHM_DEFINED | PSHM_ALLOCATED))==0) {
return (EINVAL);
}
if (pinfo->pshm_flags & PSHM_INDELETE) {
error = 0;
goto bad;
}
if (pinfo->pshm_memobject == NULL) {
error = EINVAL;
goto bad;
}
pinfo->pshm_flags |= PSHM_INDELETE;
pinfo->pshm_usecount--;
kret = mach_destroy_memory_entry(pinfo->pshm_memobject);
pshm_cache_delete(pcache);
_FREE(pcache, M_SHM);
pinfo->pshm_flags |= PSHM_REMOVED;
error = 0;
bad:
_FREE_ZONE(pnbuf, MAXPATHLEN, M_NAMEI);
return (error);
out:
switch (kret) {
case KERN_INVALID_ADDRESS:
case KERN_PROTECTION_FAILURE:
return (EACCES);
default:
return (EINVAL);
}
}
int
pshm_closefile(fp, p)
struct file *fp;
struct proc *p;
{
return (pshm_close(((struct pshmnode *)fp->f_data), fp->f_flag,
fp->f_cred, p));
}
int
pshm_close(pnode, flags, cred, p)
register struct pshmnode *pnode;
int flags;
struct ucred *cred;
struct proc *p;
{
int error=0;
kern_return_t kret;
register struct pshminfo *pinfo;
if ((pinfo = pnode->pinfo) == PSHMINFO_NULL)
return(EINVAL);
if ((pinfo->pshm_flags & PSHM_ALLOCATED) != PSHM_ALLOCATED) {
return(EINVAL);
}
#if DIAGNOSTIC
if(!pinfo->pshm_usecount) {
kprintf("negative usecount in pshm_close\n");
}
#endif
pinfo->pshm_usecount--;
if ((pinfo->pshm_flags & PSHM_REMOVED) && !pinfo->pshm_usecount) {
_FREE(pinfo,M_SHM);
}
_FREE(pnode, M_SHM);
return (error);
}
int
pshm_read(struct file *fp, struct uio *uio, struct ucred *cred)
{
return(EOPNOTSUPP);
}
int
pshm_write(struct file *fp, struct uio *uio, struct ucred *cred)
{
return(EOPNOTSUPP);
}
int
pshm_ioctl(struct file *fp, u_long com, caddr_t data, struct proc *p)
{
return(EOPNOTSUPP);
}
int
pshm_select(struct file *fp, int which, struct proc *p)
{
return(EOPNOTSUPP);
}