Tak to jste si ale dost zahrával, protože dd je minové pole. tl;dr dd bez flagu fullblock prostě yolo udělá syscall read s velikostí podle nastavené velikosti bloku, a když mu kernel vrátí méně dat, tak to neřeší a zapíše je na výstup a počítá to za plný blok. Takže můžete snadno přenést méně dat než jste chtěl. Skoro mi podle popisu bugu přijde, že tohle je sice bug v rust-coreutils (kromě částečného přečtení na vstupu udělá i částečný zápis na výstupu a ztratí přečtená data), ale kdyby se situace sešla, tak problém nastane i s normálním dd.
Já se na dd vykašlal, soubory přenáším normálně catem (nebo ještě lépe pv - zobrazuje průběh, navíc celou dobu a nemusí se dráždit jak dd), a začátky a konce usekávám pomocí head a tail (-c 100, -c +100).
Průběh obecně zobrazuji nástrojem progress, podporuje spoustu programů (gzip a další *zip, md5sum a další *sum…). Funguje tak, že se podívá do /proc/pid/fd aby zjistil, který soubor je vstup a výstup, a pak do /proc/pid/fdinfo, kde se dozví aktuální pozici. Pokud jsem na systému, kde není a nechci/nemůžu ho instalovat, tak tohle podívání se udělám ručně.
Tak zrovna tohle jde naprosto v pohodě, i když nevím, proč bych to dělal (oddíly jsou typicky číslované od začátku). Poslední gigabajt:
# time tail -c $(( 1000*1000*1000 )) /dev/nvme0n1 | md5sum 90f6764444b7952a4dcf1c7bb24e5ff5 - real 0m1.063s user 0m0.966s sys 0m0.395s
Chápu, že tenhle přístup nejde použít k přepisu hodnoty uprostřed disku.
Tuhle narážku na dd (že nemusí číst celý blok) jsem o tebe před mnoha lety četl už na ABCLinuxu. Tobě se někdy reálně stalo, že by dd nenačetl celý blok a nic by neoznámilo chybu? Mě teda nikdy a to jsem pomocí dd běžně dělal image virtuálek a přenesl možná stovky TB dat. Nedovedu si představit, že by fs vrátil méně než blok a nenahlásil by chybu čtení apod.
No ne, když kopíruješ celý disk/image atd. tak je to v pohodě, protože se ve čtení pokračuje dál. Problém je když používáš "count" a chceš aby to přeneslo přesně tolik dat kolik je count*bs.
Nasimulovat se to dá takhle:
$ (while sleep 0.1; do head -c 1000 /dev/urandom ; done) | dd bs=1024 count=10 of=/dev/null dd: warning: partial read (1000 bytes); suggest iflag=fullblock 0+10 records in 0+10 records out 10000 bytes (10 kB, 9,8 KiB) copied, 1,0492 s, 9,5 kB/s
(mělo přenést 10240 a přeneslo jen 10000). Tj. stane se to pokud se čte z programu/sítě, ze kterého padají data po menších bobečcích než chceme číst.
A je to reálné při síťovém provozu, např. ssh po normálním rozumně rychlém internetu:
$ ssh f "cat /dev/urandom" | dd bs=1M count=10 of=/dev/null dd: warning: partial read (32768 bytes); suggest iflag=fullblock 0+10 records in 0+10 records out 327680 bytes (328 kB, 320 KiB) copied, 0,527688 s, 621 kB/s
A stejně tak při čtení komprimovaného archivu:
$ zstdcat openwrt-imagebuilder-24.10.2-ramips-mt7621.Linux-x86_64.tar.zst | dd bs=1M count=10 of=/dev/null dd: warning: partial read (65536 bytes); suggest iflag=fullblock 0+10 records in 0+10 records out 593920 bytes (594 kB, 580 KiB) copied, 0,00314817 s, 189 MB/s
Přímo disku se to doufejme dít nebude, a doufejme že ani např. z NFS share. Ale nesázel bych na to… Konec konců, diskutujeme pod zprávičkou, kdy měl někdo částečný zápis při zapisování do "pomalého" md5sum.
Za mě se to chová správně. Tímhle dd říkáte "udělej 10 čtení po maximálně 1024 bajtech". Pipe v jednom čtení vrací 1000 bajtů, takže není možné, aby se vrátilo 10240 bajtů. Pokud chcete, aby dd dočetl data do plného bloku, musí udělat větší počet čtení, než jste mu zadal. To zapnete pomocí iflag=fullblock, jak doporučuje.
No, funkcnich a dobre odladenych... posledni bezpecnostni dira tam byla loni :-) Ono ten stary kod si holt v sobe nese relikty, ktere se treba uz i prehlizi a pak najednou vyplavou. Bezpecnostnich der, na ktere se prislo az po mnoha letech bylo i v opensource svete cela rada.... proste nejsou kapacity na to veskery kod zodpovedne projit, zeano :-) Ono nekdy refactor kodu smysl proste dava...
Mi dava smysl treba to, ze se vemou jedny data, nad nima se pusti puvodni overena verze a taky nova verze a porovna se vystup.
Tohle mi prijde, jako by na to snad ani kdo zadne testy neudelal.
Chyby se stanou, ale tohle mi prijde jako slusny slendrian a rozhodne to nevzbuzuje pocit jistoty ohledne kvality tech rustovych core utils jako celku a kvality procesu jejich zarazeni do distra :-(
> Tohle mi prijde, jako by na to snad ani kdo zadne testy neudelal.
Naopak, testy mají velmi extenzivní.
Tuto chybu je testy těžké odchytit, protože nastane jen v případě, že je konzument dat z dd pomalý, a není nastavený fullblock flag.
Jenže pojem správně je něco jiného než oprava bugů. Myslel jsem to v tom smyslu to prostě napsat znovu bez ohledu na kompatibilitu, prostě na základě nějakých dnešních standardů. Tohle se prostě musí udělat a ne implementovat nějaké přepínače z roku 70 nebo zachovávat podivné vlastnosti, jako Jendův příklad ohledně dd a nutnosti tam psát parametr full block.
Ale tohle je na větší diskusi. Některé projekty je prostě nutné po letech přepsat.
To by byl ale jiný projekt. Ne drop-in replacement pro coreutils ;-)
A jakkoliv je pěkné si navrhnout nové API od nuly... to pak ty tisíce a tisíce balíků a skriptů používajících coreutils bude někdo přepisovat a migrovat no nové API? Nebo tedy převážně půjde jen o nové projekty a za pár dekád se tedy to nové API rozšíří? Není to jednoduché.
25. 10. 2025, 10:41 editováno autorem komentáře
Jenže ono je to i spousta dalšího. To můžeš rovnou vyházet i sed, tail, head, wc, cut a mnoho dalšího, protože to přece umí každý textový procesor (pardon, editor).
A místo CLI nástrojů pro správu souborů (cp, mv, rm, ln, df...) použijeme krusader a mc a je vyřešeno...
P.S. Ne každé použití cat je zbytečné.
Ty automatické testy mají teď 84 % pass, 5 % skip a 11 % fail, viz https://github.com/uutils/coreutils/releases/tag/0.3.0
Už se to tu ve vláknu řešilo.
https://github.com/uutils/coreutils?tab=readme-ov-file#gnu-test-suite-compatibility
https://github.com/uutils/coreutils/issues taky velmi výživné
Tak ona ani safe podmnožina Rustu nemusí být memory safe a může způsobit segfault (například přistupovat k již uvolněné paměti). Krom toho se tam používá i dost unsafe Rustu, takže těch chyb tam může být více - protože unsafe Rust je bohužel složitější než C (zejména pravidla pro aliasing pointerů a referencí).
A asi to nebylo špatné rozhodnutí. Buď to bude fungovat a všechno bude sluníčkové. Nebo nebude a pak z toho bude ostuda a vrátíme se k tomu, co nějakou záhadou fungovalo přes 50 let (nebo k něčemu jinému).
Jediným případným negativem může být možnost, že přílišná horlivost a její důsledky zabijí zájem o Rust dřív, než se vyřeší jeho současné problémy.
Třeba, že se nepovažuje za standardní vyhazovat nepoužité funkce při kompilaci. Nebo že standardní knihovny jsou předkompilovaný obrovský blob, ze kterého se taky nevyhazují funkce, a to ani když nastavíte, že se mají funkce vyhazovat a musíte si tedy stáhnout ozdrojákovanou verzi knihoven a manuálně na ně přepnout. Nebo že to defaultně cpe debugovací symboly i do release verze. Nebo že se z binárky dozvím, kde absolutně na disku byl uložený každý zdroják, ze kterého se program kompiloval. Nakonec aby ta jednoduchá command-line aplikace komunikující po sériovce neměla 6 mega, musel jsem ji procpat UPXkem a nakonec měla "jen" 1.8. Pořád by se to ani nevešlo na disketu. A debug verze měla původně 11 mega.
Ty 50 let staré utility taky měly bugy. A afaik, GNU utility tak staré ani nejsou. Já nejsem fanda přístupu, kde se používa obsolete kód, kterého autoři se nacházejí na hřbitově, jen aby se něco nepokazilo.
Kdysi UNIX fungoval tak, že pět lidí bylo připojeno dálnopisem k PDP, když jeden něco pos*al, tak to spadlo všem. Jen probůh nedělejte žádný nový kód, aby to fungovalo dobře - neřekl zrovna Linus Torvalds v roce 1991.
Stačí se podívat na issues:
https://github.com/uutils/coreutils/issues
Těch chyb je tam spousta a bude to trvat roky než to odladí. Podle mě to nasazení bylo hodně předčasné.