+/*
+ * Palacios keyed stream interface
+ * (c) Peter Dinda, 2011
+ */
+
+#include <linux/fs.h>
+#include <linux/file.h>
+#include <linux/uaccess.h>
+
 #include "palacios.h"
 #include "util-hashtable.h"
 #include "linux-exts.h"
 #include <interfaces/vmm_keyed_stream.h>
 
 /*
-  Streams are stored in a hash table
-  The values for this hash table are hash tables associted with 
-  each stream.   A keyed stream for a "mem:" stream is 
-  an instance of the structure given here 
+  This is an implementation of the Palacios keyed stream interface
+  that supports two flavors of streams:
+
+  "mem:"   Streams are stored in a hash table
+  The values for this hash table are hash tables associated with 
+  each stream.   
+
+  "file:"  Streams are stored in files.  Each high-level
+  open corresponds to a directory, while  key corresponds to
+  a distinct file in that directory. 
+
 */
 
+#define STREAM_GENERIC 0
+#define STREAM_MEM     1
+#define STREAM_FILE    2
+
+
+/*
+  All keyed streams and streams indicate their implementation type within the first field
+ */
+struct generic_keyed_stream {
+    int stype;
+};
+
+struct generic_stream {
+    int stype;
+};
+  
+
+
+
+/****************************************************************************************
+   Memory-based implementation  ("mem:")
+****************************************************************************************/
+
 #define DEF_NUM_STREAMS 16
 #define DEF_NUM_KEYS    128
 #define DEF_SIZE        128
 
+/*
+  A memory keyed stream is a pointer to the underlying hash table
+  while a memory stream contains an extensible buffer for the stream
+ */
+struct mem_keyed_stream {
+    int stype;
+    v3_keyed_stream_open_t ot;
+    struct hashtable *ht;
+};
+
 struct mem_stream {
+    int       stype;
     char     *data;
     uint32_t  size;
     uint32_t  data_max;
        return 0;
     }
 
+
     m->data = kmalloc(DEF_SIZE,GFP_KERNEL);
     
     if (!m->data) { 
        return 0;
     }
 
+    m->stype = STREAM_MEM;
     m->size=DEF_SIZE;
     m->ptr=0;
     m->data_max=0;
 }
 
 
-// This stores all the streams
+// This stores all the memory keyed streams streams
 static struct hashtable *streams=0;
 
 
