/* * Copyright (c) 2005-2007, Kohsuke Ohtani * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the author nor the names of any co-contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * syscalls.c - everything in this file is a routine implementing * a VFS system call. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "vfs.h" int sys_open(char *path, int flags, mode_t mode, file_t *file) { vnode_t vp, dvp; file_t fp; char *filename; int err; dprintf("sys_open: path=%s flags=%x mode=%x\n", path, flags, mode); flags = FFLAGS(flags); if (flags & O_CREAT) { err = namei(path, &vp); if (err == ENOENT) { /* Create new file. */ if ((err = lookup(path, &dvp, &filename)) != 0) return err; if (dvp->v_mount->m_flags & MNT_RDONLY) { vput(dvp); return EROFS; } mode &= ~S_IFMT; mode |= S_IFREG; err = VOP_CREATE(dvp, filename, mode); vput(dvp); if (err) return err; if ((err = namei(path, &vp)) != 0) return err; flags &= ~O_TRUNC; } else if (err) { return err; } else { /* File already exits */ if (flags & O_EXCL) { vput(vp); return EEXIST; } flags &= ~O_CREAT; } } else { /* Open */ if ((err = namei(path, &vp)) != 0) return err; } if ((flags & O_CREAT) == 0) { if (flags & FWRITE || flags & O_TRUNC) { if (vp->v_mount->m_flags & MNT_RDONLY) { vput(vp); return EROFS; } if (vp->v_type == VDIR) { /* Openning directory with writable. */ vput(vp); return EISDIR; } } } if (flags & O_TRUNC) { if (!(flags & FWRITE) || (vp->v_type != VREG)) { vput(vp); return EINVAL; } } /* Process truncate request */ if (flags & O_TRUNC) { if ((err = VOP_TRUNCATE(vp)) != 0) { vput(vp); return err; } } /* Setup file structure */ if (!(fp = malloc(sizeof(struct file)))) { vput(vp); return ENOMEM; } /* Request to file system */ if ((err = VOP_OPEN(vp, mode)) != 0) { free(fp); vput(vp); return err; } memset(fp, 0, sizeof(struct file)); fp->f_vnode = vp; fp->f_flags = flags; fp->f_offset = 0; fp->f_count = 1; *file = fp; vn_unlock(vp); return 0; } int sys_close(file_t fp) { vnode_t vp; int err; dprintf("sys_close: fp=%x\n", (u_int)fp); vp = fp->f_vnode; if (--fp->f_count > 0) { vrele(vp); return 0; } vn_lock(vp); if ((err = VOP_CLOSE(vp, fp)) != 0) { vn_unlock(vp); return err; } vput(vp); free(fp); return 0; } int sys_read(file_t fp, void *buf, size_t size, size_t *count) { vnode_t vp; int err; dprintf("sys_read: fp=%x buf=%x size=%d\n", (u_int)fp, (u_int)buf, size); if ((fp->f_flags & FREAD) == 0) return EPERM; if (size == 0) { *count = 0; return 0; } vp = fp->f_vnode; vn_lock(vp); err = VOP_READ(vp, fp, buf, size, count); vn_unlock(vp); return err; } int sys_write(file_t fp, void *buf, size_t size, size_t *count) { vnode_t vp; int err; dprintf("sys_write: fp=%x buf=%x size=%d\n", (u_int)fp, (u_int)buf, size); if (size == 0) { *count = 0; return 0; } vp = fp->f_vnode; vn_lock(vp); err = VOP_WRITE(vp, fp, buf, size, count); vn_unlock(vp); return err; } int sys_lseek(file_t fp, off_t off, int type, off_t *origin) { vnode_t vp; dprintf("sys_seek: fp=%x off=%d type=%d\n", (u_int)fp, (u_int)off, type); vp = fp->f_vnode; vn_lock(vp); switch (type) { case SEEK_SET: if (off < 0) off = 0; if (off > (off_t)vp->v_size) off = vp->v_size; break; case SEEK_CUR: if (fp->f_offset + off > (off_t)vp->v_size) off = vp->v_size; else if (fp->f_offset + off < 0) off = 0; else off = fp->f_offset + off; break; case SEEK_END: if (off > 0) off = vp->v_size; else if ((int)vp->v_size + off < 0) off = 0; else off = vp->v_size + off; break; default: vn_unlock(vp); return EINVAL; } /* Request to check the file offset */ if (VOP_SEEK(vp, fp, fp->f_offset, off) != 0) { vn_unlock(vp); return EINVAL; } *origin = off; fp->f_offset = off; vn_unlock(vp); return 0; } int sys_ioctl(file_t fp, int request, char *buf) { vnode_t vp; int err; dprintf("sys_ioctl: fp=%x request=%x\n", fp, request); if ((fp->f_flags & (FREAD | FWRITE)) == 0) return EBADF; vp = fp->f_vnode; vn_lock(vp); err = VOP_IOCTL(vp, fp, request, (u_long)buf); vn_unlock(vp); return err; } int sys_fsync(file_t fp) { vnode_t vp; int err; dprintf("sys_fsync: fp=%x\n", fp); if ((fp->f_flags & FREAD) == 0) return EBADF; vp = fp->f_vnode; vn_lock(vp); err = VOP_FSYNC(vp, fp); vn_unlock(vp); return err; } int sys_fstat(file_t fp, struct stat *st) { vnode_t vp; mode_t mode; dprintf("sys_fstat: fp=%x\n", fp); memset(st, 0, sizeof(struct stat)); vp = fp->f_vnode; vn_lock(vp); st->st_ino = (ino_t)&vp; st->st_size = vp->v_size; mode = vp->v_mode; switch (vp->v_type) { case VREG: mode |= S_IFREG; break; case VDIR: mode |= S_IFDIR; break; case VBLK: mode |= S_IFBLK; break; case VCHR: mode |= S_IFCHR; break; case VLNK: mode |= S_IFLNK; break; case VSOCK: mode |= S_IFSOCK; break; case VFIFO: mode |= S_IFIFO; break; default: return EBADF; }; st->st_mode = mode; st->st_blksize = BSIZE; st->st_blocks = vp->v_size / S_BLKSIZE; st->st_uid = 0; st->st_gid = 0; if (vp->v_type == VCHR || vp->v_type == VBLK) st->st_rdev = (dev_t)vp->v_data; vn_unlock(vp); return 0; } /* * Return 0 if directory is empty */ static int check_dir_empty(char *path) { int err; file_t fp; struct dirent dir; if ((err = sys_opendir(path, &fp)) != 0) return err; do { err = sys_readdir(fp, &dir); if (err) break; } while (!strcmp(dir.d_name, ".") || !strcmp(dir.d_name, "..")); sys_closedir(fp); if (err == ENOENT) return 0; else if (err == 0) return EEXIST; return err; } int sys_opendir(char *path, file_t *file) { vnode_t dvp; file_t fp; int err; dprintf("sys_opendir: path=%s\n", path); if ((err = sys_open(path, O_RDONLY, 0, &fp)) != 0) return err; dvp = fp->f_vnode; vn_lock(dvp); if (dvp->v_type != VDIR) { vn_unlock(dvp); sys_close(fp); return ENOTDIR; } vn_unlock(dvp); *file = fp; return 0; } int sys_closedir(file_t fp) { vnode_t dvp; int err; dprintf("sys_closedir: fp=%x\n", fp); dvp = fp->f_vnode; vn_lock(dvp); if (dvp->v_type != VDIR) { vn_unlock(dvp); return EBADF; } vn_unlock(dvp); err = sys_close(fp); return err; } int sys_readdir(file_t fp, struct dirent *dir) { vnode_t dvp; int err; dprintf("sys_readdir: fp=%x\n", fp); dvp = fp->f_vnode; vn_lock(dvp); if (dvp->v_type != VDIR) { vn_unlock(dvp); return ENOTDIR; } err = VOP_READDIR(dvp, fp, dir); vn_unlock(dvp); return err; } int sys_rewinddir(file_t fp) { vnode_t dvp; dvp = fp->f_vnode; vn_lock(dvp); if (dvp->v_type != VDIR) { vn_unlock(dvp); return EINVAL; } fp->f_offset = 0; vn_unlock(dvp); return 0; } int sys_seekdir(file_t fp, long loc) { vnode_t dvp; dvp = fp->f_vnode; vn_lock(dvp); if (dvp->v_type != VDIR) { vn_unlock(dvp); return EINVAL; } fp->f_offset = (off_t)loc; vn_unlock(dvp); return 0; } int sys_telldir(file_t fp, long *loc) { vnode_t dvp; dvp = fp->f_vnode; vn_lock(dvp); if (dvp->v_type != VDIR) { vn_unlock(dvp); return EINVAL; } *loc = (long)fp->f_offset; vn_unlock(dvp); return 0; } int sys_mkdir(char *path, mode_t mode) { char *name; vnode_t vp, dvp; int err; dprintf("sys_mkdir: path=%s mode=%d\n", path, mode); if ((err = namei(path, &vp)) == 0) { /* File already exists */ vput(vp); return EEXIST; } /* Notice: vp is invalid here! */ if ((err = lookup(path, &dvp, &name)) != 0) { /* Directory already exists */ return err; } if (dvp->v_mount->m_flags & MNT_RDONLY) { err = EROFS; goto out; } mode &= ~S_IFMT; mode |= S_IFDIR; err = VOP_MKDIR(dvp, name, mode); out: vput(dvp); return err; } int sys_rmdir(char *path) { vnode_t vp, dvp; int err; char *name; dprintf("sys_rmdir: path=%s\n", path); if ((err = check_dir_empty(path)) != 0) return err; if ((err = namei(path, &vp)) != 0) return err; if (vp->v_mount->m_flags & MNT_RDONLY) { err = EROFS; goto out; } if (vp->v_type != VDIR) { err = ENOTDIR; goto out; } if (vp->v_flags & VROOT || vcount(vp) >= 2) { err = EBUSY; goto out; } if ((err = lookup(path, &dvp, &name)) != 0) goto out; err = VOP_RMDIR(dvp, vp, name); vn_unlock(vp); vgone(vp); vput(dvp); return err; out: vput(vp); return err; } int sys_mknod(char *path, mode_t mode) { char *name; vnode_t vp, dvp; int err; dprintf("sys_mknod: path=%s mode=%d\n", path, mode); if ((err = namei(path, &vp)) == 0) { vput(vp); return EEXIST; } if ((err = lookup(path, &dvp, &name)) != 0) return err; if (S_ISDIR(mode)) err = VOP_MKDIR(dvp, name, mode); else err = VOP_CREATE(dvp, name, mode); vput(dvp); return err; } int sys_rename(char *src, char *dest) { vnode_t vp1, vp2 = 0, dvp1, dvp2; char *sname, *dname; int err; size_t len; char root[] = "/"; dprintf("sys_rename: src=%s dest=%s\n", src, dest); if ((err = namei(src, &vp1)) != 0) return err; if (vp1->v_mount->m_flags & MNT_RDONLY) { err = EROFS; goto err1; } /* If source and dest are the same, do nothing */ if (!strncmp(src, dest, PATH_MAX)) goto err1; /* Check if target is directory of source */ len = strlen(dest); if (!strncmp(src, dest, len)) { err = EINVAL; goto err1; } /* Is the source busy ? */ if (vcount(vp1) >= 2) { err = EBUSY; goto err1; } /* Check type of source & target */ err = namei(dest, &vp2); if (err == 0) { /* target exists */ if (vp1->v_type == VDIR && vp2->v_type != VDIR) { err = ENOTDIR; goto err2; } else if (vp1->v_type != VDIR && vp2->v_type == VDIR) { err = EISDIR; goto err2; } if (vp2->v_type == VDIR && check_dir_empty(dest)) { err = EEXIST; goto err2; } if (vcount(vp2) >= 2) { err = EBUSY; goto err2; } } dname = strrchr(dest, '/'); if (dname == NULL) { err = ENOTDIR; goto err2; } if (dname == dest) dest = root; *dname = 0; dname++; if ((err = lookup(src, &dvp1, &sname)) != 0) goto err2; if ((err = namei(dest, &dvp2)) != 0) goto err3; /* The source and dest must be same file system */ if (dvp1->v_mount != dvp2->v_mount) { err = EXDEV; goto err4; } err = VOP_RENAME(dvp1, vp1, sname, dvp2, vp2, dname); err4: vput(dvp2); err3: vput(dvp1); err2: if (vp2) vput(vp2); err1: vput(vp1); return err; } int sys_unlink(char *path) { char *name; vnode_t vp, dvp; int err; dprintf("sys_unlink: path=%s\n", path); if ((err = namei(path, &vp)) != 0) return err; if (vp->v_mount->m_flags & MNT_RDONLY) { err = EROFS; goto out; } if (vp->v_type == VDIR) { err = EPERM; goto out; } if (vp->v_flags & VROOT || vcount(vp) >= 2) { err = EBUSY; goto out; } if ((err = lookup(path, &dvp, &name)) != 0) goto out; err = VOP_REMOVE(dvp, vp, name); vn_unlock(vp); vgone(vp); vput(dvp); return 0; out: vput(vp); return err; } int sys_access(char *path, int mode) { vnode_t vp; int err; dprintf("sys_access: path=%s\n", path); if ((err = namei(path, &vp)) != 0) return err; err = EACCES; if ((mode & X_OK) && (vp->v_mode & 0111) == 0) goto out; if ((mode & W_OK) && (vp->v_mode & 0222) == 0) goto out; if ((mode & R_OK) && (vp->v_mode & 0444) == 0) goto out; err = 0; out: vput(vp); return err; }