Goto sanos source index

//
// vfs.c
//
// Virtual filesystem
//
// Copyright (C) 2002 Michael Ringgaard. 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 project nor the names of its contributors
//    may be used to endorse or promote products derived from this software
//    without specific prior written permission. 
// 
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 COPYRIGHT OWNER 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.
// 

#include <os/krnl.h>

struct filesystem *fslist = NULL;
struct fs *mountlist = NULL;
char pathsep = '/';

#define LFBUFSIZ 1025
#define CR '\r'
#define LF '\n'

int canonicalize(char *path, char *buffer) {
  char *p;
  char *end;
  int len;

  // Check arguments
  if (!path) return -EINVAL;

  // Remove drive letter from filename (e.g. c:)
  if (path[0] != 0 && path[1] == ':') path += 2;

  // Initialize buffer
  p = buffer;
  end = buffer + MAXPATH;

  // Add current directory to filename if relative path
  if (*path != PS1 && *path != PS2) {
    struct thread *t = self();

    // Do not add current directory if it is root directory
    len = strlen(t->curdir);
    if (len > 1) {
      memcpy(p, t->curdir, len);
      p += len;
    }
  }

  while (*path) {
    // Parse path separator
    if (*path == PS1 || *path == PS2) path++;
    if (p == end) return -ENAMETOOLONG;
    *p++ = pathsep;

    // Parse next name part in path
    len = 0;
    while (*path && *path != PS1 && *path != PS2) {
      // We do not allow control characters in filenames
      if (*path > 0 && *path < ' ') return -EINVAL;
      
      if (p == end) return -ENAMETOOLONG;
      *p++ = *path++;
      len++;
    }

    // Handle empty name parts and '.' and '..'
    if (len == 0) {
      p--;
    } else if (len == 1 && path[-1] == '.') {
      p -= 2;
    } else if (len == 2 && path[-1] == '.' && path[-2] == '.') {
      p -= 4;
      if (p < buffer) return -EINVAL;
      while (*p != pathsep) p--;
    }
  }

  // Convert empty filename to /
  if (p == buffer) *p++ = pathsep;

  // Terminate string
  if (p == end) return -ENAMETOOLONG;
  *p = 0;

  return p - buffer;
}

int fnmatch(char *fn1, int len1, char *fn2, int len2) {
  if (len1 != len2) return 0;
  while (len1--) if (*fn1++ != *fn2++) return 0;
  return 1;
}

int fslookup(char *name, int full, struct fs **mntfs, char **rest) {
  struct fs *fs;
  char *p;
  char *q;
  int n;
  int m;
  int rc;

  // Remove initial path separator from filename
  if (!name) return -EINVAL;
  p = name;
  if (*p == PS1 || *p == PS2) p++;
  n = strlen(p);

  // Find matching filesystem in mount list
  fs = mountlist;
  while (fs) {
    q = fs->mntto;
    if (*q) {
      if (*q == PS1 || *q == PS2) q++;

      if (!*q) {
        rc = check(fs->mode, fs->uid, fs->gid, S_IEXEC);
        if (rc < 0) return rc;

        if (rest) *rest = p;
        *mntfs = fs;

        return 0;
      }

      m = strlen(q);
      if (n >= m && fnmatch(p, m, q, m) && (p[m] == PS1 || p[m] == PS2 || full && p[m] == 0)) {
        rc = check(fs->mode, fs->uid, fs->gid, S_IEXEC);
        if (rc < 0) return rc;

        if (rest) *rest = p + m;
        *mntfs = fs;

        return 0;
      }
    }

    fs = fs->next;
  }

  return -ENOENT;
}

int __inline lock_fs(struct fs *fs, int fsop) {
  if (fs->ops->reentrant & fsop) return 0;
  if (fs->ops->lockfs) {
    return fs->ops->lockfs(fs);
  } else {
    return wait_for_object(&fs->exclusive, VFS_LOCK_TIMEOUT);
  }
}

void __inline unlock_fs(struct fs *fs, int fsop) {
  if (fs->ops->reentrant & fsop) return;
  if (fs->ops->unlockfs) {
    fs->ops->unlockfs(fs);
  } else {
    release_mutex(&fs->exclusive);
  }
}

