#include "fs/vfs_syscall.h" #include "errno.h" #include "fs/fcntl.h" #include "fs/file.h" #include "fs/lseek.h" #include "fs/vfs.h" #include "fs/vnode.h" #include "globals.h" #include "kernel.h" #include "util/debug.h" #include "util/string.h" #include /* * Read len bytes into buf from the fd's file using the file's vnode operation * read. * * Return the number of bytes read on success, or: * - EBADF: fd is invalid or is not open for reading * - EISDIR: fd refers to a directory * - Propagate errors from the vnode operation read * * Hints: * - Be sure to update the file's position appropriately. * - Lock/unlock the file's vnode when calling its read operation. */ ssize_t do_read(int fd, void *buf, size_t len) { // NOT_YET_IMPLEMENTED("VFS: do_read"); // Check if the file descriptor is valid if (fd < 0 || fd >= NFILES) { return -EBADF; } // fget the file file_t *file = fget(fd); if (file == NULL) { return -EBADF; } // Check if the file is open for reading if (!(file->f_mode & FMODE_READ)) { fput(&file); return -EBADF; } // Check if the file is a directory if (S_ISDIR(file->f_vnode->vn_mode)) { fput(&file); return -EISDIR; } // Check is read is valid if (file->f_vnode->vn_ops->read == NULL) { fput(&file); return -EISDIR; } // Read the file vlock(file->f_vnode); ssize_t bytes_read = file->f_vnode->vn_ops->read(file->f_vnode, file->f_pos, buf, len); vunlock(file->f_vnode); // Check if the read was successful if (bytes_read < 0) { fput(&file); return bytes_read; } // Update the file position file->f_pos += bytes_read; fput(&file); return bytes_read; } /* * Write len bytes from buf into the fd's file using the file's vnode operation * write. * * Return the number of bytes written on success, or: * - EBADF: fd is invalid or is not open for writing * - Propagate errors from the vnode operation read * * Hints: * - Check out `man 2 write` for details about how to handle the FMODE_APPEND * flag. * - Be sure to update the file's position appropriately. * - Lock/unlock the file's vnode when calling its write operation. */ ssize_t do_write(int fd, const void *buf, size_t len) { // NOT_YET_IMPLEMENTED("VFS: do_write"); // Check if the file descriptor is valid if (fd < 0 || fd >= NFILES) { return -EBADF; } // dbg(DBG_PRINT, "VFS: do_write: fd = %d, len = %ld\n", fd, len); // fget the file file_t *file = fget(fd); if (file == NULL) { return -EBADF; } // Check if the file is open for writing if (!(file->f_mode & FMODE_WRITE)) { fput(&file); return -EBADF; } // Check is write is valid if (file->f_vnode->vn_ops->write == NULL) { fput(&file); return -EISDIR; } // Move the file position to the end of the file if the file is open in append mode if (file->f_mode & FMODE_APPEND) { vlock(file->f_vnode); file->f_pos = file->f_vnode->vn_len; vunlock(file->f_vnode); } // Write the file vlock(file->f_vnode); ssize_t bytes_written = file->f_vnode->vn_ops->write(file->f_vnode, file->f_pos, buf, len); vunlock(file->f_vnode); // Check if the write was successful if (bytes_written < 0) { fput(&file); return bytes_written; } file->f_pos += bytes_written; fput(&file); return bytes_written; } /* * Close the file descriptor fd. * * Return 0 on success, or: * - EBADF: fd is invalid or not open * * Hints: * Check `proc.h` to see if there are any helpful fields in the * proc_t struct for checking if the file associated with the fd is open. * Consider what happens when we open a file and what counts as closing it */ long do_close(int fd) { // NOT_YET_IMPLEMENTED("VFS: do_close"); // Check if the file descriptor is valid if (fd < 0 || fd >= NFILES) { return -EBADF; } // fget the file file_t *file = fget(fd); if (file == NULL) { return -EBADF; } // Close the file fput(&file); fput(&curproc->p_files[fd]); return 0; } /* * Duplicate the file descriptor fd. * * Return the new file descriptor on success, or: * - EBADF: fd is invalid or not open * - Propagate errors from get_empty_fd() * * Hint: Use get_empty_fd() to obtain an available file descriptor. */ long do_dup(int fd) { // NOT_YET_IMPLEMENTED("VFS: do_dup"); // Check if the file descriptor is valid if (fd < 0 || fd >= NFILES) { return -EBADF; } // fget the file file_t *file = fget(fd); if (file == NULL) { return -EBADF; } // Get an empty file descriptor int new_fd = -1; long ret = get_empty_fd(&new_fd); if (ret < 0) { fput(&file); return ret; } // Copy the file curproc->p_files[new_fd] = file; return new_fd; } /* * Duplicate the file descriptor ofd using the new file descriptor nfd. If nfd * was previously open, close it. * * Return nfd on success, or: * - EBADF: ofd is invalid or not open, or nfd is invalid * * Hint: You don't need to do anything if ofd and nfd are the same. * (If supporting MTP, this action must be atomic) */ long do_dup2(int ofd, int nfd) { // NOT_YET_IMPLEMENTED("VFS: do_dup2"); // Check if the file descriptors are valid if (ofd < 0 || ofd >= NFILES || nfd < 0 || nfd >= NFILES) { return -EBADF; } // Check if the file descriptors are the same if (!curproc->p_files[ofd]) { return -EBADF; } if (ofd == nfd) { return nfd; } // fget the file file_t *file = fget(ofd); if (file == NULL) { return -EBADF; } // figet the new file file_t *new_file = fget(nfd); if (new_file != NULL) { fput(&new_file); do_close(nfd); } // Copy the file curproc->p_files[nfd] = file; return nfd; } /* * Create a file specified by mode and devid at the location specified by path. * * Return 0 on success, or: * - EINVAL: Mode is not S_IFCHR, S_IFBLK, or S_IFREG * - Propagate errors from namev_open() * * Hints: * - Create the file by calling namev_open() with the O_CREAT flag. * - Be careful about refcounts after calling namev_open(). The newly created * vnode should have no references when do_mknod returns. The underlying * filesystem is responsible for maintaining references to the inode, which * will prevent it from being destroyed, even if the corresponding vnode is * cleaned up. * - You don't need to handle EEXIST (this would be handled within namev_open, * but doing so would likely cause problems elsewhere) */ long do_mknod(const char *path, int mode, devid_t devid) { // NOT_YET_IMPLEMENTED("VFS: do_mknod"); // Check if the mode is valid if (mode != S_IFCHR && mode != S_IFBLK && mode != S_IFREG) { return -EINVAL; } // get vnode vnode_t *vnode = NULL; vnode_t *base = curproc->p_cwd; long ret = namev_open(base, path, O_CREAT, mode, devid, &vnode); if (ret < 0) { return ret; } vput(&vnode); return ret; } /* * Create a directory at the location specified by path. * * Return 0 on success, or: * - ENAMETOOLONG: The last component of path is too long * - ENOTDIR: The parent of the directory to be created is not a directory * - EEXIST: A file located at path already exists * - Propagate errors from namev_dir(), namev_lookup(), and the vnode * operation mkdir * * Hints: * 1) Use namev_dir() to find the parent of the directory to be created. * 2) Use namev_lookup() to check that the directory does not already exist. * 3) Use the vnode operation mkdir to create the directory. * - Compare against NAME_LEN to determine if the basename is too long. * Check out ramfs_mkdir() to confirm that the basename will be null- * terminated. * - Be careful about locking and refcounts after calling namev_dir() and * namev_lookup(). */ long do_mkdir(const char *path) { // NOT_YET_IMPLEMENTED("VFS: do_mkdir"); // Call namev_dir() to find the parent of the directory to be created size_t len = 0; const char *name = NULL; vnode_t *dir = NULL; long ret = namev_dir(curproc->p_cwd, path, &dir, &name, &len); dbg(DBG_TEST, "VFS: do_mkdir: namev_dir returned %ld\n", ret); if (ret < 0) { return ret; } if (!S_ISDIR(dir->vn_mode)) { vput(&dir); return -ENOTDIR; } if (len > NAME_LEN) { vput(&dir); return -ENAMETOOLONG; } vnode_t *base = NULL; vlock(dir); ret = namev_lookup(dir, name, len, &base); vunlock(dir); // Check if the directory already exists if (ret >= 0) { vput(&base); vput(&dir); return -EEXIST; } // Create the directory long err = dir->vn_ops->mkdir(dir, name, len, &base); if (err < 0) { vput(&dir); return err; } vput(&base); vput(&dir); return 0; } /* * Delete a directory at path. * * Return 0 on success, or: * - EINVAL: Attempting to rmdir with "." as the final component * - ENOTEMPTY: Attempting to rmdir with ".." as the final component * - ENOTDIR: The parent of the directory to be removed is not a directory * - ENAMETOOLONG: the last component of path is too long * - Propagate errors from namev_dir() and the vnode operation rmdir * * Hints: * - Use namev_dir() to find the parent of the directory to be removed. * - Be careful about refcounts from calling namev_dir(). * - Use the parent directory's rmdir operation to remove the directory. * - Lock/unlock the vnode when calling its rmdir operation. */ long do_rmdir(const char *path) { // NOT_YET_IMPLEMENTED("VFS: do_rmdir"); // Call namev_dir() to find the parent of the directory to be removed size_t len = 0; const char *name = NULL; vnode_t *dir = NULL; long ret = namev_dir(curproc->p_cwd, path, &dir, &name, &len); // Check if the directory is valid if (ret < 0) { return ret; } // Check if the directory is a directory if (!S_ISDIR(dir->vn_mode)) { vput(&dir); return -ENOTDIR; } // Check if name is too long if (len > NAME_LEN) { vput(&dir); return -ENAMETOOLONG; } // Check if the directory is empty if (len == 1 && name[0] == '.') { vput(&dir); return -EINVAL; } if (len == 2 && name[0] == '.' && name[1] == '.') { vput(&dir); return -ENOTEMPTY; } // Remove the directory vnode_t *base = NULL; vlock(dir); ret = namev_lookup(dir, name, len, &base); vunlock(dir); // Check if the directory exists if (ret < 0) { vput(&dir); return ret; } // Check if the directory is a directory if (!S_ISDIR(base->vn_mode)) { vput(&base); vput(&dir); return -ENOTDIR; } vput(&base); // Remove the directory vlock(dir); ret = dir->vn_ops->rmdir(dir, name, len); vunlock(dir); vput(&dir); return ret; } /* * Remove the link between path and the file it refers to. * * Return 0 on success, or: * - ENOTDIR: the parent of the file to be unlinked is not a directory * - ENAMETOOLONG: the last component of path is too long * - Propagate errors from namev_dir() and the vnode operation unlink * * Hints: * - Use namev_dir() and be careful about refcounts. * - Lock/unlock the parent directory when calling its unlink operation. */ long do_unlink(const char *path) { // NOT_YET_IMPLEMENTED("VFS: do_unlink"); // Call namev_dir() to find the parent of the file to be unlinked size_t len = 0; const char *name = NULL; vnode_t *dir = NULL; long ret = namev_dir(curproc->p_cwd, path, &dir, &name, &len); // Check if the directory is valid if (ret < 0) { return ret; } // Check if the directory is a directory if (!S_ISDIR(dir->vn_mode)) { vput(&dir); return -ENOTDIR; } // Check if name is too long if (len > NAME_LEN) { vput(&dir); return -ENAMETOOLONG; } // Remove the link vnode_t *base = NULL; vlock(dir); ret = namev_lookup(dir, name, len, &base); vunlock(dir); // Check if the file exists if (ret < 0) { vput(&dir); return ret; } // Check if the file is a directory if (S_ISDIR(base->vn_mode)) { vput(&base); vput(&dir); return -EPERM; } vput(&base); // Call the unlink operation vlock(dir); ret = dir->vn_ops->unlink(dir, name, len); vunlock(dir); vput(&dir); return ret; } /* * Create a hard link newpath that refers to the same file as oldpath. * * Return 0 on success, or: * - EPERM: oldpath refers to a directory * - ENAMETOOLONG: The last component of newpath is too long * - ENOTDIR: The parent of the file to be linked is not a directory * * Hints: * 1) Use namev_resolve() on oldpath to get the target vnode. * 2) Use namev_dir() on newpath to get the directory vnode. * 3) Use vlock_in_order() to lock the directory and target vnodes. * 4) Use the directory vnode's link operation to create a link to the target. * 5) Use vunlock_in_order() to unlock the vnodes. * 6) Make sure to clean up references added from calling namev_resolve() and * namev_dir(). */ long do_link(const char *oldpath, const char *newpath) { // NOT_YET_IMPLEMENTED("VFS: do_link"); // Resolve the oldpath vnode_t *old_vnode = NULL; long ret = namev_resolve(NULL, oldpath, &old_vnode); // Check if the oldpath is valid if (ret < 0) { return ret; } // Check if the oldpath is a directory if (S_ISDIR(old_vnode->vn_mode)) { vput(&old_vnode); return -EPERM; } // Get the directory of the newpath size_t len = 0; const char *name = NULL; vnode_t *dir = NULL; ret = namev_dir(curproc->p_cwd, newpath, &dir, &name, &len); // Check if the directory is valid if (ret < 0) { vput(&old_vnode); return ret; } // Check if the directory is a directory if (!S_ISDIR(dir->vn_mode)) { vput(&old_vnode); return -ENOTDIR; } // Check if name is too long if (len > NAME_LEN) { vput(&old_vnode); return -ENAMETOOLONG; } // Lock the vnodes and call link vlock_in_order(old_vnode, dir); ret = dir->vn_ops->link(old_vnode, dir, name, len); vunlock_in_order(old_vnode, dir); vput(&old_vnode); vput(&dir); return ret; } /* Rename a file or directory. * * Return 0 on success, or: * - ENOTDIR: the parent of either path is not a directory * - ENAMETOOLONG: the last component of either path is too long * - Propagate errors from namev_dir() and the vnode operation rename * * You DO NOT need to support renaming of directories. * Steps: * 1. namev_dir oldpath --> olddir vnode * 2. namev_dir newpath --> newdir vnode * 4. Lock the olddir and newdir in ancestor-first order (see `vlock_in_order`) * 5. Use the `rename` vnode operation * 6. Unlock the olddir and newdir * 8. vput the olddir and newdir vnodes * * Alternatively, you can allow do_rename() to rename directories if * __RENAMEDIR__ is set in Config.mk. As with all extra credit * projects this is harder and you will get no extra credit (but you * will get our admiration). Please make sure the normal version works first. * Steps: * 1. oldpath --> olddir vnode * 2. namev_dir newpath --> newdir vnode * 3. Lock the global filesystem `vnode_rename_mutex` * 4. Lock the olddir and newdir in ancestor-first order (see `vlock_in_order`) * 5. Use the `rename` vnode operation * 6. Unlock the olddir and newdir * 7. Unlock the global filesystem `vnode_rename_mutex` * 8. vput the olddir and newdir vnodes * * P.S. This scheme /probably/ works, but we're not 100% sure. */ long do_rename(const char *oldpath, const char *newpath) { // NOT_YET_IMPLEMENTED("VFS: do_rename"); // Get the old directory size_t old_len = 0; const char *old_name = NULL; vnode_t *old_dir = NULL; long ret = namev_dir(curproc->p_cwd, oldpath, &old_dir, &old_name, &old_len); // Check if the old directory is valid if (ret < 0) { return ret; } // Check if the old directory is a directory if (!S_ISDIR(old_dir->vn_mode)) { return -ENOTDIR; } // Get the new directory size_t new_len = 0; const char *new_name = NULL; vnode_t *new_dir = NULL; ret = namev_dir(curproc->p_cwd, newpath, &new_dir, &new_name, &new_len); // Check if the new directory is valid if (ret < 0) { vput(&old_dir); return ret; } // Check if the new directory is a directory if (!S_ISDIR(new_dir->vn_mode)) { vput(&old_dir); return -ENOTDIR; } // Check if the names are too long if (old_len > NAME_LEN || new_len > NAME_LEN) { vput(&old_dir); return -ENAMETOOLONG; } // Lock the vnodes and call rename vlock_in_order(old_dir, new_dir); ret = old_dir->vn_ops->rename(old_dir, old_name, old_len, new_dir, new_name, new_len); vunlock_in_order(old_dir, new_dir); vput(&old_dir); vput(&new_dir); return ret; } /* Set the current working directory to the directory represented by path. * * Returns 0 on success, or: * - ENOTDIR: path does not refer to a directory * - Propagate errors from namev_resolve() * * Hints: * - Use namev_resolve() to get the vnode corresponding to path. * - Pay attention to refcounts! * - Remember that p_cwd should not be locked upon return from this function. * - (If doing MTP, must protect access to p_cwd) */ long do_chdir(const char *path) { // NOT_YET_IMPLEMENTED("VFS: do_chdir"); // Resolve the path vnode_t *vnode = NULL; long ret = namev_resolve(NULL, path, &vnode); // Check if the path is valid if (ret < 0) { return ret; } // Check if the path is a directory if (!S_ISDIR(vnode->vn_mode)) { vput(&vnode); return -ENOTDIR; } // Set the current working directory vput(&curproc->p_cwd); curproc->p_cwd = vnode; return 0; } /* * Read a directory entry from the file specified by fd into dirp. * * Return sizeof(dirent_t) on success, or: * - EBADF: fd is invalid or is not open * - ENOTDIR: fd does not refer to a directory * - Propagate errors from the vnode operation readdir * * Hints: * - Use the vnode operation readdir. * - Be sure to update file position according to readdir's return value. * - On success (readdir return value is strictly positive), return * sizeof(dirent_t). */ ssize_t do_getdent(int fd, struct dirent *dirp) { // NOT_YET_IMPLEMENTED("VFS: do_getdent"); // Check if the file descriptor is valid if (fd < 0 || fd >= NFILES) { return -EBADF; } // fget the file file_t *file = fget(fd); if (file == NULL) { return -EBADF; } // Check if the file is a directory if (!S_ISDIR(file->f_vnode->vn_mode)) { fput(&file); return -ENOTDIR; } // Read the directory entry vlock(file->f_vnode); ssize_t bytes_read = file->f_vnode->vn_ops->readdir(file->f_vnode, file->f_pos, dirp); vunlock(file->f_vnode); // Check if the read was successful if (bytes_read <= 0) { fput(&file); return bytes_read; } // Update the file position file->f_pos += bytes_read; fput(&file); return sizeof(dirent_t); } /* * Set the position of the file represented by fd according to offset and * whence. * * Return the new file position, or: * - EBADF: fd is invalid or is not open * - EINVAL: whence is not one of SEEK_SET, SEEK_CUR, or SEEK_END; * or, the resulting file offset would be negative * * Hints: * - See `man 2 lseek` for details about whence. * - Be sure to protect the vnode if you have to access its vn_len. */ off_t do_lseek(int fd, off_t offset, int whence) { // NOT_YET_IMPLEMENTED("VFS: do_lseek"); // Check if the file descriptor is valid if (fd < 0 || fd >= NFILES) { return -EBADF; } // fget the file file_t *file = fget(fd); if (file == NULL) { return -EBADF; } // Check if the whence is valid off_t new_pos = 0; switch (whence) { case SEEK_SET: new_pos = offset; break; case SEEK_CUR: new_pos = file->f_pos + offset; break; case SEEK_END: new_pos = file->f_vnode->vn_len + offset; break; default: fput(&file); return -EINVAL; } // Check if the new position is negative if (new_pos < 0) { fput(&file); return -EINVAL; } // Update the file position file->f_pos = new_pos; off_t pos = file->f_pos; fput(&file); return pos; } /* Use buf to return the status of the file represented by path. * * Return 0 on success, or: * - Propagate errors from namev_resolve() and the vnode operation stat. */ long do_stat(const char *path, stat_t *buf) { // NOT_YET_IMPLEMENTED("VFS: do_stat"); // Resolve the path vnode_t *vnode = NULL; long ret = namev_resolve(NULL, path, &vnode); // Check if the path is valid if (ret < 0) { return ret; } // Get the status of the file vlock(vnode); ret = vnode->vn_ops->stat(vnode, buf); vunlock(vnode); vput(&vnode); return ret; } #ifdef __MOUNTING__ /* * Implementing this function is not required and strongly discouraged unless * you are absolutely sure your Weenix is perfect. * * This is the syscall entry point into vfs for mounting. You will need to * create the fs_t struct and populate its fs_dev and fs_type fields before * calling vfs's mountfunc(). mountfunc() will use the fields you populated * in order to determine which underlying filesystem's mount function should * be run, then it will finish setting up the fs_t struct. At this point you * have a fully functioning file system, however it is not mounted on the * virtual file system, you will need to call vfs_mount to do this. * * There are lots of things which can go wrong here. Make sure you have good * error handling. Remember the fs_dev and fs_type buffers have limited size * so you should not write arbitrary length strings to them. */ int do_mount(const char *source, const char *target, const char *type) { NOT_YET_IMPLEMENTED("MOUNTING: do_mount"); return -EINVAL; } /* * Implementing this function is not required and strongly discouraged unless * you are absolutley sure your Weenix is perfect. * * This function delegates all of the real work to vfs_umount. You should not * worry about freeing the fs_t struct here, that is done in vfs_umount. All * this function does is figure out which file system to pass to vfs_umount and * do good error checking. */ int do_umount(const char *target) { NOT_YET_IMPLEMENTED("MOUNTING: do_umount"); return -EINVAL; } #endif