/********************************************************************
 *
 * vfs_smbcdio.c
 *
 ********************************************************************
 *
 * Copyright (C) 2005 Dan Sturtevant & Chris Lalancette
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 ********************************************************************
 */

#include "includes.h"
#include <string.h>
#include <cdio/cdio.h>
#include <cdio/iso9660.h>
#include <cdio/device.h>
#include <time.h>
#include <linux/cdrom.h>

struct direntlist {
  struct dirent cdiodirent;
  struct direntlist *next;
};

struct smbcdio_DIR {
  int inuse;
  int index;
  CdioList *cdioentlist;
  struct direntlist *direntlist;
  struct direntlist *direntcurr;
};

struct smbcdio_fd {
  int inuse;
  iso9660_stat_t *cdiostat;
  SMB_OFF_T offset;
};

struct smbcdio_fd_array {
  struct smbcdio_fd *fd_array;
  int num_allocated;
};

struct smbcdio_DIR_array {
  struct smbcdio_DIR *dir_array;
  int num_allocated;
};

// FIXME: get these inside 'handle' variable
static iso9660_t *p_iso= NULL;
static struct smbcdio_fd_array *fds = NULL;
static struct smbcdio_DIR_array *dirs = NULL;
static int media_unavailable = 0;
static const char *device = NULL;
static int unlock_fd = 0;

int media_available()
{
  int err;

  //DEBUG(0,("MEDIA AVAILABLE CALLED.\n"));

  if(0 == media_unavailable) {
    return 1;
  }
  if(1 == media_unavailable) {
    if(0 == test_door_opened()) {
      fds_initialize();
      dirs_initialize();
      err = iso_initialize();
      if(0 > err) {
	media_unavailable = 1;
	return 0;
      }
      return 1;
    }
    return 0;
  }
}
	
int test_door_opened()
{
  //become_root();

  //DEBUG(0,("TEST DOOR 0PENED CALLED.\n"));


  int changed;

  changed = ioctl(unlock_fd, CDROM_MEDIA_CHANGED);
  if(0 > changed) {
    DEBUG(0,("PROBLEM ASKING DEVICE IF IT'S BEEN OPENED."));
  }

  if(0 != changed) {
    DEBUG(0,("MEDIA CHANGED: %d\n", changed));
    }
  
  if(0 != changed) {
    media_unavailable = 1;
  }

  //unbecome_root();

  return changed;
}


int unlock_cdrom_door()
{
  int err = 0;

  //Turn Locking off on the drive.

  err = ioctl(unlock_fd, CDROM_LOCKDOOR, 0);
  if(0 > err) {
    DEBUG(0,("FAILED UNLOCKING CDROM DEVICE"));
  }

  return err;
}




int fds_initialize()
{
  int i;

  //only the first time.
  if(NULL == fds) {
    fds = (struct smbcdio_fd_array *) calloc (sizeof(struct smbcdio_fd_array), 1);

    fds->num_allocated = 100;
    fds->fd_array = (struct smbcdio_fd *) calloc (sizeof(struct smbcdio_fd), fds->num_allocated);

    //take the first 3 slots.  this is unnecessary, but i dont want anyone confused thinking
    //that these are *REAL* file descriptors and wondering why they have STDOUT, STDIN, or STDERR
    fds->fd_array[0].inuse = 1;
    fds->fd_array[1].inuse = 1;
    fds->fd_array[2].inuse = 1;
  }

  for(i = 3; i < fds->num_allocated; i++) {
    if(1 == fds->fd_array[i].inuse) {
      fds->fd_array[i].inuse = 0;
    }
  }

}

int dirs_initialize()
{
  int i = 0;
  struct smbcdio_DIR *dirptr;
  struct direntlist *tmp;
  struct direntlist *tmp2;

  //only the first time
  if(NULL == dirs) {
    dirs = (struct smbcdio_DIR_array *) calloc (sizeof(struct smbcdio_DIR_array), 1);
    dirs->num_allocated = 100;
    dirs->dir_array = (struct smbcdio_DIR *) calloc (sizeof(struct smbcdio_DIR), dirs->num_allocated);
    for(i = 0; i < dirs->num_allocated; i++) {
      dirs->dir_array[i].index = i;
      dirs->dir_array[i].inuse = 0;
    }
  }

  for(i = 0; i < dirs->num_allocated; i++) {
    if(1 == dirs->dir_array[i].inuse) {
	dirptr = &(dirs->dir_array[i]);
       
	  _cdio_list_free(dirptr->cdioentlist, true);
	  tmp = dirptr->direntlist;
	  while(NULL != tmp) {
	    tmp2 = tmp->next;
	    free(tmp);
	    tmp = tmp2;
	  }
	  dirptr->direntlist = NULL;
	  dirptr->direntcurr = NULL;

	  //this tells close to succeed but not to do any work.
	  dirptr->inuse = 2;
    }
  }
}

