UNIX daemon kit és tutorial, C-ben és Pascalban

Nem tudom hány embernek és mennyire lesz ez hasznos, de gondoltam írok egy tutorialt arról, hogy hogyan kell összerakni egy UNIX daemont, meg adok hozzá egy kitet is, hátha jól jön valakinek...

Azt feltételezem mindenki tudja, hogy mi a daemon, de azt, hogy ez a gyakorlatban - értsd: programozás szempontjából - hogyan is működik, azt már lehet, hogy kevesebben. Nos, a daemonok kutyaközönséges process-ek, amiket közvetlenül az init futtat, terminálkapcsolat - alapesetben - nincs, a user a különféle signalokon keresztül adhat parancsot nekik. (Legalábbis általában, természetesen lehet egyéb megközelítéssel is, de így szokták.)
Ezt kétféleképpen lehet elérni: vagy tényleg az init indítja el őket, vagy indításkor a process a fork() meghívásával készít egy másodpéldányt magából, majd kiszáll, így az init megörökli az elárvult másodpéldányt.

Ezen felül persze még pár dolgot meg kell csinálni, amiken most szisztematikusan végig fogunk menni. Kétféle kóddal illusztrálom a folyamatot, egy C-ben és egy Pascalban írttal - bár nem hiszem, hogy túl sokan mérgeznék itt magukat Pascallal rajtam kívül, de ha mégis, hát kutyaharapást macskával... A C-s példákhoz írom az include-okat is, a Pascal-os példákban egyelőre csak a "baseunix" unit kell, hogy benne legyen az uses-ben.

Mint azt előbb kitárgyaltuk, a "daemonizált" process-t a fork() segítségével hozzuk létre, aztán kiszállunk. A fork() a parent process-nek a child process process ID-jét adja vissza sikeres forkolás esetén, amúgy meg egy jó nagy -1-et, a child process-nek pedig 0-át, ennek megfelelően a 0-át nem kell lekezelni, csak az attól eltérő eredményeket.

C-ben:

#include <unistd.h>
...
pid_t pid = fork();
if (pid == -1)
{
	// hibakezelés
}
if (pid != 0)
{
	exit(0);
}

Pascalban:

var pid: tpid;
...

pid := fpfork;
if (pid = -1) then
begin
	// hibakezelés
end;
if (pid <> 0) then
begin
	halt(0);
end;

Ezzel létrehoztuk programunknak a PID1 (init) által futtatott másodpéldányát. Igen ám, csak az init alapesetben a megöröklött child process-eket egy laza SIGHUP-pal az örök folyamatmezőkre küldi, lévén a parent volt a session leader és ha a leader kiszáll, a session maga is befejeződik és az összes foreground tag megy a levesbe (hacsak nem ignorálják a SIGHUP-ot). Ezt preventálandó, létrehozunk egy új session-t a setsid() segítségével, aminek a leaderje már a child process lesz és ezzel, hogy leváltunk a régi munkamenetről, leváltunk a terminálról és visszakaptuk a promptot: a folyamat a háttérbe került. C-ben setsid(), Pascalban fpsetsid, mindkét esetben, ha -1-et kapunk, akkor történt hiba.

Ezzel tulajdonképpen már kész is az élő daemon process, legalábbis abban az értelemben, hogy a kötelező köröket lefutottuk: sikerrel leválasztottuk a programot a terminálról és most már a háttérben fut a PID1 égisze alatt, de azért még hátra van egy-két tisztázandó opcionális dolog.

De mielőtt ezekbe belemennénk, tisztázzuk le az esedékes double-fork kérdését is. Számos manual és tutorial ezen a ponton előír egy második fork() meghívást - plusz a SIGCHLD és SIGHUP signalok ignorálását - is, amit azzal indokolnak, hogy egyfelől így lehet preventálni a zombie-process létrejöttét, másfelől pedig, hogy a session leader-ré vált child process ne "szerezhessen" ismét vezérlőterminált, hanem helyette a már nem-session leader grandchild fusson tovább, ami nem is szerezhet.
Az első indok egyáltalán nem igaz, lévén a zombie-process egy olyan befejeződött child process entry-je a process táblában, aminek a parentje azelőtt kiszállt, hogy a child process visszatérési kódját kiolvasta volna, márpedig a mi child processünk nem fejezte be a futását, a szülője pedig már az init és nem a korábban kiszállt parent process, tehát semmi esetre sem lesz zombi a daemonból, feltéve, hogy a forkoló parent kiszállt.
A második indok már lehetne igaz, mert a session leader valóban megnyithat egy új terminal device file-t, ami ha még nincs session-höz rendelve, akkor - a rendszer implementációjától függően - vezérlőterminállá válhat, csakhogy ehhez egyfelől az kell, hogy szándékosan nyissunk egyet, mert nyilván nem magától nyitogat terminal device-okat a daemon, másfelől meg még ha szükség is van egyre, akkor is preventálni lehet a vezérlőterminállá válást, ha a descriptort O_NOCTTY opcióval nyitjuk meg.
Ezeknek megfelelően a double-forkra nincs szükség.

És akkor most térjünk vissza a daemonizációs folyamat még hátralévő elemeire.

Az egyik az az, hogy a child megörökölte az umask-ot a parent-től, márpedig ez egy daemon és azt szeretnénk, hogy ha kiadunk egy permission mask-ot valami parancsnak, akkor az is lenne a beállított mask. Ezt azzal érhetjük el, ha az umask-ot kinullázzuk. Ezt C-ben - a <sys/stat.h> include-olása után - az umask(0), Pascalban pedig az fpumask(0) paranccsal érhetjük el. (A visszatérési érték itt a korábbi umask, hibakezelésre nincs szükség.)

A másik, hogy egy daemon azért nem futhat akármilyen munkakönyvtárban (pl. ahonnan elindították), mert azt lecsatolhatják, törölhetik, stb. Ennek megfelelően a munkakönyvtárat át szokták állítani a "/"-re, de ez nincs kőbe vésve; ha egy daemonnak van egy direkt neki fenntartott path-ja, akkor azt is használhatja. Átállítása a chdir()-rel történik. C-ben pl. chdir("/"), Pascalban fpchdir('/'), lekezelendő hiba itt is a -1-es visszatérő érték.

A harmadik, hogy egy daemon nem szokott a terminálra firkálni, vagy onnan olvasgatni. Ez sincs kőbevésve, mert pl. develop/debug közben minden további nélkül, de alapvetően egy daemon a stdin, stdout és stderr file descriptorokat (értsd: 0, 1, 2) át szokta irányítani a /dev/null felé. Vagy: egy tetszőleges fájl felé (legalábbis az outputokat), ha valaki így akar loggolni. (Erre még később visszatérünk, mármint a loggoláshoz.)
Az átirányítást a dup2() parancs végzi, de ehhez előbb nyitnunk is kell egy új file descriptort az open() segítségével.

C-ben:

#include <fcntl.h>
...
int fd = open("/dev/null", O_RDWR);
if (fd == -1)
{
	// hibakezelés
}
dup2(fd, STDIN_FILENO);
dup2(fd, STDOUT_FILENO);
dup2(fd, STDERR_FILENO);

Pascalban:

var fd: cint;
...
fd := fpopen('/dev/null', O_RdWr);
if (fd = -1) then
begin
	// hibakezelés
end;
fpdup2(fd, 0);
fpdup2(fd, 1);
fpdup2(fd, 2);

A negyedik, hogy ha ezt a process-t signalokkal vezérelni akarjuk, akkor nem ártana mondjuk rendszerszinten tudni, hogy mi a futó daemon process ID-je. Ezt pl. úgy lehet megoldani, hogy kiírjuk egy fájlba. Erre a célra a /var/run/ könyvtár van fenntartva: ide lehet kiírni a getpid(), ill. Pascal alatt a fpgetpid eredményét. (Ezt természetesen a daemonizáció legvégén kell csinálni, amikor már mindennel megvagyunk.)

Ötödik, hogy nem ártana, ha a daemonból csak egy futna egyszerre. Ezt az egyel feljebbi részben tulajdonképpen már megoldottuk, csak azt kell lekérdezni, hogy létezik-e a fájl és ha igen, akkor kiszállni daemonizáció helyett. (Ezt értelemszerűen a legelején kell megcsinálni, minden egyéb előtt.) Illetve ez az alap, de lehet ennél jobban is csinálni, mert pl. ha - ne adja az ég - a daemon összedőlt valamiért, mint teszi azt a windóz nap-mint-nap fél disznó az ólban, akkor a PID file ott marad és a daemon nem fog elindulni többet. Ennek megfelelően ki kell olvasni belőle a letárolt PID-et és lekérdezni, hogy fut-e még a process. Ezt a legegyszerűbben úgy lehet abszolválni, ha küldünk neki egy 0-ás signalt (ld. később), amit maga a process nem fog megkapni és lekezelni, viszont az OS igen és ha a visszatérési érték nem 0, akkor a process már nem létezik, lehet törölni a fájlt és daemonizálni.

A hatodik, hogy a dupla daemonizációt úgy lehet levédeni, hogy ellenőrizzük, hogy a parent process process ID-je 1-e. Lekérdezése C-ben getppid(), Pascalban fpgetppid; a visszatérési érték mindkét esetben 1-nél nagyobb kell, hogy legyen; ha 1, akkor a parent az init, tehát ki kell szállni. (Nyilván ezt is az elején kell végrehajtani.)

Nagyjából ennyi lenne, amennyit egy process daemonizálásához meg kell csinálni.

A nehezén már túl vagyunk, most jön a neheze, egy háromsoros levél megírása outlookban, mielőtt elfogy a .NET miatt a memória, vagy az idő a következő erőszakos frissítésig a vezérlés.

Mint azt az elején sikerült körülírni: a vezérlés - általában - signalokkal történik. A linkelt wikicikk megemlíti, hogy a signalok az interruptokra hasonlítanak; a formális lekezelés szempontjából ez mindenképpen igaz, mert van egy jelünk, meg van egy vektorunk (függvényünk), amire a CPU a jel megkapásakor odaugrik és végrehajtja. (Mármint amikor a process kapja meg a jelet, nem a CPU.)
A signalra triggerelődő függvényeket (handlereket), vagy a signalok figyelmen kívül hagyását, default handlerre állítását (SIG_IGN, SIG_DFL) a signal() függvénnyel lehet beállítani, legalábbis elméletben. A gyakorlatban az implementáció UNIX-onként eltér, sőt Linux esetén még disztribúciónként is, ennek megfelelően nem kimondottan ajánlott használni, ha cross-platform megoldást akarunk: az igazi cross-platform és POSIX-compliant megoldás a sigaction() használata.
Ennek a használata az azonos nevű struct-ot használva történik, amiben egy signal handlerének beállításához először be kell állítani a sa_handler-t a callback függvény címére, kinullázni a sa_mask-ot a sigemptyset() segítségével (ez a maszk jelöli meg, hogy mely signalokat blokkolja a rendszer miközben a handler fut), kinullázni a sa_flags opcióit (egyik sem kell, ld. a manualt) és végül csak Linux alatt NULL-ra állítani a POSIX szabványban nem létező - és ebben az esetben amúgy sem használt - sa_restorer-t.

