Ich hatte neulich das Problem dass ein Script auf unserem Autobuilder 'date +%Ymd' aufruft um einen Dateinamen festzulegen.
Das ist ungeschickt, weil ich einen ganz bestimmten Dateinamen erzeugt haben wollte der den Datumsstring 20050107 enthält und nicht den heutigen Tag.
Also blieben vier Möglichkeiten:
- Systemzeit auf den 7. Januar 2005 zurücksetzen.
- /bin/date durch ein Shellscript ersetzen dass nix macht ausser echo 20050107
- Script ändern und den Namen hardcoden
- Irgendwie dem Script eine geänderte Systemzeit unterjubeln
Praktikabilitätsauswertung:
- Schlechte Idee: Ich will nicht global für alles auf dem System die Zeit zurücksetzen. Sind wir hier bei MS-DOS oder was?
- Klar. Was weiss ich was sonst noch date aufruft und b0rked während ich mein ganz spezielles /bin/date installiert habe. Unpraktikabel
- Klingt gut. Aber ich will mit pristine sources arbeiten und diese nicht modifizieren
- Tadaaaa! Die Lösung. Nur wie?
Kurze Nachfrage auf #ccc hatte die Lösung. Mit LD_PRELOAD eine Library laden die über hooks gettimeofday() modifiziert, so dass diese Funktion einen anderen Wert zurückgibt.
Manuel war auch so nett und hat mir gleich ein kleines Modul geschrieben, dass genau dies erledigt.
Klappte auch wunderbar.
Bis zu dem Zeitpunkt als ich die selbe Variante auf einer aktuellen Red Hat Enterprise Linux 4 Version brauchte.
Plötzlich bringt trotz dem gehookten gettimeofday() syscall /bin/date immer noch das aktuelle Datum aus.
Merkwürdig.
Ein strace später sieht man: Neuere Versionen von /bin/date auf Kernel-2.6.9 nutzen inzwischen nicht mehr gettimeofday() sondern clock_gettime() um das aktuelle Datum herauszubekommen.
In der Zwischenzeit hatte ich schon libfaketime.so entdeckt, eine Library die genau das selbe macht wie die von Manuel, nur kann man ihr die Wunschzeit als Parameter übergeben. Sehr praktisch das.
Also noch einen Hook für clock_gettime() eingebaut und plötzlich zeigt auch /bin/date die falsche Zeit an.
Wunderbar. Und das war auch gleich das erste mal dass ich eine Änderung an einem OSS Tool als Patch an den Autor weitergegeben habe. Cool.
UPDATE: Inzwischen ist der Patch auch vom Autor eingebaut worden und aktuelle Releases von faketime können auch clock_gettime() modifizieren.
Ansonsten, so sah der patch aus:
--- libfaketime/faketime.c.orig 2005-03-02 19:37:27.000000000 +0100
+++ libfaketime/faketime.c 2005-03-02 19:39:15.000000000 +0100
@@ -34,6 +34,7 @@
* functions. */
time_t (*orig_time) (time_t &*t);
int (*orig_gettimeofday) (struct timeval *tv, struct timezone *tz);
+int (*orig_clock_gettime) (clockid_t clk_id, struct timespec *tp);
time_t correction = 0;
int need_parse_env = 1;
@@ -51,6 +52,10 @@
orig_gettimeofday = dlsym (RTLD_NEXT, "gettimeofday");
if (dlerror() != NULL)
return -1;
+
+ orig_clock_gettime = dlsym (RTLD_NEXT, "clock_gettime");
+ if (dlerror() != NULL)
+ return -1;
return 0;
}
@@ -123,6 +128,28 @@
return ret;
}
+/* Also important - wrapper for clock_gettime(3). */
+extern int
+clock_gettime(clockid_t clk_id, struct timespec *tp)
+{
+ int ret;
+
+ if (! orig_clock_gettime && open_shlib () != 0)
+ return -1;
+
+ ret = (*orig_clock_gettime) (clk_id, tp);
+
+ if (ret == 0 && tp)
+ {
+ if (need_parse_env)
+ parse_env (tp->tv_sec);
+
+ tp->tv_sec -= correction;
+ }
+
+ return ret;
+}
+
#ifdef HAVE_ELF
/*; This is an entrypoint if the library is invoked as a program, i.e.
* $ ./libfaketime.so