diff options
-rw-r--r-- | .devcontainer/Dockerfile | 5 | ||||
-rw-r--r-- | .devcontainer/devcontainer.json | 17 | ||||
-rw-r--r-- | Config.mk | 6 | ||||
-rw-r--r-- | README.md | 1 | ||||
-rw-r--r-- | kernel/drivers/disk/sata.c | 44 | ||||
-rw-r--r-- | kernel/drivers/memdevs.c | 60 | ||||
-rw-r--r-- | kernel/drivers/tty/ldisc.c | 209 | ||||
-rw-r--r-- | kernel/drivers/tty/tty.c | 52 | ||||
-rw-r--r-- | kernel/fs/namev.c | 168 | ||||
-rw-r--r-- | kernel/fs/open.c | 99 | ||||
-rw-r--r-- | kernel/fs/s5fs/s5fs.c | 471 | ||||
-rw-r--r-- | kernel/fs/vfs_syscall.c | 628 | ||||
-rw-r--r-- | kernel/fs/vnode_specials.c | 32 | ||||
-rw-r--r-- | kernel/main/kmain.c | 48 | ||||
-rw-r--r-- | kernel/proc/kthread.c | 54 | ||||
-rw-r--r-- | kernel/proc/proc.c | 225 | ||||
-rw-r--r-- | kernel/proc/sched.c | 100 | ||||
-rw-r--r-- | kernel/test/proctest.c | 271 |
18 files changed, 2376 insertions, 114 deletions
diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 0000000..9cf7799 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,5 @@ +FROM --platform=linux/x86_64 mcr.microsoft.com/devcontainers/base:ubuntu-22.04 +LABEL authors="sotech117" + +RUN sudo apt-get update +RUN sudo apt-get install python2 python2-minimal cscope nasm make build-essential grub2-common grub-pc-bin qemu qemu-system xorriso genisoimage xterm gdb -y
\ No newline at end of file diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..02ce53e --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,17 @@ +{ + "name": "os-dev", + "build": { "dockerfile": "Dockerfile" }, + "customizations" : { + "vscode": { + "extensions": [ + "ms-vscode.cpptools", + "ms-vscode.cpptools-extension-pack", + "ms-vscode.makefile-tools", + + "ms-azuretools.vscode-docker", + + "tonybaloney.vscode-pets" + ] + } + } +}
\ No newline at end of file @@ -10,9 +10,9 @@ # Setting any of these variables will control which parts of the source tree # are built. To enable something set it to 1, otherwise set it to 0. # - DRIVERS=0 - VFS=0 - S5FS=0 + DRIVERS=1 + VFS=1 + S5FS=1 VM=0 DYNAMIC=0 # When you finish S5FS, first enable "VM"; once this is working, then enable @@ -1,3 +1,4 @@ +[](https://classroom.github.com/a/xSREhkGA) # Installation This file contains quick instructions for getting Weenix to run on diff --git a/kernel/drivers/disk/sata.c b/kernel/drivers/disk/sata.c index aa3ff41..d5ca117 100644 --- a/kernel/drivers/disk/sata.c +++ b/kernel/drivers/disk/sata.c @@ -488,8 +488,26 @@ void sata_init() long sata_read_block(blockdev_t *bdev, char *buf, blocknum_t block, size_t block_count) { - NOT_YET_IMPLEMENTED("DRIVERS: sata_read_block"); - return -1; + // NOT_YET_IMPLEMENTED("DRIVERS: sata_read_block"); + + // convert block to sector + ssize_t lba = block * SATA_SECTORS_PER_BLOCK; + ssize_t count = block_count * SATA_SECTORS_PER_BLOCK; + + // get the disk + ata_disk_t *disk = bdev_to_ata_disk(bdev); + if (disk == NULL) + { + dbg(DBG_DISK, "sata_read_block: disk is NULL\n"); + return -1; + } + + // call ahci_do_operation + long ret = ahci_do_operation(disk->port, lba, count, buf, + 0 // 0 for read + ); + + return ret; } /** @@ -507,6 +525,24 @@ long sata_read_block(blockdev_t *bdev, char *buf, blocknum_t block, long sata_write_block(blockdev_t *bdev, const char *buf, blocknum_t block, size_t block_count) { - NOT_YET_IMPLEMENTED("DRIVERS: sata_write_block"); - return -1; + // NOT_YET_IMPLEMENTED("DRIVERS: sata_write_block"); + + // convert block to sector + ssize_t lba = block * SATA_SECTORS_PER_BLOCK; + ssize_t count = block_count * SATA_SECTORS_PER_BLOCK; + + // get the disk + ata_disk_t *disk = bdev_to_ata_disk(bdev); + if (disk == NULL) + { + dbg(DBG_DISK, "sata_write_block: disk is NULL\n"); + return -1; + } + + // call ahci_do_operation + long ret = ahci_do_operation(disk->port, lba, count, (void *)buf, + 1 // 1 for write + ); + + return ret; } diff --git a/kernel/drivers/memdevs.c b/kernel/drivers/memdevs.c index 50815d3..815143a 100644 --- a/kernel/drivers/memdevs.c +++ b/kernel/drivers/memdevs.c @@ -43,7 +43,31 @@ chardev_ops_t zero_dev_ops = {.read = zero_read, */ void memdevs_init() { - NOT_YET_IMPLEMENTED("DRIVERS: memdevs_init"); + // NOT_YET_IMPLEMENTED("DRIVERS: memdevs_init"); + + // create chardev_t's for null and zero + chardev_t *null_dev = kmalloc(sizeof(chardev_t)); + if (null_dev == NULL) + { + dbg(DBG_DISK, "ERROR: kmalloc failed on null_dev in memdevs_init\n"); + return; + } + chardev_t *zero_dev = kmalloc(sizeof(chardev_t)); + if (zero_dev == NULL) + { + dbg(DBG_DISK, "ERROR: kmalloc failed on zero_dev in memdevs_init\n"); + return; + } + + // fill them in + null_dev->cd_id = MEM_NULL_DEVID; + null_dev->cd_ops = &null_dev_ops; + zero_dev->cd_id = MEM_ZERO_DEVID; + zero_dev->cd_ops = &zero_dev_ops; + + // register them + chardev_register(null_dev); + chardev_register(zero_dev); } /** @@ -58,8 +82,9 @@ void memdevs_init() */ static ssize_t null_read(chardev_t *dev, size_t pos, void *buf, size_t count) { - NOT_YET_IMPLEMENTED("DRIVERS: null_read"); - return -ENOMEM; + // NOT_YET_IMPLEMENTED("DRIVERS: null_read"); + // TODO: ask about buffer checking + return 0; // return reading no bytes } /** @@ -76,8 +101,18 @@ static ssize_t null_read(chardev_t *dev, size_t pos, void *buf, size_t count) static ssize_t null_write(chardev_t *dev, size_t pos, const void *buf, size_t count) { - NOT_YET_IMPLEMENTED("DRIVERS: null_write"); - return -ENOMEM; + // NOT_YET_IMPLEMENTED("DRIVERS: null_write"); + + // check if the buffer is NULL + if (buf == NULL) + { + return -EINVAL; + } + + // there is no true writing, so just do nothing here + + // return the number of bytes written + return count; } /** @@ -93,8 +128,19 @@ static ssize_t null_write(chardev_t *dev, size_t pos, const void *buf, */ static ssize_t zero_read(chardev_t *dev, size_t pos, void *buf, size_t count) { - NOT_YET_IMPLEMENTED("DRIVERS: zero_read"); - return 0; + // NOT_YET_IMPLEMENTED("DRIVERS: zero_read"); + + // check if the buffer is NULL + if (buf == NULL) + { + return -EINVAL; + } + + // fill the buffer with zeros + memset(buf, 0, count); + + // return the number of bytes read + return count; } /** diff --git a/kernel/drivers/tty/ldisc.c b/kernel/drivers/tty/ldisc.c index eaf0440..9b77433 100644 --- a/kernel/drivers/tty/ldisc.c +++ b/kernel/drivers/tty/ldisc.c @@ -16,7 +16,19 @@ */ void ldisc_init(ldisc_t *ldisc) { - NOT_YET_IMPLEMENTED("DRIVERS: ldisc_init"); + // NOT_YET_IMPLEMENTED("DRIVERS: ldisc_init"); + + // reset static buffer + memset(ldisc->ldisc_buffer, 0, LDISC_BUFFER_SIZE); + + // init the queue + sched_queue_init(&ldisc->ldisc_read_queue); + + // fill in other fields + ldisc->ldisc_head = 0; + ldisc->ldisc_tail = 0; + ldisc->ldisc_cooked = 0; + ldisc->ldisc_full = 0; // 0 not full } /** @@ -33,8 +45,23 @@ void ldisc_init(ldisc_t *ldisc) */ long ldisc_wait_read(ldisc_t *ldisc) { - NOT_YET_IMPLEMENTED("DRIVERS: ldisc_wait_read"); - return -1; + // NOT_YET_IMPLEMENTED("DRIVERS: ldisc_wait_read"); + + // while there are no need chars to be read, sleep + // TODO: check if this is the right condition + while ( + ldisc->ldisc_head == ldisc->ldisc_tail + ) + { + long ret = sched_cancellable_sleep_on(&ldisc->ldisc_read_queue); + if (ret != 0) + { + return ret; + } + } + + // if we are here, it means we have new chars to read + return 0; } /** @@ -54,8 +81,57 @@ long ldisc_wait_read(ldisc_t *ldisc) */ size_t ldisc_read(ldisc_t *ldisc, char *buf, size_t count) { - NOT_YET_IMPLEMENTED("DRIVERS: ldisc_read"); - return 0; + // NOT_YET_IMPLEMENTED("DRIVERS: ldisc_read"); + + dbg(DBG_DISK, "ldisc_read: count = %d\n", count); + + // read from ldisc buffer to buf + size_t i = 0; + int break_loop = 0; + while (i < count && !break_loop) + { + // if we have no new chars to read, break loop + if (ldisc->ldisc_head == ldisc->ldisc_tail) + { + break_loop = 1; + continue; + } + + + char c = ldisc->ldisc_buffer[ldisc->ldisc_tail]; + dbg(DBG_DISK, "ldisc_read: %c\n", c); + switch (c) + { + + case EOT: + buf[i] = c; + ldisc->ldisc_tail = (ldisc->ldisc_tail + 1) % LDISC_BUFFER_SIZE; + break_loop = 1; + break; + + // case ETX: + // ldisc->ldisc_tail = ldisc->ldisc_cooked; + // break; + + case '\n': + buf[i] = c; + ldisc->ldisc_tail = (ldisc->ldisc_tail + 1) % LDISC_BUFFER_SIZE; + i++; + break_loop = 1; + break; + + default: + buf[i] = c; + ldisc->ldisc_tail = (ldisc->ldisc_tail + 1) % LDISC_BUFFER_SIZE; + i++; + break; + } + + dbg(DBG_DISK, "ldisc_read: i = %d\n", i); + } + + // return the number of bytes read + return i; } /** @@ -103,7 +179,112 @@ size_t ldisc_read(ldisc_t *ldisc, char *buf, size_t count) */ void ldisc_key_pressed(ldisc_t *ldisc, char c) { - NOT_YET_IMPLEMENTED("DRIVERS: ldisc_key_pressed"); + // NOT_YET_IMPLEMENTED("DRIVERS: ldisc_key_pressed"); + + switch (c) + { + case '\b': + // if there is a character to remove + if (ldisc->ldisc_head != ldisc->ldisc_tail) + { + // remove the last char + ldisc->ldisc_head = (ldisc->ldisc_head - 1 + LDISC_BUFFER_SIZE) % LDISC_BUFFER_SIZE; + + // emit a `\b` to the vterminal + vterminal_write(&ldisc_to_tty(ldisc)->tty_vterminal, "\b", 1); + + + if (ldisc->ldisc_full) + { + ldisc->ldisc_full = 0; + } + } + break; + + case '\n': + + // add the new char to the buffer + ldisc->ldisc_buffer[ldisc->ldisc_head] = c; + + if (!ldisc->ldisc_full) + { + ldisc->ldisc_head = (ldisc->ldisc_head + 1) % LDISC_BUFFER_SIZE; + } + + // cook the buffer + ldisc->ldisc_cooked = ldisc->ldisc_head; + + sched_wakeup_on(&ldisc->ldisc_read_queue, 0); + + // emit a `\n` to the vterminal + vterminal_write(&ldisc_to_tty(ldisc)->tty_vterminal, "\n", 1); + + if (ldisc->ldisc_full) + { + ldisc->ldisc_full = 0; + } + break; + + case EOT: + // add the new char to the buffer + dbg(DBG_DISK, "EOT\n"); + ldisc->ldisc_buffer[ldisc->ldisc_head] = c; + if (!ldisc->ldisc_full) + { + ldisc->ldisc_head = (ldisc->ldisc_head + 1) % LDISC_BUFFER_SIZE; + } + + // cook the buffer + ldisc->ldisc_cooked = ldisc->ldisc_head; + + // wake up the thread that is sleeping on the wait queue of the line discipline + sched_wakeup_on(&ldisc->ldisc_read_queue, 0); + + vterminal_write(&ldisc_to_tty(ldisc)->tty_vterminal, "\n", 1); + + if (ldisc->ldisc_full) + { + ldisc->ldisc_full = 0; + } + break; + + case ETX: + // clear uncooked portion of the line + dbg(DBG_DISK, "ETX\n"); + ldisc->ldisc_head = (ldisc->ldisc_cooked + 1) % LDISC_BUFFER_SIZE; + ldisc->ldisc_cooked = ldisc->ldisc_head; + + // wake up the thread that is sleeping on the wait queue of the line discipline + sched_wakeup_on(&ldisc->ldisc_read_queue, 0); + + // emit a `\n` to the vterminal + vterminal_write(&ldisc_to_tty(ldisc)->tty_vterminal, "\n", 1); + break; + + default: + // if the buffer is full, ignore the incoming char + // if (ldisc->ldisc_full) + // { + // return; + // } + + // if none applies, fallback to vterminal_key_pressed + // vterminal_write(ldisc_to_tty(ldisc), &c, 1); + + // update the buffer if it's not full + if (!ldisc->ldisc_full) + { + ldisc->ldisc_buffer[ldisc->ldisc_head] = c; + ldisc->ldisc_head = (ldisc->ldisc_head + 1) % LDISC_BUFFER_SIZE; + if (ldisc->ldisc_head + 1 == ldisc->ldisc_tail) + { + ldisc->ldisc_full = 1; + } + vterminal_key_pressed(&ldisc_to_tty(ldisc)->tty_vterminal); + } + + break; + } } /** @@ -115,6 +296,18 @@ void ldisc_key_pressed(ldisc_t *ldisc, char c) */ size_t ldisc_get_current_line_raw(ldisc_t *ldisc, char *s) { - NOT_YET_IMPLEMENTED("DRIVERS: ldisc_get_current_line_raw"); - return 0; + // NOT_YET_IMPLEMENTED("DRIVERS: ldisc_get_current_line_raw"); + + // copy the raw part of the line discipline buffer into the buffer provided + size_t i = 0; + size_t j = ldisc->ldisc_cooked; + while (j != ldisc->ldisc_head) + { + s[i] = ldisc->ldisc_buffer[j]; + j = (j + 1) % LDISC_BUFFER_SIZE; + i++; + } + + // return the number of bytes copied + return i; } diff --git a/kernel/drivers/tty/tty.c b/kernel/drivers/tty/tty.c index 3694877..e3c77be 100644 --- a/kernel/drivers/tty/tty.c +++ b/kernel/drivers/tty/tty.c @@ -69,8 +69,33 @@ void tty_init() */ ssize_t tty_read(chardev_t *cdev, size_t pos, void *buf, size_t count) { - NOT_YET_IMPLEMENTED("DRIVERS: tty_read"); - return -1; + // NOT_YET_IMPLEMENTED("DRIVERS: tty_read"); + + // get the mapped tty + tty_t *tty = cd_to_tty(cdev); + + // set the IPL to INTR_KEYBOARD + uint8_t prev_ipl = intr_setipl(INTR_KEYBOARD); + + // lock the read mutex of the tty + kmutex_lock(&tty->tty_read_mutex); + + dbg(DBG_DISK, "tty_read: before wait_read\n"); + + // wait until there is something in the line discipline's buffer + ldisc_wait_read(&tty->tty_ldisc); + + dbg(DBG_DISK, "tty_read: after wait_read\n"); + + // read from the ldisc's buffer if there are new characters + ssize_t bytes_read = ldisc_read(&tty->tty_ldisc, buf, count); + // unlock the read mutex of the tty + kmutex_unlock(&tty->tty_read_mutex); + + // revert the IPL + intr_setipl(prev_ipl); + + return bytes_read; } /** @@ -88,8 +113,27 @@ ssize_t tty_read(chardev_t *cdev, size_t pos, void *buf, size_t count) */ ssize_t tty_write(chardev_t *cdev, size_t pos, const void *buf, size_t count) { - NOT_YET_IMPLEMENTED("DRIVERS: tty_write"); - return -1; + // NOT_YET_IMPLEMENTED("DRIVERS: tty_write"); + + // get the mapped tty + tty_t *tty = cd_to_tty(cdev); + + // set the IPL to INTR_KEYBOARD + uint8_t prev_ipl = intr_setipl(INTR_KEYBOARD); + + // lock the write mutex of the tty + kmutex_lock(&tty->tty_write_mutex); + + // write to the terminal + size_t bytes_written = vterminal_write(&tty->tty_vterminal, buf, count); + + // unlock the write mutex of the tty + kmutex_unlock(&tty->tty_write_mutex); + // revert the IPL + intr_setipl(prev_ipl); + + + return bytes_written; } static void tty_receive_char_multiplexer(uint8_t c) diff --git a/kernel/fs/namev.c b/kernel/fs/namev.c index e8b01e8..dd44450 100644 --- a/kernel/fs/namev.c +++ b/kernel/fs/namev.c @@ -76,8 +76,27 @@ long namev_is_descendant(vnode_t *a, vnode_t *b) long namev_lookup(vnode_t *dir, const char *name, size_t namelen, vnode_t **res_vnode) { - NOT_YET_IMPLEMENTED("VFS: namev_lookup"); - return 0; + // // NOT_YET_IMPLEMENTED("VFS: namev_lookup"); + + // KASSERT(NULL != dir); + // KASSERT(NULL != name); + // KASSERT(NULL != res_vnode); + + // if (namelen == 0) + // { + // return -EINVAL; + // } + + if ( + dir->vn_ops == NULL + || dir->vn_ops->lookup == NULL + || !S_ISDIR(dir->vn_mode) + ) + { + return -ENOTDIR; + } + + return dir->vn_ops->lookup(dir, name, namelen, res_vnode); } /* @@ -187,10 +206,89 @@ static const char *namev_tokenize(const char **search, size_t *len) long namev_dir(vnode_t *base, const char *path, vnode_t **res_vnode, const char **name, size_t *namelen) { - NOT_YET_IMPLEMENTED("VFS: namev_dir"); + // NOT_YET_IMPLEMENTED("VFS: namev_dir"); + + // Check if the pathname is nullish + if (path == NULL || *path == '\0') + { + return -EINVAL; + } + + // Get a vnode based on the pathname + vnode_t *dir = NULL; + if (*path == '/') + { + dir = vfs_root_fs.fs_root; + } + else if (base == NULL) + { + dir = curproc->p_cwd; + } + else + { + dir = base; + } + // Refcount it + vref(dir); + + // Tokenize the path + const char *token = NULL; + size_t len = 0; + + while ((token = namev_tokenize(&path, &len)) && len > 0) + { + if (path == NULL || *path == '\0') + { + break; + } + + // Check if the whole path is a sequence of '/' then a null + int index = 0; + while (path[index] == '/') + { + index++; + } + if (path[index] == '\0') + { + break; + } + + // Check if the name is too long + if (len > NAME_LEN) + { + vput(&dir); + return -ENAMETOOLONG; + } + + // Lookup the token + vnode_t *next = NULL; + vlock(dir); + long err = namev_lookup(dir, token, len, &next); + vunlock(dir); + if (err < 0) + { + vput(&dir); + return err; + } + + // Reduce refcount and move to the next token + vput(&dir); + dir = next; + } + + // Check if it's a directory + if (!S_ISDIR(dir->vn_mode)) + { + vput(&dir); + return -ENOTDIR; + } + + // Assign the ret vars + *res_vnode = dir; + *name = token; + *namelen = len; return 0; } - /* * Open the file specified by `base` and `path`, or create it, if necessary. * Return the file's vnode via `res_vnode`, which should be returned unlocked @@ -217,7 +315,67 @@ long namev_dir(vnode_t *base, const char *path, vnode_t **res_vnode, long namev_open(vnode_t *base, const char *path, int oflags, int mode, devid_t devid, struct vnode **res_vnode) { - NOT_YET_IMPLEMENTED("VFS: namev_open"); + // NOT_YET_IMPLEMENTED("VFS: namev_open"); + + // KASSERT(NULL != base); + KASSERT(NULL != path); + KASSERT(NULL != res_vnode); + + const char *name = NULL; + vnode_t *dir = NULL; + size_t namelen = 0; + + long err = namev_dir(base, path, &dir, &name, &namelen); + if (err < 0) + { + return err; + } + if (namelen > NAME_LEN) + { + vput(&dir); + return -ENAMETOOLONG; + } + + vnode_t *res = NULL; + vlock(dir); + err = namev_lookup(dir, name, namelen, &res); + vunlock(dir); + + if (err < 0) + { + if (!(oflags & O_CREAT)) + { + vput(&dir); + return err; + } + else + { + vlock(dir); + err = dir->vn_ops->mknod(dir, name, namelen, mode, devid, &res); + vunlock(dir); + + vput(&dir); + *res_vnode = res; + return err; + } + } + + if (path[strlen(path) - 1] == '/' && !S_ISDIR(res->vn_mode)) + { + vput(&dir); + vput(&res); + return -ENOTDIR; + } + + if (S_ISDIR(res->vn_mode) && (oflags & O_CREAT)) + { + vput(&dir); + vput(&res); + return -EISDIR; // TODO: check if this error + } + + vput(&dir); + *res_vnode = res; return 0; } diff --git a/kernel/fs/open.c b/kernel/fs/open.c index 8c9fee6..8f0d893 100644 --- a/kernel/fs/open.c +++ b/kernel/fs/open.c @@ -62,6 +62,101 @@ long get_empty_fd(int *fd) */ long do_open(const char *filename, int oflags) { - NOT_YET_IMPLEMENTED("VFS: do_open"); - return -1; + // NOT_YET_IMPLEMENTED("VFS: do_open"); + + // Check if oflags is valid + if ((oflags & O_WRONLY) && (oflags & O_RDWR)) + { + return -EINVAL; + } + + // Get an empty file descriptor + int fd = -1; + long ret = get_empty_fd(&fd); + if (ret < 0) + { + return ret; + } + + // Check if the file is a directory + if (filename[strlen(filename) - 1] == '/' && (oflags & O_WRONLY)) + { + return -EISDIR; + } + + // Open the file + vnode_t *res_vnode = NULL; + vnode_t *base = curproc->p_cwd; + ret = namev_open(base, filename, oflags, S_IFREG, 0, &res_vnode); + if (ret < 0) + { + if (res_vnode != NULL) + { + vput(&res_vnode); + } + return ret; + } + + // Check if the vnode is a directory + if (S_ISDIR(res_vnode->vn_mode) && ((oflags & O_WRONLY) || (oflags & O_RDWR))) + { + vput(&res_vnode); + return -EISDIR; + } + + // Check if the vnode is a blockdev or chardev + if (S_ISCHR(res_vnode->vn_mode) && res_vnode->vn_dev.chardev == NULL) + { + vput(&res_vnode); + return -ENXIO; + } + if (S_ISBLK(res_vnode->vn_mode) && res_vnode->vn_dev.blockdev == NULL) + { + vput(&res_vnode); + return -ENXIO; + } + + // Convert oflags to file access flags + int fmode = 0; + if (oflags & O_RDONLY) + { + fmode |= FMODE_READ; + } + + if (oflags & O_WRONLY) + { + fmode |= FMODE_WRITE; + } + else + { + fmode |= FMODE_READ; + } + + if (oflags & O_RDWR) + { + fmode |= FMODE_READ | FMODE_WRITE; + } + + if (oflags & O_APPEND) + { + fmode |= FMODE_APPEND; + } + // Truncate the file if O_TRUNC is specified + if (oflags & O_TRUNC && S_ISREG(res_vnode->vn_mode)) + { + vlock(res_vnode); + res_vnode->vn_ops->truncate_file(res_vnode); + vunlock(res_vnode); + } + + // Create the file descriptor + file_t *file = fcreate(fd, res_vnode, fmode); + if (file == NULL) + { + vput(&res_vnode); + return -ENOMEM; + } + + vput(&res_vnode); + return fd; } diff --git a/kernel/fs/s5fs/s5fs.c b/kernel/fs/s5fs/s5fs.c index d1df3a5..2bcec7d 100644 --- a/kernel/fs/s5fs/s5fs.c +++ b/kernel/fs/s5fs/s5fs.c @@ -210,7 +210,56 @@ long s5fs_mount(fs_t *fs) */ static void s5fs_read_vnode(fs_t *fs, vnode_t *vn) { - NOT_YET_IMPLEMENTED("S5FS: s5fs_read_vnode"); + // NOT_YET_IMPLEMENTED("S5FS: s5fs_read_vnode"); + + // Get the s5_node from the vnode + s5_node_t *s5_node = VNODE_TO_S5NODE(vn); + + // Get the inode + s5_inode_t *s5_inode = &s5_node->inode; + KASSERT(s5_inode); + // Update linkcount + s5_inode->s5_linkcount++; + + // Get the inode from the disk + pframe_t *pf; + s5_get_file_disk_block(vn, S5_INODE_BLOCK(vn->vn_vno), S5_INODE_OFFSET(vn->vn_vno), 0, &pf); + memcpy(s5_inode, pf->pf_addr, sizeof(s5_inode_t)); + + // Release the disk block + s5_release_disk_block(&pf); + + // Initialize the dirtied_inode field + s5_node->dirtied_inode = 0; + + // Initialize the vnode fields + // lan and inode trivial + vn->vn_len = s5_inode->s5_un.s5_size; + vn->vn_i = s5_inode; + // Initialize the vn_ops and mode field based on the type + switch (s5_inode->s5_type) + { + case S5_TYPE_DIR: // directory + vn->vn_ops = &s5fs_dir_vops; + vn->vn_mode = S_IFDIR; + break; + case S5_TYPE_CHR: // character device + vn->vn_ops = NULL; + vn->vn_mode = S_IFCHR; + vn->vn_devid = s5_inode->s5_indirect_block; + break; + case S5_TYPE_BLK: // block device + vn->vn_ops = NULL; + vn->vn_mode = S_IFBLK; + vn->vn_devid = s5_inode->s5_indirect_block; + break; + case S5_TYPE_DATA: // regular file + vn->vn_ops = &s5fs_file_vops; + vn->vn_mode = S_IFREG; + break; + default: // unknown type + panic("Unknown inode type %d\n", s5_inode->s5_type); + } } /* Clean up the inode corresponding to the given vnode. @@ -226,7 +275,38 @@ static void s5fs_read_vnode(fs_t *fs, vnode_t *vn) */ static void s5fs_delete_vnode(fs_t *fs, vnode_t *vn) { - NOT_YET_IMPLEMENTED("S5FS: s5fs_delete_vnode"); + // NOT_YET_IMPLEMENTED("S5FS: s5fs_delete_vnode"); + + // Get the s5_node from the vnode + s5_node_t *s5_node = VNODE_TO_S5NODE(vn); + + // Get the inode + s5_inode_t *s5_inode = &s5_node->inode; + KASSERT(s5_inode); + + // Update linkcount + s5_inode->s5_linkcount--; + + // Check if the inode is no longer in use + if (s5_inode->s5_linkcount == 0) + { + // Free the inode and return + s5_free_inode(FS_TO_S5FS(fs), s5_inode->s5_number); + return; + } + + // Check if the inode is dirty + if (s5_node->dirtied_inode) + { + // Write the inode back to disk and return + pframe_t *pf; + s5_get_file_disk_block(vn, S5_INODE_BLOCK(vn->vn_vno), S5_INODE_OFFSET(vn->vn_vno), 1, &pf); + memcpy(pf->pf_addr, s5_inode, sizeof(s5_inode_t)); + s5_release_disk_block(&pf); + return; + } + + // Do nothing if the inode is unchanged } /* @@ -285,8 +365,8 @@ static void s5fs_sync(fs_t *fs) static ssize_t s5fs_read(vnode_t *vnode, size_t pos, void *buf, size_t len) { KASSERT(!S_ISDIR(vnode->vn_mode) && "should be handled at the VFS level"); - NOT_YET_IMPLEMENTED("S5FS: s5fs_read"); - return -1; + // NOT_YET_IMPLEMENTED("S5FS: s5fs_read"); + return s5_read_file(VNODE_TO_S5NODE(vnode), pos, buf, len); } /* Wrapper around s5_write_file. */ @@ -294,8 +374,8 @@ static ssize_t s5fs_write(vnode_t *vnode, size_t pos, const void *buf, size_t len) { KASSERT(!S_ISDIR(vnode->vn_mode) && "should be handled at the VFS level"); - NOT_YET_IMPLEMENTED("S5FS: s5fs_write"); - return -1; + // NOT_YET_IMPLEMENTED("S5FS: s5fs_write"); + return s5_write_file(VNODE_TO_S5NODE(vnode), pos, buf, len); } /* @@ -334,8 +414,68 @@ static long s5fs_mknod(struct vnode *dir, const char *name, size_t namelen, int mode, devid_t devid, struct vnode **out) { KASSERT(S_ISDIR(dir->vn_mode) && "should be handled at the VFS level"); - NOT_YET_IMPLEMENTED("S5FS: s5fs_mknod"); - return -1; + // NOT_YET_IMPLEMENTED("S5FS: s5fs_mknod"); + + // Check if the mode is not supported + if (mode != S_IFCHR && mode != S_IFBLK && mode != S_IFREG) + { + return -ENOTSUP; + } + + // Allocate a new inode, get its properties first + s5fs_t *s5fs = VNODE_TO_S5FS(dir); + uint16_t type; + if(S_ISCHR(mode)) + { + type = S5_TYPE_CHR; + } + else if(S_ISBLK(mode)) + { + type = S5_TYPE_BLK; + } + else if(S_ISREG(mode)) + { + type = S5_TYPE_DATA; + } + else + { + panic("Invalid s5 mode!\n"); + } + + // Allocate the inode + s5_inode_t *s5_inode; + long inode_num = s5_alloc_inode(s5fs, type, &s5_inode); + // Check if the inode allocation failed + if (inode_num < 0) + { + return inode_num; + } + + // Create the new vnode + vnode_t *new_vnode = vget(dir->vn_fs, inode_num); + // Check if the vnode creation failed + if (!new_vnode) + { + s5_free_inode(s5fs, inode_num); + return -ENOMEM; + } + + // Set the devid for the new inode + s5_inode->s5_indirect_block = devid; + + // Link the new inode/vnode to the parent directory + long link = s5_link(dir, name, namelen, new_vnode); + // Check if the link operation failed + if (link < 0) + { + vput(new_vnode); + s5_free_inode(s5fs, inode_num); + return link; + } + + // Set the out pointer to the new vnode + *out = new_vnode; + return 0; } /* Search for a given entry within a directory. @@ -356,8 +496,37 @@ static long s5fs_mknod(struct vnode *dir, const char *name, size_t namelen, long s5fs_lookup(vnode_t *dir, const char *name, size_t namelen, vnode_t **ret) { - NOT_YET_IMPLEMENTED("S5FS: s5fs_lookup"); - return -1; + // NOT_YET_IMPLEMENTED("S5FS: s5fs_lookup"); + + // Find the directory entry + size_t filepos; + long ino = s5_find_dirent(VNODE_TO_S5NODE(dir), name, namelen, &filepos); + // Check if the directory entry was not found + if (ino < 0) + { + return ino; + } + + // Check if the found vnode is the directory itself + if (ino == dir->vn_vno) + { + vref(dir); + *ret = dir; + return 0; + } + + + // If not, get the found vnode + vnode_t *found_vnode = vget(dir->vn_fs, ino); + // Check if the vnode creation failed + if (!found_vnode) + { + return -ENOMEM; + } + // Increment the reference count + // vref(found_vnode); + *ret = found_vnode; + return 0; } /* Wrapper around s5_link. @@ -369,8 +538,15 @@ static long s5fs_link(vnode_t *dir, const char *name, size_t namelen, vnode_t *child) { KASSERT(S_ISDIR(dir->vn_mode) && "should be handled at the VFS level"); - NOT_YET_IMPLEMENTED("S5FS: s5fs_link"); - return -1; + //NOT_YET_IMPLEMENTED("S5FS: s5fs_link"); + + // Check if the child is a directory + if (S_ISDIR(child->vn_mode)) + { + return -EISDIR; + } + + return s5_link(VNODE_TO_S5NODE(dir), name, namelen, VNODE_TO_S5NODE(child)); } /* Remove the directory entry in dir corresponding to name and namelen. @@ -389,8 +565,29 @@ static long s5fs_unlink(vnode_t *dir, const char *name, size_t namelen) KASSERT(S_ISDIR(dir->vn_mode) && "should be handled at the VFS level"); KASSERT(!name_match(".", name, namelen)); KASSERT(!name_match("..", name, namelen)); - NOT_YET_IMPLEMENTED("S5FS: s5fs_unlink"); - return -1; + // NOT_YET_IMPLEMENTED("S5FS: s5fs_unlink"); + + // Find the directory entry + s5_node_t *s5_node = VNODE_TO_S5NODE(dir); + size_t filepos; + long ino = s5_find_dirent(s5_node, name, namelen, &filepos); + // Check if the directory entry was not found + if (ino < 0) + { + return ino; + } + + // Get the found vnode + vnode_t *child = vget_locked(dir->vn_fs, ino); + KASSERT(!S_ISDIR(child->vn_mode) && "should be handled at the VFS level"); + + // Remove the directory entry + s5_remove_dirent(s5_node, name, namelen, filepos); + // Check to see if this failed (TODO: ask in hours) + + // Release the vnode + vput_locked(&child); + return 0; } /* Change the name or location of a file. @@ -441,8 +638,98 @@ static long s5fs_rename(vnode_t *olddir, const char *oldname, size_t oldnamelen, vnode_t *newdir, const char *newname, size_t newnamelen) { - NOT_YET_IMPLEMENTED("S5FS: s5fs_rename"); - return -1; + // NOT_YET_IMPLEMENTED("S5FS: s5fs_rename"); + + // Check if the new name is too long + if (newnamelen >= NAME_LEN) + { + return -ENAMETOOLONG; + } + + // Find the old directory entry + s5_node_t *s5_node = VNODE_TO_S5NODE(olddir); + size_t filepos; + long ino = s5_find_dirent(s5_node, oldname, oldnamelen, &filepos); + // Check if the directory entry was not found + if (ino < 0) + { + return ino; + } + + // Get the found vnode + vnode_t *child = vget_locked(olddir->vn_fs, ino); + KASSERT(!S_ISDIR(child->vn_mode) && "should be handled at the VFS level"); + + // Check if the new directory is not a directory + if (!S_ISDIR(newdir->vn_mode)) + { + vput_locked(&child); + return -ENOTDIR; + } + + // Find the new directory entry + s5_node_t *new_s5_node = VNODE_TO_S5NODE(newdir); + size_t new_filepos; + long new_ino = s5_find_dirent(new_s5_node, newname, newnamelen, &new_filepos); + // Check if the directory entry is new + if (new_ino == -ENOENT) + { + // Link the new directory + long link = s5_link(new_s5_node, newname, newnamelen, VNODE_TO_S5NODE(child)); + // Check if the link operation failed + if (link < 0) + { + vput_locked(&child); + return link; + } + + // Remove the old directory entry + s5_remove_dirent(s5_node, oldname, oldnamelen, filepos); + // Check if this failed (TODO: ask in hours) + + return link; + } + + // Else, the new directory entry was found and we need to replace it + // Get the new found vnode + vnode_t *new_child = vget_locked(newdir->vn_fs, new_ino); + KASSERT(!S_ISDIR(new_child->vn_mode) && "should be handled at the VFS level"); + + // Check if the old and new vnodes are the same + if (child->vn_vno == new_child->vn_vno) + { + vput_locked(&child); + vput_locked(&new_child); + return 0; + } + + // Check if the new vnode is a directory + if (S_ISDIR(new_child->vn_mode)) + { + vput_locked(&child); + vput_locked(&new_child); + return -EISDIR; + } + + // Remove the new directory entry + s5_remove_dirent(new_s5_node, newname, newnamelen, new_filepos); + // Check if this failed (TODO: ask in hours) + // Link the new directory + long link = s5_link(new_s5_node, newname, newnamelen, VNODE_TO_S5NODE(child)); + // Check if the link operation failed + if (link < 0) + { + vput_locked(&child); + vput_locked(&new_child); + return link; + } + + // Remove the old directory entry + s5_remove_dirent(s5_node, oldname, oldnamelen, filepos); + + vput_locked(&child); + vput_locked(&new_child); + return link; } /* Create a directory. @@ -473,8 +760,66 @@ static long s5fs_mkdir(vnode_t *dir, const char *name, size_t namelen, struct vnode **out) { KASSERT(S_ISDIR((dir)->vn_mode) && "should be handled at the VFS level"); - NOT_YET_IMPLEMENTED("S5FS: s5fs_mkdir"); - return -1; + // NOT_YET_IMPLEMENTED("S5FS: s5fs_mkdir"); + + // Allocate a new inode, get its properties first + s5fs_t *s5fs = VNODE_TO_S5FS(dir); + uint16_t type = S5_TYPE_DIR; + + // Allocate the inode + s5_inode_t *s5_inode; + long inode_num = s5_alloc_inode(s5fs, type, &s5_inode); + // Check if the inode allocation failed + if (inode_num < 0) + { + return inode_num; + } + + // Create the new vnode + vnode_t *new_vnode = vget(dir->vn_fs, inode_num); + // Check if the vnode creation failed + if (!new_vnode) + { + s5_free_inode(s5fs, inode_num); + return -ENOMEM; + } + + // Create the "." entry + long link = s5_link(VNODE_TO_S5NODE(new_vnode), ".", 1, VNODE_TO_S5NODE(new_vnode)); + // Check if the link operation failed + if (link < 0) + { + vput(new_vnode); + s5_free_inode(s5fs, inode_num); + return link; + } + + // Create the ".." entry + long link2 = s5_link(VNODE_TO_S5NODE(new_vnode), "..", 2, VNODE_TO_S5NODE(dir)); + // Check if the link operation failed + if (link2 < 0) + { + s5_remove_dirent(VNODE_TO_S5NODE(new_vnode), ".", 1, 0); + vput(new_vnode); + s5_free_inode(s5fs, inode_num); + return link2; + } + + // Link the new directory to the parent + long link3 = s5_link(VNODE_TO_S5NODE(dir), name, namelen, VNODE_TO_S5NODE(new_vnode)); + // Check if the link operation failed + if (link3 < 0) + { + s5_remove_dirent(VNODE_TO_S5NODE(new_vnode), ".", 1, 0); + s5_remove_dirent(VNODE_TO_S5NODE(new_vnode), "..", 2, 0); + vput(new_vnode); + s5_free_inode(s5fs, inode_num); + return link3; + } + + // Set the out pointer to the new vnode + *out = new_vnode; + return 0; } /* Remove a directory. @@ -495,8 +840,50 @@ static long s5fs_rmdir(vnode_t *parent, const char *name, size_t namelen) KASSERT(!name_match(".", name, namelen)); KASSERT(!name_match("..", name, namelen)); KASSERT(S_ISDIR(parent->vn_mode) && "should be handled at the VFS level"); - NOT_YET_IMPLEMENTED("S5FS: s5fs_rmdir"); - return -1; + // NOT_YET_IMPLEMENTED("S5FS: s5fs_rmdir"); + + // Find the directory entry + s5_node_t *s5_node = VNODE_TO_S5NODE(parent); + size_t filepos; + long ino = s5_find_dirent(s5_node, name, namelen, &filepos); + // Check if the directory entry was not found + if (ino < 0) + { + return ino; + } + + // Get the found vnode + vnode_t *child = vget_locked(parent->vn_fs, ino); + KASSERT(S_ISDIR(child->vn_mode) && "should be handled at the VFS level"); + + // Check if this is a directory + if (!S_ISDIR(child->vn_mode)) + { + vput_locked(&child); + return -ENOTDIR; + } + + // Check if the directory is not empty + if (child->vn_len > 2 * sizeof(s5_dirent_t)) + { + vput_locked(&child); + return -ENOTEMPTY; + } + + // Remove the "." entry + s5_remove_dirent(VNODE_TO_S5NODE(child), ".", 1, 0); + // Check if this failed (TODO: ask in hours) + + // Remove the ".." entry + s5_remove_dirent(VNODE_TO_S5NODE(child), "..", 2, 0); + + // Remove the directory entry + s5_remove_dirent(s5_node, name, namelen, filepos); + // Check if this failed (TODO: ask in hours) + + // Release the vnode + vput_locked(&child); + return 0; } /* Read a directory entry. @@ -518,8 +905,23 @@ static long s5fs_rmdir(vnode_t *parent, const char *name, size_t namelen) static long s5fs_readdir(vnode_t *vnode, size_t pos, struct dirent *d) { KASSERT(S_ISDIR(vnode->vn_mode) && "should be handled at the VFS level"); - NOT_YET_IMPLEMENTED("S5FS: s5fs_readdir"); - return -1; + // NOT_YET_IMPLEMENTED("S5FS: s5fs_readdir"); + + // Read the directory entry + s5_dirent_t s5_dirent; + long bytes_read = s5_read_file(VNODE_TO_S5NODE(vnode), pos, &s5_dirent, sizeof(s5_dirent_t)); + // Check if the read operation failed + if (bytes_read < 0) + { + return bytes_read; + } + + // Initialize the dirent + d->d_ino = s5_dirent.s5d_inode; + d->d_off = pos + sizeof(s5_dirent_t); + memcpy(d->d_name, s5_dirent.s5d_name, 28); + + return bytes_read; } /* Get file status. @@ -542,8 +944,29 @@ static long s5fs_readdir(vnode_t *vnode, size_t pos, struct dirent *d) */ static long s5fs_stat(vnode_t *vnode, stat_t *ss) { - NOT_YET_IMPLEMENTED("S5FS: s5fs_stat"); - return -1; + // NOT_YET_IMPLEMENTED("S5FS: s5fs_stat"); + + // Get the inode + s5_inode_t *s5_inode = &VNODE_TO_S5NODE(vnode)->inode; + + // Initialize the stat struct + ss->st_blocks = s5_inode_blocks(s5_inode); + ss->st_mode = vnode->vn_mode; + ss->st_rdev = vnode->vn_devid; + ss->st_ino = s5_inode->s5_number; + ss->st_nlink = s5_inode->s5_linkcount; + ss->st_blksize = S5_BLOCK_SIZE; + ss->st_size = s5_inode->s5_un.s5_size; + ss->st_dev = VNODE_TO_S5FS(vnode)->s5f_bdev->bd_id; + + // Set all other fields to 0 + ss->st_uid = 0; + ss->st_gid = 0; + ss->st_atime = 0; + ss->st_mtime = 0; + ss->st_ctime = 0; + + return 0; } /** diff --git a/kernel/fs/vfs_syscall.c b/kernel/fs/vfs_syscall.c index e05fe5f..205020b 100644 --- a/kernel/fs/vfs_syscall.c +++ b/kernel/fs/vfs_syscall.c @@ -26,8 +26,58 @@ */ ssize_t do_read(int fd, void *buf, size_t len) { - NOT_YET_IMPLEMENTED("VFS: do_read"); - return -1; + // 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; } /* @@ -46,8 +96,60 @@ ssize_t do_read(int fd, void *buf, size_t len) */ ssize_t do_write(int fd, const void *buf, size_t len) { - NOT_YET_IMPLEMENTED("VFS: do_write"); - return -1; + // 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; } /* @@ -63,8 +165,26 @@ ssize_t do_write(int fd, const void *buf, size_t len) */ long do_close(int fd) { - NOT_YET_IMPLEMENTED("VFS: do_close"); - return -1; + // 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; } /* @@ -78,8 +198,33 @@ long do_close(int fd) */ long do_dup(int fd) { - NOT_YET_IMPLEMENTED("VFS: do_dup"); - return -1; + // 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; } /* @@ -94,8 +239,40 @@ long do_dup(int fd) */ long do_dup2(int ofd, int nfd) { - NOT_YET_IMPLEMENTED("VFS: do_dup2"); - return -1; + // 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; } /* @@ -117,8 +294,20 @@ long do_dup2(int ofd, int nfd) */ long do_mknod(const char *path, int mode, devid_t devid) { - NOT_YET_IMPLEMENTED("VFS: do_mknod"); - return -1; + // 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); + vput(&vnode); + return ret; } /* @@ -143,8 +332,53 @@ long do_mknod(const char *path, int mode, devid_t devid) */ long do_mkdir(const char *path) { - NOT_YET_IMPLEMENTED("VFS: do_mkdir"); - return -1; + // 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; } /* @@ -165,8 +399,75 @@ long do_mkdir(const char *path) */ long do_rmdir(const char *path) { - NOT_YET_IMPLEMENTED("VFS: do_rmdir"); - return -1; + // 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; } /* @@ -183,8 +484,62 @@ long do_rmdir(const char *path) */ long do_unlink(const char *path) { - NOT_YET_IMPLEMENTED("VFS: do_unlink"); - return -1; + // 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; } /* @@ -206,8 +561,56 @@ long do_unlink(const char *path) */ long do_link(const char *oldpath, const char *newpath) { - NOT_YET_IMPLEMENTED("VFS: do_link"); - return -1; + // 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. @@ -231,7 +634,7 @@ long do_link(const char *oldpath, const char *newpath) * 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. namev_dir oldpath --> olddir vnode + * 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`) @@ -244,8 +647,57 @@ long do_link(const char *oldpath, const char *newpath) */ long do_rename(const char *oldpath, const char *newpath) { - NOT_YET_IMPLEMENTED("VFS: do_rename"); - return -1; + // 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. @@ -262,8 +714,27 @@ long do_rename(const char *oldpath, const char *newpath) */ long do_chdir(const char *path) { - NOT_YET_IMPLEMENTED("VFS: do_chdir"); - return -1; + // 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; } /* @@ -282,8 +753,43 @@ long do_chdir(const char *path) */ ssize_t do_getdent(int fd, struct dirent *dirp) { - NOT_YET_IMPLEMENTED("VFS: do_getdent"); - return -1; + // 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); } /* @@ -301,8 +807,51 @@ ssize_t do_getdent(int fd, struct dirent *dirp) */ off_t do_lseek(int fd, off_t offset, int whence) { - NOT_YET_IMPLEMENTED("VFS: do_lseek"); - return -1; + // 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. @@ -312,8 +861,25 @@ off_t do_lseek(int fd, off_t offset, int whence) */ long do_stat(const char *path, stat_t *buf) { - NOT_YET_IMPLEMENTED("VFS: do_stat"); - return -1; + // 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__ diff --git a/kernel/fs/vnode_specials.c b/kernel/fs/vnode_specials.c index cd21549..fb6df0b 100644 --- a/kernel/fs/vnode_specials.c +++ b/kernel/fs/vnode_specials.c @@ -110,8 +110,20 @@ static long special_file_stat(vnode_t *file, stat_t *ss) static ssize_t chardev_file_read(vnode_t *file, size_t pos, void *buf, size_t count) { - NOT_YET_IMPLEMENTED("VFS: chardev_file_read"); - return 0; + // NOT_YET_IMPLEMENTED("VFS: chardev_file_read"); + + // check if the vnode represetns a chardev + if (file->vn_dev.chardev == NULL) + { + return -ENXIO; + } + + // Unlock the file and call read + vunlock(file); + ssize_t ret = file->vn_dev.chardev->cd_ops->read(file->vn_dev.chardev, pos, buf, count); + vlock(file); + + return ret; } /* @@ -125,8 +137,20 @@ static ssize_t chardev_file_read(vnode_t *file, size_t pos, void *buf, static long chardev_file_write(vnode_t *file, size_t pos, const void *buf, size_t count) { - NOT_YET_IMPLEMENTED("VFS: chardev_file_write"); - return 0; + // NOT_YET_IMPLEMENTED("VFS: chardev_file_write"); + + // check if the vnode represents a chardev + if (file->vn_dev.chardev == NULL) + { + return -ENXIO; + } + + // Unlock the file and call write + vunlock(file); + long ret = file->vn_dev.chardev->cd_ops->write(file->vn_dev.chardev, pos, buf, count); + vlock(file); + + return ret; } /* diff --git a/kernel/main/kmain.c b/kernel/main/kmain.c index 8dcb7e5..4c49b9a 100644 --- a/kernel/main/kmain.c +++ b/kernel/main/kmain.c @@ -86,6 +86,8 @@ void kmain() for (size_t i = 0; i < sizeof(init_funcs) / sizeof(init_funcs[0]); i++) init_funcs[i](); + dbg(DBG_PROC, "Starting init process\n"); + initproc_start(); panic("\nReturned to kmain()\n"); } @@ -156,7 +158,35 @@ static void *initproc_run(long arg1, void *arg2) make_devices(); #endif - NOT_YET_IMPLEMENTED("PROCS: initproc_run"); + // NOT_YET_IMPLEMENTED("PROCS: initproc_run"); + + // print all the threads in the system + // list_iterate(&curproc->p_threads, thread, kthread_t, kt_plink) + // { + // dbg(DBG_THR, "Thread: %s\n", thread->kt_proc->p_name); + // } + + // proctest_main(0, NULL); + + // dbg(DBG_PROC, "%s", "In main thread!\n"); + +#ifdef __DRIVERS__ + // driverstest_main(0, NULL); + char name[32] = {0}; + for (long i = 0; i < __NTERMS__; i++) + { + snprintf(name, sizeof(name), "kshell%ld", i); + proc_t *proc = proc_create("ksh"); + kthread_t *thread = kthread_create(proc, kshell_proc_run, i, NULL); + sched_make_runnable(thread); + } +#endif + + // see if there are any children to wait for + while (do_waitpid(-1, 0, 0) != -ECHILD) + { + // do nothing + } return NULL; } @@ -175,7 +205,21 @@ static void *initproc_run(long arg1, void *arg2) */ void initproc_start() { - NOT_YET_IMPLEMENTED("PROCS: initproc_start"); + // NOT_YET_IMPLEMENTED("PROCS: initproc_start"); + + dbg(DBG_PROC, "Setting up the initial process and preparing it to run\n"); + + proc_t *init_proc = proc_create("init"); + KASSERT(init_proc != NULL); + + kthread_t *init_thread = kthread_create(init_proc, initproc_run, 0, NULL); + KASSERT(init_thread != NULL); + + sched_make_runnable(init_thread); + + context_make_active(&curcore.kc_ctx); // start the scheduler + // context_make_active(&init_thread->kt_ctx); + // TODO: ask about how the core is linked to scheduler } void initproc_finish() diff --git a/kernel/proc/kthread.c b/kernel/proc/kthread.c index 1104b31..d066dac 100644 --- a/kernel/proc/kthread.c +++ b/kernel/proc/kthread.c @@ -68,8 +68,43 @@ void kthread_init() kthread_t *kthread_create(proc_t *proc, kthread_func_t func, long arg1, void *arg2) { - NOT_YET_IMPLEMENTED("PROCS: kthread_create"); - return NULL; + // NOT_YET_IMPLEMENTED("PROCS: kthread_create"); + dbg(DBG_THR, "ATTEMPT to create a new thread with proc name=%s, id=%d\n", proc->p_name, proc->p_pid); + + kthread_t *new_thread = slab_obj_alloc(kthread_allocator); + if (new_thread == NULL) + { + return NULL; + } + new_thread->kt_state = KT_NO_STATE; + + new_thread->kt_kstack = alloc_stack(); + if (new_thread->kt_kstack == NULL) + { + slab_obj_free(kthread_allocator, new_thread); + return NULL; + } + + new_thread->kt_proc = proc; + context_setup(&new_thread->kt_ctx, func, arg1, arg2, new_thread->kt_kstack, + DEFAULT_STACK_SIZE, new_thread->kt_proc->p_pml4); + + // give default values to rest of struct + new_thread->kt_retval = NULL; + new_thread->kt_errno = 0; + new_thread->kt_cancelled = 0; + new_thread->kt_wchan = NULL; + list_link_init(&new_thread->kt_plink); + list_link_init(&new_thread->kt_qlink); + list_init(&new_thread->kt_mutexes); + new_thread->kt_recent_core = 0; + + // put this thread on the process's thread list + list_insert_tail(&proc->p_threads, &new_thread->kt_plink); + + dbg(DBG_THR, "SUCCESFULLY created a new THREAD with proc name=%s, id=%d\n", proc->p_name, proc->p_pid); + + return new_thread; } /* @@ -124,7 +159,15 @@ void kthread_destroy(kthread_t *thr) */ void kthread_cancel(kthread_t *thr, void *retval) { - NOT_YET_IMPLEMENTED("PROCS: kthread_cancel"); + // NOT_YET_IMPLEMENTED("PROCS: kthread_cancel"); + KASSERT(thr != curthr); + + // FIXME: ask about the use of check_curthr_cancelled() in syscall_handler() + int status = (int) retval; + dbg(DBG_THR, "Cancelling thread with proc name=%s, id=%d, status=%d\n", + thr->kt_proc->p_name, thr->kt_proc->p_pid, status); + thr->kt_retval = retval; + sched_cancel(thr); } /* @@ -132,5 +175,8 @@ void kthread_cancel(kthread_t *thr, void *retval) */ void kthread_exit(void *retval) { - NOT_YET_IMPLEMENTED("PROCS: kthread_exit"); + // NOT_YET_IMPLEMENTED("PROCS: kthread_exit"); + dbg(DBG_THR, "Exiting thread with proc name=%s, id=%d, status=%d\n", + curthr->kt_proc->p_name, curthr->kt_proc->p_pid, (int) retval); + proc_thread_exiting(retval); } diff --git a/kernel/proc/proc.c b/kernel/proc/proc.c index f8453bd..91be451 100644 --- a/kernel/proc/proc.c +++ b/kernel/proc/proc.c @@ -174,8 +174,68 @@ proc_t *proc_lookup(pid_t pid) */ proc_t *proc_create(const char *name) { - NOT_YET_IMPLEMENTED("PROCS: proc_create"); - return NULL; + // NOT_YET_IMPLEMENTED("PROCS: proc_create"); + int proc_pid = _proc_getid(); + if (proc_pid == -1) + { + return NULL; + } + + proc_t *proc = (proc_t *)slab_obj_alloc(proc_allocator); + if (proc == NULL) + { + return NULL; + } + + pml4_t *pml4 = pt_create(); + if (pml4 == NULL) + { + slab_obj_free(proc_allocator, proc); + return NULL; + } + + // if successful building memory, fill the struct + proc->p_pid = proc_pid; + strncpy(proc->p_name, name, PROC_NAME_LEN); + proc->p_pml4 = pml4; + proc->p_state = PROC_RUNNING; // TODO: check if this is correct + proc->p_status = 0; + list_init(&proc->p_threads); + list_init(&proc->p_children); + list_link_init(&proc->p_child_link); + list_link_init(&proc->p_list_link); + sched_queue_init(&proc->p_wait); + + + if (proc_pid == PID_INIT) + { + // if it's init proc, set to global variable + proc_initproc = proc; + } + + proc->p_pproc = proc_initproc; + list_insert_tail(&curproc->p_children, &proc->p_child_link); + list_insert_tail(&proc_list, &proc->p_list_link); + dbg(DBG_PROC, "SUCESSFULLY created PROCESS name=%s & pid =%d\n", name, proc_pid); + +#ifdef __VFS__ + // clone and ref the files from curproc + for (int fd = 0; fd < NFILES; fd++) + { + proc->p_files[fd] = NULL; + } + if (proc->p_pid != PID_INIT) + { + proc->p_cwd = proc->p_pproc->p_cwd; + vref(proc->p_cwd); + } + else + { + proc->p_cwd = NULL; + } +#endif + + return proc; } /* @@ -197,7 +257,52 @@ proc_t *proc_create(const char *name) */ void proc_cleanup(long status) { - NOT_YET_IMPLEMENTED("PROCS: proc_cleanup"); + // NOT_YET_IMPLEMENTED("PROCS: proc_cleanup"); + dbg(DBG_PROC, "proc_cleanup called on proc with pid=%d with exit status=%d\n", curproc->p_pid, status); + +#ifdef __VFS__ + for (int fd = 0; fd < NFILES; fd++) + { + if (curproc->p_files[fd]) + { + fput(curproc->p_files + fd); + } + } + if (curproc->p_cwd) + { + vput(&curproc->p_cwd); + } +#endif + + + if (curproc->p_pid == PID_INIT) + { + // if it's init proc, shutdown weenix + initproc_finish(status); + } + + // if it's not init proc, reparent child processes to parent proc + if (curproc->p_pproc == NULL) + { + panic("parent process not found in proc_pleanup"); + } + + list_link_t *link; + list_iterate(&curproc->p_children, child, proc_t, p_child_link) + { + // remove & insert to init process + list_insert_tail(&curproc->p_pproc->p_children, &child->p_child_link); + list_remove(&child->p_child_link); + child->p_pproc = proc_initproc; + } + + // update state and status + curproc->p_state = PROC_DEAD; + // if (curthr->kt_cancelled) { + // curproc->p_status = curthr->kt_retval; + // } else { + curproc->p_status = status; + // } } /* @@ -216,7 +321,13 @@ void proc_cleanup(long status) */ void proc_thread_exiting(void *retval) { - NOT_YET_IMPLEMENTED("PROCS: proc_thread_exiting"); + // NOT_YET_IMPLEMENTED("PROCS: proc_thread_exiting"); + + proc_cleanup((long)retval); + curthr->kt_state = KT_EXITED; + + sched_broadcast_on(&curproc->p_pproc->p_wait); + sched_switch(0); } /* @@ -227,7 +338,16 @@ void proc_thread_exiting(void *retval) */ void proc_kill(proc_t *proc, long status) { - NOT_YET_IMPLEMENTED("PROCS: proc_kill"); + // NOT_YET_IMPLEMENTED("PROCS: proc_kill"); + dbg(DBG_PROC, "proc_kill called on proc with pid=%d\n", proc->p_pid); + + KASSERT(proc != curproc && "proc_kill called on curproc"); + + list_iterate(&proc->p_threads, thr, kthread_t, kt_plink) + { + dbg(DBG_PROC, "cancelling thread on proc with pid=%d with status=%ld\n", proc->p_pid, status); + kthread_cancel(thr, (void *)status); + } } /* @@ -241,7 +361,18 @@ void proc_kill(proc_t *proc, long status) */ void proc_kill_all() { - NOT_YET_IMPLEMENTED("PROCS: proc_kill_all"); + // NOT_YET_IMPLEMENTED("PROCS: proc_kill_all"); + + list_iterate(&proc_list, p, proc_t, p_list_link) + { + if (p->p_pid != curproc->p_pid && p->p_pid != PID_IDLE) + { + proc_kill(p, -1); + } + } + + // kill this proc + do_exit(0); } /* @@ -265,7 +396,9 @@ void proc_destroy(proc_t *proc) for (int fd = 0; fd < NFILES; fd++) { if (proc->p_files[fd]) + { fput(proc->p_files + fd); + } } if (proc->p_cwd) { @@ -278,7 +411,7 @@ void proc_destroy(proc_t *proc) vmmap_destroy(&proc->p_vmmap); #endif - dbg(DBG_THR, "destroying P%d\n", proc->p_pid); + dbg(DBG_PROC, "destroying P%d\n", proc->p_pid); KASSERT(proc->p_pml4); pt_destroy(proc->p_pml4); @@ -316,7 +449,77 @@ void proc_destroy(proc_t *proc) */ pid_t do_waitpid(pid_t pid, int *status, int options) { - NOT_YET_IMPLEMENTED("PROCS: do_waitpid"); + // NOT_YET_IMPLEMENTED("PROCS: do_waitpid"); + + if (pid == 0 || options != 0 || pid < -1) + { + return -ENOTSUP; + } + + dbg(DBG_PROC, "waiting for pid=%d\n", pid); + + if (pid > 0) + { + proc_t *child = proc_lookup(pid); + if (child == NULL || child->p_pproc != curproc) + { + return -ECHILD; + } + + // sleep until this specific child process exits + while (child->p_state != PROC_DEAD) + { + dbg(DBG_PROC, "waiting for specific child, calling sched_sleep_on\n"); + sched_sleep_on(&curproc->p_wait); + } + + if (status != NULL) + { + dbg(DBG_PROC, "setting status to %d\n", child->p_status); + *status = child->p_status; + } + list_remove(&child->p_child_link); + proc_destroy(child); + + dbg(DBG_PROC, "exited child pid=%d\n", child->p_pid); + return pid; + } + else if (pid == -1) + { + if (list_empty(&curproc->p_children)) + { + return -ECHILD; + } + + + while(1) { + proc_t *child; + list_iterate(&curproc->p_children, child, proc_t, p_child_link) + { + if (child->p_state == PROC_DEAD) + { + dbg(DBG_PROC, "found a dead thread with pid=%d\n", child->p_pid); + if (status != NULL) + { + dbg(DBG_PROC, "setting status to %d\n", child->p_status); + *status = child->p_status; + } + int child_pid = child->p_pid; + + + list_remove(&child->p_child_link); + proc_destroy(child); + + dbg(DBG_PROC, "exited child pid=%d\n", child->p_pid); + return child_pid; + } + } + + dbg(DBG_PROC, "waiting for any child, calling sched_sleep_on\n"); + sched_sleep_on(&curproc->p_wait); + } + } + return 0; } @@ -325,7 +528,11 @@ pid_t do_waitpid(pid_t pid, int *status, int options) */ void do_exit(long status) { - NOT_YET_IMPLEMENTED("PROCS: do_exit"); + // NOT_YET_IMPLEMENTED("PROCS: do_exit"); + + dbg(DBG_PROC, "proc exiting with pid=%d and status=%ld\n", curproc->p_pid, status); + + kthread_exit((void *)status); } /*========== diff --git a/kernel/proc/sched.c b/kernel/proc/sched.c index 3bc20c8..b422ef1 100644 --- a/kernel/proc/sched.c +++ b/kernel/proc/sched.c @@ -162,7 +162,23 @@ void sched_init(void) */ long sched_cancellable_sleep_on(ktqueue_t *queue) { - NOT_YET_IMPLEMENTED("PROCS: sched_cancellable_sleep_on"); + // NOT_YET_IMPLEMENTED("PROCS: sched_cancellable_sleep_on"); + + KASSERT(queue != NULL); + + if (curthr->kt_cancelled) + { + return -EINTR; + } + + curthr->kt_state = KT_SLEEP_CANCELLABLE; + sched_switch(queue); + + if (curthr->kt_cancelled) + { + return -EINTR; + } + return 0; } @@ -174,7 +190,15 @@ long sched_cancellable_sleep_on(ktqueue_t *queue) */ void sched_cancel(kthread_t *thr) { - NOT_YET_IMPLEMENTED("PROCS: sched_cancel"); + // NOT_YET_IMPLEMENTED("PROCS: sched_cancel"); + + thr->kt_cancelled = 1; + + if (thr->kt_state == KT_SLEEP_CANCELLABLE) + { + ktqueue_remove(thr->kt_wchan, thr); + sched_make_runnable(thr); + } } /* @@ -210,7 +234,21 @@ void sched_cancel(kthread_t *thr) */ void sched_switch(ktqueue_t *queue) { - NOT_YET_IMPLEMENTED("PROCS: sched_switch"); + // NOT_YET_IMPLEMENTED("PROCS: sched_switch"); + + // KASSERT(intr_getipl == IPL_HIGH); + intr_disable(); + int oldIPL = intr_setipl(IPL_LOW); // allow interrupts to wake up the idling core + + KASSERT(curthr->kt_state != KT_ON_CPU); + + curcore.kc_queue = queue; + last_thread_context = &curthr->kt_ctx; + + context_switch(&curthr->kt_ctx, &curcore.kc_ctx); + + intr_enable(); + intr_setipl(oldIPL); } /* @@ -236,7 +274,23 @@ void sched_yield() */ void sched_make_runnable(kthread_t *thr) { - NOT_YET_IMPLEMENTED("PROCS: sched_make_runnable"); + // NOT_YET_IMPLEMENTED("PROCS: sched_make_runnable"); + + dbg(DBG_SCHED, "Making thread with proc pid %d runnable\n in thread\n", thr->kt_proc->p_pid); + if (curthr) + { + dbg(DBG_SCHED, "I did this ^^ with thread %d\n", curthr->kt_proc->p_pid); + } else { + dbg(DBG_SCHED, "I did this ^^ with a null thread!\n"); + } + + KASSERT(thr != curthr); + KASSERT(thr->kt_state != KT_RUNNABLE); + + int oldIPL = intr_setipl(IPL_HIGH); + thr->kt_state = KT_RUNNABLE; + ktqueue_enqueue(&kt_runq, thr); + intr_setipl(oldIPL); } /* @@ -255,7 +309,12 @@ void sched_make_runnable(kthread_t *thr) */ void sched_sleep_on(ktqueue_t *q) { - NOT_YET_IMPLEMENTED("PROCS: sched_sleep_on"); + // NOT_YET_IMPLEMENTED("PROCS: sched_sleep_on"); + + int oldIPL = intr_setipl(IPL_HIGH); + curthr->kt_state = KT_SLEEP; + sched_switch(q); + intr_setipl(oldIPL); } /* @@ -271,7 +330,26 @@ void sched_sleep_on(ktqueue_t *q) */ void sched_wakeup_on(ktqueue_t *q, kthread_t **ktp) { - NOT_YET_IMPLEMENTED("PROCS: sched_wakeup_on"); + // NOT_YET_IMPLEMENTED("PROCS: sched_wakeup_on"); + + if (sched_queue_empty(q) || q == NULL) + { + if (ktp) + { + *ktp = NULL; + } + return; + } + + int oldIPL = intr_setipl(IPL_HIGH); // don't allow interrupts while modifying the queue + kthread_t *thr = ktqueue_dequeue(q); + if (ktp) + { + *ktp = thr; + } + + sched_make_runnable(thr); + intr_setipl(oldIPL); } /* @@ -279,7 +357,12 @@ void sched_wakeup_on(ktqueue_t *q, kthread_t **ktp) */ void sched_broadcast_on(ktqueue_t *q) { - NOT_YET_IMPLEMENTED("PROCS: sched_broadcast_on"); + // NOT_YET_IMPLEMENTED("PROCS: sched_broadcast_on"); + + while (!sched_queue_empty(q)) + { + sched_make_runnable(ktqueue_dequeue(q)); + } } /*=============== @@ -360,6 +443,9 @@ void core_switch() KASSERT(mapped_paddr == expected_paddr); curthr = next_thread; + + dbg(DBG_THR, "Switching to curthr thread %d\n", curthr->kt_proc->p_pid); + curthr->kt_state = KT_ON_CPU; curproc = curthr->kt_proc; context_switch(&curcore.kc_ctx, &curthr->kt_ctx); diff --git a/kernel/test/proctest.c b/kernel/test/proctest.c index 31067cd..e8b8ba8 100644 --- a/kernel/test/proctest.c +++ b/kernel/test/proctest.c @@ -21,6 +21,7 @@ void *test_func(long arg1, void *arg2) test_assert(arg1 == proc_as_arg->p_pid, "Arguments are not set up correctly"); test_assert(proc_as_arg->p_state == PROC_RUNNING, "Process state is not running"); test_assert(list_empty(&proc_as_arg->p_children), "There should be no child processes"); + dbg(DBG_TEST, "Process %s is running\n", proc_as_arg->p_name); return NULL; } @@ -36,6 +37,7 @@ void test_termination() int status; while (do_waitpid(-1, &status, 0) != -ECHILD) { + dbg(DBG_TEST, "Waiting for child process to terminate\n"); test_assert(status == 0, "Returned status not set correctly"); count++; } @@ -43,6 +45,265 @@ void test_termination() "Expected: %d, Actual: %d number of processes have been cleaned up\n", num_procs_created, count); } +/* + Tests the edge cases of do_waitpid. + 1. No child processes to wait for + 2. Waiting on a specific process id + 3. Waiting any child process (-1) +*/ +void test_do_waitpid() +{ + int status; + int ret = do_waitpid(-1, &status, 0); + test_assert(ret == -ECHILD, "No child processes to wait for"); + + proc_t *new_proc1 = proc_create("proc test 1"); + kthread_t *new_kthread1 = kthread_create(new_proc1, test_func, new_proc1->p_pid, new_proc1); + sched_make_runnable(new_kthread1); + + proc_t *new_proc2 = proc_create("proc test 2"); + kthread_t *new_kthread2 = kthread_create(new_proc2, test_func, new_proc2->p_pid, new_proc2); + sched_make_runnable(new_kthread2); + + proc_t *new_proc3 = proc_create("proc test 3"); + kthread_t *new_kthread3 = kthread_create(new_proc3, test_func, new_proc3->p_pid, new_proc3); + sched_make_runnable(new_kthread3); + + // wait for a specific process id + ret = do_waitpid(new_proc2->p_pid, &status, 0); + test_assert(ret == new_proc2->p_pid, "Waiting for a specific process id"); + + dbg(DBG_TEST, "Successfully removed proc 2 with pid: %d\n", new_proc2->p_pid); + + // wait for any child process + ret = do_waitpid(-1, &status, 0); + test_assert(ret == new_proc1->p_pid, "Waiting for any child process"); + dbg(DBG_TEST, "Successfully removed proc 1 with pid: %d\n", new_proc1->p_pid); + ret = do_waitpid(-1, &status, 0); + test_assert(ret == new_proc3->p_pid, "Waiting for any child process"); + dbg(DBG_TEST, "Successfully removed proc 3 with pid: %d\n", new_proc3->p_pid); + + ret = do_waitpid(-1, &status, 0); + test_assert(ret == -ECHILD, "No child processes to wait for"); +} + +/* + Tests proc_kill_all on a single process. +*/ +void *test_func_kill(long arg1, void *arg2) +{ + // function to be called by the process + proc_t *proc_as_arg = (proc_t *)arg2; + test_assert(arg1 == proc_as_arg->p_pid, "Arguments are not set up correctly"); + test_assert(proc_as_arg->p_state == PROC_RUNNING, "Process state is not running"); + test_assert(list_empty(&proc_as_arg->p_children), "There should be no child processes"); + dbg(DBG_TEST, "Process with pid=%d is running and calling proc_kill_all\n", proc_as_arg->p_pid); + proc_kill_all(); + return NULL; +} +void test_proc_kill_all() +{ + proc_t *new_proc_kill = proc_create("proc test kill"); + kthread_t *new_kthread_kill = kthread_create(new_proc_kill, test_func_kill, new_proc_kill->p_pid, new_proc_kill); + sched_make_runnable(new_kthread_kill); + + proc_t *new_proc1 = proc_create("proc test 1"); + kthread_t *new_kthread1 = kthread_create(new_proc1, test_func, new_proc1->p_pid, new_proc1); + sched_make_runnable(new_kthread1); + + proc_t *new_proc2 = proc_create("proc test 2"); + kthread_t *new_kthread2 = kthread_create(new_proc2, test_func, new_proc2->p_pid, new_proc2); + sched_make_runnable(new_kthread2); + + // wait on the process that calls proc_kill_all + int status; + int ret = do_waitpid(new_proc_kill->p_pid, &status, 0); + test_assert(ret == new_proc_kill->p_pid, "Waiting for a specific process id"); + dbg(DBG_TEST, "Successfully removed proc_kill with pid: %d\n", new_proc_kill->p_pid); + + // wait for all the threads to finish + int count = 0; + while (do_waitpid(-1, &status, 0) != -ECHILD) + { + dbg(DBG_TEST, "Waiting for child process to terminate\n"); + test_assert(status == 0, "Returned status not set correctly"); + count++; + } + test_assert(count == 2, + "Expected: %d, Actual: %d number of processes have been cleaned up\n", 2, count); + +} + +/* + Test to run several threads and processes concurrently. +*/ +void test_multiple() +{ + int num_procs_created = 0; + proc_t *new_proc1 = proc_create("proc test 1"); + kthread_t *new_kthread1 = kthread_create(new_proc1, test_func, new_proc1->p_pid, new_proc1); + num_procs_created++; + sched_make_runnable(new_kthread1); + + proc_t *new_proc2 = proc_create("proc test 2"); + kthread_t *new_kthread2 = kthread_create(new_proc2, test_func, new_proc2->p_pid, new_proc2); + num_procs_created++; + sched_make_runnable(new_kthread2); + + proc_t *new_proc3 = proc_create("proc test 3"); + kthread_t *new_kthread3 = kthread_create(new_proc3, test_func, new_proc3->p_pid, new_proc3); + num_procs_created++; + sched_make_runnable(new_kthread3); + + // print all of the threads in the system + list_iterate(&curproc->p_threads, thread, kthread_t, kt_plink) + { + dbg(DBG_THR, "Thread: %s\n", thread->kt_proc->p_name); + } + + // wait for all the threads to finish + int count = 0; + int status; + while (do_waitpid(-1, &status, 0) != -ECHILD) + { + dbg(DBG_TEST, "Waiting for child process to terminate\n"); + test_assert(status == 0, "Returned status not set correctly"); + count++; + } + test_assert(count == num_procs_created, + "Expected: %d, Actual: %d number of processes have been cleaned up\n", num_procs_created, count); +} + +/* + Test that creates several child processes and forces them to terminate out of order. + Then it checks to see if the processes are cleaned up correctly. +*/ +void *func_forever_yielding(long arg1, void *arg2) +{ + // function that always just yields + // goal is that it is cleaned up by a different process + while (1) + { + // have a way to stop the thread + int *to_stop = (int *)arg2; + if (*to_stop) + { + dbg(DBG_TEST, "Thread with pid=%d is stopping yield loop\n", curproc->p_pid); + do_exit(1); // return different status code + break; + } + // wait a bit + for (int i = 0; i < 1000000; i++) + { + ; + } + dbg(DBG_TEST, "Thread with pid=%d is yielding on it's parent\n", curproc->p_pid); + sched_yield(); + } + return NULL; +} +void test_out_of_order_termination() +{ + // create a yielding proc that will last for a long time + proc_t *new_proc1 = proc_create("yieling proc 1"); + int stop_func_1 = 0; + kthread_t *new_kthread1 = kthread_create(new_proc1, func_forever_yielding, new_proc1->p_pid, &stop_func_1); + sched_make_runnable(new_kthread1); + + proc_t *new_proc2 = proc_create("yielding proc 2"); + int stop_func_2 = 0; + kthread_t *new_kthread2 = kthread_create(new_proc2, func_forever_yielding, new_proc2->p_pid, &stop_func_2); + sched_make_runnable(new_kthread2); + + proc_t *new_proc3 = proc_create("proc test 3"); + kthread_t *new_kthread3 = kthread_create(new_proc3, test_func, new_proc3->p_pid, new_proc3); + sched_make_runnable(new_kthread3); + + proc_t *new_proc4 = proc_create("proc test 4"); + kthread_t *new_kthread4 = kthread_create(new_proc4, test_func, new_proc4->p_pid, new_proc4); + sched_make_runnable(new_kthread4); + + // let to first and second procs go and yield + sched_yield(); + + // the first and second proc should yield to the third proc and fourth proc + // which will return here to the init proc + test_assert(curproc->p_pid == PID_INIT, "Should have returned to init proc"); + test_assert(new_proc3->p_state == PROC_DEAD, "Third proc should be dead"); + test_assert(new_proc4->p_state == PROC_DEAD, "Fourth proc should be dead"); + + // destroy procs 3 and 4 + + // terminate the second proc + stop_func_2 = 1; + sched_yield(); + + // we should return back to the init proc + test_assert(curproc->p_pid == PID_INIT, "Should have returned to init proc"); + test_assert(new_proc2->p_state == PROC_DEAD, "Second proc should be dead"); + + // terminate the first proc + stop_func_1 = 1; + sched_yield(); + + // we should return back to the init proc + test_assert(curproc->p_pid == PID_INIT, "Should have returned to init proc"); + test_assert(new_proc1->p_state == PROC_DEAD, "First proc should be dead"); + + // clean up proc 2 using do_waitpid, with specific status 1 + int status; + int ret = do_waitpid(new_proc2->p_pid, &status, 0); + test_assert(ret == new_proc2->p_pid, "wrong specific process id"); + test_assert(status == 1, "Returned status not set correctly"); + test_assert(new_proc2->p_status == 1, "Status not set correctly"); + + // clean up the rest of the dead procs using do_waitpid + int count = 0; + while ((ret = do_waitpid(-1, &status, 0)) != -ECHILD) + { + dbg(DBG_TEST, "found child with pid=%d that needs to be cleaned up\n", ret); + if (ret == new_proc1->p_pid) { + test_assert(status == 1, "Returned status not set correctly for proc 1 with pid=%d", new_proc1->p_pid); + } else { + test_assert(status == 0, "Returned status not set correctly"); + } + count++; + } + test_assert(count == 3, + "Expected: %d, Actual: %d number of processes have been cleaned up\n", 3, count); +} + +/* + Test threads' cancellation fields. +*/ +void *did_run_func(long arg1, void *arg2) +{ + // arg2 is a boolean flag that indicated this ran + *(int *)arg2 = 1; + return NULL; +} +void test_cancellation() +{ + proc_t *new_proc1 = proc_create("proc test 1"); + int did_run = 0; + kthread_t *new_kthread1 = kthread_create(new_proc1, did_run_func, new_proc1->p_pid, (void *)&did_run); + test_assert(new_kthread1->kt_cancelled == 0, "Thread should not be cancelled"); + sched_make_runnable(new_kthread1); + + proc_kill(new_proc1, 1); + + test_assert(new_kthread1->kt_cancelled == 1, "Thread should be cancelled"); + + // wait for the thread to finish + int status; + int ret = do_waitpid(new_proc1->p_pid, &status, 0); + test_assert(ret != -ECHILD, "Should have found the process"); + test_assert(ret == new_proc1->p_pid, "Should have found the correct process"); + test_assert(status == 1, "Returned status not set correctly"); + test_assert(did_run == 0, "Thread should not have run if it was cancelled"); +} + + long proctest_main(long arg1, void *arg2) { dbg(DBG_TEST, "\nStarting Procs tests\n"); @@ -51,6 +312,16 @@ long proctest_main(long arg1, void *arg2) // Add more tests here! // We highly recommend looking at section 3.8 on the handout for help! + dbg(DBG_TEST, "\nStarting test_multiple\n"); + test_multiple(); + dbg(DBG_TEST, "\nStarting test_do_waitpid\n"); + test_do_waitpid(); + dbg(DBG_TEST, "\nStarting test_proc_kill_all\n"); + test_proc_kill_all(); + dbg(DBG_TEST, "\nStarting test_out_of_order_termination\n"); + test_out_of_order_termination(); + dbg(DBG_TEST, "\nStarting test_cancellation\n"); + test_cancellation(); test_fini(); return 0; |