Azaz, hogy ne csak rizsa, hanem kód is legyen; az implementáció C-ben:

#include <stdbool.h>
#include <signal.h>

...

bool assign_signal(int signal, void (*callback)(int))
{
	struct sigaction act;

	act.sa_handler = callback;
	sigemptyset(&act.sa_mask);
	act.sa_flags = 0;
#ifdef __linux__
	act.sa_restorer = NULL;
#endif
	return sigaction(signal, &act, NULL) != 0;
}

és Pascalban:

type
	unix_signal = cint;
	unix_signal_callback = procedure (signal: unix_signal); cdecl;
	punix_signal_callback = ^unix_signal_callback;

...

function assign_signal(signal: unix_signal; callback: punix_signal_callback): boolean;
var act: sigactionrec;
begin
	act.sa_handler := sigactionhandler(callback);
	fpsigemptyset(act.sa_mask);
	act.sa_flags := 0;
{$ifdef linux}
	act.sa_restorer := nil;
{$endif}
	result := fpsigaction(signal, @act, nil) <> 0;
end;

És akkor térjünk rá, hogy mely signalokkal van is dolgunk a gyakorlatban.
A legtöbb idevágó leírás felsorolja a SIGTTIN, SIGTTOU és SIGTSTP signalok SIG_IGN-re állítását. Az első kettőt egy process akkor kapja, ha a háttérből írni vagy olvasni akarja a vezérlőterminált, a harmadik pedig akkor keletkezik, ha a vezérlőterminálon a ^Z-re tenyerel a felhasználó, hogy megállítsa a process-t. Mivel mint azt fentebb tisztáztuk, egy daemonnak nincs vezérlőterminálja, ezért ezeket a signalokat normál esetben soha nem kaphatja meg a process. (Nyilván lehet küldeni neki ilyet kill-lel, de azzal bármit lehet...)
Az a két signal, amit ténylegesen használni szoktak ilyenkor, az a SIGHUP (a konfigok újratöltésére) és a SIGTERM (a daemon szabályszerű megállítására). Persze lehet mást, de ezek a konvencionális signalok.

Ennek megfelelően az assign C-ben:

void handle_signal(int sig)
{
	if (sig == SIGTERM)
	{
		// a daemon megállítása
	}
	else
	{
		// a daemon konfigjainak újratöltése
	}
}

...

assign_signal(SIGTERM, &handle_signal);
assign_signal(SIGHUP, &handle_signal);

És Pascalban:

procedure handle_signal(sig: unix_signal); cdecl;
begin
	if (sig = SIGTERM) then
	begin
		// a daemon megállítása
	end
	else
	begin
		// a daemon konfigjainak újratöltése
	end;
end;

...

assign_signal(SIGTERM, punix_signal_callback(@handle_signal));
assign_signal(SIGHUP, punix_signal_callback(@handle_signal));

Értelemszerűen lehet akár külön handlert is adni nekik, meg lehet egyéb signalokat is használni, pl. SIGUSR1, SIGUSR2, stb...

Ez volna a fogadó része a dolognak, most jön a küldő. Signalt küldeni a kill() paranccsal lehet, illetve Pascalban fpkill. A használat pedig úgy történik, hogy a program ugye kap(hat) argumentumokat (C-ben argv[x], Pascalban ParamStr(x)) és mindenki a maga ízlése szerint összerakhatja a maga parancsait úgy, hogy az egyikre daemonizál és futtatja a lényegi részt, a másik kettőre meg lekéri a PID-et és SIGTERM-et, vagy SIGHUP-ot küld neki (stop vagy reload végett), aztán várakozik pl. 5 másodpercig, vagy addig amíg a célprocess ki nem száll/vissza nem jelez, pl. egy a /var/lock/-ban, vagy a /tmp/-ben létrehozott semaphore file segítségével.

Nos, alapjában véve ennyi egy UNIX daemon. Alapjában véve. Hogy egy klasszikust idézzek, "kb. így fest a dolog gatyában". Most jön a kit része, amit ígértem az elején.

Tehát akkor van egy daemonizerünk; C-ben
http://oscomp.hu/daemon-kit/c/daemon.c
és Pascalban
http://oscomp.hu/daemon-kit/pascal/daemon.pas
amik a következő függvényeket tartalmazzák:

C: void remove_daemon_pidfile(char *daemon_name)
Pascal: procedure remove_daemon_pidfile(daemon_name: string);

Nomen est omen, arra való, hogy leszedje a daemon process lock-ért felelős PID fájlt a /var/run/ mélyéről. Akkor "célszerű" használni, amikor a daemon szabályszerűen áll le. (Amikor nem szabályszerűen teszi, akkor a drink() függvényt célszerű, lehetőleg uint64_t-re castolt -1-gyel...)

C: pid_t pidof_daemon(char *daemon_name)
Pascal: function pidof_daemon(daemon_name: string): tpid;

Másik beszédes nevű függvény, de ez nem csak lekéri a daemon PID-jét, hanem, amennyiben a lock létezik, de kidöglött mögüle a process, akkor feloldja. Ha a daemon nem fut, akkor nullát ad vissza.

C: int daemonize_process(char *daemon_name, bool redirect_fds, char *workdir)
Pascal: function daemonize_process(daemon_name: string; redirect_fds: boolean = true; workdir: string = '/'): integer;

Ez felel a daemonizálásért. (DOH!) Átadja neki az ember a daemon böcsületes nevét és - a signal redirect kivételével (ld. később) - mindent megcsinál. A név bármi lehet, de értelemszerűen ugyanazt kell használni mind a négy függvénynél. A redirect_fds paraméter felel azért, hogy ráhúzza-e a daemonizer a stdxyz descriptorokra a dup2()-t vagy ne. Normál esetben igen, debugnál jól jön, hogy ki lehet kapcsolni. A workdir értelemszerűen a munkakönyvtár. (Mint kiveséztük, daemonoknál általában ez a gyökérkönyvtár.) Visszatérési értéke 0, ha minden rendben volt, vagy egy hibakód. (Ld. a forrásban, nem sorolom fel, mert sok is, meg nem is érdekes.)

C: int stop_daemon(char *daemon_name, int stopsig, int32_t timeout)
Pascal: function stop_daemon(daemon_name: string; stopsig: cint; timeout: longint): integer;

Ezt lehet meghívni, ha le akarjuk állítani a daemont. A stopsig-et direkt nem drótoztam be a SIGTERM-re, mert hátha valaki máshogy akarja. Ami viszont fontos, hogy ha timeout mikroszekundumig nem száll ki a célprocess, akkor viszont garantáltan SIGKILL-lel honorálja a dolgot. Visszatérési értéke lehet 0, ha a process rendben leállt, 1, ha ki kellett nyírni és 2, ha nem is futott. UPDATE (2019.06.17.): A timeout-ot negatív értékkel letilthatjuk.

Na most, azt meg kell jegyezni, hogy a GLibC-ben van már daemonizer, de az minden, csak nem cross-platform (lévén GLibC dependens). Ez a megközelítés viszont az.

Aztán, van nekünk egy signal handlerünk is; C-ben:
http://oscomp.hu/daemon-kit/c/signals.c
és Pascalban:
http://oscomp.hu/daemon-kit/pascal/signals.pas
a következő függvényekkel:

C: bool assign_signal(int signal, void (*callback)(int))
Pascal: function assign_signal(signal: unix_signal; callback: punix_signal_callback): boolean;

Ez a fentebb már kivesézett segédfüggvény, amit a nem-POSIX-compliant signal() helyett használhatunk. Több szót nem vesztegetnék rá, csak annyit, hogy ha a SIG_DFL, ill. SIG_IGN konstansokat (0 és 1) akarjuk hozzárendelni valamelyik signalhoz, akkor azt a Pascal verzióban ugyanúgy typecastolva lehet, mintha egy függvény címét akarjuk odaadni neki. Hiba esetén ad vissza igazat.

C: bool signal_w_fsemaphore(pid_t proc, int sig, int32_t timeout, char *semaphore)
Pascal: function signal_w_fsemaphore(proc: tpid; sig: unix_signal; timeout: longint; semaphore: string): boolean;

Ez sig signalt küld a proc processnek és timeout mikroszekundumig várakozik, vagy addig, amíg a semaphore fájlt valaki létrehozza. (Meghíváskor értelemszerűen magától törli először.) Ezzel lehet pl. ütemezni a reloadot, meghívjuk, a signal handler meg újratölti a konfigot és létrehozza a semaphore-t. Ez is hiba (azaz timeout) esetén ad vissza igazat. UPDATE (2019.06.17.): A timeout-ot negatív értékkel letilthatjuk.

Nos, nagyjából így lehetett összefoglalni és "kitesíteni" a fentebb leírtakat. Mondjuk egyvalamiről nem sikerült szót ejteni, hogy maga a daemon főciklusa hogyan működik. Pofonegyszerűen: a váza egy mezei while ciklus, ami egy feltételhez kötve vár:

C:

bool run_daemon = true;

while (run_daemon)
{
	// főciklus
}

Pascal:

var run_daemon: boolean = true;

while (run_daemon) do
begin
	// főciklus
end;

És akkor a fentebbi signal handleres példa úgy módosul, hogy a leállítós ágba bekerül, hogy run_daemon = false;

Mi hiányzik még ebből? Mondjuk az, hogy ne őrölje fel a CPU-t ez a szerencsétlen ciklus: kell bele várakoztatás. Ezt a nanosleep() (Pascal: fpnanosleep) függvénnyel lehet, de annak körülményes és kényelmetlen a használata.

Úgyhogy itt egy erre alapuló microsleep (ennél precízebb amúgy sem valószínű, hogy kellene) C-ben:
http://oscomp.hu/daemon-kit/c/microsleep.c
és Pascalban:
http://oscomp.hu/daemon-kit/pascal/microsleep.pas

Egy db. függvény van benne:

C: void _usleep(uint64_t us)
Pascal: usleep(us: qword);

Használata triviális. A C-s függvény esetében az underscore prefixum azért van, hogy ne akadjon össze a GLibC-ben lévővel és azért nem arra wrappel csak simán, mert egyrészt nem biztos, hogy GLibC van a rendszerben, másrészt pedig azért, mert a GLibC-s verzió nem kezeli le a nanosleep() visszatérési értékét és a remainder timespec tartalmát is levesbe küldi, ahelyett, hogy ha van maradék, akkor azt is levárná. (De, ahogy nézem, a musl sem foglalkozik vele.)

Nos, a daemon innentől kezdve tulajdonképpen készen van. Viszont én még belinkelnék három másik libet is. (Amiknek a használata teljesen opcionális, komfortnak van.)

