Binární zápis do souboru

webhope

Binární zápis do souboru
« kdy: 10. 12. 2013, 09:59:47 »
Zdravím,
už delší dobu si neumím poradit s jedním problémem a tak bych potřeboval poradit. Možná dokážete podle symptomů poznat kde by mohl být problém. PHP má lecco společného s C++ tak možná poradíte.

Tady mám třídu, jejiž úkolem je vytvořit binární soubor pomocí metody reset(). Ale všiml jsem si že v módu "x" nebo "xb" se na začátku vytvoří unicode BOM, což nevím jestli je normální a jestli je to v pořádku.
http://paste.ofcode.org/v8M4DwrH7hQzkJMEDjaRpt
Pro samotné spuštění stačí vytvořit instanci třídy. V konstruktoru se spouští metoda addIP, která testuje jestli je možné zapsat IP do souboru, aniž by se změnila jeho délka. Chci zapsat celkem tři čísla které patří jedné IP. Jenže se mi vede zapsat pouze první číslo, viz řádek 412 blok // TEST. Soubor by měl obsahovat samé nuly a po zápisu zapsanou hodnotu. Ale tady se na druhý zápis jakoby zkkrátí soubor, BOM zmizí, velikost souboru zůstává stejná. Nedovedu si vysvětlit toto chování. Mód r+b jsem už předtím zkoušel a fungovalo mi to, do té doby než jsem třídu tu trochu rozšířil. Poradíte?
« Poslední změna: 10. 12. 2013, 10:21:33 od Petr Krčmář »


webhope

Re:Binární zápis do souboru
« Odpověď #1 kdy: 10. 12. 2013, 10:58:40 »
Zapomněl jsem vložit obrázky

před chybou:
http://oi42.tinypic.com/2ngezbk.jpg

chyba při druhém zápisu
http://oi40.tinypic.com/242id81.jpg

webhope

Re:Binární zápis do souboru
« Odpověď #2 kdy: 10. 12. 2013, 14:09:49 »
Ještě posílám funkční zjednodušenou a zkrácenou verzi, která nepracuje se třídou.
http://paste.ofcode.org/8ipwC6eWGPxZpHGK5DQFmg

Stačí spustit a vytvoří se soubor test.txt
Zápisy do souboru se povedlo provést (na první a pátý bajt)

Proč to tedy v té třídě nejede, hm?

petr

Re:Binární zápis do souboru
« Odpověď #3 kdy: 10. 12. 2013, 23:14:04 »
Citace
Ale všiml jsem si že v módu "x" nebo "xb" se na začátku vytvoří unicode BOM, což nevím jestli je normální a jestli je to v pořádku.

Normální ani v pořádku to není. A mám pochybnosti, zda ten BOM tam vkládá PHP, proč proboha? Nemohl ten soubor s tím BOM na začátku být již vytvořen před spuštěním skriptu? Nebo s ním manipuluje něco jiného?

Kód: [Vybrat]
$fh = & $this->FRWBHandler;co to?

Kód: [Vybrat]
$t .= sprintf("%c", 0);Proč ne
Kód: [Vybrat]
$t .= chr(0) ?

Proč v metodě reset() otevíráš soubor v režimu "xb" místo "wb"? Jednak se funkce nechová podle svého názvu, jednak před jejím voláním kontroluješ existenci souboru.

Doporučil bych ti pročistit ten skript a přidat ladící informace, aby jsi mohl sledovat postup od vytvoření souboru až do konce skriptu (zda byl soubor v metodě reset() vytvořen, jaký je jeho obsah v každém okamžiku atd.) A samozřejmě kontroluj návratové hodnoty fwrite, fseek apod. !

petr

Re:Binární zápis do souboru
« Odpověď #4 kdy: 10. 12. 2013, 23:40:59 »
Zapomněl jsem říct, že mně tvůj kód funguje správně.

Jen mě ještě napadlo, vzhledem k tomu, že se v tom souboru na začátku objeví BOM a potom se ořeže před prvním NUL, že s ním nejspíš manipuluje něco z venku. Třeba nějaký spuštěný editor, co já vím.


krystal

Re:Binární zápis do souboru
« Odpověď #5 kdy: 11. 12. 2013, 00:14:28 »
notepad ve windows daval nejaky bordel na zacatek souboru, nebo se pletu?

Re:Binární zápis do souboru
« Odpověď #6 kdy: 11. 12. 2013, 01:22:15 »
BOM je zpravidla před <?php
takže se přepíše do výstupu.

Některé verze PHP dokonce odmítají odeslat hlavičky, když je ten soubor s BOMem. Když se ale výstup bufferuje, tak se chyba hned neprojeví a hlavičky se přesto podaří nastavit navzdory tomu, že na výstup už byl odeslán znak.

petr

Re:Binární zápis do souboru
« Odpověď #7 kdy: 11. 12. 2013, 06:17:43 »
Citace
BOM je zpravidla před &lt?php takže se přepíše do výstupu.

