Goto sanos source index

//
// smbfs.c
//
// SMB 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>
#include "smb.h"

int smb_lockfs(struct fs *fs) {
  struct smb_share *share = (struct smb_share *) fs->data;
  return wait_for_object(&share->server->lock, VFS_LOCK_TIMEOUT);
}

void smb_unlockfs(struct fs *fs) {
  struct smb_share *share = (struct smb_share *) fs->data;
  release_mutex(&share->server->lock);
}

int smb_mkfs(char *devname, char *opts) {
  return -ENOSYS;
}

int smb_mount(struct fs *fs, char *opts) {
  struct ip_addr ipaddr;
  char username[SMB_NAMELEN];
  char password[SMB_NAMELEN];
  char domain[SMB_NAMELEN];
  struct smb_share *share;
  int rc;
  unsigned short port;

  // Get options
  ipaddr.addr = get_num_option(opts, "addr", IP_ADDR_ANY);
  if (ipaddr.addr == IP_ADDR_ANY) return -EINVAL;
  get_option(opts, "user", username, sizeof(username), "sanos");
  get_option(opts, "domain", domain, sizeof(domain), "");
  get_option(opts, "password", password, sizeof(password), "");
  port = get_num_option(opts, "port", 445);

  // Check arguments
  if (!fs->mntfrom) return -EINVAL;
  if (strlen(fs->mntfrom) + 1 > SMB_NAMELEN) return -EINVAL;

  if (strlen(password) + 1 + 
      strlen(username) + 1 + 
      strlen(domain) + 1 +
      strlen(SMB_CLIENT_OS) + 1 +
      strlen(SMB_CLIENT_LANMAN) + 1 > SMB_NAMELEN) {
    return -EBUF;
  }

  if (strlen(password) + 1 + 
      strlen(fs->mntfrom) + 1 + 
      strlen(SMB_SERVICE_DISK) + 1 > SMB_NAMELEN) {
    return -EBUF;
  }

  // Allocate share block
  share = (struct smb_share *) kmalloc(sizeof(struct smb_share));
  if (!share) return -ENOMEM;
  memset(share, 0, sizeof(struct smb_share));
  strcpy(share->sharename, fs->mntfrom);

  // Get connection to server
  rc = smb_get_connection(share, &ipaddr, port, domain, username, password);
  if (rc < 0) {
    kfree(share);
    return rc;
  }

  // Connect to share
  rc = smb_connect_tree(share);
  if (rc == -ECONN || rc == -ERST) rc = smb_reconnect(share);
  if (rc < 0) {
    smb_release_connection(share);
    kfree(share);
    return rc;
  }

  fs->data = share;

  return 0;
}

int smb_umount(struct fs *fs) {
  struct smb_share *share = (struct smb_share *) fs->data;

  // Disconnect from share
  smb_disconnect_tree(share);
  
  // Release server connection
  smb_release_connection(share);

  // Deallocate share block
  kfree(share);

  return 0;
}

int smb_statfs(struct fs *fs, struct statfs *buf) {
  struct smb_share *share = (struct smb_share *) fs->data;
  struct smb_fsinfo_request req;
  struct smb_info_allocation rsp;
  int rc;
  int rsplen;

  req.infolevel = SMB_INFO_ALLOCATION;
  rsplen = sizeof(rsp);
  rc = smb_trans(share, TRANS2_QUERY_FS_INFORMATION, &req, sizeof(req), NULL, 0, NULL, NULL, &rsp, &rsplen);
  if (rc < 0) return rc;

  buf->bsize = rsp.sector_per_unit * rsp.sectorsize;
  buf->iosize = rsp.sector_per_unit * rsp.sectorsize;
  buf->blocks = rsp.units_total;
  buf->bfree = rsp.units_avail;
  buf->files = -1;
  buf->ffree = -1;
  buf->cachesize = 0;

  return 0;
}