-static v3_keyed_stream_t open_stream(char *url,
-                                    v3_keyed_stream_open_t ot)
+static v3_keyed_stream_t open_stream_mem(char *url,
+                                        v3_keyed_stream_open_t ot)
 {
+
     if (strncasecmp(url,"mem:",4)) { 
-       printk("Only in-memory streams are currently supported\n");
+       printk("Illegitimate attempt to open memory stream \"%s\"\n",url);
        return 0;
     }
 
     switch (ot) { 
        case V3_KS_RD_ONLY:
-       case V3_KS_WR_ONLY:
-           return (v3_keyed_stream_t) palacios_htable_search(streams,(addr_t)(url+4));
+       case V3_KS_WR_ONLY: {
+           struct mem_keyed_stream *mks = (struct mem_keyed_stream *) palacios_htable_search(streams,(addr_t)(url+4));
+           if (mks) { 
+               mks->ot=ot;
+           }
+           return (v3_keyed_stream_t) mks;
+       }
            break;
+
        case V3_KS_WR_ONLY_CREATE: {
-           struct hashtable *s = (struct hashtable *) palacios_htable_search(streams,(addr_t)(url+4));
-
-           if (!s) { 
-                s = palacios_create_htable(DEF_NUM_KEYS,hash_func,hash_comp);
-                if (!s) { 
-                    printk("Cannot allocate in-memory keyed stream %s\n",url);
-                    return 0;
-                }
-                if (!palacios_htable_insert(streams,(addr_t)(url+4),(addr_t)s)) { 
-                    printk("Cannot insert in-memory keyed stream %s\n",url);
-                    return 0;
-                }
+           struct mem_keyed_stream *mks = (struct mem_keyed_stream *) palacios_htable_search(streams,(addr_t)(url+4));
+           if (!mks) { 
+               mks = (struct mem_keyed_stream *) kmalloc(sizeof(struct mem_keyed_stream),GFP_KERNEL);
+               if (!mks) { 
+                   printk("Cannot allocate in-memory keyed stream %s\n",url);
+                   return 0;
+               }
+           
+               mks->ht = (void*) palacios_create_htable(DEF_NUM_KEYS,hash_func,hash_comp);
+               if (!mks->ht) { 
+                   kfree(mks);
+                   printk("Cannot allocate in-memory keyed stream %s\n",url);
+                   return 0;
+               }
+               
+               if (!palacios_htable_insert(streams,(addr_t)(url+4),(addr_t)mks)) { 
+                   palacios_free_htable(mks->ht,1,1);
+                   kfree(mks);
+                   printk("Cannot insert in-memory keyed stream %s\n",url);
+                   return 0;
+               }
+               mks->stype=STREAM_MEM;
            }
 
-           return s;
+           mks->ot=V3_KS_WR_ONLY;
+           
+           return mks;
            
        }
+           break;
 
+       default:
+           printk("unsupported open type in open_stream_mem\n");
            break;
     }
     
     return 0;
-    
+       
 }
 
 
-static void close_stream(v3_keyed_stream_t stream)
+
+static void close_stream_mem(v3_keyed_stream_t stream)
 {
     // nothing to do
     return;
 }
 
-static v3_keyed_stream_key_t open_key(v3_keyed_stream_t stream,
-                                     char *key)
+
+static v3_keyed_stream_key_t open_key_mem(v3_keyed_stream_t stream,
+                                         char *key)
 {
-    struct hashtable *s = (struct hashtable *) stream;
+    struct mem_keyed_stream *mks = (struct mem_keyed_stream *) stream;
+    struct hashtable *s = mks->ht;
 
     struct mem_stream *m;
 
        m = create_mem_stream();
        
        if (!m) { 
-           printk("Cannot allocate keyed stream for key %s\n",key);
+           printk("Cannot allocate mem keyed stream for key %s\n",key);
            return 0;
        }
 
        if (!palacios_htable_insert(s,(addr_t)key,(addr_t)m)) {
-           printk("Cannot insert keyed stream for key %s\n",key);
+           printk("Cannot insert mem keyed stream for key %s\n",key);
            destroy_mem_stream(m);
            return 0;
        }
 
 }
 
-static void close_key(v3_keyed_stream_t stream, 
-                     v3_keyed_stream_key_t key)
+static void close_key_mem(v3_keyed_stream_t stream, 
+                         v3_keyed_stream_key_t key)
 {
     // nothing to do
     return;
 }
 
-static sint64_t write_key(v3_keyed_stream_t stream, 
-                         v3_keyed_stream_key_t key,
-                         void *buf,
-                         sint64_t len)
+static sint64_t write_key_mem(v3_keyed_stream_t stream, 
+                             v3_keyed_stream_key_t key,
+                             void *buf,
+                             sint64_t len)
 {
+    struct mem_keyed_stream *mks = (struct mem_keyed_stream *) stream;
     struct mem_stream *m = (struct mem_stream *) key;
     uint32_t mylen;
     uint32_t writelen;
 
+    if (mks->ot!=V3_KS_WR_ONLY) {
+       return -1;
+    }
+
     if (len<0) { 
-       return len;
+       return -1;
     }
     
     mylen = (uint32_t) len;
     }
 }
 
-static sint64_t read_key(v3_keyed_stream_t stream, 
-                        v3_keyed_stream_key_t key,
-                        void *buf,
-                        sint64_t len)
+static sint64_t read_key_mem(v3_keyed_stream_t stream, 
+                            v3_keyed_stream_key_t key,
+                            void *buf,
+                            sint64_t len)
 {
+    struct mem_keyed_stream *mks = (struct mem_keyed_stream *) stream;
     struct mem_stream *m = (struct mem_stream *) key;
     uint32_t mylen;
     uint32_t readlen;
     
+    if (mks->ot!=V3_KS_RD_ONLY) {
+       return -1;
+    }
+
     if (len<0) { 
-       return len;
+       return -1;
     }
     
     mylen = (uint32_t) len;
-
+    
     readlen=read_mem_stream(m,buf,mylen);
-
+    
     if (readlen!=mylen) { 
        printk("Failed to read all data for key\n");
        return -1;
        return (sint64_t)readlen;
     }
 }
