C: fifo lesen

Vom einfachen Programm zum fertigen Debian-Paket, Fragen rund um Programmiersprachen, Scripting und Lizenzierung.
RobertDebiannutzer
Beiträge: 385
Registriert: 16.06.2017 09:52:36

C: fifo lesen

Beitrag von RobertDebiannutzer » 19.08.2018 16:31:59

Ich möchte eine fifo lesen und eventuellen Text als Name des Root Windows des Xservers setzen. Das ganze soll als while-loop laufen. Der X-related Code ist i.O., habe ich mir vom Code von xsetroot abgeschaut.
Bisher habe ich geschafft: Entweder wird
1. nur eine Zeile gelesen, nach X ausgegeben und dann beendet sich das Programm.
2. gar nichts nach X ausgegeben, aber 100% CPU.

Hier mal der Code. Auskommentierten Testcode habe ich absichtlich nicht entfernt. Aktueller Stand: 1. Variante. Das Programm soll sich aber nicht beenden, sondern weiterlaufen...
Kompiliert wurde mit: "gcc -pedantic -Wall -Werror -Wextra -lX11 -o myxd myxd.c"

Über Hilfe würde ich mich sehr freuen! Ich habe mir echt schon viel Mühe gegeben, kriege es aber einfach nicht hin... :(

Code: Alles auswählen

#include <X11/Xlib.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

static const char program_name[] = "myxd";
static Display *dpy;
static int screen;
static Window root;

int
main(void) 
{
	char *display_name = NULL;
	char name[128];
	/* int ch; */
	/* FILE *in; */
	int in;

	dpy = XOpenDisplay(display_name);
	if (!dpy) {
		fprintf(stderr, "%s:  unable to open display '%s'\n",
				program_name, XDisplayName (display_name));
		exit (2);
	}
	root = RootWindow(dpy, screen);

	in = open("/home/user/fifo", O_RDONLY);
	/* in = fopen("/home/user/fifo", "r"); */
	/* in = fdopen(fd, "r"); */
	/* 
	 * if (in == NULL) {
	 * perror("fopen");
	 * exit(EXIT_FAILURE);
	 * }
	 */

	/* (ch = getchar()) != '\n' && ch != EOF */
	while (read(in, name, 128) > 0) {
		/* get input */
		/* fgets(name, 128, in); */
		/* fgets(name, 128, stdin); */
		/* read(0, name, sizeof(name)); */
		/* Handle set name */
		if (name[0] != '\0' && name[0] != '\n') {
			/* 
			 * remove trailing newline - so use echo or printf '%s\n'
			 * for input
			 */
			name[strlen(name)-1] = 0;
			XStoreName(dpy, root, name);
		}
	}
	XCloseDisplay(dpy);
	/* fclose(in); */
	close(in);

	exit(EXIT_SUCCESS);
}

Benutzeravatar
Meillo
Moderator
Beiträge: 8781
Registriert: 21.06.2005 14:55:06
Wohnort: Balmora
Kontaktdaten:

Re: C: fifo lesen

Beitrag von Meillo » 19.08.2018 18:49:33

Ich habe nur kurz drueber geschaut, will aber doch ein paar Hinweise geben.

Aus der FIFO kommt doch Text, dann lese viel leichter mit stdio (also fgets(3)) als mit Systemcalls (also read(2))!

Soweit ich das sehe, hast du eine Busy-Loop. Darum bekommst du auch 100% CPU-Last. Baue unbedingt einen Sleep ein! Es ist voellig unnoetig tausende Mails pro Sekunden zu schauen, ob was zu lesen da ist. ;-) Einmal oder zehnmal pro Sekunde reicht auch.

Was gibt denn eine FIFO zurueck, wenn gerade nichts zu lesen da ist? EOF? (Weiss das gerade nicht auswendig und bin zu faul nachzuschauen. :roll: ) Dein read() liefert bei EOF 0 zurueck, was die Schleife beendet, darum liest dein Programm nur einmal! Die selbe Situation hast du bei fgets(3) auch, bloss ist 0 da halt die Konstante ``EOF''. Entscheidend ist doch, dass dein Programm weiter laeuft, auch wenn gerade nichts gelesen werden kann. Es soll sich nur dann beenden, wenn beim Lesen ein Fehler (wie Datei nicht gefunden oder so) auftaucht. Du wirst dein Progamm also normalerweise manuell killen muessen, wenn du es beenden willst! (Oder du bringst ihm bei, dass es sich selber beenden soll, wenn du ``Hasta la vista!'' in die FIFO schreibst. ;-) )

Und dann noch eine Kleinigkeit: Dein Entfernen des Newlines am Ende des gelesenen Strings ist ein Good-Faith-Code ... du glaubst einfach, dass das letzte Zeichen schon ein Newline sein wird. Pruefe besser explizit darauf ... dann kannst du dir auch den Kommentar sparen. ;-)

Wenn du den Code grundsaetzlich am Laufen hast, dann poste ihn doch nochmal, dann kann ich auch noch mehr zum Stil sagen.

Ach und noch was ;-) : Die Puffergroesse 128 Bytes macht wenig Sinn. Verwende einfach immer die Konstante BUFSIZ (in stdio.h definiert, IIRC). Das sind typischerweise sowas wie 4096 Bytes. Wenn du in der Groesseneinheit Daten schaufelst, dann geht das meist schneller als in anderen Groesseneinheiten, weil das die natuerliche Einheit deines Systems ist.


Ich hoffe, du bekommst dein Programm zum Laufen. Der relevante Hinweis ist die Pruefung auf den Rueckgabewert der Lesefunktion im Schleifenkopf!
Use ed once in a while!

RobertDebiannutzer
Beiträge: 385
Registriert: 16.06.2017 09:52:36

Re: C: fifo lesen

Beitrag von RobertDebiannutzer » 19.08.2018 22:57:19

Vielen Dank für Deine Anregungen! :THX:
Ich konnte das Problem durch Probieren mit einem zweiten, äußeren while-loop (while (1)) nochmal dahingehend eingrenzen:
Wenn ich einen äußeren while-loop um die ganze Funktion setze (außer die Variablen und das exit), funktioniert alles wie gewünscht.
Wenn ich einen äußeren while-loop um den existierenden setze und noch open und close der fifo mitnehme, kommt es nicht zu 100% CPU, aber X kriegt nichts ab.
Wenn ich einen äußeren while-loop nur um den existierenden setze, kommt es zu 100% CPU, aber erst nach dem ersten Schreiben (also von einem anderen Terminal "echo hallo > fifo")! Auch hier kriegt X nichts ab.
Aber es kann doch nicht sein, dass ich die Verbindung zu X jedes Mal öffnen und schließen muss!? Irgendwie muss es doch mit der fifo zusammenhängen...
Ich probiere weiter. Eigentlich ist meine Idee so einfach...

Benutzeravatar
Meillo
Moderator
Beiträge: 8781
Registriert: 21.06.2005 14:55:06
Wohnort: Balmora
Kontaktdaten:

Re: C: fifo lesen

Beitrag von Meillo » 20.08.2018 07:13:30

Du suchst doch dieses:

Code: Alles auswählen

while sleep 1; do
	xsetroot -name "`cat /path/to/fifo`"
done
... bloss in einem eigenen C-Programm, weil ... weil du es selber machen willst oder weil du denkst, dass es mit Shellmitteln nicht geht?

