Add slashgrid client
authorAndrew McNab <andrew.mcnab@manchester.ac.uk>
Sun, 28 May 2006 15:39:32 +0000 (15:39 +0000)
committerAndrew McNab <andrew.mcnab@manchester.ac.uk>
Sun, 28 May 2006 15:39:32 +0000 (15:39 +0000)
org.gridsite.core/src/Makefile
org.gridsite.core/src/slashgrid.c [new file with mode: 0644]
org.gridsite.core/src/slashgrid.init [new file with mode: 0755]

index 3af684b..b01edab 100644 (file)
@@ -201,6 +201,12 @@ showx509exts: showx509exts.c libgridsite.a
             -lgridsite \
             -lssl -lcrypto -lxml2 -lz -lm
  
+slashgrid: slashgrid.c
+       gcc -g -o slashgrid -L/usr/local/lib -lfuse -lpthread slashgrid.c \
+          -D_FILE_OFFSET_BITS=64 -D_REENTRANT -DFUSE_USE_VERSION=22 \
+          -I/usr/kerberos/include `curl-config --cflags` \
+          `curl-config --libs` -lgridsite -lxml2
+
 apidoc:
        date
        doxygen Doxyfile
@@ -340,3 +346,5 @@ wtf:
        ls -l
        ls -lR /usr/local/
        ls -lR $(GSOAPDIR)
