/** @file * * vboxvfs -- VirtualBox Guest Additions for Linux: * Directory inode and file operations */ /* * Copyright (C) 2006-2007 innotek GmbH * * 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 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. */ 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) { elog ("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) { elog ("could not allocate directory info for %s\n", sf_i->path->String.utf8); return -ENOMEM; } params.CreateFlags = 0 | SHFL_CF_DIRECTORY | SHFL_CF_ACT_OPEN_IF_EXISTS | SHFL_CF_ACCESS_READ ; rc = vboxCallCreate (&client_handle, &sf_g->map, sf_i->path, ¶ms); if (VBOX_FAILURE (rc)) { elog ("vboxCallCreate(%s) failed rc=%d\n", sf_i->path->String.utf8, rc); sf_dir_info_free (sf_d); return -EPERM; } if (params.Result != SHFL_FILE_EXISTS) { elog ("directory %s does not exist\n", sf_i->path->String.utf8); sf_dir_info_free (sf_d); return -ENOENT; } err = sf_dir_read_all (sf_g, sf_i, sf_d, params.Handle); if (err) { rc = vboxCallClose (&client_handle, &sf_g->map, params.Handle); if (VBOX_FAILURE (rc)) { elog ("vboxCallClose(%s) after err=%d failed rc=%d\n", sf_i->path->String.utf8, err, rc); } sf_dir_info_free (sf_d); return err; } rc = vboxCallClose (&client_handle, &sf_g->map, params.Handle); if (VBOX_FAILURE (rc)) { elog ("vboxCallClose(%s) failed rc=%d\n", sf_i->path->String.utf8, rc); } file->private_data = sf_d; return 0; } /* 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 */ static int sf_dir_release (struct inode *inode, struct file *file) { struct sf_inode_info *sf_i = GET_INODE_INFO (inode); TRACE (); BUG_ON (!sf_i); 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], return: 0 all ok 1 end reached -errno some form of error*/ 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 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); cur = 0; list = &sf_d->info_list; list_for_each (pos, list) { struct sf_dir_buf *b; SHFLDIRINFO *info; loff_t i; size_t name_len; char *name_ptr; b = list_entry (pos, struct sf_dir_buf, head); if (dir->f_pos >= cur + b->nb_entries) { cur += b->nb_entries; 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); } name_ptr = info->name.String.utf8; name_len = info->name.u16Length; return sf_nlscpy (sf_g, d_name, NAME_MAX, name_ptr, name_len); } 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 communcation 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 */ elog ("sf_getdent error %d\n", err); dir->f_pos += 1; continue; } /* d_name now contains valid entry name */ sanity = dir->f_pos + 0xbeef; fake_ino = sanity; if (sanity - fake_ino) { elog2 ("can not compute ino\n"); return -EINVAL; } err = filldir (opaque, d_name, strlen (d_name), dir->f_pos, fake_ino, DT_UNKNOWN); if (err) { DBGC elog ("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 (); } static struct file_operations sf_dir_fops = { .open = sf_dir_open, .readdir = sf_dir_read, .release = sf_dir_release, .read = generic_read_dir }; /* 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 succes 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; RTFSOBJINFO 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 */ inode = NULL; } else goto fail1; } else { sf_new_i = kmalloc (sizeof (*sf_new_i), GFP_KERNEL); if (!sf_new_i) { elog2 ("could not allocate memory for new inode info\n"); err = -ENOMEM; goto fail1; } ino = iunique (parent->i_sb, 1); inode = iget (parent->i_sb, ino); if (!inode) { elog2 ("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; } 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 unique inode number, get an inode from vfs, initialize inode info, instantiate dentry */ static int sf_instantiate (const char *caller, struct inode *parent, struct dentry *dentry, SHFLSTRING *path, RTFSOBJINFO *info) { 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) { elog3 ("%s: %s: could not allocate inode info\n", caller, __func__); err = -ENOMEM; goto fail0; } ino = iunique (parent->i_sb, 1); inode = iget (parent->i_sb, ino); if (!inode) { elog3 ("%s: %s: iget failed\n", caller, __func__); 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); return 0; fail1: kfree (sf_new_i); fail0: return err; } static int sf_create_aux (struct inode *parent, struct dentry *dentry, int dirop) { 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); #if 0 printk ("create_aux %s/%s\n", sf_i->path->String.utf8, dentry->d_name.name); #endif params.CreateFlags = 0 | SHFL_CF_ACT_CREATE_IF_NEW | SHFL_CF_ACT_OVERWRITE_IF_EXISTS | SHFL_CF_ACCESS_READWRITE | (dirop ? SHFL_CF_DIRECTORY : 0) ; params.Info.Attr.fMode = 0 | (dirop ? RTFS_TYPE_DIRECTORY : RTFS_TYPE_FILE) | RTFS_UNIX_IRUSR | RTFS_UNIX_IWUSR | RTFS_UNIX_IXUSR ; params.Info.Attr.enmAdditional = RTFSOBJATTRADD_NOTHING; err = sf_path_from_dentry (__func__, sf_g, sf_i, dentry, &path); if (err) { goto fail0; } rc = vboxCallCreate (&client_handle, &sf_g->map, path, ¶ms); if (VBOX_FAILURE (rc)) { err = -EPROTO; elog ("(%d): vboxCallCreate(%s) failed rc=%d\n", dirop, sf_i->path->String.utf8, rc); goto fail0; } if (params.Result != SHFL_FILE_CREATED) { err = -EPERM; elog ("(%d): could not create file %s result=%d\n", dirop, sf_i->path->String.utf8, params.Result); goto fail0; } err = sf_instantiate (__func__, parent, dentry, path, ¶ms.Info); if (err) { elog ("(%d): could not instantiate dentry for %s err=%d\n", dirop, sf_i->path->String.utf8, err); goto fail1; } rc = vboxCallClose (&client_handle, &sf_g->map, params.Handle); if (VBOX_FAILURE (rc)) { elog ("(%d): vboxCallClose failed rc=%d\n", dirop, rc); } sf_i->force_restat = 1; return 0; fail1: rc = vboxCallClose (&client_handle, &sf_g->map, params.Handle); if (VBOX_FAILURE (rc)) { elog ("(%d): vboxCallClose failed rc=%d\n", dirop, rc); } fail0: kfree (path); return err; } 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, 0); } static int sf_mkdir (struct inode *parent, struct dentry *dentry, int mode) { TRACE (); return sf_create_aux (parent, dentry, 1); } static int sf_unlink_aux (struct inode *parent, struct dentry *dentry, int dirop) { 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; TRACE (); BUG_ON (!sf_g); err = sf_path_from_dentry (__func__, sf_g, sf_i, dentry, &path); if (err) { goto fail0; } rc = vboxCallRemove (&client_handle, &sf_g->map, path, dirop ? SHFL_REMOVE_DIR : SHFL_REMOVE_FILE); if (VBOX_FAILURE (rc)) { DBGC elog ("(%d): vboxCallRemove(%s) failed rc=%d\n", dirop, path->String.utf8, rc); switch (rc) { case VERR_PATH_NOT_FOUND: err = -ENOENT; break; case VERR_DIR_NOT_EMPTY: err = -ENOTEMPTY; break; default: err = -EPROTO; elog ("(%d): vboxCallRemove(%s) failed rc=%d\n", dirop, path->String.utf8, rc); break; } goto fail1; } kfree (path); return 0; fail1: kfree (path); fail0: return err; } static int sf_unlink (struct inode *parent, struct dentry *dentry) { TRACE (); return sf_unlink_aux (parent, dentry, 0); } static int sf_rmdir (struct inode *parent, struct dentry *dentry) { TRACE (); return sf_unlink_aux (parent, dentry, 1); } static int sf_rename (struct inode *old_parent, struct dentry *old_dentry, struct inode *new_parent, struct dentry *new_dentry) { int err, rc; struct sf_glob_info *sf_g = GET_GLOB_INFO (old_parent->i_sb); struct sf_inode_info *sf_old_i = GET_INODE_INFO (old_parent); struct sf_inode_info *sf_new_i = GET_INODE_INFO (new_parent); SHFLSTRING *old_path; SHFLSTRING *new_path; TRACE (); BUG_ON (!sf_old_i); BUG_ON (!sf_new_i); if (sf_g != GET_GLOB_INFO (new_parent->i_sb)) { elog2 ("rename with different roots\n"); return -EINVAL; } err = sf_path_from_dentry (__func__, sf_g, sf_old_i, old_dentry, &old_path); if (err) { elog2 ("failed to create old path\n"); return err; } err = sf_path_from_dentry (__func__, sf_g, sf_new_i, new_dentry, &new_path); if (err) { elog2 ("failed to create new path\n"); goto fail0; } rc = vboxCallRename (&client_handle, &sf_g->map, old_path, new_path, SHFL_RENAME_FILE); if (VBOX_FAILURE (rc)) { switch (rc) { case VERR_FILE_NOT_FOUND: case VERR_PATH_NOT_FOUND: err = -ENOENT; goto fail1; default: err = -EPROTO; elog ("vboxCallRename failed rc=%d\n", rc); goto fail1; } } err = 0; sf_new_i->force_restat = 1; sf_old_i->force_restat = 1; /* XXX: needed? */ fail1: kfree (old_path); fail0: kfree (new_path); return err; } static 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 #endif };