RobertDebiannutzer hat geschrieben: ↑ zum Beitrag ↑
19.08.2018 22:57:19
Ich konnte das Problem durch Probieren mit einem zweiten, äußeren while-loop (while (1)) nochmal dahingehend eingrenzen:
Wenn ich einen äußeren while-loop um die ganze Funktion setze (außer die Variablen und das exit), funktioniert alles wie gewünscht.
Wenn ich einen äußeren while-loop um den existierenden setze und noch open und close der fifo mitnehme, kommt es nicht zu 100% CPU, aber X kriegt nichts ab.
100% CPU-Auslastung bekommst du nur dann wenn du eine Busy-Loop hast. Die kannst du ganz einfach mit einem kleinen Sleep verhindern. (Auf diese Anregung von mir hast du nicht reagiert.) Ansonsten stellt sich die Frage, warum die Loop busy ist, weil, wenn ich von einer FIFO lesen will in der nichts drin ist, dann blockiert mein cat und wartet bis etwas Lesbares kommt. Ich hab nicht selber rumprobiert was fgets(3) in dem Fall macht ... aber das ist letztlich ein Standardproblem. Vielleicht suchst du mal nach Tutorials wie man richtig aus einer FIFO liest, damit wir da nichts mehr falsch machen.
Wenn ich einen äußeren while-loop nur um den existierenden setze, kommt es zu 100% CPU, aber erst nach dem ersten Schreiben (also von einem anderen Terminal "echo hallo > fifo")! Auch hier kriegt X nichts ab.
Aber es kann doch nicht sein, dass ich die Verbindung zu X jedes Mal öffnen und schließen muss!? Irgendwie muss es doch mit der fifo zusammenhängen...
Wenn ich die Zeit finde, dann gehe ich das Problem gerne auch noch praktisch an und ueberlege nicht nur theoretisch. Dazu waere es aber hilfreich, wenn du noch genau formulieren wuerdest was du unter ``richtigem Verhalten'' verstehst. Was genau soll das Programm also wann machen? Was beispielsweise wenn noch keine Daten in der FIFO sind?

Ich probiere weiter. Eigentlich ist meine Idee so einfach...
Die Erfahrung sagt, dass man sich meistens nur selber im Weg steht. ;-)
Use ed once in a while!

RobertDebiannutzer
Beiträge: 385
Registriert: 16.06.2017 09:52:36

Re: C: fifo lesen

Beitrag von RobertDebiannutzer » 20.08.2018 08:20:34

Meillo hat geschrieben: ↑ zum Beitrag ↑
20.08.2018 07:13:30
Du suchst doch dieses:
Nö, es soll auf dieses hinauslaufen (nur ein Ausschnitt):

Code: Alles auswählen

while true; do
	time_cmd
	while true; do
		wifi_cmd
		battery_cmd
		xsetroot "$time | $wifi | $volume | $ethernet | $backlight | $monitor | $battery |"
		while $(timeout 10 cat /tmp/dwmblocks); do
			xsetroot "$time | $wifi | $volume | $ethernet | $backlight | $monitor | $battery |"
		done
		n=x$n
		if [ $n = "xxxxxx" ]; then
			unset n
			continue 2
		fi
	done
done
Wobei mein Programm xsetroot ersetzen soll, möglichst den ganzen 3. (also innersten) loop soll (+ vielleicht den ersten Aufruf von xsetroot, aber eins nach dem anderen).
EDIT: Also es soll nacher mindestens mal so aussehen:

Code: Alles auswählen

while true; do
	time_cmd
	while true; do
		wifi_cmd
		battery_cmd
		echo "$time | $wifi | $volume | $ethernet | $backlight | $monitor | $battery |" > fifo
		while $(timeout 10 cat /tmp/dwmblocks); do
			echo "$time | $wifi | $volume | $ethernet | $backlight | $monitor | $battery |" > fifo
		done
		n=x$n
		if [ $n = "xxxxxx" ]; then
			unset n
			continue 2
		fi
	done
done
Aus der fifo - hier /tmp/dwmblocks (in Anlehnung an i3blocks... :mrgreen: ) - kommen Befehle für die Funktionen:

Code: Alles auswählen

static const char *volumeup[] = { "/bin/sh", "-c", "echo 'volume_cmd 4' > /tmp/dwmblocks", NULL };
static const char *volumedown[] = { "/bin/sh", "-c", "echo 'volume_cmd 5' > /tmp/dwmblocks", NULL };
static const char *volumemute[] = { "/bin/sh", "-c", "echo 'volume_cmd 3' > /tmp/dwmblocks", NULL };
static const char *backlightup[] = { "/bin/sh", "-c", "echo 'backlight_cmd 4' > /tmp/dwmblocks", NULL };
static const char *backlightdown[] = { "/bin/sh", "-c", "echo 'backlight_cmd 5' > /tmp/dwmblocks", NULL };
static const char *updatebar[] = { "/bin/bash", "-c", "kill $(< /tmp/dwmblocks_pid)", NULL };
static const char *calcmd[] = { "/bin/sh", "-c", "echo 'time_cmd 3' > /tmp/dwmblocks", NULL };
Die Zahlen standen für Mouse-Events, als die Funktionen noch Scripte für i3blocks waren. Ich war einfach zu faul, das zu ändern... Letztendlich ist es ja so auch weiterhin gut. Irgendwelche Argumente müssen die Funktionen ja kriegen...

Ich werde jetzt noch die Fifo mehr studieren, wie von Dir vorgeschlagen. Einen sleep wollte ich nicht einbauen, denn cat hat den ja auch nicht. Irgendwie muss es ohne gehen. Und es geht ja auch bis zum ersten write ohne. Nur ab dem ersten write spielt mein Programm verrückt... (Ich habe einen sleep getestet, doch an dem Verhalten meines letzten Beitrags hat sich nichts geändert, außer dass CPU nicht mehr auf 100 geht. Insofern muss noch was anderes dahinterstecken.)

Ach ja: Noch zum Ziel: Im Moment rufe ich aus meinem Script 3 externe Programme auf, davon eines ggf. mehrmals. Wobei xsetroot jetzt nicht rasend schnell ist... Da ist ein einzelnes kleines Programm extra für diesen Zweck doch wesentlich eleganter. Und es wäre ja eigentlich auch ganz einfach. Ich muss das nur noch mit der fifo hinkriegen.

Benutzeravatar
bluestar
Beiträge: 2334
Registriert: 26.10.2004 11:16:34
Wohnort: Rhein-Main-Gebiet

Re: C: fifo lesen

Beitrag von bluestar » 20.08.2018 12:55:27

Wenn du kein sleep verwenden möchtest, dann nutze select(...) und warte damit vor dem Lesen aus dem Fifo, bis tatsächlich Daten im Fifo anstehen.

Beispiel: https://outflux.net/blog/archives/2008/ ... on-a-fifo/

RobertDebiannutzer
Beiträge: 385
Registriert: 16.06.2017 09:52:36

Re: C: fifo lesen

Beitrag von RobertDebiannutzer » 20.08.2018 13:52:18

Vielen Dank für die Idee! Ich habe das Nutzen von select() schon mittels Google gefunden, aber die Beispiele, die ich fand, funktionierten irgendwie nicht. Ich werde es mal mit dem Beispiel in Deinem Link probieren und das Ergebnis mit Code posten.

BTW:
Mit Perl in 5sec und einmal ganz kurz googeln funktioniert endloses Lesen von Fifo ohne 100% CPU (der Trick ist das "+" vor dem "<"):

Code: Alles auswählen

#!/usr/bin/perl
use strict;
use warnings;

open(FH, "+<", "/home/user/fifo");
while (<FH>) {
	print "$_\n";
}
close(FH);
Warum fällt mir Perl immer leicht und C ist so ein Kampf? Naja, ich kriege es noch hin!

