blog · git · desktop · images · contact
2018-06-24
Im Jahre 2038 werden Timestamps auf Basis von 32-Bit-Integern überlaufen. Sehr viele Timestamps sind so gespeichert, also wird es ein Problem werden. Oder? 2038 ist noch 20 Jahre hin, bis dahin sollten wir das ja wohl auf die Reihe gekriegt haben. Tja, naja, ich kann mich noch gut daran erinnern, als der Satz ging: „2038 ist noch 30 Jahre hin, bis dahin …“. Und ich kann mich auch noch gut an die Panik um das Jahr 2000 herum erinnern. Das geht alles schneller, als man denkt.
Software zu aktualisieren, ist eine Sache. Dateisysteme sind aber immer etwas träger, glaube ich – es ist halt lästig, so ein On-Disk-Format zu ändern.
Schauen wir mal schnell bei ext4 nach.
Erzeugen wir mal ein Dateisystem und tun wir so, als wären wir schon in der Zukunft.
$ dd if=/dev/zero of=fs bs=1G count=1
$ mkfs.ext4 fs
$ mkdir m
# mount -o loop fs m
# touch -d '2140-01-01 12:00:00' m/future
# umount m
Das remounten wir dann, um sicherzugehen, dass wir tatsächlich mit den Timestamps arbeiten, die auf der Platte gespeichert sind – und nicht mit Werten, die von Linux nur gecached wurden.
# mount -o loop fs m
$ ls -l m
# umount m
Was mal schnell auffällt: ls
zeigt die korrekte Zeit und das korrekte
Datum an. Darauf kommen wir später nochmal. Aber hey, das sieht ja
schonmal gut aus!
Schauen wir noch ein bisschen genauer hin. Mit debugfs
kann man sich
die Rohdaten auf der Platte anschauen.
$ debugfs fs
debugfs: ls
2 (12) . 2 (12) .. 11 (20) lost+found 12 (4040) future
debugfs: inode_dump future
0000 a481 e803 0000 0000 30db c23f d294 2f5b ........0..?../[
0020 30db c23f 0000 0000 6400 0100 0000 0000 0..?....d.......
0040 0000 0800 0100 0000 0af3 0000 0400 0000 ................
0060 0000 0000 0000 0000 0000 0000 0000 0000 ................
*
0140 0000 0000 776f 4da0 0000 0000 0000 0000 ....woM.........
0160 0000 0000 0000 0000 0000 0000 6292 0000 ............b...
0200 2000 238e 2c3c dd7d 0100 0000 0100 0000 .#.,<.}........
0220 a394 2f5b f448 f37d 0000 0000 0000 0000 ../[.H.}........
0240 0000 0000 0000 0000 0000 0000 0000 0000 ................
*
Soso. Wir interpretiert man das? Um ehrlich zu sein, weiß ich nicht, wo es eine offizielle Dokumentation dazu gibt, wenn man mal vom recht kompakt kommentierten ext4-Quellcode absieht. Es gibt aber einen Artikel im ext4-Wiki auf kernel.org – der kann ja so verkehrt nicht sein.
Man beachte an der Stelle, dass die Offsets von debugfs
oktal notiert
sind, nicht hexadezimal.
Okay, wir suchen also das Datum der letzten Änderung der Datei,
i_mtime
. Das steht bei Offset 0x10 (020) und wird als 32-Bit-Integer
in Little Endian genannt. Das ist diese Byte-Sequenz:
30db c23f
Dreht man’s wegen Endianness um, dann bekommt man:
0x3fc2db30
Das kann nicht alles sein, weil wir das Jahr ja auf 2140 gesetzt haben
(man rechne 0x3fc2db30 einfach mal in eine Dezimalzahl um, dann sieht
man schnell, wie klein die ist). Stellt sich raus, dass noch zwei
weitere Bits benutzt werden, um den Timestamp zu erweitern. Gespeichert
sind sie in i_mtime_extra
bei Offset 0x88 (0210) – das sind nochmal 32
Bit:
0100 0000
Nach Endianness hat man hier nur die Zahl 1.
Im Wiki steht dann, wie man die beiden Felder zusammenfügt:
https://ext4.wiki.kernel.org/index.php/Ext4_Disk_Layout#Inode_Timestamps
Sprich, wir sind beim zweiten Feld eh nur an den zwei niedrigsten Bit interessiert.
Lange Rede, kurzer Sinn: Wir müssen nochmal 0x100000000 auf das ursprüngliche 0x3fc2db30 draufaddieren, womit wir dann bei dezimal 5364702000 wären. Und das stimmt dann auch:
$ date -d '2140-01-01 12:00:00' +%s
5364702000
tl;dr: ext4 opfert zwei Bit des Nanosekunden-Felds und fügt sie zum Timestamp hinzu. Das wird bis zum Jahre 2446 funktionieren.
Man konnte oben sehen, dass ls
schon ordentlich das Jahr 2140
angezeigt hat. Das impliziert, dass schon Arbeit investiert wurde, um
das Userland mit dem Jahr 2038 umgehen zu lassen. Einer der wichtigsten
Datentypen ist time_t
. Wie groß ist der auf heutigen amd64-Systemen?
#include <stdio.h>
#include <time.h>
int
main()
{
printf("%zd\n", sizeof (time_t));
return 0;
}
Groß genug, 64 Bit:
$ gcc -Wall -Wextra -std=c99 -o foo foo.c && ./foo
8
Ist es garantiert, dass der so groß ist? Weiß ich nicht so richtig, weil der C-Standard hinter einer Paywall eingemauert ist. Ein Draft, den ich gefunden habe, erzählt etwas von „implementation-defined“. Meh.
time_t
wird an einigen Stellen benutzt, zum Beispiel im struct
timespec
, was dann wiederum vom stat
-Syscall (heute) verwendet wird.
Sprich, solange das VFS in Linux intern Datentypen benutzt, die mit
Daten nach 2038 umgehen können, sollte das Userland keine Probleme
haben. Schätze ich. :-)