/*
* Copyright (c) 2005-2008, Kohsuke Ohtani
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the author nor the names of any co-contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <prex/prex.h>
#include <sys/vnode.h>
#include <sys/file.h>
#include <sys/mount.h>
#include <sys/dirent.h>
#include <sys/buf.h>
#include <ctype.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include "fatfs.h"
/*
* Time bits: 15-11 hours (0-23), 10-5 min, 4-0 sec /2
* Date bits: 15-9 year - 1980, 8-5 month, 4-0 day
*/
#define TEMP_DATE 0x3021
#define TEMP_TIME 0
#define fatfs_open ((vnop_open_t)vop_nullop)
#define fatfs_close ((vnop_close_t)vop_nullop)
static int fatfs_read (vnode_t, file_t, void *, size_t, size_t *);
static int fatfs_write (vnode_t, file_t, void *, size_t, size_t *);
#define fatfs_seek ((vnop_seek_t)vop_nullop)
#define fatfs_ioctl ((vnop_ioctl_t)vop_einval)
#define fatfs_fsync ((vnop_fsync_t)vop_nullop)
static int fatfs_readdir(vnode_t, file_t, struct dirent *);
static int fatfs_lookup (vnode_t, char *, vnode_t);
static int fatfs_create (vnode_t, char *, mode_t);
static int fatfs_remove (vnode_t, vnode_t, char *);
static int fatfs_rename (vnode_t, vnode_t, char *, vnode_t, vnode_t, char *);
static int fatfs_mkdir (vnode_t, char *, mode_t);
static int fatfs_rmdir (vnode_t, vnode_t, char *);
static int fatfs_getattr(vnode_t, struct vattr *);
static int fatfs_setattr(vnode_t, struct vattr *);
static int fatfs_inactive(vnode_t);
static int fatfs_truncate(vnode_t);
/*
* vnode operations
*/
struct vnops fatfs_vnops = {
fatfs_open, /* open */
fatfs_close, /* close */
fatfs_read, /* read */
fatfs_write, /* write */
fatfs_seek, /* seek */
fatfs_ioctl, /* ioctl */
fatfs_fsync, /* fsync */
fatfs_readdir, /* readdir */
fatfs_lookup, /* lookup */
fatfs_create, /* create */
fatfs_remove, /* remove */
fatfs_rename, /* remame */
fatfs_mkdir, /* mkdir */
fatfs_rmdir, /* rmdir */
fatfs_getattr, /* getattr */
fatfs_setattr, /* setattr */
fatfs_inactive, /* inactive */
fatfs_truncate, /* truncate */
};
/*
* Read one cluster to buffer.
*/
static int
fat_read_cluster(struct fatfsmount *fmp, u_long cluster)
{
u_long sec;
size_t size;
sec = cl_to_sec(fmp, cluster);
size = fmp->sec_per_cl * SEC_SIZE;
return device_read(fmp->dev, fmp->io_buf, &size, sec);
}
/*
* Write one cluster from buffer.
*/
static int
fat_write_cluster(struct fatfsmount *fmp, u_long cluster)
{
u_long sec;
size_t size;
sec = cl_to_sec(fmp, cluster);
size = fmp->sec_per_cl * SEC_SIZE;
return device_write(fmp->dev, fmp->io_buf, &size, sec);
}
/*
* Lookup vnode for the specified file/directory.
* The vnode data will be set properly.
*/
static int
fatfs_lookup(vnode_t dvp, char *name, vnode_t vp)
{
struct fatfsmount *fmp;
struct fat_dirent *de;
struct fatfs_node *np;
int err;
if (*name == '\0')
return ENOENT;
fmp = vp->v_mount->m_data;
mutex_lock(&fmp->lock);
DPRINTF(("fatfs_lookup: name=%s\n", name));
np = vp->v_data;
err = fatfs_lookup_node(dvp, name, np);
if (err) {
DPRINTF(("fatfs_lookup: failed!! name=%s\n", name));
mutex_unlock(&fmp->lock);
return err;
}
de = &np->dirent;
vp->v_type = IS_DIR(de) ? VDIR : VREG;
fat_attr_to_mode(de->attr, &vp->v_mode);
vp->v_mode = ALLPERMS;
vp->v_size = de->size;
vp->v_blkno = de->cluster;
DPRINTF(("fatfs_lookup: cl=%d\n", de->cluster));
mutex_unlock(&fmp->lock);
return 0;
}
static int
fatfs_read(vnode_t vp, file_t fp, void *buf, size_t size, size_t *result)
{
struct fatfsmount *fmp;
int nr_read, nr_copy, buf_pos, err;
u_long cl, file_pos;
DPRINTF(("fatfs_read: vp=%x\n", vp));
*result = 0;
fmp = vp->v_mount->m_data;
if (vp->v_type == VDIR)
return EISDIR;
if (vp->v_type != VREG)
return EINVAL;
/* Check if current file position is already end of file. */
file_pos = fp->f_offset;
if (file_pos >= vp->v_size)
return 0;
mutex_lock(&fmp->lock);
/* Get the actual read size. */
if (vp->v_size - file_pos < size)
size = vp->v_size - file_pos;
/* Seek to the cluster for the file offset */
err = fat_seek_cluster(fmp, vp->v_blkno, file_pos, &cl);
if (err)
goto out;
/* Read and copy data */
nr_read = 0;
buf_pos = file_pos % fmp->cluster_size;
do {
if (fat_read_cluster(fmp, cl)) {
err = EIO;
goto out;
}
nr_copy = fmp->cluster_size;
if (buf_pos > 0)
nr_copy -= buf_pos;
if (buf_pos + size < fmp->cluster_size)
nr_copy = size;
memcpy(buf, fmp->io_buf + buf_pos, nr_copy);
file_pos += nr_copy;
nr_read += nr_copy;
size -= nr_copy;
if (size <= 0)
break;
err = fat_next_cluster(fmp, cl, &cl);
if (err)
goto out;
buf = (void *)((u_long)buf + nr_copy);
buf_pos = 0;
} while (!IS_EOFCL(fmp, cl));
fp->f_offset = file_pos;
*result = nr_read;
err = 0;
out:
mutex_unlock(&fmp->lock);
return err;
}
static int
fatfs_write(vnode_t vp, file_t fp, void *buf, size_t size, size_t *result)
{
struct fatfsmount *fmp;
struct fatfs_node *np;
struct fat_dirent *de;
int nr_copy, nr_write, buf_pos, i, cl_size, err;
u_long file_pos, end_pos;
u_long cl;
DPRINTF(("fatfs_write: vp=%x\n", vp));
*result = 0;
fmp = vp->v_mount->m_data;
if (vp->v_type == VDIR)
return EISDIR;
if (vp->v_type != VREG)
return EINVAL;
mutex_lock(&fmp->lock);
/* Check if file position exceeds the end of file. */
end_pos = vp->v_size;
file_pos = (fp->f_flags & O_APPEND) ? end_pos : fp->f_offset;
if (file_pos + size > end_pos) {
/* Expand the file size before writing to it */
end_pos = file_pos + size;
err = fat_expand_file(fmp, vp->v_blkno, end_pos);
if (err) {
err = EIO;
goto out;
}
/* Update directory entry */
np = vp->v_data;
de = &np->dirent;
de->size = end_pos;
err = fatfs_put_node(fmp, np);
if (err)
goto out;
vp->v_size = end_pos;
}
/* Seek to the cluster for the file offset */
err = fat_seek_cluster(fmp, vp->v_blkno, file_pos, &cl);
if (err)
goto out;
buf_pos = file_pos % fmp->cluster_size;
cl_size = size / fmp->cluster_size + 1;
nr_write = 0;
i = 0;
do {
/* First and last cluster must be read before write */
if (i == 0 || i == cl_size) {
if (fat_read_cluster(fmp, cl)) {
err = EIO;
goto out;
}
}
nr_copy = fmp->cluster_size;
if (buf_pos > 0)
nr_copy -= buf_pos;
if (buf_pos + size < fmp->cluster_size)
nr_copy = size;
memcpy(fmp->io_buf + buf_pos, buf, nr_copy);
if (fat_write_cluster(fmp, cl)) {
err = EIO;
goto out;
}
file_pos += nr_copy;
nr_write += nr_copy;
size -= nr_copy;
if (size <= 0)
break;
err = fat_next_cluster(fmp, cl, &cl);
if (err)
goto out;
buf = (void *)((u_long)buf + nr_copy);
buf_pos = 0;
i++;
} while (!IS_EOFCL(fmp, cl));
fp->f_offset = file_pos;
/*
* XXX: Todo!
* de.time = ?
* de.date = ?
* if (dirent_set(fp, &de))
* return EIO;
*/
*result = nr_write;
err = 0;
out:
mutex_unlock(&fmp->lock);
return err;
}
static int
fatfs_readdir(vnode_t vp, file_t fp, struct dirent *dir)
{
struct fatfsmount *fmp;
struct fatfs_node np;
struct fat_dirent *de;
int err;
fmp = vp->v_mount->m_data;
mutex_lock(&fmp->lock);
err = fatfs_get_node(vp, fp->f_offset, &np);
if (err)
goto out;
de = &np.dirent;
fat_restore_name((char *)&de->name, dir->d_name);
if (de->attr & FA_SUBDIR)
dir->d_type = DT_DIR;
else if (de->attr & FA_DEVICE)
dir->d_type = DT_BLK;
else
dir->d_type = DT_REG;
dir->d_fileno = fp->f_offset;
dir->d_namlen = strlen(dir->d_name);
fp->f_offset++;
err = 0;
out:
mutex_unlock(&fmp->lock);
return err;
}
/*
* Create empty file.
*/
static int
fatfs_create(vnode_t dvp, char *name, mode_t mode)
{
struct fatfsmount *fmp;
struct fatfs_node np;
struct fat_dirent *de;
u_long cl;
int err;
DPRINTF(("fatfs_create: %s\n", name));
if (!S_ISREG(mode))
return EINVAL;
if (!fat_valid_name(name))
return EINVAL;
fmp = dvp->v_mount->m_data;
mutex_lock(&fmp->lock);
/* Allocate free cluster for new file. */
err = fat_alloc_cluster(fmp, 0, &cl);
if (err)
goto out;
de = &np.dirent;
memset(de, 0, sizeof(struct fat_dirent));
fat_convert_name(name, (char *)de->name);
de->cluster = cl;
de->time = TEMP_TIME;
de->date = TEMP_DATE;
fat_mode_to_attr(mode, &de->attr);
err = fatfs_add_node(dvp, &np);
if (err)
goto out;
err = fat_set_cluster(fmp, cl, fmp->fat_eof);
out:
mutex_unlock(&fmp->lock);
return err;
}
static int
fatfs_remove(vnode_t dvp, vnode_t vp, char *name)
{
struct fatfsmount *fmp;
struct fatfs_node np;
struct fat_dirent *de;
int err;
if (*name == '\0')
return ENOENT;
fmp = dvp->v_mount->m_data;
mutex_lock(&fmp->lock);
err = fatfs_lookup_node(dvp, name, &np);
if (err)
goto out;
de = &np.dirent;
if (IS_DIR(de)) {
err = EISDIR;
goto out;
}
if (!IS_FILE(de)) {
err = EPERM;
goto out;
}
/* Remove clusters */
err = fat_free_clusters(fmp, de->cluster);
if (err)
goto out;
/* remove directory */
de->name[0] = 0xe5;
err = fatfs_put_node(fmp, &np);
out:
mutex_unlock(&fmp->lock);
return err;
}
static int
fatfs_rename(vnode_t dvp1, vnode_t vp1, char *name1,
vnode_t dvp2, vnode_t vp2, char *name2)
{
struct fatfsmount *fmp;
struct fatfs_node np1;
struct fat_dirent *de1, *de2;
int err;
fmp = dvp1->v_mount->m_data;
mutex_lock(&fmp->lock);
err = fatfs_lookup_node(dvp1, name1, &np1);
if (err)
goto out;
de1 = &np1.dirent;
if (IS_FILE(de1)) {
/* Remove destination file, first */
err = fatfs_remove(dvp2, vp1, name2);
if (err == EIO)
goto out;
/* Change file name of directory entry */
fat_convert_name(name2, (char *)de1->name);
/* Same directory ? */
if (dvp1 == dvp2) {
/* Change the name of existing file */
err = fatfs_put_node(fmp, &np1);
if (err)
goto out;
} else {
/* Create new directory entry */
err = fatfs_add_node(dvp2, &np1);
if (err)
goto out;
/* Remove souce file */
err = fatfs_remove(dvp1, vp2, name1);
if (err)
goto out;
}
} else {
/* remove destination directory */
err = fatfs_rmdir(dvp2, NULL, name2);
if (err == EIO)
goto out;
/* Change file name of directory entry */
fat_convert_name(name2, (char *)de1->name);
/* Same directory ? */
if (dvp1 == dvp2) {
/* Change the name of existing directory */
err = fatfs_put_node(fmp, &np1);
if (err)
goto out;
} else {
/* Create new directory entry */
err = fatfs_add_node(dvp2, &np1);
if (err)
goto out;
/* Update "." and ".." for renamed directory */
if (fat_read_cluster(fmp, de1->cluster)) {
err = EIO;
goto out;
}
de2 = (struct fat_dirent *)fmp->io_buf;
de2->cluster = de1->cluster;
de2->time = TEMP_TIME;
de2->date = TEMP_DATE;
de2++;
de2->cluster = dvp2->v_blkno;
de2->time = TEMP_TIME;
de2->date = TEMP_DATE;
if (fat_write_cluster(fmp, de1->cluster)) {
err = EIO;
goto out;
}
/* Remove souce directory */
err = fatfs_rmdir(dvp1, NULL, name1);
if (err)
goto out;
}
}
out:
mutex_unlock(&fmp->lock);
return err;
}
static int
fatfs_mkdir(vnode_t dvp, char *name, mode_t mode)
{
struct fatfsmount *fmp;
struct fatfs_node np;
struct fat_dirent *de;
u_long cl;
int err;
if (!S_ISDIR(mode))
return EINVAL;
if (!fat_valid_name(name))
return ENOTDIR;
fmp = dvp->v_mount->m_data;
mutex_lock(&fmp->lock);
/* Allocate free cluster for directory data */
err = fat_alloc_cluster(fmp, 0, &cl);
if (err)
goto out;
memset(&np, 0, sizeof(struct fatfs_node));
de = &np.dirent;
fat_convert_name(name, (char *)&de->name);
de->cluster = cl;
de->time = TEMP_TIME;
de->date = TEMP_DATE;
fat_mode_to_attr(mode, &de->attr);
err = fatfs_add_node(dvp, &np);
if (err)
goto out;
/* Initialize "." and ".." for new directory */
memset(fmp->io_buf, 0, fmp->cluster_size);
de = (struct fat_dirent *)fmp->io_buf;
memcpy(de->name, ". ", 11);
de->attr = FA_SUBDIR;
de->cluster = cl;
de->time = TEMP_TIME;
de->date = TEMP_DATE;
de++;
memcpy(de->name, ".. ", 11);
de->attr = FA_SUBDIR;
de->cluster = dvp->v_blkno;
de->time = TEMP_TIME;
de->date = TEMP_DATE;
if (fat_write_cluster(fmp, cl)) {
err = EIO;
goto out;
}
/* Add eof */
err = fat_set_cluster(fmp, cl, fmp->fat_eof);
out:
mutex_unlock(&fmp->lock);
return err;
}
/*
* remove can be done only with empty directory
*/
static int
fatfs_rmdir(vnode_t dvp, vnode_t vp, char *name)
{
struct fatfsmount *fmp;
struct fatfs_node np;
struct fat_dirent *de;
int err;
if (*name == '\0')
return ENOENT;
fmp = dvp->v_mount->m_data;
mutex_lock(&fmp->lock);
err = fatfs_lookup_node(dvp, name, &np);
if (err)
goto out;
de = &np.dirent;
if (!IS_DIR(de)) {
err = ENOTDIR;
goto out;
}
/* Remove clusters */
err = fat_free_clusters(fmp, de->cluster);
if (err)
goto out;
/* remove directory */
de->name[0] = 0xe5;
err = fatfs_put_node(fmp, &np);
out:
mutex_unlock(&fmp->lock);
return err;
}
static int
fatfs_getattr(vnode_t vp, struct vattr *vap)
{
/* XXX */
return 0;
}
static int
fatfs_setattr(vnode_t vp, struct vattr *vap)
{
/* XXX */
return 0;
}
static int
fatfs_inactive(vnode_t vp)
{
free(vp->v_data);
return 0;
}
static int
fatfs_truncate(vnode_t vp)
{
struct fatfsmount *fmp;
struct fatfs_node *np;
struct fat_dirent *de;
int err;
fmp = vp->v_mount->m_data;
mutex_lock(&fmp->lock);
np = vp->v_data;
de = &np->dirent;
/* Remove clusters */
err = fat_free_clusters(fmp, de->cluster);
if (err)
goto out;
de->size = 0;
err = fatfs_put_node(fmp, np);
if (err)
goto out;
vp->v_size = 0;
out:
mutex_unlock(&fmp->lock);
return err;
}
int
fatfs_init(void)
{
return 0;
}