RobertDebiannutzer
Beiträge: 385
Registriert: 16.06.2017 09:52:36

Re: C: fifo lesen

Beitrag von RobertDebiannutzer » 20.08.2018 17:13:07

Heureka!

Code: Alles auswählen

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int
main(void) 
{
	char name[BUFSIZ];
	int fd;

	fd = open("/home/user/fifo", O_RDWR | O_NONBLOCK);

	fd_set rfds;
	int rc;

	FD_ZERO(&rfds);
	FD_SET(fd, &rfds);

	while (1) {
		rc = select(fd + 1, &rfds, NULL, NULL, NULL);
		if (rc > 0) {
			ssize_t got = read(fd, name, BUFSIZ);
			if (got < 0) {
				perror("read");
				return 1;
			} else if (got == 0) {
				printf("EOF\\n");
				return 1;
			}
			else {
				printf(name);
			}
		}
	}
	close(fd);

	exit(EXIT_SUCCESS);
}
Mehr später.

RobertDebiannutzer
Beiträge: 385
Registriert: 16.06.2017 09:52:36

Re: C: fifo lesen

Beitrag von RobertDebiannutzer » 20.08.2018 17:46:35

Jetzt habe ich sogar noch den passenden X-Code ausgetüftelt. Da hat nach XStoreName noch was gefehlt. So ganz kapiere ich noch nicht, warum es nun funktioniert, aber es funktioniert immerhin mal. Nunja ... das mit dem newline-Ersetzen ergibt irgendwie noch ein komisches Kästchen nach dem String in der Statusbar...

Code: Alles auswählen

#include <X11/Xlib.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

static const char program_name[] = "myxd";
static Display *dpy;
static int screen;
static Window root;
static XEvent ev;

int
main(void) 
{
	char *display_name = NULL;
	char name[BUFSIZ];
	int fd;

	dpy = XOpenDisplay(display_name);
	if (!dpy) {
		fprintf(stderr, "%s:  unable to open display '%s'\n",
				program_name, XDisplayName (display_name));
		exit (2);
	}
	root = RootWindow(dpy, screen);

	fd = open("/home/user/fifo", O_RDWR | O_NONBLOCK);

	fd_set rfds;
	int rc;

	FD_ZERO(&rfds);
	FD_SET(fd, &rfds);

	for (;;) {
		rc = select(fd + 1, &rfds, NULL, NULL, NULL);
		if (rc > 0) {
			ssize_t got = read(fd, name, BUFSIZ);
			if (got < 0) {
				perror("read");
				/* return 1; */
				exit(EXIT_FAILURE);
			} else if (got == 0) {
				printf("EOF\\n");
				/* return 1; */
				exit(EXIT_FAILURE);
			}
			else {
				/* remove trailing newline */
				if (name[strlen(name)]-1 == '\n')
					name[strlen(name)-1] = '\0';
				XStoreName(dpy, root, name);
				while (XPending(dpy))
					XNextEvent(dpy, &ev);
			}
		}
	}
	close(fd);
	XCloseDisplay(dpy);

	exit(EXIT_SUCCESS);
}

Benutzeravatar
Meillo
Moderator
Beiträge: 8781
Registriert: 21.06.2005 14:55:06
Wohnort: Balmora
Kontaktdaten:

Re: C: fifo lesen

Beitrag von Meillo » 20.08.2018 17:52:11

Schoen, dass es jetzt laeuft! :-)

RobertDebiannutzer hat geschrieben: ↑ zum Beitrag ↑
20.08.2018 17:46:35
Nunja ... das mit dem newline-Ersetzen ergibt irgendwie noch ein komisches Kästchen nach dem String in der Statusbar...

Code: Alles auswählen

				/* remove trailing newline */
				if (name[strlen(name)]-1 == '\n')
					name[strlen(name)-1] = '\0';
