/*
    scfs.c
    Called by scfsd to access smartcard
    NI, 5/29, 1998

    Modified, Nov. 25, 2000
    Now SCFS is reimplemented as a NFS V2 daemon for better portability. 
    Naomaru Itoi (itoi@eecs.umich.edu)
*/

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>

#include <sys/file.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <rpc/rpc.h>

#include "scfs.h"
#include "scrw.h"

#ifdef NeXT
#include <sys/dir.h>
#define dirent direct
#else
#include <dirent.h>
#endif
#include "nfsproto.h"
#include "admproto.h"
#include "cfs.h"

#include <assert.h>
#include <string.h>

/* Global Variables */
int fd;
unsigned char cur_fid;
/*int flag_mounted;*/

/* extern */
extern struct scconfig_str scconfig;
extern unsigned char doti_name[];

int scfs_reset (int port, char *buf)
{
    int vflag = 1, error, n;
    
    fd = scopen(port, 0, &error);
    printf("scopen on port %d returns %d\n", port, fd); 
    if (fd < 0) {
	fprintf (stderr, "cannot open reader\n");
	errno = error; 
	perror("scopen");
	return error;
    }
    
    /* reset */
    n = screset(fd, buf, &error);

    if (!n || error != SCEOK) {
	fprintf (stderr, "%s (%d %d)", scerrtab[error], n, error);
	return EIO;
    }
    
    if (buf){
	if (vflag) {
	    dump_reply(buf, n, 0, 0);
	}
    }
    return 0;
}

int scfs_init(int port)
{
    int rv = 0;
    char atr[256];
    
    /* reset smartcard */
    rv = scfs_reset (port, atr);
    if (rv != 0) {
	fprintf (stderr, "cannot mount smartcard\n");
        return rv;
    }

    /* init scdir */
    if ((rv = scdir_init(atr, (char *)NULL))) {
	fprintf (stderr, "cannot initialize scdir information in %s\n", SCDFILE);
	return rv;
    }

  /* install 3f.00 */
  if (rv = scdir_newroot()) {  
      fprintf (stderr, "cannot install scdir root\n");
      return rv;
  }

  /* scan the directory */
  if (rv = scdir_newdir(scconfig.file_entries[0])) { 
      fprintf (stderr, "failure in scanning the file system\n");
      return rv;
  }

  scdir_dump();

  return 0; 
}

static int open_file(int fd, unsigned char *name)
{
    int len, r1, r2;

    len = scwrite (fd, scconfig.cla_select, 0xa4, 0, 0, 2, name, &r1, &r2);
    if (len == 2 && ((r1 == 144 && r2 == 0) || (r1 == 0x61))) {
	printf ("open_file(%02x.%02x) succeeded\n", name[0], name[1]); 
	return 0;
    }

    printf ("open_file() failed.  len %d, r1 %x, r2 %x\n", len, r1, r2);

    return -1;
}

int scfs_open_file(unsigned char *name)
{
    return open_file(fd, name);
}

/* optimized godown().

   if the file is already selected, just return.
   if the file is the root, select it.
   if the file is under the current directory, select it.
   if the file is the current file's sibling, select it. 
   otherwise, select from root.
*/

int godown(file_ent *fe)
{
    int i, j, rv = 0;
    file_ent *tmp;
    unsigned char path[MAX_DEPTH][2];

    printf ("godown to fid %d from %d\n", fe->fid, cur_fid);
    
    /* if the file is already selected, do nothing. */
    if (fe->fid == cur_fid) {
	return 0;
	/*return open_file(fd, node->fileent.fid[0], node->fileent.fid[1]);*/
    }
    
    /* if the file is the root dir, select it. */
    if (fe->parent == NULL) {
	rv = open_file(fd, fe->name);
	goto godown_end;
    }

    /* if the file is under current directory, select it. */
    if (fe->parent->fid == cur_fid) {
	rv = open_file(fd, fe->name);
	goto godown_end;
    }

    tmp = scconfig.file_entries[cur_fid - 1];
    if (tmp == NULL) {
	fprintf (stderr, "No current file?!\n");
	return 1; 
    }
    
    /* if the file is the current file's sibling, select it. */
    if (tmp->type == SCFS_TYPE_ELFILE &&
	fe->parent->fid == tmp->parent->fid) {

	rv = open_file(fd, fe->name);
	goto godown_end;
    }

select_from_root:
  for (tmp = fe, i=0; tmp != NULL; tmp = tmp->parent, i++) {
      path[i][0] = tmp->name[0];
      path[i][1] = tmp->name[1];
  }

  for (j=i-1; j>=0; j--) {
      /*printf ("select %02x.%02x\n", path[j][0], path[j][1]);*/
      if (open_file(fd, path[j]) != 0) {
	  fprintf(stderr, "cannot open %02x %02x\n", path[j][0], path[j][1]);
	  return -1;
      }
  }
 godown_end:

  if (rv == 0)
      cur_fid = fe->fid;
  
  return rv;
}

/*
  convert 32 byte fhandle to unsigned long fid.
  fhandle format:
  0 - 7  : magic number
  8 - 11 : fid (u_long)
  12 - 31: RFU

  The magic number should always be 0x0123456789abcdef, except for the root.
  When I see a magic number that is not the above, I assume it is a root, 
  and I store the root magic number, and return 1 as a FID.

  This is kind of weird ... but that what CFS does, and it works. 
*/
static u_char magic[8] = {0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef};
static u_char root_handle[8]; 

void fid_to_fhandle (unsigned char *fhandle, u_long fid)
{
    int i;
    
    bzero (fhandle, 32);
    memcpy (fhandle, magic, sizeof(magic));
    memcpy (fhandle + sizeof(magic), &fid, sizeof (fid)); 

    return; 
}
     
u_long fhandle_to_fid(unsigned char *fhandle)
{
    int i;
    u_long fid; 

    if (fhandle == NULL) return ROOT_FID;

    /* if the handle does not match with the magic number,
       it's a root directory.  save it as a root handle. */
    for (i = 0 ; i < 8 ; i++) {
	if (fhandle[i] != magic[i]) {
	    /* save the root handle  */
	    bcopy (fhandle, (char*)root_handle, sizeof(root_handle));
	    printf ("fhandle_to_fid () returns root (%d)\n", ROOT_FID);
	    
	    return ROOT_FID;
	}
    }

    /* magic matches, so it is not a root directory.
       get the fid and return.
       I just cast it here ... since my client and server run on the same host,
       I shouldn't thave the endian problem.  */

    bcopy (fhandle + 8, &fid, sizeof(fid));
    printf ("fhandle_to_fid () returns %d\n", fid);

    return fid; 
}

/*
  readdir on smartcard
*/
readdirres *
scfs_readdir (readdirargs *ap)

{
    static readdirres ret;
    int i;
    u_long dir_fid; /* fid of ap->dir, which is a directory in which
		       readdir() is called. */
    file_ent *dir_fe; 
    
    static entry entrytab[MAXENTRIES];	/* just deal w/ it staticaly */
    long cookie = 0;

    dir_fid = fhandle_to_fid (ap->dir.data); 
    dir_fe = fid_to_fe (dir_fid);
    
    /* entry for "." */
    entrytab[0].name = (char *)malloc (4);
    strncpy (entrytab[0].name, ".\0\0\0", 4);
    entrytab[0].fileid = dir_fe->fid;
    cookie++;
    bcopy(&cookie, entrytab[0].cookie, sizeof(long));
    entrytab[0].nextentry = &(entrytab[1]);
    
    /* entry for ".." */
    entrytab[1].name = (char *)malloc (4);
    strncpy (entrytab[1].name, "..\0\0", 4);
    entrytab[1].fileid = dir_fe->fid;
    cookie++;
    bcopy(&cookie, entrytab[1].cookie, sizeof(long));
    entrytab[1].nextentry = &(entrytab[2]);
    
    /* entry for ".i" */
    for (i = 0 ; i < dir_fe->num_children ; i++ ) {
	entrytab[i + 2].name = (char *)malloc (4);
	bcopy (dir_fe->child[i]->name, entrytab[i + 2].name, 2);
	entrytab[i + 2].name[2] = entrytab[i + 2].name[3] = 0; 
    
	entrytab[i + 2].fileid = dir_fe->child[i]->fid; 
	cookie++;
	bcopy(&cookie, entrytab[i + 2].cookie, sizeof(long));
	if (i < dir_fe->num_children - 1) 
	    entrytab[i + 2].nextentry = &(entrytab[i + 3]);
	else
	    entrytab[i + 2].nextentry = 0; 
    }

    ret.readdirres_u.reply.entries = &(entrytab[0]);
    ret.readdirres_u.reply.eof = TRUE;
	
    return &ret; 
}

void print_attr(fattr *fa)
{
    printf ("print_attr.  type %d, mode %o, nlink %d, uid %d, gid %d\n",
	    fa->type, fa->mode, fa->nlink, fa->uid, fa->gid);
    printf ("             size %d, blocksize %d, rdev %d, blocks %d\n",
	    fa->size, fa->blocksize, fa->rdev, fa->blocks);
    printf ("             fsid %d, fileid %d, atime %d.%d, mtime %d.%d ctime %d.%d\n",
	    fa->fsid, fa->fileid, fa->atime.seconds, fa->atime.useconds,
	    fa->mtime.seconds, fa->mtime.useconds,
	    fa->ctime.seconds, fa->ctime.useconds);

    return; 
}