int smb_open(struct file *filp, char *name) {
  struct smb_share *share = (struct smb_share *) filp->fs->data;
  struct smb *smb;
  struct smb_file *file;
  unsigned long mode;
  unsigned long access;
  unsigned long sharing;
  unsigned long attrs;
  int rc;

  // Convert filename
  rc = smb_convert_filename(name);
  if (rc < 0) return rc;

  // Determine open mode
  switch (filp->flags & (O_CREAT | O_EXCL | O_TRUNC)) {
    case 0:
    case O_EXCL:
      // Open existing file
      mode = SMB_OPEN_EXISTING;
      break;

    case O_CREAT:
      // Open file, create new file if it does not exists
      mode = SMB_OPEN_ALWAYS;
      break;

    case O_CREAT | O_EXCL:
    case O_CREAT | O_TRUNC | O_EXCL:
      // Create new file, fail if it exists
      mode = SMB_CREATE_NEW;
      filp->flags |= F_MODIFIED;
      break;

    case O_TRUNC:
    case O_TRUNC | O_EXCL:
      // Open and truncate existing file
      mode = SMB_TRUNCATE_EXISTING;
      break;

    case O_CREAT | O_TRUNC:
      // Create new file, trunc existing file if it exists
      mode = SMB_CREATE_ALWAYS;
      break;

    default:
      return -EINVAL;
  }

  // Determine file access
  if (filp->flags & O_RDWR) {
    access = SMB_ACCESS_GENERIC_READ | SMB_ACCESS_GENERIC_WRITE;
  } else if (filp->flags & O_WRONLY) {
    access = SMB_ACCESS_GENERIC_WRITE;
  } else if (filp->flags & (O_CREAT | O_TRUNC)) {
    access = SMB_ACCESS_GENERIC_READ | SMB_ACCESS_GENERIC_WRITE;
  } else {
    access = SMB_ACCESS_GENERIC_READ;
  }

  // Determine sharing access
  switch (SH_FLAGS(filp->flags)) {
    case SH_DENYRW:
      sharing = 0;
      break;

    case SH_DENYWR:
      sharing = SMB_FILE_SHARE_READ;
      break;

    case SH_DENYRD:
      sharing = SMB_FILE_SHARE_WRITE;
      break;

    case SH_DENYNO:
    case SH_COMPAT:
      sharing = SMB_FILE_SHARE_READ | SMB_FILE_SHARE_WRITE;
      break;

    default:
      return -EINVAL;
  }

  // Determine file attributes
  if ((filp->flags & O_CREAT) != 0 && (filp->mode & S_IWRITE) == 0) {
    attrs = SMB_FILE_ATTR_READONLY;
  } else {
    attrs = SMB_FILE_ATTR_NORMAL;
  }

  if (filp->flags & O_TEMPORARY) {
    attrs |= SMB_FILE_FLAG_DELETE_ON_CLOSE;
    access |= SMB_ACCESS_DELETE;
    sharing |= SMB_FILE_SHARE_DELETE;
  }

  if (filp->flags & O_SHORT_LIVED) attrs |= SMB_FILE_ATTR_TEMPORARY;

  if (filp->flags & O_SEQUENTIAL) {
    attrs |= SMB_FILE_FLAG_SEQUENTIAL_SCAN;
  } else if (filp->flags & O_RANDOM) {
    attrs |= SMB_FILE_FLAG_RANDOM_ACCESS;
  }

  if (filp->flags & O_DIRECT) attrs |= SMB_FILE_FLAG_NO_BUFFERING | SMB_FILE_FLAG_WRITE_THROUGH;

  // Allocate file structure
  file = (struct smb_file *) kmalloc(sizeof(struct smb_file));
  if (!file) return -EMFILE;
  memset(file, 0, sizeof(struct smb_file));

  // Open/create file
  smb = smb_init(share, 0);
  smb->params.req.create.andx.cmd = 0xFF;
  smb->params.req.create.name_length = strlen(name) + 1;
  smb->params.req.create.desired_access = access;
  smb->params.req.create.ext_file_attributes = attrs;
  smb->params.req.create.share_access = sharing;
  smb->params.req.create.create_disposition = mode;
  smb->params.req.create.impersonation_level = 0x02;

  rc = smb_request(share, smb, SMB_COM_NT_CREATE_ANDX, 24, name, strlen(name) + 1, 1);
  if (rc < 0) {
    kfree(file);
    return rc;
  }

  file->fid = smb->params.rsp.create.fid;
  file->attrs = (unsigned short) smb->params.rsp.create.ext_file_attributes;

  if (file->attrs & SMB_FILE_ATTR_DIRECTORY) {
    file->statbuf.st_mode = S_IFDIR;
  } else {
    file->statbuf.st_mode = S_IFREG;
  }

  if (file->attrs & SMB_FILE_ATTR_READONLY) {
    file->statbuf.st_mode |= S_IREAD | S_IEXEC;
  } else {
    file->statbuf.st_mode |= S_IREAD | S_IWRITE | S_IEXEC;
  }

  file->statbuf.st_dev = NODEV;
  file->statbuf.st_ino = file->fid;
  file->statbuf.st_nlink = 1;
  file->statbuf.st_ctime = ft2time(smb->params.rsp.create.creation_time);
  file->statbuf.st_mtime = ft2time(smb->params.rsp.create.last_write_time);
  file->statbuf.st_atime = ft2time(smb->params.rsp.create.last_access_time);
  file->statbuf.st_size = smb->params.rsp.create.end_of_file;

  if (filp->flags & O_APPEND) {
    filp->pos = file->statbuf.st_size;
  } else {
    filp->pos = 0;
  }

  filp->data = file;

  return 0;
}