Das `-1' muss *in* die eckigen Klammern. ;-)
Use ed once in a while!

Benutzeravatar
bluestar
Beiträge: 2334
Registriert: 26.10.2004 11:16:34
Wohnort: Rhein-Main-Gebiet

Re: C: fifo lesen

Beitrag von bluestar » 20.08.2018 18:02:39

Um dir Speicherfehler zu ersparen habe ich noch ein paar kleine Hinweise für dich:

Code: Alles auswählen

	char name[BUFSIZ];
aus BUFSIZE mach ein

Code: Alles auswählen

BUFSIZE + 1
und vor der Zeile

Code: Alles auswählen

			ssize_t got = read(fd, name, BUFSIZ);
nullst du deinen String sauber

Code: Alles auswählen

memset (name, 0, BUFSIZE);
und last but not least

Code: Alles auswählen

				exit(EXIT_FAILURE);
Solltest du durch ein

Code: Alles auswählen

break;
ersetzen, damit die Aufräumarbeiten nach der For-Schleife sauber ausgeführt werden können.

RobertDebiannutzer
Beiträge: 385
Registriert: 16.06.2017 09:52:36

Re: C: fifo lesen

Beitrag von RobertDebiannutzer » 20.08.2018 22:23:04

Also erst einmal herzlichen Dank für eure Hilfe und eure Anregungen! :THX:

Ich möchte jetzt noch aufarbeiten, wo die haupstächlichen Probleme waren, und welche Lösungen ich gefunden habe.

1.
Das Problem mit der endlos-Schleife, aus der die fifo gelesen werden soll bzw. das Problem, dass aus der endlos-Schleife auch ohne 100% CPU-Auslastung und ohne sleep gelesen werden kann.
Lösung 1. Teil:
select() nutzen (gemäß @bluestars Link), und zwar mit dem timeout NULL, so wie in "man select" angegeben:
If timeout is NULL (no timeout), select() can block indefinitely.
Lösung 2. Teil:
open() mit den Parametern "O_RDWR | O_NONBLOCK" nutzen, so wie in @bluestars Link beschrieben:
In the case of a FIFO, however, “0 length read” means there are no more FIFO writers — but more could attach later and continue feeding in data! The problem with this is that select misbehaves and marks the file descriptor as “EOF” forever. Only the initial select() call blocks until there is something to read — once it hits EOF, select will always immediately return, defeating the purpose of select().

One solution is to re-open the FIFO, but you might miss writers between your 0-length read() and the next open().

The seemingly correct solution is rather simple: the FIFO reader should open the FIFO as a writer also. In this case, select() never thinks all the writers have vanished, so there is never an EOF condition, and the reader never misses any writers. So, instead of O_RDONLY, use:

fd = open(FIFO_PATHNAME, O_RDWR | O_NONBLOCK);

2.
Das Problem, dass X nicht "name" erhalten hat.
Lösung:
Direkt nach dem "XStoreName(dpy, root, name);" ein

Code: Alles auswählen

while (XPending(dpy))
	XNextEvent(dpy, &ev);
Ich weiß nicht genau, was das macht – ich hab‘s per Google gefunden – aber es funktioniert bei mir.
Die Funktionen dienen sozusagen dazu, den Xserver bereit für den nächsten event zu machen…


3.
Keine Flüchtigkeitsfehler machen! :mrgreen:


Noch eine Frage:
@bluestar:
Warum eigentlich "BUFSIZ+1"?


Jetzt überlege ich mir noch, ob ich auch mein "timeout 10 cat" in mein Programm packe, aber bis hierher bin ich auf jeden Fall schon einmal sehr zufrieden. Endlich ein "eigenes" kleines C-Programm, das sich zu meinen bash/dash-Scripten und Perl-Programmen gesellen kann… :D
Besonders zufrieden bin ich auch damit, dass ich jetzt eine Lösung gefunden habe, in C Programme miteinander kommunizieren lassen zu können.

Benutzeravatar
bluestar
Beiträge: 2334
Registriert: 26.10.2004 11:16:34
Wohnort: Rhein-Main-Gebiet

Re: C: fifo lesen

Beitrag von bluestar » 20.08.2018 22:38:36

RobertDebiannutzer hat geschrieben: ↑ zum Beitrag ↑
20.08.2018 22:23:04

@bluestar:
Warum eigentlich "BUFSIZ+1"?
Naja wenn du exakt BUFSIZE Zeichen einliest, dann ist dein String nicht mehr null-terminiert und schon läuft strlen(...) im worst case über den Speicherplatz hinaus.

Dein Puffer muss bei String ein Byte größer sein, damit das Stringende (ASCII-0) noch ans Ende dranpasst.

Benutzeravatar
Meillo
Moderator
Beiträge: 8781
Registriert: 21.06.2005 14:55:06
Wohnort: Balmora
Kontaktdaten:

Re: C: fifo lesen

Beitrag von Meillo » 20.08.2018 23:55:15

RobertDebiannutzer hat geschrieben: ↑ zum Beitrag ↑
20.08.2018 22:23:04
Also erst einmal herzlichen Dank für eure Hilfe und eure Anregungen! :THX:

Ich möchte jetzt noch aufarbeiten, wo die haupstächlichen Probleme waren, und welche Lösungen ich gefunden habe.

[...]
Danke fuer deine vorbildliche Aufarbeitung deines Loesungswegs. Dies sollten sich viele User zum Vorbild nehmen! :THX:



Hier mal noch eine Variante ohne Systemcalls, sondern mit stdio:

Code: Alles auswählen

#include <X11/Xlib.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

static const char program_name[] = "myxd";
static const char fifo[] = "/home/meillo/fifo";
static Display *dpy;
static int screen;
static Window root;
static XEvent ev;

int
main(void) 
{
        char *display_name = NULL;
        char name[BUFSIZ];
        FILE *fp;

        dpy = XOpenDisplay(display_name);
        if (!dpy) {
                fprintf(stderr, "%s:  unable to open display '%s'\n",
                                program_name, XDisplayName(display_name));
                exit(2);
        }
        root = RootWindow(dpy, screen);

        fp = fopen(fifo, "r+");
        while (fgets(name, BUFSIZ, fp)) {
                /* remove trailing newline */
                if (name[strlen(name)-1] == '\n') {
                        name[strlen(name)-1] = '\0';
                }
                XStoreName(dpy, root, name);
                while (XPending(dpy)) {
                        XNextEvent(dpy, &ev);
                }
        }

        perror("fgets");
        fclose(fp);
        XCloseDisplay(dpy);
        exit(1);
}
Scheint ebenfalls alles zu funktionieren. Statt nicht-blockierend zu oeffnen und dann zu warten bis was kommt, oeffne ich einfach normal und lasse fgets(3) blockieren bis Input da ist.

Gibt es Gruende, warum man das nicht so machen, sondern sich low-level mit select(2) und read(2) plagen sollte?



Btw: Auch in deiner Variante gibt es keinen erfolgreichen Exit. Jeder Exit im Code muss von einem Fehler verursacht sein. EOF kann auch nicht vorkommen, wenn du read/write oeffnest (siehe die von dir erwaehnte Doku).
Use ed once in a while!

Benutzeravatar
schorsch_76
Beiträge: 2535
Registriert: 06.11.2007 16:00:42
Lizenz eigener Beiträge: MIT Lizenz

Re: C: fifo lesen

Beitrag von schorsch_76 » 21.08.2018 08:30:09

Meillo hat geschrieben:Gibt es Gruende, warum man das nicht so machen, sondern sich low-level mit select(2) und read(2) plagen sollte?
fgets blockt im read System call. SIGINT, SIGTERM. Hier ist die Default Aktion "Terminate the process". Damit wird der Fifo nicht mehr aufgeräumt und der Handle des Rootwindow. Falls man auf select verzichten will, muss man Signal Handler setzen. Hier sind auch nur eine Hand voll System calls erlaubt (im Handler). Man müsste ein volatile oder atomic flag setzen welches in der Loop abgefragt wird und dann beenden.

Siehe [1] [2]

Lass den Sender in den Fifo mal mehr Daten als BUFSIZE reinschreiben. Dann hat der Buffer kein Ende Zeichen. Du setzt dann kein \0 mehr. strlen() geht über das ende des buffer hinaus und es ist gut möglich das es einen SIGSEV gibt. Falls nicht, gibt's du das dann trotzdem an XStoreName() weiter. XStoreName() weis nichts von deiner Buffergröße....

Du gehtst auch davon aus, dass die Daten atomar rein kommen. Durch verschiedene Faktoren kann der call aber auch die Daten nicht "in einem Rutsch" rüber geben und du hast kein Endezeichen in deinem String. Gleiches Problem wie oben beschrieben.

Durch Scheduling kann es aber auch vorkommen, dass du länger schläfst als die Sender. Es stehen 2 Requests in dem Fifo. Du erkennst aber im Höchstfall nur einen Request. Zum testen, starte das Program, CTRL+Z. Sende mehrere Requests und dann in der Shell mit dem Suspend Programm... fg.

[1] http://man7.org/tlpi/
[2] https://www.amazon.de/Linux-Programming ... 1593272200

Benutzeravatar
schorsch_76
Beiträge: 2535
Registriert: 06.11.2007 16:00:42
Lizenz eigener Beiträge: MIT Lizenz

Re: C: fifo lesen

Beitrag von schorsch_76 » 21.08.2018 09:23:00

Deshalb mach ich solche asyncronen Serverprogramme mit select/poll/epoll/kqueue. Der Signalhandler wird gesetzt und macht mit den Self Pipe Trick [1] das Signal für die select Schleife verfügbar ohne den Prozess abzubrechen. Die Daten die aus dem Request kommen werden in einen Puffer oder in einen Requestparser geschubst und dann verarbeitet. Der Request Parser gibt dann bsp. einen enum von "more_info_needed,done" zurück. In einer Membervariable des Requests ist dann der geparste Request zu finden. Da die Zeichen einzeln in den Parser geschubst werden oder per buffer, sizeofbuffer und der Rückgabewert die benutzen Bytes sind, funktioniert das. Falls weniger Bytes genutzt werden als reingegeben, behandle ich den request und gib dann die anderen Bytes wieder in den Parser.

Damit hat man dann ein sauberes Handling der Signale und Requests und einen sauberen Shutdown ohne SIGSEV ;)

[1] https://lwn.net/Articles/177897/

Benutzeravatar
Meillo
Moderator
Beiträge: 8781
Registriert: 21.06.2005 14:55:06
Wohnort: Balmora
Kontaktdaten:

Re: C: fifo lesen

Beitrag von Meillo » 21.08.2018 09:45:35

schorsch_76 hat geschrieben: ↑ zum Beitrag ↑
21.08.2018 08:30:09
Meillo hat geschrieben:Gibt es Gruende, warum man das nicht so machen, sondern sich low-level mit select(2) und read(2) plagen sollte?
fgets blockt im read System call. SIGINT, SIGTERM. Hier ist die Default Aktion "Terminate the process". Damit wird der Fifo nicht mehr aufgeräumt und der Handle des Rootwindow. Falls man auf select verzichten will, muss man Signal Handler setzen. Hier sind auch nur eine Hand voll System calls erlaubt (im Handler). Man müsste ein volatile oder atomic flag setzen welches in der Loop abgefragt wird und dann beenden.
Was meinst du mit ``FIFO aufraeumen''? Das Einzige was nicht gemacht wird, ist doch ``fclose(fp)'', was sich aber eruebrigt, wenn sich das Programm beendet, weil dann automatisch alle FDs geschlossen werden und aller Speicher freigegeben wird. Ich verstehe nicht was da nicht aufgeraeumt bleiben koennte.

Beim X-Window-Handle fehlt mir das Fach-Know-How. Beibt da etwas ueber das Programmende hinaus noch reserviert?

Lass den Sender in den Fifo mal mehr Daten als BUFSIZE reinschreiben. Dann hat der Buffer kein Ende Zeichen. Du setzt dann kein \0 mehr. strlen() geht über das ende des buffer hinaus und es ist gut möglich das es einen SIGSEV gibt. Falls nicht, gibt's du das dann trotzdem an XStoreName() weiter. XStoreName() weis nichts von deiner Buffergröße....

Du gehtst auch davon aus, dass die Daten atomar rein kommen. Durch verschiedene Faktoren kann der call aber auch die Daten nicht "in einem Rutsch" rüber geben und du hast kein Endezeichen in deinem String. Gleiches Problem wie oben beschrieben.
Sicher?
Manpage fgets(3) hat geschrieben: fgets() reads in at most one less than size characters
from stream and stores them into the buffer pointed to by
s. Reading stops after an EOF or a newline. If a new‐
line is read, it is stored into the buffer. A terminat‐
ing null byte ('\0') is stored after the last character
in the buffer.
Es ist garantiert, dass der von fgets(3) gelesene String immer Null-terminiert ist.

Falls mehr reingeschrieben wird als fgets(3) auf einmal liest, dann verarbeitet es die Eingabe eben haeppchenweise. Das habe ich eben getestet.

Durch Scheduling kann es aber auch vorkommen, dass du länger schläfst als die Sender. Es stehen 2 Requests in dem Fifo. Du erkennst aber im Höchstfall nur einen Request. Zum testen, starte das Program, CTRL+Z. Sende mehrere Requests und dann in der Shell mit dem Suspend Programm... fg.
Ich hab's genau so getestet. Mein Programm hat alle Zeilen in der FIFO gelesen, eine nach der anderen. Natuerlich werden die sofort nacheinander gelesen und so schnell angezeigt, dass man nur die letzte im Rootwindow-Title sieht. Aber das hat ja nichts mit dem Verarbeiten der FIFO selbst zu tun.



Vielleicht mal grundsaetzlich: Es waere schon sehr un-unixmaessig, wenn man FIFOs nicht wie regulaere Dateien lesen koennte. Darum geht's schliesslich in Unix immer: Everything's a file! Keine Sonderbehandlungen fuer spezielle Dateitypen. Das war ein Grunddesignziel von Unix.

FIFOs sind auch alt genug, dass diese Ziele noch umgesetzt sind. (Sockets sind die grosse Ausnahme. Das Warum wird klar, wenn man ihre Entstehung betrachtet: Sie wurden von Personen implementiert, die Unix nicht verstanden hatten. Dass man Sockets auch nach Unix-Art implementieren kann demonstriert Plan 9 ... da waren es dann auch wieder die Unix-Entwickler selbst, die sie implementiert haben.)

Und weiter: Wenn man FIFOs nicht wie regulaere Dateien lesen koennte, dann koennte auch sed(1) beispielsweise keine FIFOs lesen ... ausser es haette Sondercode zum Umgang mit ihnen ... oder man wuerde in sed(1) alles IO auf Systemcall-Ebene durchfuehren, was den ganzen Sinn von stdio zunichte machen wuerde. Wer wuerde denn stdio verwenden, wenn er damit nur regulaere Dateien lesen koennte? Und was ist eigentlich mit Terminals? Die sind schliesslich auch keine regulaeren Dateien sondern Devices. Devices kann man mit stdio problemlos lesen. Sind FIFOs eher wie Devices oder eher wie regulaere Dateien? ...

Das Lesen von FIFOs darf (in Unix!) letzlich gar nicht anders sein als das Lesen regulaerer Dateien. Und wenn das stimmt, wer arbeitet dann freiwillig auf Systemcall-Ebene wenn er stdio nutzen kann?
Use ed once in a while!

Benutzeravatar
schorsch_76
Beiträge: 2535
Registriert: 06.11.2007 16:00:42
Lizenz eigener Beiträge: MIT Lizenz

Re: C: fifo lesen

Beitrag von schorsch_76 » 21.08.2018 10:33:08

fgets kann aber nur die angegebene Puffergröße lesen. Was passiert wenn du BUFSIZE+x Bytes (ohne 0 oder \n) in den FIFO schreibst? ;)

Ich zitiere da aus "The Linux Programming Interface"
Bild

Bild

Benutzeravatar
Meillo
Moderator
Beiträge: 8781
Registriert: 21.06.2005 14:55:06
Wohnort: Balmora
Kontaktdaten:

Re: C: fifo lesen

Beitrag von Meillo » 21.08.2018 11:12:39

schorsch_76 hat geschrieben: ↑ zum Beitrag ↑
21.08.2018 10:33:08
fgets kann aber nur die angegebene Puffergröße lesen.
fgets(3) liest bis zur Puffergroesse oder bis zum ersten Newline, was immer frueher kommt.
Was passiert wenn du BUFSIZE+x Bytes (ohne 0 oder \n) in den FIFO schreibst? ;)
Dann liest fgets(3) sooft es ganze BUFSIZ-Stuecke lesen kann und verarbeitet die. Wenn noch ein Stueck in der FIFO steckt, das weder ein Newline enthaelt noch BUFSIZ gross ist, bleibt das stehen bis irgendwann ein Newline in die FIFO geschrieben wird oder BUFSIZ voll wird. (Es ist folglich notwendig, dass ganze Textzeilen, also mit abschliessendem Newline in die FIFO geschrieben werden.)

Probier's einfach aus! Mach ein printf(3) des Gelesenen in meine Schleife und verwende eine kleine Puffergroesse. Dann kannst du all die Faelle durchspielen.


fgets(3) terminiert seinen String immer!
Use ed once in a while!

Benutzeravatar
schorsch_76
Beiträge: 2535
Registriert: 06.11.2007 16:00:42
Lizenz eigener Beiträge: MIT Lizenz

Re: C: fifo lesen

Beitrag von schorsch_76 » 21.08.2018 11:50:23

@meillo: Ich wird es heut abend mal ausprobieren ;)

Zum x window Handle: Da würde ich schon schauen das ich das auf jeden Fall schließen. Die zugrunde liegende IPC zwischen dir und dem XServer hängt (so wie ich das verstehe) dazwischen. Da X eine riesige Codebase ist, würde ich mich als Programmierer schon möglichst daran halten alle Resourcen wieder zurück zu geben, da ich nicht 100% sagen kann was dort passiert (oder auch nicht passiert, falls ich es nicht mache).

Im Zweifel könnten dem xorg Prozess die File Descriptoren ausgehen.

https://www.x.org/archive/X11R7.5/doc/m ... lay.3.html
The XCloseDisplay function closes the connection to the X server for the display specified in the Display structure and destroys all windows, resource IDs (Window, Font, Pixmap, Colormap, Cursor, and GContext), or other resources that the client has created on this display, unless the close-down mode of the resource has been changed (see XSetCloseDownMode). Therefore, these windows, resource IDs, and other resources should never be referenced again or an error will be generated. Before exiting, you should call XCloseDisplay explicitly so that any pending errors are reported as XCloseDisplay performs a final XSync operation.

XCloseDisplay can generate a BadGC error.

Benutzeravatar
schorsch_76
Beiträge: 2535
Registriert: 06.11.2007 16:00:42
Lizenz eigener Beiträge: MIT Lizenz

Re: C: fifo lesen

Beitrag von schorsch_76 » 21.08.2018 20:45:32

So, das erste was ich direkt nach dem compilieren bekomme....

Code: Alles auswählen

georg@M4700:~/Dokumente/Entwicklung/misc/DfDeX11$ gcc set-title.c -lX11  -o set-title                                                                                                                                                                                          
georg@M4700:~/Dokumente/Entwicklung/misc/DfDeX11$ ./set-title                                                                                                                                                                                                                  
Speicherzugriffsfehler                                                                                                                                                                                                                                                         
georg@M4700:~/Dokumente/Entwicklung/misc/DfDeX11$ gcc set-title.c -O0 -g -lX11  -o set-title                                                                                                                                                                                   
georg@M4700:~/Dokumente/Entwicklung/misc/DfDeX11$ ./set-title 
Speicherzugriffsfehler                                                                                                                                                                                                                                                         
georg@M4700:~/Dokumente/Entwicklung/misc/DfDeX11$ gdb ./set-title                                                                                                                                                                                                              
GNU gdb (Debian 7.12-6) 7.12.0.20161007-git                                                                                                                                                                                                                                    
Copyright (C) 2016 Free Software Foundation, Inc.                                                                                                                                                                                                                              
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>                                                                                                                                                                                                  
This is free software: you are free to change and redistribute it.                                                                                                                                                                                                             
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"                                                                                                                                                                                                     
and "show warranty" for details.                                                                                                                                                                                                                                               
This GDB was configured as "x86_64-linux-gnu".                                                                                                                                                                                                                                 
Type "show configuration" for configuration details.                                                                                                                                                                                                                           
For bug reporting instructions, please see:                                                                                                                                                                                                                                    
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from ./set-title...done.
(gdb) start
Temporary breakpoint 1 at 0xb4b: file set-title.c, line 16.
Starting program: /home/georg/Dokumente/Entwicklung/misc/DfDeX11/set-title 
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".

Temporary breakpoint 1, main () at set-title.c:16
16              char *display_name = NULL;
(gdb) c
Continuing.

Program received signal SIGSEGV, Segmentation fault.
_IO_fgets (buf=0x7fffffffbfa0 "", n=8192, fp=0x0) at iofgets.c:47
47      iofgets.c: Datei oder Verzeichnis nicht gefunden.
(gdb) bt
#0  _IO_fgets (buf=0x7fffffffbfa0 "", n=8192, fp=0x0) at iofgets.c:47
#1  0x0000555555554c92 in main () at set-title.c:29
(gdb) l
42      in iofgets.c
(gdb) ls
Undefined command: "ls".  Try "help".
(gdb) bt
#0  _IO_fgets (buf=0x7fffffffbfa0 "", n=8192, fp=0x0) at iofgets.c:47
#1  0x0000555555554c92 in main () at set-title.c:29
(gdb) f 1
#1  0x0000555555554c92 in main () at set-title.c:29
29              while (fgets(name, BUFSIZ, fp)) {


fifo konnte nicht geöffnet werden, da er noch nicht existiert. Das sollte durch dieses Programm erledigt werden ;) Auch wird nicht geprüft ob der fifo geöffnet wurde. Nachdem ich den Pfad angepasst und den Fifo erstellt habe, hab ich mal die Funktion getestet.

Mit einem Programm hab ich das dann mal für 3h durchgetestet und es hat keinen Crash gegeben (nur wenn der Fifo nicht da ist). :D

SendRnd (da nopaste kaputt ist gerade)

Code: Alles auswählen

g++ --std=c++14 SendRnd.cpp -l boost_program_options -o sendrnd
./sendrnd --of /home/georg/fifo --alphabet=random --runs=1000000 --min_length=1000 --max_length=1280000


#include <array>
#include <fstream>
#include <iostream>
#include <random>
#include <string>

#include <boost/program_options.hpp>

namespace po = boost::program_options;

enum class alphabet_t
{
        alpha,
        alnum,
        print,
        rnd
};

int run(std::ostream &os, const alphabet_t alphabet, const int min_length,
                const int max_length, const int runs)
{
        // clang-format off
        // - 1 because 0 based array index
        std::uniform_int_distribution<> dis_alpha       (0,     2 * 26          - 1);
        std::uniform_int_distribution<> dis_alnum       (0,     2 * 26 + 10 - 1);
        std::uniform_int_distribution<> dis_print       (0, 95                  - 1);
        std::uniform_int_distribution<> dis_rnd         (0, 256                 - 1);

        std::array<char, 95> int2char =
        {
                'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
                'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
                'q', 'r', 's', 't', 'u', 'v', 'w', 'x',
                'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F',

                'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N',
                'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V',
                'W', 'X', 'Y', 'Z', '0', '1', '2', '3',
                '4', '5', '6', '7', '8', '9', '!', '"',

                '#', '$', '%', '&', '(', ')', '*', '+',
                ',', '-', '.', '/', ':', ';', '<', '=',
                '>', '?', '@', '[', '\\', ']', '^', '_',
                '`', '{', '|', '}', '~', '\'', ' '
        };
        // clang-format on

        std::random_device rd;
        std::mt19937_64 gen(rd());

        std::uniform_int_distribution<> dis_length(min_length, max_length);

        for (int i = 0; i < runs; ++i)
        {
                const int len = dis_length(gen);

                std::string buffer;
                buffer.reserve(len);

                for (int l = 0; l < len; ++l)
                {
                        switch (alphabet)
                        {
                                default:
                                case alphabet_t::alpha:
                                        buffer += int2char[dis_alpha(gen)];
                                        break;
                                case alphabet_t::alnum:
                                        buffer += int2char[dis_alnum(gen)];
                                        break;
                                case alphabet_t::print:
                                        buffer += int2char[dis_print(gen)];
                                        break;
                                case alphabet_t::rnd:
                                        buffer += char(dis_rnd(gen));
                                        break;
                        }
                }

                // write out buffer and the expected \n\0 ending
                os.write(buffer.data(), buffer.size());
                os.write("\n", 1);
        }
        return EXIT_SUCCESS;
}

