Für den Zugriff auf Alternate Data Streams benötigt man unter Windows keine speziellen Syscalls, da die Auswahl des Streams ein Teil des Dateipfades ist. Daher kann man z.B. auch mit fopen oder vermutlich jeder ähnlichen Funktion in anderen Programmiersprachen auf einen anderen Stream zugreifen.
FILE *f = fopen("test.txt:stream2", "w");
fprintf(f, "Hello ADS\n");
fclose(f);
Was jedoch nicht so einfach geht ist, eine Liste aller Streams einer Datei zu erhalten. Zum Einsatz kommen dabei die Funktionen FindFirstStreamW und FindNextStreamW. Diese funktionieren ähnlich wie die Funktionen zum Auflisten von Verzeichniseinträgen (FindFirstFileW, FindNextFileW). Hier ein Beispiel, wie die Funktionen benutzt werden:
#include <stdio.h>
#include <windows.h>
int main(int argc, char** argv) {
WIN32_FIND_STREAM_DATA findData;
HANDLE h = FindFirstStreamW(L"test.txt", FindStreamInfoStandard, &findData, 0);
if(h == INVALID_HANDLE_VALUE) {
return 1;
}
do {
printf("%S\n", findData.cStreamName);
} while(FindNextStreamW(h, &findData));
return 0;
}
Das Format von cStreamName ist :streamname:$streamtype. Streamtype ist in der Regel immer DATA (ich kenne kein Beispiel, in dem dies nicht der Fall ist). Für den Fall, dass die Datei test.txt in dem Beispiel einen zweiten Stream mit dem Namen mystream hat, wäre die Ausgabe folgendermaßen:
::$DATA
:mystream:$DATA
Windows unterstützt Extended Attributes in Form von Alternate Data Streams (ADS). Ähnlich wie unter Solaris sind Extended Attributes damit keine einfachen Key/Value-Paare, sondern Datei-Streams. Jede Datei hat einen Haupt-Stream mit dem Namen :$DATA. Daneben können aber weitere Streams für eine Datei existieren.
Zugegriffen wird auf ein Attribut bzw. Alternate Data Stream, in dem an den Dateinamen der Stream-Name, getrennt durch einen Doppelpunkt, angehängt wird.
> echo Hello World > test.txt
> type test.txt
Hello World
> echo my attribute value > test.txt:myattribute
> type test.txt:myattribute
my attribute value
Auf den eigentlichen Dateiinhalt kann auch mit dem Stream-Namen :$DATA zugegriffen werde, was auch schon Ursache für Sicherheitslücken war. Den IIS konnte man so austricksen und den Inhalt von ASP-Dateien erhalten, in dem man ::$DATA an die URL angehangen hat.
Da der Stream Teil des Dateipfades ist, kann eigentlich mit jedem beliebigen Programm darauf zugegriffen werden, z.B. auch mit Notepad:
> notepad test.txt:myattribute
Lustigerweise funktioniert dies nicht in den Powershell-Cmdlets. Diese haben jedoch Parameter für den Zugriff auf ADS. Z.B. kann Get-Item alle Streams einer Datei auflisten:
PS D:\> Get-Item -path test.txt -stream *
PSPath : Microsoft.PowerShell.Core\FileSystem::D:\test.txt::$DATA
PSParentPath : Microsoft.PowerShell.Core\FileSystem::D:\
PSChildName : test.txt::$DATA
PSDrive : D
PSProvider : Microsoft.PowerShell.Core\FileSystem
PSIsContainer : False
FileName : D:\test.txt
Stream : :$DATA
Length : 14
PSPath : Microsoft.PowerShell.Core\FileSystem::D:\test.txt:myattribute
PSParentPath : Microsoft.PowerShell.Core\FileSystem::D:\
PSChildName : test.txt:myattribute
PSDrive : D
PSProvider : Microsoft.PowerShell.Core\FileSystem
PSIsContainer : False
FileName : D:\test.txt
Stream : myattribute
Length : 21
Für den Zugriff auf Extended Attributes hat macOS die Syscalls listxattr, getxattr, setxattr und removexattr. Sie heißen genauso wie die Linux-Syscalls und funktionieren auch fast genauso, nur haben sie ein paar Parameter für Options mehr. Die Syscalls finden sich im Header sys/xattr.h.
Um den Inhalt eines Attributs zu erhalten, benutzen wir den Syscall getxattr
:
ssize_t
getxattr(const char *path, const char *name, void *value, size_t size,
u_int32_t position,
int options);
Dabei gibt man den Dateipfad, den Namen des Attributs, einen Pointer auf einen Buffer sowie dessen Größe an. Wie im vorherigen Artikel erwähnt, haben macOS Extended Attributes keinen Namespace, daher ist name ein frei wählbarer Name. Mit value und size gibt man einen vorher allozierten Puffer und seine Größe an. Falls value NULL
ist, wird von der Funktion nur die Länge des Inhalts zurück gegeben. Der Parameter position funktioniert nicht mit allen Typen von Attributen und sollte daher 0 sein. Der Parameter options kann abgesehn von 0 noch XATTR_NOFOLLOW
oder XATTR_SHOWCOMPRESSION
sein.
char *buf = malloc(1024);
ssize_t attrlen = ("file.txt", "myattribute", buf, 1024, 0, 0);
if(attrlen > 0) {
printf("myattribute: %.*s\n", (int)attrlen, buf);
}
Attribute hinzufügen oder verändern geht mit setxattr.
int setxattr(const char *path, const char *name, void *value, size_t size,
u_int32_t position,
int options);
position sollte auch hier wieder 0 sein. Bei den options gibt es wieder XATTR_NOFOLLOW
und die interessanten Options XATTR_CREATE, um zu erzwingen, dass das Attribut nur gesetzt wird, falls es noch nicht existiert, oder XATTR_REPLACE
, um nur den Inhalt eines bereits existierenden Attributs zu ändern.
char *value = "Hello World!";
setxattr("file.txt", "myattribute", value, strlen(value), 0, 0);
Alle existierenden Attribute auflisten geht mit listxattr.
ssize_t listxattr(const char *path, char *namebuf, size_t size, int options);
Dies schreibt in den vorher allozierten Buffer die Namen aller Attribute, getrennt durch ein 0-Byte. Hier findet sich ein Beispielprogramm, welches alle Attribute einer Datei auflistet.
Unter macOS sind Extended Attributes wie unter den meisten Betriebsystemen einfache name/value-Paare. Im unterschied zu Linux oder FreeBSD haben die Attribute keinen Namespace wie user oder system sondern einfach nur einen frei wählbaren Namen.
Extended Attributes können mit dem Tool xattr angezeigt und modifiziert werden. Außerdem kann auch ls
Extended Attributes anzeigen. Eine kleine Übersicht über xattr:
Namen aller Attribute oder oder mehrerer Dateien auflisten:
xattr file ...
Value eines Attributs ausgeben:
xattr -p attr_name file ...
Attribut setzen:
xattr -w attr_name attr_value file ...
Attribut entfernen:
xattr -d attr_name file ...
Alle Attribute entfernen:
xattr -c file ...
Außerdem zeigt ls -l@
an, ob und welche Attribute eine Datei hat.
Ein kleines Beispiel:
$ echo "hello" > test.txt
$ rm test.txt
$ echo "Hello World" > hello.txt
$ echo test > test.txt
$ xattr -w mime_type "text/plain" hello.txt
$ xattr -w author Olaf hello.txt
$ xattr -w testxattr testvalue test.txt
$ ls -l@
total 16
-rw-r--r--@ 1 olaf staff 12 9 Dez 19:18 hello.txt
author 4
mime_type 10
-rw-r--r--@ 1 olaf staff 5 9 Dez 19:18 test.txt
testxattr 9
$ xattr -p mime_type hello.txt
text/plain
Positiv ist, dass das macOS tar Extended Attributes im Archiv speichert. Auch cp kopiert sie standardmäßig. Insgesammt scheinen Extended Attributes unter macOS generell ein lebendigeres Feature als unter anderen Betriebsystemen zu sein, denn sie werden vom Finder oder anderen Programmen auch benutzt.
Wenn man ein Verzeichnis liest und von allen enthaltenen Dateien die Extended Attributes erhalten will, gibt es zwei Möglichkeiten:
- Man fügt zum Verzeichnispfad den Dateinamen hinzu und nutzt den neu erhaltenen Pfad mit den Syscalls
listxattr
oder getxattr
.
- Mit dem Filedescriptor des Verzeichnisses und
openat
öffnet man die Dateien und nutzt dann flistxattr
und getxattr
.
Ich hab mich gefragt was schneller ist. Dazu habe ich ein kleines Testprogramm geschrieben. Dieses kann mit unterschiedlichen Preprocessor-Optionen kompiliert werden. So habe ich 4 Testprogramme erstellt. Für getxattr
und fgetxattr
jeweils ein Programm, das ein Attribut liest und eines das 32 Attribute liest.
Bei einem Verzeichnis mit 128.000 Dateien hab ich folgende Werte erhalten:
getxattr:1 getxattr:32 fgetattr:1 fgetattr:32
------------------------------------------------
246100055 654704421 456172044 749849574
230183311 663632162 457183706 769223423
247109480 654775136 440397212 743349119
Die Datei erst zu öffnen um dann fgetxattr
zu nutzen ist also langsamer. Erst als ich das Programm so modifiziert habe, dass es mehrere hundert Attribute liest, war es etwas schneller. Das ist jedoch ein eher unrealistisches Szenario. Allerdings war das ganze generell sehr schnell, so dass es eigentlich egal ist, welche Methode man anwendet.