int iso_initialize()
{

  if(NULL != p_iso) {
    iso9660_close(p_iso);
  }

  p_iso = iso9660_open_ext(device, ISO_EXTENSION_ALL);
  if(NULL == p_iso) {
    DEBUG(0,("/dev/cdrom not available. Failed reading iso.%s\n",strerror(errno)));
    errno = ENOSYS;
    //unbecome_root();
    return -1;
  }

  if(unlock_fd < 1) {
    unlock_fd = open(device, O_RDONLY|O_NONBLOCK);
    if(0 > unlock_fd) {
      DEBUG(0, ("FAILED OPENING %s: you might want to change its permissions.\n", device));
      return -1;
    }
  }

  media_unavailable = 0;

  return 0;
}
  


int fds_first_open_fd()
{
  int i;
  //go through all the entries and see if any are marked not in use
  for(i = 0; i < fds->num_allocated; i++) {
    if(0 == fds->fd_array[i].inuse) {
      return i;
    }
  }

  //just fail if there are more than 100.  obviously we have other problems.
  return -1;

  /*
  //we've gotten to the end of the list and all the fd's are in use.  allocate more.
  fds->num_allocated += 100;
  //grow by 100.
  fds->fd_array = (struct smbcdio_fd *) realloc (fds->fd_array, sizeof(struct smbcdio_fd) * fds->num_allocated);
  //zero out the last 100.
  memset(& (fds->fd_array[fds->num_allocated - 100]), 0, sizeof(struct smbcdio_fd) * 100);

  return i+1;
  */
}

int dirs_first_open_dir()
{
  int i;
  //go through all the entries and see if any are marked not in use
  for(i = 0; i < dirs->num_allocated; i++) {
    if((0 > dirs->dir_array[i].inuse) || (2 < dirs->dir_array[i].inuse)) {
      DEBUG(0,("MAJOR PROBLEM\n"));
      return -1;
    }
    if(0 == dirs->dir_array[i].inuse) {
      if(i > 0) {DEBUG(0,("DIR:  %d\n", i));}
      return i;
    }
  }

  //just fail if there are more than 100.  obviously we have other problems.
  DEBUG(0,("DIR: TOO MANY\n"));
  return -1;
}


static int smbcdio_open(vfs_handle_struct *handle, connection_struct *conn, const char *filename, int flags, mode_t mode)
{
  struct smbcdio_fd *cdfd;

  int fds_num;

  become_root();

  if(1 != media_available()) {
    unbecome_root();
    return -1;
  }

  cdfd = (struct smbcdio_fd *) calloc (sizeof(struct smbcdio_fd), 1);

  cdfd->cdiostat = iso9660_ifs_stat(p_iso, filename);
  if(NULL == cdfd->cdiostat) {
    DEBUG(0,("NOT AVAILABLE\n"));
    //media_unavailable = 1;
    test_door_opened();
    unbecome_root();
    return -1;
  }

  cdfd->offset = 0;


  fds_num = fds_first_open_fd();
  if(fds_num < 0) {
    unbecome_root();
    return -EMFILE;
  }
  
  fds->fd_array[fds_num].inuse = 1;
  fds->fd_array[fds_num].cdiostat = cdfd->cdiostat;
  fds->fd_array[fds_num].offset = 0;

  //DEBUG(0,("%s opened with file descriptor %d\n", filename, fds_num));

  unbecome_root();
  return fds_num;
}

