UNIXwork

dav 1.2 veröffentlicht

2018-06-26 16:00:44.0

Es hat zwar etwas länger gedauert, aber nun gibt es ein neues Release von dav. Es werden vor allem die verschiedenen Plattformen nun besser unterstützt. Eine falsche Verwendung von vaargs hat dazu geführt, dass manche Betriebsystem-Compiler-Kombinationen nicht richtig funktioniert haben. Außerdem lässt sich dav jetzt unter macOS ohne openssl-Header kompilieren. Abgesehn von Xcode wird dort also keine zusätzliche Abhängigkeit benötigt. Und unter Windows werden jetzt Unicode-Dateinamen unterstützt.

Dazu haben die Tools ein paar kleine neue Features erhalten.

dav

dav-sync

dav Projektseite
SourceForge Projektseite

Autor: Olaf | 0 Kommentare | Tags: dav

Idee für Struct-Serialisierung

2018-05-01 13:10:48.0

Eine kleine semi-sinnvolle Idee, wie man Serialisierung in C realisieren kann, die ich einfach mal teilen möchte. Wer hingegen nach einer ordentlichen Serialisierungsmethode sucht, der sollte besser Codegeneratoren verwenden, wie z.B. Google Protocol Buffers für C++ oder protobuf-c für C.

Meine Idee hingegen basiert darauf, dass man seine Daten gar nicht erst serialisieren muss, weil sie bereits seriell im Speicher liegen.

Der triviale Fall

Structs, die keine Pointer enthalten, kann man direkt so wie sie im Speicher liegen in eine Datei schreiben oder aus ihr lesen.

struct mydata {
    int a;
    int b;
    float c;
};

...

struct mydata data;
data.a = 12;
data.b = 14;
data.c = 13.37;

write(fd, &data, sizeof(struct mydata));

...

