404 lines
10 KiB
C
404 lines
10 KiB
C
|
/* This file contains directory entry related file system call handlers.
|
||
|
*
|
||
|
* The entry points into this file are:
|
||
|
* do_create perform the CREATE file system call
|
||
|
* do_mkdir perform the MKDIR file system call
|
||
|
* do_unlink perform the UNLINK file system call
|
||
|
* do_rmdir perform the RMDIR file system call
|
||
|
* do_rename perform the RENAME file system call
|
||
|
*
|
||
|
* Created:
|
||
|
* April 2009 (D.C. van Moolenbroek)
|
||
|
*/
|
||
|
|
||
|
#include "inc.h"
|
||
|
|
||
|
#include <fcntl.h>
|
||
|
|
||
|
FORWARD _PROTOTYPE( int force_remove, (char *path, int dir) );
|
||
|
|
||
|
/*===========================================================================*
|
||
|
* do_create *
|
||
|
*===========================================================================*/
|
||
|
PUBLIC int do_create()
|
||
|
{
|
||
|
/* Create a new file.
|
||
|
*/
|
||
|
char path[PATH_MAX], name[NAME_MAX+1];
|
||
|
struct inode *parent, *ino;
|
||
|
struct hgfs_attr attr;
|
||
|
hgfs_file_t handle;
|
||
|
int r;
|
||
|
|
||
|
/* We cannot create files on a read-only file system. */
|
||
|
if (state.read_only)
|
||
|
return EROFS;
|
||
|
|
||
|
/* Get path, name, parent inode and possibly inode for the given path. */
|
||
|
if ((r = get_name(m_in.REQ_GRANT, m_in.REQ_PATH_LEN, name)) != OK)
|
||
|
return r;
|
||
|
|
||
|
if (!strcmp(name, ".") || !strcmp(name, "..")) return EEXIST;
|
||
|
|
||
|
if ((parent = find_inode(m_in.REQ_INODE_NR)) == NIL_INODE)
|
||
|
return EINVAL;
|
||
|
|
||
|
if ((r = verify_dentry(parent, name, path, &ino)) != OK)
|
||
|
return r;
|
||
|
|
||
|
/* Are we going to need a new inode upon success?
|
||
|
* Then make sure there is one available before trying anything.
|
||
|
*/
|
||
|
if (ino == NIL_INODE || ino->i_ref > 1 || HAS_CHILDREN(ino)) {
|
||
|
if (!have_free_inode()) {
|
||
|
if (ino != NIL_INODE)
|
||
|
put_inode(ino);
|
||
|
|
||
|
return ENFILE;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Perform the actual create call. */
|
||
|
r = hgfs_open(path, O_CREAT | O_EXCL | O_RDWR, m_in.REQ_MODE, &handle);
|
||
|
|
||
|
if (r != OK) {
|
||
|
/* Let's not try to be too clever with error codes here. If something
|
||
|
* is wrong with the directory, we'll find out later anyway.
|
||
|
*/
|
||
|
|
||
|
if (ino != NIL_INODE)
|
||
|
put_inode(ino);
|
||
|
|
||
|
return r;
|
||
|
}
|
||
|
|
||
|
/* Get the created file's attributes. */
|
||
|
attr.a_mask = HGFS_ATTR_MODE | HGFS_ATTR_SIZE;
|
||
|
r = hgfs_getattr(path, &attr);
|
||
|
|
||
|
/* If this fails, or returns a directory, we have a problem. This
|
||
|
* scenario is in fact possible with race conditions.
|
||
|
* Simulate a close and return a somewhat appropriate error.
|
||
|
*/
|
||
|
if (r != OK || S_ISDIR(attr.a_mode)) {
|
||
|
printf("HGFS: lost file after creation!\n");
|
||
|
|
||
|
hgfs_close(handle);
|
||
|
|
||
|
if (ino != NIL_INODE) {
|
||
|
del_dentry(ino);
|
||
|
|
||
|
put_inode(ino);
|
||
|
}
|
||
|
|
||
|
return (r == OK) ? EEXIST : r;
|
||
|
}
|
||
|
|
||
|
/* We do assume that the HGFS open(O_CREAT|O_EXCL) did its job.
|
||
|
* If we previousy found an inode, get rid of it now. It's old.
|
||
|
*/
|
||
|
if (ino != NIL_INODE) {
|
||
|
del_dentry(ino);
|
||
|
|
||
|
put_inode(ino);
|
||
|
}
|
||
|
|
||
|
/* Associate the open file handle with an inode, and reply with its details.
|
||
|
*/
|
||
|
ino = get_free_inode();
|
||
|
|
||
|
assert(ino != NIL_INODE); /* we checked before whether we had a free one */
|
||
|
|
||
|
ino->i_file = handle;
|
||
|
ino->i_flags = I_HANDLE;
|
||
|
|
||
|
add_dentry(parent, name, ino);
|
||
|
|
||
|
m_out.RES_INODE_NR = INODE_NR(ino);
|
||
|
m_out.RES_MODE = get_mode(ino, attr.a_mode);
|
||
|
m_out.RES_FILE_SIZE_HI = ex64hi(attr.a_size);
|
||
|
m_out.RES_FILE_SIZE_LO = ex64lo(attr.a_size);
|
||
|
m_out.RES_UID = opt.uid;
|
||
|
m_out.RES_GID = opt.gid;
|
||
|
m_out.RES_DEV = NO_DEV;
|
||
|
|
||
|
return OK;
|
||
|
}
|
||
|
|
||
|
/*===========================================================================*
|
||
|
* do_mkdir *
|
||
|
*===========================================================================*/
|
||
|
PUBLIC int do_mkdir()
|
||
|
{
|
||
|
/* Make a new directory.
|
||
|
*/
|
||
|
char path[PATH_MAX], name[NAME_MAX+1];
|
||
|
struct inode *parent, *ino;
|
||
|
int r;
|
||
|
|
||
|
/* We cannot create directories on a read-only file system. */
|
||
|
if (state.read_only)
|
||
|
return EROFS;
|
||
|
|
||
|
/* Get the path string and possibly an inode for the given path. */
|
||
|
if ((r = get_name(m_in.REQ_GRANT, m_in.REQ_PATH_LEN, name)) != OK)
|
||
|
return r;
|
||
|
|
||
|
if (!strcmp(name, ".") || !strcmp(name, "..")) return EEXIST;
|
||
|
|
||
|
if ((parent = find_inode(m_in.REQ_INODE_NR)) == NIL_INODE)
|
||
|
return EINVAL;
|
||
|
|
||
|
if ((r = verify_dentry(parent, name, path, &ino)) != OK)
|
||
|
return r;
|
||
|
|
||
|
/* Perform the actual mkdir call. */
|
||
|
r = hgfs_mkdir(path, m_in.REQ_MODE);
|
||
|
|
||
|
if (r != OK) {
|
||
|
if (ino != NIL_INODE)
|
||
|
put_inode(ino);
|
||
|
|
||
|
return r;
|
||
|
}
|
||
|
|
||
|
/* If we thought the new dentry already existed, it was apparently gone
|
||
|
* already. Delete it.
|
||
|
*/
|
||
|
if (ino != NIL_INODE) {
|
||
|
del_dentry(ino);
|
||
|
|
||
|
put_inode(ino);
|
||
|
}
|
||
|
|
||
|
return OK;
|
||
|
}
|
||
|
|
||
|
/*===========================================================================*
|
||
|
* force_remove *
|
||
|
*===========================================================================*/
|
||
|
PRIVATE int force_remove(path, dir)
|
||
|
char *path; /* path to file or directory */
|
||
|
int dir; /* TRUE iff directory */
|
||
|
{
|
||
|
/* Remove a file or directory. Wrapper around hgfs_unlink and hgfs_rmdir that
|
||
|
* makes the target temporarily writable if the operation fails with an access
|
||
|
* denied error. On Windows hosts, read-only files or directories cannot be
|
||
|
* removed (even though they can be renamed). In general, the HGFS server
|
||
|
* follows the behavior of the host file system, but this case just confuses
|
||
|
* the hell out of the MINIX userland..
|
||
|
*/
|
||
|
struct hgfs_attr attr;
|
||
|
int r, r2;
|
||
|
|
||
|
/* First try to remove the target. */
|
||
|
if (dir)
|
||
|
r = hgfs_rmdir(path);
|
||
|
else
|
||
|
r = hgfs_unlink(path);
|
||
|
|
||
|
if (r != EACCES) return r;
|
||
|
|
||
|
/* If this fails with an access error, retrieve the target's mode. */
|
||
|
attr.a_mask = HGFS_ATTR_MODE;
|
||
|
|
||
|
r2 = hgfs_getattr(path, &attr);
|
||
|
|
||
|
if (r2 != OK || (attr.a_mode & S_IWUSR)) return r;
|
||
|
|
||
|
/* If the target is not writable, temporarily set it to writable. */
|
||
|
attr.a_mode |= S_IWUSR;
|
||
|
|
||
|
r2 = hgfs_setattr(path, &attr);
|
||
|
|
||
|
if (r2 != OK) return r;
|
||
|
|
||
|
/* Then try the original operation again. */
|
||
|
if (dir)
|
||
|
r = hgfs_rmdir(path);
|
||
|
else
|
||
|
r = hgfs_unlink(path);
|
||
|
|
||
|
if (r == OK) return r;
|
||
|
|
||
|
/* If the operation still fails, unset the writable bit again. */
|
||
|
attr.a_mode &= ~S_IWUSR;
|
||
|
|
||
|
hgfs_setattr(path, &attr);
|
||
|
|
||
|
return r;
|
||
|
}
|
||
|
|
||
|
/*===========================================================================*
|
||
|
* do_unlink *
|
||
|
*===========================================================================*/
|
||
|
PUBLIC int do_unlink()
|
||
|
{
|
||
|
/* Delete a file.
|
||
|
*/
|
||
|
char path[PATH_MAX], name[NAME_MAX+1];
|
||
|
struct inode *parent, *ino;
|
||
|
int r;
|
||
|
|
||
|
/* We cannot delete files on a read-only file system. */
|
||
|
if (state.read_only)
|
||
|
return EROFS;
|
||
|
|
||
|
/* Get the path string and possibly preexisting inode for the given path. */
|
||
|
if ((r = get_name(m_in.REQ_GRANT, m_in.REQ_PATH_LEN, name)) != OK)
|
||
|
return r;
|
||
|
|
||
|
if (!strcmp(name, ".") || !strcmp(name, "..")) return EPERM;
|
||
|
|
||
|
if ((parent = find_inode(m_in.REQ_INODE_NR)) == NIL_INODE)
|
||
|
return EINVAL;
|
||
|
|
||
|
if ((r = verify_dentry(parent, name, path, &ino)) != OK)
|
||
|
return r;
|
||
|
|
||
|
/* Perform the unlink call. */
|
||
|
r = force_remove(path, FALSE /*dir*/);
|
||
|
|
||
|
if (r != OK) {
|
||
|
if (ino != NIL_INODE)
|
||
|
put_inode(ino);
|
||
|
|
||
|
return r;
|
||
|
}
|
||
|
|
||
|
/* If a dentry existed for this name, it is gone now. */
|
||
|
if (ino != NIL_INODE) {
|
||
|
del_dentry(ino);
|
||
|
|
||
|
put_inode(ino);
|
||
|
}
|
||
|
|
||
|
return OK;
|
||
|
}
|
||
|
|
||
|
/*===========================================================================*
|
||
|
* do_rmdir *
|
||
|
*===========================================================================*/
|
||
|
PUBLIC int do_rmdir()
|
||
|
{
|
||
|
/* Remove an empty directory.
|
||
|
*/
|
||
|
char path[PATH_MAX], name[NAME_MAX+1];
|
||
|
struct inode *parent, *ino;
|
||
|
int r;
|
||
|
|
||
|
/* We cannot remove directories on a read-only file system. */
|
||
|
if (state.read_only)
|
||
|
return EROFS;
|
||
|
|
||
|
/* Get the path string and possibly preexisting inode for the given path. */
|
||
|
if ((r = get_name(m_in.REQ_GRANT, m_in.REQ_PATH_LEN, name)) != OK)
|
||
|
return r;
|
||
|
|
||
|
if (!strcmp(name, ".")) return EINVAL;
|
||
|
if (!strcmp(name, "..")) return ENOTEMPTY;
|
||
|
|
||
|
if ((parent = find_inode(m_in.REQ_INODE_NR)) == NIL_INODE)
|
||
|
return EINVAL;
|
||
|
|
||
|
if ((r = verify_dentry(parent, name, path, &ino)) != OK)
|
||
|
return r;
|
||
|
|
||
|
/* Perform the rmdir call. */
|
||
|
r = force_remove(path, TRUE /*dir*/);
|
||
|
|
||
|
if (r != OK) {
|
||
|
if (ino != NIL_INODE)
|
||
|
put_inode(ino);
|
||
|
|
||
|
return r;
|
||
|
}
|
||
|
|
||
|
/* If a dentry existed for this name, it is gone now. */
|
||
|
if (ino != NIL_INODE) {
|
||
|
del_dentry(ino);
|
||
|
|
||
|
put_inode(ino);
|
||
|
}
|
||
|
|
||
|
return OK;
|
||
|
}
|
||
|
|
||
|
/*===========================================================================*
|
||
|
* do_rename *
|
||
|
*===========================================================================*/
|
||
|
PUBLIC int do_rename()
|
||
|
{
|
||
|
/* Rename a file or directory.
|
||
|
*/
|
||
|
char old_path[PATH_MAX], new_path[PATH_MAX];
|
||
|
char old_name[NAME_MAX+1], new_name[NAME_MAX+1];
|
||
|
struct inode *old_parent, *new_parent;
|
||
|
struct inode *old_ino, *new_ino;
|
||
|
int r;
|
||
|
|
||
|
/* We cannot do rename on a read-only file system. */
|
||
|
if (state.read_only)
|
||
|
return EROFS;
|
||
|
|
||
|
/* Get path strings, names, directory inodes and possibly preexisting inodes
|
||
|
* for the old and new paths.
|
||
|
*/
|
||
|
if ((r = get_name(m_in.REQ_REN_GRANT_OLD, m_in.REQ_REN_LEN_OLD,
|
||
|
old_name)) != OK) return r;
|
||
|
|
||
|
if ((r = get_name(m_in.REQ_REN_GRANT_NEW, m_in.REQ_REN_LEN_NEW,
|
||
|
new_name)) != OK) return r;
|
||
|
|
||
|
if (!strcmp(old_name, ".") || !strcmp(old_name, "..") ||
|
||
|
!strcmp(new_name, ".") || !strcmp(new_name, "..")) return EINVAL;
|
||
|
|
||
|
if ((old_parent = find_inode(m_in.REQ_REN_OLD_DIR)) == NIL_INODE ||
|
||
|
(new_parent = find_inode(m_in.REQ_REN_NEW_DIR)) == NIL_INODE)
|
||
|
return EINVAL;
|
||
|
|
||
|
if ((r = verify_dentry(old_parent, old_name, old_path, &old_ino)) != OK)
|
||
|
return r;
|
||
|
|
||
|
if ((r = verify_dentry(new_parent, new_name, new_path, &new_ino)) != OK) {
|
||
|
if (old_ino != NIL_INODE)
|
||
|
put_inode(old_ino);
|
||
|
|
||
|
return r;
|
||
|
}
|
||
|
|
||
|
/* Perform the actual rename call. */
|
||
|
r = hgfs_rename(old_path, new_path);
|
||
|
|
||
|
/* If we failed, or if we have nothing further to do: both inodes are
|
||
|
* NULL, or they both refer to the same file.
|
||
|
*/
|
||
|
if (r != OK || old_ino == new_ino) {
|
||
|
if (old_ino != NIL_INODE) put_inode(old_ino);
|
||
|
|
||
|
if (new_ino != NIL_INODE) put_inode(new_ino);
|
||
|
|
||
|
return r;
|
||
|
}
|
||
|
|
||
|
/* If the new dentry already existed, it has now been overwritten.
|
||
|
* Delete the associated inode if we had found one.
|
||
|
*/
|
||
|
if (new_ino != NIL_INODE) {
|
||
|
del_dentry(new_ino);
|
||
|
|
||
|
put_inode(new_ino);
|
||
|
}
|
||
|
|
||
|
/* If the old dentry existed, rename it accordingly. */
|
||
|
if (old_ino != NIL_INODE) {
|
||
|
del_dentry(old_ino);
|
||
|
|
||
|
add_dentry(new_parent, new_name, old_ino);
|
||
|
|
||
|
put_inode(old_ino);
|
||
|
}
|
||
|
|
||
|
return OK;
|
||
|
}
|