static SMB_OFF_T smbcdio_lseek(vfs_handle_struct *handle, files_struct *fsp, int fd, SMB_OFF_T offset, int whence)
{
  struct smbcdio_fd *cdfd;
  SMB_OFF_T tmp,retval;

  become_root();

  if(1 != media_available()) {
    unbecome_root();
    return -1;
  }

  cdfd = &(fds->fd_array[fd]);

  tmp = cdfd->offset;

  if(SEEK_SET == whence) {
    cdfd->offset = offset;
  }
  if(SEEK_CUR == whence) {
    cdfd->offset += offset;
  }

  if(cdfd->offset <= cdfd->cdiostat->size) {
    retval=cdfd->offset;
  }
  else {
    cdfd->offset = tmp;
    retval=-1;

  }

  unbecome_root();
  return retval;
}

static ssize_t smbcdio_read(vfs_handle_struct *handle, files_struct *fsp, int fd, void *data, size_t n)
{
  int start_byte,end_length,iso_lsn_end,i,total_bytes,iso_lsn_start,iso_num_blocks;
  char *outbuf;
  char isobuf[ISO_BLOCKSIZE];
  struct smbcdio_fd *cdfd;
  long int read;

  become_root();

  if(1 != media_available()) {
    unbecome_root();
    return -1;
  }

  cdfd = &(fds->fd_array[fd]);

  // this is to take care of the case when they asked for more data than what
  // was left
  if((cdfd->offset + n) > cdfd->cdiostat->size){
    n=cdfd->cdiostat->size - cdfd->offset;
  }

  start_byte=cdfd->offset%ISO_BLOCKSIZE;
  end_length=(cdfd->offset + n - 1)%ISO_BLOCKSIZE + 1;

  iso_lsn_start=cdfd->offset/ISO_BLOCKSIZE;
  // iso_lsn_end is exclusive; i.e. if it is 4, and iso_lsn_start is 1, we read
  // blocks 1, 2, and 3 only
  iso_num_blocks=1+((cdfd->offset+n-1)/ISO_BLOCKSIZE)-(cdfd->offset/ISO_BLOCKSIZE);
  iso_lsn_end=iso_lsn_start+iso_num_blocks;

  outbuf=(char *)data;
  total_bytes=0;

  // note that we do iso_lsn_end-1; the last block is taken care of below
  i=iso_lsn_start;
  while(i<(iso_lsn_end-1)){
    read = iso9660_iso_seek_read(p_iso,isobuf,cdfd->cdiostat->lsn + i,1);
    if(read > 0) 
      {
	memcpy(outbuf,isobuf+start_byte,2048-start_byte);
	outbuf+=(2048-start_byte);
	total_bytes+=(2048-start_byte);
	start_byte=0;
	i++;
      }
    else
      {
	goto err_out;
      }
  }

  read = iso9660_iso_seek_read(p_iso,isobuf,cdfd->cdiostat->lsn + i,1);
  if(read > 0) 
    {
      memcpy(outbuf,isobuf+start_byte,end_length-start_byte);
      total_bytes+=(end_length-start_byte);
    }

 err_out:
  unbecome_root();
  return total_bytes;
}

static ssize_t smbcdio_pread(vfs_handle_struct *handle, files_struct *fsp, int fd, void *data, size_t n, SMB_OFF_T offset)
{
  SMB_OFF_T old;
  size_t retval;


  if(1 != media_available()) {
    return -1;
  }

  old = smbcdio_lseek(handle, fsp, fd, 0, SEEK_CUR);

  smbcdio_lseek(handle, fsp, fd, offset, SEEK_SET);

  retval=smbcdio_read(handle, fsp, fd, data, n);

  smbcdio_lseek(handle, fsp, fd, old, SEEK_SET);

  return retval;
}

static int smbcdio_close(vfs_handle_struct *handle, files_struct *fsp, int fd)
{
  struct smbcdio_fd *cdfd;

  // see comment in smbcdio_open

  //DEBUG(0,("smbcdio_close called on file descriptor %d\n", fd));

  become_root();

  cdfd = &(fds->fd_array[fd]);

  cdfd->inuse = 0;
  cdfd->offset = 0;

  unbecome_root();


  return 0;
}

