2021-10-11 18:20:23 -03:00

683 lines
17 KiB
C

/* @(#)tmp_tnode.c 1.1 92/07/30 SMI */
#include <sys/types.h>
#include <sys/param.h>
#include <sys/time.h>
#include <sys/proc.h>
#include <sys/ucred.h>
#include <sys/user.h>
#include <sys/time.h>
#include <sys/vfs.h>
#include <sys/vnode.h>
#include <sys/errno.h>
#include <sys/kmem_alloc.h>
#include <debug/debug.h>
#include <vm/seg.h>
#include <vm/seg_map.h>
#include <vm/anon.h>
#include <vm/page.h>
#include <tmpfs/tmp.h>
#include <tmpfs/tmpnode.h>
#include <tmpfs/tmpdir.h>
#include <sys/debug.h>
static struct anon_map *tmpamap_freelist;
static int tmpamap_freeincr = 4;
extern func_t caller();
extern int tmp_anonmem;
extern int tmp_files;
extern int tmp_anonalloc;
struct tmpnode *
tmpnode_alloc(tm, type)
struct tmount *tm;
enum vtype type;
{
struct tmpnode *t;
extern struct vnodeops tmp_vnodeops;
extern long tmpimapalloc();
#ifdef TMPFSDEBUG
if (tmpfsdebug || tmpdebugalloc)
printf("tmpnode_alloc: tm %x type %d\n", tm, type);
#endif TMPFSDEBUG
t = (struct tmpnode *)tmp_memalloc(tm, sizeof (struct tmpnode));
if (t == NULL)
return (NULL);
/*
* Muck with the doubly linked lists, if appropriate
*/
if (tm->tm_rootnode != (struct tmpnode *)NULL) {
if ((t->tn_forw = tm->tm_rootnode->tn_forw) != NULL)
t->tn_forw->tn_back = t;
t->tn_back = tm->tm_rootnode;
tm->tm_rootnode->tn_forw = t;
}
t->tn_attr.va_nodeid = tmpimapalloc(tm);
t->tn_attr.va_type = type;
t->tn_attr.va_blocksize = PAGESIZE;
t->tn_attr.va_fsid = 0xFFFF &
(long)makedev(vfs_fixedmajor(tm->tm_vfsp), 0xFF & tm->tm_mntno);
t->tn_vnode.v_vfsp = tm->tm_vfsp;
t->tn_vnode.v_type = type;
t->tn_vnode.v_data = (char *)t;
t->tn_vnode.v_op = &tmp_vnodeops;
t->tn_amapp = (struct anon_map *)new_kmem_fast_alloc(
(caddr_t *)&tmpamap_freelist, sizeof (*tmpamap_freelist),
tmpamap_freeincr, KMEM_SLEEP);
t->tn_amapp->refcnt = 1; /* keep till last link OR mapping */
t->tn_amapp->size = 0;
t->tn_amapp->swresv = 0;
t->tn_amapp->flags = 0;
t->tn_amapp->anon = (struct anon **)NULL;
switch (type) {
case VDIR:
tm->tm_directories++;
tmp_files++;
break;
case VREG:
case VBLK:
case VCHR:
case VLNK:
case VSOCK:
case VFIFO:
tm->tm_files++;
tmp_files++;
break;
default:
panic("tmpnode_alloc()- unknown file type\n");
break;
}
#ifdef TMPFSDEBUG
if (tmpfsdebug || tmpdebugalloc)
printf("tmpnode_alloc: returning tp %x\n", t);
#endif TMPFSDEBUG
tmpnode_get(t);
return (t);
}
void
tmpnode_get(tp)
struct tmpnode *tp;
{
#ifdef TMPFSDEBUG
if (tmpfsdebug || tmplockdebug)
printf("tmpnode_get: tp %x count %d caller %x\n", tp,
tp->tn_count, caller());
#endif TMPFSDEBUG
tp->tn_flags |= TREF;
#ifdef TMPFSDEBUG
if (tmplockdebug && (tp->tn_count > 0) &&
(tp->tn_owner != uniqpid()))
printf("tmpnode_get: gonna sleep on lock!\n");
#endif TMPFSDEBUG
(void) tmpnode_lock(tp);
VN_HOLD(TP_TO_VP(tp));
}
void
tmpnode_put(tp)
struct tmpnode *tp;
{
#ifdef TMPFSDEBUG
if (tmpfsdebug || tmpdirdebug || tmplockdebug)
printf("tmpnode_put: tp %x caller %x\n", tp, caller());
#endif TMPFSDEBUG
(void) tmpnode_unlock(tp);
VN_RELE(TP_TO_VP(tp));
}
tmpnode_inactive(tm, tp)
struct tmount *tm;
struct tmpnode *tp;
{
#ifdef TMPFSDEBUG
if (tmpfsdebug || tmpdirdebug || tmplockdebug)
printf("tmpnode_inactive: tp %x count %d\n", tp, tp->tn_count);
#endif TMPFSDEBUG
if (tp->tn_attr.va_nlink <= 0)
tmpnode_free(tm, tp);
else
tp->tn_flags = 0;
}
/*
* free tmpnode and all its associated anonymous memory (if any)
*/
void
tmpnode_free(tm, tp)
struct tmount *tm;
struct tmpnode *tp;
{
#ifdef TMPFSDEBUG
if (tmpfsdebug || tmpdebugalloc)
printf("tmpnode_free: tp %x nlink %d type %x\n", tp,
tp->tn_attr.va_nlink, tp->tn_attr.va_type);
#endif TMPFSDEBUG
switch (tp->tn_attr.va_type) {
case VDIR:
/* free . & .. */
ASSERT(tp->tn_dir == NULL);
tp->tn_attr.va_nlink -= 2;
(void) tmpnode_trunc(tm, tp, (u_long)0);
tm->tm_directories--;
tmp_files--;
break;
case VLNK:
tmp_memfree(tm, (char *)tp->tn_symlink,
(u_int)tp->tn_attr.va_size);
tm->tm_files--;
tmp_files--;
break;
case VSOCK:
case VFIFO:
case VBLK:
case VCHR:
case VREG:
/*
* See comment below on anon maps
*/
if (tp->tn_amapp->refcnt == 1)
(void) tmpnode_trunc(tm, tp, (u_long)0);
tm->tm_files--;
tmp_files--;
break;
default:
panic("tmpnode_free()- unknown file type\n");
break;
}
/* adjust links in list */
if ((tp->tn_back->tn_forw = tp->tn_forw) != NULL)
tp->tn_forw->tn_back = tp->tn_back;
(void) tmpimapfree(tm, tp->tn_attr.va_nodeid);
/*
* Decrement anon map's reference count
* If the count goes to 0, then free the kmem_alloc'd anon map.
* If the count >= 1, then let the
* mapping process free it up (during a segvn_free)
*/
if (--tp->tn_amapp->refcnt == 0) {
kmem_fast_free((caddr_t *)&tmpamap_freelist,
(caddr_t)tp->tn_amapp);
#ifdef TMPFSDEBUG
if (tmpdebugalloc)
printf("tmpnode_free: free amap for tp %x\n", tp);
#endif TMPFSDEBUG
}
tmp_memfree(tm, (char *)tp, sizeof (struct tmpnode));
}
tmpnode_lock(tp)
struct tmpnode *tp;
{
#ifdef TMPFSDEBUG
if (tmpfsdebug || tmplockdebug)
printf("tmpnode_lock: tp %x caller %x\n", tp, caller());
#endif TMPFSDEBUG
while (tp->tn_flags & TLOCKED && tp->tn_owner != uniqpid()) {
tp->tn_flags |= TWANTED;
#ifdef TMPFSDEBUG
if (tmplockdebug)
printf("tmpnode_lock: pid %x sleeping on tp %x\n",
uniqpid(), tp);
#endif TMPFSDEBUG
(void) sleep((caddr_t)(tp), PINOD);
}
tp->tn_owner = uniqpid();
tp->tn_count++;
tp->tn_flags |= TLOCKED;
/* can't swap process holding a tmpnode lock */
masterprocp->p_swlocks++;
}
tmpnode_unlock(tp)
struct tmpnode *tp;
{
#ifdef TMPFSDEBUG
if (tmpfsdebug || tmplockdebug)
printf("tmpnode_unlock: tp %x count %d caller %x\n",
tp, tp->tn_count, caller());
#endif TMPFSDEBUG
if (--tp->tn_count < 0)
panic("tmpnode_unlock()- bad count");
masterprocp->p_swlocks--;
if (tp->tn_count == 0) {
tp->tn_flags &= ~TLOCKED;
if (tp->tn_flags & TWANTED) {
tp->tn_flags &= ~TWANTED;
wakeup((caddr_t)tp);
}
}
}
/*
* patchable variable used to limit the amount of anon space taken by
* a tmpfs so that we can attempt to avoid deadlock situations
* XXX this should be set at first mount to some reasonable value
* (e.g. MAX(1/4 total available swap space, 4MB) ??)
*/
int tmpfs_hiwater = TMPHIWATER;
/*ARGSUSED*/
tmpnode_findpage(tm, tp, offset)
register struct tmount *tm;
register struct tmpnode *tp;
register u_int offset;
{
register int pageno = btop(offset);
register struct anon *ap;
struct anon *anon_alloc();
register int allocated = 0;
#ifdef TMPFSDEBUG
if (tmpfsdebug)
printf("tmpnode_findpage: tp %x offset %x\n", tp, offset);
#endif TMPFSDEBUG
/*
* Has the size of the file grown off the limits of the anon array?
*/
if (offset >= tp->tn_amapp->size)
if (tmpnode_growmap(tp, offset) == 0)
return (-1);
/*
* now check to see if there is an allocated anon page
* for this access.
*/
AMAP_LOCK(tp->tn_amapp);
if (tp->tn_amapp->anon[pageno] == NULL) {
allocated = 1;
if (anoninfo.ani_free < btop(tmpfs_hiwater)) {
#ifdef TMPFSDEBUG
if (tmpdebugerrs)
printf("tmpnode_findpage: out of anon space\n");
#endif TMPFSDEBUG
AMAP_UNLOCK(tp->tn_amapp);
return (-1);
}
if ((ap = anon_alloc()) == NULL) {
#ifdef TMPFSDEBUG
if (tmpdebugerrs)
printf("tmpnode_findpage: anon_alloc failed\n");
#endif TMPFSDEBUG
AMAP_UNLOCK(tp->tn_amapp);
return (-1);
}
tmp_anonalloc++;
tp->tn_amapp->anon[pageno] = ap;
}
AMAP_UNLOCK(tp->tn_amapp);
#ifdef TMPFSDEBUG
if (tmpfsdebug)
printf("tmpnode_findpage: allocated %d anon[%d] %x\n",
allocated, pageno, tp->tn_amapp->anon[pageno]);
#endif TMPFSDEBUG
return (allocated);
}
struct timeval tmp_lastgrowtime;
/*
* grow the anon pointer array to cover 'offset' bytes plus slack
* The anon_map is always locked before allocating a new anon array
* and copying the old anon array contents into it. This is necessary
* to prevent the `seg_vn' fault handling routine from using the old
* anon array as a result of a fault on a memory mapped "tmpfs" file.
*/
tmpnode_growmap(tp, offset)
register struct tmpnode *tp;
register u_int offset;
{
register int i, oldsize, newsize;
register struct anon **newapp;
extern struct timeval time;
/*
* calculate new length, rounding up in TMAP_ALLOC clicks
* to avoid reallocating the anon array each time the file grows
* XXX faster with shifts
*/
newsize = ((offset + TMAP_ALLOC)/TMAP_ALLOC)*TMAP_ALLOC;
oldsize = tp->tn_amapp->size;
#ifdef TMPFSDEBUG
if (tmpfsdebug || tmpdebugalloc)
printf("tmpnode_growmap: tp %x oldsize %x newsize %x\n",
tp, oldsize, newsize);
#endif TMPFSDEBUG
newapp = (struct anon **)
new_kmem_zalloc(btop(newsize) * sizeof (struct anon *), KMEM_SLEEP);
/*
* copy old array (if it exists)
* could have used anon_dup()/anon_free() combination
*/
AMAP_LOCK(tp->tn_amapp);
if (tp->tn_amapp->anon != NULL) {
for (i = 0; i < btop(oldsize); i++) {
newapp[i] = tp->tn_amapp->anon[i];
}
tmp_lastgrowtime = time;
kmem_free((char *)tp->tn_amapp->anon,
btop(oldsize) * sizeof (struct anon *));
}
tp->tn_amapp->anon = newapp;
tp->tn_amapp->size = newsize;
AMAP_UNLOCK(tp->tn_amapp);
#ifdef TMPFSDEBUG
if (tmpfsdebug)
printf("tmpnode_growmap: new anonmap %x\n", tp->tn_amapp->anon);
#endif TMPFSDEBUG
return (1);
}
/*
* tmpnode_trunc()- set length of tmpnode
*/
/*ARGSUSED*/
tmpnode_trunc(tm, tp, newsize)
struct tmount *tm;
struct tmpnode *tp;
u_long newsize;
{
register u_int oldsize;
register struct tdirent *tdp, *tdpx;
register u_int delta;
register int pagecreate = 0;
extern void swap_xlate();
oldsize = tp->tn_attr.va_size;
#ifdef TMPFSDEBUG
if (tmpfsdebug || tmpdebugalloc) {
printf("tmpnode_trunc: tp %x oldsz %d newsz %d",
tp, newsize, oldsize);
printf(" type %d caller %x\n", tp->tn_attr.va_type, caller());
}
#endif TMPFSDEBUG
if (newsize == oldsize)
return (0);
switch (tp->tn_attr.va_type) {
case VREG:
if (tp->tn_amapp->refcnt > 1) /* lock first? */
return (EACCES);
if (newsize > oldsize) {
delta = newsize - oldsize;
/*
* grow the size of the anonmap here in case
* someone maps the file. Count the space we're
* growing the file here because we can't detect
* the actual allocation through a page fault.
* rwtmp or segvn_fault will fill in the holes
* in anonmap as needed
* XXX unfortunately, when the holes are filled in
* in segvn_fault, ani_free is decremented changing
* the percentage of anonymous memory tmpfs uses
*/
#ifdef TMPFSDEBUG
if (tmpfsdebug)
printf("ttrunc: growing %d bytes\n", delta);
#endif TMPFSDEBUG
if (btopr(newsize) != btopr(oldsize))
pagecreate = 1;
if (tmp_resv(tm, tp, delta, pagecreate))
return (ENOSPC);
(void) tmpnode_growmap(tp, (u_int)newsize);
break;
}
/*
* XXX - we need to fail here if someone has a mapping to
* this tmpnode. This is because there is no way to alert
* the segment layer that the file has shrunk wreaking
* havoc if one were to decide to access the anonmap
* past its current size
*/
if (tp->tn_amapp->refcnt > 1)
return (EBUSY);
AMAP_LOCK(tp->tn_amapp);
/*
* delete entire anonmap for tmpnode
*/
if (newsize == 0 && tp->tn_amapp->refcnt == 1) { /* XXX */
u_int anonextra = tp->tn_amapp->swresv - oldsize;
#ifdef TMPFSDEBUG
if (tmpfsdebug || tmpdebugalloc)
printf("ttrunc: shrinking %d bytes to 0\n",
oldsize);
#endif TMPFSDEBUG
anon_free(tp->tn_amapp->anon, tp->tn_amapp->swresv);
tmp_anonalloc -= btopr(oldsize); /* XXX holes? */
kmem_free((char *)tp->tn_amapp->anon,
btop(tp->tn_amapp->size) * sizeof (struct anon *));
ASSERT(tp->tn_amapp->swresv >= oldsize);
tmp_unresv(tm, tp, oldsize);
/*
* anonextra is nonzero if someone had a mapping to
* the file and accessed it causing page faults
* to fill holes
*/
if (anonextra) {
#ifdef TMPFSDEBUG
if (tmpfsdebug || tmpdebugalloc)
printf("ttrunc: anonextra %d\n",
anonextra);
#endif TMPFSDEBUG
anon_unresv(anonextra);
}
tp->tn_amapp->size = 0;
tp->tn_amapp->anon = NULL;
AMAP_UNLOCK(tp->tn_amapp);
break;
}
/*
* free anon pages if necessary
*/
if (btopr(newsize) != btopr(oldsize)) {
delta = oldsize - newsize;
#ifdef TMPFSDEBUG
if (tmpfsdebug || tmpdebugalloc) {
printf("ttrunc: shrinking %d bytes", delta);
printf(" anonfree anon[%d] size:%d\n",
btopr(newsize),
oldsize - roundup(newsize, PAGESIZE));
}
#endif TMPFSDEBUG
tmp_unresv(tm, tp, delta);
anon_free(&tp->tn_amapp->anon[btopr(newsize)],
(u_int)(oldsize - roundup(newsize, PAGESIZE)));
tmp_anonalloc -= btopr(delta);
}
/*
* zero remainder of page
*/
if (tp->tn_amapp->anon[btop(newsize)] != NULL) {
struct anon *ap = tp->tn_amapp->anon[btop(newsize)];
struct vnode *swapvp;
u_int swapoff;
register struct page *pp;
u_int offset = newsize & PAGEOFFSET;
swap_xlate(ap, &swapvp, &swapoff);
pp = ap->un.an_page;
#ifdef TMPFSDEBUG
if (tmpfsdebug)
printf("ttrunc: zero %d bytes in anon[%d] pp:%x\n",
PAGESIZE - offset, btop(newsize), pp);
#endif TMPFSDEBUG
if (pp != NULL && pp->p_vnode == swapvp &&
pp->p_offset == swapoff && !pp->p_gone) {
if (pp->p_free)
page_reclaim(pp);
pagezero(pp, offset, PAGESIZE - offset);
} else {
char *base;
base = segmap_getmap(segkmap, swapvp,
swapoff & MAXBMASK);
(void) kzero(base + (swapoff & MAXBOFFSET) +
offset, PAGESIZE - offset);
(void) segmap_release(segkmap, base, 0);
}
}
AMAP_UNLOCK(tp->tn_amapp);
break;
case VLNK:
if (newsize != 0)
return (EINVAL);
tmp_memfree(tm, (char *)tp->tn_symlink,
(u_int)tp->tn_attr.va_size);
tp->tn_symlink = NULL;
break;
case VDIR:
if (newsize != 0)
return (EINVAL);
#ifdef TMPFSDEBUG
if (tmpdebugalloc || tmpdirdebug)
printf("ttrunc: dir %x caller %x\n", tp, caller());
#endif TMPFSDEBUG
for (tdp = tp->tn_dir; tdp; tdp = tdpx) {
#ifdef TMPFSDEBUG
if (tmpdebugalloc || tmpdirdebug)
printf("ttrunc: deleting %s\n", tdp->td_name);
#endif TMPFSDEBUG
tdpx = tdp->td_next;
tm->tm_direntries--;
tmp_memfree(tm, (char *)tdp, (u_int)tdp->td_reclen);
}
tp->tn_dir = NULL;
break;
default:
return (0);
}
tp->tn_attr.va_size = newsize;
return (0);
}
/*
* check for any actively used files
* called from tmp_unmount
*/
tmpnode_checkopen(tm)
register struct tmount *tm;
{
register struct tmpnode *tnp;
/*
* The rootnode is always referenced with a v_count >= 1
* If v_count > 1, we're busy.
*/
if (tm->tm_rootnode->tn_vnode.v_count > 1)
return (1);
for (tnp = tm->tm_rootnode->tn_forw; tnp; tnp = tnp->tn_forw)
if (tnp->tn_flags & TREF) {
#ifdef TMPFSDEBUG
if (tmpdebugerrs)
printf("tmpnode_checkopen: %x is referenced\n",
tnp);
#endif TMPFSDEBUG
return (1);
}
return (0);
}
/*
* free up all resources associated with the file system
* called from tmp_unmount
*/
tmpnode_freeall(tm)
register struct tmount *tm;
{
register struct tmpnode *tnp, *tnpx;
register struct tmpimap *tmapp0, *tmapp1;
#ifdef TMPFSDEBUG
if (tmpfsdebug)
printf("tmpnode_freeall: tm %x\n", tm);
#endif TMPFSDEBUG
for (tnp = tm->tm_rootnode; tnp; tnp = tnpx) {
switch (tnp->tn_attr.va_type) {
case VDIR:
tm->tm_directories--;
tmp_files--;
(void) tmpnode_trunc(tm, tnp, (u_long)0);
break;
case VLNK:
case VREG:
case VBLK:
case VCHR:
case VFIFO:
case VSOCK:
tm->tm_files--;
tmp_files--;
(void) tmpnode_trunc(tm, tnp, (u_long)0);
break;
default:
break; /* XXX shouldn't this be a panic? */
}
tnpx = tnp->tn_forw;
tmp_memfree(tm, (char *)tnp, sizeof (struct tmpnode));
}
/*
* Free the inode maps
*/
tmapp0 = tm->tm_inomap.timap_next;
while (tmapp0 != NULL) {
tmapp1 = tmapp0->timap_next;
tmp_memfree(tm, (char *)tmapp0, sizeof (struct tmpimap));
tmapp0 = tmapp1;
}
}
int
tmp_resv(tm, tp, delta, pagecreate)
register struct tmount *tm;
register struct tmpnode *tp;
register u_int delta;
register int pagecreate;
{
#ifdef TMPFSDEBUG
if (tmpfsdebug || tmpdebugalloc)
printf("tmp_resv: tm %x tp %x delta %d\n", tm, tp, delta);
#endif TMPFSDEBUG
/*
* don't do anon_resv unless we actually need to reserve a page
* this is because of a rounding error that occurs because
* anon_resv does a btopr(delta)
*/
if (pagecreate && (anon_resv(delta) == 0))
return (1);
tm->tm_anonmem += delta;
tp->tn_amapp->swresv += delta;
tmp_anonmem += delta;
#ifdef TMPFSDEBUG
if (tmpfsdebug || tmpdebugalloc)
printf("tmp_resv: ani_resv %d\n", anoninfo.ani_resv);
#endif TMPFSDEBUG
return (0);
}
tmp_unresv(tm, tp, delta)
register struct tmount *tm;
register struct tmpnode *tp;
register u_int delta;
{
#ifdef TMPFSDEBUG
if (tmpfsdebug || tmpdebugalloc)
printf("tmp_unresv: tm %x tp %x delta %d\n", tm, tp, delta);
#endif TMPFSDEBUG
anon_unresv(delta);
tm->tm_anonmem -= delta;
tp->tn_amapp->swresv -= delta;
tmp_anonmem -= delta;
#ifdef TMPFSDEBUG
if (tmpfsdebug || tmpdebugalloc)
printf("tmp_unresv: ani_resv %d\n", anoninfo.ani_resv);
#endif TMPFSDEBUG
}