static int files_proc(struct proc_file *pf, void *arg) {
  int h;
  struct object *o;
  struct file *filp;
  char perm[11];

  pprintf(pf, "handle    flags mode       uid gid type     path\n");
  pprintf(pf, "------ -------- ---------- --- --- -------- ----------------------------------\n");
  for (h = 0; h < htabsize; h++) {
    if (!HUSED(htab[h])) continue;
    o = HOBJ(htab[h]);
    if (o->type != OBJECT_FILE) continue;

    filp = (struct file *) o;

    strcpy(perm, " ---------");
    switch (filp->mode & S_IFMT) {
      case S_IFREG: perm[0] = '-'; break;
      case S_IFLNK: perm[0] = 'l'; break;
      case S_IFDIR: perm[0] = 'd'; break;
      case S_IFBLK: perm[0] = 'b'; break;
      case S_IFCHR: perm[0] = 'c'; break;
      case S_IFPKT: perm[0] = 'p'; break;
    }

    if (filp->mode & 0400) perm[1] = 'r';
    if (filp->mode & 0200) perm[2] = 'w';
    if (filp->mode & 0100) perm[3] = 'x';
    if (filp->mode & 0040) perm[4] = 'r';
    if (filp->mode & 0020) perm[5] = 'w';
    if (filp->mode & 0010) perm[6] = 'x';
    if (filp->mode & 0004) perm[7] = 'r';
    if (filp->mode & 0002) perm[8] = 'w';
    if (filp->mode & 0001) perm[9] = 'x';

    pprintf(pf, "%6d %08X %s %3d %3d %-8s %s\n", h, filp->flags, perm, filp->owner, filp->group, filp->fs->fsys->name, filp->path ? filp->path : "<no name>");
  }

  return 0;
}

int init_vfs() {
  pathsep = PS1;
  self()->curdir[0] = PS1;
  self()->curdir[1] = 0;

  if (!peb) panic("peb not initialized in vfs");
  peb->pathsep = pathsep;
  register_proc_inode("files", files_proc, NULL);
  return 0;
}

struct filesystem *register_filesystem(char *name, struct fsops *ops) {
  struct filesystem *fsys;

  fsys = (struct filesystem *) kmalloc(sizeof(struct filesystem));
  if (!fsys) return NULL;

  fsys->name = name;
  fsys->ops = ops;
  fsys->next = fslist;
  fslist = fsys;

  return fsys;
}

struct file *newfile(struct fs *fs, char *path, int flags, int mode) {
  struct thread *thread = self();
  struct file *filp;
  int umaskval = 0;
  int fmodeval = 0;

  if (peb) {
    umaskval = peb->umaskval;
    fmodeval = peb->fmodeval;
  }

  filp = (struct file *) kmalloc(sizeof(struct file));
  if (!filp) return NULL;
  init_ioobject(&filp->iob, OBJECT_FILE);
  
  if ((flags & (O_TEXT | O_BINARY)) == 0) flags |= fmodeval;

  filp->fs = fs;
  filp->flags = flags;
  filp->mode = mode & ~umaskval;
  filp->owner = thread->euid; 
  filp->group = thread->egid;
  filp->pos = 0;
  filp->data = NULL;
  filp->path = strdup(path);
  filp->chbuf = LF;

  return filp;
}

int mkfs(char *devname, char *type, char *opts) {
  struct filesystem *fsys;
  int rc;

  // Check arguments
  if (!type) return -EINVAL;

  // Find file system type
  fsys = fslist;
  while (fsys) {
    if (strcmp(type, fsys->name) == 0) break;
    fsys = fsys->next;
  }
  if (!fsys) return -EINVAL;

  // Format device for filesystem
  if (!fsys->ops->mkfs) return -ENODEV;
  rc = fsys->ops->mkfs(devname, opts);
  return rc;
}

int mount(char *type, char *mntto, char *mntfrom, char *opts, struct fs **newfs) {
  struct filesystem *fsys;
  struct fs *fs;
  struct fs *prevfs;
  int rc;
  char path[MAXPATH];
  struct stat64 st;
  
  // Check parameters
  if (!type) return -EINVAL;
  if (!mntto) return -EINVAL;

  if (*mntto) {
    rc = canonicalize(mntto, path);
    if (rc < 0) return rc;
  } else {
    *path = 0;
  }

  // Check for file system already mounted
  fs = mountlist;
  prevfs = NULL;
  while (fs) {
    if (fnmatch(path, strlen(path), fs->mntto, strlen(fs->mntto))) return -EEXIST;
    if (strlen(path) < strlen(fs->mntto)) prevfs = fs;
    fs = fs->next;
  }

  // Check that mount point exists
  if (path[0] == 0 || (path[0] == PS1 || path[0] == PS2) &&  path[1] == 0) {
    st.st_uid = 0;
    st.st_gid = 0;
    st.st_mode = 0755;
  } else {
    rc = stat(path, &st);
    if (rc < 0) return rc;
  }

  // Find file system type
  fsys = fslist;
  while (fsys) {
    if (strcmp(type, fsys->name) == 0) break;
    fsys = fsys->next;
  }
  if (!fsys) return -EINVAL;

  // Allocate new mount point for file system
  fs = (struct fs *) kmalloc(sizeof(struct fs));
  if (!fs) return -ENOMEM;
  memset(fs, 0, sizeof(struct fs));

  strcpy(fs->mntto, path);
  strcpy(fs->mntfrom, mntfrom ? mntfrom : "");

  fs->fsys = fsys;
  fs->ops = fsys->ops;

  fs->uid = st.st_uid;
  fs->gid = st.st_gid;
  fs->mode = st.st_mode;

  init_mutex(&fs->exclusive, 0);

  // Initialize filesystem on device
  if (fs->ops->mount) {
    rc = fs->ops->mount(fs, opts);
    if (rc != 0) {
      kfree(fs);
      return rc;
    }
  }

  // Insert in mount list
  if (prevfs) {
    fs->next = prevfs->next;
    fs->prev = prevfs;

    if (prevfs->next) prevfs->next->prev = fs;
    prevfs->next = fs;
  } else {
    fs->prev = NULL;
    fs->next = mountlist;
    if (mountlist) mountlist->prev = fs;
    mountlist = fs;
  }

  if (newfs) *newfs = fs;
  return 0;
}

