#include <sys/param.h>
#include <sys/t_lock.h>
#include <sys/errno.h>
#include <sys/sysmacros.h>
#include <sys/buf.h>
#include <sys/systm.h>
#include <sys/vfs.h>
#include <sys/vnode.h>
#include <sys/kmem.h>
#include <sys/proc.h>
#include <sys/cred.h>
#include <sys/cmn_err.h>
#include <sys/debug.h>
#include <vm/pvn.h>
#include <sys/fs/pc_label.h>
#include <sys/fs/pc_fs.h>
#include <sys/fs/pc_dir.h>
#include <sys/fs/pc_node.h>
#include <sys/dirent.h>
#include <sys/fdio.h>
#include <sys/file.h>
#include <sys/conf.h>
struct pchead pcfhead[NPCHASH];
struct pchead pcdhead[NPCHASH];
extern krwlock_t pcnodes_lock;
static int pc_getentryblock(struct pcnode *, struct buf **);
static int syncpcp(struct pcnode *, int);
struct pcdir pcfs_rootdirentry = {
"",
"",
PCA_DIR
};
void
pc_init(void)
{
struct pchead *hdp, *hfp;
int i;
for (i = 0; i < NPCHASH; i++) {
hdp = &pcdhead[i];
hfp = &pcfhead[i];
hdp->pch_forw = (struct pcnode *)hdp;
hdp->pch_back = (struct pcnode *)hdp;
hfp->pch_forw = (struct pcnode *)hfp;
hfp->pch_back = (struct pcnode *)hfp;
}
}
struct pcnode *
pc_getnode(
struct pcfs *fsp,
daddr_t blkno,
int offset,
struct pcdir *ep)
{
struct pcnode *pcp;
struct pchead *hp;
struct vnode *vp;
pc_cluster32_t scluster;
ASSERT(fsp->pcfs_flags & PCFS_LOCKED);
if (ep == NULL) {
ep = &pcfs_rootdirentry;
scluster = 0;
} else {
scluster = pc_getstartcluster(fsp, ep);
}
if (ep->pcd_attr & PCA_DIR) {
hp = &pcdhead[PCDHASH(fsp, scluster)];
rw_enter(&pcnodes_lock, RW_READER);
for (pcp = hp->pch_forw;
pcp != (struct pcnode *)hp; pcp = pcp->pc_forw) {
if ((fsp == VFSTOPCFS(PCTOV(pcp)->v_vfsp)) &&
(scluster == pcp->pc_scluster)) {
VN_HOLD(PCTOV(pcp));
rw_exit(&pcnodes_lock);
return (pcp);
}
}
rw_exit(&pcnodes_lock);
} else {
hp = &pcfhead[PCFHASH(fsp, blkno, offset)];
rw_enter(&pcnodes_lock, RW_READER);
for (pcp = hp->pch_forw;
pcp != (struct pcnode *)hp; pcp = pcp->pc_forw) {
if ((fsp == VFSTOPCFS(PCTOV(pcp)->v_vfsp)) &&
((pcp->pc_flags & PC_INVAL) == 0) &&
(blkno == pcp->pc_eblkno) &&
(offset == pcp->pc_eoffset)) {
VN_HOLD(PCTOV(pcp));
rw_exit(&pcnodes_lock);
return (pcp);
}
}
rw_exit(&pcnodes_lock);
}
pcp = kmem_zalloc(sizeof (struct pcnode), KM_SLEEP);
vp = vn_alloc(KM_SLEEP);
pcp->pc_vn = vp;
pcp->pc_entry = *ep;
pcp->pc_eblkno = blkno;
pcp->pc_eoffset = offset;
pcp->pc_scluster = scluster;
pcp->pc_lcluster = scluster;
pcp->pc_lindex = 0;
pcp->pc_flags = 0;
if (ep->pcd_attr & PCA_DIR) {
vn_setops(vp, pcfs_dvnodeops);
vp->v_type = VDIR;
if (scluster == 0) {
vp->v_flag = VROOT;
blkno = offset = 0;
if (IS_FAT32(fsp)) {
pc_cluster32_t ncl = 0;
scluster = fsp->pcfs_rdirstart;
if (pc_fileclsize(fsp, scluster, &ncl)) {
PC_DPRINTF1(2, "cluster chain "
"corruption, scluster=%d\n",
scluster);
pcp->pc_flags |= PC_INVAL;
}
pcp->pc_size = fsp->pcfs_clsize * ncl;
} else {
pcp->pc_size =
fsp->pcfs_rdirsec * fsp->pcfs_secsize;
}
} else {
pc_cluster32_t ncl = 0;
if (pc_fileclsize(fsp, scluster, &ncl)) {
PC_DPRINTF1(2, "cluster chain corruption, "
"scluster=%d\n", scluster);
pcp->pc_flags |= PC_INVAL;
}
pcp->pc_size = fsp->pcfs_clsize * ncl;
}
} else {
vn_setops(vp, pcfs_fvnodeops);
vp->v_type = VREG;
vp->v_flag = VNOSWAP;
fsp->pcfs_frefs++;
pcp->pc_size = ltohi(ep->pcd_size);
}
fsp->pcfs_nrefs++;
VFS_HOLD(PCFSTOVFS(fsp));
vp->v_data = (caddr_t)pcp;
vp->v_vfsp = PCFSTOVFS(fsp);
vn_exists(vp);
rw_enter(&pcnodes_lock, RW_WRITER);
insque(pcp, hp);
rw_exit(&pcnodes_lock);
return (pcp);
}
int
syncpcp(struct pcnode *pcp, int flags)
{
int err;
if (!vn_has_cached_data(PCTOV(pcp)))
err = 0;
else
err = VOP_PUTPAGE(PCTOV(pcp), 0, 0, flags,
kcred, NULL);
return (err);
}
void
pc_rele(struct pcnode *pcp)
{
struct pcfs *fsp;
struct vnode *vp;
int err;
vp = PCTOV(pcp);
PC_DPRINTF1(8, "pc_rele vp=0x%p\n", (void *)vp);
fsp = VFSTOPCFS(vp->v_vfsp);
ASSERT(fsp->pcfs_flags & PCFS_LOCKED);
rw_enter(&pcnodes_lock, RW_WRITER);
pcp->pc_flags |= PC_RELEHOLD;
retry:
if (vp->v_type != VDIR && (pcp->pc_flags & PC_INVAL) == 0) {
if (pcp->pc_entry.pcd_filename[0] == PCD_ERASED) {
(void) pc_truncate(pcp, 0);
} else if (pcp->pc_flags & PC_CHG) {
(void) pc_nodeupdate(pcp);
}
err = syncpcp(pcp, B_INVAL);
if (err) {
(void) syncpcp(pcp, B_INVAL | B_FORCE);
}
}
if (vn_has_cached_data(vp)) {
(void) pvn_vplist_dirty(vp, (u_offset_t)0,
pcfs_putapage, B_INVAL, (struct cred *)NULL);
}
(void) pc_syncfat(fsp);
mutex_enter(&vp->v_lock);
if (vn_has_cached_data(vp)) {
mutex_exit(&vp->v_lock);
goto retry;
}
ASSERT(!vn_has_cached_data(vp));
VN_RELE_LOCKED(vp);
if (vp->v_count > 0) {
PC_DPRINTF1(3, "pc_rele: pcp=0x%p HELD AGAIN!\n", (void *)pcp);
mutex_exit(&vp->v_lock);
pcp->pc_flags &= ~PC_RELEHOLD;
rw_exit(&pcnodes_lock);
return;
}
remque(pcp);
rw_exit(&pcnodes_lock);
if ((vp->v_type == VREG)) {
fsp->pcfs_frefs--;
}
fsp->pcfs_nrefs--;
VFS_RELE(vp->v_vfsp);
if (fsp->pcfs_nrefs < 0) {
panic("pc_rele: nrefs count");
}
if (fsp->pcfs_frefs < 0) {
panic("pc_rele: frefs count");
}
mutex_exit(&vp->v_lock);
vn_invalid(vp);
vn_free(vp);
kmem_free(pcp, sizeof (struct pcnode));
}
void
pc_mark_mod(struct pcfs *fsp, struct pcnode *pcp)
{
timestruc_t now;
if (PCTOV(pcp)->v_type == VDIR)
return;
ASSERT(PCTOV(pcp)->v_type == VREG);
gethrestime(&now);
if (pc_tvtopct(&now, &pcp->pc_entry.pcd_mtime))
PC_DPRINTF1(2, "pc_mark_mod failed timestamp "
"conversion, curtime = %lld\n",
(long long)now.tv_sec);
pcp->pc_flags |= PC_CHG;
}
void
pc_mark_acc(struct pcfs *fsp, struct pcnode *pcp)
{
struct pctime pt = { 0, 0 };
timestruc_t now;
if (fsp->pcfs_flags & PCFS_NOATIME || PCTOV(pcp)->v_type == VDIR)
return;
ASSERT(PCTOV(pcp)->v_type == VREG);
gethrestime(&now);
if (pc_tvtopct(&now, &pt)) {
PC_DPRINTF1(2, "pc_mark_acc failed timestamp "
"conversion, curtime = %lld\n",
(long long)now.tv_sec);
return;
}
if (pcp->pc_entry.pcd_ladate != pt.pct_date) {
pcp->pc_entry.pcd_ladate = pt.pct_date;
pcp->pc_flags |= (PC_CHG | PC_ACC);
}
}
int
pc_truncate(struct pcnode *pcp, uint_t length)
{
struct pcfs *fsp;
struct vnode *vp;
int error = 0;
PC_DPRINTF3(4, "pc_truncate pcp=0x%p, len=%u, size=%u\n",
(void *)pcp, length, pcp->pc_size);
vp = PCTOV(pcp);
if (pcp->pc_flags & PC_INVAL)
return (EIO);
fsp = VFSTOPCFS(vp->v_vfsp);
if (vp->v_type == VDIR) {
error = pc_bfree(pcp, 0);
return (error);
}
if (length > pcp->pc_size) {
daddr_t bno;
uint_t llcn = howmany((offset_t)length, fsp->pcfs_clsize);
if ((daddr_t)howmany((offset_t)pcp->pc_size, fsp->pcfs_clsize) <
(daddr_t)llcn) {
error = pc_balloc(pcp, (daddr_t)(llcn - 1), 1, &bno);
}
if (error) {
pc_cluster32_t ncl = 0;
PC_DPRINTF1(2, "pc_truncate: error=%d\n", error);
if (pc_fileclsize(fsp, pcp->pc_scluster, &ncl)) {
PC_DPRINTF1(2, "cluster chain corruption, "
"scluster=%d\n", pcp->pc_scluster);
pcp->pc_flags |= PC_INVAL;
}
pcp->pc_size = fsp->pcfs_clsize * ncl;
} else
pcp->pc_size = length;
} else if (length < pcp->pc_size) {
if (pc_blkoff(fsp, length) == 0) {
(void) pvn_vplist_dirty(PCTOV(pcp), (u_offset_t)length,
pcfs_putapage, B_INVAL | B_TRUNC, CRED());
} else {
size_t nbytes =
roundup(length, MIN(fsp->pcfs_clsize, MAXBSIZE)) -
length;
pvn_vpzero(PCTOV(pcp), (u_offset_t)length, nbytes);
(void) pvn_vplist_dirty(PCTOV(pcp),
(u_offset_t)length + nbytes,
pcfs_putapage, B_INVAL | B_TRUNC, CRED());
}
error = pc_bfree(pcp, (pc_cluster32_t)
howmany((offset_t)length, fsp->pcfs_clsize));
pcp->pc_size = length;
}
pc_mark_mod(fsp, pcp);
return (error);
}
static int
pc_getentryblock(struct pcnode *pcp, struct buf **bpp)
{
struct pcfs *fsp;
fsp = VFSTOPCFS(PCTOV(pcp)->v_vfsp);
if (pcp->pc_eblkno >= fsp->pcfs_datastart ||
(pcp->pc_eblkno - fsp->pcfs_rdirstart) <
(fsp->pcfs_rdirsec & ~(fsp->pcfs_spcl - 1))) {
*bpp = bread(fsp->pcfs_xdev,
pc_dbdaddr(fsp, pcp->pc_eblkno), fsp->pcfs_clsize);
} else {
*bpp = bread(fsp->pcfs_xdev,
pc_dbdaddr(fsp, pcp->pc_eblkno),
(int)(fsp->pcfs_datastart - pcp->pc_eblkno) *
fsp->pcfs_secsize);
}
if ((*bpp)->b_flags & B_ERROR) {
brelse(*bpp);
pc_mark_irrecov(fsp);
return (EIO);
}
return (0);
}
int
pc_nodesync(struct pcnode *pcp)
{
struct pcfs *fsp;
int err;
struct vnode *vp;
vp = PCTOV(pcp);
fsp = VFSTOPCFS(vp->v_vfsp);
err = 0;
if (pcp->pc_flags & PC_MOD) {
if (err = syncpcp(pcp, 0)) {
if (err == ENOMEM)
return (err);
else {
pc_mark_irrecov(fsp);
return (EIO);
}
}
pcp->pc_flags &= ~PC_MOD;
}
if (pcp->pc_flags & PC_CHG)
(void) pc_nodeupdate(pcp);
return (err);
}
int
pc_nodeupdate(struct pcnode *pcp)
{
struct buf *bp;
int error;
struct vnode *vp;
struct pcfs *fsp;
vp = PCTOV(pcp);
fsp = VFSTOPCFS(vp->v_vfsp);
if (IS_FAT32(fsp) && (vp->v_flag & VROOT)) {
pcp->pc_flags &= ~(PC_CHG | PC_MOD | PC_ACC);
return (0);
}
if (vp->v_flag & VROOT) {
panic("pc_nodeupdate");
}
if (pcp->pc_flags & PC_INVAL)
return (0);
PC_DPRINTF3(7, "pc_nodeupdate pcp=0x%p, bn=%ld, off=%d\n", (void *)pcp,
pcp->pc_eblkno, pcp->pc_eoffset);
if (error = pc_getentryblock(pcp, &bp)) {
return (error);
}
if (vp->v_type == VREG) {
if (pcp->pc_flags & PC_CHG)
pcp->pc_entry.pcd_attr |= PCA_ARCH;
pcp->pc_entry.pcd_size = htoli(pcp->pc_size);
}
pc_setstartcluster(fsp, &pcp->pc_entry, pcp->pc_scluster);
*((struct pcdir *)(bp->b_un.b_addr + pcp->pc_eoffset)) = pcp->pc_entry;
bwrite2(bp);
error = geterror(bp);
brelse(bp);
if (error) {
error = EIO;
pc_mark_irrecov(VFSTOPCFS(vp->v_vfsp));
}
pcp->pc_flags &= ~(PC_CHG | PC_MOD | PC_ACC);
return (error);
}
int
pc_verify(struct pcfs *fsp)
{
int fdstatus = 0;
int error = 0;
if (!fsp || fsp->pcfs_flags & PCFS_IRRECOV)
return (EIO);
if (!(fsp->pcfs_flags & PCFS_NOCHK) && fsp->pcfs_fatp) {
PC_DPRINTF1(4, "pc_verify fsp=0x%p\n", (void *)fsp);
error = cdev_ioctl(fsp->pcfs_vfs->vfs_dev,
FDGETCHANGE, (intptr_t)&fdstatus, FNATIVE | FKIOCTL,
NULL, NULL);
if (error) {
if (error == ENOTTY || error == ENXIO) {
error = 0;
} else {
PC_DPRINTF1(1,
"pc_verify: FDGETCHANGE ioctl failed: %d\n",
error);
pc_mark_irrecov(fsp);
}
} else if (fsp->pcfs_fatjustread) {
fsp->pcfs_fatjustread = 0;
} else {
if (fdstatus & (FDGC_HISTORY | FDGC_DETECTED)) {
PC_DPRINTF0(1, "pc_verify: change detected\n");
pc_mark_irrecov(fsp);
}
}
}
if (error == 0 && fsp->pcfs_fatp == NULL) {
error = pc_getfat(fsp);
}
return (error);
}
void
pc_mark_irrecov(struct pcfs *fsp)
{
if (!(fsp->pcfs_flags & PCFS_NOCHK)) {
if (pc_lockfs(fsp, 1, 0)) {
return;
}
fsp->pcfs_flags |= PCFS_IRRECOV;
cmn_err(CE_WARN,
"Disk was changed during an update or\n"
"an irrecoverable error was encountered.\n"
"File damage is possible. To prevent further\n"
"damage, this pcfs instance will now be frozen.\n"
"Use umount(8) to release the instance.\n");
(void) pc_unlockfs(fsp);
}
}
void
pc_diskchanged(struct pcfs *fsp)
{
struct pcnode *pcp, *npcp = NULL;
struct pchead *hp;
struct vnode *vp;
extern vfs_t EIO_vfs;
struct vfs *vfsp;
PC_DPRINTF1(1, "pc_diskchanged fsp=0x%p\n", (void *)fsp);
vfsp = PCFSTOVFS(fsp);
for (hp = pcdhead; hp < &pcdhead[NPCHASH]; hp++) {
for (pcp = hp->pch_forw;
pcp != (struct pcnode *)hp; pcp = npcp) {
npcp = pcp -> pc_forw;
vp = PCTOV(pcp);
if ((vp->v_vfsp == vfsp) &&
!(pcp->pc_flags & PC_RELEHOLD)) {
mutex_enter(&(vp)->v_lock);
if (vp->v_count > 0) {
mutex_exit(&(vp)->v_lock);
continue;
}
mutex_exit(&(vp)->v_lock);
VN_HOLD(vp);
remque(pcp);
vp->v_data = NULL;
vp->v_vfsp = &EIO_vfs;
vp->v_type = VBAD;
VN_RELE(vp);
if (!(pcp->pc_flags & PC_EXTERNAL)) {
(void) pvn_vplist_dirty(vp,
(u_offset_t)0, pcfs_putapage,
B_INVAL | B_TRUNC,
(struct cred *)NULL);
vn_free(vp);
}
kmem_free(pcp, sizeof (struct pcnode));
fsp->pcfs_nrefs --;
VFS_RELE(vfsp);
}
}
}
for (hp = pcfhead; fsp->pcfs_frefs && hp < &pcfhead[NPCHASH]; hp++) {
for (pcp = hp->pch_forw; fsp->pcfs_frefs &&
pcp != (struct pcnode *)hp; pcp = npcp) {
npcp = pcp -> pc_forw;
vp = PCTOV(pcp);
if ((vp->v_vfsp == vfsp) &&
!(pcp->pc_flags & PC_RELEHOLD)) {
mutex_enter(&(vp)->v_lock);
if (vp->v_count > 0) {
mutex_exit(&(vp)->v_lock);
continue;
}
mutex_exit(&(vp)->v_lock);
VN_HOLD(vp);
remque(pcp);
vp->v_data = NULL;
vp->v_vfsp = &EIO_vfs;
vp->v_type = VBAD;
VN_RELE(vp);
if (!(pcp->pc_flags & PC_EXTERNAL)) {
(void) pvn_vplist_dirty(vp,
(u_offset_t)0, pcfs_putapage,
B_INVAL | B_TRUNC,
(struct cred *)NULL);
vn_free(vp);
}
kmem_free(pcp, sizeof (struct pcnode));
fsp->pcfs_frefs--;
fsp->pcfs_nrefs--;
VFS_RELE(vfsp);
}
}
}
#ifdef undef
if (fsp->pcfs_frefs) {
rw_exit(&pcnodes_lock);
panic("pc_diskchanged: frefs");
}
if (fsp->pcfs_nrefs) {
rw_exit(&pcnodes_lock);
panic("pc_diskchanged: nrefs");
}
#endif
if (!(vfsp->vfs_flag & VFS_UNMOUNTED) &&
fsp->pcfs_fatp != NULL) {
pc_invalfat(fsp);
} else {
binval(fsp->pcfs_xdev);
}
}