/* fill attributes, which has the following structure. 
   
   struct fattr {
       ftype type;
       u_int mode;
       u_int nlink;
       u_int uid;
       u_int gid;
       u_int size;
       u_int blocksize;
       u_int rdev;
       u_int blocks;
       u_int fsid;
       u_int fileid;
       nfstime atime;
       nfstime mtime;
       nfstime ctime;
   };
  */
#define FS_BLOCK_SIZE 4096 /* doesn't mean much */
int fe_to_fattr (fattr *attr, file_ent *fe)
{
    assert (attr);
    assert (fe);
    
    /* fill type and mode */
    if (fe->type == SCFS_TYPE_ELFILE) {
	attr->type = NFREG;
	attr->mode = 0100777;
    }
    else if (fe->type == SCFS_TYPE_DIR) {
	attr->type = NFDIR;
	attr->mode = 040777;
    } else {/* bad type */
	return 1; 
    }
    attr->nlink = 1;
    attr->uid = 0;
    attr->gid = 0;
    attr->size = fe->l_size; 
    attr->blocksize = FS_BLOCK_SIZE; 
    attr->rdev = 0;
    attr->blocks = fe->l_size / FS_BLOCK_SIZE;
    attr->fsid = 1;
    attr->fileid = fe->fid;
    attr->atime.seconds = attr->atime.useconds = 0; 
    attr->mtime.seconds = attr->mtime.useconds = 0;
    attr->ctime.seconds = attr->ctime.useconds = 0; 
    
    return 0; 
}

/* As of now, it sets only size, as it is the only thing I care. */
int sattr_to_fe (file_ent *fe, sattr *attr)
{
    assert (fe);
    assert (attr);

    if (attr->size > fe->p_size) {
	printf ("sattr_to_fe : size %d too big\n", fe->p_size);
	return 1; 
    }
    fe->l_size = attr->size; 
    
    return 0; 
}

/* set attirubte.

   get fid and file_ent of ap->file
   then set ap->attributes to fe */

attrstat *
scfs_setattr (sattrargs *ap)
{
    static attrstat ret; 
    int rv;
    file_ent *fe;
    u_long fid;

    assert (ap);

    /* get the file entry */
    fid = fhandle_to_fid (ap->file.data);
    fe = fid_to_fe (fid);
    if (fe == NULL) {
	ret.status = NFSERR_STALE;
	return (&ret);
    }

    rv = sattr_to_fe (fe, &(ap->attributes)); 

    if (rv != 0) {
	ret.status = NFSERR_FBIG; 
	return (&ret);
    }

    /* fill the reply argument and return. */
    
    rv = fe_to_fattr(&ret.attrstat_u.attributes, fe);
    if (rv != 0) {
	ret.status = NFSERR_STALE;
	return (&ret); 
    }
    ret.status = NFS_OK; 
    return (&ret);
}

/* lookup on smartcard

   convert ap->dir to fid
   convert fid to fe
   find ap->name in fe->child
   get child's fid
   get child's attr data
   return
 */

diropres *
scfs_lookup (diropargs *ap)
{
    static diropres ret; 
    int i, rv;
    u_long dir_fid; /* fid of ap->dir, which is a directory in which
		       lookup() is called. */
    file_ent *dir_fe; /* file entry for parent */
    file_ent *child_fe; /* file entry for child */
    fattr * attr = &(ret.diropres_u.diropres.attributes);
    int name_len;

    name_len = strlen (ap->name);

    if (name_len != 1 && name_len != 2) {
	/* scfs name has got to be 2 byte long, or ".". */
	ret.status = NFSERR_NOENT;
	return (&ret);
    }
    
    /* get dir's file entry */
    dir_fid = fhandle_to_fid (ap->dir.data);
    printf ("dir_fid = %d\n", dir_fid);
    dir_fe = fid_to_fe (dir_fid);
    if (dir_fe == NULL) {
	ret.status=NFSERR_STALE;
	return (&ret);
    }
    /* take care of "." */
    if (name_len == 1) {
	if (strncmp (ap->name, ".", 1) ==0) {
	    child_fe = dir_fe; 
	    goto lookup_found_child; 
	} else {
	    ret.status = NFSERR_NOENT;
	    return (&ret);
	}
    }

    /* take care of ".." */
    if (name_len == 2) {
	if (strncmp (ap->name, "..", 2) ==0) {
	    if (dir_fe->parent == NULL) {
		printf (".. from root\n");
		/*rootgetattr (attr);
		ret.status = NFS_OK;*/
		ret.status = NFSERR_STALE;
		return (&ret);
	    }
	    else
		child_fe = dir_fe->parent;

	    goto lookup_found_child; 
	}
    }
    
    /* find the child */
    i = find_child_by_name (dir_fe, ap->name);
    if (i < 0) {
	/* child not found.  return error. */
	ret.status = NFSERR_NOENT;
	return (&ret);
    }
    
    /* found child */
    child_fe = dir_fe->child[i];

 lookup_found_child:
    fid_to_fhandle (ret.diropres_u.diropres.file.data,
		    child_fe->fid);

    rv = fe_to_fattr (attr, child_fe);
    if (rv != 0) {
	ret.status = NFSERR_STALE;
	return (&ret); 
    }
    printf ("name %s\n", child_fe->name);
    print_attr(attr);
    
    ret.status = NFS_OK;
    return (&ret);
}

/* 
 */

attrstat *
scfs_getattr (nfs_fh *ap)
{
    static attrstat ret;
    int i, rv;
    u_long fid;
    file_ent *fe;
    fattr * attr = &(ret.attrstat_u.attributes);

    for (i = 0 ; i < NFS_FHSIZE; i++ )
	printf ("%02x ", (unsigned char)(ap->data[i]));
    printf ("\n");

    fid = fhandle_to_fid (ap->data);
    assert (fid > 0 && fid <= scconfig.num_entries);

    /* should I do something special for root? */
       
    /*if (fid == ROOT_FID) {
	ret.status = NFS_OK;
	rootgetattr (attr);
	print_attr(attr);
	return (&ret);
    }*/
    fe = fid_to_fe (fid);
    if (fe == NULL) {
	ret.status = NFSERR_STALE;
	return (&ret);
    }

    rv = fe_to_fattr (&(ret.attrstat_u.attributes), fe);
    if (rv != 0) {
	ret.status = NFSERR_STALE;
	return (&ret); 
    }

    printf ("name %s\n", fe->name);
    print_attr(&(ret.attrstat_u.attributes));
    
    ret.status = NFS_OK;  
    return (&ret); 
}
/* read file
   
   get fid and file_ent.  
   calculate actual length to read.
   select the file.
   read the file ... but have to loop in case the file is longer than
   0xff byte (smartcard's max data unit).
   fill the status. 
 */
readres *
scfs_read (readargs *ap)
{
    static readres ret;
    static u_char buf[8192];
    u_long fid;
    file_ent *fe;
    u_int offset, len;
    u_int tmp_len; /* length per smartcard call.
		    remember that a smartcard can only
		    send / receive up to 255 byte. */
    u_int tmp_offset; /* offset moves on each smartcard call. */
    int i, rv;
    u_int r1, r2; 
    
    /* get the file entry */
    fid = fhandle_to_fid (ap->file.data);
    printf ("fid = %d\n", fid);
    fe = fid_to_fe (fid);
    if (fe == NULL) {
	ret.status=NFSERR_STALE;
	return (&ret);
    }
    
    offset = ap->offset;

    if (offset >= fe->l_size) {
	ret.status = NFSERR_FBIG;
	return (&ret);
    }

    len = offset + ap->count < fe->l_size ? 
	ap->count : fe->l_size - offset;

    /* select the file to be read */
    rv = godown (fe);
    if (rv != 0) {
	printf ("could not go down to %02x.%02x\n",
		fe->name[0], fe->name[1]);
	ret.status = NFSERR_NOENT; 
	return (&ret);
    }

    /* loop to handle a long message */
    tmp_offset = offset; 

    /* to do ceiling, I have to do a weird thing */
    for (i = 0 ; i < (len - 1) / SC_MAX_LEN + 1; i++) {
	tmp_len = len - i * SC_MAX_LEN < SC_MAX_LEN ? 
	    len - i * SC_MAX_LEN : SC_MAX_LEN ;
	
	rv = scread (fd, scconfig.cla_readbinary, 0xb0,
		     (tmp_offset >> 8) & 0xff, tmp_offset & 0xff,
		     tmp_len, buf + i * SC_MAX_LEN, &r1, &r2);
	
	if (rv < 0 || r1 != 0x90 || r2 != 0x00) {/* read failed */
	    printf ("scread failed.  rv %d, r1 %02x, r2 %02x\n",
		    rv, r1, r2);
	    if (r1 == 0x69 || r2 == 0x82) {/* permission denied */
		ret.status = NFSERR_PERM; 
		return (&ret);
	    } else {/* something else */
	      ret.status = NFSERR_STALE;
		return (&ret);
	    }
	}
	tmp_offset += tmp_len; 
    }
    /* succeeded.  fill the reply argument and return. */

    rv = fe_to_fattr(&ret.readres_u.reply.attributes, fe);
    if (rv != 0) {
      ret.status = NFSERR_STALE;
      return (&ret); 
    }
    
    ret.readres_u.reply.data.data_len = len; 
    ret.readres_u.reply.data.data_val = buf;
    ret.status = NFS_OK;

    return (&ret); 
}