int umount(char *name) {
  struct fs *fs;
  int rc;
  char path[MAXPATH];

  // Check parameters
  if (!name) return -EINVAL;

  rc = canonicalize(name, path);
  if (rc < 0) return rc;

  // Find mounted filesystem
  fs = mountlist;
  while (fs) {
    if (fnmatch(path, strlen(path), fs->mntto, strlen(fs->mntto))) break;
    fs = fs->next;
  }
  if (!fs) return -ENOENT;

  // Check for locks
  if (fs->locks > 0) return -EBUSY;

  // Unmount filesystem from device
  if (fs->ops->umount) {
    rc = fs->ops->umount(fs);
    if (rc != 0) return rc;
  }

  // Remove mounted filesystem
  if (fs->next) fs->next->prev = fs->prev;
  if (fs->prev) fs->prev->next = fs->next;
  if (mountlist == fs) mountlist = fs->next;
  kfree(fs);

  return 0;
}

int umount_all() {
  struct fs *fs;
  struct fs *nextfs;

  fs = mountlist;
  while (fs) {
    if (fs->ops->umount) fs->ops->umount(fs);
    nextfs = fs->next;
    kfree(fs);
    fs = nextfs;
  }

  mountlist = NULL;
  return 0;
}

static int get_fsstat(struct fs *fs, struct statfs *buf) {
  int rc;

  strcpy(buf->fstype, fs->fsys->name);
  strcpy(buf->mntto, fs->mntto);
  strcpy(buf->mntfrom, fs->mntfrom);

  if (fs->ops->statfs) {
    if (lock_fs(fs, FSOP_STATFS) < 0) return -ETIMEOUT;
    rc = fs->ops->statfs(fs, buf);
    unlock_fs(fs, FSOP_STATFS);
    if (rc < 0) return rc;
  } else {
    buf->bsize = -1;
    buf->iosize = -1;
    buf->blocks = -1;
    buf->bfree = -1;
    buf->files = -1;
    buf->ffree = -1;
  }

  return 0;
}

int getfsstat(struct statfs *buf, size_t size) {
  int count;
  struct fs *fs;
  int rc;

  count = 0;
  fs = mountlist;
  while (fs) {
    if (buf) {
      if (size < sizeof(struct statfs)) break;
      memset(buf, 0, sizeof(struct statfs));
      rc = get_fsstat(fs, buf);
      if (rc < 0) return rc;

      buf++;
      size -= sizeof(struct statfs);
    }

    count++;
    fs = fs->next;
  }

  return count;
}

int fstatfs(struct file *filp, struct statfs *buf) {
  if (!filp) return -EINVAL;
  if (!buf) return -EINVAL;

  return get_fsstat(filp->fs, buf);
}

int statfs(char *name, struct statfs *buf) {
  struct fs *fs;
  char *rest;
  int rc;
  char path[MAXPATH];

  rc = canonicalize(name, path);
  if (rc < 0) return rc;

  rc = fslookup(path, 1, &fs, &rest);
  if (rc < 0) return rc;

  return get_fsstat(fs, buf);
}

int open(char *name, int flags, int mode, struct file **retval) {
  struct fs *fs;
  struct file *filp;
  int rc;
  char *rest;
  char path[MAXPATH];

  rc = canonicalize(name, path);
  if (rc < 0) return rc;

  rc = fslookup(path, 0, &fs, &rest);
  if (rc < 0) return rc;

  filp = newfile(fs, path, flags, mode);
  if (!filp) return -EMFILE;

  if (fs->ops->open) {
    fs->locks++;
    if (lock_fs(fs, FSOP_OPEN) < 0)  {
      fs->locks--;
      kfree(filp->path);
      kfree(filp);
      return -ETIMEOUT;
    }

    rc = fs->ops->open(filp, rest);
    if (rc == 0) {
      int access;

      if (filp->flags & O_RDWR) {
        access = S_IREAD | S_IWRITE;
      } else if (filp->flags & O_WRONLY) {
        access = S_IWRITE;
      } else {
        access = S_IREAD;
      }

      rc = check(filp->mode, filp->owner, filp->group, access);
    }

    unlock_fs(fs, FSOP_OPEN);

    if (rc != 0) {
      fs->locks--;
      kfree(filp->path);
      kfree(filp);
      return rc;
    }
  }

  *retval = filp;
  return 0;
}