int main(int argc, char *argv[])
{
        try
        {
                po::options_description desc("SendRnd options");

                // clang-format off
                desc.add_options()
                        ("help",                                                                        "produce help message")
                        ("alphabet",    po::value< std::string >(),     "alpha (default)|alnum|print|random (may contain additional newlines and zeros)")
                        ("min_length",  po::value< int >(),                     "minimum length between newline (Default 128k)")
                        ("max_length",  po::value< int >(),                     "maximum length between newline (Default 1024k)")
                        ("runs",                po::value< int >(),                     "number of runs")
                        ("of",                  po::value< std::string >(),     "output file (use - for stdout = default)")
                        ;
                // clang-format on

                po::variables_map vm;
                po::store(po::parse_command_line(argc, argv, desc), vm);
                po::notify(vm);

                if (vm.count("help"))
                {
                        std::cout << desc << "\n";
                        return EXIT_FAILURE;
                }

                // this values are now needed
                alphabet_t alphabeth = alphabet_t::alpha;
                if (vm.count("alphabet"))
                {
                        std::string value = vm["alphabet"].as<std::string>();
                        if (value == "alpha" || value == "default")
                        {
                                alphabeth = alphabet_t::alpha;
                        }
                        else if (value == "alnum")
                        {
                                alphabeth = alphabet_t::alnum;
                        }
                        else if (value == "print")
                        {
                                alphabeth = alphabet_t::print;
                        }
                        else if (value == "random")
                        {
                                alphabeth = alphabet_t::rnd;
                        }
                        else
                        {
                                std::cerr << "Invalid alphabet mode" << std::endl;
                                return EXIT_FAILURE;
                        }
                }

                // length of the output
                int min_length = 128 * 1024;
                if (vm.count("min_length"))
                {
                        min_length = vm["min_length"].as<int>();
                        if (min_length < 1)
                        {
                                std::cerr << "min_length must be >=1" << std::endl;
                                return EXIT_FAILURE;
                        }
                }

                int max_length = 1024 * 1024;
                if (vm.count("max_length"))
                {
                        max_length = vm["max_length"].as<int>();
                        if (max_length < 1)
                        {
                                std::cerr << "max_length must be >=1" << std::endl;
                                return EXIT_FAILURE;
                        }
                }

                if (max_length < min_length)
                {
                        std::cerr << "min_length must be smaller or equal than max_length"
                                          << std::endl;
                        return EXIT_FAILURE;
                }
                if (min_length < 1)
                {
                        std::cerr << "min_length must >= 1" << std::endl;
                        return EXIT_FAILURE;
                }
                if (max_length < 1)
                {
                        std::cerr << "max_length must >= 1" << std::endl;
                        return EXIT_FAILURE;
                }

                // runs
                int runs = -1;
                if (!vm.count("runs"))
                {
                        std::cout << desc << "\n";
                        return EXIT_FAILURE;
                }
                else
                {
                        runs = vm["runs"].as<int>();
                }
                if (runs < 1)
                {
                        std::cerr << "runs must be >= 1" << std::endl;
                        return EXIT_FAILURE;
                }

                // out
                std::string fname = "-";
                if (vm.count("of"))
                {
                        fname = vm["of"].as<std::string>();
                }
                if (fname != "-")
                {
                        std::ofstream ofs(fname.c_str(), std::ios_base::binary);
                        if (!ofs.is_open())
                        {
                                std::cerr << "Could not open output file: " << fname
                                                  << std::endl;
                                return EXIT_FAILURE;
                        }
                        return run(ofs, alphabeth, min_length, max_length, runs);
                }
                else
                {
                        return run(std::cout, alphabeth, min_length, max_length, runs);
                }
        }
        catch (const std::exception &ex)
        {
                std::cerr << ex.what() << std::endl;
        }

        return EXIT_FAILURE;
}