Egyfelől: ami - majdnem - minden daemonban lesz úgyis: konfigurációs állományok parsingjához itt egy lib C-ben:
http://oscomp.hu/daemon-kit/c/confparse.c
és Pascalban:
http://oscomp.hu/daemon-kit/pascal/confparse.pas

UPDATE (2020.08.07.): A C-s konfigparsert átírtam, hogy dinamikus bufferű sorbeolvasóval dolgozzon, aminek a unitja itt elérhető: http://oscomp.hu/daemon-kit/c/readline.c
Értelemszerűen már ez miatt frissült maga a parser is, de ezen felül még egy fájlezárási hibát is javítottam, ami már a Pascal-os verziót is érintette.

A következő két függvényt tartalmazzák:

C: void parse_conf_file(char *filename, int items_c, struct conf_item items[], int *error_code, int *error_line)
Pascal: procedure parse_conf_file(filename: string; items: conf_items; error_code: pinteger; error_line: pinteger);

C: void scan_conf_dir(char *dirname, int items_c, struct conf_item items[], char **error_file, int *error_code, int *error_line)
Pascal: procedure scan_conf_dir(dirname: string; items: conf_items; error_file: pstring; error_code: pinteger; error_line: pinteger);

Az első egy közönséges név = érték # komment jellegű konfigparseoló, a második pedig ezt hívja meg (nem rekurzívan) egy könyvtár összes fájljára, ha valaki szeparálni akarná a konfigokat.

Az error_code, error_line és a dirscanner esetében az error_file egyértelműek, hogy mire valók (hibakódokért ld. a forrást), az viszont fontos, hogy hiba esetén az egész megáll és kiszáll, nem próbálja meg parse-olni a hátralevőket.

Ami viszont magyarázatra szorul, az az items.

C:

struct conf_item
{
char *ci_name; int ci_type; void *ci_var; };

Pascal:

type
	conf_item = record
		ci_name: string;
		ci_type: integer;
		ci_var: pointer;
	end;
	conf_items = array of conf_item;

Lényegében arról beszélünk, hogy van egy ilyen elemekből álló tömbünk, amikben meghatározzuk, hogy milyen változókat írjon a parser (ci_var), azoknak mi a típusa (ci_type, ld. a forrást) és milyen néven hivatkozunk rájuk a konfig fájlokban (ci_name).

Példa C-ben:

A változók:

char *ezegystring;
float ezegyfloat;
uint32_t ezegyint;
bool ezegybool;

A tömb:

struct conf_item items[4] =
{
	{
		"ezegystring",
		CONFVAR_TYPE_STRING,
		&ezegystring,
	},
	{
		"ezegyfloat",
		CONFVAR_TYPE_FLOAT32,
		&ezegyfloat,
	},
	{
		"ezegyint",
		CONFVAR_TYPE_INT32,
		&ezegyint,
	},
	{
		"ezegybool",
		CONFVAR_TYPE_BOOL,
		&ezegybool,
	}
};

És a tényleges kód:

char *ef;
int ec, el;

scan_conf_dir(
	"/etc/teszt_daemon_conf_dir/",
	4,
	items,
	&ef,
	&ec,
	&el
);

Példa Pascalban:

A változók:

var
	ezegystring: string;
	ezegyfloat: single;
	ezegyint: integer;
	ezegybool: boolean;

A tömb:

const
	items: array[0..3] of conf_item =
( ( ci_name: 'ezegystring'; ci_type: CONFVAR_TYPE_STRING; ci_var: @ezegystring; ), ( ci_name: 'ezegyfloat'; ci_type: CONFVAR_TYPE_FLOAT32; ci_var: @ezegyfloat; ), ( ci_name: 'ezegyint'; ci_type: CONFVAR_TYPE_INT32; ci_var: @ezegyint; ), ( ci_name: 'ezegybool'; ci_type: CONFVAR_TYPE_BOOL; ci_var: @ezegybool; ) );

És a tényleges kód:

var
	ef: string;
	ec, el: integer;

scan_conf_dir(
	'/etc/teszt_daemon_conf_dir/',
	items,
	@ef,
	@ec,
	@el
);

Mivel a célváltozókat pointerrel adjuk meg, így nem korlátozódik semmilyen scope-ra, (nyugodtan lehet egy struct belsejében, vagy egy tömbben, vagy akárhol), vagy namespace-re (csak a tömb deklarációjánál kell, hogy "lássa" a pointerek célját).

Így sokkal kényelmesebb a konfigok kezelése és - ha szükséges - a változókészlet bővítése.

A másik a daemon argumentumainak lekezeléséhez adna egy mankót; C-ben:
http://oscomp.hu/daemon-kit/c/argforward.c
és Pascalban:
http://oscomp.hu/daemon-kit/pascal/argforward.pas

C: int forward_args(int args_c, struct fw_arg args[], int def_arg, int argc, char *argv[])
Pascal: function forward_args(args: fw_args; def_arg: integer): integer;

C: char *implode_arg_names(int args_c, struct fw_arg args[], char *sep)
Pascal: function implode_arg_names(args: fw_args; sep: string): string;

Hogy mi ez? A daemonnak átadott argumentumokat normál esetben egy jókora case "toronyban" kezeli le az ember, amit egy idő után elég kényelmetlen bővíteni, vagy csak keresgetni benne, ha sok parancsot tud és/vagy az egyes parancsok implementációi hosszúra nyúlnak; persze azokat ki lehet pakolni külön-külön függvényekbe, de akkor már majdnem ugyanott van vele az ember, mint ezzel, csak mégis hátrább, ld. mindjárt.

A felállás hasonló, mint az előbb: átadunk egy struct halmot, ahol az egyes elemek tartalmazzák a parancs nevét és a meghívandó eljárást.

Az első végigiterálja a tömböt és összehasonlítja az 1. argumentumot a nevekkel és ha egyezést talál, akkor meghívja azt a függvényt, átadván neki a bemeneti argumentumokat is (mintha az lenne a főprogram). Ha nem talál egyezést, akkor azt az függvényt hívja meg, amelyik a def_arg indexe alatt van.

A második - a nevéből sejthetően - az ismert parancsneveket fűzi össze egy tetszőleges szeparátorral. Ennek az az értelme, hogy pl. ha van help függvényünk, abban ezt meghívhatjuk és bővítéskor nem kell külön mindenütt bővíteni, csak hozzá kell adni az új nevet.

A definíciók C-ben:

struct fw_arg
{
	char *arg_name;
	int (*arg_func)(int argc, char *argv[]);
};

és Pascalban:

type
	fw_func = function (argv: array of string): integer;
	fw_arg = record
		arg_name: string;
		arg_func: fw_func;
	end;
	fw_args = array of fw_arg;

És akkor egy példa C-ben:

...

int stop(int argc, char *argv[]);
int reload(int argc, char *argv[]);
int start(int argc, char *argv[]);
int help(int argc, char *argv[]);

char *rl_sem = "/var/run/teszt_reload";

struct fw_arg args[4] =
{
	{
		"start",
		&start
	},
	{
		"stop",
		&stop
	},
	{
		"reload",
		&reload
	},
	{
		"help",
		&help
	}
};

...

int stop(int argc, char *argv[])
{
	int result;

	result = stop_daemon("teszt", SIGTERM, 5000);
	fprintf(stderr, "Stopped with code: %d\n", result);
	return result;
}

int reload(int argc, char *argv[])
{
	return signal_w_fsemaphore(pidof_daemon("teszt"), SIGHUP, 5000, rl_sem);
}

int start(int argc, char *argv[])
{
	...
}

int help(int argc, char *argv[])
{
	char *names;

	names = implode_arg_names(4, args, "/");
	fprintf(stderr, "teszt <%s>\n", names);
	free(names);
	return 0;
}

int main(int argc, char *argv[])
{
	forward_args(4, args, 3, argc, argv);
}

És Pascalban:

...

function stop(argv: array of string): integer; forward;
function reload(argv: array of string): integer; forward;
function start(argv: array of string): integer; forward;
function help(argv: array of string): integer; forward;

const
	rl_sem = '/var/run/teszt_reload';
	args: array[0..3] of fw_arg =
	(
		(
			arg_name: 'start';
			arg_func: @start;
		),
		(
			arg_name: 'stop';
			arg_func: @stop;
		),
		(
			arg_name: 'reload';
			arg_func: @reload;
		),
		(
			arg_name: 'help';
			arg_func: @help;
		)
	);

...

function stop(argv: array of string): integer;
begin
	result := stop_daemon('teszt', SIGTERM, 5000);
	writeln('Stopped with code: ', result);
end;

function reload(argv: array of string): integer;
begin
	result := integer(signal_w_fsemaphore(pidof_daemon('teszt'), SIGHUP, 5000, rl_sem));
end;

function start(argv: array of string): integer;
begin
	...
end;

function help(argv: array of string): integer;
begin
	writeln('teszt <' + implode_arg_names(args, '/') + '>');
	result := 0;
end;

begin
	forward_args(args, 3);
end.

És végül, amire ígértem, hogy még visszatérünk: a loggoláshoz egy segédlib.; C-ben:
http://oscomp.hu/daemon-kit/c/logging.c
és Pascalban:
http://oscomp.hu/daemon-kit/pascal/logging.pas

A következő függvényekkel:

C: set_logging_path(int index, char *path)

C: clear_logging()

C: void write_log(char *line, int logging_channel)
Pascal: procedure write_log(line: string; logging_channel: integer = 0);

És változókkal:

C: bool logging_echo = false;
Pascal: logging_echo: boolean = false;

Pascal: logging_paths: array of string;

Amit korábban nem írtam le: nem az a célszerű, ha az ember a stderr-t és stdout-ot fájlokba irányítja át a /dev/null helyett, hanem az, ha alapvetően logfájlokba írkál. Amint a függvények definícióiból kiderül, több naplófájlt is lehet vele kezelni, ha esetleg szükséges lenne. Két közös pont van a két implementációban: az egyik maga a write_log(), ami a naplófájlokat írja (automatikusan "[yyyy-mm-dd HH:ii:ss.TTT]: " formátumú időbélyeggel prefixálva), a másik a logging_echo változó, ami ha igaz, akkor a kiirandó sor a terminálon is meg fog jelenni (időbélyeg nélkül). Nyilván, ehhez az kell, hogy ne legyenek a stdxyz descriptorok átirányítva a /dev/null-ra. (Egyszóval ez is debugra való.)

A többi eltérés a stringtömbök kezelésének eltérése miatt van. Pascalban csak simán setlength(logging_paths, 1); logging_paths[0] := '/var/log/teszt.log'; a beállítás, törlésre meg nincs szükség, mert a stringtömböt felszabadítja a program kilépéskor. C-ben viszont set_logging_path(0, "/var/log/teszt.log"); a beállítás és a program befejezésekor kell a clear_logging() is.

