* faking time
@ 2002-02-11 11:49 SA products
2002-02-11 13:33 ` Alan Cox
` (6 more replies)
0 siblings, 7 replies; 12+ messages in thread
From: SA products @ 2002-02-11 11:49 UTC (permalink / raw)
To: linux-kernel
Dear Kernel list,
I want to fake the time returned by the time() system call so that for a
limited number
of user space programs the time can be set to the future or the past
without affecting
other applications and without affecting system time-- Ideally I would
like to install a
loadable module to accomplish this- Any hints ? Any starting points?
Thanks SA
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: faking time
2002-02-11 11:49 faking time SA products
@ 2002-02-11 13:33 ` Alan Cox
2002-02-11 20:31 ` Pavel Machek
` (5 subsequent siblings)
6 siblings, 0 replies; 12+ messages in thread
From: Alan Cox @ 2002-02-11 13:33 UTC (permalink / raw)
To: SA products; +Cc: linux-kernel
> I want to fake the time returned by the time() system call so that for a
> limited number
> of user space programs the time can be set to the future or the past
> without affecting
> other applications and without affecting system time-- Ideally I would
> like to install a
> loadable module to accomplish this- Any hints ? Any starting points?
The timetravel module that Tigran wrote for Y2K testing should do.
Alan
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: faking time
2002-02-11 11:49 faking time SA products
2002-02-11 13:33 ` Alan Cox
@ 2002-02-11 20:31 ` Pavel Machek
2002-02-11 21:29 ` Nerijus Baliunas
` (4 subsequent siblings)
6 siblings, 0 replies; 12+ messages in thread
From: Pavel Machek @ 2002-02-11 20:31 UTC (permalink / raw)
To: SA products; +Cc: linux-kernel
Hi!
> I want to fake the time returned by the time() system call so that for a
> limited number
> of user space programs the time can be set to the future or the past
> without affecting
> other applications and without affecting system time-- Ideally I would
> like to install a
> loadable module to accomplish this- Any hints ? Any starting points?
You can do that in userland, see subterfugue.sf.net.
Pavel
--
(about SSSCA) "I don't say this lightly. However, I really think that the U.S.
no longer is classifiable as a democracy, but rather as a plutocracy." --hpa
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: faking time
2002-02-11 11:49 faking time SA products
2002-02-11 13:33 ` Alan Cox
2002-02-11 20:31 ` Pavel Machek
@ 2002-02-11 21:29 ` Nerijus Baliunas
2002-02-11 23:08 ` Kilobug
` (3 subsequent siblings)
6 siblings, 0 replies; 12+ messages in thread
From: Nerijus Baliunas @ 2002-02-11 21:29 UTC (permalink / raw)
To: SA products; +Cc: linux-kernel@vger.kernel.org
[-- Attachment #1: Type: TEXT/PLAIN, Size: 597 bytes --]
On Mon, 11 Feb 2002 11:49:39 +0000 SA products <super.aorta@ntlworld.com> wrote:
Sp> I want to fake the time returned by the time() system call so that for a
Sp> limited number
Sp> of user space programs the time can be set to the future or the past
Sp> without affecting
Sp> other applications and without affecting system time-- Ideally I would
Sp> like to install a
Sp> loadable module to accomplish this- Any hints ? Any starting points?
I am attaching an "ending point", i.e. a complete working module.
Web page will be at http://www.prodata.lt/dateshift/ in a few weeks.
Regards,
Nerijus
[-- Attachment #2: dateshift-0.95.tgz --]
[-- Type: APPLICATION/X-GZIP, Size: 6761 bytes --]
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: faking time
2002-02-11 11:49 faking time SA products
` (2 preceding siblings ...)
2002-02-11 21:29 ` Nerijus Baliunas
@ 2002-02-11 23:08 ` Kilobug
2002-02-12 3:47 ` Theodore Tso
` (2 subsequent siblings)
6 siblings, 0 replies; 12+ messages in thread
From: Kilobug @ 2002-02-11 23:08 UTC (permalink / raw)
To: SA products; +Cc: linux-kernel
SA products wrote:
> Dear Kernel list,
>
> I want to fake the time returned by the time() system call so that for a
> limited number
> of user space programs the time can be set to the future or the past
> without affecting
> other applications and without affecting system time-- Ideally I would
> like to install a
> loadable module to accomplish this- Any hints ? Any starting points?
Maybe could you use a shared library loaded with LD_PRELOAD that
overrides the libc's time() function ?
IMHO this is simpler (and safer) than writing a kernel module, but
it will only work with dynamically linked programs, not with static
nor suid-ed programs.
--
** Gael Le Mignot "Kilobug", Ing3 EPITA - http://kilobug.free.fr **
Home Mail : kilobug@freesurf.fr Work Mail : le-mig_g@epita.fr
GSM : 06.71.47.18.22 (in France) ICQ UIN : 7299959
Fingerprint : 1F2C 9804 7505 79DF 95E6 7323 B66B F67B 7103 C5DA
"Software is like sex it's better when it's free.", Linus Torvalds
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: faking time
2002-02-11 11:49 faking time SA products
` (3 preceding siblings ...)
2002-02-11 23:08 ` Kilobug
@ 2002-02-12 3:47 ` Theodore Tso
2002-02-12 8:33 ` Andreas Ferber
2002-02-12 6:00 ` David Schwartz
2002-02-12 6:09 ` Nick 'Sharkey' Moore
6 siblings, 1 reply; 12+ messages in thread
From: Theodore Tso @ 2002-02-12 3:47 UTC (permalink / raw)
To: SA products; +Cc: linux-kernel
[-- Attachment #1: Type: text/plain, Size: 810 bytes --]
On Mon, Feb 11, 2002 at 11:49:39AM +0000, SA products wrote:
>
> Dear Kernel list,
>
> I want to fake the time returned by the time() system call so that
> for a limited number of user space programs the time can be set to
> the future or the past without affecting other applications and
> without affecting system time-- Ideally I would like to install a
> loadable module to accomplish this- Any hints ? Any starting points?
Here's an LD_PRELOAD shared library that will do the trick... just
export the environment variable FAKETIME with the time that you'd
like, and then export the LD_PRELOAD environment variable to point
that the faketime.so library, and then execute your program. All
programs that have these two environment variables set will have their
time faked out accordingly.
- Ted
[-- Attachment #2: faketime.c --]
[-- Type: text/x-csrc, Size: 27585 bytes --]
/* Convert struct partime into time_t. */
/* Copyright 1992, 1993, 1994, 1995 Paul Eggert
Distributed under license by the Free Software Foundation, Inc.
This file is part of RCS.
RCS is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.
RCS 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with RCS; see the file COPYING.
If not, write to the Free Software Foundation,
59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
Report problems and direct all questions to:
rcs-bugs@cs.purdue.edu
*/
#include <stdlib.h>
#include <time.h>
#include <sys/time.h>
#include <ctype.h>
#define P(x) x
#define TM_UNDEFINED (-1)
#define TM_DEFINED(x) (0 <= (x))
#define TM_UNDEFINED_ZONE ((long) -24 * 60 * 60)
#define TM_LOCAL_ZONE (TM_UNDEFINED_ZONE - 1)
struct partime {
/*
* This structure describes the parsed time.
* Only the following tm_* values in it are used:
* sec, min, hour, mday, mon, year, wday, yday.
* If TM_UNDEFINED(value), the parser never found the value.
* The tm_year field is the actual year, not the year - 1900;
* but see ymodulus below.
*/
struct tm tm;
/*
* If !TM_UNDEFINED(ymodulus),
* then tm.tm_year is actually modulo ymodulus.
*/
int ymodulus;
/*
* Week of year, ISO 8601 style.
* If TM_UNDEFINED(yweek), the parser never found yweek.
* Weeks start on Mondays.
* Week 1 includes Jan 4.
*/
int yweek;
/* Seconds east of UTC; or TM_LOCAL_ZONE or TM_UNDEFINED_ZONE. */
long zone;
};
static char *partime (char const *, struct partime *);
static char *parzone (char const *, long *);
static int isleap P((int));
static int month_days P((struct tm const*));
static time_t maketime P((struct partime const*,time_t));
/* Lookup tables for names of months, weekdays, time zones. */
#define NAME_LENGTH_MAXIMUM 4
struct name_val {
char name[NAME_LENGTH_MAXIMUM];
int val;
};
static char const *parse_decimal P((char const*,int,int,int,int,int*,int*));
static char const *parse_fixed P((char const*,int,int*));
static char const *parse_pattern_letter P((char const*,int,struct partime*));
static char const *parse_prefix P((char const*,struct partime*,int*));
static char const *parse_ranged P((char const*,int,int,int,int*));
static int lookup P((char const*,struct name_val const[]));
static int merge_partime P((struct partime*, struct partime const*));
static void undefine P((struct partime*));
static struct name_val const month_names[] = {
{"jan",0}, {"feb",1}, {"mar",2}, {"apr",3}, {"may",4}, {"jun",5},
{"jul",6}, {"aug",7}, {"sep",8}, {"oct",9}, {"nov",10}, {"dec",11},
{"", TM_UNDEFINED}
};
static struct name_val const weekday_names[] = {
{"sun",0}, {"mon",1}, {"tue",2}, {"wed",3}, {"thu",4}, {"fri",5}, {"sat",6},
{"", TM_UNDEFINED}
};
#define hr60nonnegative(t) ((t)/100 * 60 + (t)%100)
#define hr60(t) ((t)<0 ? -hr60nonnegative(-(t)) : hr60nonnegative(t))
#define zs(t,s) {s, hr60(t)}
#define zd(t,s,d) zs(t, s), zs((t)+100, d)
static struct name_val const zone_names[] = {
zs(-1000, "hst"), /* Hawaii */
zd(-1000,"hast","hadt"),/* Hawaii-Aleutian */
zd(- 900,"akst","akdt"),/* Alaska */
zd(- 800, "pst", "pdt"),/* Pacific */
zd(- 700, "mst", "mdt"),/* Mountain */
zd(- 600, "cst", "cdt"),/* Central */
zd(- 500, "est", "edt"),/* Eastern */
zd(- 400, "ast", "adt"),/* Atlantic */
zd(- 330, "nst", "ndt"),/* Newfoundland */
zs( 000, "utc"), /* Coordinated Universal */
zs( 000, "cut"), /* " */
zs( 000, "ut"), /* Universal */
zs( 000, "z"), /* Zulu (required by ISO 8601) */
zd( 000, "gmt", "bst"),/* Greenwich Mean, British Summer */
zs( 000, "wet"), /* Western Europe */
zs( 100, "met"), /* Middle Europe */
zs( 100, "cet"), /* Central Europe */
zs( 200, "eet"), /* Eastern Europe */
zs( 530, "ist"), /* India */
zd( 900, "jst", "jdt"),/* Japan */
zd( 900, "kst", "kdt"),/* Korea */
zd( 1200,"nzst","nzdt"),/* New Zealand */
{ "lt", 1 },
#if 0
/* The following names are duplicates or are not well attested. */
zs(-1100, "sst"), /* Samoa */
zs(-1000, "tht"), /* Tahiti */
zs(- 930, "mqt"), /* Marquesas */
zs(- 900, "gbt"), /* Gambier */
zd(- 900, "yst", "ydt"),/* Yukon - name is no longer used */
zs(- 830, "pit"), /* Pitcairn */
zd(- 500, "cst", "cdt"),/* Cuba */
zd(- 500, "ast", "adt"),/* Acre */
zd(- 400, "wst", "wdt"),/* Western Brazil */
zd(- 400, "ast", "adt"),/* Andes */
zd(- 400, "cst", "cdt"),/* Chile */
zs(- 300, "wgt"), /* Western Greenland */
zd(- 300, "est", "edt"),/* Eastern South America */
zs(- 300, "mgt"), /* Middle Greenland */
zd(- 200, "fst", "fdt"),/* Fernando de Noronha */
zs(- 100, "egt"), /* Eastern Greenland */
zs(- 100, "aat"), /* Atlantic Africa */
zs(- 100, "act"), /* Azores and Canaries */
zs( 000, "wat"), /* West Africa */
zs( 100, "cat"), /* Central Africa */
zd( 100, "mez","mesz"),/* Mittel-Europaeische Zeit */
zs( 200, "sat"), /* South Africa */
zd( 200, "ist", "idt"),/* Israel */
zs( 300, "eat"), /* East Africa */
zd( 300, "ast", "adt"),/* Arabia */
zd( 300, "msk", "msd"),/* Moscow */
zd( 330, "ist", "idt"),/* Iran */
zs( 400, "gst"), /* Gulf */
zs( 400, "smt"), /* Seychelles & Mascarene */
zd( 400, "esk", "esd"),/* Yekaterinburg */
zd( 400, "bsk", "bsd"),/* Baku */
zs( 430, "aft"), /* Afghanistan */
zd( 500, "osk", "osd"),/* Omsk */
zs( 500, "pkt"), /* Pakistan */
zd( 500, "tsk", "tsd"),/* Tashkent */
zs( 545, "npt"), /* Nepal */
zs( 600, "bgt"), /* Bangladesh */
zd( 600, "nsk", "nsd"),/* Novosibirsk */
zs( 630, "bmt"), /* Burma */
zs( 630, "cct"), /* Cocos */
zs( 700, "ict"), /* Indochina */
zs( 700, "jvt"), /* Java */
zd( 700, "isk", "isd"),/* Irkutsk */
zs( 800, "hkt"), /* Hong Kong */
zs( 800, "pst"), /* Philippines */
zs( 800, "sgt"), /* Singapore */
zd( 800, "cst", "cdt"),/* China */
zd( 800, "ust", "udt"),/* Ulan Bator */
zd( 800, "wst", "wst"),/* Western Australia */
zd( 800, "ysk", "ysd"),/* Yakutsk */
zs( 900, "blt"), /* Belau */
zs( 900, "mlt"), /* Moluccas */
zd( 900, "vsk", "vsd"),/* Vladivostok */
zd( 930, "cst", "cst"),/* Central Australia */
zs( 1000, "gst"), /* Guam */
zd( 1000, "gsk", "gsd"),/* Magadan */
zd( 1000, "est", "est"),/* Eastern Australia */
zd( 1100,"lhst","lhst"),/* Lord Howe */
zd( 1100, "psk", "psd"),/* Petropavlovsk-Kamchatski */
zs( 1100,"ncst"), /* New Caledonia */
zs( 1130,"nrft"), /* Norfolk */
zd( 1200, "ask", "asd"),/* Anadyr */
zs( 1245,"nz-chat"), /* Chatham */
zs( 1300, "tgt"), /* Tongatapu */
#endif
{"", -1}
};
static int
lookup (s, table)
char const *s;
struct name_val const table[];
/* Look for a prefix of S in TABLE, returning val for first matching entry. */
{
int j;
char buf[NAME_LENGTH_MAXIMUM];
for (j = 0; j < NAME_LENGTH_MAXIMUM; j++) {
unsigned char c = *s++;
buf[j] = isupper (c) ? tolower (c) : c;
if (!isalpha (c))
break;
}
for (; table[0].name[0]; table++)
for (j = 0; buf[j] == table[0].name[j]; )
if (++j == NAME_LENGTH_MAXIMUM || !table[0].name[j])
goto done;
done:
return table[0].val;
}
static void
undefine (t) struct partime *t;
/* Set *T to ``undefined'' values. */
{
t->tm.tm_sec = t->tm.tm_min = t->tm.tm_hour = t->tm.tm_mday = t->tm.tm_mon
= t->tm.tm_year = t->tm.tm_wday = t->tm.tm_yday
= t->ymodulus = t->yweek
= TM_UNDEFINED;
t->zone = TM_UNDEFINED_ZONE;
}
/*
* Array of patterns to look for in a date string.
* Order is important: we look for the first matching pattern
* whose values do not contradict values that we already know about.
* See `parse_pattern_letter' below for the meaning of the pattern codes.
*/
static char const * const patterns[] = {
/*
* These traditional patterns must come first,
* to prevent an ISO 8601 format from misinterpreting their prefixes.
*/
"E_n_y", "x", /* RFC 822 */
"E_n", "n_E", "n", "t:m:s_A", "t:m_A", "t_A", /* traditional */
"y/N/D$", /* traditional RCS */
/* ISO 8601:1988 formats, generalized a bit. */
"y-N-D$", "4ND$", "Y-N$",
"RND$", "-R=N$", "-R$", "--N=D$", "N=DT",
"--N$", "---D$", "DT",
"Y-d$", "4d$", "R=d$", "-d$", "dT",
"y-W-X", "yWX", "y=W",
"-r-W-X", "r-W-XT", "-rWX", "rWXT", "-W=X", "W=XT", "-W",
"-w-X", "w-XT", "---X$", "XT", "4$",
"T",
"h:m:s$", "hms$", "h:m$", "hm$", "h$", "-m:s$", "-ms$", "-m$", "--s$",
"Y", "Z",
0
};
static char const *
parse_prefix (str, t, pi) char const *str; struct partime *t; int *pi;
/*
* Parse an initial prefix of STR, setting *T accordingly.
* Return the first character after the prefix, or 0 if it couldn't be parsed.
* Start with pattern *PI; if success, set *PI to the next pattern to try.
* Set *PI to -1 if we know there are no more patterns to try;
* if *PI is initially negative, give up immediately.
*/
{
int i = *pi;
char const *pat;
unsigned char c;
if (i < 0)
return 0;
/* Remove initial noise. */
while (!isalnum (c = *str) && c != '-' && c != '+') {
if (!c) {
undefine (t);
*pi = -1;
return str;
}
str++;
}
/* Try a pattern until one succeeds. */
while ((pat = patterns[i++]) != 0) {
char const *s = str;
undefine (t);
do {
if (!(c = *pat++)) {
*pi = i;
return s;
}
} while ((s = parse_pattern_letter (s, c, t)) != 0);
}
return 0;
}
static char const *
parse_fixed (s, digits, res) char const *s; int digits, *res;
/*
* Parse an initial prefix of S of length DIGITS; it must be a number.
* Store the parsed number into *RES.
* Return the first character after the prefix, or 0 if it couldn't be parsed.
*/
{
int n = 0;
char const *lim = s + digits;
while (s < lim) {
unsigned d = *s++ - '0';
if (9 < d)
return 0;
n = 10*n + d;
}
*res = n;
return s;
}
static char const *
parse_ranged (s, digits, lo, hi, res) char const *s; int digits, lo, hi, *res;
/*
* Parse an initial prefix of S of length DIGITS;
* it must be a number in the range LO through HI.
* Store the parsed number into *RES.
* Return the first character after the prefix, or 0 if it couldn't be parsed.
*/
{
s = parse_fixed (s, digits, res);
return s && lo<=*res && *res<=hi ? s : 0;
}
static char const *
parse_decimal (s, digits, lo, hi, resolution, res, fres)
char const *s;
int digits, lo, hi, resolution, *res, *fres;
/*
* Parse an initial prefix of S of length DIGITS;
* it must be a number in the range LO through HI
* and it may be followed by a fraction that is to be computed using RESOLUTION.
* Store the parsed number into *RES; store the fraction times RESOLUTION,
* rounded to the nearest integer, into *FRES.
* Return the first character after the prefix, or 0 if it couldn't be parsed.
*/
{
s = parse_fixed (s, digits, res);
if (s && lo<=*res && *res<=hi) {
int f = 0;
if ((s[0]==',' || s[0]=='.') && isdigit ((unsigned char) s[1])) {
char const *s1 = ++s;
int num10 = 0, denom10 = 10, product;
while (isdigit ((unsigned char) *++s))
denom10 *= 10;
s = parse_fixed (s1, s - s1, &num10);
product = num10*resolution;
f = (product + (denom10>>1)) / denom10;
f -= f & (product%denom10 == denom10>>1); /* round to even */
if (f < 0 || product/resolution != num10)
return 0; /* overflow */
}
*fres = f;
return s;
}
return 0;
}
static char *
parzone (s, zone) char const *s; long *zone;
/*
* Parse an initial prefix of S; it must denote a time zone.
* Set *ZONE to the number of seconds east of GMT,
* or to TM_LOCAL_ZONE if it is the local time zone.
* Return the first character after the prefix, or 0 if it couldn't be parsed.
*/
{
char sign;
int hh, mm, ss;
int minutesEastOfUTC;
long offset, z;
/*
* The formats are LT, n, n DST, nDST, no, o
* where n is a time zone name
* and o is a time zone offset of the form [-+]hh[:mm[:ss]].
*/
switch (*s) {
case '-': case '+':
z = 0;
break;
default:
minutesEastOfUTC = lookup (s, zone_names);
if (minutesEastOfUTC == -1)
return 0;
/* Don't bother to check rest of spelling. */
while (isalpha ((unsigned char) *s))
s++;
/* Don't modify LT. */
if (minutesEastOfUTC == 1) {
*zone = TM_LOCAL_ZONE;
return (char *) s;
}
z = minutesEastOfUTC * 60L;
/* Look for trailing " DST". */
if (
(s[-1]=='T' || s[-1]=='t') &&
(s[-2]=='S' || s[-2]=='s') &&
(s[-3]=='D' || s[-3]=='t')
)
goto trailing_dst;
while (isspace ((unsigned char) *s))
s++;
if (
(s[0]=='D' || s[0]=='d') &&
(s[1]=='S' || s[1]=='s') &&
(s[2]=='T' || s[2]=='t')
) {
s += 3;
trailing_dst:
*zone = z + 60*60;
return (char *) s;
}
switch (*s) {
case '-': case '+': break;
default: return (char *) s;
}
}
sign = *s++;
if (!(s = parse_ranged (s, 2, 0, 23, &hh)))
return 0;
mm = ss = 0;
if (*s == ':')
s++;
if (isdigit ((unsigned char) *s)) {
if (!(s = parse_ranged (s, 2, 0, 59, &mm)))
return 0;
if (*s==':' && s[-3]==':' && isdigit ((unsigned char) s[1])) {
if (!(s = parse_ranged (s + 1, 2, 0, 59, &ss)))
return 0;
}
}
if (isdigit ((unsigned char) *s))
return 0;
offset = (hh*60 + mm)*60L + ss;
*zone = z + (sign=='-' ? -offset : offset);
/*
* ?? Are fractions allowed here?
* If so, they're not implemented.
*/
return (char *) s;
}
static char const *
parse_pattern_letter (s, c, t) char const *s; int c; struct partime *t;
/*
* Parse an initial prefix of S, matching the pattern whose code is C.
* Set *T accordingly.
* Return the first character after the prefix, or 0 if it couldn't be parsed.
*/
{
switch (c) {
case '$': /* The next character must be a non-digit. */
if (isdigit ((unsigned char) *s))
return 0;
break;
case '-': case '/': case ':':
/* These characters stand for themselves. */
if (*s++ != c)
return 0;
break;
case '4': /* 4-digit year */
s = parse_fixed (s, 4, &t->tm.tm_year);
break;
case '=': /* optional '-' */
s += *s == '-';
break;
case 'A': /* AM or PM */
/*
* This matches the regular expression [AaPp][Mm]?.
* It must not be followed by a letter or digit;
* otherwise it would match prefixes of strings like "PST".
*/
switch (*s++) {
case 'A': case 'a':
if (t->tm.tm_hour == 12)
t->tm.tm_hour = 0;
break;
case 'P': case 'p':
if (t->tm.tm_hour != 12)
t->tm.tm_hour += 12;
break;
default: return 0;
}
switch (*s) {
case 'M': case 'm': s++; break;
}
if (isalnum (*s))
return 0;
break;
case 'D': /* day of month [01-31] */
s = parse_ranged (s, 2, 1, 31, &t->tm.tm_mday);
break;
case 'd': /* day of year [001-366] */
s = parse_ranged (s, 3, 1, 366, &t->tm.tm_yday);
t->tm.tm_yday--;
break;
case 'E': /* extended day of month [1-9, 01-31] */
s = parse_ranged (s, (
isdigit ((unsigned char) s[0]) &&
isdigit ((unsigned char) s[1])
) + 1, 1, 31, &t->tm.tm_mday);
break;
case 'h': /* hour [00-23 followed by optional fraction] */
{
int frac;
s = parse_decimal (s, 2, 0, 23, 60*60, &t->tm.tm_hour, &frac);
t->tm.tm_min = frac / 60;
t->tm.tm_sec = frac % 60;
}
break;
case 'm': /* minute [00-59 followed by optional fraction] */
s = parse_decimal (s, 2, 0, 59, 60, &t->tm.tm_min, &t->tm.tm_sec);
break;
case 'n': /* month name [e.g. "Jan"] */
if (!TM_DEFINED (t->tm.tm_mon = lookup (s, month_names)))
return 0;
/* Don't bother to check rest of spelling. */
while (isalpha ((unsigned char) *s))
s++;
break;
case 'N': /* month [01-12] */
s = parse_ranged (s, 2, 1, 12, &t->tm.tm_mon);
t->tm.tm_mon--;
break;
case 'r': /* year % 10 (remainder in origin-0 decade) [0-9] */
s = parse_fixed (s, 1, &t->tm.tm_year);
t->ymodulus = 10;
break;
case_R:
case 'R': /* year % 100 (remainder in origin-0 century) [00-99] */
s = parse_fixed (s, 2, &t->tm.tm_year);
t->ymodulus = 100;
break;
case 's': /* second [00-60 followed by optional fraction] */
{
int frac;
s = parse_decimal (s, 2, 0, 60, 1, &t->tm.tm_sec, &frac);
t->tm.tm_sec += frac;
}
break;
case 'T': /* 'T' or 't' */
switch (*s++) {
case 'T': case 't': break;
default: return 0;
}
break;
case 't': /* traditional hour [1-9 or 01-12] */
s = parse_ranged (s, (
isdigit ((unsigned char) s[0]) && isdigit ((unsigned char) s[1])
) + 1, 1, 12, &t->tm.tm_hour);
break;
case 'w': /* 'W' or 'w' only (stands for current week) */
switch (*s++) {
case 'W': case 'w': break;
default: return 0;
}
break;
case 'W': /* 'W' or 'w', followed by a week of year [00-53] */
switch (*s++) {
case 'W': case 'w': break;
default: return 0;
}
s = parse_ranged (s, 2, 0, 53, &t->yweek);
break;
case 'X': /* weekday (1=Mon ... 7=Sun) [1-7] */
s = parse_ranged (s, 1, 1, 7, &t->tm.tm_wday);
t->tm.tm_wday--;
break;
case 'x': /* weekday name [e.g. "Sun"] */
if (!TM_DEFINED (t->tm.tm_wday = lookup (s, weekday_names)))
return 0;
/* Don't bother to check rest of spelling. */
while (isalpha ((unsigned char) *s))
s++;
break;
case 'y': /* either R or Y */
if (
isdigit ((unsigned char) s[0]) &&
isdigit ((unsigned char) s[1]) &&
!isdigit ((unsigned char) s[2])
)
goto case_R;
/* fall into */
case 'Y': /* year in full [4 or more digits] */
{
int len = 0;
while (isdigit ((unsigned char) s[len]))
len++;
if (len < 4)
return 0;
s = parse_fixed (s, len, &t->tm.tm_year);
}
break;
case 'Z': /* time zone */
s = parzone (s, &t->zone);
break;
case '_': /* possibly empty sequence of non-alphanumerics */
while (!isalnum (*s) && *s)
s++;
break;
default: /* bad pattern */
return 0;
}
return s;
}
static int
merge_partime (t, u) struct partime *t; struct partime const *u;
/*
* If there is no conflict, merge into *T the additional information in *U
* and return 0. Otherwise do nothing and return -1.
*/
{
# define conflict(a,b) ((a) != (b) && TM_DEFINED (a) && TM_DEFINED (b))
if (
conflict (t->tm.tm_sec, u->tm.tm_sec) ||
conflict (t->tm.tm_min, u->tm.tm_min) ||
conflict (t->tm.tm_hour, u->tm.tm_hour) ||
conflict (t->tm.tm_mday, u->tm.tm_mday) ||
conflict (t->tm.tm_mon, u->tm.tm_mon) ||
conflict (t->tm.tm_year, u->tm.tm_year) ||
conflict (t->tm.tm_wday, u->tm.tm_yday) ||
conflict (t->ymodulus, u->ymodulus) ||
conflict (t->yweek, u->yweek) ||
(
t->zone != u->zone &&
t->zone != TM_UNDEFINED_ZONE &&
u->zone != TM_UNDEFINED_ZONE
)
)
return -1;
# undef conflict
# define merge_(a,b) if (TM_DEFINED (b)) (a) = (b);
merge_ (t->tm.tm_sec, u->tm.tm_sec)
merge_ (t->tm.tm_min, u->tm.tm_min)
merge_ (t->tm.tm_hour, u->tm.tm_hour)
merge_ (t->tm.tm_mday, u->tm.tm_mday)
merge_ (t->tm.tm_mon, u->tm.tm_mon)
merge_ (t->tm.tm_year, u->tm.tm_year)
merge_ (t->tm.tm_wday, u->tm.tm_yday)
merge_ (t->ymodulus, u->ymodulus)
merge_ (t->yweek, u->yweek)
# undef merge_
if (u->zone != TM_UNDEFINED_ZONE) t->zone = u->zone;
return 0;
}
static char *
partime (s, t) char const *s; struct partime *t;
/*
* Parse a date/time prefix of S, putting the parsed result into *T.
* Return the first character after the prefix.
* The prefix may contain no useful information;
* in that case, *T will contain only undefined values.
*/
{
struct partime p;
undefine (t);
while (*s) {
int i = 0;
char const *s1;
do {
if (!(s1 = parse_prefix (s, &p, &i)))
return (char *) s;
} while (merge_partime (t, &p) != 0);
s = s1;
}
return (char *) s;
}
/*
* For maximum portability, use only localtime and gmtime.
* Make no assumptions about the time_t epoch or the range of time_t values.
* Avoid mktime because it's not universal and because there's no easy,
* portable way for mktime to yield the inverse of gmtime.
*/
#define TM_YEAR_ORIGIN 1900
static int
isleap(y)
int y;
{
return (y&3) == 0 && (y%100 != 0 || y%400 == 0);
}
static int const month_yday[] = {
/* days in year before start of months 0-12 */
0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365
};
/* Yield the number of days in TM's month. */
static int
month_days(tm)
struct tm const *tm;
{
int m = tm->tm_mon;
return month_yday[m+1] - month_yday[m]
+ (m==1 && isleap(tm->tm_year + TM_YEAR_ORIGIN));
}
/*
* Convert UNIXTIME to struct tm form.
* Use gmtime if available and if !LOCALZONE, localtime otherwise.
*/
static struct tm *
time2tm(time_t unixtime, int localzone)
{
struct tm *tm;
# if TZ_must_be_set
static char const *TZ;
if (!TZ && !(TZ = getenv("TZ")))
faterror("The TZ environment variable is not set; please set it to your timezone");
# endif
if (localzone || !(tm = gmtime(&unixtime)))
tm = localtime(&unixtime);
return tm;
}
/* Yield A - B, measured in seconds. */
static time_t
difftm(struct tm const *a, struct tm const *b)
{
int ay = a->tm_year + (TM_YEAR_ORIGIN - 1);
int by = b->tm_year + (TM_YEAR_ORIGIN - 1);
int difference_in_day_of_year = a->tm_yday - b->tm_yday;
int intervening_leap_days = (
((ay >> 2) - (by >> 2))
- (ay/100 - by/100)
+ ((ay/100 >> 2) - (by/100 >> 2))
);
time_t difference_in_years = ay - by;
time_t difference_in_days = (
difference_in_years*365
+ (intervening_leap_days + difference_in_day_of_year)
);
return
(
(
24*difference_in_days
+ (a->tm_hour - b->tm_hour)
)*60 + (a->tm_min - b->tm_min)
)*60 + (a->tm_sec - b->tm_sec);
}
/*
* Adjust time T by adding SECONDS. SECONDS must be at most 24 hours' worth.
* Adjust only T's year, mon, mday, hour, min and sec members;
* plus adjust wday if it is defined.
*/
static void
adjzone(struct tm *t, long seconds)
{
/*
* This code can be off by a second if SECONDS is not a multiple of 60,
* if T is local time, and if a leap second happens during this minute.
* But this bug has never occurred, and most likely will not ever occur.
* Liberia, the last country for which SECONDS % 60 was nonzero,
* switched to UTC in May 1972; the first leap second was in June 1972.
*/
int leap_second = t->tm_sec == 60;
long sec = seconds + (t->tm_sec - leap_second);
if (sec < 0) {
if ((t->tm_min -= (59-sec)/60) < 0) {
if ((t->tm_hour -= (59-t->tm_min)/60) < 0) {
t->tm_hour += 24;
if (TM_DEFINED(t->tm_wday) && --t->tm_wday < 0)
t->tm_wday = 6;
if (--t->tm_mday <= 0) {
if (--t->tm_mon < 0) {
--t->tm_year;
t->tm_mon = 11;
}
t->tm_mday = month_days(t);
}
}
t->tm_min += 24 * 60;
}
sec += 24L * 60 * 60;
} else
if (60 <= (t->tm_min += sec/60))
if (24 <= (t->tm_hour += t->tm_min/60)) {
t->tm_hour -= 24;
if (TM_DEFINED(t->tm_wday) && ++t->tm_wday == 7)
t->tm_wday = 0;
if (month_days(t) < ++t->tm_mday) {
if (11 < ++t->tm_mon) {
++t->tm_year;
t->tm_mon = 0;
}
t->tm_mday = 1;
}
}
t->tm_min %= 60;
t->tm_sec = (int) (sec%60) + leap_second;
}
/*
* Convert TM to time_t, using localtime if LOCALZONE and gmtime otherwise.
* Use only TM's year, mon, mday, hour, min, and sec members.
* Ignore TM's old tm_yday and tm_wday, but fill in their correct values.
* Yield -1 on failure (e.g. a member out of range).
* Posix 1003.1-1990 doesn't allow leap seconds, but some implementations
* have them anyway, so allow them if localtime/gmtime does.
*/
static time_t
tm2time(struct tm *tm, int localzone)
{
/* Cache the most recent t,tm pairs; 1 for gmtime, 1 for localtime. */
static time_t t_cache[2];
static struct tm tm_cache[2];
time_t d, gt;
struct tm const *gtm;
/*
* The maximum number of iterations should be enough to handle any
* combinations of leap seconds, time zone rule changes, and solar time.
* 4 is probably enough; we use a bigger number just to be safe.
*/
int remaining_tries = 8;
/* Avoid subscript errors. */
if (12 <= (unsigned)tm->tm_mon)
return -1;
tm->tm_yday = month_yday[tm->tm_mon] + tm->tm_mday
- (tm->tm_mon<2 || ! isleap(tm->tm_year + TM_YEAR_ORIGIN));
/* Make a first guess. */
gt = t_cache[localzone];
gtm = gt ? &tm_cache[localzone] : time2tm(gt,localzone);
/* Repeatedly use the error from the guess to improve the guess. */
while ((d = difftm(tm, gtm)) != 0) {
if (--remaining_tries == 0)
return -1;
gt += d;
gtm = time2tm(gt,localzone);
}
t_cache[localzone] = gt;
tm_cache[localzone] = *gtm;
/*
* Check that the guess actually matches;
* overflow can cause difftm to yield 0 even on differing times,
* or tm may have members out of range (e.g. bad leap seconds).
*/
if ( (tm->tm_year ^ gtm->tm_year)
| (tm->tm_mon ^ gtm->tm_mon)
| (tm->tm_mday ^ gtm->tm_mday)
| (tm->tm_hour ^ gtm->tm_hour)
| (tm->tm_min ^ gtm->tm_min)
| (tm->tm_sec ^ gtm->tm_sec))
return -1;
tm->tm_wday = gtm->tm_wday;
return gt;
}
/*
* Check *PT and convert it to time_t.
* If it is incompletely specified, use DEFAULT_TIME to fill it out.
* Use localtime if PT->zone is the special value TM_LOCAL_ZONE.
* Yield -1 on failure.
* ISO 8601 day-of-year and week numbers are not yet supported.
*/
static time_t
maketime(pt, default_time)
struct partime const *pt;
time_t default_time;
{
int localzone, wday;
struct tm tm;
struct tm *tm0 = 0;
time_t r;
tm0 = 0; /* Keep gcc -Wall happy. */
localzone = pt->zone==TM_LOCAL_ZONE;
tm = pt->tm;
if (TM_DEFINED(pt->ymodulus) || !TM_DEFINED(tm.tm_year)) {
/* Get tm corresponding to current time. */
tm0 = time2tm(default_time, localzone);
if (!localzone)
adjzone(tm0, pt->zone);
}
if (TM_DEFINED(pt->ymodulus))
tm.tm_year +=
(tm0->tm_year + TM_YEAR_ORIGIN)/pt->ymodulus * pt->ymodulus;
else if (!TM_DEFINED(tm.tm_year)) {
/* Set default year, month, day from current time. */
tm.tm_year = tm0->tm_year + TM_YEAR_ORIGIN;
if (!TM_DEFINED(tm.tm_mon)) {
tm.tm_mon = tm0->tm_mon;
if (!TM_DEFINED(tm.tm_mday))
tm.tm_mday = tm0->tm_mday;
}
}
/* Convert from partime year (Gregorian) to Posix year. */
tm.tm_year -= TM_YEAR_ORIGIN;
/* Set remaining default fields to be their minimum values. */
if (!TM_DEFINED(tm.tm_mon)) tm.tm_mon = 0;
if (!TM_DEFINED(tm.tm_mday)) tm.tm_mday = 1;
if (!TM_DEFINED(tm.tm_hour)) tm.tm_hour = 0;
if (!TM_DEFINED(tm.tm_min)) tm.tm_min = 0;
if (!TM_DEFINED(tm.tm_sec)) tm.tm_sec = 0;
if (!localzone)
adjzone(&tm, -pt->zone);
wday = tm.tm_wday;
/* Convert and fill in the rest of the tm. */
r = tm2time(&tm, localzone);
/* Check weekday. */
if (r != -1 && TM_DEFINED(wday) && wday != tm.tm_wday)
return -1;
return r;
}
/* Parse a free-format date in SOURCE, yielding a Unix format time. */
static time_t
str2time(char const *source, time_t default_time, long default_zone)
{
struct partime pt;
if (*partime(source, &pt))
return -1;
if (pt.zone == TM_UNDEFINED_ZONE)
pt.zone = default_zone;
return maketime(&pt, default_time);
}
time_t time(time_t *ptr)
{
time_t default_time = 0;
long default_zone = -5*60*60;
struct timeval tv;
time_t t;
char *s;
gettimeofday(&tv, 0);
default_time = tv.tv_sec;
s = getenv("FAKETIME");
if (s) {
t = str2time(s, default_time, default_zone);
if (t == -1)
t = default_time;
} else
t = default_time;
if (ptr)
*ptr = t;
return t;
}
[-- Attachment #3: Makefile --]
[-- Type: text/plain, Size: 219 bytes --]
all: faketime.so
faketime.o: faketime.c
cc -fpic -c -o faketime.o faketime.c
faketime.so: faketime.o
gcc -shared faketime.o -lc -Wl,-soname -Wl,faketime.so -o faketime.so
clean:
/bin/rm -f faketime.o faketime.so
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: faking time
2002-02-11 11:49 faking time SA products
` (4 preceding siblings ...)
2002-02-12 3:47 ` Theodore Tso
@ 2002-02-12 6:00 ` David Schwartz
2002-02-12 6:09 ` Nick 'Sharkey' Moore
6 siblings, 0 replies; 12+ messages in thread
From: David Schwartz @ 2002-02-12 6:00 UTC (permalink / raw)
To: super.aorta, linux-kernel
On Mon, 11 Feb 2002 11:49:39 +0000, SA products wrote:
>I want to fake the time returned by the time() system call so that for a
>limited number
>of user space programs the time can be set to the future or the past
>without affecting
>other applications and without affecting system time-- Ideally I would
>like to install a
>loadable module to accomplish this- Any hints ? Any starting points?
If you're doing this to defeat a program's security or licensing scheme, let
me warn you that there are many ways this could be detected. Inconsistencies
in different time functions, filesystem times that are way ahead of system
time, times built into networking protocols (and thus received from remote
machines), detection of times before other run time, and so on.
DS
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: faking time
2002-02-11 11:49 faking time SA products
` (5 preceding siblings ...)
2002-02-12 6:00 ` David Schwartz
@ 2002-02-12 6:09 ` Nick 'Sharkey' Moore
6 siblings, 0 replies; 12+ messages in thread
From: Nick 'Sharkey' Moore @ 2002-02-12 6:09 UTC (permalink / raw)
To: linux-kernel
On Mon, Feb 11, 2002 at 11:49:39AM +0000, SA products wrote:
>
> I want to fake the time returned by the time() system call so that for a
> limited number of user space programs [...]
Hi. I'm experimenting with a patch to User Mode Linux which enables
it to do this sort of thing.
Other responses (eg, LD_PRELOAD) here are probably more directly useful
to you, but I thought User Mode Linux bore mentioning anyway, depending
on what you're trying to achieve[1].
<URL:http://user-mode-linux.sourceforge.net/>
-----sharks
[1] Just out of interest, what _are_ you trying to achieve?
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: faking time
2002-02-12 3:47 ` Theodore Tso
@ 2002-02-12 8:33 ` Andreas Ferber
2002-02-12 8:56 ` mulix
0 siblings, 1 reply; 12+ messages in thread
From: Andreas Ferber @ 2002-02-12 8:33 UTC (permalink / raw)
To: linux-kernel; +Cc: Theodore Tso, SA products
On Mon, Feb 11, 2002 at 10:47:23PM -0500, Theodore Tso wrote:
>
> Here's an LD_PRELOAD shared library that will do the trick... just
> export the environment variable FAKETIME with the time that you'd
> like, and then export the LD_PRELOAD environment variable to point
> that the faketime.so library, and then execute your program. All
> programs that have these two environment variables set will have their
> time faked out accordingly.
But note that this doesn't work with programs linked statically. If
you must fool one of those, ptrace() is the only way to do it without
some sort of kernel patch or module I think.
Andreas
--
Andreas Ferber - dev/consulting GmbH - Bielefeld, FRG
---------------------------------------------------------
+49 521 1365800 - af@devcon.net - www.devcon.net
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: faking time
2002-02-12 8:33 ` Andreas Ferber
@ 2002-02-12 8:56 ` mulix
2002-02-12 12:39 ` Pavel Machek
0 siblings, 1 reply; 12+ messages in thread
From: mulix @ 2002-02-12 8:56 UTC (permalink / raw)
To: Andreas Ferber; +Cc: linux-kernel, Theodore Tso, SA products, choo
On Tue, 12 Feb 2002, Andreas Ferber wrote:
> On Mon, Feb 11, 2002 at 10:47:23PM -0500, Theodore Tso wrote:
> >
> > Here's an LD_PRELOAD shared library that will do the trick... just
> > export the environment variable FAKETIME with the time that you'd
> > like, and then export the LD_PRELOAD environment variable to point
> > that the faketime.so library, and then execute your program. All
> > programs that have these two environment variables set will have their
> > time faked out accordingly.
>
> But note that this doesn't work with programs linked statically. If
> you must fool one of those, ptrace() is the only way to do it without
> some sort of kernel patch or module I think.
luckily, someone wrote such a kernel module - syscalltrack,
http://syscalltrack.sf.net.
it's in alpha stages right now, but it seems pretty stable so far (It
Works For Me - i run it regularly on all of my machines). note that we
currently support only logging system calls (a-la strace) and failing
them with a user given parameter- rewriting system call parameters will
require additional hackery, but not too much of it - on the order of one
day of work. volunteers are welcome.
--
mulix
http://vipe.technion.ac.il/~mulix/
http://syscalltrack.sf.net/
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: faking time
2002-02-12 8:56 ` mulix
@ 2002-02-12 12:39 ` Pavel Machek
2002-02-12 21:43 ` guy keren
0 siblings, 1 reply; 12+ messages in thread
From: Pavel Machek @ 2002-02-12 12:39 UTC (permalink / raw)
To: mulix; +Cc: Andreas Ferber, linux-kernel, Theodore Tso, SA products, choo
Hi!
> > > Here's an LD_PRELOAD shared library that will do the trick... just
> > > export the environment variable FAKETIME with the time that you'd
> > > like, and then export the LD_PRELOAD environment variable to point
> > > that the faketime.so library, and then execute your program. All
> > > programs that have these two environment variables set will have their
> > > time faked out accordingly.
> >
> > But note that this doesn't work with programs linked statically. If
> > you must fool one of those, ptrace() is the only way to do it without
> > some sort of kernel patch or module I think.
subterfugue.sf.net can do this. It has ready-to-use timeshift/timemultiply
module.
> it's in alpha stages right now, but it seems pretty stable so far (It
> Works For Me - i run it regularly on all of my machines). note that we
> currently support only logging system calls (a-la strace) and failing
> them with a user given parameter- rewriting system call parameters will
> require additional hackery, but not too much of it - on the order of one
> day of work. volunteers are welcome.
Why do you need kernel module at all?
BTW syscall rewriting is pretty hard (subterfugue solves that, but it definitely
took more than a day.
Imagine open('/foo/bar')
you rewrite it to open('/funny/bar')
then another thread comes and rewrites it back to '/foo/bar'.
Or imagine open(address in read-only memory).
Pavel
--
Philips Velo 1: 1"x4"x8", 300gram, 60, 12MB, 40bogomips, linux, mutt,
details at http://atrey.karlin.mff.cuni.cz/~pavel/velo/index.html.
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: faking time
2002-02-12 12:39 ` Pavel Machek
@ 2002-02-12 21:43 ` guy keren
0 siblings, 0 replies; 12+ messages in thread
From: guy keren @ 2002-02-12 21:43 UTC (permalink / raw)
To: Pavel Machek
Cc: mulix, Andreas Ferber, linux-kernel, Theodore Tso, SA products
On Tue, 12 Feb 2002, Pavel Machek wrote:
> > it's in alpha stages right now, but it seems pretty stable so far (It
> > Works For Me - i run it regularly on all of my machines). note that we
> > currently support only logging system calls (a-la strace) and failing
> > them with a user given parameter- rewriting system call parameters will
> > require additional hackery, but not too much of it - on the order of one
> > day of work. volunteers are welcome.
>
> Why do you need kernel module at all?
since we want to trace unknown processes. if you check the home page,
you'll see a few examples of situations in which strace, or any user-land
solution that does not trace the _entire_ system, can't handle properly.
we also want to have a minimal performance penalty - anything using ptrace
has a large performance penalty.
> BTW syscall rewriting is pretty hard (subterfugue solves that, but it definitely
> took more than a day.
>
> Imagineopen('/foo/bar')
>
> you rewrite it to open('/funny/bar')
>
> then another thread comes and rewrites it back to '/foo/bar'.
this is because you think of "rewriting the user process's memory" in
user-space. when the code is in the kernel - this is not the case.
ofcourse, you cannot insert a full python (or perl or whatever)
interpreter in the kernel [or perhaps you can? is this complete
blasphemy? :) ]. we do think of adding some interface to externalize
syscall tracing into user-land to allow people to still have such
features, but most usage we envision for this tool won't require that.
> Or imagine open(address in read-only memory).
again - you're talking about user-space intervention that actually
re-writes the user's data. we just want to invoke the syscall with
modified parameters - or to modify the data the syscall returns to the
user (whic won't stem from these race-conditions or r/o problems, since
the user _wants_ to receive that data).
i suggest that you take a look at the project's page if you wish to see
what we have in mind. personally, i hear of "a tool that already does
this" at least once a month. then i think "oh... why do we bother?". then
i go checking and see that it's not doing what we want, or its not doing
it right (user-land that slows the system and cannot trace the whole
system. or too limited filtering mechanisms. or kernel code that only
exports to user-mode, and thus affects performance. or kernel code that
requires patching a kernel, and thus cannot just be compiled and loaded
into a running machine, etc).
hope it helps,
--
guy
"For world domination - press 1,
or dial 0, and please hold, for the creator." -- nob o. dy
^ permalink raw reply [flat|nested] 12+ messages in thread
end of thread, other threads:[~2002-02-12 21:45 UTC | newest]
Thread overview: 12+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2002-02-11 11:49 faking time SA products
2002-02-11 13:33 ` Alan Cox
2002-02-11 20:31 ` Pavel Machek
2002-02-11 21:29 ` Nerijus Baliunas
2002-02-11 23:08 ` Kilobug
2002-02-12 3:47 ` Theodore Tso
2002-02-12 8:33 ` Andreas Ferber
2002-02-12 8:56 ` mulix
2002-02-12 12:39 ` Pavel Machek
2002-02-12 21:43 ` guy keren
2002-02-12 6:00 ` David Schwartz
2002-02-12 6:09 ` Nick 'Sharkey' Moore
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox