www.pudn.com > efs.rar > fs_extfs.c


/**********************************************************************
 * fs_extfs.c
 *
 * EFS2
 * Copyright (C) 2004--2006 Qualcomm, Inc.
 *
 * External filesystem mountpoint handler.
 *
 ***********************************************************************/

/*======================================================================
 *
 * EDIT HISTORY
 *
 * $Header: //depot/asic/MSMSHARED/services/efs/MSM_EFS.01.02/fs_extfs.c#12 $ $DateTime: 2006/11/13 09:30:29 $ $Author: davidb $
 *
 * when        who  what, where, why
 * ----------  ---  ----------------------------------------------------
 * 2006-11-10  sh   Add chmod support for FAT READONLY attribute
 * 2006-08-27  sh   Added HFAT option and FS_FAT_xxx macros
 * 2006-07-07  dlb  Implement truncate.
 * 2006-07-06  dlb  Remove 'size' field from vnode.
 * 2006-06-27  dlb  Pass stat through readdir.
 * 2006-05-12  dlb  Fix construction of inodes on create.
 * 2006-03-24  sh   Moved MAX mount count into common header.
 * 2006-02-16  dlb  Null terminate vnode name.
 * 2006-01-11  nrs  Fixed Copyright header
 * 2005-11-10  nrs  Merge fix
 * 2005-11-09  sh   Renamed FS_FEATURE_EXTFS to FEATURE_EFS_EXTFS
 * 2005-11-01  nrs  Merge into Q&R
 * 2005-10-24  dlb  Cleanup for SFAT release.
 * 2005-10-21  dlb  Eliminate some compiler warnings.
 * 2005-10-11  dlb  Pass name through to stop.
 * 2005-08-11  dlb  Add umount support.
 * 2005-07-13  dlb  Mountpoint cleanup.
 * 2005-07-12  dlb  Move transactions out of mount.
 * 2005-06-06  dlb  Extensions for SFAT.
 * 2004-10-07  dlb  Whitespace cleanup.
 * 2004-06-25  dlb  Created
 *=====================================================================*/

#include "fs_extfs.h"
#include "fs_errno.h"
#include "fs_err.h"
#include "fs_fcntl.h"

#ifdef FEATURE_IG_EFS_EXT_SERVER
  #include "amssassert.h"
#else
  #include "assert.h"
#endif

#ifdef FEATURE_EFS_EXTFS

/* There are currently two possible handlers for the EXTFS mountpoint:
 * SFAT and HFAT.   Only one can be enabled. */
#include "fs_ext_sfat.h"
#include "fs_ext_hfat.h"

static struct fs_mount_extfs extfs_mounts[FS_MAX_EXTFS_MOUNTS];

static int ext_start (struct fs_mount *mpp, const char *args);
static int ext_stop (struct fs_mount *mpp);
static int ext_get_root (struct fs_mount *mpp, struct fs_vnode **rp);
static struct fs_vnode *ext_create_inode (struct fs_mount *mpp,
    fs_mode_t mode, uint32 gid, uint32 uid);
static int ext_write_inode (struct fs_mount *mp, struct fs_vnode *vp);
static int ext_read_inode (struct fs_mount *mp, struct fs_vnode *vp,
    fs_inode_t inum);
static int ext_read_named_inode (struct fs_mount *mp, struct fs_vnode *vp,
    fs_mode_t mode, const char *key, int key_len);
static void ext_vnode_cleanup (struct fs_mount *mpp, struct fs_vnode *vp);

const struct fs_mount_ops extfs_ops = {
  ext_start,
  ext_stop,
  ext_get_root,
  ext_create_inode,
  ext_write_inode,
  ext_read_inode,
  ext_read_named_inode,
  ext_vnode_cleanup,
};

/* External FS specific operations. */
static int ext_getstatvfs (struct fs_vnode *vp, struct fs_statvfs *buf);
static int ext_lookup (struct nameidata *ndp);
static int ext_getstat (struct fs_vnode *vp, struct fs_stat *buf);
static void *ext_opendir (struct fs_vnode *vp);
static int ext_readdir (struct fs_vnode *vp, void *dir,
    struct fs_dirent *dirent);
