This might be of help, as the subject was discussed frequently.
Best regards
Martin
/*
DESCRIPTION:
my_sscanf without malloc. Its helpers and spinoffs:
my_atoi
my_atof
my_strtol
my_strtoul
my_strtod
No string, language or complete error support and other limitations.
Based on glibc-1.09 by Free Software Foundation, Inc.
so this is GPL'ed.
Adapted by Martin Reczko ([EMAIL PROTECTED]), July 2001
SETTINGS:
For now, just one language supported: The one with . as a decimal
point.
Change as needed:
#define DECIMALPOINT '.'
To avoid call to pow, so no math lib is needed
#define SLOW_POWER_CALCULATION
For testing
#define STANDALONE_TEST
to include this in RTL code, #undef STANDALONE_TEST below
COMPILE:
if SLOW_POWER_CALCULATION is defined:
cc my_sscanf.c
else
cc my_sscanf.c -lm
TESTRUN:
./a.out
Enter format string for: one int, one float, one double (or quit):%x
must match %f %lf
Enter string for one int and one float:123 must match 1e-9
-3.2e123
String:123 must match 1e-9 -3.2e123
Format:%x must match %f %lf
Number of assigned arguments: 3
Values: int: 291 float: 1.000000e-09 double: -3.200000e+123
Enter format string for: one int, one float, one double (or quit):quit
GNU NOTICE from glibc:
Copyright (C) 1991, 1992 Free Software Foundation, Inc.
This file is part of the GNU C Library.
The GNU C Library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public License as
published by the Free Software Foundation; either version 2 of the
License, or (at your option) any later version.
The GNU C Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public
License along with the GNU C Library; see the file COPYING.LIB. If
not, write to the Free Software Foundation, Inc., 675 Mass Ave,
Cambridge, MA 02139, USA. */
/* #define DBG */ /* very noisy debug info */
/* MR14072001: see note above */
#define DECIMALPOINT '.'
#define SLOW_POWER_CALCULATION /* to avoid call to pow */
#define STANDALONE_TEST
#include <stdarg.h>
#ifdef STANDALONE_TEST
#include <stdio.h> /* printf's for testing */
#else
#define EOF 0
#endif
#include <errno.h>
#include <float.h> /* float limits ...*/
#ifndef SLOW_POWER_CALCULATION
#include <math.h> /* pow */
#endif
#include <limits.h>
#include <ctype.h> /* isdigit... */
#ifdef STANDALONE_TEST
/* put these into my_stdio.h */
/* from here */
#define DOUBLE double
int my_atoi(char *s);
DOUBLE my_atof(char *s);
long int my_strtol(const char *nptr, char **endptr, int base);
unsigned long int my_strtoul(const char *nptr, char **endptr, int base);
DOUBLE my_strtod(char *nptr,char **endptr);
int my_sscanf(char *s,char *format,...);
/* and #define sscanf my_sscanf etc. if unmodified calls are needed */
/* to here */
#endif
#define wchar_t char
#define inchar() ((c = *(mys++)) == 0 ? 0 : (++read_in, c))
#define conv_error() return ((c == 0 || ungetc(c, s)), done)
#define input_error() return (done == 0 ? -1 : done)
#define memory_error() return ((errno = ENOMEM), -1)
#define is_longlong is_long_double
int my_sscanf(char *s,char *format,...)
{
va_list arg;
register char *f = format;
register char *mys=s;
register char fc; /* Current character of the format. */
register size_t done = 0; /* Assignments done. */
register size_t read_in = 0; /* Chars read in. */
register int c; /* Last char read. */
register int do_assign; /* Whether to do an assignment. */
register int width; /* Maximum field width. */
/* Type modifiers. */
char is_short, is_long, is_long_double;
int malloc_string; /* Args are char ** to be filled in. */
/* Status for reading F-P nums. */
char got_dot, got_e;
/* If a [...] is a [^...]. */
char not_in;
/* Base for integral numbers. */
int base;
/* Signedness for integral numbers. */
int number_signed;
/* Integral holding variables. */
long int num;
unsigned long int unum;
/* Floating-point holding variable. */
DOUBLE fp_num;
/* Character-buffer pointer. */
register char *str, **strptr;
size_t strsize;
/* Workspace. */
char work[200];
char *w; /* Pointer into WORK. */
/* MR14072001: see note above */
wchar_t decimal=DECIMALPOINT; /* Decimal point character. */
#ifdef DBG
printf("my_sscanf called with |%s| |%s|\n",s,format);
#endif
va_start(arg, format);
if ((s==NULL) || (format == NULL)) return -1;
c = inchar();
/* Run through the format string. */
while (*f != '\0')
{
fc = *f++;
if (fc != '%')
{
/* Characters other than format specs must just match. */
if (c == EOF)
input_error();
if (isspace(fc))
{
/* Whitespace characters match any amount of whitespace. */
while (isspace (c))
inchar ();
continue;
}
else if (c == fc)
(void) inchar();
else
return done;/*conv_error();*/
continue;
}
/* Check for the assignment-suppressant. */
if (*f == '*')
{
do_assign = 0;
++f;
}
else
do_assign = 1;
/* Find the maximum field width. */
width = 0;
while (isdigit(*f))
{
width *= 10;
width += *f++ - '0';
}
if (width == 0)
width = -1;
/* Check for type modifiers. */
is_short = is_long = is_long_double = malloc_string = 0;
while (*f == 'h' || *f == 'l' || *f == 'L' || *f == 'a' || *f ==
'q')
switch (*f++)
{
case 'h':
/* int's are short int's. */
is_short = 1;
break;
case 'l':
if (is_long)
/* A double `l' is equivalent to an `L'. */
is_longlong = 1;
else
/* int's are long int's. */
is_long = 1;
break;
case 'q':
case 'L':
/* double's are long double's, and int's are long long int's. */
is_long_double = 1;
break;
case 'a':
/* String conversions (%s, %[) take a `char **'
arg and fill it in with a malloc'd pointer. */
malloc_string = 1;
break;
}
/* End of the format string? */
if (*f == '\0')
return done;/*conv_error();*/
/* Find the conversion specifier. */
w = work;
fc = *f++;
if (fc != '[' && fc != 'c' && fc != 'n')
/* Eat whitespace. */
while (isspace(c))
(void) inchar();
switch (fc)
{
case '%': /* Must match a literal '%'. */
if (c != fc)
return done;/*conv_error();*/
break;
case 'n': /* Answer number of assignments done. */
if (do_assign)
*va_arg(arg, int *) = read_in;
break;
case 'c': /* Match characters. */
if (do_assign)
{
str = va_arg (arg, char *);
if (str == NULL)
return -1;/*conv_error ();*/
}
if (c == EOF)
input_error();
if (width == -1)
width = 1;
if (do_assign)
{
do
*str++ = c;
while (inchar() != EOF && --width > 0);
}
else
while (inchar() != EOF && width > 0)
--width;
if (do_assign)
++done;
break;
#if 0 /* case without malloc should be handled... */
case 's': /* Read a string. */
#define STRING_ARG
if (do_assign)
{
if (malloc_string)
{
/* The string is to be stored in a malloc'd buffer. */
strptr = va_arg (arg, char **);
if (strptr == NULL)
return done;/*conv_error ();*/
/* Allocate an initial buffer. */
strsize = 100;
*strptr = str = malloc (strsize);
}
else
str = va_arg (arg, char *);
if (str == NULL)
return done;/*conv_error ();*/
}
STRING_ARG;
if (c == EOF)
input_error ();
do
{
if (isspace (c))
break;
#define STRING_ADD_CHAR(c)
if (do_assign
{
*str++ = c;
if (malloc_string && str == *strptr + strsize)
{
/* Enlarge the buffer. */
str = realloc (*strptr, strsize * 2);
if (str == NULL)
{
/* Can't allocate that much. Last-ditch effort. */
str = realloc (*strptr, strsize + 1);
if (str == NULL)
{
/* We lose. Oh well.
Terminate the string and stop converting,
so at least we don't swallow any input. */
(*strptr)[strsize] = '\0';
++done;
return done;/*conv_error ();*/
}
else
{
*strptr = str;
str += strsize;
++strsize;
}
}
else
{
*strptr = str;
str += strsize;
strsize *= 2;
}
}
}
STRING_ADD_CHAR (c);
} while (inchar () != EOF && (width <= 0 || --width > 0));
if (do_assign)
{
*str = '\0';
++done;
}
break;
#endif
case 'x': /* Hexadecimal integer. */
case 'X': /* Ditto. */
base = 16;
number_signed = 0;
goto number;
case 'o': /* Octal integer. */
base = 8;
number_signed = 0;
goto number;
case 'u': /* Unsigned decimal integer. */
base = 10;
number_signed = 0;
goto number;
case 'd': /* Signed decimal integer. */
base = 10;
number_signed = 1;
goto number;
case 'i': /* Generic number. */
base = 0;
number_signed = 1;
number:
if (c == EOF)
input_error();
/* Check for a sign. */
if (c == '-' || c == '+')
{
*w++ = c;
if (width > 0)
--width;
(void) inchar();
}
/* Look for a leading indication of base. */
if (c == '0')
{
if (width > 0)
--width;
*w++ = '0';
(void) inchar();
if (tolower(c) == 'x')
{
if (base == 0)
base = 16;
if (base == 16)
{
if (width > 0)
--width;
(void) inchar();
}
}
else if (base == 0)
base = 8;
}
if (base == 0)
base = 10;
/* Read the number into WORK. */
do
{
if (base == 16 ? !isxdigit(c) :
(!isdigit(c) || c - '0' >= base))
break;
*w++ = c;
if (width > 0)
--width;
} while (inchar() != EOF && width != 0);
if (w == work ||
(w - work == 1 && (work[0] == '+' || work[0] == '-')))
/* There was on number. */
return done;/*conv_error();*/
/* Convert the number. */
*w = '\0';
if (number_signed)
num = my_strtol (work, &w, base);
else
unum = my_strtoul (work, &w, base);
if (w == work)
return done;/*conv_error ();*/
#ifdef DBG
printf("INTEGER part|%s| rest|%s| base:%d\n",work,w,base);
#endif
if (do_assign)
{
if (! number_signed)
{
if (is_longlong)
*va_arg (arg, unsigned int *) = unum;
else if (is_long)
*va_arg (arg, unsigned long int *) = unum;
else if (is_short)
*va_arg (arg, unsigned short int *)
= (unsigned short int) unum;
else
*va_arg(arg, unsigned int *) = (unsigned int) unum;
}
else
{
if (is_longlong)
*va_arg(arg, int *) = num;
else if (is_long)
*va_arg(arg, long int *) = num;
else if (is_short)
*va_arg(arg, short int *) = (short int) num;
else
*va_arg(arg, int *) = (int) num;
}
++done;
#ifdef DBG
printf("INTEGER arg# : %d assigned value: num:%d
umum:%d\n",done,num,unum);
#endif
}
break;
case 'e': /* Floating-point numbers. */
case 'E':
case 'f':
case 'g':
case 'G':
if (c == EOF)
input_error();
/* Check for a sign. */
if (c == '-' || c == '+')
{
*w++ = c;
if (inchar() == EOF)
/* EOF is only an input error before we read any chars. */
return done;/*conv_error();*/
if (width > 0)
--width;
}
got_dot = got_e = 0;
do
{
if (isdigit(c))
*w++ = c;
else if (got_e && w[-1] == 'e' && (c == '-' || c == '+'))
*w++ = c;
else if (!got_e && tolower(c) == 'e')
{
*w++ = 'e';
got_e = got_dot = 1;
}
else if (c == decimal && !got_dot)
{
*w++ = c;
got_dot = 1;
}
else
break;
if (width > 0)
--width;
} while (inchar() != EOF && width != 0);
if (w == work)
return done;/*conv_error();*/
if (w[-1] == '-' || w[-1] == '+' || w[-1] == 'e')
return done;/*conv_error();*/
#ifndef MIB_HACKS
/* Convert the number. */
*w = '\0';
fp_num = my_strtod(work, &w);
#ifdef DBG
printf("FLOAT part|%s| rest|%s| value:%e\n",work,w,fp_num);
#endif
if (w == work)
return done;/*conv_error();*/
if (do_assign)
{
if (is_long_double)
*va_arg(arg, double *) = fp_num;
else if (is_long)
*va_arg(arg, double *) = (double) fp_num;
else
*va_arg(arg, float *) = (float) fp_num;
++done;
#ifdef DBG
printf("FLOAT arg# : %d assigned value: %e\n",done,fp_num);
#endif
}
break;
#endif /* MIB_HACKS */
#if 0
case '[': /* Character class. */
STRING_ARG;
if (c == EOF)
input_error();
if (*f == '^')
{
++f;
not_in = 1;
}
else
not_in = 0;
while ((fc = *f++) != '\0' && fc != ']')
{
if (fc == '-' && *f != '\0' && *f != ']' &&
w > work && w[-1] <= *f)
/* Add all characters from the one before the '-'
up to (but not including) the next format char. */
for (fc = w[-1] + 1; fc < *f; ++fc)
*w++ = fc;
else
/* Add the character to the list. */
*w++ = fc;
}
if (fc == '\0')
return done;/*conv_error();*/
*w = '\0';
unum = read_in;
do
{
if ((strchr (work, c) == NULL) != not_in)
break;
STRING_ADD_CHAR (c);
if (width > 0)
--width;
} while (inchar () != EOF && width != 0);
if (read_in == unum)
return done;/*conv_error ();*/
if (do_assign)
{
*str = '\0';
++done;
}
break;
#endif
case 'p': /* Generic pointer. */
base = 16;
/* A PTR must be the same size as a `long int'. */
is_long = 1;
goto number;
}
}
return done;/*conv_error();*/
}
int my_atoi(char *s) {
char *r;
return my_strtol(s,&r,0);
}
DOUBLE my_atof(char *s) {
char *r;
return my_strtod(s,&r);
}
/* Convert NPTR to an `unsigned long int' or `long int' in base BASE.
If BASE is 0 the base is determined by the presence of a leading
zero, indicating octal or a leading "0x" or "0X", indicating
hexadecimal.
If BASE is < 2 or > 36, it is reset to 10.
If ENDPTR is not NULL, a pointer to the character after the last
one converted is stored in *ENDPTR. */
#undef UNSIGNED
#if UNSIGNED
unsigned long int
#define my_strtol my_strtoul
#else
long int
#endif
my_strtol (const char *nptr,char **endptr,int base)
{
int negative;
register unsigned long int cutoff;
register unsigned int cutlim;
register unsigned long int i;
register const char *s;
register unsigned char c;
const char *save;
int overflow;
#ifdef DBG
printf("my_strtol: string |%s| base: %d \n",nptr,base);
#endif
if (base < 0 || base == 1 || base > 36)
base = 10;
s = nptr;
/* Skip white space. */
while (isspace (*s))
++s;
if (*s == '\0')
goto noconv;
/* Check for a sign. */
if (*s == '-')
{
negative = 1;
++s;
}
else if (*s == '+')
{
negative = 0;
++s;
}
else
negative = 0;
if (base == 16 && s[0] == '0' && toupper (s[1]) == 'X')
s += 2;
/* If BASE is zero, figure it out ourselves. */
if (base == 0)
if (*s == '0')
{
if (toupper (s[1]) == 'X')
{
s += 2;
base = 16;
}
else
base = 8;
}
else
base = 10;
/* Save the pointer so we can check later if anything happened. */
save = s;
cutoff = ULONG_MAX / (unsigned long int) base;
cutlim = ULONG_MAX % (unsigned long int) base;
overflow = 0;
i = 0;
for (c = *s; c != '\0'; c = *++s)
{
if (isdigit (c))
c -= '0';
else if (isalpha (c))
c = toupper (c) - 'A' + 10;
else
break;
if (c >= base)
break;
/* Check for overflow. */
if (i > cutoff || (i == cutoff && c > cutlim))
overflow = 1;
else
{
i *= (unsigned long int) base;
i += c;
}
}
/* Check if anything actually happened. */
if (s == save)
goto noconv;
/* Store in ENDPTR the address of one character
past the last character we converted. */
if (endptr != NULL)
*endptr = (char *) s;
#if !UNSIGNED
/* Check for a value that is within the range of
`unsigned long int', but outside the range of `long int'. */
if (i > (negative ?
-(unsigned long int) LONG_MIN : (unsigned long int) LONG_MAX))
overflow = 1;
#endif
if (overflow)
{
errno = ERANGE;
#if UNSIGNED
return ULONG_MAX;
#else
return negative ? LONG_MIN : LONG_MAX;
#endif
}
/* Return the result of the appropriate sign. */
return (negative ? -i : i);
noconv:
/* There was no number to convert. */
if (endptr != NULL)
*endptr = (char *) nptr;
return 0L;
}
#define UNSIGNED 1
#if UNSIGNED
unsigned long int
#define my_strtol my_strtoul
#else
long int
#endif
my_strtol (const char *nptr,char **endptr,int base)
{
int negative;
register unsigned long int cutoff;
register unsigned int cutlim;
register unsigned long int i;
register const char *s;
register unsigned char c;
const char *save;
int overflow;
#ifdef DBG
printf("my_strtoul: string |%s| base: %d \n",nptr,base);
#endif
if (base < 0 || base == 1 || base > 36)
base = 10;
s = nptr;
/* Skip white space. */
while (isspace (*s))
++s;
if (*s == '\0')
goto noconv;
/* Check for a sign. */
if (*s == '-')
{
negative = 1;
++s;
}
else if (*s == '+')
{
negative = 0;
++s;
}
else
negative = 0;
if (base == 16 && s[0] == '0' && toupper (s[1]) == 'X')
s += 2;
/* If BASE is zero, figure it out ourselves. */
if (base == 0)
if (*s == '0')
{
if (toupper (s[1]) == 'X')
{
s += 2;
base = 16;
}
else
base = 8;
}
else
base = 10;
/* Save the pointer so we can check later if anything happened. */
save = s;
cutoff = ULONG_MAX / (unsigned long int) base;
cutlim = ULONG_MAX % (unsigned long int) base;
overflow = 0;
i = 0;
for (c = *s; c != '\0'; c = *++s)
{
if (isdigit (c))
c -= '0';
else if (isalpha (c))
c = toupper (c) - 'A' + 10;
else
break;
if (c >= base)
break;
/* Check for overflow. */
if (i > cutoff || (i == cutoff && c > cutlim))
overflow = 1;
else
{
i *= (unsigned long int) base;
i += c;
}
}
/* Check if anything actually happened. */
if (s == save)
goto noconv;
/* Store in ENDPTR the address of one character
past the last character we converted. */
if (endptr != NULL)
*endptr = (char *) s;
#if !UNSIGNED
/* Check for a value that is within the range of
`unsigned long int', but outside the range of `long int'. */
if (i > (negative ?
-(unsigned long int) LONG_MIN : (unsigned long int) LONG_MAX))
overflow = 1;
#endif
if (overflow)
{
errno = ERANGE;
#if UNSIGNED
return ULONG_MAX;
#else
return negative ? LONG_MIN : LONG_MAX;
#endif
}
/* Return the result of the appropriate sign. */
return (negative ? -i : i);
noconv:
/* There was no number to convert. */
if (endptr != NULL)
*endptr = (char *) nptr;
return 0L;
}
#ifdef SLOW_POWER_CALCULATION
DOUBLE my_pow(DOUBLE val,int exp) {
int i;
DOUBLE x=1.0;
if (exp>0) {
for (i=1;i<=exp;i++) x*=val;
} else {
if (exp<0) {
for (i=1;i<=-exp;i++) x/=val;
}
}
#ifdef DBG
printf("pow num:%f exponent:%d factor:%e\n",val,exp,x);
#endif
return x;
}
#define pow my_pow
#endif
/* Convert NPTR to a double. If ENDPTR is not NULL, a pointer to the
character after the last one used in the number is put in *ENDPTR.
*/
DOUBLE my_strtod(char *nptr,char **endptr)
{
register char *s;
short int sign;
wchar_t decimal; /* Decimal point character. */
/* The number so far. */
DOUBLE num;
DOUBLE exp;
int got_dot; /* Found a decimal point. */
int got_digit; /* Seen any digits. */
/* The exponent of the number. */
long int exponent;
#ifdef DBG
printf("strtod called with |%s|\n",nptr);
#endif
if (nptr == NULL)
{
errno = EINVAL;
goto noconv;
}
/* Figure out the decimal point character. */
/* if (mbtowc(&decimal, _numeric_info->decimal_point, 1) <= 0)
decimal = (wchar_t) *_numeric_info->decimal_point;
*/
/* MR14072001: see note above */
decimal=DECIMALPOINT;
s = nptr;
/* Eat whitespace. */
while (isspace(*s))
++s;
/* Get the sign. */
sign = *s == '-' ? -1 : 1;
if (*s == '-' || *s == '+')
++s;
#ifdef DBG
printf("sign: %d\n",sign);
#endif
num = 0.0;
got_dot = 0;
got_digit = 0;
exponent = 0;
for (;; ++s)
{
if (isdigit (*s))
{
got_digit = 1;
#if 1
/* Make sure that multiplication by 10 will not overflow. */
if (num > DBL_MAX * 0.1) {
/* The value of the digit doesn't matter, since we have already
gotten as many digits as can be represented in a `double'.
This doesn't necessarily mean the result will overflow.
The exponent may reduce it to within range.
We just need to record that there was another
digit so that we can multiply by 10 later. */
++exponent;
#ifdef DBG
printf("++exponent:%d\n",exponent);
#endif
}
else
#endif
num = (num * 10.0) + (*s - '0');
#ifdef DBG
printf("char1:%c num:%f got_dot:%d
exponent:%d\n",*s,num,got_dot,exponent);
#endif
/* Keep track of the number of digits after the decimal point.
If we just divided by 10 here, we would lose precision. */
if (got_dot) {
--exponent;
#ifdef DBG
printf("--exponent:%d\n",exponent);
#endif
}
}
else {
if (!got_dot && (wchar_t) *s == decimal) {
/* Record that we have found the decimal point. */
got_dot = 1;
}
else
/* Any other character terminates the number. */
break;
}
}
if (!got_digit)
goto noconv;
if (tolower(*s) == 'e')
{
/* Get the exponent specified after the `e' or `E'. */
int save = errno;
char *end;
long int exp;
errno = 0;
++s;
exp = my_strtol(s, &end, 10);
if (errno == ERANGE)
{
/* The exponent overflowed a `long int'. It is probably a safe
assumption that an exponent that cannot be represented by
a `long int' exceeds the limits of a `double'. */
if (endptr != NULL)
*endptr = end;
if (exp < 0)
goto underflow;
else
goto overflow;
}
else if (end == s)
/* There was no exponent. Reset END to point to
the 'e' or 'E', so *ENDPTR will be set there. */
end = (char *) s - 1;
errno = save;
s = end;
exponent += exp;
}
if (endptr != NULL)
*endptr = (char *) s;
if (num == 0.0)
return 0.0;
/* Multiply NUM by 10 to the EXPONENT power,
checking for overflow and underflow. */
exp=pow(10.0, exponent);
#ifdef DBG
printf("num:%f exponent:%d factor:%e\n",num,exponent,exp);
#endif
if (exponent < 0)
{
if (num < DBL_MIN * exp)
goto underflow;
}
else if (exponent > 0)
{
if (num > DBL_MAX * exp)
goto overflow;
}
num *= exp;
#ifdef DBG
printf("ret num:%e \n",num);
#endif
return num * sign;
overflow:
/* Return an overflow error. */
errno = ERANGE;
/*MR14072001 return HUGE_VAL * sign; *//* where is HUGE_VAL? */
return DBL_MAX * sign;
underflow:
/* Return an underflow error. */
if (endptr != NULL)
*endptr = (char *) nptr;
errno = ERANGE;
return 0.0;
noconv:
/* There was no number. */
if (endptr != NULL)
*endptr = (char *) nptr;
return 0.0;
}
#ifdef STANDALONE_TEST
main() {
char f[255];
char b[255];
int ix;
float fx;
DOUBLE dx;
int assigned;
while (1) {
printf("Enter format string for: one int, one float, one double (or
quit):");
fgets(f,255,stdin);
if ((f[0]=='q')&&(f[1]=='u')&&(f[2]=='i')&&(f[3]=='t')) break;
printf("Enter string for one int and one float:");
fgets(b,255,stdin);
assigned=my_sscanf(b,f,&ix,&fx,&dx);
printf("\nString:%s\nFormat:%s\nNumber of assigned arguments:
%d\nValues: int: %d float: %e double: %e\n\n",b,f,assigned,ix,fx,dx);
}
}
#endif /* STANDALONE_TEST */
----- End of forwarded message from [EMAIL PROTECTED] -----
-- [rtl] ---
To unsubscribe:
echo "unsubscribe rtl" | mail [EMAIL PROTECTED] OR
echo "unsubscribe rtl <Your_email>" | mail [EMAIL PROTECTED]
--
For more information on Real-Time Linux see:
http://www.rtlinux.org/