Stored Procedure in C: Problem mit System Call open/ write

Hallo miteinander,
ich habe ein Problem mit einer Stored Procedure in C, welche mit den herkömmlichen System Calls open, write und close in eine Datei im Filesystem schreiben soll. Prinzipiell klappt das beim ersten Aufruf der SP auch, nur alle weiteren Aufrufe schlagen fehl.
Ich versuche erst mal die Details und was ich schon herausgefunden habe zu erklären:

Zweck der SP soll es sein, aus PostgreSQL heraus Binärdaten im Append-Mode in eine Datei im Filesystem zu schreiben, dazu wird in der SP mittels open die Datei geöffnet, dann mit write geschrieben und danach der Filehandle mit close wieder freigegeben:

int fct_c_fs_write_append(text* filename, bytea* data) {
	// Öffne Datei, falls noch nicht existent, erzeuge sie mit Zugriffsrecht rw-----
	// Datei im Append-Modus öffnen.
	int fh = open(VARDATA(filename), O_WRONLY | O_APPEND | O_CREAT, S_IRUSR | S_IWUSR);
	// Schreibe Daten, bei Fehler wird -1 zurückgegeben
	int written = write(fh, VARDATA(data), (VARSIZE(data) - VARHDRSZ));
	close(fh);
	return written;
}

Wie man sieht wird die Datei ggf. angelegt wenn sie noch nicht existiert und durch das O_APPEND wird die Datei beim erneuten Schreiben nicht geleert, sondern um die weiteren Daten ergänzt.

Wenn ich die SP nun ausführe (innerhalb einer psql-Sitzung) konnte ich Folgendes beobachten:

  • Der erste Aufruf funktioniert, die Datei wird wie gewünscht angelegt und die Daten werden geschrieben.
  • Ein erneuter Aufruf schlägt in dem Sinne fehl, dass write -1 zurückgibt und die Datei nicht verändert wird.
  • Beende ich psql, starte es neu und führe dann die SP wieder aus, funktioniert sie wieder. Existiert hier die Datei schon, wird wie gewünscht angefügt.
  • Es scheint mir, als ob PostgreSQL irgendwie die Datei blockiert, also z.B. den Filehandle nicht freigibt. Allerdings zeigt ein “lsof” in Linux nicht an, dass die Datei noch geöffnet wäre.
  • Ein Ein- und Ausschalten von autocommit, bzw. ein BEGIN - COMMIT zeigt keine Veränderung.
  • Führe ich diesen Code als normales C-Programm (ohne PostgreSQL) aus, so funktioniert er einwandfrei.
  • Das gleiche Problem scheint bei Verwendung von O_TRUNC zu passieren.

Hat jemand eine Ahnung/ Idee woran dies liegen kann? Vermutlich fehlt mir ein Detail bei der Ausführung von Stored Procedures. Eine Suche bei Google hat mir leider nicht weitergeholfen.

Vielen Dank schon mal & viele Grüße,
Tim

Nun, open() setzt “errno”, was steht denn dort drin?

Oh, und wirf doch mal einen Blick in:

src/backend/storage/file/fd.c

Mit deinem open() kannst du ggf. das Backend durcheinander bringen.

Hallo und vielen Dank schon mal für die schnelle Antwort.

Die Sache mit perror habe ich leider total vergessen, da ich bisher noch nicht so viel C programmiert habe.
perror liefert mir beim zweiten Aufruf der SP gleich die Fehlermeldung “No such file or directory”, damit ist der Filedeskriptor dann natürlich ungültig und das “write” schlägt fehl.
Woran dies nun liegt, kann ich aber nicht sagen. Wenn ich mir den als Parameter übergebenen text* mit dem Filenamen in ein eigenes (manuell geöffnetes) Log-File schreiben lasse, sehe ich hier keinen Unterschied zwischen dem funktionierendem ersten Aufruf und den fehlschlagenden weiteren Aufrufen.
Wenn ich übrigens eine String-Konstante im open-Call verwende (z.B. “/tmp/mytest”), klappt auch alles einwandfrei.

