dirops.c revision 2ab50b1adde63df0e13b0bda106b86feb3b323fd
/** @file
*
* vboxsf -- VirtualBox Guest Additions for Linux:
* Directory inode and file operations
*/
/*
* Copyright (C) 2006-2007 Oracle Corporation
*
* This file is part of VirtualBox Open Source Edition (OSE), as
* available from http://www.virtualbox.org. This file is free software;
* you can redistribute it and/or modify it under the terms of the GNU
* General Public License (GPL) as published by the Free Software
* Foundation, in version 2 as it comes in the "COPYING" file of the
* VirtualBox OSE distribution. VirtualBox OSE is distributed in the
* hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
*/
#include "vfsmod.h"
/**
* Open a directory. Read the complete content into a buffer.
*
* @param inode inode
* @param file file
* @returns 0 on success, Linux error code otherwise
*/
static int sf_dir_open(struct inode *inode, struct file *file)
{
int rc;
int err;
struct sf_glob_info *sf_g = GET_GLOB_INFO(inode->i_sb);
struct sf_dir_info *sf_d;
struct sf_inode_info *sf_i = GET_INODE_INFO(inode);
SHFLCREATEPARMS params;
TRACE();
BUG_ON(!sf_g);
BUG_ON(!sf_i);
if (file->private_data)
{
LogFunc(("sf_dir_open() called on already opened directory '%s'\n",
sf_i->path->String.utf8));
return 0;
}
sf_d = sf_dir_info_alloc();
if (!sf_d)
{
LogRelFunc(("could not allocate directory info for '%s'\n",
sf_i->path->String.utf8));
return -ENOMEM;
}
RT_ZERO(params);
params.Handle = SHFL_HANDLE_NIL;
params.CreateFlags = 0
| SHFL_CF_DIRECTORY
| SHFL_CF_ACT_OPEN_IF_EXISTS
| SHFL_CF_ACT_FAIL_IF_NEW
| SHFL_CF_ACCESS_READ
;
LogFunc(("sf_dir_open(): calling vboxCallCreate, folder %s, flags %#x\n",
sf_i->path->String.utf8, params.CreateFlags));
rc = vboxCallCreate(&client_handle, &sf_g->map, sf_i->path, &params);
if (RT_SUCCESS(rc))
{
if (params.Result == SHFL_FILE_EXISTS)
{
err = sf_dir_read_all(sf_g, sf_i, sf_d, params.Handle);
if (!err)
file->private_data = sf_d;
}
else
err = -ENOENT;
rc = vboxCallClose(&client_handle, &sf_g->map, params.Handle);
if (RT_FAILURE(rc))
LogFunc(("sf_dir_open(): vboxCallClose(%s) after err=%d failed rc=%Rrc\n",
sf_i->path->String.utf8, err, rc));
}
else
err = -EPERM;
if (err)
sf_dir_info_free(sf_d);
return err;
}
/**
* This is called when reference count of [file] goes to zero. Notify
* the host that it can free whatever is associated with this directory
* and deallocate our own internal buffers
*
* @param inode inode
* @param file file
* returns 0 on success, Linux error code otherwise
*/
static int sf_dir_release(struct inode *inode, struct file *file)
{
TRACE();
if (file->private_data)
sf_dir_info_free(file->private_data);
return 0;
}
/**
* Extract element ([dir]->f_pos) from the directory [dir] into [d_name].
*
* @returns 0 for success, 1 for end reached, Linux error code otherwise.
*/
static int sf_getdent(struct file *dir, char d_name[NAME_MAX])
{
loff_t cur;
struct sf_glob_info *sf_g;
struct sf_dir_info *sf_d;
struct sf_inode_info *sf_i;
struct inode *inode;
struct list_head *pos, *list;
TRACE();
sf_g = GET_GLOB_INFO(dir->f_dentry->d_inode->i_sb);
sf_d = dir->private_data;
BUG_ON(!sf_g);
BUG_ON(!sf_d);
inode = dir->f_dentry->d_inode;
sf_i = GET_INODE_INFO(inode);
BUG_ON(!sf_i);
if (sf_i->force_reread)
{
int rc;
int err;
SHFLCREATEPARMS params;
RT_ZERO(params);
params.Handle = SHFL_HANDLE_NIL;
params.CreateFlags = 0
| SHFL_CF_DIRECTORY
| SHFL_CF_ACT_OPEN_IF_EXISTS
| SHFL_CF_ACT_FAIL_IF_NEW
| SHFL_CF_ACCESS_READ
;
LogFunc(("sf_getdent: calling vboxCallCreate, folder %s, flags %#x\n",
sf_i->path->String.utf8, params.CreateFlags));
rc = vboxCallCreate(&client_handle, &sf_g->map, sf_i->path, &params);
if (RT_FAILURE(rc))
{
LogFunc(("vboxCallCreate(%s) failed rc=%Rrc\n",
sf_i->path->String.utf8, rc));
return -EPERM;
}
if (params.Result != SHFL_FILE_EXISTS)
{
LogFunc(("directory %s does not exist\n", sf_i->path->String.utf8));
sf_dir_info_free(sf_d);
return -ENOENT;
}
sf_dir_info_empty(sf_d);
err = sf_dir_read_all(sf_g, sf_i, sf_d, params.Handle);
rc = vboxCallClose(&client_handle, &sf_g->map, params.Handle);
if (RT_FAILURE(rc))
LogFunc(("vboxCallClose(%s) failed rc=%Rrc\n", sf_i->path->String.utf8, rc));
if (err)
return err;
sf_i->force_reread = 0;
}
cur = 0;
list = &sf_d->info_list;
list_for_each(pos, list)
{
struct sf_dir_buf *b;
SHFLDIRINFO *info;
loff_t i;
b = list_entry(pos, struct sf_dir_buf, head);
if (dir->f_pos >= cur + b->cEntries)
{
cur += b->cEntries;
continue;
}
for (i = 0, info = b->buf; i < dir->f_pos - cur; ++i)
{
size_t size;
size = offsetof(SHFLDIRINFO, name.String) + info->name.u16Size;
info = (SHFLDIRINFO *) ((uintptr_t) info + size);
}
return sf_nlscpy(sf_g, d_name, NAME_MAX,
info->name.String.utf8, info->name.u16Length);
}
return 1;
}
/**
* This is called when vfs wants to populate internal buffers with
* directory [dir]s contents. [opaque] is an argument to the
* [filldir]. [filldir] magically modifies it's argument - [opaque]
* and takes following additional arguments (which i in turn get from
* the host via sf_getdent):
*
* name : name of the entry (i must also supply it's length huh?)
* type : type of the entry (FILE | DIR | etc) (i ellect to use DT_UNKNOWN)
* pos : position/index of the entry
* ino : inode number of the entry (i fake those)
*
* [dir] contains:
* f_pos : cursor into the directory listing
* private_data : mean of communication with the host side
*
* Extract elements from the directory listing (incrementing f_pos
* along the way) and feed them to [filldir] until:
*
* a. there are no more entries (i.e. sf_getdent set done to 1)
* b. failure to compute fake inode number
* c. filldir returns an error (see comment on that)
*/
static int sf_dir_read (struct file *dir, void *opaque, filldir_t filldir)
{
TRACE();
for (;;)
{
int err;
ino_t fake_ino;
loff_t sanity;
char d_name[NAME_MAX];
err = sf_getdent(dir, d_name);
switch (err)
{
case 1:
return 0;
case 0:
break;
case -1:
default:
/* skip erroneous entry and proceed */
LogFunc(("sf_getdent error %d\n", err));
dir->f_pos += 1;
continue;
}
/* d_name now contains a valid entry name */
sanity = dir->f_pos + 0xbeef;
fake_ino = sanity;
if (sanity - fake_ino)
{
LogRelFunc(("can not compute ino\n"));
return -EINVAL;
}
err = filldir(opaque, d_name, strlen(d_name),
dir->f_pos, fake_ino, DT_UNKNOWN);
if (err)
{
LogFunc(("filldir returned error %d\n", err));
/* Rely on the fact that filldir returns error
only when it runs out of space in opaque */
return 0;
}
dir->f_pos += 1;
}
BUG();
}
struct file_operations sf_dir_fops =
{
.open = sf_dir_open,
.readdir = sf_dir_read,
.release = sf_dir_release,
.read = generic_read_dir
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 37)
, .llseek = generic_file_llseek
#endif
};
/* iops */
/**
* This is called when vfs failed to locate dentry in the cache. The
* job of this function is to allocate inode and link it to dentry.
* [dentry] contains the name to be looked in the [parent] directory.
* Failure to locate the name is not a "hard" error, in this case NULL
* inode is added to [dentry] and vfs should proceed trying to create
* the entry via other means. NULL(or "positive" pointer) ought to be
* returned in case of success and "negative" pointer on error
*/
static struct dentry *sf_lookup(struct inode *parent, struct dentry *dentry
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0)
, struct nameidata *nd
#endif
)
{
int err;
struct sf_inode_info *sf_i, *sf_new_i;
struct sf_glob_info *sf_g;
SHFLSTRING *path;
struct inode *inode;
ino_t ino;
SHFLFSOBJINFO fsinfo;
TRACE();
sf_g = GET_GLOB_INFO(parent->i_sb);
sf_i = GET_INODE_INFO(parent);
BUG_ON(!sf_g);
BUG_ON(!sf_i);
err = sf_path_from_dentry(__func__, sf_g, sf_i, dentry, &path);
if (err)
goto fail0;
err = sf_stat(__func__, sf_g, path, &fsinfo, 1);
if (err)
{
if (err == -ENOENT)
{
/* -ENOENT: add NULL inode to dentry so it later can be
created via call to create/mkdir/open */
kfree(path);
inode = NULL;
}
else
goto fail1;
}
else
{
sf_new_i = kmalloc(sizeof(*sf_new_i), GFP_KERNEL);
if (!sf_new_i)
{
LogRelFunc(("could not allocate memory for new inode info\n"));
err = -ENOMEM;
goto fail1;
}
sf_new_i->handle = SHFL_HANDLE_NIL;
ino = iunique(parent->i_sb, 1);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 4, 25)
inode = iget_locked(parent->i_sb, ino);
#else
inode = iget(parent->i_sb, ino);
#endif
if (!inode)
{
LogFunc(("iget failed\n"));
err = -ENOMEM; /* XXX: ??? */
goto fail2;
}
SET_INODE_INFO(inode, sf_new_i);
sf_init_inode(sf_g, inode, &fsinfo);
sf_new_i->path = path;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 4, 25)
unlock_new_inode(inode);
#endif
}
sf_i->force_restat = 0;
dentry->d_time = jiffies;
dentry->d_op = &sf_dentry_ops;
d_add(dentry, inode);
return NULL;
fail2:
kfree(sf_new_i);
fail1:
kfree(path);
fail0:
return ERR_PTR(err);
}
/**
* This should allocate memory for sf_inode_info, compute a unique inode
* number, get an inode from vfs, initialize inode info, instantiate
* dentry.
*
* @param parent inode entry of the directory
* @param dentry directory cache entry
* @param path path name
* @param info file information
* @param handle handle
* @returns 0 on success, Linux error code otherwise
*/
static int sf_instantiate(struct inode *parent, struct dentry *dentry,
SHFLSTRING *path, PSHFLFSOBJINFO info, SHFLHANDLE handle)
{
int err;
ino_t ino;
struct inode *inode;
struct sf_inode_info *sf_new_i;
struct sf_glob_info *sf_g = GET_GLOB_INFO(parent->i_sb);
TRACE();
BUG_ON(!sf_g);
sf_new_i = kmalloc(sizeof(*sf_new_i), GFP_KERNEL);
if (!sf_new_i)
{
LogRelFunc(("could not allocate inode info.\n"));
err = -ENOMEM;
goto fail0;
}
ino = iunique(parent->i_sb, 1);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 4, 25)
inode = iget_locked(parent->i_sb, ino);
#else
inode = iget(parent->i_sb, ino);
#endif
if (!inode)
{
LogFunc(("iget failed\n"));
err = -ENOMEM;
goto fail1;
}
sf_init_inode(sf_g, inode, info);
sf_new_i->path = path;
SET_INODE_INFO(inode, sf_new_i);
dentry->d_time = jiffies;
dentry->d_op = &sf_dentry_ops;
sf_new_i->force_restat = 1;
d_instantiate(dentry, inode);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 4, 25)
unlock_new_inode(inode);
#endif
/* Store this handle if we leave the handle open. */
sf_new_i->handle = handle;
return 0;
fail1:
kfree(sf_new_i);
fail0:
return err;
}
/**
* Create a new regular file / directory.
*
* @param parent inode of the directory
* @param dentry directory cache entry
* @param mode file mode
* @param fDirectory true if directory, false otherwise
* @returns 0 on success, Linux error code otherwise
*/
static int sf_create_aux(struct inode *parent, struct dentry *dentry,
int mode, int fDirectory)
{
int rc, err;
SHFLCREATEPARMS params;
SHFLSTRING *path;
struct sf_inode_info *sf_i = GET_INODE_INFO(parent);
struct sf_glob_info *sf_g = GET_GLOB_INFO(parent->i_sb);
TRACE();
BUG_ON(!sf_i);
BUG_ON(!sf_g);
err = sf_path_from_dentry(__func__, sf_g, sf_i, dentry, &path);
if (err)
goto fail0;
RT_ZERO(params);
params.Handle = SHFL_HANDLE_NIL;
params.CreateFlags = 0
| SHFL_CF_ACT_CREATE_IF_NEW
| SHFL_CF_ACT_FAIL_IF_EXISTS
| SHFL_CF_ACCESS_READWRITE
| (fDirectory ? SHFL_CF_DIRECTORY : 0)
;
params.Info.Attr.fMode = 0
| (fDirectory ? RTFS_TYPE_DIRECTORY : RTFS_TYPE_FILE)
| (mode & S_IRWXUGO)
;
params.Info.Attr.enmAdditional = RTFSOBJATTRADD_NOTHING;
LogFunc(("sf_create_aux: calling vboxCallCreate, folder %s, flags %#x\n",
path->String.utf8, params.CreateFlags));
rc = vboxCallCreate(&client_handle, &sf_g->map, path, &params);
if (RT_FAILURE(rc))
{
if (rc == VERR_WRITE_PROTECT)
{
err = -EROFS;
goto fail1;
}
err = -EPROTO;
LogFunc(("(%d): vboxCallCreate(%s) failed rc=%Rrc\n",
fDirectory, sf_i->path->String.utf8, rc));
goto fail1;
}
if (params.Result != SHFL_FILE_CREATED)
{
err = -EPERM;
LogFunc(("(%d): could not create file %s result=%d\n",
fDirectory, sf_i->path->String.utf8, params.Result));
goto fail1;
}
err = sf_instantiate(parent, dentry, path, &params.Info,
fDirectory ? SHFL_HANDLE_NIL : params.Handle);
if (err)
{
LogFunc(("(%d): could not instantiate dentry for %s err=%d\n",
fDirectory, sf_i->path->String.utf8, err));
goto fail2;
}
/*
* Don't close this handle right now. We assume that the same file is
* opened with sf_reg_open() and later closed with sf_reg_close(). Save
* the handle in between. Does not apply to directories. True?
*/
if (fDirectory)
{
rc = vboxCallClose(&client_handle, &sf_g->map, params.Handle);
if (RT_FAILURE(rc))
LogFunc(("(%d): vboxCallClose failed rc=%Rrc\n", fDirectory, rc));
}
sf_i->force_restat = 1;
return 0;
fail2:
rc = vboxCallClose(&client_handle, &sf_g->map, params.Handle);
if (RT_FAILURE(rc))
LogFunc(("(%d): vboxCallClose failed rc=%Rrc\n", fDirectory, rc));
fail1:
kfree(path);
fail0:
return err;
}
/**
* Create a new regular file.
*
* @param parent inode of the directory
* @param dentry directory cache entry
* @param mode file mode
* @returns 0 on success, Linux error code otherwise
*/
static int sf_create(struct inode *parent, struct dentry *dentry, int mode
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0)
, struct nameidata *nd
#endif
)
{
TRACE();
return sf_create_aux(parent, dentry, mode, 0);
}
/**
* Create a new directory.
*
* @param parent inode of the directory
* @param dentry directory cache entry
* @param mode file mode
* @returns 0 on success, Linux error code otherwise
*/
static int sf_mkdir(struct inode *parent, struct dentry *dentry, int mode)
{
TRACE();
return sf_create_aux(parent, dentry, mode, 1);
}
/**
* Remove a regular file / directory.
*
* @param parent inode of the directory
* @param dentry directory cache entry
* @param fDirectory true if directory, false otherwise
* @returns 0 on success, Linux error code otherwise
*/
static int sf_unlink_aux(struct inode *parent, struct dentry *dentry, int fDirectory)
{
int rc, err;
struct sf_glob_info *sf_g = GET_GLOB_INFO(parent->i_sb);
struct sf_inode_info *sf_i = GET_INODE_INFO(parent);
SHFLSTRING *path;
uint32_t fFlags;
TRACE();
BUG_ON(!sf_g);
err = sf_path_from_dentry(__func__, sf_g, sf_i, dentry, &path);
if (err)
goto fail0;
fFlags = fDirectory ? SHFL_REMOVE_DIR : SHFL_REMOVE_FILE;
if ( dentry
&& dentry->d_inode
&& ((dentry->d_inode->i_mode & S_IFLNK) == S_IFLNK))
fFlags |= SHFL_REMOVE_SYMLINK;
rc = vboxCallRemove(&client_handle, &sf_g->map, path, fFlags);
if (RT_FAILURE(rc))
{
LogFunc(("(%d): vboxCallRemove(%s) failed rc=%Rrc\n", fDirectory,
path->String.utf8, rc));
err = -RTErrConvertToErrno(rc);
goto fail1;
}
/* directory access/change time changed */
sf_i->force_restat = 1;
/* directory content changed */
sf_i->force_reread = 1;
err = 0;
fail1:
kfree(path);
fail0:
return err;
}
/**
* Remove a regular file.
*
* @param parent inode of the directory
* @param dentry directory cache entry
* @returns 0 on success, Linux error code otherwise
*/
static int sf_unlink(struct inode *parent, struct dentry *dentry)
{
TRACE();
return sf_unlink_aux(parent, dentry, 0);
}
/**
* Remove a directory.
*
* @param parent inode of the directory
* @param dentry directory cache entry
* @returns 0 on success, Linux error code otherwise
*/
static int sf_rmdir(struct inode *parent, struct dentry *dentry)
{
TRACE();
return sf_unlink_aux(parent, dentry, 1);
}
/**
* Rename a regular file / directory.
*
* @param old_parent inode of the old parent directory
* @param old_dentry old directory cache entry
* @param new_parent inode of the new parent directory
* @param new_dentry new directory cache entry
* @returns 0 on success, Linux error code otherwise
*/
static int sf_rename(struct inode *old_parent, struct dentry *old_dentry,
struct inode *new_parent, struct dentry *new_dentry)
{
int err = 0, rc = VINF_SUCCESS;
struct sf_glob_info *sf_g = GET_GLOB_INFO(old_parent->i_sb);
TRACE();
if (sf_g != GET_GLOB_INFO(new_parent->i_sb))
{
LogFunc(("rename with different roots\n"));
err = -EINVAL;
}
else
{
struct sf_inode_info *sf_old_i = GET_INODE_INFO(old_parent);
struct sf_inode_info *sf_new_i = GET_INODE_INFO(new_parent);
/* As we save the relative path inside the inode structure, we need to change
this if the rename is successful. */
struct sf_inode_info *sf_file_i = GET_INODE_INFO(old_dentry->d_inode);
SHFLSTRING *old_path;
SHFLSTRING *new_path;
BUG_ON(!sf_old_i);
BUG_ON(!sf_new_i);
BUG_ON(!sf_file_i);
old_path = sf_file_i->path;
err = sf_path_from_dentry(__func__, sf_g, sf_new_i,
new_dentry, &new_path);
if (err)
LogFunc(("failed to create new path\n"));
else
{
int fDir = ((old_dentry->d_inode->i_mode & S_IFDIR) != 0);
rc = vboxCallRename(&client_handle, &sf_g->map, old_path,
new_path, fDir ? 0 : SHFL_RENAME_FILE | SHFL_RENAME_REPLACE_IF_EXISTS);
if (RT_SUCCESS(rc))
{
kfree(old_path);
sf_new_i->force_restat = 1;
sf_old_i->force_restat = 1; /* XXX: needed? */
/* Set the new relative path in the inode. */
sf_file_i->path = new_path;
}
else
{
LogFunc(("vboxCallRename failed rc=%Rrc\n", rc));
err = -RTErrConvertToErrno(rc);
kfree(new_path);
}
}
}
return err;
}
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0)
static int sf_symlink(struct inode *parent, struct dentry *dentry, const char *symname)
{
int err;
int rc;
struct sf_inode_info *sf_i;
struct sf_glob_info *sf_g;
SHFLSTRING *path, *ssymname;
SHFLFSOBJINFO info;
int symname_len = strlen(symname) + 1;
TRACE();
sf_g = GET_GLOB_INFO(parent->i_sb);
sf_i = GET_INODE_INFO(parent);
BUG_ON(!sf_g);
BUG_ON(!sf_i);
err = sf_path_from_dentry(__func__, sf_g, sf_i, dentry, &path);
if (err)
goto fail0;
ssymname = kmalloc(offsetof(SHFLSTRING, String.utf8) + symname_len, GFP_KERNEL);
if (!ssymname)
{
LogRelFunc(("kmalloc failed, caller=sf_symlink\n"));
err = -ENOMEM;
goto fail1;
}
ssymname->u16Length = symname_len - 1;
ssymname->u16Size = symname_len;
memcpy(ssymname->String.utf8, symname, symname_len);
rc = vboxCallSymlink(&client_handle, &sf_g->map, path, ssymname, &info);
kfree(ssymname);
if (RT_FAILURE(rc))
{
if (rc == VERR_WRITE_PROTECT)
{
err = -EROFS;
goto fail1;
}
LogFunc(("vboxCallSymlink(%s) failed rc=%Rrc\n",
sf_i->path->String.utf8, rc));
err = -EPROTO;
goto fail1;
}
err = sf_instantiate(parent, dentry, path, &info, SHFL_HANDLE_NIL);
if (err)
{
LogFunc(("could not instantiate dentry for %s err=%d\n",
sf_i->path->String.utf8, err));
goto fail1;
}
sf_i->force_restat = 1;
return 0;
fail1:
kfree(path);
fail0:
return err;
}
#endif
struct inode_operations sf_dir_iops =
{
.lookup = sf_lookup,
.create = sf_create,
.mkdir = sf_mkdir,
.rmdir = sf_rmdir,
.unlink = sf_unlink,
.rename = sf_rename,
#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 0)
.revalidate = sf_inode_revalidate
#else
.getattr = sf_getattr,
.setattr = sf_setattr,
.symlink = sf_symlink
#endif
};