static int ext_closedir (struct fs_vnode *vp, void *dir);
static fs_ssize_t ext_read (struct fs_vnode *vp, fs_off_t pos,
    void *buf, fs_size_t count);
static fs_ssize_t ext_write (struct fs_vnode *vp, fs_off_t pos,
    const void *buf, fs_size_t count);
static int ext_create (struct nameidata *ndp, fs_mode_t mode);
static int ext_mkdir (struct nameidata *ndp, fs_mode_t mode);
static int ext_rmdir (struct nameidata *ndp);
static int ext_unlink (struct nameidata *ndp);
static int ext_rename (struct nameidata *ndp, struct nameidata *ndp2);
static int ext_truncate (struct fs_vnode *vp, fs_off_t pos);
static int ext_chmod (struct fs_vnode *vp, fs_mode_t mode);

static const struct vfs_ops extfs_rootdir_vops = {
  ext_lookup,
  ext_create,
  fs_vnode_invalid_link,
  ext_unlink,
  fs_vnode_invalid_write,
  fs_vnode_invalid_read,
  fs_vnode_invalid_truncate,
  ext_mkdir,
  ext_rmdir,
  ext_opendir,
  ext_readdir,
  ext_closedir,
  ext_getstat,
  ext_rename,
  fs_vnode_invalid_symlink,
  fs_vnode_invalid_readlink,
  ext_getstatvfs,
  fs_vnode_invalid_put_item,
  fs_vnode_invalid_get_item,
  fs_vnode_invalid_mknod,
  fs_vnode_invalid_chmod,
  fs_vnode_invalid_chown,
  fs_vnode_invalid_set_reservation,
  fs_vnode_invalid_set_quota,
  fs_vnode_invalid_get_group_info,
};

/* Vops for extfs entities. */
static const struct vfs_ops extfs_dir_vops = {
  fs_vnode_invalid_lookup,
  ext_create,
  fs_vnode_invalid_link,
  fs_vnode_invalid_unlink,
  fs_vnode_invalid_write,
  fs_vnode_invalid_read,
  fs_vnode_invalid_truncate,
  ext_mkdir,
  ext_rmdir,
  ext_opendir,
  ext_readdir,
  ext_closedir,
  ext_getstat,
  ext_rename,
  fs_vnode_invalid_symlink,
  fs_vnode_invalid_readlink,
  ext_getstatvfs,
  fs_vnode_invalid_put_item,
  fs_vnode_invalid_get_item,
  fs_vnode_invalid_mknod,
  ext_chmod,
  fs_vnode_invalid_chown,
  fs_vnode_invalid_set_reservation,
  fs_vnode_invalid_set_quota,
  fs_vnode_invalid_get_group_info,
};

static const struct vfs_ops extfs_file_vops = {
  fs_vnode_invalid_lookup,
  fs_vnode_invalid_create,
  fs_vnode_invalid_link,
  fs_vnode_invalid_unlink,
  ext_write,
  ext_read,
  ext_truncate,
  fs_vnode_invalid_mkdir,
  fs_vnode_invalid_rmdir,
  fs_vnode_invalid_opendir,
  fs_vnode_invalid_readdir,
  fs_vnode_invalid_closedir,
  ext_getstat,
  fs_vnode_invalid_rename,
  fs_vnode_invalid_symlink,
  fs_vnode_invalid_readlink,
  ext_getstatvfs,
  fs_vnode_invalid_put_item,
  fs_vnode_invalid_get_item,
  fs_vnode_invalid_mknod,
  ext_chmod,
  fs_vnode_invalid_chown,
  fs_vnode_invalid_set_reservation,
  fs_vnode_invalid_set_quota,
  fs_vnode_invalid_get_group_info,
};