static DIR *smbcdio_opendir(vfs_handle_struct *handle, connection_struct *conn, const char *fname)
{
  struct smbcdio_DIR *dirptr;
  CdioListNode *entnode;
  uint8_t i_joliet_level;
  iso9660_stat_t *statbuf;
  char *iso_name;
  char translated_name[MAX_ISONAME+1];

  become_root();

  test_door_opened();
  unlock_cdrom_door();

  if(1 != media_available()) {
    unbecome_root();
    return NULL;
  }

  int dir_num = dirs_first_open_dir();
  if(0 > dir_num) {
    unbecome_root();
    return NULL;
  }

  dirs->dir_array[dir_num].inuse = 1;

  i_joliet_level = iso9660_ifs_get_joliet_level(p_iso);

  int here = 0;

  //DEBUG(0,("OPENDIR: %d\n",here)); here++;

  /*
    substitued 'dirptr->cdioentlist' with 'dirs->dir_array[dir_num].cdioentlist' 
    substitued 'dirptr->direntlist' with 'dirs->dir_array[dir_num].direntlist' 
    substitued 'dirptr->direntcurr' with 'dirs->dir_array[dir_num].direntcurr' 
  */


  dirs->dir_array[dir_num].cdioentlist = iso9660_ifs_readdir(p_iso, fname);
  if (NULL == dirs->dir_array[dir_num].cdioentlist) {
    DEBUG(0,("Error doing ifs_readdir on %s\n",fname));
    dirs->dir_array[dir_num].inuse = 0;
    test_door_opened();
    unbecome_root();
    return NULL;
  }

  //DEBUG(0,("OPENDIR: %d\n",here)); here++;

  dirs->dir_array[dir_num].direntlist = NULL;
  dirs->dir_array[dir_num].direntcurr = NULL;

  //DEBUG(0,("OPENDIR: %d\n",here)); here++;

  for(entnode = _cdio_list_begin(dirs->dir_array[dir_num].cdioentlist); entnode; entnode = _cdio_list_node_next(entnode)) {
    statbuf = _cdio_list_node_data (entnode);
    iso_name = statbuf->filename;
    int loophere = here;

    //DEBUG(0,("OPENDIR: %d\n",loophere)); loophere++;
    if(NULL == dirs->dir_array[dir_num].direntlist) {
      dirs->dir_array[dir_num].direntlist = (struct direntlist *) calloc (sizeof(struct direntlist), 1);
      dirs->dir_array[dir_num].direntcurr = dirs->dir_array[dir_num].direntlist;
    }
    else {
      dirs->dir_array[dir_num].direntcurr->next = (struct direntlist *) calloc (sizeof(struct direntlist), 1);
      dirs->dir_array[dir_num].direntcurr = dirs->dir_array[dir_num].direntcurr->next;
    }

    //DEBUG(0,("OPENDIR: %d\n",loophere)); loophere++;

    iso9660_name_translate_ext(iso_name,translated_name,i_joliet_level);

    //FIXME
    snprintf(dirs->dir_array[dir_num].direntcurr->cdiodirent.d_name, 1024, "%s", iso_name);
    //snprintf(dirs->dir_array[dir_num].direntcurr->cdiodirent.d_name, 1024, "%s", translated_name);
    //DEBUG(0,("Original name is %s, translated name is %s\n",iso_name,translated_name));
  }

  //DEBUG(0,("OPENDIR: %d\n",here)); here++;

  dirs->dir_array[dir_num].direntcurr = dirs->dir_array[dir_num].direntlist;

  //DEBUG(0,("OPENDIR: %x\n",&(dirs->dir_array[dir_num])));

  unbecome_root();
  return (DIR *) &(dirs->dir_array[dir_num]);
}

static struct dirent *smbcdio_readdir(vfs_handle_struct *handle, connection_struct *conn, DIR *dirp)
{
  struct dirent *result;
  struct smbcdio_DIR *dirptr;
  int here = 0;

  become_root();

  //DEBUG(0,("READDIR: %x\n",dirptr));

  //DEBUG(0,("READDIR: %d\n",here)); here++;

  if(1 != media_available()) {
    unbecome_root();
    return NULL;
  }

  //DEBUG(0,("READDIR: %d\n",here)); here++;

  dirptr = (struct smbcdio_DIR *) dirp;

  
  //DEBUG(0,("READDIR: %d\n",here)); here++;

  if(NULL == dirptr->direntcurr) {
    unbecome_root();
    return NULL;
  }
  
  //DEBUG(0,("READDIR: %d\n",here)); here++;