Benutzeravatar
Meillo
Moderator
Beiträge: 8781
Registriert: 21.06.2005 14:55:06
Wohnort: Balmora
Kontaktdaten:

Re: C: fifo lesen

Beitrag von Meillo » 21.08.2018 21:46:37

schorsch_76 hat geschrieben: ↑ zum Beitrag ↑
21.08.2018 20:45:32
So, das erste was ich direkt nach dem compilieren bekomme....

Code: Alles auswählen

georg@M4700:~/Dokumente/Entwicklung/misc/DfDeX11$ gcc set-title.c -lX11  -o set-title                                                                                                                                                                                          
georg@M4700:~/Dokumente/Entwicklung/misc/DfDeX11$ ./set-title                                                                                                                                                                                                                  
Speicherzugriffsfehler                                                                                                                                                                                                                                                         
[...]
fifo konnte nicht geöffnet werden, da er noch nicht existiert. Das sollte durch dieses Programm erledigt werden ;) Auch wird nicht geprüft ob der fifo geöffnet wurde. Nachdem ich den Pfad angepasst und den Fifo erstellt habe, hab ich mal die Funktion getestet.
Da hast du ganz recht. Da wurde schlampig gearbeitet ... schon im Originalprogramm :-P ... ich hab's dann allerdings auch nicht besser gemacht ... war ganz auf Syscalls vs. Stdio fixiert ... hab ja noch nicht mal das ``BUFSIZ'' im fgets()-Aufruf durch ``sizeof(name)'' ersetzt, was man unbedingt tun sollte!