struct mydata d2;
read(fd2, &d2, sizeof(struct mydata);

Das Konzept

Doch was machen wir, wenn eine Struct Pointer auf Structs oder andere Daten (z.B. Strings) enthält? Wenn man nur die Adresse speichert, bringt einem das beim Deserialisieren nichts, denn es fehlen nicht nur die Daten, der Pointer ist vielleicht auch ungültig.

Die Idee, die ich nun hatte, ist, dass die ganze Objekt-Hierarchie sich nur in einem festgelegten großen Speicherblock befinden darf. Die Structs enthalten dann auch keine echten Pointer mehr, sondern nur noch eine relative Adresse zu diesem Speicherblock, also quasi ein Index.

Hier ein kleines Beispiel, wie ich mir das vorstelle. Angenommen man hat folgende Structs:

typedef struct {
    int x;
    int y;
    int z;
} ChildObj;

typedef intptr_t ChildObjPtr;

typedef struct {
    ChildObjPtr child1;
    ChildObjPtr child2;
} RootObj;

Zuerst alloziert man genug Speicher, der (hoffentlich) für alle Daten reicht.

char *block = malloc(EVERYTHING_YOU_NEED);
size_t i = 0;

Will man jetzt Speicher für eine Struct reservieren, nutzt man dann nicht mehr malloc, sondern man greift auf den vorher reservierten Speichier zurück:

RootObj *root = (RootObj*)(block+i);
i += sizeof(RootObj);

ChildObj *c1 = (ChildObj*)(block+i);
i += sizeof(ChildObj);

ChildObj *c2 = (ChildObj*)(block+i);
i += sizeof(ChildObj);

c1->x = 1;
c1->y = 2;
c1->z = 3;
c2->x = 10;
c2->y = 20;
c2->z = 30;

Im RootObj möchte man jetzt Verweise auf die beiden anderen Structs haben. Hierfür müssen wir nur aus den Pointern c1 und c2 die Adressen relativ zu dem Pointer block berechnen.

root->child1 = (ChildObjPtr) (char*)c1 - block;
root->child2 = (ChildObjPtr) (char*)c2 - block;

Im Speicher liegen jetzt hintereinander die Daten. Zuerst RootObj und dann zwei mal ChildObj. Das kann man so direkt in Dateien schreiben und auch wieder daraus lesen.

Ein Problem ist, dass ich nicht direkt auf die Felder child1 und child2 zugreifen kann. Also statt root->child1 müsste man wieder die Adresse umrechnen:

((ChildObj*)(block + root->child1))->x = 5;

Optische Verbesserungen

Das ganze ist natürlich ganz schön hässlich. Und unpraktisch. Man kann kein malloc mehr verwenden und -> geht auch nicht. Dafür hat man viel Schreibarbeit und Rumgerechne, was viel Potential für Fehler hat.

Man könnte sich aber einen eigenen malloc-ähnlichen Memory-Allocator schreiben, der ein ähnliches Interface für Speicherverwaltung zur Verfügung stellt, aber auf einem zusammenhängenden Speicherbereich operiert. Die UCX-Bibliothek enthält eine einfache Implementierung hierfür.

Und für das Arbeiten mit den relativen Pointern kann man sich ein paar Makros schreiben:

// stores an absolut pointer as a relative pointer
#define SPTR(root, ptr) (intptr_t)((char*)ptr - (char*)root)

// converts a relative pointer to an absolut pointer
#define CPTR(root, relptr) (void*)((char*)root + relptr)

Mit den Makros sieht der Code von oben dann etwas schöner aus:

root->child1 = SPTR(root, c1); // still not as nice as root->child1 = c1  :'(

ChildObj* child = CPTR(root, root->child1);

Natürlich nicht ganz perfekt, schöner geht das aber leider nicht.

Probleme

Das Konzept ist, dass einfach der ganze Speicherblock beim Serialisieren irgendwo hin geschrieben wird, oder zumindestens so viel davon, wie belegt ist. Um aber halbwegs komformabel zu arbeiten benötigt man ein malloc/free ähnliches Interface zur Verwaltung seines Speicherblocks. In der Praxis kommt es häufig vor, dass sich Daten auch ändern. Speicher wird freigegeben und neuer alloziert. Dabei würden dann im Speicher Lücken entstehen. Beim Transferieren des Speicherblocks würden somit viele unnötige Daten übertragen werden.

Ein weiteres Problem ist, dass man vorher wissen muss, wie viel Speicher man benötigt. Diesen müsste man auch von Anfang an komplett allozieren, selbst wenn man erstmal nur wenig benötigt. Und falls er nicht reicht kann man ganz aufgeben.

Das Konzept kann aber abgewandelt werden, um die Probleme zu lösen. Wir können auch einen eigenen Allocator schreiben, der beliebig den Speicher verwaltet und nicht nur einen großen Block. Und anstatt beim serialisieren einfach nur einen großen Block zu schreiben, brauchen wir einen richtigen Serialisierer für den Allocator, der also alle vom Allocator ausgestellten Speicherbereiche irgendwie serialisiert, so dass genau diese Speicherbereiche in der Form wiederhergestellt werden können. Freie Speicherbereiche können dabei dann natürlich weggelassen werden.

Die Idee mit den relativen Pointern funktioniert dann natürlich nicht mehr, aber statt Pointer auf Kind-Objekte könnte man immer noch Integer verwenden, die dann vom Allocator für einen Lookup des echten Pointers verwendet werden. Man könnte daher immer noch mit den beiden Makros arbeiten, nur dass diese dann komplexere Logik als eine einfache Addition enthalten würden.

Zusammenfassung

Anstatt also eine Struct bzw ein Objekt mit allen ihren Membern zu serialisieren, serialisiert man einen Allocator, der für die Speicherreservierung der Objekte verwendet wurde. Objekte enthalten keine direkten Pointer auf die Speicheradresse anderer Objekte, sondern nur einen Index (oder ein Integer mit anderer Bedeutung).

Ist dieses Konzept sinnvoll? Ich weiß es nicht. Jedenfalls hab ich nicht vor, es irgendwo produktiv anzuwenden. Deswegen habe ich mir auch ein komplettes Code-Beispiel gespart.

Autor: Olaf | 0 Kommentare | Tags: c

Unix Rechteverwaltung: setuid, setgid, sticky bit

2018-04-22 12:54:25.0

Die klassische Unix-Rechteverwaltung dürfte den meisten ein Begriff sein. Es gibt drei Arten von Zugriffsrechten: read, write und execute. Für jede Datei sind diese Rechte jeweils für den User, die Group und Other gesetzt. Macht insgesammt 9 Bits, es gibt jedoch noch drei weitere Bits, nämlich für setuid, setgid und das sticky bit. Die Bedeutung dieser zusätzlichen Rechte hängt teilweise davon ab, um was für eine Art Datei es sich handelt.

setuid

Wenn das setuid-Bit für Executables gesetzt ist, dann wird das Programm mit den Rechten des Dateieigentümers ausgeführt. Dies ist zum Beispiel nötig für su, welches Root-Rechte für seine Funkion benötigt.

setgid

Bei Executables bewirkt das setgid-Bit, ähnlich wie bei setuid, dass Programme die Gruppenrechte des Eigentümers beim Ausführen erhalten.

Das setgid-Bit kann jedoch auch für Verzeichnisse gesetzt werden. Dies bewirkt, dass Dateien, die in diesem Verzeichnis erstellt werden, die Gruppe des Verzeichnisses erhalten, und nicht die Gruppe des Benutzers, der die Datei erstellt.

Sticky Bit

Das Sticky Bit kann auf Verzeichnissen angewendet werden und bewirkt bei diesen, dass die dort enthaltenen Dateien nur von ihrem Besitzer gelöscht werden können. Dies ist sinnvoll bei Verzeichnissen, auf die mehrere Benutzer vollen Zugriff haben, denn ohne Sticky Bit kann jeder enthaltene Dateien löschen.

Beispiel: chmod und ls

Erstellen wir kurz eine Datei:

$ touch file
$ ls -l 
total 0
-rw-r--r-- 1 olaf user 0 Apr 22 12:51 file

setuid:

$ chmod u+s file
$ ls -l
total 0
-rwSr--r-- 1 olaf user 0 Apr 22 12:51 file

setgid:

$ chmod g+s file
$ ls -l
total 0
-rwSr-Sr-- 1 olaf user 0 Apr 22 12:51 file

Sticky Bit:

$ mkdir dir
$ ls -l
total 4
drwxr-xr-x 2 olaf user 4096 Apr 22 12:53 dir
-rwSr-Sr-- 1 olaf user    0 Apr 22 12:51 file
$ chmod +t dir
$ ls -l
total 4
drwxr-xr-t 2 olaf user 4096 Apr 22 12:53 dir
-rwSr-Sr-- 1 olaf user    0 Apr 22 12:51 file
Autor: Olaf | 0 Kommentare | Tags: unix

Überladung von Funktionen in C

2018-04-21 18:59:29.0

Wer sich C schon mal angeschaut hat, der weiß, dass es dort keine Überladung von Funktionen gibt. Unterschiedliche Deklarationen von Funktionen mit dem selben Namen sind nicht erlaubt.

void myfunction(int i);
void myfunction(char *s); /* geht nicht */
void myfunction(int a, int b); /* geht erst recht nicht */

Je nach dem, was man vorhat, gibt es allerdings ein paar Tricks, um doch noch ans Ziel zu kommen.

In meinem Fall wollte ich Funktionen in zwei Varianten, die eine dieser Structs als Argument erhält:

typedef struct {
    char *ptr;
    size_t length;
} sstr_t;

/* new struct for const strings */
typedef struct {
    const char *ptr;
    size_t length;
} scstr_t;

Wie man sieht, sind beide Structs sehr ähnlich. Mein Plan war, dass diverse alte Funktionen, die bisher sstr_t-Parameter erwartet haben, dann mit beiden Structs funktionieren. Da man Structs (ohne Pointer) nicht in andere casten kann, konnte ich nicht einfache Wrapper-Makros für die Funktionen schreiben, die einfach einen Cast enthalten.

Das ganze brauche ich, um die String-API von UCX zu verbessern. Es soll auch eine eine Struct für konstante Strings geben, die dann auch benutzt werden kann, ohne die Quellcodekompatiblität groß zu brechen. Dabei habe ich mehrere Lösungen gefunden:

C11 _Generic

Seit C11 gibt es das _Generic Keyword für generische Ausdrücke. Damit können zur Compile-Time je nach Typ eines Arguments unterschiedliche Ausdrücke ausgewählt werden. Zum Beispiel:

void print_int(int i);
void print_string(char *s);

#define print(x) _Generic(x, int: print_int, char *: print_string)(x)

Benutzt man das Makro print nun mit einem int, wird die Funktion print_int ausgeführt. Bei einem char * wird print_string verwendet.

Was übrigens nicht geht ist folgendes:

#define print(x) _Generic(x, int: print_int(x), char *: print_string(x))

gcc/clang builtins

Gcc hat ein paar nützliche Builtins, mit denen man das Gleiche erreichen kann wie mit dem C11 _Generic. Praktischerweise unterstützt clang diese Builtins auch. Trotzdem verlässt man damit natürlich den Pfad der C-Standardkonformität.

Die beiden Builtins, mit denen man _Generics nachbauen kann, sind:

type __builtin_choose_expr (const_exp, exp1, exp2)
int __builtin_types_compatible_p (type1, type2)

Mit __builtin_types_compatible_p prüfen wir, ob der Typ kompatibel ist, und nehmen dies als Bedingung um eine Expression mit __builtin_choose_expr auszuwählen.

#define print(x) __builtin_choose_expr(__builtin_types_compatible_p(x, int), print_int, print_string)(x)

Spracherweiterungen: Statement Expressions und typeof

Statement Expressions sind eine C Erweiterung, die von einigen Compilern, unter anderem gcc, clang, aber auch anderen, unterstützt werden. Damit kann ein Compound-Statement wie eine Expression funktionieren. Die Anweisungen müssen nur mit ({ }) eingeschlossen werden. Dies kann dann überall stehen, wo Expressions erlaubt sind, also z.B. auch als Funktionsparameter.

Allgemein sowas wie _Generic nachbauen geht mit Statement Expressions nicht. Mein ursprüngliches Problem mit den zwei ähnlichen Structs hingegen lässt sich lösen. Statt jeweils eine Funktion für jeden Typ zu implementieren, reicht eine einzelne Funktion, die über ein Makro aufgerufen wird. Das Makro enthält dann eine Statement Expression, die sstr_t oder scstr_t in den richtigen Typ umwandelt.

Zum Einsatz kommt dabei auch typeof, was wieder eine Erweiterung ist, die auch von mehreren Compilern unterstützt wird. Hiermit kann eine Variable mit dem selben Typ wie eine andere deklariert werden.

void print_s(scstr_t s);

#define print(x) print_s( ({ \
    typeof(x) tmp = x; \
    scstr_t arg = { tmp.ptr, tmp.length }; \
    arg; \
}) )