/* find child in parent by name
   
   return the index if found. 
   return -1 if not. 
 */
int find_child_by_name (file_ent *par_fe, u_char *cld_name)
{
    int i;
    
    if (strlen (cld_name) != 2) {
	return -1; 
    }
    
    /* I'm the i'th child of the parent */
    for (i = 0 ; i < par_fe->num_children ; i++ ) {
	printf ("pcn %s, cld_name %s, strcmp %d\n",
		par_fe->child[i]->name, cld_name, 
		strncmp (par_fe->child[i]->name, cld_name, 2));
	if (strncmp (par_fe->child[i]->name, cld_name, 2) == 0) break;
    }
    if (i == par_fe->num_children){
	printf ("cannot find the child (name %s) in parent directory 1.\n",
		cld_name);
	return -1;
    }

    return i;
}

/* find child in parent
   
   return the index if found. 
   return -1 if not. 
 */
int find_child_in_parent (file_ent *par_fe, file_ent *cld_fe)
{
  int i;

  /* I'm the i'th child of the parent */
  for (i = 0 ; i < par_fe->num_children ; i++ ) {
      if (par_fe->child[i] == cld_fe) break;
  }
  if (i == par_fe->num_children){
      printf ("cannot find the child (name %s) in parent directory 2.\n",
	      cld_fe->name);
      return -1;
  }
  
  return i;
}

/* write file
   
   get fid and file_ent.  
   calculate actual length to write.
   error if it overflows. 
   select the file.
   write the file ... but have to loop in case the file is longer than
   0xff byte (smartcard's max data unit).
   fill the status. 
 */
attrstat *
scfs_write (writeargs *ap)
{
    static attrstat ret;
    u_long fid;
    file_ent *fe;
    u_int offset, len;
    u_int tmp_len; /* length per smartcard call.
		    remember that a smartcard can only
		    send / receive up to 255 byte. */
    u_int tmp_offset; /* offset moves on each smartcard call. */
    int i, rv;
    u_int r1, r2; 
    
    /* get the file entry */
    fid = fhandle_to_fid (ap->file.data);
    printf ("fid = %d\n", fid);
    fe = fid_to_fe (fid);
    if (fe == NULL) {
	ret.status=NFSERR_STALE;
	return (&ret);
    }

    offset = ap->offset;
    len = ap->data.data_len; 

    /* check offset & len ranges */
    if (offset + len > fe->p_size) {
	/* write overflow */
	ret.status = NFSERR_FBIG;
	return (&ret);
    }
    if (offset > fe->l_size) {
	/* offset out of range */
	ret.status = NFSERR_FBIG;
	return (&ret);
    }

    /* select the file to be written */
    rv = godown (fe);
    if (rv != 0) {
	printf ("could not go down to %02x.%02x\n",
		fe->name[0], fe->name[1]);
	ret.status = NFSERR_NOENT; 
	return (&ret);
    }

    /* loop to handle a long message */
    tmp_offset = offset; 

    /* to do ceiling, I have to do a weird thing */
    for (i = 0 ; i < (len - 1) / SC_MAX_LEN + 1; i++) {
	tmp_len = len - i * SC_MAX_LEN < SC_MAX_LEN ? 
	    len - i * SC_MAX_LEN : SC_MAX_LEN ;
	
	rv = scwrite (fd, scconfig.cla_updatebinary, 0xd6,
		     (tmp_offset >> 8) & 0xff, tmp_offset & 0xff,
		     tmp_len, ap->data.data_val + i * SC_MAX_LEN, &r1, &r2);
	
	if (rv < 0 || r1 != 0x90 || r2 != 0x00) {/* read failed */
	    printf ("scwrite failed.  rv %d, r1 %02x, r2 %02x\n",
		    rv, r1, r2);
	    if (r1 == 0x69 || r2 == 0x82) {/* permission denied */
		ret.status = NFSERR_PERM; 
		return (&ret);
	    } else {/* something else */
	      ret.status = NFSERR_STALE;
		return (&ret);
	    }
	}
	tmp_offset += tmp_len; 
    }
    /* succeeded. */
    if (offset + len > fe->l_size) {
	/* size changed.  modify fe */
	printf ("size changed.  modify fe.\n");
	fe->l_size = offset + len; 
	/* update .i */
	rv = update_doti (fe);
	if (rv != 0) {
	    printf ("updating .i after write() failed.\n");
	    ret.status = NFSERR_IO; 
	    return (&ret);
	}
    }
    
    /* fill the reply argument and return. */
    rv = fe_to_fattr(&ret.attrstat_u.attributes, fe);
    if (rv != 0) {
      ret.status = NFSERR_STALE;
      return (&ret); 
    }
    
    ret.status = NFS_OK;

    return (&ret); 
}

cfsstat *
scfs_verify (scfs_verify_args *ap)
{
    static cfsstat ret;
    int r1, r2, i, rv;
    
    printf ("admproc_verify_2 (type %d, keynum %d, data ",
	    ap->type, ap->keynum);
    for (i = 0 ; i < ap->len ; i ++ )
	printf ("%02x ", (unsigned char)(ap->data[i])); 
    printf (")\n");
    
    /* make sure verify key APDU exists */
    if (scconfig.cla_verifykey == CLA_NOSUPP) {
	printf ("verify KEY APDU is not supported in this smartcard.\n");
	ret = CFSERR_NOINS;
	return (&ret); 
    }

    /* then call it */
    rv = scwrite(fd, scconfig.cla_verifykey, 0x2a, 0, ap->keynum,
		 ap->len, ap->data, &r1, &r2);

    if (rv < 0 || r1 != 0x90 || r2 != 0x00) {/* verify failed */
	printf ("scwrite failed.  rv %d, r1 %02x, r2 %02x\n",
		rv, r1, r2);
	ret = CFSERR_NOINS;
	return (&ret);
    }
    ret = CFS_OK; 
    return (&ret); 
}

/* create a file actually in the card */
static int create_in_card (u_char *name, u_long size, enum file_type type)
{
    u_char buf[256];
    int rv, r1 = 0, r2 = 0, i;

    assert (type == SCFS_TYPE_ELFILE || type == SCFS_TYPE_DIR);

    printf("create_in_card(name %s, size %d, type %d)\n",
	   name, size, type);
    
    if (scconfig.create_type == CREATE_TYPE_MULTIFLEX){
	printf("create_in_card() - Multiflex \n");
	/* MultiFlex Type */
	
	buf[0] = (u_char)0xff; /* unused */
	buf[1] = (u_char)0xff; /* unused */
	buf[2] = (u_char)((size >> 8) & 0xff); /* file size */
	buf[3] = (u_char)(size & 0xff);
	buf[4] = (u_char)(name[0]); /* fid */
	buf[5] = (u_char)(name[1]);
	if (type == SCFS_TYPE_ELFILE)
	    buf[6] = (u_char)0x01; /* type, transparent file = 0x01 */
	else if (type == SCFS_TYPE_DIR)
	    buf[6] = (u_char)0x38; /* type, directory = 0x38 */
	else
	    return 4; 
	buf[7] = (u_char)0xff; /* unused */
	buf[8] = (u_char)0;
	buf[9] = (u_char)0;
	buf[10] = (u_char)0;
	buf[11] = (u_char)0x01; /* status, currently unblocked */
	buf[12] = (u_char)0x05; /* 5 byte data follows */
	buf[13] = (u_char)0x03; /* ? */
	buf[14] = (u_char)0x00; /* ? */
	buf[15] = (u_char)0x00; /* ? */
	buf[16] = (u_char)0x02; /* ? */
	buf[17] = (u_char)0x00; /* ? */

	printf ("create buffer: ");
	printf ("%02x e0 00 00 12\n", scconfig.cla_create);
	for (i = 0 ; i < 18 ; i ++)
	    printf ("%02x ", buf[i]);
	printf ("\n");
	
	rv = scwrite (fd, scconfig.cla_create, 0xe0,
		      0x00, 0x00, 0x12,
		      buf, &r1, &r2);
    }
    else if (scconfig.create_type == CREATE_TYPE_CYBERFLEXACCESS){
	printf("create_in_card() - Cyberflex Access \n");
	buf[0] = size >> 8 & 0xff; /* file size */
	buf[1] = size & 0xff;
	buf[2] = name[0]; /* fid */
	buf[3] = name[1];
	if (type == SCFS_TYPE_ELFILE)
	    buf[4] = (u_char)0x02; /* type, transparent file = 0x02 */
	else if (type == SCFS_TYPE_DIR)
	    buf[4] = (u_char)0x20; /* type, directory = 0x20 */
	else
	    return 5; 
	buf[5] = (u_char)0x01; /* status */
	buf[6] = (u_char)0x00; /* ACL */
	buf[7] = (u_char)0x00;
	buf[8] = (u_char)0xff;
	buf[9] = (u_char)0x00;
	buf[10] = (u_char)0x00;
	buf[11] = (u_char)0x00;
	buf[12] = (u_char)0x00;
	buf[13] = (u_char)0x00;
	buf[14] = (u_char)0x00;
	buf[15] = (u_char)0x00;

	printf ("buf: %02x e0 00 00 10 ", scconfig.cla_create);
	for (i = 0 ; i < 0x10 ; i++)
	    printf ("%02x ", buf[i]);
	printf ("\n");
	
	rv = scwrite(fd, scconfig.cla_create, 0xe0,
		     0x00, 0x00, 0x10,
		     buf, &r1, &r2);
    } else {
	printf ("create_in_card() error - card not supported.\n");
	return 1;
    }
    
    if (r1 == 0x69 || r2 == 0x82) {
	printf ("create_in_card() error - permission denied.\n");
	return 2;
    }
    if (rv == -1 || r1 != 0x90 || r2 != 0x00) {
	printf ("create error.  smartcard returns %x.%x\n", r1, r2);
	return 3;
    }

    return 0; 
}