int smb_close(struct file *filp) {
  struct smb_share *share = (struct smb_share *) filp->fs->data;
  struct smb *smb;
  int rc;

  if (filp->flags & F_DIR) {
    struct smb_directory *dir = (struct smb_directory *) filp->data;

    if (!dir->eos) {
      smb = smb_init(share, 0);
      smb->params.req.findclose.sid = dir->sid;

      rc = smb_request(share, smb, SMB_COM_FIND_CLOSE2, 1, NULL, 0, 0);
      if (rc < 0) return rc;
    }

    kfree(dir);
    filp->data = NULL;
  } else {
    struct smb_file *file = (struct smb_file *) filp->data;

    smb = smb_init(share, 0);
    smb->params.req.close.fid = file->fid;

    rc = smb_request(share, smb, SMB_COM_CLOSE, 3, NULL, 0, 0);
    if (rc < 0) return rc;

    kfree(file);
    filp->data = NULL;
  }

  smb_clear_cache(share);

  return 0;
}

int smb_destroy(struct file *filp) {
  return 0;
}

int smb_fsync(struct file *filp) {
  struct smb_share *share = (struct smb_share *) filp->fs->data;
  struct smb_file *file = (struct smb_file *) filp->data;
  struct smb *smb;
  int rc;

  if (filp->flags & F_DIR) return -EBADF;

  smb = smb_init(share, 0);
  smb->params.req.flush.fid = file->fid;

  rc = smb_request(share, smb, SMB_COM_FLUSH, 1, NULL, 0, 0);
  if (rc < 0) return rc;

  return 0;
}

static int smb_read_raw(struct smb_share *share, struct smb_file *file, void *data, size_t size, unsigned long pos) {
  struct smb *smb;
  unsigned char hdr[4];
  int len;
  int rc;

  smb = smb_init(share, 0);
  smb->params.req.readraw.fid = file->fid;
  smb->params.req.readraw.offset = pos;
  smb->params.req.readraw.max_count = size;

  rc = smb_send(share, smb, SMB_COM_READ_RAW, 8, NULL, 0);
  if (rc < 0) return rc;
  
  rc = recv_fully(share->server->sock, (char *) &hdr, 4, 0);
  if (rc < 0) return rc;

  len = hdr[3] | (hdr[2] << 8) | (hdr[1] << 16) | (hdr[0] << 24);
  if (len == 0) return 0;

  rc = recv_fully(share->server->sock, data, len, 0);
  if (rc < 0) return rc;

  return rc;
}