Ami még fontos, hogy a logfájlok nincsenek folyamatosan nyitva, aminek oka, hogy ha a program megborulna, akkor is intakt állapotban maradjon a logfájl, meg az is, hogy ha valami jótét lélek menet közben törölné a nyitott fájlt, az "vicces" lenne, így viszont egyszerűen létrehozza megint és írja, előről.

A kitet teszteltem Linux, Solaris 10, FreeBSD és OpenBSD alatt (ez utóbbi alatt - Pascal fordító híján - csak a C-s edisönt) és ment mindenütt. (Egyéb BSD-ket már nem volt kedvem felrakni.) OSX alatt sajnos nem volt érkezésem tesztelni, mert szét van szedve minden (mármint a KVM), az OSX meg nem akar menni VM-ből, de ha valaki megpróbálja lefordítani OSX alatt, akkor nagyon szívesen veszem a visszajelzést, hogy mire jutott vele. AIX alatt sem tudtam tesztelni, mert nincs vasam hozzá, de ugyanazt tudom mondani, mint az OSX esetében.
Buildeléséhez elméletileg nem kell semmi, viszont Solaris 10 alatt az -lposix4 kelleni fog a forgatáskor.

Nos, azt hiszem, több marhaságot már nem tudok összehordani, de azt hiszem, hogy ennyi is bőven elég volt.

Mint az elején mondtam, nem tudom, hogy hány embernek lesznek újdonságok ezek a dolgok, vagy hasznos a daemon-kit, amit publikáltam, de remélem nem írtam hiába ezt a posztot.

Hozzászólások

Köszönjük. (Rejtett bookmark)
--
Dell Latitude 5480, Arch Linux & Xfce

1) Tudtommal a dupla fork akkortól eredeztethető, amikor még nem volt (pláne egységes) sessionkezelés. (Olyanokra gondolok, mint System III, meg 4BSD előtti idők.)

2) a Ctrl-Z-re írt fél mondat hibás. A SIGTSTP-t nem core dump generálásra szoktuk használni, ugyanis alapból pont azt csinálja, amit a neve mond: STOP állapotba rakja a processzt (pont, mint az ugyanott emlegetett SIGTTIN és SIGTTOU). Konkrétan ugye előtérben futó processz megállítására szokás használni. Kilépek a vi-ból, hogy megnézzem a man-t, majd a leblokkolt előtérprocesszt a doksi olvasás után fg paranccsal folytatom. Vagy épp előtérben elindítok egy szarul megírt démont, amelyik induláskor akarna írni az STDOUT-ra (mondjuk egy (C) szöveget), megállítom SIGTSTP-vel és továbbengedem bg-vel. Amivel kevered, az a SIGQUIT (azaz a Ctrl-\ ) - no az generál gyárilag core-t.

A többit majd később, most fáradt vagyok :-)

=====
tl;dr
Egy-két mondatban leírnátok, hogy lehet ellopni egy bitcoin-t?

1) Erről nem találtam így infót, a leírások csak a felsoroltakat emlegették, de ettől függetlenül lehet.

2) Nem tudom, hogy került oda a core dump. Én is fáradt voltam már, kora reggel kezdtem írni ezt a posztot és estére lettem meg vele. Köszi, hogy szóltál, a core dumpos fél mondatot töröltem.

Én ebben a könyvben olvastam róla először:

Advanced Programming in the UNIX Environment, 13.3-as pont (Coding Rules):

3. Call setsid to create a new session. The three steps listed in Section 9.5 occur.The process (a) becomes the leader of a new session, (b) becomes the leader of anew process group, and (c) is disassociated from its controlling terminal.

Under System V–based systems, some people recommend calling fork again at thispoint, terminating the parent, and continuing the daemon in the child. This guarantees that the daemon is not a session leader, which prevents it from acquiring a controlling terminal under the System V rules (Section 9.6). Alternatively, to avoid acquiring a controlling terminal, be sure to specify O_NOCTTY whenever opening a terminal device.

(466. oldal a PDF-ben)

Ehhez Poettering-nek is lenne még egy-két hozzáfűznivalója, hogy systemd-aware legyen. :-)
Nekem így teljesen jó, "hagyományos" init rendszerhez.

"Jegyezze fel a vádhoz - utasította Metcalf őrnagy a tizedest, aki tudott gyorsírni. - Tiszteletlenül beszélt a feljebbvalójával, amikor nem pofázott közbe."

Köszi! + sub

-----
"hagyományosan szűkebb komfortzónával rendelkezem"

Nagyon köszönöm, a legjobbkor jött.
És egy nagy +1 a Pascal változatra :)

Nagyon nem-C-s kóderként:

C: pid_t pidof_daemon(char *daemon_name)
beszédes nevű függvény, de ez nem csak lekéri a daemon PID-jét, hanem, amennyiben a lock létezik, de kidöglött mögüle a process, akkor feloldja.

Beszédes, de nem azt csinálja, amit a beszédes neve mond :)

Hiba esetén ad vissza igazat.

Wut?

BlackY
--
"en is amikor bejovok dolgozni, nem egy pc-t [..] kapcsolok be, hanem a mainframe-et..." (sj)

> Beszédes, de nem azt csinálja, amit a beszédes neve mond :)

Pontosítsunk: nem csak azt csinálja, de az meg nagyon hülyén nézne ki, hogy pidof_daemon_and_unlock_if_not_running(), nem? :P

> Wut?

Mi nem érthető? Ha hiba történt, azaz nem sikerült a signalhoz hozzárendelni a handlert, akkor a visszatérési érték true, ha sikerült, akkor false.

de az meg nagyon hülyén nézne ki, hogy pidof_daemon_and_unlock_if_not_running(), nem? :P

Valóban, camelCase-ben jobban nézne ki, meg ha egy utility class egyik metódusa lenne: new PidofDaemonUtil()->getPidAndUnlockIfRunning() [és ugyanitt: new PidofDaemonUtil()->getPid()] :) Bár még kéne bele egy abstract factory, egy-két decorator, és legalább három Exception osztály :) [de a mellékhatással járó, ezt névben nem tükröző függvényektől továbbra is kiráz a hideg, még ha C-ben standardak is :( ]

Mi nem érthető?

Érthető, csak... legalábbis furcsa. Megértem, hogy ez nagyrészt a C marhasága, de mindig kiakadok, amikor ilyet látok:


if(!foo()) { // negált feltétel, ami általában azt jelenti, hogy foo-val valami nem stimmelt
   // minden ok
} else {
   // valami el lett bökve
}

Ha viszont nem negálod a feltételt (ami szerencsés), akkor az if() ágba kerül a hibakezelés, ami ront az olvashatóságon (ha a sikeres code path-et akarod megnézni [mit csinál az fgv], akkor is kénytelen vagy átfutni az összes hibakezelő kódot]). Ráadásul nem is 0 - valami hibakód a visszatérési érték, hanem tényleges C-stílusú bool érték, a hibakódért úgyis az Ernőt kell kérdezgetni...

Vesd össze:
(te verziód)


if(assign_signal(...)) {
    /* Hibalehetőség 1 */
} else {
   /* Happy path 1 */
   if(daemonize(...)) {
     /* Hibalehetőség 2 */
   } else {
       /* Még mindig a happy path */
       if(foobar(...)) {
          /* Na, ezt a kódrészletet keresed */
       }
   }
}

vagy


if(!assign_signal(...)) {
    if(!daemonize()) {
        if(!foobar()) {
            /* Na, ezt a kódrészletet keresed, ez van legfelül */
        } else {
            /* Hibalehetőség 1 */
        }
    } else {
        /* Hibalehetőség 2 */
    }
} else {
     /* Hibalehetőség 3 */
}

(megfordított assign_signal visszatéréssel)


if(assign_signal(...)) {
    if(daemonize()) {
        if(foobar()) {
            /* Na, ezt a kódrészletet keresed, ez van legfelül */
        } else {
            /* Hibalehetőség 1 */
        }
    } else {
        /* Hibalehetőség 2 */
    }
} else {
     /* Hibalehetőség 3 */
}

És persze a képzeletbeli exceptionökkel:


try {
   assign_signal(...);
   daemonize(...);
   foobar(...);
   /* Na, ezt a kódrészletet keresed */
} catch (...) {
   /* Hibalehetőség 1 */
} catch (...) {
   /* Hibalehetőség 2 */
} catch (...) {
   /* Hibalehetőség 3 */
}

Az olvashatóságot nagyban nem rontja, ha minden if()-nél negálod a feltételt, viszont minden függvénynél az olvasónak is tudnia kell majd, hogy a hamis visszatérés = igaz ágon van a helyes futás.

BlackY
--
"en is amikor bejovok dolgozni, nem egy pc-t [..] kapcsolok be, hanem a mainframe-et..." (sj)

> Bár még kéne bele egy abstract factory, egy-két decorator, és legalább három Exception osztály :)

Remélem ez humor volt.

A többire azt tudom mondani en-bloc, hogy aki ekkora tornyokat emel if-ekből, az magának csinálja. Tekintve, hogy ha hiba van, úgy sem kell végrehajtani a többi, ott ki is lehet szállni, tehát:
if (hiba van)
{
        printd("xyz\n");
        return xyz;
}

Kb:
int ec = daemonize_process("kecske", true, "/");
if (ec != 0)
{
        printf("Daemonize error: %d\n", ec);
        return 1;
}

if (assign_signal(SIGHUP, &mittudomain))
{
        printf("Cannot assign signal, ERRNO: %d\n", errno());
        return 2;
}

if (!foobar(...))
{
        printf("FooBar2000 rulza!\n");
        return 3;
}

return 0;

És ez teljesen olvasható.

Remélem ez humor volt.

Azért volt a végén a :)

aki ekkora tornyokat emel if-ekből, az magának csinálja.

Nagyobb tornyot nem is csinálnék, ha plusz szint kellene, akkor repülnének külön függvényekbe. (ill. simán nem írnék C kódot, amiben évtizedek óta megoldott problémákat kell coding style-al és guideline-okkal kezelni :) ).

És ez teljesen olvasható.

Viszont lett négy visszatérési pontod. És továbbra is a helyes működés megismeréséhez át kellett görgetned 12 sornyi tisztán hibakezelő kódon.

BlackY
--
"en is amikor bejovok dolgozni, nem egy pc-t [..] kapcsolok be, hanem a mainframe-et..." (sj)

> Azért volt a végén a :)

Sejtettem, de nem voltam benne biztos.

> ill. simán nem írnék C kódot, amiben évtizedek óta megoldott problémákat kell coding style-al és guideline-okkal kezelni :)

Nem vitatom, hogy vannak a C-ben problémák, de az újabb nyelvek nem csak megoldották a problémákat, hanem generáltak újakat is...egynémelyik messze többet, mint amennyit megoldott.
Egyébként, amint látod adtam Pascal verziót is, meg amúgy is Public Domain, át lehet írni C++-ra, ha valaki azt preferálja.

> Viszont lett négy visszatérési pontod.

És?