/* remove a file actually in the card */
static nfsstat remove_in_card (u_char *name)
{
    int rv, r1 = 0, r2 = 0; 

    assert (name);

    printf ("remove_in_card (%s)  cla %x\n", name, scconfig.cla_deletefile);
    
    rv = scwrite (fd, scconfig.cla_deletefile, 0xe4,
		  0x00, 0x00, 0x2, name, &r1, &r2);

    if (r1 == 0x69 && r2 == 0x82) {
	printf ("remove_in_card() error - permission denied.\n");
	return NFSERR_ACCES;
    }
    if (r1 == 0x6a || r2 == 0x82) {
	printf ("remove_in_card() error - not found.\n");
	return NFSERR_NOENT;
    }
    if (rv == -1 || r1 != 0x90 || r2 != 0x00) {
	printf ("remove error.  smartcard returns %x.%x\n", r1, r2);
	return NFSERR_IO;
    }
    
    return NFS_OK; 
}
     
/*
  godown() to the parent
  create a file on card
  create entry for the file
  set attribute to the file
  update .i
  get created file's attribute
  get fhandle
  return these two
 */
extern unsigned char cur_fid;

diropres *scfs_create (createargs *ap, enum file_type type)
{
    static diropres ret;
    u_char name[256];
    u_long size;
    u_long par_fid;  /* fid of ap->dir, which is a directory in which
		        creat() is called. */
    u_long cld_fid; 
    file_ent *par_fe = NULL; /* file entry for the parent */
    file_ent *cld_fe = NULL; /* file entry for the child to be created */
    file_ent *cld_doti_fe = NULL; /* file entry for the .i of the child */
    fattr * attr = &(ret.diropres_u.diropres.attributes);
    int rv, i, r1, r2;
    u_char buf[256];

    assert (type == SCFS_TYPE_ELFILE || type == SCFS_TYPE_DIR);
    assert (strlen(ap->where.name) < 256);
    assert (ap->where.name); 
    rv = sscanf (ap->where.name, "%s %x", name, &size);
    printf ("sscanf returns %d\n", rv);
    assert (name);

    printf ("scfs_create (name %s, size %d)\n",
	    name, size);

    /* get parent dir's file entry */
    par_fid = fhandle_to_fid (ap->where.dir.data);
    printf ("par_fid = %d\n", par_fid);
    par_fe = fid_to_fe (par_fid);
    if (par_fe == NULL) {
	ret.status = NFSERR_STALE;
	return (&ret);
    }

    /* does the parent have room? */
    if (par_fe->num_children >= MAX_CHILDREN + 1) {
	printf ("scfs_create: %02x.%02x already has maximum %d files.",
		par_fe->name[0], par_fe->name[1],
		par_fe->num_children);

	ret.status = NFSERR_NOSPC;  
	return (&ret); 
    }
    
    /* select the parent directory */
    rv = godown (par_fe);
    if (rv != 0) {
	printf ("could not go down to %02x.%02x\n",
		par_fe->name[0], par_fe->name[1]);
	ret.status = NFSERR_NOENT; 
	return (&ret);
    }

    if (strlen (name) != 2) {
	ret.status = NFSERR_NAMETOOLONG;
	return (&ret);
    }

    /* create the file on smartcard */
    rv = create_in_card (name, size, type);
    if (rv != 0) {
	printf ("failed in creating file %s (%02x.%02x), size %d\n",
		name, name[0], name[1], size);
	if (rv == 2) 
	    ret.status = NFSERR_PERM;
	else 
	    ret.status = NFSERR_IO;
	
	return (&ret);
    }

    /* create entry for the file */
    cld_fe = new_file_ent (name, type, par_fe);
    par_fe->child[par_fe->num_children] = cld_fe;
    par_fe->num_children++;

    /* since I have just created the file, make sure I set the cur_fid 
       correctly. */
    rv = open_file (fd, name);
    if (rv != 0) {
      printf ("cannot select %02x.%02x which I have just created.\n", 
	      name[0], name[1]);
      ret.status = NFSERR_NOENT; 
      return (&ret);
    }
    cur_fid = cld_fe->fid; 
    
    /* set stat */
    cld_fe->p_size = size;
    if (type == SCFS_TYPE_DIR) {
	cld_fe->l_size = size;
    }
    else {
	cld_fe->l_size = 0;
    }
    
    /* update .i of the parent */
    rv = update_doti (cld_fe);
    if (rv != 0) {
	printf ("updating .i after write() failed.\n");
	ret.status = NFSERR_IO; 
	return (&ret);
    }

    /* if the created file is a directory, create .i */
    if (type == SCFS_TYPE_DIR) {
	/* select the created dir */
	rv = godown (cld_fe);
	if (rv != 0) {
	    printf ("could not go down to %02x.%02x\n",
		    cld_fe->name[0], cld_fe->name[1]);
	    ret.status = NFSERR_NOENT; 
	    return (&ret);
	}

	/* crate .i on card */
	rv = create_in_card (doti_name, MAX_CHILDREN * DOTI_LEN,
			     SCFS_TYPE_ELFILE);
	if (rv != 0) {
	    printf ("failed in creating file %s (%02x.%02x), size %d\n",
		    name, name[0], name[1], size);
	    if (rv == 2) 
		ret.status = NFSERR_PERM;
	    else 
		ret.status = NFSERR_IO;
	    
	    return (&ret);
	}

	/* create the entry for the .i */
	cld_fe->num_children = 1;
	cld_doti_fe = new_file_ent(doti_name, SCFS_TYPE_ELFILE, cld_fe);
	cld_fe->child[0] = cld_doti_fe; 
	cld_doti_fe->p_size = MAX_CHILDREN * DOTI_LEN;
	cld_doti_fe->l_size = MAX_CHILDREN * DOTI_LEN;

	/* since I have just created the file, make sure I set the cur_fid 
	   correctly. */
	rv = open_file (fd, doti_name);
	if (rv != 0) {
	  printf ("cannot select %02x.%02x which I have just created.\n", 
		  cld_fe->name[0], cld_fe->name[1]);
	  ret.status = NFSERR_NOENT; 
	  return (&ret);
	}
	cur_fid = cld_doti_fe->fid; 
	
	/* fill zero in the .i */
	bzero (buf, MAX_CHILDREN * DOTI_LEN); 

	r1 = r2 = 0;
	printf ("will scwrite : %02x d6 00 00 %02x\n",
		scconfig.cla_updatebinary, (u_char)((MAX_CHILDREN -2) * DOTI_LEN));
	rv = scwrite(fd, scconfig.cla_updatebinary, 0xd6,
		     0x0, 0x0, (u_char)((MAX_CHILDREN - 2) * DOTI_LEN), 
		     buf, &r1, &r2);

	if (rv < 0 || r1 != 0x90 || r2 != 0x00) {/* write failed */
	    printf ("scwrite failed.  rv %d, r1 %02x, r2 %02x\n",
		    rv, r1, r2);
	    if (r1 == 0x69 || r2 == 0x82) {/* permission denied */
		ret.status = NFSERR_PERM; 
		return (&ret);
	    } else {/* something else */
		ret.status = NFSERR_STALE;
		return (&ret);
	    }
	}
    }
    
    /* fill the reply argument and return. */
    rv = fe_to_fattr(attr, cld_fe);
    if (rv != 0) {
	ret.status = NFSERR_STALE;
	return (&ret); 
    }

    /* make fhandle of the created file */
    cld_fid = cld_fe->fid;
    fid_to_fhandle (ret.diropres_u.diropres.file.data, cld_fid); 
    
    ret.status = NFS_OK;  
    return (&ret); 
}

/* update .i file after removing a file.

   par_fe : parent file entry
   cld_fe : child file entry (file to be removed)
   ith    : the child is th ith'th child of the parent
 */