int close(struct file *filp) {
  int rc;

  if (!filp) return -EINVAL;
  
  if (filp->fs->ops->close) {
    if (lock_fs(filp->fs, FSOP_CLOSE) < 0) return -ETIMEOUT;
    rc = filp->fs->ops->close(filp);
    unlock_fs(filp->fs, FSOP_CLOSE);
  } else {
    rc = 0;
  }

  if (rc == 0) filp->fs->locks--;
  
  detach_ioobject(&filp->iob);
  kfree(filp->path);
  filp->path = NULL;
  filp->flags |= F_CLOSED;

  return rc;
}

int destroy(struct file *filp) {
  int rc;

  if (!filp) return -EINVAL;

  if (filp->fs->ops->destroy) {
    rc = filp->fs->ops->destroy(filp);
  } else {
    rc = 0;
  }

  kfree(filp);
  return rc;
}

int fsync(struct file *filp) {
  int rc;

  if (!filp) return -EINVAL;

  if (!filp->fs->ops->fsync) return -ENOSYS;

  if (lock_fs(filp->fs, FSOP_FSYNC) < 0) return -ETIMEOUT;
  rc = filp->fs->ops->fsync(filp);
  unlock_fs(filp->fs, FSOP_FSYNC);

  return rc;
}

int setmode(struct file *filp, int mode) {
  int oldmode;

  if (mode & ~(O_TEXT | O_BINARY | O_APPEND | O_NOINHERIT)) return -EINVAL;
  oldmode = filp->flags & (O_TEXT | O_BINARY | O_APPEND | O_NOINHERIT);
  filp->flags = (filp->flags & ~(O_TEXT | O_BINARY | O_APPEND | O_NOINHERIT)) | mode;

  return oldmode;
}

int read_translated(struct file *filp, void *data, size_t size) {
  char *buf = (char *) data;
  char *p = buf;
  char *q = buf;
  int bytes = 0;
  int rc;
  char peekch;

  if (size == 0) return 0;
  p = q = buf;

  // Read data including lookahead char if present
  if (filp->chbuf != LF) {
    buf[0] = filp->chbuf;
    filp->chbuf = LF;
    if (size == 1) return 1;

    rc = filp->fs->ops->read(filp, buf + 1, size - 1, filp->pos);
    if (rc > 0) filp->pos += rc;
    if (rc < 0) return rc;
    bytes = rc + 1;
  } else {
    rc = filp->fs->ops->read(filp, buf, size, filp->pos);
    if (rc > 0) filp->pos += rc;
    if (rc < 0) return rc;
    bytes = rc;
  }

  // Translate CR/LF to LF in the buffer
  while (p < buf + bytes)  {
    if (*p != CR) {
      *q++ = *p++;
    } else {
      // *p is CR, so must check next char for LF
      if (p < buf + bytes - 1) {
        if (*(p + 1) == LF) {
          // convert CR/LF to LF
          p += 2;
          *q++ = LF;
        } else {
          *q++ = *p++;
        }
      } else {
        // We found a CR at end of buffer. 
        // We must peek ahead to see if next char is an LF.
        p++;

        rc = filp->fs->ops->read(filp, &peekch, 1, filp->pos);
        if (rc > 0) filp->pos += rc;
        if (rc <= 0) {
          // Couldn't read ahead, store CR
          *q++ = CR;
        } else {
          // peekch now has the extra character. If char is LF store LF
          // else store CR and put char in lookahead buffer.
          if (peekch == LF) {
            *q++ = LF;
          } else {
            *q++ = CR;
            filp->chbuf = peekch;
          }
        }
      }
    }
  }

  // Return number of bytes in buffer
  return q - buf;
}

int read(struct file *filp, void *data, size_t size) {
  int rc;

  if (!filp) return -EINVAL;
  if (!data && size > 0) return -EINVAL;
  if (filp->flags & O_WRONLY) return -EACCES;
  
  if (!filp->fs->ops->read) return -ENOSYS;
  if (lock_fs(filp->fs, FSOP_READ) < 0) return -ETIMEOUT;
  if (filp->flags & O_TEXT) {
    rc = read_translated(filp, data, size);
  } else {
    rc = filp->fs->ops->read(filp, data, size, filp->pos);
    if (rc > 0) filp->pos += rc;
  }
  unlock_fs(filp->fs, FSOP_READ);
  return rc;
}