Lass uns diese Korrekturen und Verbesserungen in einem naechsten Schritt machen! Heute nicht mehr, aber ich liefere gerne noch eine Verbesserung meiner Version. Ihr koennt gerne solange schonmal damit anfangen.

Mit einem Programm hab ich das dann mal für 3h durchgetestet und es hat keinen Crash gegeben (nur wenn der Fifo nicht da ist). :D
Coole Sache ... deine Testreihe! :-)


Jetzt waere gut, wenn noch jemand mit Erfahrung und Wissen bestaetigen koennte, dass man FIFOs sehr wohl mit Stdio lesen und schreiben kann. Denn wenn Zufallseingaben kein Fehler erzeugen, heisst das ja noch nicht zwangslaeufig, dass der Code korrekt ist.

Bislang stehe ich noch alleine da, mit der Aussage, dass man auch FIFOs mit Stdio lesen und schreiben sollte -- kann und sollte! Ein paar mehr Wortmeldungen dazu wuerden gut tun.
Use ed once in a while!

RobertDebiannutzer
Beiträge: 385
Registriert: 16.06.2017 09:52:36

Re: C: fifo lesen

Beitrag von RobertDebiannutzer » 21.08.2018 22:28:20

Ui, hier war ja heute richtig viel los! 8O
Vielen Dank für euren input und eure Mühe. Ich verstehe noch nicht alles, aber versuche mal, auf ein paar Dinge einzugehen:
Meillo hat geschrieben: ↑ zum Beitrag ↑
20.08.2018 23:55:15
Gibt es Gruende, warum man das nicht so machen, sondern sich low-level mit select(2) und read(2) plagen sollte?
Also ich tendierte zu low-level, weil ich mir dachte, dass das schneller ist und keine Nachteile hat. Mir schien das irgendwie mehr "Unix" und "suckless" zu sein, low-level zu bevorzugen. Aber Dein Code ist natürlich einfacher zu lesen.
Der entscheidende Unterschied zwischen Deinem Code und meinem früheren Code, der nicht funktioniert, ist - wenn ich das richtig verstehe -, dass auch hier die fifo rw geöffnet wird, richtig?
Meillo hat geschrieben: ↑ zum Beitrag ↑
20.08.2018 23:55:15
Auch in deiner Variante gibt es keinen erfolgreichen Exit. Jeder Exit im Code muss von einem Fehler verursacht sein.
Das stimmt, das ist doof.
Ich könnte das zum Einen mit dem aufrufenden Parent-Wrapper-Script lösen, welches im Moment so aussieht:

Code: Alles auswählen

#!/bin/sh

if [ -e /tmp/dwmblocks_fifo_intern ]; then
	if [ ! -p /tmp/dwmblocks_fifo_intern ]; then
		echo "Error: file /tmp/dwmblocks_fifo_intern exists, but is not my fifo. Please check!"
		exit 1
	fi
else
	mkfifo /tmp/dwmblocks_fifo_intern
fi

/opt/scripts/bin/myxd &
myxd_pid=$!

while true; do
	if [ -e /tmp/dwmblocks ]; then
		if [ ! -p /tmp/dwmblocks ]; then
			echo "Error: file /tmp/dwmblocks exists, but is not my fifo. Please check!"
			exit 1
		fi
	else
		mkfifo /tmp/dwmblocks
	fi
	/opt/scripts/barp.sh
done

