Ez már kezd az agyamra menni... Hogy kell lekérni Linux alatt a child visszatérési értékét, ha pipe is van? Rohadtul nem úgy működik, ahogy a man page állítja!
Pipe (és WNOHANG) nélkül minden okés:
ret = 0; if(!(pid = fork())) { ret = 1; printf("child ret %d\n", ret); exit(ret); } else if(pid > 0) { waitpid(pid, &ret, 0); printf("parent ret %d\n", ret); }
Kimenet:
child ret 1 parent ret 1
Na de nekem az kellene, hogy a gyerek futtasson le egy parancsot, ha nem sikerül, akkor írja ki a hibát, és mindezt a szülő pipe-al olvassa, ugyanakkor a visszatérési érték is kéne, hogy sikerült-e a futtatás.
Teljes példa:
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <errno.h> #include <sys/types.h> #include <sys/wait.h> int main(int argc, char **argv) { char output[4096]; int stdoutpipe[2], l, ret = 0; pid_t pid; if(pipe(stdoutpipe) < 0) { fprintf(stderr, "unable to create pipe?\n"); return 1; } if(!(pid = fork())) { dup2(stdoutpipe[1], 1); dup2(stdoutpipe[1], 2); #ifdef NOTOKAY ret = system("echosaaa"); #else ret = system("echo"); #endif if(errno) perror(NULL); printf("child ret %d\n", ret); exit(ret); } if(pid > 0) { close(stdoutpipe[1]); do { if((l = read(stdoutpipe[0], output, 4096)) > 0) write(1, output, l); } while(waitpid(pid, &ret, WNOHANG) != pid); close(stdoutpipe[0]); } else { fprintf(stderr, "unable to fork?\n"); close(stdoutpipe[0]); close(stdoutpipe[1]); } printf("parent ret %d exit %d\n", ret, WEXITSTATUS(ret)); (void)argc; (void)argv; return 0; }
Ez konstans 0-át ad vissza, akármi is a gyerek exit() paramétere.
$ gcc aaa.c -o aaa; ./aaa child ret 0 parent ret 0 exit 0 $ gcc -DNOTOKAY aaa.c -o aaa; ./aaa sh: line 1: echoaaa: command not found child ret 32512 parent ret 0 exit 0
Magyarán a waitpid NEM állítja a státuszt, akkor sem, amikor a gyerek pidjével tér vissza, holott a man page szerint kéne neki. Példától függően vagy konstans -1, vagy konstans 0 lesz minden exit() értékre a ret.
WTF? Miért nem adja vissza a gyerek státuszát a watpid, ha WNOHANG opcióval hívódott?
És ha ez normális, akkor hogy a francba kell lekérni a gyerek státuszát, ha olvasni is akarja az ember a kimenetét??? Alaposan átolvastam a man page-eket, nagyon nem így kéne működnie! Rengeteg példát is áttúrtam, de bakker, azok vagy csak a visszatérési értéket kérik le, vagy a kimenetet, de egyik sem mindkettőt!
Ami leírást meg github-ot találtam, ott mindenhol azt írják, így működnie kéne. De mégsem működik! Arról sehol nem szól fáma, hogy a waitpid hibás értéket adna vissza a status paraméterben.
Stackoverflow-al sem kerültem közelebb a megoldáshoz, ezt találtam, ami hasonló (neki is a waitpid()-el van baja, ha WNOHANG meg van adva, mondjuk ő 0-át kap vissza), habár ez epoll-al is meg van spékelve. A megoldási javaslat: "One possible implementation is to repeat waitpid(..., WNOHANG) until it returns the expected PID", na de hát én pont eleve ezt csinálom! Akkor WTF?
Hozzászólások
A man leírja, hogy a status értékét a WIFEXITED és WEXITSTATUS makrókkal kell nézni.
Osz mire mész vele, ha mindkét esetben csupa 1-es bit a status??? Gyerek "exit(0)", szülő status=0xFFFFFFFF; gyerek "exit(1)", szülő status=0xFFFFFFFF!!!
Tisztában vagyok vele, hogy alsó 8 bitnek a visszatérési értéket kéne tartalmaznia, ezt írja a man is, de rohadtul nem azt tartalmazza, pont ez a bajom! Azt nem tudom, hogy miért nem. Nincs semmi signal, az exit()-nek meg minden nyitott fájlleírót be kell zárnia, de manuálisan is bezárom őket.
Tegyél bele hibakezelést. Ráfut a break-re a ciklus? A waitpid negatív értéket ad vissza? Errno mit mond? Stb.
Ezeken már rég túl vagyok, nyilvánvaló dolgokért nem nyitottam volna topikot.
Nem.
Nem. 0-át ad vissza amíg fut a gyerek, amikor végzett, akkor meg a gyerek pid-jét.
0.
A parent oldalon valaszd kulon a do/while() loopot es a waitpidet. Ha child lezarta az stdout-jat mert kilepett exit()-tel akkor a parent oldali read() nullaval ter vissza. Ekkor megszakitod a while() ciklust, majd ezutan egy blocking waitpid()-del megnezed az exit statust:
ezt most csak ugy vakon beirtam, de ugy remlik hogy igy szoktam csinalni ezeket...
Ugye a do-while megoldasoddal az a gond hogy a read()==0 esetben megszakitod a ciklust, igy az a waitpid() nem is fog lefutni valojaban... es ha blocking read()-dal is meg nonblocking waitpid()-del is tesztelsz akkor az valojaban egy race condition ha jobban belegondolok.
Már próbáltam így sem működik. Ilyenkor mindig 139-et ad vissza, függetlenül attól, hogy mi volt a gyerek exit() paramétere.
Ahogy egmond kollega is irja lentebb: tudsz bemasolni ide egy minimal working example-t?
A while-ban való vizsgálatod miatt nem derül ki, hogy mi miatt nem a pid-del tér vissza, azaz 0-val vagy -1-gyel:
tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE
De kiderül, és már meg is írtam.
szerk.:
Én úgy csinálnám, hogy - mivel úgyis van pipe -on keresztüli kommunikáció - a child írjon bele a pipe-ba minden infót, amit a parent-nek tudnia kell. Pl. ebben az esetben a saját pid-jét és a system() visszatérési értékét.
s az output, ott van az exit code:
Már írtam, ha nincs pipe, minden jó... Próbáld meg pipe-al olvasva!
megy az pipe-pal is:
Nem is megy. És minek az fcntl meg az a tetves sok sleep? Felejtős, a gyerek által hívott programban nincs sleep.
Te már megint hazudsz a forrásodról. Nem ez az első eset!
ott az output, sleep nélkül... mi a túró nem megy?
na, elmész te a jó édesanyádba, akkor... tanulj meg programozni... balfasz@hup
Nulladik: tibyke nevében a -val, -vel ragot tessék átismételni!
Első: Minimal reproducible example-t mutass! Kiegészítettem a kódodat működőképesre, de egyrészt miért kéne ezzel időt töltenie annak aki segíteni akar neked?, másrészt nem ugyanazt kapom amit te. Olyan apróságok, mint például az "l" változó típusa, előjeles vagy előjel nélküli, alulcsordul-e a -1, hibás összehasonlítást eredményezve, is számíthat.
Továbbiak: A gyerekben printf() és perror() függvénnyel írsz a kimenetre / hibakimenetre, amik már a pipe-ra vannak átrakva. Hogy jelenik meg a "child ret 0" sor? Nem értem. Hacsaknem a csinalvalamitakimenettel() függvény átlapátolja, de akkor miért nem lapátolja át a "sh: line 1: ./hibasparancs: Permission denied" sort is? Biztos vagy benne, hogy a(z amúgy működésképtelenre csonkított) programod kimenetét pontosan mutattad meg nekünk?
A system(), meg a legtöbb függvény, tudtommal nem ígéri meg hogy siker esetén az errno-t békén hagyja, vagy nullázza. (Lehet hogy tévedek amúgy, a man page nem dokumentálja ezt.) (Kivételes esetben van csak értelme kézzel nullázni az errno-t. Nézz rá mások programjaira, (szinte) sose látsz ilyet, elég gáz lenne ha úgy kéne programozni hogy minden függvényhívás előtt az errno-t nullázni kellene kézzel.) Először a ret-et vizsgáljuk, és csak ha az hibát jelez akkor nézünk rá az errno-ra.
Na de a lényegre ránézve: Az, hogy a gyerek processz kilép, és az, hogy a pipe-on EOF-ot kapsz, az két teljesen független esemény, akármelyik irányba sok-sok idő eltelhet köztük.
Ha a gyerek szül magának egy gyereket (unokát a fő programod szemszögéből), akkor az eredeti gyerek kiléphet, a pipe még él és virul és bonyolíthat további forgalmat, miközben a waitpid() már jelenti hogy kilépett. Ugyanez megtörténhet úgy is, hogy a fájlleírót átpasszolja egy másik processznek (perverz, ritkán látott, de működőképes dolog).
Ha viszont a gyerek bezárja a pipe író végét, akkor még vígan élhet sokáig, a pipe olvasó vége EOF-ot kap.
Ha csak "simán" kilép a gyerek és nem csinált semmi szokatlant, akkor a fentiek miatt olyan race condition-ök vannak, hogy nem tudhatod, hogy a két esemény közül melyiket észleli elsőként a szülő processzed, esetleg az egyik forgatókönyv történik az esetek 99.99%-ában, a másik pedig 0.01%-ban, sok sikert az ilyen hibák utólagos levadászásához :)
A programod amúgy mindkét sorrendet hibásan kezeli. Koncepcionálisan nem gondoltad végig hogy mit akarsz csinálni.
Ha előbb veszed észre hogy kilépett a gyerek, akkor a pipe-ban ott ragadhat adat, amit sose olvasol ki. Például a gyerek kiír egyben 5kB-ot és rögtön kilép, a szülő beolvas 4kB-ot majd látja hogy kilépett a gyerek és nem olvas tovább.
Ha meg előbb veszed észre az adat végét, vagyis a read() visszatérési értéke 0, akkor kiugrasz a ciklusból, a waitpid() le se fut egyszer se, a pid-ben marad ami benne volt. Ez az amibe beleütközöl, szerintem.
> hogy a francba kell lekérni a gyerek státuszát, ha olvasni is akarja az ember a kimenetét???
Először legyen egy pontos specifikációd, hogy mit is szeretnél csinálni az imént említett esetekben. Például ha tudod hogy nem lesz unoka (vagy ha esetleg lesz, oké ha őt is meg kell várnod), akkor először olvass EOF-ig, utána jöhet a waidpid.
Sajnos egyébként nincs igazán jó általános válasz. Terminál emulátor programok szenvednek is emiatt. Egyrészt elvárás, hogy kilépjen, amint a gyerek kilépett, akkor is ha van unoka aki elvileg még írhat később a terminálra. Másrészt elvárás, hogy a gyerek összes kimenetét mindenképp feldolgozza - ez nem fontos akkor ha rögtön be is záródik az ablak, de fontos ha a terminál esetleg úgy van beállítva hogy nyitva tartsa az ablakot, vagy új shell-t indítson, vagy fontos hogy accessibility szoftver felolvassa a teljes szöveget stb. A luit program a -x kapcsolóval a felhasználóra bízza hogy kétféle működés közül válasszon: a gyerek kilépése számítson (adatvesztés lehetséges) vagy a csatorna bezárása számítson (unoka fogva tarthatja a vonalat). A VTE-ben (GNOME Terminal és társai) igazi fekete mágiát alkalmazunk: ha a gyerek kilépett, akkor még olvasunk legfeljebb 64kB-ot nem blokkoló módon, de abbahagyjuk az olvasást ha épp nincs adat: ezzel a módszerrel tuti (ööö... szinte tuti) kiolvasunk mindent amit a közvetlen gyerek írt, hiszen ha már kilépett akkor új cuccot már nem írhat, a régi meg legfeljebb 12kB körül lehet, kábé ekkora a kernelben a terminál vonal puffermérete; viszont ha van még unoka aki írhat a továbbiakban akkor arra nem várunk, ő már nem érdekel minket.
Mindenhol jól irtam! Idézd be, hogy mire irogatsz, ha kaffogni akarsz!
Azt tettem, csak a nyilvánvalót hagytam ki, hogy ne terelje el a lényegről a figyelmet.
Kurvára tök mindegy ebben a példában, mert a read csak egyetlenegyszer fut le és pozitív értéket ad vissza, de már írtam ezt... Egyébként meg a default C típus, tessék felcsapni a C szabványt és kikeresni, mi a default típus.
Na de a lényeg, ez tök nyilvánvaló, a kérdés nem is ez volt, hanem az, miért ad vissza a waitpid -1-et státusznak, amikor a gyerek kilép.
Ekkora bullshitet rég olvastam. Konkrétan 0 segítőgészség van az egész posztodban, csak ömlengés és digitális szemét. (Tisztázzuk, nincs race condition, egyáltalán, tudod, mi az? A waitpid a gyerek pid-jét adja vissza, arra lép ki a ciklus, de már leírtam ezt százszor.).
Nem tőled fog érkezni a megoldás, ebben egész biztos vagyok! >P
Örülök hogy segíthettem! Szívesen, máskor is!
Nem segítettél, csak zajt generáltál.
Így van; erre a hordozható megoldás a ppoll() / pselect(). A SIGCHLD-ot elkapjuk, de a programban végig maszkolva (blokkolva) tartjuk, csak a pselect / ppoll oldja fel ideiglenesen. Így tudunk multiplexelni a pipe IO-ra és a SIGCHLD-ra egyszerre. A SIGCHLD handler-ben max. egy volatile sig_atomic_t globális változót állítunk. A ppoll / pselect után lehet read(), write(), waitpid(). A waitpid()-nek nem kell WNOHANG (csak akkor hívjuk meg, ha volt SIGCHLD delivery a ppoll-on / pselect-en belül). Minimum a write()-nak viszont így is kell az O_NONBLOCK, mivel a ppoll / pselect már akkor is írhatóságot jelez pipe-on és named FIFO-n, ha csak PIPE_BUF byte-nyi hely van a pipe buffer-ben, viszont a write O_NONBLOCK nélkül pipe-on és named FIFO-n nem végez részleges írást, csak teljes buffert ír ki.
Teljes példa (de aki ennélkül nem érti a kérdést, attól aligha várható megoldás, csak megjegyzem...):
Ez konstans 0-át ad vissza, akármi is a gyerek exit() paramétere.
Magyarán a waitpid NEM állítja a státuszt, akkor sem, amikor a gyerek pidjével tér vissza. Jól látszik, hogy az az érték marad benne, ami volt.
Szóval a kérdés, hogy teljesen tiszta legyen mindenkinek: hogy kell egy gyerek processz kimenetét ÉS visszatérési értékét is megszerezni a szülőben?
angyalbegyerő, mi lenne, ha lekezelnéd azt az esetet, amikor 0-t ad vissza a waitpid?
Seggarc, le van már kezelve. Tanulj meg C-ül. Segítek: "if(pid > 0) { ..." és "......waitpid(pid, &ret, WNOHANG) != pid);".
s az leesett-e, angyalom, hogy 32512 az 0x7F00... az alsó fele 0... s neked azért lesz 0 a statusod
tedd csak oda bele, az exit elé, hogy ret = 123;
szívesen
Nicsak-nicsak, egy MRE amely nem ekvivalens az első postolt verzióval. Ugyanis a do cikluson belüli break kikerült. [Szerk: látom, azóta az eredeti postból is, így az eddig rá érkezett kommentek egy része nem igazán értelmezhető.]
A legeslegelső komment amit kaptál rámutatott egy hibára: a W* makrók hiányára. Visszadobtad a labdát azzal, hogy az nem oszt, nem szoroz, és emiatt hibásan hagytad azt a kódrészletet. Többen - köztük én - megmutattuk, hogy más hibák is vannak a kódodban.
Ezen más hibák közül egyhez hozzányúltál, a break eltűnt. És lám-lám most már nem csupa 1-es bit a gyerek visszatérési értéke.
Lehet hogy csak össze kéne raknod a puzzle-t? Hogy a kódodban több hiba is van, melyeket - mily meglepő - mindet javítani kéne? És esetleg nem zsigerből, fantasztikus tapasztalatoddal és rálátásoddal, remek minőségű kódodat lebegtetve lehülyézni azokat akik a több hibát nem mindet egyszerre veszik észre??
Nezd meg ezt: exit(ret); => exit(WEXITSTATUS(ret));
Igen, valóban ez az alapvető hiba a programban:
A két függvény specifikációja (kiemelés tőlem):
Tehát ha úgy hívjuk meg az exit()-et, hogy utána waitpid()-del akarjuk lekaszálni, akkor a 0..255 tartományba eső értéket "érdemes" neki adnunk. Ezt a feltételt a system() közvetlen visszatérési értéke pedig nem elégíti ki. (A debug kimenetből látszik, hogy a system() visszatérési értéke közvetlenül 32512; ahogy Micsa írta is, 0x7F00.)
Tehát a system() és az exit() között kell egy WIFEXITED() + WEXITSTATUS().
Ebből pedig kiderül a nagyobb probléma is: ebben a megközelítésben azt nem tudjuk visszaadni a parent-nek, ha a system() visszatérési értéke WIFSIGNALED()+WTERMSIG()-nek megfelelő (vagyis
a gyerek shell-gyerekének gyereke szignállal ért végeta gyerek shell-gyereke szignállal ért véget).Erre az intézményesített workaround / konvenció az, hogy az exit status-ban nem használunk 8 bitet, hanem csak 7-et; tehát normál (nem szignálos) befejeződésre minden utility 0-127 közötti értéket ad vissza. (Pontosabban 0-125 közöttit, ugyanis a 126-ot és a 127-et speciális értékeknek tekintjük.) És ha a system() azt mondja, hogy WIFSIGNALED(), akkor a gyerek azt az exit()-en keresztül úgy közli, hogy 128 + WTERMSIG(). (Érdekesség, hogy a legkülső szülő pl. a 128+11=139-es értékből nem tudja megkülönböztetni, hogy a gyerekben a system() által forkolt shell halt meg SIGSEGV-vel, vagy pedig a nevezett shell-nek a gyereke (a példában az echo). Utóbbi esetben a system() által índított shell végzi el a 128+WTERMSIG() összeadást, és a gyerek ezt az értéket csak továbbítja (mivel WIFEXITED()); előbbi esetben az összeadást a gyerek végzi el (mivel WIFSIGNALED())).
nem, itt az az alapvető hiba, hogy egyesek szociális képességeivel majorális problémák vannak...
🍿 azok a fránya makrók újra támadnak
// Happy debugging, suckers
#define true (rand() > 10)
Erdemben nem tok hozzaszolni, mert hala istennek nem vagyok programozo de az altalanos, h ti igy szivjatok egymas veret? :D:D (monduk a legdurvabb, h a kerdezo, aki segitseget ker ugye, hogy olt mindenkit...)
Egyiket sem kell félteni...
En is ugy latom :)