int pread(struct file *filp, void *data, size_t size, off64_t offset) {
  int rc;

  if (!filp) return -EINVAL;
  if (!data && size > 0 || offset < 0) return -EINVAL;
  if (filp->flags & O_WRONLY) return -EACCES;
  if (filp->flags & O_TEXT) return -ENXIO;
  
  if (!filp->fs->ops->read) return -ENOSYS;
  if (lock_fs(filp->fs, FSOP_READ) < 0) return -ETIMEOUT;
  rc = filp->fs->ops->read(filp, data, size, offset);
  unlock_fs(filp->fs, FSOP_READ);
  return rc;
}

static int write_translated(struct file *filp, void *data, size_t size) {
  char *buf;
  char *p, *q;
  int rc;
  int lfcnt;
  int bytes;
  char lfbuf[LFBUFSIZ];

  // Translate LF to CR/LF on output
  buf = (char *) data;
  p = buf;
  bytes = lfcnt = 0;

  while ((unsigned) (p - buf) < size) {
    // Fill the buffer, except maybe last char
    q = lfbuf;
    while ((unsigned) (q - lfbuf) < LFBUFSIZ - 1 && (unsigned) (p - buf) < size) {
      char ch = *p++;
      if (ch == LF) {
        lfcnt++;
        *q++ = CR;
      }
      *q++ = ch;
    }

    // Write the buffer and update total
    rc = filp->fs->ops->write(filp, lfbuf, q - lfbuf, filp->pos);
    if (rc > 0) filp->pos += rc;
    if (rc < 0) return rc;
    bytes += rc;
    if (rc < q - lfbuf) break;
  }

  return bytes - lfcnt;
}

int write(struct file *filp, void *data, size_t size) {
  int rc;

  if (!filp) return -EINVAL;
  if (!data && size > 0) return -EINVAL;
  if (filp->flags == O_RDONLY) return -EACCES;

  if (!filp->fs->ops->write) return -ENOSYS;
  if (lock_fs(filp->fs, FSOP_WRITE) < 0) return -ETIMEOUT;
  if (filp->flags & O_TEXT) {
    rc = write_translated(filp, data, size);
  } else {
    rc = filp->fs->ops->write(filp, data, size, filp->pos);
    if (rc > 0) filp->pos += rc;
  }
  unlock_fs(filp->fs, FSOP_WRITE);
  return rc;
}

int pwrite(struct file *filp, void *data, size_t size, off64_t offset) {
  int rc;

  if (!filp) return -EINVAL;
  if (!data && size > 0 || offset < 0) return -EINVAL;
  if (filp->flags == O_RDONLY) return -EACCES;
  if (filp->flags & O_TEXT) return -ENXIO;

  if (!filp->fs->ops->write) return -ENOSYS;
  if (lock_fs(filp->fs, FSOP_WRITE) < 0) return -ETIMEOUT;
  rc = filp->fs->ops->write(filp, data, size, offset);
  unlock_fs(filp->fs, FSOP_WRITE);
  return rc;
}

int ioctl(struct file *filp, int cmd, void *data, size_t size) {
  int rc;

  if (!filp) return -EINVAL;
  if (!data && size > 0) return -EINVAL;

  if (cmd == FIONBIO) {
    if (size != sizeof(int)) return -EINVAL;
    if (*(int *) data) {
      filp->flags |= O_NONBLOCK;
    } else {
      filp->flags &= ~O_NONBLOCK;
    }
  } else if (cmd == IOCTL_SET_TTY) {
    if (size != sizeof(int)) return -EINVAL;
    if (*(int *) data) {
      filp->flags |= F_TTY;
    } else {
      filp->flags &= ~F_TTY;
    }
    return 0;
  } else if (cmd == IOCTL_GET_TTY) {
    if (size != sizeof(int)) return -EINVAL;
    *(int *) data = filp->flags & F_TTY ? 1 : 0;
    return 0;
  }

  if (!filp->fs->ops->ioctl) return -ENOSYS;
  if (lock_fs(filp->fs, FSOP_IOCTL) < 0) return -ETIMEOUT;
  rc = filp->fs->ops->ioctl(filp, cmd, data, size);
  unlock_fs(filp->fs, FSOP_IOCTL);
  return rc;
}

int readv(struct file *filp, struct iovec *iov, int count) {
  int i;
  int rc;
  int total = 0;

  for (i = 0; i < count; i++) {
    rc = read(filp, iov[i].iov_base, iov[i].iov_len);
    if (rc < 0) return rc;

    total += rc;
    if (rc < (int) iov[i].iov_len) return total;
  }

  return total;
}

int writev(struct file *filp, struct iovec *iov, int count) {
  int i;
  int rc;
  int total = 0;

  for (i = 0; i < count; i++) {
    rc = write(filp, iov[i].iov_base, iov[i].iov_len);
    if (rc < 0) return rc;

    total += rc;
    if (rc < (int) iov[i].iov_len) return total;
  }

  return total;
}