/* For other filetypes.  Just make everything invalid. */
static const struct vfs_ops extfs_other_vops = {
  fs_vnode_invalid_lookup,
  fs_vnode_invalid_create,
  fs_vnode_invalid_link,
  fs_vnode_invalid_unlink,
  fs_vnode_invalid_write,
  fs_vnode_invalid_read,
  fs_vnode_invalid_truncate,
  fs_vnode_invalid_mkdir,
  fs_vnode_invalid_rmdir,
  fs_vnode_invalid_opendir,
  fs_vnode_invalid_readdir,
  fs_vnode_invalid_closedir,
  fs_vnode_invalid_getstat,
  fs_vnode_invalid_rename,
  fs_vnode_invalid_symlink,
  fs_vnode_invalid_readlink,
  ext_getstatvfs,
  fs_vnode_invalid_put_item,
  fs_vnode_invalid_get_item,
  fs_vnode_invalid_mknod,
  fs_vnode_invalid_chmod,
  fs_vnode_invalid_chown,
  fs_vnode_invalid_set_reservation,
  fs_vnode_invalid_set_quota,
  fs_vnode_invalid_get_group_info,
};

#define ROOT_INODE      2
#define INVALID_INODE_NUMBER  0xffffffff

/* FD modes.  Determins how the underlying file was opened. */
#define FD_MODE_NONE    0
#define FD_MODE_READ    1
#define FD_MODE_READWRITE 2

static int
ext_start (struct fs_mount *mpp, const char *args)
{
  struct fs_mount_extfs *mp = (struct fs_mount_extfs *) mpp;
  struct fs_vnode *root;
  int result;

  if (strlen (args) > FS_PATH_MAX)
    return -ENAMETOOLONG;

  strcpy (mp->prefix, args);

  /* printf ("Mounting: %s\n", args); */

  root = fs_vnode_alloc ();
  if (root == NULL)
    return -ENOSPC;

  /* No, these don't have inodes. */
  /* fs_inode_construct (&root->p.inode, S_IFDIR | 0755); */

  root->mp = mpp;
  root->dev = mpp->dev;
  root->inum = ROOT_INODE;
  root->dirty = 0;
  root->vops = &extfs_rootdir_vops;
  root->mode = S_IFDIR | 0755;

  /* Be sure to fill in the prefix as the name, so that stat of the root
   * directory works.  Note that some filesystems will want a slash and
   * some won't.  For now we put the trailing slash, and assume that future
   * filesystems that don't want it will change this to be configurable. */
  strcpy (root->p.named.key, mp->prefix);
  strcat (root->p.named.key, "/");
  root->p.named.key_length = strlen (root->p.named.key);
  root->p.named.fd = -1;
  root->p.named.fd_mode = 0;

  mp->root = root;

  result = mp->ops->start (mp->prefix);
  if (result != 0) {
    fs_vnode_unref (root);
    mp->root = NULL;
  }
  return result;
}

static int
ext_stop (struct fs_mount *mpp)
{
  int result;
  struct fs_mount_extfs *mp = (struct fs_mount_extfs *) mpp;

  result = mp->ops->stop (mp->prefix);

  return result;
}

static int
ext_get_root (struct fs_mount *mpp, struct fs_vnode **rp)
{
  struct fs_mount_extfs *mp = (struct fs_mount_extfs *) mpp;

  if (mp->root == NULL)
    return -EINVAL;

  *rp = mp->root;
  return 0;
}

static struct
fs_vnode *ext_create_inode (struct fs_mount *mpp,
    fs_mode_t mode, uint32 uid, uint32 gid)
{
  (void) mpp;
  (void) mode;
  (void) uid;
  (void) gid;

  FS_ERR_FATAL ("ext_create_inode", 0, 0, 0);

  /* Eliminate compiler warning. */
  return NULL;
}

static int
ext_write_inode (struct fs_mount *mp, struct fs_vnode *vp)
{
  (void) mp;
  (void) vp;

  FS_ERR_FATAL ("ext_write_inode", 0, 0, 0);

  /* Eliminate compiler warning. */
  return -EINVAL;
}

static int
ext_read_inode (struct fs_mount *mp, struct fs_vnode *vp,
    fs_inode_t inum)
{
  (void) mp;
  (void) vp;
  (void) inum;

  FS_ERR_FATAL ("ext_read_inode", 0, 0, 0);

  /* Compiler warning. */
  return -EINVAL;
}

/* Construct an inode for a given named entity.  This needs to determine
 * the type of the file, and fill in the appropriate methods. */