kill $!
Wie ihr seht, prüfe ich meine fifos in diesem Wrapper-Script. Wenn die fifo für das hier diskutierte Programm, also /tmp/dwmblocks_fifo_intern (die fifo in $HOME ist nur zum Testen) nicht da ist, wird gar nichts gestartet. Dann braucht man ja auch keine Überprüfung am Anfang des Programmes, oder? Wobei, schaden kanns nicht... Was man tatsächlich prüfen müsste, ist, ob die fifo geöffnet wurde. Habe ich mal versucht - s. u.
Statt kill könnte ich hier einen spezifischen kill-string nutzen. Nachteil: Bei jedem Durchlauf muss der string, der von der fifo erhalten wurde, auf diesen spezifischen string überprüft werden. Nicht so elegant...
@schorch_76 hat jetzt dafür eine Lösung vorgestellt, aber ich verstehe leider nicht, wie das jetzt genau ablaufen soll. Könnte man nicht einfach eine Funktion implementieren, die ausgeführt wird, wenn das Programm ein bestimmtes Signal erhält?
Wobei: Was ist mit
Meillo hat geschrieben: ↑ zum Beitrag ↑
21.08.2018 09:45:35
Das Einzige was nicht gemacht wird, ist doch ``fclose(fp)'', was sich aber eruebrigt, wenn sich das Programm beendet, weil dann automatisch alle FDs geschlossen werden und aller Speicher freigegeben wird. Ich verstehe nicht was da nicht aufgeraeumt bleiben koennte.
?

Was auf jeden Fall ein Problem sein könnte, ist, dass die Verbindung zu X nicht richtig geschlossen wird. Deswegen könnte man dann vielleicht doch das Schließen des Programmes genau definieren, statt es einfach abzukillen?
schorsch_76 hat geschrieben: ↑ zum Beitrag ↑
21.08.2018 11:50:23
Zum x window Handle: Da würde ich schon schauen das ich das auf jeden Fall schließen. Die zugrunde liegende IPC zwischen dir und dem XServer hängt (so wie ich das verstehe) dazwischen.
Was genau meinst Du? Ich setze den Name des Root-Windows, dann bereite ich den nächsten Event vor. Es kann ja nicht sein, dass jedes Mal die Verbindung zu X geöffnet, geprüft und geschlossen werden muss. Das machen ja auch Window-Manager nicht...

BTW: Das mit "printf("EOF\\n");" habe ich aus @bluestars Link. Ich werde das nochmal nachschauen...
Mein Programm mit sizeof und open-check:

Code: Alles auswählen

#include <X11/Xlib.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

static const char program_name[] = "myxd";
static Display *dpy;
static int screen;
static Window root;
static XEvent ev;

int
main(void) 
{
	char *display_name = NULL;
	char name[BUFSIZ+1];
	int fd;

	dpy = XOpenDisplay(display_name);
	if (!dpy) {
		fprintf(stderr, "%s:  unable to open display '%s'\n",
				program_name, XDisplayName (display_name));
		exit(2);
	}
	root = RootWindow(dpy, screen);

	/* fd = open("/tmp/dwmblocks_fifo_intern", O_RDWR | O_NONBLOCK); */
	fd = open("/home/user/fifo", O_RDWR | O_NONBLOCK);
	if (fd < 0) {
		perror("open");
		exit(1);
	}

	fd_set rfds;
	int rc;

	FD_ZERO(&rfds);
	FD_SET(fd, &rfds);

	for (;;) {
		rc = select(fd + 1, &rfds, NULL, NULL, NULL);
		if (rc > 0) {
			memset (name, 0, sizeof(name));
			ssize_t got = read(fd, name, sizeof(name));
			if (got < 0) {
				perror("read");
				break;
			} else if (got == 0) {
				printf("EOF\\n");
				break;
			}
			else {
				/* remove trailing newline */
				if (name[strlen(name)-1] == '\n')
					name[strlen(name)-1] = '\0';
				XStoreName(dpy, root, name);
				while (XPending(dpy))
					XNextEvent(dpy, &ev);
			}
		}
	}
	close(fd);
	XCloseDisplay(dpy);

	exit(1);
}

Benutzeravatar
Meillo
Moderator
Beiträge: 8781
Registriert: 21.06.2005 14:55:06
Wohnort: Balmora
Kontaktdaten:

Re: C: fifo lesen

Beitrag von Meillo » 22.08.2018 06:57:36

RobertDebiannutzer hat geschrieben: ↑ zum Beitrag ↑
21.08.2018 22:28:20
Meillo hat geschrieben: ↑ zum Beitrag ↑
20.08.2018 23:55:15
Gibt es Gruende, warum man das nicht so machen, sondern sich low-level mit select(2) und read(2) plagen sollte?
Also ich tendierte zu low-level, weil ich mir dachte, dass das schneller ist und keine Nachteile hat. Mir schien das irgendwie mehr "Unix" und "suckless" zu sein, low-level zu bevorzugen. Aber Dein Code ist natürlich einfacher zu lesen.
Erschreckend, was fuer ein verzerrtes Verstaendnis von Unix und Suckless da angekommen ist. :shock:

Gute Lesbarkeit von Code ist eines der wichtigen Ziele von Unix und Suckless (... halt unter Erwartung eines hohen Kenntnisstandes beim Code-Leser).


Low-Level-Code ist mehr Code, was klar dem Ziel von Suckless entgegen steht, die ja besonders explizit nach moeglichst wenig Code streben.

Low-Level-Code besteht zu grossem Teil aus Fehlerbehandlung, was der Lesbarkeit schadet.

Low-Level-Code ist fehleranfaelliger.

Im Zeitalter optimierender Compiler und optimierter Libraries ist es gar nicht unwahrscheinlich, dass eigener Low-Level-Code langsamer ist als wenn man Standardbibliotheken nutzt.


Das ganze Thema ist letztlich schon komplexer und hat viele Zusammenhaenge und Abwaegungen, aber als Faustregel kannst du nehmen: Wenn es weniger Code ist und einfacher zu lesen, dann ist es besser.

Wenn du mehr darueber lernen willst, wie man guten Code schreibt, dann lies ``The Practice of Programming'' von Kernighan und Pike.
Use ed once in a while!

Benutzeravatar
schorsch_76
Beiträge: 2535
Registriert: 06.11.2007 16:00:42
Lizenz eigener Beiträge: MIT Lizenz

Re: C: fifo lesen

Beitrag von schorsch_76 » 22.08.2018 07:57:07

RobertDebiannutzer hat geschrieben: Was genau meinst Du? Ich setze den Name des Root-Windows, dann bereite ich den nächsten Event vor. Es kann ja nicht sein, dass jedes Mal die Verbindung zu X geöffnet, geprüft und geschlossen werden muss. Das machen ja auch Window-Manager nicht...
Dein XOpenDisplay baut eine Verbindung zum X window Manager auf. Hier wird eine Form von IPC verwendet. Da ich nicht weis wie im XServer bzw Client das gehandhabt wird wenn ein offenes Display nicht geschlossen wird, würde ich auf jeden Fall dafür sorgen das beim beenden des Programms ein XCloseDisplay erfolgt.

Jeder Prozess hat ein per ulimit gesetztes Limit was offene File Descriptoren angeht. Das sind, Sockets, Fifos etc pp. Wird das überschritten, verweigert der Kernel dem Prozess neue FD.

Zu Low Level: Ich schreibe auf mehr auf Low Level Ebene Code und halte mich da eben an den POSIX Standard. Hierfür habe ich eben das Buch "The Linux Programming Interface" gekauft. Da hab ich mir je nach Bedarf das ein oder andere Kapitel reingezogen.

@RobertDebiannutzer: Die select schleife ist so exakt wie sie sein soll :)
Zuletzt geändert von schorsch_76 am 22.08.2018 08:52:53, insgesamt 1-mal geändert.

Antworten