Leere Parameterliste

Deklariert man in C eine Funktion ohne Angabe irgendeines Parameters, dann heißt dies nicht, das keine Parameter übergeben werden können, sondern dass der Compiler beliebige Parameter erlaubt:

void func();

void voidfunc(void);

int main(void) {
    func(1);
    func(1, 2, 3);
    func("string");
    func();

    voidfunc(); // no arguments allowed

    return 0;
}

Ähnlich wie beim Beispiel mit den Statement Expressions kann ich damit ein Makro für meine Funktion bauen, welches mit Hilfe einer Funktion das sstr_t oder scstr_t Argument umwandelt. Die Umwandlungsfunktion wird mit leerer Argumentliste deklariert und implementiert wird sie folgendermaßen:

scstr_t convert2scstr(sstr_t s) {
    scstr_t ret;
    ret.ptr = s.ptr;
    ret.length = s.length;
    return ret;
}

Dies funktioniert logischerweise nur, weil die beiden Structs gleich groß sind und einen gleichen Aufbau haben. Der Unterschied zwischen char * und const char* ist in diesem Fall egal. Sieht etwas frickelig aus, ist aber standardkonform und kann als Fallback-Lösung für alte oder unbekannte Compiler benutzt werden.

Insgesammt also genug Lösungsansätze, um sowohl alte Compiler zu unterstützen, aber bei neueren Compilern das ganze etwas sauberer umzusetzen.

Autor: Olaf | 0 Kommentare | Tags: c, ucx

Apple, das sinkende Schiff

2018-04-21 15:29:13.0

Alt, aber immer noch lustig.

Apple is like a ship with a hole in the bottom, leaking water, and my job is to get the ship pointed in the right direction.

Der Spruch stammt ursprünglich von Gil Amelio, der CEO von Apple war, bevor Steve Jobs 1997 wieder zurückkehrte.

Ebenfalls lustig, oder aber ziemlich traurig, sind einige Kommentare auf Youtube dazu:

when your rich everything seems to be funny!

Im not rich enough to understand the joke :(

get it? it's funny because they're rich

Autor: Olaf | 0 Kommentare | Tags: apple, fun
Weiter