> És továbbra is a helyes működés megismeréséhez át kellett görgetned 12 sornyi tisztán hibakezelő kódon.

A helyes működéshez a hibakezelés is hozzátartozik, azt is meg kell ismerni.

Coding standard-tól függetlenül a Linux világ se szereti a sok return-t.
Erre egyik megoldás a "goto". A Linux kernel nem tiltja a goto-t, és a driver-ek tele is vannak vele. Általában a "hibakezelés 1", "hibakezelés 2", ... részek a függvények végénél vannak, és a függvény elején, vagy közepén levő ellenőrzések egy goto-val jutnak el ide. Kis szépséghiba, hogy a "return 0" általában a hibakezelések előtt van, de ezt hamar megszokja az ember. És viszonylag könnyű megtalálni, mert általában az első "err_..." label előtt van.

> Viszont lett négy visszatérési pontod.

Ha az exceptiönös megoldást nézed, akkor is van ugyanúgy 4 visszatérési pontod, csak el van rejtve.

A jó programok meg jól kezelik a hibákat, azt meg úgy egyszerű (szerintem), ha a hiba pathok is könnyen láthatóak, így nem tudsz elsiklani néhány hiba felett.

Érthető, csak... legalábbis furcsa. Megértem, hogy ez nagyrészt a C marhasága, de mindig kiakadok, amikor ilyet látok...

A C igyekszik tömör lenni, amit a kóderek is figyelembe vesznek. A visszatérési érték ennek megfelelően általában nem csak logikai érték (sikeres/nem sikeres a művelet), hanem hibakód is (hol/min füstölt el a művelet).
Amikor a magasabb szintet csak az érdekli, hogy sikeres-e a művelet, akkor:
0 - nincs hibakód, de ez logikai értékként hamis
!0 - van hibakód, de épp nem érdekel, viszont logikai alapokon ez az igaz érték.

Megjegyzések:
- tudom, itt most eleve logikai érték visszaadása történik - de ugye a konvenciók... :-D
- tudom, magas szinten is illik lekezelni az alacsonyabb szint hibáit - de elképzelhető, hogy adott esetben nem lényeges, mert az alacsony szint is írhat naplót, ha pedig magas szinten adott esetben csak annyi érdekel, hogy sikeres-e, akkor az érték úgyis csak logikaiként kerül feldolgozásra

Ez nem workaround, ez a standard módja a hibakezelésnek C-ben. Nem véletlenül van goto utasítás. Amit te exception handling néven emlegetsz az kb. ugyan ezt csinálja, csak fogja a kezed. Olyan fajta exception handlinget ami pl. a C++-ban van egy csomó helyen nem lehet megvalósítani, pl. egy interrupt handlerben (vagy SIGNAL handlerben), kernel kódban etc.

Szerintem a handle_signal fuggvenyrol hianyzik egy cdecl;, lehet hogy mashonnan is...

Lehet h. bizonyos platformokon megy anelkul is, de biztos tudok olyat ahol nem. (Pl. bizonyos platformokon a hivott fuggveny igazitja a stackpointert Pascal fuggvenyek visszaterese eseten, mig C eseten a hivo fuggveny, stb, igy akkor is baj lehet, ha csak 1 parametered van.)

-=- Mire a programozó: "Na és szerintetek ki csinálta a káoszt?" -=-

Ha nem adod hozza a cdecl-t, ket problema lehet.

A., peldaul mashol varja a 'sig' parametert a hivott Pascal kod, mint ahova a C-s kod rakja. Pelda: linux-i386, ahol a C kod mindent a stacken ad at, mig a Pascal kod hasznal bizonyos regisztereket.
B., a mar emlitett stack cleanup problema. Szinten i386-Linux, ha jol remlik, ill. Win32 (de az itt most ugye nem jatszik) illetve m68k-Linux biztosan.

Pl. x86_64 es ARM platformokon nem latod ezt a problemat, mert ott az FPC a SYSV ABI-t koveti, amit a rendszer tobbi resze, mivel itt nem volt "historic" Pascal ABI amit tamogatni lehetett/kellett (miert kellett? pl. inline assembly rutinok miatt, amik fix regiszterekben vartak dolgokat).

Konkretan az eggyel ezelotti cegemnel meg voltak gyozodve h. i386-Linuxon bugos az FPC, mert a fasza kodjuk arm-linuxon ment FPC-vel, de i386-on szetfagyott... Kb. ket tucat hianyzo cdecl hozzaadasa utan egybol mukodni kezdett. Ki erti.

-=- Mire a programozó: "Na és szerintetek ki csinálta a káoszt?" -=-

Nem lehet. Nincs ilyen h. default ABI. "Nem ezeket a droidokat keresed." Fuggvenyszinten kell definialni, lenyeg. h barhol ahol C kod hiv Pascal kodot, vagy Pascal kod hiv C kodot, a cdecl; legyen hozzaadva, mivel hidd el, nem akarsz random crasheket debugolni amiatt h. valami latszolag mukodik, csak valami parameter veletlenul nem adodik at.

Es attol h. Pascal kodbol is meghivod ugyanazt a fuggveny, attol meg lehet Cdecl, mivel akkor a fordito tudja h. ez egy C ABI-t hasznalo fuggveny, es annak megfelelo hivo kodot fordit. Szoval ja.

(Es vicces modon, meg igy is lehetnek trefak, pont mult heten volt egy kalandom, hogy hiaba a SYSV ABI definicio, x86_64-en a Free Pascal mashogy kezelte a C booleanokat mint az ABI elvarta (mert az ABI-t befrissitettek valamikor mar reg, a GCC-t is updateltek hozza, nekunk meg nem szolt senki), es a GCC optimalizalt egy csodalatosat, kiszorva egypar bit-maszkolast a C oldali kodbol, gondolvan h. az ABI miatt az nem kell (facepalm), emiatt ugyanaz a kod, cdecl-lel Windows, macOS-en ment (mert az clang, ugye), de x86_64-en a GCC-vel forditott kod nem. Szoval workaround rulez. De azota az FPC trunkban ezt kifixeltek elvileg.)

-=- Mire a programozó: "Na és szerintetek ki csinálta a káoszt?" -=-

Ez csak amolyan hangosan gondolkodás - értem én, hogy a printf-es üzenetkiírás kényelmes, de szerintem alapszabály, hogy stdout-ra csak a működés "normál" üzenetei mennek; szerintem mindent ami ilyen informális / figyelmeztető / hiba, az fprintf-fel az stderr-re kéne.

< /morfondír >

=====
tl;dr
Egy-két mondatban leírnátok, hogy lehet ellopni egy bitcoin-t?

Kb. arrol van szo, hogy az ABI eloirja (hogy miota azt nem tudom, de nem olyan reg ota mint ahogy az FPC C ABI kezelese keszult... :D) h. ha egy C-s bool-t adsz at, akkor csak a legalso bitje szamit, az lesz 1 v. 0. Ez nem tul ujdonsag, es a C99 ota a nyelv specifikacio resze is, szoval kb. lecopypasteltek a Pascal booleant (ami dokumentaltan 0 v. 1 ha ordinalt kersz belole, osidok ota).

Csak eppen mindenki azt hiszi, hogy a C-ben a true az barmi lehet ami nem 0, a false meg mindig 0. Na ez nem igaz mar. :) A Free Pascal is azt hitte, ezert a C boolean kezeles -1-t (0xff) forditott oda "true"-kent, ahol a C kod szigoruan 1-t vart. A C kod meg valahogy igy nezett ki:

void foo(bool bar) {
char flags = (bar & 0x1) << 1;
send_buf(flags);
}

A halozati packetben viszont mi jelent meg flags-kent? 0xfe (254). Mert ugye, a GCC okos, es rajott h. hat a bar valtozo egy bool, ahol az ABI eloirja h. csak 0 v. 1 lehet, ergo optimalizaljuk szepen ki a kodbol a 0x1-vel valo andolast hiszen nem kell az oda! :D

Igy lett az h. 0xff << 1 -> 0xfe :) Bonusz, hogyha igy hivod meg a C kododbol a fuggveny h: foo(-1); akkor a GCC mindenfele warning vagy barmi nelkul atirja a leforditott kodban a hivo oldalon a -1-t 1-re... :)

Ami azert kell, mert a C kodban (ellentetben a pascallal) ugyan most mar a booleanok 0 v. 1-re vannak eloirva, de attol meg az int vs. bool atjarhatosagot a regi kodokkal valo kompatibilitas miatt biztositani kell, ezert van az h. a fordito azt hiszi, hogy majd o csendben megoldja ezt neked, te azt hiszed minden rendben, aztan az arcodba esnek ilyen szepsegek...

Azota a Free Pascal is tudja, hogy a cbool <> barmi ami nem 0, hanem szigoruan 1. :)

-=- Mire a programozó: "Na és szerintetek ki csinálta a káoszt?" -=-

Hát ez vicces. Viszont azt nem értem, hogy akkor a 0-án és 1-en kívüli állapotokat minek akarja megfeleltetni a C? Csak mert vizsgálatkor minden nem nullára triggerelődnek a feltételes vezérlők.

Mondjuk C-ben én mindig "úgy teszek", mintha a bool típusú változók tényleg azok lennének és nem csak átnevezett int-ek, azaz aritmetikai műveletet nem végzek vele, vagy ha igen, akkor előtte typecastolom, akkor is, ha tudom, hogy valójában tkp. ugyanarra typecastolom. Pusztán azért, hogy érthetőbb legyen, ami oda van írva és ne mágiának tűnjön. Mivel a zűrt itt a compiler okoskodása okozta, gondolom a typecast (char flags = ((char)bar) & 0x1) << 1;) ezen is segített volna. (Fixme?) Persze akkor nem derül ki a bug sem. :/

Az a baj, hogy nem garantalhato h. egy bool valtozoban biztosan csak 0 v. 1 lesz (pl. mivan ha a struct erteket a halozatrol v. disk-rol toljuk fel?), csak a C fordito tulsagosan bizik az ABI-ban es tul jo a data flow analysis-e. :)

Amugy allando explicit typecastolas - igen, en is folyton Pascal kodot irok barmilyen nyelven! :P <3

-=- Mire a programozó: "Na és szerintetek ki csinálta a káoszt?" -=-

Szerintem meg nem célszerű. Lehetni lehet és az tény, hogy nélküle ezek a gondok nem lennének, de akkor meg jönnének azok a gondok, amiket a boolean típusú változók hiánya okozna. Egy boolean típusú változónál egyértelmű, hogy a változó szemaforként funkcionál - mert nem tud másként - míg egy integernél nem lesz egyértelmű, hogy mi a célja. És ezt nem lehet egyszerűen beszédes változónevekkel (pl. is_xyz) helyettesíteni:

#define NO 0
#define YES 1
#define MAYBE 2
#define DEFINITELY 3
#define INDEFINITELY 4

int is_tch_an_idiot = INDEFINITELY;