static int smb_read_normal(struct smb_share *share, struct smb_file *file, void *data, size_t size, off64_t pos) {
  struct smb *smb;
  int len;
  int rc;

  // Read from file
  smb = smb_init(share, 0);
  smb->params.req.read.andx.cmd = 0xFF;
  smb->params.req.read.fid = file->fid;
  smb->params.req.read.offset = ((struct smb_pos *) &pos)->low_part;
  smb->params.req.read.max_count = size;
  smb->params.req.read.offset_high = ((struct smb_pos *) &pos)->high_part;

  rc = smb_request(share, smb, SMB_COM_READ_ANDX, 12, NULL, 0, 0);
  if (rc < 0) return rc;

  len = smb->params.rsp.read.data_length;
  if (len) memcpy(data, (char *) smb + smb->params.rsp.read.data_offset + 4, len);

  return len;
}

int smb_read(struct file *filp, void *data, size_t size, off64_t pos) {
  struct smb_share *share = (struct smb_share *) filp->fs->data;
  struct smb_file *file = (struct smb_file *) filp->data;
  char *p;
  size_t left;
  size_t count;
  int rc;

  if (filp->flags & F_DIR) return -EBADF;
  if (size == 0) return 0;

  left = size;
  p = (char *) data;

  if (filp->pos < file->statbuf.st_size && pos < 0x80000000) {
    // Read data using raw mode
    while (1) {
      count = left;
      if (count > SMB_RAW_CHUNKSIZE) count = SMB_RAW_CHUNKSIZE;

      rc = smb_read_raw(share, file, p, count, (unsigned long) pos);
      if (rc < 0) return rc;
      if (rc == 0) break;

      pos += rc;
      left -= rc;
      p += rc;

      if (left == 0) return size;
    }
  }

  // Read rest using normal mode
  while (pos < file->statbuf.st_size && left > 0) {
    count = left;
    if (count > SMB_NORMAL_CHUNKSIZE) count = SMB_NORMAL_CHUNKSIZE;

    rc = smb_read_normal(share, file, p, count, pos);
    if (rc < 0) return rc;
    if (rc == 0) return size - left;

    pos += rc;
    left -= rc;
    p += rc;
  }

  return size - left;
}

static int smb_write_normal(struct smb_share *share, struct smb_file *file, void *data, size_t size, off64_t pos) {
  struct smb *smb;
  int rc;

  // Write to file
  smb = smb_init(share, 0);
  smb->params.req.write.andx.cmd = 0xFF;
  smb->params.req.write.fid = file->fid;
  smb->params.req.write.offset = ((struct smb_pos *) &pos)->low_part;
  smb->params.req.write.data_length = size;
  smb->params.req.write.data_offset = SMB_HEADER_LEN + 14 * 2;
  smb->params.req.write.offset_high = ((struct smb_pos *) &pos)->high_part;

  rc = smb_request(share, smb, SMB_COM_WRITE_ANDX, 14, data, size, 0);
  if (rc < 0) return rc;

  return smb->params.rsp.write.count;
}