Do výstupu otevřeného pomocí fopen(), do kterého je následně zapisováno pomocí fwrite() ?

monitor

Re:Binární zápis do souboru
« Odpověď #8 kdy: 11. 12. 2013, 08:06:57 »
hmm...
skus na zaciatok tej funkcie reset pridat:

var_dump(iconv_get_encoding('all'));

Moja teoria je, ze ak sa ti to sprava tak, ako pises, tak zrejme niektory subor, co pouziva tu triedu, nastavi pre PHP nejake ine kodovanie (nejake uCS-2), a to mozno nejako sposobi, ze sa ten vytvoreny string nejako zapise s BOMom...

inak, ak sa tomu chces vyhnut, a neriesit to, tak
namiesto 307 a 311 (pridavanie sprintf-ovych kuskov do jedneho velkeho stringu, a potom fwrite), nerob si ten velky string,
ale urob ten fopen skor, a potom rovno urob "$this->bufferSize" krat fprintf($fh,"%c", 0)) a to MUSI fungovat...

petr

Re:Binární zápis do souboru
« Odpověď #9 kdy: 11. 12. 2013, 14:29:53 »
Vy mě vykláte :) Myslel jsem, že pro soubory otevřené v binárním režimu, je fwrite() vždy binary-safe, tj. že zapíše string (interně implementovaný jako pole bajtů) tak jak je, bez jakýchkoliv změn. Především pokud je zadána délka (třetí argument fwrite), což v tom kódu není, ale zase tam jsou samé nuly a nemá se co escapovat. Kde by se tam vzal BOM? To by to PHP bylo naprosto nepoužitelné.

webhope

Re:Binární zápis do souboru
« Odpověď #10 kdy: 11. 12. 2013, 14:48:31 »
Za tu dobu co jsem tu nebyl se to trochu posunulo směrem k úspěchu. Ale na tu první otázku stále nedokážu odpovědět. To bych opravdu rád věděl jak tam ten BOM vzniká, protože nic jiného než fopen($fh, "xb") nepoužívám. Snad jediná možnost, že by to tak zobrazoval editor PSPadu. To se mi ale nezdá.

Na tu chybu proč se mi přepisoval soubor čili proč to smazalo ty nuly jsem nepřišel, čím to bylo, ale dělal jsem to znova z obyčejné funkce až po třídu a teď to jede. Nicméně, BOM tam stále je a chová se to tak, že když zapisuji např. dvě čísla v hex je to  78 a 64 tak hex editor to zobrazuje jako 7800 6400 ... jenže já kontroloval velikost toho stringu který vytváří ten soubor a jeho velikost přesně odpovídá správné velikosti. Použil jsem strlen($t) na zjištění velikosti proměnné pro vstup. Taktéž velikost souboru přesně odpovídá. Takže je to záhada, ale vlastně nevím jestli tam někde chyba je nebo to blbě zobrazuje PSPAD.

