blog · git · desktop · images · contact & privacy · gopher


Ist mein Dateisystem für 2038 bereit? (ext4)

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.

Der aktuelle Stand bei ext4

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.

Andere Größen relevanter Datentypen

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. :-)