int smb_write(struct file *filp, void *data, size_t size, off64_t pos) {
  struct smb_share *share = (struct smb_share *) filp->fs->data;
  struct smb_file *file = (struct smb_file *) filp->data;
  char *p;
  size_t left;
  size_t count;
  int rc;

  if (filp->flags & F_DIR) return -EBADF;
  if (size == 0) return 0;

  if (filp->flags & O_APPEND) pos = file->statbuf.st_size;

  left = size;
  p = (char *) data;

  while (left > 0) {
    count = left;
    if (count > SMB_NORMAL_CHUNKSIZE) count = SMB_NORMAL_CHUNKSIZE;

    rc = smb_write_normal(share, file, p, count, pos);
    if (rc < 0) return rc;

    pos += rc;
    filp->flags |= F_MODIFIED;
    left -= rc;
    p += rc;

    if (pos > file->statbuf.st_size) file->statbuf.st_size = pos;
  }

  return size;
}

int smb_ioctl(struct file *filp, int cmd, void *data, size_t size) {
  return -ENOSYS;
}

off64_t smb_tell(struct file *filp) {
  if (filp->flags & F_DIR) return -EBADF;

  return filp->pos;
}

off64_t smb_lseek(struct file *filp, off64_t offset, int origin) {
  struct smb_file *file = (struct smb_file *) filp->data;

  if (filp->flags & F_DIR) return -EBADF;

  switch (origin) {
    case SEEK_END:
      offset += file->statbuf.st_size;
      break;

    case SEEK_CUR:
      offset += filp->pos;
  }

  if (offset < 0) return -EINVAL;

  filp->pos = offset;
  return offset;
}

int smb_ftruncate(struct file *filp, off64_t size) {
  struct smb_share *share = (struct smb_share *) filp->fs->data;
  struct smb_file *file = (struct smb_file *) filp->data;
  struct smb_set_fileinfo_request req;
  struct smb_file_end_of_file_info info;
  struct smb_set_fileinfo_response rsp;
  int rc;
  int rsplen;

  memset(&req, 0, sizeof(req));
  req.fid = file->fid;
  req.infolevel = 0x104;

  info.end_of_file = size;

  rsplen = sizeof(rsp);
  rc = smb_trans(share, TRANS2_SET_FILE_INFORMATION, &req, sizeof(req), &info, sizeof(info), &rsp, &rsplen, NULL, NULL);
  if (rc < 0) return rc;

  if (filp->pos > size) filp->pos = size;

  return 0;
}

int smb_futime(struct file *filp, struct utimbuf *times) {
  struct smb_share *share = (struct smb_share *) filp->fs->data;
  struct smb_file *file = (struct smb_file *) filp->data;
  struct smb_set_fileinfo_request req;
  struct smb_file_basic_info info;
  struct smb_set_fileinfo_response rsp;
  int rc;
  int rsplen;

  memset(&req, 0, sizeof(req));
  req.fid = file->fid;
  req.infolevel = 0x101;

  info.creation_time = time2ft(times->ctime == -1 ? file->statbuf.st_ctime : times->ctime);
  info.last_access_time = time2ft(times->actime == -1 ? file->statbuf.st_atime : times->actime);
  info.last_write_time = time2ft(times->modtime == -1 ? file->statbuf.st_mtime : times->modtime);
  info.change_time = time2ft(times->modtime == -1 ? file->statbuf.st_mtime : times->modtime);
  info.attributes = file->attrs;

  rsplen = sizeof(rsp);
  rc = smb_trans(share, TRANS2_SET_FILE_INFORMATION, &req, sizeof(req), &info, sizeof(info), &rsp, &rsplen, NULL, NULL);
  if (rc < 0) return rc;

  if (times->ctime != -1) file->statbuf.st_ctime = times->ctime;
  if (times->modtime != -1) file->statbuf.st_mtime = times->modtime;
  if (times->actime != -1) file->statbuf.st_atime = times->actime;

  return 0;
}

int smb_utime(struct fs *fs, char *name, struct utimbuf *times) {
  struct file filp;
  int rc;

  memset(&filp, 0, sizeof(struct file));
  filp.fs = fs;

  rc = smb_open(&filp, name);
  if (rc < 0) return rc;

  rc = smb_futime(&filp, times);
  
  smb_close(&filp);
  return rc;
}