Petr:
člen FRWBHandler je File handler pro r+b zápis. Dal jsem to tam pro účel hromadného zápisu, tak aby se ten soubor pořád neotvíral a nezavíral. Prostě se to jednou otevře a hotovo. Metody addIP a checkIP používají r+b mód, i když checkIP by stačil rb, ale kvůli tomu zbytečnému otevírání pořád dookola jsem zvolil společný handler. $t .= chr(0) to nevím kde bylo, ale mělo to přidávat nuly, asi součást starého kódu, dnes nepotřebné. Na nové verzi ještě pracuji. wb by bylo pro zápis, kdežto já potřebuji číst i psát. čísl pro kontrolu jestli je daný bajt nula. Není tu snad nic co by mohlo zvenku ovlivnit soubor. Je to teď jen jeden skript (instanci vytvářím hned za třídou čímž spouštím konstruktor.

krystal:
notepad je nepoužitelný, lepší je ten PSPad

Ondřej Novák:
tag php s tím nemá co dělat, tenhle proces probíhá nezávisle. Nebo jsem vás nepochopil. nejde o php soubor, ale o binární soubor. Nemluvím o výstupu html ale o výstupu do souboru.

monitor:
to samé co Ondřeji Novákovi. Nejde o zpracování html, tohle funguje nezávisle.

petr

Re:Binární zápis do souboru
« Odpověď #11 kdy: 11. 12. 2013, 15:27:59 »
V tom PSPadu zobrazuješ ten výstupní soubor? Tak to bych ho přece jen zkusil zavřít.

Citace
... chová se to tak, že když zapisuji např. dvě čísla v hex je to  78 a 64 tak hex editor to zobrazuje jako 7800 6400 .
Hex editor a ne viewer? Nemůže do toho zapisovat třeba ten? Jinak 7800 6400 je v pořádku. Seekuješ o bajt při zápisu dalšího čísla:
Kód: [Vybrat]
                fseek($fh, $i);
                fwrite($fh, chr($arr[1]), 1);
                $result = fseek($fh, $i + 2);
Jen takové hodnoty asi mohou skutečně zmást nějaký editor, který reaguje na změny v souboru. Už jen proto, že v metodě reset() ten deskriptor po vyplnění souboru nulama explicitně zavíráš. Jestli např. nějaký editor (pspad, hex editor) se nechává notifkovat o změnách souboru, který má otevřen, tak to může v ten okamžik přepsat, protože to na první pohled připomíná little endian UCS-2 nebo UTF16 a ten BOM by tomu odpovídal. Taky to samozřejmě mohou být regulární hodnoty z toho skriptu, ale ten příklad co jsi uváděl tomu neodpovídá. Navíc by to nevysvětlovalo to ořezání co jsi pozoroval.

Já to vidím takhle:

Mám prázdný řetězec, do kterého přidávám \x00, potom ho uložím pomocí fwrite do prázdného souboru vytvořeného pomocí fopen v binárním režimu. Jak by se tam mohl dostat BOM? Do toho řetězce? Nebo na začátek souboru při vytváření pomocí fopen(..., 'xb')? I kdyby měl ten skript na začátku BOM nebo byl v jakémkoliv kódování?

Já tam vidím čtyři možnosti: Buď ten soubor již existoval s BOM na začátku před voláním skriptu nebo ho něco externího upravilo mimo skript - třeba ten zmiňovaný PSPad nebo použitý hex editor (neznám ani jeden) nebo něco jiného. Nebo je něco shnilého v implementaci php pro windows (to jsi mohl zmínit v prvním postu :) nebo nevím jak funguje zápis binárních souborů na windows - to skutečně nevím, nedivil bych se ničemu. A nebo jsem v tom kódu přehlédl řádek:
Kód: [Vybrat]
if (!rand()) fwrite($fh, \xEF\xBB);
Mě spíš překvapuje že tam netestuješ úspěšnost fwrite, fseek, fclose.... pokud se ten soubor ořezal před prvním \x00 za BOM tak přece selhal následný fseek.

Pokud ten problém souvisí s PHP na windows, tak k tomu poradit neumím.

Citace
wb by bylo pro zápis, kdežto já potřebuji číst i psát.
Myslel jsem použít mód "wb" v metode reset() místo módu "xb", kde ten soubor stejně jen vyplníš \x00 a zavřeš. To "xb" předělává metodu reset, takže dělá něco jiného než je její název - existenci souboru kontroluješ před jejím voláním, alespoň v aktivní části toho kódu co jsi poslal. Ale to je jen nepodstatný detail.

Citace
$t .= chr(0)
To jsem napsal já, myslel jsem to jako náhradu za ten sprintf(), který bude možná zbytečně zdržovat. Opět jen bezvýznamný detail. Optimální by bylo
Kód: [Vybrat]
str_repeat(chr(0), $this->bufferSize), ale to prý nefunguje, pokud je string \x00, tak jsem to nepsal.

webhope

Re:Binární zápis do souboru
« Odpověď #12 kdy: 11. 12. 2013, 15:58:52 »
teoreticky bych tam wb použít mohl pro reset.

Já měl za to že jeden byte je hex hodnota 00 až FF, 8bitů. Tudíž když posouvám fseek po jednom a pak zapisuji výsledek by měl být dva bajty např "FFFF" a ne "FF00 FF00". Jestli to nechápu tak to jsem už asi z toho jelen. Navíc je tu další věc, která se mi opět vynořila a přispívá k mé zmatenosti. Snažím se na pozici 500tého bajtu zapsat slovo "localhost;" (název domény). Prohlížím si kam jsem to zapsal v tom hexeditoru (v PSPadu). Pozice je 3E8 což je 1000 byte. Tak tohle už musí být hmatatelný důkaz že to funguje špatně.

Tohle je jeden příkaz který je součástí nové verze reset():
Kód: [Vybrat]
$t=substr_replace($t,$s,$this->h1+$this->h2-1,strlen($s));Zaměňuje v tom vygenerovaném stringu string $t za $s="localhost;" od pozice 100+400-1 = 499; což když vezmeme v úvahu pozici v bytech je pozice bajtu 500. No a v hexaeditoru to čtu že je to na pozici 1000 což znamená, že tam opravdu je jeden byte navíc. Jdu zkusit ten wb mód.. opět pozice 3E8, takže to nemá vliv.

webhope

Re:Binární zápis do souboru
« Odpověď #13 kdy: 11. 12. 2013, 16:09:58 »
Taky by mě zajímalo jak to bylo v té první verzi (to je ta co si zkoušel) protože mám dojem že tam to ten bajt navíc nepsalo.

Re:Binární zápis do souboru
« Odpověď #14 kdy: 11. 12. 2013, 16:59:55 »
možná by nebylo od věci napsat verzi PHP. Já zkusil toto sestavení pro windows php-5.3.27-nts-Win32-VC9-x86 a reset dal jeden zbytečně velkej soubor plnej nul a nejen PSPad to tak i zobrazil. Takže žádný BOM. (pouštěno z commadline: php.exe script.php což by nemělo mít na nic vliv)