Vielleicht liegt es ja wirklich daran, dass open das Backend durcheinander bringt. Ich werde mal versuchen, den Code umzuschreiben und die Funktionen aus fd.h zu verwenden. Ich melde mich dann nochmals mit einem weiteren “Status-Bericht”.

Danke nochmals & Gruß,
Tim

Hallo, so wie es aussieht, habe ich das Problem nun gelöst.

Zunächst habe ich erfolglos auf die Verwendung der “virtuellen Filedeskriptoren” aus src/backend/storage/file/fd.c und die “Version 1 Calling Conventions” für SP in C umgestellt. (Hatte bisher die Version 0 Calling Conventions verwendet, da ich die Grundlagen des Codes schon vor einiger Zeit (Jahren?) mal geschrieben habe, als es die Version 1 wohl noch gar nicht gab.) Aber beide Änderungen haben nichts an dem beschriebenen Fehler behoben.
Als ich dann aber mal die Zieldatei fest in den Code eincodiert habe und dann alles einwandfrei funktioniert hat, bin ich stutzig geworden.
Meine Vermutung ist nun, dass der Audruck VARDATA(text*) keinen nullterminierten char* zurück gibt, und open deshalb damit Probleme hat.
Zumindest klappt nun alles, wenn ich mir aus dem text* einen nullterminierten char* selber bastle:

    text* fn_text = PG_GETARG_TEXT_P(0);
    bytea* data = PG_GETARG_BYTEA_P(1);
    char* filename = (char*) palloc(VARSIZE(fn_text)+sizeof(char));
    memcpy(filename, VARDATA(fn_text), VARSIZE(fn_text) - VARHDRSZ);
    filename[VARSIZE(fn_text)] = '\0';
    File fh = PathNameOpenFile(filename, O_WRONLY | O_APPEND | O_CREAT, S_IRUSR | S_IWUSR);
    int written = FileWrite(fh, VARDATA(data), (VARSIZE(data) - VARHDRSZ));
    FileClose(fh);
    pfree(filename);
    PG_RETURN_INT32(written);

Da nun alles funktioniert, scheint das tatsächlich das Problem gewesen zu sein. Kann das jemand mit seinem Wissen verifizieren?

Eine andere Frage ist mir nun noch aufgekommen.
Wie verhält es sich dem mit dem Speicher der übergebenen Argumente

    text* fn_text = PG_GETARG_TEXT_P(0);
    bytea* data = PG_GETARG_BYTEA_P(1);

Den Speicher hierfür fordere ich ja nicht selber an, vermutlich muss ich ihn dann auch nicht selber freigeben. Wann wird der Speicher freigegeben? Nach dem Funktionsaufruf oder am Ende der Transaktion? (Habe gelesen, dass auch mit pmalloc angefordeter Speicher am Ende einer Transaktion auf jede Fall freigegeben wird.)

Viele Grüße,
Tim

Du möchtest in jedem Fall die Datei src/backend/utils/mmgr/README lesen und verstehen :wink:
Der Speicher wird am Ende des Kontext freigegeben, in welchem du den Speicher reserviert hast.

Wenn ich mir deinen Code so anschaue: kann es sein, dass jeder deiner Stored Procedure einfach so einen Dateinamen übergeben kann?

Hallo und danke für den Hinweis mit der README.

Das entsprechende Sicherheitsproblem ist mir bewusst. Ich setze die SP aber so ein, dass jedermann (public) der Zugriff darauf entzogen wird; nur der Admin der Datenbank darf die Funktion ausführen. Die Benutzer können - knapp zusammengefasst - die SP nur über eine “Wrapper-SP” benutzen, welche mit “SECURITY DEFINER” definiert ist und dafür sorgt, dass mit der Filesystem-SP kein “Unfug” getrieben wird.

Oder siehst du hier ein weiteres Problem?

Danke & Gruß,
Tim

Wenn du dir der Sicherheitsprobleme bewusst, dann ist das ok.