nfsstat update_doti_after_remove (file_ent *par_fe, file_ent *cld_fe,
				  int ith)
{
    int i, rv, r1, r2, offset;
    u_char zero_buf [DOTI_LEN];
    
    assert (par_fe);
    assert (cld_fe);
    assert (par_fe->num_children > 1);
    
    /* remove child from scfs database */
    free (par_fe->child[ith]);
    par_fe->num_children--;
    for (i = ith; i < par_fe->num_children; i++) {
	par_fe->child[i] = par_fe->child[i+1];
    }

    /* update .i of parent */
    for (i = ith; i < par_fe->num_children; i++) {
	rv = update_doti (par_fe->child[i]);
	if (rv != 0) {
	    printf ("update_doti for %s failed.\n", par_fe->child[i]->name);
	}
    }

    /* put NULL entry in .i */
    bzero (zero_buf, DOTI_LEN);
    rv = godown (par_fe->child[0]);
    if (rv != 0) {
	printf ("cannot select .i of %s.\n", par_fe->name);
	return NFSERR_NOENT; 
    }
    offset = (par_fe->num_children - 1) * DOTI_LEN; /* idx - 1 because of .i */
    rv = scwrite (fd, scconfig.cla_updatebinary, 0xd6,
		  (offset >> 8) & 0xff, offset & 0xff,
		  DOTI_LEN, zero_buf, &r1, &r2);
    if (rv < 0 || r1 != 0x90 || r2 != 0x00) {/* read failed */
	printf ("scwrite failed.  rv %d, r1 %02x, r2 %02x\n",
		rv, r1, r2);
	return NFSERR_IO; 
    }
    return NFS_OK; 
}
     
/*
  remove a file or a directory

  find the child
  if (type == DIR) {
      check if the directory is empty.  if not, error.
  }
  if (type == DIR) {
      select the directory
      remove the .i file in it
  }
  select the parent
  remove the file
  update .i of parent
*/
nfsstat *
scfs_remove (diropargs *ap, enum file_type type)
{
    static nfsstat ret;
    int ith, rv;
    u_long dir_fid; /* fid of ap->dir, which is a directory in which
		       remove() is called. */
    file_ent *dir_fe; /* file entry for parent */
    file_ent *child_fe; /* file entry for child to delete */
    
    assert (ap);

    /* select the parent */
    dir_fid = fhandle_to_fid (ap->dir.data);
    printf ("dir_fid = %d\n", dir_fid);
    dir_fe = fid_to_fe (dir_fid);
    if (dir_fe == NULL) {
	ret = NFSERR_STALE;
	return (&ret);
    }    

    /* find the child */
    ith = find_child_by_name (dir_fe, ap->name);
    if (ith < 0) {
	/* child not found.  return error. */
	ret = NFSERR_NOENT;
	return (&ret);
    }

    /* found the child */
    child_fe = dir_fe->child[ith]; 
    assert (child_fe); 

    if (type == SCFS_TYPE_DIR) {
	printf ("scfs_remove (directory)\n");
	/* will check if the directory is empty. */

	if (child_fe->num_children > 1) {
	    /* 1 for .i */
	    ret = NFSERR_EXIST;
	    return (&ret);
	}
	/* select the dir to delete */
	rv = godown (child_fe);
	if (rv != 0) {
	    printf ("could not go down to %02x.%02x\n",
		    child_fe->name[0], child_fe->name[1]);
	    ret = NFSERR_NOENT; 
	    return (&ret);
	}
	/* remove .i in it */
	rv = remove_in_card (doti_name);
	if (rv == NFSERR_NOENT) {
	    /* .i already doesn't exist.  fine, go ahead. */
	    printf (".i already doesn't exist.  fine, go ahead.\n");
	}
	else if (rv != NFS_OK) {
	    ret = rv; 
	    return (&ret);
	}
    } else {
	printf ("scfs_remove (elementary file)\n");
    }
    
    /* select the parent */
    rv = godown (dir_fe);
    if (rv != 0) {
	printf ("could not go down to %02x.%02x\n",
		dir_fe->name[0], dir_fe->name[1]);
	ret = NFSERR_NOENT; 
	return (&ret);
    }

    /* remove the file */
    rv = remove_in_card (ap->name);
    if (rv == NFSERR_NOENT) {
	/* .i already doesn't exist.  fine, go ahead. */
	printf ("%s already doesn't exist.  fine, go ahead.\n", ap->name);
    }
    if (rv != NFS_OK) {
	ret = rv; 
	return (&ret);
    }

    /* update .i of parent */
    rv = update_doti_after_remove (dir_fe, child_fe, ith);

    ret = rv;
    return (&ret);
}
#ifdef NIXX

arla_warnx (ADEBMSG, "Mount OK. :)");
  
  return 0; 
}
  

/* called upon unmount.  free scdir struct, close fd */

int scfs_unmount()
{
  /*arla_warnx (ADEBMSG, "closing fd = %d", fd);*/
  scclose (fd);

  /*arla_warnx (ADEBMSG, "scfs_unmount");*/
  scdir_free();
  
  return 0;
}
   
/* take 2 characters, and convert it to a number (char) */

int a2h(char c, char d, unsigned char *val)
{
  *val = 0;
  if ((c >= '0') && (c <= '9')) *val += c - '0';
  else if ((c >= 'a') && (c <= 'f')) *val += c - 'a' + 10;
  else if ((c >= 'A') && (c <= 'F')) *val += c - 'A' + 10;
  else return -1;
  *val <<= 4;
  if ((d >= '0') && (d <= '9')) *val += d - '0';
  else if ((d >= 'a') && (d <= 'f')) *val += d - 'a' + 10;
  else if ((d >= 'A') && (d <= 'F')) *val += d - 'A' + 10;
  else return -1;
  return(*val);
}

/* Read smartcard.  Fill buf.  Return # of bytes read.*/

int scfs_read(unsigned char *fid, int offset, int length, unsigned char *buf, int rec_flag, unsigned char *parent_fid, dev_t t_dev)
{
  struct scdnode *node;
  int sw1, sw2, rv, tmp_offset, tmp_length, i;

  /*arla_warnx (ADEBMSG, "scfs_read on tty %s.", scfs_ttyname(t_dev));*/
  
  if (rec_flag == SCFS_IS_NOT_RECORD) {
    /* This is not record ... do as usual. */
    if ((node = get_scdnode(fid)) == NULL) {
      arla_warnx (ADEBMSG, "cannot find %02x.%02x", fid[0], fid[1]);
      return -1;
    }

    /* offset out of range */
    if (offset > node->fileent.l_size) return -1;
    if (offset == node->fileent.l_size) return 0;
        
    /* if requested length is longer than logical length of the file,
       read local length byte of data. */
    length = length + offset > node->fileent.l_size ?
      node->fileent.l_size - offset : length;
    
    if (godown(node) == -1) return -1;
    
    /* read it */
  readagain:
    /* loop to handle a long message*/
    tmp_offset = offset;
    for (i=0; i<length/SCMaxRWLen+1; i++){
      tmp_length = length - i*SCMaxRWLen > SCMaxRWLen
        ? SCMaxRWLen : length - i*SCMaxRWLen;
      rv = scread(fd, scconfig.cla_readbinary, 0xb0, (tmp_offset >> 8) & 0xff,
                  tmp_offset & 0xff, tmp_length, buf, &sw1, &sw2);
      if (rv == -1 || sw1 != 0x90 || sw2 != 0x00) {
        if (sw1 == 0x69 && sw2 == 0x82) {
          char *pin;
          arla_warnx (ADEBWARN, "permission denied.  input PIN");
          if (scconfig.multiflexpin == 0) {
            arla_warnx (ADEBWARN, "This smartcard does not have MultiFlex type PIN support");
          } else {
            /*scfs_tcl_readpin(pin);*/
            pin = getpin(scfs_ttyname(t_dev), "Input PIN: ");
            /*arla_warnx (ADEBMSG, "Got PIN %02x %02x %02x %02x %02x %02x %02x %02x",
                        pin[0], pin[1], pin[2], pin[3],
                        pin[4], pin[5], pin[6], pin[7]);*/
            rv = scwrite(fd, scconfig.cla_verifypin, 0x20, 0, 1, 8, pin, &sw1, &sw2);
            if (rv != -1 && sw1 == 0x90 && sw2 == 0x00) {
              /*arla_warnx (ADEBMSG, "PIN verified");*/
              goto readagain;
            } else {
              arla_warnx (ADEBERROR, "incorrect PIN");
              return -1; /* incorrect PIN.  return. */
            }
            arla_warnx (ADEBERROR, "incorrect PIN?");
            return -1; /* incorrect PIN.  return. */
          }
        } else {
          if (rv == 0) return rv; /* 0 byte read.  return 0. */
          return -1; /* error other than permission.  return. */
        }
      }
      tmp_offset += tmp_length;
      buf += tmp_length;
    }
  } else { /* This is record. */
    if ((node = get_scdnode(parent_fid)) == NULL) {
      arla_warnx (ADEBMSG, "cannot find %02x.%02x",
                  parent_fid[0], parent_fid[1]);
      return -1;
    }
    if (godown(node) == -1) return -1; /* go down to parent (=record file) */
  rec_readagain:
    rv = scread(fd, scconfig.cla_readrecord, 0xb2, fid[1] & 0xff, 0x04, length, buf, &sw1, &sw2);
    if (rv != -1 && sw1 == 0x90 && sw2 == 0x00) {
      return 0;
    } else if (sw1 == 0x69 && sw2 == 0x82) {
      char pin[16];
      arla_warnx (ADEBWARN, "permission denied.  input PIN");
      if (scconfig.multiflexpin == 0) {
        arla_warnx (ADEBWARN, "This smartcard does not have MultiFlex type PIN support");
        return -1;
      } else {
        scfs_tcl_readpin(pin);
        rv = scwrite(fd,scconfig.cla_verifypin, 0x20, 0, 1, 8, pin, &sw1, &sw2);
        if (rv != -1 && sw1 == 0x90 && sw2 == 0x00) {
          /*arla_warnx (ADEBMSG, "PIN verified");*/
          goto rec_readagain;
        } else {
          arla_warnx (ADEBERROR, "incorrect PIN");
          return -1;
        }
        arla_warnx (ADEBERROR, "incorrect PIN?");
        return -1;
      }
    }
  }

  return length;
}