int smb_fstat(struct file *filp, struct stat64 *buffer) {
  struct smb_file *file = (struct smb_file *) filp->data;

  if (filp->flags & F_DIR) return -EBADF;

  if (buffer) memcpy(buffer, &file->statbuf, sizeof(struct stat64));

  return (int) file->statbuf.st_size;
}

int smb_stat(struct fs *fs, char *name, struct stat64 *buffer) {
  struct smb_share *share = (struct smb_share *) fs->data;
  struct smb_pathinfo_request req;
  struct smb_file_basic_info rspb;
  struct smb_file_standard_info rsps;
  struct smb_dentry *dentry;
  int rsplen;
  short dummy;
  int dummylen;
  int rc;

  rc = smb_convert_filename(name);
  if (rc < 0) return rc;

  // Handle root mount point
  if (!*name) {
    if (buffer) {
      memset(buffer, 0, sizeof(struct stat64));
      buffer->st_atime = time(0);
      buffer->st_ctime = share->mounttime;
      buffer->st_mtime = share->mounttime;
      buffer->st_mode = S_IFDIR | S_IREAD | S_IWRITE;
      buffer->st_nlink = 1;
      return 0;
    }
  }

  // Look in cache
  dentry = smb_find_in_cache(share, name);
  if (dentry != NULL) {
    if (buffer) memcpy(buffer, &dentry->statbuf, sizeof(struct stat64));
    return (int) dentry->statbuf.st_size;
  }

  // Query server for file information
  if (buffer) {
    req.infolevel = SMB_QUERY_FILE_BASIC_INFO;
    req.reserved = 0;
    strcpy(req.filename, name);

    rsplen = sizeof(rspb);
    dummylen = sizeof(dummy);
    rc = smb_trans(share, TRANS2_QUERY_PATH_INFORMATION, &req, sizeof(req) - MAXPATH + strlen(name) + 1, NULL, 0, &dummy, &dummylen, &rspb, &rsplen);
    if (rc < 0) return rc;
  }

  req.infolevel = SMB_QUERY_FILE_STANDARD_INFO;
  req.reserved = 0;
  strcpy(req.filename, name);

  rsplen = sizeof(rsps);
  dummylen = sizeof(dummy);
  rc = smb_trans(share, TRANS2_QUERY_PATH_INFORMATION, &req, sizeof(req) - MAXPATH + strlen(name) + 1, NULL, 0, &dummy, &dummylen, &rsps, &rsplen);
  if (rc < 0) return rc;

  if (buffer) {
    memset(buffer, 0, sizeof(struct stat64));

    if (rspb.attributes & SMB_FILE_ATTR_DIRECTORY) {
      buffer->st_mode = S_IFDIR | S_IREAD | S_IWRITE;
    } else if (rspb.attributes & SMB_FILE_ATTR_READONLY) {
      buffer->st_mode = S_IFREG | S_IREAD | S_IEXEC;
    } else {
      buffer->st_mode = S_IFREG | S_IREAD | S_IWRITE | S_IEXEC;
    }

    buffer->st_ino = 0;
    buffer->st_nlink = (short) rsps.number_of_links;
    buffer->st_dev = NODEV;
    buffer->st_atime = ft2time(rspb.last_access_time);
    buffer->st_mtime = ft2time(rspb.last_write_time);
    buffer->st_ctime = ft2time(rspb.creation_time);
    buffer->st_size = rsps.end_of_file;
  }

  return (int) rsps.end_of_file;
}

int smb_mkdir(struct fs *fs, char *name, int mode) {
  struct smb_share *share = (struct smb_share *) fs->data;
  struct smb *smb;
  char namebuf[MAXPATH + 1 + 1];
  char *p;
  int rc;

  rc = smb_convert_filename(name);
  if (rc < 0) return rc;

  // Make directory
  smb = smb_init(share, 0);

  p = namebuf;
  *p++ = 4;
  p = addstrz(p, name);

  rc = smb_request(share, smb, SMB_COM_CREATE_DIRECTORY, 0, namebuf, p - namebuf, 1);
  if (rc < 0) return rc;

  return 0;
}

