/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2009 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
/*
* Routines to allocate and deallocate data blocks on the disk
*/
#include <sys/param.h>
#include <sys/errno.h>
#include <sys/buf.h>
#include <sys/vfs.h>
#include <sys/vnode.h>
#include <sys/cmn_err.h>
#include <sys/debug.h>
#include <sys/sysmacros.h>
#include <sys/systm.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>
static pc_cluster32_t pc_getcluster(struct pcfs *fsp, pc_cluster32_t cn);
/*
* Convert file logical block (cluster) numbers to disk block numbers.
* Also return number of physically contiguous blocks if asked for.
* Used for reading only. Use pc_balloc for writing.
*/
int
pc_bmap(
struct pcnode *pcp, /* pcnode for file */
daddr_t lcn, /* logical cluster no */
daddr_t *dbnp, /* ptr to phys block no */
uint_t *contigbp) /* ptr to number of contiguous bytes */
/* may be zero if not wanted */
{
struct pcfs *fsp; /* pcfs that file is in */
struct vnode *vp;
pc_cluster32_t cn, ncn; /* current, next cluster number */
daddr_t olcn = lcn;
vp = PCTOV(pcp);
fsp = VFSTOPCFS(vp->v_vfsp);
if (lcn < 0)
return (ENOENT);
/*
* FAT12 / FAT16 root directories are a continuous section on disk
* before the actual data clusters. Specialcase this here.
*/
if (!IS_FAT32(fsp) && (vp->v_flag & VROOT)) {
daddr_t lbn; /* logical (disk) block number */
lbn = pc_cltodb(fsp, lcn);
if (lbn >= fsp->pcfs_rdirsec) {
PC_DPRINTF0(2, "pc_bmap: ENOENT1\n");
return (ENOENT);
}
*dbnp = pc_dbdaddr(fsp, fsp->pcfs_rdirstart + lbn);
if (contigbp) {
ASSERT (*contigbp >= fsp->pcfs_secsize);
*contigbp = MIN(*contigbp,
fsp->pcfs_secsize * (fsp->pcfs_rdirsec - lbn));
}
return (0);
}
if (lcn >= fsp->pcfs_ncluster) {
PC_DPRINTF0(2, "pc_bmap: ENOENT2\n");
return (ENOENT);
}
if (vp->v_type == VREG &&
(pcp->pc_size == 0 ||
lcn >= (daddr_t)howmany((offset_t)pcp->pc_size,
fsp->pcfs_clsize))) {
PC_DPRINTF0(2, "pc_bmap: ENOENT3\n");
return (ENOENT);
}
ncn = pcp->pc_scluster;
if (IS_FAT32(fsp) && ncn == 0)
ncn = fsp->pcfs_rdirstart;
/* Do we have a cached index/cluster pair? */
if (pcp->pc_lindex > 0 && lcn >= pcp->pc_lindex) {
lcn -= pcp->pc_lindex;
ncn = pcp->pc_lcluster;
}
do {
cn = ncn;
if (!pc_validcl(fsp, cn)) {
if (IS_FAT32(fsp) && cn >= PCF_LASTCLUSTER32 &&
vp->v_type == VDIR) {
PC_DPRINTF0(2, "pc_bmap: ENOENT4\n");
return (ENOENT);
} else if (!IS_FAT32(fsp) &&
cn >= PCF_LASTCLUSTER &&
vp->v_type == VDIR) {
PC_DPRINTF0(2, "pc_bmap: ENOENT5\n");
return (ENOENT);
} else {
PC_DPRINTF1(1,
"pc_bmap: badfs cn=%d\n", cn);
(void) pc_badfs(fsp);
return (EIO);
}
}
ncn = pc_getcluster(fsp, cn);
} while (lcn--);
/*
* Cache this cluster, as we'll most likely visit the
* one after this next time. Considerably improves
* performance on sequential reads and writes.
*/
pcp->pc_lindex = olcn;
pcp->pc_lcluster = cn;
*dbnp = pc_cldaddr(fsp, cn);
if (contigbp && *contigbp > fsp->pcfs_clsize) {
uint_t count = fsp->pcfs_clsize;
while ((cn + 1) == ncn && count < *contigbp &&
pc_validcl(fsp, ncn)) {
count += fsp->pcfs_clsize;
cn = ncn;
ncn = pc_getcluster(fsp, ncn);
}
*contigbp = count;
}
return (0);
}
/*
* Allocate file logical blocks (clusters).
* Return disk address of last allocated cluster.
*/
int
pc_balloc(
struct pcnode *pcp, /* pcnode for file */
daddr_t lcn, /* logical cluster no */
int zwrite, /* zerofill blocks? */
daddr_t *dbnp) /* ptr to phys block no */
{
struct pcfs *fsp; /* pcfs that file is in */
struct vnode *vp;
pc_cluster32_t cn; /* current cluster number */
pc_cluster32_t ncn; /* next cluster number */
vp = PCTOV(pcp);
fsp = VFSTOPCFS(vp -> v_vfsp);
if (lcn < 0) {
return (EFBIG);
}
/*
* Again, FAT12/FAT16 root directories are not data clusters.
*/
if (!IS_FAT32(fsp) && (vp->v_flag & VROOT)) {
daddr_t lbn;
lbn = pc_cltodb(fsp, lcn);
if (lbn >= fsp->pcfs_rdirsec)
return (ENOSPC);
*dbnp = pc_dbdaddr(fsp, fsp->pcfs_rdirstart + lbn);
return (0);
}
if (lcn >= fsp->pcfs_ncluster)
return (ENOSPC);
if ((vp->v_type == VREG && pcp->pc_size == 0) ||
(vp->v_type == VDIR && lcn == 0)) {
switch (cn = pc_alloccluster(fsp, 1)) {
case PCF_FREECLUSTER:
return (ENOSPC);
case PCF_ERRORCLUSTER:
return (EIO);
}
pcp->pc_scluster = cn;
} else {
cn = pcp->pc_scluster;
if (IS_FAT32(fsp) && cn == 0)
cn = fsp->pcfs_rdirstart;
if (!pc_validcl(fsp, cn)) {
PC_DPRINTF1(1, "pc_balloc: badfs cn=%d\n", cn);
(void) pc_badfs(fsp);
return (EIO);
}
}
if (pcp->pc_lindex > 0 && lcn > pcp->pc_lindex) {
lcn -= pcp->pc_lindex;
cn = pcp->pc_lcluster;
}
while (lcn-- > 0) {
ncn = pc_getcluster(fsp, cn);
if ((IS_FAT32(fsp) && ncn >= PCF_LASTCLUSTER32) ||
(!IS_FAT32(fsp) && ncn >= PCF_LASTCLUSTER)) {
/*
* Extend file (no holes).
*/
switch (ncn = pc_alloccluster(fsp, zwrite)) {
case PCF_FREECLUSTER:
return (ENOSPC);
case PCF_ERRORCLUSTER:
return (EIO);
}
pc_setcluster(fsp, cn, ncn);
} else if (!pc_validcl(fsp, ncn)) {
PC_DPRINTF1(1,
"pc_balloc: badfs ncn=%d\n", ncn);
(void) pc_badfs(fsp);
return (EIO);
}
cn = ncn;
}
/*
* Do not cache the new cluster/index values; when
* extending the file we're interested in the last
* written cluster and not the last cluster allocated.
*/
*dbnp = pc_cldaddr(fsp, cn);
return (0);
}
/*
* Free file cluster chain after the first skipcl clusters.
*/
int
pc_bfree(struct pcnode *pcp, pc_cluster32_t skipcl)
{
struct pcfs *fsp;
pc_cluster32_t cn;
pc_cluster32_t ncn;
int n;
struct vnode *vp;
vp = PCTOV(pcp);
fsp = VFSTOPCFS(vp->v_vfsp);
if (!IS_FAT32(fsp) && (vp->v_flag & VROOT)) {
panic("pc_bfree");
}
if (pcp->pc_size == 0 && vp->v_type == VREG) {
return (0);
}
if (vp->v_type == VREG) {
n = (int)howmany((offset_t)pcp->pc_size, fsp->pcfs_clsize);
if (n > fsp->pcfs_ncluster) {
PC_DPRINTF1(1, "pc_bfree: badfs n=%d\n", n);
(void) pc_badfs(fsp);
return (EIO);
}
} else {
n = fsp->pcfs_ncluster;
}
cn = pcp->pc_scluster;
if (IS_FAT32(fsp) && cn == 0)
cn = fsp->pcfs_rdirstart;
if (skipcl == 0) {
if (IS_FAT32(fsp))
pcp->pc_scluster = PCF_LASTCLUSTERMARK32;
else
pcp->pc_scluster = PCF_LASTCLUSTERMARK;
}
/* Invalidate last used cluster cache */
pcp->pc_lindex = 0;
pcp->pc_lcluster = pcp->pc_scluster;
while (n--) {
if (!pc_validcl(fsp, cn)) {
PC_DPRINTF1(1, "pc_bfree: badfs cn=%d\n", cn);
(void) pc_badfs(fsp);
return (EIO);
}
ncn = pc_getcluster(fsp, cn);
if (skipcl == 0) {
pc_setcluster(fsp, cn, PCF_FREECLUSTER);
} else {
skipcl--;
if (skipcl == 0) {
if (IS_FAT32(fsp)) {
pc_setcluster(fsp, cn,
PCF_LASTCLUSTERMARK32);
} else
pc_setcluster(fsp, cn,
PCF_LASTCLUSTERMARK);
}
}
if (IS_FAT32(fsp) && ncn >= PCF_LASTCLUSTER32 &&
vp->v_type == VDIR)
break;
if (!IS_FAT32(fsp) && ncn >= PCF_LASTCLUSTER &&
vp->v_type == VDIR)
break;
cn = ncn;
}
return (0);
}
/*
* Return the number of free blocks in the filesystem.
*/
int
pc_freeclusters(struct pcfs *fsp)
{
pc_cluster32_t cn;
int free = 0;
if (IS_FAT32(fsp) &&
fsp->pcfs_fsinfo.fs_free_clusters != FSINFO_UNKNOWN)
return (fsp->pcfs_fsinfo.fs_free_clusters);
/*
* make sure the FAT is in core
*/
for (cn = PCF_FIRSTCLUSTER; pc_validcl(fsp, cn); cn++) {
if (pc_getcluster(fsp, cn) == PCF_FREECLUSTER) {
free++;
}
}
if (IS_FAT32(fsp)) {
ASSERT(fsp->pcfs_fsinfo.fs_free_clusters == FSINFO_UNKNOWN);
fsp->pcfs_fsinfo.fs_free_clusters = free;
}
return (free);
}
/*
* Cluster manipulation routines.
* FAT must be resident.
*/
/*
* Get the next cluster in the file cluster chain.
* cn = current cluster number in chain
*/
static pc_cluster32_t
pc_getcluster(struct pcfs *fsp, pc_cluster32_t cn)
{
unsigned char *fp;
if (fsp->pcfs_fatp == (uchar_t *)0 || !pc_validcl(fsp, cn))
panic("pc_getcluster");
switch (fsp->pcfs_fattype) {
case FAT32:
fp = fsp->pcfs_fatp + (cn << 2);
cn = ltohi(*(pc_cluster32_t *)fp);
break;
case FAT16:
fp = fsp->pcfs_fatp + (cn << 1);
cn = ltohs(*(pc_cluster16_t *)fp);
break;
case FAT12:
fp = fsp->pcfs_fatp + (cn + (cn >> 1));
if (cn & 01) {
cn = (((unsigned int)*fp++ & 0xf0) >> 4);
cn += (*fp << 4);
} else {
cn = *fp++;
cn += ((*fp & 0x0f) << 8);
}
if (cn >= PCF_12BCLUSTER)
cn |= PCF_RESCLUSTER;
break;
default:
pc_mark_irrecov(fsp);
cn = PCF_ERRORCLUSTER;
}
return (cn);
}
/*
* Set a cluster in the FAT to a value.
* cn = cluster number to be set in FAT
* ncn = new value
*/
void
pc_setcluster(struct pcfs *fsp, pc_cluster32_t cn, pc_cluster32_t ncn)
{
unsigned char *fp;
pc_cluster16_t ncn16;
if (fsp->pcfs_fatp == (uchar_t *)0 || !pc_validcl(fsp, cn))
panic("pc_setcluster");
fsp->pcfs_flags |= PCFS_FATMOD;
pc_mark_fat_updated(fsp, cn);
switch (fsp->pcfs_fattype) {
case FAT32:
fp = fsp->pcfs_fatp + (cn << 2);
*(pc_cluster32_t *)fp = htoli(ncn);
break;
case FAT16:
fp = fsp->pcfs_fatp + (cn << 1);
ncn16 = (pc_cluster16_t)ncn;
*(pc_cluster16_t *)fp = htols(ncn16);
break;
case FAT12:
fp = fsp->pcfs_fatp + (cn + (cn >> 1));
if (cn & 01) {
*fp = (*fp & 0x0f) | ((ncn << 4) & 0xf0);
fp++;
*fp = (ncn >> 4) & 0xff;
} else {
*fp++ = ncn & 0xff;
*fp = (*fp & 0xf0) | ((ncn >> 8) & 0x0f);
}
break;
default:
pc_mark_irrecov(fsp);
}
if (ncn == PCF_FREECLUSTER) {
fsp->pcfs_nxfrecls = PCF_FIRSTCLUSTER;
if (IS_FAT32(fsp)) {
if (fsp->pcfs_fsinfo.fs_free_clusters !=
FSINFO_UNKNOWN)
fsp->pcfs_fsinfo.fs_free_clusters++;
}
}
}
/*
* Allocate a new cluster.
*/
pc_cluster32_t
pc_alloccluster(
struct pcfs *fsp, /* file sys to allocate in */
int zwrite) /* boolean for writing zeroes */
{
pc_cluster32_t cn;
int error;
if (fsp->pcfs_fatp == (uchar_t *)0)
panic("pc_addcluster: no FAT");
for (cn = fsp->pcfs_nxfrecls; pc_validcl(fsp, cn); cn++) {
if (pc_getcluster(fsp, cn) == PCF_FREECLUSTER) {
struct buf *bp;
if (IS_FAT32(fsp)) {
pc_setcluster(fsp, cn, PCF_LASTCLUSTERMARK32);
if (fsp->pcfs_fsinfo.fs_free_clusters !=
FSINFO_UNKNOWN)
fsp->pcfs_fsinfo.fs_free_clusters--;
} else
pc_setcluster(fsp, cn, PCF_LASTCLUSTERMARK);
if (zwrite) {
/*
* zero the new cluster
*/
bp = ngeteblk(fsp->pcfs_clsize);
bp->b_edev = fsp->pcfs_xdev;
bp->b_dev = cmpdev(bp->b_edev);
bp->b_blkno = pc_cldaddr(fsp, cn);
clrbuf(bp);
bwrite2(bp);
error = geterror(bp);
brelse(bp);
if (error) {
pc_mark_irrecov(fsp);
return (PCF_ERRORCLUSTER);
}
}
fsp->pcfs_nxfrecls = cn + 1;
return (cn);
}
}
return (PCF_FREECLUSTER);
}
/*
* Get the number of clusters used by a file or subdirectory
*/
int
pc_fileclsize(
struct pcfs *fsp,
pc_cluster32_t startcl, pc_cluster32_t *ncl)
{
int count = 0;
*ncl = 0;
for (count = 0; pc_validcl(fsp, startcl);
startcl = pc_getcluster(fsp, startcl)) {
if (count++ >= fsp->pcfs_ncluster)
return (EIO);
}
*ncl = (pc_cluster32_t)count;
return (0);
}