/* Write to smartcard file.
   If the length is more than 0xff long, chop it into 0xff byte pieces. 
   Also, update directory entry of the parent.
 */

int scfs_write(unsigned char *fid, int offset, int length, char *buf, int rec_flag, unsigned char *parent_fid, dev_t t_dev)
{
  struct scdnode *node; 
  int rv, sw1, sw2, i, tmp_length, tmp_offset;

  assert(buf);
  
  /* offset out of range */

  if (rec_flag == SCFS_IS_NOT_RECORD) {
    /*arla_warnx (ADEBMSG, "scfs_write: not a record.\n");*/
    /* This is not record ... do as usual. */
    if ((node = get_scdnode(fid)) == NULL) {
      arla_warnx (ADEBMSG, "cannot find %02x.%02x", fid[0], fid[1]);
      return -1;
    }

    if (offset > node->fileent.l_size) {
      arla_warnx (ADEBMSG, "scfs_write: offset out of range 1.\n");
      return -1;
    }

    if (offset == node->fileent.l_size && offset != 0) {
      arla_warnx (ADEBMSG, "scfs_write: offset out of range 2.\n");
      return 0;
    }
    
    /* if requested length is longer than logical length of the file,
       read local length byte of data. */
    /*arla_warnx (ADEBMSG, "scfs_write: length in %d.\n", length);*/
    length = length + offset > node->fileent.p_size ?
      node->fileent.p_size - offset : length;
    /*arla_warnx (ADEBMSG, "scfs_write: length set %d.\n", length);*/
    /* select the file to be written */
    if (godown(node) == -1) {
      arla_warnx (ADEBMSG, "scfs_write: godown() failed.\n");
      return -1;
    }

    /* write it */
  writeagain:
    /* loop */
    tmp_offset = offset;
    for (i=0; i<length/SCMaxRWLen+1; i++){
      tmp_length = length - i*SCMaxRWLen > SCMaxRWLen
        ? SCMaxRWLen : length - i*SCMaxRWLen;
      /*arla_warnx (ADEBWARN, "scfs_write: now write it ...");*/
      rv = scwrite(fd, scconfig.cla_updatebinary, 0xd6,
                   (tmp_offset >> 8) & 0xff, tmp_offset & 0xff, tmp_length,
                   buf, &sw1, &sw2);
      if (rv == -1 || sw1 != 0x90 || sw2 != 0x00) {
        if (sw1 == 0x69 && sw2 == 0x82) {
          char *pin; 
          arla_warnx (ADEBWARN, "permission denied.  input PIN");
          if (scconfig.multiflexpin == 0) {
            arla_warnx (ADEBWARN, "This smartcard does not have MultiFlex type PIN support");
          } else {
            /*scfs_tcl_readpin(pin);*/
            pin = getpin(scfs_ttyname(t_dev), "Input PIN: ");
            /*arla_warnx (ADEBMSG, "Got PIN %02x %02x %02x %02x %02x %02x %02x %02x",
                        pin[0], pin[1], pin[2], pin[3],
                        pin[4], pin[5], pin[6], pin[7]);*/
            rv = scwrite(fd, scconfig.cla_verifypin, 0x20, 0, 1, 8, pin, &sw1, &sw2);
            if (rv != -1 && sw1 == 0x90 && sw2 == 0x00) {
              /*arla_warnx (ADEBMSG, "PIN verified");*/
              goto writeagain;
            } else {
              arla_warnx (ADEBERROR, "incorrect PIN");
              return EACCES; /* incorrect PIN.  return. */
            }
          }
        }
        arla_warnx (ADEBERROR, "write error.  smartcard returns %02x %02x",
                    sw1, sw2);
        if (sw1 == 0x6b && sw2 == 0x00)
          arla_warnx (ADEBERROR, "Offset out of range.");
        return -1;
      }
      tmp_offset += tmp_length;
      buf += tmp_length;
    }

    arla_warnx (ADEBWARN, "scfs_write: writing a file is done.");

    node->fileent.l_size = offset + length;

    /* update directory entry */
    if ((rv = scfs_update_dirent(node))) return rv;
  }
  return rv;
}

int scfs_verifypin(int insize, char *pin)
{
  int len, r1, r2;

  if (scconfig.cla_verifypin == CLA_NOSUPP) {
    arla_warnx (ADEBERROR, "verify PIN APDU is not supported in this smartcard.");
    return EINVAL;
  }
  /*
  for (i=0; i<insize; i++) {
    fprintf(stderr, "%02x ", pin[i]);
  }
  fprintf(stderr, "\n");*/
  
  len = scwrite(fd, scconfig.cla_verifypin, 0x20, 0, 0x01, insize, pin, &r1, &r2);
  if (r1 == 144 && r2 == 0) {
    return 0;
  }
  else {
    arla_warnx (ADEBERROR, "verify PIN -> scwrite returns len %d %02x %02x", len, r1, r2);
    return EINVAL;
  }
}

int scfs_inauth(int keynum, int insize, char *key)
{
  int len, r1, r2;

  if (scconfig.cla_inauth == CLA_NOSUPP) {
    arla_warnx (ADEBERROR, "Internal Authentication APDU is not supported in this smartcard.");
    return EINVAL;
  }

  /*  for (i=0; i<insize; i++) {
    fprintf(stderr, "%02x ", key[i]);
  }
  fprintf(stderr, "\n");*/
  
  len = scwrite(fd, scconfig.cla_inauth, 0x88, 0, keynum, insize, key, &r1, &r2);
  if ((r1 == 0x90 && r2 == 0) || r1 == 0x61) {
    return 0;
  }
  else {
    arla_warnx (ADEBERROR, "Internal Authentication Error -> scwrite returns len %d %02x %02x", len, r1, r2);
    return EINVAL;
  }
}

int scfs_getresponse(char *str, int outsize)
{
  int len, r1, r2;

  if (scconfig.cla_getresponse == CLA_NOSUPP) {
    arla_warnx (ADEBERROR, "Get Response APDU is not supported in this smartcard.");
    return EINVAL;
  }
    
  len = scread(fd, scconfig.cla_getresponse, 0xc0, 0, 0, outsize, str, &r1, &r2);
  if (r1 == 0x90 && r2 == 0) {
    return 0;
  }
  else {
    arla_warnx (ADEBERROR, "Get Response Error -> scwrite returns len %d %02x %02x", len, r1, r2);
    return EINVAL;
  }
}

int scfs_getchallenge(char *str, int outsize)
{
  int len, r1, r2;
  
  if (scconfig.cla_getchallenge == CLA_NOSUPP) {
    arla_warnx (ADEBERROR, "Get Challenge APDU is not supported in this smartcard.");
    return EINVAL;
  }
  
  len = scread(fd, scconfig.cla_getchallenge, 0x84, 0, 0, outsize, str, &r1, &r2);
  if (r1 == 0x90 && r2 == 0) {
    return 0;
  }
  else {
    arla_warnx (ADEBERROR, "Get Challenge Error -> scwrite returns len %d %02x %02x", len, r1, r2);
    return EINVAL;
  }
}

int scfs_exauth(int keynum, int insize, char *response)
{
  int len, r1, r2, i;
  char buf[7];
  
  if (scconfig.cla_exauth == CLA_NOSUPP) {
    arla_warnx (ADEBERROR, "External Authentication APDU is not supported in this smartcard.");
    return EINVAL;
  }
    
  buf[0] = (char)keynum;
  for (i=0; i<insize; i++) {
    buf[i+1] = response[i];
    /*fprintf(stderr, "%02x ", buf[i+1]);*/
  }
  /*fprintf(stderr, "\n");*/
  
  len = scwrite(fd, scconfig.cla_exauth, 0x82, 0, 0, 7, buf, &r1, &r2);
  if (r1 == 0x90 && r2 == 0) {
    return 0;
  }
  else {
    arla_warnx (ADEBERROR, "External Authentication Error -> scwrite returns len %d %02x %02x", len, r1, r2);

    return EINVAL;
  }
}

int scfs_tcl_readpin(char *pin)
{
  int n, fd[2], i;
  pid_t pid;
  char line[256];
  char *systest = "/home/itoi/tcl/test/pin.tcl";
  
  if (pipe(fd) < 0) {
    arla_warnx (ADEBERROR, "pipe error");
    return -1;
  }
  if ((pid=fork()) < 0) {
    arla_warnx (ADEBERROR, "fork error");
    close (fd[0]);
    close (fd[1]);
    return -1;
  }
  if (pid>0) {/* parent read */
    close (fd[1]);
    n = read (fd[0], line, 256);
    line[n] = NULL;
    strncpy (pin, line, n);
    for (i=n; i<16; i++) pin[i] = 0xff;
  } else { /* child write */
    close(fd[0]);
    if (fd[1] != STDOUT_FILENO) {
      if (dup2(fd[1], STDOUT_FILENO) != STDOUT_FILENO){
        arla_warnx(ADEBERROR, "dup2 error to stdout");
        close (fd[1]);
        return -1;
      }
      close (fd[1]);
    }
    
    if (execl(systest, systest, (char *)0) < 0) {
      arla_warnx(ADEBERROR, "execl error for %s\n", systest);
      close (fd[0]);
      close (fd[1]);
      return (-1);
    }
  }
  return 0;
}