Persze lehet olyat, hogy prefixálod a nevét pl. azzal, hogy bool_, de ezt mindig beírni...hát agyfaszt kap a kóder.

Egyszóval a boolean változók segítenek egyértelműbbé, ezáltal gyorsabban és jobban megérthetőbbé tenni a kódot, ami nem csak időt jelent, de kevesebb buglehetőséget is, mert ha valaki nem értette meg, hogy valamelyik rész hogyan működik, akkor lehet, hogy hibázni fog.

csak az a gond ez már nem boolean. Öreg C kóderek akik asm-ból jöttek csak a FALSE-t definiálják és minden, ami nem hamis az igaz.

láttam már ilyen typedef int bool; éles kódot amiben mindenfélét tároltak, viszont ilyenkor egy korrekt pogromozó feláll diszkréten bejelenti, hogy akkor innentől kétszeres fizuval haladunk vagy kalap kabát.

--
GPLv3-as hozzászólás.

Kozben talaltam megegy helyet, ahol valszeg hianyzik a cdecl:

unix_signal_callback = procedure (signal: unix_signal); cdecl; <- mar ide is beirhato mint modifier, es akkor explicit h. ide egy C ABI fuggveny kell.

Pelda sajat kodombol:

https://github.com/chainq/mosquitto-p/blob/master/mosquitto.pas#L1350

Ez a Pascal oldalon implementalt callback mindig egy C kodnak (libmosquitto) adodik at, es igy mar a tipusdefinicioban is benne van h. cdecl, ami igy bekerul a fuggveny signature-be is, es safety++.

-=- Mire a programozó: "Na és szerintetek ki csinálta a káoszt?" -=-

van valami olyan fajl lockos megoldas amit addig fog amig nyitva a fajl, ha a process megszunik akkor a rendszer egyreszt lezarja a fajlt, masreszt igy felszabaditja a lockot.
egyreszt igy nemkell kulon unlockolni egy lehalt process utan, masreszt a singleinstance-re is kivalo: ha van lock akkor mar fut valaki.

--
A vegtelen ciklus is vegeter egyszer, csak kelloen eros hardver kell hozza!

jah bocs. az elkerulte a figyelmem. de ott a tmpfs, es en nem neztem meg hogy ez a flock kernelen belul hogyvan, de akar nfs-en is mukodhet: attol hogy a pid tartalma a tavoli helyen van, a kernel meg nyilvantarhatja hozza a lockot, es ha mas akarja megnyitani akkor azt ugyanugy nemengedi.

--
A vegtelen ciklus is vegeter egyszer, csak kelloen eros hardver kell hozza!

Nyilván eretnekség itt ilyet írni, de (csak (*)) Linux alatt van egy daemon() hívás, amivel az egészet le lehet zavarni jóval egyszerűbben:

http://man7.org/linux/man-pages/man3/daemon.3.html

Inkább csak a teljesség igénye miatt. :)

*: bocs, megnéztem a man oldalt, és úgy látom van BSD alatt is :)

Gondolom azért, mert az író szokott Pascal-an programozni és amint láttuk a hozzászólók egy része is örül neki - tehát igen jó helyen van itt a Pascal is. Köszönjük. Kellen még néhány ilyen jellegű írás, mert aki nem csinált ilyet az örük neki aki meg csinált és máshogy csinálta az úgysem bírja ki és beleszól, amiből az eredeti író is tanul - win-win helyzet.

Youtube: menő!

Én is hiányolom egyébként, hogy nem írtad le hogyan kell daemon-t programozni erlang-ban, haskell-ben, javascript-ben, objective c-ben, rust-ban és golang-ban sem. :-D

"Jegyezze fel a vádhoz - utasította Metcalf őrnagy a tizedest, aki tudott gyorsírni. - Tiszteletlenül beszélt a feljebbvalójával, amikor nem pofázott közbe."

Várjunk csak! A javascript sem menő már. :-(
Ezt a daemon-t manapság egy Ruby vagy Django service-ként illene implementálni, utána Docker-izálni és indulhat a fleet-orchestration kubernetes-szel! :-D

"Jegyezze fel a vádhoz - utasította Metcalf őrnagy a tizedest, aki tudott gyorsírni. - Tiszteletlenül beszélt a feljebbvalójával, amikor nem pofázott közbe."

Te is azota vagy ilyen, miota leszedultel az egodrol az IQ-dra? :) Mar csak a VB es a Delphi egy kalap ala vetele miatt is kerdem, es mert az sem tunik fel, hogy Un*x daemonok implementalasarol targyalunk, a Delphi meg igazan sosem volt Linuxon egy-ket rovid kaladot leszamitva, raadasul kb. 2007 ota mar nem is Borland... A Free Pascal meg egy teljesen valid Open Source projekt, amit teszel kb. az a szint mintha a GCC-t fikaznad, mert szerinted szar a Visual C.

Amugy, itt egy Pascal to JS transpiler. Termeszetesen Pascalban irva, nyilt forrassal. Szoval akar frontend kodot is irhatok Pascalban, nem kell a JS. :P Vagy ami meg fontosabb - meglevo kod is lefordithato barmilyen JS kornyezetre. Hiszen mindig kell egy nyelv, ami megvedi a befektetesed a kododban, es nem epit arra h. majd 25 junior ugyis ujrair mindent az aktualis trendi szarban.

My 2 cents.

-=- Mire a programozó: "Na és szerintetek ki csinálta a káoszt?" -=-

Ez most vicc? Egy népszerűségi statisztika? Együnk tehénszart, már megint még mindig; a legyek sose tévednek?

A népszerűségnek vajmi kevés köze van a használhatósághoz.

És egyébként is, mi az, hogy a fejlesztői környezeteknél úgy vannak a halmazok, hogy "windows, macOS, Unix/Linux, Other"? A macOS nem UNIX véletlenül? Legalább prefixálták volna egy "Other"-rel a "Unix/Linux"-ot...

Kész. Ennyi erővel szar a marsjáró robot, mert a kutya sem használja. Semmit nem jelent, hogy mit mennyien használnak, nem ettől jó, vagy rossz valami. Ennyi erővel akkor a winfostíz a legjobb desktop oprendszer a világon, hiszen azt használják a legtöbben, nem igaz? (Egyébként #define kutya sem, mert a Pascalnak még mindig többszázezres, ha nem milliós tábora van.)

És ezt a szerencsétlen felmérést ugye a világ összes fejlesztője kitöltötte. És nem csak 90 ezer, ahogy a cikkben írják. Én már nem tudom eldönteni, hogy most trollkodsz, vagy ennyire elment az eszed.

A folyamatos személyeskedésről nem is beszélve. Az én táborom? Basszam meg a Pascalt? Affinitáshiány? Mi fáj? (A seggeden kívül, úgy értem.) Valami nagyon becsípődhetett ott...

https://en.wikipedia.org/wiki/Software_engineering_demographics

"As of 2016, it is estimated that there are 21 million professional software developers."

És ezek csak a profik. Az amatőrök sokkal többen vannak. Meg sem próbálom megbecsülni, hogy mennyien, de 10-20x biztosan.

Na ebből 90 ezer kitöltötte. És a többi meg virágkötő. Szerinted. Na, ez az igazi affinitáshiány, amikor a valóságra nincs affinitásod.

Sz*rk: De ha csak a kérdőív adatait vesszük alapul: az SO saját bevallása szerint kb. 50 millió fejlesztő használja. És ebből töltötte ki kevesebb, mint 90 ezer. Vagyis az ottani fejlesztők kb. 1.8 ezreléke se. Kb. minden 560. ember. Szar ez a kérdőív, mert a kutya sem töltötte ki; legalábbis a saját "logikád" alapján.

És neked mi az erősséged? A fölényeskedés? Villants valamit python-kiddie...

Amúgy ha már statisztika: Csak a Lazarus fórumon több, mint 17 ezer ember van. Ez a 88 ezernek a ~20%-a. Ha ezek mind kitöltötték volna a szaros kérdőívedet, akkor máris ott tartanánk, hogy a fejlesztők usque 16% Pascalt használ. Nesze neked statisztika. Ráadásul minősített eset, mert ez mezei népszerűségi statisztika, semmi egyéb. És akkor a Delphis fórumokat és a többi progfórum Pascal "sarkait" nem számoltuk bele. Többszázezer Pascal user van a világon. (A milliót nem tudom eléri-e ez a szám, de az amúgyis feltételes módban volt.)

Ekkora örömet okoz, hogy belinkelték itt azt a szerencsétlen kérdőívet? Hát hirtelen nem is tudom eldönteni, hogy ez szánni vagy inkább irigyelnivaló-e...

Az megvan, hogy ezt a kérdőívet kevesebben töltötték ki, mint a fentebb említett Lazarus fórum lélekszáma?

Megy az utcán a kerdoiv, jonnek szembe az emberek. Megkerdeznek napkozben 100 embert, hogy "buzi-e vagy?".

Az eredmeny alapjan minden 10. ember buzi.

Nyilvanossagra hozzak az eredmenyt, hogy megkerdeztek 100 embert, ez ez alapjan az jott ki, amki kijott.

Erre jon egy barom, hogy a szomszed mmelegbarban naponta megfordul 1000 ember.

Itt le is zarnam a szalat.

Egyre jobbak az "érveid". Meg ne erőltesd magad, mert akkor még jobban fog fájni a segged.

Mi lenne, ha végre műszaki érvekkel próbálnád alátámasztani, hogy szar a Pascal és nem tennéd magad közröhej tárgyává azzal, hogy hevenyészett népszerűségi statisztikákkal akarod egy technolóigáról eldönteni, hogy jó-e vagy sem, amikor ez a fajta "érvrendszer" már évtizedekkel ezelőtt összedőlt, mindörökre. (Ami azt illeti, többször is: x86 és windóz, csak, hogy kettőt említsek...)

> Az megvan, hogy ezt a kérdőívet kevesebben töltötték ki, mint a fentebb említett Lazarus fórum lélekszáma?

Az megvan, hogy a forumod pont ugyanannyira nem reprezentativ, mint a linkelt felmeres? A Lazarus forumon nekem is van accountom, soha nem posztoltam, a jelszora sem emlekszem, es meg a Pascalhoz sem ertek.

Örülök, hogy átment a lényeg. (Nem.)

Nyilvánvalóan ugyanannyira nem reprezentatív, de mivel nem tellett tőle más, mint az értéktelen és érdektelen számokkal való dobálózás; dobtam én is egyet, csámcsogjon rajta... De ugye nem azt akartad mondani, hogy mindenki, aki oda regisztrált az sosem posztol és még a Pascalhoz sem ért? Csak mert a te regisztrációd ugyanannyira nem reprezentatív. Csak emlékeztetőül: hazugság, aljas hazugság és statisztika, ebben a sorrendben. Azt hoz ki belőle az ember amit akar. Ennek megfelelően, aki népszerűségi statisztikával akarja egy technológiáról eldönteni, hogy jó, vagy rossz, az egy idióta.

Az megvan, hogy ő ezzel próbálta bizonyítani, hogy szar a Pascal? Szeretnél hozzá csatlakozni?

> Szeretnél hozzá csatlakozni?

Roppant szorakoztato a thread, ahogy egymast gyozkoditek, de en inkabb kimaradnek belole. Csak megallapitottam, hogy te is azokkal a dolgokkal ervelsz, amit tole nem fogadsz el helyesnek (popularity contest), igy aztan nem is fogtok soha dulore jutni.

(De ha mar megkerdeztel, akkor zarojelben: szerintem egy nyelv nepszerusege eleg eros tenyezo egy uj projekt inditasakor, ugyanis nem mindegy, hogy nez ki a piac, amikor staffolni kell. Hiaba nem szigoruan muszaki erv, ertelmes Pascal codert sokkal nehezebben/dragabban kapok, mint valami mainstream nyelvhez ertot.)

> Csak megallapitottam, hogy te is azokkal a dolgokkal ervelsz, amit tole nem fogadsz el helyesnek (popularity contest), igy aztan nem is fogtok soha dulore jutni.

Ez így ebben a formában nem igaz. Kezdjük azzal, hogy én mindjárt az elején adtam neki egy tömör, velős műszaki összefoglalót. Nem volt bő lére eresztve, bele lehetett volna szakmailag kötni, akkor legalább lett volna mit védeni. De mit csinált ehelyett? Fölényeskedett. Bárminő érvek nélkül. Aztán kínjában benyögte, hogy "mert nem használják". És azóta a számokon lovagol. Én azokra próbáltam valami "ellenstatisztikát" adni, bár nem túl sok értelme van, mert ilyen méretű baromságra nem lehet épeszű választ sem adni. De én nem "érveltem" azokkal, amivel ő "érvelt". Érvelésnek itt nyoma sem volt, nem tudom feltűnt-e. Az nem érvelés, hogy irreleváns számokat böfög be cédula nélkül, én meg próbálok rávilágítani, hogy a népszerűség itt nem oszt és nem szoroz, ráadásul a felhozott statisztikák sem voltak reprezentatívak.

> (De ha mar megkerdeztel, akkor zarojelben: szerintem egy nyelv nepszerusege eleg eros tenyezo egy uj projekt inditasakor, ugyanis nem mindegy, hogy nez ki a piac, amikor staffolni kell. Hiaba nem szigoruan muszaki erv, ertelmes Pascal codert sokkal nehezebben/dragabban kapok, mint valami mainstream nyelvhez ertot.)

Az ugyan igaz, hogy Pascalost nehezebben és ennek megfelelően csak drágábban fogsz találni, mint pl. C++-ost, de ez ugyanúgy irreleváns, mert ettől a nyelv nem lesz szar, nem tudom érted-e. A nyelvről volt szó, hogy az mennyire penge, mennyire jó fejlesztésre és nem az, hogy Pistike miben reszeli odahaza a fingot.

Mert betegesen ragaszkodott hozzá, úgy elég nehéz másról; de ettől függetlenül a téma az volt, hogy miért szar a Pascal, szerinte. Erre nem tudott mást mondani, mint ezt a sok népszerűségi statisztikát. Én többször mondtam, hogy szeretnék látni pár műszaki érvet, nem tudom feltűnt-e. De nem volt neki egy sem. Neked van?

Alapvetően tetszik a cikk és a Pascal ág is nagyon-nagyon régi szép emlékeket idézett.

Megjegyzések:
- tetszett az a szófordulat, hogy "a nehezén már túl vagyunk, most jön a neheze". Akkor most túl vagyunk rajta vagy se? :-)
- a SignalHandlereknél mindenképpen célszerű megemlíteni, hogy a kód a lehető legtömörebb és leggyorsabb legyen. Ha kell, akár azon az áron is, hogy globális változót állít és majd a főhurok intézi, amit kell. (A példában a SIGTERM kezelést te is így oldottad meg.) Annak idején nem kevés időmbe és ősz hajszálamba került, hogy egy misztikus hibát megtaláljak, ami hol előjött, hol nem. A lényeg: ha előbb beesett egy újabb signal, mint ahogy az előző feldolgozása befejeződött volna, onnan kezdve a SignalHandler ledőlt és soha többet egyetlen ávett signalt sem dolgozott fel a daemon.
- a főhurokban a procit fölöslegesen nem pörgetjük, ez nagyon helyes - azonban a várakoztatásnak is ára van. Ha csak rövid ideig várakozunk, akkor megint csak kezdjük pörgetni a procit, ha viszont növeljük a várakozási időt, akkor a daemon lesz lomha, egyre nagyobb késéssel reagál. Tehát itt sokkal inkább célszerű megvizsgálni, hogy mi az az esemény, amelyet fel kell dolgozni, majd egy ezen eseményre specifikus függvényt érdemes használni, amely mindaddig vár, amíg az esemény be nemkövetkezik, akkor viszont azonnal visszatér. Hogy példával éljek: poll(), wait4(), ...