off64_t tell(struct file *filp) {
  off64_t rc;

  if (!filp) return -EINVAL;
 
  if (!filp->fs->ops->tell) return -ENOSYS;
  if (lock_fs(filp->fs, FSOP_TELL) < 0) return -ETIMEOUT;
  rc =  filp->fs->ops->tell(filp);
  unlock_fs(filp->fs, FSOP_TELL);
  return rc;
}

off64_t lseek(struct file *filp, off64_t offset, int origin) {
  off64_t rc;

  if (!filp) return -EINVAL;

  if (!filp->fs->ops->lseek) return -ENOSYS;
  if (lock_fs(filp->fs, FSOP_LSEEK) < 0) return -ETIMEOUT;
  rc = filp->fs->ops->lseek(filp, offset, origin);
  unlock_fs(filp->fs, FSOP_LSEEK);
  return rc;
}

int ftruncate(struct file *filp, off64_t size) {
  int rc;

  if (!filp) return -EINVAL;
  if (filp->flags & O_RDONLY) return -EACCES;

  if (!filp->fs->ops->ftruncate) return -ENOSYS;
  if (lock_fs(filp->fs, FSOP_FTRUNCATE) < 0) return -ETIMEOUT;
  rc = filp->fs->ops->ftruncate(filp, size);
  unlock_fs(filp->fs, FSOP_FTRUNCATE);
  return rc;
}

int futime(struct file *filp, struct utimbuf *times) {
  int rc;

  if (!filp) return -EINVAL;
  if (!times) return -EINVAL;
  if (filp->flags & O_RDONLY) return -EACCES;

  if (!filp->fs->ops->futime) return -ENOSYS;
  if (lock_fs(filp->fs, FSOP_FUTIME) < 0) return -ETIMEOUT;
  rc = filp->fs->ops->futime(filp, times);
  unlock_fs(filp->fs, FSOP_FUTIME);
  return rc;
}

int utime(char *name, struct utimbuf *times) {
  struct fs *fs;
  char *rest;
  int rc;
  char path[MAXPATH];

  rc = canonicalize(name, path);
  if (rc < 0) return rc;

  rc = fslookup(path, 0, &fs, &rest);
  if (rc < 0) return rc;
  
  if (!times) return -EINVAL;

  if (!fs->ops->utime) return -ENOSYS;
  fs->locks++;
  if (lock_fs(fs, FSOP_UTIME) < 0) {
    fs->locks--;
    return -ETIMEOUT;
  }
  rc = fs->ops->utime(fs, rest, times);
  unlock_fs(fs, FSOP_UTIME);
  fs->locks--;
  return rc;
}

int fstat(struct file *filp, struct stat64 *buffer) {
  int rc;

  if (!filp) return -EINVAL;
 
  if (!filp->fs->ops->fstat) return -ENOSYS;
  if (lock_fs(filp->fs, FSOP_FSTAT) < 0) return -ETIMEOUT;
  rc = filp->fs->ops->fstat(filp, buffer);
  unlock_fs(filp->fs, FSOP_FSTAT);
  return rc;  
}

int stat(char *name, struct stat64 *buffer) {
  struct fs *fs;
  char *rest;
  int rc;
  char path[MAXPATH];

  rc = canonicalize(name, path);
  if (rc < 0) return rc;

  rc = fslookup(path, 0, &fs, &rest);
  if (rc < 0) return rc;

  if (!fs->ops->stat) return -ENOSYS;
  fs->locks++;
  if (lock_fs(fs, FSOP_STAT) < 0) {
    fs->locks--;
    return -ETIMEOUT;
  }
  rc = fs->ops->stat(fs, rest, buffer);
  unlock_fs(fs, FSOP_STAT);
  fs->locks--;
  return rc;
}

int access(char *name, int mode) {
  struct fs *fs;
  char *rest;
  int rc;
  char path[MAXPATH];

  rc = canonicalize(name, path);
  if (rc < 0) return rc;

  rc = fslookup(path, 0, &fs, &rest);
  if (rc < 0) return rc;

  if (!fs->ops->access) {
    struct thread *thread = self();
    struct stat64 buf;

    rc = stat(name, &buf);
    if (rc < 0) return rc;
    if (mode == 0) return 0;

    if (thread->euid == 0) {
      if (mode == X_OK) {
        return buf.st_mode & 0111 ? 0 : -EACCES;
      } else {
        return 0;
      }
    }

    if (thread->euid == buf.st_uid) {
      mode <<= 6;
    } else if (thread->egid == buf.st_gid) {
      mode <<= 3;
    }

    if ((mode && buf.st_mode) == 0) return -EACCES;
    return 0;
  }

  fs->locks++;
  if (lock_fs(fs, FSOP_ACCESS) < 0) {
    fs->locks--;
    return -ETIMEOUT;
  }
  rc = fs->ops->access(fs, rest, mode);
  unlock_fs(fs, FSOP_ACCESS);
  fs->locks--;
  return rc;
}

