/* 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 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 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)) == 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 = 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 != NULL) 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 != NULL) { 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 != 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 = opt.uid; m_out.RES_GID = opt.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.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 = hgfs_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 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 * *===========================================================================*/ 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)) == 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.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.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 = 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 != 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; }