GNU/Linux >> Znalost Linux >  >> Linux

Proč stdout potřebuje explicitní vyprázdnění při přesměrování do souboru?

Nesprávně kombinujete funkce IO s vyrovnávací pamětí a bez vyrovnávací paměti. Taková kombinace musí být provedena velmi opatrně, zvláště když kód musí být přenosný. (a je špatné psát nepřenosný kód...)
Určitě je nejlepší vyhnout se kombinování IO s vyrovnávací pamětí a bez vyrovnávací paměti na stejném deskriptoru souboru.

Vstup do vyrovnávací paměti: fprintf() , fopen() , fclose() , freopen() ...

Vstup bez vyrovnávací paměti: write() , open() , close() , dup() ...

Když použijete dup2() k přesměrování stdout. Funkce si není vědoma vyrovnávací paměti, která byla naplněna fprintf() . Takže když dup2() zavře starý deskriptor 1, nevyprázdní vyrovnávací paměť a obsah by mohl být vyprázdněn na jiný výstup. Ve vašem případě 2a byl odeslán na /dev/null .

Řešení

Ve vašem případě je nejlepší použít freopen() místo dup2() . To vyřeší všechny vaše problémy:

  1. Vyprázdní vyrovnávací paměti původního FILE proud. (případ 2a)
  2. Nastaví režim ukládání do vyrovnávací paměti podle nově otevřeného souboru. (případ 3)

Zde je správná implementace vaší funkce:

void RedirectStdout2File(const char* log_path) {
    if(freopen(log_path, "a+", stdout) == NULL) err(EXIT_FAILURE, NULL);
}

Bohužel s bufferovaným IO nemůžete přímo nastavit oprávnění nově vytvořeného souboru. Ke změně oprávnění musíte použít jiná volání nebo můžete použít nepřenosná rozšíření glibc. Viz fopen() man page .


Proplachování pro stdout je určeno jeho chováním ve vyrovnávací paměti. Ukládání do vyrovnávací paměti lze nastavit na tři režimy:_IOFBF (plné ukládání do vyrovnávací paměti:čeká na fflush() pokud je to možné), _IOLBF (ukládání do vyrovnávací paměti řádku:nový řádek spouští automatické vyprázdnění) a _IONBF (vždy se používá přímý zápis). "Podpora těchto charakteristik je definována implementací a může být ovlivněna prostřednictvím setbuf() a setvbuf() funkce." [C99:7.19.3.3]

"Při spuštění programu jsou předdefinovány tři textové proudy a není třeba je explicitně otevírat – standardní vstup (pro čtení konvenčního vstupu), standardní výstup (pro zápis konvenčního výstupu) a standardní chyba (pro zápis diagnostického výstupu). Jak bylo původně otevřeno, standardní chyba stream není plně ukládán do vyrovnávací paměti; standardní vstupní a standardní výstupní toky jsou plně ukládány do vyrovnávací paměti pouze tehdy, pokud lze určit, že se tok neodkazuje na interaktivní zařízení." [C99:7.19.3.7]

Vysvětlení pozorovaného chování

Takže se stane, že implementace udělá něco pro platformu, aby rozhodla, zda stdout bude ukládán do vyrovnávací paměti. Ve většině implementací libc se tento test provádí při prvním použití streamu.

  1. Chování č. 1 lze snadno vysvětlit:když je stream určen pro interaktivní zařízení, je ukládán do vyrovnávací paměti a printf() se automaticky vyplachuje.
  2. Nyní se očekává i případ č. 2:když přesměrováváme na soubor, stream je plně ukládán do vyrovnávací paměti a nebude vyprázdněn kromě fflush() , pokud do něj nezapíšete spousty dat.
  3. Konečně rozumíme i případu č. 3 pro implementace, které provádějí kontrolu základního fd pouze jednou. Protože jsme vynutili inicializaci vyrovnávací paměti stdout v prvním printf() , stdout získal režim s vyrovnávací pamětí řádků. Když vyměníme fd, aby šel do souboru, je stále ukládán do vyrovnávací paměti, takže se data automaticky vyprázdní.

Některé skutečné implementace

Každá knihovna libc má volnost v tom, jak interpretuje tyto požadavky, protože C99 nespecifikuje, co je "interaktivní zařízení", ani to nerozšiřuje položka stdio POSIX (kromě požadavku, aby byl stderr otevřen pro čtení).

  1. Glibc. Viz filedoalloc.c:L111. Zde používáme stat() otestujte, zda je fd tty, a podle toho nastavte režim ukládání do vyrovnávací paměti. (To se volá z fileops.c.) stdout zpočátku má nulovou vyrovnávací paměť a je alokována při prvním použití proudu na základě charakteristik fd 1.

  2. BSD libc. Velmi podobný, ale mnohem čistší kód, který je třeba dodržovat! Viz tento řádek na makebuf.c


Linux
  1. Proč se hodnota Inode mění, když provádíme úpravy v editoru „vi“?

  2. Proč „zip“ v A For Loop funguje, když soubor existuje, ale ne, když neexistuje?

  3. Proč tento plášťový ropovod vystupuje?

  1. Proč ENOENT znamená Žádný takový soubor nebo adresář?

  2. Proč rozvětvení mého procesu způsobuje nekonečné čtení souboru

  3. Proč clang stále potřebuje libgcc.a ke kompilaci mého kódu?

  1. Proč rsync nepoužívá delta-transfer pro místní soubory?

  2. Proč clang při přesměrování generuje nesrozumitelný text?

  3. Proč rm manuál říká, že to můžeme spustit bez jakéhokoli argumentu, když to není pravda?