static int
ext_read_named_inode (struct fs_mount *mpp, struct fs_vnode *vp,
    fs_mode_t mode, const char *key, int key_len)
{
  struct fs_mount_extfs *mp = (struct fs_mount_extfs *) mpp;
  int result;

  (void) mode;   /* Not used. */
  (void) mpp;

  vp->p.named.key_length = key_len;
  memcpy (vp->p.named.key, key, key_len);
  vp->p.named.key[key_len] = '\0';

  result = mp->ops->base_stat (vp->p.named.key, &vp->mode);

  if (result < 0)
    return result;

  vp->p.named.fd = -1;
  vp->p.named.fd_mode = 0;

  if (S_ISREG (vp->mode)) {
    vp->vops = &extfs_file_vops;
  } else if (S_ISDIR (vp->mode)) {
    vp->vops = &extfs_dir_vops;
  } else {
    vp->vops = &extfs_other_vops;
  }

  return 0;
}

static void
ext_vnode_cleanup (struct fs_mount *mpp, struct fs_vnode *vp)
{
  struct fs_mount_extfs *mp = (struct fs_mount_extfs *) mpp;

  if (vp->p.named.fd_mode != FD_MODE_NONE) {
    (void) mp->ops->close (vp->p.named.fd);
    vp->p.named.fd_mode = FD_MODE_NONE;
    vp->p.named.fd = -1;
  }
}

/* Implementation specific to a given external filesystem. */

/* Local buffer for external filesystems.  This is used to build up
 * pathnames. */
static char ext_tmp_path[FS_PATH_MAX+1];

/* Lookup is different than internal filesystems.  We grab the entire rest
 * of the name, and just look that up locally. */
static int
ext_lookup (struct nameidata *ndp)
{
  struct fs_mount_extfs *mp = (struct fs_mount_extfs *) ndp->dvp->mp;
  struct fs_vnode *vp;
  const int length = strlen (ndp->next);
  const char *name;
  int tlength;

  /* printf ("ext_lookup: \"%s\"\n", ndp->next); */

  /* Adjust the length field to indicate to namei that we've parsed the
   * entire name. */
  ndp->length = length;

  if (strlen (mp->prefix) + length + 2 >= FS_PATH_MAX)
    return -ENAMETOOLONG;

  strcpy (ext_tmp_path, mp->prefix);
  strcat (ext_tmp_path, "/");

  name = ndp->next;

  /* FAT doesn't have a "." or ".." entry in the root directory.  Since it
   * is usually the root of the filesystem that is the root of the
   * mountpoint, "." and ".." won't work there.  Go through the beginning
   * of the path given, and make a simple attempt to remove any "." or ".."
   * as well as extra slashes that appear in the name given. */
  while (1) {
    if (name[0] == '.' && name[1] == '/')
      name += 2;
    else if (name[0] == '.' && name[1] == 0)
      name++;
    else if (name[0] == '.' && name[1] == '.' && name[2] == '/')
      name += 3;
    else if (name[0] == '.' && name[1] == '.' && name[2] == 0)
      name += 2;
    else
      break;
  }

  strcat (ext_tmp_path, name);

  tlength = strlen (ext_tmp_path);

  /* Look up this name in the vnode cache.  If the name is found, use the
   * same vnode to refer to this file (meaning that multiple opens of the
   * same file will use the same SFAT file handle). */
  vp = fs_vnode_named_lookup (&mp->parent, mp->parent.dev,
      0, ext_tmp_path, tlength);

  if (vp == NULL)
    return -ENOENT;

  ndp->vp = vp;

  return 0;
}

static int
ext_getstatvfs (struct fs_vnode *vp, struct fs_statvfs *buf)
{
  struct fs_mount_extfs *mp = (struct fs_mount_extfs *) vp->mp;

  return mp->ops->statvfs (mp->prefix, buf);
}

static void *
ext_opendir (struct fs_vnode *vp)
{
  struct fs_mount_extfs *mp = (struct fs_mount_extfs *) vp->mp;

  return mp->ops->opendir (vp->p.named.key);
}

