Forgot the actual patch. Better performance than first try, but really
the API needs to change to use
a single pass through the post data to make real headway
w/performance. Also it is
possible to avoid the memcpy() & allocation as pointer directly to
post data + length can be returned for each form variable is possible as the
data is not encoded.
--
Øyvind Harboe
http://www.zylin.com - eCos ARM & FPGA developer kit
### Eclipse Workspace Patch 1.0
#P ecos
Index: net/athttpd/current/include/forms.h
===================================================================
RCS file: /cvs/ecos/ecos-opt/net/net/athttpd/current/include/forms.h,v
retrieving revision 1.1
diff -u -r1.1 forms.h
--- net/athttpd/current/include/forms.h 18 Jul 2006 16:37:24 -0000 1.1
+++ net/athttpd/current/include/forms.h 15 Nov 2007 19:20:56 -0000
@@ -73,6 +73,8 @@
// Prototypes.
char *cyg_httpd_store_form_data(char*);
+void
+cyg_httpd_fetch_form_data(char *name, char *buffer, int len, int *actual);
void cyg_httpd_handle_method_POST(void);
cyg_int8 cyg_httpd_from_hex (cyg_int8);
char *cyg_httpd_find_form_variable(char*);
Index: net/athttpd/current/include/http.h
===================================================================
RCS file: /cvs/ecos/ecos-opt/net/net/athttpd/current/include/http.h,v
retrieving revision 1.2
diff -u -r1.2 http.h
--- net/athttpd/current/include/http.h 27 Nov 2006 15:41:56 -0000 1.2
+++ net/athttpd/current/include/http.h 15 Nov 2007 19:20:56 -0000
@@ -102,6 +102,7 @@
#define CYG_HTTPD_MODE_SEND_HEADER_ONLY 0x0004
#define CYG_HTTPD_MODE_NO_CACHE 0x0008
#define CYG_HTTPD_MODE_FORM_DATA 0x0010
+#define CYG_HTTPD_MODE_MULTIPART_FORM_DATA 0x0020
// This must be generated at random...
#define CYG_HTTPD_MD5_AUTH_NAME "MD5"
@@ -175,6 +176,8 @@
// data (it might come in more than one frame) and must be visible to
// handlers and cgi scripts.
char *post_data;
+ // the boundary string used for multipart/form-data
+ char *boundary;
// This pointer points to the information about the domain that needs
// to be authenticated. It is only used by the function that builds the
Index: net/athttpd/current/src/cgi.c
===================================================================
RCS file: /cvs/ecos/ecos-opt/net/net/athttpd/current/src/cgi.c,v
retrieving revision 1.3
diff -u -r1.3 cgi.c
--- net/athttpd/current/src/cgi.c 27 Nov 2006 15:41:56 -0000 1.3
+++ net/athttpd/current/src/cgi.c 15 Nov 2007 19:20:57 -0000
@@ -157,10 +157,89 @@
}
#endif
+/* We're not going to write ad-hoc CGI scripts without good old printf! */
+void cyg_httpd_fprintf(char *format, ...)
+{
+ char *buffer = NULL;
+ int size = 1;
+ int n=-1;
+ va_list ap;
+ va_start(ap, format);
+ diag_vprintf(format, ap);
+ va_end(ap);
+
+
+ /* process format string */
+ for (;;)
+ {
+ buffer=malloc(size);
+ if (buffer==NULL)
+ {
+ return;
+ }
+
+ va_start(ap, format);
+ n = vsnprintf(buffer, size, format, ap);
+ va_end(ap);
+ if (n>0 && n < (size-1))
+ break;
+ size=size*2+1;
+ free(buffer);
+ }
+
+ if (n > 0)
+ {
+ cyg_httpd_write_chunked(buffer, strlen(buffer));
+ } else
+ {
+ /* vsnprintf failed */
+ }
+
+ if (buffer)
+ free(buffer);
+}
+
+
//
=============================================================================
// tcl CGI Support
//
=============================================================================
#ifdef CYGOPT_NET_ATHTTPD_USE_CGIBIN_TCL
+
+
+/* Error message has to go to HTTP to make debugging less of a torturous
process */
+void cyg_httpd_exec_cgi_tcl_error(Jim_Interp *interp)
+{
+ int len, i;
+
+ cyg_httpd_start_chunked("html");
+ cyg_httpd_fprintf("<html><body>\n");
+
+ cyg_httpd_fprintf("Runtime error, file \"%s\", line %d:<br>" ,
+ interp->errorFileName, interp->errorLine);
+ cyg_httpd_fprintf(" %s<br>" ,
+ Jim_GetString(interp->result, NULL));
+ Jim_ListLength(interp, interp->stackTrace, &len);
+ for (i = 0; i < len; i+= 3) {
+ Jim_Obj *objPtr;
+ const char *proc, *file, *line;
+
+ Jim_ListIndex(interp, interp->stackTrace, i, &objPtr, JIM_NONE);
+ proc = Jim_GetString(objPtr, NULL);
+ Jim_ListIndex(interp, interp->stackTrace, i+1, &objPtr,
+ JIM_NONE);
+ file = Jim_GetString(objPtr, NULL);
+ Jim_ListIndex(interp, interp->stackTrace, i+2, &objPtr,
+ JIM_NONE);
+ line = Jim_GetString(objPtr, NULL);
+ cyg_httpd_fprintf("In procedure '%s' called at file \"%s\", line
%s<br>" ,
+ proc, file, line);
+ }
+ cyg_httpd_fprintf("</html></body>\n");
+
+ cyg_httpd_end_chunked();
+}
+
+
int Jim_AioInit(Jim_Interp *);
cyg_int32
cyg_httpd_exec_cgi_tcl(char *file_name)
@@ -184,7 +263,12 @@
"post_data",
httpstate.post_data);
- Jim_Eval(httpstate.jim_interp, tcl_cmd);
+ int err;
+ err=Jim_Eval(httpstate.jim_interp, tcl_cmd);
+ if (err!=JIM_OK)
+ {
+ cyg_httpd_exec_cgi_tcl_error(httpstate.jim_interp);
+ }
return 0;
}
@@ -217,10 +301,45 @@
cyg_httpd_end_chunked();
return JIM_OK;
}
-
+
+int
+cyg_httpd_Jim_Command_formfetch(Jim_Interp *interp,
+ int argc,
+ Jim_Obj *const *argv)
+{
+ char *name = (char*)Jim_GetString(argv[1], NULL);
+
+ // Find length
+ char buf;
+ int actual;
+ cyg_httpd_fetch_form_data(name, &buf, 1, &actual);
+
+ int len=actual+1;
+ char *destBuffer=malloc(len);
+ if (destBuffer==NULL)
+ return JIM_ERR;
+
+ cyg_httpd_fetch_form_data(name, destBuffer, len, &actual);
+
+ Jim_Obj *objPtr;
+ objPtr = Jim_NewStringObj(interp, destBuffer, actual);
+ Jim_SetResult(interp, objPtr);
+ free(destBuffer);
+ return JIM_OK;
+}
+
+
+// If the application wants to add commands, it will need to do so after the
+// interpreter has been initialized. However, the initialization of the http
+// server is asynchronous. This function is safe to invoke *before* the http
+// server is started.
void
cyg_httpd_init_tcl_interpreter(void)
{
+ static bool initDone=false;
+ if (initDone)
+ return;
+ initDone=true;
// Start the TCL interpreter.
Jim_InitEmbedded();
httpstate.jim_interp = Jim_CreateInterp();
@@ -243,6 +362,11 @@
cyg_httpd_Jim_Command_endchunked,
NULL,
NULL);
+ Jim_CreateCommand(httpstate.jim_interp,
+ "formfetch",
+ cyg_httpd_Jim_Command_formfetch,
+ NULL,
+ NULL);
}
#endif
Index: net/athttpd/current/src/forms.c
===================================================================
RCS file: /cvs/ecos/ecos-opt/net/net/athttpd/current/src/forms.c,v
retrieving revision 1.4
diff -u -r1.4 forms.c
--- net/athttpd/current/src/forms.c 14 Nov 2007 14:39:13 -0000 1.4
+++ net/athttpd/current/src/forms.c 15 Nov 2007 19:20:57 -0000
@@ -83,44 +83,75 @@
: c - 'a' + 10;
}
+/* Scan through the entire string, but only store as much as the buffer can
hold.
+ * That way a string that is too long does stop subsequent variables from
+ * being read
+ *
+ * buflen - length of buffer, including space for \0. must be >= 1
+ * actual - actual length of string excluding \0
+ */
char*
-cyg_httpd_store_form_variable(char *query, cyg_httpd_fvars_table_entry *entry)
+cyg_httpd_store_form_variable(char *query, char *q, int buflen, int * actual)
{
char *p = query;
- char *q = entry->buf;
int len = 0;
-
- while (len < (entry->buflen - 1))
+ char c=0;
+ int gotChar;
+
+ for (;;)
+ {
+ int done = (len >= (buflen - 1));
switch(*p)
{
case '%':
p++;
+ gotChar=0;
if (*p)
- *q = cyg_httpd_from_hex(*p++) * 16;
+ {
+ c = cyg_httpd_from_hex(*p++) * 16;
+ gotChar=1;
+ }
if (*p)
- *q = (*q + cyg_httpd_from_hex(*p++));
- q++;
+ {
+ c = (c + cyg_httpd_from_hex(*p++));
+ gotChar=1;
+ }
+ if (!done&&gotChar)
+ {
+ *q++=c;
+ }
len++;
break;
case '+':
- *q++ = ' ';
+ if (!done)
+ {
+ *q++=' ';
+ }
p++;
len++;
break;
+ case 0: // Hmmm... I'm not quite sure how, in previous versions, the
\0 sentinel was caught...
case '&':
case ' ':
- *q++ = '\0';
- return p;
- default:
- *q++ = *p++;
+ goto doneLoop;
+ default:
+ c=*p++;
+ if (!done)
+ {
+ *q++ = c;
+ }
len++;
+ break;
}
- *q = '\0';
- while ((*p != ' ') && (*p != '&'))
- p++;
- return p;
+ }
+doneLoop:
+ *q = '\0';
+ *actual=len;
+ return p;
}
+
+
// We'll try to parse the data from the form, and store it in the variables
// that have been defined by the user in the 'form_variable_table'.
char*
@@ -164,7 +195,8 @@
}
// Found the variable, store the name.
- p = cyg_httpd_store_form_variable(++p2, entry);
+ int actual;
+ p = cyg_httpd_store_form_variable(++p2, entry->buf, entry->buflen,
&actual);
#if CYGOPT_NET_ATHTTPD_DEBUG_LEVEL > 1
diag_printf("Stored form variable: %s Value: %s\n",
entry->name,
@@ -176,6 +208,313 @@
return p;
}
+/* If the string contains *all* of prefix, return true.
+ * \n in prefix can mean \n or \r\n in 'p'.
+ */
+static char *contains(char *p, char *prefix, int len)
+{
+ int i;
+ for (i=0; i<strlen(prefix); i++)
+ {
+ if (len<=0)
+ {
+// diag_printf("mismatch\n");
+ return NULL;
+ }
+// diag_printf("p=%02xprefix=%02x\n", p[i], prefix[i]);
+// diag_printf("p=%cprefix=%c\n", p[i], prefix[i]);
+ if (*p!=prefix[i])
+ {
+ if
((len>=2)&&(prefix[i]=='\n')&&(*p=='\r')&&(p[1]=='\n'))
+ {
+ p+=2;
+ len-=2;
+ continue;
+ }
+// diag_printf("mismatch\n");
+ return NULL;
+ }
+ p++;
+ }
+// diag_printf("match\n");
+ return p;
+}
+
+/* if we're looking at this text, skip it, otherwise return NULL.
+ * If p==NULL, return NULL.
+ * If the function returns NULL, then *len is untouched
+ */
+static char *skip(char *p, int *len, char *expect)
+{
+// diag_printf("skip p=%08x %s %d\n", p, expect, *len);
+ if (p==NULL)
+ return NULL;
+// int i;
+// for (i=0; i<strlen(expect); i++)
+// {
+// diag_printf("%c", p[i]);
+// }
+// diag_printf("strncmp starting\n");
+ char *after;
+ after=contains(p, expect, *len);
+ if (after!=NULL)
+ {
+ // skip boundary
+ int l=after-p;
+ p+=l;
+ *len-=l;
+ return p;
+ }
+ return NULL;
+}
+
+// Same as skip() except that we skip forwards to the expected string
+static char *skipTo(char *p, int *len, char *expect)
+{
+// diag_printf("skipTo p=%08x %s\n", p, expect);
+ if (p==NULL)
+ return NULL;
+
+ if (strcmp(expect, "\n")==0)
+ {
+ int i;
+ // Special case to improve performance. This is the code
+ // that will skip large amounts of data
+ for (i=0; i<*len; i++)
+ {
+ if (p[i]=='\n')
+ {
+ break;
+ }
+ }
+ if (i!=*len)
+ {
+ // Found \n. Do we need to back up one?
+ if ((i>0)&&(p[i-1]=='\r'))
+ {
+ i--;
+ }
+ // found string.
+ *len-=i;
+ return p+i;
+ }
+ } else
+ {
+ int i;
+ int strLen=strlen(expect);
+ for (i=0; i<*len; i++)
+ {
+ int j;
+ char *cmp=p;
+ for (j=0; j<strLen; j++)
+ {
+ if (cmp[i+j]!=expect[j])
+ {
+ if
(((*len-i)>=2)&&(expect[j]=='\n')&&(cmp[i+j]=='\r')&&(cmp[i+j+1]=='\n'))
+ {
+ // interpret \r\n as line end
+ cmp++;
+ continue;
+ }
+ break;
+ }
+ }
+ if (j==strLen)
+ {
+ // found string.
+ *len-=i;
+ return p+i;
+ }
+ }
+ }
+ // did not find string
+ return NULL;
+}
+
+// Returns pointer to the beginning of a boundary,
+// i.e. the byte *after* the last char in
+// the value
+static char *skipUntilBoundary(char *p, int *origLen)
+{
+// diag_printf("skipUntilBoundary\n");
+ if (p==NULL)
+ return NULL;
+ int len=*origLen;
+ for (;;)
+ {
+ p=skipTo(p, &len, "\n");
+ // We're now at the beginning of the boundary
+ char *t=p;
+ int l=len;
+ p=skip(p, &len, "\n");
+ if (p==NULL)
+ return NULL;
+ char *p2;
+ p2=skip(p, &len, "--");
+ if (p2!=NULL)
+ {
+ if (contains(p2, httpstate.boundary, len))
+ {
+ // We back up a bit
+ *origLen=l;
+ return t;
+ }
+ // Not the boundary, continue
+ }
+ }
+ return NULL;
+}
+
+// Example(From Firefox):
+//
+//-----------------------------107852697226440
+//Content-Disposition: form-data; name="form_filename"
+//
+//test
+//-----------------------------107852697226440
+//Content-Disposition: form-data; name="form_filecontent"; filename="abc.txt"
+//Content-Type: text/plain
+//
+//file line 1
+//file line 2
+//file line 3
+//-----------------------------107852697226440
+//Content-Disposition: form-data; name="form_action"
+//
+//Upload
+//-----------------------------107852697226440--
+
+// Skip to variable and fish out contents
+static void
+cyg_httpd_fetch_multipart_form_data(char *name, char *buffer, int bufferlen,
int *actual)
+{
+ char *p=httpstate.post_data;
+ int len=httpstate.content_len;
+
+ if (httpstate.boundary==NULL)
+ {
+ return;
+ }
+
+// diag_printf("2YYYY %d\n", len);
+// int i;
+// for (i=0; i<strlen(p); i++)
+// {
+// diag_printf("%c", p[i]);
+// }
+// diag_printf("2YYYY\n");
+
+// diag_printf("2XXXXXX\nFinding %s\n", name);
+
+ for (;;)
+ {
+ p=skipTo(p, &len, "--");
+ p=skip(p, &len, "--");
+ p=skip(p, &len, httpstate.boundary);
+ p=skip(p, &len, "\n");
+ p=skip(p, &len, "Content-Disposition: form-data; name=\"");
+ char *start=p;
+ p=skipTo(p, &len, "\"");
+ if (p==NULL)
+ return;
+ char *end=p;
+ p=skip(p, &len, "\"");
+ if (contains(start, name, end-start))
+ {
+ // Found variable!
+ // Is this a file or just data?
+ // We're ignoring the filename
+ char *p2;
+ p2=skip(p, &len, "; filename=\"");
+ if (p2!=NULL)
+ {
+ p=p2;
+ p=skipTo(p, &len, "Content-Type: ");
+ }
+ p=skipTo(p, &len, "\n");
+ p=skip(p, &len, "\n");
+ p=skip(p, &len, "\n"); // extra blank line before data
+ start=p;
+ for (;;)
+ {
+ p=skipUntilBoundary(p, &len);
+ if (p==NULL)
+ return;
+ // Found end!
+ *actual=p-start;
+ int t;
+ t=bufferlen-1; // always leave space for a 0.
+ if (t>*actual)
+ t=*actual;
+ memcpy(buffer, start, t);
+ buffer[t]=0; // always poke a 0 afterwards. Note that
the actual data can also contain 0's.
+// diag_printf("tttt\n%s\n\nFound
name=%s\nvalue=\"%s\"\n", p, name, buffer);
+ return;
+ }
+ } else
+ {
+ // Skip this variable
+ p=skipUntilBoundary(p, &len);
+ if (p==NULL)
+ return;
+ }
+ }
+}
+
+// Find form variable, copy it to the buffer and return the actual length of
the variable
+//
+// If a variable is not found(or there are no post data) an empty string
+// is returned.
+void
+cyg_httpd_fetch_form_data(char *name, char *buffer, int len, int *actual)
+{
+ char *p2;
+
+ char *p=httpstate.post_data;
+
+
+ buffer[0]=0;
+ *actual=0;
+
+ if (!p) /* No form data? just return after clearing variables */
+ return;
+
+ if (httpstate.mode & CYG_HTTPD_MODE_MULTIPART_FORM_DATA)
+ {
+ cyg_httpd_fetch_multipart_form_data(name, buffer, len, actual);
+ return;
+ }
+
+
+ while (*p && *p != ' ')
+ {
+ if (!(p2 = strchr(p, '=')))
+ return; /* Malformed post? */
+ int var_length = (cyg_int32)p2 - (cyg_int32)p;
+
+ if (strncmp((const char*)p, name, var_length))
+ {
+ // No such variable. Run through the data.
+ while ((*p != '&') && (*p && *p != ' '))
+ p++;
+ if(*p == '&')
+ p++;
+ continue;
+ }
+
+ // Found the variable, store the name.
+ p = cyg_httpd_store_form_variable(++p2, buffer, len, actual);
+#if CYGOPT_NET_ATHTTPD_DEBUG_LEVEL > 1
+ diag_printf("Stored form variable: %s Value: %s\n",
+ name,
+ buffer);
+#endif
+
+ return;
+ }
+}
+
+
char*
cyg_httpd_find_form_variable(char *p)
{
Index: net/athttpd/current/src/http.c
===================================================================
RCS file: /cvs/ecos/ecos-opt/net/net/athttpd/current/src/http.c,v
retrieving revision 1.4
diff -u -r1.4 http.c
--- net/athttpd/current/src/http.c 14 Nov 2007 14:39:13 -0000 1.4
+++ net/athttpd/current/src/http.c 15 Nov 2007 19:20:58 -0000
@@ -851,7 +851,7 @@
{
// The deafult for HTTP 1.1 is keep-alive connections, unless specifically
// closed by the far end.
- httpstate.mode &= ~(CYG_HTTPD_MODE_CLOSE_CONN | CYG_HTTPD_MODE_FORM_DATA);
+ httpstate.mode &= ~(CYG_HTTPD_MODE_CLOSE_CONN | CYG_HTTPD_MODE_FORM_DATA |
CYG_HTTPD_MODE_MULTIPART_FORM_DATA);
httpstate.modified_since = -1;
httpstate.content_len = 0;
while ((*p != '\r') && (*p != '\n') & (*p != '\0'))
@@ -892,14 +892,51 @@
}
else if (strncasecmp(p, "Content-Type: ", 14) == 0)
{
- p = strchr(p, ':') + 2;
- if (p)
- // In the case of a POST request, this is the total length of
- // the payload, which might be spread across several frames.
- if (strncasecmp(p,
- "application/x-www-form-urlencoded",
- 33) == 0)
- httpstate.mode |= CYG_HTTPD_MODE_FORM_DATA;
+ p = p + strlen("Content-Type: ");
+ // In the case of a POST request, this is the total length of
+ // the payload, which might be spread across several frames.
+ if (strncasecmp(p,
+ "application/x-www-form-urlencoded",
+ strlen("application/x-www-form-urlencoded")) == 0)
+ httpstate.mode |= CYG_HTTPD_MODE_FORM_DATA;
+ if (strncasecmp(p,
+ "multipart/form-data",
+ strlen("multipart/form-data")) == 0)
+ {
+ httpstate.mode |= CYG_HTTPD_MODE_MULTIPART_FORM_DATA;
+ p+=strlen("multipart/form-data");
+ }
+
+ // Fish out boundary
+ // Content-type: multipart/form-data, boundary=AaB03x
+ if (httpstate.boundary!=NULL)
+ free(httpstate.boundary);
+ httpstate.boundary=NULL; // no boundary by default
+
+ while ((*p == ' ')||(*p == ';'))
+ {
+ p++;
+ }
+ if (strncmp(p, "boundary", strlen("boundary"))==0)
+ {
+ p+=strlen("boundary");
+ if (*p=='=')
+ {
+ p++;
+
+ char *start=p;
+ while
((*p!='\n')&&(*p!='\r')&&(*p!=';')&&(*p!=' '))
+ {
+ p++;
+ }
+ httpstate.boundary=malloc(p-start+1);
+ if (httpstate.boundary!=NULL)
+ {
+ memcpy(httpstate.boundary, start,
p-start);
+ httpstate.boundary[p-start]=0;
+ }
+ }
+ }
while (*p++ != '\n');
}
else if (strncasecmp("Host:", p, 5) == 0)
@@ -941,28 +978,28 @@
}
else if (strncasecmp(p, "Digest", 6) == 0)
{
- p += 6;
- while (*p == ' ')
- p++;
+ p += 6;
+ while (*p == ' ')
+ p++;
while ((*p != '\r') && (*p != '\n'))
- {
- if (strncasecmp(p, "realm=", 6) == 0)
+ {
+ if (strncasecmp(p, "realm=", 6) == 0)
p = cyg_httpd_digest_skip(p + 6);
- else if (strncasecmp(p, "username=", 9) == 0)
+ else if (strncasecmp(p, "username=", 9) == 0)
p = cyg_httpd_digest_skip(p + 9);
else if (strncasecmp(p, "nonce=", 6) == 0)
p = cyg_httpd_digest_skip(p + 6);
- else if (strncasecmp(p, "response=", 9) == 0)
+ else if (strncasecmp(p, "response=", 9) == 0)
p = cyg_httpd_digest_data(cyg_httpd_md5_response,
p + 9);
- else if (strncasecmp(p, "cnonce=", 7) == 0)
+ else if (strncasecmp(p, "cnonce=", 7) == 0)
p = cyg_httpd_digest_data(cyg_httpd_md5_cnonce, p + 7);
- else if (strncasecmp(p, "qop=", 4) == 0)
+ else if (strncasecmp(p, "qop=", 4) == 0)
p = cyg_httpd_digest_skip(p + 4);
- else if (strncasecmp(p, "nc=", 3) == 0)
+ else if (strncasecmp(p, "nc=", 3) == 0)
p = cyg_httpd_digest_data(cyg_httpd_md5_noncecount,
p + 3);
- else if (strncasecmp(p, "algorithm=", 10) == 0)
+ else if (strncasecmp(p, "algorithm=", 10) == 0)
p = cyg_httpd_digest_skip(p + 10);
else if (strncasecmp(p, "opaque=", 7) == 0)
p = cyg_httpd_digest_skip(p + 7);
Index: net/athttpd/current/ChangeLog
===================================================================
RCS file: /cvs/ecos/ecos-opt/net/net/athttpd/current/ChangeLog,v
retrieving revision 1.9
diff -u -r1.9 ChangeLog
--- net/athttpd/current/ChangeLog 14 Nov 2007 14:39:13 -0000 1.9
+++ net/athttpd/current/ChangeLog 15 Nov 2007 19:20:56 -0000
@@ -1,3 +1,10 @@
+2007-11-15 Oyvind Harboe <[EMAIL PROTECTED]>
+
+ * src/cgi.c: print error message to HTML response if tcl script fails.
+ Less torturous to debug tcl scripts.
+ * include/forms.h,include/http.h,src/cgi.c,src/forms.c,src/http.c:
+ file upload support.
+
2007-11-12 Oyvind Harboe <[EMAIL PROTECTED]>
2007-11-12 Jonathan Larmour <[EMAIL PROTECTED]>
--
Before posting, please read the FAQ: http://ecos.sourceware.org/fom/ecos
and search the list archive: http://ecos.sourceware.org/ml/ecos-discuss