int smb_rmdir(struct fs *fs, char *name) {
  struct smb_share *share = (struct smb_share *) fs->data;
  struct smb *smb;
  char namebuf[MAXPATH + 1 + 1];
  char *p;
  int rc;

  rc = smb_convert_filename(name);
  if (rc < 0) return rc;

  // Delete directory
  smb = smb_init(share, 0);

  p = namebuf;
  *p++ = 4;
  p = addstrz(p, name);

  rc = smb_request(share, smb, SMB_COM_DELETE_DIRECTORY, 0, namebuf, p - namebuf, 1);
  if (rc < 0) return rc;

  return 0;
}

int smb_rename(struct fs *fs, char *oldname, char *newname) {
  struct smb_share *share = (struct smb_share *) fs->data;
  struct smb *smb;
  char namebuf[(MAXPATH + 1 + 1) * 2];
  char *p;
  int rc;

  rc = smb_convert_filename(oldname);
  if (rc < 0) return rc;

  rc = smb_convert_filename(newname);
  if (rc < 0) return rc;

  // Rename file
  smb = smb_init(share, 0);

  p = namebuf;
  *p++ = 4;
  p = addstrz(p, oldname);
  *p++ = 4;
  p = addstrz(p, newname);

  rc = smb_request(share, smb, SMB_COM_RENAME, 1, namebuf, p - namebuf, 1);
  if (rc < 0) return rc;

  return 0;
}

int smb_link(struct fs *fs, char *oldname, char *newname) {
  return -ENOSYS;
}

int smb_unlink(struct fs *fs, char *name) {
  struct smb_share *share = (struct smb_share *) fs->data;
  struct smb *smb;
  char namebuf[MAXPATH + 1 + 1];
  char *p;
  int rc;

  rc = smb_convert_filename(name);
  if (rc < 0) return rc;

  // Delete file
  smb = smb_init(share, 0);

  p = namebuf;
  *p++ = 4;
  p = addstrz(p, name);

  rc = smb_request(share, smb, SMB_COM_DELETE, 1, namebuf, p - namebuf, 1);
  if (rc < 0) return rc;

  return 0;
}

int smb_opendir(struct file *filp, char *name) {
  struct smb_share *share = (struct smb_share *) filp->fs->data;
  struct smb_findfirst_request req;
  struct smb_findfirst_response rsp;
  struct smb_directory *dir;
  int rsplen;
  int buflen;
  int rc;

  rc = smb_convert_filename(name);
  if (rc < 0) return rc;

  dir = (struct smb_directory *) kmalloc(sizeof(struct smb_directory));
  if (!dir) return -ENOMEM;

  memset(&req, 0, sizeof(req));
  req.search_attributes = SMB_FILE_ATTR_SYSTEM | SMB_FILE_ATTR_HIDDEN | SMB_FILE_ATTR_DIRECTORY;
  req.flags = SMB_CLOSE_IF_END;
  req.infolevel = 0x101;
  req.search_count = 512;
  strcpy(req.filename, name);
  if (*name) strcat(req.filename, "\\*");

  rsplen = sizeof(rsp);
  buflen = SMB_DIRBUF_SIZE;
  rc = smb_trans(share, TRANS2_FIND_FIRST2, &req, 12 + strlen(req.filename) + 1, NULL, 0, &rsp, &rsplen, dir->buffer, &buflen);
  if (rc < 0) {
    kfree(dir);
    return rc;
  }

  dir->sid = rsp.sid;
  dir->eos = rsp.end_of_search;
  dir->entries_left = rsp.search_count;
  dir->fi = (struct smb_file_directory_info *) dir->buffer;
  strcpy(dir->path, name);

  filp->data = dir;
  return 0;
}