static int
ext_readdir (struct fs_vnode *vp, void *dir, struct fs_dirent *dirent)
{
  struct fs_mount_extfs *mp = (struct fs_mount_extfs *) vp->mp;

  return mp->ops->readdir (dir, dirent);
}

static int
ext_closedir (struct fs_vnode *vp, void *dir)
{
  struct fs_mount_extfs *mp = (struct fs_mount_extfs *) vp->mp;

  return mp->ops->closedir (dir);
}

static int
ext_getstat (struct fs_vnode *vp, struct fs_stat *buf)
{
  struct fs_mount_extfs *mp = (struct fs_mount_extfs *) vp->mp;

  buf->st_dev = mp->parent.dev;
  buf->st_ino = INVALID_INODE_NUMBER;

  return mp->ops->getstat (vp->p.named.key, buf);
}

/* Ensure that the descriptor for the given vnode is opened for at least
 * the required mode.  Should always be called with either FD_MODE_READ or
 * FD_MODE_READWRITE.  Returns 0 on success or a negative errno. */
static int
ext_set_open_mode (struct fs_vnode *vp, int required_mode)
{
  struct fs_mount_extfs *mp = (struct fs_mount_extfs *) vp->mp;
  int fd;
  int mode;

  /* Sanity check on the argument. */
  ASSERT (required_mode == FD_MODE_READ ||
      required_mode == FD_MODE_READWRITE);

  /* If we're requesting write and it was only opened in read mode, close
   * it first. */
  if (required_mode == FD_MODE_READWRITE &&
      vp->p.named.fd_mode == FD_MODE_READ)
  {
    (void) mp->ops->close (vp->p.named.fd);
    vp->p.named.fd_mode = FD_MODE_NONE;
  }

  /* Open the descriptor, if it isn't already open. */
  if (vp->p.named.fd_mode == FD_MODE_NONE) {
    mode = O_RDONLY;
    if (required_mode == FD_MODE_READWRITE)
      mode = O_RDWR;

    fd = mp->ops->open (vp->p.named.key, mode);

    if (fd < 0)
      return fd;

    vp->p.named.fd_mode = required_mode;
    vp->p.named.fd = fd;
  }

  return 0;
}

static fs_ssize_t
ext_read (struct fs_vnode *vp, fs_off_t pos,
    void *buf, fs_size_t count)
{
  struct fs_mount_extfs *mp = (struct fs_mount_extfs *) vp->mp;
  int fd;
  int result;
  int rcount;

  /* Make sure the FD is opened. */
  result = ext_set_open_mode (vp, FD_MODE_READ);
  if (result < 0) {
    return result;
  }

  fd = vp->p.named.fd;

  result = mp->ops->lseek (fd, pos);
  if (result < 0) {
    return result;
  }

  rcount = mp->ops->read (fd, buf, count);

  return rcount;
}

static fs_ssize_t
ext_write (struct fs_vnode *vp, fs_off_t pos,
    const void *buf, fs_size_t count)
{
  struct fs_mount_extfs *mp = (struct fs_mount_extfs *) vp->mp;
  int fd;
  int result;
  int rcount;

  /* Make sure the FD is opened right. */
  result = ext_set_open_mode (vp, FD_MODE_READWRITE);
  if (result < 0) {
    return result;
  }

  fd = vp->p.named.fd;

  result = mp->ops->lseek (fd, pos);
  if (result < 0) {
    return result;
  }

  rcount = mp->ops->write (fd, buf, count);

  return rcount;
}

