Training Solo (Paper, GitHub) je nejnovější bezpečnostní problém procesorů Intel s eIBRS a některých procesorů ARM. Intel vydal opravnou verzi 20250512 mikrokódů pro své procesory.
Byla vydána nová verze 25.05.11 svobodného multiplatformního video editoru Shotcut (Wikipedie) postaveného nad multimediálním frameworkem MLT. Nejnovější Shotcut je již vedle zdrojových kódů k dispozici také ve formátech AppImage, Flatpak a Snap.
Svobodný elektronický platební systém GNU Taler (Wikipedie, cgit) byl vydán ve verzi 1.0. GNU Taler chrání soukromí plátců a zároveň zajišťuje, aby byl příjem viditelný pro úřady. S vydáním verze 1.0 byl systém spuštěn ve Švýcarsku.
Spolek OpenAlt zve příznivce otevřených řešení a přístupu na 209. brněnský sraz, který proběhne tento pátek 16. května od 18:00 ve studentském klubu U Kachničky na Fakultě informačních technologií Vysokého učení technického na adrese Božetěchova 2/1. Jelikož se Brno stalo jedním z hlavních míst, kde se vyvíjí open source knihovna OpenSSL, tentokrát se OpenAlt komunita potká s komunitou OpenSSL. V rámci srazu Anton Arapov z OpenSSL
… více »GNOME Foundation má nového výkonného ředitele. Po deseti měsících skončil dočasný výkonný ředitel Richard Littauer. Vedení nadace převzal Steven Deobald.
Byl publikován přehled vývoje renderovacího jádra webového prohlížeče Servo (Wikipedie) za uplynulé dva měsíce. Servo zvládne už i Gmail. Zakázány jsou příspěvky generované pomocí AI.
Raspberry Pi Connect, tj. oficiální služba Raspberry Pi pro vzdálený přístup k jednodeskovým počítačům Raspberry Pi z webového prohlížeče, byla vydána v nové verzi 2.5. Nejedná se už o beta verzi.
Google zveřejnil seznam 1272 projektů (vývojářů) od 185 organizací přijatých do letošního, již jednadvacátého, Google Summer of Code. Plánovaným vylepšením v grafických a multimediálních aplikacích se věnuje článek na Libre Arts.
Byla vydána (𝕏) dubnová aktualizace aneb nová verze 1.100 editoru zdrojových kódů Visual Studio Code (Wikipedie). Přehled novinek i s náhledy a videi v poznámkách k vydání. Ve verzi 1.100 vyjde také VSCodium, tj. komunitní sestavení Visual Studia Code bez telemetrie a licenčních podmínek Microsoftu.
Open source platforma Home Assistant (Demo, GitHub, Wikipedie) pro monitorování a řízení inteligentní domácnosti byla vydána v nové verzi 2025.5.
Ahoj, potřeboval bych poradit, jak implementovat pipe do C. Jako příklad jsem napsal jednoduchý prográmek
#include <stdio.h> int main(int argc, char **argv) { const char size=16; FILE *f; char c,buffer[size]; f=fopen(*++argv, "r"); int i=0; while((c=getc(f)) != EOF){ if (i>=size) break; buffer[i]=c; i++; } printf("%s\n", buffer); fclose(f); return 0; }
u kterého funguje
$ ./programek hello.txt
hello world
ale potřeboval bych nějaký kód, ve kterém by fungovalo i
$ echo "hello world" | ./programek
hello world
Řešení dotazu:
f = argc > 1 ? fopen(*++argv, "r") : stdin;
... pominu-li různé kontroly na options, jestli je víc argumentů než jeden apod...
isatty
.
…jak implementovat pipe do C…
Huh? To, co popisuješ, není dýmka, nýbrž kočka.
A když už implementovat, tak co třeba
Takže třeba:
#include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <unistd.h> static int copyfd(const int source, const int dest) { char buffer[4096]; for (;;) { ssize_t to_write = read(source, buffer, sizeof(buffer)); if (to_write <= 0) { return close(source) == -1 || to_write == -1 ? -1 : 0; } const char *wbuffer = buffer; for (;;) { const ssize_t written = write(dest, wbuffer, to_write); if (written < 0) { close(source); return -1; } to_write -= written; if (to_write == 0) break; wbuffer += written; } } } int main(int argc, const char* const* argv) { int result = EXIT_SUCCESS; if (argc > 1) { for (++argv; *argv; ++argv) { const int fd = open(strcmp(*argv, "-") == 0 ? "/dev/stdin" : *argv, O_RDONLY); if (fd == -1) { perror(*argv); result = EXIT_FAILURE; continue; } if (copyfd(fd, STDOUT_FILENO) == -1) { perror(*argv); result = EXIT_FAILURE; } } } else { if (copyfd(STDIN_FILENO, STDOUT_FILENO) == -1) { perror("dýmka"); result = EXIT_FAILURE; } } return result; }
nečíst to po jednom znaku
Přes FILE
a getc()
se to nečte po jednom, ne?
$ echo ahoj | strace ./programek … read(0, "ahoj\n", 4096) = 5 read(0, "", 4096) = 0 … write(1, "ahoj\n\n", 6) = 6 …
(s těmi ostatními připomínkami souhlasím)
Jasně, bufferuje se to. Potud fajn.
Dokonce getc()
se může zainlajnovat, pokud ho (kni)hovny rozumně inlajnovatelně definují [[nevím; doufám]].
Ale ten test na EOF
po každém bytu tam prostě přebývat bude; to by musel být hodně zázračný kompilátor, aby tohle zefektivnil na úroveň práce s celými buffery.
(Když se data tak či tak interpretují a procházejí, je to úplně jedno; stejně se bude na každý byte sahat. Nicméně když to má být jenom dýmka nebo kočka, čtení po znacích není zrovna super. (Vzpomínám si, že Java to kdysi takhle měla, ale to bylo jenom v těch (kni)hovnách, do kterých se člověk proklikal z IDE a které tam byly jenom na ukázku, zatímco v praxi [[praxe == běh mimo debugging]] tam bylo něco kolem JIT + JNI, co takové věci provádělo rozumně.))
fread(..., 1, 1, ...)
a bylo to dost pomalé (operace s každým bajtem byla triviální, třeba řekněme sčítání). Overhead se stal zanedbatelným až když jsem freadem četl něco jako 8 bajtů najednou.
Pri kazdom volani fread dochadza k prepinany do kernelu (zvycajne, ale tiez C lib to trochu cachuje, takze kus menej prepinani)
Funkce fread() přece pracuje nad FILE
, ne nad surovým FD, takže tam už buffer je a – jak ukazuje strace výše – systémové volání read()
se volá méně často.
Jaký má smysl dělat další buffer v aplikaci? Je to opravdu jen kvůli té efektivnější kontrole na EOF, jak píše Andrej? Nebo je k tomu ještě nějaký jiný důvod?
Když chci mít buffer v aplikaci, není pak lepší se vykašlat na FILE
a pracovat rovnou s FD?
Když chci mít buffer v aplikaci, není pak lepší se vykašlat na FILE a pracovat rovnou s FD?s FILE su osetrene kadejake signali (takze ak nie si expert) tak je lepsie spracovat s tym. Samozrejme, ak sa da citat po blokov, tak citat po blokov.
Jaký má smysl dělat další buffer v aplikaci? Je to opravdu jen kvůli té efektivnější kontrole na EOF, jak píše Andrej? Nebo je k tomu ještě nějaký jiný důvod?Programoval si nieco? Svet nie je len o http streamoch so stahovanim suborov. Lepsie je, ak prikazy (trebars v nejakej GUI hre) prichadzaju po blokoch. Ak neuplny prikaz, tak sa docita a sparcuje. Zas aj sietovy protokol funguje ze sa posiela minimalne po blokoch (ak chces poslat bajt po siety, tak to stoji rovnako ak posles naraz 5B (po MTU). rovno pristup je citlevejsie na chyby - nejake chyba = poslanie signal() od OS (co casto byva kill aplikacie).
Pri kazdom volani fread dochadza k prepinany do kernelu
To právě při testu fread(..., 1, 1, ...)
nebude platit ani zdaleka. Tam bude spíš jiný problém, protože fread()
je obecná funkce, která bude mít pro speciální případ čtení jednoho byte oproti getc()
nebo fgetc()
dost velký overhead.
Pokud se těch dat kopíruje opravdu hodně a jde o výkon, tak už může podle situace být výhodnější použít třeba něco jako mmap()
, splice()
nebo sendfile()
.
fgetc()
a getc()
je zanedbatelný, fread(..., 1, 1, ...)
je skoro pětkrát pomalejší - volání jádra je samozřejmě stejně. Další zrychlení (asi na čtyřnásobek) se dá docílit použitím _unlocked
verzí, s tím už jsem se s fgetc_unlocked()
a getc_unlocked()
dostal asi na 1.7-1.8 GB/s (4GB soubor na tmpfs).
Tady jsou výsledky pro všechny možné varianty na 4GB souboru, kromě read_1
, kde jde o extrapolaci z výsledku pro 1GB soubor (pouštět to znovu na čtyřikrát větší se mi opravdu nechtělo).
Je to načtení celého souboru a aby se vyloučily nějaké špinavé triky optimalizace, udělá po bytech xor. Soubor je samozřejmě na tmpfs. Čte se to pomocí fgetc()
, getc()
, fread()
a read()
. U libc funkcí navíc i s unlocked variantami. U fread()
a read()
se čte po blocích o 1B, 4KB nebo 1MB.
fgetc 8.315s 492.6MB/s fgetc_unlocked 2.237s 1831.2MB/s getc 8.247s 496.7MB/s getc_unlocked 2.236s 1831.5MB/s fread_1 70.633s 58.0MB/s fread_4K 1.521s 2693.5MB/s fread_1M 1.585s 2584.1MB/s fread_unlocked_1 5.439s 753.1MB/s fread_unlocked_4K 1.493s 2743.5MB/s fread_unlocked_1M 1.644s 2491.6MB/s read_1 750.744s 5.5MB/s read_4K 0.531s 7714.1MB/s read_1M 0.400s 10237.2MB/s
Zajímavé výsledky jsou víceméně jen dva:
{f,}getc_unlocked() není nijak propastně pomalejší než fread()
nebo fread_unlocked()
s rozumnou velikostí bloku
fread()
s příliš velkým blokem může být i pomalejší než s 4KB (totéž platí i pro read()
, ale tam je optimální velikost větší)Zbytek je podle očekávání:
read()
po 1B vychází naprosto tragickyread()
)fread_1
, read_1
)Ano, tak už to bývá, že kočka není multiplatformní. Vnější projevy kočky multiplatformní jsou a musí být, ale kočka samotná ani moc ne.
To /dev/stdin
je tam — zcela záměrně — kvůli chování podobnému cat
v podivných okrajových případech. Třeba cat - -
, cat - - -
a tak podobně. Samozřejmě je to okrajová hloupost a neexistuje jedno jediné správné chování v takové situaci, takže nezbývá než zvolit prostě nějaké chování.
Nebude STDIN_FILENO
(či stdin
) přenositelnější než /dev/stdin
?
Navíc u toho STDIN_FILENO
používáš již otevřený FD.
cat - -
" při spuštění z terminálu (je mu potřeba ukončit vstup dvakrát). Ale i to by asi šlo napsat bez spoléhání na /dev/stdin
Já si chování kočky představuju tak, že bez parametrů bere stdin
a posílá to na stdout
, bez velkého přemýšlení, zatímco s parametry vždycky něco otevírá (od čehož se bude odvíjet taky počáteční stav věcí kolem lseek()
a tak.
Samozřejmě ten případ s několika pomlčkami nedává rozumný smysl, takže asi nestojí za to přehnaně se jím zabývat.
Jo, šlo by to klidně i jenom pomocí STDIN_FILENO
a bez /dev/stdin
. Pokud je standardní vstup seekovatelný, můžu si zapamatovat pozici, někam to převinout a kdesicosi. Pokud standardní vstup není seekovatelný, je úplně jedno, jestli ho znovu otevírám nebo ne; výsledek už nemůže být stejný. (Například když je to dýmka.)
Že používám už otevřený deskriptor, to je normální. Můžu třeba něco rozečíst a potom to dočíst kočkou. Například: Vypiš všechny řádky z /etc/passwd
pod uživatelem ntp
.
( while IFS=: read user discard; do [[ "$user" = 'ntp' ]] && break done cat ) < /etc/passwd
Tady^^^ se využívá toho, že vstup už je otevřený a v nějaké pozici.
ssize_t to_write = read(source, buffer, sizeof(buffer)); if (to_write <= 0) { return close(source) == -1 || to_write == -1 ? -1 : 0; }
EINTR
anyone?
Ne. Až tomu bude chtít uživatel opravdu posílat signály, pak ať si to ošetří. Do té doby je to úplně jedno.
A ani pak ať to raději explicitně neošetřuje — k tomu máme SA_RESTART
, ne?
Implicitně je reakce na podstatné signály celkem předvídatelná — u SIGHUP
/ SIGBUS
/ SIGPIPE
(tedy u toho, co opravdu v praxi může přijít samo od sebe a má to význam) beztak nejde o stav, ve kterém by se mělo něco zkoušet znova.
U ostatních signálů platí, že EINTR
(nebo raději a lépe SA_RESTART
) budu řešit jedině tehdy, pokud budu mít aspoň jeden netriviální handler signálu.
SIGWINCH
(protože se změní velikost terminálu), tak skončí "chybou"? Podle mne ne.
V pořádku je to, co neodporuje specifikaci. Bez specifikace je v pořádku téměř cokoliv.
Zkoušel jste vůbec něco z uvedeného? Nebo jenom zbytečně střílíte od pasu a úplně vedle? To druhé, že jo!
Ne, neselže to na SIGWINCH
, protože implicitní akce je Ign
. EINTR
se týká jen případů, kdy proběhne handler. To není tento případ.
Jednoduchý test 1: ./program /dev/zero > /dev/null
(a změnit velikost okna, a zkusit Ctrl+Z a pak fg
)
Jednoduchý test 2: ./program
(a psát ptákoviny na terminálu a změnit mezitím velikost okna a párkrát zkusit Ctrl+Z a fg
)
Ne a ne selhat, co???
No a pak třeba poslat kill -USR1 ...
Teprve tohle ten proces (celkem správně) zabije, protože implicitní akce je Term
.
Jak už jsem psal níže: „Potřeba“ ošetřit EINTR
pramení ze všeho nejčastěji z nepochopení problematiky.
Jednoduchý test 3: Pomocí sigaction()
si nastavte jednoduchý handler na SIGWINCH
. Teprve s handlerem to odletí na SIGWINCH
—> EINTR
. S implicitní akci (Ign)
rozhodně ne.
Takže résumé ještě jednou: Uživatel si EINTR
ošetří až teprve tehdy, kdy bude chtít posílat a ošetřovat signály. Nebo se na to (raději a lépe) vybodne a nastaví si SA_RESTART
. Ale to až bude používat vlastní handlery; dřív to ani nejde.
Ne a ne selhat, co???
A zkusil jste se taky podívat pomocí strace
, co přesně se tam děje? Viděl byste totiž něco jako
24241 read(0, 0x7ffcf84071d0, 4096) = ? ERESTARTSYS (To be restarted if SA_RESTART is set) 24241 --- SIGWINCH {si_signo=SIGWINCH, si_code=SI_KERNEL} --- 24241 read(0, 0x7ffcf84071d0, 4096) = ? ERESTARTSYS (To be restarted if SA_RESTART is set) 24241 --- SIGWINCH {si_signo=SIGWINCH, si_code=SI_KERNEL} --- 24241 read(0, 0x7ffcf84071d0, 4096) = ? ERESTARTSYS (To be restarted if SA_RESTART is set) 24241 --- SIGCONT {si_signo=SIGCONT, si_code=SI_USER, si_pid=22361, si_uid=1000} --- 24241 read(0, 0x7ffcf84071d0, 4096) = ? ERESTARTSYS (To be restarted if SA_RESTART is set)
Jestli chcete psát své programy tak, aby byla funkčnost závislá na tom, že vždy poběží na systému, který bude nastavovat flagy přesně tak, jako ten jeden, na kterém jste si to empircky vyzkoušel, je to váš problém. Jen to, prosím, neučte ostatní.
EINTR se týká jen případů, kdy proběhne handler.
Ne, u read()
se EINTR
se týká jakéhokoli případu, kdy je syscall přerušen signálem, aniž by přečetl data. Máte to napsané i v manuálové stránce, možná by bylo lepší si ji přečíst, než začnete psát věci jako
„Potřeba“ ošetřit EINTR pramení ze všeho nejčastěji z nepochopení problematiky.
Jinak taková věta vyzní poněkud ironicky.
A zkusil jste se taky podívat pomocí strace, co přesně se tam děje? Viděl byste totiž něco jako
Napadlo vás někdy, že syscally z strace
jsou interní implementační detail, který se může a nemusí promítnout do výsledného chování toho API v C?
Máte to napsané i v manuálové stránce, možná by bylo lepší si ji přečíst
Nemám.
Takže nejenom střílíte od pasu, ale ani tu manuálovou stránku jste si nepřečetl. Výborně! Tak tohle prosím hlavně neučte ostatní.
Pojďme se, prosím, podívat do té manuálové stránky (kterékoliv, která pojednává o EINTR
).
EINTR While blocked waiting to complete an open of a slow device (e.g., a FIFO; see fifo(7)), the call was interrupted by a signal handler; see signal(7).
Co tam^^^ stojí? Signal handler.
Už? "signal handler" != "signal"
Napadlo vás někdy, že syscally z strace jsou interní implementační detail, který se může a nemusí promítnout do výsledného chování toho API v C?
A napadlo někdy vás, že jste prostě jen měl štěstí a zdědil SA_RESTART
flag od bashe?
Nemám. Takže nejenom střílíte od pasu, ale ani tu manuálovou stránku jste si nepřečetl.
Četl - a na rozdíl od vás dostatečně pozorně. Zkusme začít s read(3p)
, tj. oficiální posixovou dokumentací:
EINTR
The read operation was terminated due to the receipt of a signal, and no data was transferred.
Ať čtu, jak čtu, žádné slovo "handler", bez ohledu na tučnost nebo velikost, tam není.
No a když se podíváme na read(2)
, tedy linuxovou verzi:
EINTR
The call was interrupted by a signal before any data was read; see signal(7).
Zase žádné handler, bez ohledu na font. V signal(7)
sice slovo handler je, ale z kontextu je zřejmé, že tím není myšlen pouze vlastní handler, který by si proces zaregistroval pomocí signal()
nebo sigaction()
, ale handler obecně, což zahrnuje i případ ignorovaného signálu.
Napadlo vás někdy, že syscally z strace jsou interní implementační detail, který se může a nemusí promítnout do výsledného chování toho API v C?A napadlo někdy vás, že jste prostě jen měl štěstí a zdědil
SA_RESTART
flag od bashe?
Vaříte z vody. Už zase jste to totiž nedočetl:
A child created via fork(2) inherits a copy of its parent's signal dispositions. During an execve(2), the dispositions of handled signals are reset to the default; the dispositions of ignored signals are left unchanged.
S tím čtením prostě nemůžete skončit v půlce, po první větě. Je třeba to dočíst.
Nebo, když už v tom tápete, můžete si to přece snadno vyzkoušet, ne? Tak, jestlipak se SA_RESTART
dědí?
#define _POSIX_C_SOURCE 200809L #include <signal.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> static void print_sigaction(const char* name, const struct sigaction* sa) { printf("%s %s SA_RESTART\n", name, sa->sa_flags & SA_RESTART ? "has" : "doesn't have"); } static void useless(int sig) { puts(strsignal(sig)); } int main(int argc, char* const* argv) { struct sigaction old_sa; if (argc == 1) { // BEFORE exec const struct sigaction new_sa = { .sa_handler = useless, .sa_flags = SA_RESTART, }; if (sigaction(SIGWINCH, &new_sa, &old_sa) == -1) { perror(argv[0]); return EXIT_FAILURE; } print_sigaction("old sigaction BEFORE exec", &old_sa); print_sigaction("new sigaction BEFORE exec", &new_sa); char* const one_empty[] = {argv[0], "", NULL}; if (execv(argv[0], one_empty) == -1) { perror(argv[0]); return EXIT_FAILURE; } } else if (argc == 2) { // AFTER exec if (sigaction(SIGWINCH, NULL, &old_sa)) { return EXIT_FAILURE; } print_sigaction("old sigaction AFTER exec", &old_sa); } else { return EXIT_FAILURE; } return EXIT_SUCCESS; }
Kvízová otázka za 10 bodů: Co tohle^^^ vypíše?
old sigaction BEFORE exec doesn't have SA_RESTART new sigaction BEFORE exec has SA_RESTART old sigaction AFTER exec doesn't have SA_RESTART
Pro příště: Nevymýšlejte si, prosím, ptákoviny. Ptákoviny (jako že se dědí
SA_RESTART
) se snadno vymýšlejí, ale obtížně vyvracejí, protože to můžou být přece jen okrajové případy a signal disposition může znít příliš vágně. Příští tvrzení tohoto druhu bych prosil s manuálovou stránkou (bez vynechaného kontextu) a se snippetem, který to ukazuje.
Nemám. Takže nejenom střílíte od pasu, ale ani tu manuálovou stránku jste si nepřečetl.Četl - a na rozdíl od vás dostatečně pozorně. Zkusme začít s
read(3p)
, tj. oficiální posixovou dokumentací:EINTR
The read operation was terminated due to the receipt of a signal, and no data was transferred.Ať čtu, jak čtu, žádné slovo "handler", bez ohledu na tučnost nebo velikost, tam není.
Takže ani tu POSIXovou stránku neinterpretujete správně, jak jste se koneckonců už sám přesvědčil experimentem, že?
Tentokrát vám unikl přesný význam pojmu receipt of a signal; do této kategorie se implicitní akce Ign
nepočítá.
Výmluvné jsou také některé kousky textu, které najdete v man 7 signal
:
Interruption of system calls and library functions by signal handlers If a signal handler is invoked while a system call or library function call is blocked, then either: ...
Klíčová slova: handler, invoked
No a když se podíváme na
read(2)
, tedy linuxovou verzi:EINTR
The call was interrupted by a signal before any data was read; see signal(7).
Tedy najednou má specifická linuxová verze (o které jste se už sám přesvědčil, že je nepřesná) přednost? Jenže o pár komentářů výše jste napsal toto:
Jestli chcete psát své programy tak, aby byla funkčnost závislá na tom, že vždy poběží na systému, který bude nastavovat flagy přesně tak, jako ten jeden, na kterém jste si to empircky vyzkoušel…
Aha? Jenže najednou má podle vás linuxová verze manuálové stránky jakousi váhu? Nezměnil jste nějak rychle názor, doslova obratem?
Zase žádné handler, bez ohledu na font. V
signal(7)
sice slovo handler je, ale z kontextu je zřejmé, že tím není myšlen pouze vlastní handler, který by si proces zaregistroval pomocísignal()
nebosigaction()
, ale handler obecně, což zahrnuje i případ ignorovaného signálu.
Proto jsou ty POSIXové stránky většinou lepší než linuxové. Rozlišují velmi důsledně pojmy action, handler a signal. To jsou velmi důležité nuance.
Upřímně řečeno, já jsem opravdu (ale opravdu (ale opravdu ...)) ten poslední, komu má smysl cpát tohle profláknuté EINTR
-náboženství. Jsem na něj alergický hlavně proto, že zbytečné rádoby-ošetření EINTR
vyhazuju z kódu už asi tak dekádu. Vyhazuju to vidlema. Vyhazuju to oknem. Vyhazuju to vraty. Splachuju to do hajzlu. Ale ono se to vždycky vrátí komínem, protože někdo (už zase) nepochopil, že POSIX je navržený tak, aby se s tím API snadno / rychle / korektně programovalo, nikoliv tak, aby se člověk musel na 50 řádcích věnovat ošetření signálů, než si přečte jeden soubor.
OK, nezdědí se, tak za to holt může glibc, ale to je ve výsledku úplně jedno. Pořád to není garantované chování, na které by se bylo možné spolehnout.
Takže ani tu POSIXovou stránku neinterpretujete správně, jak jste se koneckonců už sám přesvědčil experimentem, že?
Experimentem se můžu přesvědčit nanejvýš o tom, že se v jednom konkrétním případě (nebo několika) ten syscall defaultně restartuje, i když to tak explicitně nenastavím. Na rozdíl od vás mi takový experiment nestačí k závěru, že se na to dá spoléhat vždy a všude.
Tentokrát vám unikl přesný význam pojmu receipt of a signal; do této kategorie se implicitní akce Ign nepočítá.
Vůbec ne, to je jen vaše interpretace. A strace vás usvědčuje z omylu, protože je z něj naprosto jasně vidět, že ten syscall přerušen byl a vy jste to nepoznal jen proto, že byl restartován.
Tedy najednou má specifická linuxová verze (o které jste se už sám přesvědčil, že je nepřesná) přednost? Nezměnil jste nějak rychle názor, doslova obratem?
A to jste si vymyslel jakým myšlenkovým pochodem? Jen jsem vám ukázal, že obě verze manuálové stránky (1) v rozporu s vaším trvzením EINTR
zmiňují a (2) o žádném handleru nemluví. A pokud, ja sám tvrdíte, upřednostňujete posixovou dokumentaci tak to signal(7) není.
že zbytečné rádoby-ošetření EINTR vyhazuju z kódu už asi tak dekádu. Vyhazuju to vidlema. Vyhazuju to oknem. Vyhazuju to vraty. Splachuju to do hajzlu.
Inu, jen si vyhazujte, splachujte, jak je ctěná libost. Jen se pak holt nedivte, až vás s tím někde vyhodí maintainer, který na rozdíl od vás nechce spoléhat na to, že vaše emprická zkušenost ("zkusil jsem to a funguje mi to") má univerzální platnost.
Mně může být koneckonců jedno, co si děláte na svém písečku, to je váš problém. Pokud vás nepřesvědčil ani naprosto jednoznačný výstup strace, pak asi nemá smysl, abych se o cokoli snažil. Žijte si blaze.
Vůbec ne, to je jen vaše interpretace. A strace vás usvědčuje z omylu, protože je z něj naprosto jasně vidět, že ten syscall přerušen byl a vy jste to nepoznal jen proto, že byl restartován.
Zdá se, že nechápete, že to API nepracuje (navenek) s konceptem syscallů (které POSIXové API jo?) a že interně si může bez vašeho vědomí (ne)používat syscally, jak (ne)chce. No, tak to prostě je.
A to jste si vymyslel jakým myšlenkovým pochodem? Jen jsem vám ukázal, že obě verze manuálové stránky (1) v rozporu s vaším trvzením EINTR zmiňují a (2) o žádném handleru nemluví. A pokud, ja sám tvrdíte, upřednostňujete posixovou dokumentaci tak to signal(7) není.
Obě manuálové stránky jsou v souladu s mým tvrzením. Že jste je nepochopil a/nebo nedočetl, to už není můj problém.
Inu, jen si vyhazujte, splachujte, jak je ctěná libost. Jen se pak holt nedivte, až vás s tím někde vyhodí maintainer, který na rozdíl od vás nechce spoléhat na to, že vaše emprická zkušenost ("zkusil jsem to a funguje mi to") má univerzální platnost.
Nebojte se, nevyhodí. Chápe totiž psaný text, chápe, jak tohle API funguje, chápe, jak tenhle svět funguje, atd.
Mně může být koneckonců jedno, co si děláte na svém písečku, to je váš problém. Pokud vás nepřesvědčil ani naprosto jednoznačný výstup strace, pak asi nemá smysl, abych se o cokoli snažil. Žijte si blaze.
Jednoznačný výstup strace
? To má být vtip? Vy opravdu nechápete rozdíl mezi rozhraním a implementací? To myslíte fakt vážně? Uf. To snad ne.
Tentokrát vám unikl přesný význam pojmu receipt of a signal; do této kategorie se implicitní akce Ign
nepočítá.
A odkázal bys pls, kde je tohle v POSIXu deklarováno?
Osobně bych taky očekával, že pokud jsem si nenastavil signal handlery, neměl by EINTR
nastat, ale zajímalo by mě, jestli to POSIX skutečně takhle říká.
No jinak ale samozřejmě je tu stále ten argument, že i když program aktuálně žádný signal handlery nemá, někdo může přidat handler v budoucnu a pak procházet kód a hledat syscally je opruz. (Už jsem to musel dělat na netriviálním SW a není to dobrý.)
Tentokrát vám unikl přesný význam pojmu receipt of a signal; do této kategorie se implicitní akceA odkázal bys pls, kde je tohle v POSIXu deklarováno? Osobně bych taky očekával, že pokud jsem si nenastavil signal handlery, neměl byIgn
nepočítá.EINTR
nastat, ale zajímalo by mě, jestli to POSIX skutečně takhle říká.
SIG_IGN
: Ignore signal.
Delivery of the signal shall have no effect on the process.
Tohle^^^ je implicitní nastavení třeba zrovna pro SIGWINCH
. Kdyby se procesu zničehonic přerušilo blokující volání s chybou EINTR
, byl by to setsakra veliký effect. Specifikace ovšem nařizuje no effect.
Nikde to POSIX neříká zpříma v jedné větě. Je to rozprostřené v drobných vágních náznacích na mnoha místech v popisu signálů. (Proto v tom má tolik lidí nejasnosti a proto se tyhle „chyby“ (ošetření chyb, které nenastanou) vidí tak často.)
No jinak ale samozřejmě je tu stále ten argument, že i když program aktuálně žádný signal handlery nemá, někdo může přidat handler v budoucnu a pak procházet kód a hledat syscally je opruz. (Už jsem to musel dělat na netriviálním SW a není to dobrý.)
Na tohle mám jednoduchý protiargument: Zbytečné ošetření neexistujícího EINTR
přidává netestovaný / netestovatelný kód, který nikdy neběžel. Neměl jak. Někde tam sedí a čeká, může být úplně špatně, může se zacyklit napořád, může leakovat paměť, může to celé shodit.
Pár let to takhle funguje. Pak někdo přidá handler na SIGWINCH
… (Nebo na jiný implicitně ignorovaný signál.) Najednou se vrátí chyba EINTR
. A celé to zařve, spousta věcí bude špatně, třeba to nezopakuje správně zápis a poškodí integritu dat atd. A bude se to fakt špatně debuggovat.
Lepší scénář: EINTR
zprvu nikdo neošetřuje — což je v nepřítomnosti handlerů správně —, později se přidá handler signálu, nastane EINTR
a celé to korektně odletí s chybou EINTR
. V té chvíli se bude (teprve) řešit osetření EINTR
— správné, testovatelné, projde valgrindem při změnách velikosti okna atd. A od té doby to bude fungovat šťastně a spokojeně se SIGWINCH
—> EINTR
, bez ošklivých překvapení.
Zbytečné ošetření neexistujícího EINTR
přidává netestovaný / netestovatelný kód, který nikdy neběžel. Neměl jak.
Myslíš ten loop? Vždyť je to poměrně velmi jednoduchá věc, nakterou by nemělo být velký problém napsat makro (v GNU libc už existuje), to otestovat a pak prostě nasadit víceméně kdekoliv. Nebo v čem tam vidíš tu kompliaci?
Lepší scénář:Jak celé to korektně odletí? TenEINTR
zprvu nikdo neošetřuje — což je v nepřítomnosti handlerů správně —, později se přidá handler signálu, nastaneEINTR
a celé to korektně odletí s chybouEINTR
.
EINTR
může nastat někde kdesi daleko v kódu třeba někdy o roky později. Jak se dozvíš, na všech místech v programu (který může být rozsáhlý), kde by mohl nastat EINTR
, je tato možnost správně ošetřena?
Např. bys mohl spouštět testy a u toho mít v test harnessu nějaký background thread, který bude spamovat signály. Všimni si ale, že tím se zároveň řeší i tvůj problém s netestovaným/netestovatelným kódem.
Např. bys mohl spouštět testy a u toho mít v test harnessu nějaký background thread, který bude spamovat signály. Všimni si ale, že tím se zároveň řeší i tvůj problém s netestovaným/netestovatelným kódem.
Tak potom bych vůbec nic nenamítal a všechno by bylo v nejlepším pořádku.
Jen jsme se tím dostali příliš daleko od snippetu v mém původním komentáři, co do rozsahu a celkové složitosti příslušného softwaru.
Že něco korektně odletí, tím myslím, že to ohlásí rozumnou chybu, zavře správně (aspoň) všechno, co nezavírá samotný konec programu (různé IPC prostředky), nenadělá paseku v souborech nebo jiných „perzistentních“ datech a neskončí karambolem typu SIGSEGV
, SIGILL
ani ničím podobným. (SIGABRT
je tak na hraně; pokud to má někdo dobře ošetřené, tak fajn.)
read
/write
můžou vrátit 0 nebo -1, i když je všechno v pořádku (jenom přišel signál), a tedy ho stačí zavolat znovu a zápis/čtení proběhne?
Takže pro read by podmínka měla být
ssize_t r = read(...); if (r == 0) // konec souboru if (r < 0 && errno != EINTR) // chyba // jinak pokračovat ve čtení na pozici buf+rA pro write
ssize_t r = write(...); if (r <= 0 && errno != EINTR) // chyba // jinak pokračovat v zápisu na pozici buf+MIN(0,r) (fakt? protože r může být -1 a errno EINTR?)?
Pokud dostanu chybu a errno je EINTR
, pak se "nic nestalo" a typická akce je zopakovat to volání se stejnými parametry. V glibc je na to makro TEMP_FAILURE_RETRY()
:
ret = TEMP_FAILURE_RETRY(read(...));
ten read()
opakuje, tak dlouho, dokud buď neuspěje nebo nedostane jinou errno než EINTR
. Pokud to budeš dělat ručně (a s jednou smyčkou), tak je prostě potřeba v případě -1
/EINTR neupdatovat pozici (ani zbývající délku).
if (r <= 0 && errno != EINTR)
Na tohle taky pozor. Standard pouze stanoví, že skončí-li volání chybou, nastaví se errno
na odpovídající hodnotu, ale neříká nic o tom, co se s errno
stane, pokud volání uspěje. Empirická zkušenost je taková, že obvykle zůstane beze změny, a už jsem párkrát viděl kód, který na to spoléhal (nastavil před voláním errno
na nulu a testoval hodnotu po návratu). Obecně to ale není zaručeno a i když jsem takovou implementaci v praxi neviděl, teoreticky je možné, že hodnota po úspěšném volání nebude ani nula, ani původní.
Standard pouze stanoví, že skončí-li volání chybou, nastaví seTakže by to mohlo nastaviterrno
na odpovídající hodnotu, ale neříká nic o tom, co se serrno
stane, pokud volání uspěje.
errno = EINTR
po neúspěšném volání a po úspěšném ho nezměnit, tzn. že by TEMP_FAILURE_RETRY
vedl na nekonečnou smyčku?
read
.
Ne, to makro testuje obě podmínky, tj. opakuje jen pokud se argument vyhodnotí jako -1
a navíc je errno
rovno EINTR
. Na mém systému např. vypadá takhle (je v /usr/include/unistd.h
):
/* Evaluate EXPRESSION, and repeat as long as it returns -1 with `errno' set to EINTR. */ # define TEMP_FAILURE_RETRY(expression) \ (__extension__ \ ({ long int __result; \ do __result = (long int) (expression); \ while (__result == -1L && errno == EINTR); \ __result; }))
Jediná berná mince je návratová hodnota z příslušné funkce. Při/po úspěchu se na errno
nesahá.
0 z read()
je EOF, tedy v podstatě the ultimate úspěch.
0 z write()
při nenulové délce je snad leda chyba v kernelu nebo v (kni)hovnách. Ale já se v počítačích nevyznám, tak nevím.
-1 z read()
nebo write()
je
EINTR
, pokud jsem si hrál s handlery a někde jsem si nějaký netriviální handler na daný signál nastavil a nemám signál zamaskovaný a dostal jsem ten signál, handler proběhl atd. Jenže kdybych si byl radši nastavil SA_RESTART
, tak jsem se touhle věcí nemusel vůbec zabývat. (Jinak -1 vždycky říká, že se nezapsalo nic, takže po EINTR
se to má znova zkusit se stejnými parametry. Ale megafonem všem místo toho doporučuju: SA_RESTART
.)0 z read()
je EOF, tedy v podstatě the ultimate úspěch.
(*) při nenulové délce. (Než aby mě někdo zase bral za slovo, radši se za něj vezmu sám.)
protože se změní velikost terminálu
Tahle hrůza byla (nebo možná ještě je) v LetsEncrypt, když se používá v manuálním režimu. (jasně, certifikáty by se měly obnovovat automaticky, ale to teď nechme stranou) To si takhle člověk projde přes X domén, vykopíruje si jejich kódy, odskočí si do jiného terminálu… a když se vrátí zpět, tak zjistí, že LetsEncrypt spadl (kvůli změně okna – třeba při otevřeném vyhledávacím panelu v Konsoli) a je třeba začít znova.
Tahle hrůza postihuje kdekoho, kdo si zbytečně a neopatrně hraje s handlery. Někdy se to týká skriptovacích jazyků, někdy zase některých nastavení ncurses atd. atp.
Možnosti jsou v podstatě dvojí: Buď z toho udělat téměř neinteraktivní utilitu, bez chytrých promptů, editace v terminálu atd., a nechat všechno v implicitní konfiguraci; pak to zkrátka funguje, podle očekávání. Nebo dělat různé triky se správou terminálu a se signály s ní souvisejícími. To je sice bezva, jenže pak se musí všechno nastavit od začátku do konce, nikoliv jen napůl.
Ona ta chyba dost možná není v těch aplikacích, ale ve výchozím chování Pythonu nebo nějaké jeho knihovny…
To rozhodně. Ale těžko to těm (kni)hovnám vyčítat, když takový interpret Pythonu má single-source portabilitu přes velké N platforem. Pak tam možná bude něco jako ncurses přes swig nebo cojávímco a pak už není vůbec jasné, co se tam děje za paseku.
(Jinak samozřejmě je to bug, to bezesporu, když se to takhle posírá. Nikdy jsem u běžící Let's Encrypt nezkoušel změnit velikost terminálu a ani to zkoušet nehodlám, no nicméně tohle přece musí ustát bez debat. Přesně jako (výše uvedený) kód, který si se signály vůbec nehraje a jen spoléhá na to, že implicitně je vše nastavené rozumně (což je!).
(Protože upřímně, kdo by v praxi chtěl, aby nějaká dvacetiřádková prasárna v C, kterou narychlo sesmolil jako prototyp, náhodně zařvala kvůliSIGWINCH
? (No nehlaste se všichni.))
Napr. s f funckie osetruju vynimky signal(7).
Což není potřeba ošetřovat (resp. „potřeba“ nejčastěji plyne z nepochopení problematiky), ale jinak souhlasím, že používat čistě pro dobrý pocit (a malý kousek portovatelnosti navíc) funkce f.* je správné.
Tak já jsem odpověděl konkrétním kódem, který si může tazatel spustit.
Nemůžu za to, že pak (už zase) někdo vytáhl na světlo EINTR
pověru.
Když už prý „dělám chytrého“, tak dobrá, začnu „dělat chytrého“:
Na původním příkladu nevadilo, že byl krátký — to by byla velká přednost, kdyby fungoval.
Jenže on nefunguje — nejen kvůli omezení velikosti souboru na 16 bytů; to nechme stranou. Horší je, že dělá nedefinované operace s neinicializivanou pamětí.
Jak printf()
zjistí, kde končí string? Co za string zapíše koncovou nulu? Nic. Nula tam může být díky šťastné náhodě. Za 16. bytem bufferu už je to spíš výhra v loterii, že to neodletí (sahání na blíže neurčenou část zásobníku). (Na 17. byte (a možná dál) sáhne
printf()
pokaždé, když vstup bude mít aspoň 16 bytů nebo když buffer nebude šťastnou náhodou ukončený nulou.)
Tedy celý rádoby-jednoduchý příklad nikdy, ani jednou neběžel s valgrind
em.
Teď se zbytečně povaluje na webu další totálně rozbitý příklad, který má v C nedefinované chování.
(A ano, chápu, že předmět dotazu byl úplně jiný.)
Tiskni
Sdílej: