minix/lib/libsffs/link.c
David van Moolenbroek ef7b484e5c Create SFFS library out of HGFS
This Shared Folders File System library (libsffs) now contains all the
file system logic originally in HGFS. The actual HGFS server code is
now a stub that passes on all the work to libsffs. The libhgfs library
is changed accordingly.
2012-04-09 18:08:26 +02:00

405 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>
static int force_remove(char *path, int dir);
/*===========================================================================*
* do_create *
*===========================================================================*/
int do_create()
{
/* Create a new file.
*/
char path[PATH_MAX], name[NAME_MAX+1];
struct inode *parent, *ino;
struct sffs_attr attr;
sffs_file_t handle;
int r;
/* We cannot create files on a read-only file system. */
if (state.s_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)) == NULL)
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 == NULL || ino->i_ref > 1 || HAS_CHILDREN(ino)) {
if (!have_free_inode()) {
if (ino != NULL)
put_inode(ino);
return ENFILE;
}
}
/* Perform the actual create call. */
r = sffs_table->t_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 != NULL)
put_inode(ino);
return r;
}
/* Get the created file's attributes. */
attr.a_mask = SFFS_ATTR_MODE | SFFS_ATTR_SIZE;
r = sffs_table->t_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("%s: lost file after creation!\n", sffs_name);
sffs_table->t_close(handle);
if (ino != NULL) {
del_dentry(ino);
put_inode(ino);
}
return (r == OK) ? EEXIST : r;
}
/* We do assume that the underlying open(O_CREAT|O_EXCL) call did its job.
* If we previousy found an inode, get rid of it now. It's old.
*/
if (ino != NULL) {
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 != NULL); /* 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 = sffs_params->p_uid;
m_out.RES_GID = sffs_params->p_gid;
m_out.RES_DEV = NO_DEV;
return OK;
}
/*===========================================================================*
* do_mkdir *
*===========================================================================*/
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.s_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)) == NULL)
return EINVAL;
if ((r = verify_dentry(parent, name, path, &ino)) != OK)
return r;
/* Perform the actual mkdir call. */
r = sffs_table->t_mkdir(path, m_in.REQ_MODE);
if (r != OK) {
if (ino != NULL)
put_inode(ino);
return r;
}
/* If we thought the new dentry already existed, it was apparently gone
* already. Delete it.
*/
if (ino != NULL) {
del_dentry(ino);
put_inode(ino);
}
return OK;
}
/*===========================================================================*
* force_remove *
*===========================================================================*/
static int force_remove(path, dir)
char *path; /* path to file or directory */
int dir; /* TRUE iff directory */
{
/* Remove a file or directory. Wrapper around unlink and 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 SFFS library follows the
* behavior of the host file system, but this case just confuses the hell out
* of the MINIX userland..
*/
struct sffs_attr attr;
int r, r2;
/* First try to remove the target. */
if (dir)
r = sffs_table->t_rmdir(path);
else
r = sffs_table->t_unlink(path);
if (r != EACCES) return r;
/* If this fails with an access error, retrieve the target's mode. */
attr.a_mask = SFFS_ATTR_MODE;
r2 = sffs_table->t_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 = sffs_table->t_setattr(path, &attr);
if (r2 != OK) return r;
/* Then try the original operation again. */
if (dir)
r = sffs_table->t_rmdir(path);
else
r = sffs_table->t_unlink(path);
if (r == OK) return r;
/* If the operation still fails, unset the writable bit again. */
attr.a_mode &= ~S_IWUSR;
sffs_table->t_setattr(path, &attr);
return r;
}
/*===========================================================================*
* do_unlink *
*===========================================================================*/
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.s_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)) == NULL)
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 != NULL)
put_inode(ino);
return r;
}
/* If a dentry existed for this name, it is gone now. */
if (ino != NULL) {
del_dentry(ino);
put_inode(ino);
}
return OK;
}
/*===========================================================================*
* do_rmdir *
*===========================================================================*/
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.s_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)) == NULL)
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 != NULL)
put_inode(ino);
return r;
}
/* If a dentry existed for this name, it is gone now. */
if (ino != NULL) {
del_dentry(ino);
put_inode(ino);
}
return OK;
}
/*===========================================================================*
* do_rename *
*===========================================================================*/
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.s_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)) == NULL ||
(new_parent = find_inode(m_in.REQ_REN_NEW_DIR)) == NULL)
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 != NULL)
put_inode(old_ino);
return r;
}
/* Perform the actual rename call. */
r = sffs_table->t_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 != NULL) put_inode(old_ino);
if (new_ino != NULL) 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 != NULL) {
del_dentry(new_ino);
put_inode(new_ino);
}
/* If the old dentry existed, rename it accordingly. */
if (old_ino != NULL) {
del_dentry(old_ino);
add_dentry(new_parent, new_name, old_ino);
put_inode(old_ino);
}
return OK;
}