static int
ext_create (struct nameidata *ndp, fs_mode_t mode)
{
  struct fs_mount_extfs *mp = (struct fs_mount_extfs *) ndp->dvp->mp;
  int length = strlen (ndp->next);
  struct fs_vnode *vp;
  int result;
  int tlength;

  if (strlen (mp->prefix) + length + 2 >= FS_PATH_MAX)
    return -ENAMETOOLONG;

  strcpy (ext_tmp_path, mp->prefix);
  strcat (ext_tmp_path, "/");
  strcat (ext_tmp_path, ndp->next);

  tlength = strlen (ext_tmp_path);

  /* Ignore mode. */
  (void) mode;

  vp = fs_vnode_alloc ();
  if (vp == NULL)
    return -ENOSPC;

  result = mp->ops->open (ext_tmp_path, O_CREAT | O_EXCL | O_RDWR);
  if (result < 0) {
    fs_vnode_unref (vp);
    return result;
  }

  /* Construct the vnode correctly. */
  vp->dev = mp->parent.dev;
  vp->inum = FS_INVALID_INODE;
  vp->mp = &mp->parent;
  vp->dirty = 0;

  /* From ext_read_named_inode */
  vp->p.named.key_length = tlength;
  strcpy (vp->p.named.key, ext_tmp_path);
  vp->mode = S_IFREG | 0644;
  vp->vops = &extfs_file_vops;

  vp->p.named.fd_mode = FD_MODE_READWRITE;
  vp->p.named.fd = result;

  ndp->vp = vp;

  return 0;
}

static int
ext_mkdir (struct nameidata *ndp, fs_mode_t mode)
{
  struct fs_mount_extfs *mp = (struct fs_mount_extfs *) ndp->dvp->mp;
  int length = strlen (ndp->next);

  /* No modes here. */
  (void) mode;

  if (strlen (mp->prefix) + length + 2 >= FS_PATH_MAX)
    return -ENAMETOOLONG;

  strcpy (ext_tmp_path, mp->prefix);
  strcat (ext_tmp_path, "/");
  strcat (ext_tmp_path, ndp->next);

  return mp->ops->mkdir (ext_tmp_path);
}

static int
ext_rmdir (struct nameidata *ndp)
{
  struct fs_mount_extfs *mp = (struct fs_mount_extfs *) ndp->dvp->mp;
  int result;

  result = mp->ops->rmdir (ndp->vp->p.named.key);

  if (result == 0) {
    fs_vnode_unref (ndp->vp);
    ndp->vp = NULL;
  }

  return result;
}

static int
ext_unlink (struct nameidata *ndp)
{
  struct fs_mount_extfs *mp = (struct fs_mount_extfs *) ndp->dvp->mp;
  int result;

  result = mp->ops->unlink (ndp->vp->p.named.key);

  fs_vnode_unref (ndp->vp);
  ndp->vp = NULL;

  return result;
}

static int
ext_rename (struct nameidata *ndp, struct nameidata *ndp2)
{
  struct fs_mount_extfs *mp = (struct fs_mount_extfs *) ndp->dvp->mp;
  int result;

  /* If the destination exists, we already have a name for it. */
  if (ndp2->vp != NULL) {
    result = mp->ops->rename (ndp->vp->p.named.key,
        ndp2->vp->p.named.key);
  } else {
    /* Construct the new name. */
    if (strlen (mp->prefix) + strlen (ndp2->next) + 2 >= FS_PATH_MAX)
      return -ENAMETOOLONG;

    strcpy (ext_tmp_path, mp->prefix);
    strcat (ext_tmp_path, "/");
    strcat (ext_tmp_path, ndp2->next);

    result = mp->ops->rename (ndp->vp->p.named.key, ext_tmp_path);
  }

  return result;
}

static int
ext_truncate (struct fs_vnode *vp, fs_off_t pos)
{
  struct fs_mount_extfs *mp = (struct fs_mount_extfs *) vp->mp;
  int result;

  result = ext_set_open_mode (vp, FD_MODE_READWRITE);
  if (result < 0)
    return result;

  return mp->ops->truncate (vp->p.named.fd, pos);
}

static int
ext_chmod (struct fs_vnode *vp, fs_mode_t mode)
{
  struct fs_mount_extfs *mp = (struct fs_mount_extfs *) vp->mp;

  return mp->ops->chmod (vp->p.named.key, mode);
}

void
fs_extfs_init (void)
{
  int i;

  FS_FAT_INIT();

  for (i = 0; i < FS_MAX_EXTFS_MOUNTS; i++) {
    extfs_mounts[i].root = NULL;
    extfs_mounts[i].parent.ops = &extfs_ops;

    /* XXX: Make this dynamic. */
    extfs_mounts[i].ops = &FS_FAT_OPS;

    fs_mount_register ("extfs", &extfs_mounts[i].parent);
  }
}

#endif /* FEATURE_EFS_EXTFS */