+
+
+/***************************************************************************************************
+  File-based implementation  ("file:")
+*************************************************************************************************/
+
+/*
+  A file keyed stream contains the fd of the directory
+  and a path
+*/
+
+struct file_keyed_stream {
+    int   stype;
+    v3_keyed_stream_open_t ot;
+    char  *path;
+};
+
+struct file_stream {
+    int   stype;
+    struct file *f;   // the opened file
+};
+
+
+static int directory_ok(char *path, int flags)
+{
+    struct file *d;
+
+    // HACK
+    return 1;
+
+    d = filp_open(path,flags,0);
+
+    if (IS_ERR(d)) { 
+       return 0;
+    } else {
+       filp_close(d,NULL);
+       return 1;
+    }
+
+}
+
+static v3_keyed_stream_t open_stream_file(char *url,
+                                         v3_keyed_stream_open_t ot)
+{
+    struct file_keyed_stream *fks;
+
+    if (strncasecmp(url,"file:",5)) { 
+       printk("Illegitimate attempt to open file stream \"%s\"\n",url);
+       return 0;
+    }
+
+    fks = kmalloc(sizeof(struct file_keyed_stream),GFP_KERNEL);
+    
+    if (!fks) { 
+       printk("Cannot allocate space for file stream\n");
+       return 0;
+    }
+
+    fks->path = (char*)kmalloc(strlen(url+5)+1,GFP_KERNEL);
+    
+    if (!(fks->path)) { 
+       printk("Cannot allocate space for file stream\n");
+       kfree(fks);
+       return 0;
+    }
+    
+    strcpy(fks->path,url+5);
+    
+    fks->stype=STREAM_FILE;
+
+    //lookup starts at current->fs->root or current->fs->cwd
+
+    switch (ot) { 
+       case V3_KS_RD_ONLY:
+       case V3_KS_WR_ONLY:
+           
+           if (directory_ok(fks->path, ot==V3_KS_RD_ONLY ? O_RDONLY : O_WRONLY)) { 
+               fks->ot=ot;
+               return (v3_keyed_stream_t) fks;
+           } else {
+               printk("Directory \"%s\" not found\n",fks->path);
+               kfree(fks->path);
+               kfree(fks);
+               return 0;
+           }
+
+           break;
+
+       case V3_KS_WR_ONLY_CREATE: {
+          
+           if (directory_ok(fks->path, ot==V3_KS_RD_ONLY ? O_RDONLY : O_WRONLY)) { 
+               fks->ot=V3_KS_WR_ONLY;
+               return (v3_keyed_stream_t) fks;
+           } else {
+               // TODO: Create Directory
+               printk("Directory \"%s\" not found and will not be created\n",fks->path);
+               kfree(fks->path);
+               kfree(fks);
+               return 0;
+           }
+
+       }
+           break;
+           
+       default:
+           printk("unsupported open type in open_stream_file\n");
+           break;
+    }
+    
+    return 0;
+       
+}
+
+static void close_stream_file(v3_keyed_stream_t stream)
+{
+    struct file_keyed_stream *fks = (struct file_keyed_stream *) stream;
+    
+    kfree(fks->path);
+    kfree(fks);
+
+}
+
+static v3_keyed_stream_key_t open_key_file(v3_keyed_stream_t stream,
+                                          char *key)
+{
+    struct file_keyed_stream *fks = (struct file_keyed_stream *) stream;
+    struct file_stream *fs;
+    char *path;
+
+    // the path is the stream's path plus the key name
+    // file:/home/foo + "regext" => "/home/foo/regext"
+    path = (char *) kmalloc(strlen(fks->path)+strlen(key)+2,GFP_KERNEL);
+    if (!path) {                               
+       printk("Cannot allocate file keyed stream for key %s\n",key);
+       return 0;
+    }
+    strcpy(path,fks->path);
+    strcat(path,"/");
+    strcat(path,key);
+    
+    fs = (struct file_stream *) kmalloc(sizeof(struct file_stream *),GFP_KERNEL);
+    
+    if (!fs) { 
+       printk("Cannot allocate file keyed stream for key %s\n",key);
+       kfree(path);
+       return 0;
+    }
+
+    fs->stype=STREAM_FILE;
+
+    fs->f = filp_open(path,O_RDWR|O_CREAT,0600);
+    
+    if (IS_ERR(fs->f)) {
+       printk("Cannot open relevent file \"%s\" for stream \"file:%s\" and key \"%s\"\n",path,fks->path,key);
+       kfree(fs);
+       kfree(path);
+       return 0;
+    }
+
+    kfree(path);
+
+    return fs;
+}
+
+
+static void close_key_file(v3_keyed_stream_t stream, 
+                          v3_keyed_stream_key_t key)
+{
+    struct file_stream *fs = (struct file_stream *) key;
+
+    filp_close(fs->f,NULL);
+
+    kfree(fs);
+}
+
+static sint64_t write_key_file(v3_keyed_stream_t stream, 
+                              v3_keyed_stream_key_t key,
+                              void *buf,
+                              sint64_t len)
+{
+    struct file_keyed_stream *fks = (struct file_keyed_stream *) stream;
+    struct file_stream *fs = (struct file_stream *) key;
+    mm_segment_t old_fs;
+    ssize_t done, left, total;
+    
+    if (fks->ot!=V3_KS_WR_ONLY) { 
+       return -1;
+    }
+    
+    if (len<0) { 
+       return -1;
+    }
+
+    total=len;
+    left=len;
+
+    old_fs = get_fs();
+    set_fs(get_ds());
+
+    while (left>0) {
+       done = fs->f->f_op->write(fs->f, buf+(total-left), left, &(fs->f->f_pos));
+       if (done<=0) {
+           return -1;
+       } else {
+           left -= done;
+       }
+    }
+    set_fs(old_fs);
+
+    return len;
+}
+
+
+
+static sint64_t read_key_file(v3_keyed_stream_t stream, 
+                             v3_keyed_stream_key_t key,
+                             void *buf,
+                             sint64_t len)
+{
+    struct file_keyed_stream *fks = (struct file_keyed_stream *) stream;
+    struct file_stream *fs = (struct file_stream *) key;
+    mm_segment_t old_fs;
+    ssize_t done, left, total;
+    
+    if (fks->ot!=V3_KS_RD_ONLY) { 
+       return -1;
+    }
+
+    if (len<0) { 
+       return -1;
+    }
+
+    total=len;
+    left=len;
+
+    old_fs = get_fs();
+    set_fs(get_ds());
+
+    while (left>0) {
+       done = fs->f->f_op->read(fs->f, buf+(total-left), left, &(fs->f->f_pos));
+       if (done<=0) {
+           return -1;
+       } else {
+           left -= done;
+       }
+    }
+    set_fs(old_fs);
+
+    return len;
+
+}
+
+
+
+
+/***************************************************************************************************
+  Generic interface
+*************************************************************************************************/
+
+static v3_keyed_stream_t open_stream(char *url,
+                                    v3_keyed_stream_open_t ot)
+{
+    if (!strncasecmp(url,"mem:",4)) { 
+       return open_stream_mem(url,ot);
+    } else if (!strncasecmp(url,"file:",5)) { 
+       return open_stream_file(url,ot);
+    } else {
+       printk("Unsupported type in attempt to open keyed stream \"%s\"\n",url);
+       return 0;
+    }
+}
+
+static void close_stream(v3_keyed_stream_t stream)
+{
+    struct generic_keyed_stream *gks = (struct generic_keyed_stream *) stream;
+    switch (gks->stype){ 
+       case STREAM_MEM:
+           return close_stream_mem(stream);
+           break;
+       case STREAM_FILE:
+           return close_stream_file(stream);
+           break;
+       default:
+           printk("unknown stream type %d in close\n",gks->stype);
+           break;
+    }
+}
+
+static v3_keyed_stream_key_t open_key(v3_keyed_stream_t stream,
+                                     char *key)
+{
+    struct generic_keyed_stream *gks = (struct generic_keyed_stream *) stream;
+    switch (gks->stype){ 
+       case STREAM_MEM:
+           return open_key_mem(stream,key);
+           break;
+       case STREAM_FILE:
+           return open_key_file(stream,key);
+           break;
+       default:
+           printk("unknown stream type %d in open_key\n",gks->stype);
+           break;
+    }
+    return 0;
+}
+
+
+static void close_key(v3_keyed_stream_t stream, 
+                     v3_keyed_stream_key_t key)
+{
+    struct generic_keyed_stream *gks = (struct generic_keyed_stream *) stream;
+    switch (gks->stype){ 
+       case STREAM_MEM:
+           return close_key_mem(stream,key);
+           break;
+       case STREAM_FILE:
+           return close_key_file(stream,key);
+           break;
+       default:
+           printk("unknown stream type %d in close_key\n",gks->stype);
+           break;
+    }
+    // nothing to do
+    return;
+}
+
+static sint64_t write_key(v3_keyed_stream_t stream, 
+                         v3_keyed_stream_key_t key,
+                         void *buf,
+                         sint64_t len)
+{
+    struct generic_keyed_stream *gks = (struct generic_keyed_stream *) stream;
+    switch (gks->stype){ 
+       case STREAM_MEM:
+           return write_key_mem(stream,key,buf,len);
+           break;
+       case STREAM_FILE:
+           return write_key_file(stream,key,buf,len);
+           break;
+       default:
+           printk("unknown stream type %d in write_key\n",gks->stype);
+           return -1;
+           break;
+    }
+    return -1;
+}
+
+
+static sint64_t read_key(v3_keyed_stream_t stream, 
+                        v3_keyed_stream_key_t key,
+                        void *buf,
+                        sint64_t len)
+{
+    struct generic_keyed_stream *gks = (struct generic_keyed_stream *) stream;
+    switch (gks->stype){ 
+       case STREAM_MEM:
+           return read_key_mem(stream,key,buf,len);
+           break;
+       case STREAM_FILE:
+           return read_key_file(stream,key,buf,len);
+           break;
+       default:
+           printk("unknown stream type %d in write_key\n",gks->stype);
+           return -1;
+           break;
+    }
+    return -1;
+}
+
+
+
+
+/***************************************************************************************************
+  Hooks to palacios and inititialization
+*************************************************************************************************/
+
     
 static struct v3_keyed_stream_hooks hooks = {
     .open = open_stream,
     streams = palacios_create_htable(DEF_NUM_STREAMS,hash_func,hash_comp);
 
     if (!streams) { 
-       printk("Failed to allocated stream pool\n");
+       printk("Failed to allocated stream pool for in-memory streams\n");
        return -1;
     }
 
     V3_Init_Keyed_Streams(&hooks);
+
+    /*
+
+    {
+    v3_keyed_stream_t s;
+    v3_keyed_stream_key_t k;
+    uint8_t buf[256];
+    uint32_t i;
     
+    printk("Testing memory interface\n");
+    s=open_stream("mem:/foo",V3_KS_WR_ONLY_CREATE);
+    k=open_key(s,"bar");
+    write_key(s,k,"hello",5);
+    close_key(s,k);
+    k=open_key(s,"baz");
+    write_key(s,k,"foobar",6);
+    close_key(s,k);
+    for (i=0;i<=256;i++) { 
+       buf[i]=i;
+    }
+    k=open_key(s,"goo");
+    write_key(s,k,buf,256);
+    close_key(s,k);
+    close_stream(s);
+    s=open_stream("mem:/foo",V3_KS_RD_ONLY);
+    k=open_key(s,"bar");
+    read_key(s,k,buf,5);
+    buf[5]=0;
+    printk("wrote 'hello', read '%s'\n",buf);
+    close_key(s,k);
+    k=open_key(s,"baz");
+    read_key(s,k,buf,6);
+    buf[6]=0;
+    printk("wrote 'foobar', read '%s'\n",buf);
+    close_key(s,k);
+    k=open_key(s,"goo");
+    read_key(s,k,buf,256);
+    for (i=0;i<256;i++) { 
+       printk("wrote 0x%x, read 0x%x (%s)\n",i,buf[i],i==buf[i] ? "ok" : "BAD");
+    }
+    close_key(s,k);
+    close_stream(s);
+
+    printk("Testing file interface\n");
+    s=open_stream("file:/foo",V3_KS_WR_ONLY_CREATE);
+    k=open_key(s,"bar");
+    write_key(s,k,"hello",5);
+    close_key(s,k);
+    k=open_key(s,"baz");
+    write_key(s,k,"foobar",6);
+    close_key(s,k);
+    for (i=0;i<=256;i++) { 
+       buf[i]=i;
+    }
+    k=open_key(s,"goo");
+    write_key(s,k,buf,256);
+    close_key(s,k);
+    close_stream(s);
+    s=open_stream("file:/foo",V3_KS_RD_ONLY);
+    k=open_key(s,"bar");
+    read_key(s,k,buf,5);
+    buf[5]=0;
+    printk("wrote 'hello', read '%s'\n",buf);
+    close_key(s,k);
+    k=open_key(s,"baz");
+    read_key(s,k,buf,6);
+    buf[6]=0;
+    printk("wrote 'foobar', read '%s'\n",buf);
+    close_key(s,k);
+    k=open_key(s,"goo");
+    read_key(s,k,buf,256);
+    for (i=0;i<256;i++) { 
+       printk("wrote 0x%x, read 0x%x (%s)\n",i,buf[i],i==buf[i] ? "ok" : "BAD");
+    }
+    close_key(s,k);
+    close_stream(s);
+    }
+    */
+
     return 0;
 
 }
 
 static int deinit_keyed_streams( void )
 {
-    printk("DEINIT OF PALACIOS KEYED STREAMS NOT IMPLEMENTED - WE HAVE JUST LEAKED MEMORY!\n");
+    printk("DEINIT OF PALACIOS KEYED STREAMS NOT IMPLEMENTED - WE HAVE JUST LEAKED MEMORY and/or file handles!\n");
     return -1;
 }