+
+
diff --git a/org.gridsite.core/src/slashgrid.c b/org.gridsite.core/src/slashgrid.c
new file mode 100644 (file)
index 0000000..4fb2ce7
--- /dev/null
@@ -0,0 +1,1673 @@
+/*5~
+    SlashGrid2 (slashgrid for GridSite version 1.3.x)
+
+    As root, start with:
+
+     mkdir /grid
+     slashgrid /grid -d -o allow_other
+*/
+
+#define _XOPEN_SOURCE
+
+#include <stdio.h>
+#include <string.h>
+#include <sys/mount.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <time.h>
+#include <syslog.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/vfs.h>
+#include <unistd.h>              
+#include <dirent.h>
+#include <malloc.h>
+#include <curl/curl.h>
+#include <pthread.h>
+
+#include <fuse.h>
+
+#define GRST_SLASH_PIDFILE "/var/run/slashgrid.pid"
+
+#define GRST_SLASH_HEADERS "/var/spool/slashgrid/headers"
+#define GRST_SLASH_BLOCKS  "/var/spool/slashgrid/blocks"
+#define GRST_SLASH_TMP     "/var/spool/slashgrid/tmp"
+#define GRST_SLASH_DIRFILE "::DIR::"
+
+#define GRST_SLASH_HEAD   0
+#define GRST_SLASH_GET    1
+#define GRST_SLASH_PUT    2
+#define GRST_SLASH_DELETE 3
+#define GRST_SLASH_MOVE   4
+#define GRST_SLASH_TRUNC  5
+
+#define GRST_SLASH_HEADERS_EXPIRE      60
+#define GRST_SLASH_BLOCK_SIZE          4096
+#define GRST_SLASH_MAX_HANDLES         16
+
+#ifndef CURLOPT_WRITEDATA
+#define CURLOPT_WRITEDATA CURLOPT_FILE
+#endif
+#ifndef CURLOPT_READDATA
+#define CURLOPT_READDATA CURLOPT_FILE
+#endif
+
+#ifndef CURLE_HTTP_RETURNED_ERROR
+#define CURLE_HTTP_RETURNED_ERROR CURLE_HTTP_NOT_FOUND
+#endif
+
+struct grst_body_text { char   *text;
+                        size_t  used;
+                        size_t  allocated; } ;
+
+struct grst_read_data { const char  *buf;
+                        off_t sent; 
+                        off_t maxsent; };
+
+struct grst_dir_list { char   *filename;
+                       off_t   length;
+                       int     length_set;
+                       time_t  modified;
+                       int     modified_set; } ;
+
+struct grst_request { int     retcode;                         
+                      char   *location;
+                      size_t  length;
+                      int     length_set;
+                      time_t  modified;                           
+                      int     modified_set; 
+                      void   *readfunction;
+                      void   *readdata;
+                      void   *writefunction;
+                      void   *writedata;
+                      size_t  infilesize;
+                      char   *errorbuffer;
+                      char   *url;
+                      int     method;
+                      char   *destination;
+                      off_t   start;
+                      off_t   finish; } ;
+
+struct grst_handle { pthread_mutex_t   mutex;
+                     CURL              *curl_handle;
+                     uid_t             uid;
+                     char              *proxyfile;
+                     time_t            last_used;
+                   }  handles[GRST_SLASH_MAX_HANDLES];
+int debugmode = 0;
+
+size_t headers_callback(void *ptr, size_t size, size_t nmemb, void *p)
+/* Find the values of the return code, Content-Length, Last-Modified
+   and Location headers */
+{
+  float f;
+  char  *s, *q;
+  size_t realsize;
+  struct tm modified_tm;
+  struct grst_request *request_data = (struct grst_request *) p;
+
+  realsize = size * nmemb;
+  s = malloc(realsize + 1);
+  memcpy(s, ptr, realsize);
+  s[realsize] = '\0';
+
+  if      (sscanf(s, "Content-Length: %d", &(request_data->length)) == 1) 
+            request_data->length_set = 1;
+  else if (sscanf(s, "HTTP/%f %d ", &f, &(request_data->retcode)) == 2) ;
+  else if (strncmp(s, "Location: ", 10) == 0) 
+      {
+        request_data->location = strdup(&s[10]);
+        
+        for (q=request_data->location; *q != '\0'; ++q)
+         if ((*q == '\r') || (*q == '\n')) *q = '\0';
+      }
+  else if (strncmp(s, "Last-Modified: ", 15) == 0)
+      {
+        /* follow RFC 2616: first try RFC 822 (kosher), then RFC 850 and 
+           asctime() formats too. Must be GMT whatever the format. */
+
+        if (strptime(&s[15], "%a, %d %b %Y %T GMT", &modified_tm) != NULL)
+          {
+            request_data->modified = mktime(&modified_tm);
+            request_data->modified_set = 1;
+          }
+        else if (strptime(&s[15], "%a, %d-%b-%y %T GMT", &modified_tm) != NULL)
+          {
+            request_data->modified = mktime(&modified_tm);
+            request_data->modified_set = 1;
+          }
+        else if (strptime(&s[15], "%a %b %d %T %Y", &modified_tm) != NULL)
+          {
+            request_data->modified = mktime(&modified_tm);
+            request_data->modified_set = 1;
+          }
+      }
+    
+  free(s);
+  return realsize;
+}
+
+int debug_callback(CURL *handle, curl_infotype infotype, 
+                   char *rawmesg, size_t size, void *i)
+{
+  int   n;
+  char *mesg;
+
+  if ((infotype == CURLINFO_DATA_IN) ||
+      (infotype == CURLINFO_DATA_OUT)) return 0;
+
+  mesg = malloc(size + 1);
+  
+  for (n=0; n < size; ++n)
+     {
+       if ((rawmesg[n] == '\r') && (n >= size - 2)) mesg[n] = '\0';
+       else if (((rawmesg[n] == '\r') || (rawmesg[n] == '\n')) && 
+                (infotype == CURLINFO_HEADER_IN)) mesg[n] = '<';
+       else if (((rawmesg[n] == '\r') || (rawmesg[n] == '\n')) && 
+                (infotype == CURLINFO_HEADER_OUT)) mesg[n] = '>';
+       else if ((rawmesg[n] < ' ') || (rawmesg[n] >= 127)) mesg[n] = '.';
+       else mesg[n] = rawmesg[n];
+     }
+     
+  mesg[n] = '\0';
+
+  syslog(LOG_DEBUG, "%d %s%s%s%s", 
+                    *((int *) i), 
+                    (infotype == CURLINFO_HEADER_IN ) ? "<<" : "",
+                    (infotype == CURLINFO_HEADER_OUT) ? ">>" : "",
+                    (infotype == CURLINFO_TEXT      ) ? "**" : "",
+                    mesg);
+
+  free(mesg);  
+  return 0;
+}                  
+
+int perform_request(struct grst_request *request_data,
+                    struct fuse_context *fuse_ctx)
+{
+  int                ret, i, j;
+  char              *proxyfile = NULL, *range_header = NULL;
+  struct stat        statbuf;
+  struct curl_slist *headers_list = NULL;
+
+  if (strncmp(request_data->url, "https://", 8) == 0) /* HTTPS options */
+    {
+// check for X509_USER_PROXY in that PID's environ too
+
+      asprintf(&proxyfile, "/tmp/x509up_u%d", fuse_ctx->uid);
+      /* if proxyfile is used, it will be referenced by handles[].proxyfile
+         and freed when this handle is eventually freed */
+
+      if ((stat(proxyfile, &statbuf) != 0) ||
+          (statbuf.st_uid != fuse_ctx->uid))
+        {
+          free(proxyfile);
+          proxyfile = NULL;
+        }
+    }
+
+  /* try to find an existing handle for this uid/proxyfile */
+
+  for (i=0; i < GRST_SLASH_MAX_HANDLES; ++i)
+     {
+       if ((handles[i].curl_handle != NULL)  &&
+           (handles[i].uid == fuse_ctx->uid) &&
+           (((handles[i].proxyfile == NULL) && (proxyfile == NULL)) ||
+            ((handles[i].proxyfile != NULL) && (proxyfile != NULL) &&
+             (strcmp(handles[i].proxyfile, proxyfile) == 0))))
+         {
+           break;
+         }
+     }
+     
+  if (i >= GRST_SLASH_MAX_HANDLES) /* no existing match found */
+    {
+      i=0;
+    
+      for (j=0; j < GRST_SLASH_MAX_HANDLES; ++j)
+         {
+           if (handles[j].curl_handle == NULL) /* unused slot */
+             {
+               i = j;
+               break;
+             }
+             
+           if (handles[j].last_used < handles[i].last_used) i = j;
+         }
+    }
+
+  /* now lock this handle and recheck settings inside the mutex lock */
+
+  pthread_mutex_lock(&(handles[i].mutex));
+
+  if ((handles[i].curl_handle == NULL)  ||
+      (handles[i].uid != fuse_ctx->uid) ||
+      (((handles[i].proxyfile != NULL) || (proxyfile != NULL)) &&
+       ((handles[i].proxyfile == NULL) || (proxyfile == NULL) ||
+        (strcmp(handles[i].proxyfile, proxyfile) != 0))))
+    {
+      /* we do need to initialise this handle */
+      
+      handles[i].uid = fuse_ctx->uid;
+
+      if (handles[i].curl_handle != NULL)
+                              curl_easy_cleanup(handles[i].curl_handle);
+      handles[i].curl_handle = curl_easy_init();
+      
+      if (handles[i].proxyfile != NULL) free(handles[i].proxyfile);
+      handles[i].proxyfile = proxyfile; /* proxyfile might be NULL itself */
+      
+      if (handles[i].proxyfile != NULL)
+        {
+          curl_easy_setopt(handles[i].curl_handle, CURLOPT_SSLCERTTYPE, "PEM");
+          curl_easy_setopt(handles[i].curl_handle, CURLOPT_SSLCERT,
+                                                    handles[i].proxyfile);
+          curl_easy_setopt(handles[i].curl_handle, CURLOPT_SSLKEYTYPE, "PEM");
+          curl_easy_setopt(handles[i].curl_handle, CURLOPT_SSLKEY,  
+                                                    handles[i].proxyfile);
+        }
+      else
+        {
+          curl_easy_setopt(handles[i].curl_handle, CURLOPT_SSLKEYTYPE,  "ENG");
+          curl_easy_setopt(handles[i].curl_handle, CURLOPT_SSLCERTTYPE, "ENG");
+          curl_easy_setopt(handles[i].curl_handle, CURLOPT_SSLCERT,     NULL);
+        }
+
+      if (debugmode)
+        {
+          curl_easy_setopt(handles[i].curl_handle, CURLOPT_VERBOSE, 1);
+          curl_easy_setopt(handles[i].curl_handle, CURLOPT_DEBUGFUNCTION,
+                                                          debug_callback);
+        }
+
+      curl_easy_setopt(handles[i].curl_handle, CURLOPT_USERAGENT, 
+                       "SlashGrid http://www.gridsite.org/slashgrid/");
+      curl_easy_setopt(handles[i].curl_handle, CURLOPT_FOLLOWLOCATION, 0);
+      curl_easy_setopt(handles[i].curl_handle, CURLOPT_HEADERFUNCTION, headers_callback);
+
+      curl_easy_setopt(handles[i].curl_handle, CURLOPT_CAPATH, 
+                                        "/etc/grid-security/certificates");
+
+      curl_easy_setopt(handles[i].curl_handle, CURLOPT_SSL_VERIFYPEER, 2);
+      curl_easy_setopt(handles[i].curl_handle, CURLOPT_SSL_VERIFYHOST, 2);
+    }   
+    
+  if (request_data->method == GRST_SLASH_HEAD)
+    {
+      curl_easy_setopt(handles[i].curl_handle, CURLOPT_CUSTOMREQUEST, NULL);
+      curl_easy_setopt(handles[i].curl_handle, CURLOPT_NOBODY,  1);
+      curl_easy_setopt(handles[i].curl_handle, CURLOPT_HTTPGET, 0);
+      curl_easy_setopt(handles[i].curl_handle, CURLOPT_UPLOAD,  0);
+    }
+  else if (request_data->method == GRST_SLASH_GET)
+    {
+      curl_easy_setopt(handles[i].curl_handle, CURLOPT_CUSTOMREQUEST, NULL);
+      curl_easy_setopt(handles[i].curl_handle, CURLOPT_NOBODY,  0);
+      curl_easy_setopt(handles[i].curl_handle, CURLOPT_HTTPGET, 1);
+      curl_easy_setopt(handles[i].curl_handle, CURLOPT_UPLOAD,  0);
+    }
+  else if ((request_data->method == GRST_SLASH_PUT) || 
+           (request_data->method == GRST_SLASH_TRUNC))
+    {
+      curl_easy_setopt(handles[i].curl_handle, CURLOPT_CUSTOMREQUEST, NULL);
+      curl_easy_setopt(handles[i].curl_handle, CURLOPT_NOBODY,  0);
+      curl_easy_setopt(handles[i].curl_handle, CURLOPT_HTTPGET, 0);
+      curl_easy_setopt(handles[i].curl_handle, CURLOPT_UPLOAD,  1);
+      curl_easy_setopt(handles[i].curl_handle, CURLOPT_INFILESIZE,  
+                                            (long) request_data->infilesize);
+    }
+  else if (request_data->method == GRST_SLASH_DELETE)
+    {
+      curl_easy_setopt(handles[i].curl_handle, CURLOPT_NOBODY,  0);
+      curl_easy_setopt(handles[i].curl_handle, CURLOPT_HTTPGET, 0);
+      curl_easy_setopt(handles[i].curl_handle, CURLOPT_UPLOAD,  0);
+      curl_easy_setopt(handles[i].curl_handle, CURLOPT_CUSTOMREQUEST, "DELETE");
+    }
+  else if (request_data->method == GRST_SLASH_MOVE)
+    {
+      curl_easy_setopt(handles[i].curl_handle, CURLOPT_NOBODY,  0);
+      curl_easy_setopt(handles[i].curl_handle, CURLOPT_HTTPGET, 0);
+      curl_easy_setopt(handles[i].curl_handle, CURLOPT_UPLOAD,  0);
+      curl_easy_setopt(handles[i].curl_handle, CURLOPT_CUSTOMREQUEST, "MOVE");
+    }
+  else return CURLE_UNSUPPORTED_PROTOCOL;
+
+  curl_easy_setopt(handles[i].curl_handle, CURLOPT_WRITEHEADER,    request_data);
+  
+  if (request_data->errorbuffer != NULL)
+        curl_easy_setopt(handles[i].curl_handle, CURLOPT_ERRORBUFFER,
+                                      request_data->errorbuffer);
+
+  if (debugmode)
+        curl_easy_setopt(handles[i].curl_handle, CURLOPT_DEBUGDATA, &i);
+
+  curl_easy_setopt(handles[i].curl_handle, CURLOPT_READFUNCTION, request_data->readfunction);
+  curl_easy_setopt(handles[i].curl_handle, CURLOPT_READDATA, request_data->readdata);
+  curl_easy_setopt(handles[i].curl_handle, CURLOPT_WRITEFUNCTION, request_data->writefunction);
+  curl_easy_setopt(handles[i].curl_handle, CURLOPT_WRITEDATA, request_data->writedata);
+
+  curl_easy_setopt(handles[i].curl_handle, CURLOPT_URL, request_data->url);
+
+  if ((request_data->start >= 0) && 
+      (request_data->finish >= request_data->start))
+    {
+      if (request_data->method == GRST_SLASH_PUT)
+           asprintf(&range_header, "Content-Range: bytes %ld-%ld/*", 
+               (long) request_data->start, (long) request_data->finish);
+      else if (request_data->method == GRST_SLASH_TRUNC)
+           asprintf(&range_header, "Content-Range: bytes *-*/%ld", 
+               (long) request_data->finish);
+      else asprintf(&range_header, "Range: bytes=%ld-%ld", 
+               (long) request_data->start, (long) request_data->finish);
+
+      headers_list = curl_slist_append(headers_list, range_header);
+      curl_easy_setopt(handles[i].curl_handle, CURLOPT_HTTPHEADER, headers_list);
+    }
+  else curl_easy_setopt(handles[i].curl_handle, CURLOPT_HTTPHEADER, NULL);
+
+  ret = curl_easy_perform(handles[i].curl_handle);
+  
+  if (headers_list != NULL) curl_slist_free_all(headers_list);
+  if (range_header != NULL) free(range_header);
+
+  pthread_mutex_unlock(&(handles[i].mutex));
+  
+  return ret;
+}
+
+size_t rawbody_callback(void *ptr, size_t size, size_t nmemb, void *data)
+{
+  if ( ((struct grst_body_text *) data)->used + size * nmemb >=
+                             ((struct grst_body_text *) data)->allocated )
+    {
+      ((struct grst_body_text *) data)->allocated = 
+        ((struct grst_body_text *) data)->used + size * nmemb + 4096;
+
+      ((struct grst_body_text *) data)->text = 
+         realloc( ((struct grst_body_text *) data)->text,
+                  ((struct grst_body_text *) data)->allocated );
+    }
+    
+  memcpy( &( ((struct grst_body_text *) 
+                 data)->text[((struct grst_body_text *) data)->used] ),
+          ptr, size * nmemb);
+          
+  ((struct grst_body_text *) data)->used += size * nmemb;
+  
+  return size * nmemb;
+}
+
+size_t null_callback(void *ptr, size_t size, size_t nmemb, void *data)
+{
+  return size * nmemb;
+}
+
+size_t read_data_callback(void *ptr, size_t size, size_t nmemb, void *data)
+{
+  size_t sent;
+  
+  if (((struct grst_read_data *) data)->sent 
+        >= ((struct grst_read_data *) data)->maxsent) return 0;
+        
+  if (size * nmemb + ((struct grst_read_data *) data)->sent 
+        >= ((struct grst_read_data *) data)->maxsent)
+    {
+      sent = ((struct grst_read_data *) data)->maxsent 
+             - ((struct grst_read_data *) data)->sent;
+    }
+  else sent = size * nmemb;        
+
+  memcpy(ptr, 
+         ((struct grst_read_data *) data)->buf +
+         ((struct grst_read_data *) data)->sent,
+         sent);
+
+  ((struct grst_read_data *) data)->sent += sent;
+  
+  return sent;
+}
+
+char *canonicalise(char *link, char *source)
+{
+  int   i, j, srclen;
+  char *s;
+
+  srclen = strlen(source);
+
+  if ((strncmp(link, "https://", 8) == 0) ||
+      (strncmp(link, "http://", 7) == 0))
+    {
+      if (strncmp(link, source, srclen) != 0) return NULL; /* other site */
+      
+      if (link[srclen] == '\0') return NULL; /* we dont self-link! */
+      
+      for (i=0; link[srclen + i] != '\0'; ++i)
+        if (link[srclen + i] == '/')
+          { 
+            if (link[srclen + i + 1] != '\0') return NULL; /* no subdirs */
+            else return strdup(&link[srclen]); /* resolves to this dir */
+          }
+    }
+  else if (link[0] != '/') /* relative link - need to check for subsubdirs */
+    {
+      for (i=0; link[i] != '\0'; ++i) 
+        if ((link[i] == '/') && (link[i+1] != '\0')) return NULL;
+
+      s = strdup(link);
+      
+      for (i=0; s[i] != '\0'; ++i) 
+       if (s[i] == '#')
+         {
+           s[i] = '\0';
+           break;
+         }
+
+      return s;
+    }
+
+  /* absolute link on this server, starting / */
+
+  for (i=8; source[i] != '\0'; ++i) if (source[i] == '/') break;
+       
+  if (strncmp(link, &source[i], srclen - i) != 0) return NULL;
+
+  for (j = srclen - i; link[j] != '\0'; ++j)
+        if ((link[j] == '/') && (link[j+1] != '\0')) return NULL;
+        
+  s = strdup(&link[srclen - i]);
+      
+  for (i=0; s[i] != '\0'; ++i) 
+       if (s[i] == '#')
+         {
+           s[i] = '\0';
+           break;
+         }
+
+  if (s[0] == '\0') /* on second thoughts... */
+    {
+      free(s);
+      return NULL;
+    }
+         
+  return s;      
+}
+
+int grst_dir_list_cmp(const void *a, const void *b)
+{
+  return strcmp( ((struct grst_dir_list *) a)->filename, 
+                 ((struct grst_dir_list *) b)->filename);
+}
+
+struct grst_dir_list *index_to_dir_list(char *text, char *source)
+{
+  int   taglevel = 0, wordnew = 1, i, namestart, used = 0, 
+        allocated = 256;
+  char *p, *s;
+  struct grst_dir_list *list;
+  
+  list = (struct grst_dir_list *)
+              malloc(allocated * sizeof(struct grst_dir_list));
+              
+  list[0].filename     = NULL;
+  list[0].length       = 0;
+  list[0].length_set   = 0;
+  list[0].modified     = 0;
+  list[0].modified_set = 0;
+    
+  for (p=text; *p != '\0'; ++p)
+     {
+       if (*p == '<') 
+         {
+           ++taglevel;
+           
+           if ((taglevel == 1) && (list[used].filename != NULL))
+             {
+               ++used;
+               if (used >= allocated) 
+                 {
+                   allocated += 256;
+                   list = (struct grst_dir_list *)
+                           realloc((void *) list,
+                                   allocated * sizeof(struct grst_dir_list));
+                 }
+                 
+               list[used].filename     = NULL;
+               list[used].length       = 0;
+               list[used].length_set   = 0;
+               list[used].modified     = 0;
+               list[used].modified_set = 0;
+             }
+
+           wordnew = 1;
+           continue;
+         }
+
+       if (*p == '>') 
+         {
+           --taglevel;
+           wordnew = 1;
+           continue;
+         }
+         
+       if (isspace(*p))
+         {
+           wordnew = 1;
+           continue;
+         }
+
+       if ((wordnew) && (taglevel == 1))
+         {        
+           if (((*p == 'h') || (*p == 'H')) && 
+               (strncasecmp(p, "href=", 5) == 0))
+             {
+               if (p[5] == '"') { namestart = 6;
+                                  for (i=namestart; (p[i] != '\0') &&
+                                                    (p[i] != '"' ) &&
+                                                    (p[i] != '\n') &&
+                                                    (p[i] != '\t') &&
+                                                    (p[i] != '>' ) ; ++i) ; }
+               else { namestart = 5;
+                      for (i=namestart; (p[i] != '\0') &&
+                                        (p[i] != '"' ) &&
+                                        (p[i] != ' ' ) &&
+                                        (p[i] != '\n') &&
+                                        (p[i] != '\t') &&
+                                        (p[i] != ')' ) &&
+                                        (p[i] != '>' ) ; ++i) ; }
+               if (i > namestart) 
+                 {
+                   s = malloc(1 + i - namestart);
+                   memcpy(s, &p[namestart], i - namestart);
+                   s[i - namestart] = '\0';
+
+                   list[used].filename = canonicalise(s, source);
+                   free(s);
+                 }
+                 
+               p = &p[i-1]; /* -1 since continue results in ++i */
+               continue;
+             }
+
+           if (((*p == 'c') || (*p == 'C')) && 
+               (strncasecmp(p, "content-length=", 15) == 0))
+             {
+               list[used].length     = 0;
+               list[used].length_set = 1;
+               
+               if (p[15] == '"') list[used].length = atoi(&p[16]);
+               else              list[used].length = atoi(&p[15]);
+
+               p = &p[15];
+               continue;
+             }
+
+           if (((*p == 'l') || (*p == 'L')) && 
+               (strncasecmp(p, "last-modified=", 14) == 0))
+             {
+               list[used].modified     = 0;
+               list[used].modified_set = 1;
+               
+               if (p[14] == '"') list[used].modified = atoi(&p[15]);
+               else              list[used].modified = atoi(&p[14]);
+
+               p = &p[14];
+               continue;
+             }
+         }
+         
+       wordnew = 0;
+     }  
+
+  qsort((void *) list, used, sizeof(struct grst_dir_list), grst_dir_list_cmp);
+
+  return list;  
+}
+
+static char *GRSThttpUrlMildencode(char *in)
+/* Return a pointer to a malloc'd string holding a partially URL-encoded
+   version of *in. "Partially" means that A-Z a-z 0-9 . = - _ @ and /
+   are passed through unmodified. (DN's processed by GRSThttpUrlMildencode()
+   can be used as valid Unix paths+filenames if you are prepared to
+   create or simulate the resulting /X=xyz directories.) */
+{
+  char *out, *p, *q;
+
+  out = malloc(3*strlen(in) + 1);
+
+  p = in;
+  q = out;
+
+  while (*p != '\0')
+       {
+         if (isalnum(*p) || (*p == '.') || (*p == '=') || (*p == '-')
+                         || (*p == '/') || (*p == '@') || (*p == '_'))
+           {
+             *q = *p;
+             ++q;
+           }
+         else if (*p == ' ')
+           {
+             *q = '+';
+             ++q;
+           }
+         else
+           {
+             sprintf(q, "%%%2X", *p);
+             q = &q[3];
+           }
+
+         ++p;
+       }
+
+  *q = '\0';
+  return out;
+}
+
+int read_headers_from_cache(struct fuse_context *fuse_ctx, char *filename, 
+                            off_t *length, time_t *modified)
+{
+  char *encoded_filename, *disk_filename;
+  int   len;
+  long  content_length, last_modified;
+  FILE *fp;
+  struct stat statbuf;
+  time_t now;
+  
+  encoded_filename = GRSThttpUrlMildencode(filename);
+  
+  len = strlen(encoded_filename);
+
+  if (encoded_filename[len - 1] == '/') /* a directory */
+       asprintf(&disk_filename, "%s/%d%s%s", 
+                GRST_SLASH_HEADERS, fuse_ctx->uid, encoded_filename, GRST_SLASH_DIRFILE);
+  else asprintf(&disk_filename, "%s/%d%s%s", 
+                GRST_SLASH_HEADERS, fuse_ctx->uid, encoded_filename);
+
+  free(encoded_filename);
+
+// Change to fstat for the benefit of multiple threads:
+
+  if (stat(disk_filename, &statbuf) != 0) /* no cache file to read */
+    {
+      free(disk_filename);
+      return 0;
+    }
+
+  time(&now);
+
+  if (statbuf.st_mtime < now - GRST_SLASH_HEADERS_EXPIRE)
+    {
+      unlink(disk_filename); /* tidy up expired cache file */
+      free(disk_filename);
+      return 0;
+    }      
+
+  last_modified  = 0;
+  content_length = 0;
+
+  if (debugmode) syslog(LOG_DEBUG, "Opening %s from cache", disk_filename);
+
+  fp = fopen(disk_filename, "r");
+  free(disk_filename);
+
+  if (fp != NULL)
+    {
+      fscanf(fp, "content-length=%ld last-modified=%ld ", 
+                 &content_length, &last_modified);
+      fclose(fp);
+
+      if (debugmode) syslog(LOG_DEBUG, "content-length=%ld last-modified=%ld", 
+                            content_length, last_modified);
+
+      *length   = (off_t)  content_length;
+      *modified = (time_t) last_modified;
+
+      return 1;
+    }
+
+  return 0;
+}
+
+int write_headers_to_cache(struct fuse_context *fuse_ctx, char *filename, 
+                           off_t length, time_t modified)
+{
+  int         fd, len;
+  char       *tempfile, *headline, *encoded_filename, *p, *newdir,
+             *new_filename;
+  struct stat statbuf;
+
+  asprintf(&tempfile, "%s/headers-XXXXXX", GRST_SLASH_TMP);
+  fd = mkstemp(tempfile);
+
+  if (fd == -1)
+    {
+      free(tempfile);
+      return 0;
+    }
+
+  asprintf(&headline, "content-length=%ld last-modified=%ld \n", 
+                                (long) length, (long) modified);
+  
+  if ((write(fd, headline, strlen(headline)) == -1) ||
+      (close(fd) == -1))
+    {
+      free(tempfile);
+      free(headline);
+      return 0;
+    }
+
+  free(headline);
+                     
+  encoded_filename = GRSThttpUrlMildencode(filename);
+
+// need to protect against .. ?
+   
+  for (p = encoded_filename; *p != '\0'; ++p)
+     {  
+       if (*p != '/') continue;
+     
+       *p = '\0';
+       asprintf(&newdir, "%s/%d%s", GRST_SLASH_HEADERS, fuse_ctx->uid, encoded_filename);
+       *p = '/';
+           
+       if (stat(newdir, &statbuf) == 0)
+         {
+           if (!S_ISDIR(statbuf.st_mode)) /* exists already - not a directory! */
+             {
+               unlink(newdir);
+               mkdir(newdir, S_IRUSR | S_IWUSR | S_IXUSR);
+             }
+           /* else it already exists as a directory - so ok */
+         }
+       else mkdir(newdir, S_IRUSR | S_IWUSR | S_IXUSR);
+
+       free(newdir);
+     }
+
+  len = strlen(encoded_filename);
+
+  if (encoded_filename[len - 1] == '/') /* a directory */
+       asprintf(&new_filename, "%s/%d%s%s", 
+                GRST_SLASH_HEADERS, fuse_ctx->uid, encoded_filename, GRST_SLASH_DIRFILE);
+  else asprintf(&new_filename, "%s/%d%s", 
+                GRST_SLASH_HEADERS, fuse_ctx->uid, encoded_filename);
+
+  free(encoded_filename);
+  
+  if ((stat(new_filename, &statbuf) == 0) && S_ISDIR(statbuf.st_mode))
+    {
+// need change this to do it recursively in case any files/subdirs too
+      rmdir(new_filename);
+    }
+
+  rename(tempfile, new_filename);
+
+  if (debugmode) syslog(LOG_DEBUG, "Added %s to cache (%ld %ld)\n", 
+                                   new_filename, length, modified);
+
+  free(tempfile);
+  free(new_filename);
+
+  return 1;
+}
+
+static int slashgrid_readdir(const char *path, void *buf, 
+                             fuse_fill_dir_t filler,
+                             off_t offset, struct fuse_file_info *fi)
+{
+  (void) offset;
+  (void) fi;
+
+  int          anyerror = 0, thiserror, i, ilast;
+  const char  *months[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", 
+                            "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
+  char        *s, *url, errorbuffer[CURL_ERROR_SIZE+1] = "";
+  struct       grst_body_text  rawindex;
+  struct       grst_dir_list   *list;
+  struct       grst_request request_data;
+  struct       tm               modified_tm;
+  struct       stat             stat_tmp;
+  time_t                        now;
+  struct fuse_context fuse_ctx;
+  
+  memcpy(&fuse_ctx, fuse_get_context(), sizeof(struct fuse_context));
+
+  if (debugmode) syslog(LOG_DEBUG, "in slashgrid_readdir");
+
+  if (strncmp(path, "/http/", 6) == 0)
+    asprintf(&url, "http://%s/", &path[6]);
+  else if (strncmp(path, "/https/", 7) == 0)
+    asprintf(&url, "https://%s/", &path[7]);
+  else return -ENOENT;
+
+  rawindex.text      = NULL;
+  rawindex.used      = 0;
+  rawindex.allocated = 0;
+
+  bzero(&request_data, sizeof(struct grst_request));
+  request_data.writefunction = rawbody_callback;
+  request_data.writedata     = (void *) &rawindex;
+  request_data.errorbuffer   = errorbuffer;
+  request_data.url           = url;
+  request_data.method        = GRST_SLASH_GET;
+  request_data.start         = -1;
+  request_data.finish        = -1;
+  
+  if (debugmode) syslog(LOG_DEBUG, "Get directory listing from URL %s", url);
+  
+  thiserror = perform_request(&request_data, &fuse_ctx);
+
+  if ((thiserror != 0) ||
+           (request_data.retcode <  200) ||
+           (request_data.retcode >= 300))
+         {
+           if (debugmode)
+                syslog(LOG_DEBUG, "... curl error: %s (%d), HTTP error: %d\n",
+                       errorbuffer, thiserror, request_data.retcode);
+           
+           if (thiserror != 0) anyerror = thiserror;
+           else                anyerror = request_data.retcode;
+
+           if (request_data.retcode == 403) return -EACCES;
+           else return -ENOENT; 
+/* memory clean up still needed here!!!!!! */
+         }
+  else
+         {
+           time(&now);
+
+           filler(buf, ".", NULL, 0);
+           filler(buf, "..", NULL, 0);
+
+           rawindex.text[rawindex.used] = '\0';
+
+           list  = index_to_dir_list(rawindex.text, url);
+           ilast = -1;
+
+           for (i=0; list[i].filename != NULL; ++i)
+              {
+                if (strncmp(list[i].filename, "mailto:", 7) == 0) continue;
+
+                /* skip over duplicates */
+                
+                if ((ilast >= 0) && 
+                    (strcmp(list[i].filename, list[ilast].filename) == 0))
+                                                                 continue;
+                ilast=i; /* last distinct entry */
+                
+                asprintf(&s, "%s/%s", path, list[i].filename);
+                write_headers_to_cache(&fuse_ctx, s, list[i].length, 
+                                       list[i].modified);
+                free(s);
+           
+                bzero(&stat_tmp, sizeof(struct stat));
+                stat_tmp.st_size  = list[i].length;
+                stat_tmp.st_mtime = list[i].modified;
+                stat_tmp.st_ctime = list[i].modified;
+                stat_tmp.st_atime = now;
+                stat_tmp.st_mode  = 0666;
+            
+                filler(buf, list[i].filename, &stat_tmp, 0);
+              }
+         }
+     
+  return 0;
+}
+
+static int slashgrid_getattr(const char *rawpath, struct stat *stbuf)
+{
+  int          anyerror = 0, thiserror, i, ilast, len;
+  char        *s, *url, *path, errorbuffer[CURL_ERROR_SIZE+1] = "";
+  struct       grst_dir_list   *list;
+  struct       grst_request request_data;
+  struct       tm               modified_tm;
+  struct       stat             stat_tmp;
+  time_t                        now;
+
+  struct fuse_context fuse_ctx;
+
+  memcpy(&fuse_ctx, fuse_get_context(), sizeof(struct fuse_context));
+
+  if (debugmode) syslog(LOG_DEBUG, 
+                         "in slashgrid_getattr, rawpath=%s, UID=%d\n",
+                         rawpath, fuse_ctx.uid);
+
+  memset(stbuf, 0, sizeof(struct stat));
+  stbuf->st_mode  = S_IFREG | 0755;
+  stbuf->st_nlink = 1;
+  
+  if ((strcmp(rawpath, "/")      == 0) ||
+      (strcmp(rawpath, "/http")  == 0) ||
+      (strcmp(rawpath, "/https") == 0))
+    {
+      stbuf->st_mode = S_IFDIR | 0755;
+      stbuf->st_nlink = 2;
+              
+      return 0; /* The empty top level directory: OK */
+    }
+  else if (strncmp(rawpath, "/http/", 6) == 0)
+    {
+      if (index(&rawpath[6], '/') == NULL) /* top directory for remote server */
+        {
+          stbuf->st_mode = S_IFDIR | 0755;
+          stbuf->st_nlink = 2;
+          
+          asprintf(&url, "http://%s/", &rawpath[6]);
+          asprintf(&path, "%s/", rawpath);
+        }
+      else 
+        {
+          asprintf(&url, "http://%s", &rawpath[6]);      
+          path = strdup(rawpath);
+        }
+    }
+  else if (strncmp(rawpath, "/https/", 7) == 0)
+    {
+      if (index(&rawpath[7], '/') == NULL) /* top directory for remote server */
+        {
+          stbuf->st_mode = S_IFDIR | 0755;
+          stbuf->st_nlink = 2;
+
+          asprintf(&url, "https://%s/", &rawpath[7]);
+          asprintf(&path, "%s/", rawpath);
+        }
+      else 
+        {
+          asprintf(&url, "https://%s", &rawpath[7]);
+          path = strdup(rawpath);
+        }
+    }
+  else return -ENOENT;
+  
+  time(&now);
+
+  if (read_headers_from_cache(&fuse_ctx, path, 
+                              &(stbuf->st_size), &(stbuf->st_mtime)))
+    {
+      if (debugmode) syslog(LOG_DEBUG, 
+          "Retrieving details for %s from cache (%ld %ld)\n", url,
+          (long) stbuf->st_mtime, (long) stbuf->st_size);
+    
+      stbuf->st_ctime = stbuf->st_mtime;
+      stbuf->st_atime = now;
+      
+      free(url);
+      free(path);
+      return 0;    
+    }
+
+  bzero(&request_data, sizeof(struct grst_request));
+  request_data.writefunction = null_callback;
+  request_data.writedata     = NULL;
+  request_data.errorbuffer   = errorbuffer;
+  request_data.url           = url;
+  request_data.method        = GRST_SLASH_HEAD;
+  request_data.start         = -1;
+  request_data.finish        = -1;
+
+  thiserror = perform_request(&request_data, &fuse_ctx);
+
+  if ((thiserror != 0) ||
+           (request_data.retcode < 200) ||
+           (request_data.retcode > 301))
+         {
+           if (debugmode)
+                syslog(LOG_DEBUG, "... curl error: %s (%d), HTTP error: %d\n",
+                       errorbuffer, thiserror, request_data.retcode);
+           
+           if (thiserror != 0) anyerror = thiserror;
+           else                anyerror = request_data.retcode;
+
+           free(url);
+           free(path);
+           
+           if (request_data.retcode == 403) return -EACCES;
+           else return -ENOENT; 
+/* memory clean up still needed here!!!!!! */
+         }
+         
+  if (request_data.retcode == 301)
+    {
+       if (debugmode) syslog(LOG_DEBUG, "301 detected");
+
+       len = strlen(url);
+      
+       if ((request_data.location != NULL) &&
+          (len + 1 == strlen(request_data.location)) &&
+          (request_data.location[len] == '/') &&
+          (strncmp(url, request_data.location, len) == 0))
+        {
+          request_data.length_set   = 0;
+          request_data.modified_set = 0;
+          request_data.retcode      = 0;
+          request_data.url          = request_data.location;
+        
+          thiserror = perform_request(&request_data, &fuse_ctx);
+
+          if ((thiserror != 0) ||
+              (request_data.retcode < 200) ||
+              ((request_data.retcode > 299) && (request_data.retcode != 403)))
+            {
+              if (debugmode)
+                syslog(LOG_DEBUG, "... curl error: %s (%d), HTTP error: %d\n",
+                       errorbuffer, thiserror, request_data.retcode);
+           
+              if (thiserror != 0) anyerror = thiserror;
+              else                anyerror = request_data.retcode;
+
+              free(url);
+              free(path);
+              return -ENOENT; 
+/* memory clean up still needed here!!!!!! */
+            }
+            
+          stbuf->st_mode  = S_IFDIR | 0755;  /* this is a directory */
+          stbuf->st_nlink = 2;
+
+          free(path);
+          asprintf(&path, "%s/", rawpath);
+        }
+      else 
+        {
+          free(url);
+          free(path);
+          return -ENOENT;
+        }
+    }
+
+  if (request_data.length_set) stbuf->st_size  = request_data.length;
+  else stbuf->st_size = 0;
+  
+  if (request_data.modified_set)
+    {
+      stbuf->st_mtime = request_data.modified;
+      stbuf->st_ctime = request_data.modified;
+    }
+
+  stbuf->st_atime = now;
+
+  write_headers_to_cache(&fuse_ctx, path, stbuf->st_size, stbuf->st_mtime);
+
+  free(url);
+  free(path);
+  return 0;
+}
+
+
+int write_block_to_cache(struct fuse_context *fuse_ctx, char *filename,  
+                         off_t start, off_t finish)
+{
+  int          anyerror = 0, thiserror, i, fd;
+  char        *s, *url, *tempfile, *encoded_filename, *p,
+              *newdir, *new_filename, errorbuffer[CURL_ERROR_SIZE+1] = "";
+  struct       stat statbuf;
+  struct       grst_request request_data;
+  FILE        *fp;
+
+  asprintf(&tempfile, "%s/blocks-XXXXXX", GRST_SLASH_TMP);
+  fd = mkstemp(tempfile);
+
+  if (fd == -1)
+    {
+      free(tempfile);
+      return -EIO;
+    }
+
+  fp = fdopen(fd, "w");
+
+  if (strncmp(filename, "/http/", 6) == 0)
+    asprintf(&url, "http://%s", &filename[6]);
+  else if (strncmp(filename, "/https/", 7) == 0)
+    asprintf(&url, "https://%s", &filename[7]);
+  else return -ENOENT;
+
+  bzero(&request_data, sizeof(struct grst_request));
+  request_data.writefunction = fwrite;
+  request_data.writedata     = (void *) fp;
+  request_data.errorbuffer   = errorbuffer;
+  request_data.url           = url;
+  request_data.method        = GRST_SLASH_GET;
+  request_data.start         = start;
+  request_data.finish        = finish;
+
+  if (debugmode) syslog(LOG_DEBUG, "Get block %ld-%ld from URL %s\n",
+                                   (long) start, (long) finish, url);
+  
+  thiserror = perform_request(&request_data, fuse_ctx);
+
+  free(url);
+
+  fclose(fp);  
+
+  if ((thiserror != 0) ||
+           (request_data.retcode <  200) ||
+           (request_data.retcode >= 300))
+         {
+           if (debugmode)
+                syslog(LOG_DEBUG, "... curl error: %s (%d), HTTP error: %d\n",
+                       errorbuffer, thiserror, request_data.retcode);
+
+           if (thiserror != 0) anyerror = thiserror;
+           else                anyerror = request_data.retcode;
+
+           if (request_data.retcode == 403) return -EACCES;
+           else return -ENOENT; 
+/* memory clean up still needed here!!!!!! */
+         }
+
+  encoded_filename = GRSThttpUrlMildencode(filename);
+
+// need to protect against .. ?
+// can optimise by checking for existing of filename as a dir at the start
+
+  for (p = encoded_filename; ; ++p)
+     {  
+       if ((*p != '/') && (*p != '\0')) continue;
+     
+       if (*p == '/') 
+         {
+           *p = '\0';
+           asprintf(&newdir, "%s/%d%s", 
+                    GRST_SLASH_BLOCKS, fuse_ctx->uid, encoded_filename);
+           *p = '/';
+         }
+       else asprintf(&newdir, "%s/%d%s", 
+                     GRST_SLASH_BLOCKS, fuse_ctx->uid, encoded_filename);
+           
+       if (stat(newdir, &statbuf) != 0)
+                                   mkdir(newdir, S_IRUSR | S_IWUSR | S_IXUSR);
+       free(newdir);
+       
+       if (*p == '\0') break;
+     }
+
+  asprintf(&new_filename, "%s/%d%s/%ld-%ld", 
+       GRST_SLASH_BLOCKS, fuse_ctx->uid, encoded_filename, (long) start, (long) finish);
+
+  free(encoded_filename);
+  
+  rename(tempfile, new_filename);
+
+  if (debugmode) syslog(LOG_DEBUG, "Added %s to block cache", new_filename);
+
+  free(tempfile);
+  free(new_filename);
+
+  return 0;
+}
+
+int drop_cache_blocks(struct fuse_context *fuse_ctx, char *filename)
+/* drop ALL the blocks cached for this file, and delete the directory in
+   the blocks cache for this file */
+{
+  int   ret;
+  char *encoded_filename, *dirname, *blockname;
+  DIR *blocksDIR;
+  struct dirent *blocks_ent;
+
+  encoded_filename = GRSThttpUrlMildencode(filename);
+  
+  asprintf(&dirname, "%s/%d%s", 
+                     GRST_SLASH_BLOCKS, fuse_ctx->uid, encoded_filename);
+
+  free(encoded_filename);
+
+  blocksDIR = opendir(dirname);
+  
+  if (blocksDIR == NULL) /* no directory to delete (probably) */
+    {
+      free(dirname);
+      return 1;
+    }
+    
+  while ((blocks_ent = readdir(blocksDIR)) != NULL)
+       {
+         asprintf(&blockname, "%s/%s", dirname, blocks_ent->d_name);
+         remove(blockname);
+         free(blockname);
+       }  
+  
+  closedir(blocksDIR);
+    
+  ret = rmdir(dirname);
+  free(dirname);  
+
+  return ret ? 1 : 0; /* return 1 on error, 0 on rmdir() success */
+}
+
+static int slashgrid_read(const char *path, char *buf, 
+                          size_t size, off_t offset,
+                          struct fuse_file_info *fi)
+{
+  (void) offset;
+  (void) fi;
+
+  int          anyerror = 0, thiserror, i, ilast, fd;
+  char        *s, *url, *disk_filename, *encoded_filename;
+  off_t        block_start, block_finish, block_i, len;
+  struct       grst_body_text   rawbody;
+  struct       grst_request request_data;
+  struct       tm               modified_tm;
+  struct       stat             statbuf;
+  time_t                        now;
+  struct fuse_context fuse_ctx;
+
+  memcpy(&fuse_ctx, fuse_get_context(), sizeof(struct fuse_context));
+  
+  if (debugmode) syslog(LOG_DEBUG, "in slashgrid_read size=%ld offset=%ld",
+                                    (long) size, (long) offset);
+
+  if ((strncmp(path, "/http/",  6) != 0) &&
+      (strncmp(path, "/https/", 7) != 0)) return -ENOENT;
+
+  block_start  = GRST_SLASH_BLOCK_SIZE * (offset / GRST_SLASH_BLOCK_SIZE);
+  block_finish = GRST_SLASH_BLOCK_SIZE *
+                                ((offset + size - 1) / GRST_SLASH_BLOCK_SIZE);
+
+  encoded_filename = GRSThttpUrlMildencode((char *) path);
+  time(&now);
+  for (block_i = block_start; block_i <= block_finish; block_i += GRST_SLASH_BLOCK_SIZE)
+     {     
+       asprintf(&disk_filename, "%s/%d%s/%ld-%ld", 
+                 GRST_SLASH_BLOCKS, fuse_ctx.uid, encoded_filename, 
+                 (long) block_i, (long) (block_i + GRST_SLASH_BLOCK_SIZE - 1));
+
+       if (debugmode) syslog(LOG_DEBUG, "disk_filename=%s", disk_filename);
+                 
+       if ((stat(disk_filename, &statbuf) != 0) ||
+           (statbuf.st_mtime < now - GRST_SLASH_HEADERS_EXPIRE))
+         {
+           write_block_to_cache(&fuse_ctx, (char *) path, 
+                            block_i, block_i + GRST_SLASH_BLOCK_SIZE - 1);
+         }
+
+// need to worry about cached copy being deleted (invalidated by a writing
+// thread?) between write_block_to_cache() and these reads?
+// maybe return fd from write_block_to_cache() itself???
+// the initial stat() needs to be part of this too
+
+       if ((fd = open(disk_filename, O_RDONLY)) != -1)
+         {
+           if (block_i == block_start)              
+             {
+               lseek(fd, offset - block_start, SEEK_SET);
+               read(fd, buf, 
+                        (offset - block_start + size < GRST_SLASH_BLOCK_SIZE) 
+                       ? size : GRST_SLASH_BLOCK_SIZE - offset + block_start);
+             }
+           else if (block_i == block_finish)
+             {
+               read(fd, buf + (block_i - block_start),
+                        offset + size - block_i);
+             }
+           else 
+             {
+               read(fd, buf + (block_i - block_start), 
+                        GRST_SLASH_BLOCK_SIZE);
+             }
+             
+           close(fd);
+         }        
+       else syslog(LOG_ERR, "Failed to open %s in cache", disk_filename);
+     }
+
+  free(disk_filename);
+  free(encoded_filename);
+
+  return size;
+}
+
+static int slashgrid_write(const char *path, const char *buf, 
+                           size_t size, off_t offset,
+                           struct fuse_file_info *fi)
+{
+  int          anyerror = 0, thiserror, i, fd;
+  char        *s, *url, *p, errorbuffer[CURL_ERROR_SIZE+1] = "";
+
+  struct grst_read_data read_data;
+  struct grst_request request_data;
+  struct fuse_context fuse_ctx;
+  
+  memcpy(&fuse_ctx, fuse_get_context(), sizeof(struct fuse_context));  
+
+  if (strncmp(path, "/http/", 6) == 0)
+    asprintf(&url, "http://%s", &path[6]);
+  else if (strncmp(path, "/https/", 7) == 0)
+    asprintf(&url, "https://%s", &path[7]);
+  else return -ENOENT;
+
+  read_data.buf     = buf;
+  read_data.sent    = 0;
+  read_data.maxsent = size;
+
+  if (debugmode) syslog(LOG_DEBUG, "Put block %ld-%ld to URL %s", 
+                                   (long) offset, (long) offset+size-1, url);
+
+  drop_cache_blocks(&fuse_ctx, (char *) path); /* we drop all read-cache blocks first */
+  
+  bzero(&request_data, sizeof(struct grst_request));
+  request_data.writefunction = null_callback;
+  request_data.readfunction  = read_data_callback;
+  request_data.readdata      = &read_data;
+  request_data.infilesize    = size;
+  request_data.errorbuffer   = errorbuffer;
+  request_data.url           = url;
+  request_data.method        = GRST_SLASH_PUT;
+  request_data.start         = offset;
+  request_data.finish        = (off_t) (offset + size - 1);
+
+  thiserror = perform_request(&request_data, &fuse_ctx);
+
+  free(url);
+
+  if ((thiserror != 0) ||
+      (request_data.retcode <  200) ||
+      (request_data.retcode >= 300))
+    {
+           if (debugmode)
+                syslog(LOG_DEBUG, "... curl error: %s (%d), HTTP error: %d\n",
+                       errorbuffer, thiserror, request_data.retcode);
+           
+           if (thiserror != 0) anyerror = thiserror;
+           else                anyerror = request_data.retcode;
+
+           if (request_data.retcode == 403) return -EACCES;
+           else return -ENOENT; 
+/* memory clean up still needed here!!!!!! */
+    }
+
+  return size;
+}
+
+int slashgrid_rename(const char *oldpath, const char *newpath)
+{
+  int          anyerror = 0, thiserror, i, fd;
+  char        *s, *url, *p, *destination, errorbuffer[CURL_ERROR_SIZE+1] = "";
+
+  struct grst_read_data read_data;
+  struct fuse_context fuse_ctx;
+  struct grst_request request_data;
+
+  memcpy(&fuse_ctx, fuse_get_context(), sizeof(struct fuse_context));
+
+  if (strncmp(oldpath, "/http/", 6) == 0)
+    {
+      if (strncmp(newpath, "/http/", 6) != 0) return -EXDEV;
+
+      asprintf(&url,         "http://%s", &oldpath[6]);
+      asprintf(&destination, "http://%s", &newpath[6]);
+    }
+  else if (strncmp(oldpath, "/https/", 7) == 0)
+    {
+      if (strncmp(newpath, "/https/", 7) != 0) return -EXDEV;
+
+      asprintf(&url,         "https://%s", &oldpath[7]);
+      asprintf(&destination, "https://%s", &newpath[7]);
+    }
+  else return -ENOENT;
+
+  read_data.buf     = "";
+  read_data.sent    = 0;
+  read_data.maxsent = 0;
+
+  if (debugmode) syslog(LOG_DEBUG, "MOVE URL %s to %s", url, destination);
+  
+  bzero(&request_data, sizeof(struct grst_request));
+  request_data.writefunction = null_callback;
+  request_data.readfunction  = read_data_callback;
+  request_data.readdata      = &read_data;
+  request_data.errorbuffer   = errorbuffer;
+  request_data.url           = url;
+  request_data.method        = GRST_SLASH_MOVE;
+  request_data.destination   = destination;
+
+  thiserror = perform_request(&request_data, &fuse_ctx);
+
+  free(url);
+  free(destination);
+
+  if ((thiserror != 0) ||
+      (request_data.retcode <  200) ||
+      (request_data.retcode >= 300))
+    {
+      if (debugmode)
+                syslog(LOG_DEBUG, "... curl error: %s (%d), HTTP error: %d\n",
+                       errorbuffer, thiserror, request_data.retcode);
+
+      if (thiserror != 0) anyerror = thiserror;
+      else                anyerror = request_data.retcode;
+
+      if (request_data.retcode == 403) return -EACCES;
+      else return -ENOENT; 
+/* memory clean up still needed here!!!!!! */
+    }
+
+  return 0;
+}
+
+int slashgrid_unlink(const char *path)
+{
+  int          anyerror = 0, thiserror, i, fd;
+  char        *s, *url, *p, errorbuffer[CURL_ERROR_SIZE+1] = "";
+
+  struct grst_read_data read_data;
+  struct fuse_context fuse_ctx;
+  struct grst_request request_data;
+
+  memcpy(&fuse_ctx, fuse_get_context(), sizeof(struct fuse_context));
+
+  if (strncmp(path, "/http/", 6) == 0)
+    asprintf(&url, "http://%s", &path[6]);
+  else if (strncmp(path, "/https/", 7) == 0)
+    asprintf(&url, "https://%s", &path[7]);
+  else return -ENOENT;
+
+  read_data.buf     = "";
+  read_data.sent    = 0;
+  read_data.maxsent = 0;
+
+  if (debugmode) syslog(LOG_DEBUG, "DELETE URL %s", url);
+  
+  bzero(&request_data, sizeof(struct grst_request));
+  request_data.writefunction = null_callback;
+  request_data.readfunction  = read_data_callback;
+  request_data.readdata      = &read_data;
+  request_data.errorbuffer   = errorbuffer;
+  request_data.url           = url;
+  request_data.method        = GRST_SLASH_DELETE;
+
+  thiserror = perform_request(&request_data, &fuse_ctx);
+
+  free(url);
+
+  if ((thiserror != 0) ||
+           (request_data.retcode <  200) ||
+           (request_data.retcode >= 300))
+         {
+           if (debugmode)
+                syslog(LOG_DEBUG, "... curl error: %s (%d), HTTP error: %d\n",
+                       errorbuffer, thiserror, request_data.retcode);
+
+           if (thiserror != 0) anyerror = thiserror;
+           else                anyerror = request_data.retcode;
+
+           if (request_data.retcode == 403) return -EACCES;
+           else return -ENOENT; 
+/* memory clean up still needed here!!!!!! */
+         }
+
+  return 0;
+}
+
+int slashgrid_rmdir(const char *path)
+{
+  int   ret;
+  char *pathwithslash;
+  
+  asprintf(&pathwithslash, "%s/", path);
+  ret = slashgrid_unlink(pathwithslash);  
+  free(pathwithslash);
+
+/* error on GridSite side still??? */
+  
+  return ret;
+}
+
+int slashgrid_mknod(const char *path, mode_t mode, dev_t dev)
+{
+  int ret;
+  
+  ret = slashgrid_write(path, "", 0, 0, NULL);
+
+  return (ret < 0) ? ret : 0;
+}
+
+int slashgrid_mkdir(const char *path, mode_t mode)
+{
+  int   ret;
+  char *pathwithslash;
+  
+  asprintf(&pathwithslash, "%s/", path);
+  ret = slashgrid_write(pathwithslash, "", 0, 0, NULL);
+  free(pathwithslash);
+
+  return (ret < 0) ? ret : 0;
+}
+
+int slashgrid_chown(const char *path, uid_t uid, gid_t gid)
+{
+  puts("slashgrid_chown - NOP");
+  return 0;
+}
+
+int slashgrid_chmod(const char *path, mode_t mode)
+{
+  puts("slashgrid_chmod - NOP");
+  return 0;
+}
+
+int slashgrid_truncate(const char *path, off_t offset)
+{
+  int          anyerror = 0, thiserror, i, fd;
+  char        *s, *url, *p, errorbuffer[CURL_ERROR_SIZE+1] = "";
+
+  struct grst_read_data read_data;
+  struct fuse_context fuse_ctx;
+  struct grst_request request_data;
+
+  memcpy(&fuse_ctx, fuse_get_context(), sizeof(struct fuse_context));
+
+  if (strncmp(path, "/http/", 6) == 0)
+    asprintf(&url, "http://%s", &path[6]);
+  else if (strncmp(path, "/https/", 7) == 0)
+    asprintf(&url, "https://%s", &path[7]);
+  else return -ENOENT;
+
+  read_data.buf     = "";
+  read_data.sent    = 0;
+  read_data.maxsent = 0;
+
+  if (debugmode) syslog(LOG_DEBUG, "Truncate URL %s to %ld\n", 
+                                   url, (long) offset);
+  
+  bzero(&request_data, sizeof(struct grst_request));
+  request_data.writefunction = null_callback;
+  request_data.readfunction  = read_data_callback;
+  request_data.readdata      = &read_data;
+  request_data.errorbuffer   = errorbuffer;
+  request_data.url           = url;
+  request_data.method        = GRST_SLASH_TRUNC;
+  request_data.finish        = offset;
+
+  thiserror = perform_request(&request_data, &fuse_ctx);
+
+  free(url);
+
+  if ((thiserror != 0) ||
+           (request_data.retcode <  200) ||
+           (request_data.retcode >= 300))
+         {
+           if (debugmode)
+                syslog(LOG_DEBUG, "... curl error: %s (%d), HTTP error: %d\n",
+                       errorbuffer, thiserror, request_data.retcode);
+
+           if (thiserror != 0) anyerror = thiserror;
+           else                anyerror = request_data.retcode;
+
+           if (request_data.retcode == 403) return -EACCES;
+           else return -ENOENT; 
+/* memory clean up still needed here!!!!!! */
+         }
+
+  return 0;
+}
+
+int slashgrid_statfs(const char *path, struct statfs *fs)
+{
+  return statfs(GRST_SLASH_BLOCKS, fs);
+}
+
+void *slashgrid_init(void)
+{
+  FILE *fp;
+  
+  if ((fp = fopen(GRST_SLASH_PIDFILE, "w")) != NULL)
+    {
+      fprintf(fp, "%d\n", (int) getpid());
+      fclose(fp);
+    }
+
+  return NULL;
+}
+
+void slashgrid_destroy(void *p)
+{
+  unlink(GRST_SLASH_PIDFILE);
+}
+
+static struct fuse_operations slashgrid_oper = {
+  .getattr     = slashgrid_getattr,
+  .chown       = slashgrid_chown,    
+  .chmod       = slashgrid_chmod,
+  .truncate    = slashgrid_truncate,    
+  .readdir     = slashgrid_readdir,
+  .write       = slashgrid_write,
+  .read                = slashgrid_read,
+  .mknod       = slashgrid_mknod,
+  .mkdir       = slashgrid_mkdir,
+  .unlink      = slashgrid_unlink,
+  .rmdir       = slashgrid_rmdir,
+  .rename      = slashgrid_rename,
+  .statfs      = slashgrid_statfs,
+  .init                = slashgrid_init,
+  .destroy     = slashgrid_destroy
+};
+
+int main(int argc, char *argv[])
+{
+#define FUSE_ARGC 4
+  char *fuse_argv[FUSE_ARGC] = { "slashgrid", "/grid", "-o", "allow_other" };
+  int   i, ret;
+  
+  for (i=1; i < argc; ++i)
+    if (strcmp(argv[i], "--debug") == 0) debugmode = 1;
+
+  openlog("slashgrid", 0, LOG_DAEMON);
+    
+  umount("/grid"); /* in case of a crash, but will fail if still busy */
+
+  for (i=0; i < GRST_SLASH_MAX_HANDLES; ++i)
+     {
+       pthread_mutex_init(&(handles[i].mutex), NULL);
+       handles[i].curl_handle = NULL;
+       handles[i].proxyfile   = NULL;
+       handles[i].last_used   = 0;
+     }
+
+  ret = fuse_main(FUSE_ARGC, fuse_argv, &slashgrid_oper);
+
+  return ret;
+}
diff --git a/org.gridsite.core/src/slashgrid.init b/org.gridsite.core/src/slashgrid.init
new file mode 100755 (executable)
index 0000000..44a12ad
--- /dev/null
@@ -0,0 +1,66 @@
+#!/bin/bash
+#
+# slashgrid        Startup script for the SlashGrid client-side server
+#
+# chkconfig: - 90 10
+# description: Filesystem extension for access to HTTP(S) file servers
+# processname: slashgrid
+# config: /etc/sysconfig/slashgrid
+# pidfile: /var/run/slashgrid.pid
+
+# Source function library.
+. /etc/rc.d/init.d/functions
+
+# Examples of useful overrides of default settings:
+#
+# OPTIONS=--debug                      # to turn on LOG_DEBUG in syslog
+# SLASHGRID=/usr/local/sbin/slashgrid  # if slashgrid not in /usr/sbin
+# PATH=$PATH:/usr/local/bin            # if fusermount not on old $PATH
+#
+if [ -f /etc/sysconfig/slashgrid ] ; then
+ . /etc/sysconfig/slashgrid
+fi
+
+slashgrid=${SLASHGRID-/usr/sbin/slashgrid}
+pidfile=${PIDFILE-/var/run/slashgrid.pid}
+lockfile=${LOCKFILE-/var/lock/subsys/slashgrid}
+prog=slashgrid
+RETVAL=0
+
+start() {
+        echo -n $"Starting $prog: "
+        modprobe fuse
+        daemon $slashgrid $OPTIONS
+        RETVAL=$?
+        echo
+        [ $RETVAL = 0 ] && touch ${lockfile}
+        return $RETVAL
+}
+
+stop() {
+       echo -n $"Stopping $prog: "
+       killproc $slashgrid
+       RETVAL=$?
+       echo
+       [ $RETVAL = 0 ] && rm -f ${lockfile} ${pidfile}
+}
+
+# Invoke the right function
+
+case "$1" in
+  start)
+       start
+       ;;
+  stop)
+       stop
+       ;;
+  restart)
+       stop
+       start
+       ;;
+  *)
+       echo $"Usage: $prog {start|stop|restart|help}"
+       exit 1
+esac
+
+exit $RETVAL