Hm. Amit a SignalHandlerről írtál, az nekem gyanús. Konkrétan amit leírtál az a klasszikus signal(2) rendszerhívás viselkedése(*), ellenben a sigaction(2)-t pont úgy tervezték meg, hogy ez kiküszöbölhető legyen. Erre szolgál a leírásban is emlegetett sa_mask - azaz, hogy mely signal-okat engedjük beesni a signalhandler futása során.

(*) A signal(2)-nál az volt a mondás, hogy a signalhandler-ben az első lépés legyen a signal újrahúzása, hogy ezt a problémát minimalizáljuk; hátulütője, hogy az első megszakítás beütése és a signalhandler tényleges elindulása (és így a újabb signal(2) hívása) között tetszőlegesen nagy lehet az időkülönbség az ütemező és a klasszikus időosztásosság miatt.

=====
tl;dr
Egy-két mondatban leírnátok, hogy lehet ellopni egy bitcoin-t?

Jogos. Tudtommal nincs. BSD-ken és Mac OS-en kqueue van, ami régebbi, és sokak szerint jobb is. Nem használtam még.

Végülis a select jobb lehet emiatt, hogy platformfüggetlen, viszont az epoll-t tényleg sokkal könnyebb használni, szóval ha nem kell platformfüggetlenség, akkor jó választás. Illetve ha igazán nagy teljesítmény kell, akkor muszáj ezeket az "új" API-kat használni.

Elvileg létezhetne platformfüggetlen lib a kqueue/epoll felett. Biztos van is, csak keresni kell. A java.nio például Linux alatt epoll-lal megy, Mac OS-en pedig kqueue-val. Tehát neki sikerült egy korrekt absztrakciót rátenni.

> - tetszett az a szófordulat, hogy "a nehezén már túl vagyunk, most jön a neheze". Akkor most túl vagyunk rajta vagy se? :-)

Azon sose. :P

> - a SignalHandlereknél mindenképpen célszerű megemlíteni, hogy a kód a lehető legtömörebb és leggyorsabb legyen. Ha kell, akár azon az áron is, hogy globális változót állít és majd a főhurok intézi, amit kell. (A példában a SIGTERM kezelést te is így oldottad meg.) Annak idején nem kevés időmbe és ősz hajszálamba került, hogy egy misztikus hibát megtaláljak, ami hol előjött, hol nem. A lényeg: ha előbb beesett egy újabb signal, mint ahogy az előző feldolgozása befejeződött volna, onnan kezdve a SignalHandler ledőlt és soha többet egyetlen ávett signalt sem dolgozott fel a daemon.

Ezt Zahy már közben megválaszolta.

> - a főhurokban a procit fölöslegesen nem pörgetjük, ez nagyon helyes - azonban a várakoztatásnak is ára van. Ha csak rövid ideig várakozunk, akkor megint csak kezdjük pörgetni a procit, ha viszont növeljük a várakozási időt, akkor a daemon lesz lomha, egyre nagyobb késéssel reagál. Tehát itt sokkal inkább célszerű megvizsgálni, hogy mi az az esemény, amelyet fel kell dolgozni, majd egy ezen eseményre specifikus függvényt érdemes használni, amely mindaddig vár, amíg az esemény be nemkövetkezik, akkor viszont azonnal visszatér. Hogy példával éljek: poll(), wait4(), ...

Ezt én is tudom, de az itt írt példák tényleg csak példák, nem muszáj így csinálni. Alapvetően én főszálba csak olyat rakok, aminek jó a lomha (kb. millisec nagyságrendű) reakcióidő is; ami pedig fontos, hogy gyors legyen, azt pedig külön threadbe szervezem. Régen persze a threadek használata sokat lassíthatott, de amióta többmagosak a gépek, azóta inkább gyorsítja a programot. (Értelemszerűen.)

az a gond, hogy a systemd óta kár ilyenekkel foglalkozni, hacsak az ember nem beágyazottban utazik.
Egyébként szép írás.

--
GPLv3-as hozzászólás.

Igen, én is akartam írni, hogy egyetlen systemd service fájllal bármilyen processzt daemonként lehet futtatni. Nyilván jól kell viselkednie: nuku terminál, nuku X, de ezenkívül bármit csinálhatunk.

Talán ilyen router kategóriájú gépeken fontos lehet, hogy oprendszer-támogatás nélkül csináljunk daemont.

De érdekes a leírás, hogy hogy is működnek ezek a dolgok Unix-szerű oprendszereken.

> az a gond, hogy a systemd óta kár ilyenekkel foglalkozni, hacsak az ember nem beágyazottban utazik.

Vagy esetleg túllát a Linux világon. Remélem a Postfix (Exim, qmail, Sendmail, OpenSMTPD, stb) vagy a ntpd vagy akár az OpenSSH fejlesztői nem dobálják ki a démonizálás képességét a szoftverükből csak azért, mert most épp dübörög a gazd izé a Pötter-ring.

=====
tl;dr
Egy-két mondatban leírnátok, hogy lehet ellopni egy bitcoin-t?

Elnezest, de netto agyhugykovet kapok ettol az ervrendszertol, hogy minek belerakni egy 40 eve ismert, kb. 50 sorbol megoldhato, jol mukodo metodust, mert a systemd (ami egy kurvanagy Linux-only binary blob) majd megoldja.

A legnehezebb dolog manapsag egy szoftverprojektben kozep-hosszu tavon az ertelmezheto dependency management, ergo minel kevesebb cuccra dependelsz amit par sorbol meg tudsz oldani a sajat kododban (mint a fenti peldaban), es nem arra szamitassz h. majd mindenfele system service feltakarit utanad, annal jobb. Es ez most egy aprosag, de lassan mindenben erre epitenek a szoftverfejlesztok: "nem baj ha lefagy, majd a service ujrainditja", "nem baj h. otven tonna fost kell melle-bundlezni majd betesszuk dockerbe, igy az 100K-s python cuccunk ugyan egy 800MB-s full Ubuntu image lesz vegul, de kit erdekel", "nem baj h. lassu, majd a 500000 lambda instance Amazonon (evi parszazezer dollarert, teszem hozza), gyorsan futtatja majd."