int smb_readdir(struct file *filp, struct direntry *dirp, int count) {
  struct smb_share *share = (struct smb_share *) filp->fs->data;
  struct smb_directory *dir = (struct smb_directory *) filp->data;
  struct stat64 statbuf;

  if (count != 1) return -EINVAL;

again:
  if (dir->entries_left == 0) {
    struct smb_findnext_request req;
    struct smb_findnext_response rsp;
    int rsplen;
    int buflen;
    int rc;

    if (dir->eos) return 0;

    memset(&req, 0, sizeof(req));
    req.sid = dir->sid;
    req.flags = SMB_CONTINUE_BIT | SMB_CLOSE_IF_END;
    req.infolevel = 0x101;
    req.search_count = 512;

    rsplen = sizeof(rsp);
    buflen = SMB_DIRBUF_SIZE;
    rc = smb_trans(share, TRANS2_FIND_NEXT2, &req, sizeof(req), NULL, 0, &rsp, &rsplen, dir->buffer, &buflen);
    if (rc < 0) return rc;

    dir->eos = rsp.end_of_search;
    dir->entries_left = rsp.search_count;
    dir->fi = (struct smb_file_directory_info *) dir->buffer;

    if (dir->entries_left == 0) return 0;
  }

  if (dir->fi->filename[0] == '.' && (dir->fi->filename[1] == 0 || (dir->fi->filename[1] == '.' && dir->fi->filename[2] == 0))) {
    dir->entries_left--;
    dir->fi = (struct smb_file_directory_info *) ((char *) dir->fi + dir->fi->next_entry_offset);
    goto again;
  }

  memset(&statbuf, 0, sizeof(struct stat64));

  if (dir->fi->ext_file_attributes & SMB_FILE_ATTR_DIRECTORY) {
    statbuf.st_mode = S_IFDIR | S_IREAD;
  } else if (dir->fi->ext_file_attributes & SMB_FILE_ATTR_READONLY) {
    statbuf.st_mode = S_IFREG | S_IREAD | S_IEXEC;
  } else {
    statbuf.st_mode = S_IFREG | S_IREAD | S_IWRITE | S_IEXEC;
  }

  statbuf.st_nlink = 1;
  statbuf.st_ctime = ft2time(dir->fi->creation_time);
  statbuf.st_mtime = ft2time(dir->fi->last_write_time);
  statbuf.st_atime = ft2time(dir->fi->last_access_time);
  statbuf.st_size = dir->fi->end_of_file;

  smb_add_to_cache(share, dir->path, dir->fi->filename, &statbuf);

  dirp->ino = 0;
  dirp->namelen = strlen(dir->fi->filename);
  dirp->reclen = sizeof(struct direntry) - MAXPATH + dirp->namelen + 1;
  strcpy(dirp->name, dir->fi->filename);

  dir->entries_left--;
  dir->fi = (struct smb_file_directory_info *) ((char *) dir->fi + dir->fi->next_entry_offset);

  return 1;
}

struct fsops smbfsops = {
  0,

  smb_lockfs,
  smb_unlockfs,

  smb_mkfs,
  smb_mount,
  smb_umount,

  smb_statfs,

  smb_open,
  smb_close,
  smb_destroy,
  smb_fsync,

  smb_read,
  smb_write,
  smb_ioctl,

  smb_tell,
  smb_lseek,
  smb_ftruncate,

  smb_futime,
  smb_utime,

  smb_fstat,
  smb_stat,

  NULL,

  NULL,
  NULL,
  NULL,
  NULL,

  smb_mkdir,
  smb_rmdir,

  smb_rename,
  smb_link,
  smb_unlink,

  smb_opendir,
  smb_readdir
};

void init_smbfs() {
  register_filesystem("smbfs", &smbfsops);
}