  // if((0 == dirptr->inuse) || (2 == dirptr->inuse)) {
      if(0 == dirptr->inuse) {
    unbecome_root();
    return NULL;
  }

  //DEBUG(0,("READDIR: %d\n",here)); here++;

  //DEBUG(0,("reading directory: %s\n",dirptr->direntcurr->cdiodirent.d_name));


  result =  &dirptr->direntcurr->cdiodirent;

  dirptr->direntcurr = dirptr->direntcurr->next;

  //DEBUG(0,("READDIR: %d\n",here)); here++;

  unbecome_root();
  return result;
}

static int smbcdio_closedir(vfs_handle_struct *handle, connection_struct *conn, DIR *dirp)
{
  struct smbcdio_DIR *dirptr;
  struct direntlist *tmp;
  struct direntlist *tmp2;

  //DEBUG(0,("smbcdio_closedir called\n"));

  become_root();

  dirptr = (struct smbcdio_DIR *) dirp;

  if(dirptr->inuse == 0) {
    unbecome_root();
    return -1;
  }


  if(dirptr->inuse == 1) {
    _cdio_list_free(dirptr->cdioentlist, true);

    tmp = dirptr->direntlist;
    while(NULL != tmp) {
      tmp2 = tmp->next;
      free(tmp);
      tmp = tmp2;
    }
    dirptr->direntlist = NULL;
    dirptr->direntcurr = NULL;
  }

  dirptr->inuse = 0;
  dirs->dir_array[dirptr->index].inuse = 0;

  /*
  DEBUG(0,("dirptr: %x               &dirs->dir_array[dirptr->index]: %x\n", dirptr,   &(dirs->dir_array[dirptr->index])));
  DEBUG(0,("dirptr->index: %d        dirs->dir_array[dirptr->index].index: %d\n", dirptr->index,   dirs->dir_array[dirptr->index].index));
  DEBUG(0,("dirptr->inuse: %d        dirs->dir_array[dirptr->index].inuse: %d\n", dirptr->inuse,   dirs->dir_array[dirptr->index].inuse));
  */

  unbecome_root();

  return 0;
}

static int stat_cdio2smb(iso9660_stat_t *cdiostat , SMB_STRUCT_STAT *smbstat)
{
  if(NULL == cdiostat) {
    DEBUG(0,("NULL cdiostat structure passed\n"));
    return -1;
  }
  if(smbstat==NULL){
    DEBUG(0,("NULL smbstat structure passed\n"));
    return -1;
  }

  smbstat->st_dev=0; // FIXME
  smbstat->st_ino=0; // FIXME
  smbstat->st_mode=S_IRUSR|S_IRGRP|S_IROTH;
  if(cdiostat->type==_STAT_DIR){
    smbstat->st_mode|=S_IFDIR|S_IXUSR|S_IXGRP|S_IXOTH;
  }
  else{
    smbstat->st_mode|=S_IFREG;
  }
  smbstat->st_nlink=1;
  smbstat->st_uid=getuid();
  smbstat->st_gid=getgid();
  smbstat->st_rdev=0; // FIXME
  smbstat->st_size=cdiostat->size;
  smbstat->st_blksize=2048;
  smbstat->st_blocks=cdiostat->size/512;
  smbstat->st_atime = mktime(&cdiostat->tm);
  smbstat->st_mtime = smbstat->st_atime;
  smbstat->st_ctime = smbstat->st_atime;

  return 0;
}

static int smbcdio_fstat(vfs_handle_struct *handle, files_struct *fsp, int fd, SMB_STRUCT_STAT *smbstat)
{
  int err;
  become_root();

  if(1 != media_available()) {
    unbecome_root();
    return -1;
  }

  struct smbcdio_fd *cdfd;

  cdfd = &(fds->fd_array[fd]);

  // set up samba's stat structure
  err =  stat_cdio2smb(cdfd->cdiostat, smbstat);

  unbecome_root();
  return err;
}