int fchmod(struct file *filp, int mode) {
  int rc;

  if (!filp) return -EINVAL;
  if (filp->flags & O_RDONLY) return -EACCES;
 
  if (!filp->fs->ops->fchmod) return -ENOSYS;
  if (lock_fs(filp->fs, FSOP_FCHMOD) < 0) return -ETIMEOUT;
  rc = filp->fs->ops->fchmod(filp, mode);
  unlock_fs(filp->fs, FSOP_FCHMOD);
  return rc;
}

int chmod(char *name, int mode) {
  struct fs *fs;
  char *rest;
  int rc;
  char path[MAXPATH];

  rc = canonicalize(name, path);
  if (rc < 0) return rc;

  rc = fslookup(path, 0, &fs, &rest);
  if (rc < 0) return rc;

  if (!fs->ops->chmod) return -ENOSYS;
  fs->locks++;
  if (lock_fs(fs, FSOP_CHMOD) < 0) {
    fs->locks--;
    return -ETIMEOUT;
  }
  rc = fs->ops->chmod(fs, rest, mode);
  unlock_fs(fs, FSOP_CHMOD);
  fs->locks--;
  return rc;
}

int fchown(struct file *filp, int owner, int group) {
  int rc;

  if (!filp) return -EINVAL;
  if (filp->flags & O_RDONLY) return -EACCES;

  if (!filp->fs->ops->fchown) return -ENOSYS;
  if (lock_fs(filp->fs, FSOP_FCHOWN) < 0) return -ETIMEOUT;
  rc = filp->fs->ops->fchown(filp, owner, group);
  unlock_fs(filp->fs, FSOP_FCHOWN);
  return rc;
}

int chown(char *name, int owner, int group) {
  struct fs *fs;
  char *rest;
  int rc;
  char path[MAXPATH];

  rc = canonicalize(name, path);
  if (rc < 0) return rc;

  rc = fslookup(path, 0, &fs, &rest);
  if (rc < 0) return rc;

  if (!fs->ops->chmod) return -ENOSYS;
  fs->locks++;
  if (lock_fs(fs, FSOP_CHOWN) < 0) {
    fs->locks--;
    return -ETIMEOUT;
  }
  rc = fs->ops->chown(fs, rest, owner, group);
  unlock_fs(fs, FSOP_CHOWN);
  fs->locks--;
  return rc;
}

int chdir(char *name) {
  struct fs *fs;
  char *rest;
  int rc;
  char path[MAXPATH];
  char newdir[MAXPATH];
  struct stat64 buffer;

  rc = canonicalize(name, path);
  if (rc < 0) return rc;
  strcpy(newdir, path);

  rc = fslookup(path, 0, &fs, &rest);
  if (rc < 0) return rc;
  
  if (fs->ops->stat) {
    fs->locks++;
    if (lock_fs(fs, FSOP_STAT) < 0) {
      fs->locks--;
      return -ETIMEOUT;
    }
    rc = fs->ops->stat(fs, rest, &buffer);
    unlock_fs(fs, FSOP_STAT);
    fs->locks--;

    if (rc < 0) return rc;
    if ((buffer.st_mode & S_IFMT) != S_IFDIR) return -ENOTDIR;
  }

  strcpy(self()->curdir, newdir);

  return 0;
}

int getcwd(char *buf, size_t size) {
  if (!buf || size == 0) return -EINVAL;
  if (size <= strlen(self()->curdir)) return -ERANGE;
  strcpy(buf, self()->curdir);

  return 0;
}

int mkdir(char *name, int mode) {
  struct fs *fs;
  char *rest;
  int rc;
  char path[MAXPATH];

  rc = canonicalize(name, path);
  if (rc < 0) return rc;

  rc = fslookup(path, 0, &fs, &rest);
  if (rc < 0) return rc;

  if (!fs->ops->mkdir) return -ENOSYS;
  fs->locks++;
  if (lock_fs(fs, FSOP_MKDIR) < 0) {
    fs->locks--;
    return -ETIMEOUT;
  }
  rc = fs->ops->mkdir(fs, rest, mode & ~(peb ? peb->umaskval : 0));
  unlock_fs(fs, FSOP_MKDIR);
  fs->locks--;
  return rc;
}

int rmdir(char *name) {
  struct fs *fs;
  char *rest;
  int rc;
  char path[MAXPATH];

  rc = canonicalize(name, path);
  if (rc < 0) return rc;

  rc = fslookup(path, 0, &fs, &rest);
  if (rc < 0) return rc;
  
  if (!fs->ops->rmdir) return -ENOSYS;
  fs->locks++;
  if (lock_fs(fs, FSOP_RMDIR) < 0) {
    fs->locks--;
    return -ETIMEOUT;
  }
  rc = fs->ops->rmdir(fs, rest);
  unlock_fs(fs, FSOP_RMDIR);
  fs->locks--;
  return rc;
}