int scfs_query_file(struct scfs_response *scfs_res, struct scdnode *node)
{
  char str[16];

  if (scconfig.multiflexgetres == 0) {
    scfs_res->size = 256;
    return 0;
  }
  if (godown(node) == -1) return -1;
  if (scfs_getresponse(str, 0x0f) != 0) {
    return -1;
  }
  /*for (i=0; i<15; i++) {fprintf(stderr, "%02x ", str[i]);}
    fprintf(stderr, "\n");*/
    
  scfs_res->size = (str[2] & 0xff) * 0x100 + (str[3] & 0xff);
  /*fprintf(stderr, "size=%x\n", scfs_res->size);*/
  return 0;
}

/* Update a directory entry for 'node'.
   The entry is in the parent's meta data (.i) */

int scfs_update_dirent(struct scdnode *node)
{
  struct scdnode *parent;
  int i, rv;

  /* do not update directory entry if the updated file is .i */
  if (node->fileent.fid[0] == DIRENT_FILE0 &&
      node->fileent.fid[1] == DIRENT_FILE1) return 0;

  assert(node);
  /* get parent */
  if ((parent = node->parent) == NULL){
    arla_warnx (ADEBERROR, "No parent??");
    return -1;
  }

  /* I'm the i'th child of the parent */
  if ((i = find_child_in_parent(parent, node)) == -1) {
    arla_warnx (ADEBERROR, "Can't find??");
    return -1;
  }
  if ((rv = update_ith_dirent(parent, i, &node->fileent))) return rv;
    
  return 0;
}  

/* update i'th entry of parent_node/.i (meta data) */
/* this i'th includes .i
   for example, if the directory contains (.i aa bb), then .i is 0'th,
   aa is 1'th, and bb is 2'th entry. */
int update_ith_dirent(struct scdnode *parent_node, int ith, struct fileent *fe)
{
  int de_offset, rv, sw1, sw2;

  assert(parent_node);
  assert(ith >=0);
  assert(ith < MaxNumChildren + 1); /* +1 for .i */

  if ((rv = godown(parent_node))) return rv;
  /*arla_warnx (ADEBMSG, "I am %d'th child of the parent.", ith);*/
  de_offset = (ith - 1) * sizeof(struct fileent); /* ith -1 because of .i */

  /* open .i */
  if (open_file(fd, DIRENT_FILE0, DIRENT_FILE1) != 0) {
    arla_warnx (ADEBERROR, "Cannot open directory entry %02x.%02x??",
                  DIRENT_FILE0, DIRENT_FILE1);
      return -1;
  }
  rv = scwrite(fd, scconfig.cla_updatebinary, 0xd6, (de_offset >> 8) & 0xff, de_offset & 0xff, sizeof(struct fileent), (char *)fe, &sw1, &sw2);
  if (rv == -1) {
    arla_warnx (ADEBERROR, "write error.  smartcard returns %02x %02x", sw1, sw2);
    if (sw1 == 0x6b && sw2 == 0x00)
      arla_warnx (ADEBERROR, "Offset out of range.");
    return -1;
  }
  return 0;
}
  
int scfs_add_dirent(struct scdnode *parent_node, struct scdnode *new_node)
{
  int i, de_offset, sw1, sw2, rv; 

  assert(parent_node);
  assert(new_node);
  
  /* I'm the i'th child of the parent */
  /*arla_warnx (ADEBMSG, "I am %d'th child of the parent.", parent_node->num_children);*/
  if (parent_node->num_children >= MaxNumChildren + 1) return EIO;
  i = parent_node->num_children;
  
  de_offset = (i - 1) * sizeof(struct fileent); /* i-1 because of .i */

  /* open parent, then meta data (.i) */
  godown(parent_node);
  if (open_file(fd, DIRENT_FILE0, DIRENT_FILE1) != 0) {
    arla_warnx (ADEBERROR, "Cannot open directory entry %02x.%02x??",
                DIRENT_FILE0, DIRENT_FILE1);
    return -1;
  }
  
  rv = scwrite(fd, scconfig.cla_updatebinary, 0xd6, (de_offset >> 8) & 0xff, de_offset & 0xff, sizeof(struct fileent), (char *)&new_node->fileent, &sw1, &sw2);
  if (sw1 == 0x69 || sw2 == 82) return EACCES;
  if (rv == -1) {
    arla_warnx (ADEBERROR, "write error.  smartcard returns %02x %02x", sw1, sw2);
    if (sw1 == 0x6b && sw2 == 0x00)
      arla_warnx (ADEBERROR, "Offset out of range.");
    return EIO;
  }

  return 0;
}

int scfs_remove(xfs_handle parent_fid, char *name)
{
  struct scdnode *parent_node;
  xfs_handle child_fid;
  int sw1, sw2, rv;
  
  assert(parent_fid);
  assert(name);

  if (name[0] == DIRENT_FILE0 && name[1] == DIRENT_FILE1) return EPERM;

  /*arla_warnx (ADEBMSG, "scfs_remove parent: %02x.%02x.%02x.%02x.%02x.%02x.%02x.%02x\n",
              parent_fid[0], parent_fid[1],
              parent_fid[2], parent_fid[3],
              parent_fid[4], parent_fid[5],
              parent_fid[6], parent_fid[7]);*/

  /* First go down to parent directory. */
  if ((parent_node = get_scdnode(parent_fid)) == NULL) {
    arla_warnx (ADEBERROR, "cannot find %02x.%02x",
                parent_fid[0], parent_fid[1]);
    return ENOENT;
  }
  if (godown(parent_node) == -1) return ENOENT; /* go down to parent (=record file) */
  /*arla_warnx (ADEBMSG, "scfs_remove name: %s\n", name);*/

  /* Then delete the file in a smartcard. */
  if ((rv = name_to_fid(parent_fid, name, &child_fid))) return rv;
  /*arla_warnx (ADEBMSG, "scfs_remove child: %02x.%02x.%02x.%02x.%02x.%02x.%02x.%02x\n",
              child_fid[0], child_fid[1],
              child_fid[2], child_fid[3],
              child_fid[4], child_fid[5],
              child_fid[6], child_fid[7]);*/

  rv = scwrite(fd, scconfig.cla_deletefile, 0xe4, 0x00, 0x00, 2, child_fid, &sw1, &sw2);
  arla_warnx (ADEBMSG, "smartcard returns %02x.%02x\n", sw1, sw2);
  if (sw1 == 0x69 || sw2 == 82) return EACCES;
  if (rv == -1) return EIO;
  if (sw1 != 0x90 || sw2 != 00) return EIO;

  /*if ((remove_node(parent_node, ))) return EIO;*/
  /* remove a file entry in the scfs database */
  if ((remove_node(parent_node, child_fid))) return EIO;

  return 0;
}

int scfs_rmdir(xfs_handle parent_fid, char *name)
{
  struct scdnode *parent_node, *removed_node;
  xfs_handle removed_fid;
  int sw1, sw2, rv;
  unsigned char file[2];
  
  assert(parent_fid);
  assert(name);

  /*arla_warnx (ADEBMSG, "scfs_rmdir parent: %02x.%02x.%02x.%02x.%02x.%02x.%02x.%02x\n",
              parent_fid[0], parent_fid[1],
              parent_fid[2], parent_fid[3],
              parent_fid[4], parent_fid[5],
              parent_fid[6], parent_fid[7]);*/

  if ((parent_node = get_scdnode(parent_fid)) == NULL) {
    arla_warnx (ADEBERROR, "cannot find %02x.%02x",
                parent_fid[0], parent_fid[1]);
    return ENOENT;
  }

  /* Get the removed node */
  if ((rv = name_to_fid(parent_fid, name, &removed_fid))) return rv;
  /*arla_warnx (ADEBMSG, "scfs_rmdir removed_node: %02x.%02x.%02x.%02x.%02x.%02x.%02x.%02x\n",
              removed_fid[0], removed_fid[1],
              removed_fid[2], removed_fid[3],
              removed_fid[4], removed_fid[5],
              removed_fid[6], removed_fid[7]);*/

  if ((removed_node = get_scdnode(removed_fid)) == NULL) {
    arla_warnx (ADEBERROR, "cannot find %02x.%02x",
                removed_fid[0], removed_fid[1]);
    return ENOENT;
  }
  /* go down to the removed node */
  if (godown(removed_node) == -1) return ENOENT;

  /* now remove meta data (.i) */
  file[0] = DIRENT_FILE0;
  file[1] = DIRENT_FILE1;
  rv = scwrite(fd, scconfig.cla_deletefile, 0xe4, 0x00, 0x00, 2, file, &sw1, &sw2);
  /*arla_warnx (ADEBMSG, "rmdir1: smartcard returns %02x.%02x\n", sw1, sw2);*/
  if (sw1 == 0x6a || sw2 == 0x82) {
    /* no meta data ... don't care */
    goto rmdir2;
  }
  if (sw1 == 0x69 || sw2 == 0x82) return EACCES;
  if (rv == -1 || sw1 != 0x90 || sw2 != 00) return EIO;
  
 rmdir2:
  /* go down to the parent node */
  if (godown(parent_node) == -1) return ENOENT;

  /* remove the removed node */
  rv = scwrite(fd, scconfig.cla_deletefile, 0xe4, 0x00, 0x00, 2, removed_fid, &sw1, &sw2);
  /*arla_warnx (ADEBMSG, "rmdir2: smartcard returns %02x.%02x\n", sw1, sw2);*/
  if (sw1 == 0x69 || sw2 == 0x82) return EACCES;
  if (rv == -1 || sw1 != 0x90 || sw2 != 00) return EIO;

  /* remove a file entry in the scfs database */
  if ((remove_node(parent_node, removed_fid))) return EIO;

  return 0;
}