You had one job, f*ckers...

-=- Mire a programozó: "Na és szerintetek ki csinálta a káoszt?" -=-

Ezt megkönyvjelzőztem! Különösen jó, hogy sok kiegészítő komment van, amelyek szintén nagyon informatívak. Köszönet a munkáért.

> Sol omnibus lucet.

Szuper! Köszönet a munkáért! :-)

Sub!
Hasznos volt, a hozzászólások is :)

A szignálokkal egész sok baj van:
https://ldpreload.com/blog/signalfd-is-useless

Az aszinkron handler okozza a problémákat, mert megszakít mindent, rendszerhívást is.

Amikor nem kell gyors reakcióidő, illetve nem baj, hogy egy-egy ismétlődő szignált lenyel a rendszer (pl. SIGTERM, SIGWINCH, ilyenek közül egyet is elég elkapni), akkor nincs nagy gond. Pl. egy eseménykezelő ciklussal és sigpending()-gel időnként meg lehet nézni, hogy történt-e valami - emellett az összes többi esemény lehet azonnal kezelhető.

Van, amit fifo-val lehet megoldani, pl. felhasználói szignálokat ilyesmire váltanám.

Van, amit nem lehet máshogy megoldani, pl. SIGCHLD; itt úgy tűnik, benne marad némi versenyhelyzet, még a külön szálas megoldásban is. Plusz ott van a close()-probléma:
http://www.daemonology.net/blog/2011-12-17-POSIX-close-is-broken.html

A SIGCHLD pont megoldható, már csak azért is, mert tulajdonképpen nincs is rá szükség, ahogy a kapott siginfo-(k)ra sem.

Bármilyen child process-t is indítok, annak tudom a PID-jét. Ezeket tárolom egy listában. Ha beesik egy - vagy több - SIGCHLD, akkor - vagy akár periodikusan is - lehet végigiterálni a táblán és a child processeknek 0-ás signalt küldeni, ami a processeket nem bántja, de a kill() nullát ad vissza, ha a process még fut. Így pontosan tudja a program, hogy mikor melyik child process adta vissza return code-ját a parentjének, anélkül, hogy egyáltalán foglalkozna a SIGCHLD után kapott siginfo-kkal, vagy akár magával a signallal.

A meghatározatlan állapotban hagyott file descriptor viszont tényleg elég nagy gáz.

Az egyik elméleti probléma a PID-ek túlcsordulása lehet, bár a maximum PID-et már elég jól lehet emelni, és azért annak elég kicsi az esélye, hogy mióta kilépett egy processz, de még azelőtt, hogy ránéznél, a PID-jét elfoglalta egy másik. A másik meg az, hogy ha sok rövid életű processz van, akkor gyakran kell végig kill 0-zni az egész listát.

Őszintén már nem emlékszem, hogy mennyi volt a legtöbb worker processz, amit láttam, de sok. :)

Igazából azt gondolom, hogy tényleg kicsi az esélye annak, hogy ebből is versenyhelyzet legyen, de minél terheltebb rendszerről beszélünk, annál meredekebben emelkedik, mert a hatások egymást erősítik (a szabad PID-ek számának csökkenése, a lassulás, és a kezelendő lista növekedése).

De hirtelen én se látok jobb megoldást.

> Az egyik elméleti probléma a PID-ek túlcsordulása lehet, bár a maximum PID-et már elég jól lehet emelni, és azért annak elég kicsi az esélye, hogy mióta kilépett egy processz, de még azelőtt, hogy ránéznél, a PID-jét elfoglalta egy másik.

A PID-reoccupy-overlap nem probléma - nem lehet probléma - hiszen ezek child processek, a processünk kreálja őket, nem maguktól jönnek létre. Ennek megfelelően, ha egy frissen létrehozott child process visszakapott PID-je már szerepel a táblában, akkor egyértelmű, hogy az csak úgy lehet, ha a korábbi tulajdonos kiszállt és újra kiosztották a PID-jét. Ennek megfelelően az általad említett overlap nem lehetséges.

> A másik meg az, hogy ha sok rövid életű processz van, akkor gyakran kell végig kill 0-zni az egész listát.

Nem muszáj a SIGCHLD-ra várni, erre írtam fentebb, hogy akár periodikusan is lehet nézgeteni ezt a táblát, teljesen ignorálva a SIGCHLD signalokat. Akkor nincs signal-összeolvadás, nincsenek egymásba érő PID check ciklusok.

> Őszintén már nem emlékszem, hogy mennyi volt a legtöbb worker processz, amit láttam, de sok. :)

Hát ez azért elég tág tartomány. :)

> Igazából azt gondolom, hogy tényleg kicsi az esélye annak, hogy ebből is versenyhelyzet legyen, de minél terheltebb rendszerről beszélünk, annál meredekebben emelkedik, mert a hatások egymást erősítik (a szabad PID-ek számának csökkenése, a lassulás, és a kezelendő lista növekedése).

Lehet, de ha tényleg annyi processünk lesz, akkor IMHO előbb fogynak el a fizikai erőforrások a rendszer alól, mintsem, hogy érzékelhetővé váljon a PID lista kezelésének lassúsága.

> De hirtelen én se látok jobb megoldást.

Na, majd mérek egyet valamikor.

A könnyebb téma:

> Nem muszáj a SIGCHLD-ra várni, erre írtam fentebb, hogy akár periodikusan is lehet nézgeteni ezt a táblát, teljesen ignorálva a SIGCHLD signalokat. Akkor nincs signal-összeolvadás, nincsenek egymásba érő PID check ciklusok.

Egymásba érő PID check ciklusok tényleg nem lesznek. „Eseményösszeolvadás” - mondjuk így - mindenképp lehet, csak ha végignézzük a táblát, akkor már nem zavar. Igen, lehet periodikusan nézni, sőt, újabb ötlet:
- nincs aszinkron handler függvény (ahogy beszéltük)
- t időnként megnézzük, hogy a sigpending() mutat-e egyáltalán olyat, hogy SIGCHLD érkezett. Nem tudjuk, mennyi, de ez nem baj.
- ha épp nem, akkor végig se kell nézni. Ha igen, akkor kell.

Jut eszembe, a kill 0 helyett meg lehet nézni a processztáblát, hogy fölöslegesen ne zavarjuk a child-ok esetleges close()-ait, meg miegymást.

A nehezebb téma:

> ha egy frissen létrehozott child process visszakapott PID-je már szerepel a táblában, akkor egyértelmű, hogy az csak úgy lehet, ha a korábbi tulajdonos kiszállt és újra kiosztották a PID-jét.

No de honnan tudjuk, hogy a korábbi kiszállt-e egyáltalán?

> ha tényleg annyi processünk lesz, akkor IMHO előbb fogynak el a fizikai erőforrások a rendszer alól, mintsem, hogy érzékelhetővé váljon a PID lista kezelésének lassúsága.

Hogy érzékelhetővé válik a kezelés lassúsága, az nem baj, viszont egyre megbízhatatlanabb lesz. De az igaz, hogy addigra nagyobb bajaink is lesznek. :) Részemről kb. eljutottunk oda, hogy ami fontos, azt megoldottuk közösen - kösz; - a többi már nem túl gyakorlati, viszont elég nehéz kérdés, azt elengedem. :)

> Jut eszembe, a kill 0 helyett meg lehet nézni a processztáblát, hogy fölöslegesen ne zavarjuk a child-ok esetleges close()-ait, meg miegymást.

És mit érünk a processztábla megnézésével? Látunk benne egy X PID-ű processzt, aki vagy a mi leszármazottunk, vagy nem. Pont ezért jó a kill 0, mert csak a mi leszármazottaink esetén történik bármi is. Idézem a FreeBSD-féle man 2 kill -t:

===
The sig argument may be one of the signals specified in sigaction(2) or it may be 0, in which case error checking is performed but no signal is actually sent. This can be used to check the validity of pid.
===
Szóval nem zavarnánk senkit, hisz annyit ellenőriz a rendszer, hogy van-e ilyen pid, és van-e jogom neki signal-t küldeni. (gy.k.: leszármazottam, vagy éppen egy újra kiosztott PID-ről beszélünk, vagy épp vaktába tüzelek)

=====
tl;dr
Egy-két mondatban leírnátok, hogy lehet ellopni egy bitcoin-t?

> - t időnként megnézzük, hogy a sigpending() mutat-e egyáltalán olyat, hogy SIGCHLD érkezett. Nem tudjuk, mennyi, de ez nem baj.
> - ha épp nem, akkor végig se kell nézni. Ha igen, akkor kell.

Ez jogos, jó ötlet.

> Jut eszembe, a kill 0 helyett meg lehet nézni a processztáblát, hogy fölöslegesen ne zavarjuk a child-ok esetleges close()-ait, meg miegymást.

Ezt Zahy már megválaszolta, de én is leírtam még a cikkben, hogy

Ezt a legegyszerűbben úgy lehet abszolválni, ha küldünk neki egy 0-ás signalt (ld. később), amit maga a process nem fog megkapni és lekezelni, viszont az OS igen és ha a visszatérési érték nem 0, akkor a process már nem létezik, lehet törölni a fájlt és daemonizálni.

> No de honnan tudjuk, hogy a korábbi kiszállt-e egyáltalán?

Mert ha nem szállt volna ki, akkor a rendszer nem osztotta volna ki azt a PID-et még egyszer. A PID egy egyedi szám, nem létezhet belőle két egyforma egy processlistben. Ha mégis, akkor írunk az érintett rendszer fejlesztőinek, mert kifogtuk az évszázad bugját. :P
Egy PID-et csak akkor lehet újra kiosztani, ha az előző process már kiszállt. Ha el is fogynak a kiosztható PID-ek, mert annyi processünk van már egyszerre (esélytelen, előbb omlik össze a teljes rendszer a zéróra redukált erőforrások miatt, hacsak nem direkt limitáljuk le a max PID-et valami alacsony számra), akkor sem ismétlődő PID-eket fog az újabb spawn eredményezni, hanem egy gyönyörű -1-et, azaz a rendszer megtagadja az új child process létrehozását. (Vagy rosszabb esetben egy gyönyörű kernel panicot, vagy valami hasonló kaliberű rendszerösszeomlást.)

> Hogy érzékelhetővé válik a kezelés lassúsága, az nem baj, viszont egyre megbízhatatlanabb lesz.

Mitől? Attól nem lesz kevésbé stabil, hogy belassul; lassabb lesz a kiszálló process-ek után esedékes takarítás (ha van mit), de ez a stabilitást nem veszélyezteti.

> Részemről kb. eljutottunk oda, hogy ami fontos, azt megoldottuk közösen - kösz; - a többi már nem túl gyakorlati, viszont elég nehéz kérdés, azt elengedem. :)

Már érdemes volt kivesézni. :)