int rename(char *oldname, char *newname) {
  struct fs *oldfs;
  struct fs *newfs;
  char *oldrest;
  char *newrest;
  int rc;
  char oldpath[MAXPATH];
  char newpath[MAXPATH];

  rc = canonicalize(oldname, oldpath);
  if (rc < 0) return rc;

  rc = fslookup(oldpath, 0, &oldfs, &oldrest);
  if (rc < 0) return rc;

  rc = canonicalize(newname, newpath);
  if (rc < 0) return rc;

  rc = fslookup(newpath, 0, &newfs, &newrest);
  if (rc < 0) return rc;

  if (oldfs != newfs) return -EXDEV;

  if (!oldfs->ops->rename) return -ENOSYS;
  oldfs->locks++;
  if (lock_fs(oldfs, FSOP_RENAME) < 0) {
    oldfs->locks--;
    return -ETIMEOUT;
  }
  rc = oldfs->ops->rename(oldfs, oldrest, newrest);
  unlock_fs(oldfs, FSOP_RENAME);
  oldfs->locks--;
  return rc;
}

int link(char *oldname, char *newname) {
  struct fs *oldfs;
  struct fs *newfs;
  char *oldrest;
  char *newrest;
  int rc;
  char oldpath[MAXPATH];
  char newpath[MAXPATH];

  rc = canonicalize(oldname, oldpath);
  if (rc < 0) return rc;

  rc = fslookup(oldpath, 0, &oldfs, &oldrest);
  if (rc < 0) return rc;

  rc = canonicalize(newname, newpath);
  if (rc < 0) return rc;

  rc = fslookup(newpath, 0, &newfs, &newrest);
  if (rc < 0) return rc;

  if (oldfs != newfs) return -EXDEV;

  if (!oldfs->ops->link) return -ENOSYS;
  oldfs->locks++;
  if (lock_fs(oldfs, FSOP_LINK) < 0) {
    oldfs->locks--;
    return -ETIMEOUT;
  }
  rc = oldfs->ops->link(oldfs, oldrest, newrest);
  unlock_fs(oldfs, FSOP_LINK);
  oldfs->locks--;
  return rc;
}

int unlink(char *name) {
  struct fs *fs;
  char *rest;
  int rc;
  char path[MAXPATH];

  rc = canonicalize(name, path);
  if (rc < 0) return rc;

  rc = fslookup(path, 0, &fs, &rest);
  if (rc < 0) return rc;

  if (!fs->ops->unlink) return -ENOSYS;
  fs->locks++;
  if (lock_fs(fs, FSOP_UNLINK) < 0) {
    fs->locks--;
    return -ETIMEOUT;
  }
  rc = fs->ops->unlink(fs, rest);
  fs->locks--;
  unlock_fs(fs, FSOP_UNLINK);
  return rc;
}

int opendir(char *name, struct file **retval) {
  struct fs *fs;
  struct file *filp;
  int rc;
  char *rest;
  char path[MAXPATH];

  rc = canonicalize(name, path);
  if (rc < 0) return rc;

  rc = fslookup(path, 1, &fs, &rest);
  if (rc < 0) return rc;

  if (!fs->ops->opendir) return -ENOSYS;

  filp = (struct file *) kmalloc(sizeof(struct file));
  if (!filp) return -ENOMEM;
  init_ioobject(&filp->iob, OBJECT_FILE);
  
  filp->fs = fs;
  filp->flags = O_RDONLY | F_DIR;
  filp->pos = 0;
  filp->data = NULL;
  filp->path = strdup(path);

  fs->locks++;
  if (lock_fs(fs, FSOP_OPENDIR) < 0) {
    fs->locks--;
    kfree(filp->path);
    kfree(filp);
    return -ETIMEOUT;
  }
  rc = fs->ops->opendir(filp, rest);
  unlock_fs(fs, FSOP_OPENDIR);
  if (rc != 0) {
    fs->locks--;
    kfree(filp->path);
    kfree(filp);
    return rc;
  }

  *retval = filp;
  return 0;
}

int readdir(struct file *filp, struct direntry *dirp, int count) {
  int rc;

  if (!filp) return -EINVAL;
  if (!dirp) return -EINVAL;
  if (!(filp->flags & F_DIR)) return -EINVAL;
  
  if (!filp->fs->ops->readdir) return -ENOSYS;
  if (lock_fs(filp->fs, FSOP_READDIR) < 0) return -ETIMEOUT;
  rc = filp->fs->ops->readdir(filp, dirp, count);
  unlock_fs(filp->fs, FSOP_READDIR);
  return rc;
}