int name_to_fid(xfs_handle parent_fid, char *name, xfs_handle *child_fid)
{
  int i;
 
  assert(parent_fid);
  assert(name);
  assert(*child_fid);

  if (strlen(name) != FILE_NAME_LEN) return EINVAL;
  (*child_fid)[0] = name[0];
  (*child_fid)[1] = name[1];
  for (i=0; i<FID_LEN-2; i++){
    (*child_fid)[i+2] = parent_fid[i];
  }

  return 0; 
}
 
int remove_node(struct scdnode *parent_node, unsigned char *child_fid)
{
  int i, ith, rv;
  struct scdnode *child_node;
  char *clean_buf;
  
  if ((child_node = get_scdnode(child_fid)) == NULL) {
    arla_warnx (ADEBERROR, "cannot find %02x.%02x",
                child_fid[0], child_fid[1]);
    return ENOENT;
  }
      
  if ((ith = find_child_in_parent(parent_node, child_node)) == -1) return -1;

  arla_warnx (ADEBMSG, "remove_node: parent_num_children=%d, ith=%d",
              parent_node->num_children, ith); 

  /* remove it from scfs database */
  assert(parent_node->num_children > 1); /* 1 because of .i */
  parent_node->num_children--;
  free(parent_node->child[ith]);
  for (i=ith; i<parent_node->num_children; i++)
    parent_node->child[i] = parent_node->child[i+1];

  for (i=ith; i<parent_node->num_children; i++){
    if ((rv = update_ith_dirent(parent_node, i, &parent_node->child[i]->fileent)))
      return rv;
  }
  
  /* set clean_buf */
  clean_buf = (char *)malloc(sizeof(struct fileent));
  memset (clean_buf, 0, sizeof(struct fileent));
  
  /* now remove #children'th entry in meta data */
  if ((rv = update_ith_dirent(parent_node, parent_node->num_children, (struct fileent *)clean_buf))){
    free(clean_buf);
    return rv;
  }
  
  free(clean_buf);
  return 0; 
}

int find_child_in_parent(struct scdnode *parent_node, struct scdnode *child_node)
{
  int i;

  /* I'm the i'th child of the parent */
  for (i=0; i<parent_node->num_children; i++){
    if (parent_node->child[i] == child_node) break;
  }
  if (i == parent_node->num_children){
    arla_warnx (ADEBERROR, "Cannot find myself in parent??");
    return -1;
  }
  return i;
}

int scfs_rename(xfs_handle old_parent_fid, char *old_name,
                xfs_handle new_parent_fid, char *new_name,
                dev_t t_dev)
{
#ifdef NIXX
  struct scdnode *old_parent_node, *new_parent_node,
    *old_child_node, *new_child_node;
  xfs_handle old_child_fid, new_child_fid;
  int sw1, sw2, rv;
  unsigned char *buf; 
  
  assert(old_parent_fid);
  assert(new_parent_fid);
  assert(old_name);
  assert(new_name);

  /* get parents */
  if ((old_parent_node = get_scdnode(old_parent_fid)) == NULL) {
    arla_warnx (ADEBERROR, "cannot find %02x.%02x",
                old_parent_fid[0], old_parent_fid[1]);
    return ENOENT;
  }

  if ((new_parent_node = get_scdnode(new_parent_fid)) == NULL) {
    arla_warnx (ADEBERROR, "cannot find %02x.%02x",
                new_parent_fid[0], new_parent_fid[1]);
    return ENOENT;
  }

  if ((rv = name_to_fid(old_parent_fid, old_name, &old_child_fid))) return rv;
  if ((rv = name_to_fid(new_parent_fid, new_name, &new_child_fid))) return rv;
  /*arla_warnx (ADEBMSG, "scfs_rename move %02x.%02x.%02x.%02x.%02x.%02x.%02x.%02x to %02x.%02x.%02x.%02x.%02x.%02x.%02x.%02x.\n",
              old_child_fid[0], old_child_fid[1],
              old_child_fid[2], old_child_fid[3],
              old_child_fid[4], old_child_fid[5],
              old_child_fid[6], old_child_fid[7],
              new_child_fid[0], new_child_fid[1],
              new_child_fid[2], new_child_fid[3],
              new_child_fid[4], new_child_fid[5],
              new_child_fid[6], new_child_fid[7]);*/

  /* malloc copy buffer */
  if (!(buf = malloc(old_parent_node->fileent.l_size))) {
    /*arla_warnx (ADEBERROR, "cannot malloc %d bytes.\n",
      old_parent_node->fileent.l_size);*/
    free(buf); 
    return ENOMEM;
  } 

  /* read it */
  if (godown(old_parent_node) == -1) return ENOENT; /* go down to parent (=record file) */

  /* get child */
  if ((old_child_node = get_scdnode(old_child_fid)) == NULL) {
    arla_warnx (ADEBERROR, "cannot find %02x.%02x",
                old_child_fid[0], old_child_fid[1]);
    free(buf); 
    return ENOENT;
  }

  /* can't rename directories */
  if (old_child_node->fileent.d_type == SCFS_TYPE_DIR) {
    arla_warnx (ADEBERROR, "cannot rename directory.");
    free(buf); 
    return EISDIR;
  }

  /* read data */
  if ((rv = scfs_read(old_child_fid, 0, old_child_node->fileent.l_size, buf,
                      SCFS_IS_NOT_RECORD, old_parent_fid, t_dev)) < 0) {
    arla_warnx (ADEBERROR, "cannot read %02x.%02x.",
                old_child_fid[0], old_child_fid[1]);
    free(buf); 
    return EIO;
  }

  /* Get the destination.
     If there is not a file in the destination, create one. */
  if ((new_child_node = get_scdnode(new_child_fid)) == NULL) {
    struct scfs_creat_arg ca;
    int i;
    
    ca.path[0] = new_child_fid[0];
    ca.path[1] = new_child_fid[1];
    ca.size = old_child_node->fileent.p_size;
    if ((scfs_create_sub(&ca, new_parent_node))) {
      arla_warnx (ADEBERROR, "cannot create %02x.%02x.",
                  new_child_fid[0], new_child_fid[1]);
      free(buf); 
      return EIO;
    }
    /*
    new_child_node = old_child_node;

    if ((i = find_child_in_parent(new_parent_node, new_child_node)) == -1) {
      arla_warnx (ADEBERROR, "Can't find??");
      return -1;
    }
    fidcpy(new_child_node->fileent.fid, new_child_fid);
    if ((rv = update_ith_dirent(new_parent_node, i, &new_child_node->fileent))) return rv;
    */

    /* get new_child_node */
    if ((new_child_node = get_scdnode(new_child_fid)) == NULL) {
      arla_warnx (ADEBERROR, "How come no child?!",
                  new_child_fid[0], new_child_fid[1]);
      free(buf); 
      return EIO;
    }
    
    /* write data */
    if ((rv = scfs_write(new_child_fid, 0, old_child_node->fileent.l_size, buf,
                         SCFS_IS_NOT_RECORD, new_parent_fid, t_dev)) < 0) {
      arla_warnx (ADEBERROR, "cannot write %02x.%02x.",
                  new_child_fid[0], new_child_fid[1]);
      free(buf); 
      return EIO;
    }
    
    /*
      TODO: 
      DONE get new_child_node,
      DONE copy data, 
      remove old_child_node's entry in meta data, 
      remove old_child_node,
      update meta data of new_parent.
     */
  }
  
#endif /* NIXX */
  return 0;
}

#ifdef NIXX
int scfs_restart()
{
  int rv;
  
  arla_warnx (ADEBMSG, "card removed");
  
  /* store state */
  /*store_state ();*/

  /* unmount the filesystem */
  arla_warnx (ADEBMSG, "Now unmount the filesystem!");

  /*
  if (unmount("/smartcard", 0))
    perror("scfsd: cannot unmount smartcard");
  */

  xfs_message_unmount_force();

  rv = scfs_unmount();
  if (rv) {
    perror("scfsd: cannot unmount smartcard");
    return; 
  }

  free(buf); 
  return 0;

  /*
  arla_warnx (ADEBMSG, "And initialize it again.");

  return scfs_init(2 - 1);
  */
}
#endif
#endif /* NIXX */