static int smbcdio_stat(vfs_handle_struct *handle,connection_struct *conn,const char *filename, SMB_STRUCT_STAT *smbstat)
{
  // libcdio has its own type of stat structure.
  iso9660_stat_t *cdiostat;
  int err;

  become_root();

  if(1 != media_available()) {
    unbecome_root();
    return -1;
  }


  // get libcdio's stat structure for the file on the cd

  //FIXME
  cdiostat = iso9660_ifs_stat(p_iso, filename);
  //cdiostat = iso9660_ifs_stat_translate(p_iso, filename);
  if(NULL == cdiostat) {
    // note that we do not print out an error message here because we flood
    // the logs
    unbecome_root();
    return -1;
  }

  err = stat_cdio2smb(cdiostat, smbstat);
  unbecome_root();
  return err;
}

static int smbcdio_lstat(vfs_handle_struct *handle, connection_struct *conn,const char *filename, SMB_STRUCT_STAT *sbuf)
{
  return smbcdio_stat(handle, conn, filename, sbuf);
}

static int smbcdio_connect(vfs_handle_struct *handle, connection_struct *conn, const char *svc, const char *user)
{
  //DEBUG(0,("SMBCDIO CONNECT\n"));

  become_root();

  device=lp_parm_const_string(conn->service,"smbcdio","CD device",NULL);
  if(device==NULL){
    errno=ENOSYS;
    unbecome_root();
    return -1;
  }

  /*  test_door_opened();
  if(1 !=  media_available()) {
    unbecome_root();
    return -1;
  }
  */

  fds_initialize();
  dirs_initialize();
  if(0 > iso_initialize()) {
    DEBUG(0,("/dev/cdrom not available. Failed reading iso.%s\n",strerror(errno)));
    unbecome_root();
    return -1;
  }

  unlock_cdrom_door();

  unbecome_root();
  return 0;
}

static void smbcdio_disconnect(vfs_handle_struct *handle, connection_struct *conn)
{
  //DEBUG(0,("SMBCDIO DISCONNECT\n"));

  become_root();

  if(NULL != p_iso) {
    iso9660_close(p_iso);
  }

  fds_initialize();
  dirs_initialize();
  
  unbecome_root();
}

static vfs_op_tuple smbcdio_op_tuples[] = {
  // file operations (on fd's)
  {SMB_VFS_OP(smbcdio_open), SMB_VFS_OP_OPEN, SMB_VFS_LAYER_OPAQUE},
  {SMB_VFS_OP(smbcdio_read), SMB_VFS_OP_READ, SMB_VFS_LAYER_OPAQUE},
  {SMB_VFS_OP(smbcdio_pread), SMB_VFS_OP_PREAD, SMB_VFS_LAYER_OPAQUE},
  {SMB_VFS_OP(smbcdio_fstat), SMB_VFS_OP_FSTAT, SMB_VFS_LAYER_OPAQUE},
  {SMB_VFS_OP(smbcdio_lseek), SMB_VFS_OP_READ, SMB_VFS_LAYER_OPAQUE},
  {SMB_VFS_OP(smbcdio_close), SMB_VFS_OP_CLOSE, SMB_VFS_LAYER_OPAQUE},

  // file operations (on names)
  {SMB_VFS_OP(smbcdio_stat), SMB_VFS_OP_STAT, SMB_VFS_LAYER_OPAQUE},
  {SMB_VFS_OP(smbcdio_lstat), SMB_VFS_OP_LSTAT, SMB_VFS_LAYER_OPAQUE},

  // directory operations
  {SMB_VFS_OP(smbcdio_opendir), SMB_VFS_OP_OPENDIR, SMB_VFS_LAYER_OPAQUE},
  {SMB_VFS_OP(smbcdio_readdir), SMB_VFS_OP_READDIR, SMB_VFS_LAYER_OPAQUE},
  {SMB_VFS_OP(smbcdio_closedir), SMB_VFS_OP_CLOSEDIR, SMB_VFS_LAYER_OPAQUE},

  {SMB_VFS_OP(smbcdio_connect), SMB_VFS_OP_CONNECT, SMB_VFS_LAYER_OPAQUE},
  {SMB_VFS_OP(smbcdio_disconnect), SMB_VFS_OP_DISCONNECT, SMB_VFS_LAYER_OPAQUE},

  {SMB_VFS_OP(NULL), SMB_VFS_OP_NOOP, SMB_VFS_LAYER_NOOP},
};

NTSTATUS init_module(void)
{
  return smb_register_vfs(SMB_VFS_INTERFACE_VERSION,"smbcdio",smbcdio_op_tuples);
}
